diff options
author | talasila <talasila@research.att.com> | 2017-02-07 15:03:57 -0500 |
---|---|---|
committer | talasila <talasila@research.att.com> | 2017-02-07 15:05:15 -0500 |
commit | 4ad39a5c96dd99acf819ce189b13fec946d7506b (patch) | |
tree | a1449286441947cc3d07a45227fa0d6f978e1a7d /ecomp-portal-FE/client/bower_components/lodash/vendor | |
parent | 5500448cbd1f374d0ac743ee2fd636fe2d3c0027 (diff) |
Initial OpenECOMP Portal commit
Change-Id: I804b80e0830c092e307da1599bd9fbb5c3e2da77
Signed-off-by: talasila <talasila@research.att.com>
Diffstat (limited to 'ecomp-portal-FE/client/bower_components/lodash/vendor')
88 files changed, 50045 insertions, 0 deletions
diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/backbone/LICENSE b/ecomp-portal-FE/client/bower_components/lodash/vendor/backbone/LICENSE new file mode 100644 index 00000000..02c89b26 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/backbone/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2010-2016 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. diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/backbone/backbone.js b/ecomp-portal-FE/client/bower_components/lodash/vendor/backbone/backbone.js new file mode 100644 index 00000000..55ccb22b --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/backbone/backbone.js @@ -0,0 +1,1920 @@ +// Backbone.js 1.3.3 + +// (c) 2010-2016 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors +// Backbone may be freely distributed under the MIT license. +// For all details and documentation: +// http://backbonejs.org + +(function(factory) { + + // Establish the root object, `window` (`self`) in the browser, or `global` on the server. + // We use `self` instead of `window` for `WebWorker` support. + var root = (typeof self == 'object' && self.self === self && self) || + (typeof global == 'object' && global.global === global && global); + + // Set up Backbone appropriately for the environment. Start with AMD. + if (typeof define === 'function' && define.amd) { + define(['underscore', 'jquery', 'exports'], function(_, $, exports) { + // Export global even in AMD case in case this script is loaded with + // others that may still expect a global Backbone. + root.Backbone = factory(root, exports, _, $); + }); + + // Next for Node.js or CommonJS. jQuery may not be needed as a module. + } else if (typeof exports !== 'undefined') { + var _ = require('underscore'), $; + try { $ = require('jquery'); } catch (e) {} + factory(root, exports, _, $); + + // Finally, as a browser global. + } else { + root.Backbone = factory(root, {}, root._, (root.jQuery || root.Zepto || root.ender || root.$)); + } + +})(function(root, Backbone, _, $) { + + // Initial Setup + // ------------- + + // 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 a common array method we'll want to use later. + var slice = Array.prototype.slice; + + // Current version of the library. Keep in sync with `package.json`. + Backbone.VERSION = '1.3.3'; + + // For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns + // the `$` variable. + Backbone.$ = $; + + // 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 `"PATCH"`, `"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 ... this 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; + + // Proxy Backbone class methods to Underscore functions, wrapping the model's + // `attributes` object or collection's `models` array behind the scenes. + // + // collection.filter(function(model) { return model.get('age') > 10 }); + // collection.each(this.addView); + // + // `Function#apply` can be slow so we use the method's arg count, if we know it. + var addMethod = function(length, method, attribute) { + switch (length) { + case 1: return function() { + return _[method](this[attribute]); + }; + case 2: return function(value) { + return _[method](this[attribute], value); + }; + case 3: return function(iteratee, context) { + return _[method](this[attribute], cb(iteratee, this), context); + }; + case 4: return function(iteratee, defaultVal, context) { + return _[method](this[attribute], cb(iteratee, this), defaultVal, context); + }; + default: return function() { + var args = slice.call(arguments); + args.unshift(this[attribute]); + return _[method].apply(_, args); + }; + } + }; + var addUnderscoreMethods = function(Class, methods, attribute) { + _.each(methods, function(length, method) { + if (_[method]) Class.prototype[method] = addMethod(length, method, attribute); + }); + }; + + // Support `collection.sortBy('attr')` and `collection.findWhere({id: 1})`. + var cb = function(iteratee, instance) { + if (_.isFunction(iteratee)) return iteratee; + if (_.isObject(iteratee) && !instance._isModel(iteratee)) return modelMatcher(iteratee); + if (_.isString(iteratee)) return function(model) { return model.get(iteratee); }; + return iteratee; + }; + var modelMatcher = function(attrs) { + var matcher = _.matches(attrs); + return function(model) { + return matcher(model.attributes); + }; + }; + + // Backbone.Events + // --------------- + + // A module that can be mixed in to *any object* in order to provide it with + // a custom event channel. You may bind a callback to an event with `on` or + // remove with `off`; `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 = {}; + + // Regular expression used to split event strings. + var eventSplitter = /\s+/; + + // Iterates over the standard `event, callback` (as well as the fancy multiple + // space-separated events `"change blur", callback` and jQuery-style event + // maps `{event: callback}`). + var eventsApi = function(iteratee, events, name, callback, opts) { + var i = 0, names; + if (name && typeof name === 'object') { + // Handle event maps. + if (callback !== void 0 && 'context' in opts && opts.context === void 0) opts.context = callback; + for (names = _.keys(name); i < names.length ; i++) { + events = eventsApi(iteratee, events, names[i], name[names[i]], opts); + } + } else if (name && eventSplitter.test(name)) { + // Handle space-separated event names by delegating them individually. + for (names = name.split(eventSplitter); i < names.length; i++) { + events = iteratee(events, names[i], callback, opts); + } + } else { + // Finally, standard events. + events = iteratee(events, name, callback, opts); + } + return events; + }; + + // Bind an event to a `callback` function. Passing `"all"` will bind + // the callback to all events fired. + Events.on = function(name, callback, context) { + return internalOn(this, name, callback, context); + }; + + // Guard the `listening` argument from the public API. + var internalOn = function(obj, name, callback, context, listening) { + obj._events = eventsApi(onApi, obj._events || {}, name, callback, { + context: context, + ctx: obj, + listening: listening + }); + + if (listening) { + var listeners = obj._listeners || (obj._listeners = {}); + listeners[listening.id] = listening; + } + + return obj; + }; + + // Inversion-of-control versions of `on`. Tell *this* object to listen to + // an event in another object... keeping track of what it's listening to + // for easier unbinding later. + Events.listenTo = function(obj, name, callback) { + if (!obj) return this; + var id = obj._listenId || (obj._listenId = _.uniqueId('l')); + var listeningTo = this._listeningTo || (this._listeningTo = {}); + var listening = listeningTo[id]; + + // This object is not listening to any other events on `obj` yet. + // Setup the necessary references to track the listening callbacks. + if (!listening) { + var thisId = this._listenId || (this._listenId = _.uniqueId('l')); + listening = listeningTo[id] = {obj: obj, objId: id, id: thisId, listeningTo: listeningTo, count: 0}; + } + + // Bind callbacks on obj, and keep track of them on listening. + internalOn(obj, name, callback, this, listening); + return this; + }; + + // The reducing API that adds a callback to the `events` object. + var onApi = function(events, name, callback, options) { + if (callback) { + var handlers = events[name] || (events[name] = []); + var context = options.context, ctx = options.ctx, listening = options.listening; + if (listening) listening.count++; + + handlers.push({callback: callback, context: context, ctx: context || ctx, listening: listening}); + } + return events; + }; + + // 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 `name` is null, removes all bound + // callbacks for all events. + Events.off = function(name, callback, context) { + if (!this._events) return this; + this._events = eventsApi(offApi, this._events, name, callback, { + context: context, + listeners: this._listeners + }); + return this; + }; + + // Tell this object to stop listening to either specific events ... or + // to every object it's currently listening to. + Events.stopListening = function(obj, name, callback) { + var listeningTo = this._listeningTo; + if (!listeningTo) return this; + + var ids = obj ? [obj._listenId] : _.keys(listeningTo); + + for (var i = 0; i < ids.length; i++) { + var listening = listeningTo[ids[i]]; + + // If listening doesn't exist, this object is not currently + // listening to obj. Break out early. + if (!listening) break; + + listening.obj.off(name, callback, this); + } + + return this; + }; + + // The reducing API that removes a callback from the `events` object. + var offApi = function(events, name, callback, options) { + if (!events) return; + + var i = 0, listening; + var context = options.context, listeners = options.listeners; + + // Delete all events listeners and "drop" events. + if (!name && !callback && !context) { + var ids = _.keys(listeners); + for (; i < ids.length; i++) { + listening = listeners[ids[i]]; + delete listeners[listening.id]; + delete listening.listeningTo[listening.objId]; + } + return; + } + + var names = name ? [name] : _.keys(events); + for (; i < names.length; i++) { + name = names[i]; + var handlers = events[name]; + + // Bail out if there are no events stored. + if (!handlers) break; + + // Replace events if there are any remaining. Otherwise, clean up. + var remaining = []; + for (var j = 0; j < handlers.length; j++) { + var handler = handlers[j]; + if ( + callback && callback !== handler.callback && + callback !== handler.callback._callback || + context && context !== handler.context + ) { + remaining.push(handler); + } else { + listening = handler.listening; + if (listening && --listening.count === 0) { + delete listeners[listening.id]; + delete listening.listeningTo[listening.objId]; + } + } + } + + // Update tail event if the list has any events. Otherwise, clean up. + if (remaining.length) { + events[name] = remaining; + } else { + delete events[name]; + } + } + return events; + }; + + // Bind an event to only be triggered a single time. After the first time + // the callback is invoked, its listener will be removed. If multiple events + // are passed in using the space-separated syntax, the handler will fire + // once for each event, not once for a combination of all events. + Events.once = function(name, callback, context) { + // Map the event into a `{event: once}` object. + var events = eventsApi(onceMap, {}, name, callback, _.bind(this.off, this)); + if (typeof name === 'string' && context == null) callback = void 0; + return this.on(events, callback, context); + }; + + // Inversion-of-control versions of `once`. + Events.listenToOnce = function(obj, name, callback) { + // Map the event into a `{event: once}` object. + var events = eventsApi(onceMap, {}, name, callback, _.bind(this.stopListening, this, obj)); + return this.listenTo(obj, events); + }; + + // Reduces the event callbacks into a map of `{event: onceWrapper}`. + // `offer` unbinds the `onceWrapper` after it has been called. + var onceMap = function(map, name, callback, offer) { + if (callback) { + var once = map[name] = _.once(function() { + offer(name, once); + callback.apply(this, arguments); + }); + once._callback = callback; + } + return map; + }; + + // 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). + Events.trigger = function(name) { + if (!this._events) return this; + + var length = Math.max(0, arguments.length - 1); + var args = Array(length); + for (var i = 0; i < length; i++) args[i] = arguments[i + 1]; + + eventsApi(triggerApi, this._events, name, void 0, args); + return this; + }; + + // Handles triggering the appropriate event callbacks. + var triggerApi = function(objEvents, name, callback, args) { + if (objEvents) { + var events = objEvents[name]; + var allEvents = objEvents.all; + if (events && allEvents) allEvents = allEvents.slice(); + if (events) triggerEvents(events, args); + if (allEvents) triggerEvents(allEvents, [name].concat(args)); + } + return objEvents; + }; + + // A difficult-to-believe, but optimized internal dispatch function for + // triggering events. Tries to keep the usual cases speedy (most internal + // Backbone events have 3 arguments). + var triggerEvents = function(events, args) { + var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2]; + switch (args.length) { + case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return; + case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return; + case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return; + case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return; + default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args); return; + } + }; + + // Aliases for backwards compatibility. + Events.bind = Events.on; + Events.unbind = Events.off; + + // Allow the `Backbone` object to serve as a global event bus, for folks who + // want global "pubsub" in a convenient place. + _.extend(Backbone, Events); + + // Backbone.Model + // -------------- + + // Backbone **Models** are the basic data object in the framework -- + // frequently representing a row in a table in a database on your server. + // A discrete chunk of data and a bunch of useful, related methods for + // performing computations and transformations on that data. + + // Create a new model with the specified attributes. A client id (`cid`) + // is automatically generated and assigned for you. + var Model = Backbone.Model = function(attributes, options) { + var attrs = attributes || {}; + options || (options = {}); + this.cid = _.uniqueId(this.cidPrefix); + this.attributes = {}; + if (options.collection) this.collection = options.collection; + if (options.parse) attrs = this.parse(attrs, options) || {}; + var defaults = _.result(this, 'defaults'); + attrs = _.defaults(_.extend({}, defaults, attrs), defaults); + this.set(attrs, options); + this.changed = {}; + 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, + + // The value returned during the last failed validation. + validationError: null, + + // The default name for the JSON `id` attribute is `"id"`. MongoDB and + // CouchDB users may want to set this to `"_id"`. + idAttribute: 'id', + + // The prefix is used to create the client id which is used to identify models locally. + // You may want to override this if you're experiencing name clashes with model ids. + cidPrefix: 'c', + + // 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 -- but override this if you need + // custom syncing semantics for *this* particular model. + 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) { + return _.escape(this.get(attr)); + }, + + // Returns `true` if the attribute contains a value that is not null + // or undefined. + has: function(attr) { + return this.get(attr) != null; + }, + + // Special-cased proxy to underscore's `_.matches` method. + matches: function(attrs) { + return !!_.iteratee(attrs, this)(this.attributes); + }, + + // Set a hash of model attributes on the object, firing `"change"`. This is + // the core primitive operation of a model, updating the data and notifying + // anyone who needs to know about the change in state. The heart of the beast. + set: function(key, val, options) { + if (key == null) return this; + + // Handle both `"key", value` and `{key: value}` -style arguments. + var attrs; + if (typeof key === 'object') { + attrs = key; + options = val; + } else { + (attrs = {})[key] = val; + } + + options || (options = {}); + + // Run validation. + if (!this._validate(attrs, options)) return false; + + // Extract attributes and options. + var unset = options.unset; + var silent = options.silent; + var changes = []; + var changing = this._changing; + this._changing = true; + + if (!changing) { + this._previousAttributes = _.clone(this.attributes); + this.changed = {}; + } + + var current = this.attributes; + var changed = this.changed; + var prev = this._previousAttributes; + + // For each `set` attribute, update or delete the current value. + for (var attr in attrs) { + val = attrs[attr]; + if (!_.isEqual(current[attr], val)) changes.push(attr); + if (!_.isEqual(prev[attr], val)) { + changed[attr] = val; + } else { + delete changed[attr]; + } + unset ? delete current[attr] : current[attr] = val; + } + + // Update the `id`. + if (this.idAttribute in attrs) this.id = this.get(this.idAttribute); + + // Trigger all relevant attribute changes. + if (!silent) { + if (changes.length) this._pending = options; + for (var i = 0; i < changes.length; i++) { + this.trigger('change:' + changes[i], this, current[changes[i]], options); + } + } + + // You might be wondering why there's a `while` loop here. Changes can + // be recursively nested within `"change"` events. + if (changing) return this; + if (!silent) { + while (this._pending) { + options = this._pending; + this._pending = false; + this.trigger('change', this, options); + } + } + this._pending = false; + this._changing = false; + return this; + }, + + // Remove an attribute from the model, firing `"change"`. `unset` is a noop + // if the attribute doesn't exist. + unset: function(attr, options) { + return this.set(attr, void 0, _.extend({}, options, {unset: true})); + }, + + // Clear all attributes on the model, firing `"change"`. + clear: function(options) { + var attrs = {}; + for (var key in this.attributes) attrs[key] = void 0; + return this.set(attrs, _.extend({}, options, {unset: true})); + }, + + // 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 old = this._changing ? this._previousAttributes : this.attributes; + var changed = {}; + for (var attr in diff) { + var val = diff[attr]; + if (_.isEqual(old[attr], val)) continue; + changed[attr] = val; + } + return _.size(changed) ? changed : false; + }, + + // 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); + }, + + // Fetch the model from the server, merging the response with the model's + // local attributes. Any changed attributes will trigger a "change" event. + fetch: function(options) { + options = _.extend({parse: true}, options); + var model = this; + var success = options.success; + options.success = function(resp) { + var serverAttrs = options.parse ? model.parse(resp, options) : resp; + if (!model.set(serverAttrs, options)) return false; + if (success) success.call(options.context, model, resp, options); + model.trigger('sync', model, resp, options); + }; + wrapError(this, 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, val, options) { + // Handle both `"key", value` and `{key: value}` -style arguments. + var attrs; + if (key == null || typeof key === 'object') { + attrs = key; + options = val; + } else { + (attrs = {})[key] = val; + } + + options = _.extend({validate: true, parse: true}, options); + var wait = options.wait; + + // If we're not waiting and attributes exist, save acts as + // `set(attr).save(null, opts)` with validation. Otherwise, check if + // the model will be valid when the attributes, if any, are set. + if (attrs && !wait) { + if (!this.set(attrs, options)) return false; + } else if (!this._validate(attrs, options)) { + 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; + var attributes = this.attributes; + options.success = function(resp) { + // Ensure attributes are restored during synchronous saves. + model.attributes = attributes; + var serverAttrs = options.parse ? model.parse(resp, options) : resp; + if (wait) serverAttrs = _.extend({}, attrs, serverAttrs); + if (serverAttrs && !model.set(serverAttrs, options)) return false; + if (success) success.call(options.context, model, resp, options); + model.trigger('sync', model, resp, options); + }; + wrapError(this, options); + + // Set temporary attributes if `{wait: true}` to properly find new ids. + if (attrs && wait) this.attributes = _.extend({}, attributes, attrs); + + var method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update'); + if (method === 'patch' && !options.attrs) options.attrs = attrs; + var xhr = this.sync(method, this, options); + + // Restore attributes. + this.attributes = attributes; + + 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 wait = options.wait; + + var destroy = function() { + model.stopListening(); + model.trigger('destroy', model, model.collection, options); + }; + + options.success = function(resp) { + if (wait) destroy(); + if (success) success.call(options.context, model, resp, options); + if (!model.isNew()) model.trigger('sync', model, resp, options); + }; + + var xhr = false; + if (this.isNew()) { + _.defer(options.success); + } else { + wrapError(this, options); + xhr = this.sync('delete', this, options); + } + if (!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 = + _.result(this, 'urlRoot') || + _.result(this.collection, 'url') || + urlError(); + if (this.isNew()) return base; + var id = this.get(this.idAttribute); + return base.replace(/[^\/]$/, '$&/') + encodeURIComponent(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, options) { + 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.has(this.idAttribute); + }, + + // Check if the model is currently in a valid state. + isValid: function(options) { + return this._validate({}, _.extend({}, options, {validate: true})); + }, + + // Run validation against the next complete set of model attributes, + // returning `true` if all is well. Otherwise, fire an `"invalid"` event. + _validate: function(attrs, options) { + if (!options.validate || !this.validate) return true; + attrs = _.extend({}, this.attributes, attrs); + var error = this.validationError = this.validate(attrs, options) || null; + if (!error) return true; + this.trigger('invalid', this, error, _.extend(options, {validationError: error})); + return false; + } + + }); + + // Underscore methods that we want to implement on the Model, mapped to the + // number of arguments they take. + var modelMethods = {keys: 1, values: 1, pairs: 1, invert: 1, pick: 0, + omit: 0, chain: 1, isEmpty: 1}; + + // Mix in each Underscore method as a proxy to `Model#attributes`. + addUnderscoreMethods(Model, modelMethods, 'attributes'); + + // Backbone.Collection + // ------------------- + + // If models tend to represent a single row of data, a Backbone Collection is + // more analogous to a table full of data ... or a small slice or page of that + // table, or a collection of rows that belong together for a particular reason + // -- all of the messages in this particular folder, all of the documents + // belonging to this particular author, and so on. Collections maintain + // indexes of their models, both in order, and for lookup by `id`. + + // Create a new **Collection**, perhaps to contain a specific type of `model`. + // 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 !== void 0) this.comparator = options.comparator; + this._reset(); + this.initialize.apply(this, arguments); + if (models) this.reset(models, _.extend({silent: true}, options)); + }; + + // Default options for `Collection#set`. + var setOptions = {add: true, remove: true, merge: true}; + var addOptions = {add: true, remove: false}; + + // Splices `insert` into `array` at index `at`. + var splice = function(array, insert, at) { + at = Math.min(Math.max(at, 0), array.length); + var tail = Array(array.length - at); + var length = insert.length; + var i; + for (i = 0; i < tail.length; i++) tail[i] = array[i + at]; + for (i = 0; i < length; i++) array[i + at] = insert[i]; + for (i = 0; i < tail.length; i++) array[i + length + at] = tail[i]; + }; + + // 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. `models` may be Backbone + // Models or raw JavaScript objects to be converted to Models, or any + // combination of the two. + add: function(models, options) { + return this.set(models, _.extend({merge: false}, options, addOptions)); + }, + + // Remove a model, or a list of models from the set. + remove: function(models, options) { + options = _.extend({}, options); + var singular = !_.isArray(models); + models = singular ? [models] : models.slice(); + var removed = this._removeModels(models, options); + if (!options.silent && removed.length) { + options.changes = {added: [], merged: [], removed: removed}; + this.trigger('update', this, options); + } + return singular ? removed[0] : removed; + }, + + // Update a collection by `set`-ing a new list of models, adding new ones, + // removing models that are no longer present, and merging models that + // already exist in the collection, as necessary. Similar to **Model#set**, + // the core operation for updating the data contained by the collection. + set: function(models, options) { + if (models == null) return; + + options = _.extend({}, setOptions, options); + if (options.parse && !this._isModel(models)) { + models = this.parse(models, options) || []; + } + + var singular = !_.isArray(models); + models = singular ? [models] : models.slice(); + + var at = options.at; + if (at != null) at = +at; + if (at > this.length) at = this.length; + if (at < 0) at += this.length + 1; + + var set = []; + var toAdd = []; + var toMerge = []; + var toRemove = []; + var modelMap = {}; + + var add = options.add; + var merge = options.merge; + var remove = options.remove; + + var sort = false; + var sortable = this.comparator && at == null && options.sort !== false; + var sortAttr = _.isString(this.comparator) ? this.comparator : null; + + // Turn bare objects into model references, and prevent invalid models + // from being added. + var model, i; + for (i = 0; i < models.length; i++) { + model = models[i]; + + // If a duplicate is found, prevent it from being added and + // optionally merge it into the existing model. + var existing = this.get(model); + if (existing) { + if (merge && model !== existing) { + var attrs = this._isModel(model) ? model.attributes : model; + if (options.parse) attrs = existing.parse(attrs, options); + existing.set(attrs, options); + toMerge.push(existing); + if (sortable && !sort) sort = existing.hasChanged(sortAttr); + } + if (!modelMap[existing.cid]) { + modelMap[existing.cid] = true; + set.push(existing); + } + models[i] = existing; + + // If this is a new, valid model, push it to the `toAdd` list. + } else if (add) { + model = models[i] = this._prepareModel(model, options); + if (model) { + toAdd.push(model); + this._addReference(model, options); + modelMap[model.cid] = true; + set.push(model); + } + } + } + + // Remove stale models. + if (remove) { + for (i = 0; i < this.length; i++) { + model = this.models[i]; + if (!modelMap[model.cid]) toRemove.push(model); + } + if (toRemove.length) this._removeModels(toRemove, options); + } + + // See if sorting is needed, update `length` and splice in new models. + var orderChanged = false; + var replace = !sortable && add && remove; + if (set.length && replace) { + orderChanged = this.length !== set.length || _.some(this.models, function(m, index) { + return m !== set[index]; + }); + this.models.length = 0; + splice(this.models, set, 0); + this.length = this.models.length; + } else if (toAdd.length) { + if (sortable) sort = true; + splice(this.models, toAdd, at == null ? this.length : at); + this.length = this.models.length; + } + + // Silently sort the collection if appropriate. + if (sort) this.sort({silent: true}); + + // Unless silenced, it's time to fire all appropriate add/sort/update events. + if (!options.silent) { + for (i = 0; i < toAdd.length; i++) { + if (at != null) options.index = at + i; + model = toAdd[i]; + model.trigger('add', model, this, options); + } + if (sort || orderChanged) this.trigger('sort', this, options); + if (toAdd.length || toRemove.length || toMerge.length) { + options.changes = { + added: toAdd, + removed: toRemove, + merged: toMerge + }; + this.trigger('update', this, options); + } + } + + // Return the added (or merged) model (or models). + return singular ? models[0] : models; + }, + + // 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 granular `add` or `remove` events. Fires `reset` when finished. + // Useful for bulk operations and optimizations. + reset: function(models, options) { + options = options ? _.clone(options) : {}; + for (var i = 0; i < this.models.length; i++) { + this._removeReference(this.models[i], options); + } + options.previousModels = this.models; + this._reset(); + models = this.add(models, _.extend({silent: true}, options)); + if (!options.silent) this.trigger('reset', this, options); + return models; + }, + + // Add a model to the end of the collection. + push: function(model, options) { + return this.add(model, _.extend({at: this.length}, options)); + }, + + // Remove a model from the end of the collection. + pop: function(options) { + var model = this.at(this.length - 1); + return this.remove(model, options); + }, + + // Add a model to the beginning of the collection. + unshift: function(model, options) { + return this.add(model, _.extend({at: 0}, options)); + }, + + // Remove a model from the beginning of the collection. + shift: function(options) { + var model = this.at(0); + return this.remove(model, options); + }, + + // Slice out a sub-array of models from the collection. + slice: function() { + return slice.apply(this.models, arguments); + }, + + // Get a model from the set by id, cid, model object with id or cid + // properties, or an attributes object that is transformed through modelId. + get: function(obj) { + if (obj == null) return void 0; + return this._byId[obj] || + this._byId[this.modelId(obj.attributes || obj)] || + obj.cid && this._byId[obj.cid]; + }, + + // Returns `true` if the model is in the collection. + has: function(obj) { + return this.get(obj) != null; + }, + + // Get the model at the given index. + at: function(index) { + if (index < 0) index += this.length; + return this.models[index]; + }, + + // Return models with matching attributes. Useful for simple cases of + // `filter`. + where: function(attrs, first) { + return this[first ? 'find' : 'filter'](attrs); + }, + + // Return the first model with matching attributes. Useful for simple cases + // of `find`. + findWhere: function(attrs) { + return this.where(attrs, 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) { + var comparator = this.comparator; + if (!comparator) throw new Error('Cannot sort a set without a comparator'); + options || (options = {}); + + var length = comparator.length; + if (_.isFunction(comparator)) comparator = _.bind(comparator, this); + + // Run sort based on type of `comparator`. + if (length === 1 || _.isString(comparator)) { + this.models = this.sortBy(comparator); + } else { + this.models.sort(comparator); + } + if (!options.silent) this.trigger('sort', this, options); + return this; + }, + + // Pluck an attribute from each model in the collection. + pluck: function(attr) { + return this.map(attr + ''); + }, + + // Fetch the default set of models for this collection, resetting the + // collection when they arrive. If `reset: true` is passed, the response + // data will be passed through the `reset` method instead of `set`. + fetch: function(options) { + options = _.extend({parse: true}, options); + var success = options.success; + var collection = this; + options.success = function(resp) { + var method = options.reset ? 'reset' : 'set'; + collection[method](resp, options); + if (success) success.call(options.context, collection, resp, options); + collection.trigger('sync', collection, resp, options); + }; + wrapError(this, 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) { + options = options ? _.clone(options) : {}; + var wait = options.wait; + model = this._prepareModel(model, options); + if (!model) return false; + if (!wait) this.add(model, options); + var collection = this; + var success = options.success; + options.success = function(m, resp, callbackOpts) { + if (wait) collection.add(m, callbackOpts); + if (success) success.call(callbackOpts.context, m, resp, callbackOpts); + }; + 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, options) { + return resp; + }, + + // Create a new collection with an identical list of models as this one. + clone: function() { + return new this.constructor(this.models, { + model: this.model, + comparator: this.comparator + }); + }, + + // Define how to uniquely identify models in the collection. + modelId: function(attrs) { + return attrs[this.model.prototype.idAttribute || 'id']; + }, + + // Private method to reset all internal state. Called when the collection + // is first initialized or reset. + _reset: function() { + this.length = 0; + this.models = []; + this._byId = {}; + }, + + // Prepare a hash of attributes (or other model) to be added to this + // collection. + _prepareModel: function(attrs, options) { + if (this._isModel(attrs)) { + if (!attrs.collection) attrs.collection = this; + return attrs; + } + options = options ? _.clone(options) : {}; + options.collection = this; + var model = new this.model(attrs, options); + if (!model.validationError) return model; + this.trigger('invalid', this, model.validationError, options); + return false; + }, + + // Internal method called by both remove and set. + _removeModels: function(models, options) { + var removed = []; + for (var i = 0; i < models.length; i++) { + var model = this.get(models[i]); + if (!model) continue; + + var index = this.indexOf(model); + this.models.splice(index, 1); + this.length--; + + // Remove references before triggering 'remove' event to prevent an + // infinite loop. #3693 + delete this._byId[model.cid]; + var id = this.modelId(model.attributes); + if (id != null) delete this._byId[id]; + + if (!options.silent) { + options.index = index; + model.trigger('remove', model, this, options); + } + + removed.push(model); + this._removeReference(model, options); + } + return removed; + }, + + // Method for checking whether an object should be considered a model for + // the purposes of adding to the collection. + _isModel: function(model) { + return model instanceof Model; + }, + + // Internal method to create a model's ties to a collection. + _addReference: function(model, options) { + this._byId[model.cid] = model; + var id = this.modelId(model.attributes); + if (id != null) this._byId[id] = model; + model.on('all', this._onModelEvent, this); + }, + + // Internal method to sever a model's ties to a collection. + _removeReference: function(model, options) { + delete this._byId[model.cid]; + var id = this.modelId(model.attributes); + if (id != null) delete this._byId[id]; + 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 (model) { + if ((event === 'add' || event === 'remove') && collection !== this) return; + if (event === 'destroy') this.remove(model, options); + if (event === 'change') { + var prevId = this.modelId(model.previousAttributes()); + var id = this.modelId(model.attributes); + if (prevId !== id) { + if (prevId != null) delete this._byId[prevId]; + if (id != null) this._byId[id] = model; + } + } + } + this.trigger.apply(this, arguments); + } + + }); + + // Underscore methods that we want to implement on the Collection. + // 90% of the core usefulness of Backbone Collections is actually implemented + // right here: + var collectionMethods = {forEach: 3, each: 3, map: 3, collect: 3, reduce: 0, + foldl: 0, inject: 0, reduceRight: 0, foldr: 0, find: 3, detect: 3, filter: 3, + select: 3, reject: 3, every: 3, all: 3, some: 3, any: 3, include: 3, includes: 3, + contains: 3, invoke: 0, max: 3, min: 3, toArray: 1, size: 1, first: 3, + head: 3, take: 3, initial: 3, rest: 3, tail: 3, drop: 3, last: 3, + without: 0, difference: 0, indexOf: 3, shuffle: 1, lastIndexOf: 3, + isEmpty: 1, chain: 1, sample: 3, partition: 3, groupBy: 3, countBy: 3, + sortBy: 3, indexBy: 3, findIndex: 3, findLastIndex: 3}; + + // Mix in each Underscore method as a proxy to `Collection#models`. + addUnderscoreMethods(Collection, collectionMethods, 'models'); + + // Backbone.View + // ------------- + + // Backbone Views are almost more convention than they are actual code. A View + // is simply a JavaScript object that represents a logical chunk of UI in the + // DOM. This might be a single item, an entire list, a sidebar or panel, or + // even the surrounding frame which wraps your whole app. Defining a chunk of + // UI as a **View** allows you to define your DOM events declaratively, without + // having to worry about render order ... and makes it easy for the view to + // react to specific changes in the state of your models. + + // 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'); + _.extend(this, _.pick(options, viewOptions)); + this._ensureElement(); + this.initialize.apply(this, arguments); + }; + + // Cached regex to split keys for `delegate`. + var delegateEventSplitter = /^(\S+)\s*(.*)$/; + + // List of view options to be set as properties. + var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events']; + + // 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 preferred 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 by taking the element out of the DOM, and removing any + // applicable Backbone.Events listeners. + remove: function() { + this._removeElement(); + this.stopListening(); + return this; + }, + + // Remove this view's element from the document and all event listeners + // attached to it. Exposed for subclasses using an alternative DOM + // manipulation API. + _removeElement: function() { + this.$el.remove(); + }, + + // Change the view's element (`this.el` property) and re-delegate the + // view's events on the new element. + setElement: function(element) { + this.undelegateEvents(); + this._setElement(element); + this.delegateEvents(); + return this; + }, + + // Creates the `this.el` and `this.$el` references for this view using the + // given `el`. `el` can be a CSS selector or an HTML string, a jQuery + // context or an element. Subclasses can override this to utilize an + // alternative DOM manipulation API and are only required to set the + // `this.el` property. + _setElement: function(el) { + this.$el = el instanceof Backbone.$ ? el : Backbone.$(el); + this.el = this.$el[0]; + }, + + // 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`. + delegateEvents: function(events) { + events || (events = _.result(this, 'events')); + if (!events) return this; + this.undelegateEvents(); + for (var key in events) { + var method = events[key]; + if (!_.isFunction(method)) method = this[method]; + if (!method) continue; + var match = key.match(delegateEventSplitter); + this.delegate(match[1], match[2], _.bind(method, this)); + } + return this; + }, + + // Add a single event listener to the view's element (or a child element + // using `selector`). This only works for delegate-able events: not `focus`, + // `blur`, and not `change`, `submit`, and `reset` in Internet Explorer. + delegate: function(eventName, selector, listener) { + this.$el.on(eventName + '.delegateEvents' + this.cid, selector, listener); + return this; + }, + + // Clears all callbacks previously bound to the view by `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() { + if (this.$el) this.$el.off('.delegateEvents' + this.cid); + return this; + }, + + // A finer-grained `undelegateEvents` for removing a single delegated event. + // `selector` and `listener` are both optional. + undelegate: function(eventName, selector, listener) { + this.$el.off(eventName + '.delegateEvents' + this.cid, selector, listener); + return this; + }, + + // Produces a DOM element to be assigned to your view. Exposed for + // subclasses using an alternative DOM manipulation API. + _createElement: function(tagName) { + return document.createElement(tagName); + }, + + // 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({}, _.result(this, 'attributes')); + if (this.id) attrs.id = _.result(this, 'id'); + if (this.className) attrs['class'] = _.result(this, 'className'); + this.setElement(this._createElement(_.result(this, 'tagName'))); + this._setAttributes(attrs); + } else { + this.setElement(_.result(this, 'el')); + } + }, + + // Set attributes from a hash on this view's element. Exposed for + // subclasses using an alternative DOM manipulation API. + _setAttributes: function(attributes) { + this.$el.attr(attributes); + } + + }); + + // Backbone.sync + // ------------- + + // 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. + _.defaults(options || (options = {}), { + emulateHTTP: Backbone.emulateHTTP, + emulateJSON: Backbone.emulateJSON + }); + + // Default JSON-request options. + var params = {type: type, dataType: 'json'}; + + // Ensure that we have a URL. + if (!options.url) { + params.url = _.result(model, 'url') || urlError(); + } + + // Ensure that we have the appropriate request data. + if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) { + params.contentType = 'application/json'; + params.data = JSON.stringify(options.attrs || model.toJSON(options)); + } + + // For older servers, emulate JSON by encoding the request into an HTML-form. + if (options.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 (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) { + params.type = 'POST'; + if (options.emulateJSON) params.data._method = type; + var beforeSend = options.beforeSend; + options.beforeSend = function(xhr) { + xhr.setRequestHeader('X-HTTP-Method-Override', type); + if (beforeSend) return beforeSend.apply(this, arguments); + }; + } + + // Don't process data on a non-GET request. + if (params.type !== 'GET' && !options.emulateJSON) { + params.processData = false; + } + + // Pass along `textStatus` and `errorThrown` from jQuery. + var error = options.error; + options.error = function(xhr, textStatus, errorThrown) { + options.textStatus = textStatus; + options.errorThrown = errorThrown; + if (error) error.call(options.context, xhr, textStatus, errorThrown); + }; + + // Make the request, allowing the user to override any Ajax options. + var xhr = options.xhr = Backbone.ajax(_.extend(params, options)); + model.trigger('request', model, xhr, options); + return xhr; + }; + + // Map from CRUD to HTTP for our default `Backbone.sync` implementation. + var methodMap = { + 'create': 'POST', + 'update': 'PUT', + 'patch': 'PATCH', + 'delete': 'DELETE', + 'read': 'GET' + }; + + // Set the default implementation of `Backbone.ajax` to proxy through to `$`. + // Override this if you'd like to use a different library. + Backbone.ajax = function() { + return Backbone.$.ajax.apply(Backbone.$, 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 optionalParam = /\((.*?)\)/g; + 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) { + if (!_.isRegExp(route)) route = this._routeToRegExp(route); + if (_.isFunction(name)) { + callback = name; + name = ''; + } + if (!callback) callback = this[name]; + var router = this; + Backbone.history.route(route, function(fragment) { + var args = router._extractParameters(route, fragment); + if (router.execute(callback, args, name) !== false) { + router.trigger.apply(router, ['route:' + name].concat(args)); + router.trigger('route', name, args); + Backbone.history.trigger('route', router, name, args); + } + }); + return this; + }, + + // Execute a route handler with the provided parameters. This is an + // excellent place to do pre-route setup or post-route cleanup. + execute: function(callback, args, name) { + if (callback) callback.apply(this, args); + }, + + // Simple proxy to `Backbone.history` to save a fragment into the history. + navigate: function(fragment, options) { + Backbone.history.navigate(fragment, options); + return this; + }, + + // 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; + this.routes = _.result(this, 'routes'); + var route, routes = _.keys(this.routes); + while ((route = routes.pop()) != null) { + this.route(route, this.routes[route]); + } + }, + + // Convert a route string into a regular expression, suitable for matching + // against the current location hash. + _routeToRegExp: function(route) { + route = route.replace(escapeRegExp, '\\$&') + .replace(optionalParam, '(?:$1)?') + .replace(namedParam, function(match, optional) { + return optional ? match : '([^/?]+)'; + }) + .replace(splatParam, '([^?]*?)'); + return new RegExp('^' + route + '(?:\\?([\\s\\S]*))?$'); + }, + + // Given a route, and a URL fragment that it matches, return the array of + // extracted decoded parameters. Empty or unmatched parameters will be + // treated as `null` to normalize cross-browser behavior. + _extractParameters: function(route, fragment) { + var params = route.exec(fragment).slice(1); + return _.map(params, function(param, i) { + // Don't decode the search params. + if (i === params.length - 1) return param || null; + return param ? decodeURIComponent(param) : null; + }); + } + + }); + + // Backbone.History + // ---------------- + + // Handles cross-browser history management, based on either + // [pushState](http://diveintohtml5.info/history.html) and real URLs, or + // [onhashchange](https://developer.mozilla.org/en-US/docs/DOM/window.onhashchange) + // and URL fragments. If the browser supports neither (old IE, natch), + // falls back to polling. + var History = Backbone.History = function() { + this.handlers = []; + this.checkUrl = _.bind(this.checkUrl, this); + + // Ensure that `History` can be used outside of the browser. + if (typeof window !== 'undefined') { + this.location = window.location; + this.history = window.history; + } + }; + + // Cached regex for stripping a leading hash/slash and trailing space. + var routeStripper = /^[#\/]|\s+$/g; + + // Cached regex for stripping leading and trailing slashes. + var rootStripper = /^\/+|\/+$/g; + + // Cached regex for stripping urls of hash. + var pathStripper = /#.*$/; + + // 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, + + // Are we at the app root? + atRoot: function() { + var path = this.location.pathname.replace(/[^\/]$/, '$&/'); + return path === this.root && !this.getSearch(); + }, + + // Does the pathname match the root? + matchRoot: function() { + var path = this.decodeFragment(this.location.pathname); + var rootPath = path.slice(0, this.root.length - 1) + '/'; + return rootPath === this.root; + }, + + // Unicode characters in `location.pathname` are percent encoded so they're + // decoded for comparison. `%25` should not be decoded since it may be part + // of an encoded parameter. + decodeFragment: function(fragment) { + return decodeURI(fragment.replace(/%25/g, '%2525')); + }, + + // In IE6, the hash fragment and search params are incorrect if the + // fragment contains `?`. + getSearch: function() { + var match = this.location.href.replace(/#.*/, '').match(/\?.+/); + return match ? match[0] : ''; + }, + + // 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 pathname and search params, without the root. + getPath: function() { + var path = this.decodeFragment( + this.location.pathname + this.getSearch() + ).slice(this.root.length - 1); + return path.charAt(0) === '/' ? path.slice(1) : path; + }, + + // Get the cross-browser normalized URL fragment from the path or hash. + getFragment: function(fragment) { + if (fragment == null) { + if (this._usePushState || !this._wantsHashChange) { + fragment = this.getPath(); + } else { + fragment = this.getHash(); + } + } + return 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.root = this.options.root; + this._wantsHashChange = this.options.hashChange !== false; + this._hasHashChange = 'onhashchange' in window && (document.documentMode === void 0 || document.documentMode > 7); + this._useHashChange = this._wantsHashChange && this._hasHashChange; + this._wantsPushState = !!this.options.pushState; + this._hasPushState = !!(this.history && this.history.pushState); + this._usePushState = this._wantsPushState && this._hasPushState; + this.fragment = this.getFragment(); + + // Normalize root to always include a leading and trailing slash. + this.root = ('/' + this.root + '/').replace(rootStripper, '/'); + + // Transition from hashChange to pushState or vice versa if both are + // requested. + if (this._wantsHashChange && this._wantsPushState) { + + // 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._hasPushState && !this.atRoot()) { + var rootPath = this.root.slice(0, -1) || '/'; + this.location.replace(rootPath + '#' + this.getPath()); + // 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._hasPushState && this.atRoot()) { + this.navigate(this.getHash(), {replace: true}); + } + + } + + // Proxy an iframe to handle location events if the browser doesn't + // support the `hashchange` event, HTML5 history, or the user wants + // `hashChange` but not `pushState`. + if (!this._hasHashChange && this._wantsHashChange && !this._usePushState) { + this.iframe = document.createElement('iframe'); + this.iframe.src = 'javascript:0'; + this.iframe.style.display = 'none'; + this.iframe.tabIndex = -1; + var body = document.body; + // Using `appendChild` will throw on IE < 9 if the document is not ready. + var iWindow = body.insertBefore(this.iframe, body.firstChild).contentWindow; + iWindow.document.open(); + iWindow.document.close(); + iWindow.location.hash = '#' + this.fragment; + } + + // Add a cross-platform `addEventListener` shim for older browsers. + var addEventListener = window.addEventListener || function(eventName, listener) { + return attachEvent('on' + eventName, listener); + }; + + // Depending on whether we're using pushState or hashes, and whether + // 'onhashchange' is supported, determine how we check the URL state. + if (this._usePushState) { + addEventListener('popstate', this.checkUrl, false); + } else if (this._useHashChange && !this.iframe) { + addEventListener('hashchange', this.checkUrl, false); + } else if (this._wantsHashChange) { + this._checkUrlInterval = setInterval(this.checkUrl, this.interval); + } + + 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() { + // Add a cross-platform `removeEventListener` shim for older browsers. + var removeEventListener = window.removeEventListener || function(eventName, listener) { + return detachEvent('on' + eventName, listener); + }; + + // Remove window listeners. + if (this._usePushState) { + removeEventListener('popstate', this.checkUrl, false); + } else if (this._useHashChange && !this.iframe) { + removeEventListener('hashchange', this.checkUrl, false); + } + + // Clean up the iframe if necessary. + if (this.iframe) { + document.body.removeChild(this.iframe); + this.iframe = null; + } + + // Some environments will throw when clearing an undefined interval. + if (this._checkUrlInterval) 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 the user pressed the back button, the iframe's hash will have + // changed and we should use that for comparison. + if (current === this.fragment && this.iframe) { + current = this.getHash(this.iframe.contentWindow); + } + + if (current === this.fragment) return false; + if (this.iframe) this.navigate(current); + this.loadUrl(); + }, + + // 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(fragment) { + // If the root doesn't match, no routes can match either. + if (!this.matchRoot()) return false; + fragment = this.fragment = this.getFragment(fragment); + return _.some(this.handlers, function(handler) { + if (handler.route.test(fragment)) { + handler.callback(fragment); + return true; + } + }); + }, + + // 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}; + + // Normalize the fragment. + fragment = this.getFragment(fragment || ''); + + // Don't include a trailing slash on the root. + var rootPath = this.root; + if (fragment === '' || fragment.charAt(0) === '?') { + rootPath = rootPath.slice(0, -1) || '/'; + } + var url = rootPath + fragment; + + // Strip the hash and decode for matching. + fragment = this.decodeFragment(fragment.replace(pathStripper, '')); + + if (this.fragment === fragment) return; + this.fragment = fragment; + + // If pushState is available, we use it to set the fragment as a real URL. + if (this._usePushState) { + 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, fragment, options.replace); + if (this.iframe && fragment !== this.getHash(this.iframe.contentWindow)) { + var iWindow = this.iframe.contentWindow; + + // 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) { + iWindow.document.open(); + iWindow.document.close(); + } + + this._updateHash(iWindow.location, fragment, 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) return 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) { + var href = location.href.replace(/(javascript:|#).*$/, ''); + location.replace(href + '#' + fragment); + } else { + // Some browsers require that `hash` contains a leading #. + location.hash = '#' + fragment; + } + } + + }); + + // Create the default Backbone.history. + Backbone.history = new History; + + // Helpers + // ------- + + // 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 extend = function(protoProps, staticProps) { + var parent = this; + 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 constructor. + if (protoProps && _.has(protoProps, 'constructor')) { + child = protoProps.constructor; + } else { + child = function(){ return parent.apply(this, arguments); }; + } + + // Add static properties to the constructor function, if supplied. + _.extend(child, parent, staticProps); + + // Set the prototype chain to inherit from `parent`, without calling + // `parent`'s constructor function and add the prototype properties. + child.prototype = _.create(parent.prototype, protoProps); + child.prototype.constructor = child; + + // Set a convenience property in case the parent's prototype is needed + // later. + child.__super__ = parent.prototype; + + return child; + }; + + // Set up inheritance for the model, collection, router, view and history. + Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend; + + // 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'); + }; + + // Wrap an optional error callback with a fallback error event. + var wrapError = function(model, options) { + var error = options.error; + options.error = function(resp) { + if (error) error.call(options.context, model, resp, options); + model.trigger('error', model, resp, options); + }; + }; + + return Backbone; +}); diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/backbone/test/collection.js b/ecomp-portal-FE/client/bower_components/lodash/vendor/backbone/test/collection.js new file mode 100644 index 00000000..dd98aca5 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/backbone/test/collection.js @@ -0,0 +1,1998 @@ +(function() { + + var a, b, c, d, e, col, otherCol; + + QUnit.module('Backbone.Collection', { + + beforeEach: function(assert) { + 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(); + } + + }); + + QUnit.test('new and sort', function(assert) { + assert.expect(6); + var counter = 0; + col.on('sort', function(){ counter++; }); + assert.deepEqual(col.pluck('label'), ['a', 'b', 'c', 'd']); + col.comparator = function(m1, m2) { + return m1.id > m2.id ? -1 : 1; + }; + col.sort(); + assert.equal(counter, 1); + assert.deepEqual(col.pluck('label'), ['a', 'b', 'c', 'd']); + col.comparator = function(model) { return model.id; }; + col.sort(); + assert.equal(counter, 2); + assert.deepEqual(col.pluck('label'), ['d', 'c', 'b', 'a']); + assert.equal(col.length, 4); + }); + + QUnit.test('String comparator.', function(assert) { + assert.expect(1); + var collection = new Backbone.Collection([ + {id: 3}, + {id: 1}, + {id: 2} + ], {comparator: 'id'}); + assert.deepEqual(collection.pluck('id'), [1, 2, 3]); + }); + + QUnit.test('new and parse', function(assert) { + assert.expect(3); + var Collection = Backbone.Collection.extend({ + parse: function(data) { + return _.filter(data, function(datum) { + return datum.a % 2 === 0; + }); + } + }); + var models = [{a: 1}, {a: 2}, {a: 3}, {a: 4}]; + var collection = new Collection(models, {parse: true}); + assert.strictEqual(collection.length, 2); + assert.strictEqual(collection.first().get('a'), 2); + assert.strictEqual(collection.last().get('a'), 4); + }); + + QUnit.test('clone preserves model and comparator', function(assert) { + assert.expect(3); + var Model = Backbone.Model.extend(); + var comparator = function(model){ return model.id; }; + + var collection = new Backbone.Collection([{id: 1}], { + model: Model, + comparator: comparator + }).clone(); + collection.add({id: 2}); + assert.ok(collection.at(0) instanceof Model); + assert.ok(collection.at(1) instanceof Model); + assert.strictEqual(collection.comparator, comparator); + }); + + QUnit.test('get', function(assert) { + assert.expect(6); + assert.equal(col.get(0), d); + assert.equal(col.get(d.clone()), d); + assert.equal(col.get(2), b); + assert.equal(col.get({id: 1}), c); + assert.equal(col.get(c.clone()), c); + assert.equal(col.get(col.first().cid), col.first()); + }); + + QUnit.test('get with non-default ids', function(assert) { + assert.expect(5); + var MongoModel = Backbone.Model.extend({idAttribute: '_id'}); + var model = new MongoModel({_id: 100}); + var collection = new Backbone.Collection([model], {model: MongoModel}); + assert.equal(collection.get(100), model); + assert.equal(collection.get(model.cid), model); + assert.equal(collection.get(model), model); + assert.equal(collection.get(101), void 0); + + var collection2 = new Backbone.Collection(); + collection2.model = MongoModel; + collection2.add(model.attributes); + assert.equal(collection2.get(model.clone()), collection2.first()); + }); + + QUnit.test('has', function(assert) { + assert.expect(15); + assert.ok(col.has(a)); + assert.ok(col.has(b)); + assert.ok(col.has(c)); + assert.ok(col.has(d)); + assert.ok(col.has(a.id)); + assert.ok(col.has(b.id)); + assert.ok(col.has(c.id)); + assert.ok(col.has(d.id)); + assert.ok(col.has(a.cid)); + assert.ok(col.has(b.cid)); + assert.ok(col.has(c.cid)); + assert.ok(col.has(d.cid)); + var outsider = new Backbone.Model({id: 4}); + assert.notOk(col.has(outsider)); + assert.notOk(col.has(outsider.id)); + assert.notOk(col.has(outsider.cid)); + }); + + QUnit.test('update index when id changes', function(assert) { + assert.expect(4); + var collection = new Backbone.Collection(); + collection.add([ + {id: 0, name: 'one'}, + {id: 1, name: 'two'} + ]); + var one = collection.get(0); + assert.equal(one.get('name'), 'one'); + collection.on('change:name', function(model) { assert.ok(this.get(model)); }); + one.set({name: 'dalmatians', id: 101}); + assert.equal(collection.get(0), null); + assert.equal(collection.get(101).get('name'), 'dalmatians'); + }); + + QUnit.test('at', function(assert) { + assert.expect(2); + assert.equal(col.at(2), c); + assert.equal(col.at(-2), c); + }); + + QUnit.test('pluck', function(assert) { + assert.expect(1); + assert.equal(col.pluck('label').join(' '), 'a b c d'); + }); + + QUnit.test('add', function(assert) { + assert.expect(14); + 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'); + opts = options; + }); + col.add(e, {amazing: true}); + assert.equal(added, 'e'); + assert.equal(col.length, 5); + assert.equal(col.last(), e); + assert.equal(otherCol.length, 1); + assert.equal(secondAdded, null); + assert.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]); + assert.equal(atCol.length, 3); + atCol.add(e, {at: 1}); + assert.equal(atCol.length, 4); + assert.equal(atCol.at(1), e); + assert.equal(atCol.last(), h); + + var coll = new Backbone.Collection(new Array(2)); + var addCount = 0; + coll.on('add', function(){ + addCount += 1; + }); + coll.add([undefined, f, g]); + assert.equal(coll.length, 5); + assert.equal(addCount, 3); + coll.add(new Array(4)); + assert.equal(coll.length, 9); + assert.equal(addCount, 7); + }); + + QUnit.test('add multiple models', function(assert) { + assert.expect(6); + var collection = new Backbone.Collection([{at: 0}, {at: 1}, {at: 9}]); + collection.add([{at: 2}, {at: 3}, {at: 4}, {at: 5}, {at: 6}, {at: 7}, {at: 8}], {at: 2}); + for (var i = 0; i <= 5; i++) { + assert.equal(collection.at(i).get('at'), i); + } + }); + + QUnit.test('add; at should have preference over comparator', function(assert) { + assert.expect(1); + var Col = Backbone.Collection.extend({ + comparator: function(m1, m2) { + return m1.id > m2.id ? -1 : 1; + } + }); + + var collection = new Col([{id: 2}, {id: 3}]); + collection.add(new Backbone.Model({id: 1}), {at: 1}); + + assert.equal(collection.pluck('id').join(' '), '3 1 2'); + }); + + QUnit.test('add; at should add to the end if the index is out of bounds', function(assert) { + assert.expect(1); + var collection = new Backbone.Collection([{id: 2}, {id: 3}]); + collection.add(new Backbone.Model({id: 1}), {at: 5}); + + assert.equal(collection.pluck('id').join(' '), '2 3 1'); + }); + + QUnit.test("can't add model to collection twice", function(assert) { + var collection = new Backbone.Collection([{id: 1}, {id: 2}, {id: 1}, {id: 2}, {id: 3}]); + assert.equal(collection.pluck('id').join(' '), '1 2 3'); + }); + + QUnit.test("can't add different model with same id to collection twice", function(assert) { + assert.expect(1); + var collection = new Backbone.Collection; + collection.unshift({id: 101}); + collection.add({id: 101}); + assert.equal(collection.length, 1); + }); + + QUnit.test('merge in duplicate models with {merge: true}', function(assert) { + assert.expect(3); + var collection = new Backbone.Collection; + collection.add([{id: 1, name: 'Moe'}, {id: 2, name: 'Curly'}, {id: 3, name: 'Larry'}]); + collection.add({id: 1, name: 'Moses'}); + assert.equal(collection.first().get('name'), 'Moe'); + collection.add({id: 1, name: 'Moses'}, {merge: true}); + assert.equal(collection.first().get('name'), 'Moses'); + collection.add({id: 1, name: 'Tim'}, {merge: true, silent: true}); + assert.equal(collection.first().get('name'), 'Tim'); + }); + + QUnit.test('add model to multiple collections', function(assert) { + assert.expect(10); + var counter = 0; + var m = new Backbone.Model({id: 10, label: 'm'}); + m.on('add', function(model, collection) { + counter++; + assert.equal(m, model); + if (counter > 1) { + assert.equal(collection, col2); + } else { + assert.equal(collection, col1); + } + }); + var col1 = new Backbone.Collection([]); + col1.on('add', function(model, collection) { + assert.equal(m, model); + assert.equal(col1, collection); + }); + var col2 = new Backbone.Collection([]); + col2.on('add', function(model, collection) { + assert.equal(m, model); + assert.equal(col2, collection); + }); + col1.add(m); + assert.equal(m.collection, col1); + col2.add(m); + assert.equal(m.collection, col1); + }); + + QUnit.test('add model with parse', function(assert) { + assert.expect(1); + var Model = Backbone.Model.extend({ + parse: function(obj) { + obj.value += 1; + return obj; + } + }); + + var Col = Backbone.Collection.extend({model: Model}); + var collection = new Col; + collection.add({value: 1}, {parse: true}); + assert.equal(collection.at(0).get('value'), 2); + }); + + QUnit.test('add with parse and merge', function(assert) { + var collection = new Backbone.Collection(); + collection.parse = function(attrs) { + return _.map(attrs, function(model) { + if (model.model) return model.model; + return model; + }); + }; + collection.add({id: 1}); + collection.add({model: {id: 1, name: 'Alf'}}, {parse: true, merge: true}); + assert.equal(collection.first().get('name'), 'Alf'); + }); + + QUnit.test('add model to collection with sort()-style comparator', function(assert) { + assert.expect(3); + var collection = new Backbone.Collection; + collection.comparator = function(m1, m2) { + return m1.get('name') < m2.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'}); + collection.add(tom); + collection.add(rob); + collection.add(tim); + assert.equal(collection.indexOf(rob), 0); + assert.equal(collection.indexOf(tim), 1); + assert.equal(collection.indexOf(tom), 2); + }); + + QUnit.test('comparator that depends on `this`', function(assert) { + assert.expect(2); + var collection = new Backbone.Collection; + collection.negative = function(num) { + return -num; + }; + collection.comparator = function(model) { + return this.negative(model.id); + }; + collection.add([{id: 1}, {id: 2}, {id: 3}]); + assert.deepEqual(collection.pluck('id'), [3, 2, 1]); + collection.comparator = function(m1, m2) { + return this.negative(m2.id) - this.negative(m1.id); + }; + collection.sort(); + assert.deepEqual(collection.pluck('id'), [1, 2, 3]); + }); + + QUnit.test('remove', function(assert) { + assert.expect(12); + var removed = null; + var result = null; + col.on('remove', function(model, collection, options) { + removed = model.get('label'); + assert.equal(options.index, 3); + assert.equal(collection.get(model), undefined, '#3693: model cannot be fetched from collection'); + }); + result = col.remove(d); + assert.equal(removed, 'd'); + assert.strictEqual(result, d); + //if we try to remove d again, it's not going to actually get removed + result = col.remove(d); + assert.strictEqual(result, undefined); + assert.equal(col.length, 3); + assert.equal(col.first(), a); + col.off(); + result = col.remove([c, d]); + assert.equal(result.length, 1, 'only returns removed models'); + assert.equal(result[0], c, 'only returns removed models'); + result = col.remove([c, b]); + assert.equal(result.length, 1, 'only returns removed models'); + assert.equal(result[0], b, 'only returns removed models'); + result = col.remove([]); + assert.deepEqual(result, [], 'returns empty array when nothing removed'); + }); + + QUnit.test('add and remove return values', function(assert) { + assert.expect(13); + var Even = Backbone.Model.extend({ + validate: function(attrs) { + if (attrs.id % 2 !== 0) return 'odd'; + } + }); + var collection = new Backbone.Collection; + collection.model = Even; + + var list = collection.add([{id: 2}, {id: 4}], {validate: true}); + assert.equal(list.length, 2); + assert.ok(list[0] instanceof Backbone.Model); + assert.equal(list[1], collection.last()); + assert.equal(list[1].get('id'), 4); + + list = collection.add([{id: 3}, {id: 6}], {validate: true}); + assert.equal(collection.length, 3); + assert.equal(list[0], false); + assert.equal(list[1].get('id'), 6); + + var result = collection.add({id: 6}); + assert.equal(result.cid, list[1].cid); + + result = collection.remove({id: 6}); + assert.equal(collection.length, 2); + assert.equal(result.id, 6); + + list = collection.remove([{id: 2}, {id: 8}]); + assert.equal(collection.length, 1); + assert.equal(list[0].get('id'), 2); + assert.equal(list[1], null); + }); + + QUnit.test('shift and pop', function(assert) { + assert.expect(2); + var collection = new Backbone.Collection([{a: 'a'}, {b: 'b'}, {c: 'c'}]); + assert.equal(collection.shift().get('a'), 'a'); + assert.equal(collection.pop().get('c'), 'c'); + }); + + QUnit.test('slice', function(assert) { + assert.expect(2); + var collection = new Backbone.Collection([{a: 'a'}, {b: 'b'}, {c: 'c'}]); + var array = collection.slice(1, 3); + assert.equal(array.length, 2); + assert.equal(array[0].get('b'), 'b'); + }); + + QUnit.test('events are unbound on remove', function(assert) { + assert.expect(3); + var counter = 0; + var dj = new Backbone.Model(); + var emcees = new Backbone.Collection([dj]); + emcees.on('change', function(){ counter++; }); + dj.set({name: 'Kool'}); + assert.equal(counter, 1); + emcees.reset([]); + assert.equal(dj.collection, undefined); + dj.set({name: 'Shadow'}); + assert.equal(counter, 1); + }); + + QUnit.test('remove in multiple collections', function(assert) { + assert.expect(7); + var modelData = { + id: 5, + title: 'Othello' + }; + var passed = false; + var m1 = new Backbone.Model(modelData); + var m2 = new Backbone.Model(modelData); + m2.on('remove', function() { + passed = true; + }); + var col1 = new Backbone.Collection([m1]); + var col2 = new Backbone.Collection([m2]); + assert.notEqual(m1, m2); + assert.ok(col1.length === 1); + assert.ok(col2.length === 1); + col1.remove(m1); + assert.equal(passed, false); + assert.ok(col1.length === 0); + col2.remove(m1); + assert.ok(col2.length === 0); + assert.equal(passed, true); + }); + + QUnit.test('remove same model in multiple collection', function(assert) { + assert.expect(16); + var counter = 0; + var m = new Backbone.Model({id: 5, title: 'Othello'}); + m.on('remove', function(model, collection) { + counter++; + assert.equal(m, model); + if (counter > 1) { + assert.equal(collection, col1); + } else { + assert.equal(collection, col2); + } + }); + var col1 = new Backbone.Collection([m]); + col1.on('remove', function(model, collection) { + assert.equal(m, model); + assert.equal(col1, collection); + }); + var col2 = new Backbone.Collection([m]); + col2.on('remove', function(model, collection) { + assert.equal(m, model); + assert.equal(col2, collection); + }); + assert.equal(col1, m.collection); + col2.remove(m); + assert.ok(col2.length === 0); + assert.ok(col1.length === 1); + assert.equal(counter, 1); + assert.equal(col1, m.collection); + col1.remove(m); + assert.equal(null, m.collection); + assert.ok(col1.length === 0); + assert.equal(counter, 2); + }); + + QUnit.test('model destroy removes from all collections', function(assert) { + assert.expect(3); + var m = new Backbone.Model({id: 5, title: 'Othello'}); + m.sync = function(method, model, options) { options.success(); }; + var col1 = new Backbone.Collection([m]); + var col2 = new Backbone.Collection([m]); + m.destroy(); + assert.ok(col1.length === 0); + assert.ok(col2.length === 0); + assert.equal(undefined, m.collection); + }); + + QUnit.test('Collection: non-persisted model destroy removes from all collections', function(assert) { + assert.expect(3); + var m = new Backbone.Model({title: 'Othello'}); + m.sync = function(method, model, options) { throw 'should not be called'; }; + var col1 = new Backbone.Collection([m]); + var col2 = new Backbone.Collection([m]); + m.destroy(); + assert.ok(col1.length === 0); + assert.ok(col2.length === 0); + assert.equal(undefined, m.collection); + }); + + QUnit.test('fetch', function(assert) { + assert.expect(4); + var collection = new Backbone.Collection; + collection.url = '/test'; + collection.fetch(); + assert.equal(this.syncArgs.method, 'read'); + assert.equal(this.syncArgs.model, collection); + assert.equal(this.syncArgs.options.parse, true); + + collection.fetch({parse: false}); + assert.equal(this.syncArgs.options.parse, false); + }); + + QUnit.test('fetch with an error response triggers an error event', function(assert) { + assert.expect(1); + var collection = new Backbone.Collection(); + collection.on('error', function() { + assert.ok(true); + }); + collection.sync = function(method, model, options) { options.error(); }; + collection.fetch(); + }); + + QUnit.test('#3283 - fetch with an error response calls error with context', function(assert) { + assert.expect(1); + var collection = new Backbone.Collection(); + var obj = {}; + var options = { + context: obj, + error: function() { + assert.equal(this, obj); + } + }; + collection.sync = function(method, model, opts) { + opts.error.call(opts.context); + }; + collection.fetch(options); + }); + + QUnit.test('ensure fetch only parses once', function(assert) { + assert.expect(1); + var collection = new Backbone.Collection; + var counter = 0; + collection.parse = function(models) { + counter++; + return models; + }; + collection.url = '/test'; + collection.fetch(); + this.syncArgs.options.success([]); + assert.equal(counter, 1); + }); + + QUnit.test('create', function(assert) { + assert.expect(4); + var collection = new Backbone.Collection; + collection.url = '/test'; + var model = collection.create({label: 'f'}, {wait: true}); + assert.equal(this.syncArgs.method, 'create'); + assert.equal(this.syncArgs.model, model); + assert.equal(model.get('label'), 'f'); + assert.equal(model.collection, collection); + }); + + QUnit.test('create with validate:true enforces validation', function(assert) { + assert.expect(3); + var ValidatingModel = Backbone.Model.extend({ + validate: function(attrs) { + return 'fail'; + } + }); + var ValidatingCollection = Backbone.Collection.extend({ + model: ValidatingModel + }); + var collection = new ValidatingCollection(); + collection.on('invalid', function(coll, error, options) { + assert.equal(error, 'fail'); + assert.equal(options.validationError, 'fail'); + }); + assert.equal(collection.create({'foo': 'bar'}, {validate: true}), false); + }); + + QUnit.test('create will pass extra options to success callback', function(assert) { + assert.expect(1); + var Model = Backbone.Model.extend({ + sync: function(method, model, options) { + _.extend(options, {specialSync: true}); + return Backbone.Model.prototype.sync.call(this, method, model, options); + } + }); + + var Collection = Backbone.Collection.extend({ + model: Model, + url: '/test' + }); + + var collection = new Collection; + + var success = function(model, response, options) { + assert.ok(options.specialSync, 'Options were passed correctly to callback'); + }; + + collection.create({}, {success: success}); + this.ajaxSettings.success(); + }); + + QUnit.test('create with wait:true should not call collection.parse', function(assert) { + assert.expect(0); + var Collection = Backbone.Collection.extend({ + url: '/test', + parse: function() { + assert.ok(false); + } + }); + + var collection = new Collection; + + collection.create({}, {wait: true}); + this.ajaxSettings.success(); + }); + + QUnit.test('a failing create returns model with errors', function(assert) { + var ValidatingModel = Backbone.Model.extend({ + validate: function(attrs) { + return 'fail'; + } + }); + var ValidatingCollection = Backbone.Collection.extend({ + model: ValidatingModel + }); + var collection = new ValidatingCollection(); + var m = collection.create({foo: 'bar'}); + assert.equal(m.validationError, 'fail'); + assert.equal(collection.length, 1); + }); + + QUnit.test('initialize', function(assert) { + assert.expect(1); + var Collection = Backbone.Collection.extend({ + initialize: function() { + this.one = 1; + } + }); + var coll = new Collection; + assert.equal(coll.one, 1); + }); + + QUnit.test('toJSON', function(assert) { + assert.expect(1); + assert.equal(JSON.stringify(col), '[{"id":3,"label":"a"},{"id":2,"label":"b"},{"id":1,"label":"c"},{"id":0,"label":"d"}]'); + }); + + QUnit.test('where and findWhere', function(assert) { + assert.expect(8); + var model = new Backbone.Model({a: 1}); + var coll = new Backbone.Collection([ + model, + {a: 1}, + {a: 1, b: 2}, + {a: 2, b: 2}, + {a: 3} + ]); + assert.equal(coll.where({a: 1}).length, 3); + assert.equal(coll.where({a: 2}).length, 1); + assert.equal(coll.where({a: 3}).length, 1); + assert.equal(coll.where({b: 1}).length, 0); + assert.equal(coll.where({b: 2}).length, 2); + assert.equal(coll.where({a: 1, b: 2}).length, 1); + assert.equal(coll.findWhere({a: 1}), model); + assert.equal(coll.findWhere({a: 4}), void 0); + }); + + QUnit.test('Underscore methods', function(assert) { + assert.expect(21); + assert.equal(col.map(function(model){ return model.get('label'); }).join(' '), 'a b c d'); + assert.equal(col.some(function(model){ return model.id === 100; }), false); + assert.equal(col.some(function(model){ return model.id === 0; }), true); + assert.equal(col.reduce(function(m1, m2) {return m1.id > m2.id ? m1 : m2;}).id, 3); + assert.equal(col.reduceRight(function(m1, m2) {return m1.id > m2.id ? m1 : m2;}).id, 3); + assert.equal(col.indexOf(b), 1); + assert.equal(col.size(), 4); + assert.equal(col.rest().length, 3); + assert.ok(!_.includes(col.rest(), a)); + assert.ok(_.includes(col.rest(), d)); + assert.ok(!col.isEmpty()); + assert.ok(!_.includes(col.without(d), d)); + + var wrapped = col.chain(); + assert.equal(wrapped.map('id').max().value(), 3); + assert.equal(wrapped.map('id').min().value(), 0); + assert.deepEqual(wrapped + .filter(function(o){ return o.id % 2 === 0; }) + .map(function(o){ return o.id * 2; }) + .value(), + [4, 0]); + assert.deepEqual(col.difference([c, d]), [a, b]); + assert.ok(col.includes(col.sample())); + + var first = col.first(); + assert.deepEqual(col.groupBy(function(model){ return model.id; })[first.id], [first]); + assert.deepEqual(col.countBy(function(model){ return model.id; }), {0: 1, 1: 1, 2: 1, 3: 1}); + assert.deepEqual(col.sortBy(function(model){ return model.id; })[0], col.at(3)); + assert.ok(col.indexBy('id')[first.id] === first); + }); + + QUnit.test('Underscore methods with object-style and property-style iteratee', function(assert) { + assert.expect(26); + var model = new Backbone.Model({a: 4, b: 1, e: 3}); + var coll = new Backbone.Collection([ + {a: 1, b: 1}, + {a: 2, b: 1, c: 1}, + {a: 3, b: 1}, + model + ]); + assert.equal(coll.find({a: 0}), undefined); + assert.deepEqual(coll.find({a: 4}), model); + assert.equal(coll.find('d'), undefined); + assert.deepEqual(coll.find('e'), model); + assert.equal(coll.filter({a: 0}), false); + assert.deepEqual(coll.filter({a: 4}), [model]); + assert.equal(coll.some({a: 0}), false); + assert.equal(coll.some({a: 1}), true); + assert.equal(coll.reject({a: 0}).length, 4); + assert.deepEqual(coll.reject({a: 4}), _.without(coll.models, model)); + assert.equal(coll.every({a: 0}), false); + assert.equal(coll.every({b: 1}), true); + assert.deepEqual(coll.partition({a: 0})[0], []); + assert.deepEqual(coll.partition({a: 0})[1], coll.models); + assert.deepEqual(coll.partition({a: 4})[0], [model]); + assert.deepEqual(coll.partition({a: 4})[1], _.without(coll.models, model)); + assert.deepEqual(coll.map({a: 2}), [false, true, false, false]); + assert.deepEqual(coll.map('a'), [1, 2, 3, 4]); + assert.deepEqual(coll.sortBy('a')[3], model); + assert.deepEqual(coll.sortBy('e')[0], model); + assert.deepEqual(coll.countBy({a: 4}), {'false': 3, 'true': 1}); + assert.deepEqual(coll.countBy('d'), {'undefined': 4}); + assert.equal(coll.findIndex({b: 1}), 0); + assert.equal(coll.findIndex({b: 9}), -1); + assert.equal(coll.findLastIndex({b: 1}), 3); + assert.equal(coll.findLastIndex({b: 9}), -1); + }); + + QUnit.test('reset', function(assert) { + assert.expect(16); + + var resetCount = 0; + var models = col.models; + col.on('reset', function() { resetCount += 1; }); + col.reset([]); + assert.equal(resetCount, 1); + assert.equal(col.length, 0); + assert.equal(col.last(), null); + col.reset(models); + assert.equal(resetCount, 2); + assert.equal(col.length, 4); + assert.equal(col.last(), d); + col.reset(_.map(models, function(m){ return m.attributes; })); + assert.equal(resetCount, 3); + assert.equal(col.length, 4); + assert.ok(col.last() !== d); + assert.ok(_.isEqual(col.last().attributes, d.attributes)); + col.reset(); + assert.equal(col.length, 0); + assert.equal(resetCount, 4); + + var f = new Backbone.Model({id: 20, label: 'f'}); + col.reset([undefined, f]); + assert.equal(col.length, 2); + assert.equal(resetCount, 5); + + col.reset(new Array(4)); + assert.equal(col.length, 4); + assert.equal(resetCount, 6); + }); + + QUnit.test('reset with different values', function(assert) { + var collection = new Backbone.Collection({id: 1}); + collection.reset({id: 1, a: 1}); + assert.equal(collection.get(1).get('a'), 1); + }); + + QUnit.test('same references in reset', function(assert) { + var model = new Backbone.Model({id: 1}); + var collection = new Backbone.Collection({id: 1}); + collection.reset(model); + assert.equal(collection.get(1), model); + }); + + QUnit.test('reset passes caller options', function(assert) { + assert.expect(3); + var Model = Backbone.Model.extend({ + initialize: function(attrs, options) { + this.modelParameter = options.modelParameter; + } + }); + var collection = new (Backbone.Collection.extend({model: Model}))(); + collection.reset([{astring: 'green', anumber: 1}, {astring: 'blue', anumber: 2}], {modelParameter: 'model parameter'}); + assert.equal(collection.length, 2); + collection.each(function(model) { + assert.equal(model.modelParameter, 'model parameter'); + }); + }); + + QUnit.test('reset does not alter options by reference', function(assert) { + assert.expect(2); + var collection = new Backbone.Collection([{id: 1}]); + var origOpts = {}; + collection.on('reset', function(coll, opts){ + assert.equal(origOpts.previousModels, undefined); + assert.equal(opts.previousModels[0].id, 1); + }); + collection.reset([], origOpts); + }); + + QUnit.test('trigger custom events on models', function(assert) { + assert.expect(1); + var fired = null; + a.on('custom', function() { fired = true; }); + a.trigger('custom'); + assert.equal(fired, true); + }); + + QUnit.test('add does not alter arguments', function(assert) { + assert.expect(2); + var attrs = {}; + var models = [attrs]; + new Backbone.Collection().add(models); + assert.equal(models.length, 1); + assert.ok(attrs === models[0]); + }); + + QUnit.test('#714: access `model.collection` in a brand new model.', function(assert) { + assert.expect(2); + var collection = new Backbone.Collection; + collection.url = '/test'; + var Model = Backbone.Model.extend({ + set: function(attrs) { + assert.equal(attrs.prop, 'value'); + assert.equal(this.collection, collection); + return this; + } + }); + collection.model = Model; + collection.create({prop: 'value'}); + }); + + QUnit.test('#574, remove its own reference to the .models array.', function(assert) { + assert.expect(2); + var collection = new Backbone.Collection([ + {id: 1}, {id: 2}, {id: 3}, {id: 4}, {id: 5}, {id: 6} + ]); + assert.equal(collection.length, 6); + collection.remove(collection.models); + assert.equal(collection.length, 0); + }); + + QUnit.test('#861, adding models to a collection which do not pass validation, with validate:true', function(assert) { + assert.expect(2); + 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 collection = new Collection; + collection.on('invalid', function() { assert.ok(true); }); + + collection.add([{id: 1}, {id: 2}, {id: 3}, {id: 4}, {id: 5}, {id: 6}], {validate: true}); + assert.deepEqual(collection.pluck('id'), [1, 2, 4, 5, 6]); + }); + + QUnit.test('Invalid models are discarded with validate:true.', function(assert) { + assert.expect(5); + var collection = new Backbone.Collection; + collection.on('test', function() { assert.ok(true); }); + collection.model = Backbone.Model.extend({ + validate: function(attrs){ if (!attrs.valid) return 'invalid'; } + }); + var model = new collection.model({id: 1, valid: true}); + collection.add([model, {id: 2}], {validate: true}); + model.trigger('test'); + assert.ok(collection.get(model.cid)); + assert.ok(collection.get(1)); + assert.ok(!collection.get(2)); + assert.equal(collection.length, 1); + }); + + QUnit.test('multiple copies of the same model', function(assert) { + assert.expect(3); + var collection = new Backbone.Collection(); + var model = new Backbone.Model(); + collection.add([model, model]); + assert.equal(collection.length, 1); + collection.add([{id: 1}, {id: 1}]); + assert.equal(collection.length, 2); + assert.equal(collection.last().id, 1); + }); + + QUnit.test('#964 - collection.get return inconsistent', function(assert) { + assert.expect(2); + var collection = new Backbone.Collection(); + assert.ok(collection.get(null) === undefined); + assert.ok(collection.get() === undefined); + }); + + QUnit.test('#1112 - passing options.model sets collection.model', function(assert) { + assert.expect(2); + var Model = Backbone.Model.extend({}); + var collection = new Backbone.Collection([{id: 1}], {model: Model}); + assert.ok(collection.model === Model); + assert.ok(collection.at(0) instanceof Model); + }); + + QUnit.test('null and undefined are invalid ids.', function(assert) { + assert.expect(2); + var model = new Backbone.Model({id: 1}); + var collection = new Backbone.Collection([model]); + model.set({id: null}); + assert.ok(!collection.get('null')); + model.set({id: 1}); + model.set({id: undefined}); + assert.ok(!collection.get('undefined')); + }); + + QUnit.test('falsy comparator', function(assert) { + assert.expect(4); + var Col = Backbone.Collection.extend({ + comparator: function(model){ return model.id; } + }); + var collection = new Col(); + var colFalse = new Col(null, {comparator: false}); + var colNull = new Col(null, {comparator: null}); + var colUndefined = new Col(null, {comparator: undefined}); + assert.ok(collection.comparator); + assert.ok(!colFalse.comparator); + assert.ok(!colNull.comparator); + assert.ok(colUndefined.comparator); + }); + + QUnit.test('#1355 - `options` is passed to success callbacks', function(assert) { + assert.expect(2); + var m = new Backbone.Model({x: 1}); + var collection = new Backbone.Collection(); + var opts = { + opts: true, + success: function(coll, resp, options) { + assert.ok(options.opts); + } + }; + collection.sync = m.sync = function( method, coll, options ){ + options.success({}); + }; + collection.fetch(opts); + collection.create(m, opts); + }); + + QUnit.test("#1412 - Trigger 'request' and 'sync' events.", function(assert) { + assert.expect(4); + var collection = new Backbone.Collection; + collection.url = '/test'; + Backbone.ajax = function(settings){ settings.success(); }; + + collection.on('request', function(obj, xhr, options) { + assert.ok(obj === collection, "collection has correct 'request' event after fetching"); + }); + collection.on('sync', function(obj, response, options) { + assert.ok(obj === collection, "collection has correct 'sync' event after fetching"); + }); + collection.fetch(); + collection.off(); + + collection.on('request', function(obj, xhr, options) { + assert.ok(obj === collection.get(1), "collection has correct 'request' event after one of its models save"); + }); + collection.on('sync', function(obj, response, options) { + assert.ok(obj === collection.get(1), "collection has correct 'sync' event after one of its models save"); + }); + collection.create({id: 1}); + collection.off(); + }); + + QUnit.test('#3283 - fetch, create calls success with context', function(assert) { + assert.expect(2); + var collection = new Backbone.Collection; + collection.url = '/test'; + Backbone.ajax = function(settings) { + settings.success.call(settings.context); + }; + var obj = {}; + var options = { + context: obj, + success: function() { + assert.equal(this, obj); + } + }; + + collection.fetch(options); + collection.create({id: 1}, options); + }); + + QUnit.test('#1447 - create with wait adds model.', function(assert) { + assert.expect(1); + var collection = new Backbone.Collection; + var model = new Backbone.Model; + model.sync = function(method, m, options){ options.success(); }; + collection.on('add', function(){ assert.ok(true); }); + collection.create(model, {wait: true}); + }); + + QUnit.test('#1448 - add sorts collection after merge.', function(assert) { + assert.expect(1); + 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}); + assert.deepEqual(collection.pluck('id'), [2, 1]); + }); + + QUnit.test('#1655 - groupBy can be used with a string argument.', function(assert) { + assert.expect(3); + var collection = new Backbone.Collection([{x: 1}, {x: 2}]); + var grouped = collection.groupBy('x'); + assert.strictEqual(_.keys(grouped).length, 2); + assert.strictEqual(grouped[1][0].get('x'), 1); + assert.strictEqual(grouped[2][0].get('x'), 2); + }); + + QUnit.test('#1655 - sortBy can be used with a string argument.', function(assert) { + assert.expect(1); + var collection = new Backbone.Collection([{x: 3}, {x: 1}, {x: 2}]); + var values = _.map(collection.sortBy('x'), function(model) { + return model.get('x'); + }); + assert.deepEqual(values, [1, 2, 3]); + }); + + QUnit.test('#1604 - Removal during iteration.', function(assert) { + assert.expect(0); + var collection = new Backbone.Collection([{}, {}]); + collection.on('add', function() { + collection.at(0).destroy(); + }); + collection.add({}, {at: 0}); + }); + + QUnit.test('#1638 - `sort` during `add` triggers correctly.', function(assert) { + var collection = new Backbone.Collection; + collection.comparator = function(model) { return model.get('x'); }; + var added = []; + collection.on('add', function(model) { + model.set({x: 3}); + collection.sort(); + added.push(model.id); + }); + collection.add([{id: 1, x: 1}, {id: 2, x: 2}]); + assert.deepEqual(added, [1, 2]); + }); + + QUnit.test('fetch parses models by default', function(assert) { + assert.expect(1); + var model = {}; + var Collection = Backbone.Collection.extend({ + url: 'test', + model: Backbone.Model.extend({ + parse: function(resp) { + assert.strictEqual(resp, model); + } + }) + }); + new Collection().fetch(); + this.ajaxSettings.success([model]); + }); + + QUnit.test("`sort` shouldn't always fire on `add`", function(assert) { + assert.expect(1); + var collection = new Backbone.Collection([{id: 1}, {id: 2}, {id: 3}], { + comparator: 'id' + }); + collection.sort = function(){ assert.ok(true); }; + collection.add([]); + collection.add({id: 1}); + collection.add([{id: 2}, {id: 3}]); + collection.add({id: 4}); + }); + + QUnit.test('#1407 parse option on constructor parses collection and models', function(assert) { + assert.expect(2); + var model = { + namespace: [{id: 1}, {id: 2}] + }; + var Collection = Backbone.Collection.extend({ + model: Backbone.Model.extend({ + parse: function(m) { + m.name = 'test'; + return m; + } + }), + parse: function(m) { + return m.namespace; + } + }); + var collection = new Collection(model, {parse: true}); + + assert.equal(collection.length, 2); + assert.equal(collection.at(0).get('name'), 'test'); + }); + + QUnit.test('#1407 parse option on reset parses collection and models', function(assert) { + assert.expect(2); + var model = { + namespace: [{id: 1}, {id: 2}] + }; + var Collection = Backbone.Collection.extend({ + model: Backbone.Model.extend({ + parse: function(m) { + m.name = 'test'; + return m; + } + }), + parse: function(m) { + return m.namespace; + } + }); + var collection = new Collection(); + collection.reset(model, {parse: true}); + + assert.equal(collection.length, 2); + assert.equal(collection.at(0).get('name'), 'test'); + }); + + + QUnit.test('Reset includes previous models in triggered event.', function(assert) { + assert.expect(1); + var model = new Backbone.Model(); + var collection = new Backbone.Collection([model]); + collection.on('reset', function(coll, options) { + assert.deepEqual(options.previousModels, [model]); + }); + collection.reset([]); + }); + + QUnit.test('set', function(assert) { + var m1 = new Backbone.Model(); + var m2 = new Backbone.Model({id: 2}); + var m3 = new Backbone.Model(); + var collection = new Backbone.Collection([m1, m2]); + + // Test add/change/remove events + collection.on('add', function(model) { + assert.strictEqual(model, m3); + }); + collection.on('change', function(model) { + assert.strictEqual(model, m2); + }); + collection.on('remove', function(model) { + assert.strictEqual(model, m1); + }); + + // remove: false doesn't remove any models + collection.set([], {remove: false}); + assert.strictEqual(collection.length, 2); + + // add: false doesn't add any models + collection.set([m1, m2, m3], {add: false}); + assert.strictEqual(collection.length, 2); + + // merge: false doesn't change any models + collection.set([m1, {id: 2, a: 1}], {merge: false}); + assert.strictEqual(m2.get('a'), void 0); + + // add: false, remove: false only merges existing models + collection.set([m1, {id: 2, a: 0}, m3, {id: 4}], {add: false, remove: false}); + assert.strictEqual(collection.length, 2); + assert.strictEqual(m2.get('a'), 0); + + // default options add/remove/merge as appropriate + collection.set([{id: 2, a: 1}, m3]); + assert.strictEqual(collection.length, 2); + assert.strictEqual(m2.get('a'), 1); + + // Test removing models not passing an argument + collection.off('remove').on('remove', function(model) { + assert.ok(model === m2 || model === m3); + }); + collection.set([]); + assert.strictEqual(collection.length, 0); + + // Test null models on set doesn't clear collection + collection.off(); + collection.set([{id: 1}]); + collection.set(); + assert.strictEqual(collection.length, 1); + }); + + QUnit.test('set with only cids', function(assert) { + assert.expect(3); + var m1 = new Backbone.Model; + var m2 = new Backbone.Model; + var collection = new Backbone.Collection; + collection.set([m1, m2]); + assert.equal(collection.length, 2); + collection.set([m1]); + assert.equal(collection.length, 1); + collection.set([m1, m1, m1, m2, m2], {remove: false}); + assert.equal(collection.length, 2); + }); + + QUnit.test('set with only idAttribute', function(assert) { + assert.expect(3); + var m1 = {_id: 1}; + var m2 = {_id: 2}; + var Col = Backbone.Collection.extend({ + model: Backbone.Model.extend({ + idAttribute: '_id' + }) + }); + var collection = new Col; + collection.set([m1, m2]); + assert.equal(collection.length, 2); + collection.set([m1]); + assert.equal(collection.length, 1); + collection.set([m1, m1, m1, m2, m2], {remove: false}); + assert.equal(collection.length, 2); + }); + + QUnit.test('set + merge with default values defined', function(assert) { + var Model = Backbone.Model.extend({ + defaults: { + key: 'value' + } + }); + var m = new Model({id: 1}); + var collection = new Backbone.Collection([m], {model: Model}); + assert.equal(collection.first().get('key'), 'value'); + + collection.set({id: 1, key: 'other'}); + assert.equal(collection.first().get('key'), 'other'); + + collection.set({id: 1, other: 'value'}); + assert.equal(collection.first().get('key'), 'other'); + assert.equal(collection.length, 1); + }); + + QUnit.test('merge without mutation', function(assert) { + var Model = Backbone.Model.extend({ + initialize: function(attrs, options) { + if (attrs.child) { + this.set('child', new Model(attrs.child, options), options); + } + } + }); + var Collection = Backbone.Collection.extend({model: Model}); + var data = [{id: 1, child: {id: 2}}]; + var collection = new Collection(data); + assert.equal(collection.first().id, 1); + collection.set(data); + assert.equal(collection.first().id, 1); + collection.set([{id: 2, child: {id: 2}}].concat(data)); + assert.deepEqual(collection.pluck('id'), [2, 1]); + }); + + QUnit.test('`set` and model level `parse`', function(assert) { + var Model = Backbone.Model.extend({}); + var Collection = Backbone.Collection.extend({ + model: Model, + parse: function(res) { return _.map(res.models, 'model'); } + }); + var model = new Model({id: 1}); + var collection = new Collection(model); + collection.set({models: [ + {model: {id: 1}}, + {model: {id: 2}} + ]}, {parse: true}); + assert.equal(collection.first(), model); + }); + + QUnit.test('`set` data is only parsed once', function(assert) { + var collection = new Backbone.Collection(); + collection.model = Backbone.Model.extend({ + parse: function(data) { + assert.equal(data.parsed, void 0); + data.parsed = true; + return data; + } + }); + collection.set({}, {parse: true}); + }); + + QUnit.test('`set` matches input order in the absence of a comparator', function(assert) { + var one = new Backbone.Model({id: 1}); + var two = new Backbone.Model({id: 2}); + var three = new Backbone.Model({id: 3}); + var collection = new Backbone.Collection([one, two, three]); + collection.set([{id: 3}, {id: 2}, {id: 1}]); + assert.deepEqual(collection.models, [three, two, one]); + collection.set([{id: 1}, {id: 2}]); + assert.deepEqual(collection.models, [one, two]); + collection.set([two, three, one]); + assert.deepEqual(collection.models, [two, three, one]); + collection.set([{id: 1}, {id: 2}], {remove: false}); + assert.deepEqual(collection.models, [two, three, one]); + collection.set([{id: 1}, {id: 2}, {id: 3}], {merge: false}); + assert.deepEqual(collection.models, [one, two, three]); + collection.set([three, two, one, {id: 4}], {add: false}); + assert.deepEqual(collection.models, [one, two, three]); + }); + + QUnit.test('#1894 - Push should not trigger a sort', function(assert) { + assert.expect(0); + var Collection = Backbone.Collection.extend({ + comparator: 'id', + sort: function() { assert.ok(false); } + }); + new Collection().push({id: 1}); + }); + + QUnit.test('#2428 - push duplicate models, return the correct one', function(assert) { + assert.expect(1); + var collection = new Backbone.Collection; + var model1 = collection.push({id: 101}); + var model2 = collection.push({id: 101}); + assert.ok(model2.cid === model1.cid); + }); + + QUnit.test('`set` with non-normal id', function(assert) { + var Collection = Backbone.Collection.extend({ + model: Backbone.Model.extend({idAttribute: '_id'}) + }); + var collection = new Collection({_id: 1}); + collection.set([{_id: 1, a: 1}], {add: false}); + assert.equal(collection.first().get('a'), 1); + }); + + QUnit.test('#1894 - `sort` can optionally be turned off', function(assert) { + assert.expect(0); + var Collection = Backbone.Collection.extend({ + comparator: 'id', + sort: function() { assert.ok(false); } + }); + new Collection().add({id: 1}, {sort: false}); + }); + + QUnit.test('#1915 - `parse` data in the right order in `set`', function(assert) { + var collection = new (Backbone.Collection.extend({ + parse: function(data) { + assert.strictEqual(data.status, 'ok'); + return data.data; + } + })); + var res = {status: 'ok', data: [{id: 1}]}; + collection.set(res, {parse: true}); + }); + + QUnit.test('#1939 - `parse` is passed `options`', function(assert) { + var done = assert.async(); + assert.expect(1); + var collection = new (Backbone.Collection.extend({ + url: '/', + parse: function(data, options) { + assert.strictEqual(options.xhr.someHeader, 'headerValue'); + return data; + } + })); + var ajax = Backbone.ajax; + Backbone.ajax = function(params) { + _.defer(params.success, []); + return {someHeader: 'headerValue'}; + }; + collection.fetch({ + success: function() { done(); } + }); + Backbone.ajax = ajax; + }); + + QUnit.test('fetch will pass extra options to success callback', function(assert) { + assert.expect(1); + var SpecialSyncCollection = Backbone.Collection.extend({ + url: '/test', + sync: function(method, collection, options) { + _.extend(options, {specialSync: true}); + return Backbone.Collection.prototype.sync.call(this, method, collection, options); + } + }); + + var collection = new SpecialSyncCollection(); + + var onSuccess = function(coll, resp, options) { + assert.ok(options.specialSync, 'Options were passed correctly to callback'); + }; + + collection.fetch({success: onSuccess}); + this.ajaxSettings.success(); + }); + + QUnit.test('`add` only `sort`s when necessary', function(assert) { + assert.expect(2); + var collection = new (Backbone.Collection.extend({ + comparator: 'a' + }))([{id: 1}, {id: 2}, {id: 3}]); + collection.on('sort', function() { assert.ok(true); }); + collection.add({id: 4}); // do sort, new model + collection.add({id: 1, a: 1}, {merge: true}); // do sort, comparator change + collection.add({id: 1, b: 1}, {merge: true}); // don't sort, no comparator change + collection.add({id: 1, a: 1}, {merge: true}); // don't sort, no comparator change + collection.add(collection.models); // don't sort, nothing new + collection.add(collection.models, {merge: true}); // don't sort + }); + + QUnit.test('`add` only `sort`s when necessary with comparator function', function(assert) { + assert.expect(3); + var collection = new (Backbone.Collection.extend({ + comparator: function(m1, m2) { + return m1.get('a') > m2.get('a') ? 1 : (m1.get('a') < m2.get('a') ? -1 : 0); + } + }))([{id: 1}, {id: 2}, {id: 3}]); + collection.on('sort', function() { assert.ok(true); }); + collection.add({id: 4}); // do sort, new model + collection.add({id: 1, a: 1}, {merge: true}); // do sort, model change + collection.add({id: 1, b: 1}, {merge: true}); // do sort, model change + collection.add({id: 1, a: 1}, {merge: true}); // don't sort, no model change + collection.add(collection.models); // don't sort, nothing new + collection.add(collection.models, {merge: true}); // don't sort + }); + + QUnit.test('Attach options to collection.', function(assert) { + assert.expect(2); + var Model = Backbone.Model; + var comparator = function(){}; + + var collection = new Backbone.Collection([], { + model: Model, + comparator: comparator + }); + + assert.ok(collection.model === Model); + assert.ok(collection.comparator === comparator); + }); + + QUnit.test('Pass falsey for `models` for empty Col with `options`', function(assert) { + assert.expect(9); + var opts = {a: 1, b: 2}; + _.forEach([undefined, null, false], function(falsey) { + var Collection = Backbone.Collection.extend({ + initialize: function(models, options) { + assert.strictEqual(models, falsey); + assert.strictEqual(options, opts); + } + }); + + var collection = new Collection(falsey, opts); + assert.strictEqual(collection.length, 0); + }); + }); + + QUnit.test('`add` overrides `set` flags', function(assert) { + var collection = new Backbone.Collection(); + collection.once('add', function(model, coll, options) { + coll.add({id: 2}, options); + }); + collection.set({id: 1}); + assert.equal(collection.length, 2); + }); + + QUnit.test('#2606 - Collection#create, success arguments', function(assert) { + assert.expect(1); + var collection = new Backbone.Collection; + collection.url = 'test'; + collection.create({}, { + success: function(model, resp, options) { + assert.strictEqual(resp, 'response'); + } + }); + this.ajaxSettings.success('response'); + }); + + QUnit.test('#2612 - nested `parse` works with `Collection#set`', function(assert) { + + var Job = Backbone.Model.extend({ + constructor: function() { + this.items = new Items(); + Backbone.Model.apply(this, arguments); + }, + parse: function(attrs) { + this.items.set(attrs.items, {parse: true}); + return _.omit(attrs, 'items'); + } + }); + + var Item = Backbone.Model.extend({ + constructor: function() { + this.subItems = new Backbone.Collection(); + Backbone.Model.apply(this, arguments); + }, + parse: function(attrs) { + this.subItems.set(attrs.subItems, {parse: true}); + return _.omit(attrs, 'subItems'); + } + }); + + var Items = Backbone.Collection.extend({ + model: Item + }); + + var data = { + name: 'JobName', + id: 1, + items: [{ + id: 1, + name: 'Sub1', + subItems: [ + {id: 1, subName: 'One'}, + {id: 2, subName: 'Two'} + ] + }, { + id: 2, + name: 'Sub2', + subItems: [ + {id: 3, subName: 'Three'}, + {id: 4, subName: 'Four'} + ] + }] + }; + + var newData = { + name: 'NewJobName', + id: 1, + items: [{ + id: 1, + name: 'NewSub1', + subItems: [ + {id: 1, subName: 'NewOne'}, + {id: 2, subName: 'NewTwo'} + ] + }, { + id: 2, + name: 'NewSub2', + subItems: [ + {id: 3, subName: 'NewThree'}, + {id: 4, subName: 'NewFour'} + ] + }] + }; + + var job = new Job(data, {parse: true}); + assert.equal(job.get('name'), 'JobName'); + assert.equal(job.items.at(0).get('name'), 'Sub1'); + assert.equal(job.items.length, 2); + assert.equal(job.items.get(1).subItems.get(1).get('subName'), 'One'); + assert.equal(job.items.get(2).subItems.get(3).get('subName'), 'Three'); + job.set(job.parse(newData, {parse: true})); + assert.equal(job.get('name'), 'NewJobName'); + assert.equal(job.items.at(0).get('name'), 'NewSub1'); + assert.equal(job.items.length, 2); + assert.equal(job.items.get(1).subItems.get(1).get('subName'), 'NewOne'); + assert.equal(job.items.get(2).subItems.get(3).get('subName'), 'NewThree'); + }); + + QUnit.test('_addReference binds all collection events & adds to the lookup hashes', function(assert) { + assert.expect(8); + + var calls = {add: 0, remove: 0}; + + var Collection = Backbone.Collection.extend({ + + _addReference: function(model) { + Backbone.Collection.prototype._addReference.apply(this, arguments); + calls.add++; + assert.equal(model, this._byId[model.id]); + assert.equal(model, this._byId[model.cid]); + assert.equal(model._events.all.length, 1); + }, + + _removeReference: function(model) { + Backbone.Collection.prototype._removeReference.apply(this, arguments); + calls.remove++; + assert.equal(this._byId[model.id], void 0); + assert.equal(this._byId[model.cid], void 0); + assert.equal(model.collection, void 0); + } + + }); + + var collection = new Collection(); + var model = collection.add({id: 1}); + collection.remove(model); + + assert.equal(calls.add, 1); + assert.equal(calls.remove, 1); + }); + + QUnit.test('Do not allow duplicate models to be `add`ed or `set`', function(assert) { + var collection = new Backbone.Collection(); + + collection.add([{id: 1}, {id: 1}]); + assert.equal(collection.length, 1); + assert.equal(collection.models.length, 1); + + collection.set([{id: 1}, {id: 1}]); + assert.equal(collection.length, 1); + assert.equal(collection.models.length, 1); + }); + + QUnit.test('#3020: #set with {add: false} should not throw.', function(assert) { + assert.expect(2); + var collection = new Backbone.Collection; + collection.set([{id: 1}], {add: false}); + assert.strictEqual(collection.length, 0); + assert.strictEqual(collection.models.length, 0); + }); + + QUnit.test('create with wait, model instance, #3028', function(assert) { + assert.expect(1); + var collection = new Backbone.Collection(); + var model = new Backbone.Model({id: 1}); + model.sync = function(){ + assert.equal(this.collection, collection); + }; + collection.create(model, {wait: true}); + }); + + QUnit.test('modelId', function(assert) { + var Stooge = Backbone.Model.extend(); + var StoogeCollection = Backbone.Collection.extend({model: Stooge}); + + // Default to using `Collection::model::idAttribute`. + assert.equal(StoogeCollection.prototype.modelId({id: 1}), 1); + Stooge.prototype.idAttribute = '_id'; + assert.equal(StoogeCollection.prototype.modelId({_id: 1}), 1); + }); + + QUnit.test('Polymorphic models work with "simple" constructors', function(assert) { + var A = Backbone.Model.extend(); + var B = Backbone.Model.extend(); + var C = Backbone.Collection.extend({ + model: function(attrs) { + return attrs.type === 'a' ? new A(attrs) : new B(attrs); + } + }); + var collection = new C([{id: 1, type: 'a'}, {id: 2, type: 'b'}]); + assert.equal(collection.length, 2); + assert.ok(collection.at(0) instanceof A); + assert.equal(collection.at(0).id, 1); + assert.ok(collection.at(1) instanceof B); + assert.equal(collection.at(1).id, 2); + }); + + QUnit.test('Polymorphic models work with "advanced" constructors', function(assert) { + var A = Backbone.Model.extend({idAttribute: '_id'}); + var B = Backbone.Model.extend({idAttribute: '_id'}); + var C = Backbone.Collection.extend({ + model: Backbone.Model.extend({ + constructor: function(attrs) { + return attrs.type === 'a' ? new A(attrs) : new B(attrs); + }, + + idAttribute: '_id' + }) + }); + var collection = new C([{_id: 1, type: 'a'}, {_id: 2, type: 'b'}]); + assert.equal(collection.length, 2); + assert.ok(collection.at(0) instanceof A); + assert.equal(collection.at(0), collection.get(1)); + assert.ok(collection.at(1) instanceof B); + assert.equal(collection.at(1), collection.get(2)); + + C = Backbone.Collection.extend({ + model: function(attrs) { + return attrs.type === 'a' ? new A(attrs) : new B(attrs); + }, + + modelId: function(attrs) { + return attrs.type + '-' + attrs.id; + } + }); + collection = new C([{id: 1, type: 'a'}, {id: 1, type: 'b'}]); + assert.equal(collection.length, 2); + assert.ok(collection.at(0) instanceof A); + assert.equal(collection.at(0), collection.get('a-1')); + assert.ok(collection.at(1) instanceof B); + assert.equal(collection.at(1), collection.get('b-1')); + }); + + QUnit.test('Collection with polymorphic models receives default id from modelId', function(assert) { + assert.expect(6); + // When the polymorphic models use 'id' for the idAttribute, all is fine. + var C1 = Backbone.Collection.extend({ + model: function(attrs) { + return new Backbone.Model(attrs); + } + }); + var c1 = new C1({id: 1}); + assert.equal(c1.get(1).id, 1); + assert.equal(c1.modelId({id: 1}), 1); + + // If the polymorphic models define their own idAttribute, + // the modelId method should be overridden, for the reason below. + var M = Backbone.Model.extend({ + idAttribute: '_id' + }); + var C2 = Backbone.Collection.extend({ + model: function(attrs) { + return new M(attrs); + } + }); + var c2 = new C2({'_id': 1}); + assert.equal(c2.get(1), void 0); + assert.equal(c2.modelId(c2.at(0).attributes), void 0); + var m = new M({'_id': 2}); + c2.add(m); + assert.equal(c2.get(2), void 0); + assert.equal(c2.modelId(m.attributes), void 0); + }); + + QUnit.test('#3039 #3951: adding at index fires with correct at', function(assert) { + assert.expect(4); + var collection = new Backbone.Collection([{val: 0}, {val: 4}]); + collection.on('add', function(model, coll, options) { + assert.equal(model.get('val'), options.index); + }); + collection.add([{val: 1}, {val: 2}, {val: 3}], {at: 1}); + collection.add({val: 5}, {at: 10}); + }); + + QUnit.test('#3039: index is not sent when at is not specified', function(assert) { + assert.expect(2); + var collection = new Backbone.Collection([{at: 0}]); + collection.on('add', function(model, coll, options) { + assert.equal(undefined, options.index); + }); + collection.add([{at: 1}, {at: 2}]); + }); + + QUnit.test('#3199 - Order changing should trigger a sort', function(assert) { + assert.expect(1); + var one = new Backbone.Model({id: 1}); + var two = new Backbone.Model({id: 2}); + var three = new Backbone.Model({id: 3}); + var collection = new Backbone.Collection([one, two, three]); + collection.on('sort', function() { + assert.ok(true); + }); + collection.set([{id: 3}, {id: 2}, {id: 1}]); + }); + + QUnit.test('#3199 - Adding a model should trigger a sort', function(assert) { + assert.expect(1); + var one = new Backbone.Model({id: 1}); + var two = new Backbone.Model({id: 2}); + var three = new Backbone.Model({id: 3}); + var collection = new Backbone.Collection([one, two, three]); + collection.on('sort', function() { + assert.ok(true); + }); + collection.set([{id: 1}, {id: 2}, {id: 3}, {id: 0}]); + }); + + QUnit.test('#3199 - Order not changing should not trigger a sort', function(assert) { + assert.expect(0); + var one = new Backbone.Model({id: 1}); + var two = new Backbone.Model({id: 2}); + var three = new Backbone.Model({id: 3}); + var collection = new Backbone.Collection([one, two, three]); + collection.on('sort', function() { + assert.ok(false); + }); + collection.set([{id: 1}, {id: 2}, {id: 3}]); + }); + + QUnit.test('add supports negative indexes', function(assert) { + assert.expect(1); + var collection = new Backbone.Collection([{id: 1}]); + collection.add([{id: 2}, {id: 3}], {at: -1}); + collection.add([{id: 2.5}], {at: -2}); + collection.add([{id: 0.5}], {at: -6}); + assert.equal(collection.pluck('id').join(','), '0.5,1,2,2.5,3'); + }); + + QUnit.test('#set accepts options.at as a string', function(assert) { + assert.expect(1); + var collection = new Backbone.Collection([{id: 1}, {id: 2}]); + collection.add([{id: 3}], {at: '1'}); + assert.deepEqual(collection.pluck('id'), [1, 3, 2]); + }); + + QUnit.test('adding multiple models triggers `update` event once', function(assert) { + assert.expect(1); + var collection = new Backbone.Collection; + collection.on('update', function() { assert.ok(true); }); + collection.add([{id: 1}, {id: 2}, {id: 3}]); + }); + + QUnit.test('removing models triggers `update` event once', function(assert) { + assert.expect(1); + var collection = new Backbone.Collection([{id: 1}, {id: 2}, {id: 3}]); + collection.on('update', function() { assert.ok(true); }); + collection.remove([{id: 1}, {id: 2}]); + }); + + QUnit.test('remove does not trigger `update` when nothing removed', function(assert) { + assert.expect(0); + var collection = new Backbone.Collection([{id: 1}, {id: 2}]); + collection.on('update', function() { assert.ok(false); }); + collection.remove([{id: 3}]); + }); + + QUnit.test('set triggers `set` event once', function(assert) { + assert.expect(1); + var collection = new Backbone.Collection([{id: 1}, {id: 2}]); + collection.on('update', function() { assert.ok(true); }); + collection.set([{id: 1}, {id: 3}]); + }); + + QUnit.test('set does not trigger `update` event when nothing added nor removed', function(assert) { + var collection = new Backbone.Collection([{id: 1}, {id: 2}]); + collection.on('update', function(coll, options) { + assert.equal(options.changes.added.length, 0); + assert.equal(options.changes.removed.length, 0); + assert.equal(options.changes.merged.length, 2); + }); + collection.set([{id: 1}, {id: 2}]); + }); + + QUnit.test('#3610 - invoke collects arguments', function(assert) { + assert.expect(3); + var Model = Backbone.Model.extend({ + method: function(x, y, z) { + assert.equal(x, 1); + assert.equal(y, 2); + assert.equal(z, 3); + } + }); + var Collection = Backbone.Collection.extend({ + model: Model + }); + var collection = new Collection([{id: 1}]); + collection.invoke('method', 1, 2, 3); + }); + + QUnit.test('#3662 - triggering change without model will not error', function(assert) { + assert.expect(1); + var collection = new Backbone.Collection([{id: 1}]); + var model = collection.first(); + collection.on('change', function(m) { + assert.equal(m, undefined); + }); + model.trigger('change'); + }); + + QUnit.test('#3871 - falsy parse result creates empty collection', function(assert) { + var collection = new (Backbone.Collection.extend({ + parse: function(data, options) {} + })); + collection.set('', {parse: true}); + assert.equal(collection.length, 0); + }); + + QUnit.test("#3711 - remove's `update` event returns one removed model", function(assert) { + var model = new Backbone.Model({id: 1, title: 'First Post'}); + var collection = new Backbone.Collection([model]); + collection.on('update', function(context, options) { + var changed = options.changes; + assert.deepEqual(changed.added, []); + assert.deepEqual(changed.merged, []); + assert.strictEqual(changed.removed[0], model); + }); + collection.remove(model); + }); + + QUnit.test("#3711 - remove's `update` event returns multiple removed models", function(assert) { + var model = new Backbone.Model({id: 1, title: 'First Post'}); + var model2 = new Backbone.Model({id: 2, title: 'Second Post'}); + var collection = new Backbone.Collection([model, model2]); + collection.on('update', function(context, options) { + var changed = options.changes; + assert.deepEqual(changed.added, []); + assert.deepEqual(changed.merged, []); + assert.ok(changed.removed.length === 2); + + assert.ok(_.indexOf(changed.removed, model) > -1 && _.indexOf(changed.removed, model2) > -1); + }); + collection.remove([model, model2]); + }); + + QUnit.test("#3711 - set's `update` event returns one added model", function(assert) { + var model = new Backbone.Model({id: 1, title: 'First Post'}); + var collection = new Backbone.Collection(); + collection.on('update', function(context, options) { + var addedModels = options.changes.added; + assert.ok(addedModels.length === 1); + assert.strictEqual(addedModels[0], model); + }); + collection.set(model); + }); + + QUnit.test("#3711 - set's `update` event returns multiple added models", function(assert) { + var model = new Backbone.Model({id: 1, title: 'First Post'}); + var model2 = new Backbone.Model({id: 2, title: 'Second Post'}); + var collection = new Backbone.Collection(); + collection.on('update', function(context, options) { + var addedModels = options.changes.added; + assert.ok(addedModels.length === 2); + assert.strictEqual(addedModels[0], model); + assert.strictEqual(addedModels[1], model2); + }); + collection.set([model, model2]); + }); + + QUnit.test("#3711 - set's `update` event returns one removed model", function(assert) { + var model = new Backbone.Model({id: 1, title: 'First Post'}); + var model2 = new Backbone.Model({id: 2, title: 'Second Post'}); + var model3 = new Backbone.Model({id: 3, title: 'My Last Post'}); + var collection = new Backbone.Collection([model]); + collection.on('update', function(context, options) { + var changed = options.changes; + assert.equal(changed.added.length, 2); + assert.equal(changed.merged.length, 0); + assert.ok(changed.removed.length === 1); + assert.strictEqual(changed.removed[0], model); + }); + collection.set([model2, model3]); + }); + + QUnit.test("#3711 - set's `update` event returns multiple removed models", function(assert) { + var model = new Backbone.Model({id: 1, title: 'First Post'}); + var model2 = new Backbone.Model({id: 2, title: 'Second Post'}); + var model3 = new Backbone.Model({id: 3, title: 'My Last Post'}); + var collection = new Backbone.Collection([model, model2]); + collection.on('update', function(context, options) { + var removedModels = options.changes.removed; + assert.ok(removedModels.length === 2); + assert.strictEqual(removedModels[0], model); + assert.strictEqual(removedModels[1], model2); + }); + collection.set([model3]); + }); + + QUnit.test("#3711 - set's `update` event returns one merged model", function(assert) { + var model = new Backbone.Model({id: 1, title: 'First Post'}); + var model2 = new Backbone.Model({id: 2, title: 'Second Post'}); + var model2Update = new Backbone.Model({id: 2, title: 'Second Post V2'}); + var collection = new Backbone.Collection([model, model2]); + collection.on('update', function(context, options) { + var mergedModels = options.changes.merged; + assert.ok(mergedModels.length === 1); + assert.strictEqual(mergedModels[0].get('title'), model2Update.get('title')); + }); + collection.set([model2Update]); + }); + + QUnit.test("#3711 - set's `update` event returns multiple merged models", function(assert) { + var model = new Backbone.Model({id: 1, title: 'First Post'}); + var modelUpdate = new Backbone.Model({id: 1, title: 'First Post V2'}); + var model2 = new Backbone.Model({id: 2, title: 'Second Post'}); + var model2Update = new Backbone.Model({id: 2, title: 'Second Post V2'}); + var collection = new Backbone.Collection([model, model2]); + collection.on('update', function(context, options) { + var mergedModels = options.changes.merged; + assert.ok(mergedModels.length === 2); + assert.strictEqual(mergedModels[0].get('title'), model2Update.get('title')); + assert.strictEqual(mergedModels[1].get('title'), modelUpdate.get('title')); + }); + collection.set([model2Update, modelUpdate]); + }); + + QUnit.test("#3711 - set's `update` event should not be triggered adding a model which already exists exactly alike", function(assert) { + var fired = false; + var model = new Backbone.Model({id: 1, title: 'First Post'}); + var collection = new Backbone.Collection([model]); + collection.on('update', function(context, options) { + fired = true; + }); + collection.set([model]); + assert.equal(fired, false); + }); + +})(); diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/backbone/test/events.js b/ecomp-portal-FE/client/bower_components/lodash/vendor/backbone/test/events.js new file mode 100644 index 00000000..544b39a1 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/backbone/test/events.js @@ -0,0 +1,706 @@ +(function() { + + QUnit.module('Backbone.Events'); + + QUnit.test('on and trigger', function(assert) { + assert.expect(2); + var obj = {counter: 0}; + _.extend(obj, Backbone.Events); + obj.on('event', function() { obj.counter += 1; }); + obj.trigger('event'); + assert.equal(obj.counter, 1, 'counter should be incremented.'); + obj.trigger('event'); + obj.trigger('event'); + obj.trigger('event'); + obj.trigger('event'); + assert.equal(obj.counter, 5, 'counter should be incremented five times.'); + }); + + QUnit.test('binding and triggering multiple events', function(assert) { + assert.expect(4); + var obj = {counter: 0}; + _.extend(obj, Backbone.Events); + + obj.on('a b c', function() { obj.counter += 1; }); + + obj.trigger('a'); + assert.equal(obj.counter, 1); + + obj.trigger('a b'); + assert.equal(obj.counter, 3); + + obj.trigger('c'); + assert.equal(obj.counter, 4); + + obj.off('a c'); + obj.trigger('a b c'); + assert.equal(obj.counter, 5); + }); + + QUnit.test('binding and triggering with event maps', function(assert) { + var obj = {counter: 0}; + _.extend(obj, Backbone.Events); + + var increment = function() { + this.counter += 1; + }; + + obj.on({ + a: increment, + b: increment, + c: increment + }, obj); + + obj.trigger('a'); + assert.equal(obj.counter, 1); + + obj.trigger('a b'); + assert.equal(obj.counter, 3); + + obj.trigger('c'); + assert.equal(obj.counter, 4); + + obj.off({ + a: increment, + c: increment + }, obj); + obj.trigger('a b c'); + assert.equal(obj.counter, 5); + }); + + QUnit.test('binding and triggering multiple event names with event maps', function(assert) { + var obj = {counter: 0}; + _.extend(obj, Backbone.Events); + + var increment = function() { + this.counter += 1; + }; + + obj.on({ + 'a b c': increment + }); + + obj.trigger('a'); + assert.equal(obj.counter, 1); + + obj.trigger('a b'); + assert.equal(obj.counter, 3); + + obj.trigger('c'); + assert.equal(obj.counter, 4); + + obj.off({ + 'a c': increment + }); + obj.trigger('a b c'); + assert.equal(obj.counter, 5); + }); + + QUnit.test('binding and trigger with event maps context', function(assert) { + assert.expect(2); + var obj = {counter: 0}; + var context = {}; + _.extend(obj, Backbone.Events); + + obj.on({ + a: function() { + assert.strictEqual(this, context, 'defaults `context` to `callback` param'); + } + }, context).trigger('a'); + + obj.off().on({ + a: function() { + assert.strictEqual(this, context, 'will not override explicit `context` param'); + } + }, this, context).trigger('a'); + }); + + QUnit.test('listenTo and stopListening', function(assert) { + assert.expect(1); + var a = _.extend({}, Backbone.Events); + var b = _.extend({}, Backbone.Events); + a.listenTo(b, 'all', function(){ assert.ok(true); }); + b.trigger('anything'); + a.listenTo(b, 'all', function(){ assert.ok(false); }); + a.stopListening(); + b.trigger('anything'); + }); + + QUnit.test('listenTo and stopListening with event maps', function(assert) { + assert.expect(4); + var a = _.extend({}, Backbone.Events); + var b = _.extend({}, Backbone.Events); + var cb = function(){ assert.ok(true); }; + a.listenTo(b, {event: cb}); + b.trigger('event'); + a.listenTo(b, {event2: cb}); + b.on('event2', cb); + a.stopListening(b, {event2: cb}); + b.trigger('event event2'); + a.stopListening(); + b.trigger('event event2'); + }); + + QUnit.test('stopListening with omitted args', function(assert) { + assert.expect(2); + var a = _.extend({}, Backbone.Events); + var b = _.extend({}, Backbone.Events); + var cb = function() { assert.ok(true); }; + a.listenTo(b, 'event', cb); + b.on('event', cb); + a.listenTo(b, 'event2', cb); + a.stopListening(null, {event: cb}); + b.trigger('event event2'); + b.off(); + a.listenTo(b, 'event event2', cb); + a.stopListening(null, 'event'); + a.stopListening(); + b.trigger('event2'); + }); + + QUnit.test('listenToOnce', function(assert) { + assert.expect(2); + // Same as the previous test, but we use once rather than having to explicitly unbind + var obj = {counterA: 0, counterB: 0}; + _.extend(obj, Backbone.Events); + var incrA = function(){ obj.counterA += 1; obj.trigger('event'); }; + var incrB = function(){ obj.counterB += 1; }; + obj.listenToOnce(obj, 'event', incrA); + obj.listenToOnce(obj, 'event', incrB); + obj.trigger('event'); + assert.equal(obj.counterA, 1, 'counterA should have only been incremented once.'); + assert.equal(obj.counterB, 1, 'counterB should have only been incremented once.'); + }); + + QUnit.test('listenToOnce and stopListening', function(assert) { + assert.expect(1); + var a = _.extend({}, Backbone.Events); + var b = _.extend({}, Backbone.Events); + a.listenToOnce(b, 'all', function() { assert.ok(true); }); + b.trigger('anything'); + b.trigger('anything'); + a.listenToOnce(b, 'all', function() { assert.ok(false); }); + a.stopListening(); + b.trigger('anything'); + }); + + QUnit.test('listenTo, listenToOnce and stopListening', function(assert) { + assert.expect(1); + var a = _.extend({}, Backbone.Events); + var b = _.extend({}, Backbone.Events); + a.listenToOnce(b, 'all', function() { assert.ok(true); }); + b.trigger('anything'); + b.trigger('anything'); + a.listenTo(b, 'all', function() { assert.ok(false); }); + a.stopListening(); + b.trigger('anything'); + }); + + QUnit.test('listenTo and stopListening with event maps', function(assert) { + assert.expect(1); + var a = _.extend({}, Backbone.Events); + var b = _.extend({}, Backbone.Events); + a.listenTo(b, {change: function(){ assert.ok(true); }}); + b.trigger('change'); + a.listenTo(b, {change: function(){ assert.ok(false); }}); + a.stopListening(); + b.trigger('change'); + }); + + QUnit.test('listenTo yourself', function(assert) { + assert.expect(1); + var e = _.extend({}, Backbone.Events); + e.listenTo(e, 'foo', function(){ assert.ok(true); }); + e.trigger('foo'); + }); + + QUnit.test('listenTo yourself cleans yourself up with stopListening', function(assert) { + assert.expect(1); + var e = _.extend({}, Backbone.Events); + e.listenTo(e, 'foo', function(){ assert.ok(true); }); + e.trigger('foo'); + e.stopListening(); + e.trigger('foo'); + }); + + QUnit.test('stopListening cleans up references', function(assert) { + assert.expect(12); + var a = _.extend({}, Backbone.Events); + var b = _.extend({}, Backbone.Events); + var fn = function() {}; + b.on('event', fn); + a.listenTo(b, 'event', fn).stopListening(); + assert.equal(_.size(a._listeningTo), 0); + assert.equal(_.size(b._events.event), 1); + assert.equal(_.size(b._listeners), 0); + a.listenTo(b, 'event', fn).stopListening(b); + assert.equal(_.size(a._listeningTo), 0); + assert.equal(_.size(b._events.event), 1); + assert.equal(_.size(b._listeners), 0); + a.listenTo(b, 'event', fn).stopListening(b, 'event'); + assert.equal(_.size(a._listeningTo), 0); + assert.equal(_.size(b._events.event), 1); + assert.equal(_.size(b._listeners), 0); + a.listenTo(b, 'event', fn).stopListening(b, 'event', fn); + assert.equal(_.size(a._listeningTo), 0); + assert.equal(_.size(b._events.event), 1); + assert.equal(_.size(b._listeners), 0); + }); + + QUnit.test('stopListening cleans up references from listenToOnce', function(assert) { + assert.expect(12); + var a = _.extend({}, Backbone.Events); + var b = _.extend({}, Backbone.Events); + var fn = function() {}; + b.on('event', fn); + a.listenToOnce(b, 'event', fn).stopListening(); + assert.equal(_.size(a._listeningTo), 0); + assert.equal(_.size(b._events.event), 1); + assert.equal(_.size(b._listeners), 0); + a.listenToOnce(b, 'event', fn).stopListening(b); + assert.equal(_.size(a._listeningTo), 0); + assert.equal(_.size(b._events.event), 1); + assert.equal(_.size(b._listeners), 0); + a.listenToOnce(b, 'event', fn).stopListening(b, 'event'); + assert.equal(_.size(a._listeningTo), 0); + assert.equal(_.size(b._events.event), 1); + assert.equal(_.size(b._listeners), 0); + a.listenToOnce(b, 'event', fn).stopListening(b, 'event', fn); + assert.equal(_.size(a._listeningTo), 0); + assert.equal(_.size(b._events.event), 1); + assert.equal(_.size(b._listeners), 0); + }); + + QUnit.test('listenTo and off cleaning up references', function(assert) { + assert.expect(8); + var a = _.extend({}, Backbone.Events); + var b = _.extend({}, Backbone.Events); + var fn = function() {}; + a.listenTo(b, 'event', fn); + b.off(); + assert.equal(_.size(a._listeningTo), 0); + assert.equal(_.size(b._listeners), 0); + a.listenTo(b, 'event', fn); + b.off('event'); + assert.equal(_.size(a._listeningTo), 0); + assert.equal(_.size(b._listeners), 0); + a.listenTo(b, 'event', fn); + b.off(null, fn); + assert.equal(_.size(a._listeningTo), 0); + assert.equal(_.size(b._listeners), 0); + a.listenTo(b, 'event', fn); + b.off(null, null, a); + assert.equal(_.size(a._listeningTo), 0); + assert.equal(_.size(b._listeners), 0); + }); + + QUnit.test('listenTo and stopListening cleaning up references', function(assert) { + assert.expect(2); + var a = _.extend({}, Backbone.Events); + var b = _.extend({}, Backbone.Events); + a.listenTo(b, 'all', function(){ assert.ok(true); }); + b.trigger('anything'); + a.listenTo(b, 'other', function(){ assert.ok(false); }); + a.stopListening(b, 'other'); + a.stopListening(b, 'all'); + assert.equal(_.size(a._listeningTo), 0); + }); + + QUnit.test('listenToOnce without context cleans up references after the event has fired', function(assert) { + assert.expect(2); + var a = _.extend({}, Backbone.Events); + var b = _.extend({}, Backbone.Events); + a.listenToOnce(b, 'all', function(){ assert.ok(true); }); + b.trigger('anything'); + assert.equal(_.size(a._listeningTo), 0); + }); + + QUnit.test('listenToOnce with event maps cleans up references', function(assert) { + assert.expect(2); + var a = _.extend({}, Backbone.Events); + var b = _.extend({}, Backbone.Events); + a.listenToOnce(b, { + one: function() { assert.ok(true); }, + two: function() { assert.ok(false); } + }); + b.trigger('one'); + assert.equal(_.size(a._listeningTo), 1); + }); + + QUnit.test('listenToOnce with event maps binds the correct `this`', function(assert) { + assert.expect(1); + var a = _.extend({}, Backbone.Events); + var b = _.extend({}, Backbone.Events); + a.listenToOnce(b, { + one: function() { assert.ok(this === a); }, + two: function() { assert.ok(false); } + }); + b.trigger('one'); + }); + + QUnit.test("listenTo with empty callback doesn't throw an error", function(assert) { + assert.expect(1); + var e = _.extend({}, Backbone.Events); + e.listenTo(e, 'foo', null); + e.trigger('foo'); + assert.ok(true); + }); + + QUnit.test('trigger all for each event', function(assert) { + assert.expect(3); + 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'); + assert.ok(a); + assert.ok(b); + assert.equal(obj.counter, 2); + }); + + QUnit.test('on, then unbind all functions', function(assert) { + assert.expect(1); + 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'); + assert.equal(obj.counter, 1, 'counter should have only been incremented once.'); + }); + + QUnit.test('bind two callbacks, unbind only one', function(assert) { + assert.expect(2); + 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'); + assert.equal(obj.counterA, 1, 'counterA should have only been incremented once.'); + assert.equal(obj.counterB, 2, 'counterB should have been incremented twice.'); + }); + + QUnit.test('unbind a callback in the midst of it firing', function(assert) { + assert.expect(1); + 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'); + assert.equal(obj.counter, 1, 'the callback should have been unbound.'); + }); + + QUnit.test('two binds that unbind themeselves', function(assert) { + assert.expect(2); + 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'); + assert.equal(obj.counterA, 1, 'counterA should have only been incremented once.'); + assert.equal(obj.counterB, 1, 'counterB should have only been incremented once.'); + }); + + QUnit.test('bind a callback with a default context when none supplied', function(assert) { + assert.expect(1); + var obj = _.extend({ + assertTrue: function() { + assert.equal(this, obj, '`this` was bound to the callback'); + } + }, Backbone.Events); + + obj.once('event', obj.assertTrue); + obj.trigger('event'); + }); + + QUnit.test('bind a callback with a supplied context', function(assert) { + assert.expect(1); + var TestClass = function() { + return this; + }; + TestClass.prototype.assertTrue = function() { + assert.ok(true, '`this` was bound to the callback'); + }; + + var obj = _.extend({}, Backbone.Events); + obj.on('event', function() { this.assertTrue(); }, new TestClass); + obj.trigger('event'); + }); + + QUnit.test('nested trigger with unbind', function(assert) { + assert.expect(1); + 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'); + assert.equal(obj.counter, 3, 'counter should have been incremented three times'); + }); + + QUnit.test('callback list is not altered during trigger', function(assert) { + assert.expect(2); + var counter = 0, obj = _.extend({}, Backbone.Events); + var incr = function(){ counter++; }; + var incrOn = function(){ obj.on('event all', incr); }; + var incrOff = function(){ obj.off('event all', incr); }; + + obj.on('event all', incrOn).trigger('event'); + assert.equal(counter, 0, 'on does not alter callback list'); + + obj.off().on('event', incrOff).on('event all', incr).trigger('event'); + assert.equal(counter, 2, 'off does not alter callback list'); + }); + + QUnit.test("#1282 - 'all' callback list is retrieved after each event.", function(assert) { + assert.expect(1); + 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'); + assert.strictEqual(counter, 2); + }); + + QUnit.test('if no callback is provided, `on` is a noop', function(assert) { + assert.expect(0); + _.extend({}, Backbone.Events).on('test').trigger('test'); + }); + + QUnit.test('if callback is truthy but not a function, `on` should throw an error just like jQuery', function(assert) { + assert.expect(1); + var view = _.extend({}, Backbone.Events).on('test', 'noop'); + assert.raises(function() { + view.trigger('test'); + }); + }); + + QUnit.test('remove all events for a specific context', function(assert) { + assert.expect(4); + var obj = _.extend({}, Backbone.Events); + obj.on('x y all', function() { assert.ok(true); }); + obj.on('x y all', function() { assert.ok(false); }, obj); + obj.off(null, null, obj); + obj.trigger('x y'); + }); + + QUnit.test('remove all events for a specific callback', function(assert) { + assert.expect(4); + var obj = _.extend({}, Backbone.Events); + var success = function() { assert.ok(true); }; + var fail = function() { assert.ok(false); }; + obj.on('x y all', success); + obj.on('x y all', fail); + obj.off(null, fail); + obj.trigger('x y'); + }); + + QUnit.test('#1310 - off does not skip consecutive events', function(assert) { + assert.expect(0); + var obj = _.extend({}, Backbone.Events); + obj.on('event', function() { assert.ok(false); }, obj); + obj.on('event', function() { assert.ok(false); }, obj); + obj.off(null, null, obj); + obj.trigger('event'); + }); + + QUnit.test('once', function(assert) { + assert.expect(2); + // Same as the previous test, but we use once rather than having to explicitly unbind + var obj = {counterA: 0, counterB: 0}; + _.extend(obj, Backbone.Events); + var incrA = function(){ obj.counterA += 1; obj.trigger('event'); }; + var incrB = function(){ obj.counterB += 1; }; + obj.once('event', incrA); + obj.once('event', incrB); + obj.trigger('event'); + assert.equal(obj.counterA, 1, 'counterA should have only been incremented once.'); + assert.equal(obj.counterB, 1, 'counterB should have only been incremented once.'); + }); + + QUnit.test('once variant one', function(assert) { + assert.expect(3); + var f = function(){ assert.ok(true); }; + + var a = _.extend({}, Backbone.Events).once('event', f); + var b = _.extend({}, Backbone.Events).on('event', f); + + a.trigger('event'); + + b.trigger('event'); + b.trigger('event'); + }); + + QUnit.test('once variant two', function(assert) { + assert.expect(3); + var f = function(){ assert.ok(true); }; + var obj = _.extend({}, Backbone.Events); + + obj + .once('event', f) + .on('event', f) + .trigger('event') + .trigger('event'); + }); + + QUnit.test('once with off', function(assert) { + assert.expect(0); + var f = function(){ assert.ok(true); }; + var obj = _.extend({}, Backbone.Events); + + obj.once('event', f); + obj.off('event', f); + obj.trigger('event'); + }); + + QUnit.test('once with event maps', function(assert) { + var obj = {counter: 0}; + _.extend(obj, Backbone.Events); + + var increment = function() { + this.counter += 1; + }; + + obj.once({ + a: increment, + b: increment, + c: increment + }, obj); + + obj.trigger('a'); + assert.equal(obj.counter, 1); + + obj.trigger('a b'); + assert.equal(obj.counter, 2); + + obj.trigger('c'); + assert.equal(obj.counter, 3); + + obj.trigger('a b c'); + assert.equal(obj.counter, 3); + }); + + QUnit.test('bind a callback with a supplied context using once with object notation', function(assert) { + assert.expect(1); + var obj = {counter: 0}; + var context = {}; + _.extend(obj, Backbone.Events); + + obj.once({ + a: function() { + assert.strictEqual(this, context, 'defaults `context` to `callback` param'); + } + }, context).trigger('a'); + }); + + QUnit.test('once with off only by context', function(assert) { + assert.expect(0); + var context = {}; + var obj = _.extend({}, Backbone.Events); + obj.once('event', function(){ assert.ok(false); }, context); + obj.off(null, null, context); + obj.trigger('event'); + }); + + QUnit.test('Backbone object inherits Events', function(assert) { + assert.ok(Backbone.on === Backbone.Events.on); + }); + + QUnit.test('once with asynchronous events', function(assert) { + var done = assert.async(); + assert.expect(1); + var func = _.debounce(function() { assert.ok(true); done(); }, 50); + var obj = _.extend({}, Backbone.Events).once('async', func); + + obj.trigger('async'); + obj.trigger('async'); + }); + + QUnit.test('once with multiple events.', function(assert) { + assert.expect(2); + var obj = _.extend({}, Backbone.Events); + obj.once('x y', function() { assert.ok(true); }); + obj.trigger('x y'); + }); + + QUnit.test('Off during iteration with once.', function(assert) { + assert.expect(2); + var obj = _.extend({}, Backbone.Events); + var f = function(){ this.off('event', f); }; + obj.on('event', f); + obj.once('event', function(){}); + obj.on('event', function(){ assert.ok(true); }); + + obj.trigger('event'); + obj.trigger('event'); + }); + + QUnit.test('`once` on `all` should work as expected', function(assert) { + assert.expect(1); + Backbone.once('all', function() { + assert.ok(true); + Backbone.trigger('all'); + }); + Backbone.trigger('all'); + }); + + QUnit.test('once without a callback is a noop', function(assert) { + assert.expect(0); + _.extend({}, Backbone.Events).once('event').trigger('event'); + }); + + QUnit.test('listenToOnce without a callback is a noop', function(assert) { + assert.expect(0); + var obj = _.extend({}, Backbone.Events); + obj.listenToOnce(obj, 'event').trigger('event'); + }); + + QUnit.test('event functions are chainable', function(assert) { + var obj = _.extend({}, Backbone.Events); + var obj2 = _.extend({}, Backbone.Events); + var fn = function() {}; + assert.equal(obj, obj.trigger('noeventssetyet')); + assert.equal(obj, obj.off('noeventssetyet')); + assert.equal(obj, obj.stopListening('noeventssetyet')); + assert.equal(obj, obj.on('a', fn)); + assert.equal(obj, obj.once('c', fn)); + assert.equal(obj, obj.trigger('a')); + assert.equal(obj, obj.listenTo(obj2, 'a', fn)); + assert.equal(obj, obj.listenToOnce(obj2, 'b', fn)); + assert.equal(obj, obj.off('a c')); + assert.equal(obj, obj.stopListening(obj2, 'a')); + assert.equal(obj, obj.stopListening()); + }); + + QUnit.test('#3448 - listenToOnce with space-separated events', function(assert) { + assert.expect(2); + var one = _.extend({}, Backbone.Events); + var two = _.extend({}, Backbone.Events); + var count = 1; + one.listenToOnce(two, 'x y', function(n) { assert.ok(n === count++); }); + two.trigger('x', 1); + two.trigger('x', 1); + two.trigger('y', 2); + two.trigger('y', 2); + }); + +})(); diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/backbone/test/model.js b/ecomp-portal-FE/client/bower_components/lodash/vendor/backbone/test/model.js new file mode 100644 index 00000000..b73a1c79 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/backbone/test/model.js @@ -0,0 +1,1418 @@ +(function() { + + var ProxyModel = Backbone.Model.extend(); + var Klass = Backbone.Collection.extend({ + url: function() { return '/collection'; } + }); + var doc, collection; + + QUnit.module('Backbone.Model', { + + beforeEach: function(assert) { + doc = new ProxyModel({ + id: '1-the-tempest', + title: 'The Tempest', + author: 'Bill Shakespeare', + length: 123 + }); + collection = new Klass(); + collection.add(doc); + } + + }); + + QUnit.test('initialize', function(assert) { + assert.expect(3); + var Model = Backbone.Model.extend({ + initialize: function() { + this.one = 1; + assert.equal(this.collection, collection); + } + }); + var model = new Model({}, {collection: collection}); + assert.equal(model.one, 1); + assert.equal(model.collection, collection); + }); + + QUnit.test('Object.prototype properties are overridden by attributes', function(assert) { + assert.expect(1); + var model = new Backbone.Model({hasOwnProperty: true}); + assert.equal(model.get('hasOwnProperty'), true); + }); + + QUnit.test('initialize with attributes and options', function(assert) { + assert.expect(1); + var Model = Backbone.Model.extend({ + initialize: function(attributes, options) { + this.one = options.one; + } + }); + var model = new Model({}, {one: 1}); + assert.equal(model.one, 1); + }); + + QUnit.test('initialize with parsed attributes', function(assert) { + assert.expect(1); + var Model = Backbone.Model.extend({ + parse: function(attrs) { + attrs.value += 1; + return attrs; + } + }); + var model = new Model({value: 1}, {parse: true}); + assert.equal(model.get('value'), 2); + }); + + QUnit.test('parse can return null', function(assert) { + assert.expect(1); + var Model = Backbone.Model.extend({ + parse: function(attrs) { + attrs.value += 1; + return null; + } + }); + var model = new Model({value: 1}, {parse: true}); + assert.equal(JSON.stringify(model.toJSON()), '{}'); + }); + + QUnit.test('url', function(assert) { + assert.expect(3); + doc.urlRoot = null; + assert.equal(doc.url(), '/collection/1-the-tempest'); + doc.collection.url = '/collection/'; + assert.equal(doc.url(), '/collection/1-the-tempest'); + doc.collection = null; + assert.raises(function() { doc.url(); }); + doc.collection = collection; + }); + + QUnit.test('url when using urlRoot, and uri encoding', function(assert) { + assert.expect(2); + var Model = Backbone.Model.extend({ + urlRoot: '/collection' + }); + var model = new Model(); + assert.equal(model.url(), '/collection'); + model.set({id: '+1+'}); + assert.equal(model.url(), '/collection/%2B1%2B'); + }); + + QUnit.test('url when using urlRoot as a function to determine urlRoot at runtime', function(assert) { + assert.expect(2); + var Model = Backbone.Model.extend({ + urlRoot: function() { + return '/nested/' + this.get('parentId') + '/collection'; + } + }); + + var model = new Model({parentId: 1}); + assert.equal(model.url(), '/nested/1/collection'); + model.set({id: 2}); + assert.equal(model.url(), '/nested/1/collection/2'); + }); + + QUnit.test('underscore methods', function(assert) { + assert.expect(5); + var model = new Backbone.Model({foo: 'a', bar: 'b', baz: 'c'}); + var model2 = model.clone(); + assert.deepEqual(model.keys(), ['foo', 'bar', 'baz']); + assert.deepEqual(model.values(), ['a', 'b', 'c']); + assert.deepEqual(model.invert(), {a: 'foo', b: 'bar', c: 'baz'}); + assert.deepEqual(model.pick('foo', 'baz'), {foo: 'a', baz: 'c'}); + assert.deepEqual(model.omit('foo', 'bar'), {baz: 'c'}); + }); + + QUnit.test('chain', function(assert) { + var model = new Backbone.Model({a: 0, b: 1, c: 2}); + assert.deepEqual(model.chain().pick('a', 'b', 'c').values().compact().value(), [1, 2]); + }); + + QUnit.test('clone', function(assert) { + assert.expect(10); + var a = new Backbone.Model({foo: 1, bar: 2, baz: 3}); + var b = a.clone(); + assert.equal(a.get('foo'), 1); + assert.equal(a.get('bar'), 2); + assert.equal(a.get('baz'), 3); + assert.equal(b.get('foo'), a.get('foo'), 'Foo should be the same on the clone.'); + assert.equal(b.get('bar'), a.get('bar'), 'Bar should be the same on the clone.'); + assert.equal(b.get('baz'), a.get('baz'), 'Baz should be the same on the clone.'); + a.set({foo: 100}); + assert.equal(a.get('foo'), 100); + assert.equal(b.get('foo'), 1, 'Changing a parent attribute does not change the clone.'); + + var foo = new Backbone.Model({p: 1}); + var bar = new Backbone.Model({p: 2}); + bar.set(foo.clone().attributes, {unset: true}); + assert.equal(foo.get('p'), 1); + assert.equal(bar.get('p'), undefined); + }); + + QUnit.test('isNew', function(assert) { + assert.expect(6); + var a = new Backbone.Model({foo: 1, bar: 2, baz: 3}); + assert.ok(a.isNew(), 'it should be new'); + a = new Backbone.Model({foo: 1, bar: 2, baz: 3, id: -5}); + assert.ok(!a.isNew(), 'any defined ID is legal, negative or positive'); + a = new Backbone.Model({foo: 1, bar: 2, baz: 3, id: 0}); + assert.ok(!a.isNew(), 'any defined ID is legal, including zero'); + assert.ok(new Backbone.Model().isNew(), 'is true when there is no id'); + assert.ok(!new Backbone.Model({id: 2}).isNew(), 'is false for a positive integer'); + assert.ok(!new Backbone.Model({id: -5}).isNew(), 'is false for a negative integer'); + }); + + QUnit.test('get', function(assert) { + assert.expect(2); + assert.equal(doc.get('title'), 'The Tempest'); + assert.equal(doc.get('author'), 'Bill Shakespeare'); + }); + + QUnit.test('escape', function(assert) { + assert.expect(5); + assert.equal(doc.escape('title'), 'The Tempest'); + doc.set({audience: 'Bill & Bob'}); + assert.equal(doc.escape('audience'), 'Bill & Bob'); + doc.set({audience: 'Tim > Joan'}); + assert.equal(doc.escape('audience'), 'Tim > Joan'); + doc.set({audience: 10101}); + assert.equal(doc.escape('audience'), '10101'); + doc.unset('audience'); + assert.equal(doc.escape('audience'), ''); + }); + + QUnit.test('has', function(assert) { + assert.expect(10); + var model = new Backbone.Model(); + + assert.strictEqual(model.has('name'), false); + + model.set({ + '0': 0, + '1': 1, + 'true': true, + 'false': false, + 'empty': '', + 'name': 'name', + 'null': null, + 'undefined': undefined + }); + + assert.strictEqual(model.has('0'), true); + assert.strictEqual(model.has('1'), true); + assert.strictEqual(model.has('true'), true); + assert.strictEqual(model.has('false'), true); + assert.strictEqual(model.has('empty'), true); + assert.strictEqual(model.has('name'), true); + + model.unset('name'); + + assert.strictEqual(model.has('name'), false); + assert.strictEqual(model.has('null'), false); + assert.strictEqual(model.has('undefined'), false); + }); + + QUnit.test('matches', function(assert) { + assert.expect(4); + var model = new Backbone.Model(); + + assert.strictEqual(model.matches({name: 'Jonas', cool: true}), false); + + model.set({name: 'Jonas', cool: true}); + + assert.strictEqual(model.matches({name: 'Jonas'}), true); + assert.strictEqual(model.matches({name: 'Jonas', cool: true}), true); + assert.strictEqual(model.matches({name: 'Jonas', cool: false}), false); + }); + + QUnit.test('matches with predicate', function(assert) { + var model = new Backbone.Model({a: 0}); + + assert.strictEqual(model.matches(function(attr) { + return attr.a > 1 && attr.b != null; + }), false); + + model.set({a: 3, b: true}); + + assert.strictEqual(model.matches(function(attr) { + return attr.a > 1 && attr.b != null; + }), true); + }); + + QUnit.test('set and unset', function(assert) { + assert.expect(8); + 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}); + assert.equal(a.get('foo'), 2, 'Foo should have changed.'); + assert.equal(changeCount, 1, 'Change count should have incremented.'); + // set with value that is not new shouldn't fire change event + a.set({foo: 2}); + assert.equal(a.get('foo'), 2, 'Foo should NOT have changed, still 2'); + assert.equal(changeCount, 1, 'Change count should NOT have incremented.'); + + a.validate = function(attrs) { + assert.equal(attrs.foo, void 0, 'validate:true passed while unsetting'); + }; + a.unset('foo', {validate: true}); + assert.equal(a.get('foo'), void 0, 'Foo should have changed'); + delete a.validate; + assert.equal(changeCount, 2, 'Change count should have incremented for unset.'); + + a.unset('id'); + assert.equal(a.id, undefined, 'Unsetting the id should remove the id property.'); + }); + + QUnit.test('#2030 - set with failed validate, followed by another set triggers change', function(assert) { + var attr = 0, main = 0, error = 0; + var Model = Backbone.Model.extend({ + validate: function(attrs) { + if (attrs.x > 1) { + error++; + return 'this is an error'; + } + } + }); + var model = new Model({x: 0}); + model.on('change:x', function() { attr++; }); + model.on('change', function() { main++; }); + model.set({x: 2}, {validate: true}); + model.set({x: 1}, {validate: true}); + assert.deepEqual([attr, main, error], [1, 1, 1]); + }); + + QUnit.test('set triggers changes in the correct order', function(assert) { + var value = null; + var model = new Backbone.Model; + model.on('last', function(){ value = 'last'; }); + model.on('first', function(){ value = 'first'; }); + model.trigger('first'); + model.trigger('last'); + assert.equal(value, 'last'); + }); + + QUnit.test('set falsy values in the correct order', function(assert) { + assert.expect(2); + var model = new Backbone.Model({result: 'result'}); + model.on('change', function() { + assert.equal(model.changed.result, void 0); + assert.equal(model.previous('result'), false); + }); + model.set({result: void 0}, {silent: true}); + model.set({result: null}, {silent: true}); + model.set({result: false}, {silent: true}); + model.set({result: void 0}); + }); + + QUnit.test('nested set triggers with the correct options', function(assert) { + var model = new Backbone.Model(); + var o1 = {}; + var o2 = {}; + var o3 = {}; + model.on('change', function(__, options) { + switch (model.get('a')) { + case 1: + assert.equal(options, o1); + return model.set('a', 2, o2); + case 2: + assert.equal(options, o2); + return model.set('a', 3, o3); + case 3: + assert.equal(options, o3); + } + }); + model.set('a', 1, o1); + }); + + QUnit.test('multiple unsets', function(assert) { + assert.expect(1); + 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'); + assert.equal(i, 2, 'Unset does not fire an event for missing attributes.'); + }); + + QUnit.test('unset and changedAttributes', function(assert) { + assert.expect(1); + var model = new Backbone.Model({a: 1}); + model.on('change', function() { + assert.ok('a' in model.changedAttributes(), 'changedAttributes should contain unset properties'); + }); + model.unset('a'); + }); + + QUnit.test('using a non-default id attribute.', function(assert) { + assert.expect(5); + var MongoModel = Backbone.Model.extend({idAttribute: '_id'}); + var model = new MongoModel({id: 'eye-dee', _id: 25, title: 'Model'}); + assert.equal(model.get('id'), 'eye-dee'); + assert.equal(model.id, 25); + assert.equal(model.isNew(), false); + model.unset('_id'); + assert.equal(model.id, undefined); + assert.equal(model.isNew(), true); + }); + + QUnit.test('setting an alternative cid prefix', function(assert) { + assert.expect(4); + var Model = Backbone.Model.extend({ + cidPrefix: 'm' + }); + var model = new Model(); + + assert.equal(model.cid.charAt(0), 'm'); + + model = new Backbone.Model(); + assert.equal(model.cid.charAt(0), 'c'); + + var Collection = Backbone.Collection.extend({ + model: Model + }); + var col = new Collection([{id: 'c5'}, {id: 'c6'}, {id: 'c7'}]); + + assert.equal(col.get('c6').cid.charAt(0), 'm'); + col.set([{id: 'c6', value: 'test'}], { + merge: true, + add: true, + remove: false + }); + assert.ok(col.get('c6').has('value')); + }); + + QUnit.test('set an empty string', function(assert) { + assert.expect(1); + var model = new Backbone.Model({name: 'Model'}); + model.set({name: ''}); + assert.equal(model.get('name'), ''); + }); + + QUnit.test('setting an object', function(assert) { + assert.expect(1); + var model = new Backbone.Model({ + custom: {foo: 1} + }); + model.on('change', function() { + assert.ok(1); + }); + model.set({ + custom: {foo: 1} // no change should be fired + }); + model.set({ + custom: {foo: 2} // change event should be fired + }); + }); + + QUnit.test('clear', function(assert) { + assert.expect(3); + 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(); + assert.ok('name' in changedAttrs); + }); + model.clear(); + assert.equal(changed, true); + assert.equal(model.get('name'), undefined); + }); + + QUnit.test('defaults', function(assert) { + assert.expect(9); + var Defaulted = Backbone.Model.extend({ + defaults: { + one: 1, + two: 2 + } + }); + var model = new Defaulted({two: undefined}); + assert.equal(model.get('one'), 1); + assert.equal(model.get('two'), 2); + model = new Defaulted({two: 3}); + assert.equal(model.get('one'), 1); + assert.equal(model.get('two'), 3); + Defaulted = Backbone.Model.extend({ + defaults: function() { + return { + one: 3, + two: 4 + }; + } + }); + model = new Defaulted({two: undefined}); + assert.equal(model.get('one'), 3); + assert.equal(model.get('two'), 4); + Defaulted = Backbone.Model.extend({ + defaults: {hasOwnProperty: true} + }); + model = new Defaulted(); + assert.equal(model.get('hasOwnProperty'), true); + model = new Defaulted({hasOwnProperty: undefined}); + assert.equal(model.get('hasOwnProperty'), true); + model = new Defaulted({hasOwnProperty: false}); + assert.equal(model.get('hasOwnProperty'), false); + }); + + QUnit.test('change, hasChanged, changedAttributes, previous, previousAttributes', function(assert) { + assert.expect(9); + var model = new Backbone.Model({name: 'Tim', age: 10}); + assert.deepEqual(model.changedAttributes(), false); + model.on('change', function() { + assert.ok(model.hasChanged('name'), 'name changed'); + assert.ok(!model.hasChanged('age'), 'age did not'); + assert.ok(_.isEqual(model.changedAttributes(), {name: 'Rob'}), 'changedAttributes returns the changed attrs'); + assert.equal(model.previous('name'), 'Tim'); + assert.ok(_.isEqual(model.previousAttributes(), {name: 'Tim', age: 10}), 'previousAttributes is correct'); + }); + assert.equal(model.hasChanged(), false); + assert.equal(model.hasChanged(undefined), false); + model.set({name: 'Rob'}); + assert.equal(model.get('name'), 'Rob'); + }); + + QUnit.test('changedAttributes', function(assert) { + assert.expect(3); + var model = new Backbone.Model({a: 'a', b: 'b'}); + assert.deepEqual(model.changedAttributes(), false); + assert.equal(model.changedAttributes({a: 'a'}), false); + assert.equal(model.changedAttributes({a: 'b'}).a, 'b'); + }); + + QUnit.test('change with options', function(assert) { + assert.expect(2); + var value; + var model = new Backbone.Model({name: 'Rob'}); + model.on('change', function(m, options) { + value = options.prefix + m.get('name'); + }); + model.set({name: 'Bob'}, {prefix: 'Mr. '}); + assert.equal(value, 'Mr. Bob'); + model.set({name: 'Sue'}, {prefix: 'Ms. '}); + assert.equal(value, 'Ms. Sue'); + }); + + QUnit.test('change after initialize', function(assert) { + assert.expect(1); + var changed = 0; + var attrs = {id: 1, label: 'c'}; + var obj = new Backbone.Model(attrs); + obj.on('change', function() { changed += 1; }); + obj.set(attrs); + assert.equal(changed, 0); + }); + + QUnit.test('save within change event', function(assert) { + assert.expect(1); + var env = this; + var model = new Backbone.Model({firstName: 'Taylor', lastName: 'Swift'}); + model.url = '/test'; + model.on('change', function() { + model.save(); + assert.ok(_.isEqual(env.syncArgs.model, model)); + }); + model.set({lastName: 'Hicks'}); + }); + + QUnit.test('validate after save', function(assert) { + assert.expect(2); + var lastError, model = new Backbone.Model(); + model.validate = function(attrs) { + if (attrs.admin) return "Can't change admin status."; + }; + model.sync = function(method, m, options) { + options.success.call(this, {admin: true}); + }; + model.on('invalid', function(m, error) { + lastError = error; + }); + model.save(null); + + assert.equal(lastError, "Can't change admin status."); + assert.equal(model.validationError, "Can't change admin status."); + }); + + QUnit.test('save', function(assert) { + assert.expect(2); + doc.save({title: 'Henry V'}); + assert.equal(this.syncArgs.method, 'update'); + assert.ok(_.isEqual(this.syncArgs.model, doc)); + }); + + QUnit.test('save, fetch, destroy triggers error event when an error occurs', function(assert) { + assert.expect(3); + var model = new Backbone.Model(); + model.on('error', function() { + assert.ok(true); + }); + model.sync = function(method, m, options) { + options.error(); + }; + model.save({data: 2, id: 1}); + model.fetch(); + model.destroy(); + }); + + QUnit.test('#3283 - save, fetch, destroy calls success with context', function(assert) { + assert.expect(3); + var model = new Backbone.Model(); + var obj = {}; + var options = { + context: obj, + success: function() { + assert.equal(this, obj); + } + }; + model.sync = function(method, m, opts) { + opts.success.call(opts.context); + }; + model.save({data: 2, id: 1}, options); + model.fetch(options); + model.destroy(options); + }); + + QUnit.test('#3283 - save, fetch, destroy calls error with context', function(assert) { + assert.expect(3); + var model = new Backbone.Model(); + var obj = {}; + var options = { + context: obj, + error: function() { + assert.equal(this, obj); + } + }; + model.sync = function(method, m, opts) { + opts.error.call(opts.context); + }; + model.save({data: 2, id: 1}, options); + model.fetch(options); + model.destroy(options); + }); + + QUnit.test('#3470 - save and fetch with parse false', function(assert) { + assert.expect(2); + var i = 0; + var model = new Backbone.Model(); + model.parse = function() { + assert.ok(false); + }; + model.sync = function(method, m, options) { + options.success({i: ++i}); + }; + model.fetch({parse: false}); + assert.equal(model.get('i'), i); + model.save(null, {parse: false}); + assert.equal(model.get('i'), i); + }); + + QUnit.test('save with PATCH', function(assert) { + doc.clear().set({id: 1, a: 1, b: 2, c: 3, d: 4}); + doc.save(); + assert.equal(this.syncArgs.method, 'update'); + assert.equal(this.syncArgs.options.attrs, undefined); + + doc.save({b: 2, d: 4}, {patch: true}); + assert.equal(this.syncArgs.method, 'patch'); + assert.equal(_.size(this.syncArgs.options.attrs), 2); + assert.equal(this.syncArgs.options.attrs.d, 4); + assert.equal(this.syncArgs.options.attrs.a, undefined); + assert.equal(this.ajaxSettings.data, '{"b":2,"d":4}'); + }); + + QUnit.test('save with PATCH and different attrs', function(assert) { + doc.clear().save({b: 2, d: 4}, {patch: true, attrs: {B: 1, D: 3}}); + assert.equal(this.syncArgs.options.attrs.D, 3); + assert.equal(this.syncArgs.options.attrs.d, undefined); + assert.equal(this.ajaxSettings.data, '{"B":1,"D":3}'); + assert.deepEqual(doc.attributes, {b: 2, d: 4}); + }); + + QUnit.test('save in positional style', function(assert) { + assert.expect(1); + var model = new Backbone.Model(); + model.sync = function(method, m, options) { + options.success(); + }; + model.save('title', 'Twelfth Night'); + assert.equal(model.get('title'), 'Twelfth Night'); + }); + + QUnit.test('save with non-object success response', function(assert) { + assert.expect(2); + var model = new Backbone.Model(); + model.sync = function(method, m, options) { + options.success('', options); + options.success(null, options); + }; + model.save({testing: 'empty'}, { + success: function(m) { + assert.deepEqual(m.attributes, {testing: 'empty'}); + } + }); + }); + + QUnit.test('save with wait and supplied id', function(assert) { + var Model = Backbone.Model.extend({ + urlRoot: '/collection' + }); + var model = new Model(); + model.save({id: 42}, {wait: true}); + assert.equal(this.ajaxSettings.url, '/collection/42'); + }); + + QUnit.test('save will pass extra options to success callback', function(assert) { + assert.expect(1); + var SpecialSyncModel = Backbone.Model.extend({ + sync: function(method, m, options) { + _.extend(options, {specialSync: true}); + return Backbone.Model.prototype.sync.call(this, method, m, options); + }, + urlRoot: '/test' + }); + + var model = new SpecialSyncModel(); + + var onSuccess = function(m, response, options) { + assert.ok(options.specialSync, 'Options were passed correctly to callback'); + }; + + model.save(null, {success: onSuccess}); + this.ajaxSettings.success(); + }); + + QUnit.test('fetch', function(assert) { + assert.expect(2); + doc.fetch(); + assert.equal(this.syncArgs.method, 'read'); + assert.ok(_.isEqual(this.syncArgs.model, doc)); + }); + + QUnit.test('fetch will pass extra options to success callback', function(assert) { + assert.expect(1); + var SpecialSyncModel = Backbone.Model.extend({ + sync: function(method, m, options) { + _.extend(options, {specialSync: true}); + return Backbone.Model.prototype.sync.call(this, method, m, options); + }, + urlRoot: '/test' + }); + + var model = new SpecialSyncModel(); + + var onSuccess = function(m, response, options) { + assert.ok(options.specialSync, 'Options were passed correctly to callback'); + }; + + model.fetch({success: onSuccess}); + this.ajaxSettings.success(); + }); + + QUnit.test('destroy', function(assert) { + assert.expect(3); + doc.destroy(); + assert.equal(this.syncArgs.method, 'delete'); + assert.ok(_.isEqual(this.syncArgs.model, doc)); + + var newModel = new Backbone.Model; + assert.equal(newModel.destroy(), false); + }); + + QUnit.test('destroy will pass extra options to success callback', function(assert) { + assert.expect(1); + var SpecialSyncModel = Backbone.Model.extend({ + sync: function(method, m, options) { + _.extend(options, {specialSync: true}); + return Backbone.Model.prototype.sync.call(this, method, m, options); + }, + urlRoot: '/test' + }); + + var model = new SpecialSyncModel({id: 'id'}); + + var onSuccess = function(m, response, options) { + assert.ok(options.specialSync, 'Options were passed correctly to callback'); + }; + + model.destroy({success: onSuccess}); + this.ajaxSettings.success(); + }); + + QUnit.test('non-persisted destroy', function(assert) { + assert.expect(1); + var a = new Backbone.Model({foo: 1, bar: 2, baz: 3}); + a.sync = function() { throw 'should not be called'; }; + a.destroy(); + assert.ok(true, 'non-persisted model should not call sync'); + }); + + QUnit.test('validate', function(assert) { + 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('invalid', function(m, error) { + lastError = error; + }); + var result = model.set({a: 100}); + assert.equal(result, model); + assert.equal(model.get('a'), 100); + assert.equal(lastError, undefined); + result = model.set({admin: true}); + assert.equal(model.get('admin'), true); + result = model.set({a: 200, admin: false}, {validate: true}); + assert.equal(lastError, "Can't change admin status."); + assert.equal(result, false); + assert.equal(model.get('a'), 100); + }); + + QUnit.test('validate on unset and clear', function(assert) { + assert.expect(6); + 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'}); + assert.equal(model.get('name'), 'Two'); + assert.equal(error, undefined); + model.unset('name', {validate: true}); + assert.equal(error, true); + assert.equal(model.get('name'), 'Two'); + model.clear({validate: true}); + assert.equal(model.get('name'), 'Two'); + delete model.validate; + model.clear(); + assert.equal(model.get('name'), undefined); + }); + + QUnit.test('validate with error callback', function(assert) { + assert.expect(8); + var lastError, boundError; + var model = new Backbone.Model(); + model.validate = function(attrs) { + if (attrs.admin) return "Can't change admin status."; + }; + model.on('invalid', function(m, error) { + boundError = true; + }); + var result = model.set({a: 100}, {validate: true}); + assert.equal(result, model); + assert.equal(model.get('a'), 100); + assert.equal(model.validationError, null); + assert.equal(boundError, undefined); + result = model.set({a: 200, admin: true}, {validate: true}); + assert.equal(result, false); + assert.equal(model.get('a'), 100); + assert.equal(model.validationError, "Can't change admin status."); + assert.equal(boundError, true); + }); + + QUnit.test('defaults always extend attrs (#459)', function(assert) { + assert.expect(2); + var Defaulted = Backbone.Model.extend({ + defaults: {one: 1}, + initialize: function(attrs, opts) { + assert.equal(this.attributes.one, 1); + } + }); + var providedattrs = new Defaulted({}); + var emptyattrs = new Defaulted(); + }); + + QUnit.test('Inherit class properties', function(assert) { + assert.expect(6); + 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; + + assert.equal(Child.classProp, Parent.classProp); + assert.notEqual(Child.classProp, undefined); + + assert.equal(kid.instancePropSame, adult.instancePropSame); + assert.notEqual(kid.instancePropSame, undefined); + + assert.notEqual(Child.prototype.instancePropDiff, Parent.prototype.instancePropDiff); + assert.notEqual(Child.prototype.instancePropDiff, undefined); + }); + + QUnit.test("Nested change events don't clobber previous attributes", function(assert) { + assert.expect(4); + new Backbone.Model() + .on('change:state', function(m, newState) { + assert.equal(m.previous('state'), undefined); + assert.equal(newState, 'hello'); + // Fire a nested change event. + m.set({other: 'whatever'}); + }) + .on('change:state', function(m, newState) { + assert.equal(m.previous('state'), undefined); + assert.equal(newState, 'hello'); + }) + .set({state: 'hello'}); + }); + + QUnit.test('hasChanged/set should use same comparison', function(assert) { + assert.expect(2); + var changed = 0, model = new Backbone.Model({a: null}); + model.on('change', function() { + assert.ok(this.hasChanged('a')); + }) + .on('change:a', function() { + changed++; + }) + .set({a: undefined}); + assert.equal(changed, 1); + }); + + QUnit.test('#582, #425, change:attribute callbacks should fire after all changes have occurred', function(assert) { + assert.expect(9); + var model = new Backbone.Model; + + var assertion = function() { + assert.equal(model.get('a'), 'a'); + assert.equal(model.get('b'), 'b'); + assert.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'}); + }); + + QUnit.test('#871, set with attributes property', function(assert) { + assert.expect(1); + var model = new Backbone.Model(); + model.set({attributes: true}); + assert.ok(model.has('attributes')); + }); + + QUnit.test('set value regardless of equality/change', function(assert) { + assert.expect(1); + var model = new Backbone.Model({x: []}); + var a = []; + model.set({x: a}); + assert.ok(model.get('x') === a); + }); + + QUnit.test('set same value does not trigger change', function(assert) { + assert.expect(0); + var model = new Backbone.Model({x: 1}); + model.on('change change:x', function() { assert.ok(false); }); + model.set({x: 1}); + model.set({x: 1}); + }); + + QUnit.test('unset does not fire a change for undefined attributes', function(assert) { + assert.expect(0); + var model = new Backbone.Model({x: undefined}); + model.on('change:x', function(){ assert.ok(false); }); + model.unset('x'); + }); + + QUnit.test('set: undefined values', function(assert) { + assert.expect(1); + var model = new Backbone.Model({x: undefined}); + assert.ok('x' in model.attributes); + }); + + QUnit.test('hasChanged works outside of change events, and true within', function(assert) { + assert.expect(6); + var model = new Backbone.Model({x: 1}); + model.on('change:x', function() { + assert.ok(model.hasChanged('x')); + assert.equal(model.get('x'), 1); + }); + model.set({x: 2}, {silent: true}); + assert.ok(model.hasChanged()); + assert.equal(model.hasChanged('x'), true); + model.set({x: 1}); + assert.ok(model.hasChanged()); + assert.equal(model.hasChanged('x'), true); + }); + + QUnit.test('hasChanged gets cleared on the following set', function(assert) { + assert.expect(4); + var model = new Backbone.Model; + model.set({x: 1}); + assert.ok(model.hasChanged()); + model.set({x: 1}); + assert.ok(!model.hasChanged()); + model.set({x: 2}); + assert.ok(model.hasChanged()); + model.set({}); + assert.ok(!model.hasChanged()); + }); + + QUnit.test('save with `wait` succeeds without `validate`', function(assert) { + assert.expect(1); + var model = new Backbone.Model(); + model.url = '/test'; + model.save({x: 1}, {wait: true}); + assert.ok(this.syncArgs.model === model); + }); + + QUnit.test("save without `wait` doesn't set invalid attributes", function(assert) { + var model = new Backbone.Model(); + model.validate = function() { return 1; }; + model.save({a: 1}); + assert.equal(model.get('a'), void 0); + }); + + QUnit.test("save doesn't validate twice", function(assert) { + var model = new Backbone.Model(); + var times = 0; + model.sync = function() {}; + model.validate = function() { ++times; }; + model.save({}); + assert.equal(times, 1); + }); + + QUnit.test('`hasChanged` for falsey keys', function(assert) { + assert.expect(2); + var model = new Backbone.Model(); + model.set({x: true}, {silent: true}); + assert.ok(!model.hasChanged(0)); + assert.ok(!model.hasChanged('')); + }); + + QUnit.test('`previous` for falsey keys', function(assert) { + assert.expect(2); + var model = new Backbone.Model({'0': true, '': true}); + model.set({'0': false, '': false}, {silent: true}); + assert.equal(model.previous(0), true); + assert.equal(model.previous(''), true); + }); + + QUnit.test('`save` with `wait` sends correct attributes', function(assert) { + assert.expect(5); + var changed = 0; + var model = new Backbone.Model({x: 1, y: 2}); + model.url = '/test'; + model.on('change:x', function() { changed++; }); + model.save({x: 3}, {wait: true}); + assert.deepEqual(JSON.parse(this.ajaxSettings.data), {x: 3, y: 2}); + assert.equal(model.get('x'), 1); + assert.equal(changed, 0); + this.syncArgs.options.success({}); + assert.equal(model.get('x'), 3); + assert.equal(changed, 1); + }); + + QUnit.test("a failed `save` with `wait` doesn't leave attributes behind", function(assert) { + assert.expect(1); + var model = new Backbone.Model; + model.url = '/test'; + model.save({x: 1}, {wait: true}); + assert.equal(model.get('x'), void 0); + }); + + QUnit.test('#1030 - `save` with `wait` results in correct attributes if success is called during sync', function(assert) { + assert.expect(2); + var model = new Backbone.Model({x: 1, y: 2}); + model.sync = function(method, m, options) { + options.success(); + }; + model.on('change:x', function() { assert.ok(true); }); + model.save({x: 3}, {wait: true}); + assert.equal(model.get('x'), 3); + }); + + QUnit.test('save with wait validates attributes', function(assert) { + var model = new Backbone.Model(); + model.url = '/test'; + model.validate = function() { assert.ok(true); }; + model.save({x: 1}, {wait: true}); + }); + + QUnit.test('save turns on parse flag', function(assert) { + var Model = Backbone.Model.extend({ + sync: function(method, m, options) { assert.ok(options.parse); } + }); + new Model().save(); + }); + + QUnit.test("nested `set` during `'change:attr'`", function(assert) { + assert.expect(2); + 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}); + assert.deepEqual(events, ['change:y', 'change:x', 'change']); + events = []; + model.set({z: true}); + assert.deepEqual(events, []); + }); + + QUnit.test('nested `change` only fires once', function(assert) { + assert.expect(1); + var model = new Backbone.Model(); + model.on('change', function() { + assert.ok(true); + model.set({x: true}); + }); + model.set({x: true}); + }); + + QUnit.test("nested `set` during `'change'`", function(assert) { + assert.expect(6); + var count = 0; + var model = new Backbone.Model(); + model.on('change', function() { + switch (count++) { + case 0: + assert.deepEqual(this.changedAttributes(), {x: true}); + assert.equal(model.previous('x'), undefined); + model.set({y: true}); + break; + case 1: + assert.deepEqual(this.changedAttributes(), {x: true, y: true}); + assert.equal(model.previous('x'), undefined); + model.set({z: true}); + break; + case 2: + assert.deepEqual(this.changedAttributes(), {x: true, y: true, z: true}); + assert.equal(model.previous('y'), undefined); + break; + default: + assert.ok(false); + } + }); + model.set({x: true}); + }); + + QUnit.test('nested `change` with silent', function(assert) { + assert.expect(3); + var count = 0; + var model = new Backbone.Model(); + model.on('change:y', function() { assert.ok(false); }); + model.on('change', function() { + switch (count++) { + case 0: + assert.deepEqual(this.changedAttributes(), {x: true}); + model.set({y: true}, {silent: true}); + model.set({z: true}); + break; + case 1: + assert.deepEqual(this.changedAttributes(), {x: true, y: true, z: true}); + break; + case 2: + assert.deepEqual(this.changedAttributes(), {z: false}); + break; + default: + assert.ok(false); + } + }); + model.set({x: true}); + model.set({z: false}); + }); + + QUnit.test('nested `change:attr` with silent', function(assert) { + assert.expect(0); + var model = new Backbone.Model(); + model.on('change:y', function(){ assert.ok(false); }); + model.on('change', function() { + model.set({y: true}, {silent: true}); + model.set({z: true}); + }); + model.set({x: true}); + }); + + QUnit.test('multiple nested changes with silent', function(assert) { + assert.expect(1); + 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(m, val) { + assert.equal(val, 2); + }); + model.set({x: true}); + }); + + QUnit.test('multiple nested changes with silent', function(assert) { + assert.expect(1); + var changes = []; + var model = new Backbone.Model(); + model.on('change:b', function(m, val) { changes.push(val); }); + model.on('change', function() { + model.set({b: 1}); + }); + model.set({b: 0}); + assert.deepEqual(changes, [0, 1]); + }); + + QUnit.test('basic silent change semantics', function(assert) { + assert.expect(1); + var model = new Backbone.Model; + model.set({x: 1}); + model.on('change', function(){ assert.ok(true); }); + model.set({x: 2}, {silent: true}); + model.set({x: 1}); + }); + + QUnit.test('nested set multiple times', function(assert) { + assert.expect(1); + var model = new Backbone.Model(); + model.on('change:b', function() { + assert.ok(true); + }); + model.on('change:a', function() { + model.set({b: true}); + model.set({b: true}); + }); + model.set({a: true}); + }); + + QUnit.test('#1122 - clear does not alter options.', function(assert) { + assert.expect(1); + var model = new Backbone.Model(); + var options = {}; + model.clear(options); + assert.ok(!options.unset); + }); + + QUnit.test('#1122 - unset does not alter options.', function(assert) { + assert.expect(1); + var model = new Backbone.Model(); + var options = {}; + model.unset('x', options); + assert.ok(!options.unset); + }); + + QUnit.test('#1355 - `options` is passed to success callbacks', function(assert) { + assert.expect(3); + var model = new Backbone.Model(); + var opts = { + success: function( m, resp, options ) { + assert.ok(options); + } + }; + model.sync = function(method, m, options) { + options.success(); + }; + model.save({id: 1}, opts); + model.fetch(opts); + model.destroy(opts); + }); + + QUnit.test("#1412 - Trigger 'sync' event.", function(assert) { + assert.expect(3); + var model = new Backbone.Model({id: 1}); + model.sync = function(method, m, options) { options.success(); }; + model.on('sync', function(){ assert.ok(true); }); + model.fetch(); + model.save(); + model.destroy(); + }); + + QUnit.test('#1365 - Destroy: New models execute success callback.', function(assert) { + var done = assert.async(); + assert.expect(2); + new Backbone.Model() + .on('sync', function() { assert.ok(false); }) + .on('destroy', function(){ assert.ok(true); }) + .destroy({success: function(){ + assert.ok(true); + done(); + }}); + }); + + QUnit.test('#1433 - Save: An invalid model cannot be persisted.', function(assert) { + assert.expect(1); + var model = new Backbone.Model; + model.validate = function(){ return 'invalid'; }; + model.sync = function(){ assert.ok(false); }; + assert.strictEqual(model.save(), false); + }); + + QUnit.test("#1377 - Save without attrs triggers 'error'.", function(assert) { + assert.expect(1); + var Model = Backbone.Model.extend({ + url: '/test/', + sync: function(method, m, options){ options.success(); }, + validate: function(){ return 'invalid'; } + }); + var model = new Model({id: 1}); + model.on('invalid', function(){ assert.ok(true); }); + model.save(); + }); + + QUnit.test('#1545 - `undefined` can be passed to a model constructor without coersion', function(assert) { + var Model = Backbone.Model.extend({ + defaults: {one: 1}, + initialize: function(attrs, opts) { + assert.equal(attrs, undefined); + } + }); + var emptyattrs = new Model(); + var undefinedattrs = new Model(undefined); + }); + + QUnit.test('#1478 - Model `save` does not trigger change on unchanged attributes', function(assert) { + var done = assert.async(); + assert.expect(0); + var Model = Backbone.Model.extend({ + sync: function(method, m, options) { + setTimeout(function(){ + options.success(); + done(); + }, 0); + } + }); + new Model({x: true}) + .on('change:x', function(){ assert.ok(false); }) + .save(null, {wait: true}); + }); + + QUnit.test('#1664 - Changing from one value, silently to another, back to original triggers a change.', function(assert) { + assert.expect(1); + var model = new Backbone.Model({x: 1}); + model.on('change:x', function() { assert.ok(true); }); + model.set({x: 2}, {silent: true}); + model.set({x: 3}, {silent: true}); + model.set({x: 1}); + }); + + QUnit.test('#1664 - multiple silent changes nested inside a change event', function(assert) { + assert.expect(2); + var changes = []; + var model = new Backbone.Model(); + model.on('change', function() { + model.set({a: 'c'}, {silent: true}); + model.set({b: 2}, {silent: true}); + model.unset('c', {silent: true}); + }); + model.on('change:a change:b change:c', function(m, val) { changes.push(val); }); + model.set({a: 'a', b: 1, c: 'item'}); + assert.deepEqual(changes, ['a', 1, 'item']); + assert.deepEqual(model.attributes, {a: 'c', b: 2}); + }); + + QUnit.test('#1791 - `attributes` is available for `parse`', function(assert) { + var Model = Backbone.Model.extend({ + parse: function() { this.has('a'); } // shouldn't throw an error + }); + var model = new Model(null, {parse: true}); + assert.expect(0); + }); + + QUnit.test('silent changes in last `change` event back to original triggers change', function(assert) { + assert.expect(2); + var changes = []; + var model = new Backbone.Model(); + model.on('change:a change:b change:c', function(m, val) { changes.push(val); }); + model.on('change', function() { + model.set({a: 'c'}, {silent: true}); + }); + model.set({a: 'a'}); + assert.deepEqual(changes, ['a']); + model.set({a: 'a'}); + assert.deepEqual(changes, ['a', 'a']); + }); + + QUnit.test('#1943 change calculations should use _.isEqual', function(assert) { + var model = new Backbone.Model({a: {key: 'value'}}); + model.set('a', {key: 'value'}, {silent: true}); + assert.equal(model.changedAttributes(), false); + }); + + QUnit.test('#1964 - final `change` event is always fired, regardless of interim changes', function(assert) { + assert.expect(1); + var model = new Backbone.Model(); + model.on('change:property', function() { + model.set('property', 'bar'); + }); + model.on('change', function() { + assert.ok(true); + }); + model.set('property', 'foo'); + }); + + QUnit.test('isValid', function(assert) { + var model = new Backbone.Model({valid: true}); + model.validate = function(attrs) { + if (!attrs.valid) return 'invalid'; + }; + assert.equal(model.isValid(), true); + assert.equal(model.set({valid: false}, {validate: true}), false); + assert.equal(model.isValid(), true); + model.set({valid: false}); + assert.equal(model.isValid(), false); + assert.ok(!model.set('valid', false, {validate: true})); + }); + + QUnit.test('#1179 - isValid returns true in the absence of validate.', function(assert) { + assert.expect(1); + var model = new Backbone.Model(); + model.validate = null; + assert.ok(model.isValid()); + }); + + QUnit.test('#1961 - Creating a model with {validate:true} will call validate and use the error callback', function(assert) { + var Model = Backbone.Model.extend({ + validate: function(attrs) { + if (attrs.id === 1) return "This shouldn't happen"; + } + }); + var model = new Model({id: 1}, {validate: true}); + assert.equal(model.validationError, "This shouldn't happen"); + }); + + QUnit.test('toJSON receives attrs during save(..., {wait: true})', function(assert) { + assert.expect(1); + var Model = Backbone.Model.extend({ + url: '/test', + toJSON: function() { + assert.strictEqual(this.attributes.x, 1); + return _.clone(this.attributes); + } + }); + var model = new Model; + model.save({x: 1}, {wait: true}); + }); + + QUnit.test('#2034 - nested set with silent only triggers one change', function(assert) { + assert.expect(1); + var model = new Backbone.Model(); + model.on('change', function() { + model.set({b: true}, {silent: true}); + assert.ok(true); + }); + model.set({a: true}); + }); + + QUnit.test('#3778 - id will only be updated if it is set', function(assert) { + assert.expect(2); + var model = new Backbone.Model({id: 1}); + model.id = 2; + model.set({foo: 'bar'}); + assert.equal(model.id, 2); + model.set({id: 3}); + assert.equal(model.id, 3); + }); + +})(); diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/backbone/test/noconflict.js b/ecomp-portal-FE/client/bower_components/lodash/vendor/backbone/test/noconflict.js new file mode 100644 index 00000000..9968f688 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/backbone/test/noconflict.js @@ -0,0 +1,13 @@ +(function() { + + QUnit.module('Backbone.noConflict'); + + QUnit.test('noConflict', function(assert) { + assert.expect(2); + var noconflictBackbone = Backbone.noConflict(); + assert.equal(window.Backbone, undefined, 'Returned window.Backbone'); + window.Backbone = noconflictBackbone; + assert.equal(window.Backbone, noconflictBackbone, 'Backbone is still pointing to the original Backbone'); + }); + +})(); diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/backbone/test/router.js b/ecomp-portal-FE/client/bower_components/lodash/vendor/backbone/test/router.js new file mode 100644 index 00000000..13110c45 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/backbone/test/router.js @@ -0,0 +1,1062 @@ +(function() { + + var router = null; + var location = null; + var lastRoute = null; + var lastArgs = []; + + var onRoute = function(routerParam, route, args) { + lastRoute = route; + lastArgs = args; + }; + + var Location = function(href) { + this.replace(href); + }; + + _.extend(Location.prototype, { + + parser: document.createElement('a'), + + replace: function(href) { + this.parser.href = href; + _.extend(this, _.pick(this.parser, + 'href', + 'hash', + 'host', + 'search', + 'fragment', + 'pathname', + 'protocol' + )); + // 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; + } + + }); + + QUnit.module('Backbone.Router', { + + setup: function() { + location = new Location('http://example.com'); + Backbone.history = _.extend(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 ExternalObject = { + value: 'unset', + + routingFunction: function(value) { + this.value = value; + } + }; + ExternalObject.routingFunction = _.bind(ExternalObject.routingFunction, ExternalObject); + + var Router = Backbone.Router.extend({ + + count: 0, + + routes: { + 'noCallback': 'noCallback', + 'counter': 'counter', + 'search/:query': 'search', + 'search/:query/p:page': 'search', + 'charñ': 'charUTF', + 'char%C3%B1': 'charEscaped', + 'contacts': 'contacts', + 'contacts/new': 'newContact', + 'contacts/:id': 'loadContact', + 'route-event/:arg': 'routeEvent', + 'optional(/:item)': 'optionalItem', + 'named/optional/(y:z)': 'namedOptional', + 'splat/*args/end': 'splat', + ':repo/compare/*from...*to': 'github', + 'decode/:named/*splat': 'decode', + '*first/complex-*part/*rest': 'complex', + 'query/:entity': 'query', + 'function/:value': ExternalObject.routingFunction, + '*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; + }, + + charUTF: function() { + this.charType = 'UTF'; + }, + + charEscaped: function() { + this.charType = 'escaped'; + }, + + contacts: function(){ + this.contact = 'index'; + }, + + newContact: function(){ + this.contact = 'new'; + }, + + loadContact: function(){ + this.contact = 'load'; + }, + + optionalItem: function(arg){ + this.arg = arg !== void 0 ? arg : null; + }, + + splat: function(args) { + this.args = args; + }, + + github: function(repo, from, to) { + this.repo = repo; + this.from = from; + this.to = to; + }, + + 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; + }, + + namedOptional: function(z) { + this.z = z; + }, + + decode: function(named, path) { + this.named = named; + this.path = path; + }, + + routeEvent: function(arg) { + } + + }); + + QUnit.test('initialize', function(assert) { + assert.expect(1); + assert.equal(router.testing, 101); + }); + + QUnit.test('routes (simple)', function(assert) { + assert.expect(4); + location.replace('http://example.com#search/news'); + Backbone.history.checkUrl(); + assert.equal(router.query, 'news'); + assert.equal(router.page, void 0); + assert.equal(lastRoute, 'search'); + assert.equal(lastArgs[0], 'news'); + }); + + QUnit.test('routes (simple, but unicode)', function(assert) { + assert.expect(4); + location.replace('http://example.com#search/тест'); + Backbone.history.checkUrl(); + assert.equal(router.query, 'тест'); + assert.equal(router.page, void 0); + assert.equal(lastRoute, 'search'); + assert.equal(lastArgs[0], 'тест'); + }); + + QUnit.test('routes (two part)', function(assert) { + assert.expect(2); + location.replace('http://example.com#search/nyc/p10'); + Backbone.history.checkUrl(); + assert.equal(router.query, 'nyc'); + assert.equal(router.page, '10'); + }); + + QUnit.test('routes via navigate', function(assert) { + assert.expect(2); + Backbone.history.navigate('search/manhattan/p20', {trigger: true}); + assert.equal(router.query, 'manhattan'); + assert.equal(router.page, '20'); + }); + + QUnit.test('routes via navigate with params', function(assert) { + assert.expect(1); + Backbone.history.navigate('query/test?a=b', {trigger: true}); + assert.equal(router.queryArgs, 'a=b'); + }); + + QUnit.test('routes via navigate for backwards-compatibility', function(assert) { + assert.expect(2); + Backbone.history.navigate('search/manhattan/p20', true); + assert.equal(router.query, 'manhattan'); + assert.equal(router.page, '20'); + }); + + QUnit.test('reports matched route via nagivate', function(assert) { + assert.expect(1); + assert.ok(Backbone.history.navigate('search/manhattan/p20', true)); + }); + + QUnit.test('route precedence via navigate', function(assert){ + assert.expect(6); + // check both 0.9.x and backwards-compatibility options + _.each([{trigger: true}, true], function( options ){ + Backbone.history.navigate('contacts', options); + assert.equal(router.contact, 'index'); + Backbone.history.navigate('contacts/new', options); + assert.equal(router.contact, 'new'); + Backbone.history.navigate('contacts/foo', options); + assert.equal(router.contact, 'load'); + }); + }); + + QUnit.test('loadUrl is not called for identical routes.', function(assert) { + assert.expect(0); + Backbone.history.loadUrl = function(){ assert.ok(false); }; + location.replace('http://example.com#route'); + Backbone.history.navigate('route'); + Backbone.history.navigate('/route'); + Backbone.history.navigate('/route'); + }); + + QUnit.test('use implicit callback if none provided', function(assert) { + assert.expect(1); + router.count = 0; + router.navigate('implicit', {trigger: true}); + assert.equal(router.count, 1); + }); + + QUnit.test('routes via navigate with {replace: true}', function(assert) { + assert.expect(1); + location.replace('http://example.com#start_here'); + Backbone.history.checkUrl(); + location.replace = function(href) { + assert.strictEqual(href, new Location('http://example.com#end_here').href); + }; + Backbone.history.navigate('end_here', {replace: true}); + }); + + QUnit.test('routes (splats)', function(assert) { + assert.expect(1); + location.replace('http://example.com#splat/long-list/of/splatted_99args/end'); + Backbone.history.checkUrl(); + assert.equal(router.args, 'long-list/of/splatted_99args'); + }); + + QUnit.test('routes (github)', function(assert) { + assert.expect(3); + location.replace('http://example.com#backbone/compare/1.0...braddunbar:with/slash'); + Backbone.history.checkUrl(); + assert.equal(router.repo, 'backbone'); + assert.equal(router.from, '1.0'); + assert.equal(router.to, 'braddunbar:with/slash'); + }); + + QUnit.test('routes (optional)', function(assert) { + assert.expect(2); + location.replace('http://example.com#optional'); + Backbone.history.checkUrl(); + assert.ok(!router.arg); + location.replace('http://example.com#optional/thing'); + Backbone.history.checkUrl(); + assert.equal(router.arg, 'thing'); + }); + + QUnit.test('routes (complex)', function(assert) { + assert.expect(3); + location.replace('http://example.com#one/two/three/complex-part/four/five/six/seven'); + Backbone.history.checkUrl(); + assert.equal(router.first, 'one/two/three'); + assert.equal(router.part, 'part'); + assert.equal(router.rest, 'four/five/six/seven'); + }); + + QUnit.test('routes (query)', function(assert) { + assert.expect(5); + location.replace('http://example.com#query/mandel?a=b&c=d'); + Backbone.history.checkUrl(); + assert.equal(router.entity, 'mandel'); + assert.equal(router.queryArgs, 'a=b&c=d'); + assert.equal(lastRoute, 'query'); + assert.equal(lastArgs[0], 'mandel'); + assert.equal(lastArgs[1], 'a=b&c=d'); + }); + + QUnit.test('routes (anything)', function(assert) { + assert.expect(1); + location.replace('http://example.com#doesnt-match-a-route'); + Backbone.history.checkUrl(); + assert.equal(router.anything, 'doesnt-match-a-route'); + }); + + QUnit.test('routes (function)', function(assert) { + assert.expect(3); + router.on('route', function(name) { + assert.ok(name === ''); + }); + assert.equal(ExternalObject.value, 'unset'); + location.replace('http://example.com#function/set'); + Backbone.history.checkUrl(); + assert.equal(ExternalObject.value, 'set'); + }); + + QUnit.test('Decode named parameters, not splats.', function(assert) { + assert.expect(2); + location.replace('http://example.com#decode/a%2Fb/c%2Fd/e'); + Backbone.history.checkUrl(); + assert.strictEqual(router.named, 'a/b'); + assert.strictEqual(router.path, 'c/d/e'); + }); + + QUnit.test("fires event when router doesn't have callback on it", function(assert) { + assert.expect(1); + router.on('route:noCallback', function(){ assert.ok(true); }); + location.replace('http://example.com#noCallback'); + Backbone.history.checkUrl(); + }); + + QUnit.test('No events are triggered if #execute returns false.', function(assert) { + assert.expect(1); + var MyRouter = Backbone.Router.extend({ + + routes: { + foo: function() { + assert.ok(true); + } + }, + + execute: function(callback, args) { + callback.apply(this, args); + return false; + } + + }); + + var myRouter = new MyRouter; + + myRouter.on('route route:foo', function() { + assert.ok(false); + }); + + Backbone.history.on('route', function() { + assert.ok(false); + }); + + location.replace('http://example.com#foo'); + Backbone.history.checkUrl(); + }); + + QUnit.test('#933, #908 - leading slash', function(assert) { + assert.expect(2); + location.replace('http://example.com/root/foo'); + + Backbone.history.stop(); + Backbone.history = _.extend(new Backbone.History, {location: location}); + Backbone.history.start({root: '/root', hashChange: false, silent: true}); + assert.strictEqual(Backbone.history.getFragment(), 'foo'); + + Backbone.history.stop(); + Backbone.history = _.extend(new Backbone.History, {location: location}); + Backbone.history.start({root: '/root/', hashChange: false, silent: true}); + assert.strictEqual(Backbone.history.getFragment(), 'foo'); + }); + + QUnit.test('#967 - Route callback gets passed encoded values.', function(assert) { + assert.expect(3); + var route = 'has%2Fslash/complex-has%23hash/has%20space'; + Backbone.history.navigate(route, {trigger: true}); + assert.strictEqual(router.first, 'has/slash'); + assert.strictEqual(router.part, 'has#hash'); + assert.strictEqual(router.rest, 'has space'); + }); + + QUnit.test('correctly handles URLs with % (#868)', function(assert) { + assert.expect(3); + location.replace('http://example.com#search/fat%3A1.5%25'); + Backbone.history.checkUrl(); + location.replace('http://example.com#search/fat'); + Backbone.history.checkUrl(); + assert.equal(router.query, 'fat'); + assert.equal(router.page, void 0); + assert.equal(lastRoute, 'search'); + }); + + QUnit.test('#2666 - Hashes with UTF8 in them.', function(assert) { + assert.expect(2); + Backbone.history.navigate('charñ', {trigger: true}); + assert.equal(router.charType, 'UTF'); + Backbone.history.navigate('char%C3%B1', {trigger: true}); + assert.equal(router.charType, 'UTF'); + }); + + QUnit.test('#1185 - Use pathname when hashChange is not wanted.', function(assert) { + assert.expect(1); + Backbone.history.stop(); + location.replace('http://example.com/path/name#hash'); + Backbone.history = _.extend(new Backbone.History, {location: location}); + Backbone.history.start({hashChange: false}); + var fragment = Backbone.history.getFragment(); + assert.strictEqual(fragment, location.pathname.replace(/^\//, '')); + }); + + QUnit.test('#1206 - Strip leading slash before location.assign.', function(assert) { + assert.expect(1); + Backbone.history.stop(); + location.replace('http://example.com/root/'); + Backbone.history = _.extend(new Backbone.History, {location: location}); + Backbone.history.start({hashChange: false, root: '/root/'}); + location.assign = function(pathname) { + assert.strictEqual(pathname, '/root/fragment'); + }; + Backbone.history.navigate('/fragment'); + }); + + QUnit.test('#1387 - Root fragment without trailing slash.', function(assert) { + assert.expect(1); + Backbone.history.stop(); + location.replace('http://example.com/root'); + Backbone.history = _.extend(new Backbone.History, {location: location}); + Backbone.history.start({hashChange: false, root: '/root/', silent: true}); + assert.strictEqual(Backbone.history.getFragment(), ''); + }); + + QUnit.test('#1366 - History does not prepend root to fragment.', function(assert) { + assert.expect(2); + Backbone.history.stop(); + location.replace('http://example.com/root/'); + Backbone.history = _.extend(new Backbone.History, { + location: location, + history: { + pushState: function(state, title, url) { + assert.strictEqual(url, '/root/x'); + } + } + }); + Backbone.history.start({ + root: '/root/', + pushState: true, + hashChange: false + }); + Backbone.history.navigate('x'); + assert.strictEqual(Backbone.history.fragment, 'x'); + }); + + QUnit.test('Normalize root.', function(assert) { + assert.expect(1); + Backbone.history.stop(); + location.replace('http://example.com/root'); + Backbone.history = _.extend(new Backbone.History, { + location: location, + history: { + pushState: function(state, title, url) { + assert.strictEqual(url, '/root/fragment'); + } + } + }); + Backbone.history.start({ + pushState: true, + root: '/root', + hashChange: false + }); + Backbone.history.navigate('fragment'); + }); + + QUnit.test('Normalize root.', function(assert) { + assert.expect(1); + Backbone.history.stop(); + location.replace('http://example.com/root#fragment'); + Backbone.history = _.extend(new Backbone.History, { + location: location, + history: { + pushState: function(state, title, url) {}, + replaceState: function(state, title, url) { + assert.strictEqual(url, '/root/fragment'); + } + } + }); + Backbone.history.start({ + pushState: true, + root: '/root' + }); + }); + + QUnit.test('Normalize root.', function(assert) { + assert.expect(1); + Backbone.history.stop(); + location.replace('http://example.com/root'); + Backbone.history = _.extend(new Backbone.History, {location: location}); + Backbone.history.loadUrl = function() { assert.ok(true); }; + Backbone.history.start({ + pushState: true, + root: '/root' + }); + }); + + QUnit.test('Normalize root - leading slash.', function(assert) { + assert.expect(1); + Backbone.history.stop(); + location.replace('http://example.com/root'); + Backbone.history = _.extend(new Backbone.History, { + location: location, + history: { + pushState: function(){}, + replaceState: function(){} + } + }); + Backbone.history.start({root: 'root'}); + assert.strictEqual(Backbone.history.root, '/root/'); + }); + + QUnit.test('Transition from hashChange to pushState.', function(assert) { + assert.expect(1); + Backbone.history.stop(); + location.replace('http://example.com/root#x/y'); + Backbone.history = _.extend(new Backbone.History, { + location: location, + history: { + pushState: function(){}, + replaceState: function(state, title, url){ + assert.strictEqual(url, '/root/x/y'); + } + } + }); + Backbone.history.start({ + root: 'root', + pushState: true + }); + }); + + QUnit.test('#1619: Router: Normalize empty root', function(assert) { + assert.expect(1); + Backbone.history.stop(); + location.replace('http://example.com/'); + Backbone.history = _.extend(new Backbone.History, { + location: location, + history: { + pushState: function(){}, + replaceState: function(){} + } + }); + Backbone.history.start({root: ''}); + assert.strictEqual(Backbone.history.root, '/'); + }); + + QUnit.test('#1619: Router: nagivate with empty root', function(assert) { + assert.expect(1); + Backbone.history.stop(); + location.replace('http://example.com/'); + Backbone.history = _.extend(new Backbone.History, { + location: location, + history: { + pushState: function(state, title, url) { + assert.strictEqual(url, '/fragment'); + } + } + }); + Backbone.history.start({ + pushState: true, + root: '', + hashChange: false + }); + Backbone.history.navigate('fragment'); + }); + + QUnit.test('Transition from pushState to hashChange.', function(assert) { + assert.expect(1); + Backbone.history.stop(); + location.replace('http://example.com/root/x/y?a=b'); + location.replace = function(url) { + assert.strictEqual(url, '/root#x/y?a=b'); + }; + Backbone.history = _.extend(new Backbone.History, { + location: location, + history: { + pushState: null, + replaceState: null + } + }); + Backbone.history.start({ + root: 'root', + pushState: true + }); + }); + + QUnit.test('#1695 - hashChange to pushState with search.', function(assert) { + assert.expect(1); + Backbone.history.stop(); + location.replace('http://example.com/root#x/y?a=b'); + Backbone.history = _.extend(new Backbone.History, { + location: location, + history: { + pushState: function(){}, + replaceState: function(state, title, url){ + assert.strictEqual(url, '/root/x/y?a=b'); + } + } + }); + Backbone.history.start({ + root: 'root', + pushState: true + }); + }); + + QUnit.test('#1746 - Router allows empty route.', function(assert) { + assert.expect(1); + var MyRouter = Backbone.Router.extend({ + routes: {'': 'empty'}, + empty: function(){}, + route: function(route){ + assert.strictEqual(route, ''); + } + }); + new MyRouter; + }); + + QUnit.test('#1794 - Trailing space in fragments.', function(assert) { + assert.expect(1); + var history = new Backbone.History; + assert.strictEqual(history.getFragment('fragment '), 'fragment'); + }); + + QUnit.test('#1820 - Leading slash and trailing space.', 1, function(assert) { + var history = new Backbone.History; + assert.strictEqual(history.getFragment('/fragment '), 'fragment'); + }); + + QUnit.test('#1980 - Optional parameters.', function(assert) { + assert.expect(2); + location.replace('http://example.com#named/optional/y'); + Backbone.history.checkUrl(); + assert.strictEqual(router.z, undefined); + location.replace('http://example.com#named/optional/y123'); + Backbone.history.checkUrl(); + assert.strictEqual(router.z, '123'); + }); + + QUnit.test("#2062 - Trigger 'route' event on router instance.", function(assert) { + assert.expect(2); + router.on('route', function(name, args) { + assert.strictEqual(name, 'routeEvent'); + assert.deepEqual(args, ['x', null]); + }); + location.replace('http://example.com#route-event/x'); + Backbone.history.checkUrl(); + }); + + QUnit.test('#2255 - Extend routes by making routes a function.', function(assert) { + assert.expect(1); + var RouterBase = Backbone.Router.extend({ + routes: function() { + return { + home: 'root', + index: 'index.html' + }; + } + }); + + var RouterExtended = RouterBase.extend({ + routes: function() { + var _super = RouterExtended.__super__.routes; + return _.extend(_super(), {show: 'show', search: 'search'}); + } + }); + + var myRouter = new RouterExtended(); + assert.deepEqual({home: 'root', index: 'index.html', show: 'show', search: 'search'}, myRouter.routes); + }); + + QUnit.test('#2538 - hashChange to pushState only if both requested.', function(assert) { + assert.expect(0); + Backbone.history.stop(); + location.replace('http://example.com/root?a=b#x/y'); + Backbone.history = _.extend(new Backbone.History, { + location: location, + history: { + pushState: function(){}, + replaceState: function(){ assert.ok(false); } + } + }); + Backbone.history.start({ + root: 'root', + pushState: true, + hashChange: false + }); + }); + + QUnit.test('No hash fallback.', function(assert) { + assert.expect(0); + Backbone.history.stop(); + Backbone.history = _.extend(new Backbone.History, { + location: location, + history: { + pushState: function(){}, + replaceState: function(){} + } + }); + + var MyRouter = Backbone.Router.extend({ + routes: { + hash: function() { assert.ok(false); } + } + }); + var myRouter = new MyRouter; + + location.replace('http://example.com/'); + Backbone.history.start({ + pushState: true, + hashChange: false + }); + location.replace('http://example.com/nomatch#hash'); + Backbone.history.checkUrl(); + }); + + QUnit.test('#2656 - No trailing slash on root.', function(assert) { + assert.expect(1); + Backbone.history.stop(); + Backbone.history = _.extend(new Backbone.History, { + location: location, + history: { + pushState: function(state, title, url){ + assert.strictEqual(url, '/root'); + } + } + }); + location.replace('http://example.com/root/path'); + Backbone.history.start({pushState: true, hashChange: false, root: 'root'}); + Backbone.history.navigate(''); + }); + + QUnit.test('#2656 - No trailing slash on root.', function(assert) { + assert.expect(1); + Backbone.history.stop(); + Backbone.history = _.extend(new Backbone.History, { + location: location, + history: { + pushState: function(state, title, url) { + assert.strictEqual(url, '/'); + } + } + }); + location.replace('http://example.com/path'); + Backbone.history.start({pushState: true, hashChange: false}); + Backbone.history.navigate(''); + }); + + QUnit.test('#2656 - No trailing slash on root.', function(assert) { + assert.expect(1); + Backbone.history.stop(); + Backbone.history = _.extend(new Backbone.History, { + location: location, + history: { + pushState: function(state, title, url){ + assert.strictEqual(url, '/root?x=1'); + } + } + }); + location.replace('http://example.com/root/path'); + Backbone.history.start({pushState: true, hashChange: false, root: 'root'}); + Backbone.history.navigate('?x=1'); + }); + + QUnit.test('#2765 - Fragment matching sans query/hash.', function(assert) { + assert.expect(2); + Backbone.history.stop(); + Backbone.history = _.extend(new Backbone.History, { + location: location, + history: { + pushState: function(state, title, url) { + assert.strictEqual(url, '/path?query#hash'); + } + } + }); + + var MyRouter = Backbone.Router.extend({ + routes: { + path: function() { assert.ok(true); } + } + }); + var myRouter = new MyRouter; + + location.replace('http://example.com/'); + Backbone.history.start({pushState: true, hashChange: false}); + Backbone.history.navigate('path?query#hash', true); + }); + + QUnit.test('Do not decode the search params.', function(assert) { + assert.expect(1); + var MyRouter = Backbone.Router.extend({ + routes: { + path: function(params){ + assert.strictEqual(params, 'x=y%3Fz'); + } + } + }); + var myRouter = new MyRouter; + Backbone.history.navigate('path?x=y%3Fz', true); + }); + + QUnit.test('Navigate to a hash url.', function(assert) { + assert.expect(1); + Backbone.history.stop(); + Backbone.history = _.extend(new Backbone.History, {location: location}); + Backbone.history.start({pushState: true}); + var MyRouter = Backbone.Router.extend({ + routes: { + path: function(params) { + assert.strictEqual(params, 'x=y'); + } + } + }); + var myRouter = new MyRouter; + location.replace('http://example.com/path?x=y#hash'); + Backbone.history.checkUrl(); + }); + + QUnit.test('#navigate to a hash url.', function(assert) { + assert.expect(1); + Backbone.history.stop(); + Backbone.history = _.extend(new Backbone.History, {location: location}); + Backbone.history.start({pushState: true}); + var MyRouter = Backbone.Router.extend({ + routes: { + path: function(params) { + assert.strictEqual(params, 'x=y'); + } + } + }); + var myRouter = new MyRouter; + Backbone.history.navigate('path?x=y#hash', true); + }); + + QUnit.test('unicode pathname', function(assert) { + assert.expect(1); + location.replace('http://example.com/myyjä'); + Backbone.history.stop(); + Backbone.history = _.extend(new Backbone.History, {location: location}); + var MyRouter = Backbone.Router.extend({ + routes: { + myyjä: function() { + assert.ok(true); + } + } + }); + new MyRouter; + Backbone.history.start({pushState: true}); + }); + + QUnit.test('unicode pathname with % in a parameter', function(assert) { + assert.expect(1); + location.replace('http://example.com/myyjä/foo%20%25%3F%2f%40%25%20bar'); + location.pathname = '/myyj%C3%A4/foo%20%25%3F%2f%40%25%20bar'; + Backbone.history.stop(); + Backbone.history = _.extend(new Backbone.History, {location: location}); + var MyRouter = Backbone.Router.extend({ + routes: { + 'myyjä/:query': function(query) { + assert.strictEqual(query, 'foo %?/@% bar'); + } + } + }); + new MyRouter; + Backbone.history.start({pushState: true}); + }); + + QUnit.test('newline in route', function(assert) { + assert.expect(1); + location.replace('http://example.com/stuff%0Anonsense?param=foo%0Abar'); + Backbone.history.stop(); + Backbone.history = _.extend(new Backbone.History, {location: location}); + var MyRouter = Backbone.Router.extend({ + routes: { + 'stuff\nnonsense': function() { + assert.ok(true); + } + } + }); + new MyRouter; + Backbone.history.start({pushState: true}); + }); + + QUnit.test('Router#execute receives callback, args, name.', function(assert) { + assert.expect(3); + location.replace('http://example.com#foo/123/bar?x=y'); + Backbone.history.stop(); + Backbone.history = _.extend(new Backbone.History, {location: location}); + var MyRouter = Backbone.Router.extend({ + routes: {'foo/:id/bar': 'foo'}, + foo: function(){}, + execute: function(callback, args, name) { + assert.strictEqual(callback, this.foo); + assert.deepEqual(args, ['123', 'x=y']); + assert.strictEqual(name, 'foo'); + } + }); + var myRouter = new MyRouter; + Backbone.history.start(); + }); + + QUnit.test('pushState to hashChange with only search params.', function(assert) { + assert.expect(1); + Backbone.history.stop(); + location.replace('http://example.com?a=b'); + location.replace = function(url) { + assert.strictEqual(url, '/#?a=b'); + }; + Backbone.history = _.extend(new Backbone.History, { + location: location, + history: null + }); + Backbone.history.start({pushState: true}); + }); + + QUnit.test('#3123 - History#navigate decodes before comparison.', function(assert) { + assert.expect(1); + Backbone.history.stop(); + location.replace('http://example.com/shop/search?keyword=short%20dress'); + Backbone.history = _.extend(new Backbone.History, { + location: location, + history: { + pushState: function(){ assert.ok(false); }, + replaceState: function(){ assert.ok(false); } + } + }); + Backbone.history.start({pushState: true}); + Backbone.history.navigate('shop/search?keyword=short%20dress', true); + assert.strictEqual(Backbone.history.fragment, 'shop/search?keyword=short dress'); + }); + + QUnit.test('#3175 - Urls in the params', function(assert) { + assert.expect(1); + Backbone.history.stop(); + location.replace('http://example.com#login?a=value&backUrl=https%3A%2F%2Fwww.msn.com%2Fidp%2Fidpdemo%3Fspid%3Dspdemo%26target%3Db'); + Backbone.history = _.extend(new Backbone.History, {location: location}); + var myRouter = new Backbone.Router; + myRouter.route('login', function(params) { + assert.strictEqual(params, 'a=value&backUrl=https%3A%2F%2Fwww.msn.com%2Fidp%2Fidpdemo%3Fspid%3Dspdemo%26target%3Db'); + }); + Backbone.history.start(); + }); + + QUnit.test('#3358 - pushState to hashChange transition with search params', function(assert) { + assert.expect(1); + Backbone.history.stop(); + location.replace('http://example.com/root?foo=bar'); + location.replace = function(url) { + assert.strictEqual(url, '/root#?foo=bar'); + }; + Backbone.history = _.extend(new Backbone.History, { + location: location, + history: { + pushState: undefined, + replaceState: undefined + } + }); + Backbone.history.start({root: '/root', pushState: true}); + }); + + QUnit.test("Paths that don't match the root should not match no root", function(assert) { + assert.expect(0); + location.replace('http://example.com/foo'); + Backbone.history.stop(); + Backbone.history = _.extend(new Backbone.History, {location: location}); + var MyRouter = Backbone.Router.extend({ + routes: { + foo: function(){ + assert.ok(false, 'should not match unless root matches'); + } + } + }); + var myRouter = new MyRouter; + Backbone.history.start({root: 'root', pushState: true}); + }); + + QUnit.test("Paths that don't match the root should not match roots of the same length", function(assert) { + assert.expect(0); + location.replace('http://example.com/xxxx/foo'); + Backbone.history.stop(); + Backbone.history = _.extend(new Backbone.History, {location: location}); + var MyRouter = Backbone.Router.extend({ + routes: { + foo: function(){ + assert.ok(false, 'should not match unless root matches'); + } + } + }); + var myRouter = new MyRouter; + Backbone.history.start({root: 'root', pushState: true}); + }); + + QUnit.test('roots with regex characters', function(assert) { + assert.expect(1); + location.replace('http://example.com/x+y.z/foo'); + Backbone.history.stop(); + Backbone.history = _.extend(new Backbone.History, {location: location}); + var MyRouter = Backbone.Router.extend({ + routes: {foo: function(){ assert.ok(true); }} + }); + var myRouter = new MyRouter; + Backbone.history.start({root: 'x+y.z', pushState: true}); + }); + + QUnit.test('roots with unicode characters', function(assert) { + assert.expect(1); + location.replace('http://example.com/®ooτ/foo'); + Backbone.history.stop(); + Backbone.history = _.extend(new Backbone.History, {location: location}); + var MyRouter = Backbone.Router.extend({ + routes: {foo: function(){ assert.ok(true); }} + }); + var myRouter = new MyRouter; + Backbone.history.start({root: '®ooτ', pushState: true}); + }); + + QUnit.test('roots without slash', function(assert) { + assert.expect(1); + location.replace('http://example.com/®ooτ'); + Backbone.history.stop(); + Backbone.history = _.extend(new Backbone.History, {location: location}); + var MyRouter = Backbone.Router.extend({ + routes: {'': function(){ assert.ok(true); }} + }); + var myRouter = new MyRouter; + Backbone.history.start({root: '®ooτ', pushState: true}); + }); + +})(); diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/backbone/test/setup/dom-setup.js b/ecomp-portal-FE/client/bower_components/lodash/vendor/backbone/test/setup/dom-setup.js new file mode 100644 index 00000000..f2242282 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/backbone/test/setup/dom-setup.js @@ -0,0 +1,4 @@ +$('body').append( + '<div id="qunit"></div>' + + '<div id="qunit-fixture"></div>' +); diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/backbone/test/setup/environment.js b/ecomp-portal-FE/client/bower_components/lodash/vendor/backbone/test/setup/environment.js new file mode 100644 index 00000000..c2441ac7 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/backbone/test/setup/environment.js @@ -0,0 +1,45 @@ +(function() { + + var sync = Backbone.sync; + var ajax = Backbone.ajax; + var emulateHTTP = Backbone.emulateHTTP; + var emulateJSON = Backbone.emulateJSON; + var history = window.history; + var pushState = history.pushState; + var replaceState = history.replaceState; + + QUnit.config.noglobals = true; + + QUnit.testStart(function() { + var env = QUnit.config.current.testEnvironment; + + // We never want to actually call these during tests. + history.pushState = history.replaceState = function(){}; + + // Capture ajax settings for comparison. + Backbone.ajax = function(settings) { + env.ajaxSettings = settings; + }; + + // Capture the arguments to Backbone.sync for comparison. + Backbone.sync = function(method, model, options) { + env.syncArgs = { + method: method, + model: model, + options: options + }; + sync.apply(this, arguments); + }; + + }); + + QUnit.testDone(function() { + Backbone.sync = sync; + Backbone.ajax = ajax; + Backbone.emulateHTTP = emulateHTTP; + Backbone.emulateJSON = emulateJSON; + history.pushState = pushState; + history.replaceState = replaceState; + }); + +})(); diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/backbone/test/sync.js b/ecomp-portal-FE/client/bower_components/lodash/vendor/backbone/test/sync.js new file mode 100644 index 00000000..8813f158 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/backbone/test/sync.js @@ -0,0 +1,239 @@ +(function() { + + var Library = Backbone.Collection.extend({ + url: function() { return '/library'; } + }); + var library; + + var attrs = { + title: 'The Tempest', + author: 'Bill Shakespeare', + length: 123 + }; + + QUnit.module('Backbone.sync', { + + beforeEach: function(assert) { + library = new Library; + library.create(attrs, {wait: false}); + }, + + afterEach: function(assert) { + Backbone.emulateHTTP = false; + } + + }); + + QUnit.test('read', function(assert) { + assert.expect(4); + library.fetch(); + assert.equal(this.ajaxSettings.url, '/library'); + assert.equal(this.ajaxSettings.type, 'GET'); + assert.equal(this.ajaxSettings.dataType, 'json'); + assert.ok(_.isEmpty(this.ajaxSettings.data)); + }); + + QUnit.test('passing data', function(assert) { + assert.expect(3); + library.fetch({data: {a: 'a', one: 1}}); + assert.equal(this.ajaxSettings.url, '/library'); + assert.equal(this.ajaxSettings.data.a, 'a'); + assert.equal(this.ajaxSettings.data.one, 1); + }); + + QUnit.test('create', function(assert) { + assert.expect(6); + assert.equal(this.ajaxSettings.url, '/library'); + assert.equal(this.ajaxSettings.type, 'POST'); + assert.equal(this.ajaxSettings.dataType, 'json'); + var data = JSON.parse(this.ajaxSettings.data); + assert.equal(data.title, 'The Tempest'); + assert.equal(data.author, 'Bill Shakespeare'); + assert.equal(data.length, 123); + }); + + QUnit.test('update', function(assert) { + assert.expect(7); + library.first().save({id: '1-the-tempest', author: 'William Shakespeare'}); + assert.equal(this.ajaxSettings.url, '/library/1-the-tempest'); + assert.equal(this.ajaxSettings.type, 'PUT'); + assert.equal(this.ajaxSettings.dataType, 'json'); + var data = JSON.parse(this.ajaxSettings.data); + assert.equal(data.id, '1-the-tempest'); + assert.equal(data.title, 'The Tempest'); + assert.equal(data.author, 'William Shakespeare'); + assert.equal(data.length, 123); + }); + + QUnit.test('update with emulateHTTP and emulateJSON', function(assert) { + assert.expect(7); + library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'}, { + emulateHTTP: true, + emulateJSON: true + }); + assert.equal(this.ajaxSettings.url, '/library/2-the-tempest'); + assert.equal(this.ajaxSettings.type, 'POST'); + assert.equal(this.ajaxSettings.dataType, 'json'); + assert.equal(this.ajaxSettings.data._method, 'PUT'); + var data = JSON.parse(this.ajaxSettings.data.model); + assert.equal(data.id, '2-the-tempest'); + assert.equal(data.author, 'Tim Shakespeare'); + assert.equal(data.length, 123); + }); + + QUnit.test('update with just emulateHTTP', function(assert) { + assert.expect(6); + library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'}, { + emulateHTTP: true + }); + assert.equal(this.ajaxSettings.url, '/library/2-the-tempest'); + assert.equal(this.ajaxSettings.type, 'POST'); + assert.equal(this.ajaxSettings.contentType, 'application/json'); + var data = JSON.parse(this.ajaxSettings.data); + assert.equal(data.id, '2-the-tempest'); + assert.equal(data.author, 'Tim Shakespeare'); + assert.equal(data.length, 123); + }); + + QUnit.test('update with just emulateJSON', function(assert) { + assert.expect(6); + library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'}, { + emulateJSON: true + }); + assert.equal(this.ajaxSettings.url, '/library/2-the-tempest'); + assert.equal(this.ajaxSettings.type, 'PUT'); + assert.equal(this.ajaxSettings.contentType, 'application/x-www-form-urlencoded'); + var data = JSON.parse(this.ajaxSettings.data.model); + assert.equal(data.id, '2-the-tempest'); + assert.equal(data.author, 'Tim Shakespeare'); + assert.equal(data.length, 123); + }); + + QUnit.test('read model', function(assert) { + assert.expect(3); + library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'}); + library.first().fetch(); + assert.equal(this.ajaxSettings.url, '/library/2-the-tempest'); + assert.equal(this.ajaxSettings.type, 'GET'); + assert.ok(_.isEmpty(this.ajaxSettings.data)); + }); + + QUnit.test('destroy', function(assert) { + assert.expect(3); + library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'}); + library.first().destroy({wait: true}); + assert.equal(this.ajaxSettings.url, '/library/2-the-tempest'); + assert.equal(this.ajaxSettings.type, 'DELETE'); + assert.equal(this.ajaxSettings.data, null); + }); + + QUnit.test('destroy with emulateHTTP', function(assert) { + assert.expect(3); + library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'}); + library.first().destroy({ + emulateHTTP: true, + emulateJSON: true + }); + assert.equal(this.ajaxSettings.url, '/library/2-the-tempest'); + assert.equal(this.ajaxSettings.type, 'POST'); + assert.equal(JSON.stringify(this.ajaxSettings.data), '{"_method":"DELETE"}'); + }); + + QUnit.test('urlError', function(assert) { + assert.expect(2); + var model = new Backbone.Model(); + assert.raises(function() { + model.fetch(); + }); + model.fetch({url: '/one/two'}); + assert.equal(this.ajaxSettings.url, '/one/two'); + }); + + QUnit.test('#1052 - `options` is optional.', function(assert) { + assert.expect(0); + var model = new Backbone.Model(); + model.url = '/test'; + Backbone.sync('create', model); + }); + + QUnit.test('Backbone.ajax', function(assert) { + assert.expect(1); + Backbone.ajax = function(settings){ + assert.strictEqual(settings.url, '/test'); + }; + var model = new Backbone.Model(); + model.url = '/test'; + Backbone.sync('create', model); + }); + + QUnit.test('Call provided error callback on error.', function(assert) { + assert.expect(1); + var model = new Backbone.Model; + model.url = '/test'; + Backbone.sync('read', model, { + error: function() { assert.ok(true); } + }); + this.ajaxSettings.error(); + }); + + QUnit.test('Use Backbone.emulateHTTP as default.', function(assert) { + assert.expect(2); + var model = new Backbone.Model; + model.url = '/test'; + + Backbone.emulateHTTP = true; + model.sync('create', model); + assert.strictEqual(this.ajaxSettings.emulateHTTP, true); + + Backbone.emulateHTTP = false; + model.sync('create', model); + assert.strictEqual(this.ajaxSettings.emulateHTTP, false); + }); + + QUnit.test('Use Backbone.emulateJSON as default.', function(assert) { + assert.expect(2); + var model = new Backbone.Model; + model.url = '/test'; + + Backbone.emulateJSON = true; + model.sync('create', model); + assert.strictEqual(this.ajaxSettings.emulateJSON, true); + + Backbone.emulateJSON = false; + model.sync('create', model); + assert.strictEqual(this.ajaxSettings.emulateJSON, false); + }); + + QUnit.test('#1756 - Call user provided beforeSend function.', function(assert) { + assert.expect(4); + Backbone.emulateHTTP = true; + var model = new Backbone.Model; + model.url = '/test'; + var xhr = { + setRequestHeader: function(header, value) { + assert.strictEqual(header, 'X-HTTP-Method-Override'); + assert.strictEqual(value, 'DELETE'); + } + }; + model.sync('delete', model, { + beforeSend: function(_xhr) { + assert.ok(_xhr === xhr); + return false; + } + }); + assert.strictEqual(this.ajaxSettings.beforeSend(xhr), false); + }); + + QUnit.test('#2928 - Pass along `textStatus` and `errorThrown`.', function(assert) { + assert.expect(2); + var model = new Backbone.Model; + model.url = '/test'; + model.on('error', function(m, xhr, options) { + assert.strictEqual(options.textStatus, 'textStatus'); + assert.strictEqual(options.errorThrown, 'errorThrown'); + }); + model.fetch(); + this.ajaxSettings.error({}, 'textStatus', 'errorThrown'); + }); + +})(); diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/backbone/test/view.js b/ecomp-portal-FE/client/bower_components/lodash/vendor/backbone/test/view.js new file mode 100644 index 00000000..faf3445b --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/backbone/test/view.js @@ -0,0 +1,495 @@ +(function() { + + var view; + + QUnit.module('Backbone.View', { + + beforeEach: function(assert) { + $('#qunit-fixture').append( + '<div id="testElement"><h1>Test</h1></div>' + ); + + view = new Backbone.View({ + id: 'test-view', + className: 'test-view', + other: 'non-special-option' + }); + }, + + afterEach: function() { + $('#testElement').remove(); + $('#test-view').remove(); + } + + }); + + QUnit.test('constructor', function(assert) { + assert.expect(3); + assert.equal(view.el.id, 'test-view'); + assert.equal(view.el.className, 'test-view'); + assert.equal(view.el.other, void 0); + }); + + QUnit.test('$', function(assert) { + assert.expect(2); + var myView = new Backbone.View; + myView.setElement('<p><a><b>test</b></a></p>'); + var result = myView.$('a b'); + + assert.strictEqual(result[0].innerHTML, 'test'); + assert.ok(result.length === +result.length); + }); + + QUnit.test('$el', function(assert) { + assert.expect(3); + var myView = new Backbone.View; + myView.setElement('<p><a><b>test</b></a></p>'); + assert.strictEqual(myView.el.nodeType, 1); + + assert.ok(myView.$el instanceof Backbone.$); + assert.strictEqual(myView.$el[0], myView.el); + }); + + QUnit.test('initialize', function(assert) { + assert.expect(1); + var View = Backbone.View.extend({ + initialize: function() { + this.one = 1; + } + }); + + assert.strictEqual(new View().one, 1); + }); + + QUnit.test('render', function(assert) { + assert.expect(1); + var myView = new Backbone.View; + assert.equal(myView.render(), myView, '#render returns the view instance'); + }); + + QUnit.test('delegateEvents', function(assert) { + assert.expect(6); + var counter1 = 0, counter2 = 0; + + var myView = new Backbone.View({el: '#testElement'}); + myView.increment = function(){ counter1++; }; + myView.$el.on('click', function(){ counter2++; }); + + var events = {'click h1': 'increment'}; + + myView.delegateEvents(events); + myView.$('h1').trigger('click'); + assert.equal(counter1, 1); + assert.equal(counter2, 1); + + myView.$('h1').trigger('click'); + assert.equal(counter1, 2); + assert.equal(counter2, 2); + + myView.delegateEvents(events); + myView.$('h1').trigger('click'); + assert.equal(counter1, 3); + assert.equal(counter2, 3); + }); + + QUnit.test('delegate', function(assert) { + assert.expect(3); + var myView = new Backbone.View({el: '#testElement'}); + myView.delegate('click', 'h1', function() { + assert.ok(true); + }); + myView.delegate('click', function() { + assert.ok(true); + }); + myView.$('h1').trigger('click'); + + assert.equal(myView.delegate(), myView, '#delegate returns the view instance'); + }); + + QUnit.test('delegateEvents allows functions for callbacks', function(assert) { + assert.expect(3); + var myView = new Backbone.View({el: '<p></p>'}); + myView.counter = 0; + + var events = { + click: function() { + this.counter++; + } + }; + + myView.delegateEvents(events); + myView.$el.trigger('click'); + assert.equal(myView.counter, 1); + + myView.$el.trigger('click'); + assert.equal(myView.counter, 2); + + myView.delegateEvents(events); + myView.$el.trigger('click'); + assert.equal(myView.counter, 3); + }); + + + QUnit.test('delegateEvents ignore undefined methods', function(assert) { + assert.expect(0); + var myView = new Backbone.View({el: '<p></p>'}); + myView.delegateEvents({'click': 'undefinedMethod'}); + myView.$el.trigger('click'); + }); + + QUnit.test('undelegateEvents', function(assert) { + assert.expect(7); + var counter1 = 0, counter2 = 0; + + var myView = new Backbone.View({el: '#testElement'}); + myView.increment = function(){ counter1++; }; + myView.$el.on('click', function(){ counter2++; }); + + var events = {'click h1': 'increment'}; + + myView.delegateEvents(events); + myView.$('h1').trigger('click'); + assert.equal(counter1, 1); + assert.equal(counter2, 1); + + myView.undelegateEvents(); + myView.$('h1').trigger('click'); + assert.equal(counter1, 1); + assert.equal(counter2, 2); + + myView.delegateEvents(events); + myView.$('h1').trigger('click'); + assert.equal(counter1, 2); + assert.equal(counter2, 3); + + assert.equal(myView.undelegateEvents(), myView, '#undelegateEvents returns the view instance'); + }); + + QUnit.test('undelegate', function(assert) { + assert.expect(1); + var myView = new Backbone.View({el: '#testElement'}); + myView.delegate('click', function() { assert.ok(false); }); + myView.delegate('click', 'h1', function() { assert.ok(false); }); + + myView.undelegate('click'); + + myView.$('h1').trigger('click'); + myView.$el.trigger('click'); + + assert.equal(myView.undelegate(), myView, '#undelegate returns the view instance'); + }); + + QUnit.test('undelegate with passed handler', function(assert) { + assert.expect(1); + var myView = new Backbone.View({el: '#testElement'}); + var listener = function() { assert.ok(false); }; + myView.delegate('click', listener); + myView.delegate('click', function() { assert.ok(true); }); + myView.undelegate('click', listener); + myView.$el.trigger('click'); + }); + + QUnit.test('undelegate with selector', function(assert) { + assert.expect(2); + var myView = new Backbone.View({el: '#testElement'}); + myView.delegate('click', function() { assert.ok(true); }); + myView.delegate('click', 'h1', function() { assert.ok(false); }); + myView.undelegate('click', 'h1'); + myView.$('h1').trigger('click'); + myView.$el.trigger('click'); + }); + + QUnit.test('undelegate with handler and selector', function(assert) { + assert.expect(2); + var myView = new Backbone.View({el: '#testElement'}); + myView.delegate('click', function() { assert.ok(true); }); + var handler = function(){ assert.ok(false); }; + myView.delegate('click', 'h1', handler); + myView.undelegate('click', 'h1', handler); + myView.$('h1').trigger('click'); + myView.$el.trigger('click'); + }); + + QUnit.test('tagName can be provided as a string', function(assert) { + assert.expect(1); + var View = Backbone.View.extend({ + tagName: 'span' + }); + + assert.equal(new View().el.tagName, 'SPAN'); + }); + + QUnit.test('tagName can be provided as a function', function(assert) { + assert.expect(1); + var View = Backbone.View.extend({ + tagName: function() { + return 'p'; + } + }); + + assert.ok(new View().$el.is('p')); + }); + + QUnit.test('_ensureElement with DOM node el', function(assert) { + assert.expect(1); + var View = Backbone.View.extend({ + el: document.body + }); + + assert.equal(new View().el, document.body); + }); + + QUnit.test('_ensureElement with string el', function(assert) { + assert.expect(3); + var View = Backbone.View.extend({ + el: 'body' + }); + assert.strictEqual(new View().el, document.body); + + View = Backbone.View.extend({ + el: '#testElement > h1' + }); + assert.strictEqual(new View().el, $('#testElement > h1').get(0)); + + View = Backbone.View.extend({ + el: '#nonexistent' + }); + assert.ok(!new View().el); + }); + + QUnit.test('with className and id functions', function(assert) { + assert.expect(2); + var View = Backbone.View.extend({ + className: function() { + return 'className'; + }, + id: function() { + return 'id'; + } + }); + + assert.strictEqual(new View().el.className, 'className'); + assert.strictEqual(new View().el.id, 'id'); + }); + + QUnit.test('with attributes', function(assert) { + assert.expect(2); + var View = Backbone.View.extend({ + attributes: { + 'id': 'id', + 'class': 'class' + } + }); + + assert.strictEqual(new View().el.className, 'class'); + assert.strictEqual(new View().el.id, 'id'); + }); + + QUnit.test('with attributes as a function', function(assert) { + assert.expect(1); + var View = Backbone.View.extend({ + attributes: function() { + return {'class': 'dynamic'}; + } + }); + + assert.strictEqual(new View().el.className, 'dynamic'); + }); + + QUnit.test('should default to className/id properties', function(assert) { + assert.expect(4); + var View = Backbone.View.extend({ + className: 'backboneClass', + id: 'backboneId', + attributes: { + 'class': 'attributeClass', + 'id': 'attributeId' + } + }); + + var myView = new View; + assert.strictEqual(myView.el.className, 'backboneClass'); + assert.strictEqual(myView.el.id, 'backboneId'); + assert.strictEqual(myView.$el.attr('class'), 'backboneClass'); + assert.strictEqual(myView.$el.attr('id'), 'backboneId'); + }); + + QUnit.test('multiple views per element', function(assert) { + assert.expect(3); + var count = 0; + var $el = $('<p></p>'); + + var View = Backbone.View.extend({ + el: $el, + events: { + click: function() { + count++; + } + } + }); + + var view1 = new View; + $el.trigger('click'); + assert.equal(1, count); + + var view2 = new View; + $el.trigger('click'); + assert.equal(3, count); + + view1.delegateEvents(); + $el.trigger('click'); + assert.equal(5, count); + }); + + QUnit.test('custom events', function(assert) { + assert.expect(2); + var View = Backbone.View.extend({ + el: $('body'), + events: { + fake$event: function() { assert.ok(true); } + } + }); + + var myView = new View; + $('body').trigger('fake$event').trigger('fake$event'); + + $('body').off('fake$event'); + $('body').trigger('fake$event'); + }); + + QUnit.test('#1048 - setElement uses provided object.', function(assert) { + assert.expect(2); + var $el = $('body'); + + var myView = new Backbone.View({el: $el}); + assert.ok(myView.$el === $el); + + myView.setElement($el = $($el)); + assert.ok(myView.$el === $el); + }); + + QUnit.test('#986 - Undelegate before changing element.', function(assert) { + assert.expect(1); + var button1 = $('<button></button>'); + var button2 = $('<button></button>'); + + var View = Backbone.View.extend({ + events: { + click: function(e) { + assert.ok(myView.el === e.target); + } + } + }); + + var myView = new View({el: button1}); + myView.setElement(button2); + + button1.trigger('click'); + button2.trigger('click'); + }); + + QUnit.test('#1172 - Clone attributes object', function(assert) { + assert.expect(2); + var View = Backbone.View.extend({ + attributes: {foo: 'bar'} + }); + + var view1 = new View({id: 'foo'}); + assert.strictEqual(view1.el.id, 'foo'); + + var view2 = new View(); + assert.ok(!view2.el.id); + }); + + QUnit.test('views stopListening', function(assert) { + assert.expect(0); + var View = Backbone.View.extend({ + initialize: function() { + this.listenTo(this.model, 'all x', function(){ assert.ok(false); }); + this.listenTo(this.collection, 'all x', function(){ assert.ok(false); }); + } + }); + + var myView = new View({ + model: new Backbone.Model, + collection: new Backbone.Collection + }); + + myView.stopListening(); + myView.model.trigger('x'); + myView.collection.trigger('x'); + }); + + QUnit.test('Provide function for el.', function(assert) { + assert.expect(2); + var View = Backbone.View.extend({ + el: function() { + return '<p><a></a></p>'; + } + }); + + var myView = new View; + assert.ok(myView.$el.is('p')); + assert.ok(myView.$el.has('a')); + }); + + QUnit.test('events passed in options', function(assert) { + assert.expect(1); + var counter = 0; + + var View = Backbone.View.extend({ + el: '#testElement', + increment: function() { + counter++; + } + }); + + var myView = new View({ + events: { + 'click h1': 'increment' + } + }); + + myView.$('h1').trigger('click').trigger('click'); + assert.equal(counter, 2); + }); + + QUnit.test('remove', function(assert) { + assert.expect(2); + var myView = new Backbone.View; + document.body.appendChild(view.el); + + myView.delegate('click', function() { assert.ok(false); }); + myView.listenTo(myView, 'all x', function() { assert.ok(false); }); + + assert.equal(myView.remove(), myView, '#remove returns the view instance'); + myView.$el.trigger('click'); + myView.trigger('x'); + + // In IE8 and below, parentNode still exists but is not document.body. + assert.notEqual(myView.el.parentNode, document.body); + }); + + QUnit.test('setElement', function(assert) { + assert.expect(3); + var myView = new Backbone.View({ + events: { + click: function() { assert.ok(false); } + } + }); + myView.events = { + click: function() { assert.ok(true); } + }; + var oldEl = myView.el; + var $oldEl = myView.$el; + + myView.setElement(document.createElement('div')); + + $oldEl.click(); + myView.$el.click(); + + assert.notEqual(oldEl, myView.el); + assert.notEqual($oldEl, myView.$el); + }); + +})(); diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/license.txt b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/license.txt new file mode 100644 index 00000000..ba43b751 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/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/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/blank.gif b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/blank.gif Binary files differnew file mode 100644 index 00000000..6865c960 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/blank.gif diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/buttonBg.png b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/buttonBg.png Binary files differnew file mode 100644 index 00000000..f367b427 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/buttonBg.png diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/buttonBgHover.png b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/buttonBgHover.png Binary files differnew file mode 100644 index 00000000..cd37a0d5 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/buttonBgHover.png diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/debugger.css b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/debugger.css new file mode 100644 index 00000000..ba55c7ea --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/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/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/detach.png b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/detach.png Binary files differnew file mode 100644 index 00000000..0ddb9a17 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/detach.png diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/detachHover.png b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/detachHover.png Binary files differnew file mode 100644 index 00000000..e4192729 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/detachHover.png diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/disable.gif b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/disable.gif Binary files differnew file mode 100644 index 00000000..dd9eb0e3 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/disable.gif diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/disable.png b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/disable.png Binary files differnew file mode 100644 index 00000000..c28bcdf2 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/disable.png diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/disableHover.gif b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/disableHover.gif Binary files differnew file mode 100644 index 00000000..70565a83 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/disableHover.gif diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/disableHover.png b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/disableHover.png Binary files differnew file mode 100644 index 00000000..26fe3754 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/disableHover.png diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/down.png b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/down.png Binary files differnew file mode 100644 index 00000000..acbbd30c --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/down.png diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/downActive.png b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/downActive.png Binary files differnew file mode 100644 index 00000000..f4312b2f --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/downActive.png diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/downHover.png b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/downHover.png Binary files differnew file mode 100644 index 00000000..8144e637 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/downHover.png diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/errorIcon-sm.png b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/errorIcon-sm.png Binary files differnew file mode 100644 index 00000000..0c377e30 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/errorIcon-sm.png diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/errorIcon.gif b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/errorIcon.gif Binary files differnew file mode 100644 index 00000000..8ee8116a --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/errorIcon.gif diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/errorIcon.png b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/errorIcon.png Binary files differnew file mode 100644 index 00000000..2d75261b --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/errorIcon.png diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/firebug-1.3a2.css b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/firebug-1.3a2.css new file mode 100644 index 00000000..42f9faf5 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/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/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/firebug.IE6.css b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/firebug.IE6.css new file mode 100644 index 00000000..13a41d28 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/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/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/firebug.css b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/firebug.css new file mode 100644 index 00000000..a1465ec5 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/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/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/firebug.html b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/firebug.html new file mode 100644 index 00000000..aa078099 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/firebug.html @@ -0,0 +1,215 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/DTD/strict.dtd"> +<html> +<head> +<meta http-equiv="content-type" content="text/html; charset=utf-8"> +<title>Firebug Lite</title> +<!-- An empty script to avoid FOUC when loading the stylesheet --> +<script type="text/javascript"></script> +<style type="text/css" media="screen">@import "firebug.css";</style> +<style>html,body{margin:0;padding:0;overflow:hidden;}</style> +</head> +<body class="fbBody"> +<table id="fbChrome" cellpadding="0" cellspacing="0" border="0"> + <tbody> + <tr> + <!-- Interface - Top Area --> + <td id="fbTop" colspan="2"> + + <!-- + <div> + --><!-- <span id="fbToolbarErrors" class="fbErrors">2 errors</span> --><!-- + <input type="text" id="fbToolbarSearch" /> + </div> + --> + + <!-- Window Buttons --> + <div id="fbWindowButtons"> + <a id="fbWindow_btDeactivate" class="fbSmallButton fbHover" title="Deactivate Firebug for this web page"> </a> + <a id="fbWindow_btDetach" class="fbSmallButton fbHover" title="Open Firebug in popup window"> </a> + <a id="fbWindow_btClose" class="fbSmallButton fbHover" title="Minimize Firebug"> </a> + </div> + + <!-- Toolbar buttons and Status Bar --> + <div id="fbToolbar"> + <div id="fbToolbarContent"> + + <!-- Firebug Button --> + <span id="fbToolbarIcon"> + <a id="fbFirebugButton" class="fbIconButton" class="fbHover" target="_blank"> </a> + </span> + + <!-- + <span id="fbLeftToolbarErrors" class="fbErrors">2 errors</span> + --> + + <!-- Toolbar Buttons --> + <span id="fbToolbarButtons"> + <!-- Fixed Toolbar Buttons --> + <span id="fbFixedButtons"> + <a id="fbChrome_btInspect" class="fbButton fbHover" title="Click an element in the page to inspect">Inspect</a> + </span> + + <!-- Console Panel Toolbar Buttons --> + <span id="fbConsoleButtons" class="fbToolbarButtons"> + <a id="fbConsole_btClear" class="fbButton fbHover" title="Clear the console">Clear</a> + </span> + + <!-- HTML Panel Toolbar Buttons --> + <!-- + <span id="fbHTMLButtons" class="fbToolbarButtons"> + <a id="fbHTML_btEdit" class="fbHover" title="Edit this HTML">Edit</a> + </span> + --> + </span> + + <!-- Status Bar --> + <span id="fbStatusBarBox"> + <span class="fbToolbarSeparator"></span> + <!-- HTML Panel Status Bar --> + <!-- + <span id="fbHTMLStatusBar" class="fbStatusBar fbToolbarButtons"> + </span> + --> + </span> + + </div> + + </div> + + <!-- PanelBars --> + <div id="fbPanelBarBox"> + + <!-- Main PanelBar --> + <div id="fbPanelBar1" class="fbPanelBar"> + <a id="fbConsoleTab" class="fbTab fbHover"> + <span class="fbTabL"></span> + <span class="fbTabText">Console</span> + <span class="fbTabMenuTarget"></span> + <span class="fbTabR"></span> + </a> + <a id="fbHTMLTab" class="fbTab fbHover"> + <span class="fbTabL"></span> + <span class="fbTabText">HTML</span> + <span class="fbTabR"></span> + </a> + <a class="fbTab fbHover"> + <span class="fbTabL"></span> + <span class="fbTabText">CSS</span> + <span class="fbTabR"></span> + </a> + <a class="fbTab fbHover"> + <span class="fbTabL"></span> + <span class="fbTabText">Script</span> + <span class="fbTabR"></span> + </a> + <a class="fbTab fbHover"> + <span class="fbTabL"></span> + <span class="fbTabText">DOM</span> + <span class="fbTabR"></span> + </a> + </div> + + <!-- Side PanelBars --> + <div id="fbPanelBar2Box" class="hide"> + <div id="fbPanelBar2" class="fbPanelBar"> + <!-- + <a class="fbTab fbHover"> + <span class="fbTabL"></span> + <span class="fbTabText">Style</span> + <span class="fbTabR"></span> + </a> + <a class="fbTab fbHover"> + <span class="fbTabL"></span> + <span class="fbTabText">Layout</span> + <span class="fbTabR"></span> + </a> + <a class="fbTab fbHover"> + <span class="fbTabL"></span> + <span class="fbTabText">DOM</span> + <span class="fbTabR"></span> + </a> + --> + </div> + </div> + + </div> + + <!-- Horizontal Splitter --> + <div id="fbHSplitter"> </div> + + </td> + </tr> + + <!-- Interface - Main Area --> + <tr id="fbContent"> + + <!-- Panels --> + <td id="fbPanelBox1"> + <div id="fbPanel1" class="fbFitHeight"> + <div id="fbConsole" class="fbPanel"></div> + <div id="fbHTML" class="fbPanel"></div> + </div> + </td> + + <!-- Side Panel Box --> + <td id="fbPanelBox2" class="hide"> + + <!-- VerticalSplitter --> + <div id="fbVSplitter" class="fbVSplitter"> </div> + + <!-- Side Panels --> + <div id="fbPanel2" class="fbFitHeight"> + + <!-- HTML Side Panels --> + <div id="fbHTML_Style" class="fbPanel"></div> + <div id="fbHTML_Layout" class="fbPanel"></div> + <div id="fbHTML_DOM" class="fbPanel"></div> + + </div> + + <!-- Large Command Line --> + <textarea id="fbLargeCommandLine" class="fbFitHeight"></textarea> + + <!-- Large Command Line Buttons --> + <div id="fbLargeCommandButtons"> + <a id="fbCommand_btRun" class="fbButton fbHover">Run</a> + <a id="fbCommand_btClear" class="fbButton fbHover">Clear</a> + + <a id="fbSmallCommandLineIcon" class="fbSmallButton fbHover"></a> + </div> + + </td> + + </tr> + + <!-- Interface - Bottom Area --> + <tr id="fbBottom" class="hide"> + + <!-- Command Line --> + <td id="fbCommand" colspan="2"> + <div id="fbCommandBox"> + <div id="fbCommandIcon">>>></div> + <input id="fbCommandLine" name="fbCommandLine" type="text" /> + <a id="fbLargeCommandLineIcon" class="fbSmallButton fbHover"></a> + </div> + </td> + + </tr> + + </tbody> +</table> +<span id="fbMiniChrome"> + <span id="fbMiniContent"> + <span id="fbMiniIcon" title="Open Firebug Lite"></span> + <span id="fbMiniErrors" class="fbErrors"><!-- 2 errors --></span> + </span> +</span> +<!-- +<div id="fbErrorPopup"> + <div id="fbErrorPopupContent"> + <div id="fbErrorIndicator" class="fbErrors">2 errors</div> + </div> +</div> + --> +</body> +</html>
\ No newline at end of file diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/firebug.png b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/firebug.png Binary files differnew file mode 100644 index 00000000..e10affeb --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/firebug.png diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/group.gif b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/group.gif Binary files differnew file mode 100644 index 00000000..8db97c21 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/group.gif diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/html.css b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/html.css new file mode 100644 index 00000000..5b7c5f4b --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/html.css @@ -0,0 +1,272 @@ +/* See license.txt for terms of usage */ + +.panelNode-html { + -moz-box-sizing: padding-box; + padding: 4px 0 0 2px; +} + +.nodeBox { + position: relative; + font-family: Monaco, monospace; + padding-left: 13px; + -moz-user-select: -moz-none; +} +.nodeBox.search-selection { + -moz-user-select: text; +} +.twisty { + position: absolute; + left: 0px; + top: 0px; + width: 14px; + height: 14px; +} + +.nodeChildBox { + margin-left: 12px; + display: none; +} + +.nodeLabel, +.nodeCloseLabel { + margin: -2px 2px 0 2px; + border: 2px solid transparent; + -moz-border-radius: 3px; + padding: 0 2px; + color: #000088; +} + +.nodeCloseLabel { + display: none; +} + +.nodeTag { + cursor: pointer; + color: blue; +} + +.nodeValue { + color: #FF0000; + font-weight: normal; +} + +.nodeText, +.nodeComment { + margin: 0 2px; + vertical-align: top; +} + +.nodeText { + color: #333333; +} + +.nodeWhiteSpace { + border: 1px solid LightGray; + white-space: pre; /* otherwise the border will be collapsed around zero pixels */ + margin-left: 1px; + color: gray; +} + + +.nodeWhiteSpace_Space { + border: 1px solid #ddd; +} + +.nodeTextEntity { + border: 1px solid gray; + white-space: pre; /* otherwise the border will be collapsed around zero pixels */ + margin-left: 1px; +} + +.nodeComment { + color: DarkGreen; +} + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +.nodeBox.highlightOpen > .nodeLabel { + background-color: #EEEEEE; +} + +.nodeBox.highlightOpen > .nodeCloseLabel, +.nodeBox.highlightOpen > .nodeChildBox, +.nodeBox.open > .nodeCloseLabel, +.nodeBox.open > .nodeChildBox { + display: block; +} + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +.nodeBox.selected > .nodeLabel > .nodeLabelBox, +.nodeBox.selected > .nodeLabel { + border-color: Highlight; + background-color: Highlight; + color: HighlightText !important; +} + +.nodeBox.selected > .nodeLabel > .nodeLabelBox, +.nodeBox.selected > .nodeLabel > .nodeLabelBox > .nodeTag, +.nodeBox.selected > .nodeLabel > .nodeLabelBox > .nodeAttr > .nodeValue, +.nodeBox.selected > .nodeLabel > .nodeLabelBox > .nodeText { + color: inherit !important; +} + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +.nodeBox.highlighted > .nodeLabel { + border-color: Highlight !important; + background-color: cyan !important; + color: #000000 !important; +} + +.nodeBox.highlighted > .nodeLabel > .nodeLabelBox, +.nodeBox.highlighted > .nodeLabel > .nodeLabelBox > .nodeTag, +.nodeBox.highlighted > .nodeLabel > .nodeLabelBox > .nodeAttr > .nodeValue, +.nodeBox.highlighted > .nodeLabel > .nodeLabelBox > .nodeText { + color: #000000 !important; +} + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +.nodeBox.nodeHidden .nodeLabel > .nodeLabelBox, +.nodeBox.nodeHidden .nodeCloseLabel, +.nodeBox.nodeHidden .nodeLabel > .nodeLabelBox > .nodeText, +.nodeBox.nodeHidden .nodeText { + color: #888888; +} + +.nodeBox.nodeHidden .nodeLabel > .nodeLabelBox > .nodeTag, +.nodeBox.nodeHidden .nodeCloseLabel > .nodeCloseLabelBox > .nodeTag { + color: #5F82D9; +} + +.nodeBox.nodeHidden .nodeLabel > .nodeLabelBox > .nodeAttr > .nodeValue { + color: #D86060; +} + +.nodeBox.nodeHidden.selected > .nodeLabel > .nodeLabelBox, +.nodeBox.nodeHidden.selected > .nodeLabel > .nodeLabelBox > .nodeTag, +.nodeBox.nodeHidden.selected > .nodeLabel > .nodeLabelBox > .nodeAttr > .nodeValue, +.nodeBox.nodeHidden.selected > .nodeLabel > .nodeLabelBox > .nodeText { + color: SkyBlue !important; +} + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +.nodeBox.mutated > .nodeLabel, +.nodeAttr.mutated, +.nodeValue.mutated, +.nodeText.mutated, +.nodeBox.mutated > .nodeText { + background-color: #EFFF79; + color: #FF0000 !important; +} + +.nodeBox.selected.mutated > .nodeLabel, +.nodeBox.selected.mutated > .nodeLabel > .nodeLabelBox, +.nodeBox.selected > .nodeLabel > .nodeLabelBox > .nodeAttr.mutated > .nodeValue, +.nodeBox.selected > .nodeLabel > .nodeLabelBox > .nodeAttr > .nodeValue.mutated, +.nodeBox.selected > .nodeLabel > .nodeLabelBox > .nodeText.mutated { + background-color: #EFFF79; + border-color: #EFFF79; + color: #FF0000 !important; +} + +/************************************************************************************************/ + +.logRow-dirxml { + padding-left: 0; +} + +.soloElement > .nodeBox { + padding-left: 0; +} + +.useA11y .nodeLabel.focused { + outline: 2px solid #FF9933; + -moz-outline-radius: 3px; + outline-offset: -2px; +} + +.useA11y .nodeLabelBox:focus { + outline: none; +} + +/************************************************************************************************/ + +.breakpointCode .twisty { + display: none; +} + +.breakpointCode .nodeBox.containerNodeBox, +.breakpointCode .nodeLabel { + padding-left: 0px; + margin-left: 0px; + font-family: Monaco, monospace !important; +} + +.breakpointCode .nodeTag, +.breakpointCode .nodeAttr, +.breakpointCode .nodeText, +.breakpointCode .nodeValue, +.breakpointCode .nodeLabel { + color: DarkGreen !important; +} + +.breakpointMutationType { + position: absolute; + top: 4px; + right: 20px; + color: gray; +} + + + + + + +/************************************************************************************************/ +/************************************************************************************************/ +/************************************************************************************************/ +/************************************************************************************************/ +/************************************************************************************************/ +/************************************************************************************************/ +/************************************************************************************************/ +/************************************************************************************************/ +/************************************************************************************************/ +/************************************************************************************************/ + + + +/************************************************************************************************/ +/* 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(twistyClosed.png); + 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(twistyOpen.png); +} + +.twisty { + background-position: 4px 4px; +}
\ No newline at end of file diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/infoIcon.gif b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/infoIcon.gif Binary files differnew file mode 100644 index 00000000..0618e208 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/infoIcon.gif diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/infoIcon.png b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/infoIcon.png Binary files differnew file mode 100644 index 00000000..da1e5334 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/infoIcon.png diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/loading_16.gif b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/loading_16.gif Binary files differnew file mode 100644 index 00000000..085ccaec --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/loading_16.gif diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/min.png b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/min.png Binary files differnew file mode 100644 index 00000000..1034d66f --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/min.png diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/minHover.png b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/minHover.png Binary files differnew file mode 100644 index 00000000..b0d1e1af --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/minHover.png diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/off.png b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/off.png Binary files differnew file mode 100644 index 00000000..b70b1d24 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/off.png diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/offHover.png b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/offHover.png Binary files differnew file mode 100644 index 00000000..f3670f19 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/offHover.png diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/pixel_transparent.gif b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/pixel_transparent.gif Binary files differnew file mode 100644 index 00000000..6865c960 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/pixel_transparent.gif diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/roundCorner.svg b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/roundCorner.svg new file mode 100644 index 00000000..2dfa7280 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/roundCorner.svg @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg xmlns="http://www.w3.org/2000/svg"> + <rect fill="white" x="0" y="0" width="100%" height="100%" /> + <rect fill="highlight" x="0" y="0" width="100%" height="100%" rx="2px"/> +</svg> + diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/search.gif b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/search.gif Binary files differnew file mode 100644 index 00000000..2a620987 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/search.gif diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/search.png b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/search.png Binary files differnew file mode 100644 index 00000000..fba33b8a --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/search.png diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/shadow.gif b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/shadow.gif Binary files differnew file mode 100644 index 00000000..af7f537e --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/shadow.gif diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/shadow2.gif b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/shadow2.gif Binary files differnew file mode 100644 index 00000000..099cbf35 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/shadow2.gif diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/shadowAlpha.png b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/shadowAlpha.png Binary files differnew file mode 100644 index 00000000..a2561df9 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/shadowAlpha.png diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/sprite.png b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/sprite.png Binary files differnew file mode 100644 index 00000000..33d2c4d4 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/sprite.png diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/tabHoverLeft.png b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/tabHoverLeft.png Binary files differnew file mode 100644 index 00000000..0fb24d0c --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/tabHoverLeft.png diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/tabHoverMid.png b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/tabHoverMid.png Binary files differnew file mode 100644 index 00000000..fbccab54 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/tabHoverMid.png diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/tabHoverRight.png b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/tabHoverRight.png Binary files differnew file mode 100644 index 00000000..3db0f361 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/tabHoverRight.png diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/tabLeft.png b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/tabLeft.png Binary files differnew file mode 100644 index 00000000..a6cc9e94 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/tabLeft.png diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMenuCheckbox.png b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMenuCheckbox.png Binary files differnew file mode 100644 index 00000000..4726e622 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMenuCheckbox.png diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMenuPin.png b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMenuPin.png Binary files differnew file mode 100644 index 00000000..eb4b11ef --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMenuPin.png diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMenuRadio.png b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMenuRadio.png Binary files differnew file mode 100644 index 00000000..55b982d7 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMenuRadio.png diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMenuTarget.png b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMenuTarget.png Binary files differnew file mode 100644 index 00000000..957bd9f2 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMenuTarget.png diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMenuTargetHover.png b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMenuTargetHover.png Binary files differnew file mode 100644 index 00000000..200a3708 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMenuTargetHover.png diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMid.png b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMid.png Binary files differnew file mode 100644 index 00000000..68986c3b --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMid.png diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/tabRight.png b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/tabRight.png Binary files differnew file mode 100644 index 00000000..50113079 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/tabRight.png diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/textEditorBorders.gif b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/textEditorBorders.gif Binary files differnew file mode 100644 index 00000000..0ee54978 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/textEditorBorders.gif diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/textEditorBorders.png b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/textEditorBorders.png Binary files differnew file mode 100644 index 00000000..21682c3d --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/textEditorBorders.png diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/textEditorCorners.gif b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/textEditorCorners.gif Binary files differnew file mode 100644 index 00000000..04f84215 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/textEditorCorners.gif diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/textEditorCorners.png b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/textEditorCorners.png Binary files differnew file mode 100644 index 00000000..a0f839dc --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/textEditorCorners.png diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/titlebarMid.png b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/titlebarMid.png Binary files differnew file mode 100644 index 00000000..10998ae7 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/titlebarMid.png diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/toolbarMid.png b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/toolbarMid.png Binary files differnew file mode 100644 index 00000000..aa21dee6 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/toolbarMid.png diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/tree_close.gif b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/tree_close.gif Binary files differnew file mode 100644 index 00000000..e26728ab --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/tree_close.gif diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/tree_open.gif b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/tree_open.gif Binary files differnew file mode 100644 index 00000000..edf662f3 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/tree_open.gif diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/twistyClosed.png b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/twistyClosed.png Binary files differnew file mode 100644 index 00000000..f80319b0 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/twistyClosed.png diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/twistyOpen.png b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/twistyOpen.png Binary files differnew file mode 100644 index 00000000..86801243 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/twistyOpen.png diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/up.png b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/up.png Binary files differnew file mode 100644 index 00000000..2174d03a --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/up.png diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/upActive.png b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/upActive.png Binary files differnew file mode 100644 index 00000000..236cf676 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/upActive.png diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/upHover.png b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/upHover.png Binary files differnew file mode 100644 index 00000000..cd813170 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/upHover.png diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/warningIcon.gif b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/warningIcon.gif Binary files differnew file mode 100644 index 00000000..84972788 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/warningIcon.gif diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/warningIcon.png b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/warningIcon.png Binary files differnew file mode 100644 index 00000000..de51084e --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/skin/xp/warningIcon.png diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/src/firebug-lite-debug.js b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/src/firebug-lite-debug.js new file mode 100644 index 00000000..40b1ae70 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/src/firebug-lite-debug.js @@ -0,0 +1,31176 @@ +(function(){ + +/*!************************************************************* + * + * Firebug Lite 1.4.0 + * + * Copyright (c) 2007, Parakey Inc. + * Released under BSD license. + * More information: http://getfirebug.com/firebuglite + * + **************************************************************/ + +/*! + * CSS selectors powered by: + * + * Sizzle CSS Selector Engine - v1.0 + * Copyright 2009, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ + +/** @namespace describe lib */ + +// FIXME: xxxpedro if we use "var FBL = {}" the FBL won't appear in the DOM Panel in IE +var FBL = {}; + +( /** @scope s_lib @this FBL */ function() { +// ************************************************************************************************ + +// ************************************************************************************************ +// Constants + +var productionDir = "http://getfirebug.com/releases/lite/"; +var bookmarkletVersion = 4; + +// ************************************************************************************************ + +var reNotWhitespace = /[^\s]/; +var reSplitFile = /:\/{1,3}(.*?)\/([^\/]*?)\/?($|\?.*)/; + +// Globals +this.reJavascript = /\s*javascript:\s*(.*)/; +this.reChrome = /chrome:\/\/([^\/]*)\//; +this.reFile = /file:\/\/([^\/]*)\//; + + +// ************************************************************************************************ +// properties + +var userAgent = navigator.userAgent.toLowerCase(); +this.isFirefox = /firefox/.test(userAgent); +this.isOpera = /opera/.test(userAgent); +this.isSafari = /webkit/.test(userAgent); +this.isIE = /msie/.test(userAgent) && !/opera/.test(userAgent); +this.isIE6 = /msie 6/i.test(navigator.appVersion); +this.browserVersion = (userAgent.match( /.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [0,'0'])[1]; +this.isIElt8 = this.isIE && (this.browserVersion-0 < 8); + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +this.NS = null; +this.pixelsPerInch = null; + + +// ************************************************************************************************ +// Namespaces + +var namespaces = []; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +this.ns = function(fn) +{ + var ns = {}; + namespaces.push(fn, ns); + return ns; +}; + +var FBTrace = null; + +this.initialize = function() +{ + // Firebug Lite is already running in persistent mode so we just quit + if (window.firebug && firebug.firebuglite || window.console && console.firebuglite) + return; + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // initialize environment + + // point the FBTrace object to the local variable + if (FBL.FBTrace) + FBTrace = FBL.FBTrace; + else + FBTrace = FBL.FBTrace = {}; + + // check if the actual window is a persisted chrome context + var isChromeContext = window.Firebug && typeof window.Firebug.SharedEnv == "object"; + + // chrome context of the persistent application + if (isChromeContext) + { + // TODO: xxxpedro persist - make a better synchronization + sharedEnv = window.Firebug.SharedEnv; + delete window.Firebug.SharedEnv; + + FBL.Env = sharedEnv; + FBL.Env.isChromeContext = true; + FBTrace.messageQueue = FBL.Env.traceMessageQueue; + } + // non-persistent application + else + { + FBL.NS = document.documentElement.namespaceURI; + FBL.Env.browser = window; + FBL.Env.destroy = destroyEnvironment; + + if (document.documentElement.getAttribute("debug") == "true") + FBL.Env.Options.startOpened = true; + + // find the URL location of the loaded application + findLocation(); + + // TODO: get preferences here... + // The problem is that we don't have the Firebug object yet, so we can't use + // Firebug.loadPrefs. We're using the Store module directly instead. + var prefs = FBL.Store.get("FirebugLite") || {}; + FBL.Env.DefaultOptions = FBL.Env.Options; + FBL.Env.Options = FBL.extend(FBL.Env.Options, prefs.options || {}); + + if (FBL.isFirefox && + typeof FBL.Env.browser.console == "object" && + FBL.Env.browser.console.firebug && + FBL.Env.Options.disableWhenFirebugActive) + return; + } + + // exposes the FBL to the global namespace when in debug mode + if (FBL.Env.isDebugMode) + { + FBL.Env.browser.FBL = FBL; + } + + // check browser compatibilities + this.isQuiksMode = FBL.Env.browser.document.compatMode == "BackCompat"; + this.isIEQuiksMode = this.isIE && this.isQuiksMode; + this.isIEStantandMode = this.isIE && !this.isQuiksMode; + + this.noFixedPosition = this.isIE6 || this.isIEQuiksMode; + + // after creating/synchronizing the environment, initialize the FBTrace module + if (FBL.Env.Options.enableTrace) FBTrace.initialize(); + + if (FBTrace.DBG_INITIALIZE && isChromeContext) FBTrace.sysout("FBL.initialize - persistent application", "initialize chrome context"); + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // initialize namespaces + + if (FBTrace.DBG_INITIALIZE) FBTrace.sysout("FBL.initialize", namespaces.length/2+" namespaces BEGIN"); + + for (var i = 0; i < namespaces.length; i += 2) + { + var fn = namespaces[i]; + var ns = namespaces[i+1]; + fn.apply(ns); + } + + if (FBTrace.DBG_INITIALIZE) { + FBTrace.sysout("FBL.initialize", namespaces.length/2+" namespaces END"); + FBTrace.sysout("FBL waitForDocument", "waiting document load"); + } + + FBL.Ajax.initialize(); + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // finish environment initialization + FBL.Firebug.loadPrefs(); + + if (FBL.Env.Options.enablePersistent) + { + // TODO: xxxpedro persist - make a better synchronization + if (isChromeContext) + { + FBL.FirebugChrome.clone(FBL.Env.FirebugChrome); + } + else + { + FBL.Env.FirebugChrome = FBL.FirebugChrome; + FBL.Env.traceMessageQueue = FBTrace.messageQueue; + } + } + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // wait document load + + waitForDocument(); +}; + +var waitForDocument = function waitForDocument() +{ + // document.body not available in XML+XSL documents in Firefox + var doc = FBL.Env.browser.document; + var body = doc.getElementsByTagName("body")[0]; + + if (body) + { + calculatePixelsPerInch(doc, body); + onDocumentLoad(); + } + else + setTimeout(waitForDocument, 50); +}; + +var onDocumentLoad = function onDocumentLoad() +{ + if (FBTrace.DBG_INITIALIZE) FBTrace.sysout("FBL onDocumentLoad", "document loaded"); + + // fix IE6 problem with cache of background images, causing a lot of flickering + if (FBL.isIE6) + fixIE6BackgroundImageCache(); + + // chrome context of the persistent application + if (FBL.Env.Options.enablePersistent && FBL.Env.isChromeContext) + { + // finally, start the application in the chrome context + FBL.Firebug.initialize(); + + // if is not development mode, remove the shared environment cache object + // used to synchronize the both persistent contexts + if (!FBL.Env.isDevelopmentMode) + { + sharedEnv.destroy(); + sharedEnv = null; + } + } + // non-persistent application + else + { + FBL.FirebugChrome.create(); + } +}; + +// ************************************************************************************************ +// Env + +var sharedEnv; + +this.Env = +{ + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Env Options (will be transported to Firebug options) + Options: + { + saveCookies: true, + + saveWindowPosition: false, + saveCommandLineHistory: false, + + startOpened: false, + startInNewWindow: false, + showIconWhenHidden: true, + + overrideConsole: true, + ignoreFirebugElements: true, + disableWhenFirebugActive: true, + + disableXHRListener: false, + disableResourceFetching: false, + + enableTrace: false, + enablePersistent: false + + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Library location + Location: + { + sourceDir: null, + baseDir: null, + skinDir: null, + skin: null, + app: null + }, + + skin: "xp", + useLocalSkin: false, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Env states + isDevelopmentMode: false, + isDebugMode: false, + isChromeContext: false, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Env references + browser: null, + chrome: null +}; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +var destroyEnvironment = function destroyEnvironment() +{ + setTimeout(function() + { + FBL = null; + }, 100); +}; + +// ************************************************************************************************ +// Library location + +var findLocation = function findLocation() +{ + var reFirebugFile = /(firebug-lite(?:-\w+)?(?:\.js|\.jgz))(?:#(.+))?$/; + var reGetFirebugSite = /(?:http|https):\/\/getfirebug.com\//; + var isGetFirebugSite; + + var rePath = /^(.*\/)/; + var reProtocol = /^\w+:\/\//; + var path = null; + var doc = document; + + // Firebug Lite 1.3.0 bookmarklet identification + var script = doc.getElementById("FirebugLite"); + + var scriptSrc; + var hasSrcAttribute = true; + + // If the script was loaded via bookmarklet, we already have the script tag + if (script) + { + scriptSrc = script.src; + file = reFirebugFile.exec(scriptSrc); + + var version = script.getAttribute("FirebugLite"); + var number = version ? parseInt(version) : 0; + + if (!version || !number || number < bookmarkletVersion) + { + FBL.Env.bookmarkletOutdated = true; + } + } + // otherwise we must search for the correct script tag + else + { + for(var i=0, s=doc.getElementsByTagName("script"), si; si=s[i]; i++) + { + var file = null; + if ( si.nodeName.toLowerCase() == "script" ) + { + if (file = reFirebugFile.exec(si.getAttribute("firebugSrc"))) + { + scriptSrc = si.getAttribute("firebugSrc"); + hasSrcAttribute = false; + } + else if (file = reFirebugFile.exec(si.src)) + { + scriptSrc = si.src; + } + else + continue; + + script = si; + break; + } + } + } + + // mark the script tag to be ignored by Firebug Lite + if (script) + script.firebugIgnore = true; + + if (file) + { + var fileName = file[1]; + var fileOptions = file[2]; + + // absolute path + if (reProtocol.test(scriptSrc)) { + path = rePath.exec(scriptSrc)[1]; + + } + // relative path + else + { + var r = rePath.exec(scriptSrc); + var src = r ? r[1] : scriptSrc; + var backDir = /^((?:\.\.\/)+)(.*)/.exec(src); + var reLastDir = /^(.*\/)[^\/]+\/$/; + path = rePath.exec(location.href)[1]; + + // "../some/path" + if (backDir) + { + var j = backDir[1].length/3; + var p; + while (j-- > 0) + path = reLastDir.exec(path)[1]; + + path += backDir[2]; + } + + else if(src.indexOf("/") != -1) + { + // "./some/path" + if(/^\.\/./.test(src)) + { + path += src.substring(2); + } + // "/some/path" + else if(/^\/./.test(src)) + { + var domain = /^(\w+:\/\/[^\/]+)/.exec(path); + path = domain[1] + src; + } + // "some/path" + else + { + path += src; + } + } + } + } + + FBL.Env.isChromeExtension = script && script.getAttribute("extension") == "Chrome"; + if (FBL.Env.isChromeExtension) + { + path = productionDir; + FBL.Env.bookmarkletOutdated = false; + script = {innerHTML: "{showIconWhenHidden:false}"}; + } + + isGetFirebugSite = reGetFirebugSite.test(path); + + if (isGetFirebugSite && path.indexOf("/releases/lite/") == -1) + { + // See Issue 4587 - If we are loading the script from getfirebug.com shortcut, like + // https://getfirebug.com/firebug-lite.js, then we must manually add the full path, + // otherwise the Env.Location will hold the wrong path, which will in turn lead to + // undesirable effects like the problem in Issue 4587 + path += "releases/lite/" + (fileName == "firebug-lite-beta.js" ? "beta/" : "latest/"); + } + + var m = path && path.match(/([^\/]+)\/$/) || null; + + if (path && m) + { + var Env = FBL.Env; + + // Always use the local skin when running in the same domain + // See Issue 3554: Firebug Lite should use local images when loaded locally + Env.useLocalSkin = path.indexOf(location.protocol + "//" + location.host + "/") == 0 && + // but we cannot use the locan skin when loaded from getfirebug.com, otherwise + // the bookmarklet won't work when visiting getfirebug.com + !isGetFirebugSite; + + // detecting development and debug modes via file name + if (fileName == "firebug-lite-dev.js") + { + Env.isDevelopmentMode = true; + Env.isDebugMode = true; + } + else if (fileName == "firebug-lite-debug.js") + { + Env.isDebugMode = true; + } + + // process the <html debug="true"> + if (Env.browser.document.documentElement.getAttribute("debug") == "true") + { + Env.Options.startOpened = true; + } + + // process the Script URL Options + if (fileOptions) + { + var options = fileOptions.split(","); + + for (var i = 0, length = options.length; i < length; i++) + { + var option = options[i]; + var name, value; + + if (option.indexOf("=") != -1) + { + var parts = option.split("="); + name = parts[0]; + value = eval(unescape(parts[1])); + } + else + { + name = option; + value = true; + } + + if (name == "debug") + { + Env.isDebugMode = !!value; + } + else if (name in Env.Options) + { + Env.Options[name] = value; + } + else + { + Env[name] = value; + } + } + } + + // process the Script JSON Options + if (hasSrcAttribute) + { + var innerOptions = FBL.trim(script.innerHTML); + if (innerOptions) + { + var innerOptionsObject = eval("(" + innerOptions + ")"); + + for (var name in innerOptionsObject) + { + var value = innerOptionsObject[name]; + + if (name == "debug") + { + Env.isDebugMode = !!value; + } + else if (name in Env.Options) + { + Env.Options[name] = value; + } + else + { + Env[name] = value; + } + } + } + } + + if (!Env.Options.saveCookies) + FBL.Store.remove("FirebugLite"); + + // process the Debug Mode + if (Env.isDebugMode) + { + Env.Options.startOpened = true; + Env.Options.enableTrace = true; + Env.Options.disableWhenFirebugActive = false; + } + + var loc = Env.Location; + var isProductionRelease = path.indexOf(productionDir) != -1; + + loc.sourceDir = path; + loc.baseDir = path.substr(0, path.length - m[1].length - 1); + loc.skinDir = (isProductionRelease ? path : loc.baseDir) + "skin/" + Env.skin + "/"; + loc.skin = loc.skinDir + "firebug.html"; + loc.app = path + fileName; + } + else + { + throw new Error("Firebug Error: Library path not found"); + } +}; + +// ************************************************************************************************ +// Basics + +this.bind = function() // fn, thisObject, args => thisObject.fn(args, arguments); +{ + var args = cloneArray(arguments), fn = args.shift(), object = args.shift(); + return function() { return fn.apply(object, arrayInsert(cloneArray(args), 0, arguments)); }; +}; + +this.bindFixed = function() // fn, thisObject, args => thisObject.fn(args); +{ + var args = cloneArray(arguments), fn = args.shift(), object = args.shift(); + return function() { return fn.apply(object, args); }; +}; + +this.extend = function(l, r) +{ + var newOb = {}; + for (var n in l) + newOb[n] = l[n]; + for (var n in r) + newOb[n] = r[n]; + return newOb; +}; + +this.descend = function(prototypeParent, childProperties) +{ + function protoSetter() {}; + protoSetter.prototype = prototypeParent; + var newOb = new protoSetter(); + for (var n in childProperties) + newOb[n] = childProperties[n]; + return newOb; +}; + +this.append = function(l, r) +{ + for (var n in r) + l[n] = r[n]; + + return l; +}; + +this.keys = function(map) // At least sometimes the keys will be on user-level window objects +{ + var keys = []; + try + { + for (var name in map) // enumeration is safe + keys.push(name); // name is string, safe + } + catch (exc) + { + // Sometimes we get exceptions trying to iterate properties + } + + return keys; // return is safe +}; + +this.values = function(map) +{ + var values = []; + try + { + for (var name in map) + { + try + { + values.push(map[name]); + } + catch (exc) + { + // Sometimes we get exceptions trying to access properties + if (FBTrace.DBG_ERRORS) + FBTrace.sysout("lib.values FAILED ", exc); + } + + } + } + catch (exc) + { + // Sometimes we get exceptions trying to iterate properties + if (FBTrace.DBG_ERRORS) + FBTrace.sysout("lib.values FAILED ", exc); + } + + return values; +}; + +this.remove = function(list, item) +{ + for (var i = 0; i < list.length; ++i) + { + if (list[i] == item) + { + list.splice(i, 1); + break; + } + } +}; + +this.sliceArray = function(array, index) +{ + var slice = []; + for (var i = index; i < array.length; ++i) + slice.push(array[i]); + + return slice; +}; + +function cloneArray(array, fn) +{ + var newArray = []; + + if (fn) + for (var i = 0; i < array.length; ++i) + newArray.push(fn(array[i])); + else + for (var i = 0; i < array.length; ++i) + newArray.push(array[i]); + + return newArray; +} + +function extendArray(array, array2) +{ + var newArray = []; + newArray.push.apply(newArray, array); + newArray.push.apply(newArray, array2); + return newArray; +} + +this.extendArray = extendArray; +this.cloneArray = cloneArray; + +function arrayInsert(array, index, other) +{ + for (var i = 0; i < other.length; ++i) + array.splice(i+index, 0, other[i]); + + return array; +} + +// ************************************************************************************************ + +this.createStyleSheet = function(doc, url) +{ + //TODO: xxxpedro + //var style = doc.createElementNS("http://www.w3.org/1999/xhtml", "style"); + var style = this.createElement("link"); + style.setAttribute("charset","utf-8"); + style.firebugIgnore = true; + style.setAttribute("rel", "stylesheet"); + style.setAttribute("type", "text/css"); + style.setAttribute("href", url); + + //TODO: xxxpedro + //style.innerHTML = this.getResource(url); + return style; +}; + +this.addStyleSheet = function(doc, style) +{ + var heads = doc.getElementsByTagName("head"); + if (heads.length) + heads[0].appendChild(style); + else + doc.documentElement.appendChild(style); +}; + +this.appendStylesheet = function(doc, uri) +{ + // Make sure the stylesheet is not appended twice. + if (this.$(uri, doc)) + return; + + var styleSheet = this.createStyleSheet(doc, uri); + styleSheet.setAttribute("id", uri); + this.addStyleSheet(doc, styleSheet); +}; + +this.addScript = function(doc, id, src) +{ + var element = doc.createElementNS("http://www.w3.org/1999/xhtml", "html:script"); + element.setAttribute("type", "text/javascript"); + element.setAttribute("id", id); + if (!FBTrace.DBG_CONSOLE) + FBL.unwrapObject(element).firebugIgnore = true; + + element.innerHTML = src; + if (doc.documentElement) + doc.documentElement.appendChild(element); + else + { + // See issue 1079, the svg test case gives this error + if (FBTrace.DBG_ERRORS) + FBTrace.sysout("lib.addScript doc has no documentElement:", doc); + } + return element; +}; + + +// ************************************************************************************************ + +this.getStyle = this.isIE ? + function(el, name) + { + return el.currentStyle[name] || el.style[name] || undefined; + } + : + function(el, name) + { + return el.ownerDocument.defaultView.getComputedStyle(el,null)[name] + || el.style[name] || undefined; + }; + + +// ************************************************************************************************ +// Whitespace and Entity conversions + +var entityConversionLists = this.entityConversionLists = { + normal : { + whitespace : { + '\t' : '\u200c\u2192', + '\n' : '\u200c\u00b6', + '\r' : '\u200c\u00ac', + ' ' : '\u200c\u00b7' + } + }, + reverse : { + whitespace : { + '	' : '\t', + '
' : '\n', + '\u200c\u2192' : '\t', + '\u200c\u00b6' : '\n', + '\u200c\u00ac' : '\r', + '\u200c\u00b7' : ' ' + } + } +}; + +var normal = entityConversionLists.normal, + reverse = entityConversionLists.reverse; + +function addEntityMapToList(ccode, entity) +{ + var lists = Array.prototype.slice.call(arguments, 2), + len = lists.length, + ch = String.fromCharCode(ccode); + for (var i = 0; i < len; i++) + { + var list = lists[i]; + normal[list]=normal[list] || {}; + normal[list][ch] = '&' + entity + ';'; + reverse[list]=reverse[list] || {}; + reverse[list]['&' + entity + ';'] = ch; + } +}; + +var e = addEntityMapToList, + white = 'whitespace', + text = 'text', + attr = 'attributes', + css = 'css', + editor = 'editor'; + +e(0x0022, 'quot', attr, css); +e(0x0026, 'amp', attr, text, css); +e(0x0027, 'apos', css); +e(0x003c, 'lt', attr, text, css); +e(0x003e, 'gt', attr, text, css); +e(0xa9, 'copy', text, editor); +e(0xae, 'reg', text, editor); +e(0x2122, 'trade', text, editor); + +// See http://en.wikipedia.org/wiki/Dash +e(0x2012, '#8210', attr, text, editor); // figure dash +e(0x2013, 'ndash', attr, text, editor); // en dash +e(0x2014, 'mdash', attr, text, editor); // em dash +e(0x2015, '#8213', attr, text, editor); // horizontal bar + +e(0x00a0, 'nbsp', attr, text, white, editor); +e(0x2002, 'ensp', attr, text, white, editor); +e(0x2003, 'emsp', attr, text, white, editor); +e(0x2009, 'thinsp', attr, text, white, editor); +e(0x200c, 'zwnj', attr, text, white, editor); +e(0x200d, 'zwj', attr, text, white, editor); +e(0x200e, 'lrm', attr, text, white, editor); +e(0x200f, 'rlm', attr, text, white, editor); +e(0x200b, '#8203', attr, text, white, editor); // zero-width space (ZWSP) + +//************************************************************************************************ +// Entity escaping + +var entityConversionRegexes = { + normal : {}, + reverse : {} + }; + +var escapeEntitiesRegEx = { + normal : function(list) + { + var chars = []; + for ( var ch in list) + { + chars.push(ch); + } + return new RegExp('([' + chars.join('') + '])', 'gm'); + }, + reverse : function(list) + { + var chars = []; + for ( var ch in list) + { + chars.push(ch); + } + return new RegExp('(' + chars.join('|') + ')', 'gm'); + } +}; + +function getEscapeRegexp(direction, lists) +{ + var name = '', re; + var groups = [].concat(lists); + for (i = 0; i < groups.length; i++) + { + name += groups[i].group; + } + re = entityConversionRegexes[direction][name]; + if (!re) + { + var list = {}; + if (groups.length > 1) + { + for ( var i = 0; i < groups.length; i++) + { + var aList = entityConversionLists[direction][groups[i].group]; + for ( var item in aList) + list[item] = aList[item]; + } + } else if (groups.length==1) + { + list = entityConversionLists[direction][groups[0].group]; // faster for special case + } else { + list = {}; // perhaps should print out an error here? + } + re = entityConversionRegexes[direction][name] = escapeEntitiesRegEx[direction](list); + } + return re; +}; + +function createSimpleEscape(name, direction) +{ + return function(value) + { + var list = entityConversionLists[direction][name]; + return String(value).replace( + getEscapeRegexp(direction, { + group : name, + list : list + }), + function(ch) + { + return list[ch]; + } + ); + }; +}; + +function escapeGroupsForEntities(str, lists) +{ + lists = [].concat(lists); + var re = getEscapeRegexp('normal', lists), + split = String(str).split(re), + len = split.length, + results = [], + cur, r, i, ri = 0, l, list, last = ''; + if (!len) + return [ { + str : String(str), + group : '', + name : '' + } ]; + for (i = 0; i < len; i++) + { + cur = split[i]; + if (cur == '') + continue; + for (l = 0; l < lists.length; l++) + { + list = lists[l]; + r = entityConversionLists.normal[list.group][cur]; + // if (cur == ' ' && list.group == 'whitespace' && last == ' ') // only show for runs of more than one space + // r = ' '; + if (r) + { + results[ri] = { + 'str' : r, + 'class' : list['class'], + 'extra' : list.extra[cur] ? list['class'] + + list.extra[cur] : '' + }; + break; + } + } + // last=cur; + if (!r) + results[ri] = { + 'str' : cur, + 'class' : '', + 'extra' : '' + }; + ri++; + } + return results; +}; + +this.escapeGroupsForEntities = escapeGroupsForEntities; + + +function unescapeEntities(str, lists) +{ + var re = getEscapeRegexp('reverse', lists), + split = String(str).split(re), + len = split.length, + results = [], + cur, r, i, ri = 0, l, list; + if (!len) + return str; + lists = [].concat(lists); + for (i = 0; i < len; i++) + { + cur = split[i]; + if (cur == '') + continue; + for (l = 0; l < lists.length; l++) + { + list = lists[l]; + r = entityConversionLists.reverse[list.group][cur]; + if (r) + { + results[ri] = r; + break; + } + } + if (!r) + results[ri] = cur; + ri++; + } + return results.join('') || ''; +}; + + +// ************************************************************************************************ +// String escaping + +var escapeForTextNode = this.escapeForTextNode = createSimpleEscape('text', 'normal'); +var escapeForHtmlEditor = this.escapeForHtmlEditor = createSimpleEscape('editor', 'normal'); +var escapeForElementAttribute = this.escapeForElementAttribute = createSimpleEscape('attributes', 'normal'); +var escapeForCss = this.escapeForCss = createSimpleEscape('css', 'normal'); + +// deprecated compatibility functions +//this.deprecateEscapeHTML = createSimpleEscape('text', 'normal'); +//this.deprecatedUnescapeHTML = createSimpleEscape('text', 'reverse'); +//this.escapeHTML = deprecated("use appropriate escapeFor... function", this.deprecateEscapeHTML); +//this.unescapeHTML = deprecated("use appropriate unescapeFor... function", this.deprecatedUnescapeHTML); + +var escapeForSourceLine = this.escapeForSourceLine = createSimpleEscape('text', 'normal'); + +var unescapeWhitespace = createSimpleEscape('whitespace', 'reverse'); + +this.unescapeForTextNode = function(str) +{ + if (Firebug.showTextNodesWithWhitespace) + str = unescapeWhitespace(str); + if (!Firebug.showTextNodesWithEntities) + str = escapeForElementAttribute(str); + return str; +}; + +this.escapeNewLines = function(value) +{ + return value.replace(/\r/g, "\\r").replace(/\n/g, "\\n"); +}; + +this.stripNewLines = function(value) +{ + return typeof(value) == "string" ? value.replace(/[\r\n]/g, " ") : value; +}; + +this.escapeJS = function(value) +{ + return value.replace(/\r/g, "\\r").replace(/\n/g, "\\n").replace('"', '\\"', "g"); +}; + +function escapeHTMLAttribute(value) +{ + function replaceChars(ch) + { + switch (ch) + { + case "&": + return "&"; + case "'": + return apos; + case '"': + return quot; + } + return "?"; + }; + var apos = "'", quot = """, around = '"'; + if( value.indexOf('"') == -1 ) { + quot = '"'; + apos = "'"; + } else if( value.indexOf("'") == -1 ) { + quot = '"'; + around = "'"; + } + return around + (String(value).replace(/[&'"]/g, replaceChars)) + around; +} + + +function escapeHTML(value) +{ + function replaceChars(ch) + { + switch (ch) + { + case "<": + return "<"; + case ">": + return ">"; + case "&": + return "&"; + case "'": + return "'"; + case '"': + return """; + } + return "?"; + }; + return String(value).replace(/[<>&"']/g, replaceChars); +} + +this.escapeHTML = escapeHTML; + +this.cropString = function(text, limit) +{ + text = text + ""; + + if (!limit) + var halfLimit = 50; + else + var halfLimit = limit / 2; + + if (text.length > limit) + return this.escapeNewLines(text.substr(0, halfLimit) + "..." + text.substr(text.length-halfLimit)); + else + return this.escapeNewLines(text); +}; + +this.isWhitespace = function(text) +{ + return !reNotWhitespace.exec(text); +}; + +this.splitLines = function(text) +{ + var reSplitLines2 = /.*(:?\r\n|\n|\r)?/mg; + var lines; + if (text.match) + { + lines = text.match(reSplitLines2); + } + else + { + var str = text+""; + lines = str.match(reSplitLines2); + } + lines.pop(); + return lines; +}; + + +// ************************************************************************************************ + +this.safeToString = function(ob) +{ + if (this.isIE) + { + try + { + // FIXME: xxxpedro this is failing in IE for the global "external" object + return ob + ""; + } + catch(E) + { + FBTrace.sysout("Lib.safeToString() failed for ", ob); + return ""; + } + } + + try + { + if (ob && "toString" in ob && typeof ob.toString == "function") + return ob.toString(); + } + catch (exc) + { + // xxxpedro it is not safe to use ob+""? + return ob + ""; + ///return "[an object with no toString() function]"; + } +}; + +// ************************************************************************************************ + +this.hasProperties = function(ob) +{ + try + { + for (var name in ob) + return true; + } catch (exc) {} + return false; +}; + +// ************************************************************************************************ +// String Util + +var reTrim = /^\s+|\s+$/g; +this.trim = function(s) +{ + return s.replace(reTrim, ""); +}; + + +// ************************************************************************************************ +// Empty + +this.emptyFn = function(){}; + + + +// ************************************************************************************************ +// Visibility + +this.isVisible = function(elt) +{ + /* + if (elt instanceof XULElement) + { + //FBTrace.sysout("isVisible elt.offsetWidth: "+elt.offsetWidth+" offsetHeight:"+ elt.offsetHeight+" localName:"+ elt.localName+" nameSpace:"+elt.nameSpaceURI+"\n"); + return (!elt.hidden && !elt.collapsed); + } + /**/ + + return this.getStyle(elt, "visibility") != "hidden" && + ( elt.offsetWidth > 0 || elt.offsetHeight > 0 + || elt.tagName in invisibleTags + || elt.namespaceURI == "http://www.w3.org/2000/svg" + || elt.namespaceURI == "http://www.w3.org/1998/Math/MathML" ); +}; + +this.collapse = function(elt, collapsed) +{ + // IE6 doesn't support the [collapsed] CSS selector. IE7 does support the selector, + // but it is causing a bug (the element disappears when you set the "collapsed" + // attribute, but it doesn't appear when you remove the attribute. So, for those + // cases, we need to use the class attribute. + if (this.isIElt8) + { + if (collapsed) + this.setClass(elt, "collapsed"); + else + this.removeClass(elt, "collapsed"); + } + else + elt.setAttribute("collapsed", collapsed ? "true" : "false"); +}; + +this.obscure = function(elt, obscured) +{ + if (obscured) + this.setClass(elt, "obscured"); + else + this.removeClass(elt, "obscured"); +}; + +this.hide = function(elt, hidden) +{ + elt.style.visibility = hidden ? "hidden" : "visible"; +}; + +this.clearNode = function(node) +{ + var nodeName = " " + node.nodeName.toLowerCase() + " "; + var ignoreTags = " table tbody thead tfoot th tr td "; + + // IE can't use innerHTML of table elements + if (this.isIE && ignoreTags.indexOf(nodeName) != -1) + this.eraseNode(node); + else + node.innerHTML = ""; +}; + +this.eraseNode = function(node) +{ + while (node.lastChild) + node.removeChild(node.lastChild); +}; + +// ************************************************************************************************ +// Window iteration + +this.iterateWindows = function(win, handler) +{ + if (!win || !win.document) + return; + + handler(win); + + if (win == top || !win.frames) return; // XXXjjb hack for chromeBug + + for (var i = 0; i < win.frames.length; ++i) + { + var subWin = win.frames[i]; + if (subWin != win) + this.iterateWindows(subWin, handler); + } +}; + +this.getRootWindow = function(win) +{ + for (; win; win = win.parent) + { + if (!win.parent || win == win.parent || !this.instanceOf(win.parent, "Window")) + return win; + } + return null; +}; + +// ************************************************************************************************ +// Graphics + +this.getClientOffset = function(elt) +{ + var addOffset = function addOffset(elt, coords, view) + { + var p = elt.offsetParent; + + ///var style = isIE ? elt.currentStyle : view.getComputedStyle(elt, ""); + var chrome = Firebug.chrome; + + if (elt.offsetLeft) + ///coords.x += elt.offsetLeft + parseInt(style.borderLeftWidth); + coords.x += elt.offsetLeft + chrome.getMeasurementInPixels(elt, "borderLeft"); + if (elt.offsetTop) + ///coords.y += elt.offsetTop + parseInt(style.borderTopWidth); + coords.y += elt.offsetTop + chrome.getMeasurementInPixels(elt, "borderTop"); + + if (p) + { + if (p.nodeType == 1) + addOffset(p, coords, view); + } + else + { + var otherView = isIE ? elt.ownerDocument.parentWindow : elt.ownerDocument.defaultView; + // IE will fail when reading the frameElement property of a popup window. + // We don't need it anyway once it is outside the (popup) viewport, so we're + // ignoring the frameElement check when the window is a popup + if (!otherView.opener && otherView.frameElement) + addOffset(otherView.frameElement, coords, otherView); + } + }; + + var isIE = this.isIE; + var coords = {x: 0, y: 0}; + if (elt) + { + var view = isIE ? elt.ownerDocument.parentWindow : elt.ownerDocument.defaultView; + addOffset(elt, coords, view); + } + + return coords; +}; + +this.getViewOffset = function(elt, singleFrame) +{ + function addOffset(elt, coords, view) + { + var p = elt.offsetParent; + coords.x += elt.offsetLeft - (p ? p.scrollLeft : 0); + coords.y += elt.offsetTop - (p ? p.scrollTop : 0); + + if (p) + { + if (p.nodeType == 1) + { + var parentStyle = view.getComputedStyle(p, ""); + if (parentStyle.position != "static") + { + coords.x += parseInt(parentStyle.borderLeftWidth); + coords.y += parseInt(parentStyle.borderTopWidth); + + if (p.localName == "TABLE") + { + coords.x += parseInt(parentStyle.paddingLeft); + coords.y += parseInt(parentStyle.paddingTop); + } + else if (p.localName == "BODY") + { + var style = view.getComputedStyle(elt, ""); + coords.x += parseInt(style.marginLeft); + coords.y += parseInt(style.marginTop); + } + } + else if (p.localName == "BODY") + { + coords.x += parseInt(parentStyle.borderLeftWidth); + coords.y += parseInt(parentStyle.borderTopWidth); + } + + var parent = elt.parentNode; + while (p != parent) + { + coords.x -= parent.scrollLeft; + coords.y -= parent.scrollTop; + parent = parent.parentNode; + } + addOffset(p, coords, view); + } + } + else + { + if (elt.localName == "BODY") + { + var style = view.getComputedStyle(elt, ""); + coords.x += parseInt(style.borderLeftWidth); + coords.y += parseInt(style.borderTopWidth); + + var htmlStyle = view.getComputedStyle(elt.parentNode, ""); + coords.x -= parseInt(htmlStyle.paddingLeft); + coords.y -= parseInt(htmlStyle.paddingTop); + } + + if (elt.scrollLeft) + coords.x += elt.scrollLeft; + if (elt.scrollTop) + coords.y += elt.scrollTop; + + var win = elt.ownerDocument.defaultView; + if (win && (!singleFrame && win.frameElement)) + addOffset(win.frameElement, coords, win); + } + + } + + var coords = {x: 0, y: 0}; + if (elt) + addOffset(elt, coords, elt.ownerDocument.defaultView); + + return coords; +}; + +this.getLTRBWH = function(elt) +{ + var bcrect, + dims = {"left": 0, "top": 0, "right": 0, "bottom": 0, "width": 0, "height": 0}; + + if (elt) + { + bcrect = elt.getBoundingClientRect(); + dims.left = bcrect.left; + dims.top = bcrect.top; + dims.right = bcrect.right; + dims.bottom = bcrect.bottom; + + if(bcrect.width) + { + dims.width = bcrect.width; + dims.height = bcrect.height; + } + else + { + dims.width = dims.right - dims.left; + dims.height = dims.bottom - dims.top; + } + } + return dims; +}; + +this.applyBodyOffsets = function(elt, clientRect) +{ + var od = elt.ownerDocument; + if (!od.body) + return clientRect; + + var style = od.defaultView.getComputedStyle(od.body, null); + + var pos = style.getPropertyValue('position'); + if(pos === 'absolute' || pos === 'relative') + { + var borderLeft = parseInt(style.getPropertyValue('border-left-width').replace('px', ''),10) || 0; + var borderTop = parseInt(style.getPropertyValue('border-top-width').replace('px', ''),10) || 0; + var paddingLeft = parseInt(style.getPropertyValue('padding-left').replace('px', ''),10) || 0; + var paddingTop = parseInt(style.getPropertyValue('padding-top').replace('px', ''),10) || 0; + var marginLeft = parseInt(style.getPropertyValue('margin-left').replace('px', ''),10) || 0; + var marginTop = parseInt(style.getPropertyValue('margin-top').replace('px', ''),10) || 0; + + var offsetX = borderLeft + paddingLeft + marginLeft; + var offsetY = borderTop + paddingTop + marginTop; + + clientRect.left -= offsetX; + clientRect.top -= offsetY; + clientRect.right -= offsetX; + clientRect.bottom -= offsetY; + } + + return clientRect; +}; + +this.getOffsetSize = function(elt) +{ + return {width: elt.offsetWidth, height: elt.offsetHeight}; +}; + +this.getOverflowParent = function(element) +{ + for (var scrollParent = element.parentNode; scrollParent; scrollParent = scrollParent.offsetParent) + { + if (scrollParent.scrollHeight > scrollParent.offsetHeight) + return scrollParent; + } +}; + +this.isScrolledToBottom = function(element) +{ + var onBottom = (element.scrollTop + element.offsetHeight) == element.scrollHeight; + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("isScrolledToBottom offsetHeight: "+element.offsetHeight +" onBottom:"+onBottom); + return onBottom; +}; + +this.scrollToBottom = function(element) +{ + element.scrollTop = element.scrollHeight; + + if (FBTrace.DBG_CONSOLE) + { + FBTrace.sysout("scrollToBottom reset scrollTop "+element.scrollTop+" = "+element.scrollHeight); + if (element.scrollHeight == element.offsetHeight) + FBTrace.sysout("scrollToBottom attempt to scroll non-scrollable element "+element, element); + } + + return (element.scrollTop == element.scrollHeight); +}; + +this.move = function(element, x, y) +{ + element.style.left = x + "px"; + element.style.top = y + "px"; +}; + +this.resize = function(element, w, h) +{ + element.style.width = w + "px"; + element.style.height = h + "px"; +}; + +this.linesIntoCenterView = function(element, scrollBox) // {before: int, after: int} +{ + if (!scrollBox) + scrollBox = this.getOverflowParent(element); + + if (!scrollBox) + return; + + var offset = this.getClientOffset(element); + + var topSpace = offset.y - scrollBox.scrollTop; + var bottomSpace = (scrollBox.scrollTop + scrollBox.clientHeight) + - (offset.y + element.offsetHeight); + + if (topSpace < 0 || bottomSpace < 0) + { + var split = (scrollBox.clientHeight/2); + var centerY = offset.y - split; + scrollBox.scrollTop = centerY; + topSpace = split; + bottomSpace = split - element.offsetHeight; + } + + return {before: Math.round((topSpace/element.offsetHeight) + 0.5), + after: Math.round((bottomSpace/element.offsetHeight) + 0.5) }; +}; + +this.scrollIntoCenterView = function(element, scrollBox, notX, notY) +{ + if (!element) + return; + + if (!scrollBox) + scrollBox = this.getOverflowParent(element); + + if (!scrollBox) + return; + + var offset = this.getClientOffset(element); + + if (!notY) + { + var topSpace = offset.y - scrollBox.scrollTop; + var bottomSpace = (scrollBox.scrollTop + scrollBox.clientHeight) + - (offset.y + element.offsetHeight); + + if (topSpace < 0 || bottomSpace < 0) + { + var centerY = offset.y - (scrollBox.clientHeight/2); + scrollBox.scrollTop = centerY; + } + } + + if (!notX) + { + var leftSpace = offset.x - scrollBox.scrollLeft; + var rightSpace = (scrollBox.scrollLeft + scrollBox.clientWidth) + - (offset.x + element.clientWidth); + + if (leftSpace < 0 || rightSpace < 0) + { + var centerX = offset.x - (scrollBox.clientWidth/2); + scrollBox.scrollLeft = centerX; + } + } + if (FBTrace.DBG_SOURCEFILES) + FBTrace.sysout("lib.scrollIntoCenterView ","Element:"+element.innerHTML); +}; + + +// ************************************************************************************************ +// CSS + +var cssKeywordMap = null; +var cssPropNames = null; +var cssColorNames = null; +var imageRules = null; + +this.getCSSKeywordsByProperty = function(propName) +{ + if (!cssKeywordMap) + { + cssKeywordMap = {}; + + for (var name in this.cssInfo) + { + var list = []; + + var types = this.cssInfo[name]; + for (var i = 0; i < types.length; ++i) + { + var keywords = this.cssKeywords[types[i]]; + if (keywords) + list.push.apply(list, keywords); + } + + cssKeywordMap[name] = list; + } + } + + return propName in cssKeywordMap ? cssKeywordMap[propName] : []; +}; + +this.getCSSPropertyNames = function() +{ + if (!cssPropNames) + { + cssPropNames = []; + + for (var name in this.cssInfo) + cssPropNames.push(name); + } + + return cssPropNames; +}; + +this.isColorKeyword = function(keyword) +{ + if (keyword == "transparent") + return false; + + if (!cssColorNames) + { + cssColorNames = []; + + var colors = this.cssKeywords["color"]; + for (var i = 0; i < colors.length; ++i) + cssColorNames.push(colors[i].toLowerCase()); + + var systemColors = this.cssKeywords["systemColor"]; + for (var i = 0; i < systemColors.length; ++i) + cssColorNames.push(systemColors[i].toLowerCase()); + } + + return cssColorNames.indexOf ? // Array.indexOf is not available in IE + cssColorNames.indexOf(keyword.toLowerCase()) != -1 : + (" " + cssColorNames.join(" ") + " ").indexOf(" " + keyword.toLowerCase() + " ") != -1; +}; + +this.isImageRule = function(rule) +{ + if (!imageRules) + { + imageRules = []; + + for (var i in this.cssInfo) + { + var r = i.toLowerCase(); + var suffix = "image"; + if (r.match(suffix + "$") == suffix || r == "background") + imageRules.push(r); + } + } + + return imageRules.indexOf ? // Array.indexOf is not available in IE + imageRules.indexOf(rule.toLowerCase()) != -1 : + (" " + imageRules.join(" ") + " ").indexOf(" " + rule.toLowerCase() + " ") != -1; +}; + +this.copyTextStyles = function(fromNode, toNode, style) +{ + var view = this.isIE ? + fromNode.ownerDocument.parentWindow : + fromNode.ownerDocument.defaultView; + + if (view) + { + if (!style) + style = this.isIE ? fromNode.currentStyle : view.getComputedStyle(fromNode, ""); + + toNode.style.fontFamily = style.fontFamily; + + // TODO: xxxpedro need to create a FBL.getComputedStyle() because IE + // returns wrong computed styles for inherited properties (like font-*) + // + // Also would be good to create a FBL.getStyle() + toNode.style.fontSize = style.fontSize; + toNode.style.fontWeight = style.fontWeight; + toNode.style.fontStyle = style.fontStyle; + + return style; + } +}; + +this.copyBoxStyles = function(fromNode, toNode, style) +{ + var view = this.isIE ? + fromNode.ownerDocument.parentWindow : + fromNode.ownerDocument.defaultView; + + if (view) + { + if (!style) + style = this.isIE ? fromNode.currentStyle : view.getComputedStyle(fromNode, ""); + + toNode.style.marginTop = style.marginTop; + toNode.style.marginRight = style.marginRight; + toNode.style.marginBottom = style.marginBottom; + toNode.style.marginLeft = style.marginLeft; + toNode.style.borderTopWidth = style.borderTopWidth; + toNode.style.borderRightWidth = style.borderRightWidth; + toNode.style.borderBottomWidth = style.borderBottomWidth; + toNode.style.borderLeftWidth = style.borderLeftWidth; + + return style; + } +}; + +this.readBoxStyles = function(style) +{ + var styleNames = { + "margin-top": "marginTop", "margin-right": "marginRight", + "margin-left": "marginLeft", "margin-bottom": "marginBottom", + "border-top-width": "borderTop", "border-right-width": "borderRight", + "border-left-width": "borderLeft", "border-bottom-width": "borderBottom", + "padding-top": "paddingTop", "padding-right": "paddingRight", + "padding-left": "paddingLeft", "padding-bottom": "paddingBottom", + "z-index": "zIndex" + }; + + var styles = {}; + for (var styleName in styleNames) + styles[styleNames[styleName]] = parseInt(style.getPropertyCSSValue(styleName).cssText) || 0; + if (FBTrace.DBG_INSPECT) + FBTrace.sysout("readBoxStyles ", styles); + return styles; +}; + +this.getBoxFromStyles = function(style, element) +{ + var args = this.readBoxStyles(style); + args.width = element.offsetWidth + - (args.paddingLeft+args.paddingRight+args.borderLeft+args.borderRight); + args.height = element.offsetHeight + - (args.paddingTop+args.paddingBottom+args.borderTop+args.borderBottom); + return args; +}; + +this.getElementCSSSelector = function(element) +{ + var label = element.localName.toLowerCase(); + if (element.id) + label += "#" + element.id; + if (element.hasAttribute("class")) + label += "." + element.getAttribute("class").split(" ")[0]; + + return label; +}; + +this.getURLForStyleSheet= function(styleSheet) +{ + //http://www.w3.org/TR/DOM-Level-2-Style/stylesheets.html#StyleSheets-StyleSheet. For inline style sheets, the value of this attribute is null. + return (styleSheet.href ? styleSheet.href : styleSheet.ownerNode.ownerDocument.URL); +}; + +this.getDocumentForStyleSheet = function(styleSheet) +{ + while (styleSheet.parentStyleSheet && !styleSheet.ownerNode) + { + styleSheet = styleSheet.parentStyleSheet; + } + if (styleSheet.ownerNode) + return styleSheet.ownerNode.ownerDocument; +}; + +/** + * Retrieves the instance number for a given style sheet. The instance number + * is sheet's index within the set of all other sheets whose URL is the same. + */ +this.getInstanceForStyleSheet = function(styleSheet, ownerDocument) +{ + // System URLs are always unique (or at least we are making this assumption) + if (FBL.isSystemStyleSheet(styleSheet)) + return 0; + + // ownerDocument is an optional hint for performance + if (FBTrace.DBG_CSS) FBTrace.sysout("getInstanceForStyleSheet: " + styleSheet.href + " " + styleSheet.media.mediaText + " " + (styleSheet.ownerNode && FBL.getElementXPath(styleSheet.ownerNode)), ownerDocument); + ownerDocument = ownerDocument || FBL.getDocumentForStyleSheet(styleSheet); + + var ret = 0, + styleSheets = ownerDocument.styleSheets, + href = styleSheet.href; + for (var i = 0; i < styleSheets.length; i++) + { + var curSheet = styleSheets[i]; + if (FBTrace.DBG_CSS) FBTrace.sysout("getInstanceForStyleSheet: compare href " + i + " " + curSheet.href + " " + curSheet.media.mediaText + " " + (curSheet.ownerNode && FBL.getElementXPath(curSheet.ownerNode))); + if (curSheet == styleSheet) + break; + if (curSheet.href == href) + ret++; + } + return ret; +}; + +// ************************************************************************************************ +// HTML and XML Serialization + + +var getElementType = this.getElementType = function(node) +{ + if (isElementXUL(node)) + return 'xul'; + else if (isElementSVG(node)) + return 'svg'; + else if (isElementMathML(node)) + return 'mathml'; + else if (isElementXHTML(node)) + return 'xhtml'; + else if (isElementHTML(node)) + return 'html'; +}; + +var getElementSimpleType = this.getElementSimpleType = function(node) +{ + if (isElementSVG(node)) + return 'svg'; + else if (isElementMathML(node)) + return 'mathml'; + else + return 'html'; +}; + +var isElementHTML = this.isElementHTML = function(node) +{ + return node.nodeName == node.nodeName.toUpperCase(); +}; + +var isElementXHTML = this.isElementXHTML = function(node) +{ + return node.nodeName == node.nodeName.toLowerCase(); +}; + +var isElementMathML = this.isElementMathML = function(node) +{ + return node.namespaceURI == 'http://www.w3.org/1998/Math/MathML'; +}; + +var isElementSVG = this.isElementSVG = function(node) +{ + return node.namespaceURI == 'http://www.w3.org/2000/svg'; +}; + +var isElementXUL = this.isElementXUL = function(node) +{ + return node instanceof XULElement; +}; + +this.isSelfClosing = function(element) +{ + if (isElementSVG(element) || isElementMathML(element)) + return true; + var tag = element.localName.toLowerCase(); + return (this.selfClosingTags.hasOwnProperty(tag)); +}; + +this.getElementHTML = function(element) +{ + var self=this; + function toHTML(elt) + { + if (elt.nodeType == Node.ELEMENT_NODE) + { + if (unwrapObject(elt).firebugIgnore) + return; + + html.push('<', elt.nodeName.toLowerCase()); + + for (var i = 0; i < elt.attributes.length; ++i) + { + var attr = elt.attributes[i]; + + // Hide attributes set by Firebug + if (attr.localName.indexOf("firebug-") == 0) + continue; + + // MathML + if (attr.localName.indexOf("-moz-math") == 0) + { + // just hide for now + continue; + } + + html.push(' ', attr.nodeName, '="', escapeForElementAttribute(attr.nodeValue),'"'); + } + + if (elt.firstChild) + { + html.push('>'); + + var pureText=true; + for (var child = element.firstChild; child; child = child.nextSibling) + pureText=pureText && (child.nodeType == Node.TEXT_NODE); + + if (pureText) + html.push(escapeForHtmlEditor(elt.textContent)); + else { + for (var child = elt.firstChild; child; child = child.nextSibling) + toHTML(child); + } + + html.push('</', elt.nodeName.toLowerCase(), '>'); + } + else if (isElementSVG(elt) || isElementMathML(elt)) + { + html.push('/>'); + } + else if (self.isSelfClosing(elt)) + { + html.push((isElementXHTML(elt))?'/>':'>'); + } + else + { + html.push('></', elt.nodeName.toLowerCase(), '>'); + } + } + else if (elt.nodeType == Node.TEXT_NODE) + html.push(escapeForTextNode(elt.textContent)); + else if (elt.nodeType == Node.CDATA_SECTION_NODE) + html.push('<![CDATA[', elt.nodeValue, ']]>'); + else if (elt.nodeType == Node.COMMENT_NODE) + html.push('<!--', elt.nodeValue, '-->'); + } + + var html = []; + toHTML(element); + return html.join(""); +}; + +this.getElementXML = function(element) +{ + function toXML(elt) + { + if (elt.nodeType == Node.ELEMENT_NODE) + { + if (unwrapObject(elt).firebugIgnore) + return; + + xml.push('<', elt.nodeName.toLowerCase()); + + for (var i = 0; i < elt.attributes.length; ++i) + { + var attr = elt.attributes[i]; + + // Hide attributes set by Firebug + if (attr.localName.indexOf("firebug-") == 0) + continue; + + // MathML + if (attr.localName.indexOf("-moz-math") == 0) + { + // just hide for now + continue; + } + + xml.push(' ', attr.nodeName, '="', escapeForElementAttribute(attr.nodeValue),'"'); + } + + if (elt.firstChild) + { + xml.push('>'); + + for (var child = elt.firstChild; child; child = child.nextSibling) + toXML(child); + + xml.push('</', elt.nodeName.toLowerCase(), '>'); + } + else + xml.push('/>'); + } + else if (elt.nodeType == Node.TEXT_NODE) + xml.push(elt.nodeValue); + else if (elt.nodeType == Node.CDATA_SECTION_NODE) + xml.push('<![CDATA[', elt.nodeValue, ']]>'); + else if (elt.nodeType == Node.COMMENT_NODE) + xml.push('<!--', elt.nodeValue, '-->'); + } + + var xml = []; + toXML(element); + return xml.join(""); +}; + + +// ************************************************************************************************ +// CSS classes + +this.hasClass = function(node, name) // className, className, ... +{ + // TODO: xxxpedro when lib.hasClass is called with more than 2 arguments? + // this function can be optimized a lot if assumed 2 arguments only, + // which seems to be what happens 99% of the time + if (arguments.length == 2) + return (' '+node.className+' ').indexOf(' '+name+' ') != -1; + + if (!node || node.nodeType != 1) + return false; + else + { + for (var i=1; i<arguments.length; ++i) + { + var name = arguments[i]; + var re = new RegExp("(^|\\s)"+name+"($|\\s)"); + if (!re.exec(node.className)) + return false; + } + + return true; + } +}; + +this.old_hasClass = function(node, name) // className, className, ... +{ + if (!node || node.nodeType != 1) + return false; + else + { + for (var i=1; i<arguments.length; ++i) + { + var name = arguments[i]; + var re = new RegExp("(^|\\s)"+name+"($|\\s)"); + if (!re.exec(node.className)) + return false; + } + + return true; + } +}; + +this.setClass = function(node, name) +{ + if (node && (' '+node.className+' ').indexOf(' '+name+' ') == -1) + ///if (node && !this.hasClass(node, name)) + node.className += " " + name; +}; + +this.getClassValue = function(node, name) +{ + var re = new RegExp(name+"-([^ ]+)"); + var m = re.exec(node.className); + return m ? m[1] : ""; +}; + +this.removeClass = function(node, name) +{ + if (node && node.className) + { + var index = node.className.indexOf(name); + if (index >= 0) + { + var size = name.length; + node.className = node.className.substr(0,index-1) + node.className.substr(index+size); + } + } +}; + +this.toggleClass = function(elt, name) +{ + if ((' '+elt.className+' ').indexOf(' '+name+' ') != -1) + ///if (this.hasClass(elt, name)) + this.removeClass(elt, name); + else + this.setClass(elt, name); +}; + +this.setClassTimed = function(elt, name, context, timeout) +{ + if (!timeout) + timeout = 1300; + + if (elt.__setClassTimeout) + context.clearTimeout(elt.__setClassTimeout); + else + this.setClass(elt, name); + + elt.__setClassTimeout = context.setTimeout(function() + { + delete elt.__setClassTimeout; + + FBL.removeClass(elt, name); + }, timeout); +}; + +this.cancelClassTimed = function(elt, name, context) +{ + if (elt.__setClassTimeout) + { + FBL.removeClass(elt, name); + context.clearTimeout(elt.__setClassTimeout); + delete elt.__setClassTimeout; + } +}; + + +// ************************************************************************************************ +// DOM queries + +this.$ = function(id, doc) +{ + if (doc) + return doc.getElementById(id); + else + { + return FBL.Firebug.chrome.document.getElementById(id); + } +}; + +this.$$ = function(selector, doc) +{ + if (doc || !FBL.Firebug.chrome) + return FBL.Firebug.Selector(selector, doc); + else + { + return FBL.Firebug.Selector(selector, FBL.Firebug.chrome.document); + } +}; + +this.getChildByClass = function(node) // ,classname, classname, classname... +{ + for (var i = 1; i < arguments.length; ++i) + { + var className = arguments[i]; + var child = node.firstChild; + node = null; + for (; child; child = child.nextSibling) + { + if (this.hasClass(child, className)) + { + node = child; + break; + } + } + } + + return node; +}; + +this.getAncestorByClass = function(node, className) +{ + for (var parent = node; parent; parent = parent.parentNode) + { + if (this.hasClass(parent, className)) + return parent; + } + + return null; +}; + + +this.getElementsByClass = function(node, className) +{ + var result = []; + + for (var child = node.firstChild; child; child = child.nextSibling) + { + if (this.hasClass(child, className)) + result.push(child); + } + + return result; +}; + +this.getElementByClass = function(node, className) // className, className, ... +{ + var args = cloneArray(arguments); args.splice(0, 1); + for (var child = node.firstChild; child; child = child.nextSibling) + { + var args1 = cloneArray(args); args1.unshift(child); + if (FBL.hasClass.apply(null, args1)) + return child; + else + { + var found = FBL.getElementByClass.apply(null, args1); + if (found) + return found; + } + } + + return null; +}; + +this.isAncestor = function(node, potentialAncestor) +{ + for (var parent = node; parent; parent = parent.parentNode) + { + if (parent == potentialAncestor) + return true; + } + + return false; +}; + +this.getNextElement = function(node) +{ + while (node && node.nodeType != 1) + node = node.nextSibling; + + return node; +}; + +this.getPreviousElement = function(node) +{ + while (node && node.nodeType != 1) + node = node.previousSibling; + + return node; +}; + +this.getBody = function(doc) +{ + if (doc.body) + return doc.body; + + var body = doc.getElementsByTagName("body")[0]; + if (body) + return body; + + return doc.firstChild; // For non-HTML docs +}; + +this.findNextDown = function(node, criteria) +{ + if (!node) + return null; + + for (var child = node.firstChild; child; child = child.nextSibling) + { + if (criteria(child)) + return child; + + var next = this.findNextDown(child, criteria); + if (next) + return next; + } +}; + +this.findPreviousUp = function(node, criteria) +{ + if (!node) + return null; + + for (var child = node.lastChild; child; child = child.previousSibling) + { + var next = this.findPreviousUp(child, criteria); + if (next) + return next; + + if (criteria(child)) + return child; + } +}; + +this.findNext = function(node, criteria, upOnly, maxRoot) +{ + if (!node) + return null; + + if (!upOnly) + { + var next = this.findNextDown(node, criteria); + if (next) + return next; + } + + for (var sib = node.nextSibling; sib; sib = sib.nextSibling) + { + if (criteria(sib)) + return sib; + + var next = this.findNextDown(sib, criteria); + if (next) + return next; + } + + if (node.parentNode && node.parentNode != maxRoot) + return this.findNext(node.parentNode, criteria, true); +}; + +this.findPrevious = function(node, criteria, downOnly, maxRoot) +{ + if (!node) + return null; + + for (var sib = node.previousSibling; sib; sib = sib.previousSibling) + { + var prev = this.findPreviousUp(sib, criteria); + if (prev) + return prev; + + if (criteria(sib)) + return sib; + } + + if (!downOnly) + { + var next = this.findPreviousUp(node, criteria); + if (next) + return next; + } + + if (node.parentNode && node.parentNode != maxRoot) + { + if (criteria(node.parentNode)) + return node.parentNode; + + return this.findPrevious(node.parentNode, criteria, true); + } +}; + +this.getNextByClass = function(root, state) +{ + var iter = function iter(node) { return node.nodeType == 1 && FBL.hasClass(node, state); }; + return this.findNext(root, iter); +}; + +this.getPreviousByClass = function(root, state) +{ + var iter = function iter(node) { return node.nodeType == 1 && FBL.hasClass(node, state); }; + return this.findPrevious(root, iter); +}; + +this.isElement = function(o) +{ + try { + return o && this.instanceOf(o, "Element"); + } + catch (ex) { + return false; + } +}; + + +// ************************************************************************************************ +// DOM Modification + +// TODO: xxxpedro use doc fragments in Context API +var appendFragment = null; + +this.appendInnerHTML = function(element, html, referenceElement) +{ + // if undefined, we must convert it to null otherwise it will throw an error in IE + // when executing element.insertBefore(firstChild, referenceElement) + referenceElement = referenceElement || null; + + var doc = element.ownerDocument; + + // doc.createRange not available in IE + if (doc.createRange) + { + var range = doc.createRange(); // a helper object + range.selectNodeContents(element); // the environment to interpret the html + + var fragment = range.createContextualFragment(html); // parse + var firstChild = fragment.firstChild; + element.insertBefore(fragment, referenceElement); + } + else + { + if (!appendFragment || appendFragment.ownerDocument != doc) + appendFragment = doc.createDocumentFragment(); + + var div = doc.createElement("div"); + div.innerHTML = html; + + var firstChild = div.firstChild; + while (div.firstChild) + appendFragment.appendChild(div.firstChild); + + element.insertBefore(appendFragment, referenceElement); + + div = null; + } + + return firstChild; +}; + + +// ************************************************************************************************ +// DOM creation + +this.createElement = function(tagName, properties) +{ + properties = properties || {}; + var doc = properties.document || FBL.Firebug.chrome.document; + + var element = doc.createElement(tagName); + + for(var name in properties) + { + if (name != "document") + { + element[name] = properties[name]; + } + } + + return element; +}; + +this.createGlobalElement = function(tagName, properties) +{ + properties = properties || {}; + var doc = FBL.Env.browser.document; + + var element = this.NS && doc.createElementNS ? + doc.createElementNS(FBL.NS, tagName) : + doc.createElement(tagName); + + for(var name in properties) + { + var propname = name; + if (FBL.isIE && name == "class") propname = "className"; + + if (name != "document") + { + element.setAttribute(propname, properties[name]); + } + } + + return element; +}; + +//************************************************************************************************ + +this.safeGetWindowLocation = function(window) +{ + try + { + if (window) + { + if (window.closed) + return "(window.closed)"; + if ("location" in window) + return window.location+""; + else + return "(no window.location)"; + } + else + return "(no context.window)"; + } + catch(exc) + { + if (FBTrace.DBG_WINDOWS || FBTrace.DBG_ERRORS) + FBTrace.sysout("TabContext.getWindowLocation failed "+exc, exc); + FBTrace.sysout("TabContext.getWindowLocation failed window:", window); + return "(getWindowLocation: "+exc+")"; + } +}; + +// ************************************************************************************************ +// Events + +this.isLeftClick = function(event) +{ + return (this.isIE && event.type != "click" && event.type != "dblclick" ? + event.button == 1 : // IE "click" and "dblclick" button model + event.button == 0) && // others + this.noKeyModifiers(event); +}; + +this.isMiddleClick = function(event) +{ + return (this.isIE && event.type != "click" && event.type != "dblclick" ? + event.button == 4 : // IE "click" and "dblclick" button model + event.button == 1) && + this.noKeyModifiers(event); +}; + +this.isRightClick = function(event) +{ + return (this.isIE && event.type != "click" && event.type != "dblclick" ? + event.button == 2 : // IE "click" and "dblclick" button model + event.button == 2) && + this.noKeyModifiers(event); +}; + +this.noKeyModifiers = function(event) +{ + return !event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey; +}; + +this.isControlClick = function(event) +{ + return (this.isIE && event.type != "click" && event.type != "dblclick" ? + event.button == 1 : // IE "click" and "dblclick" button model + event.button == 0) && + this.isControl(event); +}; + +this.isShiftClick = function(event) +{ + return (this.isIE && event.type != "click" && event.type != "dblclick" ? + event.button == 1 : // IE "click" and "dblclick" button model + event.button == 0) && + this.isShift(event); +}; + +this.isControl = function(event) +{ + return (event.metaKey || event.ctrlKey) && !event.shiftKey && !event.altKey; +}; + +this.isAlt = function(event) +{ + return event.altKey && !event.ctrlKey && !event.shiftKey && !event.metaKey; +}; + +this.isAltClick = function(event) +{ + return (this.isIE && event.type != "click" && event.type != "dblclick" ? + event.button == 1 : // IE "click" and "dblclick" button model + event.button == 0) && + this.isAlt(event); +}; + +this.isControlShift = function(event) +{ + return (event.metaKey || event.ctrlKey) && event.shiftKey && !event.altKey; +}; + +this.isShift = function(event) +{ + return event.shiftKey && !event.metaKey && !event.ctrlKey && !event.altKey; +}; + +this.addEvent = function(object, name, handler, useCapture) +{ + if (object.addEventListener) + object.addEventListener(name, handler, useCapture); + else + object.attachEvent("on"+name, handler); +}; + +this.removeEvent = function(object, name, handler, useCapture) +{ + try + { + if (object.removeEventListener) + object.removeEventListener(name, handler, useCapture); + else + object.detachEvent("on"+name, handler); + } + catch(e) + { + if (FBTrace.DBG_ERRORS) + FBTrace.sysout("FBL.removeEvent error: ", object, name); + } +}; + +this.cancelEvent = function(e, preventDefault) +{ + if (!e) return; + + if (preventDefault) + { + if (e.preventDefault) + e.preventDefault(); + else + e.returnValue = false; + } + + if (e.stopPropagation) + e.stopPropagation(); + else + e.cancelBubble = true; +}; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +this.addGlobalEvent = function(name, handler) +{ + var doc = this.Firebug.browser.document; + var frames = this.Firebug.browser.window.frames; + + this.addEvent(doc, name, handler); + + if (this.Firebug.chrome.type == "popup") + this.addEvent(this.Firebug.chrome.document, name, handler); + + for (var i = 0, frame; frame = frames[i]; i++) + { + try + { + this.addEvent(frame.document, name, handler); + } + catch(E) + { + // Avoid acess denied + } + } +}; + +this.removeGlobalEvent = function(name, handler) +{ + var doc = this.Firebug.browser.document; + var frames = this.Firebug.browser.window.frames; + + this.removeEvent(doc, name, handler); + + if (this.Firebug.chrome.type == "popup") + this.removeEvent(this.Firebug.chrome.document, name, handler); + + for (var i = 0, frame; frame = frames[i]; i++) + { + try + { + this.removeEvent(frame.document, name, handler); + } + catch(E) + { + // Avoid acess denied + } + } +}; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +this.dispatch = function(listeners, name, args) +{ + if (!listeners) return; + + try + {/**/ + if (typeof listeners.length != "undefined") + { + if (FBTrace.DBG_DISPATCH) FBTrace.sysout("FBL.dispatch", name+" to "+listeners.length+" listeners"); + + for (var i = 0; i < listeners.length; ++i) + { + var listener = listeners[i]; + if ( listener[name] ) + listener[name].apply(listener, args); + } + } + else + { + if (FBTrace.DBG_DISPATCH) FBTrace.sysout("FBL.dispatch", name+" to listeners of an object"); + + for (var prop in listeners) + { + var listener = listeners[prop]; + if ( listener[name] ) + listener[name].apply(listener, args); + } + } + } + catch (exc) + { + if (FBTrace.DBG_ERRORS) + { + FBTrace.sysout(" Exception in lib.dispatch "+ name, exc); + //FBTrace.dumpProperties(" Exception in lib.dispatch listener", listener); + } + } + /**/ +}; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +var disableTextSelectionHandler = function(event) +{ + FBL.cancelEvent(event, true); + + return false; +}; + +this.disableTextSelection = function(e) +{ + if (typeof e.onselectstart != "undefined") // IE + this.addEvent(e, "selectstart", disableTextSelectionHandler); + + else // others + { + e.style.cssText = "user-select: none; -khtml-user-select: none; -moz-user-select: none;"; + + // canceling the event in FF will prevent the menu popups to close when clicking over + // text-disabled elements + if (!this.isFirefox) + this.addEvent(e, "mousedown", disableTextSelectionHandler); + } + + e.style.cursor = "default"; +}; + +this.restoreTextSelection = function(e) +{ + if (typeof e.onselectstart != "undefined") // IE + this.removeEvent(e, "selectstart", disableTextSelectionHandler); + + else // others + { + e.style.cssText = "cursor: default;"; + + // canceling the event in FF will prevent the menu popups to close when clicking over + // text-disabled elements + if (!this.isFirefox) + this.removeEvent(e, "mousedown", disableTextSelectionHandler); + } +}; + +// ************************************************************************************************ +// DOM Events + +var eventTypes = +{ + composition: [ + "composition", + "compositionstart", + "compositionend" ], + contextmenu: [ + "contextmenu" ], + drag: [ + "dragenter", + "dragover", + "dragexit", + "dragdrop", + "draggesture" ], + focus: [ + "focus", + "blur" ], + form: [ + "submit", + "reset", + "change", + "select", + "input" ], + key: [ + "keydown", + "keyup", + "keypress" ], + load: [ + "load", + "beforeunload", + "unload", + "abort", + "error" ], + mouse: [ + "mousedown", + "mouseup", + "click", + "dblclick", + "mouseover", + "mouseout", + "mousemove" ], + mutation: [ + "DOMSubtreeModified", + "DOMNodeInserted", + "DOMNodeRemoved", + "DOMNodeRemovedFromDocument", + "DOMNodeInsertedIntoDocument", + "DOMAttrModified", + "DOMCharacterDataModified" ], + paint: [ + "paint", + "resize", + "scroll" ], + scroll: [ + "overflow", + "underflow", + "overflowchanged" ], + text: [ + "text" ], + ui: [ + "DOMActivate", + "DOMFocusIn", + "DOMFocusOut" ], + xul: [ + "popupshowing", + "popupshown", + "popuphiding", + "popuphidden", + "close", + "command", + "broadcast", + "commandupdate" ] +}; + +this.getEventFamily = function(eventType) +{ + if (!this.families) + { + this.families = {}; + + for (var family in eventTypes) + { + var types = eventTypes[family]; + for (var i = 0; i < types.length; ++i) + this.families[types[i]] = family; + } + } + + return this.families[eventType]; +}; + + +// ************************************************************************************************ +// URLs + +this.getFileName = function(url) +{ + var split = this.splitURLBase(url); + return split.name; +}; + +this.splitURLBase = function(url) +{ + if (this.isDataURL(url)) + return this.splitDataURL(url); + return this.splitURLTrue(url); +}; + +this.splitDataURL = function(url) +{ + var mark = url.indexOf(':', 3); + if (mark != 4) + return false; // the first 5 chars must be 'data:' + + var point = url.indexOf(',', mark+1); + if (point < mark) + return false; // syntax error + + var props = { encodedContent: url.substr(point+1) }; + + var metadataBuffer = url.substr(mark+1, point); + var metadata = metadataBuffer.split(';'); + for (var i = 0; i < metadata.length; i++) + { + var nv = metadata[i].split('='); + if (nv.length == 2) + props[nv[0]] = nv[1]; + } + + // Additional Firebug-specific properties + if (props.hasOwnProperty('fileName')) + { + var caller_URL = decodeURIComponent(props['fileName']); + var caller_split = this.splitURLTrue(caller_URL); + + if (props.hasOwnProperty('baseLineNumber')) // this means it's probably an eval() + { + props['path'] = caller_split.path; + props['line'] = props['baseLineNumber']; + var hint = decodeURIComponent(props['encodedContent'].substr(0,200)).replace(/\s*$/, ""); + props['name'] = 'eval->'+hint; + } + else + { + props['name'] = caller_split.name; + props['path'] = caller_split.path; + } + } + else + { + if (!props.hasOwnProperty('path')) + props['path'] = "data:"; + if (!props.hasOwnProperty('name')) + props['name'] = decodeURIComponent(props['encodedContent'].substr(0,200)).replace(/\s*$/, ""); + } + + return props; +}; + +this.splitURLTrue = function(url) +{ + var m = reSplitFile.exec(url); + if (!m) + return {name: url, path: url}; + else if (!m[2]) + return {path: m[1], name: m[1]}; + else + return {path: m[1], name: m[2]+m[3]}; +}; + +this.getFileExtension = function(url) +{ + if (!url) + return null; + + // Remove query string from the URL if any. + var queryString = url.indexOf("?"); + if (queryString != -1) + url = url.substr(0, queryString); + + // Now get the file extension. + var lastDot = url.lastIndexOf("."); + return url.substr(lastDot+1); +}; + +this.isSystemURL = function(url) +{ + if (!url) return true; + if (url.length == 0) return true; + if (url[0] == 'h') return false; + if (url.substr(0, 9) == "resource:") + return true; + else if (url.substr(0, 16) == "chrome://firebug") + return true; + else if (url == "XPCSafeJSObjectWrapper.cpp") + return true; + else if (url.substr(0, 6) == "about:") + return true; + else if (url.indexOf("firebug-service.js") != -1) + return true; + else + return false; +}; + +this.isSystemPage = function(win) +{ + try + { + var doc = win.document; + if (!doc) + return false; + + // Detect pages for pretty printed XML + if ((doc.styleSheets.length && doc.styleSheets[0].href + == "chrome://global/content/xml/XMLPrettyPrint.css") + || (doc.styleSheets.length > 1 && doc.styleSheets[1].href + == "chrome://browser/skin/feeds/subscribe.css")) + return true; + + return FBL.isSystemURL(win.location.href); + } + catch (exc) + { + // Sometimes documents just aren't ready to be manipulated here, but don't let that + // gum up the works + ERROR("tabWatcher.isSystemPage document not ready:"+ exc); + return false; + } +}; + +this.isSystemStyleSheet = function(sheet) +{ + var href = sheet && sheet.href; + return href && FBL.isSystemURL(href); +}; + +this.getURIHost = function(uri) +{ + try + { + if (uri) + return uri.host; + else + return ""; + } + catch (exc) + { + return ""; + } +}; + +this.isLocalURL = function(url) +{ + if (url.substr(0, 5) == "file:") + return true; + else if (url.substr(0, 8) == "wyciwyg:") + return true; + else + return false; +}; + +this.isDataURL = function(url) +{ + return (url && url.substr(0,5) == "data:"); +}; + +this.getLocalPath = function(url) +{ + if (this.isLocalURL(url)) + { + var fileHandler = ioService.getProtocolHandler("file").QueryInterface(Ci.nsIFileProtocolHandler); + var file = fileHandler.getFileFromURLSpec(url); + return file.path; + } +}; + +this.getURLFromLocalFile = function(file) +{ + var fileHandler = ioService.getProtocolHandler("file").QueryInterface(Ci.nsIFileProtocolHandler); + var URL = fileHandler.getURLSpecFromFile(file); + return URL; +}; + +this.getDataURLForContent = function(content, url) +{ + // data:text/javascript;fileName=x%2Cy.js;baseLineNumber=10,<the-url-encoded-data> + var uri = "data:text/html;"; + uri += "fileName="+encodeURIComponent(url)+ ","; + uri += encodeURIComponent(content); + return uri; +}, + +this.getDomain = function(url) +{ + var m = /[^:]+:\/{1,3}([^\/]+)/.exec(url); + return m ? m[1] : ""; +}; + +this.getURLPath = function(url) +{ + var m = /[^:]+:\/{1,3}[^\/]+(\/.*?)$/.exec(url); + return m ? m[1] : ""; +}; + +this.getPrettyDomain = function(url) +{ + var m = /[^:]+:\/{1,3}(www\.)?([^\/]+)/.exec(url); + return m ? m[2] : ""; +}; + +this.absoluteURL = function(url, baseURL) +{ + return this.absoluteURLWithDots(url, baseURL).replace("/./", "/", "g"); +}; + +this.absoluteURLWithDots = function(url, baseURL) +{ + if (url[0] == "?") + return baseURL + url; + + var reURL = /(([^:]+:)\/{1,2}[^\/]*)(.*?)$/; + var m = reURL.exec(url); + if (m) + return url; + + var m = reURL.exec(baseURL); + if (!m) + return ""; + + var head = m[1]; + var tail = m[3]; + if (url.substr(0, 2) == "//") + return m[2] + url; + else if (url[0] == "/") + { + return head + url; + } + else if (tail[tail.length-1] == "/") + return baseURL + url; + else + { + var parts = tail.split("/"); + return head + parts.slice(0, parts.length-1).join("/") + "/" + url; + } +}; + +this.normalizeURL = function(url) // this gets called a lot, any performance improvement welcome +{ + if (!url) + return ""; + // Replace one or more characters that are not forward-slash followed by /.., by space. + if (url.length < 255) // guard against monsters. + { + // Replace one or more characters that are not forward-slash followed by /.., by space. + url = url.replace(/[^\/]+\/\.\.\//, "", "g"); + // Issue 1496, avoid # + url = url.replace(/#.*/,""); + // For some reason, JSDS reports file URLs like "file:/" instead of "file:///", so they + // don't match up with the URLs we get back from the DOM + url = url.replace(/file:\/([^\/])/g, "file:///$1"); + if (url.indexOf('chrome:')==0) + { + var m = reChromeCase.exec(url); // 1 is package name, 2 is path + if (m) + { + url = "chrome://"+m[1].toLowerCase()+"/"+m[2]; + } + } + } + return url; +}; + +this.denormalizeURL = function(url) +{ + return url.replace(/file:\/\/\//g, "file:/"); +}; + +this.parseURLParams = function(url) +{ + var q = url ? url.indexOf("?") : -1; + if (q == -1) + return []; + + var search = url.substr(q+1); + var h = search.lastIndexOf("#"); + if (h != -1) + search = search.substr(0, h); + + if (!search) + return []; + + return this.parseURLEncodedText(search); +}; + +this.parseURLEncodedText = function(text) +{ + var maxValueLength = 25000; + + var params = []; + + // Unescape '+' characters that are used to encode a space. + // See section 2.2.in RFC 3986: http://www.ietf.org/rfc/rfc3986.txt + text = text.replace(/\+/g, " "); + + var args = text.split("&"); + for (var i = 0; i < args.length; ++i) + { + try { + var parts = args[i].split("="); + if (parts.length == 2) + { + if (parts[1].length > maxValueLength) + parts[1] = this.$STR("LargeData"); + + params.push({name: decodeURIComponent(parts[0]), value: decodeURIComponent(parts[1])}); + } + else + params.push({name: decodeURIComponent(parts[0]), value: ""}); + } + catch (e) + { + if (FBTrace.DBG_ERRORS) + { + FBTrace.sysout("parseURLEncodedText EXCEPTION ", e); + FBTrace.sysout("parseURLEncodedText EXCEPTION URI", args[i]); + } + } + } + + params.sort(function(a, b) { return a.name <= b.name ? -1 : 1; }); + + return params; +}; + +// TODO: xxxpedro lib. why loops in domplate are requiring array in parameters +// as in response/request headers and get/post parameters in Net module? +this.parseURLParamsArray = function(url) +{ + var q = url ? url.indexOf("?") : -1; + if (q == -1) + return []; + + var search = url.substr(q+1); + var h = search.lastIndexOf("#"); + if (h != -1) + search = search.substr(0, h); + + if (!search) + return []; + + return this.parseURLEncodedTextArray(search); +}; + +this.parseURLEncodedTextArray = function(text) +{ + var maxValueLength = 25000; + + var params = []; + + // Unescape '+' characters that are used to encode a space. + // See section 2.2.in RFC 3986: http://www.ietf.org/rfc/rfc3986.txt + text = text.replace(/\+/g, " "); + + var args = text.split("&"); + for (var i = 0; i < args.length; ++i) + { + try { + var parts = args[i].split("="); + if (parts.length == 2) + { + if (parts[1].length > maxValueLength) + parts[1] = this.$STR("LargeData"); + + params.push({name: decodeURIComponent(parts[0]), value: [decodeURIComponent(parts[1])]}); + } + else + params.push({name: decodeURIComponent(parts[0]), value: [""]}); + } + catch (e) + { + if (FBTrace.DBG_ERRORS) + { + FBTrace.sysout("parseURLEncodedText EXCEPTION ", e); + FBTrace.sysout("parseURLEncodedText EXCEPTION URI", args[i]); + } + } + } + + params.sort(function(a, b) { return a.name <= b.name ? -1 : 1; }); + + return params; +}; + +this.reEncodeURL = function(file, text) +{ + var lines = text.split("\n"); + var params = this.parseURLEncodedText(lines[lines.length-1]); + + var args = []; + for (var i = 0; i < params.length; ++i) + args.push(encodeURIComponent(params[i].name)+"="+encodeURIComponent(params[i].value)); + + var url = file.href; + url += (url.indexOf("?") == -1 ? "?" : "&") + args.join("&"); + + return url; +}; + +this.getResource = function(aURL) +{ + try + { + var channel=ioService.newChannel(aURL,null,null); + var input=channel.open(); + return FBL.readFromStream(input); + } + catch (e) + { + if (FBTrace.DBG_ERRORS) + FBTrace.sysout("lib.getResource FAILS for "+aURL, e); + } +}; + +this.parseJSONString = function(jsonString, originURL) +{ + // See if this is a Prototype style *-secure request. + var regex = new RegExp(/^\/\*-secure-([\s\S]*)\*\/\s*$/); + var matches = regex.exec(jsonString); + + if (matches) + { + jsonString = matches[1]; + + if (jsonString[0] == "\\" && jsonString[1] == "n") + jsonString = jsonString.substr(2); + + if (jsonString[jsonString.length-2] == "\\" && jsonString[jsonString.length-1] == "n") + jsonString = jsonString.substr(0, jsonString.length-2); + } + + if (jsonString.indexOf("&&&START&&&")) + { + regex = new RegExp(/&&&START&&& (.+) &&&END&&&/); + matches = regex.exec(jsonString); + if (matches) + jsonString = matches[1]; + } + + // throw on the extra parentheses + jsonString = "(" + jsonString + ")"; + + ///var s = Components.utils.Sandbox(originURL); + var jsonObject = null; + + try + { + ///jsonObject = Components.utils.evalInSandbox(jsonString, s); + + //jsonObject = Firebug.context.eval(jsonString); + jsonObject = Firebug.context.evaluate(jsonString, null, null, function(){return null;}); + } + catch(e) + { + /*** + if (e.message.indexOf("is not defined")) + { + var parts = e.message.split(" "); + s[parts[0]] = function(str){ return str; }; + try { + jsonObject = Components.utils.evalInSandbox(jsonString, s); + } catch(ex) { + if (FBTrace.DBG_ERRORS || FBTrace.DBG_JSONVIEWER) + FBTrace.sysout("jsonviewer.parseJSON EXCEPTION", e); + return null; + } + } + else + {/**/ + if (FBTrace.DBG_ERRORS || FBTrace.DBG_JSONVIEWER) + FBTrace.sysout("jsonviewer.parseJSON EXCEPTION", e); + return null; + ///} + } + + return jsonObject; +}; + +// ************************************************************************************************ + +this.objectToString = function(object) +{ + try + { + return object+""; + } + catch (exc) + { + return null; + } +}; + +// ************************************************************************************************ +// Input Caret Position + +this.setSelectionRange = function(input, start, length) +{ + if (input.createTextRange) + { + var range = input.createTextRange(); + range.moveStart("character", start); + range.moveEnd("character", length - input.value.length); + range.select(); + } + else if (input.setSelectionRange) + { + input.setSelectionRange(start, length); + input.focus(); + } +}; + +// ************************************************************************************************ +// Input Selection Start / Caret Position + +this.getInputSelectionStart = function(input) +{ + if (document.selection) + { + var range = input.ownerDocument.selection.createRange(); + var text = range.text; + + //console.log("range", range.text); + + // if there is a selection, find the start position + if (text) + { + return input.value.indexOf(text); + } + // if there is no selection, find the caret position + else + { + range.moveStart("character", -input.value.length); + + return range.text.length; + } + } + else if (typeof input.selectionStart != "undefined") + return input.selectionStart; + + return 0; +}; + +// ************************************************************************************************ +// Opera Tab Fix + +function onOperaTabBlur(e) +{ + if (this.lastKey == 9) + this.focus(); +}; + +function onOperaTabKeyDown(e) +{ + this.lastKey = e.keyCode; +}; + +function onOperaTabFocus(e) +{ + this.lastKey = null; +}; + +this.fixOperaTabKey = function(el) +{ + el.onfocus = onOperaTabFocus; + el.onblur = onOperaTabBlur; + el.onkeydown = onOperaTabKeyDown; +}; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +this.Property = function(object, name) +{ + this.object = object; + this.name = name; + + this.getObject = function() + { + return object[name]; + }; +}; + +this.ErrorCopy = function(message) +{ + this.message = message; +}; + +function EventCopy(event) +{ + // Because event objects are destroyed arbitrarily by Gecko, we must make a copy of them to + // represent them long term in the inspector. + for (var name in event) + { + try { + this[name] = event[name]; + } catch (exc) { } + } +} + +this.EventCopy = EventCopy; + + +// ************************************************************************************************ +// Type Checking + +var toString = Object.prototype.toString; +var reFunction = /^\s*function(\s+[\w_$][\w\d_$]*)?\s*\(/; + +this.isArray = function(object) { + return toString.call(object) === '[object Array]'; +}; + +this.isFunction = function(object) { + if (!object) return false; + + try + { + // FIXME: xxxpedro this is failing in IE for the global "external" object + return toString.call(object) === "[object Function]" || + this.isIE && typeof object != "string" && reFunction.test(""+object); + } + catch (E) + { + FBTrace.sysout("Lib.isFunction() failed for ", object); + return false; + } +}; + + +// ************************************************************************************************ +// Instance Checking + +this.instanceOf = function(object, className) +{ + if (!object || typeof object != "object") + return false; + + // Try to use the native instanceof operator. We can only use it when we know + // exactly the window where the object is located at + if (object.ownerDocument) + { + // find the correct window of the object + var win = object.ownerDocument.defaultView || object.ownerDocument.parentWindow; + + // if the class is accessible in the window, uses the native instanceof operator + // if the instanceof evaluates to "true" we can assume it is a instance, but if it + // evaluates to "false" we must continue with the duck type detection below because + // the native object may be extended, thus breaking the instanceof result + // See Issue 3524: Firebug Lite Style Panel doesn't work if the native Element is extended + if (className in win && object instanceof win[className]) + return true; + } + // If the object doesn't have the ownerDocument property, we'll try to look at + // the current context's window + else + { + // TODO: xxxpedro context + // Since we're not using yet a Firebug.context, we'll just use the top window + // (browser) as a reference + var win = Firebug.browser.window; + if (className in win) + return object instanceof win[className]; + } + + // get the duck type model from the cache + var cache = instanceCheckMap[className]; + if (!cache) + return false; + + // starts the hacky duck type detection + for(var n in cache) + { + var obj = cache[n]; + var type = typeof obj; + obj = type == "object" ? obj : [obj]; + + for(var name in obj) + { + // avoid problems with extended native objects + // See Issue 3524: Firebug Lite Style Panel doesn't work if the native Element is extended + if (!obj.hasOwnProperty(name)) + continue; + + var value = obj[name]; + + if( n == "property" && !(value in object) || + n == "method" && !this.isFunction(object[value]) || + n == "value" && (""+object[name]).toLowerCase() != (""+value).toLowerCase() ) + return false; + } + } + + return true; +}; + +var instanceCheckMap = +{ + // DuckTypeCheck: + // { + // property: ["window", "document"], + // method: "setTimeout", + // value: {nodeType: 1} + // }, + + Window: + { + property: ["window", "document"], + method: "setTimeout" + }, + + Document: + { + property: ["body", "cookie"], + method: "getElementById" + }, + + Node: + { + property: "ownerDocument", + method: "appendChild" + }, + + Element: + { + property: "tagName", + value: {nodeType: 1} + }, + + Location: + { + property: ["hostname", "protocol"], + method: "assign" + }, + + HTMLImageElement: + { + property: "useMap", + value: + { + nodeType: 1, + tagName: "img" + } + }, + + HTMLAnchorElement: + { + property: "hreflang", + value: + { + nodeType: 1, + tagName: "a" + } + }, + + HTMLInputElement: + { + property: "form", + value: + { + nodeType: 1, + tagName: "input" + } + }, + + HTMLButtonElement: + { + // ? + }, + + HTMLFormElement: + { + method: "submit", + value: + { + nodeType: 1, + tagName: "form" + } + }, + + HTMLBodyElement: + { + + }, + + HTMLHtmlElement: + { + + }, + + CSSStyleRule: + { + property: ["selectorText", "style"] + } + +}; + + +// ************************************************************************************************ +// DOM Constants + +/* + +Problems: + + - IE does not have window.Node, window.Element, etc + - for (var name in Node.prototype) return nothing on FF + +*/ + + +var domMemberMap2 = {}; + +var domMemberMap2Sandbox = null; + +var getDomMemberMap2 = function(name) +{ + if (!domMemberMap2Sandbox) + { + var doc = Firebug.chrome.document; + var frame = doc.createElement("iframe"); + + frame.id = "FirebugSandbox"; + frame.style.display = "none"; + frame.src = "about:blank"; + + doc.body.appendChild(frame); + + domMemberMap2Sandbox = frame.window || frame.contentWindow; + } + + var props = []; + + //var object = domMemberMap2Sandbox[name]; + //object = object.prototype || object; + + var object = null; + + if (name == "Window") + object = domMemberMap2Sandbox.window; + + else if (name == "Document") + object = domMemberMap2Sandbox.document; + + else if (name == "HTMLScriptElement") + object = domMemberMap2Sandbox.document.createElement("script"); + + else if (name == "HTMLAnchorElement") + object = domMemberMap2Sandbox.document.createElement("a"); + + else if (name.indexOf("Element") != -1) + { + object = domMemberMap2Sandbox.document.createElement("div"); + } + + if (object) + { + //object = object.prototype || object; + + //props = 'addEventListener,document,location,navigator,window'.split(','); + + for (var n in object) + props.push(n); + } + /**/ + + return props; + return extendArray(props, domMemberMap[name]); +}; + +// xxxpedro experimental get DOM members +this.getDOMMembers = function(object) +{ + if (!domMemberCache) + { + FBL.domMemberCache = domMemberCache = {}; + + for (var name in domMemberMap) + { + var builtins = getDomMemberMap2(name); + var cache = domMemberCache[name] = {}; + + /* + if (name.indexOf("Element") != -1) + { + this.append(cache, this.getDOMMembers("Node")); + this.append(cache, this.getDOMMembers("Element")); + } + /**/ + + for (var i = 0; i < builtins.length; ++i) + cache[builtins[i]] = i; + } + } + + try + { + if (this.instanceOf(object, "Window")) + { return domMemberCache.Window; } + else if (this.instanceOf(object, "Document") || this.instanceOf(object, "XMLDocument")) + { return domMemberCache.Document; } + else if (this.instanceOf(object, "Location")) + { return domMemberCache.Location; } + else if (this.instanceOf(object, "HTMLImageElement")) + { return domMemberCache.HTMLImageElement; } + else if (this.instanceOf(object, "HTMLAnchorElement")) + { return domMemberCache.HTMLAnchorElement; } + else if (this.instanceOf(object, "HTMLInputElement")) + { return domMemberCache.HTMLInputElement; } + else if (this.instanceOf(object, "HTMLButtonElement")) + { return domMemberCache.HTMLButtonElement; } + else if (this.instanceOf(object, "HTMLFormElement")) + { return domMemberCache.HTMLFormElement; } + else if (this.instanceOf(object, "HTMLBodyElement")) + { return domMemberCache.HTMLBodyElement; } + else if (this.instanceOf(object, "HTMLHtmlElement")) + { return domMemberCache.HTMLHtmlElement; } + else if (this.instanceOf(object, "HTMLScriptElement")) + { return domMemberCache.HTMLScriptElement; } + else if (this.instanceOf(object, "HTMLTableElement")) + { return domMemberCache.HTMLTableElement; } + else if (this.instanceOf(object, "HTMLTableRowElement")) + { return domMemberCache.HTMLTableRowElement; } + else if (this.instanceOf(object, "HTMLTableCellElement")) + { return domMemberCache.HTMLTableCellElement; } + else if (this.instanceOf(object, "HTMLIFrameElement")) + { return domMemberCache.HTMLIFrameElement; } + else if (this.instanceOf(object, "SVGSVGElement")) + { return domMemberCache.SVGSVGElement; } + else if (this.instanceOf(object, "SVGElement")) + { return domMemberCache.SVGElement; } + else if (this.instanceOf(object, "Element")) + { return domMemberCache.Element; } + else if (this.instanceOf(object, "Text") || this.instanceOf(object, "CDATASection")) + { return domMemberCache.Text; } + else if (this.instanceOf(object, "Attr")) + { return domMemberCache.Attr; } + else if (this.instanceOf(object, "Node")) + { return domMemberCache.Node; } + else if (this.instanceOf(object, "Event") || this.instanceOf(object, "EventCopy")) + { return domMemberCache.Event; } + else + return {}; + } + catch(E) + { + if (FBTrace.DBG_ERRORS) + FBTrace.sysout("lib.getDOMMembers FAILED ", E); + + return {}; + } +}; + + +/* +this.getDOMMembers = function(object) +{ + if (!domMemberCache) + { + domMemberCache = {}; + + for (var name in domMemberMap) + { + var builtins = domMemberMap[name]; + var cache = domMemberCache[name] = {}; + + for (var i = 0; i < builtins.length; ++i) + cache[builtins[i]] = i; + } + } + + try + { + if (this.instanceOf(object, "Window")) + { return domMemberCache.Window; } + else if (object instanceof Document || object instanceof XMLDocument) + { return domMemberCache.Document; } + else if (object instanceof Location) + { return domMemberCache.Location; } + else if (object instanceof HTMLImageElement) + { return domMemberCache.HTMLImageElement; } + else if (object instanceof HTMLAnchorElement) + { return domMemberCache.HTMLAnchorElement; } + else if (object instanceof HTMLInputElement) + { return domMemberCache.HTMLInputElement; } + else if (object instanceof HTMLButtonElement) + { return domMemberCache.HTMLButtonElement; } + else if (object instanceof HTMLFormElement) + { return domMemberCache.HTMLFormElement; } + else if (object instanceof HTMLBodyElement) + { return domMemberCache.HTMLBodyElement; } + else if (object instanceof HTMLHtmlElement) + { return domMemberCache.HTMLHtmlElement; } + else if (object instanceof HTMLScriptElement) + { return domMemberCache.HTMLScriptElement; } + else if (object instanceof HTMLTableElement) + { return domMemberCache.HTMLTableElement; } + else if (object instanceof HTMLTableRowElement) + { return domMemberCache.HTMLTableRowElement; } + else if (object instanceof HTMLTableCellElement) + { return domMemberCache.HTMLTableCellElement; } + else if (object instanceof HTMLIFrameElement) + { return domMemberCache.HTMLIFrameElement; } + else if (object instanceof SVGSVGElement) + { return domMemberCache.SVGSVGElement; } + else if (object instanceof SVGElement) + { return domMemberCache.SVGElement; } + else if (object instanceof Element) + { return domMemberCache.Element; } + else if (object instanceof Text || object instanceof CDATASection) + { return domMemberCache.Text; } + else if (object instanceof Attr) + { return domMemberCache.Attr; } + else if (object instanceof Node) + { return domMemberCache.Node; } + else if (object instanceof Event || object instanceof EventCopy) + { return domMemberCache.Event; } + else + return {}; + } + catch(E) + { + return {}; + } +}; +/**/ + +this.isDOMMember = function(object, propName) +{ + var members = this.getDOMMembers(object); + return members && propName in members; +}; + +var domMemberCache = null; +var domMemberMap = {}; + +domMemberMap.Window = +[ + "document", + "frameElement", + + "innerWidth", + "innerHeight", + "outerWidth", + "outerHeight", + "screenX", + "screenY", + "pageXOffset", + "pageYOffset", + "scrollX", + "scrollY", + "scrollMaxX", + "scrollMaxY", + + "status", + "defaultStatus", + + "parent", + "opener", + "top", + "window", + "content", + "self", + + "location", + "history", + "frames", + "navigator", + "screen", + "menubar", + "toolbar", + "locationbar", + "personalbar", + "statusbar", + "directories", + "scrollbars", + "fullScreen", + "netscape", + "java", + "console", + "Components", + "controllers", + "closed", + "crypto", + "pkcs11", + + "name", + "property", + "length", + + "sessionStorage", + "globalStorage", + + "setTimeout", + "setInterval", + "clearTimeout", + "clearInterval", + "addEventListener", + "removeEventListener", + "dispatchEvent", + "getComputedStyle", + "captureEvents", + "releaseEvents", + "routeEvent", + "enableExternalCapture", + "disableExternalCapture", + "moveTo", + "moveBy", + "resizeTo", + "resizeBy", + "scroll", + "scrollTo", + "scrollBy", + "scrollByLines", + "scrollByPages", + "sizeToContent", + "setResizable", + "getSelection", + "open", + "openDialog", + "close", + "alert", + "confirm", + "prompt", + "dump", + "focus", + "blur", + "find", + "back", + "forward", + "home", + "stop", + "print", + "atob", + "btoa", + "updateCommands", + "XPCNativeWrapper", + "GeckoActiveXObject", + "applicationCache" // FF3 +]; + +domMemberMap.Location = +[ + "href", + "protocol", + "host", + "hostname", + "port", + "pathname", + "search", + "hash", + + "assign", + "reload", + "replace" +]; + +domMemberMap.Node = +[ + "id", + "className", + + "nodeType", + "tagName", + "nodeName", + "localName", + "prefix", + "namespaceURI", + "nodeValue", + + "ownerDocument", + "parentNode", + "offsetParent", + "nextSibling", + "previousSibling", + "firstChild", + "lastChild", + "childNodes", + "attributes", + + "dir", + "baseURI", + "textContent", + "innerHTML", + + "addEventListener", + "removeEventListener", + "dispatchEvent", + "cloneNode", + "appendChild", + "insertBefore", + "replaceChild", + "removeChild", + "compareDocumentPosition", + "hasAttributes", + "hasChildNodes", + "lookupNamespaceURI", + "lookupPrefix", + "normalize", + "isDefaultNamespace", + "isEqualNode", + "isSameNode", + "isSupported", + "getFeature", + "getUserData", + "setUserData" +]; + +domMemberMap.Document = extendArray(domMemberMap.Node, +[ + "documentElement", + "body", + "title", + "location", + "referrer", + "cookie", + "contentType", + "lastModified", + "characterSet", + "inputEncoding", + "xmlEncoding", + "xmlStandalone", + "xmlVersion", + "strictErrorChecking", + "documentURI", + "URL", + + "defaultView", + "doctype", + "implementation", + "styleSheets", + "images", + "links", + "forms", + "anchors", + "embeds", + "plugins", + "applets", + + "width", + "height", + + "designMode", + "compatMode", + "async", + "preferredStylesheetSet", + + "alinkColor", + "linkColor", + "vlinkColor", + "bgColor", + "fgColor", + "domain", + + "addEventListener", + "removeEventListener", + "dispatchEvent", + "captureEvents", + "releaseEvents", + "routeEvent", + "clear", + "open", + "close", + "execCommand", + "execCommandShowHelp", + "getElementsByName", + "getSelection", + "queryCommandEnabled", + "queryCommandIndeterm", + "queryCommandState", + "queryCommandSupported", + "queryCommandText", + "queryCommandValue", + "write", + "writeln", + "adoptNode", + "appendChild", + "removeChild", + "renameNode", + "cloneNode", + "compareDocumentPosition", + "createAttribute", + "createAttributeNS", + "createCDATASection", + "createComment", + "createDocumentFragment", + "createElement", + "createElementNS", + "createEntityReference", + "createEvent", + "createExpression", + "createNSResolver", + "createNodeIterator", + "createProcessingInstruction", + "createRange", + "createTextNode", + "createTreeWalker", + "domConfig", + "evaluate", + "evaluateFIXptr", + "evaluateXPointer", + "getAnonymousElementByAttribute", + "getAnonymousNodes", + "addBinding", + "removeBinding", + "getBindingParent", + "getBoxObjectFor", + "setBoxObjectFor", + "getElementById", + "getElementsByTagName", + "getElementsByTagNameNS", + "hasAttributes", + "hasChildNodes", + "importNode", + "insertBefore", + "isDefaultNamespace", + "isEqualNode", + "isSameNode", + "isSupported", + "load", + "loadBindingDocument", + "lookupNamespaceURI", + "lookupPrefix", + "normalize", + "normalizeDocument", + "getFeature", + "getUserData", + "setUserData" +]); + +domMemberMap.Element = extendArray(domMemberMap.Node, +[ + "clientWidth", + "clientHeight", + "offsetLeft", + "offsetTop", + "offsetWidth", + "offsetHeight", + "scrollLeft", + "scrollTop", + "scrollWidth", + "scrollHeight", + + "style", + + "tabIndex", + "title", + "lang", + "align", + "spellcheck", + + "addEventListener", + "removeEventListener", + "dispatchEvent", + "focus", + "blur", + "cloneNode", + "appendChild", + "insertBefore", + "replaceChild", + "removeChild", + "compareDocumentPosition", + "getElementsByTagName", + "getElementsByTagNameNS", + "getAttribute", + "getAttributeNS", + "getAttributeNode", + "getAttributeNodeNS", + "setAttribute", + "setAttributeNS", + "setAttributeNode", + "setAttributeNodeNS", + "removeAttribute", + "removeAttributeNS", + "removeAttributeNode", + "hasAttribute", + "hasAttributeNS", + "hasAttributes", + "hasChildNodes", + "lookupNamespaceURI", + "lookupPrefix", + "normalize", + "isDefaultNamespace", + "isEqualNode", + "isSameNode", + "isSupported", + "getFeature", + "getUserData", + "setUserData" +]); + +domMemberMap.SVGElement = extendArray(domMemberMap.Element, +[ + "x", + "y", + "width", + "height", + "rx", + "ry", + "transform", + "href", + + "ownerSVGElement", + "viewportElement", + "farthestViewportElement", + "nearestViewportElement", + + "getBBox", + "getCTM", + "getScreenCTM", + "getTransformToElement", + "getPresentationAttribute", + "preserveAspectRatio" +]); + +domMemberMap.SVGSVGElement = extendArray(domMemberMap.Element, +[ + "x", + "y", + "width", + "height", + "rx", + "ry", + "transform", + + "viewBox", + "viewport", + "currentView", + "useCurrentView", + "pixelUnitToMillimeterX", + "pixelUnitToMillimeterY", + "screenPixelToMillimeterX", + "screenPixelToMillimeterY", + "currentScale", + "currentTranslate", + "zoomAndPan", + + "ownerSVGElement", + "viewportElement", + "farthestViewportElement", + "nearestViewportElement", + "contentScriptType", + "contentStyleType", + + "getBBox", + "getCTM", + "getScreenCTM", + "getTransformToElement", + "getEnclosureList", + "getIntersectionList", + "getViewboxToViewportTransform", + "getPresentationAttribute", + "getElementById", + "checkEnclosure", + "checkIntersection", + "createSVGAngle", + "createSVGLength", + "createSVGMatrix", + "createSVGNumber", + "createSVGPoint", + "createSVGRect", + "createSVGString", + "createSVGTransform", + "createSVGTransformFromMatrix", + "deSelectAll", + "preserveAspectRatio", + "forceRedraw", + "suspendRedraw", + "unsuspendRedraw", + "unsuspendRedrawAll", + "getCurrentTime", + "setCurrentTime", + "animationsPaused", + "pauseAnimations", + "unpauseAnimations" +]); + +domMemberMap.HTMLImageElement = extendArray(domMemberMap.Element, +[ + "src", + "naturalWidth", + "naturalHeight", + "width", + "height", + "x", + "y", + "name", + "alt", + "longDesc", + "lowsrc", + "border", + "complete", + "hspace", + "vspace", + "isMap", + "useMap" +]); + +domMemberMap.HTMLAnchorElement = extendArray(domMemberMap.Element, +[ + "name", + "target", + "accessKey", + "href", + "protocol", + "host", + "hostname", + "port", + "pathname", + "search", + "hash", + "hreflang", + "coords", + "shape", + "text", + "type", + "rel", + "rev", + "charset" +]); + +domMemberMap.HTMLIFrameElement = extendArray(domMemberMap.Element, +[ + "contentDocument", + "contentWindow", + "frameBorder", + "height", + "longDesc", + "marginHeight", + "marginWidth", + "name", + "scrolling", + "src", + "width" +]); + +domMemberMap.HTMLTableElement = extendArray(domMemberMap.Element, +[ + "bgColor", + "border", + "caption", + "cellPadding", + "cellSpacing", + "frame", + "rows", + "rules", + "summary", + "tBodies", + "tFoot", + "tHead", + "width", + + "createCaption", + "createTFoot", + "createTHead", + "deleteCaption", + "deleteRow", + "deleteTFoot", + "deleteTHead", + "insertRow" +]); + +domMemberMap.HTMLTableRowElement = extendArray(domMemberMap.Element, +[ + "bgColor", + "cells", + "ch", + "chOff", + "rowIndex", + "sectionRowIndex", + "vAlign", + + "deleteCell", + "insertCell" +]); + +domMemberMap.HTMLTableCellElement = extendArray(domMemberMap.Element, +[ + "abbr", + "axis", + "bgColor", + "cellIndex", + "ch", + "chOff", + "colSpan", + "headers", + "height", + "noWrap", + "rowSpan", + "scope", + "vAlign", + "width" + +]); + +domMemberMap.HTMLScriptElement = extendArray(domMemberMap.Element, +[ + "src" +]); + +domMemberMap.HTMLButtonElement = extendArray(domMemberMap.Element, +[ + "accessKey", + "disabled", + "form", + "name", + "type", + "value", + + "click" +]); + +domMemberMap.HTMLInputElement = extendArray(domMemberMap.Element, +[ + "type", + "value", + "checked", + "accept", + "accessKey", + "alt", + "controllers", + "defaultChecked", + "defaultValue", + "disabled", + "form", + "maxLength", + "name", + "readOnly", + "selectionEnd", + "selectionStart", + "size", + "src", + "textLength", + "useMap", + + "click", + "select", + "setSelectionRange" +]); + +domMemberMap.HTMLFormElement = extendArray(domMemberMap.Element, +[ + "acceptCharset", + "action", + "author", + "elements", + "encoding", + "enctype", + "entry_id", + "length", + "method", + "name", + "post", + "target", + "text", + "url", + + "reset", + "submit" +]); + +domMemberMap.HTMLBodyElement = extendArray(domMemberMap.Element, +[ + "aLink", + "background", + "bgColor", + "link", + "text", + "vLink" +]); + +domMemberMap.HTMLHtmlElement = extendArray(domMemberMap.Element, +[ + "version" +]); + +domMemberMap.Text = extendArray(domMemberMap.Node, +[ + "data", + "length", + + "appendData", + "deleteData", + "insertData", + "replaceData", + "splitText", + "substringData" +]); + +domMemberMap.Attr = extendArray(domMemberMap.Node, +[ + "name", + "value", + "specified", + "ownerElement" +]); + +domMemberMap.Event = +[ + "type", + "target", + "currentTarget", + "originalTarget", + "explicitOriginalTarget", + "relatedTarget", + "rangeParent", + "rangeOffset", + "view", + + "keyCode", + "charCode", + "screenX", + "screenY", + "clientX", + "clientY", + "layerX", + "layerY", + "pageX", + "pageY", + + "detail", + "button", + "which", + "ctrlKey", + "shiftKey", + "altKey", + "metaKey", + + "eventPhase", + "timeStamp", + "bubbles", + "cancelable", + "cancelBubble", + + "isTrusted", + "isChar", + + "getPreventDefault", + "initEvent", + "initMouseEvent", + "initKeyEvent", + "initUIEvent", + "preventBubble", + "preventCapture", + "preventDefault", + "stopPropagation" +]; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +this.domConstantMap = +{ + "ELEMENT_NODE": 1, + "ATTRIBUTE_NODE": 1, + "TEXT_NODE": 1, + "CDATA_SECTION_NODE": 1, + "ENTITY_REFERENCE_NODE": 1, + "ENTITY_NODE": 1, + "PROCESSING_INSTRUCTION_NODE": 1, + "COMMENT_NODE": 1, + "DOCUMENT_NODE": 1, + "DOCUMENT_TYPE_NODE": 1, + "DOCUMENT_FRAGMENT_NODE": 1, + "NOTATION_NODE": 1, + + "DOCUMENT_POSITION_DISCONNECTED": 1, + "DOCUMENT_POSITION_PRECEDING": 1, + "DOCUMENT_POSITION_FOLLOWING": 1, + "DOCUMENT_POSITION_CONTAINS": 1, + "DOCUMENT_POSITION_CONTAINED_BY": 1, + "DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC": 1, + + "UNKNOWN_RULE": 1, + "STYLE_RULE": 1, + "CHARSET_RULE": 1, + "IMPORT_RULE": 1, + "MEDIA_RULE": 1, + "FONT_FACE_RULE": 1, + "PAGE_RULE": 1, + + "CAPTURING_PHASE": 1, + "AT_TARGET": 1, + "BUBBLING_PHASE": 1, + + "SCROLL_PAGE_UP": 1, + "SCROLL_PAGE_DOWN": 1, + + "MOUSEUP": 1, + "MOUSEDOWN": 1, + "MOUSEOVER": 1, + "MOUSEOUT": 1, + "MOUSEMOVE": 1, + "MOUSEDRAG": 1, + "CLICK": 1, + "DBLCLICK": 1, + "KEYDOWN": 1, + "KEYUP": 1, + "KEYPRESS": 1, + "DRAGDROP": 1, + "FOCUS": 1, + "BLUR": 1, + "SELECT": 1, + "CHANGE": 1, + "RESET": 1, + "SUBMIT": 1, + "SCROLL": 1, + "LOAD": 1, + "UNLOAD": 1, + "XFER_DONE": 1, + "ABORT": 1, + "ERROR": 1, + "LOCATE": 1, + "MOVE": 1, + "RESIZE": 1, + "FORWARD": 1, + "HELP": 1, + "BACK": 1, + "TEXT": 1, + + "ALT_MASK": 1, + "CONTROL_MASK": 1, + "SHIFT_MASK": 1, + "META_MASK": 1, + + "DOM_VK_TAB": 1, + "DOM_VK_PAGE_UP": 1, + "DOM_VK_PAGE_DOWN": 1, + "DOM_VK_UP": 1, + "DOM_VK_DOWN": 1, + "DOM_VK_LEFT": 1, + "DOM_VK_RIGHT": 1, + "DOM_VK_CANCEL": 1, + "DOM_VK_HELP": 1, + "DOM_VK_BACK_SPACE": 1, + "DOM_VK_CLEAR": 1, + "DOM_VK_RETURN": 1, + "DOM_VK_ENTER": 1, + "DOM_VK_SHIFT": 1, + "DOM_VK_CONTROL": 1, + "DOM_VK_ALT": 1, + "DOM_VK_PAUSE": 1, + "DOM_VK_CAPS_LOCK": 1, + "DOM_VK_ESCAPE": 1, + "DOM_VK_SPACE": 1, + "DOM_VK_END": 1, + "DOM_VK_HOME": 1, + "DOM_VK_PRINTSCREEN": 1, + "DOM_VK_INSERT": 1, + "DOM_VK_DELETE": 1, + "DOM_VK_0": 1, + "DOM_VK_1": 1, + "DOM_VK_2": 1, + "DOM_VK_3": 1, + "DOM_VK_4": 1, + "DOM_VK_5": 1, + "DOM_VK_6": 1, + "DOM_VK_7": 1, + "DOM_VK_8": 1, + "DOM_VK_9": 1, + "DOM_VK_SEMICOLON": 1, + "DOM_VK_EQUALS": 1, + "DOM_VK_A": 1, + "DOM_VK_B": 1, + "DOM_VK_C": 1, + "DOM_VK_D": 1, + "DOM_VK_E": 1, + "DOM_VK_F": 1, + "DOM_VK_G": 1, + "DOM_VK_H": 1, + "DOM_VK_I": 1, + "DOM_VK_J": 1, + "DOM_VK_K": 1, + "DOM_VK_L": 1, + "DOM_VK_M": 1, + "DOM_VK_N": 1, + "DOM_VK_O": 1, + "DOM_VK_P": 1, + "DOM_VK_Q": 1, + "DOM_VK_R": 1, + "DOM_VK_S": 1, + "DOM_VK_T": 1, + "DOM_VK_U": 1, + "DOM_VK_V": 1, + "DOM_VK_W": 1, + "DOM_VK_X": 1, + "DOM_VK_Y": 1, + "DOM_VK_Z": 1, + "DOM_VK_CONTEXT_MENU": 1, + "DOM_VK_NUMPAD0": 1, + "DOM_VK_NUMPAD1": 1, + "DOM_VK_NUMPAD2": 1, + "DOM_VK_NUMPAD3": 1, + "DOM_VK_NUMPAD4": 1, + "DOM_VK_NUMPAD5": 1, + "DOM_VK_NUMPAD6": 1, + "DOM_VK_NUMPAD7": 1, + "DOM_VK_NUMPAD8": 1, + "DOM_VK_NUMPAD9": 1, + "DOM_VK_MULTIPLY": 1, + "DOM_VK_ADD": 1, + "DOM_VK_SEPARATOR": 1, + "DOM_VK_SUBTRACT": 1, + "DOM_VK_DECIMAL": 1, + "DOM_VK_DIVIDE": 1, + "DOM_VK_F1": 1, + "DOM_VK_F2": 1, + "DOM_VK_F3": 1, + "DOM_VK_F4": 1, + "DOM_VK_F5": 1, + "DOM_VK_F6": 1, + "DOM_VK_F7": 1, + "DOM_VK_F8": 1, + "DOM_VK_F9": 1, + "DOM_VK_F10": 1, + "DOM_VK_F11": 1, + "DOM_VK_F12": 1, + "DOM_VK_F13": 1, + "DOM_VK_F14": 1, + "DOM_VK_F15": 1, + "DOM_VK_F16": 1, + "DOM_VK_F17": 1, + "DOM_VK_F18": 1, + "DOM_VK_F19": 1, + "DOM_VK_F20": 1, + "DOM_VK_F21": 1, + "DOM_VK_F22": 1, + "DOM_VK_F23": 1, + "DOM_VK_F24": 1, + "DOM_VK_NUM_LOCK": 1, + "DOM_VK_SCROLL_LOCK": 1, + "DOM_VK_COMMA": 1, + "DOM_VK_PERIOD": 1, + "DOM_VK_SLASH": 1, + "DOM_VK_BACK_QUOTE": 1, + "DOM_VK_OPEN_BRACKET": 1, + "DOM_VK_BACK_SLASH": 1, + "DOM_VK_CLOSE_BRACKET": 1, + "DOM_VK_QUOTE": 1, + "DOM_VK_META": 1, + + "SVG_ZOOMANDPAN_DISABLE": 1, + "SVG_ZOOMANDPAN_MAGNIFY": 1, + "SVG_ZOOMANDPAN_UNKNOWN": 1 +}; + +this.cssInfo = +{ + "background": ["bgRepeat", "bgAttachment", "bgPosition", "color", "systemColor", "none"], + "background-attachment": ["bgAttachment"], + "background-color": ["color", "systemColor"], + "background-image": ["none"], + "background-position": ["bgPosition"], + "background-repeat": ["bgRepeat"], + + "border": ["borderStyle", "thickness", "color", "systemColor", "none"], + "border-top": ["borderStyle", "borderCollapse", "color", "systemColor", "none"], + "border-right": ["borderStyle", "borderCollapse", "color", "systemColor", "none"], + "border-bottom": ["borderStyle", "borderCollapse", "color", "systemColor", "none"], + "border-left": ["borderStyle", "borderCollapse", "color", "systemColor", "none"], + "border-collapse": ["borderCollapse"], + "border-color": ["color", "systemColor"], + "border-top-color": ["color", "systemColor"], + "border-right-color": ["color", "systemColor"], + "border-bottom-color": ["color", "systemColor"], + "border-left-color": ["color", "systemColor"], + "border-spacing": [], + "border-style": ["borderStyle"], + "border-top-style": ["borderStyle"], + "border-right-style": ["borderStyle"], + "border-bottom-style": ["borderStyle"], + "border-left-style": ["borderStyle"], + "border-width": ["thickness"], + "border-top-width": ["thickness"], + "border-right-width": ["thickness"], + "border-bottom-width": ["thickness"], + "border-left-width": ["thickness"], + + "bottom": ["auto"], + "caption-side": ["captionSide"], + "clear": ["clear", "none"], + "clip": ["auto"], + "color": ["color", "systemColor"], + "content": ["content"], + "counter-increment": ["none"], + "counter-reset": ["none"], + "cursor": ["cursor", "none"], + "direction": ["direction"], + "display": ["display", "none"], + "empty-cells": [], + "float": ["float", "none"], + "font": ["fontStyle", "fontVariant", "fontWeight", "fontFamily"], + + "font-family": ["fontFamily"], + "font-size": ["fontSize"], + "font-size-adjust": [], + "font-stretch": [], + "font-style": ["fontStyle"], + "font-variant": ["fontVariant"], + "font-weight": ["fontWeight"], + + "height": ["auto"], + "left": ["auto"], + "letter-spacing": [], + "line-height": [], + + "list-style": ["listStyleType", "listStylePosition", "none"], + "list-style-image": ["none"], + "list-style-position": ["listStylePosition"], + "list-style-type": ["listStyleType", "none"], + + "margin": [], + "margin-top": [], + "margin-right": [], + "margin-bottom": [], + "margin-left": [], + + "marker-offset": ["auto"], + "min-height": ["none"], + "max-height": ["none"], + "min-width": ["none"], + "max-width": ["none"], + + "outline": ["borderStyle", "color", "systemColor", "none"], + "outline-color": ["color", "systemColor"], + "outline-style": ["borderStyle"], + "outline-width": [], + + "overflow": ["overflow", "auto"], + "overflow-x": ["overflow", "auto"], + "overflow-y": ["overflow", "auto"], + + "padding": [], + "padding-top": [], + "padding-right": [], + "padding-bottom": [], + "padding-left": [], + + "position": ["position"], + "quotes": ["none"], + "right": ["auto"], + "table-layout": ["tableLayout", "auto"], + "text-align": ["textAlign"], + "text-decoration": ["textDecoration", "none"], + "text-indent": [], + "text-shadow": [], + "text-transform": ["textTransform", "none"], + "top": ["auto"], + "unicode-bidi": [], + "vertical-align": ["verticalAlign"], + "white-space": ["whiteSpace"], + "width": ["auto"], + "word-spacing": [], + "z-index": [], + + "-moz-appearance": ["mozAppearance"], + "-moz-border-radius": [], + "-moz-border-radius-bottomleft": [], + "-moz-border-radius-bottomright": [], + "-moz-border-radius-topleft": [], + "-moz-border-radius-topright": [], + "-moz-border-top-colors": ["color", "systemColor"], + "-moz-border-right-colors": ["color", "systemColor"], + "-moz-border-bottom-colors": ["color", "systemColor"], + "-moz-border-left-colors": ["color", "systemColor"], + "-moz-box-align": ["mozBoxAlign"], + "-moz-box-direction": ["mozBoxDirection"], + "-moz-box-flex": [], + "-moz-box-ordinal-group": [], + "-moz-box-orient": ["mozBoxOrient"], + "-moz-box-pack": ["mozBoxPack"], + "-moz-box-sizing": ["mozBoxSizing"], + "-moz-opacity": [], + "-moz-user-focus": ["userFocus", "none"], + "-moz-user-input": ["userInput"], + "-moz-user-modify": [], + "-moz-user-select": ["userSelect", "none"], + "-moz-background-clip": [], + "-moz-background-inline-policy": [], + "-moz-background-origin": [], + "-moz-binding": [], + "-moz-column-count": [], + "-moz-column-gap": [], + "-moz-column-width": [], + "-moz-image-region": [] +}; + +this.inheritedStyleNames = +{ + "border-collapse": 1, + "border-spacing": 1, + "border-style": 1, + "caption-side": 1, + "color": 1, + "cursor": 1, + "direction": 1, + "empty-cells": 1, + "font": 1, + "font-family": 1, + "font-size-adjust": 1, + "font-size": 1, + "font-style": 1, + "font-variant": 1, + "font-weight": 1, + "letter-spacing": 1, + "line-height": 1, + "list-style": 1, + "list-style-image": 1, + "list-style-position": 1, + "list-style-type": 1, + "quotes": 1, + "text-align": 1, + "text-decoration": 1, + "text-indent": 1, + "text-shadow": 1, + "text-transform": 1, + "white-space": 1, + "word-spacing": 1 +}; + +this.cssKeywords = +{ + "appearance": + [ + "button", + "button-small", + "checkbox", + "checkbox-container", + "checkbox-small", + "dialog", + "listbox", + "menuitem", + "menulist", + "menulist-button", + "menulist-textfield", + "menupopup", + "progressbar", + "radio", + "radio-container", + "radio-small", + "resizer", + "scrollbar", + "scrollbarbutton-down", + "scrollbarbutton-left", + "scrollbarbutton-right", + "scrollbarbutton-up", + "scrollbartrack-horizontal", + "scrollbartrack-vertical", + "separator", + "statusbar", + "tab", + "tab-left-edge", + "tabpanels", + "textfield", + "toolbar", + "toolbarbutton", + "toolbox", + "tooltip", + "treeheadercell", + "treeheadersortarrow", + "treeitem", + "treetwisty", + "treetwistyopen", + "treeview", + "window" + ], + + "systemColor": + [ + "ActiveBorder", + "ActiveCaption", + "AppWorkspace", + "Background", + "ButtonFace", + "ButtonHighlight", + "ButtonShadow", + "ButtonText", + "CaptionText", + "GrayText", + "Highlight", + "HighlightText", + "InactiveBorder", + "InactiveCaption", + "InactiveCaptionText", + "InfoBackground", + "InfoText", + "Menu", + "MenuText", + "Scrollbar", + "ThreeDDarkShadow", + "ThreeDFace", + "ThreeDHighlight", + "ThreeDLightShadow", + "ThreeDShadow", + "Window", + "WindowFrame", + "WindowText", + "-moz-field", + "-moz-fieldtext", + "-moz-workspace", + "-moz-visitedhyperlinktext", + "-moz-use-text-color" + ], + + "color": + [ + "AliceBlue", + "AntiqueWhite", + "Aqua", + "Aquamarine", + "Azure", + "Beige", + "Bisque", + "Black", + "BlanchedAlmond", + "Blue", + "BlueViolet", + "Brown", + "BurlyWood", + "CadetBlue", + "Chartreuse", + "Chocolate", + "Coral", + "CornflowerBlue", + "Cornsilk", + "Crimson", + "Cyan", + "DarkBlue", + "DarkCyan", + "DarkGoldenRod", + "DarkGray", + "DarkGreen", + "DarkKhaki", + "DarkMagenta", + "DarkOliveGreen", + "DarkOrange", + "DarkOrchid", + "DarkRed", + "DarkSalmon", + "DarkSeaGreen", + "DarkSlateBlue", + "DarkSlateGray", + "DarkTurquoise", + "DarkViolet", + "DeepPink", + "DarkSkyBlue", + "DimGray", + "DodgerBlue", + "Feldspar", + "FireBrick", + "FloralWhite", + "ForestGreen", + "Fuchsia", + "Gainsboro", + "GhostWhite", + "Gold", + "GoldenRod", + "Gray", + "Green", + "GreenYellow", + "HoneyDew", + "HotPink", + "IndianRed", + "Indigo", + "Ivory", + "Khaki", + "Lavender", + "LavenderBlush", + "LawnGreen", + "LemonChiffon", + "LightBlue", + "LightCoral", + "LightCyan", + "LightGoldenRodYellow", + "LightGrey", + "LightGreen", + "LightPink", + "LightSalmon", + "LightSeaGreen", + "LightSkyBlue", + "LightSlateBlue", + "LightSlateGray", + "LightSteelBlue", + "LightYellow", + "Lime", + "LimeGreen", + "Linen", + "Magenta", + "Maroon", + "MediumAquaMarine", + "MediumBlue", + "MediumOrchid", + "MediumPurple", + "MediumSeaGreen", + "MediumSlateBlue", + "MediumSpringGreen", + "MediumTurquoise", + "MediumVioletRed", + "MidnightBlue", + "MintCream", + "MistyRose", + "Moccasin", + "NavajoWhite", + "Navy", + "OldLace", + "Olive", + "OliveDrab", + "Orange", + "OrangeRed", + "Orchid", + "PaleGoldenRod", + "PaleGreen", + "PaleTurquoise", + "PaleVioletRed", + "PapayaWhip", + "PeachPuff", + "Peru", + "Pink", + "Plum", + "PowderBlue", + "Purple", + "Red", + "RosyBrown", + "RoyalBlue", + "SaddleBrown", + "Salmon", + "SandyBrown", + "SeaGreen", + "SeaShell", + "Sienna", + "Silver", + "SkyBlue", + "SlateBlue", + "SlateGray", + "Snow", + "SpringGreen", + "SteelBlue", + "Tan", + "Teal", + "Thistle", + "Tomato", + "Turquoise", + "Violet", + "VioletRed", + "Wheat", + "White", + "WhiteSmoke", + "Yellow", + "YellowGreen", + "transparent", + "invert" + ], + + "auto": + [ + "auto" + ], + + "none": + [ + "none" + ], + + "captionSide": + [ + "top", + "bottom", + "left", + "right" + ], + + "clear": + [ + "left", + "right", + "both" + ], + + "cursor": + [ + "auto", + "cell", + "context-menu", + "crosshair", + "default", + "help", + "pointer", + "progress", + "move", + "e-resize", + "all-scroll", + "ne-resize", + "nw-resize", + "n-resize", + "se-resize", + "sw-resize", + "s-resize", + "w-resize", + "ew-resize", + "ns-resize", + "nesw-resize", + "nwse-resize", + "col-resize", + "row-resize", + "text", + "vertical-text", + "wait", + "alias", + "copy", + "move", + "no-drop", + "not-allowed", + "-moz-alias", + "-moz-cell", + "-moz-copy", + "-moz-grab", + "-moz-grabbing", + "-moz-contextmenu", + "-moz-zoom-in", + "-moz-zoom-out", + "-moz-spinning" + ], + + "direction": + [ + "ltr", + "rtl" + ], + + "bgAttachment": + [ + "scroll", + "fixed" + ], + + "bgPosition": + [ + "top", + "center", + "bottom", + "left", + "right" + ], + + "bgRepeat": + [ + "repeat", + "repeat-x", + "repeat-y", + "no-repeat" + ], + + "borderStyle": + [ + "hidden", + "dotted", + "dashed", + "solid", + "double", + "groove", + "ridge", + "inset", + "outset", + "-moz-bg-inset", + "-moz-bg-outset", + "-moz-bg-solid" + ], + + "borderCollapse": + [ + "collapse", + "separate" + ], + + "overflow": + [ + "visible", + "hidden", + "scroll", + "-moz-scrollbars-horizontal", + "-moz-scrollbars-none", + "-moz-scrollbars-vertical" + ], + + "listStyleType": + [ + "disc", + "circle", + "square", + "decimal", + "decimal-leading-zero", + "lower-roman", + "upper-roman", + "lower-greek", + "lower-alpha", + "lower-latin", + "upper-alpha", + "upper-latin", + "hebrew", + "armenian", + "georgian", + "cjk-ideographic", + "hiragana", + "katakana", + "hiragana-iroha", + "katakana-iroha", + "inherit" + ], + + "listStylePosition": + [ + "inside", + "outside" + ], + + "content": + [ + "open-quote", + "close-quote", + "no-open-quote", + "no-close-quote", + "inherit" + ], + + "fontStyle": + [ + "normal", + "italic", + "oblique", + "inherit" + ], + + "fontVariant": + [ + "normal", + "small-caps", + "inherit" + ], + + "fontWeight": + [ + "normal", + "bold", + "bolder", + "lighter", + "inherit" + ], + + "fontSize": + [ + "xx-small", + "x-small", + "small", + "medium", + "large", + "x-large", + "xx-large", + "smaller", + "larger" + ], + + "fontFamily": + [ + "Arial", + "Comic Sans MS", + "Georgia", + "Tahoma", + "Verdana", + "Times New Roman", + "Trebuchet MS", + "Lucida Grande", + "Helvetica", + "serif", + "sans-serif", + "cursive", + "fantasy", + "monospace", + "caption", + "icon", + "menu", + "message-box", + "small-caption", + "status-bar", + "inherit" + ], + + "display": + [ + "block", + "inline", + "inline-block", + "list-item", + "marker", + "run-in", + "compact", + "table", + "inline-table", + "table-row-group", + "table-column", + "table-column-group", + "table-header-group", + "table-footer-group", + "table-row", + "table-cell", + "table-caption", + "-moz-box", + "-moz-compact", + "-moz-deck", + "-moz-grid", + "-moz-grid-group", + "-moz-grid-line", + "-moz-groupbox", + "-moz-inline-block", + "-moz-inline-box", + "-moz-inline-grid", + "-moz-inline-stack", + "-moz-inline-table", + "-moz-marker", + "-moz-popup", + "-moz-runin", + "-moz-stack" + ], + + "position": + [ + "static", + "relative", + "absolute", + "fixed", + "inherit" + ], + + "float": + [ + "left", + "right" + ], + + "textAlign": + [ + "left", + "right", + "center", + "justify" + ], + + "tableLayout": + [ + "fixed" + ], + + "textDecoration": + [ + "underline", + "overline", + "line-through", + "blink" + ], + + "textTransform": + [ + "capitalize", + "lowercase", + "uppercase", + "inherit" + ], + + "unicodeBidi": + [ + "normal", + "embed", + "bidi-override" + ], + + "whiteSpace": + [ + "normal", + "pre", + "nowrap" + ], + + "verticalAlign": + [ + "baseline", + "sub", + "super", + "top", + "text-top", + "middle", + "bottom", + "text-bottom", + "inherit" + ], + + "thickness": + [ + "thin", + "medium", + "thick" + ], + + "userFocus": + [ + "ignore", + "normal" + ], + + "userInput": + [ + "disabled", + "enabled" + ], + + "userSelect": + [ + "normal" + ], + + "mozBoxSizing": + [ + "content-box", + "padding-box", + "border-box" + ], + + "mozBoxAlign": + [ + "start", + "center", + "end", + "baseline", + "stretch" + ], + + "mozBoxDirection": + [ + "normal", + "reverse" + ], + + "mozBoxOrient": + [ + "horizontal", + "vertical" + ], + + "mozBoxPack": + [ + "start", + "center", + "end" + ] +}; + +this.nonEditableTags = +{ + "HTML": 1, + "HEAD": 1, + "html": 1, + "head": 1 +}; + +this.innerEditableTags = +{ + "BODY": 1, + "body": 1 +}; + +this.selfClosingTags = +{ // End tags for void elements are forbidden http://wiki.whatwg.org/wiki/HTML_vs._XHTML + "meta": 1, + "link": 1, + "area": 1, + "base": 1, + "col": 1, + "input": 1, + "img": 1, + "br": 1, + "hr": 1, + "param":1, + "embed":1 +}; + +var invisibleTags = this.invisibleTags = +{ + "HTML": 1, + "HEAD": 1, + "TITLE": 1, + "META": 1, + "LINK": 1, + "STYLE": 1, + "SCRIPT": 1, + "NOSCRIPT": 1, + "BR": 1, + "PARAM": 1, + "COL": 1, + + "html": 1, + "head": 1, + "title": 1, + "meta": 1, + "link": 1, + "style": 1, + "script": 1, + "noscript": 1, + "br": 1, + "param": 1, + "col": 1 + /* + "window": 1, + "browser": 1, + "frame": 1, + "tabbrowser": 1, + "WINDOW": 1, + "BROWSER": 1, + "FRAME": 1, + "TABBROWSER": 1, + */ +}; + + +if (typeof KeyEvent == "undefined") { + this.KeyEvent = { + DOM_VK_CANCEL: 3, + DOM_VK_HELP: 6, + DOM_VK_BACK_SPACE: 8, + DOM_VK_TAB: 9, + DOM_VK_CLEAR: 12, + DOM_VK_RETURN: 13, + DOM_VK_ENTER: 14, + DOM_VK_SHIFT: 16, + DOM_VK_CONTROL: 17, + DOM_VK_ALT: 18, + DOM_VK_PAUSE: 19, + DOM_VK_CAPS_LOCK: 20, + DOM_VK_ESCAPE: 27, + DOM_VK_SPACE: 32, + DOM_VK_PAGE_UP: 33, + DOM_VK_PAGE_DOWN: 34, + DOM_VK_END: 35, + DOM_VK_HOME: 36, + DOM_VK_LEFT: 37, + DOM_VK_UP: 38, + DOM_VK_RIGHT: 39, + DOM_VK_DOWN: 40, + DOM_VK_PRINTSCREEN: 44, + DOM_VK_INSERT: 45, + DOM_VK_DELETE: 46, + DOM_VK_0: 48, + DOM_VK_1: 49, + DOM_VK_2: 50, + DOM_VK_3: 51, + DOM_VK_4: 52, + DOM_VK_5: 53, + DOM_VK_6: 54, + DOM_VK_7: 55, + DOM_VK_8: 56, + DOM_VK_9: 57, + DOM_VK_SEMICOLON: 59, + DOM_VK_EQUALS: 61, + DOM_VK_A: 65, + DOM_VK_B: 66, + DOM_VK_C: 67, + DOM_VK_D: 68, + DOM_VK_E: 69, + DOM_VK_F: 70, + DOM_VK_G: 71, + DOM_VK_H: 72, + DOM_VK_I: 73, + DOM_VK_J: 74, + DOM_VK_K: 75, + DOM_VK_L: 76, + DOM_VK_M: 77, + DOM_VK_N: 78, + DOM_VK_O: 79, + DOM_VK_P: 80, + DOM_VK_Q: 81, + DOM_VK_R: 82, + DOM_VK_S: 83, + DOM_VK_T: 84, + DOM_VK_U: 85, + DOM_VK_V: 86, + DOM_VK_W: 87, + DOM_VK_X: 88, + DOM_VK_Y: 89, + DOM_VK_Z: 90, + DOM_VK_CONTEXT_MENU: 93, + DOM_VK_NUMPAD0: 96, + DOM_VK_NUMPAD1: 97, + DOM_VK_NUMPAD2: 98, + DOM_VK_NUMPAD3: 99, + DOM_VK_NUMPAD4: 100, + DOM_VK_NUMPAD5: 101, + DOM_VK_NUMPAD6: 102, + DOM_VK_NUMPAD7: 103, + DOM_VK_NUMPAD8: 104, + DOM_VK_NUMPAD9: 105, + DOM_VK_MULTIPLY: 106, + DOM_VK_ADD: 107, + DOM_VK_SEPARATOR: 108, + DOM_VK_SUBTRACT: 109, + DOM_VK_DECIMAL: 110, + DOM_VK_DIVIDE: 111, + DOM_VK_F1: 112, + DOM_VK_F2: 113, + DOM_VK_F3: 114, + DOM_VK_F4: 115, + DOM_VK_F5: 116, + DOM_VK_F6: 117, + DOM_VK_F7: 118, + DOM_VK_F8: 119, + DOM_VK_F9: 120, + DOM_VK_F10: 121, + DOM_VK_F11: 122, + DOM_VK_F12: 123, + DOM_VK_F13: 124, + DOM_VK_F14: 125, + DOM_VK_F15: 126, + DOM_VK_F16: 127, + DOM_VK_F17: 128, + DOM_VK_F18: 129, + DOM_VK_F19: 130, + DOM_VK_F20: 131, + DOM_VK_F21: 132, + DOM_VK_F22: 133, + DOM_VK_F23: 134, + DOM_VK_F24: 135, + DOM_VK_NUM_LOCK: 144, + DOM_VK_SCROLL_LOCK: 145, + DOM_VK_COMMA: 188, + DOM_VK_PERIOD: 190, + DOM_VK_SLASH: 191, + DOM_VK_BACK_QUOTE: 192, + DOM_VK_OPEN_BRACKET: 219, + DOM_VK_BACK_SLASH: 220, + DOM_VK_CLOSE_BRACKET: 221, + DOM_VK_QUOTE: 222, + DOM_VK_META: 224 + }; +} + + +// ************************************************************************************************ +// Ajax + +/** + * @namespace + */ +this.Ajax = +{ + + requests: [], + transport: null, + states: ["Uninitialized","Loading","Loaded","Interactive","Complete"], + + initialize: function() + { + this.transport = FBL.getNativeXHRObject(); + }, + + getXHRObject: function() + { + var xhrObj = false; + try + { + xhrObj = new XMLHttpRequest(); + } + catch(e) + { + var progid = [ + "MSXML2.XMLHTTP.5.0", "MSXML2.XMLHTTP.4.0", + "MSXML2.XMLHTTP.3.0", "MSXML2.XMLHTTP", "Microsoft.XMLHTTP" + ]; + + for ( var i=0; i < progid.length; ++i ) { + try + { + xhrObj = new ActiveXObject(progid[i]); + } + catch(e) + { + continue; + } + break; + } + } + finally + { + return xhrObj; + } + }, + + + /** + * Create a AJAX request. + * + * @name request + * @param {Object} options request options + * @param {String} options.url URL to be requested + * @param {String} options.type Request type ("get" ou "post"). Default is "get". + * @param {Boolean} options.async Asynchronous flag. Default is "true". + * @param {String} options.dataType Data type ("text", "html", "xml" or "json"). Default is "text". + * @param {String} options.contentType Content-type of the data being sent. Default is "application/x-www-form-urlencoded". + * @param {Function} options.onLoading onLoading callback + * @param {Function} options.onLoaded onLoaded callback + * @param {Function} options.onInteractive onInteractive callback + * @param {Function} options.onComplete onComplete callback + * @param {Function} options.onUpdate onUpdate callback + * @param {Function} options.onSuccess onSuccess callback + * @param {Function} options.onFailure onFailure callback + */ + request: function(options) + { + // process options + var o = FBL.extend( + { + // default values + type: "get", + async: true, + dataType: "text", + contentType: "application/x-www-form-urlencoded" + }, + options || {} + ); + + this.requests.push(o); + + var s = this.getState(); + if (s == "Uninitialized" || s == "Complete" || s == "Loaded") + this.sendRequest(); + }, + + serialize: function(data) + { + var r = [""], rl = 0; + if (data) { + if (typeof data == "string") r[rl++] = data; + + else if (data.innerHTML && data.elements) { + for (var i=0,el,l=(el=data.elements).length; i < l; i++) + if (el[i].name) { + r[rl++] = encodeURIComponent(el[i].name); + r[rl++] = "="; + r[rl++] = encodeURIComponent(el[i].value); + r[rl++] = "&"; + } + + } else + for(var param in data) { + r[rl++] = encodeURIComponent(param); + r[rl++] = "="; + r[rl++] = encodeURIComponent(data[param]); + r[rl++] = "&"; + } + } + return r.join("").replace(/&$/, ""); + }, + + sendRequest: function() + { + var t = FBL.Ajax.transport, r = FBL.Ajax.requests.shift(), data; + + // open XHR object + t.open(r.type, r.url, r.async); + + //setRequestHeaders(); + + // indicates that it is a XHR request to the server + t.setRequestHeader("X-Requested-With", "XMLHttpRequest"); + + // if data is being sent, sets the appropriate content-type + if (data = FBL.Ajax.serialize(r.data)) + t.setRequestHeader("Content-Type", r.contentType); + + /** @ignore */ + // onreadystatechange handler + t.onreadystatechange = function() + { + FBL.Ajax.onStateChange(r); + }; + + // send the request + t.send(data); + }, + + /** + * Handles the state change + */ + onStateChange: function(options) + { + var fn, o = options, t = this.transport; + var state = this.getState(t); + + if (fn = o["on" + state]) fn(this.getResponse(o), o); + + if (state == "Complete") + { + var success = t.status == 200, response = this.getResponse(o); + + if (fn = o["onUpdate"]) + fn(response, o); + + if (fn = o["on" + (success ? "Success" : "Failure")]) + fn(response, o); + + t.onreadystatechange = FBL.emptyFn; + + if (this.requests.length > 0) + setTimeout(this.sendRequest, 10); + } + }, + + /** + * gets the appropriate response value according the type + */ + getResponse: function(options) + { + var t = this.transport, type = options.dataType; + + if (t.status != 200) return t.statusText; + else if (type == "text") return t.responseText; + else if (type == "html") return t.responseText; + else if (type == "xml") return t.responseXML; + else if (type == "json") return eval("(" + t.responseText + ")"); + }, + + /** + * returns the current state of the XHR object + */ + getState: function() + { + return this.states[this.transport.readyState]; + } + +}; + + +// ************************************************************************************************ +// Cookie, from http://www.quirksmode.org/js/cookies.html + +this.createCookie = function(name,value,days) +{ + if ('cookie' in document) + { + if (days) + { + var date = new Date(); + date.setTime(date.getTime()+(days*24*60*60*1000)); + var expires = "; expires="+date.toGMTString(); + } + else + var expires = ""; + + document.cookie = name+"="+value+expires+"; path=/"; + } +}; + +this.readCookie = function (name) +{ + if ('cookie' in document) + { + var nameEQ = name + "="; + var ca = document.cookie.split(';'); + + for(var i=0; i < ca.length; i++) + { + var c = ca[i]; + while (c.charAt(0)==' ') c = c.substring(1,c.length); + if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length); + } + } + + return null; +}; + +this.removeCookie = function(name) +{ + this.createCookie(name, "", -1); +}; + + +// ************************************************************************************************ +// http://www.mister-pixel.com/#Content__state=is_that_simple +var fixIE6BackgroundImageCache = function(doc) +{ + doc = doc || document; + try + { + doc.execCommand("BackgroundImageCache", false, true); + } + catch(E) + { + + } +}; + +// ************************************************************************************************ +// calculatePixelsPerInch + +var resetStyle = "margin:0; padding:0; border:0; position:absolute; overflow:hidden; display:block;"; + +var calculatePixelsPerInch = function calculatePixelsPerInch(doc, body) +{ + var inch = FBL.createGlobalElement("div"); + inch.style.cssText = resetStyle + "width:1in; height:1in; position:absolute; top:-1234px; left:-1234px;"; + body.appendChild(inch); + + FBL.pixelsPerInch = { + x: inch.offsetWidth, + y: inch.offsetHeight + }; + + body.removeChild(inch); +}; + + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +this.SourceLink = function(url, line, type, object, instance) +{ + this.href = url; + this.instance = instance; + this.line = line; + this.type = type; + this.object = object; +}; + +this.SourceLink.prototype = +{ + toString: function() + { + return this.href; + }, + toJSON: function() // until 3.1... + { + return "{\"href\":\""+this.href+"\", "+ + (this.line?("\"line\":"+this.line+","):"")+ + (this.type?(" \"type\":\""+this.type+"\","):"")+ + "}"; + } + +}; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +this.SourceText = function(lines, owner) +{ + this.lines = lines; + this.owner = owner; +}; + +this.SourceText.getLineAsHTML = function(lineNo) +{ + return escapeForSourceLine(this.lines[lineNo-1]); +}; + + +// ************************************************************************************************ +}).apply(FBL); + +/* See license.txt for terms of usage */ + +FBL.ns( /** @scope s_i18n */ function() { with (FBL) { +// ************************************************************************************************ + +// TODO: xxxpedro localization +var oSTR = +{ + "NoMembersWarning": "There are no properties to show for this object.", + + "EmptyStyleSheet": "There are no rules in this stylesheet.", + "EmptyElementCSS": "This element has no style rules.", + "AccessRestricted": "Access to restricted URI denied.", + + "net.label.Parameters": "Parameters", + "net.label.Source": "Source", + "URLParameters": "Params", + + "EditStyle": "Edit Element Style...", + "NewRule": "New Rule...", + + "NewProp": "New Property...", + "EditProp": 'Edit "%s"', + "DeleteProp": 'Delete "%s"', + "DisableProp": 'Disable "%s"' +}; + +// ************************************************************************************************ + +FBL.$STR = function(name) +{ + return oSTR.hasOwnProperty(name) ? oSTR[name] : name; +}; + +FBL.$STRF = function(name, args) +{ + if (!oSTR.hasOwnProperty(name)) return name; + + var format = oSTR[name]; + var objIndex = 0; + + var parts = parseFormat(format); + var trialIndex = objIndex; + var objects = args; + + for (var i= 0; i < parts.length; i++) + { + var part = parts[i]; + if (part && typeof(part) == "object") + { + if (++trialIndex > objects.length) // then too few parameters for format, assume unformatted. + { + format = ""; + objIndex = -1; + parts.length = 0; + break; + } + } + + } + + var result = []; + for (var i = 0; i < parts.length; ++i) + { + var part = parts[i]; + if (part && typeof(part) == "object") + { + result.push(""+args.shift()); + } + else + result.push(part); + } + + return result.join(""); +}; + +// ************************************************************************************************ + +var parseFormat = function parseFormat(format) +{ + var parts = []; + if (format.length <= 0) + return parts; + + var reg = /((^%|.%)(\d+)?(\.)([a-zA-Z]))|((^%|.%)([a-zA-Z]))/; + for (var m = reg.exec(format); m; m = reg.exec(format)) + { + if (m[0].substr(0, 2) == "%%") + { + parts.push(format.substr(0, m.index)); + parts.push(m[0].substr(1)); + } + else + { + var type = m[8] ? m[8] : m[5]; + var precision = m[3] ? parseInt(m[3]) : (m[4] == "." ? -1 : 0); + + var rep = null; + switch (type) + { + case "s": + rep = FirebugReps.Text; + break; + case "f": + case "i": + case "d": + rep = FirebugReps.Number; + break; + case "o": + rep = null; + break; + } + + parts.push(format.substr(0, m[0][0] == "%" ? m.index : m.index+1)); + parts.push({rep: rep, precision: precision, type: ("%" + type)}); + } + + format = format.substr(m.index+m[0].length); + } + + parts.push(format); + return parts; +}; + +// ************************************************************************************************ +}}); + +/* See license.txt for terms of usage */ + +FBL.ns( /** @scope s_firebug */ function() { with (FBL) { +// ************************************************************************************************ + +// ************************************************************************************************ +// Globals + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// Internals + +var modules = []; +var panelTypes = []; +var panelTypeMap = {}; +var reps = []; + +var parentPanelMap = {}; + + +// ************************************************************************************************ +// Firebug + +/** + * @namespace describe Firebug + * @exports FBL.Firebug as Firebug + */ +FBL.Firebug = +{ + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + version: "Firebug Lite 1.4.0", + revision: "$Revision$", + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + modules: modules, + panelTypes: panelTypes, + panelTypeMap: panelTypeMap, + reps: reps, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Initialization + + initialize: function() + { + if (FBTrace.DBG_INITIALIZE) FBTrace.sysout("Firebug.initialize", "initializing application"); + + Firebug.browser = new Context(Env.browser); + Firebug.context = Firebug.browser; + + Firebug.loadPrefs(); + Firebug.context.persistedState.isOpen = false; + + // Document must be cached before chrome initialization + cacheDocument(); + + if (Firebug.Inspector && Firebug.Inspector.create) + Firebug.Inspector.create(); + + if (FBL.CssAnalyzer && FBL.CssAnalyzer.processAllStyleSheets) + FBL.CssAnalyzer.processAllStyleSheets(Firebug.browser.document); + + FirebugChrome.initialize(); + + dispatch(modules, "initialize", []); + + if (Firebug.disableResourceFetching) + Firebug.Console.logFormatted(["Some Firebug Lite features are not working because " + + "resource fetching is disabled. To enabled it set the Firebug Lite option " + + "\"disableResourceFetching\" to \"false\". More info at " + + "http://getfirebug.com/firebuglite#Options"], + Firebug.context, "warn"); + + if (Env.onLoad) + { + var onLoad = Env.onLoad; + delete Env.onLoad; + + setTimeout(onLoad, 200); + } + }, + + shutdown: function() + { + if (Firebug.saveCookies) + Firebug.savePrefs(); + + if (Firebug.Inspector) + Firebug.Inspector.destroy(); + + dispatch(modules, "shutdown", []); + + var chromeMap = FirebugChrome.chromeMap; + + for (var name in chromeMap) + { + if (chromeMap.hasOwnProperty(name)) + { + try + { + chromeMap[name].destroy(); + } + catch(E) + { + if (FBTrace.DBG_ERRORS) FBTrace.sysout("chrome.destroy() failed to: " + name); + } + } + } + + Firebug.Lite.Cache.Element.clear(); + Firebug.Lite.Cache.StyleSheet.clear(); + + Firebug.browser = null; + Firebug.context = null; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Registration + + registerModule: function() + { + modules.push.apply(modules, arguments); + + if (FBTrace.DBG_INITIALIZE) FBTrace.sysout("Firebug.registerModule"); + }, + + registerPanel: function() + { + panelTypes.push.apply(panelTypes, arguments); + + for (var i = 0, panelType; panelType = arguments[i]; ++i) + { + panelTypeMap[panelType.prototype.name] = arguments[i]; + + if (panelType.prototype.parentPanel) + parentPanelMap[panelType.prototype.parentPanel] = 1; + } + + if (FBTrace.DBG_INITIALIZE) + for (var i = 0; i < arguments.length; ++i) + FBTrace.sysout("Firebug.registerPanel", arguments[i].prototype.name); + }, + + registerRep: function() + { + reps.push.apply(reps, arguments); + }, + + unregisterRep: function() + { + for (var i = 0; i < arguments.length; ++i) + remove(reps, arguments[i]); + }, + + setDefaultReps: function(funcRep, rep) + { + FBL.defaultRep = rep; + FBL.defaultFuncRep = funcRep; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Reps + + getRep: function(object) + { + var type = typeof object; + if (isIE && isFunction(object)) + type = "function"; + + for (var i = 0; i < reps.length; ++i) + { + var rep = reps[i]; + try + { + if (rep.supportsObject(object, type)) + { + if (FBTrace.DBG_DOM) + FBTrace.sysout("getRep type: "+type+" object: "+object, rep); + return rep; + } + } + catch (exc) + { + if (FBTrace.DBG_ERRORS) + { + FBTrace.sysout("firebug.getRep FAILS: ", exc.message || exc); + FBTrace.sysout("firebug.getRep reps["+i+"/"+reps.length+"]: Rep="+reps[i].className); + // TODO: xxxpedro add trace to FBTrace logs like in Firebug + //firebug.trace(); + } + } + } + + return (type == 'function') ? defaultFuncRep : defaultRep; + }, + + getRepObject: function(node) + { + var target = null; + for (var child = node; child; child = child.parentNode) + { + if (hasClass(child, "repTarget")) + target = child; + + if (child.repObject) + { + if (!target && hasClass(child, "repIgnore")) + break; + else + return child.repObject; + } + } + }, + + getRepNode: function(node) + { + for (var child = node; child; child = child.parentNode) + { + if (child.repObject) + return child; + } + }, + + getElementByRepObject: function(element, object) + { + for (var child = element.firstChild; child; child = child.nextSibling) + { + if (child.repObject == object) + return child; + } + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Preferences + + getPref: function(name) + { + return Firebug[name]; + }, + + setPref: function(name, value) + { + Firebug[name] = value; + + Firebug.savePrefs(); + }, + + setPrefs: function(prefs) + { + for (var name in prefs) + { + if (prefs.hasOwnProperty(name)) + Firebug[name] = prefs[name]; + } + + Firebug.savePrefs(); + }, + + restorePrefs: function() + { + var Options = Env.DefaultOptions; + + for (var name in Options) + { + Firebug[name] = Options[name]; + } + }, + + loadPrefs: function() + { + this.restorePrefs(); + + var prefs = Store.get("FirebugLite") || {}; + var options = prefs.options; + var persistedState = prefs.persistedState || FBL.defaultPersistedState; + + for (var name in options) + { + if (options.hasOwnProperty(name)) + Firebug[name] = options[name]; + } + + if (Firebug.context && persistedState) + Firebug.context.persistedState = persistedState; + }, + + savePrefs: function() + { + var prefs = { + options: {} + }; + + var EnvOptions = Env.Options; + var options = prefs.options; + for (var name in EnvOptions) + { + if (EnvOptions.hasOwnProperty(name)) + { + options[name] = Firebug[name]; + } + } + + var persistedState = Firebug.context.persistedState; + if (!persistedState) + { + persistedState = Firebug.context.persistedState = FBL.defaultPersistedState; + } + + prefs.persistedState = persistedState; + + Store.set("FirebugLite", prefs); + }, + + erasePrefs: function() + { + Store.remove("FirebugLite"); + this.restorePrefs(); + } +}; + +Firebug.restorePrefs(); + +// xxxpedro should we remove this? +window.Firebug = FBL.Firebug; + +if (!Env.Options.enablePersistent || + Env.Options.enablePersistent && Env.isChromeContext || + Env.isDebugMode) + Env.browser.window.Firebug = FBL.Firebug; + + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// Other methods + +FBL.cacheDocument = function cacheDocument() +{ + var ElementCache = Firebug.Lite.Cache.Element; + var els = Firebug.browser.document.getElementsByTagName("*"); + for (var i=0, l=els.length, el; i<l; i++) + { + el = els[i]; + ElementCache(el); + } +}; + +// ************************************************************************************************ + +/** + * @class + * + * Support for listeners registration. This object also extended by Firebug.Module so, + * all modules supports listening automatically. Notice that array of listeners + * is created for each intance of a module within initialize method. Thus all derived + * module classes must ensure that Firebug.Module.initialize method is called for the + * super class. + */ +Firebug.Listener = function() +{ + // The array is created when the first listeners is added. + // It can't be created here since derived objects would share + // the same array. + this.fbListeners = null; +}; + +Firebug.Listener.prototype = +{ + addListener: function(listener) + { + if (!this.fbListeners) + this.fbListeners = []; // delay the creation until the objects are created so 'this' causes new array for each module + + this.fbListeners.push(listener); + }, + + removeListener: function(listener) + { + remove(this.fbListeners, listener); // if this.fbListeners is null, remove is being called with no add + } +}; + +// ************************************************************************************************ + + +// ************************************************************************************************ +// Module + +/** + * @module Base class for all modules. Every derived module object must be registered using + * <code>Firebug.registerModule</code> method. There is always one instance of a module object + * per browser window. + * @extends Firebug.Listener + */ +Firebug.Module = extend(new Firebug.Listener(), +/** @extend Firebug.Module */ +{ + /** + * Called when the window is opened. + */ + initialize: function() + { + }, + + /** + * Called when the window is closed. + */ + shutdown: function() + { + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + /** + * Called when a new context is created but before the page is loaded. + */ + initContext: function(context) + { + }, + + /** + * Called after a context is detached to a separate window; + */ + reattachContext: function(browser, context) + { + }, + + /** + * Called when a context is destroyed. Module may store info on persistedState for reloaded pages. + */ + destroyContext: function(context, persistedState) + { + }, + + // Called when a FF tab is create or activated (user changes FF tab) + // Called after context is created or with context == null (to abort?) + showContext: function(browser, context) + { + }, + + /** + * Called after a context's page gets DOMContentLoaded + */ + loadedContext: function(context) + { + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + showPanel: function(browser, panel) + { + }, + + showSidePanel: function(browser, panel) + { + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + updateOption: function(name, value) + { + }, + + getObjectByURL: function(context, url) + { + } +}); + +// ************************************************************************************************ +// Panel + +/** + * @panel Base class for all panels. Every derived panel must define a constructor and + * register with "Firebug.registerPanel" method. An instance of the panel + * object is created by the framework for each browser tab where Firebug is activated. + */ +Firebug.Panel = +{ + name: "HelloWorld", + title: "Hello World!", + + parentPanel: null, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + options: { + hasCommandLine: false, + hasStatusBar: false, + hasToolButtons: false, + + // Pre-rendered panels are those included in the skin file (firebug.html) + isPreRendered: false, + innerHTMLSync: false + + /* + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // To be used by external extensions + panelHTML: "", + panelCSS: "", + + toolButtonsHTML: "" + /**/ + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + tabNode: null, + panelNode: null, + sidePanelNode: null, + statusBarNode: null, + toolButtonsNode: null, + + panelBarNode: null, + + sidePanelBarBoxNode: null, + sidePanelBarNode: null, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + sidePanelBar: null, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + searchable: false, + editable: true, + order: 2147483647, + statusSeparator: "<", + + create: function(context, doc) + { + this.hasSidePanel = parentPanelMap.hasOwnProperty(this.name); + + this.panelBarNode = $("fbPanelBar1"); + this.sidePanelBarBoxNode = $("fbPanelBar2"); + + if (this.hasSidePanel) + { + this.sidePanelBar = extend({}, PanelBar); + this.sidePanelBar.create(this); + } + + var options = this.options = extend(Firebug.Panel.options, this.options); + var panelId = "fb" + this.name; + + if (options.isPreRendered) + { + this.panelNode = $(panelId); + + this.tabNode = $(panelId + "Tab"); + this.tabNode.style.display = "block"; + + if (options.hasToolButtons) + { + this.toolButtonsNode = $(panelId + "Buttons"); + } + + if (options.hasStatusBar) + { + this.statusBarBox = $("fbStatusBarBox"); + this.statusBarNode = $(panelId + "StatusBar"); + } + } + else + { + var containerSufix = this.parentPanel ? "2" : "1"; + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Create Panel + var panelNode = this.panelNode = createElement("div", { + id: panelId, + className: "fbPanel" + }); + + $("fbPanel" + containerSufix).appendChild(panelNode); + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Create Panel Tab + var tabHTML = '<span class="fbTabL"></span><span class="fbTabText">' + + this.title + '</span><span class="fbTabR"></span>'; + + var tabNode = this.tabNode = createElement("a", { + id: panelId + "Tab", + className: "fbTab fbHover", + innerHTML: tabHTML + }); + + if (isIE6) + { + tabNode.href = "javascript:void(0)"; + } + + var panelBarNode = this.parentPanel ? + Firebug.chrome.getPanel(this.parentPanel).sidePanelBarNode : + this.panelBarNode; + + panelBarNode.appendChild(tabNode); + tabNode.style.display = "block"; + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // create ToolButtons + if (options.hasToolButtons) + { + this.toolButtonsNode = createElement("span", { + id: panelId + "Buttons", + className: "fbToolbarButtons" + }); + + $("fbToolbarButtons").appendChild(this.toolButtonsNode); + } + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // create StatusBar + if (options.hasStatusBar) + { + this.statusBarBox = $("fbStatusBarBox"); + + this.statusBarNode = createElement("span", { + id: panelId + "StatusBar", + className: "fbToolbarButtons fbStatusBar" + }); + + this.statusBarBox.appendChild(this.statusBarNode); + } + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // create SidePanel + } + + this.containerNode = this.panelNode.parentNode; + + if (FBTrace.DBG_INITIALIZE) FBTrace.sysout("Firebug.Panel.create", this.name); + + // xxxpedro contextMenu + this.onContextMenu = bind(this.onContextMenu, this); + + /* + this.context = context; + this.document = doc; + + this.panelNode = doc.createElement("div"); + this.panelNode.ownerPanel = this; + + setClass(this.panelNode, "panelNode panelNode-"+this.name+" contextUID="+context.uid); + doc.body.appendChild(this.panelNode); + + if (FBTrace.DBG_INITIALIZE) + FBTrace.sysout("firebug.initialize panelNode for "+this.name+"\n"); + + this.initializeNode(this.panelNode); + /**/ + }, + + destroy: function(state) // Panel may store info on state + { + if (FBTrace.DBG_INITIALIZE) FBTrace.sysout("Firebug.Panel.destroy", this.name); + + if (this.hasSidePanel) + { + this.sidePanelBar.destroy(); + this.sidePanelBar = null; + } + + this.options = null; + this.name = null; + this.parentPanel = null; + + this.tabNode = null; + this.panelNode = null; + this.containerNode = null; + + this.toolButtonsNode = null; + this.statusBarBox = null; + this.statusBarNode = null; + + //if (this.panelNode) + // delete this.panelNode.ownerPanel; + + //this.destroyNode(); + }, + + initialize: function() + { + if (FBTrace.DBG_INITIALIZE) FBTrace.sysout("Firebug.Panel.initialize", this.name); + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + if (this.hasSidePanel) + { + this.sidePanelBar.initialize(); + } + + var options = this.options = extend(Firebug.Panel.options, this.options); + var panelId = "fb" + this.name; + + this.panelNode = $(panelId); + + this.tabNode = $(panelId + "Tab"); + this.tabNode.style.display = "block"; + + if (options.hasStatusBar) + { + this.statusBarBox = $("fbStatusBarBox"); + this.statusBarNode = $(panelId + "StatusBar"); + } + + if (options.hasToolButtons) + { + this.toolButtonsNode = $(panelId + "Buttons"); + } + + this.containerNode = this.panelNode.parentNode; + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // restore persistent state + this.containerNode.scrollTop = this.lastScrollTop; + + // xxxpedro contextMenu + addEvent(this.containerNode, "contextmenu", this.onContextMenu); + + + /// TODO: xxxpedro infoTip Hack + Firebug.chrome.currentPanel = + Firebug.chrome.selectedPanel && Firebug.chrome.selectedPanel.sidePanelBar ? + Firebug.chrome.selectedPanel.sidePanelBar.selectedPanel : + Firebug.chrome.selectedPanel; + + Firebug.showInfoTips = true; + if (Firebug.InfoTip) + Firebug.InfoTip.initializeBrowser(Firebug.chrome); + }, + + shutdown: function() + { + if (FBTrace.DBG_INITIALIZE) FBTrace.sysout("Firebug.Panel.shutdown", this.name); + + /// TODO: xxxpedro infoTip Hack + if (Firebug.InfoTip) + Firebug.InfoTip.uninitializeBrowser(Firebug.chrome); + + if (Firebug.chrome.largeCommandLineVisible) + Firebug.chrome.hideLargeCommandLine(); + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + if (this.hasSidePanel) + { + // TODO: xxxpedro firebug1.3a6 + // new PanelBar mechanism will need to call shutdown to hide the panels (so it + // doesn't appears in other panel's sidePanelBar. Therefore, we need to implement + // a "remember selected panel" feature in the sidePanelBar + //this.sidePanelBar.shutdown(); + } + + // store persistent state + this.lastScrollTop = this.containerNode.scrollTop; + + // xxxpedro contextMenu + removeEvent(this.containerNode, "contextmenu", this.onContextMenu); + }, + + detach: function(oldChrome, newChrome) + { + if (oldChrome && oldChrome.selectedPanel && oldChrome.selectedPanel.name == this.name) + this.lastScrollTop = oldChrome.selectedPanel.containerNode.scrollTop; + }, + + reattach: function(doc) + { + if (this.options.innerHTMLSync) + this.synchronizeUI(); + }, + + synchronizeUI: function() + { + this.containerNode.scrollTop = this.lastScrollTop || 0; + }, + + show: function(state) + { + var options = this.options; + + if (options.hasStatusBar) + { + this.statusBarBox.style.display = "inline"; + this.statusBarNode.style.display = "inline"; + } + + if (options.hasToolButtons) + { + this.toolButtonsNode.style.display = "inline"; + } + + this.panelNode.style.display = "block"; + + this.visible = true; + + if (!this.parentPanel) + Firebug.chrome.layout(this); + }, + + hide: function(state) + { + var options = this.options; + + if (options.hasStatusBar) + { + this.statusBarBox.style.display = "none"; + this.statusBarNode.style.display = "none"; + } + + if (options.hasToolButtons) + { + this.toolButtonsNode.style.display = "none"; + } + + this.panelNode.style.display = "none"; + + this.visible = false; + }, + + watchWindow: function(win) + { + }, + + unwatchWindow: function(win) + { + }, + + updateOption: function(name, value) + { + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + /** + * Toolbar helpers + */ + showToolbarButtons: function(buttonsId, show) + { + try + { + if (!this.context.browser) // XXXjjb this is bug. Somehow the panel context is not FirebugContext. + { + if (FBTrace.DBG_ERRORS) + FBTrace.sysout("firebug.Panel showToolbarButtons this.context has no browser, this:", this); + + return; + } + var buttons = this.context.browser.chrome.$(buttonsId); + if (buttons) + collapse(buttons, show ? "false" : "true"); + } + catch (exc) + { + if (FBTrace.DBG_ERRORS) + { + FBTrace.dumpProperties("firebug.Panel showToolbarButtons FAILS", exc); + if (!this.context.browser)FBTrace.dumpStack("firebug.Panel showToolbarButtons no browser"); + } + } + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + /** + * Returns a number indicating the view's ability to inspect the object. + * + * Zero means not supported, and higher numbers indicate specificity. + */ + supportsObject: function(object) + { + return 0; + }, + + hasObject: function(object) // beyond type testing, is this object selectable? + { + return false; + }, + + select: function(object, forceUpdate) + { + if (!object) + object = this.getDefaultSelection(this.context); + + if(FBTrace.DBG_PANELS) + FBTrace.sysout("firebug.select "+this.name+" forceUpdate: "+forceUpdate+" "+object+((object==this.selection)?"==":"!=")+this.selection); + + if (forceUpdate || object != this.selection) + { + this.selection = object; + this.updateSelection(object); + + // TODO: xxxpedro + // XXXjoe This is kind of cheating, but, feh. + //Firebug.chrome.onPanelSelect(object, this); + //if (uiListeners.length > 0) + // dispatch(uiListeners, "onPanelSelect", [object, this]); // TODO: make Firebug.chrome a uiListener + } + }, + + updateSelection: function(object) + { + }, + + markChange: function(skipSelf) + { + if (this.dependents) + { + if (skipSelf) + { + for (var i = 0; i < this.dependents.length; ++i) + { + var panelName = this.dependents[i]; + if (panelName != this.name) + this.context.invalidatePanels(panelName); + } + } + else + this.context.invalidatePanels.apply(this.context, this.dependents); + } + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + startInspecting: function() + { + }, + + stopInspecting: function(object, cancelled) + { + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + search: function(text, reverse) + { + }, + + /** + * Retrieves the search options that this modules supports. + * This is used by the search UI to present the proper options. + */ + getSearchOptionsMenuItems: function() + { + return [ + Firebug.Search.searchOptionMenu("search.Case Sensitive", "searchCaseSensitive") + ]; + }, + + /** + * Navigates to the next document whose match parameter returns true. + */ + navigateToNextDocument: function(match, reverse) + { + // This is an approximation of the UI that is displayed by the location + // selector. This should be close enough, although it may be better + // to simply generate the sorted list within the module, rather than + // sorting within the UI. + var self = this; + function compare(a, b) { + var locA = self.getObjectDescription(a); + var locB = self.getObjectDescription(b); + if(locA.path > locB.path) + return 1; + if(locA.path < locB.path) + return -1; + if(locA.name > locB.name) + return 1; + if(locA.name < locB.name) + return -1; + return 0; + } + var allLocs = this.getLocationList().sort(compare); + for (var curPos = 0; curPos < allLocs.length && allLocs[curPos] != this.location; curPos++); + + function transformIndex(index) { + if (reverse) { + // For the reverse case we need to implement wrap around. + var intermediate = curPos - index - 1; + return (intermediate < 0 ? allLocs.length : 0) + intermediate; + } else { + return (curPos + index + 1) % allLocs.length; + } + }; + + for (var next = 0; next < allLocs.length - 1; next++) + { + var object = allLocs[transformIndex(next)]; + + if (match(object)) + { + this.navigate(object); + return object; + } + } + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + // Called when "Options" clicked. Return array of + // {label: 'name', nol10n: true, type: "checkbox", checked: <value>, command:function to set <value>} + getOptionsMenuItems: function() + { + return null; + }, + + /* + * Called by chrome.onContextMenu to build the context menu when this panel has focus. + * See also FirebugRep for a similar function also called by onContextMenu + * Extensions may monkey patch and chain off this call + * @param object: the 'realObject', a model value, eg a DOM property + * @param target: the HTML element clicked on. + * @return an array of menu items. + */ + getContextMenuItems: function(object, target) + { + return []; + }, + + getBreakOnMenuItems: function() + { + return []; + }, + + getEditor: function(target, value) + { + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + getDefaultSelection: function() + { + return null; + }, + + browseObject: function(object) + { + }, + + getPopupObject: function(target) + { + return Firebug.getRepObject(target); + }, + + getTooltipObject: function(target) + { + return Firebug.getRepObject(target); + }, + + showInfoTip: function(infoTip, x, y) + { + + }, + + getObjectPath: function(object) + { + return null; + }, + + // An array of objects that can be passed to getObjectLocation. + // The list of things a panel can show, eg sourceFiles. + // Only shown if panel.location defined and supportsObject true + getLocationList: function() + { + return null; + }, + + getDefaultLocation: function() + { + return null; + }, + + getObjectLocation: function(object) + { + return ""; + }, + + // Text for the location list menu eg script panel source file list + // return.path: group/category label, return.name: item label + getObjectDescription: function(object) + { + var url = this.getObjectLocation(object); + return FBL.splitURLBase(url); + }, + + /* + * UI signal that a tab needs attention, eg Script panel is currently stopped on a breakpoint + * @param: show boolean, true turns on. + */ + highlight: function(show) + { + var tab = this.getTab(); + if (!tab) + return; + + if (show) + tab.setAttribute("highlight", "true"); + else + tab.removeAttribute("highlight"); + }, + + getTab: function() + { + var chrome = Firebug.chrome; + + var tab = chrome.$("fbPanelBar2").getTab(this.name); + if (!tab) + tab = chrome.$("fbPanelBar1").getTab(this.name); + return tab; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Support for Break On Next + + /** + * Called by the framework when the user clicks on the Break On Next button. + * @param {Boolean} armed Set to true if the Break On Next feature is + * to be armed for action and set to false if the Break On Next should be disarmed. + * If 'armed' is true, then the next call to shouldBreakOnNext should be |true|. + */ + breakOnNext: function(armed) + { + }, + + /** + * Called when a panel is selected/displayed. The method should return true + * if the Break On Next feature is currently armed for this panel. + */ + shouldBreakOnNext: function() + { + return false; + }, + + /** + * Returns labels for Break On Next tooltip (one for enabled and one for disabled state). + * @param {Boolean} enabled Set to true if the Break On Next feature is + * currently activated for this panel. + */ + getBreakOnNextTooltip: function(enabled) + { + return null; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + // xxxpedro contextMenu + onContextMenu: function(event) + { + if (!this.getContextMenuItems) + return; + + cancelEvent(event, true); + + var target = event.target || event.srcElement; + + var menu = this.getContextMenuItems(this.selection, target); + if (!menu) + return; + + var contextMenu = new Menu( + { + id: "fbPanelContextMenu", + + items: menu + }); + + contextMenu.show(event.clientX, event.clientY); + + return true; + + /* + // TODO: xxxpedro move code to somewhere. code to get cross-browser + // window to screen coordinates + var box = Firebug.browser.getElementPosition(Firebug.chrome.node); + + var screenY = 0; + + // Firefox + if (typeof window.mozInnerScreenY != "undefined") + { + screenY = window.mozInnerScreenY; + } + // Chrome + else if (typeof window.innerHeight != "undefined") + { + screenY = window.outerHeight - window.innerHeight; + } + // IE + else if (typeof window.screenTop != "undefined") + { + screenY = window.screenTop; + } + + contextMenu.show(event.screenX-box.left, event.screenY-screenY-box.top); + /**/ + } + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +}; + + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +/** + * MeasureBox + * To get pixels size.width and size.height: + * <ul><li> this.startMeasuring(view); </li> + * <li> var size = this.measureText(lineNoCharsSpacer); </li> + * <li> this.stopMeasuring(); </li> + * </ul> + * + * @namespace + */ +Firebug.MeasureBox = +{ + startMeasuring: function(target) + { + if (!this.measureBox) + { + this.measureBox = target.ownerDocument.createElement("span"); + this.measureBox.className = "measureBox"; + } + + copyTextStyles(target, this.measureBox); + target.ownerDocument.body.appendChild(this.measureBox); + }, + + getMeasuringElement: function() + { + return this.measureBox; + }, + + measureText: function(value) + { + this.measureBox.innerHTML = value ? escapeForSourceLine(value) : "m"; + return {width: this.measureBox.offsetWidth, height: this.measureBox.offsetHeight-1}; + }, + + measureInputText: function(value) + { + value = value ? escapeForTextNode(value) : "m"; + if (!Firebug.showTextNodesWithWhitespace) + value = value.replace(/\t/g,'mmmmmm').replace(/\ /g,'m'); + this.measureBox.innerHTML = value; + return {width: this.measureBox.offsetWidth, height: this.measureBox.offsetHeight-1}; + }, + + getBox: function(target) + { + var style = this.measureBox.ownerDocument.defaultView.getComputedStyle(this.measureBox, ""); + var box = getBoxFromStyles(style, this.measureBox); + return box; + }, + + stopMeasuring: function() + { + this.measureBox.parentNode.removeChild(this.measureBox); + } +}; + + +// ************************************************************************************************ +if (FBL.domplate) Firebug.Rep = domplate( +{ + className: "", + inspectable: true, + + supportsObject: function(object, type) + { + return false; + }, + + inspectObject: function(object, context) + { + Firebug.chrome.select(object); + }, + + browseObject: function(object, context) + { + }, + + persistObject: function(object, context) + { + }, + + getRealObject: function(object, context) + { + return object; + }, + + getTitle: function(object) + { + var label = safeToString(object); + + var re = /\[object (.*?)\]/; + var m = re.exec(label); + + ///return m ? m[1] : label; + + // if the label is in the "[object TYPE]" format return its type + if (m) + { + return m[1]; + } + // if it is IE we need to handle some special cases + else if ( + // safeToString() fails to recognize some objects in IE + isIE && + // safeToString() returns "[object]" for some objects like window.Image + (label == "[object]" || + // safeToString() returns undefined for some objects like window.clientInformation + typeof object == "object" && typeof label == "undefined") + ) + { + return "Object"; + } + else + { + return label; + } + }, + + getTooltip: function(object) + { + return null; + }, + + getContextMenuItems: function(object, target, context) + { + return []; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Convenience for domplates + + STR: function(name) + { + return $STR(name); + }, + + cropString: function(text) + { + return cropString(text); + }, + + cropMultipleLines: function(text, limit) + { + return cropMultipleLines(text, limit); + }, + + toLowerCase: function(text) + { + return text ? text.toLowerCase() : text; + }, + + plural: function(n) + { + return n == 1 ? "" : "s"; + } +}); + +// ************************************************************************************************ + + +// ************************************************************************************************ +}}); + +/* See license.txt for terms of usage */ + +FBL.ns( /** @scope s_gui */ function() { with (FBL) { +// ************************************************************************************************ + +// ************************************************************************************************ +// Controller + +/**@namespace*/ +FBL.Controller = { + + controllers: null, + controllerContext: null, + + initialize: function(context) + { + this.controllers = []; + this.controllerContext = context || Firebug.chrome; + }, + + shutdown: function() + { + this.removeControllers(); + + //this.controllers = null; + //this.controllerContext = null; + }, + + addController: function() + { + for (var i=0, arg; arg=arguments[i]; i++) + { + // If the first argument is a string, make a selector query + // within the controller node context + if (typeof arg[0] == "string") + { + arg[0] = $$(arg[0], this.controllerContext); + } + + // bind the handler to the proper context + var handler = arg[2]; + arg[2] = bind(handler, this); + // save the original handler as an extra-argument, so we can + // look for it later, when removing a particular controller + arg[3] = handler; + + this.controllers.push(arg); + addEvent.apply(this, arg); + } + }, + + removeController: function() + { + for (var i=0, arg; arg=arguments[i]; i++) + { + for (var j=0, c; c=this.controllers[j]; j++) + { + if (arg[0] == c[0] && arg[1] == c[1] && arg[2] == c[3]) + removeEvent.apply(this, c); + } + } + }, + + removeControllers: function() + { + for (var i=0, c; c=this.controllers[i]; i++) + { + removeEvent.apply(this, c); + } + } +}; + + +// ************************************************************************************************ +// PanelBar + +/**@namespace*/ +FBL.PanelBar = +{ + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + panelMap: null, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + selectedPanel: null, + parentPanelName: null, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + create: function(ownerPanel) + { + this.panelMap = {}; + this.ownerPanel = ownerPanel; + + if (ownerPanel) + { + ownerPanel.sidePanelBarNode = createElement("span"); + ownerPanel.sidePanelBarNode.style.display = "none"; + ownerPanel.sidePanelBarBoxNode.appendChild(ownerPanel.sidePanelBarNode); + } + + var panels = Firebug.panelTypes; + for (var i=0, p; p=panels[i]; i++) + { + if ( // normal Panel of the Chrome's PanelBar + !ownerPanel && !p.prototype.parentPanel || + // Child Panel of the current Panel's SidePanelBar + ownerPanel && p.prototype.parentPanel && + ownerPanel.name == p.prototype.parentPanel) + { + this.addPanel(p.prototype.name); + } + } + }, + + destroy: function() + { + PanelBar.shutdown.call(this); + + for (var name in this.panelMap) + { + this.removePanel(name); + + var panel = this.panelMap[name]; + panel.destroy(); + + this.panelMap[name] = null; + delete this.panelMap[name]; + } + + this.panelMap = null; + this.ownerPanel = null; + }, + + initialize: function() + { + if (this.ownerPanel) + this.ownerPanel.sidePanelBarNode.style.display = "inline"; + + for(var name in this.panelMap) + { + (function(self, name){ + + // tab click handler + var onTabClick = function onTabClick() + { + self.selectPanel(name); + return false; + }; + + Firebug.chrome.addController([self.panelMap[name].tabNode, "mousedown", onTabClick]); + + })(this, name); + } + }, + + shutdown: function() + { + var selectedPanel = this.selectedPanel; + + if (selectedPanel) + { + removeClass(selectedPanel.tabNode, "fbSelectedTab"); + selectedPanel.hide(); + selectedPanel.shutdown(); + } + + if (this.ownerPanel) + this.ownerPanel.sidePanelBarNode.style.display = "none"; + + this.selectedPanel = null; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + addPanel: function(panelName, parentPanel) + { + var PanelType = Firebug.panelTypeMap[panelName]; + var panel = this.panelMap[panelName] = new PanelType(); + + panel.create(); + }, + + removePanel: function(panelName) + { + var panel = this.panelMap[panelName]; + if (panel.hasOwnProperty(panelName)) + panel.destroy(); + }, + + selectPanel: function(panelName) + { + var selectedPanel = this.selectedPanel; + var panel = this.panelMap[panelName]; + + if (panel && selectedPanel != panel) + { + if (selectedPanel) + { + removeClass(selectedPanel.tabNode, "fbSelectedTab"); + selectedPanel.shutdown(); + selectedPanel.hide(); + } + + if (!panel.parentPanel) + Firebug.context.persistedState.selectedPanelName = panelName; + + this.selectedPanel = panel; + + setClass(panel.tabNode, "fbSelectedTab"); + panel.show(); + panel.initialize(); + } + }, + + getPanel: function(panelName) + { + var panel = this.panelMap[panelName]; + + return panel; + } + +}; + +//************************************************************************************************ +// Button + +/** + * options.element + * options.caption + * options.title + * + * options.owner + * options.className + * options.pressedClassName + * + * options.onPress + * options.onUnpress + * options.onClick + * + * @class + * @extends FBL.Controller + * + */ + +FBL.Button = function(options) +{ + options = options || {}; + + append(this, options); + + this.state = "unpressed"; + this.display = "unpressed"; + + if (this.element) + { + this.container = this.element.parentNode; + } + else + { + this.shouldDestroy = true; + + this.container = this.owner.getPanel().toolButtonsNode; + + this.element = createElement("a", { + className: this.baseClassName + " " + this.className + " fbHover", + innerHTML: this.caption + }); + + if (this.title) + this.element.title = this.title; + + this.container.appendChild(this.element); + } +}; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +Button.prototype = extend(Controller, +/**@extend FBL.Button.prototype*/ +{ + type: "normal", + caption: "caption", + title: null, + + className: "", // custom class + baseClassName: "fbButton", // control class + pressedClassName: "fbBtnPressed", // control pressed class + + element: null, + container: null, + owner: null, + + state: null, + display: null, + + destroy: function() + { + this.shutdown(); + + // only remove if it is a dynamically generated button (not pre-rendered) + if (this.shouldDestroy) + this.container.removeChild(this.element); + + this.element = null; + this.container = null; + this.owner = null; + }, + + initialize: function() + { + Controller.initialize.apply(this); + + var element = this.element; + + this.addController([element, "mousedown", this.handlePress]); + + if (this.type == "normal") + this.addController( + [element, "mouseup", this.handleUnpress], + [element, "mouseout", this.handleUnpress], + [element, "click", this.handleClick] + ); + }, + + shutdown: function() + { + Controller.shutdown.apply(this); + }, + + restore: function() + { + this.changeState("unpressed"); + }, + + changeState: function(state) + { + this.state = state; + this.changeDisplay(state); + }, + + changeDisplay: function(display) + { + if (display != this.display) + { + if (display == "pressed") + { + setClass(this.element, this.pressedClassName); + } + else if (display == "unpressed") + { + removeClass(this.element, this.pressedClassName); + } + this.display = display; + } + }, + + handlePress: function(event) + { + cancelEvent(event, true); + + if (this.type == "normal") + { + this.changeDisplay("pressed"); + this.beforeClick = true; + } + else if (this.type == "toggle") + { + if (this.state == "pressed") + { + this.changeState("unpressed"); + + if (this.onUnpress) + this.onUnpress.apply(this.owner, arguments); + } + else + { + this.changeState("pressed"); + + if (this.onPress) + this.onPress.apply(this.owner, arguments); + } + + if (this.onClick) + this.onClick.apply(this.owner, arguments); + } + + return false; + }, + + handleUnpress: function(event) + { + cancelEvent(event, true); + + if (this.beforeClick) + this.changeDisplay("unpressed"); + + return false; + }, + + handleClick: function(event) + { + cancelEvent(event, true); + + if (this.type == "normal") + { + if (this.onClick) + this.onClick.apply(this.owner); + + this.changeState("unpressed"); + } + + this.beforeClick = false; + + return false; + } +}); + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +/** + * @class + * @extends FBL.Button + */ +FBL.IconButton = function() +{ + Button.apply(this, arguments); +}; + +IconButton.prototype = extend(Button.prototype, +/**@extend FBL.IconButton.prototype*/ +{ + baseClassName: "fbIconButton", + pressedClassName: "fbIconPressed" +}); + + +//************************************************************************************************ +// Menu + +var menuItemProps = {"class": "$item.className", type: "$item.type", value: "$item.value", + _command: "$item.command"}; + +if (isIE6) + menuItemProps.href = "javascript:void(0)"; + +// Allow GUI to be loaded even when Domplate module is not installed. +if (FBL.domplate) +var MenuPlate = domplate(Firebug.Rep, +{ + tag: + DIV({"class": "fbMenu fbShadow"}, + DIV({"class": "fbMenuContent fbShadowContent"}, + FOR("item", "$object.items|memberIterator", + TAG("$item.tag", {item: "$item"}) + ) + ) + ), + + itemTag: + A(menuItemProps, + "$item.label" + ), + + checkBoxTag: + A(extend(menuItemProps, {checked : "$item.checked"}), + + "$item.label" + ), + + radioButtonTag: + A(extend(menuItemProps, {selected : "$item.selected"}), + + "$item.label" + ), + + groupTag: + A(extend(menuItemProps, {child: "$item.child"}), + "$item.label" + ), + + shortcutTag: + A(menuItemProps, + "$item.label", + SPAN({"class": "fbMenuShortcutKey"}, + "$item.key" + ) + ), + + separatorTag: + SPAN({"class": "fbMenuSeparator"}), + + memberIterator: function(items) + { + var result = []; + + for (var i=0, length=items.length; i<length; i++) + { + var item = items[i]; + + // separator representation + if (typeof item == "string" && item.indexOf("-") == 0) + { + result.push({tag: this.separatorTag}); + continue; + } + + item = extend(item, {}); + + item.type = item.type || ""; + item.value = item.value || ""; + + var type = item.type; + + // default item representation + item.tag = this.itemTag; + + var className = item.className || ""; + + className += "fbMenuOption fbHover "; + + // specific representations + if (type == "checkbox") + { + className += "fbMenuCheckBox "; + item.tag = this.checkBoxTag; + } + else if (type == "radiobutton") + { + className += "fbMenuRadioButton "; + item.tag = this.radioButtonTag; + } + else if (type == "group") + { + className += "fbMenuGroup "; + item.tag = this.groupTag; + } + else if (type == "shortcut") + { + className += "fbMenuShortcut "; + item.tag = this.shortcutTag; + } + + if (item.checked) + className += "fbMenuChecked "; + else if (item.selected) + className += "fbMenuRadioSelected "; + + if (item.disabled) + className += "fbMenuDisabled "; + + item.className = className; + + item.label = $STR(item.label); + + result.push(item); + } + + return result; + } +}); + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +/** + * options + * options.element + * options.id + * options.items + * + * item.label + * item.className + * item.type + * item.value + * item.disabled + * item.checked + * item.selected + * item.command + * item.child + * + * + * @class + * @extends FBL.Controller + * + */ +FBL.Menu = function(options) +{ + // if element is not pre-rendered, we must render it now + if (!options.element) + { + if (options.getItems) + options.items = options.getItems(); + + options.element = MenuPlate.tag.append( + {object: options}, + getElementByClass(Firebug.chrome.document, "fbBody"), + MenuPlate + ); + } + + // extend itself with the provided options + append(this, options); + + if (typeof this.element == "string") + { + this.id = this.element; + this.element = $(this.id); + } + else if (this.id) + { + this.element.id = this.id; + } + + this.element.firebugIgnore = true; + this.elementStyle = this.element.style; + + this.isVisible = false; + + this.handleMouseDown = bind(this.handleMouseDown, this); + this.handleMouseOver = bind(this.handleMouseOver, this); + this.handleMouseOut = bind(this.handleMouseOut, this); + + this.handleWindowMouseDown = bind(this.handleWindowMouseDown, this); +}; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +var menuMap = {}; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +Menu.prototype = extend(Controller, +/**@extend FBL.Menu.prototype*/ +{ + destroy: function() + { + //if (this.element) console.log("destroy", this.element.id); + + this.hide(); + + // if it is a childMenu, remove its reference from the parentMenu + if (this.parentMenu) + this.parentMenu.childMenu = null; + + // remove the element from the document + this.element.parentNode.removeChild(this.element); + + // clear references + this.element = null; + this.elementStyle = null; + this.parentMenu = null; + this.parentTarget = null; + }, + + initialize: function() + { + Controller.initialize.call(this); + + this.addController( + [this.element, "mousedown", this.handleMouseDown], + [this.element, "mouseover", this.handleMouseOver] + ); + }, + + shutdown: function() + { + Controller.shutdown.call(this); + }, + + show: function(x, y) + { + this.initialize(); + + if (this.isVisible) return; + + //console.log("show", this.element.id); + + x = x || 0; + y = y || 0; + + if (this.parentMenu) + { + var oldChildMenu = this.parentMenu.childMenu; + if (oldChildMenu && oldChildMenu != this) + { + oldChildMenu.destroy(); + } + + this.parentMenu.childMenu = this; + } + else + addEvent(Firebug.chrome.document, "mousedown", this.handleWindowMouseDown); + + this.elementStyle.display = "block"; + this.elementStyle.visibility = "hidden"; + + var size = Firebug.chrome.getSize(); + + x = Math.min(x, size.width - this.element.clientWidth - 10); + x = Math.max(x, 0); + + y = Math.min(y, size.height - this.element.clientHeight - 10); + y = Math.max(y, 0); + + this.elementStyle.left = x + "px"; + this.elementStyle.top = y + "px"; + + this.elementStyle.visibility = "visible"; + + this.isVisible = true; + + if (isFunction(this.onShow)) + this.onShow.apply(this, arguments); + }, + + hide: function() + { + this.clearHideTimeout(); + this.clearShowChildTimeout(); + + if (!this.isVisible) return; + + //console.log("hide", this.element.id); + + this.elementStyle.display = "none"; + + if(this.childMenu) + { + this.childMenu.destroy(); + this.childMenu = null; + } + + if(this.parentTarget) + removeClass(this.parentTarget, "fbMenuGroupSelected"); + + this.isVisible = false; + + this.shutdown(); + + if (isFunction(this.onHide)) + this.onHide.apply(this, arguments); + }, + + showChildMenu: function(target) + { + var id = target.getAttribute("child"); + + var parent = this; + var target = target; + + this.showChildTimeout = Firebug.chrome.window.setTimeout(function(){ + + //if (!parent.isVisible) return; + + var box = Firebug.chrome.getElementBox(target); + + var childMenuObject = menuMap.hasOwnProperty(id) ? + menuMap[id] : {element: $(id)}; + + var childMenu = new Menu(extend(childMenuObject, + { + parentMenu: parent, + parentTarget: target + })); + + var offsetLeft = isIE6 ? -1 : -6; // IE6 problem with fixed position + childMenu.show(box.left + box.width + offsetLeft, box.top -6); + setClass(target, "fbMenuGroupSelected"); + + },350); + }, + + clearHideTimeout: function() + { + if (this.hideTimeout) + { + Firebug.chrome.window.clearTimeout(this.hideTimeout); + delete this.hideTimeout; + } + }, + + clearShowChildTimeout: function() + { + if(this.showChildTimeout) + { + Firebug.chrome.window.clearTimeout(this.showChildTimeout); + this.showChildTimeout = null; + } + }, + + handleMouseDown: function(event) + { + cancelEvent(event, true); + + var topParent = this; + while (topParent.parentMenu) + topParent = topParent.parentMenu; + + var target = event.target || event.srcElement; + + target = getAncestorByClass(target, "fbMenuOption"); + + if(!target || hasClass(target, "fbMenuGroup")) + return false; + + if (target && !hasClass(target, "fbMenuDisabled")) + { + var type = target.getAttribute("type"); + + if (type == "checkbox") + { + var checked = target.getAttribute("checked"); + var value = target.getAttribute("value"); + var wasChecked = hasClass(target, "fbMenuChecked"); + + if (wasChecked) + { + removeClass(target, "fbMenuChecked"); + target.setAttribute("checked", ""); + } + else + { + setClass(target, "fbMenuChecked"); + target.setAttribute("checked", "true"); + } + + if (isFunction(this.onCheck)) + this.onCheck.call(this, target, value, !wasChecked); + } + + if (type == "radiobutton") + { + var selectedRadios = getElementsByClass(target.parentNode, "fbMenuRadioSelected"); + + var group = target.getAttribute("group"); + + for (var i = 0, length = selectedRadios.length; i < length; i++) + { + radio = selectedRadios[i]; + + if (radio.getAttribute("group") == group) + { + removeClass(radio, "fbMenuRadioSelected"); + radio.setAttribute("selected", ""); + } + } + + setClass(target, "fbMenuRadioSelected"); + target.setAttribute("selected", "true"); + } + + var handler = null; + + // target.command can be a function or a string. + var cmd = target.command; + + // If it is a function it will be used as the handler + if (isFunction(cmd)) + handler = cmd; + // If it is a string it the property of the current menu object + // will be used as the handler + else if (typeof cmd == "string") + handler = this[cmd]; + + var closeMenu = true; + + if (handler) + closeMenu = handler.call(this, target) !== false; + + if (closeMenu) + topParent.hide(); + } + + return false; + }, + + handleWindowMouseDown: function(event) + { + //console.log("handleWindowMouseDown"); + + var target = event.target || event.srcElement; + + target = getAncestorByClass(target, "fbMenu"); + + if (!target) + { + removeEvent(Firebug.chrome.document, "mousedown", this.handleWindowMouseDown); + this.hide(); + } + }, + + handleMouseOver: function(event) + { + //console.log("handleMouseOver", this.element.id); + + this.clearHideTimeout(); + this.clearShowChildTimeout(); + + var target = event.target || event.srcElement; + + target = getAncestorByClass(target, "fbMenuOption"); + + if(!target) + return; + + var childMenu = this.childMenu; + if(childMenu) + { + removeClass(childMenu.parentTarget, "fbMenuGroupSelected"); + + if (childMenu.parentTarget != target && childMenu.isVisible) + { + childMenu.clearHideTimeout(); + childMenu.hideTimeout = Firebug.chrome.window.setTimeout(function(){ + childMenu.destroy(); + },300); + } + } + + if(hasClass(target, "fbMenuGroup")) + { + this.showChildMenu(target); + } + } +}); + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +append(Menu, +/**@extend FBL.Menu*/ +{ + register: function(object) + { + menuMap[object.id] = object; + }, + + check: function(element) + { + setClass(element, "fbMenuChecked"); + element.setAttribute("checked", "true"); + }, + + uncheck: function(element) + { + removeClass(element, "fbMenuChecked"); + element.setAttribute("checked", ""); + }, + + disable: function(element) + { + setClass(element, "fbMenuDisabled"); + }, + + enable: function(element) + { + removeClass(element, "fbMenuDisabled"); + } +}); + + +//************************************************************************************************ +// Status Bar + +/**@class*/ +function StatusBar(){}; + +StatusBar.prototype = extend(Controller, { + +}); + +// ************************************************************************************************ + + +// ************************************************************************************************ +}}); + +/* See license.txt for terms of usage */ + +FBL.ns( /**@scope s_context*/ function() { with (FBL) { +// ************************************************************************************************ + +// ************************************************************************************************ +// Globals + +var refreshDelay = 300; + +// Opera and some versions of webkit returns the wrong value of document.elementFromPoint() +// function, without taking into account the scroll position. Safari 4 (webkit/531.21.8) +// still have this issue. Google Chrome 4 (webkit/532.5) does not. So, we're assuming this +// issue was fixed in the 532 version +var shouldFixElementFromPoint = isOpera || isSafari && browserVersion < "532"; + +var evalError = "___firebug_evaluation_error___"; +var pixelsPerInch; + +var resetStyle = "margin:0; padding:0; border:0; position:absolute; overflow:hidden; display:block;"; +var offscreenStyle = resetStyle + "top:-1234px; left:-1234px;"; + + +// ************************************************************************************************ +// Context + +/** @class */ +FBL.Context = function(win) +{ + this.window = win.window; + this.document = win.document; + + this.browser = Env.browser; + + // Some windows in IE, like iframe, doesn't have the eval() method + if (isIE && !this.window.eval) + { + // But after executing the following line the method magically appears! + this.window.execScript("null"); + // Just to make sure the "magic" really happened + if (!this.window.eval) + throw new Error("Firebug Error: eval() method not found in this window"); + } + + // Create a new "black-box" eval() method that runs in the global namespace + // of the context window, without exposing the local variables declared + // by the function that calls it + this.eval = this.window.eval("new Function('" + + "try{ return window.eval.apply(window,arguments) }catch(E){ E."+evalError+"=true; return E }" + + "')"); +}; + +FBL.Context.prototype = +{ + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // partial-port of Firebug tabContext.js + + browser: null, + loaded: true, + + setTimeout: function(fn, delay) + { + var win = this.window; + + if (win.setTimeout == this.setTimeout) + throw new Error("setTimeout recursion"); + + var timeout = win.setTimeout.apply ? // IE doesn't have apply method on setTimeout + win.setTimeout.apply(win, arguments) : + win.setTimeout(fn, delay); + + if (!this.timeouts) + this.timeouts = {}; + + this.timeouts[timeout] = 1; + + return timeout; + }, + + clearTimeout: function(timeout) + { + clearTimeout(timeout); + + if (this.timeouts) + delete this.timeouts[timeout]; + }, + + setInterval: function(fn, delay) + { + var win = this.window; + + var timeout = win.setInterval.apply ? // IE doesn't have apply method on setTimeout + win.setInterval.apply(win, arguments) : + win.setInterval(fn, delay); + + if (!this.intervals) + this.intervals = {}; + + this.intervals[timeout] = 1; + + return timeout; + }, + + clearInterval: function(timeout) + { + clearInterval(timeout); + + if (this.intervals) + delete this.intervals[timeout]; + }, + + invalidatePanels: function() + { + if (!this.invalidPanels) + this.invalidPanels = {}; + + for (var i = 0; i < arguments.length; ++i) + { + var panelName = arguments[i]; + + // avoid error. need to create a better getPanel() function as explained below + if (!Firebug.chrome || !Firebug.chrome.selectedPanel) + return; + + //var panel = this.getPanel(panelName, true); + //TODO: xxxpedro context how to get all panels using a single function? + // the current workaround to make the invalidation works is invalidating + // only sidePanels. There's also a problem with panel name (LowerCase in Firebug Lite) + var panel = Firebug.chrome.selectedPanel.sidePanelBar ? + Firebug.chrome.selectedPanel.sidePanelBar.getPanel(panelName, true) : + null; + + if (panel && !panel.noRefresh) + this.invalidPanels[panelName] = 1; + } + + if (this.refreshTimeout) + { + this.clearTimeout(this.refreshTimeout); + delete this.refreshTimeout; + } + + this.refreshTimeout = this.setTimeout(bindFixed(function() + { + var invalids = []; + + for (var panelName in this.invalidPanels) + { + //var panel = this.getPanel(panelName, true); + //TODO: xxxpedro context how to get all panels using a single function? + // the current workaround to make the invalidation works is invalidating + // only sidePanels. There's also a problem with panel name (LowerCase in Firebug Lite) + var panel = Firebug.chrome.selectedPanel.sidePanelBar ? + Firebug.chrome.selectedPanel.sidePanelBar.getPanel(panelName, true) : + null; + + if (panel) + { + if (panel.visible && !panel.editing) + panel.refresh(); + else + panel.needsRefresh = true; + + // If the panel is being edited, we'll keep trying to + // refresh it until editing is done + if (panel.editing) + invalids.push(panelName); + } + } + + delete this.invalidPanels; + delete this.refreshTimeout; + + // Keep looping until every tab is valid + if (invalids.length) + this.invalidatePanels.apply(this, invalids); + }, this), refreshDelay); + }, + + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Evalutation Method + + /** + * Evaluates an expression in the current context window. + * + * @param {String} expr expression to be evaluated + * + * @param {String} context string indicating the global location + * of the object that will be used as the + * context. The context is referred in + * the expression as the "this" keyword. + * If no context is informed, the "window" + * context is used. + * + * @param {String} api string indicating the global location + * of the object that will be used as the + * api of the evaluation. + * + * @param {Function} errorHandler(message) error handler to be called + * if the evaluation fails. + */ + evaluate: function(expr, context, api, errorHandler) + { + // the default context is the "window" object. It can be any string that represents + // a global accessible element as: "my.namespaced.object" + context = context || "window"; + + var isObjectLiteral = trim(expr).indexOf("{") == 0, + cmd, + result; + + // if the context is the "window" object, we don't need a closure + if (context == "window") + { + // If it is an object literal, then wrap the expression with parenthesis so we can + // capture the return value + if (isObjectLiteral) + { + cmd = api ? + "with("+api+"){ ("+expr+") }" : + "(" + expr + ")"; + } + else + { + cmd = api ? + "with("+api+"){ "+expr+" }" : + expr; + } + } + else + { + cmd = api ? + // with API and context, no return value + "(function(arguments){ with(" + api + "){ " + + expr + + " } }).call(" + context + ",undefined)" + : + // with context only, no return value + "(function(arguments){ " + + expr + + " }).call(" + context + ",undefined)"; + } + + result = this.eval(cmd); + + if (result && result[evalError]) + { + var msg = result.name ? (result.name + ": ") : ""; + msg += result.message || result; + + if (errorHandler) + result = errorHandler(msg); + else + result = msg; + } + + return result; + }, + + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Window Methods + + getWindowSize: function() + { + var width=0, height=0, el; + + if (typeof this.window.innerWidth == "number") + { + width = this.window.innerWidth; + height = this.window.innerHeight; + } + else if ((el=this.document.documentElement) && (el.clientHeight || el.clientWidth)) + { + width = el.clientWidth; + height = el.clientHeight; + } + else if ((el=this.document.body) && (el.clientHeight || el.clientWidth)) + { + width = el.clientWidth; + height = el.clientHeight; + } + + return {width: width, height: height}; + }, + + getWindowScrollSize: function() + { + var width=0, height=0, el; + + // first try the document.documentElement scroll size + if (!isIEQuiksMode && (el=this.document.documentElement) && + (el.scrollHeight || el.scrollWidth)) + { + width = el.scrollWidth; + height = el.scrollHeight; + } + + // then we need to check if document.body has a bigger scroll size value + // because sometimes depending on the browser and the page, the document.body + // scroll size returns a smaller (and wrong) measure + if ((el=this.document.body) && (el.scrollHeight || el.scrollWidth) && + (el.scrollWidth > width || el.scrollHeight > height)) + { + width = el.scrollWidth; + height = el.scrollHeight; + } + + return {width: width, height: height}; + }, + + getWindowScrollPosition: function() + { + var top=0, left=0, el; + + if(typeof this.window.pageYOffset == "number") + { + top = this.window.pageYOffset; + left = this.window.pageXOffset; + } + else if((el=this.document.body) && (el.scrollTop || el.scrollLeft)) + { + top = el.scrollTop; + left = el.scrollLeft; + } + else if((el=this.document.documentElement) && (el.scrollTop || el.scrollLeft)) + { + top = el.scrollTop; + left = el.scrollLeft; + } + + return {top:top, left:left}; + }, + + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Element Methods + + getElementFromPoint: function(x, y) + { + if (shouldFixElementFromPoint) + { + var scroll = this.getWindowScrollPosition(); + return this.document.elementFromPoint(x + scroll.left, y + scroll.top); + } + else + return this.document.elementFromPoint(x, y); + }, + + getElementPosition: function(el) + { + var left = 0; + var top = 0; + + do + { + left += el.offsetLeft; + top += el.offsetTop; + } + while (el = el.offsetParent); + + return {left:left, top:top}; + }, + + getElementBox: function(el) + { + var result = {}; + + if (el.getBoundingClientRect) + { + var rect = el.getBoundingClientRect(); + + // fix IE problem with offset when not in fullscreen mode + var offset = isIE ? this.document.body.clientTop || this.document.documentElement.clientTop: 0; + + var scroll = this.getWindowScrollPosition(); + + result.top = Math.round(rect.top - offset + scroll.top); + result.left = Math.round(rect.left - offset + scroll.left); + result.height = Math.round(rect.bottom - rect.top); + result.width = Math.round(rect.right - rect.left); + } + else + { + var position = this.getElementPosition(el); + + result.top = position.top; + result.left = position.left; + result.height = el.offsetHeight; + result.width = el.offsetWidth; + } + + return result; + }, + + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Measurement Methods + + getMeasurement: function(el, name) + { + var result = {value: 0, unit: "px"}; + + var cssValue = this.getStyle(el, name); + + if (!cssValue) return result; + if (cssValue.toLowerCase() == "auto") return result; + + var reMeasure = /(\d+\.?\d*)(.*)/; + var m = cssValue.match(reMeasure); + + if (m) + { + result.value = m[1]-0; + result.unit = m[2].toLowerCase(); + } + + return result; + }, + + getMeasurementInPixels: function(el, name) + { + if (!el) return null; + + var m = this.getMeasurement(el, name); + var value = m.value; + var unit = m.unit; + + if (unit == "px") + return value; + + else if (unit == "pt") + return this.pointsToPixels(name, value); + + else if (unit == "em") + return this.emToPixels(el, value); + + else if (unit == "%") + return this.percentToPixels(el, value); + + else if (unit == "ex") + return this.exToPixels(el, value); + + // TODO: add other units. Maybe create a better general way + // to calculate measurements in different units. + }, + + getMeasurementBox1: function(el, name) + { + var sufixes = ["Top", "Left", "Bottom", "Right"]; + var result = []; + + for(var i=0, sufix; sufix=sufixes[i]; i++) + result[i] = Math.round(this.getMeasurementInPixels(el, name + sufix)); + + return {top:result[0], left:result[1], bottom:result[2], right:result[3]}; + }, + + getMeasurementBox: function(el, name) + { + var result = []; + var sufixes = name == "border" ? + ["TopWidth", "LeftWidth", "BottomWidth", "RightWidth"] : + ["Top", "Left", "Bottom", "Right"]; + + if (isIE) + { + var propName, cssValue; + var autoMargin = null; + + for(var i=0, sufix; sufix=sufixes[i]; i++) + { + propName = name + sufix; + + cssValue = el.currentStyle[propName] || el.style[propName]; + + if (cssValue == "auto") + { + if (!autoMargin) + autoMargin = this.getCSSAutoMarginBox(el); + + result[i] = autoMargin[sufix.toLowerCase()]; + } + else + result[i] = this.getMeasurementInPixels(el, propName); + + } + + } + else + { + for(var i=0, sufix; sufix=sufixes[i]; i++) + result[i] = this.getMeasurementInPixels(el, name + sufix); + } + + return {top:result[0], left:result[1], bottom:result[2], right:result[3]}; + }, + + getCSSAutoMarginBox: function(el) + { + if (isIE && " meta title input script link a ".indexOf(" "+el.nodeName.toLowerCase()+" ") != -1) + return {top:0, left:0, bottom:0, right:0}; + /**/ + + if (isIE && " h1 h2 h3 h4 h5 h6 h7 ul p ".indexOf(" "+el.nodeName.toLowerCase()+" ") == -1) + return {top:0, left:0, bottom:0, right:0}; + /**/ + + var offsetTop = 0; + if (false && isIEStantandMode) + { + var scrollSize = Firebug.browser.getWindowScrollSize(); + offsetTop = scrollSize.height; + } + + var box = this.document.createElement("div"); + //box.style.cssText = "margin:0; padding:1px; border: 0; position:static; overflow:hidden; visibility: hidden;"; + box.style.cssText = "margin:0; padding:1px; border: 0; visibility: hidden;"; + + var clone = el.cloneNode(false); + var text = this.document.createTextNode(" "); + clone.appendChild(text); + + box.appendChild(clone); + + this.document.body.appendChild(box); + + var marginTop = clone.offsetTop - box.offsetTop - 1; + var marginBottom = box.offsetHeight - clone.offsetHeight - 2 - marginTop; + + var marginLeft = clone.offsetLeft - box.offsetLeft - 1; + var marginRight = box.offsetWidth - clone.offsetWidth - 2 - marginLeft; + + this.document.body.removeChild(box); + + return {top:marginTop+offsetTop, left:marginLeft, bottom:marginBottom-offsetTop, right:marginRight}; + }, + + getFontSizeInPixels: function(el) + { + var size = this.getMeasurement(el, "fontSize"); + + if (size.unit == "px") return size.value; + + // get font size, the dirty way + var computeDirtyFontSize = function(el, calibration) + { + var div = this.document.createElement("div"); + var divStyle = offscreenStyle; + + if (calibration) + divStyle += " font-size:"+calibration+"px;"; + + div.style.cssText = divStyle; + div.innerHTML = "A"; + el.appendChild(div); + + var value = div.offsetHeight; + el.removeChild(div); + return value; + }; + + /* + var calibrationBase = 200; + var calibrationValue = computeDirtyFontSize(el, calibrationBase); + var rate = calibrationBase / calibrationValue; + /**/ + + // the "dirty technique" fails in some environments, so we're using a static value + // based in some tests. + var rate = 200 / 225; + + var value = computeDirtyFontSize(el); + + return value * rate; + }, + + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Unit Funtions + + pointsToPixels: function(name, value, returnFloat) + { + var axis = /Top$|Bottom$/.test(name) ? "y" : "x"; + + var result = value * pixelsPerInch[axis] / 72; + + return returnFloat ? result : Math.round(result); + }, + + emToPixels: function(el, value) + { + if (!el) return null; + + var fontSize = this.getFontSizeInPixels(el); + + return Math.round(value * fontSize); + }, + + exToPixels: function(el, value) + { + if (!el) return null; + + // get ex value, the dirty way + var div = this.document.createElement("div"); + div.style.cssText = offscreenStyle + "width:"+value + "ex;"; + + el.appendChild(div); + var value = div.offsetWidth; + el.removeChild(div); + + return value; + }, + + percentToPixels: function(el, value) + { + if (!el) return null; + + // get % value, the dirty way + var div = this.document.createElement("div"); + div.style.cssText = offscreenStyle + "width:"+value + "%;"; + + el.appendChild(div); + var value = div.offsetWidth; + el.removeChild(div); + + return value; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + getStyle: isIE ? function(el, name) + { + return el.currentStyle[name] || el.style[name] || undefined; + } + : function(el, name) + { + return this.document.defaultView.getComputedStyle(el,null)[name] + || el.style[name] || undefined; + } + +}; + + +// ************************************************************************************************ +}}); + +/* See license.txt for terms of usage */ + +FBL.ns( /**@scope ns-chrome*/ function() { with (FBL) { +// ************************************************************************************************ + +// ************************************************************************************************ +// Globals + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// Window Options + +var WindowDefaultOptions = + { + type: "frame", + id: "FirebugUI" + //height: 350 // obsolete + }, + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// Instantiated objects + + commandLine, + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// Interface Elements Cache + + fbTop, + fbContent, + fbContentStyle, + fbBottom, + fbBtnInspect, + + fbToolbar, + + fbPanelBox1, + fbPanelBox1Style, + fbPanelBox2, + fbPanelBox2Style, + fbPanelBar2Box, + fbPanelBar2BoxStyle, + + fbHSplitter, + fbVSplitter, + fbVSplitterStyle, + + fbPanel1, + fbPanel1Style, + fbPanel2, + fbPanel2Style, + + fbConsole, + fbConsoleStyle, + fbHTML, + + fbCommandLine, + fbLargeCommandLine, + fbLargeCommandButtons, + +//* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// Cached size values + + topHeight, + topPartialHeight, + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + chromeRedrawSkipRate = isIE ? 75 : isOpera ? 80 : 75, + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + lastSelectedPanelName, + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + focusCommandLineState = 0, + lastFocusedPanelName, + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + lastHSplitterMouseMove = 0, + onHSplitterMouseMoveBuffer = null, + onHSplitterMouseMoveTimer = null, + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + lastVSplitterMouseMove = 0; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + +// ************************************************************************************************ +// FirebugChrome + +FBL.defaultPersistedState = +{ + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + isOpen: false, + height: 300, + sidePanelWidth: 350, + + selectedPanelName: "Console", + selectedHTMLElementId: null, + + htmlSelectionStack: [] + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +}; + +/**@namespace*/ +FBL.FirebugChrome = +{ + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + //isOpen: false, + //height: 300, + //sidePanelWidth: 350, + + //selectedPanelName: "Console", + //selectedHTMLElementId: null, + + chromeMap: {}, + + htmlSelectionStack: [], + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + create: function() + { + if (FBTrace.DBG_INITIALIZE) FBTrace.sysout("FirebugChrome.create", "creating chrome window"); + + createChromeWindow(); + }, + + initialize: function() + { + if (FBTrace.DBG_INITIALIZE) FBTrace.sysout("FirebugChrome.initialize", "initializing chrome window"); + + if (Env.chrome.type == "frame" || Env.chrome.type == "div") + ChromeMini.create(Env.chrome); + + var chrome = Firebug.chrome = new Chrome(Env.chrome); + FirebugChrome.chromeMap[chrome.type] = chrome; + + addGlobalEvent("keydown", onGlobalKeyDown); + + if (Env.Options.enablePersistent && chrome.type == "popup") + { + // TODO: xxxpedro persist - revise chrome synchronization when in persistent mode + var frame = FirebugChrome.chromeMap.frame; + if (frame) + frame.close(); + + //chrome.reattach(frame, chrome); + //TODO: xxxpedro persist synchronize? + chrome.initialize(); + } + }, + + clone: function(FBChrome) + { + for (var name in FBChrome) + { + var prop = FBChrome[name]; + if (FBChrome.hasOwnProperty(name) && !isFunction(prop)) + { + this[name] = prop; + } + } + } +}; + + + +// ************************************************************************************************ +// Chrome Window Creation + +var createChromeWindow = function(options) +{ + options = extend(WindowDefaultOptions, options || {}); + + //* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Locals + + var browserWin = Env.browser.window; + var browserContext = new Context(browserWin); + var prefs = Store.get("FirebugLite"); + var persistedState = prefs && prefs.persistedState || defaultPersistedState; + + var chrome = {}, + + context = options.context || Env.browser, + + type = chrome.type = Env.Options.enablePersistent ? + "popup" : + options.type, + + isChromeFrame = type == "frame", + + useLocalSkin = Env.useLocalSkin, + + url = useLocalSkin ? + Env.Location.skin : + "about:blank", + + // document.body not available in XML+XSL documents in Firefox + body = context.document.getElementsByTagName("body")[0], + + formatNode = function(node) + { + if (!Env.isDebugMode) + { + node.firebugIgnore = true; + } + + var browserWinSize = browserContext.getWindowSize(); + var height = persistedState.height || 300; + + height = Math.min(browserWinSize.height, height); + height = Math.max(200, height); + + node.style.border = "0"; + node.style.visibility = "hidden"; + node.style.zIndex = "2147483647"; // MAX z-index = 2147483647 + node.style.position = noFixedPosition ? "absolute" : "fixed"; + node.style.width = "100%"; // "102%"; IE auto margin bug + node.style.left = "0"; + node.style.bottom = noFixedPosition ? "-1px" : "0"; + node.style.height = height + "px"; + + // avoid flickering during chrome rendering + //if (isFirefox) + // node.style.display = "none"; + }, + + createChromeDiv = function() + { + //Firebug.Console.warn("Firebug Lite GUI is working in 'windowless mode'. It may behave slower and receive interferences from the page in which it is installed."); + + var node = chrome.node = createGlobalElement("div"), + style = createGlobalElement("style"), + + css = FirebugChrome.Skin.CSS + /* + .replace(/;/g, " !important;") + .replace(/!important\s!important/g, "!important") + .replace(/display\s*:\s*(\w+)\s*!important;/g, "display:$1;")*/, + + // reset some styles to minimize interference from the main page's style + rules = ".fbBody *{margin:0;padding:0;font-size:11px;line-height:13px;color:inherit;}" + + // load the chrome styles + css + + // adjust some remaining styles + ".fbBody #fbHSplitter{position:absolute !important;} .fbBody #fbHTML span{line-height:14px;} .fbBody .lineNo div{line-height:inherit !important;}"; + /* + if (isIE) + { + // IE7 CSS bug (FbChrome table bigger than its parent div) + rules += ".fbBody table.fbChrome{position: static !important;}"; + }/**/ + + style.type = "text/css"; + + if (style.styleSheet) + style.styleSheet.cssText = rules; + else + style.appendChild(context.document.createTextNode(rules)); + + document.getElementsByTagName("head")[0].appendChild(style); + + node.className = "fbBody"; + node.style.overflow = "hidden"; + node.innerHTML = getChromeDivTemplate(); + + if (isIE) + { + // IE7 CSS bug (FbChrome table bigger than its parent div) + setTimeout(function(){ + node.firstChild.style.height = "1px"; + node.firstChild.style.position = "static"; + },0); + /**/ + } + + formatNode(node); + + body.appendChild(node); + + chrome.window = window; + chrome.document = document; + onChromeLoad(chrome); + }; + + //* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + try + { + //* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // create the Chrome as a "div" (windowless mode) + if (type == "div") + { + createChromeDiv(); + return; + } + + //* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // cretate the Chrome as an "iframe" + else if (isChromeFrame) + { + // Create the Chrome Frame + var node = chrome.node = createGlobalElement("iframe"); + node.setAttribute("src", url); + node.setAttribute("frameBorder", "0"); + + formatNode(node); + + body.appendChild(node); + + // must set the id after appending to the document, otherwise will cause an + // strange error in IE, making the iframe load the page in which the bookmarklet + // was created (like getfirebug.com), before loading the injected UI HTML, + // generating an "Access Denied" error. + node.id = options.id; + } + + //* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // create the Chrome as a "popup" + else + { + var height = persistedState.popupHeight || 300; + var browserWinSize = browserContext.getWindowSize(); + + var browserWinLeft = typeof browserWin.screenX == "number" ? + browserWin.screenX : browserWin.screenLeft; + + var popupLeft = typeof persistedState.popupLeft == "number" ? + persistedState.popupLeft : browserWinLeft; + + var browserWinTop = typeof browserWin.screenY == "number" ? + browserWin.screenY : browserWin.screenTop; + + var popupTop = typeof persistedState.popupTop == "number" ? + persistedState.popupTop : + Math.max( + 0, + Math.min( + browserWinTop + browserWinSize.height - height, + // Google Chrome bug + screen.availHeight - height - 61 + ) + ); + + var popupWidth = typeof persistedState.popupWidth == "number" ? + persistedState.popupWidth : + Math.max( + 0, + Math.min( + browserWinSize.width, + // Opera opens popup in a new tab if it's too big! + screen.availWidth-10 + ) + ); + + var popupHeight = typeof persistedState.popupHeight == "number" ? + persistedState.popupHeight : 300; + + var options = [ + "true,top=", popupTop, + ",left=", popupLeft, + ",height=", popupHeight, + ",width=", popupWidth, + ",resizable" + ].join(""), + + node = chrome.node = context.window.open( + url, + "popup", + options + ); + + if (node) + { + try + { + node.focus(); + } + catch(E) + { + alert("Firebug Error: Firebug popup was blocked."); + return; + } + } + else + { + alert("Firebug Error: Firebug popup was blocked."); + return; + } + } + + //* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Inject the interface HTML if it is not using the local skin + + if (!useLocalSkin) + { + var tpl = getChromeTemplate(!isChromeFrame), + doc = isChromeFrame ? node.contentWindow.document : node.document; + + doc.write(tpl); + doc.close(); + } + + //* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Wait the Window to be loaded + + var win, + + waitDelay = useLocalSkin ? isChromeFrame ? 200 : 300 : 100, + + waitForWindow = function() + { + if ( // Frame loaded... OR + isChromeFrame && (win=node.contentWindow) && + node.contentWindow.document.getElementById("fbCommandLine") || + + // Popup loaded + !isChromeFrame && (win=node.window) && node.document && + node.document.getElementById("fbCommandLine") ) + { + chrome.window = win.window; + chrome.document = win.document; + + // Prevent getting the wrong chrome height in FF when opening a popup + setTimeout(function(){ + onChromeLoad(chrome); + }, useLocalSkin ? 200 : 0); + } + else + setTimeout(waitForWindow, waitDelay); + }; + + waitForWindow(); + } + catch(e) + { + var msg = e.message || e; + + if (/access/i.test(msg)) + { + // Firebug Lite could not create a window for its Graphical User Interface due to + // a access restriction. This happens in some pages, when loading via bookmarklet. + // In such cases, the only way is to load the GUI in a "windowless mode". + + if (isChromeFrame) + body.removeChild(node); + else if(type == "popup") + node.close(); + + // Load the GUI in a "windowless mode" + createChromeDiv(); + } + else + { + alert("Firebug Error: Firebug GUI could not be created."); + } + } +}; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +var onChromeLoad = function onChromeLoad(chrome) +{ + Env.chrome = chrome; + + if (FBTrace.DBG_INITIALIZE) FBTrace.sysout("Chrome onChromeLoad", "chrome window loaded"); + + if (Env.Options.enablePersistent) + { + // TODO: xxxpedro persist - make better chrome synchronization when in persistent mode + Env.FirebugChrome = FirebugChrome; + + chrome.window.Firebug = chrome.window.Firebug || {}; + chrome.window.Firebug.SharedEnv = Env; + + if (Env.isDevelopmentMode) + { + Env.browser.window.FBDev.loadChromeApplication(chrome); + } + else + { + var doc = chrome.document; + var script = doc.createElement("script"); + script.src = Env.Location.app + "#remote,persist"; + doc.getElementsByTagName("head")[0].appendChild(script); + } + } + else + { + if (chrome.type == "frame" || chrome.type == "div") + { + // initialize the chrome application + setTimeout(function(){ + FBL.Firebug.initialize(); + },0); + } + else if (chrome.type == "popup") + { + var oldChrome = FirebugChrome.chromeMap.frame; + + var newChrome = new Chrome(chrome); + + // TODO: xxxpedro sync detach reattach attach + dispatch(newChrome.panelMap, "detach", [oldChrome, newChrome]); + + newChrome.reattach(oldChrome, newChrome); + } + } +}; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +var getChromeDivTemplate = function() +{ + return FirebugChrome.Skin.HTML; +}; + +var getChromeTemplate = function(isPopup) +{ + var tpl = FirebugChrome.Skin; + var r = [], i = -1; + + r[++i] = '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/DTD/strict.dtd">'; + r[++i] = '<html><head><title>'; + r[++i] = Firebug.version; + + /* + r[++i] = '</title><link href="'; + r[++i] = Env.Location.skinDir + 'firebug.css'; + r[++i] = '" rel="stylesheet" type="text/css" />'; + /**/ + + r[++i] = '</title><style>html,body{margin:0;padding:0;overflow:hidden;}'; + r[++i] = tpl.CSS; + r[++i] = '</style>'; + /**/ + + r[++i] = '</head><body class="fbBody' + (isPopup ? ' FirebugPopup' : '') + '">'; + r[++i] = tpl.HTML; + r[++i] = '</body></html>'; + + return r.join(""); +}; + + +// ************************************************************************************************ +// Chrome Class + +/**@class*/ +var Chrome = function Chrome(chrome) +{ + var type = chrome.type; + var Base = type == "frame" || type == "div" ? ChromeFrameBase : ChromePopupBase; + + append(this, Base); // inherit from base class (ChromeFrameBase or ChromePopupBase) + append(this, chrome); // inherit chrome window properties + append(this, new Context(chrome.window)); // inherit from Context class + + FirebugChrome.chromeMap[type] = this; + Firebug.chrome = this; + Env.chrome = chrome.window; + + this.commandLineVisible = false; + this.sidePanelVisible = false; + + this.create(); + + return this; +}; + +// ************************************************************************************************ +// ChromeBase + +/** + * @namespace + * @extends FBL.Controller + * @extends FBL.PanelBar + **/ +var ChromeBase = {}; +append(ChromeBase, Controller); +append(ChromeBase, PanelBar); +append(ChromeBase, +/**@extend ns-chrome-ChromeBase*/ +{ + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // inherited properties + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // inherited from createChrome function + + node: null, + type: null, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // inherited from Context.prototype + + document: null, + window: null, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // value properties + + sidePanelVisible: false, + commandLineVisible: false, + largeCommandLineVisible: false, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // object properties + + inspectButton: null, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + create: function() + { + PanelBar.create.call(this); + + if (Firebug.Inspector) + this.inspectButton = new Button({ + type: "toggle", + element: $("fbChrome_btInspect"), + owner: Firebug.Inspector, + + onPress: Firebug.Inspector.startInspecting, + onUnpress: Firebug.Inspector.stopInspecting + }); + }, + + destroy: function() + { + if(Firebug.Inspector) + this.inspectButton.destroy(); + + PanelBar.destroy.call(this); + + this.shutdown(); + }, + + testMenu: function() + { + var firebugMenu = new Menu( + { + id: "fbFirebugMenu", + + items: + [ + { + label: "Open Firebug", + type: "shortcut", + key: isFirefox ? "Shift+F12" : "F12", + checked: true, + command: "toggleChrome" + }, + { + label: "Open Firebug in New Window", + type: "shortcut", + key: isFirefox ? "Ctrl+Shift+F12" : "Ctrl+F12", + command: "openPopup" + }, + { + label: "Inspect Element", + type: "shortcut", + key: "Ctrl+Shift+C", + command: "toggleInspect" + }, + { + label: "Command Line", + type: "shortcut", + key: "Ctrl+Shift+L", + command: "focusCommandLine" + }, + "-", + { + label: "Options", + type: "group", + child: "fbFirebugOptionsMenu" + }, + "-", + { + label: "Firebug Lite Website...", + command: "visitWebsite" + }, + { + label: "Discussion Group...", + command: "visitDiscussionGroup" + }, + { + label: "Issue Tracker...", + command: "visitIssueTracker" + } + ], + + onHide: function() + { + iconButton.restore(); + }, + + toggleChrome: function() + { + Firebug.chrome.toggle(); + }, + + openPopup: function() + { + Firebug.chrome.toggle(true, true); + }, + + toggleInspect: function() + { + Firebug.Inspector.toggleInspect(); + }, + + focusCommandLine: function() + { + Firebug.chrome.focusCommandLine(); + }, + + visitWebsite: function() + { + this.visit("http://getfirebug.com/lite.html"); + }, + + visitDiscussionGroup: function() + { + this.visit("http://groups.google.com/group/firebug"); + }, + + visitIssueTracker: function() + { + this.visit("http://code.google.com/p/fbug/issues/list"); + }, + + visit: function(url) + { + window.open(url); + } + + }); + + /**@private*/ + var firebugOptionsMenu = + { + id: "fbFirebugOptionsMenu", + + getItems: function() + { + var cookiesDisabled = !Firebug.saveCookies; + + return [ + { + label: "Start Opened", + type: "checkbox", + value: "startOpened", + checked: Firebug.startOpened, + disabled: cookiesDisabled + }, + { + label: "Start in New Window", + type: "checkbox", + value: "startInNewWindow", + checked: Firebug.startInNewWindow, + disabled: cookiesDisabled + }, + { + label: "Show Icon When Hidden", + type: "checkbox", + value: "showIconWhenHidden", + checked: Firebug.showIconWhenHidden, + disabled: cookiesDisabled + }, + { + label: "Override Console Object", + type: "checkbox", + value: "overrideConsole", + checked: Firebug.overrideConsole, + disabled: cookiesDisabled + }, + { + label: "Ignore Firebug Elements", + type: "checkbox", + value: "ignoreFirebugElements", + checked: Firebug.ignoreFirebugElements, + disabled: cookiesDisabled + }, + { + label: "Disable When Firebug Active", + type: "checkbox", + value: "disableWhenFirebugActive", + checked: Firebug.disableWhenFirebugActive, + disabled: cookiesDisabled + }, + { + label: "Disable XHR Listener", + type: "checkbox", + value: "disableXHRListener", + checked: Firebug.disableXHRListener, + disabled: cookiesDisabled + }, + { + label: "Disable Resource Fetching", + type: "checkbox", + value: "disableResourceFetching", + checked: Firebug.disableResourceFetching, + disabled: cookiesDisabled + }, + { + label: "Enable Trace Mode", + type: "checkbox", + value: "enableTrace", + checked: Firebug.enableTrace, + disabled: cookiesDisabled + }, + { + label: "Enable Persistent Mode (experimental)", + type: "checkbox", + value: "enablePersistent", + checked: Firebug.enablePersistent, + disabled: cookiesDisabled + }, + "-", + { + label: "Reset All Firebug Options", + command: "restorePrefs", + disabled: cookiesDisabled + } + ]; + }, + + onCheck: function(target, value, checked) + { + Firebug.setPref(value, checked); + }, + + restorePrefs: function(target) + { + Firebug.erasePrefs(); + + if (target) + this.updateMenu(target); + }, + + updateMenu: function(target) + { + var options = getElementsByClass(target.parentNode, "fbMenuOption"); + + var firstOption = options[0]; + var enabled = Firebug.saveCookies; + if (enabled) + Menu.check(firstOption); + else + Menu.uncheck(firstOption); + + if (enabled) + Menu.check(options[0]); + else + Menu.uncheck(options[0]); + + for (var i = 1, length = options.length; i < length; i++) + { + var option = options[i]; + + var value = option.getAttribute("value"); + var pref = Firebug[value]; + + if (pref) + Menu.check(option); + else + Menu.uncheck(option); + + if (enabled) + Menu.enable(option); + else + Menu.disable(option); + } + } + }; + + Menu.register(firebugOptionsMenu); + + var menu = firebugMenu; + + var testMenuClick = function(event) + { + //console.log("testMenuClick"); + cancelEvent(event, true); + + var target = event.target || event.srcElement; + + if (menu.isVisible) + menu.hide(); + else + { + var offsetLeft = isIE6 ? 1 : -4, // IE6 problem with fixed position + + chrome = Firebug.chrome, + + box = chrome.getElementBox(target), + + offset = chrome.type == "div" ? + chrome.getElementPosition(chrome.node) : + {top: 0, left: 0}; + + menu.show( + box.left + offsetLeft - offset.left, + box.top + box.height -5 - offset.top + ); + } + + return false; + }; + + var iconButton = new IconButton({ + type: "toggle", + element: $("fbFirebugButton"), + + onClick: testMenuClick + }); + + iconButton.initialize(); + + //addEvent($("fbToolbarIcon"), "click", testMenuClick); + }, + + initialize: function() + { + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + if (Env.bookmarkletOutdated) + Firebug.Console.logFormatted([ + "A new bookmarklet version is available. " + + "Please visit http://getfirebug.com/firebuglite#Install and update it." + ], Firebug.context, "warn"); + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + if (Firebug.Console) + Firebug.Console.flush(); + + if (Firebug.Trace) + FBTrace.flush(Firebug.Trace); + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + if (FBTrace.DBG_INITIALIZE) FBTrace.sysout("Firebug.chrome.initialize", "initializing chrome application"); + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // initialize inherited classes + Controller.initialize.call(this); + PanelBar.initialize.call(this); + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // create the interface elements cache + + fbTop = $("fbTop"); + fbContent = $("fbContent"); + fbContentStyle = fbContent.style; + fbBottom = $("fbBottom"); + fbBtnInspect = $("fbBtnInspect"); + + fbToolbar = $("fbToolbar"); + + fbPanelBox1 = $("fbPanelBox1"); + fbPanelBox1Style = fbPanelBox1.style; + fbPanelBox2 = $("fbPanelBox2"); + fbPanelBox2Style = fbPanelBox2.style; + fbPanelBar2Box = $("fbPanelBar2Box"); + fbPanelBar2BoxStyle = fbPanelBar2Box.style; + + fbHSplitter = $("fbHSplitter"); + fbVSplitter = $("fbVSplitter"); + fbVSplitterStyle = fbVSplitter.style; + + fbPanel1 = $("fbPanel1"); + fbPanel1Style = fbPanel1.style; + fbPanel2 = $("fbPanel2"); + fbPanel2Style = fbPanel2.style; + + fbConsole = $("fbConsole"); + fbConsoleStyle = fbConsole.style; + fbHTML = $("fbHTML"); + + fbCommandLine = $("fbCommandLine"); + fbLargeCommandLine = $("fbLargeCommandLine"); + fbLargeCommandButtons = $("fbLargeCommandButtons"); + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // static values cache + topHeight = fbTop.offsetHeight; + topPartialHeight = fbToolbar.offsetHeight; + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + disableTextSelection($("fbToolbar")); + disableTextSelection($("fbPanelBarBox")); + disableTextSelection($("fbPanelBar1")); + disableTextSelection($("fbPanelBar2")); + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Add the "javascript:void(0)" href attributes used to make the hover effect in IE6 + if (isIE6 && Firebug.Selector) + { + // TODO: xxxpedro change to getElementsByClass + var as = $$(".fbHover"); + for (var i=0, a; a=as[i]; i++) + { + a.setAttribute("href", "javascript:void(0)"); + } + } + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // initialize all panels + /* + var panelMap = Firebug.panelTypes; + for (var i=0, p; p=panelMap[i]; i++) + { + if (!p.parentPanel) + { + this.addPanel(p.prototype.name); + } + } + /**/ + + // ************************************************************************************************ + // ************************************************************************************************ + // ************************************************************************************************ + // ************************************************************************************************ + + if(Firebug.Inspector) + this.inspectButton.initialize(); + + // ************************************************************************************************ + // ************************************************************************************************ + // ************************************************************************************************ + // ************************************************************************************************ + + this.addController( + [$("fbLargeCommandLineIcon"), "click", this.showLargeCommandLine] + ); + + // ************************************************************************************************ + + // Select the first registered panel + // TODO: BUG IE7 + var self = this; + setTimeout(function(){ + self.selectPanel(Firebug.context.persistedState.selectedPanelName); + + if (Firebug.context.persistedState.selectedPanelName == "Console" && Firebug.CommandLine) + Firebug.chrome.focusCommandLine(); + },0); + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + //this.draw(); + + + + + + + + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + var onPanelMouseDown = function onPanelMouseDown(event) + { + //console.log("onPanelMouseDown", event.target || event.srcElement, event); + + var target = event.target || event.srcElement; + + if (FBL.isLeftClick(event)) + { + var editable = FBL.getAncestorByClass(target, "editable"); + + // if an editable element has been clicked then start editing + if (editable) + { + Firebug.Editor.startEditing(editable); + FBL.cancelEvent(event); + } + // if any other element has been clicked then stop editing + else + { + if (!hasClass(target, "textEditorInner")) + Firebug.Editor.stopEditing(); + } + } + else if (FBL.isMiddleClick(event) && Firebug.getRepNode(target)) + { + // Prevent auto-scroll when middle-clicking a rep object + FBL.cancelEvent(event); + } + }; + + Firebug.getElementPanel = function(element) + { + var panelNode = getAncestorByClass(element, "fbPanel"); + var id = panelNode.id.substr(2); + + var panel = Firebug.chrome.panelMap[id]; + + if (!panel) + { + if (Firebug.chrome.selectedPanel.sidePanelBar) + panel = Firebug.chrome.selectedPanel.sidePanelBar.panelMap[id]; + } + + return panel; + }; + + + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + // TODO: xxxpedro port to Firebug + + // 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. + var onKeyCodeListenersMap = []; + var onKeyCodeListen = function(event) + { + for (var keyCode in onKeyCodeListenersMap) + { + var listeners = onKeyCodeListenersMap[keyCode]; + + for (var i = 0, listener; listener = listeners[i]; i++) + { + var filter = listener.filter || FBL.noKeyModifiers; + + if (event.keyCode == keyCode && (!filter || filter(event))) + { + listener.listener(); + FBL.cancelEvent(event, true); + return false; + } + } + } + }; + + addEvent(Firebug.chrome.document, "keydown", onKeyCodeListen); + + /** + * @name keyCodeListen + * @memberOf FBL.FirebugChrome + */ + Firebug.chrome.keyCodeListen = function(key, filter, listener, capture) + { + var keyCode = KeyEvent["DOM_VK_"+key]; + + if (!onKeyCodeListenersMap[keyCode]) + onKeyCodeListenersMap[keyCode] = []; + + onKeyCodeListenersMap[keyCode].push({ + filter: filter, + listener: listener + }); + + return keyCode; + }; + + /** + * @name keyIgnore + * @memberOf FBL.FirebugChrome + */ + Firebug.chrome.keyIgnore = function(keyCode) + { + onKeyCodeListenersMap[keyCode] = null; + delete onKeyCodeListenersMap[keyCode]; + }; + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + /**/ + // move to shutdown + //removeEvent(Firebug.chrome.document, "keydown", listener[0]); + + + /* + Firebug.chrome.keyCodeListen = function(key, filter, listener, capture) + { + if (!filter) + filter = FBL.noKeyModifiers; + + var keyCode = KeyEvent["DOM_VK_"+key]; + + var fn = function fn(event) + { + if (event.keyCode == keyCode && (!filter || filter(event))) + { + listener(); + FBL.cancelEvent(event, true); + return false; + } + } + + addEvent(Firebug.chrome.document, "keydown", fn); + + return [fn, capture]; + }; + + Firebug.chrome.keyIgnore = function(listener) + { + removeEvent(Firebug.chrome.document, "keydown", listener[0]); + }; + /**/ + + + this.addController( + [fbPanel1, "mousedown", onPanelMouseDown], + [fbPanel2, "mousedown", onPanelMouseDown] + ); +/**/ + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + + // menus can be used without domplate + if (FBL.domplate) + this.testMenu(); + /**/ + + //test XHR + /* + setTimeout(function(){ + + FBL.Ajax.request({url: "../content/firebug/boot.js"}); + FBL.Ajax.request({url: "../content/firebug/boot.js.invalid"}); + + },1000); + /**/ + }, + + shutdown: function() + { + // ************************************************************************************************ + // ************************************************************************************************ + // ************************************************************************************************ + // ************************************************************************************************ + + if(Firebug.Inspector) + this.inspectButton.shutdown(); + + // ************************************************************************************************ + // ************************************************************************************************ + // ************************************************************************************************ + // ************************************************************************************************ + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + // remove disableTextSelection event handlers + restoreTextSelection($("fbToolbar")); + restoreTextSelection($("fbPanelBarBox")); + restoreTextSelection($("fbPanelBar1")); + restoreTextSelection($("fbPanelBar2")); + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // shutdown inherited classes + Controller.shutdown.call(this); + PanelBar.shutdown.call(this); + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Remove the interface elements cache (this must happen after calling + // the shutdown method of all dependent components to avoid errors) + + fbTop = null; + fbContent = null; + fbContentStyle = null; + fbBottom = null; + fbBtnInspect = null; + + fbToolbar = null; + + fbPanelBox1 = null; + fbPanelBox1Style = null; + fbPanelBox2 = null; + fbPanelBox2Style = null; + fbPanelBar2Box = null; + fbPanelBar2BoxStyle = null; + + fbHSplitter = null; + fbVSplitter = null; + fbVSplitterStyle = null; + + fbPanel1 = null; + fbPanel1Style = null; + fbPanel2 = null; + + fbConsole = null; + fbConsoleStyle = null; + fbHTML = null; + + fbCommandLine = null; + fbLargeCommandLine = null; + fbLargeCommandButtons = null; + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // static values cache + + topHeight = null; + topPartialHeight = null; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + toggle: function(forceOpen, popup) + { + if(popup) + { + this.detach(); + } + else + { + if (isOpera && Firebug.chrome.type == "popup" && Firebug.chrome.node.closed) + { + var frame = FirebugChrome.chromeMap.frame; + frame.reattach(); + + FirebugChrome.chromeMap.popup = null; + + frame.open(); + + return; + } + + // If the context is a popup, ignores the toggle process + if (Firebug.chrome.type == "popup") return; + + var shouldOpen = forceOpen || !Firebug.context.persistedState.isOpen; + + if(shouldOpen) + this.open(); + else + this.close(); + } + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + detach: function() + { + if(!FirebugChrome.chromeMap.popup) + { + this.close(); + createChromeWindow({type: "popup"}); + } + }, + + reattach: function(oldChrome, newChrome) + { + Firebug.browser.window.Firebug = Firebug; + + // chrome synchronization + var newPanelMap = newChrome.panelMap; + var oldPanelMap = oldChrome.panelMap; + + var panel; + for(var name in newPanelMap) + { + // TODO: xxxpedro innerHTML + panel = newPanelMap[name]; + if (panel.options.innerHTMLSync) + panel.panelNode.innerHTML = oldPanelMap[name].panelNode.innerHTML; + } + + Firebug.chrome = newChrome; + + // TODO: xxxpedro sync detach reattach attach + //dispatch(Firebug.chrome.panelMap, "detach", [oldChrome, newChrome]); + + if (newChrome.type == "popup") + { + newChrome.initialize(); + //dispatch(Firebug.modules, "initialize", []); + } + else + { + // TODO: xxxpedro only needed in persistent + // should use FirebugChrome.clone, but popup FBChrome + // isn't acessible + Firebug.context.persistedState.selectedPanelName = oldChrome.selectedPanel.name; + } + + dispatch(newPanelMap, "reattach", [oldChrome, newChrome]); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + draw: function() + { + var size = this.getSize(); + + // Height related values + var commandLineHeight = Firebug.chrome.commandLineVisible ? fbCommandLine.offsetHeight : 0, + + y = Math.max(size.height /* chrome height */, topHeight), + + heightValue = Math.max(y - topHeight - commandLineHeight /* fixed height */, 0), + + height = heightValue + "px", + + // Width related values + sideWidthValue = Firebug.chrome.sidePanelVisible ? Firebug.context.persistedState.sidePanelWidth : 0, + + width = Math.max(size.width /* chrome width */ - sideWidthValue, 0) + "px"; + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Height related rendering + fbPanelBox1Style.height = height; + fbPanel1Style.height = height; + + if (isIE || isOpera) + { + // Fix IE and Opera problems with auto resizing the verticall splitter + fbVSplitterStyle.height = Math.max(y - topPartialHeight - commandLineHeight, 0) + "px"; + } + //xxxpedro FF2 only? + /* + else if (isFirefox) + { + // Fix Firefox problem with table rows with 100% height (fit height) + fbContentStyle.maxHeight = Math.max(y - fixedHeight, 0)+ "px"; + }/**/ + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Width related rendering + fbPanelBox1Style.width = width; + fbPanel1Style.width = width; + + // SidePanel rendering + if (Firebug.chrome.sidePanelVisible) + { + sideWidthValue = Math.max(sideWidthValue - 6, 0); + + var sideWidth = sideWidthValue + "px"; + + fbPanelBox2Style.width = sideWidth; + + fbVSplitterStyle.right = sideWidth; + + if (Firebug.chrome.largeCommandLineVisible) + { + fbLargeCommandLine = $("fbLargeCommandLine"); + + fbLargeCommandLine.style.height = heightValue - 4 + "px"; + fbLargeCommandLine.style.width = sideWidthValue - 2 + "px"; + + fbLargeCommandButtons = $("fbLargeCommandButtons"); + fbLargeCommandButtons.style.width = sideWidth; + } + else + { + fbPanel2Style.height = height; + fbPanel2Style.width = sideWidth; + + fbPanelBar2BoxStyle.width = sideWidth; + } + } + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + getSize: function() + { + return this.type == "div" ? + { + height: this.node.offsetHeight, + width: this.node.offsetWidth + } + : + this.getWindowSize(); + }, + + resize: function() + { + var self = this; + + // avoid partial resize when maximizing window + setTimeout(function(){ + self.draw(); + + if (noFixedPosition && (self.type == "frame" || self.type == "div")) + self.fixIEPosition(); + }, 0); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + layout: function(panel) + { + if (FBTrace.DBG_CHROME) FBTrace.sysout("Chrome.layout", ""); + + var options = panel.options; + + changeCommandLineVisibility(options.hasCommandLine); + changeSidePanelVisibility(panel.hasSidePanel); + + Firebug.chrome.draw(); + }, + + showLargeCommandLine: function(hideToggleIcon) + { + var chrome = Firebug.chrome; + + if (!chrome.largeCommandLineVisible) + { + chrome.largeCommandLineVisible = true; + + if (chrome.selectedPanel.options.hasCommandLine) + { + if (Firebug.CommandLine) + Firebug.CommandLine.blur(); + + changeCommandLineVisibility(false); + } + + changeSidePanelVisibility(true); + + fbLargeCommandLine.style.display = "block"; + fbLargeCommandButtons.style.display = "block"; + + fbPanel2Style.display = "none"; + fbPanelBar2BoxStyle.display = "none"; + + chrome.draw(); + + fbLargeCommandLine.focus(); + + if (Firebug.CommandLine) + Firebug.CommandLine.setMultiLine(true); + } + }, + + hideLargeCommandLine: function() + { + if (Firebug.chrome.largeCommandLineVisible) + { + Firebug.chrome.largeCommandLineVisible = false; + + if (Firebug.CommandLine) + Firebug.CommandLine.setMultiLine(false); + + fbLargeCommandLine.blur(); + + fbPanel2Style.display = "block"; + fbPanelBar2BoxStyle.display = "block"; + + fbLargeCommandLine.style.display = "none"; + fbLargeCommandButtons.style.display = "none"; + + changeSidePanelVisibility(false); + + if (Firebug.chrome.selectedPanel.options.hasCommandLine) + changeCommandLineVisibility(true); + + Firebug.chrome.draw(); + + } + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + focusCommandLine: function() + { + var selectedPanelName = this.selectedPanel.name, panelToSelect; + + if (focusCommandLineState == 0 || selectedPanelName != "Console") + { + focusCommandLineState = 0; + lastFocusedPanelName = selectedPanelName; + + panelToSelect = "Console"; + } + if (focusCommandLineState == 1) + { + panelToSelect = lastFocusedPanelName; + } + + this.selectPanel(panelToSelect); + + try + { + if (Firebug.CommandLine) + { + if (panelToSelect == "Console") + Firebug.CommandLine.focus(); + else + Firebug.CommandLine.blur(); + } + } + catch(e) + { + //TODO: xxxpedro trace error + } + + focusCommandLineState = ++focusCommandLineState % 2; + } + +}); + +// ************************************************************************************************ +// ChromeFrameBase + +/** + * @namespace + * @extends ns-chrome-ChromeBase + */ +var ChromeFrameBase = extend(ChromeBase, +/**@extend ns-chrome-ChromeFrameBase*/ +{ + create: function() + { + ChromeBase.create.call(this); + + // restore display for the anti-flicker trick + if (isFirefox) + this.node.style.display = "block"; + + if (Env.Options.startInNewWindow) + { + this.close(); + this.toggle(true, true); + return; + } + + if (Env.Options.startOpened) + this.open(); + else + this.close(); + }, + + destroy: function() + { + var size = Firebug.chrome.getWindowSize(); + + Firebug.context.persistedState.height = size.height; + + if (Firebug.saveCookies) + Firebug.savePrefs(); + + removeGlobalEvent("keydown", onGlobalKeyDown); + + ChromeBase.destroy.call(this); + + this.document = null; + delete this.document; + + this.window = null; + delete this.window; + + this.node.parentNode.removeChild(this.node); + this.node = null; + delete this.node; + }, + + initialize: function() + { + //FBTrace.sysout("Frame", "initialize();") + ChromeBase.initialize.call(this); + + this.addController( + [Firebug.browser.window, "resize", this.resize], + [$("fbWindow_btClose"), "click", this.close], + [$("fbWindow_btDetach"), "click", this.detach], + [$("fbWindow_btDeactivate"), "click", this.deactivate] + ); + + if (!Env.Options.enablePersistent) + this.addController([Firebug.browser.window, "unload", Firebug.shutdown]); + + if (noFixedPosition) + { + this.addController( + [Firebug.browser.window, "scroll", this.fixIEPosition] + ); + } + + fbVSplitter.onmousedown = onVSplitterMouseDown; + fbHSplitter.onmousedown = onHSplitterMouseDown; + + this.isInitialized = true; + }, + + shutdown: function() + { + fbVSplitter.onmousedown = null; + fbHSplitter.onmousedown = null; + + ChromeBase.shutdown.apply(this); + + this.isInitialized = false; + }, + + reattach: function() + { + var frame = FirebugChrome.chromeMap.frame; + + ChromeBase.reattach(FirebugChrome.chromeMap.popup, this); + }, + + open: function() + { + if (!Firebug.context.persistedState.isOpen) + { + Firebug.context.persistedState.isOpen = true; + + if (Env.isChromeExtension) + localStorage.setItem("Firebug", "1,1"); + + var node = this.node; + + node.style.visibility = "hidden"; // Avoid flickering + + if (Firebug.showIconWhenHidden) + { + if (ChromeMini.isInitialized) + { + ChromeMini.shutdown(); + } + + } + else + node.style.display = "block"; + + var main = $("fbChrome"); + + // IE6 throws an error when setting this property! why? + //main.style.display = "table"; + main.style.display = ""; + + var self = this; + /// TODO: xxxpedro FOUC + node.style.visibility = "visible"; + setTimeout(function(){ + ///node.style.visibility = "visible"; + + //dispatch(Firebug.modules, "initialize", []); + self.initialize(); + + if (noFixedPosition) + self.fixIEPosition(); + + self.draw(); + + }, 10); + } + }, + + close: function() + { + if (Firebug.context.persistedState.isOpen) + { + if (this.isInitialized) + { + //dispatch(Firebug.modules, "shutdown", []); + this.shutdown(); + } + + Firebug.context.persistedState.isOpen = false; + + if (Env.isChromeExtension) + localStorage.setItem("Firebug", "1,0"); + + var node = this.node; + + if (Firebug.showIconWhenHidden) + { + node.style.visibility = "hidden"; // Avoid flickering + + // TODO: xxxpedro - persist IE fixed? + var main = $("fbChrome", FirebugChrome.chromeMap.frame.document); + main.style.display = "none"; + + ChromeMini.initialize(); + + node.style.visibility = "visible"; + } + else + node.style.display = "none"; + } + }, + + deactivate: function() + { + // if it is running as a Chrome extension, dispatch a message to the extension signaling + // that Firebug should be deactivated for the current tab + if (Env.isChromeExtension) + { + localStorage.removeItem("Firebug"); + Firebug.GoogleChrome.dispatch("FB_deactivate"); + + // xxxpedro problem here regarding Chrome extension. We can't deactivate the whole + // app, otherwise it won't be able to be reactivated without reloading the page. + // but we need to stop listening global keys, otherwise the key activation won't work. + Firebug.chrome.close(); + } + else + { + Firebug.shutdown(); + } + }, + + fixIEPosition: function() + { + // fix IE problem with offset when not in fullscreen mode + var doc = this.document; + var offset = isIE ? doc.body.clientTop || doc.documentElement.clientTop: 0; + + var size = Firebug.browser.getWindowSize(); + var scroll = Firebug.browser.getWindowScrollPosition(); + var maxHeight = size.height; + var height = this.node.offsetHeight; + + var bodyStyle = doc.body.currentStyle; + + this.node.style.top = maxHeight - height + scroll.top + "px"; + + if ((this.type == "frame" || this.type == "div") && + (bodyStyle.marginLeft || bodyStyle.marginRight)) + { + this.node.style.width = size.width + "px"; + } + + if (fbVSplitterStyle) + fbVSplitterStyle.right = Firebug.context.persistedState.sidePanelWidth + "px"; + + this.draw(); + } + +}); + + +// ************************************************************************************************ +// ChromeMini + +/** + * @namespace + * @extends FBL.Controller + */ +var ChromeMini = extend(Controller, +/**@extend ns-chrome-ChromeMini*/ +{ + create: function(chrome) + { + append(this, chrome); + this.type = "mini"; + }, + + initialize: function() + { + Controller.initialize.apply(this); + + var doc = FirebugChrome.chromeMap.frame.document; + + var mini = $("fbMiniChrome", doc); + mini.style.display = "block"; + + var miniIcon = $("fbMiniIcon", doc); + var width = miniIcon.offsetWidth + 10; + miniIcon.title = "Open " + Firebug.version; + + var errors = $("fbMiniErrors", doc); + if (errors.offsetWidth) + width += errors.offsetWidth + 10; + + var node = this.node; + node.style.height = "27px"; + node.style.width = width + "px"; + node.style.left = ""; + node.style.right = 0; + + if (this.node.nodeName.toLowerCase() == "iframe") + { + node.setAttribute("allowTransparency", "true"); + this.document.body.style.backgroundColor = "transparent"; + } + else + node.style.background = "transparent"; + + if (noFixedPosition) + this.fixIEPosition(); + + this.addController( + [$("fbMiniIcon", doc), "click", onMiniIconClick] + ); + + if (noFixedPosition) + { + this.addController( + [Firebug.browser.window, "scroll", this.fixIEPosition] + ); + } + + this.isInitialized = true; + }, + + shutdown: function() + { + var node = this.node; + node.style.height = Firebug.context.persistedState.height + "px"; + node.style.width = "100%"; + node.style.left = 0; + node.style.right = ""; + + if (this.node.nodeName.toLowerCase() == "iframe") + { + node.setAttribute("allowTransparency", "false"); + this.document.body.style.backgroundColor = "#fff"; + } + else + node.style.background = "#fff"; + + if (noFixedPosition) + this.fixIEPosition(); + + var doc = FirebugChrome.chromeMap.frame.document; + + var mini = $("fbMiniChrome", doc); + mini.style.display = "none"; + + Controller.shutdown.apply(this); + + this.isInitialized = false; + }, + + draw: function() + { + + }, + + fixIEPosition: ChromeFrameBase.fixIEPosition + +}); + + +// ************************************************************************************************ +// ChromePopupBase + +/** + * @namespace + * @extends ns-chrome-ChromeBase + */ +var ChromePopupBase = extend(ChromeBase, +/**@extend ns-chrome-ChromePopupBase*/ +{ + + initialize: function() + { + setClass(this.document.body, "FirebugPopup"); + + ChromeBase.initialize.call(this); + + this.addController( + [Firebug.chrome.window, "resize", this.resize], + [Firebug.chrome.window, "unload", this.destroy] + //[Firebug.chrome.window, "beforeunload", this.destroy] + ); + + if (Env.Options.enablePersistent) + { + this.persist = bind(this.persist, this); + addEvent(Firebug.browser.window, "unload", this.persist); + } + else + this.addController( + [Firebug.browser.window, "unload", this.close] + ); + + fbVSplitter.onmousedown = onVSplitterMouseDown; + }, + + destroy: function() + { + var chromeWin = Firebug.chrome.window; + var left = chromeWin.screenX || chromeWin.screenLeft; + var top = chromeWin.screenY || chromeWin.screenTop; + var size = Firebug.chrome.getWindowSize(); + + Firebug.context.persistedState.popupTop = top; + Firebug.context.persistedState.popupLeft = left; + Firebug.context.persistedState.popupWidth = size.width; + Firebug.context.persistedState.popupHeight = size.height; + + if (Firebug.saveCookies) + Firebug.savePrefs(); + + // TODO: xxxpedro sync detach reattach attach + var frame = FirebugChrome.chromeMap.frame; + + if(frame) + { + dispatch(frame.panelMap, "detach", [this, frame]); + + frame.reattach(this, frame); + } + + if (Env.Options.enablePersistent) + { + removeEvent(Firebug.browser.window, "unload", this.persist); + } + + ChromeBase.destroy.apply(this); + + FirebugChrome.chromeMap.popup = null; + + this.node.close(); + }, + + persist: function() + { + persistTimeStart = new Date().getTime(); + + removeEvent(Firebug.browser.window, "unload", this.persist); + + Firebug.Inspector.destroy(); + Firebug.browser.window.FirebugOldBrowser = true; + + var persistTimeStart = new Date().getTime(); + + var waitMainWindow = function() + { + var doc, head; + + try + { + if (window.opener && !window.opener.FirebugOldBrowser && (doc = window.opener.document)/* && + doc.documentElement && (head = doc.documentElement.firstChild)*/) + { + + try + { + // exposes the FBL to the global namespace when in debug mode + if (Env.isDebugMode) + { + window.FBL = FBL; + } + + window.Firebug = Firebug; + window.opener.Firebug = Firebug; + + Env.browser = window.opener; + Firebug.browser = Firebug.context = new Context(Env.browser); + Firebug.loadPrefs(); + + registerConsole(); + + // the delay time should be calculated right after registering the + // console, once right after the console registration, call log messages + // will be properly handled + var persistDelay = new Date().getTime() - persistTimeStart; + + var chrome = Firebug.chrome; + addEvent(Firebug.browser.window, "unload", chrome.persist); + + FBL.cacheDocument(); + Firebug.Inspector.create(); + + Firebug.Console.logFormatted( + ["Firebug could not capture console calls during " + + persistDelay + "ms"], + Firebug.context, + "info" + ); + + setTimeout(function(){ + var htmlPanel = chrome.getPanel("HTML"); + htmlPanel.createUI(); + },50); + + } + catch(pE) + { + alert("persist error: " + (pE.message || pE)); + } + + } + else + { + window.setTimeout(waitMainWindow, 0); + } + + } catch (E) { + window.close(); + } + }; + + waitMainWindow(); + }, + + close: function() + { + this.destroy(); + } + +}); + + +//************************************************************************************************ +// UI helpers + +var changeCommandLineVisibility = function changeCommandLineVisibility(visibility) +{ + var last = Firebug.chrome.commandLineVisible; + var visible = Firebug.chrome.commandLineVisible = + typeof visibility == "boolean" ? visibility : !Firebug.chrome.commandLineVisible; + + if (visible != last) + { + if (visible) + { + fbBottom.className = ""; + + if (Firebug.CommandLine) + Firebug.CommandLine.activate(); + } + else + { + if (Firebug.CommandLine) + Firebug.CommandLine.deactivate(); + + fbBottom.className = "hide"; + } + } +}; + +var changeSidePanelVisibility = function changeSidePanelVisibility(visibility) +{ + var last = Firebug.chrome.sidePanelVisible; + Firebug.chrome.sidePanelVisible = + typeof visibility == "boolean" ? visibility : !Firebug.chrome.sidePanelVisible; + + if (Firebug.chrome.sidePanelVisible != last) + { + fbPanelBox2.className = Firebug.chrome.sidePanelVisible ? "" : "hide"; + fbPanelBar2Box.className = Firebug.chrome.sidePanelVisible ? "" : "hide"; + } +}; + + +// ************************************************************************************************ +// F12 Handler + +var onGlobalKeyDown = function onGlobalKeyDown(event) +{ + var keyCode = event.keyCode; + var shiftKey = event.shiftKey; + var ctrlKey = event.ctrlKey; + + if (keyCode == 123 /* F12 */ && (!isFirefox && !shiftKey || shiftKey && isFirefox)) + { + Firebug.chrome.toggle(false, ctrlKey); + cancelEvent(event, true); + + // TODO: xxxpedro replace with a better solution. we're doing this + // to allow reactivating with the F12 key after being deactivated + if (Env.isChromeExtension) + { + Firebug.GoogleChrome.dispatch("FB_enableIcon"); + } + } + else if (keyCode == 67 /* C */ && ctrlKey && shiftKey) + { + Firebug.Inspector.toggleInspect(); + cancelEvent(event, true); + } + else if (keyCode == 76 /* L */ && ctrlKey && shiftKey) + { + Firebug.chrome.focusCommandLine(); + cancelEvent(event, true); + } +}; + +var onMiniIconClick = function onMiniIconClick(event) +{ + Firebug.chrome.toggle(false, event.ctrlKey); + cancelEvent(event, true); +}; + + +// ************************************************************************************************ +// Horizontal Splitter Handling + +var onHSplitterMouseDown = function onHSplitterMouseDown(event) +{ + addGlobalEvent("mousemove", onHSplitterMouseMove); + addGlobalEvent("mouseup", onHSplitterMouseUp); + + if (isIE) + addEvent(Firebug.browser.document.documentElement, "mouseleave", onHSplitterMouseUp); + + fbHSplitter.className = "fbOnMovingHSplitter"; + + return false; +}; + +var onHSplitterMouseMove = function onHSplitterMouseMove(event) +{ + cancelEvent(event, true); + + var clientY = event.clientY; + var win = isIE + ? event.srcElement.ownerDocument.parentWindow + : event.target.defaultView || event.target.ownerDocument && event.target.ownerDocument.defaultView; + + if (!win) + return; + + if (win != win.parent) + { + var frameElement = win.frameElement; + if (frameElement) + { + var framePos = Firebug.browser.getElementPosition(frameElement).top; + clientY += framePos; + + if (frameElement.style.position != "fixed") + clientY -= Firebug.browser.getWindowScrollPosition().top; + } + } + + if (isOpera && isQuiksMode && win.frameElement.id == "FirebugUI") + { + clientY = Firebug.browser.getWindowSize().height - win.frameElement.offsetHeight + clientY; + } + + /* + console.log( + typeof win.FBL != "undefined" ? "no-Chrome" : "Chrome", + //win.frameElement.id, + event.target, + clientY + );/**/ + + onHSplitterMouseMoveBuffer = clientY; // buffer + + if (new Date().getTime() - lastHSplitterMouseMove > chromeRedrawSkipRate) // frame skipping + { + lastHSplitterMouseMove = new Date().getTime(); + handleHSplitterMouseMove(); + } + else + if (!onHSplitterMouseMoveTimer) + onHSplitterMouseMoveTimer = setTimeout(handleHSplitterMouseMove, chromeRedrawSkipRate); + + // improving the resizing performance by canceling the mouse event. + // canceling events will prevent the page to receive such events, which would imply + // in more processing being expended. + cancelEvent(event, true); + return false; +}; + +var handleHSplitterMouseMove = function() +{ + if (onHSplitterMouseMoveTimer) + { + clearTimeout(onHSplitterMouseMoveTimer); + onHSplitterMouseMoveTimer = null; + } + + var clientY = onHSplitterMouseMoveBuffer; + + var windowSize = Firebug.browser.getWindowSize(); + var scrollSize = Firebug.browser.getWindowScrollSize(); + + // compute chrome fixed size (top bar and command line) + var commandLineHeight = Firebug.chrome.commandLineVisible ? fbCommandLine.offsetHeight : 0; + var fixedHeight = topHeight + commandLineHeight; + var chromeNode = Firebug.chrome.node; + + var scrollbarSize = !isIE && (scrollSize.width > windowSize.width) ? 17 : 0; + + //var height = !isOpera ? chromeNode.offsetTop + chromeNode.clientHeight : windowSize.height; + var height = windowSize.height; + + // compute the min and max size of the chrome + var chromeHeight = Math.max(height - clientY + 5 - scrollbarSize, fixedHeight); + chromeHeight = Math.min(chromeHeight, windowSize.height - scrollbarSize); + + Firebug.context.persistedState.height = chromeHeight; + chromeNode.style.height = chromeHeight + "px"; + + if (noFixedPosition) + Firebug.chrome.fixIEPosition(); + + Firebug.chrome.draw(); +}; + +var onHSplitterMouseUp = function onHSplitterMouseUp(event) +{ + removeGlobalEvent("mousemove", onHSplitterMouseMove); + removeGlobalEvent("mouseup", onHSplitterMouseUp); + + if (isIE) + removeEvent(Firebug.browser.document.documentElement, "mouseleave", onHSplitterMouseUp); + + fbHSplitter.className = ""; + + Firebug.chrome.draw(); + + // avoid text selection in IE when returning to the document + // after the mouse leaves the document during the resizing + return false; +}; + + +// ************************************************************************************************ +// Vertical Splitter Handling + +var onVSplitterMouseDown = function onVSplitterMouseDown(event) +{ + addGlobalEvent("mousemove", onVSplitterMouseMove); + addGlobalEvent("mouseup", onVSplitterMouseUp); + + return false; +}; + +var onVSplitterMouseMove = function onVSplitterMouseMove(event) +{ + if (new Date().getTime() - lastVSplitterMouseMove > chromeRedrawSkipRate) // frame skipping + { + var target = event.target || event.srcElement; + if (target && target.ownerDocument) // avoid error when cursor reaches out of the chrome + { + var clientX = event.clientX; + var win = document.all + ? event.srcElement.ownerDocument.parentWindow + : event.target.ownerDocument.defaultView; + + if (win != win.parent) + clientX += win.frameElement ? win.frameElement.offsetLeft : 0; + + var size = Firebug.chrome.getSize(); + var x = Math.max(size.width - clientX + 3, 6); + + Firebug.context.persistedState.sidePanelWidth = x; + Firebug.chrome.draw(); + } + + lastVSplitterMouseMove = new Date().getTime(); + } + + cancelEvent(event, true); + return false; +}; + +var onVSplitterMouseUp = function onVSplitterMouseUp(event) +{ + removeGlobalEvent("mousemove", onVSplitterMouseMove); + removeGlobalEvent("mouseup", onVSplitterMouseUp); + + Firebug.chrome.draw(); +}; + + +// ************************************************************************************************ +}}); + +/* See license.txt for terms of usage */ + +FBL.ns(function() { with (FBL) { +// ************************************************************************************************ + +Firebug.Lite = +{ +}; + +// ************************************************************************************************ +}}); + + +/* See license.txt for terms of usage */ + +FBL.ns(function() { with (FBL) { +// ************************************************************************************************ + +Firebug.Lite.Cache = +{ + ID: "firebug-" + new Date().getTime() +}; + +// ************************************************************************************************ + +/** + * TODO: if a cached element is cloned, the expando property will be cloned too in IE + * which will result in a bug. Firebug Lite will think the new cloned node is the old + * one. + * + * TODO: Investigate a possibility of cache validation, to be customized by each + * kind of cache. For ElementCache it should validate if the element still is + * inserted at the DOM. + */ +var cacheUID = 0; +var createCache = function() +{ + var map = {}; + var data = {}; + + var CID = Firebug.Lite.Cache.ID; + + // better detection + var supportsDeleteExpando = !document.all; + + var cacheFunction = function(element) + { + return cacheAPI.set(element); + }; + + var cacheAPI = + { + get: function(key) + { + return map.hasOwnProperty(key) ? + map[key] : + null; + }, + + set: function(element) + { + var id = getValidatedKey(element); + + if (!id) + { + id = ++cacheUID; + element[CID] = id; + } + + if (!map.hasOwnProperty(id)) + { + map[id] = element; + data[id] = {}; + } + + return id; + }, + + unset: function(element) + { + var id = getValidatedKey(element); + + if (!id) return; + + if (supportsDeleteExpando) + { + delete element[CID]; + } + else if (element.removeAttribute) + { + element.removeAttribute(CID); + } + + delete map[id]; + delete data[id]; + + }, + + key: function(element) + { + return getValidatedKey(element); + }, + + has: function(element) + { + var id = getValidatedKey(element); + return id && map.hasOwnProperty(id); + }, + + each: function(callback) + { + for (var key in map) + { + if (map.hasOwnProperty(key)) + { + callback(key, map[key]); + } + } + }, + + data: function(element, name, value) + { + // set data + if (value) + { + if (!name) return null; + + var id = cacheAPI.set(element); + + return data[id][name] = value; + } + // get data + else + { + var id = cacheAPI.key(element); + + return data.hasOwnProperty(id) && data[id].hasOwnProperty(name) ? + data[id][name] : + null; + } + }, + + clear: function() + { + for (var id in map) + { + var element = map[id]; + cacheAPI.unset(element); + } + } + }; + + var getValidatedKey = function(element) + { + var id = element[CID]; + + // If a cached element is cloned in IE, the expando property CID will be also + // cloned (differently than other browsers) resulting in a bug: Firebug Lite + // will think the new cloned node is the old one. To prevent this problem we're + // checking if the cached element matches the given element. + if ( + !supportsDeleteExpando && // the problem happens when supportsDeleteExpando is false + id && // the element has the expando property + map.hasOwnProperty(id) && // there is a cached element with the same id + map[id] != element // but it is a different element than the current one + ) + { + // remove the problematic property + element.removeAttribute(CID); + + id = null; + } + + return id; + }; + + FBL.append(cacheFunction, cacheAPI); + + return cacheFunction; +}; + +// ************************************************************************************************ + +// TODO: xxxpedro : check if we need really this on FBL scope +Firebug.Lite.Cache.StyleSheet = createCache(); +Firebug.Lite.Cache.Element = createCache(); + +// TODO: xxxpedro +Firebug.Lite.Cache.Event = createCache(); + + +// ************************************************************************************************ +}}); + + +/* See license.txt for terms of usage */ + +FBL.ns(function() { with (FBL) { +// ************************************************************************************************ + +// ************************************************************************************************ +var sourceMap = {}; + +// ************************************************************************************************ +Firebug.Lite.Proxy = +{ + // jsonp callbacks + _callbacks: {}, + + /** + * Load a resource, either locally (directly) or externally (via proxy) using + * synchronous XHR calls. Loading external resources requires the proxy plugin to + * be installed and configured (see /plugin/proxy/proxy.php). + */ + load: function(url) + { + var resourceDomain = getDomain(url); + var isLocalResource = + // empty domain means local URL + !resourceDomain || + // same domain means local too + resourceDomain == Firebug.context.window.location.host; // TODO: xxxpedro context + + return isLocalResource ? fetchResource(url) : fetchProxyResource(url); + }, + + /** + * Load a resource using JSONP technique. + */ + loadJSONP: function(url, callback) + { + var script = createGlobalElement("script"), + doc = Firebug.context.document, + + uid = "" + new Date().getTime(), + callbackName = "callback=Firebug.Lite.Proxy._callbacks." + uid, + + jsonpURL = url.indexOf("?") != -1 ? + url + "&" + callbackName : + url + "?" + callbackName; + + Firebug.Lite.Proxy._callbacks[uid] = function(data) + { + if (callback) + callback(data); + + script.parentNode.removeChild(script); + delete Firebug.Lite.Proxy._callbacks[uid]; + }; + + script.src = jsonpURL; + + if (doc.documentElement) + doc.documentElement.appendChild(script); + }, + + /** + * Load a resource using YQL (not reliable). + */ + YQL: function(url, callback) + { + var yql = "http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20html%20where%20url%3D%22" + + encodeURIComponent(url) + "%22&format=xml"; + + this.loadJSONP(yql, function(data) + { + var source = data.results[0]; + + // clean up YQL bogus elements + var match = /<body>\s+<p>([\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 = "<a name='" + id + "'/>"; + + // 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 = "<a href='#'></a>"; + 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 = "<p class='TEST'></p>"; + + // 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 && context.nodeType === 9 && !isXML(context) ) { + try { + return makeArray( context.querySelectorAll(query), extra ); + } catch(e){} + } + + return oldSizzle(query, context, extra, seed); + }; + + for ( var prop in oldSizzle ) { + Sizzle[ prop ] = oldSizzle[ prop ]; + } + + div = null; // release memory in IE +})(); + +if ( document.getElementsByClassName && document.documentElement.getElementsByClassName ) (function(){ + var div = document.createElement("div"); + div.innerHTML = "<div class='test e'></div><div class='test'></div>"; + + // Opera can't find a second classname (in 9.6) + if ( 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]); + } + }; + + div = null; // release memory in IE +})(); + +function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + var sibDir = dir == "previousSibling" && !isXML; + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + if ( sibDir && elem.nodeType === 1 ){ + elem.sizcache = doneName; + elem.sizset = i; + } + elem = elem[dir]; + var match = false; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 && !isXML ){ + elem.sizcache = doneName; + elem.sizset = i; + } + + if ( elem.nodeName === cur ) { + match = elem; + break; + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + var sibDir = dir == "previousSibling" && !isXML; + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + if ( sibDir && elem.nodeType === 1 ) { + elem.sizcache = doneName; + elem.sizset = i; + } + elem = elem[dir]; + var match = false; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 ) { + if ( !isXML ) { + elem.sizcache = 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; + } + } +} + +var contains = document.compareDocumentPosition ? function(a, b){ + return a.compareDocumentPosition(b) & 16; +} : function(a, b){ + return a !== b && (a.contains ? a.contains(b) : true); +}; + +var isXML = function(elem){ + return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" || + !!elem.ownerDocument && elem.ownerDocument.documentElement.nodeName !== "HTML"; +}; + +var posProcess = function(selector, context){ + var tmpSet = [], later = "", match, + 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 ); + } + + return Sizzle.filter( later, tmpSet ); +}; + +// EXPOSE + +Firebug.Selector = Sizzle; + +/**#@-*/ + +// ************************************************************************************************ +}}); + +/* See license.txt for terms of usage */ + +FBL.ns(function() { with (FBL) { +// ************************************************************************************************ + +// ************************************************************************************************ +// Inspector Module + +var ElementCache = Firebug.Lite.Cache.Element; + +var inspectorTS, inspectorTimer, isInspecting; + +Firebug.Inspector = +{ + create: function() + { + offlineFragment = Env.browser.document.createDocumentFragment(); + + createBoxModelInspector(); + createOutlineInspector(); + }, + + destroy: function() + { + destroyBoxModelInspector(); + destroyOutlineInspector(); + + offlineFragment = null; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Inspect functions + + toggleInspect: function() + { + if (isInspecting) + { + this.stopInspecting(); + } + else + { + Firebug.chrome.inspectButton.changeState("pressed"); + this.startInspecting(); + } + }, + + startInspecting: function() + { + isInspecting = true; + + Firebug.chrome.selectPanel("HTML"); + + createInspectorFrame(); + + var size = Firebug.browser.getWindowScrollSize(); + + fbInspectFrame.style.width = size.width + "px"; + fbInspectFrame.style.height = size.height + "px"; + + //addEvent(Firebug.browser.document.documentElement, "mousemove", Firebug.Inspector.onInspectingBody); + + addEvent(fbInspectFrame, "mousemove", Firebug.Inspector.onInspecting); + addEvent(fbInspectFrame, "mousedown", Firebug.Inspector.onInspectingClick); + }, + + stopInspecting: function() + { + isInspecting = false; + + if (outlineVisible) this.hideOutline(); + removeEvent(fbInspectFrame, "mousemove", Firebug.Inspector.onInspecting); + removeEvent(fbInspectFrame, "mousedown", Firebug.Inspector.onInspectingClick); + + destroyInspectorFrame(); + + Firebug.chrome.inspectButton.restore(); + + if (Firebug.chrome.type == "popup") + Firebug.chrome.node.focus(); + }, + + onInspectingClick: function(e) + { + fbInspectFrame.style.display = "none"; + var targ = Firebug.browser.getElementFromPoint(e.clientX, e.clientY); + fbInspectFrame.style.display = "block"; + + // Avoid inspecting the outline, and the FirebugUI + var id = targ.id; + if (id && /^fbOutline\w$/.test(id)) return; + if (id == "FirebugUI") return; + + // Avoid looking at text nodes in Opera + while (targ.nodeType != 1) targ = targ.parentNode; + + //Firebug.Console.log(targ); + Firebug.Inspector.stopInspecting(); + }, + + onInspecting: function(e) + { + if (new Date().getTime() - lastInspecting > 30) + { + fbInspectFrame.style.display = "none"; + var targ = Firebug.browser.getElementFromPoint(e.clientX, e.clientY); + fbInspectFrame.style.display = "block"; + + // Avoid inspecting the outline, and the FirebugUI + var id = targ.id; + if (id && /^fbOutline\w$/.test(id)) return; + if (id == "FirebugUI") return; + + // Avoid looking at text nodes in Opera + while (targ.nodeType != 1) targ = targ.parentNode; + + if (targ.nodeName.toLowerCase() == "body") return; + + //Firebug.Console.log(e.clientX, e.clientY, targ); + Firebug.Inspector.drawOutline(targ); + + if (ElementCache(targ)) + { + var target = ""+ElementCache.key(targ); + var lazySelect = function() + { + inspectorTS = new Date().getTime(); + + if (Firebug.HTML) + Firebug.HTML.selectTreeNode(""+ElementCache.key(targ)); + }; + + if (inspectorTimer) + { + clearTimeout(inspectorTimer); + inspectorTimer = null; + } + + if (new Date().getTime() - inspectorTS > 200) + setTimeout(lazySelect, 0); + else + inspectorTimer = setTimeout(lazySelect, 300); + } + + lastInspecting = new Date().getTime(); + } + }, + + // TODO: xxxpedro remove this? + onInspectingBody: function(e) + { + if (new Date().getTime() - lastInspecting > 30) + { + var targ = e.target; + + // Avoid inspecting the outline, and the FirebugUI + var id = targ.id; + if (id && /^fbOutline\w$/.test(id)) return; + if (id == "FirebugUI") return; + + // Avoid looking at text nodes in Opera + while (targ.nodeType != 1) targ = targ.parentNode; + + if (targ.nodeName.toLowerCase() == "body") return; + + //Firebug.Console.log(e.clientX, e.clientY, targ); + Firebug.Inspector.drawOutline(targ); + + if (ElementCache.has(targ)) + FBL.Firebug.HTML.selectTreeNode(""+ElementCache.key(targ)); + + lastInspecting = new Date().getTime(); + } + }, + + /** + * + * llttttttrr + * llttttttrr + * ll rr + * ll rr + * llbbbbbbrr + * llbbbbbbrr + */ + drawOutline: function(el) + { + var border = 2; + var scrollbarSize = 17; + + var windowSize = Firebug.browser.getWindowSize(); + var scrollSize = Firebug.browser.getWindowScrollSize(); + var scrollPosition = Firebug.browser.getWindowScrollPosition(); + + var box = Firebug.browser.getElementBox(el); + + var top = box.top; + var left = box.left; + var height = box.height; + var width = box.width; + + var freeHorizontalSpace = scrollPosition.left + windowSize.width - left - width - + (!isIE && scrollSize.height > windowSize.height ? // is *vertical* scrollbar visible + scrollbarSize : 0); + + var freeVerticalSpace = scrollPosition.top + windowSize.height - top - height - + (!isIE && scrollSize.width > windowSize.width ? // is *horizontal* scrollbar visible + scrollbarSize : 0); + + var numVerticalBorders = freeVerticalSpace > 0 ? 2 : 1; + + var o = outlineElements; + var style; + + style = o.fbOutlineT.style; + style.top = top-border + "px"; + style.left = left + "px"; + style.height = border + "px"; // TODO: on initialize() + style.width = width + "px"; + + style = o.fbOutlineL.style; + style.top = top-border + "px"; + style.left = left-border + "px"; + style.height = height+ numVerticalBorders*border + "px"; + style.width = border + "px"; // TODO: on initialize() + + style = o.fbOutlineB.style; + if (freeVerticalSpace > 0) + { + style.top = top+height + "px"; + style.left = left + "px"; + style.width = width + "px"; + //style.height = border + "px"; // TODO: on initialize() or worst case? + } + else + { + style.top = -2*border + "px"; + style.left = -2*border + "px"; + style.width = border + "px"; + //style.height = border + "px"; + } + + style = o.fbOutlineR.style; + if (freeHorizontalSpace > 0) + { + style.top = top-border + "px"; + style.left = left+width + "px"; + style.height = height + numVerticalBorders*border + "px"; + style.width = (freeHorizontalSpace < border ? freeHorizontalSpace : border) + "px"; + } + else + { + style.top = -2*border + "px"; + style.left = -2*border + "px"; + style.height = border + "px"; + style.width = border + "px"; + } + + if (!outlineVisible) this.showOutline(); + }, + + hideOutline: function() + { + if (!outlineVisible) return; + + for (var name in outline) + offlineFragment.appendChild(outlineElements[name]); + + outlineVisible = false; + }, + + showOutline: function() + { + if (outlineVisible) return; + + if (boxModelVisible) this.hideBoxModel(); + + for (var name in outline) + Firebug.browser.document.getElementsByTagName("body")[0].appendChild(outlineElements[name]); + + outlineVisible = true; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Box Model + + drawBoxModel: function(el) + { + // avoid error when the element is not attached a document + if (!el || !el.parentNode) + return; + + var box = Firebug.browser.getElementBox(el); + + var windowSize = Firebug.browser.getWindowSize(); + var scrollPosition = Firebug.browser.getWindowScrollPosition(); + + // element may be occluded by the chrome, when in frame mode + var offsetHeight = Firebug.chrome.type == "frame" ? Firebug.context.persistedState.height : 0; + + // if element box is not inside the viewport, don't draw the box model + if (box.top > scrollPosition.top + windowSize.height - offsetHeight || + box.left > scrollPosition.left + windowSize.width || + scrollPosition.top > box.top + box.height || + scrollPosition.left > box.left + box.width ) + return; + + var top = box.top; + var left = box.left; + var height = box.height; + var width = box.width; + + var margin = Firebug.browser.getMeasurementBox(el, "margin"); + var padding = Firebug.browser.getMeasurementBox(el, "padding"); + var border = Firebug.browser.getMeasurementBox(el, "border"); + + boxModelStyle.top = top - margin.top + "px"; + boxModelStyle.left = left - margin.left + "px"; + boxModelStyle.height = height + margin.top + margin.bottom + "px"; + boxModelStyle.width = width + margin.left + margin.right + "px"; + + boxBorderStyle.top = margin.top + "px"; + boxBorderStyle.left = margin.left + "px"; + boxBorderStyle.height = height + "px"; + boxBorderStyle.width = width + "px"; + + boxPaddingStyle.top = margin.top + border.top + "px"; + boxPaddingStyle.left = margin.left + border.left + "px"; + boxPaddingStyle.height = height - border.top - border.bottom + "px"; + boxPaddingStyle.width = width - border.left - border.right + "px"; + + boxContentStyle.top = margin.top + border.top + padding.top + "px"; + boxContentStyle.left = margin.left + border.left + padding.left + "px"; + boxContentStyle.height = height - border.top - padding.top - padding.bottom - border.bottom + "px"; + boxContentStyle.width = width - border.left - padding.left - padding.right - border.right + "px"; + + if (!boxModelVisible) this.showBoxModel(); + }, + + hideBoxModel: function() + { + if (!boxModelVisible) return; + + offlineFragment.appendChild(boxModel); + boxModelVisible = false; + }, + + showBoxModel: function() + { + if (boxModelVisible) return; + + if (outlineVisible) this.hideOutline(); + + Firebug.browser.document.getElementsByTagName("body")[0].appendChild(boxModel); + boxModelVisible = true; + } + +}; + +// ************************************************************************************************ +// Inspector Internals + + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// Shared variables + + + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// Internal variables + +var offlineFragment = null; + +var boxModelVisible = false; + +var boxModel, boxModelStyle, + boxMargin, boxMarginStyle, + boxBorder, boxBorderStyle, + boxPadding, boxPaddingStyle, + boxContent, boxContentStyle; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +var resetStyle = "margin:0; padding:0; border:0; position:absolute; overflow:hidden; display:block;"; +var offscreenStyle = resetStyle + "top:-1234px; left:-1234px;"; + +var inspectStyle = resetStyle + "z-index: 2147483500;"; +var inspectFrameStyle = resetStyle + "z-index: 2147483550; top:0; left:0; background:url(" + + Env.Location.skinDir + "pixel_transparent.gif);"; + +//if (Env.Options.enableTrace) inspectFrameStyle = resetStyle + "z-index: 2147483550; top: 0; left: 0; background: #ff0; opacity: 0.05; _filter: alpha(opacity=5);"; + +var inspectModelOpacity = isIE ? "filter:alpha(opacity=80);" : "opacity:0.8;"; +var inspectModelStyle = inspectStyle + inspectModelOpacity; +var inspectMarginStyle = inspectStyle + "background: #EDFF64; height:100%; width:100%;"; +var inspectBorderStyle = inspectStyle + "background: #666;"; +var inspectPaddingStyle = inspectStyle + "background: SlateBlue;"; +var inspectContentStyle = inspectStyle + "background: SkyBlue;"; + + +var outlineStyle = { + fbHorizontalLine: "background: #3875D7;height: 2px;", + fbVerticalLine: "background: #3875D7;width: 2px;" +}; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +var lastInspecting = 0; +var fbInspectFrame = null; + + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +var outlineVisible = false; +var outlineElements = {}; +var outline = { + "fbOutlineT": "fbHorizontalLine", + "fbOutlineL": "fbVerticalLine", + "fbOutlineB": "fbHorizontalLine", + "fbOutlineR": "fbVerticalLine" +}; + + +var getInspectingTarget = function() +{ + +}; + +// ************************************************************************************************ +// Section + +var createInspectorFrame = function createInspectorFrame() +{ + fbInspectFrame = createGlobalElement("div"); + fbInspectFrame.id = "fbInspectFrame"; + fbInspectFrame.firebugIgnore = true; + fbInspectFrame.style.cssText = inspectFrameStyle; + Firebug.browser.document.getElementsByTagName("body")[0].appendChild(fbInspectFrame); +}; + +var destroyInspectorFrame = function destroyInspectorFrame() +{ + if (fbInspectFrame) + { + Firebug.browser.document.getElementsByTagName("body")[0].removeChild(fbInspectFrame); + fbInspectFrame = null; + } +}; + +var createOutlineInspector = function createOutlineInspector() +{ + for (var name in outline) + { + var el = outlineElements[name] = createGlobalElement("div"); + el.id = name; + el.firebugIgnore = true; + el.style.cssText = inspectStyle + outlineStyle[outline[name]]; + offlineFragment.appendChild(el); + } +}; + +var destroyOutlineInspector = function destroyOutlineInspector() +{ + for (var name in outline) + { + var el = outlineElements[name]; + el.parentNode.removeChild(el); + } +}; + +var createBoxModelInspector = function createBoxModelInspector() +{ + boxModel = createGlobalElement("div"); + boxModel.id = "fbBoxModel"; + boxModel.firebugIgnore = true; + boxModelStyle = boxModel.style; + boxModelStyle.cssText = inspectModelStyle; + + boxMargin = createGlobalElement("div"); + boxMargin.id = "fbBoxMargin"; + boxMarginStyle = boxMargin.style; + boxMarginStyle.cssText = inspectMarginStyle; + boxModel.appendChild(boxMargin); + + boxBorder = createGlobalElement("div"); + boxBorder.id = "fbBoxBorder"; + boxBorderStyle = boxBorder.style; + boxBorderStyle.cssText = inspectBorderStyle; + boxModel.appendChild(boxBorder); + + boxPadding = createGlobalElement("div"); + boxPadding.id = "fbBoxPadding"; + boxPaddingStyle = boxPadding.style; + boxPaddingStyle.cssText = inspectPaddingStyle; + boxModel.appendChild(boxPadding); + + boxContent = createGlobalElement("div"); + boxContent.id = "fbBoxContent"; + boxContentStyle = boxContent.style; + boxContentStyle.cssText = inspectContentStyle; + boxModel.appendChild(boxContent); + + offlineFragment.appendChild(boxModel); +}; + +var destroyBoxModelInspector = function destroyBoxModelInspector() +{ + boxModel.parentNode.removeChild(boxModel); +}; + +// ************************************************************************************************ +// Section + + + + +// ************************************************************************************************ +}}); + +// Problems in IE +// FIXED - eval return +// FIXED - addEventListener problem in IE +// FIXED doc.createRange? +// +// class reserved word +// test all honza examples in IE6 and IE7 + + +/* See license.txt for terms of usage */ + +( /** @scope s_domplate */ function() { + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +/** @class */ +FBL.DomplateTag = function DomplateTag(tagName) +{ + this.tagName = tagName; +}; + +/** + * @class + * @extends FBL.DomplateTag + */ +FBL.DomplateEmbed = function DomplateEmbed() +{ +}; + +/** + * @class + * @extends FBL.DomplateTag + */ +FBL.DomplateLoop = function DomplateLoop() +{ +}; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +var DomplateTag = FBL.DomplateTag; +var DomplateEmbed = FBL.DomplateEmbed; +var DomplateLoop = FBL.DomplateLoop; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +var womb = null; + +FBL.domplate = function() +{ + var lastSubject; + for (var i = 0; i < arguments.length; ++i) + lastSubject = lastSubject ? copyObject(lastSubject, arguments[i]) : arguments[i]; + + for (var name in lastSubject) + { + var val = lastSubject[name]; + if (isTag(val)) + val.tag.subject = lastSubject; + } + + return lastSubject; +}; + +var domplate = FBL.domplate; + +FBL.domplate.context = function(context, fn) +{ + var lastContext = domplate.lastContext; + domplate.topContext = context; + fn.apply(context); + domplate.topContext = lastContext; +}; + +FBL.TAG = function() +{ + var embed = new DomplateEmbed(); + return embed.merge(arguments); +}; + +FBL.FOR = function() +{ + var loop = new DomplateLoop(); + return loop.merge(arguments); +}; + +FBL.DomplateTag.prototype = +{ + merge: function(args, oldTag) + { + if (oldTag) + this.tagName = oldTag.tagName; + + this.context = oldTag ? oldTag.context : null; + this.subject = oldTag ? oldTag.subject : null; + this.attrs = oldTag ? copyObject(oldTag.attrs) : {}; + this.classes = oldTag ? copyObject(oldTag.classes) : {}; + this.props = oldTag ? copyObject(oldTag.props) : null; + this.listeners = oldTag ? copyArray(oldTag.listeners) : null; + this.children = oldTag ? copyArray(oldTag.children) : []; + this.vars = oldTag ? copyArray(oldTag.vars) : []; + + var attrs = args.length ? args[0] : null; + var hasAttrs = typeof(attrs) == "object" && !isTag(attrs); + + this.children = []; + + if (domplate.topContext) + this.context = domplate.topContext; + + if (args.length) + parseChildren(args, hasAttrs ? 1 : 0, this.vars, this.children); + + if (hasAttrs) + this.parseAttrs(attrs); + + return creator(this, DomplateTag); + }, + + parseAttrs: function(args) + { + for (var name in args) + { + var val = parseValue(args[name]); + readPartNames(val, this.vars); + + if (name.indexOf("on") == 0) + { + var eventName = name.substr(2); + if (!this.listeners) + this.listeners = []; + this.listeners.push(eventName, val); + } + else if (name.indexOf("_") == 0) + { + var propName = name.substr(1); + if (!this.props) + this.props = {}; + this.props[propName] = val; + } + else if (name.indexOf("$") == 0) + { + var className = name.substr(1); + if (!this.classes) + this.classes = {}; + this.classes[className] = val; + } + else + { + if (name == "class" && this.attrs.hasOwnProperty(name) ) + this.attrs[name] += " " + val; + else + this.attrs[name] = val; + } + } + }, + + compile: function() + { + if (this.renderMarkup) + return; + + this.compileMarkup(); + this.compileDOM(); + + //if (FBTrace.DBG_DOM) FBTrace.sysout("domplate renderMarkup: ", this.renderMarkup); + //if (FBTrace.DBG_DOM) FBTrace.sysout("domplate renderDOM:", this.renderDOM); + //if (FBTrace.DBG_DOM) FBTrace.sysout("domplate domArgs:", this.domArgs); + }, + + compileMarkup: function() + { + this.markupArgs = []; + var topBlock = [], topOuts = [], blocks = [], info = {args: this.markupArgs, argIndex: 0}; + + this.generateMarkup(topBlock, topOuts, blocks, info); + this.addCode(topBlock, topOuts, blocks); + + var fnBlock = ['r=(function (__code__, __context__, __in__, __out__']; + for (var i = 0; i < info.argIndex; ++i) + fnBlock.push(', s', i); + fnBlock.push(') {'); + + if (this.subject) + fnBlock.push('with (this) {'); + if (this.context) + fnBlock.push('with (__context__) {'); + fnBlock.push('with (__in__) {'); + + fnBlock.push.apply(fnBlock, blocks); + + if (this.subject) + fnBlock.push('}'); + if (this.context) + fnBlock.push('}'); + + fnBlock.push('}})'); + + function __link__(tag, code, outputs, args) + { + if (!tag || !tag.tag) + return; + + tag.tag.compile(); + + var tagOutputs = []; + var markupArgs = [code, tag.tag.context, args, tagOutputs]; + markupArgs.push.apply(markupArgs, tag.tag.markupArgs); + tag.tag.renderMarkup.apply(tag.tag.subject, markupArgs); + + outputs.push(tag); + outputs.push(tagOutputs); + } + + function __escape__(value) + { + function replaceChars(ch) + { + switch (ch) + { + case "<": + return "<"; + case ">": + return ">"; + case "&": + return "&"; + case "'": + return "'"; + case '"': + return """; + } + return "?"; + }; + return String(value).replace(/[<>&"']/g, replaceChars); + } + + function __loop__(iter, outputs, fn) + { + var iterOuts = []; + outputs.push(iterOuts); + + if (iter instanceof Array) + iter = new ArrayIterator(iter); + + try + { + while (1) + { + var value = iter.next(); + var itemOuts = [0,0]; + iterOuts.push(itemOuts); + fn.apply(this, [value, itemOuts]); + } + } + catch (exc) + { + if (exc != StopIteration) + throw exc; + } + } + + var js = fnBlock.join(""); + var r = null; + eval(js); + this.renderMarkup = r; + }, + + getVarNames: function(args) + { + if (this.vars) + args.push.apply(args, this.vars); + + for (var i = 0; i < this.children.length; ++i) + { + var child = this.children[i]; + if (isTag(child)) + child.tag.getVarNames(args); + else if (child instanceof Parts) + { + for (var i = 0; i < child.parts.length; ++i) + { + if (child.parts[i] instanceof Variable) + { + var name = child.parts[i].name; + var names = name.split("."); + args.push(names[0]); + } + } + } + } + }, + + generateMarkup: function(topBlock, topOuts, blocks, info) + { + topBlock.push(',"<', this.tagName, '"'); + + for (var name in this.attrs) + { + if (name != "class") + { + var val = this.attrs[name]; + topBlock.push(', " ', name, '=\\""'); + addParts(val, ',', topBlock, info, true); + topBlock.push(', "\\""'); + } + } + + if (this.listeners) + { + for (var i = 0; i < this.listeners.length; i += 2) + readPartNames(this.listeners[i+1], topOuts); + } + + if (this.props) + { + for (var name in this.props) + readPartNames(this.props[name], topOuts); + } + + if ( this.attrs.hasOwnProperty("class") || this.classes) + { + topBlock.push(', " class=\\""'); + if (this.attrs.hasOwnProperty("class")) + addParts(this.attrs["class"], ',', topBlock, info, true); + topBlock.push(', " "'); + for (var name in this.classes) + { + topBlock.push(', ('); + addParts(this.classes[name], '', topBlock, info); + topBlock.push(' ? "', name, '" + " " : "")'); + } + topBlock.push(', "\\""'); + } + topBlock.push(',">"'); + + this.generateChildMarkup(topBlock, topOuts, blocks, info); + topBlock.push(',"</', this.tagName, '>"'); + }, + + generateChildMarkup: function(topBlock, topOuts, blocks, info) + { + for (var i = 0; i < this.children.length; ++i) + { + var child = this.children[i]; + if (isTag(child)) + child.tag.generateMarkup(topBlock, topOuts, blocks, info); + else + addParts(child, ',', topBlock, info, true); + } + }, + + addCode: function(topBlock, topOuts, blocks) + { + if (topBlock.length) + blocks.push('__code__.push(""', topBlock.join(""), ');'); + if (topOuts.length) + blocks.push('__out__.push(', topOuts.join(","), ');'); + topBlock.splice(0, topBlock.length); + topOuts.splice(0, topOuts.length); + }, + + addLocals: function(blocks) + { + var varNames = []; + this.getVarNames(varNames); + + var map = {}; + for (var i = 0; i < varNames.length; ++i) + { + var name = varNames[i]; + if ( map.hasOwnProperty(name) ) + continue; + + map[name] = 1; + var names = name.split("."); + blocks.push('var ', names[0] + ' = ' + '__in__.' + names[0] + ';'); + } + }, + + compileDOM: function() + { + var path = []; + var blocks = []; + this.domArgs = []; + path.embedIndex = 0; + path.loopIndex = 0; + path.staticIndex = 0; + path.renderIndex = 0; + var nodeCount = this.generateDOM(path, blocks, this.domArgs); + + var fnBlock = ['r=(function (root, context, o']; + + for (var i = 0; i < path.staticIndex; ++i) + fnBlock.push(', ', 's'+i); + + for (var i = 0; i < path.renderIndex; ++i) + fnBlock.push(', ', 'd'+i); + + fnBlock.push(') {'); + for (var i = 0; i < path.loopIndex; ++i) + fnBlock.push('var l', i, ' = 0;'); + for (var i = 0; i < path.embedIndex; ++i) + fnBlock.push('var e', i, ' = 0;'); + + if (this.subject) + fnBlock.push('with (this) {'); + if (this.context) + fnBlock.push('with (context) {'); + + fnBlock.push(blocks.join("")); + + if (this.subject) + fnBlock.push('}'); + if (this.context) + fnBlock.push('}'); + + fnBlock.push('return ', nodeCount, ';'); + fnBlock.push('})'); + + function __bind__(object, fn) + { + return function(event) { return fn.apply(object, [event]); }; + } + + function __link__(node, tag, args) + { + if (!tag || !tag.tag) + return; + + tag.tag.compile(); + + var domArgs = [node, tag.tag.context, 0]; + domArgs.push.apply(domArgs, tag.tag.domArgs); + domArgs.push.apply(domArgs, args); + //if (FBTrace.DBG_DOM) FBTrace.dumpProperties("domplate__link__ domArgs:", domArgs); + return tag.tag.renderDOM.apply(tag.tag.subject, domArgs); + } + + var self = this; + function __loop__(iter, fn) + { + var nodeCount = 0; + for (var i = 0; i < iter.length; ++i) + { + iter[i][0] = i; + iter[i][1] = nodeCount; + nodeCount += fn.apply(this, iter[i]); + //if (FBTrace.DBG_DOM) FBTrace.sysout("nodeCount", nodeCount); + } + return nodeCount; + } + + function __path__(parent, offset) + { + //if (FBTrace.DBG_DOM) FBTrace.sysout("domplate __path__ offset: "+ offset+"\n"); + var root = parent; + + for (var i = 2; i < arguments.length; ++i) + { + var index = arguments[i]; + if (i == 3) + index += offset; + + if (index == -1) + parent = parent.parentNode; + else + parent = parent.childNodes[index]; + } + + //if (FBTrace.DBG_DOM) FBTrace.sysout("domplate: "+arguments[2]+", root: "+ root+", parent: "+ parent+"\n"); + return parent; + } + + var js = fnBlock.join(""); + //if (FBTrace.DBG_DOM) FBTrace.sysout(js.replace(/(\;|\{)/g, "$1\n")); + var r = null; + eval(js); + this.renderDOM = r; + }, + + generateDOM: function(path, blocks, args) + { + if (this.listeners || this.props) + this.generateNodePath(path, blocks); + + if (this.listeners) + { + for (var i = 0; i < this.listeners.length; i += 2) + { + var val = this.listeners[i+1]; + var arg = generateArg(val, path, args); + //blocks.push('node.addEventListener("', this.listeners[i], '", __bind__(this, ', arg, '), false);'); + blocks.push('addEvent(node, "', this.listeners[i], '", __bind__(this, ', arg, '), false);'); + } + } + + if (this.props) + { + for (var name in this.props) + { + var val = this.props[name]; + var arg = generateArg(val, path, args); + blocks.push('node.', name, ' = ', arg, ';'); + } + } + + this.generateChildDOM(path, blocks, args); + return 1; + }, + + generateNodePath: function(path, blocks) + { + blocks.push("var node = __path__(root, o"); + for (var i = 0; i < path.length; ++i) + blocks.push(",", path[i]); + blocks.push(");"); + }, + + generateChildDOM: function(path, blocks, args) + { + path.push(0); + for (var i = 0; i < this.children.length; ++i) + { + var child = this.children[i]; + if (isTag(child)) + path[path.length-1] += '+' + child.tag.generateDOM(path, blocks, args); + else + path[path.length-1] += '+1'; + } + path.pop(); + } +}; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +FBL.DomplateEmbed.prototype = copyObject(FBL.DomplateTag.prototype, +/** @lends FBL.DomplateEmbed.prototype */ +{ + merge: function(args, oldTag) + { + this.value = oldTag ? oldTag.value : parseValue(args[0]); + this.attrs = oldTag ? oldTag.attrs : {}; + this.vars = oldTag ? copyArray(oldTag.vars) : []; + + var attrs = args[1]; + for (var name in attrs) + { + var val = parseValue(attrs[name]); + this.attrs[name] = val; + readPartNames(val, this.vars); + } + + return creator(this, DomplateEmbed); + }, + + getVarNames: function(names) + { + if (this.value instanceof Parts) + names.push(this.value.parts[0].name); + + if (this.vars) + names.push.apply(names, this.vars); + }, + + generateMarkup: function(topBlock, topOuts, blocks, info) + { + this.addCode(topBlock, topOuts, blocks); + + blocks.push('__link__('); + addParts(this.value, '', blocks, info); + blocks.push(', __code__, __out__, {'); + + var lastName = null; + for (var name in this.attrs) + { + if (lastName) + blocks.push(','); + lastName = name; + + var val = this.attrs[name]; + blocks.push('"', name, '":'); + addParts(val, '', blocks, info); + } + + blocks.push('});'); + //this.generateChildMarkup(topBlock, topOuts, blocks, info); + }, + + generateDOM: function(path, blocks, args) + { + var embedName = 'e'+path.embedIndex++; + + this.generateNodePath(path, blocks); + + var valueName = 'd' + path.renderIndex++; + var argsName = 'd' + path.renderIndex++; + blocks.push(embedName + ' = __link__(node, ', valueName, ', ', argsName, ');'); + + return embedName; + } +}); + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +FBL.DomplateLoop.prototype = copyObject(FBL.DomplateTag.prototype, +/** @lends FBL.DomplateLoop.prototype */ +{ + merge: function(args, oldTag) + { + this.varName = oldTag ? oldTag.varName : args[0]; + this.iter = oldTag ? oldTag.iter : parseValue(args[1]); + this.vars = []; + + this.children = oldTag ? copyArray(oldTag.children) : []; + + var offset = Math.min(args.length, 2); + parseChildren(args, offset, this.vars, this.children); + + return creator(this, DomplateLoop); + }, + + getVarNames: function(names) + { + if (this.iter instanceof Parts) + names.push(this.iter.parts[0].name); + + DomplateTag.prototype.getVarNames.apply(this, [names]); + }, + + generateMarkup: function(topBlock, topOuts, blocks, info) + { + this.addCode(topBlock, topOuts, blocks); + + var iterName; + if (this.iter instanceof Parts) + { + var part = this.iter.parts[0]; + iterName = part.name; + + if (part.format) + { + for (var i = 0; i < part.format.length; ++i) + iterName = part.format[i] + "(" + iterName + ")"; + } + } + else + iterName = this.iter; + + blocks.push('__loop__.apply(this, [', iterName, ', __out__, function(', this.varName, ', __out__) {'); + this.generateChildMarkup(topBlock, topOuts, blocks, info); + this.addCode(topBlock, topOuts, blocks); + blocks.push('}]);'); + }, + + generateDOM: function(path, blocks, args) + { + var iterName = 'd'+path.renderIndex++; + var counterName = 'i'+path.loopIndex; + var loopName = 'l'+path.loopIndex++; + + if (!path.length) + path.push(-1, 0); + + var preIndex = path.renderIndex; + path.renderIndex = 0; + + var nodeCount = 0; + + var subBlocks = []; + var basePath = path[path.length-1]; + for (var i = 0; i < this.children.length; ++i) + { + path[path.length-1] = basePath+'+'+loopName+'+'+nodeCount; + + var child = this.children[i]; + if (isTag(child)) + nodeCount += '+' + child.tag.generateDOM(path, subBlocks, args); + else + nodeCount += '+1'; + } + + path[path.length-1] = basePath+'+'+loopName; + + blocks.push(loopName,' = __loop__.apply(this, [', iterName, ', function(', counterName,',',loopName); + for (var i = 0; i < path.renderIndex; ++i) + blocks.push(',d'+i); + blocks.push(') {'); + blocks.push(subBlocks.join("")); + blocks.push('return ', nodeCount, ';'); + blocks.push('}]);'); + + path.renderIndex = preIndex; + + return loopName; + } +}); + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +/** @class */ +function Variable(name, format) +{ + this.name = name; + this.format = format; +} + +/** @class */ +function Parts(parts) +{ + this.parts = parts; +} + +// ************************************************************************************************ + +function parseParts(str) +{ + var re = /\$([_A-Za-z][_A-Za-z0-9.|]*)/g; + var index = 0; + var parts = []; + + var m; + while (m = re.exec(str)) + { + var pre = str.substr(index, (re.lastIndex-m[0].length)-index); + if (pre) + parts.push(pre); + + var expr = m[1].split("|"); + parts.push(new Variable(expr[0], expr.slice(1))); + index = re.lastIndex; + } + + if (!index) + return str; + + var post = str.substr(index); + if (post) + parts.push(post); + + return new Parts(parts); +} + +function parseValue(val) +{ + return typeof(val) == 'string' ? parseParts(val) : val; +} + +function parseChildren(args, offset, vars, children) +{ + for (var i = offset; i < args.length; ++i) + { + var val = parseValue(args[i]); + children.push(val); + readPartNames(val, vars); + } +} + +function readPartNames(val, vars) +{ + if (val instanceof Parts) + { + for (var i = 0; i < val.parts.length; ++i) + { + var part = val.parts[i]; + if (part instanceof Variable) + vars.push(part.name); + } + } +} + +function generateArg(val, path, args) +{ + if (val instanceof Parts) + { + var vals = []; + for (var i = 0; i < val.parts.length; ++i) + { + var part = val.parts[i]; + if (part instanceof Variable) + { + var varName = 'd'+path.renderIndex++; + if (part.format) + { + for (var j = 0; j < part.format.length; ++j) + varName = part.format[j] + '(' + varName + ')'; + } + + vals.push(varName); + } + else + vals.push('"'+part.replace(/"/g, '\\"')+'"'); + } + + return vals.join('+'); + } + else + { + args.push(val); + return 's' + path.staticIndex++; + } +} + +function addParts(val, delim, block, info, escapeIt) +{ + var vals = []; + if (val instanceof Parts) + { + for (var i = 0; i < val.parts.length; ++i) + { + var part = val.parts[i]; + if (part instanceof Variable) + { + var partName = part.name; + if (part.format) + { + for (var j = 0; j < part.format.length; ++j) + partName = part.format[j] + "(" + partName + ")"; + } + + if (escapeIt) + vals.push("__escape__(" + partName + ")"); + else + vals.push(partName); + } + else + vals.push('"'+ part + '"'); + } + } + else if (isTag(val)) + { + info.args.push(val); + vals.push('s'+info.argIndex++); + } + else + vals.push('"'+ val + '"'); + + var parts = vals.join(delim); + if (parts) + block.push(delim, parts); +} + +function isTag(obj) +{ + return (typeof(obj) == "function" || obj instanceof Function) && !!obj.tag; +} + +function creator(tag, cons) +{ + var fn = new Function( + "var tag = arguments.callee.tag;" + + "var cons = arguments.callee.cons;" + + "var newTag = new cons();" + + "return newTag.merge(arguments, tag);"); + + fn.tag = tag; + fn.cons = cons; + extend(fn, Renderer); + + return fn; +} + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +function copyArray(oldArray) +{ + var ary = []; + if (oldArray) + for (var i = 0; i < oldArray.length; ++i) + ary.push(oldArray[i]); + return ary; +} + +function copyObject(l, r) +{ + var m = {}; + extend(m, l); + extend(m, r); + return m; +} + +function extend(l, r) +{ + for (var n in r) + l[n] = r[n]; +} + +function addEvent(object, name, handler) +{ + if (document.all) + object.attachEvent("on"+name, handler); + else + object.addEventListener(name, handler, false); +} + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +/** @class */ +function ArrayIterator(array) +{ + var index = -1; + + this.next = function() + { + if (++index >= array.length) + throw StopIteration; + + return array[index]; + }; +} + +/** @class */ +function StopIteration() {} + +FBL.$break = function() +{ + throw StopIteration; +}; + +// ************************************************************************************************ + +/** @namespace */ +var Renderer = +{ + renderHTML: function(args, outputs, self) + { + var code = []; + var markupArgs = [code, this.tag.context, args, outputs]; + markupArgs.push.apply(markupArgs, this.tag.markupArgs); + this.tag.renderMarkup.apply(self ? self : this.tag.subject, markupArgs); + return code.join(""); + }, + + insertRows: function(args, before, self) + { + this.tag.compile(); + + var outputs = []; + var html = this.renderHTML(args, outputs, self); + + var doc = before.ownerDocument; + var div = doc.createElement("div"); + div.innerHTML = "<table><tbody>"+html+"</tbody></table>"; + + var tbody = div.firstChild.firstChild; + var parent = before.tagName == "TR" ? before.parentNode : before; + var after = before.tagName == "TR" ? before.nextSibling : null; + + var firstRow = tbody.firstChild, lastRow; + while (tbody.firstChild) + { + lastRow = tbody.firstChild; + if (after) + parent.insertBefore(lastRow, after); + else + parent.appendChild(lastRow); + } + + var offset = 0; + if (before.tagName == "TR") + { + var node = firstRow.parentNode.firstChild; + for (; node && node != firstRow; node = node.nextSibling) + ++offset; + } + + var domArgs = [firstRow, this.tag.context, offset]; + domArgs.push.apply(domArgs, this.tag.domArgs); + domArgs.push.apply(domArgs, outputs); + + this.tag.renderDOM.apply(self ? self : this.tag.subject, domArgs); + return [firstRow, lastRow]; + }, + + insertBefore: function(args, before, self) + { + return this.insertNode(args, before.ownerDocument, before, false, self); + }, + + insertAfter: function(args, after, self) + { + return this.insertNode(args, after.ownerDocument, after, true, self); + }, + + insertNode: function(args, doc, element, isAfter, self) + { + if (!args) + args = {}; + + this.tag.compile(); + + var outputs = []; + var html = this.renderHTML(args, outputs, self); + + //if (FBTrace.DBG_DOM) + // FBTrace.sysout("domplate.insertNode html: "+html+"\n"); + + var doc = element.ownerDocument; + if (!womb || womb.ownerDocument != doc) + womb = doc.createElement("div"); + + womb.innerHTML = html; + + var root = womb.firstChild; + if (isAfter) + { + while (womb.firstChild) + if (element.nextSibling) + element.parentNode.insertBefore(womb.firstChild, element.nextSibling); + else + element.parentNode.appendChild(womb.firstChild); + } + else + { + while (womb.lastChild) + element.parentNode.insertBefore(womb.lastChild, element); + } + + var domArgs = [root, this.tag.context, 0]; + domArgs.push.apply(domArgs, this.tag.domArgs); + domArgs.push.apply(domArgs, outputs); + + //if (FBTrace.DBG_DOM) + // FBTrace.sysout("domplate.insertNode domArgs:", domArgs); + this.tag.renderDOM.apply(self ? self : this.tag.subject, domArgs); + + return root; + }, + /**/ + + /* + insertAfter: function(args, before, self) + { + this.tag.compile(); + + var outputs = []; + var html = this.renderHTML(args, outputs, self); + + var doc = before.ownerDocument; + if (!womb || womb.ownerDocument != doc) + womb = doc.createElement("div"); + + womb.innerHTML = html; + + var root = womb.firstChild; + while (womb.firstChild) + if (before.nextSibling) + before.parentNode.insertBefore(womb.firstChild, before.nextSibling); + else + before.parentNode.appendChild(womb.firstChild); + + var domArgs = [root, this.tag.context, 0]; + domArgs.push.apply(domArgs, this.tag.domArgs); + domArgs.push.apply(domArgs, outputs); + + this.tag.renderDOM.apply(self ? self : (this.tag.subject ? this.tag.subject : null), + domArgs); + + return root; + }, + /**/ + + replace: function(args, parent, self) + { + this.tag.compile(); + + var outputs = []; + var html = this.renderHTML(args, outputs, self); + + var root; + if (parent.nodeType == 1) + { + parent.innerHTML = html; + root = parent.firstChild; + } + else + { + if (!parent || parent.nodeType != 9) + parent = document; + + if (!womb || womb.ownerDocument != parent) + womb = parent.createElement("div"); + womb.innerHTML = html; + + root = womb.firstChild; + //womb.removeChild(root); + } + + var domArgs = [root, this.tag.context, 0]; + domArgs.push.apply(domArgs, this.tag.domArgs); + domArgs.push.apply(domArgs, outputs); + this.tag.renderDOM.apply(self ? self : this.tag.subject, domArgs); + + return root; + }, + + append: function(args, parent, self) + { + this.tag.compile(); + + var outputs = []; + var html = this.renderHTML(args, outputs, self); + //if (FBTrace.DBG_DOM) FBTrace.sysout("domplate.append html: "+html+"\n"); + + if (!womb || womb.ownerDocument != parent.ownerDocument) + womb = parent.ownerDocument.createElement("div"); + womb.innerHTML = html; + + // TODO: xxxpedro domplate port to Firebug + var root = womb.firstChild; + while (womb.firstChild) + parent.appendChild(womb.firstChild); + + // clearing element reference to avoid reference error in IE8 when switching contexts + womb = null; + + var domArgs = [root, this.tag.context, 0]; + domArgs.push.apply(domArgs, this.tag.domArgs); + domArgs.push.apply(domArgs, outputs); + + //if (FBTrace.DBG_DOM) FBTrace.dumpProperties("domplate append domArgs:", domArgs); + this.tag.renderDOM.apply(self ? self : this.tag.subject, domArgs); + + return root; + } +}; + +// ************************************************************************************************ + +function defineTags() +{ + for (var i = 0; i < arguments.length; ++i) + { + var tagName = arguments[i]; + var fn = new Function("var newTag = new arguments.callee.DomplateTag('"+tagName+"'); return newTag.merge(arguments);"); + fn.DomplateTag = DomplateTag; + + var fnName = tagName.toUpperCase(); + FBL[fnName] = fn; + } +} + +defineTags( + "a", "button", "br", "canvas", "code", "col", "colgroup", "div", "fieldset", "form", "h1", "h2", "h3", "hr", + "img", "input", "label", "legend", "li", "ol", "optgroup", "option", "p", "pre", "select", + "span", "strong", "table", "tbody", "td", "textarea", "tfoot", "th", "thead", "tr", "tt", "ul", "iframe" +); + +})(); + + +/* See license.txt for terms of usage */ + +var FirebugReps = FBL.ns(function() { with (FBL) { + + +// ************************************************************************************************ +// Common Tags + +var OBJECTBOX = this.OBJECTBOX = + SPAN({"class": "objectBox objectBox-$className"}); + +var OBJECTBLOCK = this.OBJECTBLOCK = + DIV({"class": "objectBox objectBox-$className"}); + +var OBJECTLINK = this.OBJECTLINK = isIE6 ? // IE6 object link representation + A({ + "class": "objectLink objectLink-$className a11yFocus", + href: "javascript:void(0)", + // workaround to show XPath (a better approach would use the tooltip on mouseover, + // so the XPath information would be calculated dynamically, but we need to create + // a tooltip class/wrapper around Menu or InfoTip) + title: "$object|FBL.getElementXPath", + _repObject: "$object" + }) + : // Other browsers + A({ + "class": "objectLink objectLink-$className a11yFocus", + // workaround to show XPath (a better approach would use the tooltip on mouseover, + // so the XPath information would be calculated dynamically, but we need to create + // a tooltip class/wrapper around Menu or InfoTip) + title: "$object|FBL.getElementXPath", + _repObject: "$object" + }); + + +// ************************************************************************************************ + +this.Undefined = domplate(Firebug.Rep, +{ + tag: OBJECTBOX("undefined"), + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "undefined", + + supportsObject: function(object, type) + { + return type == "undefined"; + } +}); + +// ************************************************************************************************ + +this.Null = domplate(Firebug.Rep, +{ + tag: OBJECTBOX("null"), + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "null", + + supportsObject: function(object, type) + { + return object == null; + } +}); + +// ************************************************************************************************ + +this.Nada = domplate(Firebug.Rep, +{ + tag: SPAN(""), + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "nada" +}); + +// ************************************************************************************************ + +this.Number = domplate(Firebug.Rep, +{ + tag: OBJECTBOX("$object"), + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "number", + + supportsObject: function(object, type) + { + return type == "boolean" || type == "number"; + } +}); + +// ************************************************************************************************ + +this.String = domplate(Firebug.Rep, +{ + tag: OBJECTBOX(""$object""), + + shortTag: OBJECTBOX(""$object|cropString""), + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "string", + + supportsObject: function(object, type) + { + return type == "string"; + } +}); + +// ************************************************************************************************ + +this.Text = domplate(Firebug.Rep, +{ + tag: OBJECTBOX("$object"), + + shortTag: OBJECTBOX("$object|cropString"), + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "text" +}); + +// ************************************************************************************************ + +this.Caption = domplate(Firebug.Rep, +{ + tag: SPAN({"class": "caption"}, "$object") +}); + +// ************************************************************************************************ + +this.Warning = domplate(Firebug.Rep, +{ + tag: DIV({"class": "warning focusRow", role : 'listitem'}, "$object|STR") +}); + +// ************************************************************************************************ + +this.Func = domplate(Firebug.Rep, +{ + tag: + OBJECTLINK("$object|summarizeFunction"), + + summarizeFunction: function(fn) + { + var fnRegex = /function ([^(]+\([^)]*\)) \{/; + var fnText = safeToString(fn); + + var m = fnRegex.exec(fnText); + return m ? m[1] : "function()"; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + copySource: function(fn) + { + copyToClipboard(safeToString(fn)); + }, + + monitor: function(fn, script, monitored) + { + if (monitored) + Firebug.Debugger.unmonitorScript(fn, script, "monitor"); + else + Firebug.Debugger.monitorScript(fn, script, "monitor"); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "function", + + supportsObject: function(object, type) + { + return isFunction(object); + }, + + inspectObject: function(fn, context) + { + var sourceLink = findSourceForFunction(fn, context); + if (sourceLink) + Firebug.chrome.select(sourceLink); + if (FBTrace.DBG_FUNCTION_NAME) + FBTrace.sysout("reps.function.inspectObject selected sourceLink is ", sourceLink); + }, + + getTooltip: function(fn, context) + { + var script = findScriptForFunctionInContext(context, fn); + if (script) + return $STRF("Line", [normalizeURL(script.fileName), script.baseLineNumber]); + else + if (fn.toString) + return fn.toString(); + }, + + getTitle: function(fn, context) + { + var name = fn.name ? fn.name : "function"; + return name + "()"; + }, + + getContextMenuItems: function(fn, target, context, script) + { + if (!script) + script = findScriptForFunctionInContext(context, fn); + if (!script) + return; + + var scriptInfo = getSourceFileAndLineByScript(context, script); + var monitored = scriptInfo ? fbs.isMonitored(scriptInfo.sourceFile.href, scriptInfo.lineNo) : false; + + var name = script ? getFunctionName(script, context) : fn.name; + return [ + {label: "CopySource", command: bindFixed(this.copySource, this, fn) }, + "-", + {label: $STRF("ShowCallsInConsole", [name]), nol10n: true, + type: "checkbox", checked: monitored, + command: bindFixed(this.monitor, this, fn, script, monitored) } + ]; + } +}); + +// ************************************************************************************************ +/* +this.jsdScript = domplate(Firebug.Rep, +{ + copySource: function(script) + { + var fn = script.functionObject.getWrappedValue(); + return FirebugReps.Func.copySource(fn); + }, + + monitor: function(fn, script, monitored) + { + fn = script.functionObject.getWrappedValue(); + return FirebugReps.Func.monitor(fn, script, monitored); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "jsdScript", + inspectable: false, + + supportsObject: function(object, type) + { + return object instanceof jsdIScript; + }, + + inspectObject: function(script, context) + { + var sourceLink = getSourceLinkForScript(script, context); + if (sourceLink) + Firebug.chrome.select(sourceLink); + }, + + getRealObject: function(script, context) + { + return script; + }, + + getTooltip: function(script) + { + return $STRF("jsdIScript", [script.tag]); + }, + + getTitle: function(script, context) + { + var fn = script.functionObject.getWrappedValue(); + return FirebugReps.Func.getTitle(fn, context); + }, + + getContextMenuItems: function(script, target, context) + { + var fn = script.functionObject.getWrappedValue(); + + var scriptInfo = getSourceFileAndLineByScript(context, script); + var monitored = scriptInfo ? fbs.isMonitored(scriptInfo.sourceFile.href, scriptInfo.lineNo) : false; + + var name = getFunctionName(script, context); + + return [ + {label: "CopySource", command: bindFixed(this.copySource, this, script) }, + "-", + {label: $STRF("ShowCallsInConsole", [name]), nol10n: true, + type: "checkbox", checked: monitored, + command: bindFixed(this.monitor, this, fn, script, monitored) } + ]; + } +}); +/**/ +//************************************************************************************************ + +this.Obj = domplate(Firebug.Rep, +{ + tag: + OBJECTLINK( + SPAN({"class": "objectTitle"}, "$object|getTitle "), + + SPAN({"class": "objectProps"}, + SPAN({"class": "objectLeftBrace", role: "presentation"}, "{"), + FOR("prop", "$object|propIterator", + SPAN({"class": "objectPropName", role: "presentation"}, "$prop.name"), + SPAN({"class": "objectEqual", role: "presentation"}, "$prop.equal"), + TAG("$prop.tag", {object: "$prop.object"}), + SPAN({"class": "objectComma", role: "presentation"}, "$prop.delim") + ), + SPAN({"class": "objectRightBrace"}, "}") + ) + ), + + propNumberTag: + SPAN({"class": "objectProp-number"}, "$object"), + + propStringTag: + SPAN({"class": "objectProp-string"}, ""$object""), + + propObjectTag: + SPAN({"class": "objectProp-object"}, "$object"), + + propIterator: function (object) + { + ///Firebug.ObjectShortIteratorMax; + var maxLength = 55; // default max length for long representation + + if (!object) + return []; + + var props = []; + var length = 0; + + var numProperties = 0; + var numPropertiesShown = 0; + var maxLengthReached = false; + + var lib = this; + + var propRepsMap = + { + "boolean": this.propNumberTag, + "number": this.propNumberTag, + "string": this.propStringTag, + "object": this.propObjectTag + }; + + try + { + var title = Firebug.Rep.getTitle(object); + length += title.length; + + for (var name in object) + { + var value; + try + { + value = object[name]; + } + catch (exc) + { + continue; + } + + var type = typeof(value); + if (type == "boolean" || + type == "number" || + (type == "string" && value) || + (type == "object" && value && value.toString)) + { + var tag = propRepsMap[type]; + + var value = (type == "object") ? + Firebug.getRep(value).getTitle(value) : + value + ""; + + length += name.length + value.length + 4; + + if (length <= maxLength) + { + props.push({ + tag: tag, + name: name, + object: value, + equal: "=", + delim: ", " + }); + + numPropertiesShown++; + } + else + maxLengthReached = true; + + } + + numProperties++; + + if (maxLengthReached && numProperties > numPropertiesShown) + break; + } + + if (numProperties > numPropertiesShown) + { + props.push({ + object: "...", //xxxHonza localization + tag: FirebugReps.Caption.tag, + name: "", + equal:"", + delim:"" + }); + } + else if (props.length > 0) + { + props[props.length-1].delim = ''; + } + } + catch (exc) + { + // Sometimes we get exceptions when trying to read from certain objects, like + // StorageList, but don't let that gum up the works + // XXXjjb also History.previous fails because object is a web-page object which does not have + // permission to read the history + } + return props; + }, + + fb_1_6_propIterator: function (object, max) + { + max = max || 3; + if (!object) + return []; + + var props = []; + var len = 0, count = 0; + + try + { + for (var name in object) + { + var value; + try + { + value = object[name]; + } + catch (exc) + { + continue; + } + + var t = typeof(value); + if (t == "boolean" || t == "number" || (t == "string" && value) + || (t == "object" && value && value.toString)) + { + var rep = Firebug.getRep(value); + var tag = rep.shortTag || rep.tag; + if (t == "object") + { + value = rep.getTitle(value); + tag = rep.titleTag; + } + count++; + if (count <= max) + props.push({tag: tag, name: name, object: value, equal: "=", delim: ", "}); + else + break; + } + } + if (count > max) + { + props[Math.max(1,max-1)] = { + object: "more...", //xxxHonza localization + tag: FirebugReps.Caption.tag, + name: "", + equal:"", + delim:"" + }; + } + else if (props.length > 0) + { + props[props.length-1].delim = ''; + } + } + catch (exc) + { + // Sometimes we get exceptions when trying to read from certain objects, like + // StorageList, but don't let that gum up the works + // XXXjjb also History.previous fails because object is a web-page object which does not have + // permission to read the history + } + return props; + }, + + /* + propIterator: function (object) + { + if (!object) + return []; + + var props = []; + var len = 0; + + try + { + for (var name in object) + { + var val; + try + { + val = object[name]; + } + catch (exc) + { + continue; + } + + var t = typeof val; + if (t == "boolean" || t == "number" || (t == "string" && val) + || (t == "object" && !isFunction(val) && val && val.toString)) + { + var title = (t == "object") + ? Firebug.getRep(val).getTitle(val) + : val+""; + + len += name.length + title.length + 1; + if (len < 50) + props.push({name: name, value: title}); + else + break; + } + } + } + catch (exc) + { + // Sometimes we get exceptions when trying to read from certain objects, like + // StorageList, but don't let that gum up the works + // XXXjjb also History.previous fails because object is a web-page object which does not have + // permission to read the history + } + + return props; + }, + /**/ + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "object", + + supportsObject: function(object, type) + { + return true; + } +}); + + +// ************************************************************************************************ + +this.Arr = domplate(Firebug.Rep, +{ + tag: + OBJECTBOX({_repObject: "$object"}, + SPAN({"class": "arrayLeftBracket", role : "presentation"}, "["), + FOR("item", "$object|arrayIterator", + TAG("$item.tag", {object: "$item.object"}), + SPAN({"class": "arrayComma", role : "presentation"}, "$item.delim") + ), + SPAN({"class": "arrayRightBracket", role : "presentation"}, "]") + ), + + shortTag: + OBJECTBOX({_repObject: "$object"}, + SPAN({"class": "arrayLeftBracket", role : "presentation"}, "["), + FOR("item", "$object|shortArrayIterator", + TAG("$item.tag", {object: "$item.object"}), + SPAN({"class": "arrayComma", role : "presentation"}, "$item.delim") + ), + // TODO: xxxpedro - confirm this on Firebug + //FOR("prop", "$object|shortPropIterator", + // " $prop.name=", + // SPAN({"class": "objectPropValue"}, "$prop.value|cropString") + //), + SPAN({"class": "arrayRightBracket"}, "]") + ), + + arrayIterator: function(array) + { + var items = []; + for (var i = 0; i < array.length; ++i) + { + var value = array[i]; + var rep = Firebug.getRep(value); + var tag = rep.shortTag ? rep.shortTag : rep.tag; + var delim = (i == array.length-1 ? "" : ", "); + + items.push({object: value, tag: tag, delim: delim}); + } + + return items; + }, + + shortArrayIterator: function(array) + { + var items = []; + for (var i = 0; i < array.length && i < 3; ++i) + { + var value = array[i]; + var rep = Firebug.getRep(value); + var tag = rep.shortTag ? rep.shortTag : rep.tag; + var delim = (i == array.length-1 ? "" : ", "); + + items.push({object: value, tag: tag, delim: delim}); + } + + if (array.length > 3) + items.push({object: (array.length-3) + " more...", tag: FirebugReps.Caption.tag, delim: ""}); + + return items; + }, + + shortPropIterator: this.Obj.propIterator, + + getItemIndex: function(child) + { + var arrayIndex = 0; + for (child = child.previousSibling; child; child = child.previousSibling) + { + if (child.repObject) + ++arrayIndex; + } + return arrayIndex; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "array", + + supportsObject: function(object) + { + return this.isArray(object); + }, + + // http://code.google.com/p/fbug/issues/detail?id=874 + // BEGIN Yahoo BSD Source (modified here) YAHOO.lang.isArray, YUI 2.2.2 June 2007 + isArray: function(obj) { + try { + if (!obj) + return false; + else if (isIE && !isFunction(obj) && typeof obj == "object" && isFinite(obj.length) && obj.nodeType != 8) + return true; + else if (isFinite(obj.length) && isFunction(obj.splice)) + return true; + else if (isFinite(obj.length) && isFunction(obj.callee)) // arguments + return true; + else if (instanceOf(obj, "HTMLCollection")) + return true; + else if (instanceOf(obj, "NodeList")) + return true; + else + return false; + } + catch(exc) + { + if (FBTrace.DBG_ERRORS) + { + FBTrace.sysout("isArray FAILS:", exc); /* Something weird: without the try/catch, OOM, with no exception?? */ + FBTrace.sysout("isArray Fails on obj", obj); + } + } + + return false; + }, + // END Yahoo BSD SOURCE See license below. + + getTitle: function(object, context) + { + return "[" + object.length + "]"; + } +}); + +// ************************************************************************************************ + +this.Property = domplate(Firebug.Rep, +{ + supportsObject: function(object) + { + return object instanceof Property; + }, + + getRealObject: function(prop, context) + { + return prop.object[prop.name]; + }, + + getTitle: function(prop, context) + { + return prop.name; + } +}); + +// ************************************************************************************************ + +this.NetFile = domplate(this.Obj, +{ + supportsObject: function(object) + { + return object instanceof Firebug.NetFile; + }, + + browseObject: function(file, context) + { + openNewTab(file.href); + return true; + }, + + getRealObject: function(file, context) + { + return null; + } +}); + +// ************************************************************************************************ + +this.Except = domplate(Firebug.Rep, +{ + tag: + OBJECTBOX({_repObject: "$object"}, "$object.message"), + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "exception", + + supportsObject: function(object) + { + return object instanceof ErrorCopy; + } +}); + + +// ************************************************************************************************ + +this.Element = domplate(Firebug.Rep, +{ + tag: + OBJECTLINK( + "<", + SPAN({"class": "nodeTag"}, "$object.nodeName|toLowerCase"), + FOR("attr", "$object|attrIterator", + " $attr.nodeName="", SPAN({"class": "nodeValue"}, "$attr.nodeValue"), """ + ), + ">" + ), + + shortTag: + OBJECTLINK( + SPAN({"class": "$object|getVisible"}, + SPAN({"class": "selectorTag"}, "$object|getSelectorTag"), + SPAN({"class": "selectorId"}, "$object|getSelectorId"), + SPAN({"class": "selectorClass"}, "$object|getSelectorClass"), + SPAN({"class": "selectorValue"}, "$object|getValue") + ) + ), + + getVisible: function(elt) + { + return isVisible(elt) ? "" : "selectorHidden"; + }, + + getSelectorTag: function(elt) + { + return elt.nodeName.toLowerCase(); + }, + + getSelectorId: function(elt) + { + return elt.id ? "#" + elt.id : ""; + }, + + getSelectorClass: function(elt) + { + return elt.className ? "." + elt.className.split(" ")[0] : ""; + }, + + getValue: function(elt) + { + // TODO: xxxpedro + return ""; + var value; + if (elt instanceof HTMLImageElement) + value = getFileName(elt.src); + else if (elt instanceof HTMLAnchorElement) + value = getFileName(elt.href); + else if (elt instanceof HTMLInputElement) + value = elt.value; + else if (elt instanceof HTMLFormElement) + value = getFileName(elt.action); + else if (elt instanceof HTMLScriptElement) + value = getFileName(elt.src); + + return value ? " " + cropString(value, 20) : ""; + }, + + attrIterator: function(elt) + { + var attrs = []; + var idAttr, classAttr; + if (elt.attributes) + { + for (var i = 0; i < elt.attributes.length; ++i) + { + var attr = elt.attributes[i]; + + // we must check if the attribute is specified otherwise IE will show them + if (!attr.specified || attr.nodeName && attr.nodeName.indexOf("firebug-") != -1) + continue; + else if (attr.nodeName == "id") + idAttr = attr; + else if (attr.nodeName == "class") + classAttr = attr; + else if (attr.nodeName == "style") + attrs.push({ + nodeName: attr.nodeName, + nodeValue: attr.nodeValue || + // IE won't recognize the attr.nodeValue of <style> nodes ... + // and will return CSS property names in upper case, so we need to convert them + elt.style.cssText.replace(/([^\s]+)\s*:/g, + function(m,g){return g.toLowerCase()+":"}) + }); + else + attrs.push(attr); + } + } + if (classAttr) + attrs.splice(0, 0, classAttr); + if (idAttr) + attrs.splice(0, 0, idAttr); + + return attrs; + }, + + shortAttrIterator: function(elt) + { + var attrs = []; + if (elt.attributes) + { + for (var i = 0; i < elt.attributes.length; ++i) + { + var attr = elt.attributes[i]; + if (attr.nodeName == "id" || attr.nodeName == "class") + attrs.push(attr); + } + } + + return attrs; + }, + + getHidden: function(elt) + { + return isVisible(elt) ? "" : "nodeHidden"; + }, + + getXPath: function(elt) + { + return getElementTreeXPath(elt); + }, + + // TODO: xxxpedro remove this? + getNodeText: function(element) + { + var text = element.textContent; + if (Firebug.showFullTextNodes) + return text; + else + return cropString(text, 50); + }, + /**/ + + getNodeTextGroups: function(element) + { + var text = element.textContent; + if (!Firebug.showFullTextNodes) + { + text=cropString(text,50); + } + + var escapeGroups=[]; + + if (Firebug.showTextNodesWithWhitespace) + escapeGroups.push({ + 'group': 'whitespace', + 'class': 'nodeWhiteSpace', + 'extra': { + '\t': '_Tab', + '\n': '_Para', + ' ' : '_Space' + } + }); + if (Firebug.showTextNodesWithEntities) + escapeGroups.push({ + 'group':'text', + 'class':'nodeTextEntity', + 'extra':{} + }); + + if (escapeGroups.length) + return escapeGroupsForEntities(text, escapeGroups); + else + return [{str:text,'class':'',extra:''}]; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + copyHTML: function(elt) + { + var html = getElementXML(elt); + copyToClipboard(html); + }, + + copyInnerHTML: function(elt) + { + copyToClipboard(elt.innerHTML); + }, + + copyXPath: function(elt) + { + var xpath = getElementXPath(elt); + copyToClipboard(xpath); + }, + + persistor: function(context, xpath) + { + var elts = xpath + ? getElementsByXPath(context.window.document, xpath) + : null; + + return elts && elts.length ? elts[0] : null; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "element", + + supportsObject: function(object) + { + //return object instanceof Element || object.nodeType == 1 && typeof object.nodeName == "string"; + return instanceOf(object, "Element"); + }, + + browseObject: function(elt, context) + { + var tag = elt.nodeName.toLowerCase(); + if (tag == "script") + openNewTab(elt.src); + else if (tag == "link") + openNewTab(elt.href); + else if (tag == "a") + openNewTab(elt.href); + else if (tag == "img") + openNewTab(elt.src); + + return true; + }, + + persistObject: function(elt, context) + { + var xpath = getElementXPath(elt); + + return bind(this.persistor, top, xpath); + }, + + getTitle: function(element, context) + { + return getElementCSSSelector(element); + }, + + getTooltip: function(elt) + { + return this.getXPath(elt); + }, + + getContextMenuItems: function(elt, target, context) + { + var monitored = areEventsMonitored(elt, null, context); + + return [ + {label: "CopyHTML", command: bindFixed(this.copyHTML, this, elt) }, + {label: "CopyInnerHTML", command: bindFixed(this.copyInnerHTML, this, elt) }, + {label: "CopyXPath", command: bindFixed(this.copyXPath, this, elt) }, + "-", + {label: "ShowEventsInConsole", type: "checkbox", checked: monitored, + command: bindFixed(toggleMonitorEvents, FBL, elt, null, monitored, context) }, + "-", + {label: "ScrollIntoView", command: bindFixed(elt.scrollIntoView, elt) } + ]; + } +}); + +// ************************************************************************************************ + +this.TextNode = domplate(Firebug.Rep, +{ + tag: + OBJECTLINK( + "<", + SPAN({"class": "nodeTag"}, "TextNode"), + " textContent="", SPAN({"class": "nodeValue"}, "$object.textContent|cropString"), """, + ">" + ), + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "textNode", + + supportsObject: function(object) + { + return object instanceof Text; + } +}); + +// ************************************************************************************************ + +this.Document = domplate(Firebug.Rep, +{ + tag: + OBJECTLINK("Document ", SPAN({"class": "objectPropValue"}, "$object|getLocation")), + + getLocation: function(doc) + { + return doc.location ? getFileName(doc.location.href) : ""; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "object", + + supportsObject: function(object) + { + //return object instanceof Document || object instanceof XMLDocument; + return instanceOf(object, "Document"); + }, + + browseObject: function(doc, context) + { + openNewTab(doc.location.href); + return true; + }, + + persistObject: function(doc, context) + { + return this.persistor; + }, + + persistor: function(context) + { + return context.window.document; + }, + + getTitle: function(win, context) + { + return "document"; + }, + + getTooltip: function(doc) + { + return doc.location.href; + } +}); + +// ************************************************************************************************ + +this.StyleSheet = domplate(Firebug.Rep, +{ + tag: + OBJECTLINK("StyleSheet ", SPAN({"class": "objectPropValue"}, "$object|getLocation")), + + getLocation: function(styleSheet) + { + return getFileName(styleSheet.href); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + copyURL: function(styleSheet) + { + copyToClipboard(styleSheet.href); + }, + + openInTab: function(styleSheet) + { + openNewTab(styleSheet.href); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "object", + + supportsObject: function(object) + { + //return object instanceof CSSStyleSheet; + return instanceOf(object, "CSSStyleSheet"); + }, + + browseObject: function(styleSheet, context) + { + openNewTab(styleSheet.href); + return true; + }, + + persistObject: function(styleSheet, context) + { + return bind(this.persistor, top, styleSheet.href); + }, + + getTooltip: function(styleSheet) + { + return styleSheet.href; + }, + + getContextMenuItems: function(styleSheet, target, context) + { + return [ + {label: "CopyLocation", command: bindFixed(this.copyURL, this, styleSheet) }, + "-", + {label: "OpenInTab", command: bindFixed(this.openInTab, this, styleSheet) } + ]; + }, + + persistor: function(context, href) + { + return getStyleSheetByHref(href, context); + } +}); + +// ************************************************************************************************ + +this.Window = domplate(Firebug.Rep, +{ + tag: + OBJECTLINK("Window ", SPAN({"class": "objectPropValue"}, "$object|getLocation")), + + getLocation: function(win) + { + try + { + return (win && win.location && !win.closed) ? getFileName(win.location.href) : ""; + } + catch (exc) + { + if (FBTrace.DBG_ERRORS) + FBTrace.sysout("reps.Window window closed?"); + } + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "object", + + supportsObject: function(object) + { + return instanceOf(object, "Window"); + }, + + browseObject: function(win, context) + { + openNewTab(win.location.href); + return true; + }, + + persistObject: function(win, context) + { + return this.persistor; + }, + + persistor: function(context) + { + return context.window; + }, + + getTitle: function(win, context) + { + return "window"; + }, + + getTooltip: function(win) + { + if (win && !win.closed) + return win.location.href; + } +}); + +// ************************************************************************************************ + +this.Event = domplate(Firebug.Rep, +{ + tag: TAG("$copyEventTag", {object: "$object|copyEvent"}), + + copyEventTag: + OBJECTLINK("$object|summarizeEvent"), + + summarizeEvent: function(event) + { + var info = [event.type, ' ']; + + var eventFamily = getEventFamily(event.type); + if (eventFamily == "mouse") + info.push("clientX=", event.clientX, ", clientY=", event.clientY); + else if (eventFamily == "key") + info.push("charCode=", event.charCode, ", keyCode=", event.keyCode); + + return info.join(""); + }, + + copyEvent: function(event) + { + return new EventCopy(event); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "object", + + supportsObject: function(object) + { + //return object instanceof Event || object instanceof EventCopy; + return instanceOf(object, "Event") || instanceOf(object, "EventCopy"); + }, + + getTitle: function(event, context) + { + return "Event " + event.type; + } +}); + +// ************************************************************************************************ + +this.SourceLink = domplate(Firebug.Rep, +{ + tag: + OBJECTLINK({$collapsed: "$object|hideSourceLink"}, "$object|getSourceLinkTitle"), + + hideSourceLink: function(sourceLink) + { + return sourceLink ? sourceLink.href.indexOf("XPCSafeJSObjectWrapper") != -1 : true; + }, + + getSourceLinkTitle: function(sourceLink) + { + if (!sourceLink) + return ""; + + try + { + var fileName = getFileName(sourceLink.href); + fileName = decodeURIComponent(fileName); + fileName = cropString(fileName, 17); + } + catch(exc) + { + if (FBTrace.DBG_ERRORS) + FBTrace.sysout("reps.getSourceLinkTitle decodeURIComponent fails for \'"+fileName+"\': "+exc, exc); + } + + return typeof sourceLink.line == "number" ? + fileName + " (line " + sourceLink.line + ")" : + fileName; + + // TODO: xxxpedro + //return $STRF("Line", [fileName, sourceLink.line]); + }, + + copyLink: function(sourceLink) + { + copyToClipboard(sourceLink.href); + }, + + openInTab: function(sourceLink) + { + openNewTab(sourceLink.href); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "sourceLink", + + supportsObject: function(object) + { + return object instanceof SourceLink; + }, + + getTooltip: function(sourceLink) + { + return decodeURI(sourceLink.href); + }, + + inspectObject: function(sourceLink, context) + { + if (sourceLink.type == "js") + { + var scriptFile = getSourceFileByHref(sourceLink.href, context); + if (scriptFile) + return Firebug.chrome.select(sourceLink); + } + else if (sourceLink.type == "css") + { + // If an object is defined, treat it as the highest priority for + // inspect actions + if (sourceLink.object) { + Firebug.chrome.select(sourceLink.object); + return; + } + + var stylesheet = getStyleSheetByHref(sourceLink.href, context); + if (stylesheet) + { + var ownerNode = stylesheet.ownerNode; + if (ownerNode) + { + Firebug.chrome.select(sourceLink, "html"); + return; + } + + var panel = context.getPanel("stylesheet"); + if (panel && panel.getRuleByLine(stylesheet, sourceLink.line)) + return Firebug.chrome.select(sourceLink); + } + } + + // Fallback is to just open the view-source window on the file + viewSource(sourceLink.href, sourceLink.line); + }, + + browseObject: function(sourceLink, context) + { + openNewTab(sourceLink.href); + return true; + }, + + getContextMenuItems: function(sourceLink, target, context) + { + return [ + {label: "CopyLocation", command: bindFixed(this.copyLink, this, sourceLink) }, + "-", + {label: "OpenInTab", command: bindFixed(this.openInTab, this, sourceLink) } + ]; + } +}); + +// ************************************************************************************************ + +this.SourceFile = domplate(this.SourceLink, +{ + tag: + OBJECTLINK({$collapsed: "$object|hideSourceLink"}, "$object|getSourceLinkTitle"), + + persistor: function(context, href) + { + return getSourceFileByHref(href, context); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "sourceFile", + + supportsObject: function(object) + { + return object instanceof SourceFile; + }, + + persistObject: function(sourceFile) + { + return bind(this.persistor, top, sourceFile.href); + }, + + browseObject: function(sourceLink, context) + { + }, + + getTooltip: function(sourceFile) + { + return sourceFile.href; + } +}); + +// ************************************************************************************************ + +this.StackFrame = domplate(Firebug.Rep, // XXXjjb Since the repObject is fn the stack does not have correct line numbers +{ + tag: + OBJECTBLOCK( + A({"class": "objectLink objectLink-function focusRow a11yFocus", _repObject: "$object.fn"}, "$object|getCallName"), + " ( ", + FOR("arg", "$object|argIterator", + TAG("$arg.tag", {object: "$arg.value"}), + SPAN({"class": "arrayComma"}, "$arg.delim") + ), + " )", + SPAN({"class": "objectLink-sourceLink objectLink"}, "$object|getSourceLinkTitle") + ), + + getCallName: function(frame) + { + //TODO: xxxpedro reps StackFrame + return frame.name || "anonymous"; + + //return getFunctionName(frame.script, frame.context); + }, + + getSourceLinkTitle: function(frame) + { + //TODO: xxxpedro reps StackFrame + var fileName = cropString(getFileName(frame.href), 20); + return fileName + (frame.lineNo ? " (line " + frame.lineNo + ")" : ""); + + var fileName = cropString(getFileName(frame.href), 17); + return $STRF("Line", [fileName, frame.lineNo]); + }, + + argIterator: function(frame) + { + if (!frame.args) + return []; + + var items = []; + + for (var i = 0; i < frame.args.length; ++i) + { + var arg = frame.args[i]; + + if (!arg) + break; + + var rep = Firebug.getRep(arg.value); + var tag = rep.shortTag ? rep.shortTag : rep.tag; + + var delim = (i == frame.args.length-1 ? "" : ", "); + + items.push({name: arg.name, value: arg.value, tag: tag, delim: delim}); + } + + return items; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "stackFrame", + + supportsObject: function(object) + { + return object instanceof StackFrame; + }, + + inspectObject: function(stackFrame, context) + { + var sourceLink = new SourceLink(stackFrame.href, stackFrame.lineNo, "js"); + Firebug.chrome.select(sourceLink); + }, + + getTooltip: function(stackFrame, context) + { + return $STRF("Line", [stackFrame.href, stackFrame.lineNo]); + } + +}); + +// ************************************************************************************************ + +this.StackTrace = domplate(Firebug.Rep, +{ + tag: + FOR("frame", "$object.frames focusRow", + TAG(this.StackFrame.tag, {object: "$frame"}) + ), + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "stackTrace", + + supportsObject: function(object) + { + return object instanceof StackTrace; + } +}); + +// ************************************************************************************************ + +this.jsdStackFrame = domplate(Firebug.Rep, +{ + inspectable: false, + + supportsObject: function(object) + { + return (object instanceof jsdIStackFrame) && (object.isValid); + }, + + getTitle: function(frame, context) + { + if (!frame.isValid) return "(invalid frame)"; // XXXjjb avoid frame.script == null + return getFunctionName(frame.script, context); + }, + + getTooltip: function(frame, context) + { + if (!frame.isValid) return "(invalid frame)"; // XXXjjb avoid frame.script == null + var sourceInfo = FBL.getSourceFileAndLineByScript(context, frame.script, frame); + if (sourceInfo) + return $STRF("Line", [sourceInfo.sourceFile.href, sourceInfo.lineNo]); + else + return $STRF("Line", [frame.script.fileName, frame.line]); + }, + + getContextMenuItems: function(frame, target, context) + { + var fn = frame.script.functionObject.getWrappedValue(); + return FirebugReps.Func.getContextMenuItems(fn, target, context, frame.script); + } +}); + +// ************************************************************************************************ + +this.ErrorMessage = domplate(Firebug.Rep, +{ + tag: + OBJECTBOX({ + $hasTwisty: "$object|hasStackTrace", + $hasBreakSwitch: "$object|hasBreakSwitch", + $breakForError: "$object|hasErrorBreak", + _repObject: "$object", + _stackTrace: "$object|getLastErrorStackTrace", + onclick: "$onToggleError"}, + + DIV({"class": "errorTitle a11yFocus", role : 'checkbox', 'aria-checked' : 'false'}, + "$object.message|getMessage" + ), + DIV({"class": "errorTrace"}), + DIV({"class": "errorSourceBox errorSource-$object|getSourceType"}, + IMG({"class": "errorBreak a11yFocus", src:"blank.gif", role : 'checkbox', 'aria-checked':'false', title: "Break on this error"}), + A({"class": "errorSource a11yFocus"}, "$object|getLine") + ), + TAG(this.SourceLink.tag, {object: "$object|getSourceLink"}) + ), + + getLastErrorStackTrace: function(error) + { + return error.trace; + }, + + hasStackTrace: function(error) + { + var url = error.href.toString(); + var fromCommandLine = (url.indexOf("XPCSafeJSObjectWrapper") != -1); + return !fromCommandLine && error.trace; + }, + + hasBreakSwitch: function(error) + { + return error.href && error.lineNo > 0; + }, + + hasErrorBreak: function(error) + { + return fbs.hasErrorBreakpoint(error.href, error.lineNo); + }, + + getMessage: function(message) + { + var re = /\[Exception... "(.*?)" nsresult:/; + var m = re.exec(message); + return m ? m[1] : message; + }, + + getLine: function(error) + { + if (error.category == "js") + { + if (error.source) + return cropString(error.source, 80); + else if (error.href && error.href.indexOf("XPCSafeJSObjectWrapper") == -1) + return cropString(error.getSourceLine(), 80); + } + }, + + getSourceLink: function(error) + { + var ext = error.category == "css" ? "css" : "js"; + return error.lineNo ? new SourceLink(error.href, error.lineNo, ext) : null; + }, + + getSourceType: function(error) + { + // Errors occurring inside of HTML event handlers look like "foo.html (line 1)" + // so let's try to skip those + if (error.source) + return "syntax"; + else if (error.lineNo == 1 && getFileExtension(error.href) != "js") + return "none"; + else if (error.category == "css") + return "none"; + else if (!error.href || !error.lineNo) + return "none"; + else + return "exec"; + }, + + onToggleError: function(event) + { + var target = event.currentTarget; + if (hasClass(event.target, "errorBreak")) + { + this.breakOnThisError(target.repObject); + } + else if (hasClass(event.target, "errorSource")) + { + var panel = Firebug.getElementPanel(event.target); + this.inspectObject(target.repObject, panel.context); + } + else if (hasClass(event.target, "errorTitle")) + { + var traceBox = target.childNodes[1]; + toggleClass(target, "opened"); + event.target.setAttribute('aria-checked', hasClass(target, "opened")); + if (hasClass(target, "opened")) + { + if (target.stackTrace) + var node = FirebugReps.StackTrace.tag.append({object: target.stackTrace}, traceBox); + if (Firebug.A11yModel.enabled) + { + var panel = Firebug.getElementPanel(event.target); + dispatch([Firebug.A11yModel], "onLogRowContentCreated", [panel , traceBox]); + } + } + else + clearNode(traceBox); + } + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + copyError: function(error) + { + var message = [ + this.getMessage(error.message), + error.href, + "Line " + error.lineNo + ]; + copyToClipboard(message.join("\n")); + }, + + breakOnThisError: function(error) + { + if (this.hasErrorBreak(error)) + Firebug.Debugger.clearErrorBreakpoint(error.href, error.lineNo); + else + Firebug.Debugger.setErrorBreakpoint(error.href, error.lineNo); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "errorMessage", + inspectable: false, + + supportsObject: function(object) + { + return object instanceof ErrorMessage; + }, + + inspectObject: function(error, context) + { + var sourceLink = this.getSourceLink(error); + FirebugReps.SourceLink.inspectObject(sourceLink, context); + }, + + getContextMenuItems: function(error, target, context) + { + var breakOnThisError = this.hasErrorBreak(error); + + var items = [ + {label: "CopyError", command: bindFixed(this.copyError, this, error) } + ]; + + if (error.category == "css") + { + items.push( + "-", + {label: "BreakOnThisError", type: "checkbox", checked: breakOnThisError, + command: bindFixed(this.breakOnThisError, this, error) }, + + optionMenu("BreakOnAllErrors", "breakOnErrors") + ); + } + + return items; + } +}); + +// ************************************************************************************************ + +this.Assert = domplate(Firebug.Rep, +{ + tag: + DIV( + DIV({"class": "errorTitle"}), + DIV({"class": "assertDescription"}) + ), + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "assert", + + inspectObject: function(error, context) + { + var sourceLink = this.getSourceLink(error); + Firebug.chrome.select(sourceLink); + }, + + getContextMenuItems: function(error, target, context) + { + var breakOnThisError = this.hasErrorBreak(error); + + return [ + {label: "CopyError", command: bindFixed(this.copyError, this, error) }, + "-", + {label: "BreakOnThisError", type: "checkbox", checked: breakOnThisError, + command: bindFixed(this.breakOnThisError, this, error) }, + {label: "BreakOnAllErrors", type: "checkbox", checked: Firebug.breakOnErrors, + command: bindFixed(this.breakOnAllErrors, this, error) } + ]; + } +}); + +// ************************************************************************************************ + +this.SourceText = domplate(Firebug.Rep, +{ + tag: + DIV( + FOR("line", "$object|lineIterator", + DIV({"class": "sourceRow", role : "presentation"}, + SPAN({"class": "sourceLine", role : "presentation"}, "$line.lineNo"), + SPAN({"class": "sourceRowText", role : "presentation"}, "$line.text") + ) + ) + ), + + lineIterator: function(sourceText) + { + var maxLineNoChars = (sourceText.lines.length + "").length; + var list = []; + + for (var i = 0; i < sourceText.lines.length; ++i) + { + // Make sure all line numbers are the same width (with a fixed-width font) + var lineNo = (i+1) + ""; + while (lineNo.length < maxLineNoChars) + lineNo = " " + lineNo; + + list.push({lineNo: lineNo, text: sourceText.lines[i]}); + } + + return list; + }, + + getHTML: function(sourceText) + { + return getSourceLineRange(sourceText, 1, sourceText.lines.length); + } +}); + +//************************************************************************************************ +this.nsIDOMHistory = domplate(Firebug.Rep, +{ + tag:OBJECTBOX({onclick: "$showHistory"}, + OBJECTLINK("$object|summarizeHistory") + ), + + className: "nsIDOMHistory", + + summarizeHistory: function(history) + { + try + { + var items = history.length; + return items + " history entries"; + } + catch(exc) + { + return "object does not support history (nsIDOMHistory)"; + } + }, + + showHistory: function(history) + { + try + { + var items = history.length; // if this throws, then unsupported + Firebug.chrome.select(history); + } + catch (exc) + { + } + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + supportsObject: function(object, type) + { + return (object instanceof Ci.nsIDOMHistory); + } +}); + +// ************************************************************************************************ +this.ApplicationCache = domplate(Firebug.Rep, +{ + tag:OBJECTBOX({onclick: "$showApplicationCache"}, + OBJECTLINK("$object|summarizeCache") + ), + + summarizeCache: function(applicationCache) + { + try + { + return applicationCache.length + " items in offline cache"; + } + catch(exc) + { + return "https://bugzilla.mozilla.org/show_bug.cgi?id=422264"; + } + }, + + showApplicationCache: function(event) + { + openNewTab("https://bugzilla.mozilla.org/show_bug.cgi?id=422264"); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "applicationCache", + + supportsObject: function(object, type) + { + if (Ci.nsIDOMOfflineResourceList) + return (object instanceof Ci.nsIDOMOfflineResourceList); + } + +}); + +this.Storage = domplate(Firebug.Rep, +{ + tag: OBJECTBOX({onclick: "$show"}, OBJECTLINK("$object|summarize")), + + summarize: function(storage) + { + return storage.length +" items in Storage"; + }, + show: function(storage) + { + openNewTab("http://dev.w3.org/html5/webstorage/#storage-0"); + }, + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "Storage", + + supportsObject: function(object, type) + { + return (object instanceof Storage); + } + +}); + +// ************************************************************************************************ +Firebug.registerRep( + //this.nsIDOMHistory, // make this early to avoid exceptions + this.Undefined, + this.Null, + this.Number, + this.String, + this.Window, + //this.ApplicationCache, // must come before Arr (array) else exceptions. + //this.ErrorMessage, + this.Element, + //this.TextNode, + this.Document, + this.StyleSheet, + this.Event, + //this.SourceLink, + //this.SourceFile, + //this.StackTrace, + //this.StackFrame, + //this.jsdStackFrame, + //this.jsdScript, + //this.NetFile, + this.Property, + this.Except, + this.Arr +); + +Firebug.setDefaultReps(this.Func, this.Obj); + +}}); + +// ************************************************************************************************ +/* + * The following is http://developer.yahoo.com/yui/license.txt and applies to only code labeled "Yahoo BSD Source" + * in only this file reps.js. John J. Barton June 2007. + * +Software License Agreement (BSD License) + +Copyright (c) 2006, Yahoo! 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 Yahoo! 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 Yahoo! 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. + * / + */ + + +/* See license.txt for terms of usage */ + +FBL.ns(function() { with (FBL) { + +// ************************************************************************************************ +// Constants + +var saveTimeout = 400; +var pageAmount = 10; + +// ************************************************************************************************ +// Globals + +var currentTarget = null; +var currentGroup = null; +var currentPanel = null; +var currentEditor = null; + +var defaultEditor = null; + +var originalClassName = null; + +var originalValue = null; +var defaultValue = null; +var previousValue = null; + +var invalidEditor = false; +var ignoreNextInput = false; + +// ************************************************************************************************ + +Firebug.Editor = extend(Firebug.Module, +{ + supportsStopEvent: true, + + dispatchName: "editor", + tabCharacter: " ", + + startEditing: function(target, value, editor) + { + this.stopEditing(); + + if (hasClass(target, "insertBefore") || hasClass(target, "insertAfter")) + return; + + var panel = Firebug.getElementPanel(target); + if (!panel.editable) + return; + + if (FBTrace.DBG_EDITOR) + FBTrace.sysout("editor.startEditing " + value, target); + + defaultValue = target.getAttribute("defaultValue"); + if (value == undefined) + { + var textContent = isIE ? "innerText" : "textContent"; + value = target[textContent]; + if (value == defaultValue) + value = ""; + } + + originalValue = previousValue = value; + + invalidEditor = false; + currentTarget = target; + currentPanel = panel; + currentGroup = getAncestorByClass(target, "editGroup"); + + currentPanel.editing = true; + + var panelEditor = currentPanel.getEditor(target, value); + currentEditor = editor ? editor : panelEditor; + if (!currentEditor) + currentEditor = getDefaultEditor(currentPanel); + + var inlineParent = getInlineParent(target); + var targetSize = getOffsetSize(inlineParent); + + setClass(panel.panelNode, "editing"); + setClass(target, "editing"); + if (currentGroup) + setClass(currentGroup, "editing"); + + currentEditor.show(target, currentPanel, value, targetSize); + //dispatch(this.fbListeners, "onBeginEditing", [currentPanel, currentEditor, target, value]); + currentEditor.beginEditing(target, value); + if (FBTrace.DBG_EDITOR) + FBTrace.sysout("Editor start panel "+currentPanel.name); + this.attachListeners(currentEditor, panel.context); + }, + + stopEditing: function(cancel) + { + if (!currentTarget) + return; + + if (FBTrace.DBG_EDITOR) + FBTrace.sysout("editor.stopEditing cancel:" + cancel+" saveTimeout: "+this.saveTimeout); + + clearTimeout(this.saveTimeout); + delete this.saveTimeout; + + this.detachListeners(currentEditor, currentPanel.context); + + removeClass(currentPanel.panelNode, "editing"); + removeClass(currentTarget, "editing"); + if (currentGroup) + removeClass(currentGroup, "editing"); + + var value = currentEditor.getValue(); + if (value == defaultValue) + value = ""; + + var removeGroup = currentEditor.endEditing(currentTarget, value, cancel); + + try + { + if (cancel) + { + //dispatch([Firebug.A11yModel], 'onInlineEditorClose', [currentPanel, currentTarget, removeGroup && !originalValue]); + if (value != originalValue) + this.saveEditAndNotifyListeners(currentTarget, originalValue, previousValue); + + if (removeGroup && !originalValue && currentGroup) + currentGroup.parentNode.removeChild(currentGroup); + } + else if (!value) + { + this.saveEditAndNotifyListeners(currentTarget, null, previousValue); + + if (removeGroup && currentGroup) + currentGroup.parentNode.removeChild(currentGroup); + } + else + this.save(value); + } + catch (exc) + { + //throw exc.message; + //ERROR(exc); + } + + currentEditor.hide(); + currentPanel.editing = false; + + //dispatch(this.fbListeners, "onStopEdit", [currentPanel, currentEditor, currentTarget]); + //if (FBTrace.DBG_EDITOR) + // FBTrace.sysout("Editor stop panel "+currentPanel.name); + + currentTarget = null; + currentGroup = null; + currentPanel = null; + currentEditor = null; + originalValue = null; + invalidEditor = false; + + return value; + }, + + cancelEditing: function() + { + return this.stopEditing(true); + }, + + update: function(saveNow) + { + if (this.saveTimeout) + clearTimeout(this.saveTimeout); + + invalidEditor = true; + + currentEditor.layout(); + + if (saveNow) + this.save(); + else + { + var context = currentPanel.context; + this.saveTimeout = context.setTimeout(bindFixed(this.save, this), saveTimeout); + if (FBTrace.DBG_EDITOR) + FBTrace.sysout("editor.update saveTimeout: "+this.saveTimeout); + } + }, + + save: function(value) + { + if (!invalidEditor) + return; + + if (value == undefined) + value = currentEditor.getValue(); + if (FBTrace.DBG_EDITOR) + FBTrace.sysout("editor.save saveTimeout: "+this.saveTimeout+" currentPanel: "+(currentPanel?currentPanel.name:"null")); + try + { + this.saveEditAndNotifyListeners(currentTarget, value, previousValue); + + previousValue = value; + invalidEditor = false; + } + catch (exc) + { + if (FBTrace.DBG_ERRORS) + FBTrace.sysout("editor.save FAILS "+exc, exc); + } + }, + + saveEditAndNotifyListeners: function(currentTarget, value, previousValue) + { + currentEditor.saveEdit(currentTarget, value, previousValue); + //dispatch(this.fbListeners, "onSaveEdit", [currentPanel, currentEditor, currentTarget, value, previousValue]); + }, + + setEditTarget: function(element) + { + if (!element) + { + dispatch([Firebug.A11yModel], 'onInlineEditorClose', [currentPanel, currentTarget, true]); + this.stopEditing(); + } + else if (hasClass(element, "insertBefore")) + this.insertRow(element, "before"); + else if (hasClass(element, "insertAfter")) + this.insertRow(element, "after"); + else + this.startEditing(element); + }, + + tabNextEditor: function() + { + if (!currentTarget) + return; + + var value = currentEditor.getValue(); + var nextEditable = currentTarget; + do + { + nextEditable = !value && currentGroup + ? getNextOutsider(nextEditable, currentGroup) + : getNextByClass(nextEditable, "editable"); + } + while (nextEditable && !nextEditable.offsetHeight); + + this.setEditTarget(nextEditable); + }, + + tabPreviousEditor: function() + { + if (!currentTarget) + return; + + var value = currentEditor.getValue(); + var prevEditable = currentTarget; + do + { + prevEditable = !value && currentGroup + ? getPreviousOutsider(prevEditable, currentGroup) + : getPreviousByClass(prevEditable, "editable"); + } + while (prevEditable && !prevEditable.offsetHeight); + + this.setEditTarget(prevEditable); + }, + + insertRow: function(relative, insertWhere) + { + var group = + relative || getAncestorByClass(currentTarget, "editGroup") || currentTarget; + var value = this.stopEditing(); + + currentPanel = Firebug.getElementPanel(group); + + currentEditor = currentPanel.getEditor(group, value); + if (!currentEditor) + currentEditor = getDefaultEditor(currentPanel); + + currentGroup = currentEditor.insertNewRow(group, insertWhere); + if (!currentGroup) + return; + + var editable = hasClass(currentGroup, "editable") + ? currentGroup + : getNextByClass(currentGroup, "editable"); + + if (editable) + this.setEditTarget(editable); + }, + + insertRowForObject: function(relative) + { + var container = getAncestorByClass(relative, "insertInto"); + if (container) + { + relative = getChildByClass(container, "insertBefore"); + if (relative) + this.insertRow(relative, "before"); + } + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + attachListeners: function(editor, context) + { + var win = isIE ? + currentTarget.ownerDocument.parentWindow : + currentTarget.ownerDocument.defaultView; + + addEvent(win, "resize", this.onResize); + addEvent(win, "blur", this.onBlur); + + var chrome = Firebug.chrome; + + this.listeners = [ + chrome.keyCodeListen("ESCAPE", null, bind(this.cancelEditing, this)) + ]; + + if (editor.arrowCompletion) + { + this.listeners.push( + chrome.keyCodeListen("UP", null, bindFixed(editor.completeValue, editor, -1)), + chrome.keyCodeListen("DOWN", null, bindFixed(editor.completeValue, editor, 1)), + chrome.keyCodeListen("PAGE_UP", null, bindFixed(editor.completeValue, editor, -pageAmount)), + chrome.keyCodeListen("PAGE_DOWN", null, bindFixed(editor.completeValue, editor, pageAmount)) + ); + } + + if (currentEditor.tabNavigation) + { + this.listeners.push( + chrome.keyCodeListen("RETURN", null, bind(this.tabNextEditor, this)), + chrome.keyCodeListen("RETURN", isControl, bind(this.insertRow, this, null, "after")), + chrome.keyCodeListen("TAB", null, bind(this.tabNextEditor, this)), + chrome.keyCodeListen("TAB", isShift, bind(this.tabPreviousEditor, this)) + ); + } + else if (currentEditor.multiLine) + { + this.listeners.push( + chrome.keyCodeListen("TAB", null, insertTab) + ); + } + else + { + this.listeners.push( + chrome.keyCodeListen("RETURN", null, bindFixed(this.stopEditing, this)) + ); + + if (currentEditor.tabCompletion) + { + this.listeners.push( + chrome.keyCodeListen("TAB", null, bind(editor.completeValue, editor, 1)), + chrome.keyCodeListen("TAB", isShift, bind(editor.completeValue, editor, -1)) + ); + } + } + }, + + detachListeners: function(editor, context) + { + if (!this.listeners) + return; + + var win = isIE ? + currentTarget.ownerDocument.parentWindow : + currentTarget.ownerDocument.defaultView; + + removeEvent(win, "resize", this.onResize); + removeEvent(win, "blur", this.onBlur); + + var chrome = Firebug.chrome; + if (chrome) + { + for (var i = 0; i < this.listeners.length; ++i) + chrome.keyIgnore(this.listeners[i]); + } + + delete this.listeners; + }, + + onResize: function(event) + { + currentEditor.layout(true); + }, + + onBlur: function(event) + { + if (currentEditor.enterOnBlur && isAncestor(event.target, currentEditor.box)) + this.stopEditing(); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // extends Module + + initialize: function() + { + Firebug.Module.initialize.apply(this, arguments); + + this.onResize = bindFixed(this.onResize, this); + this.onBlur = bind(this.onBlur, this); + }, + + disable: function() + { + this.stopEditing(); + }, + + showContext: function(browser, context) + { + this.stopEditing(); + }, + + showPanel: function(browser, panel) + { + this.stopEditing(); + } +}); + +// ************************************************************************************************ +// BaseEditor + +Firebug.BaseEditor = extend(Firebug.MeasureBox, +{ + getValue: function() + { + }, + + setValue: function(value) + { + }, + + show: function(target, panel, value, textSize, targetSize) + { + }, + + hide: function() + { + }, + + layout: function(forceAll) + { + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Support for context menus within inline editors. + + getContextMenuItems: function(target) + { + var items = []; + items.push({label: "Cut", commandID: "cmd_cut"}); + items.push({label: "Copy", commandID: "cmd_copy"}); + items.push({label: "Paste", commandID: "cmd_paste"}); + return items; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Editor Module listeners will get "onBeginEditing" just before this call + + beginEditing: function(target, value) + { + }, + + // Editor Module listeners will get "onSaveEdit" just after this call + saveEdit: function(target, value, previousValue) + { + }, + + endEditing: function(target, value, cancel) + { + // Remove empty groups by default + return true; + }, + + insertNewRow: function(target, insertWhere) + { + } +}); + +// ************************************************************************************************ +// InlineEditor + +// basic inline editor attributes +var inlineEditorAttributes = { + "class": "textEditorInner", + + type: "text", + spellcheck: "false", + + onkeypress: "$onKeyPress", + + onoverflow: "$onOverflow", + oncontextmenu: "$onContextMenu" +}; + +// IE does not support the oninput event, so we're using the onkeydown to signalize +// the relevant keyboard events, and the onpropertychange to actually handle the +// input event, which should happen after the onkeydown event is fired and after the +// value of the input is updated, but before the onkeyup and before the input (with the +// new value) is rendered +if (isIE) +{ + inlineEditorAttributes.onpropertychange = "$onInput"; + inlineEditorAttributes.onkeydown = "$onKeyDown"; +} +// for other browsers we use the oninput event +else +{ + inlineEditorAttributes.oninput = "$onInput"; +} + +Firebug.InlineEditor = function(doc) +{ + this.initializeInline(doc); +}; + +Firebug.InlineEditor.prototype = domplate(Firebug.BaseEditor, +{ + enterOnBlur: true, + outerMargin: 8, + shadowExpand: 7, + + tag: + DIV({"class": "inlineEditor"}, + DIV({"class": "textEditorTop1"}, + DIV({"class": "textEditorTop2"}) + ), + DIV({"class": "textEditorInner1"}, + DIV({"class": "textEditorInner2"}, + INPUT( + inlineEditorAttributes + ) + ) + ), + DIV({"class": "textEditorBottom1"}, + DIV({"class": "textEditorBottom2"}) + ) + ), + + inputTag : + INPUT({"class": "textEditorInner", type: "text", + /*oninput: "$onInput",*/ onkeypress: "$onKeyPress", onoverflow: "$onOverflow"} + ), + + expanderTag: + IMG({"class": "inlineExpander", src: "blank.gif"}), + + initialize: function() + { + this.fixedWidth = false; + this.completeAsYouType = true; + this.tabNavigation = true; + this.multiLine = false; + this.tabCompletion = false; + this.arrowCompletion = true; + this.noWrap = true; + this.numeric = false; + }, + + destroy: function() + { + this.destroyInput(); + }, + + initializeInline: function(doc) + { + if (FBTrace.DBG_EDITOR) + FBTrace.sysout("Firebug.InlineEditor initializeInline()"); + + //this.box = this.tag.replace({}, doc, this); + this.box = this.tag.append({}, doc.body, this); + + //this.input = this.box.childNodes[1].firstChild.firstChild; // XXXjjb childNode[1] required + this.input = this.box.getElementsByTagName("input")[0]; + + if (isIElt8) + { + this.input.style.top = "-8px"; + } + + this.expander = this.expanderTag.replace({}, doc, this); + this.initialize(); + }, + + destroyInput: function() + { + // XXXjoe Need to remove input/keypress handlers to avoid leaks + }, + + getValue: function() + { + return this.input.value; + }, + + setValue: function(value) + { + // It's only a one-line editor, so new lines shouldn't be allowed + return this.input.value = stripNewLines(value); + }, + + show: function(target, panel, value, targetSize) + { + //dispatch([Firebug.A11yModel], "onInlineEditorShow", [panel, this]); + this.target = target; + this.panel = panel; + + this.targetSize = targetSize; + + // TODO: xxxpedro editor + //this.targetOffset = getClientOffset(target); + + // Some browsers (IE, Google Chrome and Safari) will have problem trying to get the + // offset values of invisible elements, or empty elements. So, in order to get the + // correct values, we temporary inject a character in the innerHTML of the empty element, + // then we get the offset values, and next, we restore the original innerHTML value. + var innerHTML = target.innerHTML; + var isEmptyElement = !innerHTML; + if (isEmptyElement) + target.innerHTML = "."; + + // Get the position of the target element (that is about to be edited) + this.targetOffset = + { + x: target.offsetLeft, + y: target.offsetTop + }; + + // Restore the original innerHTML value of the empty element + if (isEmptyElement) + target.innerHTML = innerHTML; + + this.originalClassName = this.box.className; + + var classNames = target.className.split(" "); + for (var i = 0; i < classNames.length; ++i) + setClass(this.box, "editor-" + classNames[i]); + + // Make the editor match the target's font style + copyTextStyles(target, this.box); + + this.setValue(value); + + if (this.fixedWidth) + this.updateLayout(true); + else + { + this.startMeasuring(target); + this.textSize = this.measureInputText(value); + + // Correct the height of the box to make the funky CSS drop-shadow line up + var parent = this.input.parentNode; + if (hasClass(parent, "textEditorInner2")) + { + var yDiff = this.textSize.height - this.shadowExpand; + + // IE6 height offset + if (isIE6) + yDiff -= 2; + + parent.style.height = yDiff + "px"; + parent.parentNode.style.height = yDiff + "px"; + } + + this.updateLayout(true); + } + + this.getAutoCompleter().reset(); + + if (isIElt8) + panel.panelNode.appendChild(this.box); + else + target.offsetParent.appendChild(this.box); + + //console.log(target); + //this.input.select(); // it's called bellow, with setTimeout + + if (isIE) + { + // reset input style + this.input.style.fontFamily = "Monospace"; + this.input.style.fontSize = "11px"; + } + + // Insert the "expander" to cover the target element with white space + if (!this.fixedWidth) + { + copyBoxStyles(target, this.expander); + + target.parentNode.replaceChild(this.expander, target); + collapse(target, true); + this.expander.parentNode.insertBefore(target, this.expander); + } + + //TODO: xxxpedro + //scrollIntoCenterView(this.box, null, true); + + // Display the editor after change its size and position to avoid flickering + this.box.style.display = "block"; + + // we need to call input.focus() and input.select() with a timeout, + // otherwise it won't work on all browsers due to timing issues + var self = this; + setTimeout(function(){ + self.input.focus(); + self.input.select(); + },0); + }, + + hide: function() + { + this.box.className = this.originalClassName; + + if (!this.fixedWidth) + { + this.stopMeasuring(); + + collapse(this.target, false); + + if (this.expander.parentNode) + this.expander.parentNode.removeChild(this.expander); + } + + if (this.box.parentNode) + { + ///setSelectionRange(this.input, 0, 0); + this.input.blur(); + + this.box.parentNode.removeChild(this.box); + } + + delete this.target; + delete this.panel; + }, + + layout: function(forceAll) + { + if (!this.fixedWidth) + this.textSize = this.measureInputText(this.input.value); + + if (forceAll) + this.targetOffset = getClientOffset(this.expander); + + this.updateLayout(false, forceAll); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + beginEditing: function(target, value) + { + }, + + saveEdit: function(target, value, previousValue) + { + }, + + endEditing: function(target, value, cancel) + { + // Remove empty groups by default + return true; + }, + + insertNewRow: function(target, insertWhere) + { + }, + + advanceToNext: function(target, charCode) + { + return false; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + getAutoCompleteRange: function(value, offset) + { + }, + + getAutoCompleteList: function(preExpr, expr, postExpr) + { + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + getAutoCompleter: function() + { + if (!this.autoCompleter) + { + this.autoCompleter = new Firebug.AutoCompleter(null, + bind(this.getAutoCompleteRange, this), bind(this.getAutoCompleteList, this), + true, false); + } + + return this.autoCompleter; + }, + + completeValue: function(amt) + { + //console.log("completeValue"); + + var selectRangeCallback = this.getAutoCompleter().complete(currentPanel.context, this.input, true, amt < 0); + + if (selectRangeCallback) + { + Firebug.Editor.update(true); + + // We need to select the editor text after calling update in Safari/Chrome, + // otherwise the text won't be selected + if (isSafari) + setTimeout(selectRangeCallback,0); + else + selectRangeCallback(); + } + else + this.incrementValue(amt); + }, + + incrementValue: function(amt) + { + var value = this.input.value; + + // TODO: xxxpedro editor + if (isIE) + var start = getInputSelectionStart(this.input), end = start; + else + var start = this.input.selectionStart, end = this.input.selectionEnd; + + //debugger; + var range = this.getAutoCompleteRange(value, start); + if (!range || range.type != "int") + range = {start: 0, end: value.length-1}; + + var expr = value.substr(range.start, range.end-range.start+1); + preExpr = value.substr(0, range.start); + postExpr = value.substr(range.end+1); + + // See if the value is an integer, and if so increment it + var intValue = parseInt(expr); + if (!!intValue || intValue == 0) + { + var m = /\d+/.exec(expr); + var digitPost = expr.substr(m.index+m[0].length); + + var completion = intValue-amt; + this.input.value = preExpr + completion + digitPost + postExpr; + + setSelectionRange(this.input, start, end); + + Firebug.Editor.update(true); + + return true; + } + else + return false; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + onKeyPress: function(event) + { + //console.log("onKeyPress", event); + if (event.keyCode == 27 && !this.completeAsYouType) + { + var reverted = this.getAutoCompleter().revert(this.input); + if (reverted) + cancelEvent(event); + } + else if (event.charCode && this.advanceToNext(this.target, event.charCode)) + { + Firebug.Editor.tabNextEditor(); + cancelEvent(event); + } + else + { + if (this.numeric && event.charCode && (event.charCode < 48 || event.charCode > 57) + && event.charCode != 45 && event.charCode != 46) + FBL.cancelEvent(event); + else + { + // If the user backspaces, don't autocomplete after the upcoming input event + this.ignoreNextInput = event.keyCode == 8; + } + } + }, + + onOverflow: function() + { + this.updateLayout(false, false, 3); + }, + + onKeyDown: function(event) + { + //console.log("onKeyDown", event.keyCode); + if (event.keyCode > 46 || event.keyCode == 32 || event.keyCode == 8) + { + this.keyDownPressed = true; + } + }, + + onInput: function(event) + { + //debugger; + + // skip not relevant onpropertychange calls on IE + if (isIE) + { + if (event.propertyName != "value" || !isVisible(this.input) || !this.keyDownPressed) + return; + + this.keyDownPressed = false; + } + + //console.log("onInput", event); + //console.trace(); + + var selectRangeCallback; + + if (this.ignoreNextInput) + { + this.ignoreNextInput = false; + this.getAutoCompleter().reset(); + } + else if (this.completeAsYouType) + selectRangeCallback = this.getAutoCompleter().complete(currentPanel.context, this.input, false); + else + this.getAutoCompleter().reset(); + + Firebug.Editor.update(); + + if (selectRangeCallback) + { + // We need to select the editor text after calling update in Safari/Chrome, + // otherwise the text won't be selected + if (isSafari) + setTimeout(selectRangeCallback,0); + else + selectRangeCallback(); + } + }, + + onContextMenu: function(event) + { + cancelEvent(event); + + var popup = $("fbInlineEditorPopup"); + FBL.eraseNode(popup); + + var target = event.target || event.srcElement; + var menu = this.getContextMenuItems(target); + if (menu) + { + for (var i = 0; i < menu.length; ++i) + FBL.createMenuItem(popup, menu[i]); + } + + if (!popup.firstChild) + return false; + + popup.openPopupAtScreen(event.screenX, event.screenY, true); + return true; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + updateLayout: function(initial, forceAll, extraWidth) + { + if (this.fixedWidth) + { + this.box.style.left = (this.targetOffset.x) + "px"; + this.box.style.top = (this.targetOffset.y) + "px"; + + var w = this.target.offsetWidth; + var h = this.target.offsetHeight; + this.input.style.width = w + "px"; + this.input.style.height = (h-3) + "px"; + } + else + { + if (initial || forceAll) + { + this.box.style.left = this.targetOffset.x + "px"; + this.box.style.top = this.targetOffset.y + "px"; + } + + var approxTextWidth = this.textSize.width; + var maxWidth = (currentPanel.panelNode.scrollWidth - this.targetOffset.x) + - this.outerMargin; + + var wrapped = initial + ? this.noWrap && this.targetSize.height > this.textSize.height+3 + : this.noWrap && approxTextWidth > maxWidth; + + if (wrapped) + { + var style = isIE ? + this.target.currentStyle : + this.target.ownerDocument.defaultView.getComputedStyle(this.target, ""); + + targetMargin = parseInt(style.marginLeft) + parseInt(style.marginRight); + + // Make the width fit the remaining x-space from the offset to the far right + approxTextWidth = maxWidth - targetMargin; + + this.input.style.width = "100%"; + this.box.style.width = approxTextWidth + "px"; + } + else + { + // Make the input one character wider than the text value so that + // typing does not ever cause the textbox to scroll + var charWidth = this.measureInputText('m').width; + + // Sometimes we need to make the editor a little wider, specifically when + // an overflow happens, otherwise it will scroll off some text on the left + if (extraWidth) + charWidth *= extraWidth; + + var inputWidth = approxTextWidth + charWidth; + + if (initial) + { + if (isIE) + { + // TODO: xxxpedro + var xDiff = 13; + this.box.style.width = (inputWidth + xDiff) + "px"; + } + else + this.box.style.width = "auto"; + } + else + { + // TODO: xxxpedro + var xDiff = isIE ? 13: this.box.scrollWidth - this.input.offsetWidth; + this.box.style.width = (inputWidth + xDiff) + "px"; + } + + this.input.style.width = inputWidth + "px"; + } + + this.expander.style.width = approxTextWidth + "px"; + this.expander.style.height = Math.max(this.textSize.height-3,0) + "px"; + } + + if (forceAll) + scrollIntoCenterView(this.box, null, true); + } +}); + +// ************************************************************************************************ +// Autocompletion + +Firebug.AutoCompleter = function(getExprOffset, getRange, evaluator, selectMode, caseSensitive) +{ + var candidates = null; + var originalValue = null; + var originalOffset = -1; + var lastExpr = null; + var lastOffset = -1; + var exprOffset = 0; + var lastIndex = 0; + var preParsed = null; + var preExpr = null; + var postExpr = null; + + this.revert = function(textBox) + { + if (originalOffset != -1) + { + textBox.value = originalValue; + + setSelectionRange(textBox, originalOffset, originalOffset); + + this.reset(); + return true; + } + else + { + this.reset(); + return false; + } + }; + + this.reset = function() + { + candidates = null; + originalValue = null; + originalOffset = -1; + lastExpr = null; + lastOffset = 0; + exprOffset = 0; + }; + + this.complete = function(context, textBox, cycle, reverse) + { + //console.log("complete", context, textBox, cycle, reverse); + // TODO: xxxpedro important port to firebug (variable leak) + //var value = lastValue = textBox.value; + var value = textBox.value; + + //var offset = textBox.selectionStart; + var offset = getInputSelectionStart(textBox); + + // The result of selectionStart() in Safari/Chrome is 1 unit less than the result + // in Firefox. Therefore, we need to manually adjust the value here. + if (isSafari && !cycle && offset >= 0) offset++; + + if (!selectMode && originalOffset != -1) + offset = originalOffset; + + if (!candidates || !cycle || offset != lastOffset) + { + originalOffset = offset; + originalValue = value; + + // Find the part of the string that will be parsed + var parseStart = getExprOffset ? getExprOffset(value, offset, context) : 0; + preParsed = value.substr(0, parseStart); + var parsed = value.substr(parseStart); + + // Find the part of the string that is being completed + var range = getRange ? getRange(parsed, offset-parseStart, context) : null; + if (!range) + range = {start: 0, end: parsed.length-1 }; + + var expr = parsed.substr(range.start, range.end-range.start+1); + preExpr = parsed.substr(0, range.start); + postExpr = parsed.substr(range.end+1); + exprOffset = parseStart + range.start; + + if (!cycle) + { + if (!expr) + return; + else if (lastExpr && lastExpr.indexOf(expr) != 0) + { + candidates = null; + } + else if (lastExpr && lastExpr.length >= expr.length) + { + candidates = null; + lastExpr = expr; + return; + } + } + + lastExpr = expr; + lastOffset = offset; + + var searchExpr; + + // Check if the cursor is at the very right edge of the expression, or + // somewhere in the middle of it + if (expr && offset != parseStart+range.end+1) + { + if (cycle) + { + // We are in the middle of the expression, but we can + // complete by cycling to the next item in the values + // list after the expression + offset = range.start; + searchExpr = expr; + expr = ""; + } + else + { + // We can't complete unless we are at the ridge edge + return; + } + } + + var values = evaluator(preExpr, expr, postExpr, context); + if (!values) + return; + + if (expr) + { + // Filter the list of values to those which begin with expr. We + // will then go on to complete the first value in the resulting list + candidates = []; + + if (caseSensitive) + { + for (var i = 0; i < values.length; ++i) + { + var name = values[i]; + if (name.indexOf && name.indexOf(expr) == 0) + candidates.push(name); + } + } + else + { + var lowerExpr = caseSensitive ? expr : expr.toLowerCase(); + for (var i = 0; i < values.length; ++i) + { + var name = values[i]; + if (name.indexOf && name.toLowerCase().indexOf(lowerExpr) == 0) + candidates.push(name); + } + } + + lastIndex = reverse ? candidates.length-1 : 0; + } + else if (searchExpr) + { + var searchIndex = -1; + + // Find the first instance of searchExpr in the values list. We + // will then complete the string that is found + if (caseSensitive) + { + searchIndex = values.indexOf(expr); + } + else + { + var lowerExpr = searchExpr.toLowerCase(); + for (var i = 0; i < values.length; ++i) + { + var name = values[i]; + if (name && name.toLowerCase().indexOf(lowerExpr) == 0) + { + searchIndex = i; + break; + } + } + } + + // Nothing found, so there's nothing to complete to + if (searchIndex == -1) + return this.reset(); + + expr = searchExpr; + candidates = cloneArray(values); + lastIndex = searchIndex; + } + else + { + expr = ""; + candidates = []; + for (var i = 0; i < values.length; ++i) + { + if (values[i].substr) + candidates.push(values[i]); + } + lastIndex = -1; + } + } + + if (cycle) + { + expr = lastExpr; + lastIndex += reverse ? -1 : 1; + } + + if (!candidates.length) + return; + + if (lastIndex >= candidates.length) + lastIndex = 0; + else if (lastIndex < 0) + lastIndex = candidates.length-1; + + var completion = candidates[lastIndex]; + var preCompletion = expr.substr(0, offset-exprOffset); + var postCompletion = completion.substr(offset-exprOffset); + + textBox.value = preParsed + preExpr + preCompletion + postCompletion + postExpr; + var offsetEnd = preParsed.length + preExpr.length + completion.length; + + // TODO: xxxpedro remove the following commented code, if the lib.setSelectionRange() + // is working well. + /* + if (textBox.setSelectionRange) + { + // we must select the range with a timeout, otherwise the text won't + // be properly selected (because after this function executes, the editor's + // input will be resized to fit the whole text) + setTimeout(function(){ + if (selectMode) + textBox.setSelectionRange(offset, offsetEnd); + else + textBox.setSelectionRange(offsetEnd, offsetEnd); + },0); + } + /**/ + + // we must select the range with a timeout, otherwise the text won't + // be properly selected (because after this function executes, the editor's + // input will be resized to fit the whole text) + /* + setTimeout(function(){ + if (selectMode) + setSelectionRange(textBox, offset, offsetEnd); + else + setSelectionRange(textBox, offsetEnd, offsetEnd); + },0); + + return true; + /**/ + + // The editor text should be selected only after calling the editor.update() + // in Safari/Chrome, otherwise the text won't be selected. So, we're returning + // a function to be called later (in the proper time for all browsers). + // + // TODO: xxxpedro see if we can move the editor.update() calls to here, and avoid + // returning a closure. the complete() function seems to be called only twice in + // editor.js. See if this function is called anywhere else (like css.js for example). + return function(){ + //console.log("autocomplete ", textBox, offset, offsetEnd); + + if (selectMode) + setSelectionRange(textBox, offset, offsetEnd); + else + setSelectionRange(textBox, offsetEnd, offsetEnd); + }; + /**/ + }; +}; + +// ************************************************************************************************ +// Local Helpers + +var getDefaultEditor = function getDefaultEditor(panel) +{ + if (!defaultEditor) + { + var doc = panel.document; + defaultEditor = new Firebug.InlineEditor(doc); + } + + return defaultEditor; +} + +/** + * An outsider is the first element matching the stepper element that + * is not an child of group. Elements tagged with insertBefore or insertAfter + * classes are also excluded from these results unless they are the sibling + * of group, relative to group's parent editGroup. This allows for the proper insertion + * rows when groups are nested. + */ +var getOutsider = function getOutsider(element, group, stepper) +{ + var parentGroup = getAncestorByClass(group.parentNode, "editGroup"); + var next; + do + { + next = stepper(next || element); + } + while (isAncestor(next, group) || isGroupInsert(next, parentGroup)); + + return next; +} + +var isGroupInsert = function isGroupInsert(next, group) +{ + return (!group || isAncestor(next, group)) + && (hasClass(next, "insertBefore") || hasClass(next, "insertAfter")); +} + +var getNextOutsider = function getNextOutsider(element, group) +{ + return getOutsider(element, group, bind(getNextByClass, FBL, "editable")); +} + +var getPreviousOutsider = function getPreviousOutsider(element, group) +{ + return getOutsider(element, group, bind(getPreviousByClass, FBL, "editable")); +} + +var getInlineParent = function getInlineParent(element) +{ + var lastInline = element; + for (; element; element = element.parentNode) + { + //var s = element.ownerDocument.defaultView.getComputedStyle(element, ""); + var s = isIE ? + element.currentStyle : + element.ownerDocument.defaultView.getComputedStyle(element, ""); + + if (s.display != "inline") + return lastInline; + else + lastInline = element; + } + return null; +} + +var insertTab = function insertTab() +{ + insertTextIntoElement(currentEditor.input, Firebug.Editor.tabCharacter); +} + +// ************************************************************************************************ + +Firebug.registerModule(Firebug.Editor); + +// ************************************************************************************************ + +}}); + + +/* See license.txt for terms of usage */ + +FBL.ns(function() { with (FBL) { +// ************************************************************************************************ + +if (Env.Options.disableXHRListener) + return; + +// ************************************************************************************************ +// XHRSpy + +var XHRSpy = function() +{ + this.requestHeaders = []; + this.responseHeaders = []; +}; + +XHRSpy.prototype = +{ + method: null, + url: null, + async: null, + + xhrRequest: null, + + href: null, + + loaded: false, + + logRow: null, + + responseText: null, + + requestHeaders: null, + responseHeaders: null, + + sourceLink: null, // {href:"file.html", line: 22} + + getURL: function() + { + return this.href; + } +}; + +// ************************************************************************************************ +// XMLHttpRequestWrapper + +var XMLHttpRequestWrapper = function(activeXObject) +{ + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // XMLHttpRequestWrapper internal variables + + var xhrRequest = typeof activeXObject != "undefined" ? + activeXObject : + new _XMLHttpRequest(), + + spy = new XHRSpy(), + + self = this, + + reqType, + reqUrl, + reqStartTS; + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // XMLHttpRequestWrapper internal methods + + var updateSelfPropertiesIgnore = { + abort: 1, + channel: 1, + getAllResponseHeaders: 1, + getInterface: 1, + getResponseHeader: 1, + mozBackgroundRequest: 1, + multipart: 1, + onreadystatechange: 1, + open: 1, + send: 1, + setRequestHeader: 1 + }; + + var updateSelfProperties = function() + { + if (supportsXHRIterator) + { + for (var propName in xhrRequest) + { + if (propName in updateSelfPropertiesIgnore) + continue; + + try + { + var propValue = xhrRequest[propName]; + + if (propValue && !isFunction(propValue)) + self[propName] = propValue; + } + catch(E) + { + //console.log(propName, E.message); + } + } + } + else + { + // will fail to read these xhrRequest properties if the request is not completed + if (xhrRequest.readyState == 4) + { + self.status = xhrRequest.status; + self.statusText = xhrRequest.statusText; + self.responseText = xhrRequest.responseText; + self.responseXML = xhrRequest.responseXML; + } + } + }; + + var updateXHRPropertiesIgnore = { + channel: 1, + onreadystatechange: 1, + readyState: 1, + responseBody: 1, + responseText: 1, + responseXML: 1, + status: 1, + statusText: 1, + upload: 1 + }; + + var updateXHRProperties = function() + { + for (var propName in self) + { + if (propName in updateXHRPropertiesIgnore) + continue; + + try + { + var propValue = self[propName]; + + if (propValue && !xhrRequest[propName]) + { + xhrRequest[propName] = propValue; + } + } + catch(E) + { + //console.log(propName, E.message); + } + } + }; + + var logXHR = function() + { + var row = Firebug.Console.log(spy, null, "spy", Firebug.Spy.XHR); + + if (row) + { + setClass(row, "loading"); + spy.logRow = row; + } + }; + + var finishXHR = function() + { + var duration = new Date().getTime() - reqStartTS; + var success = xhrRequest.status == 200; + + var responseHeadersText = xhrRequest.getAllResponseHeaders(); + var responses = responseHeadersText ? responseHeadersText.split(/[\n\r]/) : []; + var reHeader = /^(\S+):\s*(.*)/; + + for (var i=0, l=responses.length; i<l; i++) + { + var text = responses[i]; + var match = text.match(reHeader); + + if (match) + { + var name = match[1]; + var value = match[2]; + + // update the spy mimeType property so we can detect when to show + // custom response viewers (such as HTML, XML or JSON viewer) + if (name == "Content-Type") + spy.mimeType = value; + + /* + if (name == "Last Modified") + { + if (!spy.cacheEntry) + spy.cacheEntry = []; + + spy.cacheEntry.push({ + name: [name], + value: [value] + }); + } + /**/ + + spy.responseHeaders.push({ + name: [name], + value: [value] + }); + } + } + + with({ + row: spy.logRow, + status: xhrRequest.status == 0 ? + // if xhrRequest.status == 0 then accessing xhrRequest.statusText + // will cause an error, so we must handle this case (Issue 3504) + "" : xhrRequest.status + " " + xhrRequest.statusText, + time: duration, + success: success + }) + { + setTimeout(function(){ + + spy.responseText = xhrRequest.responseText; + + // update row information to avoid "ethernal spinning gif" bug in IE + row = row || spy.logRow; + + // if chrome document is not loaded, there will be no row yet, so just ignore + if (!row) return; + + // update the XHR representation data + handleRequestStatus(success, status, time); + + },200); + } + + spy.loaded = true; + /* + // commented because they are being updated by the updateSelfProperties() function + self.status = xhrRequest.status; + self.statusText = xhrRequest.statusText; + self.responseText = xhrRequest.responseText; + self.responseXML = xhrRequest.responseXML; + /**/ + updateSelfProperties(); + }; + + var handleStateChange = function() + { + //Firebug.Console.log(["onreadystatechange", xhrRequest.readyState, xhrRequest.readyState == 4 && xhrRequest.status]); + + self.readyState = xhrRequest.readyState; + + if (xhrRequest.readyState == 4) + { + finishXHR(); + + xhrRequest.onreadystatechange = function(){}; + } + + //Firebug.Console.log(spy.url + ": " + xhrRequest.readyState); + + self.onreadystatechange(); + }; + + // update the XHR representation data + var handleRequestStatus = function(success, status, time) + { + var row = spy.logRow; + FBL.removeClass(row, "loading"); + + if (!success) + FBL.setClass(row, "error"); + + var item = FBL.$$(".spyStatus", row)[0]; + item.innerHTML = status; + + if (time) + { + var item = FBL.$$(".spyTime", row)[0]; + item.innerHTML = time + "ms"; + } + }; + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // XMLHttpRequestWrapper public properties and handlers + + this.readyState = 0; + + this.onreadystatechange = function(){}; + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // XMLHttpRequestWrapper public methods + + this.open = function(method, url, async, user, password) + { + //Firebug.Console.log("xhrRequest open"); + + updateSelfProperties(); + + if (spy.loaded) + spy = new XHRSpy(); + + spy.method = method; + spy.url = url; + spy.async = async; + spy.href = url; + spy.xhrRequest = xhrRequest; + spy.urlParams = parseURLParamsArray(url); + + try + { + // xhrRequest.open.apply may not be available in IE + if (supportsApply) + xhrRequest.open.apply(xhrRequest, arguments); + else + xhrRequest.open(method, url, async, user, password); + } + catch(e) + { + } + + xhrRequest.onreadystatechange = handleStateChange; + + }; + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + this.send = function(data) + { + //Firebug.Console.log("xhrRequest send"); + spy.data = data; + + reqStartTS = new Date().getTime(); + + updateXHRProperties(); + + try + { + xhrRequest.send(data); + } + catch(e) + { + // TODO: xxxpedro XHR throws or not? + //throw e; + } + finally + { + logXHR(); + + if (!spy.async) + { + self.readyState = xhrRequest.readyState; + + // sometimes an error happens when calling finishXHR() + // Issue 3422: Firebug Lite breaks Google Instant Search + try + { + finishXHR(); + } + catch(E) + { + } + } + } + }; + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + this.setRequestHeader = function(header, value) + { + spy.requestHeaders.push({name: [header], value: [value]}); + return xhrRequest.setRequestHeader(header, value); + }; + + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + this.abort = function() + { + xhrRequest.abort(); + updateSelfProperties(); + handleRequestStatus(false, "Aborted"); + }; + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + this.getResponseHeader = function(header) + { + return xhrRequest.getResponseHeader(header); + }; + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + this.getAllResponseHeaders = function() + { + return xhrRequest.getAllResponseHeaders(); + }; + + /**/ + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Clone XHR object + + // xhrRequest.open.apply not available in IE and will throw an error in + // IE6 by simply reading xhrRequest.open so we must sniff it + var supportsApply = !isIE6 && + xhrRequest && + xhrRequest.open && + typeof xhrRequest.open.apply != "undefined"; + + var numberOfXHRProperties = 0; + for (var propName in xhrRequest) + { + numberOfXHRProperties++; + + if (propName in updateSelfPropertiesIgnore) + continue; + + try + { + var propValue = xhrRequest[propName]; + + if (isFunction(propValue)) + { + if (typeof self[propName] == "undefined") + { + this[propName] = (function(name, xhr){ + + return supportsApply ? + // if the browser supports apply + function() + { + return xhr[name].apply(xhr, arguments); + } + : + function(a,b,c,d,e) + { + return xhr[name](a,b,c,d,e); + }; + + })(propName, xhrRequest); + } + } + else + this[propName] = propValue; + } + catch(E) + { + //console.log(propName, E.message); + } + } + + // IE6 does not support for (var prop in XHR) + var supportsXHRIterator = numberOfXHRProperties > 0; + + /**/ + + return this; +}; + +// ************************************************************************************************ +// ActiveXObject Wrapper (IE6 only) + +var _ActiveXObject; +var isIE6 = /msie 6/i.test(navigator.appVersion); + +if (isIE6) +{ + _ActiveXObject = window.ActiveXObject; + + var xhrObjects = " MSXML2.XMLHTTP.5.0 MSXML2.XMLHTTP.4.0 MSXML2.XMLHTTP.3.0 MSXML2.XMLHTTP Microsoft.XMLHTTP "; + + window.ActiveXObject = function(name) + { + var error = null; + + try + { + var activeXObject = new _ActiveXObject(name); + } + catch(e) + { + error = e; + } + finally + { + if (!error) + { + if (xhrObjects.indexOf(" " + name + " ") != -1) + return new XMLHttpRequestWrapper(activeXObject); + else + return activeXObject; + } + else + throw error.message; + } + }; +} + +// ************************************************************************************************ + +// Register the XMLHttpRequestWrapper for non-IE6 browsers +if (!isIE6) +{ + var _XMLHttpRequest = XMLHttpRequest; + window.XMLHttpRequest = function() + { + return new XMLHttpRequestWrapper(); + }; +} + +//************************************************************************************************ + +FBL.getNativeXHRObject = function() +{ + var xhrObj = false; + try + { + xhrObj = new _XMLHttpRequest(); + } + catch(e) + { + var progid = [ + "MSXML2.XMLHTTP.5.0", "MSXML2.XMLHTTP.4.0", + "MSXML2.XMLHTTP.3.0", "MSXML2.XMLHTTP", "Microsoft.XMLHTTP" + ]; + + for ( var i=0; i < progid.length; ++i ) { + try + { + xhrObj = new _ActiveXObject(progid[i]); + } + catch(e) + { + continue; + } + break; + } + } + finally + { + return xhrObj; + } +}; + +// ************************************************************************************************ +}}); + + +/* See license.txt for terms of usage */ + +FBL.ns(function() { with (FBL) { +// ************************************************************************************************ + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +var reIgnore = /about:|javascript:|resource:|chrome:|jar:/; +var layoutInterval = 300; +var indentWidth = 18; + +var cacheSession = null; +var contexts = new Array(); +var panelName = "net"; +var maxQueueRequests = 500; +//var panelBar1 = $("fbPanelBar1"); // chrome not available at startup +var activeRequests = []; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +var mimeExtensionMap = +{ + "txt": "text/plain", + "html": "text/html", + "htm": "text/html", + "xhtml": "text/html", + "xml": "text/xml", + "css": "text/css", + "js": "application/x-javascript", + "jss": "application/x-javascript", + "jpg": "image/jpg", + "jpeg": "image/jpeg", + "gif": "image/gif", + "png": "image/png", + "bmp": "image/bmp", + "swf": "application/x-shockwave-flash", + "flv": "video/x-flv" +}; + +var fileCategories = +{ + "undefined": 1, + "html": 1, + "css": 1, + "js": 1, + "xhr": 1, + "image": 1, + "flash": 1, + "txt": 1, + "bin": 1 +}; + +var textFileCategories = +{ + "txt": 1, + "html": 1, + "xhr": 1, + "css": 1, + "js": 1 +}; + +var binaryFileCategories = +{ + "bin": 1, + "flash": 1 +}; + +var mimeCategoryMap = +{ + "text/plain": "txt", + "application/octet-stream": "bin", + "text/html": "html", + "text/xml": "html", + "text/css": "css", + "application/x-javascript": "js", + "text/javascript": "js", + "application/javascript" : "js", + "image/jpeg": "image", + "image/jpg": "image", + "image/gif": "image", + "image/png": "image", + "image/bmp": "image", + "application/x-shockwave-flash": "flash", + "video/x-flv": "flash" +}; + +var binaryCategoryMap = +{ + "image": 1, + "flash" : 1 +}; + +// ************************************************************************************************ + +/** + * @module Represents a module object for the Net panel. This object is derived + * from <code>Firebug.ActivableModule</code> in order to support activation (enable/disable). + * This allows to avoid (performance) expensive features if the functionality is not necessary + * for the user. + */ +Firebug.NetMonitor = extend(Firebug.ActivableModule, +{ + dispatchName: "netMonitor", + + clear: function(context) + { + // The user pressed a Clear button so, remove content of the panel... + var panel = context.getPanel(panelName, true); + if (panel) + panel.clear(); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // extends Module + + initialize: function() + { + return; + + this.panelName = panelName; + + Firebug.ActivableModule.initialize.apply(this, arguments); + + if (Firebug.TraceModule) + Firebug.TraceModule.addListener(this.TraceListener); + + // HTTP observer must be registered now (and not in monitorContext, since if a + // page is opened in a new tab the top document request would be missed otherwise. + NetHttpObserver.registerObserver(); + NetHttpActivityObserver.registerObserver(); + + Firebug.Debugger.addListener(this.DebuggerListener); + }, + + shutdown: function() + { + return; + + prefs.removeObserver(Firebug.prefDomain, this, false); + if (Firebug.TraceModule) + Firebug.TraceModule.removeListener(this.TraceListener); + + NetHttpObserver.unregisterObserver(); + NetHttpActivityObserver.unregisterObserver(); + + Firebug.Debugger.removeListener(this.DebuggerListener); + } +}); + + +/** + * @domplate Represents a template that is used to reneder detailed info about a request. + * This template is rendered when a request is expanded. + */ +Firebug.NetMonitor.NetInfoBody = domplate(Firebug.Rep, new Firebug.Listener(), +{ + tag: + DIV({"class": "netInfoBody", _repObject: "$file"}, + TAG("$infoTabs", {file: "$file"}), + TAG("$infoBodies", {file: "$file"}) + ), + + infoTabs: + DIV({"class": "netInfoTabs focusRow subFocusRow", "role": "tablist"}, + A({"class": "netInfoParamsTab netInfoTab a11yFocus", onclick: "$onClickTab", "role": "tab", + view: "Params", + $collapsed: "$file|hideParams"}, + $STR("URLParameters") + ), + A({"class": "netInfoHeadersTab netInfoTab a11yFocus", onclick: "$onClickTab", "role": "tab", + view: "Headers"}, + $STR("Headers") + ), + A({"class": "netInfoPostTab netInfoTab a11yFocus", onclick: "$onClickTab", "role": "tab", + view: "Post", + $collapsed: "$file|hidePost"}, + $STR("Post") + ), + A({"class": "netInfoPutTab netInfoTab a11yFocus", onclick: "$onClickTab", "role": "tab", + view: "Put", + $collapsed: "$file|hidePut"}, + $STR("Put") + ), + A({"class": "netInfoResponseTab netInfoTab a11yFocus", onclick: "$onClickTab", "role": "tab", + view: "Response", + $collapsed: "$file|hideResponse"}, + $STR("Response") + ), + A({"class": "netInfoCacheTab netInfoTab a11yFocus", onclick: "$onClickTab", "role": "tab", + view: "Cache", + $collapsed: "$file|hideCache"}, + $STR("Cache") + ), + A({"class": "netInfoHtmlTab netInfoTab a11yFocus", onclick: "$onClickTab", "role": "tab", + view: "Html", + $collapsed: "$file|hideHtml"}, + $STR("HTML") + ) + ), + + infoBodies: + DIV({"class": "netInfoBodies outerFocusRow"}, + TABLE({"class": "netInfoParamsText netInfoText netInfoParamsTable", "role": "tabpanel", + cellpadding: 0, cellspacing: 0}, TBODY()), + DIV({"class": "netInfoHeadersText netInfoText", "role": "tabpanel"}), + DIV({"class": "netInfoPostText netInfoText", "role": "tabpanel"}), + DIV({"class": "netInfoPutText netInfoText", "role": "tabpanel"}), + PRE({"class": "netInfoResponseText netInfoText", "role": "tabpanel"}), + DIV({"class": "netInfoCacheText netInfoText", "role": "tabpanel"}, + TABLE({"class": "netInfoCacheTable", cellpadding: 0, cellspacing: 0, "role": "presentation"}, + TBODY({"role": "list", "aria-label": $STR("Cache")}) + ) + ), + DIV({"class": "netInfoHtmlText netInfoText", "role": "tabpanel"}, + IFRAME({"class": "netInfoHtmlPreview", "role": "document"}) + ) + ), + + headerDataTag: + FOR("param", "$headers", + TR({"role": "listitem"}, + TD({"class": "netInfoParamName", "role": "presentation"}, + TAG("$param|getNameTag", {param: "$param"}) + ), + TD({"class": "netInfoParamValue", "role": "list", "aria-label": "$param.name"}, + FOR("line", "$param|getParamValueIterator", + CODE({"class": "focusRow subFocusRow", "role": "listitem"}, "$line") + ) + ) + ) + ), + + customTab: + A({"class": "netInfo$tabId\\Tab netInfoTab", onclick: "$onClickTab", view: "$tabId", "role": "tab"}, + "$tabTitle" + ), + + customBody: + DIV({"class": "netInfo$tabId\\Text netInfoText", "role": "tabpanel"}), + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + nameTag: + SPAN("$param|getParamName"), + + nameWithTooltipTag: + SPAN({title: "$param.name"}, "$param|getParamName"), + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + getNameTag: function(param) + { + return (this.getParamName(param) == param.name) ? this.nameTag : this.nameWithTooltipTag; + }, + + getParamName: function(param) + { + var limit = 25; + var name = param.name; + if (name.length > limit) + name = name.substr(0, limit) + "..."; + return name; + }, + + getParamTitle: function(param) + { + var limit = 25; + var name = param.name; + if (name.length > limit) + return name; + return ""; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + hideParams: function(file) + { + return !file.urlParams || !file.urlParams.length; + }, + + hidePost: function(file) + { + return file.method.toUpperCase() != "POST"; + }, + + hidePut: function(file) + { + return file.method.toUpperCase() != "PUT"; + }, + + hideResponse: function(file) + { + return false; + //return file.category in binaryFileCategories; + }, + + hideCache: function(file) + { + return true; + //xxxHonza: I don't see any reason why not to display the cache also info for images. + return !file.cacheEntry; // || file.category=="image"; + }, + + hideHtml: function(file) + { + return (file.mimeType != "text/html") && (file.mimeType != "application/xhtml+xml"); + }, + + onClickTab: function(event) + { + this.selectTab(event.currentTarget || event.srcElement); + }, + + getParamValueIterator: function(param) + { + // TODO: xxxpedro console2 + return param.value; + + // This value is inserted into CODE element and so, make sure the HTML isn't escaped (1210). + // This is why the second parameter is true. + // The CODE (with style white-space:pre) element preserves whitespaces so they are + // displayed the same, as they come from the server (1194). + // In case of a long header values of post parameters the value must be wrapped (2105). + return wrapText(param.value, true); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + appendTab: function(netInfoBox, tabId, tabTitle) + { + // Create new tab and body. + var args = {tabId: tabId, tabTitle: tabTitle}; + ///this.customTab.append(args, netInfoBox.getElementsByClassName("netInfoTabs").item(0)); + ///this.customBody.append(args, netInfoBox.getElementsByClassName("netInfoBodies").item(0)); + this.customTab.append(args, $$(".netInfoTabs", netInfoBox)[0]); + this.customBody.append(args, $$(".netInfoBodies", netInfoBox)[0]); + }, + + selectTabByName: function(netInfoBox, tabName) + { + var tab = getChildByClass(netInfoBox, "netInfoTabs", "netInfo"+tabName+"Tab"); + if (tab) + this.selectTab(tab); + }, + + selectTab: function(tab) + { + var view = tab.getAttribute("view"); + + var netInfoBox = getAncestorByClass(tab, "netInfoBody"); + + var selectedTab = netInfoBox.selectedTab; + + if (selectedTab) + { + //netInfoBox.selectedText.removeAttribute("selected"); + removeClass(netInfoBox.selectedText, "netInfoTextSelected"); + + removeClass(selectedTab, "netInfoTabSelected"); + //selectedTab.removeAttribute("selected"); + selectedTab.setAttribute("aria-selected", "false"); + } + + var textBodyName = "netInfo" + view + "Text"; + + selectedTab = netInfoBox.selectedTab = tab; + + netInfoBox.selectedText = $$("."+textBodyName, netInfoBox)[0]; + //netInfoBox.selectedText = netInfoBox.getElementsByClassName(textBodyName).item(0); + + //netInfoBox.selectedText.setAttribute("selected", "true"); + setClass(netInfoBox.selectedText, "netInfoTextSelected"); + + setClass(selectedTab, "netInfoTabSelected"); + selectedTab.setAttribute("selected", "true"); + selectedTab.setAttribute("aria-selected", "true"); + + var file = Firebug.getRepObject(netInfoBox); + + //var context = Firebug.getElementPanel(netInfoBox).context; + var context = Firebug.chrome; + + this.updateInfo(netInfoBox, file, context); + }, + + updateInfo: function(netInfoBox, file, context) + { + if (FBTrace.DBG_NET) + FBTrace.sysout("net.updateInfo; file", file); + + if (!netInfoBox) + { + if (FBTrace.DBG_NET || FBTrace.DBG_ERRORS) + FBTrace.sysout("net.updateInfo; ERROR netInfo == null " + file.href, file); + return; + } + + var tab = netInfoBox.selectedTab; + + if (hasClass(tab, "netInfoParamsTab")) + { + if (file.urlParams && !netInfoBox.urlParamsPresented) + { + netInfoBox.urlParamsPresented = true; + this.insertHeaderRows(netInfoBox, file.urlParams, "Params"); + } + } + + else if (hasClass(tab, "netInfoHeadersTab")) + { + var headersText = $$(".netInfoHeadersText", netInfoBox)[0]; + //var headersText = netInfoBox.getElementsByClassName("netInfoHeadersText").item(0); + + if (file.responseHeaders && !netInfoBox.responseHeadersPresented) + { + netInfoBox.responseHeadersPresented = true; + NetInfoHeaders.renderHeaders(headersText, file.responseHeaders, "ResponseHeaders"); + } + + if (file.requestHeaders && !netInfoBox.requestHeadersPresented) + { + netInfoBox.requestHeadersPresented = true; + NetInfoHeaders.renderHeaders(headersText, file.requestHeaders, "RequestHeaders"); + } + } + + else if (hasClass(tab, "netInfoPostTab")) + { + if (!netInfoBox.postPresented) + { + netInfoBox.postPresented = true; + //var postText = netInfoBox.getElementsByClassName("netInfoPostText").item(0); + var postText = $$(".netInfoPostText", netInfoBox)[0]; + NetInfoPostData.render(context, postText, file); + } + } + + else if (hasClass(tab, "netInfoPutTab")) + { + if (!netInfoBox.putPresented) + { + netInfoBox.putPresented = true; + //var putText = netInfoBox.getElementsByClassName("netInfoPutText").item(0); + var putText = $$(".netInfoPutText", netInfoBox)[0]; + NetInfoPostData.render(context, putText, file); + } + } + + else if (hasClass(tab, "netInfoResponseTab") && file.loaded && !netInfoBox.responsePresented) + { + ///var responseTextBox = netInfoBox.getElementsByClassName("netInfoResponseText").item(0); + var responseTextBox = $$(".netInfoResponseText", netInfoBox)[0]; + if (file.category == "image") + { + netInfoBox.responsePresented = true; + + var responseImage = netInfoBox.ownerDocument.createElement("img"); + responseImage.src = file.href; + + clearNode(responseTextBox); + responseTextBox.appendChild(responseImage, responseTextBox); + } + else ///if (!(binaryCategoryMap.hasOwnProperty(file.category))) + { + this.setResponseText(file, netInfoBox, responseTextBox, context); + } + } + + else if (hasClass(tab, "netInfoCacheTab") && file.loaded && !netInfoBox.cachePresented) + { + var responseTextBox = netInfoBox.getElementsByClassName("netInfoCacheText").item(0); + if (file.cacheEntry) { + netInfoBox.cachePresented = true; + this.insertHeaderRows(netInfoBox, file.cacheEntry, "Cache"); + } + } + + else if (hasClass(tab, "netInfoHtmlTab") && file.loaded && !netInfoBox.htmlPresented) + { + netInfoBox.htmlPresented = true; + + var text = Utils.getResponseText(file, context); + + ///var iframe = netInfoBox.getElementsByClassName("netInfoHtmlPreview").item(0); + var iframe = $$(".netInfoHtmlPreview", netInfoBox)[0]; + + ///iframe.contentWindow.document.body.innerHTML = text; + + // TODO: xxxpedro net - remove scripts + var reScript = /<script(.|\s)*?\/script>/gi; + + text = text.replace(reScript, ""); + + iframe.contentWindow.document.write(text); + iframe.contentWindow.document.close(); + } + + // Notify listeners about update so, content of custom tabs can be updated. + dispatch(NetInfoBody.fbListeners, "updateTabBody", [netInfoBox, file, context]); + }, + + setResponseText: function(file, netInfoBox, responseTextBox, context) + { + //********************************************** + //********************************************** + //********************************************** + netInfoBox.responsePresented = true; + // line breaks somehow are different in IE + // make this only once in the initialization? we don't have net panels and modules yet. + if (isIE) + responseTextBox.style.whiteSpace = "nowrap"; + + responseTextBox[ + typeof responseTextBox.textContent != "undefined" ? + "textContent" : + "innerText" + ] = file.responseText; + + return; + //********************************************** + //********************************************** + //********************************************** + + // Get response text and make sure it doesn't exceed the max limit. + var text = Utils.getResponseText(file, context); + var limit = Firebug.netDisplayedResponseLimit + 15; + var limitReached = text ? (text.length > limit) : false; + if (limitReached) + text = text.substr(0, limit) + "..."; + + // Insert the response into the UI. + if (text) + insertWrappedText(text, responseTextBox); + else + insertWrappedText("", responseTextBox); + + // Append a message informing the user that the response isn't fully displayed. + if (limitReached) + { + var object = { + text: $STR("net.responseSizeLimitMessage"), + onClickLink: function() { + var panel = context.getPanel("net", true); + panel.openResponseInTab(file); + } + }; + Firebug.NetMonitor.ResponseSizeLimit.append(object, responseTextBox); + } + + netInfoBox.responsePresented = true; + + if (FBTrace.DBG_NET) + FBTrace.sysout("net.setResponseText; response text updated"); + }, + + insertHeaderRows: function(netInfoBox, headers, tableName, rowName) + { + if (!headers.length) + return; + + var headersTable = $$(".netInfo"+tableName+"Table", netInfoBox)[0]; + //var headersTable = netInfoBox.getElementsByClassName("netInfo"+tableName+"Table").item(0); + var tbody = getChildByClass(headersTable, "netInfo" + rowName + "Body"); + if (!tbody) + tbody = headersTable.firstChild; + var titleRow = getChildByClass(tbody, "netInfo" + rowName + "Title"); + + this.headerDataTag.insertRows({headers: headers}, titleRow ? titleRow : tbody); + removeClass(titleRow, "collapsed"); + } +}); + +var NetInfoBody = Firebug.NetMonitor.NetInfoBody; + +// ************************************************************************************************ + +/** + * @domplate Used within the Net panel to display raw source of request and response headers + * as well as pretty-formatted summary of these headers. + */ +Firebug.NetMonitor.NetInfoHeaders = domplate(Firebug.Rep, //new Firebug.Listener(), +{ + tag: + DIV({"class": "netInfoHeadersTable", "role": "tabpanel"}, + DIV({"class": "netInfoHeadersGroup netInfoResponseHeadersTitle"}, + SPAN($STR("ResponseHeaders")), + SPAN({"class": "netHeadersViewSource response collapsed", onclick: "$onViewSource", + _sourceDisplayed: false, _rowName: "ResponseHeaders"}, + $STR("net.headers.view source") + ) + ), + TABLE({cellpadding: 0, cellspacing: 0}, + TBODY({"class": "netInfoResponseHeadersBody", "role": "list", + "aria-label": $STR("ResponseHeaders")}) + ), + DIV({"class": "netInfoHeadersGroup netInfoRequestHeadersTitle"}, + SPAN($STR("RequestHeaders")), + SPAN({"class": "netHeadersViewSource request collapsed", onclick: "$onViewSource", + _sourceDisplayed: false, _rowName: "RequestHeaders"}, + $STR("net.headers.view source") + ) + ), + TABLE({cellpadding: 0, cellspacing: 0}, + TBODY({"class": "netInfoRequestHeadersBody", "role": "list", + "aria-label": $STR("RequestHeaders")}) + ) + ), + + sourceTag: + TR({"role": "presentation"}, + TD({colspan: 2, "role": "presentation"}, + PRE({"class": "source"}) + ) + ), + + onViewSource: function(event) + { + var target = event.target; + var requestHeaders = (target.rowName == "RequestHeaders"); + + var netInfoBox = getAncestorByClass(target, "netInfoBody"); + var file = netInfoBox.repObject; + + if (target.sourceDisplayed) + { + var headers = requestHeaders ? file.requestHeaders : file.responseHeaders; + this.insertHeaderRows(netInfoBox, headers, target.rowName); + target.innerHTML = $STR("net.headers.view source"); + } + else + { + var source = requestHeaders ? file.requestHeadersText : file.responseHeadersText; + this.insertSource(netInfoBox, source, target.rowName); + target.innerHTML = $STR("net.headers.pretty print"); + } + + target.sourceDisplayed = !target.sourceDisplayed; + + cancelEvent(event); + }, + + insertSource: function(netInfoBox, source, rowName) + { + // This breaks copy to clipboard. + //if (source) + // source = source.replace(/\r\n/gm, "<span style='color:lightgray'>\\r\\n</span>\r\n"); + + ///var tbody = netInfoBox.getElementsByClassName("netInfo" + rowName + "Body").item(0); + var tbody = $$(".netInfo" + rowName + "Body", netInfoBox)[0]; + var node = this.sourceTag.replace({}, tbody); + ///var sourceNode = node.getElementsByClassName("source").item(0); + var sourceNode = $$(".source", node)[0]; + sourceNode.innerHTML = source; + }, + + insertHeaderRows: function(netInfoBox, headers, rowName) + { + var headersTable = $$(".netInfoHeadersTable", netInfoBox)[0]; + var tbody = $$(".netInfo" + rowName + "Body", headersTable)[0]; + + //var headersTable = netInfoBox.getElementsByClassName("netInfoHeadersTable").item(0); + //var tbody = headersTable.getElementsByClassName("netInfo" + rowName + "Body").item(0); + + clearNode(tbody); + + if (!headers.length) + return; + + NetInfoBody.headerDataTag.insertRows({headers: headers}, tbody); + + var titleRow = getChildByClass(headersTable, "netInfo" + rowName + "Title"); + removeClass(titleRow, "collapsed"); + }, + + init: function(parent) + { + var rootNode = this.tag.append({}, parent); + + var netInfoBox = getAncestorByClass(parent, "netInfoBody"); + var file = netInfoBox.repObject; + + var viewSource; + + viewSource = $$(".request", rootNode)[0]; + //viewSource = rootNode.getElementsByClassName("netHeadersViewSource request").item(0); + if (file.requestHeadersText) + removeClass(viewSource, "collapsed"); + + viewSource = $$(".response", rootNode)[0]; + //viewSource = rootNode.getElementsByClassName("netHeadersViewSource response").item(0); + if (file.responseHeadersText) + removeClass(viewSource, "collapsed"); + }, + + renderHeaders: function(parent, headers, rowName) + { + if (!parent.firstChild) + this.init(parent); + + this.insertHeaderRows(parent, headers, rowName); + } +}); + +var NetInfoHeaders = Firebug.NetMonitor.NetInfoHeaders; + +// ************************************************************************************************ + +/** + * @domplate Represents posted data within request info (the info, which is visible when + * a request entry is expanded. This template renders content of the Post tab. + */ +Firebug.NetMonitor.NetInfoPostData = domplate(Firebug.Rep, /*new Firebug.Listener(),*/ +{ + // application/x-www-form-urlencoded + paramsTable: + TABLE({"class": "netInfoPostParamsTable", cellpadding: 0, cellspacing: 0, "role": "presentation"}, + TBODY({"role": "list", "aria-label": $STR("net.label.Parameters")}, + TR({"class": "netInfoPostParamsTitle", "role": "presentation"}, + TD({colspan: 3, "role": "presentation"}, + DIV({"class": "netInfoPostParams"}, + $STR("net.label.Parameters"), + SPAN({"class": "netInfoPostContentType"}, + "application/x-www-form-urlencoded" + ) + ) + ) + ) + ) + ), + + // multipart/form-data + partsTable: + TABLE({"class": "netInfoPostPartsTable", cellpadding: 0, cellspacing: 0, "role": "presentation"}, + TBODY({"role": "list", "aria-label": $STR("net.label.Parts")}, + TR({"class": "netInfoPostPartsTitle", "role": "presentation"}, + TD({colspan: 2, "role":"presentation" }, + DIV({"class": "netInfoPostParams"}, + $STR("net.label.Parts"), + SPAN({"class": "netInfoPostContentType"}, + "multipart/form-data" + ) + ) + ) + ) + ) + ), + + // application/json + jsonTable: + TABLE({"class": "netInfoPostJSONTable", cellpadding: 0, cellspacing: 0, "role": "presentation"}, + ///TBODY({"role": "list", "aria-label": $STR("jsonviewer.tab.JSON")}, + TBODY({"role": "list", "aria-label": $STR("JSON")}, + TR({"class": "netInfoPostJSONTitle", "role": "presentation"}, + TD({"role": "presentation" }, + DIV({"class": "netInfoPostParams"}, + ///$STR("jsonviewer.tab.JSON") + $STR("JSON") + ) + ) + ), + TR( + TD({"class": "netInfoPostJSONBody"}) + ) + ) + ), + + // application/xml + xmlTable: + TABLE({"class": "netInfoPostXMLTable", cellpadding: 0, cellspacing: 0, "role": "presentation"}, + TBODY({"role": "list", "aria-label": $STR("xmlviewer.tab.XML")}, + TR({"class": "netInfoPostXMLTitle", "role": "presentation"}, + TD({"role": "presentation" }, + DIV({"class": "netInfoPostParams"}, + $STR("xmlviewer.tab.XML") + ) + ) + ), + TR( + TD({"class": "netInfoPostXMLBody"}) + ) + ) + ), + + sourceTable: + TABLE({"class": "netInfoPostSourceTable", cellpadding: 0, cellspacing: 0, "role": "presentation"}, + TBODY({"role": "list", "aria-label": $STR("net.label.Source")}, + TR({"class": "netInfoPostSourceTitle", "role": "presentation"}, + TD({colspan: 2, "role": "presentation"}, + DIV({"class": "netInfoPostSource"}, + $STR("net.label.Source") + ) + ) + ) + ) + ), + + sourceBodyTag: + TR({"role": "presentation"}, + TD({colspan: 2, "role": "presentation"}, + FOR("line", "$param|getParamValueIterator", + CODE({"class":"focusRow subFocusRow" , "role": "listitem"},"$line") + ) + ) + ), + + getParamValueIterator: function(param) + { + return NetInfoBody.getParamValueIterator(param); + }, + + render: function(context, parentNode, file) + { + //debugger; + var spy = getAncestorByClass(parentNode, "spyHead"); + var spyObject = spy.repObject; + var data = spyObject.data; + + ///var contentType = Utils.findHeader(file.requestHeaders, "content-type"); + var contentType = file.mimeType; + + ///var text = Utils.getPostText(file, context, true); + ///if (text == undefined) + /// return; + + ///if (Utils.isURLEncodedRequest(file, context)) + // fake Utils.isURLEncodedRequest identification + if (contentType && contentType == "application/x-www-form-urlencoded" || + data && data.indexOf("=") != -1) + { + ///var lines = text.split("\n"); + ///var params = parseURLEncodedText(lines[lines.length-1]); + var params = parseURLEncodedTextArray(data); + if (params) + this.insertParameters(parentNode, params); + } + + ///if (Utils.isMultiPartRequest(file, context)) + ///{ + /// var data = this.parseMultiPartText(file, context); + /// if (data) + /// this.insertParts(parentNode, data); + ///} + + // moved to the top + ///var contentType = Utils.findHeader(file.requestHeaders, "content-type"); + + ///if (Firebug.JSONViewerModel.isJSON(contentType)) + var jsonData = { + responseText: data + }; + + if (Firebug.JSONViewerModel.isJSON(contentType, data)) + ///this.insertJSON(parentNode, file, context); + this.insertJSON(parentNode, jsonData, context); + + ///if (Firebug.XMLViewerModel.isXML(contentType)) + /// this.insertXML(parentNode, file, context); + + ///var postText = Utils.getPostText(file, context); + ///postText = Utils.formatPostText(postText); + var postText = data; + if (postText) + this.insertSource(parentNode, postText); + }, + + insertParameters: function(parentNode, params) + { + if (!params || !params.length) + return; + + var paramTable = this.paramsTable.append({object:{}}, parentNode); + var row = $$(".netInfoPostParamsTitle", paramTable)[0]; + //var paramTable = this.paramsTable.append(null, parentNode); + //var row = paramTable.getElementsByClassName("netInfoPostParamsTitle").item(0); + + var tbody = paramTable.getElementsByTagName("tbody")[0]; + + NetInfoBody.headerDataTag.insertRows({headers: params}, row); + }, + + insertParts: function(parentNode, data) + { + if (!data.params || !data.params.length) + return; + + var partsTable = this.partsTable.append({object:{}}, parentNode); + var row = $$(".netInfoPostPartsTitle", paramTable)[0]; + //var partsTable = this.partsTable.append(null, parentNode); + //var row = partsTable.getElementsByClassName("netInfoPostPartsTitle").item(0); + + NetInfoBody.headerDataTag.insertRows({headers: data.params}, row); + }, + + insertJSON: function(parentNode, file, context) + { + ///var text = Utils.getPostText(file, context); + var text = file.responseText; + ///var data = parseJSONString(text, "http://" + file.request.originalURI.host); + var data = parseJSONString(text); + if (!data) + return; + + ///var jsonTable = this.jsonTable.append(null, parentNode); + var jsonTable = this.jsonTable.append({}, parentNode); + ///var jsonBody = jsonTable.getElementsByClassName("netInfoPostJSONBody").item(0); + var jsonBody = $$(".netInfoPostJSONBody", jsonTable)[0]; + + if (!this.toggles) + this.toggles = {}; + + Firebug.DOMPanel.DirTable.tag.replace( + {object: data, toggles: this.toggles}, jsonBody); + }, + + insertXML: function(parentNode, file, context) + { + var text = Utils.getPostText(file, context); + + var jsonTable = this.xmlTable.append(null, parentNode); + ///var jsonBody = jsonTable.getElementsByClassName("netInfoPostXMLBody").item(0); + var jsonBody = $$(".netInfoPostXMLBody", jsonTable)[0]; + + Firebug.XMLViewerModel.insertXML(jsonBody, text); + }, + + insertSource: function(parentNode, text) + { + var sourceTable = this.sourceTable.append({object:{}}, parentNode); + var row = $$(".netInfoPostSourceTitle", sourceTable)[0]; + //var sourceTable = this.sourceTable.append(null, parentNode); + //var row = sourceTable.getElementsByClassName("netInfoPostSourceTitle").item(0); + + var param = {value: [text]}; + this.sourceBodyTag.insertRows({param: param}, row); + }, + + parseMultiPartText: function(file, context) + { + var text = Utils.getPostText(file, context); + if (text == undefined) + return null; + + FBTrace.sysout("net.parseMultiPartText; boundary: ", text); + + var boundary = text.match(/\s*boundary=\s*(.*)/)[1]; + + var divider = "\r\n\r\n"; + var bodyStart = text.indexOf(divider); + var body = text.substr(bodyStart + divider.length); + + var postData = {}; + postData.mimeType = "multipart/form-data"; + postData.params = []; + + var parts = body.split("--" + boundary); + for (var i=0; i<parts.length; i++) + { + var part = parts[i].split(divider); + if (part.length != 2) + continue; + + var m = part[0].match(/\s*name=\"(.*)\"(;|$)/); + postData.params.push({ + name: (m && m.length > 1) ? m[1] : "", + value: trim(part[1]) + }); + } + + return postData; + } +}); + +var NetInfoPostData = Firebug.NetMonitor.NetInfoPostData; + +// ************************************************************************************************ + + +// TODO: xxxpedro net i18n +var $STRP = function(a){return a;}; + +Firebug.NetMonitor.NetLimit = domplate(Firebug.Rep, +{ + collapsed: true, + + tableTag: + DIV( + TABLE({width: "100%", cellpadding: 0, cellspacing: 0}, + TBODY() + ) + ), + + limitTag: + TR({"class": "netRow netLimitRow", $collapsed: "$isCollapsed"}, + TD({"class": "netCol netLimitCol", colspan: 6}, + TABLE({cellpadding: 0, cellspacing: 0}, + TBODY( + TR( + TD( + SPAN({"class": "netLimitLabel"}, + $STRP("plural.Limit_Exceeded", [0]) + ) + ), + TD({style: "width:100%"}), + TD( + BUTTON({"class": "netLimitButton", title: "$limitPrefsTitle", + onclick: "$onPreferences"}, + $STR("LimitPrefs") + ) + ), + TD(" ") + ) + ) + ) + ) + ), + + isCollapsed: function() + { + return this.collapsed; + }, + + onPreferences: function(event) + { + openNewTab("about:config"); + }, + + updateCounter: function(row) + { + removeClass(row, "collapsed"); + + // Update info within the limit row. + var limitLabel = row.getElementsByClassName("netLimitLabel").item(0); + limitLabel.firstChild.nodeValue = $STRP("plural.Limit_Exceeded", [row.limitInfo.totalCount]); + }, + + createTable: function(parent, limitInfo) + { + var table = this.tableTag.replace({}, parent); + var row = this.createRow(table.firstChild.firstChild, limitInfo); + return [table, row]; + }, + + createRow: function(parent, limitInfo) + { + var row = this.limitTag.insertRows(limitInfo, parent, this)[0]; + row.limitInfo = limitInfo; + return row; + }, + + // nsIPrefObserver + observe: function(subject, topic, data) + { + // We're observing preferences only. + if (topic != "nsPref:changed") + return; + + if (data.indexOf("net.logLimit") != -1) + this.updateMaxLimit(); + }, + + updateMaxLimit: function() + { + var value = Firebug.getPref(Firebug.prefDomain, "net.logLimit"); + maxQueueRequests = value ? value : maxQueueRequests; + } +}); + +var NetLimit = Firebug.NetMonitor.NetLimit; + +// ************************************************************************************************ + +Firebug.NetMonitor.ResponseSizeLimit = domplate(Firebug.Rep, +{ + tag: + DIV({"class": "netInfoResponseSizeLimit"}, + SPAN("$object.beforeLink"), + A({"class": "objectLink", onclick: "$onClickLink"}, + "$object.linkText" + ), + SPAN("$object.afterLink") + ), + + reLink: /^(.*)<a>(.*)<\/a>(.*$)/, + append: function(obj, parent) + { + var m = obj.text.match(this.reLink); + return this.tag.append({onClickLink: obj.onClickLink, + object: { + beforeLink: m[1], + linkText: m[2], + afterLink: m[3] + }}, parent, this); + } +}); + +// ************************************************************************************************ +// ************************************************************************************************ + +Firebug.NetMonitor.Utils = +{ + findHeader: function(headers, name) + { + if (!headers) + return null; + + name = name.toLowerCase(); + for (var i = 0; i < headers.length; ++i) + { + var headerName = headers[i].name.toLowerCase(); + if (headerName == name) + return headers[i].value; + } + }, + + formatPostText: function(text) + { + if (text instanceof XMLDocument) + return getElementXML(text.documentElement); + else + return text; + }, + + getPostText: function(file, context, noLimit) + { + if (!file.postText) + { + file.postText = readPostTextFromRequest(file.request, context); + + if (!file.postText && context) + file.postText = readPostTextFromPage(file.href, context); + } + + if (!file.postText) + return file.postText; + + var limit = Firebug.netDisplayedPostBodyLimit; + if (file.postText.length > limit && !noLimit) + { + return cropString(file.postText, limit, + "\n\n... " + $STR("net.postDataSizeLimitMessage") + " ...\n\n"); + } + + return file.postText; + }, + + getResponseText: function(file, context) + { + // The response can be also empty string so, check agains "undefined". + return (typeof(file.responseText) != "undefined")? file.responseText : + context.sourceCache.loadText(file.href, file.method, file); + }, + + isURLEncodedRequest: function(file, context) + { + var text = Utils.getPostText(file, context); + if (text && text.toLowerCase().indexOf("content-type: application/x-www-form-urlencoded") == 0) + return true; + + // The header value doesn't have to be always exactly "application/x-www-form-urlencoded", + // there can be even charset specified. So, use indexOf rather than just "==". + var headerValue = Utils.findHeader(file.requestHeaders, "content-type"); + if (headerValue && headerValue.indexOf("application/x-www-form-urlencoded") == 0) + return true; + + return false; + }, + + isMultiPartRequest: function(file, context) + { + var text = Utils.getPostText(file, context); + if (text && text.toLowerCase().indexOf("content-type: multipart/form-data") == 0) + return true; + return false; + }, + + getMimeType: function(mimeType, uri) + { + if (!mimeType || !(mimeCategoryMap.hasOwnProperty(mimeType))) + { + var ext = getFileExtension(uri); + if (!ext) + return mimeType; + else + { + var extMimeType = mimeExtensionMap[ext.toLowerCase()]; + return extMimeType ? extMimeType : mimeType; + } + } + else + return mimeType; + }, + + getDateFromSeconds: function(s) + { + var d = new Date(); + d.setTime(s*1000); + return d; + }, + + getHttpHeaders: function(request, file) + { + try + { + var http = QI(request, Ci.nsIHttpChannel); + file.status = request.responseStatus; + + // xxxHonza: is there any problem to do this in requestedFile method? + file.method = http.requestMethod; + file.urlParams = parseURLParams(file.href); + file.mimeType = Utils.getMimeType(request.contentType, request.name); + + if (!file.responseHeaders && Firebug.collectHttpHeaders) + { + var requestHeaders = [], responseHeaders = []; + + http.visitRequestHeaders({ + visitHeader: function(name, value) + { + requestHeaders.push({name: name, value: value}); + } + }); + http.visitResponseHeaders({ + visitHeader: function(name, value) + { + responseHeaders.push({name: name, value: value}); + } + }); + + file.requestHeaders = requestHeaders; + file.responseHeaders = responseHeaders; + } + } + catch (exc) + { + // An exception can be throwed e.g. when the request is aborted and + // request.responseStatus is accessed. + if (FBTrace.DBG_ERRORS) + FBTrace.sysout("net.getHttpHeaders FAILS " + file.href, exc); + } + }, + + isXHR: function(request) + { + try + { + var callbacks = request.notificationCallbacks; + var xhrRequest = callbacks ? callbacks.getInterface(Ci.nsIXMLHttpRequest) : null; + if (FBTrace.DBG_NET) + FBTrace.sysout("net.isXHR; " + (xhrRequest != null) + ", " + safeGetName(request)); + + return (xhrRequest != null); + } + catch (exc) + { + } + + return false; + }, + + getFileCategory: function(file) + { + if (file.category) + { + if (FBTrace.DBG_NET) + FBTrace.sysout("net.getFileCategory; current: " + file.category + " for: " + file.href, file); + return file.category; + } + + if (file.isXHR) + { + if (FBTrace.DBG_NET) + FBTrace.sysout("net.getFileCategory; XHR for: " + file.href, file); + return file.category = "xhr"; + } + + if (!file.mimeType) + { + var ext = getFileExtension(file.href); + if (ext) + file.mimeType = mimeExtensionMap[ext.toLowerCase()]; + } + + /*if (FBTrace.DBG_NET) + FBTrace.sysout("net.getFileCategory; " + mimeCategoryMap[file.mimeType] + + ", mimeType: " + file.mimeType + " for: " + file.href, file);*/ + + if (!file.mimeType) + return ""; + + // Solve cases when charset is also specified, eg "text/html; charset=UTF-8". + var mimeType = file.mimeType; + if (mimeType) + mimeType = mimeType.split(";")[0]; + + return (file.category = mimeCategoryMap[mimeType]); + } +}; + +var Utils = Firebug.NetMonitor.Utils; + +// ************************************************************************************************ + +//Firebug.registerRep(Firebug.NetMonitor.NetRequestTable); +//Firebug.registerActivableModule(Firebug.NetMonitor); +//Firebug.registerPanel(NetPanel); + +Firebug.registerModule(Firebug.NetMonitor); +//Firebug.registerRep(Firebug.NetMonitor.BreakpointRep); + +// ************************************************************************************************ +}}); + + +/* See license.txt for terms of usage */ + +FBL.ns(function() { with (FBL) { + +// ************************************************************************************************ +// Constants + +//const Cc = Components.classes; +//const Ci = Components.interfaces; + +// List of contexts with XHR spy attached. +var contexts = []; + +// ************************************************************************************************ +// Spy Module + +/** + * @module Represents a XHR Spy module. The main purpose of the XHR Spy feature is to monitor + * XHR activity of the current page and create appropriate log into the Console panel. + * This feature can be controlled by an option <i>Show XMLHttpRequests</i> (from within the + * console panel). + * + * The module is responsible for attaching/detaching a HTTP Observers when Firebug is + * activated/deactivated for a site. + */ +Firebug.Spy = extend(Firebug.Module, +/** @lends Firebug.Spy */ +{ + dispatchName: "spy", + + initialize: function() + { + if (Firebug.TraceModule) + Firebug.TraceModule.addListener(this.TraceListener); + + Firebug.Module.initialize.apply(this, arguments); + }, + + shutdown: function() + { + Firebug.Module.shutdown.apply(this, arguments); + + if (Firebug.TraceModule) + Firebug.TraceModule.removeListener(this.TraceListener); + }, + + initContext: function(context) + { + context.spies = []; + + if (Firebug.showXMLHttpRequests && Firebug.Console.isAlwaysEnabled()) + this.attachObserver(context, context.window); + + if (FBTrace.DBG_SPY) + FBTrace.sysout("spy.initContext " + contexts.length + " ", context.getName()); + }, + + destroyContext: function(context) + { + // For any spies that are in progress, remove our listeners so that they don't leak + this.detachObserver(context, null); + + if (FBTrace.DBG_SPY && context.spies.length) + FBTrace.sysout("spy.destroyContext; ERROR There are leaking Spies (" + + context.spies.length + ") " + context.getName()); + + delete context.spies; + + if (FBTrace.DBG_SPY) + FBTrace.sysout("spy.destroyContext " + contexts.length + " ", context.getName()); + }, + + watchWindow: function(context, win) + { + if (Firebug.showXMLHttpRequests && Firebug.Console.isAlwaysEnabled()) + this.attachObserver(context, win); + }, + + unwatchWindow: function(context, win) + { + try + { + // This make sure that the existing context is properly removed from "contexts" array. + this.detachObserver(context, win); + } + catch (ex) + { + // Get exceptions here sometimes, so let's just ignore them + // since the window is going away anyhow + ERROR(ex); + } + }, + + updateOption: function(name, value) + { + // XXXjjb Honza, if Console.isEnabled(context) false, then this can't be called, + // but somehow seems not correct + if (name == "showXMLHttpRequests") + { + var tach = value ? this.attachObserver : this.detachObserver; + for (var i = 0; i < TabWatcher.contexts.length; ++i) + { + var context = TabWatcher.contexts[i]; + iterateWindows(context.window, function(win) + { + tach.apply(this, [context, win]); + }); + } + } + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Attaching Spy to XHR requests. + + /** + * Returns false if Spy should not be attached to XHRs executed by the specified window. + */ + skipSpy: function(win) + { + if (!win) + return true; + + // Don't attach spy to chrome. + var uri = safeGetWindowLocation(win); + if (uri && (uri.indexOf("about:") == 0 || uri.indexOf("chrome:") == 0)) + return true; + }, + + attachObserver: function(context, win) + { + if (Firebug.Spy.skipSpy(win)) + return; + + for (var i=0; i<contexts.length; ++i) + { + if ((contexts[i].context == context) && (contexts[i].win == win)) + return; + } + + // Register HTTP observers only once. + if (contexts.length == 0) + { + httpObserver.addObserver(SpyHttpObserver, "firebug-http-event", false); + SpyHttpActivityObserver.registerObserver(); + } + + contexts.push({context: context, win: win}); + + if (FBTrace.DBG_SPY) + FBTrace.sysout("spy.attachObserver (HTTP) " + contexts.length + " ", context.getName()); + }, + + detachObserver: function(context, win) + { + for (var i=0; i<contexts.length; ++i) + { + if (contexts[i].context == context) + { + if (win && (contexts[i].win != win)) + continue; + + contexts.splice(i, 1); + + // If no context is using spy, remvove the (only one) HTTP observer. + if (contexts.length == 0) + { + httpObserver.removeObserver(SpyHttpObserver, "firebug-http-event"); + SpyHttpActivityObserver.unregisterObserver(); + } + + if (FBTrace.DBG_SPY) + FBTrace.sysout("spy.detachObserver (HTTP) " + contexts.length + " ", + context.getName()); + return; + } + } + }, + + /** + * Return XHR object that is associated with specified request <i>nsIHttpChannel</i>. + * Returns null if the request doesn't represent XHR. + */ + getXHR: function(request) + { + // Does also query-interface for nsIHttpChannel. + if (!(request instanceof Ci.nsIHttpChannel)) + return null; + + try + { + var callbacks = request.notificationCallbacks; + return (callbacks ? callbacks.getInterface(Ci.nsIXMLHttpRequest) : null); + } + catch (exc) + { + if (exc.name == "NS_NOINTERFACE") + { + if (FBTrace.DBG_SPY) + FBTrace.sysout("spy.getXHR; Request is not nsIXMLHttpRequest: " + + safeGetRequestName(request)); + } + } + + return null; + } +}); + + + + + +// ************************************************************************************************ + +/* +function getSpyForXHR(request, xhrRequest, context, noCreate) +{ + var spy = null; + + // Iterate all existing spy objects in this context and look for one that is + // already created for this request. + var length = context.spies.length; + for (var i=0; i<length; i++) + { + spy = context.spies[i]; + if (spy.request == request) + return spy; + } + + if (noCreate) + return null; + + spy = new Firebug.Spy.XMLHttpRequestSpy(request, xhrRequest, context); + context.spies.push(spy); + + var name = request.URI.asciiSpec; + var origName = request.originalURI.asciiSpec; + + // Attach spy only to the original request. Notice that there can be more network requests + // made by the same XHR if redirects are involved. + if (name == origName) + spy.attach(); + + if (FBTrace.DBG_SPY) + FBTrace.sysout("spy.getSpyForXHR; New spy object created (" + + (name == origName ? "new XHR" : "redirected XHR") + ") for: " + name, spy); + + return spy; +} +/**/ + +// ************************************************************************************************ + +/** + * @class This class represents a Spy object that is attached to XHR. This object + * registers various listeners into the XHR in order to monitor various events fired + * during the request process (onLoad, onAbort, etc.) + */ +/* +Firebug.Spy.XMLHttpRequestSpy = function(request, xhrRequest, context) +{ + this.request = request; + this.xhrRequest = xhrRequest; + this.context = context; + this.responseText = ""; + + // For compatibility with the Net templates. + this.isXHR = true; + + // Support for activity-observer + this.transactionStarted = false; + this.transactionClosed = false; +}; +/**/ + +//Firebug.Spy.XMLHttpRequestSpy.prototype = +/** @lends Firebug.Spy.XMLHttpRequestSpy */ +/* +{ + attach: function() + { + var spy = this; + this.onReadyStateChange = function(event) { onHTTPSpyReadyStateChange(spy, event); }; + this.onLoad = function() { onHTTPSpyLoad(spy); }; + this.onError = function() { onHTTPSpyError(spy); }; + this.onAbort = function() { onHTTPSpyAbort(spy); }; + + // xxxHonza: #502959 is still failing on Fx 3.5 + // Use activity distributor to identify 3.6 + if (SpyHttpActivityObserver.getActivityDistributor()) + { + this.onreadystatechange = this.xhrRequest.onreadystatechange; + this.xhrRequest.onreadystatechange = this.onReadyStateChange; + } + + this.xhrRequest.addEventListener("load", this.onLoad, false); + this.xhrRequest.addEventListener("error", this.onError, false); + this.xhrRequest.addEventListener("abort", this.onAbort, false); + + // xxxHonza: should be removed from FB 3.6 + if (!SpyHttpActivityObserver.getActivityDistributor()) + this.context.sourceCache.addListener(this); + }, + + detach: function() + { + // Bubble out if already detached. + if (!this.onLoad) + return; + + // If the activity distributor is available, let's detach it when the XHR + // transaction is closed. Since, in case of multipart XHRs the onLoad method + // (readyState == 4) can be called mutliple times. + // Keep in mind: + // 1) It can happen that that the TRANSACTION_CLOSE event comes before + // the onLoad (if the XHR is made as part of the page load) so, detach if + // it's already closed. + // 2) In case of immediate cache responses, the transaction doesn't have to + // be started at all (or the activity observer is no available in Firefox 3.5). + // So, also detach in this case. + if (this.transactionStarted && !this.transactionClosed) + return; + + if (FBTrace.DBG_SPY) + FBTrace.sysout("spy.detach; " + this.href); + + // Remove itself from the list of active spies. + remove(this.context.spies, this); + + if (this.onreadystatechange) + this.xhrRequest.onreadystatechange = this.onreadystatechange; + + try { this.xhrRequest.removeEventListener("load", this.onLoad, false); } catch (e) {} + try { this.xhrRequest.removeEventListener("error", this.onError, false); } catch (e) {} + try { this.xhrRequest.removeEventListener("abort", this.onAbort, false); } catch (e) {} + + this.onreadystatechange = null; + this.onLoad = null; + this.onError = null; + this.onAbort = null; + + // xxxHonza: shouuld be removed from FB 1.6 + if (!SpyHttpActivityObserver.getActivityDistributor()) + this.context.sourceCache.removeListener(this); + }, + + getURL: function() + { + return this.xhrRequest.channel ? this.xhrRequest.channel.name : this.href; + }, + + // Cache listener + onStopRequest: function(context, request, responseText) + { + if (!responseText) + return; + + if (request == this.request) + this.responseText = responseText; + }, +}; +/**/ +// ************************************************************************************************ +/* +function onHTTPSpyReadyStateChange(spy, event) +{ + if (FBTrace.DBG_SPY) + FBTrace.sysout("spy.onHTTPSpyReadyStateChange " + spy.xhrRequest.readyState + + " (multipart: " + spy.xhrRequest.multipart + ")"); + + // Remember just in case spy is detached (readyState == 4). + var originalHandler = spy.onreadystatechange; + + // Force response text to be updated in the UI (in case the console entry + // has been already expanded and the response tab selected). + if (spy.logRow && spy.xhrRequest.readyState >= 3) + { + var netInfoBox = getChildByClass(spy.logRow, "spyHead", "netInfoBody"); + if (netInfoBox) + { + netInfoBox.htmlPresented = false; + netInfoBox.responsePresented = false; + } + } + + // If the request is loading update the end time. + if (spy.xhrRequest.readyState == 3) + { + spy.responseTime = spy.endTime - spy.sendTime; + updateTime(spy); + } + + // Request loaded. Get all the info from the request now, just in case the + // XHR would be aborted in the original onReadyStateChange handler. + if (spy.xhrRequest.readyState == 4) + { + // Cumulate response so, multipart response content is properly displayed. + if (SpyHttpActivityObserver.getActivityDistributor()) + spy.responseText += spy.xhrRequest.responseText; + else + { + // xxxHonza: remove from FB 1.6 + if (!spy.responseText) + spy.responseText = spy.xhrRequest.responseText; + } + + // The XHR is loaded now (used also by the activity observer). + spy.loaded = true; + + // Update UI. + updateHttpSpyInfo(spy); + + // Notify Net pane about a request beeing loaded. + // xxxHonza: I don't think this is necessary. + var netProgress = spy.context.netProgress; + if (netProgress) + netProgress.post(netProgress.stopFile, [spy.request, spy.endTime, spy.postText, spy.responseText]); + + // Notify registered listeners about finish of the XHR. + dispatch(Firebug.Spy.fbListeners, "onLoad", [spy.context, spy]); + } + + // Pass the event to the original page handler. + callPageHandler(spy, event, originalHandler); +} + +function onHTTPSpyLoad(spy) +{ + if (FBTrace.DBG_SPY) + FBTrace.sysout("spy.onHTTPSpyLoad: " + spy.href, spy); + + // Detach must be done in onLoad (not in onreadystatechange) otherwise + // onAbort would not be handled. + spy.detach(); + + // xxxHonza: Still needed for Fx 3.5 (#502959) + if (!SpyHttpActivityObserver.getActivityDistributor()) + onHTTPSpyReadyStateChange(spy, null); +} + +function onHTTPSpyError(spy) +{ + if (FBTrace.DBG_SPY) + FBTrace.sysout("spy.onHTTPSpyError; " + spy.href, spy); + + spy.detach(); + spy.loaded = true; + + if (spy.logRow) + { + removeClass(spy.logRow, "loading"); + setClass(spy.logRow, "error"); + } +} + +function onHTTPSpyAbort(spy) +{ + if (FBTrace.DBG_SPY) + FBTrace.sysout("spy.onHTTPSpyAbort: " + spy.href, spy); + + spy.detach(); + spy.loaded = true; + + if (spy.logRow) + { + removeClass(spy.logRow, "loading"); + setClass(spy.logRow, "error"); + } + + spy.statusText = "Aborted"; + updateLogRow(spy); + + // Notify Net pane about a request beeing aborted. + // xxxHonza: the net panel shoud find out this itself. + var netProgress = spy.context.netProgress; + if (netProgress) + netProgress.post(netProgress.abortFile, [spy.request, spy.endTime, spy.postText, spy.responseText]); +} +/**/ + +// ************************************************************************************************ + +/** + * @domplate Represents a template for XHRs logged in the Console panel. The body of the + * log (displayed when expanded) is rendered using {@link Firebug.NetMonitor.NetInfoBody}. + */ + +Firebug.Spy.XHR = domplate(Firebug.Rep, +/** @lends Firebug.Spy.XHR */ + +{ + tag: + DIV({"class": "spyHead", _repObject: "$object"}, + TABLE({"class": "spyHeadTable focusRow outerFocusRow", cellpadding: 0, cellspacing: 0, + "role": "listitem", "aria-expanded": "false"}, + TBODY({"role": "presentation"}, + TR({"class": "spyRow"}, + TD({"class": "spyTitleCol spyCol", onclick: "$onToggleBody"}, + DIV({"class": "spyTitle"}, + "$object|getCaption" + ), + DIV({"class": "spyFullTitle spyTitle"}, + "$object|getFullUri" + ) + ), + TD({"class": "spyCol"}, + DIV({"class": "spyStatus"}, "$object|getStatus") + ), + TD({"class": "spyCol"}, + SPAN({"class": "spyIcon"}) + ), + TD({"class": "spyCol"}, + SPAN({"class": "spyTime"}) + ), + TD({"class": "spyCol"}, + TAG(FirebugReps.SourceLink.tag, {object: "$object.sourceLink"}) + ) + ) + ) + ) + ), + + getCaption: function(spy) + { + return spy.method.toUpperCase() + " " + cropString(spy.getURL(), 100); + }, + + getFullUri: function(spy) + { + return spy.method.toUpperCase() + " " + spy.getURL(); + }, + + getStatus: function(spy) + { + var text = ""; + if (spy.statusCode) + text += spy.statusCode + " "; + + if (spy.statusText) + return text += spy.statusText; + + return text; + }, + + onToggleBody: function(event) + { + var target = event.currentTarget || event.srcElement; + var logRow = getAncestorByClass(target, "logRow-spy"); + + if (isLeftClick(event)) + { + toggleClass(logRow, "opened"); + + var spy = getChildByClass(logRow, "spyHead").repObject; + var spyHeadTable = getAncestorByClass(target, "spyHeadTable"); + + if (hasClass(logRow, "opened")) + { + updateHttpSpyInfo(spy, logRow); + if (spyHeadTable) + spyHeadTable.setAttribute('aria-expanded', 'true'); + } + else + { + //var netInfoBox = getChildByClass(spy.logRow, "spyHead", "netInfoBody"); + //dispatch(Firebug.NetMonitor.NetInfoBody.fbListeners, "destroyTabBody", [netInfoBox, spy]); + //if (spyHeadTable) + // spyHeadTable.setAttribute('aria-expanded', 'false'); + } + } + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + copyURL: function(spy) + { + copyToClipboard(spy.getURL()); + }, + + copyParams: function(spy) + { + var text = spy.postText; + if (!text) + return; + + var url = reEncodeURL(spy, text, true); + copyToClipboard(url); + }, + + copyResponse: function(spy) + { + copyToClipboard(spy.responseText); + }, + + openInTab: function(spy) + { + openNewTab(spy.getURL(), spy.postText); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + supportsObject: function(object) + { + // TODO: xxxpedro spy xhr + return false; + + return object instanceof Firebug.Spy.XMLHttpRequestSpy; + }, + + browseObject: function(spy, context) + { + var url = spy.getURL(); + openNewTab(url); + return true; + }, + + getRealObject: function(spy, context) + { + return spy.xhrRequest; + }, + + getContextMenuItems: function(spy) + { + var items = [ + {label: "CopyLocation", command: bindFixed(this.copyURL, this, spy) } + ]; + + if (spy.postText) + { + items.push( + {label: "CopyLocationParameters", command: bindFixed(this.copyParams, this, spy) } + ); + } + + items.push( + {label: "CopyResponse", command: bindFixed(this.copyResponse, this, spy) }, + "-", + {label: "OpenInTab", command: bindFixed(this.openInTab, this, spy) } + ); + + return items; + } +}); + +// ************************************************************************************************ + +function updateTime(spy) +{ + var timeBox = spy.logRow.getElementsByClassName("spyTime").item(0); + if (spy.responseTime) + timeBox.textContent = " " + formatTime(spy.responseTime); +} + +function updateLogRow(spy) +{ + updateTime(spy); + + var statusBox = spy.logRow.getElementsByClassName("spyStatus").item(0); + statusBox.textContent = Firebug.Spy.XHR.getStatus(spy); + + removeClass(spy.logRow, "loading"); + setClass(spy.logRow, "loaded"); + + try + { + var errorRange = Math.floor(spy.xhrRequest.status/100); + if (errorRange == 4 || errorRange == 5) + setClass(spy.logRow, "error"); + } + catch (exc) + { + } +} + +var updateHttpSpyInfo = function updateHttpSpyInfo(spy, logRow) +{ + if (!spy.logRow && logRow) + spy.logRow = logRow; + + if (!spy.logRow || !hasClass(spy.logRow, "opened")) + return; + + if (!spy.params) + //spy.params = parseURLParams(spy.href+""); + spy.params = parseURLParams(spy.href+""); + + if (!spy.requestHeaders) + spy.requestHeaders = getRequestHeaders(spy); + + if (!spy.responseHeaders && spy.loaded) + spy.responseHeaders = getResponseHeaders(spy); + + var template = Firebug.NetMonitor.NetInfoBody; + var netInfoBox = getChildByClass(spy.logRow, "spyHead", "netInfoBody"); + if (!netInfoBox) + { + var head = getChildByClass(spy.logRow, "spyHead"); + netInfoBox = template.tag.append({"file": spy}, head); + dispatch(template.fbListeners, "initTabBody", [netInfoBox, spy]); + template.selectTabByName(netInfoBox, "Response"); + } + else + { + template.updateInfo(netInfoBox, spy, spy.context); + } +}; + + + +// ************************************************************************************************ + +function getRequestHeaders(spy) +{ + var headers = []; + + var channel = spy.xhrRequest.channel; + if (channel instanceof Ci.nsIHttpChannel) + { + channel.visitRequestHeaders({ + visitHeader: function(name, value) + { + headers.push({name: name, value: value}); + } + }); + } + + return headers; +} + +function getResponseHeaders(spy) +{ + var headers = []; + + try + { + var channel = spy.xhrRequest.channel; + if (channel instanceof Ci.nsIHttpChannel) + { + channel.visitResponseHeaders({ + visitHeader: function(name, value) + { + headers.push({name: name, value: value}); + } + }); + } + } + catch (exc) + { + if (FBTrace.DBG_SPY || FBTrace.DBG_ERRORS) + FBTrace.sysout("spy.getResponseHeaders; EXCEPTION " + + safeGetRequestName(spy.request), exc); + } + + return headers; +} + +// ************************************************************************************************ +// Registration + +Firebug.registerModule(Firebug.Spy); +//Firebug.registerRep(Firebug.Spy.XHR); + +// ************************************************************************************************ +}}); + + +/* See license.txt for terms of usage */ + +FBL.ns(function() { with (FBL) { + +// ************************************************************************************************ + +// List of JSON content types. +var contentTypes = +{ + // TODO: create issue: jsonViewer will not try to evaluate the contents of the requested file + // if the content-type is set to "text/plain" + //"text/plain": 1, + "text/javascript": 1, + "text/x-javascript": 1, + "text/json": 1, + "text/x-json": 1, + "application/json": 1, + "application/x-json": 1, + "application/javascript": 1, + "application/x-javascript": 1, + "application/json-rpc": 1 +}; + +// ************************************************************************************************ +// Model implementation + +Firebug.JSONViewerModel = extend(Firebug.Module, +{ + dispatchName: "jsonViewer", + initialize: function() + { + Firebug.NetMonitor.NetInfoBody.addListener(this); + + // Used by Firebug.DOMPanel.DirTable domplate. + this.toggles = {}; + }, + + shutdown: function() + { + Firebug.NetMonitor.NetInfoBody.removeListener(this); + }, + + initTabBody: function(infoBox, file) + { + if (FBTrace.DBG_JSONVIEWER) + FBTrace.sysout("jsonviewer.initTabBody", infoBox); + + // Let listeners to parse the JSON. + dispatch(this.fbListeners, "onParseJSON", [file]); + + // The JSON is still no there, try to parse most common cases. + if (!file.jsonObject) + { + ///if (this.isJSON(safeGetContentType(file.request), file.responseText)) + if (this.isJSON(file.mimeType, file.responseText)) + file.jsonObject = this.parseJSON(file); + } + + // The jsonObject is created so, the JSON tab can be displayed. + if (file.jsonObject && hasProperties(file.jsonObject)) + { + Firebug.NetMonitor.NetInfoBody.appendTab(infoBox, "JSON", + ///$STR("jsonviewer.tab.JSON")); + $STR("JSON")); + + if (FBTrace.DBG_JSONVIEWER) + FBTrace.sysout("jsonviewer.initTabBody; JSON object available " + + (typeof(file.jsonObject) != "undefined"), file.jsonObject); + } + }, + + isJSON: function(contentType, data) + { + // Workaround for JSON responses without proper content type + // Let's consider all responses starting with "{" as JSON. In the worst + // case there will be an exception when parsing. This means that no-JSON + // responses (and post data) (with "{") can be parsed unnecessarily, + // which represents a little overhead, but this happens only if the request + // is actually expanded by the user in the UI (Net & Console panels). + + ///var responseText = data ? trimLeft(data) : null; + ///if (responseText && responseText.indexOf("{") == 0) + /// return true; + var responseText = data ? trim(data) : null; + if (responseText && responseText.indexOf("{") == 0) + return true; + + if (!contentType) + return false; + + contentType = contentType.split(";")[0]; + contentType = trim(contentType); + return contentTypes[contentType]; + }, + + // Update listener for TabView + updateTabBody: function(infoBox, file, context) + { + var tab = infoBox.selectedTab; + ///var tabBody = infoBox.getElementsByClassName("netInfoJSONText").item(0); + var tabBody = $$(".netInfoJSONText", infoBox)[0]; + if (!hasClass(tab, "netInfoJSONTab") || tabBody.updated) + return; + + tabBody.updated = true; + + if (file.jsonObject) { + Firebug.DOMPanel.DirTable.tag.replace( + {object: file.jsonObject, toggles: this.toggles}, tabBody); + } + }, + + parseJSON: function(file) + { + var jsonString = new String(file.responseText); + ///return parseJSONString(jsonString, "http://" + file.request.originalURI.host); + return parseJSONString(jsonString); + } +}); + +// ************************************************************************************************ +// Registration + +Firebug.registerModule(Firebug.JSONViewerModel); + +// ************************************************************************************************ +}}); + + +/* See license.txt for terms of usage */ + +FBL.ns(function() { with (FBL) { + +// ************************************************************************************************ +// Constants + +// List of XML related content types. +var xmlContentTypes = +[ + "text/xml", + "application/xml", + "application/xhtml+xml", + "application/rss+xml", + "application/atom+xml",, + "application/vnd.mozilla.maybe.feed", + "application/rdf+xml", + "application/vnd.mozilla.xul+xml" +]; + +// ************************************************************************************************ +// Model implementation + +/** + * @module Implements viewer for XML based network responses. In order to create a new + * tab wihin network request detail, a listener is registered into + * <code>Firebug.NetMonitor.NetInfoBody</code> object. + */ +Firebug.XMLViewerModel = extend(Firebug.Module, +{ + dispatchName: "xmlViewer", + + initialize: function() + { + ///Firebug.ActivableModule.initialize.apply(this, arguments); + Firebug.Module.initialize.apply(this, arguments); + Firebug.NetMonitor.NetInfoBody.addListener(this); + }, + + shutdown: function() + { + ///Firebug.ActivableModule.shutdown.apply(this, arguments); + Firebug.Module.shutdown.apply(this, arguments); + Firebug.NetMonitor.NetInfoBody.removeListener(this); + }, + + /** + * Check response's content-type and if it's a XML, create a new tab with XML preview. + */ + initTabBody: function(infoBox, file) + { + if (FBTrace.DBG_XMLVIEWER) + FBTrace.sysout("xmlviewer.initTabBody", infoBox); + + // If the response is XML let's display a pretty preview. + ///if (this.isXML(safeGetContentType(file.request))) + if (this.isXML(file.mimeType, file.responseText)) + { + Firebug.NetMonitor.NetInfoBody.appendTab(infoBox, "XML", + ///$STR("xmlviewer.tab.XML")); + $STR("XML")); + + if (FBTrace.DBG_XMLVIEWER) + FBTrace.sysout("xmlviewer.initTabBody; XML response available"); + } + }, + + isXML: function(contentType) + { + if (!contentType) + return false; + + // Look if the response is XML based. + for (var i=0; i<xmlContentTypes.length; i++) + { + if (contentType.indexOf(xmlContentTypes[i]) == 0) + return true; + } + + return false; + }, + + /** + * Parse XML response and render pretty printed preview. + */ + updateTabBody: function(infoBox, file, context) + { + var tab = infoBox.selectedTab; + ///var tabBody = infoBox.getElementsByClassName("netInfoXMLText").item(0); + var tabBody = $$(".netInfoXMLText", infoBox)[0]; + if (!hasClass(tab, "netInfoXMLTab") || tabBody.updated) + return; + + tabBody.updated = true; + + this.insertXML(tabBody, Firebug.NetMonitor.Utils.getResponseText(file, context)); + }, + + insertXML: function(parentNode, text) + { + var xmlText = text.replace(/^\s*<?.+?>\s*/, ""); + + var div = parentNode.ownerDocument.createElement("div"); + div.innerHTML = xmlText; + + var root = div.getElementsByTagName("*")[0]; + + /*** + var parser = CCIN("@mozilla.org/xmlextras/domparser;1", "nsIDOMParser"); + var doc = parser.parseFromString(text, "text/xml"); + var root = doc.documentElement; + + // Error handling + var nsURI = "http://www.mozilla.org/newlayout/xml/parsererror.xml"; + if (root.namespaceURI == nsURI && root.nodeName == "parsererror") + { + this.ParseError.tag.replace({error: { + message: root.firstChild.nodeValue, + source: root.lastChild.textContent + }}, parentNode); + return; + } + /**/ + + if (FBTrace.DBG_XMLVIEWER) + FBTrace.sysout("xmlviewer.updateTabBody; XML response parsed", doc); + + // Override getHidden in these templates. The parsed XML documen is + // hidden, but we want to display it using 'visible' styling. + /* + var templates = [ + Firebug.HTMLPanel.CompleteElement, + Firebug.HTMLPanel.Element, + Firebug.HTMLPanel.TextElement, + Firebug.HTMLPanel.EmptyElement, + Firebug.HTMLPanel.XEmptyElement, + ]; + + var originals = []; + for (var i=0; i<templates.length; i++) + { + originals[i] = templates[i].getHidden; + templates[i].getHidden = function() { + return ""; + } + } + /**/ + + // Generate XML preview. + ///Firebug.HTMLPanel.CompleteElement.tag.replace({object: doc.documentElement}, parentNode); + + // TODO: xxxpedro html3 + ///Firebug.HTMLPanel.CompleteElement.tag.replace({object: root}, parentNode); + var html = []; + Firebug.Reps.appendNode(root, html); + parentNode.innerHTML = html.join(""); + + + /* + for (var i=0; i<originals.length; i++) + templates[i].getHidden = originals[i];/**/ + } +}); + +// ************************************************************************************************ +// Domplate + +/** + * @domplate Represents a template for displaying XML parser errors. Used by + * <code>Firebug.XMLViewerModel</code>. + */ +Firebug.XMLViewerModel.ParseError = domplate(Firebug.Rep, +{ + tag: + DIV({"class": "xmlInfoError"}, + DIV({"class": "xmlInfoErrorMsg"}, "$error.message"), + PRE({"class": "xmlInfoErrorSource"}, "$error|getSource") + ), + + getSource: function(error) + { + var parts = error.source.split("\n"); + if (parts.length != 2) + return error.source; + + var limit = 50; + var column = parts[1].length; + if (column >= limit) { + parts[0] = "..." + parts[0].substr(column - limit); + parts[1] = "..." + parts[1].substr(column - limit); + } + + if (parts[0].length > 80) + parts[0] = parts[0].substr(0, 80) + "..."; + + return parts.join("\n"); + } +}); + +// ************************************************************************************************ +// Registration + +Firebug.registerModule(Firebug.XMLViewerModel); + +}}); + + +/* See license.txt for terms of usage */ + +// next-generation Console Panel (will override consoje.js) +FBL.ns(function() { with (FBL) { +// ************************************************************************************************ + +// ************************************************************************************************ +// Constants + +/* +const Cc = Components.classes; +const Ci = Components.interfaces; +const nsIPrefBranch2 = Ci.nsIPrefBranch2; +const PrefService = Cc["@mozilla.org/preferences-service;1"]; +const prefs = PrefService.getService(nsIPrefBranch2); +/**/ +/* + +// new offline message handler +o = {x:1,y:2}; + +r = Firebug.getRep(o); + +r.tag.tag.compile(); + +outputs = []; +html = r.tag.renderHTML({object:o}, outputs); + + +// finish rendering the template (the DOM part) +target = $("build"); +target.innerHTML = html; +root = target.firstChild; + +domArgs = [root, r.tag.context, 0]; +domArgs.push.apply(domArgs, r.tag.domArgs); +domArgs.push.apply(domArgs, outputs); +r.tag.tag.renderDOM.apply(self ? self : r.tag.subject, domArgs); + + + */ +var consoleQueue = []; +var lastHighlightedObject; +var FirebugContext = Env.browser; + +// ************************************************************************************************ + +var maxQueueRequests = 500; + +// ************************************************************************************************ + +Firebug.ConsoleBase = +{ + log: function(object, context, className, rep, noThrottle, sourceLink) + { + //dispatch(this.fbListeners,"log",[context, object, className, sourceLink]); + return this.logRow(appendObject, object, context, className, rep, sourceLink, noThrottle); + }, + + logFormatted: function(objects, context, className, noThrottle, sourceLink) + { + //dispatch(this.fbListeners,"logFormatted",[context, objects, className, sourceLink]); + return this.logRow(appendFormatted, objects, context, className, null, sourceLink, noThrottle); + }, + + openGroup: function(objects, context, className, rep, noThrottle, sourceLink, noPush) + { + return this.logRow(appendOpenGroup, objects, context, className, rep, sourceLink, noThrottle); + }, + + closeGroup: function(context, noThrottle) + { + return this.logRow(appendCloseGroup, null, context, null, null, null, noThrottle, true); + }, + + logRow: function(appender, objects, context, className, rep, sourceLink, noThrottle, noRow) + { + // TODO: xxxpedro console console2 + noThrottle = true; // xxxpedro forced because there is no TabContext yet + + if (!context) + context = FirebugContext; + + if (FBTrace.DBG_ERRORS && !context) + FBTrace.sysout("Console.logRow has no context, skipping objects", objects); + + if (!context) + return; + + if (noThrottle || !context) + { + var panel = this.getPanel(context); + if (panel) + { + var row = panel.append(appender, objects, className, rep, sourceLink, noRow); + var container = panel.panelNode; + + // TODO: xxxpedro what is this? console console2 + /* + var template = Firebug.NetMonitor.NetLimit; + + while (container.childNodes.length > maxQueueRequests + 1) + { + clearDomplate(container.firstChild.nextSibling); + container.removeChild(container.firstChild.nextSibling); + panel.limit.limitInfo.totalCount++; + template.updateCounter(panel.limit); + } + dispatch([Firebug.A11yModel], "onLogRowCreated", [panel , row]); + /**/ + return row; + } + else + { + consoleQueue.push([appender, objects, context, className, rep, sourceLink, noThrottle, noRow]); + } + } + else + { + if (!context.throttle) + { + //FBTrace.sysout("console.logRow has not context.throttle! "); + return; + } + var args = [appender, objects, context, className, rep, sourceLink, true, noRow]; + context.throttle(this.logRow, this, args); + } + }, + + appendFormatted: function(args, row, context) + { + if (!context) + context = FirebugContext; + + var panel = this.getPanel(context); + panel.appendFormatted(args, row); + }, + + clear: function(context) + { + if (!context) + //context = FirebugContext; + context = Firebug.context; + + /* + if (context) + Firebug.Errors.clear(context); + /**/ + + var panel = this.getPanel(context, true); + if (panel) + { + panel.clear(); + } + }, + + // Override to direct output to your panel + getPanel: function(context, noCreate) + { + //return context.getPanel("console", noCreate); + // TODO: xxxpedro console console2 + return Firebug.chrome ? Firebug.chrome.getPanel("Console") : null; + } + +}; + +// ************************************************************************************************ + +//TODO: xxxpedro +//var ActivableConsole = extend(Firebug.ActivableModule, Firebug.ConsoleBase); +var ActivableConsole = extend(Firebug.ConsoleBase, +{ + isAlwaysEnabled: function() + { + return true; + } +}); + +Firebug.Console = Firebug.Console = extend(ActivableConsole, +//Firebug.Console = extend(ActivableConsole, +{ + dispatchName: "console", + + error: function() + { + Firebug.Console.logFormatted(arguments, Firebug.browser, "error"); + }, + + flush: function() + { + dispatch(this.fbListeners,"flush",[]); + + for (var i=0, length=consoleQueue.length; i<length; i++) + { + var args = consoleQueue[i]; + this.logRow.apply(this, args); + } + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // extends Module + + showPanel: function(browser, panel) + { + }, + + getFirebugConsoleElement: function(context, win) + { + var element = win.document.getElementById("_firebugConsole"); + if (!element) + { + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("getFirebugConsoleElement forcing element"); + var elementForcer = "(function(){var r=null; try { r = window._getFirebugConsoleElement();}catch(exc){r=exc;} return r;})();"; // we could just add the elements here + + if (context.stopped) + Firebug.Console.injector.evaluateConsoleScript(context); // todo evaluate consoleForcer on stack + else + var r = Firebug.CommandLine.evaluateInWebPage(elementForcer, context, win); + + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("getFirebugConsoleElement forcing element result "+r, r); + + var element = win.document.getElementById("_firebugConsole"); + if (!element) // elementForce fails + { + if (FBTrace.DBG_ERRORS) FBTrace.sysout("console.getFirebugConsoleElement: no _firebugConsole in win:", win); + Firebug.Console.logFormatted(["Firebug cannot find _firebugConsole element", r, win], context, "error", true); + } + } + + return element; + }, + + isReadyElsePreparing: function(context, win) // this is the only code that should call injector.attachIfNeeded + { + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("console.isReadyElsePreparing, win is " + + (win?"an argument: ":"null, context.window: ") + + (win?win.location:context.window.location), (win?win:context.window)); + + if (win) + return this.injector.attachIfNeeded(context, win); + else + { + var attached = true; + for (var i = 0; i < context.windows.length; i++) + attached = attached && this.injector.attachIfNeeded(context, context.windows[i]); + // already in the list above attached = attached && this.injector.attachIfNeeded(context, context.window); + if (context.windows.indexOf(context.window) == -1) + FBTrace.sysout("isReadyElsePreparing ***************** context.window not in context.windows"); + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("console.isReadyElsePreparing attached to "+context.windows.length+" and returns "+attached); + return attached; + } + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // extends ActivableModule + + initialize: function() + { + this.panelName = "console"; + + //TODO: xxxpedro + //Firebug.ActivableModule.initialize.apply(this, arguments); + //Firebug.Debugger.addListener(this); + }, + + enable: function() + { + if (Firebug.Console.isAlwaysEnabled()) + this.watchForErrors(); + }, + + disable: function() + { + if (Firebug.Console.isAlwaysEnabled()) + this.unwatchForErrors(); + }, + + initContext: function(context, persistedState) + { + Firebug.ActivableModule.initContext.apply(this, arguments); + context.consoleReloadWarning = true; // mark as need to warn. + }, + + loadedContext: function(context) + { + for (var url in context.sourceFileMap) + return; // if there are any sourceFiles, then do nothing + + // else we saw no JS, so the reload warning it not needed. + this.clearReloadWarning(context); + }, + + clearReloadWarning: function(context) // remove the warning about reloading. + { + if (context.consoleReloadWarning) + { + var panel = context.getPanel(this.panelName); + panel.clearReloadWarning(); + delete context.consoleReloadWarning; + } + }, + + togglePersist: function(context) + { + var panel = context.getPanel(this.panelName); + panel.persistContent = panel.persistContent ? false : true; + Firebug.chrome.setGlobalAttribute("cmd_togglePersistConsole", "checked", panel.persistContent); + }, + + showContext: function(browser, context) + { + Firebug.chrome.setGlobalAttribute("cmd_clearConsole", "disabled", !context); + + Firebug.ActivableModule.showContext.apply(this, arguments); + }, + + destroyContext: function(context, persistedState) + { + Firebug.Console.injector.detachConsole(context, context.window); // TODO iterate windows? + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + onPanelEnable: function(panelName) + { + if (panelName != this.panelName) // we don't care about other panels + return; + + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("console.onPanelEnable**************"); + + this.watchForErrors(); + Firebug.Debugger.addDependentModule(this); // we inject the console during JS compiles so we need jsd + }, + + onPanelDisable: function(panelName) + { + if (panelName != this.panelName) // we don't care about other panels + return; + + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("console.onPanelDisable**************"); + + Firebug.Debugger.removeDependentModule(this); // we inject the console during JS compiles so we need jsd + this.unwatchForErrors(); + + // Make sure possible errors coming from the page and displayed in the Firefox + // status bar are removed. + this.clear(); + }, + + onSuspendFirebug: function() + { + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("console.onSuspendFirebug\n"); + if (Firebug.Console.isAlwaysEnabled()) + this.unwatchForErrors(); + }, + + onResumeFirebug: function() + { + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("console.onResumeFirebug\n"); + if (Firebug.Console.isAlwaysEnabled()) + this.watchForErrors(); + }, + + watchForErrors: function() + { + Firebug.Errors.checkEnabled(); + $('fbStatusIcon').setAttribute("console", "on"); + }, + + unwatchForErrors: function() + { + Firebug.Errors.checkEnabled(); + $('fbStatusIcon').removeAttribute("console"); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Firebug.Debugger listener + + onMonitorScript: function(context, frame) + { + Firebug.Console.log(frame, context); + }, + + onFunctionCall: function(context, frame, depth, calling) + { + if (calling) + Firebug.Console.openGroup([frame, "depth:"+depth], context); + else + Firebug.Console.closeGroup(context); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + logRow: function(appender, objects, context, className, rep, sourceLink, noThrottle, noRow) + { + if (!context) + context = FirebugContext; + + if (FBTrace.DBG_WINDOWS && !context) FBTrace.sysout("Console.logRow: no context \n"); + + if (this.isAlwaysEnabled()) + return Firebug.ConsoleBase.logRow.apply(this, arguments); + } +}); + +Firebug.ConsoleListener = +{ + log: function(context, object, className, sourceLink) + { + }, + + logFormatted: function(context, objects, className, sourceLink) + { + } +}; + +// ************************************************************************************************ + +Firebug.ConsolePanel = function () {} // XXjjb attach Firebug so this panel can be extended. + +//TODO: xxxpedro +//Firebug.ConsolePanel.prototype = extend(Firebug.ActivablePanel, +Firebug.ConsolePanel.prototype = extend(Firebug.Panel, +{ + wasScrolledToBottom: false, + messageCount: 0, + lastLogTime: 0, + groups: null, + limit: null, + + append: function(appender, objects, className, rep, sourceLink, noRow) + { + var container = this.getTopContainer(); + + if (noRow) + { + appender.apply(this, [objects]); + } + else + { + // xxxHonza: Don't update the this.wasScrolledToBottom flag now. + // At the beginning (when the first log is created) the isScrolledToBottom + // always returns true. + //if (this.panelNode.offsetHeight) + // this.wasScrolledToBottom = isScrolledToBottom(this.panelNode); + + var row = this.createRow("logRow", className); + appender.apply(this, [objects, row, rep]); + + if (sourceLink) + FirebugReps.SourceLink.tag.append({object: sourceLink}, row); + + container.appendChild(row); + + this.filterLogRow(row, this.wasScrolledToBottom); + + if (this.wasScrolledToBottom) + scrollToBottom(this.panelNode); + + return row; + } + }, + + clear: function() + { + if (this.panelNode) + { + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("ConsolePanel.clear"); + clearNode(this.panelNode); + this.insertLogLimit(this.context); + } + }, + + insertLogLimit: function() + { + // Create limit row. This row is the first in the list of entries + // and initially hidden. It's displayed as soon as the number of + // entries reaches the limit. + var row = this.createRow("limitRow"); + + var limitInfo = { + totalCount: 0, + limitPrefsTitle: $STRF("LimitPrefsTitle", [Firebug.prefDomain+".console.logLimit"]) + }; + + //TODO: xxxpedro console net limit!? + return; + var netLimitRep = Firebug.NetMonitor.NetLimit; + var nodes = netLimitRep.createTable(row, limitInfo); + + this.limit = nodes[1]; + + var container = this.panelNode; + container.insertBefore(nodes[0], container.firstChild); + }, + + insertReloadWarning: function() + { + // put the message in, we will clear if the window console is injected. + this.warningRow = this.append(appendObject, $STR("message.Reload to activate window console"), "info"); + }, + + clearReloadWarning: function() + { + if (this.warningRow) + { + this.warningRow.parentNode.removeChild(this.warningRow); + delete this.warningRow; + } + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + appendObject: function(object, row, rep) + { + if (!rep) + rep = Firebug.getRep(object); + return rep.tag.append({object: object}, row); + }, + + appendFormatted: function(objects, row, rep) + { + if (!objects || !objects.length) + return; + + function logText(text, row) + { + var node = row.ownerDocument.createTextNode(text); + row.appendChild(node); + } + + var format = objects[0]; + var objIndex = 0; + + if (typeof(format) != "string") + { + format = ""; + objIndex = -1; + } + else // a string + { + if (objects.length === 1) // then we have only a string... + { + if (format.length < 1) { // ...and it has no characters. + logText("(an empty string)", row); + return; + } + } + } + + var parts = parseFormat(format); + var trialIndex = objIndex; + for (var i= 0; i < parts.length; i++) + { + var part = parts[i]; + if (part && typeof(part) == "object") + { + if (++trialIndex > objects.length) // then too few parameters for format, assume unformatted. + { + format = ""; + objIndex = -1; + parts.length = 0; + break; + } + } + + } + for (var i = 0; i < parts.length; ++i) + { + var part = parts[i]; + if (part && typeof(part) == "object") + { + var object = objects[++objIndex]; + if (typeof(object) != "undefined") + this.appendObject(object, row, part.rep); + else + this.appendObject(part.type, row, FirebugReps.Text); + } + else + FirebugReps.Text.tag.append({object: part}, row); + } + + for (var i = objIndex+1; i < objects.length; ++i) + { + logText(" ", row); + var object = objects[i]; + if (typeof(object) == "string") + FirebugReps.Text.tag.append({object: object}, row); + else + this.appendObject(object, row); + } + }, + + appendOpenGroup: function(objects, row, rep) + { + if (!this.groups) + this.groups = []; + + setClass(row, "logGroup"); + setClass(row, "opened"); + + var innerRow = this.createRow("logRow"); + setClass(innerRow, "logGroupLabel"); + if (rep) + rep.tag.replace({"objects": objects}, innerRow); + else + this.appendFormatted(objects, innerRow, rep); + row.appendChild(innerRow); + //dispatch([Firebug.A11yModel], 'onLogRowCreated', [this, innerRow]); + var groupBody = this.createRow("logGroupBody"); + row.appendChild(groupBody); + groupBody.setAttribute('role', 'group'); + this.groups.push(groupBody); + + addEvent(innerRow, "mousedown", function(event) + { + if (isLeftClick(event)) + { + //console.log(event.currentTarget == event.target); + + var target = event.target || event.srcElement; + + target = getAncestorByClass(target, "logGroupLabel"); + + var groupRow = target.parentNode; + + if (hasClass(groupRow, "opened")) + { + removeClass(groupRow, "opened"); + target.setAttribute('aria-expanded', 'false'); + } + else + { + setClass(groupRow, "opened"); + target.setAttribute('aria-expanded', 'true'); + } + } + }); + }, + + appendCloseGroup: function(object, row, rep) + { + if (this.groups) + this.groups.pop(); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // TODO: xxxpedro console2 + onMouseMove: function(event) + { + if (!Firebug.Inspector) return; + + var target = event.srcElement || event.target; + + var object = getAncestorByClass(target, "objectLink-element"); + object = object ? object.repObject : null; + + if(object && instanceOf(object, "Element") && object.nodeType == 1) + { + if(object != lastHighlightedObject) + { + Firebug.Inspector.drawBoxModel(object); + object = lastHighlightedObject; + } + } + else + Firebug.Inspector.hideBoxModel(); + + }, + + onMouseDown: function(event) + { + var target = event.srcElement || event.target; + + var object = getAncestorByClass(target, "objectLink"); + var repObject = object ? object.repObject : null; + + if (!repObject) + { + return; + } + + if (hasClass(object, "objectLink-object")) + { + Firebug.chrome.selectPanel("DOM"); + Firebug.chrome.getPanel("DOM").select(repObject, true); + } + else if (hasClass(object, "objectLink-element")) + { + Firebug.chrome.selectPanel("HTML"); + Firebug.chrome.getPanel("HTML").select(repObject, true); + } + + /* + if(object && instanceOf(object, "Element") && object.nodeType == 1) + { + if(object != lastHighlightedObject) + { + Firebug.Inspector.drawBoxModel(object); + object = lastHighlightedObject; + } + } + else + Firebug.Inspector.hideBoxModel(); + /**/ + + }, + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // extends Panel + + name: "Console", + title: "Console", + //searchable: true, + //breakable: true, + //editable: false, + + options: + { + hasCommandLine: true, + hasToolButtons: true, + isPreRendered: true + }, + + create: function() + { + Firebug.Panel.create.apply(this, arguments); + + this.context = Firebug.browser.window; + this.document = Firebug.chrome.document; + this.onMouseMove = bind(this.onMouseMove, this); + this.onMouseDown = bind(this.onMouseDown, this); + + this.clearButton = new Button({ + element: $("fbConsole_btClear"), + owner: Firebug.Console, + onClick: Firebug.Console.clear + }); + }, + + initialize: function() + { + Firebug.Panel.initialize.apply(this, arguments); // loads persisted content + //Firebug.ActivablePanel.initialize.apply(this, arguments); // loads persisted content + + if (!this.persistedContent && Firebug.Console.isAlwaysEnabled()) + { + this.insertLogLimit(this.context); + + // Initialize log limit and listen for changes. + this.updateMaxLimit(); + + if (this.context.consoleReloadWarning) // we have not yet injected the console + this.insertReloadWarning(); + } + + //Firebug.Console.injector.install(Firebug.browser.window); + + addEvent(this.panelNode, "mouseover", this.onMouseMove); + addEvent(this.panelNode, "mousedown", this.onMouseDown); + + this.clearButton.initialize(); + + //consolex.trace(); + //TODO: xxxpedro remove this + /* + Firebug.Console.openGroup(["asd"], null, "group", null, false); + Firebug.Console.log("asd"); + Firebug.Console.log("asd"); + Firebug.Console.log("asd"); + /**/ + + //TODO: xxxpedro preferences prefs + //prefs.addObserver(Firebug.prefDomain, this, false); + }, + + initializeNode : function() + { + //dispatch([Firebug.A11yModel], 'onInitializeNode', [this]); + if (FBTrace.DBG_CONSOLE) + { + this.onScroller = bind(this.onScroll, this); + addEvent(this.panelNode, "scroll", this.onScroller); + } + + this.onResizer = bind(this.onResize, this); + this.resizeEventTarget = Firebug.chrome.$('fbContentBox'); + addEvent(this.resizeEventTarget, "resize", this.onResizer); + }, + + destroyNode : function() + { + //dispatch([Firebug.A11yModel], 'onDestroyNode', [this]); + if (this.onScroller) + removeEvent(this.panelNode, "scroll", this.onScroller); + + //removeEvent(this.resizeEventTarget, "resize", this.onResizer); + }, + + shutdown: function() + { + //TODO: xxxpedro console console2 + this.clearButton.shutdown(); + + removeEvent(this.panelNode, "mousemove", this.onMouseMove); + removeEvent(this.panelNode, "mousedown", this.onMouseDown); + + this.destroyNode(); + + Firebug.Panel.shutdown.apply(this, arguments); + + //TODO: xxxpedro preferences prefs + //prefs.removeObserver(Firebug.prefDomain, this, false); + }, + + ishow: function(state) + { + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("Console.panel show; " + this.context.getName(), state); + + var enabled = Firebug.Console.isAlwaysEnabled(); + if (enabled) + { + Firebug.Console.disabledPanelPage.hide(this); + this.showCommandLine(true); + this.showToolbarButtons("fbConsoleButtons", true); + Firebug.chrome.setGlobalAttribute("cmd_togglePersistConsole", "checked", this.persistContent); + + if (state && state.wasScrolledToBottom) + { + this.wasScrolledToBottom = state.wasScrolledToBottom; + delete state.wasScrolledToBottom; + } + + if (this.wasScrolledToBottom) + scrollToBottom(this.panelNode); + + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("console.show ------------------ wasScrolledToBottom: " + + this.wasScrolledToBottom + ", " + this.context.getName()); + } + else + { + this.hide(state); + Firebug.Console.disabledPanelPage.show(this); + } + }, + + ihide: function(state) + { + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("Console.panel hide; " + this.context.getName(), state); + + this.showToolbarButtons("fbConsoleButtons", false); + this.showCommandLine(false); + + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("console.hide ------------------ wasScrolledToBottom: " + + this.wasScrolledToBottom + ", " + this.context.getName()); + }, + + destroy: function(state) + { + if (this.panelNode.offsetHeight) + this.wasScrolledToBottom = isScrolledToBottom(this.panelNode); + + if (state) + state.wasScrolledToBottom = this.wasScrolledToBottom; + + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("console.destroy ------------------ wasScrolledToBottom: " + + this.wasScrolledToBottom + ", " + this.context.getName()); + }, + + shouldBreakOnNext: function() + { + // xxxHonza: shouldn't the breakOnErrors be context related? + // xxxJJB, yes, but we can't support it because we can't yet tell + // which window the error is on. + return Firebug.getPref(Firebug.servicePrefDomain, "breakOnErrors"); + }, + + getBreakOnNextTooltip: function(enabled) + { + return (enabled ? $STR("console.Disable Break On All Errors") : + $STR("console.Break On All Errors")); + }, + + enablePanel: function(module) + { + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("console.ConsolePanel.enablePanel; " + this.context.getName()); + + Firebug.ActivablePanel.enablePanel.apply(this, arguments); + + this.showCommandLine(true); + + if (this.wasScrolledToBottom) + scrollToBottom(this.panelNode); + }, + + disablePanel: function(module) + { + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("console.ConsolePanel.disablePanel; " + this.context.getName()); + + Firebug.ActivablePanel.disablePanel.apply(this, arguments); + + this.showCommandLine(false); + }, + + getOptionsMenuItems: function() + { + return [ + optionMenu("ShowJavaScriptErrors", "showJSErrors"), + optionMenu("ShowJavaScriptWarnings", "showJSWarnings"), + optionMenu("ShowCSSErrors", "showCSSErrors"), + optionMenu("ShowXMLErrors", "showXMLErrors"), + optionMenu("ShowXMLHttpRequests", "showXMLHttpRequests"), + optionMenu("ShowChromeErrors", "showChromeErrors"), + optionMenu("ShowChromeMessages", "showChromeMessages"), + optionMenu("ShowExternalErrors", "showExternalErrors"), + optionMenu("ShowNetworkErrors", "showNetworkErrors"), + this.getShowStackTraceMenuItem(), + this.getStrictOptionMenuItem(), + "-", + optionMenu("LargeCommandLine", "largeCommandLine") + ]; + }, + + getShowStackTraceMenuItem: function() + { + var menuItem = serviceOptionMenu("ShowStackTrace", "showStackTrace"); + if (FirebugContext && !Firebug.Debugger.isAlwaysEnabled()) + menuItem.disabled = true; + return menuItem; + }, + + getStrictOptionMenuItem: function() + { + var strictDomain = "javascript.options"; + var strictName = "strict"; + var strictValue = prefs.getBoolPref(strictDomain+"."+strictName); + return {label: "JavascriptOptionsStrict", type: "checkbox", checked: strictValue, + command: bindFixed(Firebug.setPref, Firebug, strictDomain, strictName, !strictValue) }; + }, + + getBreakOnMenuItems: function() + { + //xxxHonza: no BON options for now. + /*return [ + optionMenu("console.option.Persist Break On Error", "persistBreakOnError") + ];*/ + return []; + }, + + search: function(text) + { + if (!text) + return; + + // Make previously visible nodes invisible again + if (this.matchSet) + { + for (var i in this.matchSet) + removeClass(this.matchSet[i], "matched"); + } + + this.matchSet = []; + + function findRow(node) { return getAncestorByClass(node, "logRow"); } + var search = new TextSearch(this.panelNode, findRow); + + var logRow = search.find(text); + if (!logRow) + { + dispatch([Firebug.A11yModel], 'onConsoleSearchMatchFound', [this, text, []]); + return false; + } + for (; logRow; logRow = search.findNext()) + { + setClass(logRow, "matched"); + this.matchSet.push(logRow); + } + dispatch([Firebug.A11yModel], 'onConsoleSearchMatchFound', [this, text, this.matchSet]); + return true; + }, + + breakOnNext: function(breaking) + { + Firebug.setPref(Firebug.servicePrefDomain, "breakOnErrors", breaking); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // private + + createRow: function(rowName, className) + { + var elt = this.document.createElement("div"); + elt.className = rowName + (className ? " " + rowName + "-" + className : ""); + return elt; + }, + + getTopContainer: function() + { + if (this.groups && this.groups.length) + return this.groups[this.groups.length-1]; + else + return this.panelNode; + }, + + filterLogRow: function(logRow, scrolledToBottom) + { + if (this.searchText) + { + setClass(logRow, "matching"); + setClass(logRow, "matched"); + + // Search after a delay because we must wait for a frame to be created for + // the new logRow so that the finder will be able to locate it + setTimeout(bindFixed(function() + { + if (this.searchFilter(this.searchText, logRow)) + this.matchSet.push(logRow); + else + removeClass(logRow, "matched"); + + removeClass(logRow, "matching"); + + if (scrolledToBottom) + scrollToBottom(this.panelNode); + }, this), 100); + } + }, + + searchFilter: function(text, logRow) + { + var count = this.panelNode.childNodes.length; + var searchRange = this.document.createRange(); + searchRange.setStart(this.panelNode, 0); + searchRange.setEnd(this.panelNode, count); + + var startPt = this.document.createRange(); + startPt.setStartBefore(logRow); + + var endPt = this.document.createRange(); + endPt.setStartAfter(logRow); + + return finder.Find(text, searchRange, startPt, endPt) != null; + }, + + // nsIPrefObserver + observe: function(subject, topic, data) + { + // We're observing preferences only. + if (topic != "nsPref:changed") + return; + + // xxxHonza check this out. + var prefDomain = "Firebug.extension."; + var prefName = data.substr(prefDomain.length); + if (prefName == "console.logLimit") + this.updateMaxLimit(); + }, + + updateMaxLimit: function() + { + var value = 1000; + //TODO: xxxpedro preferences log limit? + //var value = Firebug.getPref(Firebug.prefDomain, "console.logLimit"); + maxQueueRequests = value ? value : maxQueueRequests; + }, + + showCommandLine: function(shouldShow) + { + //TODO: xxxpedro show command line important + return; + + if (shouldShow) + { + collapse(Firebug.chrome.$("fbCommandBox"), false); + Firebug.CommandLine.setMultiLine(Firebug.largeCommandLine, Firebug.chrome); + } + else + { + // Make sure that entire content of the Console panel is hidden when + // the panel is disabled. + Firebug.CommandLine.setMultiLine(false, Firebug.chrome, Firebug.largeCommandLine); + collapse(Firebug.chrome.$("fbCommandBox"), true); + } + }, + + onScroll: function(event) + { + // Update the scroll position flag if the position changes. + this.wasScrolledToBottom = FBL.isScrolledToBottom(this.panelNode); + + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("console.onScroll ------------------ wasScrolledToBottom: " + + this.wasScrolledToBottom + ", wasScrolledToBottom: " + + this.context.getName(), event); + }, + + onResize: function(event) + { + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("console.onResize ------------------ wasScrolledToBottom: " + + this.wasScrolledToBottom + ", offsetHeight: " + this.panelNode.offsetHeight + + ", scrollTop: " + this.panelNode.scrollTop + ", scrollHeight: " + + this.panelNode.scrollHeight + ", " + this.context.getName(), event); + + if (this.wasScrolledToBottom) + scrollToBottom(this.panelNode); + } +}); + +// ************************************************************************************************ + +function parseFormat(format) +{ + var parts = []; + if (format.length <= 0) + return parts; + + var reg = /((^%|.%)(\d+)?(\.)([a-zA-Z]))|((^%|.%)([a-zA-Z]))/; + for (var m = reg.exec(format); m; m = reg.exec(format)) + { + if (m[0].substr(0, 2) == "%%") + { + parts.push(format.substr(0, m.index)); + parts.push(m[0].substr(1)); + } + else + { + var type = m[8] ? m[8] : m[5]; + var precision = m[3] ? parseInt(m[3]) : (m[4] == "." ? -1 : 0); + + var rep = null; + switch (type) + { + case "s": + rep = FirebugReps.Text; + break; + case "f": + case "i": + case "d": + rep = FirebugReps.Number; + break; + case "o": + rep = null; + break; + } + + parts.push(format.substr(0, m[0][0] == "%" ? m.index : m.index+1)); + parts.push({rep: rep, precision: precision, type: ("%" + type)}); + } + + format = format.substr(m.index+m[0].length); + } + + parts.push(format); + return parts; +} + +// ************************************************************************************************ + +var appendObject = Firebug.ConsolePanel.prototype.appendObject; +var appendFormatted = Firebug.ConsolePanel.prototype.appendFormatted; +var appendOpenGroup = Firebug.ConsolePanel.prototype.appendOpenGroup; +var appendCloseGroup = Firebug.ConsolePanel.prototype.appendCloseGroup; + +// ************************************************************************************************ + +//Firebug.registerActivableModule(Firebug.Console); +Firebug.registerModule(Firebug.Console); +Firebug.registerPanel(Firebug.ConsolePanel); + +// ************************************************************************************************ +}}); + + +/* See license.txt for terms of usage */ + +FBL.ns(function() { with (FBL) { + +// ************************************************************************************************ +// Constants + +//const Cc = Components.classes; +//const Ci = Components.interfaces; + +var frameCounters = {}; +var traceRecursion = 0; + +Firebug.Console.injector = +{ + install: function(context) + { + var win = context.window; + + var consoleHandler = new FirebugConsoleHandler(context, win); + + var properties = + [ + "log", + "debug", + "info", + "warn", + "error", + "assert", + "dir", + "dirxml", + "group", + "groupCollapsed", + "groupEnd", + "time", + "timeEnd", + "count", + "trace", + "profile", + "profileEnd", + "clear", + "open", + "close" + ]; + + var Handler = function(name) + { + var c = consoleHandler; + var f = consoleHandler[name]; + return function(){return f.apply(c,arguments);}; + }; + + var installer = function(c) + { + for (var i=0, l=properties.length; i<l; i++) + { + var name = properties[i]; + c[name] = new Handler(name); + c.firebuglite = Firebug.version; + } + }; + + var sandbox; + + if (win.console) + { + if (Env.Options.overrideConsole) + sandbox = new win.Function("arguments.callee.install(window.console={})"); + else + // if there's a console object and overrideConsole is false we should just quit + return; + } + else + { + try + { + // try overriding the console object + sandbox = new win.Function("arguments.callee.install(window.console={})"); + } + catch(E) + { + // if something goes wrong create the firebug object instead + sandbox = new win.Function("arguments.callee.install(window.firebug={})"); + } + } + + sandbox.install = installer; + sandbox(); + }, + + isAttached: function(context, win) + { + if (win.wrappedJSObject) + { + var attached = (win.wrappedJSObject._getFirebugConsoleElement ? true : false); + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("Console.isAttached:"+attached+" to win.wrappedJSObject "+safeGetWindowLocation(win.wrappedJSObject)); + + return attached; + } + else + { + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("Console.isAttached? to win "+win.location+" fnc:"+win._getFirebugConsoleElement); + return (win._getFirebugConsoleElement ? true : false); + } + }, + + attachIfNeeded: function(context, win) + { + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("Console.attachIfNeeded has win "+(win? ((win.wrappedJSObject?"YES":"NO")+" wrappedJSObject"):"null") ); + + if (this.isAttached(context, win)) + return true; + + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("Console.attachIfNeeded found isAttached false "); + + this.attachConsoleInjector(context, win); + this.addConsoleListener(context, win); + + Firebug.Console.clearReloadWarning(context); + + var attached = this.isAttached(context, win); + if (attached) + dispatch(Firebug.Console.fbListeners, "onConsoleInjected", [context, win]); + + return attached; + }, + + attachConsoleInjector: function(context, win) + { + var consoleInjection = this.getConsoleInjectionScript(); // Do it all here. + + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("attachConsoleInjector evaluating in "+win.location, consoleInjection); + + Firebug.CommandLine.evaluateInWebPage(consoleInjection, context, win); + + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("attachConsoleInjector evaluation completed for "+win.location); + }, + + getConsoleInjectionScript: function() { + if (!this.consoleInjectionScript) + { + var script = ""; + script += "window.__defineGetter__('console', function() {\n"; + script += " return (window._firebug ? window._firebug : window.loadFirebugConsole()); })\n\n"; + + script += "window.loadFirebugConsole = function() {\n"; + script += "window._firebug = new _FirebugConsole();"; + + if (FBTrace.DBG_CONSOLE) + script += " window.dump('loadFirebugConsole '+window.location+'\\n');\n"; + + script += " return window._firebug };\n"; + + var theFirebugConsoleScript = getResource("chrome://firebug/content/consoleInjected.js"); + script += theFirebugConsoleScript; + + + this.consoleInjectionScript = script; + } + return this.consoleInjectionScript; + }, + + forceConsoleCompilationInPage: function(context, win) + { + if (!win) + { + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("no win in forceConsoleCompilationInPage!"); + return; + } + + var consoleForcer = "window.loadFirebugConsole();"; + + if (context.stopped) + Firebug.Console.injector.evaluateConsoleScript(context); // todo evaluate consoleForcer on stack + else + Firebug.CommandLine.evaluateInWebPage(consoleForcer, context, win); + + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("forceConsoleCompilationInPage "+win.location, consoleForcer); + }, + + evaluateConsoleScript: function(context) + { + var scriptSource = this.getConsoleInjectionScript(); // TODO XXXjjb this should be getConsoleInjectionScript + Firebug.Debugger.evaluate(scriptSource, context); + }, + + addConsoleListener: function(context, win) + { + if (!context.activeConsoleHandlers) // then we have not been this way before + context.activeConsoleHandlers = []; + else + { // we've been this way before... + for (var i=0; i<context.activeConsoleHandlers.length; i++) + { + if (context.activeConsoleHandlers[i].window == win) + { + context.activeConsoleHandlers[i].detach(); + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("consoleInjector addConsoleListener removed handler("+context.activeConsoleHandlers[i].handler_name+") from _firebugConsole in : "+win.location+"\n"); + context.activeConsoleHandlers.splice(i,1); + } + } + } + + // We need the element to attach our event listener. + var element = Firebug.Console.getFirebugConsoleElement(context, win); + if (element) + element.setAttribute("FirebugVersion", Firebug.version); // Initialize Firebug version. + else + return false; + + var handler = new FirebugConsoleHandler(context, win); + handler.attachTo(element); + + context.activeConsoleHandlers.push(handler); + + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("consoleInjector addConsoleListener attached handler("+handler.handler_name+") to _firebugConsole in : "+win.location+"\n"); + return true; + }, + + detachConsole: function(context, win) + { + if (win && win.document) + { + var element = win.document.getElementById("_firebugConsole"); + if (element) + element.parentNode.removeChild(element); + } + } +}; + +var total_handlers = 0; +var FirebugConsoleHandler = function FirebugConsoleHandler(context, win) +{ + this.window = win; + + this.attachTo = function(element) + { + this.element = element; + // When raised on our injected element, callback to Firebug and append to console + this.boundHandler = bind(this.handleEvent, this); + this.element.addEventListener('firebugAppendConsole', this.boundHandler, true); // capturing + }; + + this.detach = function() + { + this.element.removeEventListener('firebugAppendConsole', this.boundHandler, true); + }; + + this.handler_name = ++total_handlers; + this.handleEvent = function(event) + { + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("FirebugConsoleHandler("+this.handler_name+") "+event.target.getAttribute("methodName")+", event", event); + if (!Firebug.CommandLine.CommandHandler.handle(event, this, win)) + { + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("FirebugConsoleHandler", this); + + var methodName = event.target.getAttribute("methodName"); + Firebug.Console.log($STRF("console.MethodNotSupported", [methodName])); + } + }; + + this.firebuglite = Firebug.version; + + this.init = function() + { + var consoleElement = win.document.getElementById('_firebugConsole'); + consoleElement.setAttribute("FirebugVersion", Firebug.version); + }; + + this.log = function() + { + logFormatted(arguments, "log"); + }; + + this.debug = function() + { + logFormatted(arguments, "debug", true); + }; + + this.info = function() + { + logFormatted(arguments, "info", true); + }; + + this.warn = function() + { + logFormatted(arguments, "warn", true); + }; + + this.error = function() + { + //TODO: xxxpedro console error + //if (arguments.length == 1) + //{ + // logAssert("error", arguments); // add more info based on stack trace + //} + //else + //{ + //Firebug.Errors.increaseCount(context); + logFormatted(arguments, "error", true); // user already added info + //} + }; + + this.exception = function() + { + logAssert("error", arguments); + }; + + this.assert = function(x) + { + if (!x) + { + var rest = []; + for (var i = 1; i < arguments.length; i++) + rest.push(arguments[i]); + logAssert("assert", rest); + } + }; + + this.dir = function(o) + { + Firebug.Console.log(o, context, "dir", Firebug.DOMPanel.DirTable); + }; + + this.dirxml = function(o) + { + ///if (o instanceof Window) + if (instanceOf(o, "Window")) + o = o.document.documentElement; + ///else if (o instanceof Document) + else if (instanceOf(o, "Document")) + o = o.documentElement; + + Firebug.Console.log(o, context, "dirxml", Firebug.HTMLPanel.SoloElement); + }; + + this.group = function() + { + //TODO: xxxpedro; + //var sourceLink = getStackLink(); + var sourceLink = null; + Firebug.Console.openGroup(arguments, null, "group", null, false, sourceLink); + }; + + this.groupEnd = function() + { + Firebug.Console.closeGroup(context); + }; + + this.groupCollapsed = function() + { + var sourceLink = getStackLink(); + // noThrottle true is probably ok, openGroups will likely be short strings. + var row = Firebug.Console.openGroup(arguments, null, "group", null, true, sourceLink); + removeClass(row, "opened"); + }; + + this.profile = function(title) + { + logFormatted(["console.profile() not supported."], "warn", true); + + //Firebug.Profiler.startProfiling(context, title); + }; + + this.profileEnd = function() + { + logFormatted(["console.profile() not supported."], "warn", true); + + //Firebug.Profiler.stopProfiling(context); + }; + + this.count = function(key) + { + // TODO: xxxpedro console2: is there a better way to find a unique ID for the coun() call? + var frameId = "0"; + //var frameId = FBL.getStackFrameId(); + if (frameId) + { + if (!frameCounters) + frameCounters = {}; + + if (key != undefined) + frameId += key; + + var frameCounter = frameCounters[frameId]; + if (!frameCounter) + { + var logRow = logFormatted(["0"], null, true, true); + + frameCounter = {logRow: logRow, count: 1}; + frameCounters[frameId] = frameCounter; + } + else + ++frameCounter.count; + + var label = key == undefined + ? frameCounter.count + : key + " " + frameCounter.count; + + frameCounter.logRow.firstChild.firstChild.nodeValue = label; + } + }; + + this.trace = function() + { + var getFuncName = function getFuncName (f) + { + if (f.getName instanceof Function) + { + return f.getName(); + } + if (f.name) // in FireFox, Function objects have a name property... + { + return f.name; + } + + var name = f.toString().match(/function\s*([_$\w\d]*)/)[1]; + return name || "anonymous"; + }; + + var wasVisited = function(fn) + { + for (var i=0, l=frames.length; i<l; i++) + { + if (frames[i].fn == fn) + { + return true; + } + } + + return false; + }; + + traceRecursion++; + + if (traceRecursion > 1) + { + traceRecursion--; + return; + } + + var frames = []; + + for (var fn = arguments.callee.caller.caller; fn; fn = fn.caller) + { + if (wasVisited(fn)) break; + + var args = []; + + for (var i = 0, l = fn.arguments.length; i < l; ++i) + { + args.push({value: fn.arguments[i]}); + } + + frames.push({fn: fn, name: getFuncName(fn), args: args}); + } + + + // **************************************************************************************** + + try + { + (0)(); + } + catch(e) + { + var result = e; + + var stack = + result.stack || // Firefox / Google Chrome + result.stacktrace || // Opera + ""; + + stack = stack.replace(/\n\r|\r\n/g, "\n"); // normalize line breaks + var items = stack.split(/[\n\r]/); + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Google Chrome + if (FBL.isSafari) + { + //var reChromeStackItem = /^\s+at\s+([^\(]+)\s\((.*)\)$/; + //var reChromeStackItem = /^\s+at\s+(.*)((?:http|https|ftp|file):\/\/.*)$/; + var reChromeStackItem = /^\s+at\s+(.*)((?:http|https|ftp|file):\/\/.*)$/; + + var reChromeStackItemName = /\s*\($/; + var reChromeStackItemValue = /^(.+)\:(\d+\:\d+)\)?$/; + + var framePos = 0; + for (var i=4, length=items.length; i<length; i++, framePos++) + { + var frame = frames[framePos]; + var item = items[i]; + var match = item.match(reChromeStackItem); + + //Firebug.Console.log("["+ framePos +"]--------------------------"); + //Firebug.Console.log(item); + //Firebug.Console.log("................"); + + if (match) + { + var name = match[1]; + if (name) + { + name = name.replace(reChromeStackItemName, ""); + frame.name = name; + } + + //Firebug.Console.log("name: "+name); + + var value = match[2].match(reChromeStackItemValue); + if (value) + { + frame.href = value[1]; + frame.lineNo = value[2]; + + //Firebug.Console.log("url: "+value[1]); + //Firebug.Console.log("line: "+value[2]); + } + //else + // Firebug.Console.log(match[2]); + + } + } + } + /**/ + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + else if (FBL.isFirefox) + { + // Firefox + var reFirefoxStackItem = /^(.*)@(.*)$/; + var reFirefoxStackItemValue = /^(.+)\:(\d+)$/; + + var framePos = 0; + for (var i=2, length=items.length; i<length; i++, framePos++) + { + var frame = frames[framePos] || {}; + var item = items[i]; + var match = item.match(reFirefoxStackItem); + + if (match) + { + var name = match[1]; + + //Firebug.Console.logFormatted("name: "+name); + + var value = match[2].match(reFirefoxStackItemValue); + if (value) + { + frame.href = value[1]; + frame.lineNo = value[2]; + + //Firebug.Console.log("href: "+ value[1]); + //Firebug.Console.log("line: " + value[2]); + } + //else + // Firebug.Console.logFormatted([match[2]]); + } + } + } + /**/ + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + /* + else if (FBL.isOpera) + { + // Opera + var reOperaStackItem = /^\s\s(?:\.\.\.\s\s)?Line\s(\d+)\sof\s(.+)$/; + var reOperaStackItemValue = /^linked\sscript\s(.+)$/; + + for (var i=0, length=items.length; i<length; i+=2) + { + var item = items[i]; + + var match = item.match(reOperaStackItem); + + if (match) + { + //Firebug.Console.log(match[1]); + + var value = match[2].match(reOperaStackItemValue); + + if (value) + { + //Firebug.Console.log(value[1]); + } + //else + // Firebug.Console.log(match[2]); + + //Firebug.Console.log("--------------------------"); + } + } + } + /**/ + } + + //console.log(stack); + //console.dir(frames); + Firebug.Console.log({frames: frames}, context, "stackTrace", FirebugReps.StackTrace); + + traceRecursion--; + }; + + this.trace_ok = function() + { + var getFuncName = function getFuncName (f) + { + if (f.getName instanceof Function) + return f.getName(); + if (f.name) // in FireFox, Function objects have a name property... + return f.name; + + var name = f.toString().match(/function\s*([_$\w\d]*)/)[1]; + return name || "anonymous"; + }; + + var wasVisited = function(fn) + { + for (var i=0, l=frames.length; i<l; i++) + { + if (frames[i].fn == fn) + return true; + } + + return false; + }; + + var frames = []; + + for (var fn = arguments.callee.caller; fn; fn = fn.caller) + { + if (wasVisited(fn)) break; + + var args = []; + + for (var i = 0, l = fn.arguments.length; i < l; ++i) + { + args.push({value: fn.arguments[i]}); + } + + frames.push({fn: fn, name: getFuncName(fn), args: args}); + } + + Firebug.Console.log({frames: frames}, context, "stackTrace", FirebugReps.StackTrace); + }; + + this.clear = function() + { + Firebug.Console.clear(context); + }; + + this.time = function(name, reset) + { + if (!name) + return; + + var time = new Date().getTime(); + + if (!this.timeCounters) + this.timeCounters = {}; + + var key = "KEY"+name.toString(); + + if (!reset && this.timeCounters[key]) + return; + + this.timeCounters[key] = time; + }; + + this.timeEnd = function(name) + { + var time = new Date().getTime(); + + if (!this.timeCounters) + return; + + var key = "KEY"+name.toString(); + + var timeCounter = this.timeCounters[key]; + if (timeCounter) + { + var diff = time - timeCounter; + var label = name + ": " + diff + "ms"; + + this.info(label); + + delete this.timeCounters[key]; + } + return diff; + }; + + // These functions are over-ridden by commandLine + this.evaluated = function(result, context) + { + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("consoleInjector.FirebugConsoleHandler evalutated default called", result); + + Firebug.Console.log(result, context); + }; + this.evaluateError = function(result, context) + { + Firebug.Console.log(result, context, "errorMessage"); + }; + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + function logFormatted(args, className, linkToSource, noThrottle) + { + var sourceLink = linkToSource ? getStackLink() : null; + return Firebug.Console.logFormatted(args, context, className, noThrottle, sourceLink); + } + + function logAssert(category, args) + { + Firebug.Errors.increaseCount(context); + + if (!args || !args.length || args.length == 0) + var msg = [FBL.$STR("Assertion")]; + else + var msg = args[0]; + + if (Firebug.errorStackTrace) + { + var trace = Firebug.errorStackTrace; + delete Firebug.errorStackTrace; + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("logAssert trace from errorStackTrace", trace); + } + else if (msg.stack) + { + var trace = parseToStackTrace(msg.stack); + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("logAssert trace from msg.stack", trace); + } + else + { + var trace = getJSDUserStack(); + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("logAssert trace from getJSDUserStack", trace); + } + + var errorObject = new FBL.ErrorMessage(msg, (msg.fileName?msg.fileName:win.location), (msg.lineNumber?msg.lineNumber:0), "", category, context, trace); + + + if (trace && trace.frames && trace.frames[0]) + errorObject.correctWithStackTrace(trace); + + errorObject.resetSource(); + + var objects = errorObject; + if (args.length > 1) + { + objects = [errorObject]; + for (var i = 1; i < args.length; i++) + objects.push(args[i]); + } + + var row = Firebug.Console.log(objects, context, "errorMessage", null, true); // noThrottle + row.scrollIntoView(); + } + + function getComponentsStackDump() + { + // Starting with our stack, walk back to the user-level code + var frame = Components.stack; + var userURL = win.location.href.toString(); + + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("consoleInjector.getComponentsStackDump initial stack for userURL "+userURL, frame); + + // Drop frames until we get into user code. + while (frame && FBL.isSystemURL(frame.filename) ) + frame = frame.caller; + + // Drop two more frames, the injected console function and firebugAppendConsole() + if (frame) + frame = frame.caller; + if (frame) + frame = frame.caller; + + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("consoleInjector.getComponentsStackDump final stack for userURL "+userURL, frame); + + return frame; + } + + function getStackLink() + { + // TODO: xxxpedro console2 + return; + //return FBL.getFrameSourceLink(getComponentsStackDump()); + } + + function getJSDUserStack() + { + var trace = FBL.getCurrentStackTrace(context); + + var frames = trace ? trace.frames : null; + if (frames && (frames.length > 0) ) + { + var oldest = frames.length - 1; // 6 - 1 = 5 + for (var i = 0; i < frames.length; i++) + { + if (frames[oldest - i].href.indexOf("chrome:") == 0) break; + var fn = frames[oldest - i].fn + ""; + if (fn && (fn.indexOf("_firebugEvalEvent") != -1) ) break; // command line + } + FBTrace.sysout("consoleInjector getJSDUserStack: "+frames.length+" oldest: "+oldest+" i: "+i+" i - oldest + 2: "+(i - oldest + 2), trace); + trace.frames = trace.frames.slice(2 - i); // take the oldest frames, leave 2 behind they are injection code + + return trace; + } + else + return "Firebug failed to get stack trace with any frames"; + } +}; + +// ************************************************************************************************ +// Register console namespace + +FBL.registerConsole = function() +{ + var win = Env.browser.window; + Firebug.Console.injector.install(win); +}; + +registerConsole(); + +}}); + + +/* See license.txt for terms of usage */ + +FBL.ns(function() { with (FBL) { +// ************************************************************************************************ + + +// ************************************************************************************************ +// Globals + +var commandPrefix = ">>>"; +var reOpenBracket = /[\[\(\{]/; +var reCloseBracket = /[\]\)\}]/; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +var commandHistory = []; +var commandPointer = -1; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +var isAutoCompleting = null; +var autoCompletePrefix = null; +var autoCompleteExpr = null; +var autoCompleteBuffer = null; +var autoCompletePosition = null; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +var fbCommandLine = null; +var fbLargeCommandLine = null; +var fbLargeCommandButtons = null; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +var _completion = +{ + window: + [ + "console" + ], + + document: + [ + "getElementById", + "getElementsByTagName" + ] +}; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +var _stack = function(command) +{ + Firebug.context.persistedState.commandHistory.push(command); + Firebug.context.persistedState.commandPointer = + Firebug.context.persistedState.commandHistory.length; +}; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +// ************************************************************************************************ +// CommandLine + +Firebug.CommandLine = extend(Firebug.Module, +{ + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + element: null, + isMultiLine: false, + isActive: false, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + initialize: function(doc) + { + this.clear = bind(this.clear, this); + this.enter = bind(this.enter, this); + + this.onError = bind(this.onError, this); + this.onKeyDown = bind(this.onKeyDown, this); + this.onMultiLineKeyDown = bind(this.onMultiLineKeyDown, this); + + addEvent(Firebug.browser.window, "error", this.onError); + addEvent(Firebug.chrome.window, "error", this.onError); + }, + + shutdown: function(doc) + { + this.deactivate(); + + removeEvent(Firebug.browser.window, "error", this.onError); + removeEvent(Firebug.chrome.window, "error", this.onError); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + activate: function(multiLine, hideToggleIcon, onRun) + { + defineCommandLineAPI(); + + Firebug.context.persistedState.commandHistory = + Firebug.context.persistedState.commandHistory || []; + + Firebug.context.persistedState.commandPointer = + Firebug.context.persistedState.commandPointer || -1; + + if (this.isActive) + { + if (this.isMultiLine == multiLine) return; + + this.deactivate(); + } + + fbCommandLine = $("fbCommandLine"); + fbLargeCommandLine = $("fbLargeCommandLine"); + fbLargeCommandButtons = $("fbLargeCommandButtons"); + + if (multiLine) + { + onRun = onRun || this.enter; + + this.isMultiLine = true; + + this.element = fbLargeCommandLine; + + addEvent(this.element, "keydown", this.onMultiLineKeyDown); + + addEvent($("fbSmallCommandLineIcon"), "click", Firebug.chrome.hideLargeCommandLine); + + this.runButton = new Button({ + element: $("fbCommand_btRun"), + owner: Firebug.CommandLine, + onClick: onRun + }); + + this.runButton.initialize(); + + this.clearButton = new Button({ + element: $("fbCommand_btClear"), + owner: Firebug.CommandLine, + onClick: this.clear + }); + + this.clearButton.initialize(); + } + else + { + this.isMultiLine = false; + this.element = fbCommandLine; + + if (!fbCommandLine) + return; + + addEvent(this.element, "keydown", this.onKeyDown); + } + + //Firebug.Console.log("activate", this.element); + + if (isOpera) + fixOperaTabKey(this.element); + + if(this.lastValue) + this.element.value = this.lastValue; + + this.isActive = true; + }, + + deactivate: function() + { + if (!this.isActive) return; + + //Firebug.Console.log("deactivate", this.element); + + this.isActive = false; + + this.lastValue = this.element.value; + + if (this.isMultiLine) + { + removeEvent(this.element, "keydown", this.onMultiLineKeyDown); + + removeEvent($("fbSmallCommandLineIcon"), "click", Firebug.chrome.hideLargeCommandLine); + + this.runButton.destroy(); + this.clearButton.destroy(); + } + else + { + removeEvent(this.element, "keydown", this.onKeyDown); + } + + this.element = null; + delete this.element; + + fbCommandLine = null; + fbLargeCommandLine = null; + fbLargeCommandButtons = null; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + focus: function() + { + this.element.focus(); + }, + + blur: function() + { + this.element.blur(); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + clear: function() + { + this.element.value = ""; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + evaluate: function(expr) + { + // TODO: need to register the API in console.firebug.commandLineAPI + var api = "Firebug.CommandLine.API"; + + var result = Firebug.context.evaluate(expr, "window", api, Firebug.Console.error); + + return result; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + enter: function() + { + var command = this.element.value; + + if (!command) return; + + _stack(command); + + Firebug.Console.log(commandPrefix + " " + stripNewLines(command), + Firebug.browser, "command", FirebugReps.Text); + + var result = this.evaluate(command); + + Firebug.Console.log(result); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + prevCommand: function() + { + if (Firebug.context.persistedState.commandPointer > 0 && + Firebug.context.persistedState.commandHistory.length > 0) + { + this.element.value = Firebug.context.persistedState.commandHistory + [--Firebug.context.persistedState.commandPointer]; + } + }, + + nextCommand: function() + { + var element = this.element; + + var limit = Firebug.context.persistedState.commandHistory.length -1; + var i = Firebug.context.persistedState.commandPointer; + + if (i < limit) + element.value = Firebug.context.persistedState.commandHistory + [++Firebug.context.persistedState.commandPointer]; + + else if (i == limit) + { + ++Firebug.context.persistedState.commandPointer; + element.value = ""; + } + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + autocomplete: function(reverse) + { + var element = this.element; + + var command = element.value; + var offset = getExpressionOffset(command); + + var valBegin = offset ? command.substr(0, offset) : ""; + var val = command.substr(offset); + + var buffer, obj, objName, commandBegin, result, prefix; + + // if it is the beginning of the completion + if(!isAutoCompleting) + { + + // group1 - command begin + // group2 - base object + // group3 - property prefix + var reObj = /(.*[^_$\w\d\.])?((?:[_$\w][_$\w\d]*\.)*)([_$\w][_$\w\d]*)?$/; + var r = reObj.exec(val); + + // parse command + if (r[1] || r[2] || r[3]) + { + commandBegin = r[1] || ""; + objName = r[2] || ""; + prefix = r[3] || ""; + } + else if (val == "") + { + commandBegin = objName = prefix = ""; + } else + return; + + isAutoCompleting = true; + + // find base object + if(objName == "") + obj = window; + + else + { + objName = objName.replace(/\.$/, ""); + + var n = objName.split("."); + var target = window, o; + + for (var i=0, ni; ni = n[i]; i++) + { + if (o = target[ni]) + target = o; + + else + { + target = null; + break; + } + } + obj = target; + } + + // map base object + if(obj) + { + autoCompletePrefix = prefix; + autoCompleteExpr = valBegin + commandBegin + (objName ? objName + "." : ""); + autoCompletePosition = -1; + + buffer = autoCompleteBuffer = isIE ? + _completion[objName || "window"] || [] : []; + + for(var p in obj) + buffer.push(p); + } + + // if it is the continuation of the last completion + } else + buffer = autoCompleteBuffer; + + if (buffer) + { + prefix = autoCompletePrefix; + + var diff = reverse ? -1 : 1; + + for(var i=autoCompletePosition+diff, l=buffer.length, bi; i>=0 && i<l; i+=diff) + { + bi = buffer[i]; + + if (bi.indexOf(prefix) == 0) + { + autoCompletePosition = i; + result = bi; + break; + } + } + } + + if (result) + element.value = autoCompleteExpr + result; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + setMultiLine: function(multiLine) + { + if (multiLine == this.isMultiLine) return; + + this.activate(multiLine); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + onError: function(msg, href, lineNo) + { + href = href || ""; + + var lastSlash = href.lastIndexOf("/"); + var fileName = lastSlash == -1 ? href : href.substr(lastSlash+1); + var html = [ + '<span class="errorMessage">', msg, '</span>', + '<div class="objectBox-sourceLink">', fileName, ' (line ', lineNo, ')</div>' + ]; + + // TODO: xxxpedro ajust to Console2 + //Firebug.Console.writeRow(html, "error"); + }, + + onKeyDown: function(e) + { + e = e || event; + + var code = e.keyCode; + + /*tab, shift, control, alt*/ + if (code != 9 && code != 16 && code != 17 && code != 18) + { + isAutoCompleting = false; + } + + if (code == 13 /* enter */) + { + this.enter(); + this.clear(); + } + else if (code == 27 /* ESC */) + { + setTimeout(this.clear, 0); + } + else if (code == 38 /* up */) + { + this.prevCommand(); + } + else if (code == 40 /* down */) + { + this.nextCommand(); + } + else if (code == 9 /* tab */) + { + this.autocomplete(e.shiftKey); + } + else + return; + + cancelEvent(e, true); + return false; + }, + + onMultiLineKeyDown: function(e) + { + e = e || event; + + var code = e.keyCode; + + if (code == 13 /* enter */ && e.ctrlKey) + { + this.enter(); + } + } +}); + +Firebug.registerModule(Firebug.CommandLine); + + +// ************************************************************************************************ +// + +function getExpressionOffset(command) +{ + // XXXjoe This is kind of a poor-man's JavaScript parser - trying + // to find the start of the expression that the cursor is inside. + // Not 100% fool proof, but hey... + + var bracketCount = 0; + + var start = command.length-1; + for (; start >= 0; --start) + { + var c = command[start]; + if ((c == "," || c == ";" || c == " ") && !bracketCount) + break; + if (reOpenBracket.test(c)) + { + if (bracketCount) + --bracketCount; + else + break; + } + else if (reCloseBracket.test(c)) + ++bracketCount; + } + + return start + 1; +} + +// ************************************************************************************************ +// CommandLine API + +var CommandLineAPI = +{ + $: function(id) + { + return Firebug.browser.document.getElementById(id); + }, + + $$: function(selector, context) + { + context = context || Firebug.browser.document; + return Firebug.Selector ? + Firebug.Selector(selector, context) : + Firebug.Console.error("Firebug.Selector module not loaded."); + }, + + $0: null, + + $1: null, + + dir: function(o) + { + Firebug.Console.log(o, Firebug.context, "dir", Firebug.DOMPanel.DirTable); + }, + + dirxml: function(o) + { + ///if (o instanceof Window) + if (instanceOf(o, "Window")) + o = o.document.documentElement; + ///else if (o instanceof Document) + else if (instanceOf(o, "Document")) + o = o.documentElement; + + Firebug.Console.log(o, Firebug.context, "dirxml", Firebug.HTMLPanel.SoloElement); + } +}; + +// ************************************************************************************************ + +var defineCommandLineAPI = function defineCommandLineAPI() +{ + Firebug.CommandLine.API = {}; + for (var m in CommandLineAPI) + if (!Env.browser.window[m]) + Firebug.CommandLine.API[m] = CommandLineAPI[m]; + + var stack = FirebugChrome.htmlSelectionStack; + if (stack) + { + Firebug.CommandLine.API.$0 = stack[0]; + Firebug.CommandLine.API.$1 = stack[1]; + } +}; + +// ************************************************************************************************ +}}); + +/* See license.txt for terms of usage */ + +FBL.ns(function() { with (FBL) { +// ************************************************************************************************ + +// ************************************************************************************************ +// Globals + +var ElementCache = Firebug.Lite.Cache.Element; +var cacheID = Firebug.Lite.Cache.ID; + +var ignoreHTMLProps = +{ + // ignores the attributes injected by Sizzle, otherwise it will + // be visible on IE (when enumerating element.attributes) + sizcache: 1, + sizset: 1 +}; + +if (Firebug.ignoreFirebugElements) + // ignores also the cache property injected by firebug + ignoreHTMLProps[cacheID] = 1; + + +// ************************************************************************************************ +// HTML Module + +Firebug.HTML = extend(Firebug.Module, +{ + appendTreeNode: function(nodeArray, html) + { + var reTrim = /^\s+|\s+$/g; + + if (!nodeArray.length) nodeArray = [nodeArray]; + + for (var n=0, node; node=nodeArray[n]; n++) + { + if (node.nodeType == 1) + { + if (Firebug.ignoreFirebugElements && node.firebugIgnore) continue; + + var uid = ElementCache(node); + var child = node.childNodes; + var childLength = child.length; + + var nodeName = node.nodeName.toLowerCase(); + + var nodeVisible = isVisible(node); + + var hasSingleTextChild = childLength == 1 && node.firstChild.nodeType == 3 && + nodeName != "script" && nodeName != "style"; + + var nodeControl = !hasSingleTextChild && childLength > 0 ? + ('<div class="nodeControl"></div>') : ''; + + // FIXME xxxpedro remove this + //var isIE = false; + + if(isIE && nodeControl) + html.push(nodeControl); + + if (typeof uid != 'undefined') + html.push( + '<div class="objectBox-element" ', + 'id="', uid, + '">', + !isIE && nodeControl ? nodeControl: "", + '<span ', + cacheID, + '="', uid, + '" class="nodeBox', + nodeVisible ? "" : " nodeHidden", + '"><<span class="nodeTag">', nodeName, '</span>' + ); + else + html.push( + '<div class="objectBox-element"><span class="nodeBox', + nodeVisible ? "" : " nodeHidden", + '"><<span class="nodeTag">', + nodeName, '</span>' + ); + + for (var i = 0; i < node.attributes.length; ++i) + { + var attr = node.attributes[i]; + if (!attr.specified || + // Issue 4432: Firebug Lite: HTML is mixed-up with functions + // The problem here is that expando properties added to DOM elements in + // IE < 9 will behave like DOM attributes and so they'll show up when + // looking at element.attributes list. + isIE && (browserVersion-0<9) && typeof attr.nodeValue != "string" || + Firebug.ignoreFirebugElements && ignoreHTMLProps.hasOwnProperty(attr.nodeName)) + continue; + + var name = attr.nodeName.toLowerCase(); + var value = name == "style" ? formatStyles(node.style.cssText) : attr.nodeValue; + + html.push(' <span class="nodeName">', name, + '</span>="<span class="nodeValue">', escapeHTML(value), + '</span>"'); + } + + /* + // source code nodes + if (nodeName == 'script' || nodeName == 'style') + { + + if(document.all){ + var src = node.innerHTML+'\n'; + + }else { + var src = '\n'+node.innerHTML+'\n'; + } + + var match = src.match(/\n/g); + var num = match ? match.length : 0; + var s = [], sl = 0; + + for(var c=1; c<num; c++){ + s[sl++] = '<div line="'+c+'">' + c + '</div>'; + } + + html.push('></div><div class="nodeGroup"><div class="nodeChildren"><div class="lineNo">', + s.join(''), + '</div><pre class="nodeCode">', + escapeHTML(src), + '</pre>', + '</div><div class="objectBox-element"></<span class="nodeTag">', + nodeName, + '</span>></div>', + '</div>' + ); + + + }/**/ + + // Just a single text node child + if (hasSingleTextChild) + { + var value = child[0].nodeValue.replace(reTrim, ''); + if(value) + { + html.push( + '><span class="nodeText">', + escapeHTML(value), + '</span></<span class="nodeTag">', + nodeName, + '</span>></span></div>' + ); + } + else + html.push('/></span></div>'); // blank text, print as childless node + + } + else if (childLength > 0) + { + html.push('></span></div>'); + } + else + html.push('/></span></div>'); + + } + else if (node.nodeType == 3) + { + if ( node.parentNode && ( node.parentNode.nodeName.toLowerCase() == "script" || + node.parentNode.nodeName.toLowerCase() == "style" ) ) + { + var value = node.nodeValue.replace(reTrim, ''); + + if(isIE){ + var src = value+'\n'; + + }else { + var src = '\n'+value+'\n'; + } + + var match = src.match(/\n/g); + var num = match ? match.length : 0; + var s = [], sl = 0; + + for(var c=1; c<num; c++){ + s[sl++] = '<div line="'+c+'">' + c + '</div>'; + } + + html.push('<div class="lineNo">', + s.join(''), + '</div><pre class="sourceCode">', + escapeHTML(src), + '</pre>' + ); + + } + else + { + var value = node.nodeValue.replace(reTrim, ''); + if (value) + html.push('<div class="nodeText">', escapeHTML(value),'</div>'); + } + } + } + }, + + appendTreeChildren: function(treeNode) + { + var doc = Firebug.chrome.document; + var uid = treeNode.id; + var parentNode = ElementCache.get(uid); + + if (parentNode.childNodes.length == 0) return; + + var treeNext = treeNode.nextSibling; + var treeParent = treeNode.parentNode; + + // FIXME xxxpedro remove this + //var isIE = false; + var control = isIE ? treeNode.previousSibling : treeNode.firstChild; + control.className = 'nodeControl nodeMaximized'; + + var html = []; + var children = doc.createElement("div"); + children.className = "nodeChildren"; + this.appendTreeNode(parentNode.childNodes, html); + children.innerHTML = html.join(""); + + treeParent.insertBefore(children, treeNext); + + var closeElement = doc.createElement("div"); + closeElement.className = "objectBox-element"; + closeElement.innerHTML = '</<span class="nodeTag">' + + parentNode.nodeName.toLowerCase() + '></span>'; + + treeParent.insertBefore(closeElement, treeNext); + + }, + + removeTreeChildren: function(treeNode) + { + var children = treeNode.nextSibling; + var closeTag = children.nextSibling; + + // FIXME xxxpedro remove this + //var isIE = false; + var control = isIE ? treeNode.previousSibling : treeNode.firstChild; + control.className = 'nodeControl'; + + children.parentNode.removeChild(children); + closeTag.parentNode.removeChild(closeTag); + }, + + isTreeNodeVisible: function(id) + { + return $(id); + }, + + select: function(el) + { + var id = el && ElementCache(el); + if (id) + this.selectTreeNode(id); + }, + + selectTreeNode: function(id) + { + id = ""+id; + var node, stack = []; + while(id && !this.isTreeNodeVisible(id)) + { + stack.push(id); + + var node = ElementCache.get(id).parentNode; + + if (node) + id = ElementCache(node); + else + break; + } + + stack.push(id); + + while(stack.length > 0) + { + id = stack.pop(); + node = $(id); + + if (stack.length > 0 && ElementCache.get(id).childNodes.length > 0) + this.appendTreeChildren(node); + } + + selectElement(node); + + // TODO: xxxpedro + if (fbPanel1) + fbPanel1.scrollTop = Math.round(node.offsetTop - fbPanel1.clientHeight/2); + } + +}); + +Firebug.registerModule(Firebug.HTML); + +// ************************************************************************************************ +// HTML Panel + +function HTMLPanel(){}; + +HTMLPanel.prototype = extend(Firebug.Panel, +{ + name: "HTML", + title: "HTML", + + options: { + hasSidePanel: true, + //hasToolButtons: true, + isPreRendered: !Firebug.flexChromeEnabled /* FIXME xxxpedro chromenew */, + innerHTMLSync: true + }, + + create: function(){ + Firebug.Panel.create.apply(this, arguments); + + this.panelNode.style.padding = "4px 3px 1px 15px"; + this.panelNode.style.minWidth = "500px"; + + if (Env.Options.enablePersistent || Firebug.chrome.type != "popup") + this.createUI(); + + if(this.sidePanelBar && !this.sidePanelBar.selectedPanel) + { + this.sidePanelBar.selectPanel("css"); + } + }, + + destroy: function() + { + selectedElement = null; + fbPanel1 = null; + + selectedSidePanelTS = null; + selectedSidePanelTimer = null; + + Firebug.Panel.destroy.apply(this, arguments); + }, + + createUI: function() + { + var rootNode = Firebug.browser.document.documentElement; + var html = []; + Firebug.HTML.appendTreeNode(rootNode, html); + + this.panelNode.innerHTML = html.join(""); + }, + + initialize: function() + { + Firebug.Panel.initialize.apply(this, arguments); + addEvent(this.panelNode, 'click', Firebug.HTML.onTreeClick); + + fbPanel1 = $("fbPanel1"); + + if(!selectedElement) + { + Firebug.context.persistedState.selectedHTMLElementId = + Firebug.context.persistedState.selectedHTMLElementId && + ElementCache.get(Firebug.context.persistedState.selectedHTMLElementId) ? + Firebug.context.persistedState.selectedHTMLElementId : + ElementCache(Firebug.browser.document.body); + + Firebug.HTML.selectTreeNode(Firebug.context.persistedState.selectedHTMLElementId); + } + + // TODO: xxxpedro + addEvent(fbPanel1, 'mousemove', Firebug.HTML.onListMouseMove); + addEvent($("fbContent"), 'mouseout', Firebug.HTML.onListMouseMove); + addEvent(Firebug.chrome.node, 'mouseout', Firebug.HTML.onListMouseMove); + }, + + shutdown: function() + { + // TODO: xxxpedro + removeEvent(fbPanel1, 'mousemove', Firebug.HTML.onListMouseMove); + removeEvent($("fbContent"), 'mouseout', Firebug.HTML.onListMouseMove); + removeEvent(Firebug.chrome.node, 'mouseout', Firebug.HTML.onListMouseMove); + + removeEvent(this.panelNode, 'click', Firebug.HTML.onTreeClick); + + fbPanel1 = null; + + Firebug.Panel.shutdown.apply(this, arguments); + }, + + reattach: function() + { + // TODO: panel reattach + if(Firebug.context.persistedState.selectedHTMLElementId) + Firebug.HTML.selectTreeNode(Firebug.context.persistedState.selectedHTMLElementId); + }, + + updateSelection: function(object) + { + var id = ElementCache(object); + + if (id) + { + Firebug.HTML.selectTreeNode(id); + } + } +}); + +Firebug.registerPanel(HTMLPanel); + +// ************************************************************************************************ + +var formatStyles = function(styles) +{ + return isIE ? + // IE return CSS property names in upper case, so we need to convert them + styles.replace(/([^\s]+)\s*:/g, function(m,g){return g.toLowerCase()+":";}) : + // other browsers are just fine + styles; +}; + +// ************************************************************************************************ + +var selectedElement = null; +var fbPanel1 = null; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +var selectedSidePanelTS, selectedSidePanelTimer; + +var selectElement= function selectElement(e) +{ + if (e != selectedElement) + { + if (selectedElement) + selectedElement.className = "objectBox-element"; + + e.className = e.className + " selectedElement"; + + if (FBL.isFirefox) + e.style.MozBorderRadius = "2px"; + + else if (FBL.isSafari) + e.style.WebkitBorderRadius = "2px"; + + e.style.borderRadius = "2px"; + + selectedElement = e; + + Firebug.context.persistedState.selectedHTMLElementId = e.id; + + var target = ElementCache.get(e.id); + var sidePanelBar = Firebug.chrome.getPanel("HTML").sidePanelBar; + var selectedSidePanel = sidePanelBar ? sidePanelBar.selectedPanel : null; + + var stack = FirebugChrome.htmlSelectionStack; + + stack.unshift(target); + + if (stack.length > 2) + stack.pop(); + + var lazySelect = function() + { + selectedSidePanelTS = new Date().getTime(); + + if (selectedSidePanel) + selectedSidePanel.select(target, true); + }; + + if (selectedSidePanelTimer) + { + clearTimeout(selectedSidePanelTimer); + selectedSidePanelTimer = null; + } + + if (new Date().getTime() - selectedSidePanelTS > 100) + setTimeout(lazySelect, 0); + else + selectedSidePanelTimer = setTimeout(lazySelect, 150); + } +}; + + +// ************************************************************************************************ +// *** TODO: REFACTOR ************************************************************************** +// ************************************************************************************************ +Firebug.HTML.onTreeClick = function (e) +{ + e = e || event; + var targ; + + if (e.target) targ = e.target; + else if (e.srcElement) targ = e.srcElement; + if (targ.nodeType == 3) // defeat Safari bug + targ = targ.parentNode; + + + if (targ.className.indexOf('nodeControl') != -1 || targ.className == 'nodeTag') + { + // FIXME xxxpedro remove this + //var isIE = false; + + if(targ.className == 'nodeTag') + { + var control = isIE ? (targ.parentNode.previousSibling || targ) : + (targ.parentNode.previousSibling || targ); + + selectElement(targ.parentNode.parentNode); + + if (control.className.indexOf('nodeControl') == -1) + return; + + } else + control = targ; + + FBL.cancelEvent(e); + + var treeNode = isIE ? control.nextSibling : control.parentNode; + + //FBL.Firebug.Console.log(treeNode); + + if (control.className.indexOf(' nodeMaximized') != -1) { + FBL.Firebug.HTML.removeTreeChildren(treeNode); + } else { + FBL.Firebug.HTML.appendTreeChildren(treeNode); + } + } + else if (targ.className == 'nodeValue' || targ.className == 'nodeName') + { + /* + var input = FBL.Firebug.chrome.document.getElementById('treeInput'); + + input.style.display = "block"; + input.style.left = targ.offsetLeft + 'px'; + input.style.top = FBL.topHeight + targ.offsetTop - FBL.fbPanel1.scrollTop + 'px'; + input.style.width = targ.offsetWidth + 6 + 'px'; + input.value = targ.textContent || targ.innerText; + input.focus(); + /**/ + } +}; + +function onListMouseOut(e) +{ + e = e || event || window; + var targ; + + if (e.target) targ = e.target; + else if (e.srcElement) targ = e.srcElement; + if (targ.nodeType == 3) // defeat Safari bug + targ = targ.parentNode; + + if (hasClass(targ, "fbPanel")) { + FBL.Firebug.Inspector.hideBoxModel(); + hoverElement = null; + } +}; + +var hoverElement = null; +var hoverElementTS = 0; + +Firebug.HTML.onListMouseMove = function onListMouseMove(e) +{ + try + { + e = e || event || window; + var targ; + + if (e.target) targ = e.target; + else if (e.srcElement) targ = e.srcElement; + if (targ.nodeType == 3) // defeat Safari bug + targ = targ.parentNode; + + var found = false; + while (targ && !found) { + if (!/\snodeBox\s|\sobjectBox-selector\s/.test(" " + targ.className + " ")) + targ = targ.parentNode; + else + found = true; + } + + if (!targ) + { + FBL.Firebug.Inspector.hideBoxModel(); + hoverElement = null; + return; + } + + /* + if (typeof targ.attributes[cacheID] == 'undefined') return; + + var uid = targ.attributes[cacheID]; + if (!uid) return; + /**/ + + if (typeof targ.attributes[cacheID] == 'undefined') return; + + var uid = targ.attributes[cacheID]; + if (!uid) return; + + var el = ElementCache.get(uid.value); + + var nodeName = el.nodeName.toLowerCase(); + + if (FBL.isIE && " meta title script link ".indexOf(" "+nodeName+" ") != -1) + return; + + if (!/\snodeBox\s|\sobjectBox-selector\s/.test(" " + targ.className + " ")) return; + + if (el.id == "FirebugUI" || " html head body br script link iframe ".indexOf(" "+nodeName+" ") != -1) { + FBL.Firebug.Inspector.hideBoxModel(); + hoverElement = null; + return; + } + + if ((new Date().getTime() - hoverElementTS > 40) && hoverElement != el) { + hoverElementTS = new Date().getTime(); + hoverElement = el; + FBL.Firebug.Inspector.drawBoxModel(el); + } + } + catch(E) + { + } +}; + + +// ************************************************************************************************ + +Firebug.Reps = { + + appendText: function(object, html) + { + html.push(escapeHTML(objectToString(object))); + }, + + appendNull: function(object, html) + { + html.push('<span class="objectBox-null">', escapeHTML(objectToString(object)), '</span>'); + }, + + appendString: function(object, html) + { + html.push('<span class="objectBox-string">"', escapeHTML(objectToString(object)), + '"</span>'); + }, + + appendInteger: function(object, html) + { + html.push('<span class="objectBox-number">', escapeHTML(objectToString(object)), '</span>'); + }, + + appendFloat: function(object, html) + { + html.push('<span class="objectBox-number">', escapeHTML(objectToString(object)), '</span>'); + }, + + appendFunction: function(object, html) + { + var reName = /function ?(.*?)\(/; + var m = reName.exec(objectToString(object)); + var name = m && m[1] ? m[1] : "function"; + html.push('<span class="objectBox-function">', escapeHTML(name), '()</span>'); + }, + + appendObject: function(object, html) + { + /* + var rep = Firebug.getRep(object); + var outputs = []; + + rep.tag.tag.compile(); + + var str = rep.tag.renderHTML({object: object}, outputs); + html.push(str); + /**/ + + try + { + if (object == undefined) + this.appendNull("undefined", html); + else if (object == null) + this.appendNull("null", html); + else if (typeof object == "string") + this.appendString(object, html); + else if (typeof object == "number") + this.appendInteger(object, html); + else if (typeof object == "boolean") + this.appendInteger(object, html); + else if (typeof object == "function") + this.appendFunction(object, html); + else if (object.nodeType == 1) + this.appendSelector(object, html); + else if (typeof object == "object") + { + if (typeof object.length != "undefined") + this.appendArray(object, html); + else + this.appendObjectFormatted(object, html); + } + else + this.appendText(object, html); + } + catch (exc) + { + } + /**/ + }, + + appendObjectFormatted: function(object, html) + { + var text = objectToString(object); + var reObject = /\[object (.*?)\]/; + + var m = reObject.exec(text); + html.push('<span class="objectBox-object">', m ? m[1] : text, '</span>'); + }, + + appendSelector: function(object, html) + { + var uid = ElementCache(object); + var uidString = uid ? [cacheID, '="', uid, '"'].join("") : ""; + + html.push('<span class="objectBox-selector"', uidString, '>'); + + html.push('<span class="selectorTag">', escapeHTML(object.nodeName.toLowerCase()), '</span>'); + if (object.id) + html.push('<span class="selectorId">#', escapeHTML(object.id), '</span>'); + if (object.className) + html.push('<span class="selectorClass">.', escapeHTML(object.className), '</span>'); + + html.push('</span>'); + }, + + appendNode: function(node, html) + { + if (node.nodeType == 1) + { + var uid = ElementCache(node); + var uidString = uid ? [cacheID, '="', uid, '"'].join("") : ""; + + html.push( + '<div class="objectBox-element"', uidString, '">', + '<span ', cacheID, '="', uid, '" class="nodeBox">', + '<<span class="nodeTag">', node.nodeName.toLowerCase(), '</span>'); + + for (var i = 0; i < node.attributes.length; ++i) + { + var attr = node.attributes[i]; + if (!attr.specified || attr.nodeName == cacheID) + continue; + + var name = attr.nodeName.toLowerCase(); + var value = name == "style" ? node.style.cssText : attr.nodeValue; + + html.push(' <span class="nodeName">', name, + '</span>="<span class="nodeValue">', escapeHTML(value), + '</span>"'); + } + + if (node.firstChild) + { + html.push('></div><div class="nodeChildren">'); + + for (var child = node.firstChild; child; child = child.nextSibling) + this.appendNode(child, html); + + html.push('</div><div class="objectBox-element"></<span class="nodeTag">', + node.nodeName.toLowerCase(), '></span></span></div>'); + } + else + html.push('/></span></div>'); + } + else if (node.nodeType == 3) + { + var value = trim(node.nodeValue); + if (value) + html.push('<div class="nodeText">', escapeHTML(value),'</div>'); + } + }, + + appendArray: function(object, html) + { + html.push('<span class="objectBox-array"><b>[</b> '); + + for (var i = 0, l = object.length, obj; i < l; ++i) + { + this.appendObject(object[i], html); + + if (i < l-1) + html.push(', '); + } + + html.push(' <b>]</b></span>'); + } + +}; + + + +// ************************************************************************************************ +}}); + +/* See license.txt for terms of usage */ + +/* + +Hack: +Firebug.chrome.currentPanel = Firebug.chrome.selectedPanel; +Firebug.showInfoTips = true; +Firebug.InfoTip.initializeBrowser(Firebug.chrome); + +/**/ + +FBL.ns(function() { with (FBL) { + +// ************************************************************************************************ +// Constants + +var maxWidth = 100, maxHeight = 80; +var infoTipMargin = 10; +var infoTipWindowPadding = 25; + +// ************************************************************************************************ + +Firebug.InfoTip = extend(Firebug.Module, +{ + dispatchName: "infoTip", + tags: domplate( + { + infoTipTag: DIV({"class": "infoTip"}), + + colorTag: + DIV({style: "background: $rgbValue; width: 100px; height: 40px"}, " "), + + imgTag: + DIV({"class": "infoTipImageBox infoTipLoading"}, + IMG({"class": "infoTipImage", src: "$urlValue", repeat: "$repeat", + onload: "$onLoadImage"}), + IMG({"class": "infoTipBgImage", collapsed: true, src: "blank.gif"}), + DIV({"class": "infoTipCaption"}) + ), + + onLoadImage: function(event) + { + var img = event.currentTarget || event.srcElement; + ///var bgImg = img.nextSibling; + ///if (!bgImg) + /// return; // Sometimes gets called after element is dead + + ///var caption = bgImg.nextSibling; + var innerBox = img.parentNode; + + /// TODO: xxxpedro infoTip hack + var caption = getElementByClass(innerBox, "infoTipCaption"); + var bgImg = getElementByClass(innerBox, "infoTipBgImage"); + if (!bgImg) + return; // Sometimes gets called after element is dead + + // TODO: xxxpedro infoTip IE and timing issue + // TODO: use offline document to avoid flickering + if (isIE) + removeClass(innerBox, "infoTipLoading"); + + var updateInfoTip = function(){ + + var w = img.naturalWidth || img.width || 10, + h = img.naturalHeight || img.height || 10; + + var repeat = img.getAttribute("repeat"); + + if (repeat == "repeat-x" || (w == 1 && h > 1)) + { + collapse(img, true); + collapse(bgImg, false); + bgImg.style.background = "url(" + img.src + ") repeat-x"; + bgImg.style.width = maxWidth + "px"; + if (h > maxHeight) + bgImg.style.height = maxHeight + "px"; + else + bgImg.style.height = h + "px"; + } + else if (repeat == "repeat-y" || (h == 1 && w > 1)) + { + collapse(img, true); + collapse(bgImg, false); + bgImg.style.background = "url(" + img.src + ") repeat-y"; + bgImg.style.height = maxHeight + "px"; + if (w > maxWidth) + bgImg.style.width = maxWidth + "px"; + else + bgImg.style.width = w + "px"; + } + else if (repeat == "repeat" || (w == 1 && h == 1)) + { + collapse(img, true); + collapse(bgImg, false); + bgImg.style.background = "url(" + img.src + ") repeat"; + bgImg.style.width = maxWidth + "px"; + bgImg.style.height = maxHeight + "px"; + } + else + { + if (w > maxWidth || h > maxHeight) + { + if (w > h) + { + img.style.width = maxWidth + "px"; + img.style.height = Math.round((h / w) * maxWidth) + "px"; + } + else + { + img.style.width = Math.round((w / h) * maxHeight) + "px"; + img.style.height = maxHeight + "px"; + } + } + } + + //caption.innerHTML = $STRF("Dimensions", [w, h]); + caption.innerHTML = $STRF(w + " x " + h); + + + }; + + if (isIE) + setTimeout(updateInfoTip, 0); + else + { + updateInfoTip(); + removeClass(innerBox, "infoTipLoading"); + } + + /// + } + + /* + /// onLoadImage original + onLoadImage: function(event) + { + var img = event.currentTarget; + var bgImg = img.nextSibling; + if (!bgImg) + return; // Sometimes gets called after element is dead + + var caption = bgImg.nextSibling; + var innerBox = img.parentNode; + + var w = img.naturalWidth, h = img.naturalHeight; + var repeat = img.getAttribute("repeat"); + + if (repeat == "repeat-x" || (w == 1 && h > 1)) + { + collapse(img, true); + collapse(bgImg, false); + bgImg.style.background = "url(" + img.src + ") repeat-x"; + bgImg.style.width = maxWidth + "px"; + if (h > maxHeight) + bgImg.style.height = maxHeight + "px"; + else + bgImg.style.height = h + "px"; + } + else if (repeat == "repeat-y" || (h == 1 && w > 1)) + { + collapse(img, true); + collapse(bgImg, false); + bgImg.style.background = "url(" + img.src + ") repeat-y"; + bgImg.style.height = maxHeight + "px"; + if (w > maxWidth) + bgImg.style.width = maxWidth + "px"; + else + bgImg.style.width = w + "px"; + } + else if (repeat == "repeat" || (w == 1 && h == 1)) + { + collapse(img, true); + collapse(bgImg, false); + bgImg.style.background = "url(" + img.src + ") repeat"; + bgImg.style.width = maxWidth + "px"; + bgImg.style.height = maxHeight + "px"; + } + else + { + if (w > maxWidth || h > maxHeight) + { + if (w > h) + { + img.style.width = maxWidth + "px"; + img.style.height = Math.round((h / w) * maxWidth) + "px"; + } + else + { + img.style.width = Math.round((w / h) * maxHeight) + "px"; + img.style.height = maxHeight + "px"; + } + } + } + + caption.innerHTML = $STRF("Dimensions", [w, h]); + + removeClass(innerBox, "infoTipLoading"); + } + /**/ + + }), + + initializeBrowser: function(browser) + { + browser.onInfoTipMouseOut = bind(this.onMouseOut, this, browser); + browser.onInfoTipMouseMove = bind(this.onMouseMove, this, browser); + + ///var doc = browser.contentDocument; + var doc = browser.document; + if (!doc) + return; + + ///doc.addEventListener("mouseover", browser.onInfoTipMouseMove, true); + ///doc.addEventListener("mouseout", browser.onInfoTipMouseOut, true); + ///doc.addEventListener("mousemove", browser.onInfoTipMouseMove, true); + addEvent(doc, "mouseover", browser.onInfoTipMouseMove); + addEvent(doc, "mouseout", browser.onInfoTipMouseOut); + addEvent(doc, "mousemove", browser.onInfoTipMouseMove); + + return browser.infoTip = this.tags.infoTipTag.append({}, getBody(doc)); + }, + + uninitializeBrowser: function(browser) + { + if (browser.infoTip) + { + ///var doc = browser.contentDocument; + var doc = browser.document; + ///doc.removeEventListener("mouseover", browser.onInfoTipMouseMove, true); + ///doc.removeEventListener("mouseout", browser.onInfoTipMouseOut, true); + ///doc.removeEventListener("mousemove", browser.onInfoTipMouseMove, true); + removeEvent(doc, "mouseover", browser.onInfoTipMouseMove); + removeEvent(doc, "mouseout", browser.onInfoTipMouseOut); + removeEvent(doc, "mousemove", browser.onInfoTipMouseMove); + + browser.infoTip.parentNode.removeChild(browser.infoTip); + delete browser.infoTip; + delete browser.onInfoTipMouseMove; + } + }, + + showInfoTip: function(infoTip, panel, target, x, y, rangeParent, rangeOffset) + { + if (!Firebug.showInfoTips) + return; + + var scrollParent = getOverflowParent(target); + var scrollX = x + (scrollParent ? scrollParent.scrollLeft : 0); + + if (panel.showInfoTip(infoTip, target, scrollX, y, rangeParent, rangeOffset)) + { + var htmlElt = infoTip.ownerDocument.documentElement; + var panelWidth = htmlElt.clientWidth; + var panelHeight = htmlElt.clientHeight; + + if (x+infoTip.offsetWidth+infoTipMargin > panelWidth) + { + infoTip.style.left = Math.max(0, panelWidth-(infoTip.offsetWidth+infoTipMargin)) + "px"; + infoTip.style.right = "auto"; + } + else + { + infoTip.style.left = (x+infoTipMargin) + "px"; + infoTip.style.right = "auto"; + } + + if (y+infoTip.offsetHeight+infoTipMargin > panelHeight) + { + infoTip.style.top = Math.max(0, panelHeight-(infoTip.offsetHeight+infoTipMargin)) + "px"; + infoTip.style.bottom = "auto"; + } + else + { + infoTip.style.top = (y+infoTipMargin) + "px"; + infoTip.style.bottom = "auto"; + } + + if (FBTrace.DBG_INFOTIP) + FBTrace.sysout("infotip.showInfoTip; top: " + infoTip.style.top + + ", left: " + infoTip.style.left + ", bottom: " + infoTip.style.bottom + + ", right:" + infoTip.style.right + ", offsetHeight: " + infoTip.offsetHeight + + ", offsetWidth: " + infoTip.offsetWidth + + ", x: " + x + ", panelWidth: " + panelWidth + + ", y: " + y + ", panelHeight: " + panelHeight); + + infoTip.setAttribute("active", "true"); + } + else + this.hideInfoTip(infoTip); + }, + + hideInfoTip: function(infoTip) + { + if (infoTip) + infoTip.removeAttribute("active"); + }, + + onMouseOut: function(event, browser) + { + if (!event.relatedTarget) + this.hideInfoTip(browser.infoTip); + }, + + onMouseMove: function(event, browser) + { + // Ignore if the mouse is moving over the existing info tip. + if (getAncestorByClass(event.target, "infoTip")) + return; + + if (browser.currentPanel) + { + var x = event.clientX, y = event.clientY, target = event.target || event.srcElement; + this.showInfoTip(browser.infoTip, browser.currentPanel, target, x, y, event.rangeParent, event.rangeOffset); + } + else + this.hideInfoTip(browser.infoTip); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + populateColorInfoTip: function(infoTip, color) + { + this.tags.colorTag.replace({rgbValue: color}, infoTip); + return true; + }, + + populateImageInfoTip: function(infoTip, url, repeat) + { + if (!repeat) + repeat = "no-repeat"; + + this.tags.imgTag.replace({urlValue: url, repeat: repeat}, infoTip); + + return true; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // extends Module + + disable: function() + { + // XXXjoe For each browser, call uninitializeBrowser + }, + + showPanel: function(browser, panel) + { + if (panel) + { + var infoTip = panel.panelBrowser.infoTip; + if (!infoTip) + infoTip = this.initializeBrowser(panel.panelBrowser); + this.hideInfoTip(infoTip); + } + + }, + + showSidePanel: function(browser, panel) + { + this.showPanel(browser, panel); + } +}); + +// ************************************************************************************************ + +Firebug.registerModule(Firebug.InfoTip); + +// ************************************************************************************************ + +}}); + + +/* See license.txt for terms of usage */ + +FBL.ns(function() { with (FBL) { +// ************************************************************************************************ + +var CssParser = null; + +// ************************************************************************************************ + +// Simple CSS stylesheet parser from: +// https://github.com/sergeche/webkit-css + +/** + * Simple CSS stylesheet parser that remembers rule's lines in file + * @author Sergey Chikuyonok (serge.che@gmail.com) + * @link http://chikuyonok.ru + */ +CssParser = (function(){ + /** + * Returns rule object + * @param {Number} start Character index where CSS rule definition starts + * @param {Number} body_start Character index where CSS rule's body starts + * @param {Number} end Character index where CSS rule definition ends + */ + function rule(start, body_start, end) { + return { + start: start || 0, + body_start: body_start || 0, + end: end || 0, + line: -1, + selector: null, + parent: null, + + /** @type {rule[]} */ + children: [], + + addChild: function(start, body_start, end) { + var r = rule(start, body_start, end); + r.parent = this; + this.children.push(r); + return r; + }, + /** + * Returns last child element + * @return {rule} + */ + lastChild: function() { + return this.children[this.children.length - 1]; + } + }; + } + + /** + * Replaces all occurances of substring defined by regexp + * @param {String} str + * @return {RegExp} re + * @return {String} + */ + function removeAll(str, re) { + var m; + while (m = str.match(re)) { + str = str.substring(m[0].length); + } + + return str; + } + + /** + * Trims whitespace from the beginning and the end of string + * @param {String} str + * @return {String} + */ + function trim(str) { + return str.replace(/^\s+|\s+$/g, ''); + } + + /** + * Normalizes CSS rules selector + * @param {String} selector + */ + function normalizeSelector(selector) { + // remove newlines + selector = selector.replace(/[\n\r]/g, ' '); + + selector = trim(selector); + + // remove spaces after commas + selector = selector.replace(/\s*,\s*/g, ','); + + return selector; + } + + /** + * Preprocesses parsed rules: adjusts char indexes, skipping whitespace and + * newlines, saves rule selector, removes comments, etc. + * @param {String} text CSS stylesheet + * @param {rule} rule_node CSS rule node + * @return {rule[]} + */ + function preprocessRules(text, rule_node) { + for (var i = 0, il = rule_node.children.length; i < il; i++) { + var r = rule_node.children[i], + rule_start = text.substring(r.start, r.body_start), + cur_len = rule_start.length; + + // remove newlines for better regexp matching + rule_start = rule_start.replace(/[\n\r]/g, ' '); + + // remove @import rules +// rule_start = removeAll(rule_start, /^\s*@import\s*url\((['"])?.+?\1?\)\;?/g); + + // remove comments + rule_start = removeAll(rule_start, /^\s*\/\*.*?\*\/[\s\t]*/); + + // remove whitespace + rule_start = rule_start.replace(/^[\s\t]+/, ''); + + r.start += (cur_len - rule_start.length); + r.selector = normalizeSelector(rule_start); + } + + return rule_node; + } + + /** + * Saves all lise starting indexes for faster search + * @param {String} text CSS stylesheet + * @return {Number[]} + */ + function saveLineIndexes(text) { + var result = [0], + i = 0, + il = text.length, + ch, ch2; + + while (i < il) { + ch = text.charAt(i); + + if (ch == '\n' || ch == '\r') { + if (ch == '\r' && i < il - 1 && text.charAt(i + 1) == '\n') { + // windows line ending: CRLF. Skip next character + i++; + } + + result.push(i + 1); + } + + i++; + } + + return result; + } + + /** + * Saves line number for parsed rules + * @param {String} text CSS stylesheet + * @param {rule} rule_node Rule node + * @return {rule[]} + */ + function saveLineNumbers(text, rule_node, line_indexes, startLine) { + preprocessRules(text, rule_node); + + startLine = startLine || 0; + + // remember lines start indexes, preserving line ending characters + if (!line_indexes) + var line_indexes = saveLineIndexes(text); + + // now find each rule's line + for (var i = 0, il = rule_node.children.length; i < il; i++) { + var r = rule_node.children[i]; + r.line = line_indexes.length + startLine; + for (var j = 0, jl = line_indexes.length - 1; j < jl; j++) { + var line_ix = line_indexes[j]; + if (r.start >= line_indexes[j] && r.start < line_indexes[j + 1]) { + r.line = j + 1 + startLine; + break; + } + } + + saveLineNumbers(text, r, line_indexes); + } + + return rule_node; + } + + return { + /** + * Parses text as CSS stylesheet, remembring each rule position inside + * text + * @param {String} text CSS stylesheet to parse + */ + read: function(text, startLine) { + var rule_start = [], + rule_body_start = [], + rules = [], + in_comment = 0, + root = rule(), + cur_parent = root, + last_rule = null, + stack = [], + ch, ch2; + + stack.last = function() { + return this[this.length - 1]; + }; + + function hasStr(pos, substr) { + return text.substr(pos, substr.length) == substr; + } + + for (var i = 0, il = text.length; i < il; i++) { + ch = text.charAt(i); + ch2 = i < il - 1 ? text.charAt(i + 1) : ''; + + if (!rule_start.length) + rule_start.push(i); + + switch (ch) { + case '@': + if (!in_comment) { + if (hasStr(i, '@import')) { + var m = text.substr(i).match(/^@import\s*url\((['"])?.+?\1?\)\;?/); + if (m) { + cur_parent.addChild(i, i + 7, i + m[0].length); + i += m[0].length; + rule_start.pop(); + } + break; + } + } + case '/': + // xxxpedro allowing comment inside comment + if (!in_comment && ch2 == '*') { // comment start + in_comment++; + } + break; + + case '*': + if (ch2 == '/') { // comment end + in_comment--; + } + break; + + case '{': + if (!in_comment) { + rule_body_start.push(i); + + cur_parent = cur_parent.addChild(rule_start.pop()); + stack.push(cur_parent); + } + break; + + case '}': + // found the end of the rule + if (!in_comment) { + /** @type {rule} */ + var last_rule = stack.pop(); + rule_start.pop(); + last_rule.body_start = rule_body_start.pop(); + last_rule.end = i; + cur_parent = last_rule.parent || root; + } + break; + } + + } + + return saveLineNumbers(text, root, null, startLine); + }, + + normalizeSelector: normalizeSelector, + + /** + * Find matched rule by selector. + * @param {rule} rule_node Parsed rule node + * @param {String} selector CSS selector + * @param {String} source CSS stylesheet source code + * + * @return {rule[]|null} Array of matched rules, sorted by priority (most + * recent on top) + */ + findBySelector: function(rule_node, selector, source) { + var selector = normalizeSelector(selector), + result = []; + + if (rule_node) { + for (var i = 0, il = rule_node.children.length; i < il; i++) { + /** @type {rule} */ + var r = rule_node.children[i]; + if (r.selector == selector) { + result.push(r); + } + } + } + + if (result.length) { + return result; + } else { + return null; + } + } + }; +})(); + + +// ************************************************************************************************ + +FBL.CssParser = CssParser; + +// ************************************************************************************************ +}}); + +/* See license.txt for terms of usage */ + +FBL.ns(function() { with (FBL) { + +// ************************************************************************************************ +// StyleSheet Parser + +var CssAnalyzer = {}; + +// ************************************************************************************************ +// Locals + +var CSSRuleMap = {}; +var ElementCSSRulesMap = {}; + +var internalStyleSheetIndex = -1; + +var reSelectorTag = /(^|\s)(?:\w+)/g; +var reSelectorClass = /\.[\w\d_-]+/g; +var reSelectorId = /#[\w\d_-]+/g; + +var globalCSSRuleIndex; + +var processAllStyleSheetsTimeout = null; + +var externalStyleSheetURLs = []; + +var ElementCache = Firebug.Lite.Cache.Element; +var StyleSheetCache = Firebug.Lite.Cache.StyleSheet; + +//************************************************************************************************ +// CSS Analyzer templates + +CssAnalyzer.externalStyleSheetWarning = domplate(Firebug.Rep, +{ + tag: + DIV({"class": "warning focusRow", style: "font-weight:normal;", role: 'listitem'}, + SPAN("$object|STR"), + A({"href": "$href", target:"_blank"}, "$link|STR") + ) +}); + +// ************************************************************************************************ +// CSS Analyzer methods + +CssAnalyzer.processAllStyleSheets = function(doc, styleSheetIterator) +{ + try + { + processAllStyleSheets(doc, styleSheetIterator); + } + catch(e) + { + // TODO: FBTrace condition + FBTrace.sysout("CssAnalyzer.processAllStyleSheets fails: ", e); + } +}; + +/** + * + * @param element + * @returns {String[]} Array of IDs of CSS Rules + */ +CssAnalyzer.getElementCSSRules = function(element) +{ + try + { + return getElementCSSRules(element); + } + catch(e) + { + // TODO: FBTrace condition + FBTrace.sysout("CssAnalyzer.getElementCSSRules fails: ", e); + } +}; + +CssAnalyzer.getRuleData = function(ruleId) +{ + return CSSRuleMap[ruleId]; +}; + +// TODO: do we need this? +CssAnalyzer.getRuleLine = function() +{ +}; + +CssAnalyzer.hasExternalStyleSheet = function() +{ + return externalStyleSheetURLs.length > 0; +}; + +CssAnalyzer.parseStyleSheet = function(href) +{ + var sourceData = extractSourceData(href); + var parsedObj = CssParser.read(sourceData.source, sourceData.startLine); + var parsedRules = parsedObj.children; + + // See: Issue 4776: [Firebug lite] CSS Media Types + // + // Ignore all special selectors like @media and @page + for(var i=0; i < parsedRules.length; ) + { + if (parsedRules[i].selector.indexOf("@") != -1) + { + parsedRules.splice(i, 1); + } + else + i++; + } + + return parsedRules; +}; + +//************************************************************************************************ +// Internals +//************************************************************************************************ + +// ************************************************************************************************ +// StyleSheet processing + +var processAllStyleSheets = function(doc, styleSheetIterator) +{ + styleSheetIterator = styleSheetIterator || processStyleSheet; + + globalCSSRuleIndex = -1; + + var styleSheets = doc.styleSheets; + var importedStyleSheets = []; + + if (FBTrace.DBG_CSS) + var start = new Date().getTime(); + + for(var i=0, length=styleSheets.length; i<length; i++) + { + try + { + var styleSheet = styleSheets[i]; + + if ("firebugIgnore" in styleSheet) continue; + + // we must read the length to make sure we have permission to read + // the stylesheet's content. If an error occurs here, we cannot + // read the stylesheet due to access restriction policy + var rules = isIE ? styleSheet.rules : styleSheet.cssRules; + rules.length; + } + catch(e) + { + externalStyleSheetURLs.push(styleSheet.href); + styleSheet.restricted = true; + var ssid = StyleSheetCache(styleSheet); + + /// TODO: xxxpedro external css + //loadExternalStylesheet(doc, styleSheetIterator, styleSheet); + } + + // process internal and external styleSheets + styleSheetIterator(doc, styleSheet); + + var importedStyleSheet, importedRules; + + // process imported styleSheets in IE + if (isIE) + { + var imports = styleSheet.imports; + + for(var j=0, importsLength=imports.length; j<importsLength; j++) + { + try + { + importedStyleSheet = imports[j]; + // we must read the length to make sure we have permission + // to read the imported stylesheet's content. + importedRules = importedStyleSheet.rules; + importedRules.length; + } + catch(e) + { + externalStyleSheetURLs.push(styleSheet.href); + importedStyleSheet.restricted = true; + var ssid = StyleSheetCache(importedStyleSheet); + } + + styleSheetIterator(doc, importedStyleSheet); + } + } + // process imported styleSheets in other browsers + else if (rules) + { + for(var j=0, rulesLength=rules.length; j<rulesLength; j++) + { + try + { + var rule = rules[j]; + + importedStyleSheet = rule.styleSheet; + + if (importedStyleSheet) + { + // we must read the length to make sure we have permission + // to read the imported stylesheet's content. + importedRules = importedStyleSheet.cssRules; + importedRules.length; + } + else + break; + } + catch(e) + { + externalStyleSheetURLs.push(styleSheet.href); + importedStyleSheet.restricted = true; + var ssid = StyleSheetCache(importedStyleSheet); + } + + styleSheetIterator(doc, importedStyleSheet); + } + } + }; + + if (FBTrace.DBG_CSS) + { + FBTrace.sysout("FBL.processAllStyleSheets", "all stylesheet rules processed in " + (new Date().getTime() - start) + "ms"); + } +}; + +// ************************************************************************************************ + +var processStyleSheet = function(doc, styleSheet) +{ + if (styleSheet.restricted) + return; + + var rules = isIE ? styleSheet.rules : styleSheet.cssRules; + + var ssid = StyleSheetCache(styleSheet); + + var href = styleSheet.href; + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // CSS Parser + var shouldParseCSS = typeof CssParser != "undefined" && !Firebug.disableResourceFetching; + if (shouldParseCSS) + { + try + { + var parsedRules = CssAnalyzer.parseStyleSheet(href); + } + catch(e) + { + if (FBTrace.DBG_ERRORS) FBTrace.sysout("processStyleSheet FAILS", e.message || e); + shouldParseCSS = false; + } + finally + { + var parsedRulesIndex = 0; + + var dontSupportGroupedRules = isIE && browserVersion < 9; + var group = []; + var groupItem; + } + } + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + for (var i=0, length=rules.length; i<length; i++) + { + // TODO: xxxpedro is there a better way to cache CSS Rules? The problem is that + // we cannot add expando properties in the rule object in IE + var rid = ssid + ":" + i; + var rule = rules[i]; + var selector = rule.selectorText || ""; + var lineNo = null; + + // See: Issue 4776: [Firebug lite] CSS Media Types + // + // Ignore all special selectors like @media and @page + if (!selector || selector.indexOf("@") != -1) + continue; + + if (isIE) + selector = selector.replace(reSelectorTag, function(s){return s.toLowerCase();}); + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // CSS Parser + if (shouldParseCSS) + { + var parsedRule = parsedRules[parsedRulesIndex]; + var parsedSelector = parsedRule.selector; + + if (dontSupportGroupedRules && parsedSelector.indexOf(",") != -1 && group.length == 0) + group = parsedSelector.split(","); + + if (dontSupportGroupedRules && group.length > 0) + { + groupItem = group.shift(); + + if (CssParser.normalizeSelector(selector) == groupItem) + lineNo = parsedRule.line; + + if (group.length == 0) + parsedRulesIndex++; + } + else if (CssParser.normalizeSelector(selector) == parsedRule.selector) + { + lineNo = parsedRule.line; + parsedRulesIndex++; + } + } + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + CSSRuleMap[rid] = + { + styleSheetId: ssid, + styleSheetIndex: i, + order: ++globalCSSRuleIndex, + specificity: + // See: Issue 4777: [Firebug lite] Specificity of CSS Rules + // + // if it is a normal selector then calculate the specificity + selector && selector.indexOf(",") == -1 ? + getCSSRuleSpecificity(selector) : + // See: Issue 3262: [Firebug lite] Specificity of grouped CSS Rules + // + // if it is a grouped selector, do not calculate the specificity + // because the correct value will depend of the matched element. + // The proper specificity value for grouped selectors are calculated + // via getElementCSSRules(element) + 0, + + rule: rule, + lineNo: lineNo, + selector: selector, + cssText: rule.style ? rule.style.cssText : rule.cssText ? rule.cssText : "" + }; + + // TODO: what happens with elements added after this? Need to create a test case. + // Maybe we should place this at getElementCSSRules() but it will make the function + // a lot more expensive. + // + // Maybe add a "refresh" button? + var elements = Firebug.Selector(selector, doc); + + for (var j=0, elementsLength=elements.length; j<elementsLength; j++) + { + var element = elements[j]; + var eid = ElementCache(element); + + if (!ElementCSSRulesMap[eid]) + ElementCSSRulesMap[eid] = []; + + ElementCSSRulesMap[eid].push(rid); + } + + //console.log(selector, elements); + } +}; + +// ************************************************************************************************ +// External StyleSheet Loader + +var loadExternalStylesheet = function(doc, styleSheetIterator, styleSheet) +{ + var url = styleSheet.href; + styleSheet.firebugIgnore = true; + + var source = Firebug.Lite.Proxy.load(url); + + // TODO: check for null and error responses + + // remove comments + //var reMultiComment = /(\/\*([^\*]|\*(?!\/))*\*\/)/g; + //source = source.replace(reMultiComment, ""); + + // convert relative addresses to absolute ones + source = source.replace(/url\(([^\)]+)\)/g, function(a,name){ + + var hasDomain = /\w+:\/\/./.test(name); + + if (!hasDomain) + { + name = name.replace(/^(["'])(.+)\1$/, "$2"); + var first = name.charAt(0); + + // relative path, based on root + if (first == "/") + { + // TODO: xxxpedro move to lib or Firebug.Lite.something + // getURLRoot + var m = /^([^:]+:\/{1,3}[^\/]+)/.exec(url); + + return m ? + "url(" + m[1] + name + ")" : + "url(" + name + ")"; + } + // relative path, based on current location + else + { + // TODO: xxxpedro move to lib or Firebug.Lite.something + // getURLPath + var path = url.replace(/[^\/]+\.[\w\d]+(\?.+|#.+)?$/g, ""); + + path = path + name; + + var reBack = /[^\/]+\/\.\.\//; + while(reBack.test(path)) + { + path = path.replace(reBack, ""); + } + + //console.log("url(" + path + ")"); + + return "url(" + path + ")"; + } + } + + // if it is an absolute path, there is nothing to do + return a; + }); + + var oldStyle = styleSheet.ownerNode; + + if (!oldStyle) return; + + if (!oldStyle.parentNode) return; + + var style = createGlobalElement("style"); + style.setAttribute("charset","utf-8"); + style.setAttribute("type", "text/css"); + style.innerHTML = source; + + //debugger; + oldStyle.parentNode.insertBefore(style, oldStyle.nextSibling); + oldStyle.parentNode.removeChild(oldStyle); + + doc.styleSheets[doc.styleSheets.length-1].externalURL = url; + + console.log(url, "call " + externalStyleSheetURLs.length, source); + + externalStyleSheetURLs.pop(); + + if (processAllStyleSheetsTimeout) + { + clearTimeout(processAllStyleSheetsTimeout); + } + + processAllStyleSheetsTimeout = setTimeout(function(){ + console.log("processing"); + FBL.processAllStyleSheets(doc, styleSheetIterator); + processAllStyleSheetsTimeout = null; + },200); + +}; + +//************************************************************************************************ +// getElementCSSRules + +var getElementCSSRules = function(element) +{ + var eid = ElementCache(element); + var rules = ElementCSSRulesMap[eid]; + + if (!rules) return; + + var arr = [element]; + var Selector = Firebug.Selector; + var ruleId, rule; + + // for the case of grouped selectors, we need to calculate the highest + // specificity within the selectors of the group that matches the element, + // so we can sort the rules properly without over estimating the specificity + // of grouped selectors + for (var i = 0, length = rules.length; i < length; i++) + { + ruleId = rules[i]; + rule = CSSRuleMap[ruleId]; + + // check if it is a grouped selector + if (rule.selector.indexOf(",") != -1) + { + var selectors = rule.selector.split(","); + var maxSpecificity = -1; + var sel, spec, mostSpecificSelector; + + // loop over all selectors in the group + for (var j, len = selectors.length; j < len; j++) + { + sel = selectors[j]; + + // find if the selector matches the element + if (Selector.matches(sel, arr).length == 1) + { + spec = getCSSRuleSpecificity(sel); + + // find the most specific selector that macthes the element + if (spec > maxSpecificity) + { + maxSpecificity = spec; + mostSpecificSelector = sel; + } + } + } + + rule.specificity = maxSpecificity; + } + } + + rules.sort(sortElementRules); + //rules.sort(solveRulesTied); + + return rules; +}; + +// ************************************************************************************************ +// Rule Specificity + +var sortElementRules = function(a, b) +{ + var ruleA = CSSRuleMap[a]; + var ruleB = CSSRuleMap[b]; + + var specificityA = ruleA.specificity; + var specificityB = ruleB.specificity; + + if (specificityA > specificityB) + return 1; + + else if (specificityA < specificityB) + return -1; + + else + return ruleA.order > ruleB.order ? 1 : -1; +}; + +var solveRulesTied = function(a, b) +{ + var ruleA = CSSRuleMap[a]; + var ruleB = CSSRuleMap[b]; + + if (ruleA.specificity == ruleB.specificity) + return ruleA.order > ruleB.order ? 1 : -1; + + return null; +}; + +var getCSSRuleSpecificity = function(selector) +{ + var match = selector.match(reSelectorTag); + var tagCount = match ? match.length : 0; + + match = selector.match(reSelectorClass); + var classCount = match ? match.length : 0; + + match = selector.match(reSelectorId); + var idCount = match ? match.length : 0; + + return tagCount + 10*classCount + 100*idCount; +}; + +// ************************************************************************************************ +// StyleSheet data + +var extractSourceData = function(href) +{ + var sourceData = + { + source: null, + startLine: 0 + }; + + if (href) + { + sourceData.source = Firebug.Lite.Proxy.load(href); + } + else + { + // TODO: create extractInternalSourceData(index) + // TODO: pre process the position of the inline styles so this will happen only once + // in case of having multiple inline styles + var index = 0; + var ssIndex = ++internalStyleSheetIndex; + var reStyleTag = /\<\s*style.*\>/gi; + var reEndStyleTag = /\<\/\s*style.*\>/gi; + + var source = Firebug.Lite.Proxy.load(Env.browser.location.href); + source = source.replace(/\n\r|\r\n/g, "\n"); // normalize line breaks + + var startLine = 0; + + do + { + var matchStyleTag = source.match(reStyleTag); + var i0 = source.indexOf(matchStyleTag[0]) + matchStyleTag[0].length; + + for (var i=0; i < i0; i++) + { + if (source.charAt(i) == "\n") + startLine++; + } + + source = source.substr(i0); + + index++; + } + while (index <= ssIndex); + + var matchEndStyleTag = source.match(reEndStyleTag); + var i1 = source.indexOf(matchEndStyleTag[0]); + + var extractedSource = source.substr(0, i1); + + sourceData.source = extractedSource; + sourceData.startLine = startLine; + } + + return sourceData; +}; + +// ************************************************************************************************ +// Registration + +FBL.CssAnalyzer = CssAnalyzer; + +// ************************************************************************************************ +}}); + + +/* See license.txt for terms of usage */ + +// move to FBL +(function() { + +// ************************************************************************************************ +// XPath + +/** + * Gets an XPath for an element which describes its hierarchical location. + */ +this.getElementXPath = function(element) +{ + try + { + if (element && element.id) + return '//*[@id="' + element.id + '"]'; + else + return this.getElementTreeXPath(element); + } + catch(E) + { + // xxxpedro: trying to detect the mysterious error: + // Security error" code: "1000 + //debugger; + } +}; + +this.getElementTreeXPath = function(element) +{ + var paths = []; + + for (; element && element.nodeType == 1; element = element.parentNode) + { + var index = 0; + var nodeName = element.nodeName; + + for (var sibling = element.previousSibling; sibling; sibling = sibling.previousSibling) + { + if (sibling.nodeType != 1) continue; + + if (sibling.nodeName == nodeName) + ++index; + } + + var tagName = element.nodeName.toLowerCase(); + var pathIndex = (index ? "[" + (index+1) + "]" : ""); + paths.splice(0, 0, tagName + pathIndex); + } + + return paths.length ? "/" + paths.join("/") : null; +}; + +this.getElementsByXPath = function(doc, xpath) +{ + var nodes = []; + + try { + var result = doc.evaluate(xpath, doc, null, XPathResult.ANY_TYPE, null); + for (var item = result.iterateNext(); item; item = result.iterateNext()) + nodes.push(item); + } + catch (exc) + { + // Invalid xpath expressions make their way here sometimes. If that happens, + // we still want to return an empty set without an exception. + } + + return nodes; +}; + +this.getRuleMatchingElements = function(rule, doc) +{ + var css = rule.selectorText; + var xpath = this.cssToXPath(css); + return this.getElementsByXPath(doc, xpath); +}; + + +}).call(FBL); + + + + +FBL.ns(function() { with (FBL) { + +// ************************************************************************************************ +// ************************************************************************************************ +// ************************************************************************************************ +// ************************************************************************************************ +// ************************************************************************************************ + +var toCamelCase = function toCamelCase(s) +{ + return s.replace(reSelectorCase, toCamelCaseReplaceFn); +}; + +var toSelectorCase = function toSelectorCase(s) +{ + return s.replace(reCamelCase, "-$1").toLowerCase(); + +}; + +var reCamelCase = /([A-Z])/g; +var reSelectorCase = /\-(.)/g; +var toCamelCaseReplaceFn = function toCamelCaseReplaceFn(m,g) +{ + return g.toUpperCase(); +}; + +// ************************************************************************************************ + +var ElementCache = Firebug.Lite.Cache.Element; +var StyleSheetCache = Firebug.Lite.Cache.StyleSheet; + +// ************************************************************************************************ +// ************************************************************************************************ +// ************************************************************************************************ +// ************************************************************************************************ +// ************************************************************************************************ +// ************************************************************************************************ + + +// ************************************************************************************************ +// Constants + +//const Cc = Components.classes; +//const Ci = Components.interfaces; +//const nsIDOMCSSStyleRule = Ci.nsIDOMCSSStyleRule; +//const nsIInterfaceRequestor = Ci.nsIInterfaceRequestor; +//const nsISelectionDisplay = Ci.nsISelectionDisplay; +//const nsISelectionController = Ci.nsISelectionController; + +// See: http://mxr.mozilla.org/mozilla1.9.2/source/content/events/public/nsIEventStateManager.h#153 +//const STATE_ACTIVE = 0x01; +//const STATE_FOCUS = 0x02; +//const STATE_HOVER = 0x04; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +Firebug.SourceBoxPanel = Firebug.Panel; + +var reSelectorTag = /(^|\s)(?:\w+)/g; + +var domUtils = null; + +var textContent = isIE ? "innerText" : "textContent"; +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +var CSSDomplateBase = { + isEditable: function(rule) + { + return !rule.isSystemSheet; + }, + isSelectorEditable: function(rule) + { + return rule.isSelectorEditable && this.isEditable(rule); + } +}; + +var CSSPropTag = domplate(CSSDomplateBase, { + tag: DIV({"class": "cssProp focusRow", $disabledStyle: "$prop.disabled", + $editGroup: "$rule|isEditable", + $cssOverridden: "$prop.overridden", role : "option"}, + A({"class": "cssPropDisable"}, " "), + SPAN({"class": "cssPropName", $editable: "$rule|isEditable"}, "$prop.name"), + SPAN({"class": "cssColon"}, ":"), + SPAN({"class": "cssPropValue", $editable: "$rule|isEditable"}, "$prop.value$prop.important"), + SPAN({"class": "cssSemi"}, ";") + ) +}); + +var CSSRuleTag = + TAG("$rule.tag", {rule: "$rule"}); + +var CSSImportRuleTag = domplate({ + tag: DIV({"class": "cssRule insertInto focusRow importRule", _repObject: "$rule.rule"}, + "@import "", + A({"class": "objectLink", _repObject: "$rule.rule.styleSheet"}, "$rule.rule.href"), + "";" + ) +}); + +var CSSStyleRuleTag = domplate(CSSDomplateBase, { + tag: DIV({"class": "cssRule insertInto", + $cssEditableRule: "$rule|isEditable", + $editGroup: "$rule|isSelectorEditable", + _repObject: "$rule.rule", + "ruleId": "$rule.id", role : 'presentation'}, + DIV({"class": "cssHead focusRow", role : 'listitem'}, + SPAN({"class": "cssSelector", $editable: "$rule|isSelectorEditable"}, "$rule.selector"), " {" + ), + DIV({role : 'group'}, + DIV({"class": "cssPropertyListBox", role : 'listbox'}, + FOR("prop", "$rule.props", + TAG(CSSPropTag.tag, {rule: "$rule", prop: "$prop"}) + ) + ) + ), + DIV({"class": "editable insertBefore", role:"presentation"}, "}") + ) +}); + +var reSplitCSS = /(url\("?[^"\)]+?"?\))|(rgb\(.*?\))|(#[\dA-Fa-f]+)|(-?\d+(\.\d+)?(%|[a-z]{1,2})?)|([^,\s]+)|"(.*?)"/; + +var reURL = /url\("?([^"\)]+)?"?\)/; + +var reRepeat = /no-repeat|repeat-x|repeat-y|repeat/; + +//const sothinkInstalled = !!$("swfcatcherKey_sidebar"); +var sothinkInstalled = false; +var styleGroups = +{ + text: [ + "font-family", + "font-size", + "font-weight", + "font-style", + "color", + "text-transform", + "text-decoration", + "letter-spacing", + "word-spacing", + "line-height", + "text-align", + "vertical-align", + "direction", + "column-count", + "column-gap", + "column-width" + ], + + background: [ + "background-color", + "background-image", + "background-repeat", + "background-position", + "background-attachment", + "opacity" + ], + + box: [ + "width", + "height", + "top", + "right", + "bottom", + "left", + "margin-top", + "margin-right", + "margin-bottom", + "margin-left", + "padding-top", + "padding-right", + "padding-bottom", + "padding-left", + "border-top-width", + "border-right-width", + "border-bottom-width", + "border-left-width", + "border-top-color", + "border-right-color", + "border-bottom-color", + "border-left-color", + "border-top-style", + "border-right-style", + "border-bottom-style", + "border-left-style", + "-moz-border-top-radius", + "-moz-border-right-radius", + "-moz-border-bottom-radius", + "-moz-border-left-radius", + "outline-top-width", + "outline-right-width", + "outline-bottom-width", + "outline-left-width", + "outline-top-color", + "outline-right-color", + "outline-bottom-color", + "outline-left-color", + "outline-top-style", + "outline-right-style", + "outline-bottom-style", + "outline-left-style" + ], + + layout: [ + "position", + "display", + "visibility", + "z-index", + "overflow-x", // http://www.w3.org/TR/2002/WD-css3-box-20021024/#overflow + "overflow-y", + "overflow-clip", + "white-space", + "clip", + "float", + "clear", + "-moz-box-sizing" + ], + + other: [ + "cursor", + "list-style-image", + "list-style-position", + "list-style-type", + "marker-offset", + "user-focus", + "user-select", + "user-modify", + "user-input" + ] +}; + +var styleGroupTitles = +{ + text: "Text", + background: "Background", + box: "Box Model", + layout: "Layout", + other: "Other" +}; + +Firebug.CSSModule = extend(Firebug.Module, +{ + freeEdit: function(styleSheet, value) + { + if (!styleSheet.editStyleSheet) + { + var ownerNode = getStyleSheetOwnerNode(styleSheet); + styleSheet.disabled = true; + + var url = CCSV("@mozilla.org/network/standard-url;1", Components.interfaces.nsIURL); + url.spec = styleSheet.href; + + var editStyleSheet = ownerNode.ownerDocument.createElementNS( + "http://www.w3.org/1999/xhtml", + "style"); + unwrapObject(editStyleSheet).firebugIgnore = true; + editStyleSheet.setAttribute("type", "text/css"); + editStyleSheet.setAttributeNS( + "http://www.w3.org/XML/1998/namespace", + "base", + url.directory); + if (ownerNode.hasAttribute("media")) + { + editStyleSheet.setAttribute("media", ownerNode.getAttribute("media")); + } + + // Insert the edited stylesheet directly after the old one to ensure the styles + // cascade properly. + ownerNode.parentNode.insertBefore(editStyleSheet, ownerNode.nextSibling); + + styleSheet.editStyleSheet = editStyleSheet; + } + + styleSheet.editStyleSheet.innerHTML = value; + if (FBTrace.DBG_CSS) + FBTrace.sysout("css.saveEdit styleSheet.href:"+styleSheet.href+" got innerHTML:"+value+"\n"); + + dispatch(this.fbListeners, "onCSSFreeEdit", [styleSheet, value]); + }, + + insertRule: function(styleSheet, cssText, ruleIndex) + { + if (FBTrace.DBG_CSS) FBTrace.sysout("Insert: " + ruleIndex + " " + cssText); + var insertIndex = styleSheet.insertRule(cssText, ruleIndex); + + dispatch(this.fbListeners, "onCSSInsertRule", [styleSheet, cssText, ruleIndex]); + + return insertIndex; + }, + + deleteRule: function(styleSheet, ruleIndex) + { + if (FBTrace.DBG_CSS) FBTrace.sysout("deleteRule: " + ruleIndex + " " + styleSheet.cssRules.length, styleSheet.cssRules); + dispatch(this.fbListeners, "onCSSDeleteRule", [styleSheet, ruleIndex]); + + styleSheet.deleteRule(ruleIndex); + }, + + setProperty: function(rule, propName, propValue, propPriority) + { + var style = rule.style || rule; + + // Record the original CSS text for the inline case so we can reconstruct at a later + // point for diffing purposes + var baseText = style.cssText; + + // good browsers + if (style.getPropertyValue) + { + var prevValue = style.getPropertyValue(propName); + var prevPriority = style.getPropertyPriority(propName); + + // XXXjoe Gecko bug workaround: Just changing priority doesn't have any effect + // unless we remove the property first + style.removeProperty(propName); + + style.setProperty(propName, propValue, propPriority); + } + // sad browsers + else + { + // TODO: xxxpedro parse CSS rule to find property priority in IE? + //console.log(propName, propValue); + style[toCamelCase(propName)] = propValue; + } + + if (propName) { + dispatch(this.fbListeners, "onCSSSetProperty", [style, propName, propValue, propPriority, prevValue, prevPriority, rule, baseText]); + } + }, + + removeProperty: function(rule, propName, parent) + { + var style = rule.style || rule; + + // Record the original CSS text for the inline case so we can reconstruct at a later + // point for diffing purposes + var baseText = style.cssText; + + if (style.getPropertyValue) + { + + var prevValue = style.getPropertyValue(propName); + var prevPriority = style.getPropertyPriority(propName); + + style.removeProperty(propName); + } + else + { + style[toCamelCase(propName)] = ""; + } + + if (propName) { + dispatch(this.fbListeners, "onCSSRemoveProperty", [style, propName, prevValue, prevPriority, rule, baseText]); + } + }/*, + + cleanupSheets: function(doc, context) + { + // Due to the manner in which the layout engine handles multiple + // references to the same sheet we need to kick it a little bit. + // The injecting a simple stylesheet then removing it will force + // Firefox to regenerate it's CSS hierarchy. + // + // WARN: This behavior was determined anecdotally. + // See http://code.google.com/p/fbug/issues/detail?id=2440 + var style = doc.createElementNS("http://www.w3.org/1999/xhtml", "style"); + style.setAttribute("charset","utf-8"); + unwrapObject(style).firebugIgnore = true; + style.setAttribute("type", "text/css"); + style.innerHTML = "#fbIgnoreStyleDO_NOT_USE {}"; + addStyleSheet(doc, style); + style.parentNode.removeChild(style); + + // https://bugzilla.mozilla.org/show_bug.cgi?id=500365 + // This voodoo touches each style sheet to force some Firefox internal change to allow edits. + var styleSheets = getAllStyleSheets(context); + for(var i = 0; i < styleSheets.length; i++) + { + try + { + var rules = styleSheets[i].cssRules; + if (rules.length > 0) + var touch = rules[0]; + if (FBTrace.DBG_CSS && touch) + FBTrace.sysout("css.show() touch "+typeof(touch)+" in "+(styleSheets[i].href?styleSheets[i].href:context.getName())); + } + catch(e) + { + if (FBTrace.DBG_ERRORS) + FBTrace.sysout("css.show: sheet.cssRules FAILS for "+(styleSheets[i]?styleSheets[i].href:"null sheet")+e, e); + } + } + }, + cleanupSheetHandler: function(event, context) + { + var target = event.target || event.srcElement, + tagName = (target.tagName || "").toLowerCase(); + if (tagName == "link") + { + this.cleanupSheets(target.ownerDocument, context); + } + }, + watchWindow: function(context, win) + { + var cleanupSheets = bind(this.cleanupSheets, this), + cleanupSheetHandler = bind(this.cleanupSheetHandler, this, context), + doc = win.document; + + //doc.addEventListener("DOMAttrModified", cleanupSheetHandler, false); + //doc.addEventListener("DOMNodeInserted", cleanupSheetHandler, false); + }, + loadedContext: function(context) + { + var self = this; + iterateWindows(context.browser.contentWindow, function(subwin) + { + self.cleanupSheets(subwin.document, context); + }); + } + /**/ +}); + +// ************************************************************************************************ + +Firebug.CSSStyleSheetPanel = function() {}; + +Firebug.CSSStyleSheetPanel.prototype = extend(Firebug.SourceBoxPanel, +{ + template: domplate( + { + tag: + DIV({"class": "cssSheet insertInto a11yCSSView"}, + FOR("rule", "$rules", + CSSRuleTag + ), + DIV({"class": "cssSheet editable insertBefore"}, "") + ) + }), + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + refresh: function() + { + if (this.location) + this.updateLocation(this.location); + else if (this.selection) + this.updateSelection(this.selection); + }, + + toggleEditing: function() + { + if (!this.stylesheetEditor) + this.stylesheetEditor = new StyleSheetEditor(this.document); + + if (this.editing) + Firebug.Editor.stopEditing(); + else + { + if (!this.location) + return; + + var styleSheet = this.location.editStyleSheet + ? this.location.editStyleSheet.sheet + : this.location; + + var css = getStyleSheetCSS(styleSheet, this.context); + //var topmost = getTopmostRuleLine(this.panelNode); + + this.stylesheetEditor.styleSheet = this.location; + Firebug.Editor.startEditing(this.panelNode, css, this.stylesheetEditor); + //this.stylesheetEditor.scrollToLine(topmost.line, topmost.offset); + } + }, + + getStylesheetURL: function(rule) + { + if (this.location.href) + return this.location.href; + else + return this.context.window.location.href; + }, + + getRuleByLine: function(styleSheet, line) + { + if (!domUtils) + return null; + + var cssRules = styleSheet.cssRules; + for (var i = 0; i < cssRules.length; ++i) + { + var rule = cssRules[i]; + if (rule instanceof CSSStyleRule) + { + var ruleLine = domUtils.getRuleLine(rule); + if (ruleLine >= line) + return rule; + } + } + }, + + highlightRule: function(rule) + { + var ruleElement = Firebug.getElementByRepObject(this.panelNode.firstChild, rule); + if (ruleElement) + { + scrollIntoCenterView(ruleElement, this.panelNode); + setClassTimed(ruleElement, "jumpHighlight", this.context); + } + }, + + getStyleSheetRules: function(context, styleSheet) + { + var isSystemSheet = isSystemStyleSheet(styleSheet); + + function appendRules(cssRules) + { + for (var i = 0; i < cssRules.length; ++i) + { + var rule = cssRules[i]; + + // TODO: xxxpedro opera instanceof stylesheet remove the following comments when + // the issue with opera and style sheet Classes has been solved. + + //if (rule instanceof CSSStyleRule) + if (instanceOf(rule, "CSSStyleRule")) + { + var props = this.getRuleProperties(context, rule); + //var line = domUtils.getRuleLine(rule); + var line = null; + + var selector = rule.selectorText; + + if (isIE) + { + selector = selector.replace(reSelectorTag, + function(s){return s.toLowerCase();}); + } + + var ruleId = rule.selectorText+"/"+line; + rules.push({tag: CSSStyleRuleTag.tag, rule: rule, id: ruleId, + selector: selector, props: props, + isSystemSheet: isSystemSheet, + isSelectorEditable: true}); + } + //else if (rule instanceof CSSImportRule) + else if (instanceOf(rule, "CSSImportRule")) + rules.push({tag: CSSImportRuleTag.tag, rule: rule}); + //else if (rule instanceof CSSMediaRule) + else if (instanceOf(rule, "CSSMediaRule")) + appendRules.apply(this, [rule.cssRules]); + else + { + if (FBTrace.DBG_ERRORS || FBTrace.DBG_CSS) + FBTrace.sysout("css getStyleSheetRules failed to classify a rule ", rule); + } + } + } + + var rules = []; + appendRules.apply(this, [styleSheet.cssRules || styleSheet.rules]); + return rules; + }, + + parseCSSProps: function(style, inheritMode) + { + var props = []; + + if (Firebug.expandShorthandProps) + { + var count = style.length-1, + index = style.length; + while (index--) + { + var propName = style.item(count - index); + this.addProperty(propName, style.getPropertyValue(propName), !!style.getPropertyPriority(propName), false, inheritMode, props); + } + } + else + { + var lines = style.cssText.match(/(?:[^;\(]*(?:\([^\)]*?\))?[^;\(]*)*;?/g); + var propRE = /\s*([^:\s]*)\s*:\s*(.*?)\s*(! important)?;?$/; + var line,i=0; + // TODO: xxxpedro port to firebug: variable leaked into global namespace + var m; + + while(line=lines[i++]){ + m = propRE.exec(line); + if(!m) + continue; + //var name = m[1], value = m[2], important = !!m[3]; + if (m[2]) + this.addProperty(m[1], m[2], !!m[3], false, inheritMode, props); + }; + } + + return props; + }, + + getRuleProperties: function(context, rule, inheritMode) + { + var props = this.parseCSSProps(rule.style, inheritMode); + + // TODO: xxxpedro port to firebug: variable leaked into global namespace + //var line = domUtils.getRuleLine(rule); + var line; + var ruleId = rule.selectorText+"/"+line; + this.addOldProperties(context, ruleId, inheritMode, props); + sortProperties(props); + + return props; + }, + + addOldProperties: function(context, ruleId, inheritMode, props) + { + if (context.selectorMap && context.selectorMap.hasOwnProperty(ruleId) ) + { + var moreProps = context.selectorMap[ruleId]; + for (var i = 0; i < moreProps.length; ++i) + { + var prop = moreProps[i]; + this.addProperty(prop.name, prop.value, prop.important, true, inheritMode, props); + } + } + }, + + addProperty: function(name, value, important, disabled, inheritMode, props) + { + name = name.toLowerCase(); + + if (inheritMode && !inheritedStyleNames[name]) + return; + + name = this.translateName(name, value); + if (name) + { + value = stripUnits(rgbToHex(value)); + important = important ? " !important" : ""; + + var prop = {name: name, value: value, important: important, disabled: disabled}; + props.push(prop); + } + }, + + translateName: function(name, value) + { + // Don't show these proprietary Mozilla properties + if ((value == "-moz-initial" + && (name == "-moz-background-clip" || name == "-moz-background-origin" + || name == "-moz-background-inline-policy")) + || (value == "physical" + && (name == "margin-left-ltr-source" || name == "margin-left-rtl-source" + || name == "margin-right-ltr-source" || name == "margin-right-rtl-source")) + || (value == "physical" + && (name == "padding-left-ltr-source" || name == "padding-left-rtl-source" + || name == "padding-right-ltr-source" || name == "padding-right-rtl-source"))) + return null; + + // Translate these back to the form the user probably expects + if (name == "margin-left-value") + return "margin-left"; + else if (name == "margin-right-value") + return "margin-right"; + else if (name == "margin-top-value") + return "margin-top"; + else if (name == "margin-bottom-value") + return "margin-bottom"; + else if (name == "padding-left-value") + return "padding-left"; + else if (name == "padding-right-value") + return "padding-right"; + else if (name == "padding-top-value") + return "padding-top"; + else if (name == "padding-bottom-value") + return "padding-bottom"; + // XXXjoe What about border! + else + return name; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + editElementStyle: function() + { + ///var rulesBox = this.panelNode.getElementsByClassName("cssElementRuleContainer")[0]; + var rulesBox = $$(".cssElementRuleContainer", this.panelNode)[0]; + var styleRuleBox = rulesBox && Firebug.getElementByRepObject(rulesBox, this.selection); + if (!styleRuleBox) + { + var rule = {rule: this.selection, inherited: false, selector: "element.style", props: []}; + if (!rulesBox) + { + // The element did not have any displayed styles. We need to create the whole tree and remove + // the no styles message + styleRuleBox = this.template.cascadedTag.replace({ + rules: [rule], inherited: [], inheritLabel: "Inherited from" // $STR("InheritedFrom") + }, this.panelNode); + + ///styleRuleBox = styleRuleBox.getElementsByClassName("cssElementRuleContainer")[0]; + styleRuleBox = $$(".cssElementRuleContainer", styleRuleBox)[0]; + } + else + styleRuleBox = this.template.ruleTag.insertBefore({rule: rule}, rulesBox); + + ///styleRuleBox = styleRuleBox.getElementsByClassName("insertInto")[0]; + styleRuleBox = $$(".insertInto", styleRuleBox)[0]; + } + + Firebug.Editor.insertRowForObject(styleRuleBox); + }, + + insertPropertyRow: function(row) + { + Firebug.Editor.insertRowForObject(row); + }, + + insertRule: function(row) + { + var location = getAncestorByClass(row, "cssRule"); + if (!location) + { + location = getChildByClass(this.panelNode, "cssSheet"); + Firebug.Editor.insertRowForObject(location); + } + else + { + Firebug.Editor.insertRow(location, "before"); + } + }, + + editPropertyRow: function(row) + { + var propValueBox = getChildByClass(row, "cssPropValue"); + Firebug.Editor.startEditing(propValueBox); + }, + + deletePropertyRow: function(row) + { + var rule = Firebug.getRepObject(row); + var propName = getChildByClass(row, "cssPropName")[textContent]; + Firebug.CSSModule.removeProperty(rule, propName); + + // Remove the property from the selector map, if it was disabled + var ruleId = Firebug.getRepNode(row).getAttribute("ruleId"); + if ( this.context.selectorMap && this.context.selectorMap.hasOwnProperty(ruleId) ) + { + var map = this.context.selectorMap[ruleId]; + for (var i = 0; i < map.length; ++i) + { + if (map[i].name == propName) + { + map.splice(i, 1); + break; + } + } + } + if (this.name == "stylesheet") + dispatch([Firebug.A11yModel], 'onInlineEditorClose', [this, row.firstChild, true]); + row.parentNode.removeChild(row); + + this.markChange(this.name == "stylesheet"); + }, + + disablePropertyRow: function(row) + { + toggleClass(row, "disabledStyle"); + + var rule = Firebug.getRepObject(row); + var propName = getChildByClass(row, "cssPropName")[textContent]; + + if (!this.context.selectorMap) + this.context.selectorMap = {}; + + // XXXjoe Generate unique key for elements too + var ruleId = Firebug.getRepNode(row).getAttribute("ruleId"); + if (!(this.context.selectorMap.hasOwnProperty(ruleId))) + this.context.selectorMap[ruleId] = []; + + var map = this.context.selectorMap[ruleId]; + var propValue = getChildByClass(row, "cssPropValue")[textContent]; + var parsedValue = parsePriority(propValue); + if (hasClass(row, "disabledStyle")) + { + Firebug.CSSModule.removeProperty(rule, propName); + + map.push({"name": propName, "value": parsedValue.value, + "important": parsedValue.priority}); + } + else + { + Firebug.CSSModule.setProperty(rule, propName, parsedValue.value, parsedValue.priority); + + var index = findPropByName(map, propName); + map.splice(index, 1); + } + + this.markChange(this.name == "stylesheet"); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + onMouseDown: function(event) + { + //console.log("onMouseDown", event.target || event.srcElement, event); + + // xxxpedro adjusting coordinates because the panel isn't a window yet + var offset = event.clientX - this.panelNode.parentNode.offsetLeft; + + // XXjoe Hack to only allow clicking on the checkbox + if (!isLeftClick(event) || offset > 20) + return; + + var target = event.target || event.srcElement; + if (hasClass(target, "textEditor")) + return; + + var row = getAncestorByClass(target, "cssProp"); + if (row && hasClass(row, "editGroup")) + { + this.disablePropertyRow(row); + cancelEvent(event); + } + }, + + onDoubleClick: function(event) + { + //console.log("onDoubleClick", event.target || event.srcElement, event); + + // xxxpedro adjusting coordinates because the panel isn't a window yet + var offset = event.clientX - this.panelNode.parentNode.offsetLeft; + + if (!isLeftClick(event) || offset <= 20) + return; + + var target = event.target || event.srcElement; + + //console.log("ok", target, hasClass(target, "textEditorInner"), !isLeftClick(event), offset <= 20); + + // if the inline editor was clicked, don't insert a new rule + if (hasClass(target, "textEditorInner")) + return; + + var row = getAncestorByClass(target, "cssRule"); + if (row && !getAncestorByClass(target, "cssPropName") + && !getAncestorByClass(target, "cssPropValue")) + { + this.insertPropertyRow(row); + cancelEvent(event); + } + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // extends Panel + + name: "stylesheet", + title: "CSS", + parentPanel: null, + searchable: true, + dependents: ["css", "stylesheet", "dom", "domSide", "layout"], + + options: + { + hasToolButtons: true + }, + + create: function() + { + Firebug.Panel.create.apply(this, arguments); + + this.onMouseDown = bind(this.onMouseDown, this); + this.onDoubleClick = bind(this.onDoubleClick, this); + + if (this.name == "stylesheet") + { + this.onChangeSelect = bind(this.onChangeSelect, this); + + var doc = Firebug.browser.document; + var selectNode = this.selectNode = createElement("select"); + + CssAnalyzer.processAllStyleSheets(doc, function(doc, styleSheet) + { + var key = StyleSheetCache.key(styleSheet); + var fileName = getFileName(styleSheet.href) || getFileName(doc.location.href); + var option = createElement("option", {value: key}); + + option.appendChild(Firebug.chrome.document.createTextNode(fileName)); + selectNode.appendChild(option); + }); + + this.toolButtonsNode.appendChild(selectNode); + } + /**/ + }, + + onChangeSelect: function(event) + { + event = event || window.event; + var target = event.srcElement || event.currentTarget; + var key = target.value; + var styleSheet = StyleSheetCache.get(key); + + this.updateLocation(styleSheet); + }, + + initialize: function() + { + Firebug.Panel.initialize.apply(this, arguments); + + //if (!domUtils) + //{ + // try { + // domUtils = CCSV("@mozilla.org/inspector/dom-utils;1", "inIDOMUtils"); + // } catch (exc) { + // if (FBTrace.DBG_ERRORS) + // FBTrace.sysout("@mozilla.org/inspector/dom-utils;1 FAILED to load: "+exc, exc); + // } + //} + + //TODO: xxxpedro + this.context = Firebug.chrome; // TODO: xxxpedro css2 + this.document = Firebug.chrome.document; // TODO: xxxpedro css2 + + this.initializeNode(); + + if (this.name == "stylesheet") + { + var styleSheets = Firebug.browser.document.styleSheets; + + if (styleSheets.length > 0) + { + addEvent(this.selectNode, "change", this.onChangeSelect); + + this.updateLocation(styleSheets[0]); + } + } + + //Firebug.SourceBoxPanel.initialize.apply(this, arguments); + }, + + shutdown: function() + { + // must destroy the editor when we leave the panel to avoid problems (Issue 2981) + Firebug.Editor.stopEditing(); + + if (this.name == "stylesheet") + { + removeEvent(this.selectNode, "change", this.onChangeSelect); + } + + this.destroyNode(); + + Firebug.Panel.shutdown.apply(this, arguments); + }, + + destroy: function(state) + { + //state.scrollTop = this.panelNode.scrollTop ? this.panelNode.scrollTop : this.lastScrollTop; + + //persistObjects(this, state); + + // xxxpedro we are stopping the editor in the shutdown method already + //Firebug.Editor.stopEditing(); + Firebug.Panel.destroy.apply(this, arguments); + }, + + initializeNode: function(oldPanelNode) + { + addEvent(this.panelNode, "mousedown", this.onMouseDown); + addEvent(this.panelNode, "dblclick", this.onDoubleClick); + //Firebug.SourceBoxPanel.initializeNode.apply(this, arguments); + //dispatch([Firebug.A11yModel], 'onInitializeNode', [this, 'css']); + }, + + destroyNode: function() + { + removeEvent(this.panelNode, "mousedown", this.onMouseDown); + removeEvent(this.panelNode, "dblclick", this.onDoubleClick); + //Firebug.SourceBoxPanel.destroyNode.apply(this, arguments); + //dispatch([Firebug.A11yModel], 'onDestroyNode', [this, 'css']); + }, + + ishow: function(state) + { + Firebug.Inspector.stopInspecting(true); + + this.showToolbarButtons("fbCSSButtons", true); + + if (this.context.loaded && !this.location) // wait for loadedContext to restore the panel + { + restoreObjects(this, state); + + if (!this.location) + this.location = this.getDefaultLocation(); + + if (state && state.scrollTop) + this.panelNode.scrollTop = state.scrollTop; + } + }, + + ihide: function() + { + this.showToolbarButtons("fbCSSButtons", false); + + this.lastScrollTop = this.panelNode.scrollTop; + }, + + supportsObject: function(object) + { + if (object instanceof CSSStyleSheet) + return 1; + else if (object instanceof CSSStyleRule) + return 2; + else if (object instanceof CSSStyleDeclaration) + return 2; + else if (object instanceof SourceLink && object.type == "css" && reCSS.test(object.href)) + return 2; + else + return 0; + }, + + updateLocation: function(styleSheet) + { + if (!styleSheet) + return; + if (styleSheet.editStyleSheet) + styleSheet = styleSheet.editStyleSheet.sheet; + + // if it is a restricted stylesheet, show the warning message and abort the update process + if (styleSheet.restricted) + { + FirebugReps.Warning.tag.replace({object: "AccessRestricted"}, this.panelNode); + + // TODO: xxxpedro remove when there the external resource problem is fixed + CssAnalyzer.externalStyleSheetWarning.tag.append({ + object: "The stylesheet could not be loaded due to access restrictions. ", + link: "more...", + href: "http://getfirebug.com/wiki/index.php/Firebug_Lite_FAQ#I_keep_seeing_.22Access_to_restricted_URI_denied.22" + }, this.panelNode); + + return; + } + + var rules = this.getStyleSheetRules(this.context, styleSheet); + + var result; + if (rules.length) + // FIXME xxxpedro chromenew this is making iPad's Safari to crash + result = this.template.tag.replace({rules: rules}, this.panelNode); + else + result = FirebugReps.Warning.tag.replace({object: "EmptyStyleSheet"}, this.panelNode); + + // TODO: xxxpedro need to fix showToolbarButtons function + //this.showToolbarButtons("fbCSSButtons", !isSystemStyleSheet(this.location)); + + //dispatch([Firebug.A11yModel], 'onCSSRulesAdded', [this, this.panelNode]); + }, + + updateSelection: function(object) + { + this.selection = null; + + if (object instanceof CSSStyleDeclaration) { + object = object.parentRule; + } + + if (object instanceof CSSStyleRule) + { + this.navigate(object.parentStyleSheet); + this.highlightRule(object); + } + else if (object instanceof CSSStyleSheet) + { + this.navigate(object); + } + else if (object instanceof SourceLink) + { + try + { + var sourceLink = object; + + var sourceFile = getSourceFileByHref(sourceLink.href, this.context); + if (sourceFile) + { + clearNode(this.panelNode); // replace rendered stylesheets + this.showSourceFile(sourceFile); + + var lineNo = object.line; + if (lineNo) + this.scrollToLine(lineNo, this.jumpHighlightFactory(lineNo, this.context)); + } + else // XXXjjb we should not be taking this path + { + var stylesheet = getStyleSheetByHref(sourceLink.href, this.context); + if (stylesheet) + this.navigate(stylesheet); + else + { + if (FBTrace.DBG_CSS) + FBTrace.sysout("css.updateSelection no sourceFile for "+sourceLink.href, sourceLink); + } + } + } + catch(exc) { + if (FBTrace.DBG_CSS) + FBTrace.sysout("css.upDateSelection FAILS "+exc, exc); + } + } + }, + + updateOption: function(name, value) + { + if (name == "expandShorthandProps") + this.refresh(); + }, + + getLocationList: function() + { + var styleSheets = getAllStyleSheets(this.context); + return styleSheets; + }, + + getOptionsMenuItems: function() + { + return [ + {label: "Expand Shorthand Properties", type: "checkbox", checked: Firebug.expandShorthandProps, + command: bindFixed(Firebug.togglePref, Firebug, "expandShorthandProps") }, + "-", + {label: "Refresh", command: bind(this.refresh, this) } + ]; + }, + + getContextMenuItems: function(style, target) + { + var items = []; + + if (this.infoTipType == "color") + { + items.push( + {label: "CopyColor", + command: bindFixed(copyToClipboard, FBL, this.infoTipObject) } + ); + } + else if (this.infoTipType == "image") + { + items.push( + {label: "CopyImageLocation", + command: bindFixed(copyToClipboard, FBL, this.infoTipObject) }, + {label: "OpenImageInNewTab", + command: bindFixed(openNewTab, FBL, this.infoTipObject) } + ); + } + + ///if (this.selection instanceof Element) + if (isElement(this.selection)) + { + items.push( + //"-", + {label: "EditStyle", + command: bindFixed(this.editElementStyle, this) } + ); + } + else if (!isSystemStyleSheet(this.selection)) + { + items.push( + //"-", + {label: "NewRule", + command: bindFixed(this.insertRule, this, target) } + ); + } + + var cssRule = getAncestorByClass(target, "cssRule"); + if (cssRule && hasClass(cssRule, "cssEditableRule")) + { + items.push( + "-", + {label: "NewProp", + command: bindFixed(this.insertPropertyRow, this, target) } + ); + + var propRow = getAncestorByClass(target, "cssProp"); + if (propRow) + { + var propName = getChildByClass(propRow, "cssPropName")[textContent]; + var isDisabled = hasClass(propRow, "disabledStyle"); + + items.push( + {label: $STRF("EditProp", [propName]), nol10n: true, + command: bindFixed(this.editPropertyRow, this, propRow) }, + {label: $STRF("DeleteProp", [propName]), nol10n: true, + command: bindFixed(this.deletePropertyRow, this, propRow) }, + {label: $STRF("DisableProp", [propName]), nol10n: true, + type: "checkbox", checked: isDisabled, + command: bindFixed(this.disablePropertyRow, this, propRow) } + ); + } + } + + items.push( + "-", + {label: "Refresh", command: bind(this.refresh, this) } + ); + + return items; + }, + + browseObject: function(object) + { + if (this.infoTipType == "image") + { + openNewTab(this.infoTipObject); + return true; + } + }, + + showInfoTip: function(infoTip, target, x, y) + { + var propValue = getAncestorByClass(target, "cssPropValue"); + if (propValue) + { + var offset = getClientOffset(propValue); + var offsetX = x-offset.x; + + var text = propValue[textContent]; + var charWidth = propValue.offsetWidth/text.length; + var charOffset = Math.floor(offsetX/charWidth); + + var cssValue = parseCSSValue(text, charOffset); + if (cssValue) + { + if (cssValue.value == this.infoTipValue) + return true; + + this.infoTipValue = cssValue.value; + + if (cssValue.type == "rgb" || (!cssValue.type && isColorKeyword(cssValue.value))) + { + this.infoTipType = "color"; + this.infoTipObject = cssValue.value; + + return Firebug.InfoTip.populateColorInfoTip(infoTip, cssValue.value); + } + else if (cssValue.type == "url") + { + ///var propNameNode = target.parentNode.getElementsByClassName("cssPropName").item(0); + var propNameNode = getElementByClass(target.parentNode, "cssPropName"); + if (propNameNode && isImageRule(propNameNode[textContent])) + { + var rule = Firebug.getRepObject(target); + var baseURL = this.getStylesheetURL(rule); + var relURL = parseURLValue(cssValue.value); + var absURL = isDataURL(relURL) ? relURL:absoluteURL(relURL, baseURL); + var repeat = parseRepeatValue(text); + + this.infoTipType = "image"; + this.infoTipObject = absURL; + + return Firebug.InfoTip.populateImageInfoTip(infoTip, absURL, repeat); + } + } + } + } + + delete this.infoTipType; + delete this.infoTipValue; + delete this.infoTipObject; + }, + + getEditor: function(target, value) + { + if (target == this.panelNode + || hasClass(target, "cssSelector") || hasClass(target, "cssRule") + || hasClass(target, "cssSheet")) + { + if (!this.ruleEditor) + this.ruleEditor = new CSSRuleEditor(this.document); + + return this.ruleEditor; + } + else + { + if (!this.editor) + this.editor = new CSSEditor(this.document); + + return this.editor; + } + }, + + getDefaultLocation: function() + { + try + { + var styleSheets = this.context.window.document.styleSheets; + if (styleSheets.length) + { + var sheet = styleSheets[0]; + return (Firebug.filterSystemURLs && isSystemURL(getURLForStyleSheet(sheet))) ? null : sheet; + } + } + catch (exc) + { + if (FBTrace.DBG_LOCATIONS) + FBTrace.sysout("css.getDefaultLocation FAILS "+exc, exc); + } + }, + + getObjectDescription: function(styleSheet) + { + var url = getURLForStyleSheet(styleSheet); + var instance = getInstanceForStyleSheet(styleSheet); + + var baseDescription = splitURLBase(url); + if (instance) { + baseDescription.name = baseDescription.name + " #" + (instance + 1); + } + return baseDescription; + }, + + search: function(text, reverse) + { + var curDoc = this.searchCurrentDoc(!Firebug.searchGlobal, text, reverse); + if (!curDoc && Firebug.searchGlobal) + { + return this.searchOtherDocs(text, reverse); + } + return curDoc; + }, + + searchOtherDocs: function(text, reverse) + { + var scanRE = Firebug.Search.getTestingRegex(text); + function scanDoc(styleSheet) { + // we don't care about reverse here as we are just looking for existence, + // if we do have a result we will handle the reverse logic on display + for (var i = 0; i < styleSheet.cssRules.length; i++) + { + if (scanRE.test(styleSheet.cssRules[i].cssText)) + { + return true; + } + } + } + + if (this.navigateToNextDocument(scanDoc, reverse)) + { + return this.searchCurrentDoc(true, text, reverse); + } + }, + + searchCurrentDoc: function(wrapSearch, text, reverse) + { + if (!text) + { + delete this.currentSearch; + return false; + } + + var row; + if (this.currentSearch && text == this.currentSearch.text) + { + row = this.currentSearch.findNext(wrapSearch, false, reverse, Firebug.Search.isCaseSensitive(text)); + } + else + { + if (this.editing) + { + this.currentSearch = new TextSearch(this.stylesheetEditor.box); + row = this.currentSearch.find(text, reverse, Firebug.Search.isCaseSensitive(text)); + + if (row) + { + var sel = this.document.defaultView.getSelection(); + sel.removeAllRanges(); + sel.addRange(this.currentSearch.range); + scrollSelectionIntoView(this); + return true; + } + else + return false; + } + else + { + function findRow(node) { return node.nodeType == 1 ? node : node.parentNode; } + this.currentSearch = new TextSearch(this.panelNode, findRow); + row = this.currentSearch.find(text, reverse, Firebug.Search.isCaseSensitive(text)); + } + } + + if (row) + { + this.document.defaultView.getSelection().selectAllChildren(row); + scrollIntoCenterView(row, this.panelNode); + dispatch([Firebug.A11yModel], 'onCSSSearchMatchFound', [this, text, row]); + return true; + } + else + { + dispatch([Firebug.A11yModel], 'onCSSSearchMatchFound', [this, text, null]); + return false; + } + }, + + getSearchOptionsMenuItems: function() + { + return [ + Firebug.Search.searchOptionMenu("search.Case_Sensitive", "searchCaseSensitive"), + Firebug.Search.searchOptionMenu("search.Multiple_Files", "searchGlobal") + ]; + } +}); +/**/ +// ************************************************************************************************ + +function CSSElementPanel() {} + +CSSElementPanel.prototype = extend(Firebug.CSSStyleSheetPanel.prototype, +{ + template: domplate( + { + cascadedTag: + DIV({"class": "a11yCSSView", role : 'presentation'}, + DIV({role : 'list', 'aria-label' : $STR('aria.labels.style rules') }, + FOR("rule", "$rules", + TAG("$ruleTag", {rule: "$rule"}) + ) + ), + DIV({role : "list", 'aria-label' :$STR('aria.labels.inherited style rules')}, + FOR("section", "$inherited", + H1({"class": "cssInheritHeader groupHeader focusRow", role : 'listitem' }, + SPAN({"class": "cssInheritLabel"}, "$inheritLabel"), + TAG(FirebugReps.Element.shortTag, {object: "$section.element"}) + ), + DIV({role : 'group'}, + FOR("rule", "$section.rules", + TAG("$ruleTag", {rule: "$rule"}) + ) + ) + ) + ) + ), + + ruleTag: + isIE ? + // IE needs the sourceLink first, otherwise it will be rendered outside the panel + DIV({"class": "cssElementRuleContainer"}, + TAG(FirebugReps.SourceLink.tag, {object: "$rule.sourceLink"}), + TAG(CSSStyleRuleTag.tag, {rule: "$rule"}) + ) + : + // other browsers need the sourceLink last, otherwise it will cause an extra space + // before the rule representation + DIV({"class": "cssElementRuleContainer"}, + TAG(CSSStyleRuleTag.tag, {rule: "$rule"}), + TAG(FirebugReps.SourceLink.tag, {object: "$rule.sourceLink"}) + ) + }), + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + updateCascadeView: function(element) + { + //dispatch([Firebug.A11yModel], 'onBeforeCSSRulesAdded', [this]); + var rules = [], sections = [], usedProps = {}; + this.getInheritedRules(element, sections, usedProps); + this.getElementRules(element, rules, usedProps); + + if (rules.length || sections.length) + { + var inheritLabel = "Inherited from"; // $STR("InheritedFrom"); + var result = this.template.cascadedTag.replace({rules: rules, inherited: sections, + inheritLabel: inheritLabel}, this.panelNode); + //dispatch([Firebug.A11yModel], 'onCSSRulesAdded', [this, result]); + } + else + { + var result = FirebugReps.Warning.tag.replace({object: "EmptyElementCSS"}, this.panelNode); + //dispatch([Firebug.A11yModel], 'onCSSRulesAdded', [this, result]); + } + + // TODO: xxxpedro remove when there the external resource problem is fixed + if (CssAnalyzer.hasExternalStyleSheet()) + CssAnalyzer.externalStyleSheetWarning.tag.append({ + object: "The results here may be inaccurate because some " + + "stylesheets could not be loaded due to access restrictions. ", + link: "more...", + href: "http://getfirebug.com/wiki/index.php/Firebug_Lite_FAQ#I_keep_seeing_.22This_element_has_no_style_rules.22" + }, this.panelNode); + }, + + getStylesheetURL: function(rule) + { + // if the parentStyleSheet.href is null, CSS std says its inline style. + // TODO: xxxpedro IE doesn't have rule.parentStyleSheet so we must fall back to the doc.location + if (rule && rule.parentStyleSheet && rule.parentStyleSheet.href) + return rule.parentStyleSheet.href; + else + return this.selection.ownerDocument.location.href; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + getInheritedRules: function(element, sections, usedProps) + { + var parent = element.parentNode; + if (parent && parent.nodeType == 1) + { + this.getInheritedRules(parent, sections, usedProps); + + var rules = []; + this.getElementRules(parent, rules, usedProps, true); + + if (rules.length) + sections.splice(0, 0, {element: parent, rules: rules}); + } + }, + + getElementRules: function(element, rules, usedProps, inheritMode) + { + var inspectedRules, displayedRules = {}; + + inspectedRules = CssAnalyzer.getElementCSSRules(element); + + if (inspectedRules) + { + for (var i = 0, length=inspectedRules.length; i < length; ++i) + { + var ruleId = inspectedRules[i]; + var ruleData = CssAnalyzer.getRuleData(ruleId); + var rule = ruleData.rule; + + var ssid = ruleData.styleSheetId; + var parentStyleSheet = StyleSheetCache.get(ssid); + + var href = parentStyleSheet.externalURL ? parentStyleSheet.externalURL : parentStyleSheet.href; // Null means inline + + var instance = null; + //var instance = getInstanceForStyleSheet(rule.parentStyleSheet, element.ownerDocument); + + var isSystemSheet = false; + //var isSystemSheet = isSystemStyleSheet(rule.parentStyleSheet); + + if (!Firebug.showUserAgentCSS && isSystemSheet) // This removes user agent rules + continue; + + if (!href) + href = element.ownerDocument.location.href; // http://code.google.com/p/fbug/issues/detail?id=452 + + var props = this.getRuleProperties(this.context, rule, inheritMode); + if (inheritMode && !props.length) + continue; + + // + //var line = domUtils.getRuleLine(rule); + // TODO: xxxpedro CSS line number + var line = ruleData.lineNo; + + var ruleId = rule.selectorText+"/"+line; + var sourceLink = new SourceLink(href, line, "css", rule, instance); + + this.markOverridenProps(props, usedProps, inheritMode); + + rules.splice(0, 0, {rule: rule, id: ruleId, + selector: ruleData.selector, sourceLink: sourceLink, + props: props, inherited: inheritMode, + isSystemSheet: isSystemSheet}); + } + } + + if (element.style) + this.getStyleProperties(element, rules, usedProps, inheritMode); + + if (FBTrace.DBG_CSS) + FBTrace.sysout("getElementRules "+rules.length+" rules for "+getElementXPath(element), rules); + }, + /* + getElementRules: function(element, rules, usedProps, inheritMode) + { + var inspectedRules, displayedRules = {}; + try + { + inspectedRules = domUtils ? domUtils.getCSSStyleRules(element) : null; + } catch (exc) {} + + if (inspectedRules) + { + for (var i = 0; i < inspectedRules.Count(); ++i) + { + var rule = QI(inspectedRules.GetElementAt(i), nsIDOMCSSStyleRule); + + var href = rule.parentStyleSheet.href; // Null means inline + + var instance = getInstanceForStyleSheet(rule.parentStyleSheet, element.ownerDocument); + + var isSystemSheet = isSystemStyleSheet(rule.parentStyleSheet); + if (!Firebug.showUserAgentCSS && isSystemSheet) // This removes user agent rules + continue; + if (!href) + href = element.ownerDocument.location.href; // http://code.google.com/p/fbug/issues/detail?id=452 + + var props = this.getRuleProperties(this.context, rule, inheritMode); + if (inheritMode && !props.length) + continue; + + var line = domUtils.getRuleLine(rule); + var ruleId = rule.selectorText+"/"+line; + var sourceLink = new SourceLink(href, line, "css", rule, instance); + + this.markOverridenProps(props, usedProps, inheritMode); + + rules.splice(0, 0, {rule: rule, id: ruleId, + selector: rule.selectorText, sourceLink: sourceLink, + props: props, inherited: inheritMode, + isSystemSheet: isSystemSheet}); + } + } + + if (element.style) + this.getStyleProperties(element, rules, usedProps, inheritMode); + + if (FBTrace.DBG_CSS) + FBTrace.sysout("getElementRules "+rules.length+" rules for "+getElementXPath(element), rules); + }, + /**/ + markOverridenProps: function(props, usedProps, inheritMode) + { + for (var i = 0; i < props.length; ++i) + { + var prop = props[i]; + if ( usedProps.hasOwnProperty(prop.name) ) + { + var deadProps = usedProps[prop.name]; // all previous occurrences of this property + for (var j = 0; j < deadProps.length; ++j) + { + var deadProp = deadProps[j]; + if (!deadProp.disabled && !deadProp.wasInherited && deadProp.important && !prop.important) + prop.overridden = true; // new occurrence overridden + else if (!prop.disabled) + deadProp.overridden = true; // previous occurrences overridden + } + } + else + usedProps[prop.name] = []; + + prop.wasInherited = inheritMode ? true : false; + usedProps[prop.name].push(prop); // all occurrences of a property seen so far, by name + } + }, + + getStyleProperties: function(element, rules, usedProps, inheritMode) + { + var props = this.parseCSSProps(element.style, inheritMode); + this.addOldProperties(this.context, getElementXPath(element), inheritMode, props); + + sortProperties(props); + this.markOverridenProps(props, usedProps, inheritMode); + + if (props.length) + rules.splice(0, 0, + {rule: element, id: getElementXPath(element), + selector: "element.style", props: props, inherited: inheritMode}); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // extends Panel + + name: "css", + title: "Style", + parentPanel: "HTML", + order: 0, + + initialize: function() + { + this.context = Firebug.chrome; // TODO: xxxpedro css2 + this.document = Firebug.chrome.document; // TODO: xxxpedro css2 + + Firebug.CSSStyleSheetPanel.prototype.initialize.apply(this, arguments); + + // TODO: xxxpedro css2 + var selection = ElementCache.get(Firebug.context.persistedState.selectedHTMLElementId); + if (selection) + this.select(selection, true); + + //this.updateCascadeView(document.getElementsByTagName("h1")[0]); + //this.updateCascadeView(document.getElementById("build")); + + /* + this.onStateChange = bindFixed(this.contentStateCheck, this); + this.onHoverChange = bindFixed(this.contentStateCheck, this, STATE_HOVER); + this.onActiveChange = bindFixed(this.contentStateCheck, this, STATE_ACTIVE); + /**/ + }, + + ishow: function(state) + { + }, + + watchWindow: function(win) + { + if (domUtils) + { + // Normally these would not be required, but in order to update after the state is set + // using the options menu we need to monitor these global events as well + var doc = win.document; + ///addEvent(doc, "mouseover", this.onHoverChange); + ///addEvent(doc, "mousedown", this.onActiveChange); + } + }, + unwatchWindow: function(win) + { + var doc = win.document; + ///removeEvent(doc, "mouseover", this.onHoverChange); + ///removeEvent(doc, "mousedown", this.onActiveChange); + + if (isAncestor(this.stateChangeEl, doc)) + { + this.removeStateChangeHandlers(); + } + }, + + supportsObject: function(object) + { + return object instanceof Element ? 1 : 0; + }, + + updateView: function(element) + { + this.updateCascadeView(element); + if (domUtils) + { + this.contentState = safeGetContentState(element); + this.addStateChangeHandlers(element); + } + }, + + updateSelection: function(element) + { + if ( !instanceOf(element , "Element") ) // html supports SourceLink + return; + + if (sothinkInstalled) + { + FirebugReps.Warning.tag.replace({object: "SothinkWarning"}, this.panelNode); + return; + } + + /* + if (!domUtils) + { + FirebugReps.Warning.tag.replace({object: "DOMInspectorWarning"}, this.panelNode); + return; + } + /**/ + + if (!element) + return; + + this.updateView(element); + }, + + updateOption: function(name, value) + { + if (name == "showUserAgentCSS" || name == "expandShorthandProps") + this.refresh(); + }, + + getOptionsMenuItems: function() + { + var ret = [ + {label: "Show User Agent CSS", type: "checkbox", checked: Firebug.showUserAgentCSS, + command: bindFixed(Firebug.togglePref, Firebug, "showUserAgentCSS") }, + {label: "Expand Shorthand Properties", type: "checkbox", checked: Firebug.expandShorthandProps, + command: bindFixed(Firebug.togglePref, Firebug, "expandShorthandProps") } + ]; + if (domUtils && this.selection) + { + var state = safeGetContentState(this.selection); + + ret.push("-"); + ret.push({label: ":active", type: "checkbox", checked: state & STATE_ACTIVE, + command: bindFixed(this.updateContentState, this, STATE_ACTIVE, state & STATE_ACTIVE)}); + ret.push({label: ":hover", type: "checkbox", checked: state & STATE_HOVER, + command: bindFixed(this.updateContentState, this, STATE_HOVER, state & STATE_HOVER)}); + } + return ret; + }, + + updateContentState: function(state, remove) + { + domUtils.setContentState(remove ? this.selection.ownerDocument.documentElement : this.selection, state); + this.refresh(); + }, + + addStateChangeHandlers: function(el) + { + this.removeStateChangeHandlers(); + + /* + addEvent(el, "focus", this.onStateChange); + addEvent(el, "blur", this.onStateChange); + addEvent(el, "mouseup", this.onStateChange); + addEvent(el, "mousedown", this.onStateChange); + addEvent(el, "mouseover", this.onStateChange); + addEvent(el, "mouseout", this.onStateChange); + /**/ + + this.stateChangeEl = el; + }, + + removeStateChangeHandlers: function() + { + var sel = this.stateChangeEl; + if (sel) + { + /* + removeEvent(sel, "focus", this.onStateChange); + removeEvent(sel, "blur", this.onStateChange); + removeEvent(sel, "mouseup", this.onStateChange); + removeEvent(sel, "mousedown", this.onStateChange); + removeEvent(sel, "mouseover", this.onStateChange); + removeEvent(sel, "mouseout", this.onStateChange); + /**/ + } + }, + + contentStateCheck: function(state) + { + if (!state || this.contentState & state) + { + var timeoutRunner = bindFixed(function() + { + var newState = safeGetContentState(this.selection); + if (newState != this.contentState) + { + this.context.invalidatePanels(this.name); + } + }, this); + + // Delay exec until after the event has processed and the state has been updated + setTimeout(timeoutRunner, 0); + } + } +}); + +function safeGetContentState(selection) +{ + try + { + return domUtils.getContentState(selection); + } + catch (e) + { + if (FBTrace.DBG_ERRORS) + FBTrace.sysout("css.safeGetContentState; EXCEPTION", e); + } +} + +// ************************************************************************************************ + +function CSSComputedElementPanel() {} + +CSSComputedElementPanel.prototype = extend(CSSElementPanel.prototype, +{ + template: domplate( + { + computedTag: + DIV({"class": "a11yCSSView", role : "list", "aria-label" : $STR('aria.labels.computed styles')}, + FOR("group", "$groups", + H1({"class": "cssInheritHeader groupHeader focusRow", role : "listitem"}, + SPAN({"class": "cssInheritLabel"}, "$group.title") + ), + TABLE({width: "100%", role : 'group'}, + TBODY({role : 'presentation'}, + FOR("prop", "$group.props", + TR({"class": 'focusRow computedStyleRow', role : 'listitem'}, + TD({"class": "stylePropName", role : 'presentation'}, "$prop.name"), + TD({"class": "stylePropValue", role : 'presentation'}, "$prop.value") + ) + ) + ) + ) + ) + ) + }), + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + updateComputedView: function(element) + { + var win = isIE ? + element.ownerDocument.parentWindow : + element.ownerDocument.defaultView; + + var style = isIE ? + element.currentStyle : + win.getComputedStyle(element, ""); + + var groups = []; + + for (var groupName in styleGroups) + { + // TODO: xxxpedro i18n $STR + //var title = $STR("StyleGroup-" + groupName); + var title = styleGroupTitles[groupName]; + var group = {title: title, props: []}; + groups.push(group); + + var props = styleGroups[groupName]; + for (var i = 0; i < props.length; ++i) + { + var propName = props[i]; + var propValue = style.getPropertyValue ? + style.getPropertyValue(propName) : + ""+style[toCamelCase(propName)]; + + if (propValue === undefined || propValue === null) + continue; + + propValue = stripUnits(rgbToHex(propValue)); + if (propValue) + group.props.push({name: propName, value: propValue}); + } + } + + var result = this.template.computedTag.replace({groups: groups}, this.panelNode); + //dispatch([Firebug.A11yModel], 'onCSSRulesAdded', [this, result]); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // extends Panel + + name: "computed", + title: "Computed", + parentPanel: "HTML", + order: 1, + + updateView: function(element) + { + this.updateComputedView(element); + }, + + getOptionsMenuItems: function() + { + return [ + {label: "Refresh", command: bind(this.refresh, this) } + ]; + } +}); + +// ************************************************************************************************ +// CSSEditor + +function CSSEditor(doc) +{ + this.initializeInline(doc); +} + +CSSEditor.prototype = domplate(Firebug.InlineEditor.prototype, +{ + insertNewRow: function(target, insertWhere) + { + var rule = Firebug.getRepObject(target); + var emptyProp = + { + // TODO: xxxpedro - uses charCode(255) to force the element being rendered, + // allowing webkit to get the correct position of the property name "span", + // when inserting a new CSS rule? + name: "", + value: "", + important: "" + }; + + if (insertWhere == "before") + return CSSPropTag.tag.insertBefore({prop: emptyProp, rule: rule}, target); + else + return CSSPropTag.tag.insertAfter({prop: emptyProp, rule: rule}, target); + }, + + saveEdit: function(target, value, previousValue) + { + // We need to check the value first in order to avoid a problem in IE8 + // See Issue 3038: Empty (null) styles when adding CSS styles in Firebug Lite + if (!value) return; + + target.innerHTML = escapeForCss(value); + + var row = getAncestorByClass(target, "cssProp"); + if (hasClass(row, "disabledStyle")) + toggleClass(row, "disabledStyle"); + + var rule = Firebug.getRepObject(target); + + if (hasClass(target, "cssPropName")) + { + if (value && previousValue != value) // name of property has changed. + { + var propValue = getChildByClass(row, "cssPropValue")[textContent]; + var parsedValue = parsePriority(propValue); + + if (propValue && propValue != "undefined") { + if (FBTrace.DBG_CSS) + FBTrace.sysout("CSSEditor.saveEdit : "+previousValue+"->"+value+" = "+propValue+"\n"); + if (previousValue) + Firebug.CSSModule.removeProperty(rule, previousValue); + Firebug.CSSModule.setProperty(rule, value, parsedValue.value, parsedValue.priority); + } + } + else if (!value) // name of the property has been deleted, so remove the property. + Firebug.CSSModule.removeProperty(rule, previousValue); + } + else if (getAncestorByClass(target, "cssPropValue")) + { + var propName = getChildByClass(row, "cssPropName")[textContent]; + var propValue = getChildByClass(row, "cssPropValue")[textContent]; + + if (FBTrace.DBG_CSS) + { + FBTrace.sysout("CSSEditor.saveEdit propName=propValue: "+propName +" = "+propValue+"\n"); + // FBTrace.sysout("CSSEditor.saveEdit BEFORE style:",style); + } + + if (value && value != "null") + { + var parsedValue = parsePriority(value); + Firebug.CSSModule.setProperty(rule, propName, parsedValue.value, parsedValue.priority); + } + else if (previousValue && previousValue != "null") + Firebug.CSSModule.removeProperty(rule, propName); + } + + this.panel.markChange(this.panel.name == "stylesheet"); + }, + + advanceToNext: function(target, charCode) + { + if (charCode == 58 /*":"*/ && hasClass(target, "cssPropName")) + return true; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + getAutoCompleteRange: function(value, offset) + { + if (hasClass(this.target, "cssPropName")) + return {start: 0, end: value.length-1}; + else + return parseCSSValue(value, offset); + }, + + getAutoCompleteList: function(preExpr, expr, postExpr) + { + if (hasClass(this.target, "cssPropName")) + { + return getCSSPropertyNames(); + } + else + { + var row = getAncestorByClass(this.target, "cssProp"); + var propName = getChildByClass(row, "cssPropName")[textContent]; + return getCSSKeywordsByProperty(propName); + } + } +}); + +//************************************************************************************************ +//CSSRuleEditor + +function CSSRuleEditor(doc) +{ + this.initializeInline(doc); + this.completeAsYouType = false; +} +CSSRuleEditor.uniquifier = 0; +CSSRuleEditor.prototype = domplate(Firebug.InlineEditor.prototype, +{ + insertNewRow: function(target, insertWhere) + { + var emptyRule = { + selector: "", + id: "", + props: [], + isSelectorEditable: true + }; + + if (insertWhere == "before") + return CSSStyleRuleTag.tag.insertBefore({rule: emptyRule}, target); + else + return CSSStyleRuleTag.tag.insertAfter({rule: emptyRule}, target); + }, + + saveEdit: function(target, value, previousValue) + { + if (FBTrace.DBG_CSS) + FBTrace.sysout("CSSRuleEditor.saveEdit: '" + value + "' '" + previousValue + "'", target); + + target.innerHTML = escapeForCss(value); + + if (value === previousValue) return; + + var row = getAncestorByClass(target, "cssRule"); + var styleSheet = this.panel.location; + styleSheet = styleSheet.editStyleSheet ? styleSheet.editStyleSheet.sheet : styleSheet; + + var cssRules = styleSheet.cssRules; + var rule = Firebug.getRepObject(target), oldRule = rule; + var ruleIndex = cssRules.length; + if (rule || Firebug.getRepObject(row.nextSibling)) + { + var searchRule = rule || Firebug.getRepObject(row.nextSibling); + for (ruleIndex=0; ruleIndex<cssRules.length && searchRule!=cssRules[ruleIndex]; ruleIndex++) {} + } + + // Delete in all cases except for new add + // We want to do this before the insert to ease change tracking + if (oldRule) + { + Firebug.CSSModule.deleteRule(styleSheet, ruleIndex); + } + + // Firefox does not follow the spec for the update selector text case. + // When attempting to update the value, firefox will silently fail. + // See https://bugzilla.mozilla.org/show_bug.cgi?id=37468 for the quite + // old discussion of this bug. + // As a result we need to recreate the style every time the selector + // changes. + if (value) + { + var cssText = [ value, "{" ]; + var props = row.getElementsByClassName("cssProp"); + for (var i = 0; i < props.length; i++) { + var propEl = props[i]; + if (!hasClass(propEl, "disabledStyle")) { + cssText.push(getChildByClass(propEl, "cssPropName")[textContent]); + cssText.push(":"); + cssText.push(getChildByClass(propEl, "cssPropValue")[textContent]); + cssText.push(";"); + } + } + cssText.push("}"); + cssText = cssText.join(""); + + try + { + var insertLoc = Firebug.CSSModule.insertRule(styleSheet, cssText, ruleIndex); + rule = cssRules[insertLoc]; + ruleIndex++; + } + catch (err) + { + if (FBTrace.DBG_CSS || FBTrace.DBG_ERRORS) + FBTrace.sysout("CSS Insert Error: "+err, err); + + target.innerHTML = escapeForCss(previousValue); + row.repObject = undefined; + return; + } + } else { + rule = undefined; + } + + // Update the rep object + row.repObject = rule; + if (!oldRule) + { + // Who knows what the domutils will return for rule line + // for a recently created rule. To be safe we just generate + // a unique value as this is only used as an internal key. + var ruleId = "new/"+value+"/"+(++CSSRuleEditor.uniquifier); + row.setAttribute("ruleId", ruleId); + } + + this.panel.markChange(this.panel.name == "stylesheet"); + } +}); + +// ************************************************************************************************ +// StyleSheetEditor + +function StyleSheetEditor(doc) +{ + this.box = this.tag.replace({}, doc, this); + this.input = this.box.firstChild; +} + +StyleSheetEditor.prototype = domplate(Firebug.BaseEditor, +{ + multiLine: true, + + tag: DIV( + TEXTAREA({"class": "styleSheetEditor fullPanelEditor", oninput: "$onInput"}) + ), + + getValue: function() + { + return this.input.value; + }, + + setValue: function(value) + { + return this.input.value = value; + }, + + show: function(target, panel, value, textSize, targetSize) + { + this.target = target; + this.panel = panel; + + this.panel.panelNode.appendChild(this.box); + + this.input.value = value; + this.input.focus(); + + var command = Firebug.chrome.$("cmd_toggleCSSEditing"); + command.setAttribute("checked", true); + }, + + hide: function() + { + var command = Firebug.chrome.$("cmd_toggleCSSEditing"); + command.setAttribute("checked", false); + + if (this.box.parentNode == this.panel.panelNode) + this.panel.panelNode.removeChild(this.box); + + delete this.target; + delete this.panel; + delete this.styleSheet; + }, + + saveEdit: function(target, value, previousValue) + { + Firebug.CSSModule.freeEdit(this.styleSheet, value); + }, + + endEditing: function() + { + this.panel.refresh(); + return true; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + onInput: function() + { + Firebug.Editor.update(); + }, + + scrollToLine: function(line, offset) + { + this.startMeasuring(this.input); + var lineHeight = this.measureText().height; + this.stopMeasuring(); + + this.input.scrollTop = (line * lineHeight) + offset; + } +}); + +// ************************************************************************************************ +// Local Helpers + +var rgbToHex = function rgbToHex(value) +{ + return value.replace(/\brgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)/gi, rgbToHexReplacer); +}; + +var rgbToHexReplacer = function(_, r, g, b) { + return '#' + ((1 << 24) + (r << 16) + (g << 8) + (b << 0)).toString(16).substr(-6).toUpperCase(); +}; + +var stripUnits = function stripUnits(value) +{ + // remove units from '0px', '0em' etc. leave non-zero units in-tact. + return value.replace(/(url\(.*?\)|[^0]\S*\s*)|0(%|em|ex|px|in|cm|mm|pt|pc)(\s|$)/gi, stripUnitsReplacer); +}; + +var stripUnitsReplacer = function(_, skip, remove, whitespace) { + return skip || ('0' + whitespace); +}; + +function parsePriority(value) +{ + var rePriority = /(.*?)\s*(!important)?$/; + var m = rePriority.exec(value); + var propValue = m ? m[1] : ""; + var priority = m && m[2] ? "important" : ""; + return {value: propValue, priority: priority}; +} + +function parseURLValue(value) +{ + var m = reURL.exec(value); + return m ? m[1] : ""; +} + +function parseRepeatValue(value) +{ + var m = reRepeat.exec(value); + return m ? m[0] : ""; +} + +function parseCSSValue(value, offset) +{ + var start = 0; + var m; + while (1) + { + m = reSplitCSS.exec(value); + if (m && m.index+m[0].length < offset) + { + value = value.substr(m.index+m[0].length); + start += m.index+m[0].length; + offset -= m.index+m[0].length; + } + else + break; + } + + if (m) + { + var type; + if (m[1]) + type = "url"; + else if (m[2] || m[3]) + type = "rgb"; + else if (m[4]) + type = "int"; + + return {value: m[0], start: start+m.index, end: start+m.index+(m[0].length-1), type: type}; + } +} + +function findPropByName(props, name) +{ + for (var i = 0; i < props.length; ++i) + { + if (props[i].name == name) + return i; + } +} + +function sortProperties(props) +{ + props.sort(function(a, b) + { + return a.name > b.name ? 1 : -1; + }); +} + +function getTopmostRuleLine(panelNode) +{ + for (var child = panelNode.firstChild; child; child = child.nextSibling) + { + if (child.offsetTop+child.offsetHeight > panelNode.scrollTop) + { + var rule = child.repObject; + if (rule) + return { + line: domUtils.getRuleLine(rule), + offset: panelNode.scrollTop-child.offsetTop + }; + } + } + return 0; +} + +function getStyleSheetCSS(sheet, context) +{ + if (sheet.ownerNode instanceof HTMLStyleElement) + return sheet.ownerNode.innerHTML; + else + return context.sourceCache.load(sheet.href).join(""); +} + +function getStyleSheetOwnerNode(sheet) { + for (; sheet && !sheet.ownerNode; sheet = sheet.parentStyleSheet); + + return sheet.ownerNode; +} + +function scrollSelectionIntoView(panel) +{ + var selCon = getSelectionController(panel); + selCon.scrollSelectionIntoView( + nsISelectionController.SELECTION_NORMAL, + nsISelectionController.SELECTION_FOCUS_REGION, true); +} + +function getSelectionController(panel) +{ + var browser = Firebug.chrome.getPanelBrowser(panel); + return browser.docShell.QueryInterface(nsIInterfaceRequestor) + .getInterface(nsISelectionDisplay) + .QueryInterface(nsISelectionController); +} + +// ************************************************************************************************ + +Firebug.registerModule(Firebug.CSSModule); +Firebug.registerPanel(Firebug.CSSStyleSheetPanel); +Firebug.registerPanel(CSSElementPanel); +Firebug.registerPanel(CSSComputedElementPanel); + +// ************************************************************************************************ + +}}); + + +/* See license.txt for terms of usage */ + +FBL.ns(function() { with (FBL) { +// ************************************************************************************************ + +// ************************************************************************************************ +// Script Module + +Firebug.Script = extend(Firebug.Module, +{ + getPanel: function() + { + return Firebug.chrome ? Firebug.chrome.getPanel("Script") : null; + }, + + selectSourceCode: function(index) + { + this.getPanel().selectSourceCode(index); + } +}); + +Firebug.registerModule(Firebug.Script); + + +// ************************************************************************************************ +// Script Panel + +function ScriptPanel(){}; + +ScriptPanel.prototype = extend(Firebug.Panel, +{ + name: "Script", + title: "Script", + + selectIndex: 0, // index of the current selectNode's option + sourceIndex: -1, // index of the script node, based in doc.getElementsByTagName("script") + + options: { + hasToolButtons: true + }, + + create: function() + { + Firebug.Panel.create.apply(this, arguments); + + this.onChangeSelect = bind(this.onChangeSelect, this); + + var doc = Firebug.browser.document; + var scripts = doc.getElementsByTagName("script"); + var selectNode = this.selectNode = createElement("select"); + + for(var i=0, script; script=scripts[i]; i++) + { + // Don't show Firebug Lite source code in the list of options + if (Firebug.ignoreFirebugElements && script.getAttribute("firebugIgnore")) + continue; + + var fileName = getFileName(script.src) || getFileName(doc.location.href); + var option = createElement("option", {value:i}); + + option.appendChild(Firebug.chrome.document.createTextNode(fileName)); + selectNode.appendChild(option); + }; + + this.toolButtonsNode.appendChild(selectNode); + }, + + initialize: function() + { + // we must render the code first, so the persistent state can be restore + this.selectSourceCode(this.selectIndex); + + Firebug.Panel.initialize.apply(this, arguments); + + addEvent(this.selectNode, "change", this.onChangeSelect); + }, + + shutdown: function() + { + removeEvent(this.selectNode, "change", this.onChangeSelect); + + Firebug.Panel.shutdown.apply(this, arguments); + }, + + detach: function(oldChrome, newChrome) + { + Firebug.Panel.detach.apply(this, arguments); + + var oldPanel = oldChrome.getPanel("Script"); + var index = oldPanel.selectIndex; + + this.selectNode.selectedIndex = index; + this.selectIndex = index; + this.sourceIndex = -1; + }, + + onChangeSelect: function(event) + { + var select = this.selectNode; + + this.selectIndex = select.selectedIndex; + + var option = select.options[select.selectedIndex]; + if (!option) + return; + + var selectedSourceIndex = parseInt(option.value); + + this.renderSourceCode(selectedSourceIndex); + }, + + selectSourceCode: function(index) + { + var select = this.selectNode; + select.selectedIndex = index; + + var option = select.options[index]; + if (!option) + return; + + var selectedSourceIndex = parseInt(option.value); + + this.renderSourceCode(selectedSourceIndex); + }, + + renderSourceCode: function(index) + { + if (this.sourceIndex != index) + { + var renderProcess = function renderProcess(src) + { + var html = [], + hl = 0; + + src = isIE && !isExternal ? + src+'\n' : // IE put an extra line when reading source of local resources + '\n'+src; + + // find the number of lines of code + src = src.replace(/\n\r|\r\n/g, "\n"); + var match = src.match(/[\n]/g); + var lines=match ? match.length : 0; + + // render the full source code + line numbers html + html[hl++] = '<div><div class="sourceBox" style="left:'; + html[hl++] = 35 + 7*(lines+'').length; + html[hl++] = 'px;"><pre class="sourceCode">'; + html[hl++] = escapeHTML(src); + html[hl++] = '</pre></div><div class="lineNo">'; + + // render the line number divs + for(var l=1, lines; l<=lines; l++) + { + html[hl++] = '<div line="'; + html[hl++] = l; + html[hl++] = '">'; + html[hl++] = l; + html[hl++] = '</div>'; + } + + html[hl++] = '</div></div>'; + + updatePanel(html); + }; + + var updatePanel = function(html) + { + self.panelNode.innerHTML = html.join(""); + + // IE needs this timeout, otherwise the panel won't scroll + setTimeout(function(){ + self.synchronizeUI(); + },0); + }; + + var onFailure = function() + { + FirebugReps.Warning.tag.replace({object: "AccessRestricted"}, self.panelNode); + }; + + var self = this; + + var doc = Firebug.browser.document; + var script = doc.getElementsByTagName("script")[index]; + var url = getScriptURL(script); + var isExternal = url && url != doc.location.href; + + try + { + if (Firebug.disableResourceFetching) + { + renderProcess(Firebug.Lite.Proxy.fetchResourceDisabledMessage); + } + else if (isExternal) + { + Ajax.request({url: url, onSuccess: renderProcess, onFailure: onFailure}); + } + else + { + var src = script.innerHTML; + renderProcess(src); + } + } + catch(e) + { + onFailure(); + } + + this.sourceIndex = index; + } + } +}); + +Firebug.registerPanel(ScriptPanel); + + +// ************************************************************************************************ + + +var getScriptURL = function getScriptURL(script) +{ + var reFile = /([^\/\?#]+)(#.+)?$/; + var rePath = /^(.*\/)/; + var reProtocol = /^\w+:\/\//; + var path = null; + var doc = Firebug.browser.document; + + var file = reFile.exec(script.src); + + if (file) + { + var fileName = file[1]; + var fileOptions = file[2]; + + // absolute path + if (reProtocol.test(script.src)) { + path = rePath.exec(script.src)[1]; + + } + // relative path + else + { + var r = rePath.exec(script.src); + var src = r ? r[1] : script.src; + var backDir = /^((?:\.\.\/)+)(.*)/.exec(src); + var reLastDir = /^(.*\/)[^\/]+\/$/; + path = rePath.exec(doc.location.href)[1]; + + // "../some/path" + if (backDir) + { + var j = backDir[1].length/3; + var p; + while (j-- > 0) + path = reLastDir.exec(path)[1]; + + path += backDir[2]; + } + + else if(src.indexOf("/") != -1) + { + // "./some/path" + if(/^\.\/./.test(src)) + { + path += src.substring(2); + } + // "/some/path" + else if(/^\/./.test(src)) + { + var domain = /^(\w+:\/\/[^\/]+)/.exec(path); + path = domain[1] + src; + } + // "some/path" + else + { + path += src; + } + } + } + } + + var m = path && path.match(/([^\/]+)\/$/) || null; + + if (path && m) + { + return path + fileName; + } +}; + +var getFileName = function getFileName(path) +{ + if (!path) return ""; + + var match = path && path.match(/[^\/]+(\?.*)?(#.*)?$/); + + return match && match[0] || path; +}; + + +// ************************************************************************************************ +}}); + +/* See license.txt for terms of usage */ + +FBL.ns(function() { with (FBL) { +// ************************************************************************************************ + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +var ElementCache = Firebug.Lite.Cache.Element; + +var insertSliceSize = 18; +var insertInterval = 40; + +var ignoreVars = +{ + "__firebug__": 1, + "eval": 1, + + // We are forced to ignore Java-related variables, because + // trying to access them causes browser freeze + "java": 1, + "sun": 1, + "Packages": 1, + "JavaArray": 1, + "JavaMember": 1, + "JavaObject": 1, + "JavaClass": 1, + "JavaPackage": 1, + "_firebug": 1, + "_FirebugConsole": 1, + "_FirebugCommandLine": 1 +}; + +if (Firebug.ignoreFirebugElements) + ignoreVars[Firebug.Lite.Cache.ID] = 1; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +var memberPanelRep = + isIE6 ? + {"class": "memberLabel $member.type\\Label", href: "javacript:void(0)"} + : + {"class": "memberLabel $member.type\\Label"}; + +var RowTag = + TR({"class": "memberRow $member.open $member.type\\Row", $hasChildren: "$member.hasChildren", role : 'presentation', + level: "$member.level"}, + TD({"class": "memberLabelCell", style: "padding-left: $member.indent\\px", role : 'presentation'}, + A(memberPanelRep, + SPAN({}, "$member.name") + ) + ), + TD({"class": "memberValueCell", role : 'presentation'}, + TAG("$member.tag", {object: "$member.value"}) + ) + ); + +var WatchRowTag = + TR({"class": "watchNewRow", level: 0}, + TD({"class": "watchEditCell", colspan: 2}, + DIV({"class": "watchEditBox a11yFocusNoTab", role: "button", 'tabindex' : '0', + 'aria-label' : $STR('press enter to add new watch expression')}, + $STR("NewWatch") + ) + ) + ); + +var SizerRow = + TR({role : 'presentation'}, + TD({width: "30%"}), + TD({width: "70%"}) + ); + +var domTableClass = isIElt8 ? "domTable domTableIE" : "domTable"; +var DirTablePlate = domplate(Firebug.Rep, +{ + tag: + TABLE({"class": domTableClass, cellpadding: 0, cellspacing: 0, onclick: "$onClick", role :"tree"}, + TBODY({role: 'presentation'}, + SizerRow, + FOR("member", "$object|memberIterator", RowTag) + ) + ), + + watchTag: + TABLE({"class": domTableClass, cellpadding: 0, cellspacing: 0, + _toggles: "$toggles", _domPanel: "$domPanel", onclick: "$onClick", role : 'tree'}, + TBODY({role : 'presentation'}, + SizerRow, + WatchRowTag + ) + ), + + tableTag: + TABLE({"class": domTableClass, cellpadding: 0, cellspacing: 0, + _toggles: "$toggles", _domPanel: "$domPanel", onclick: "$onClick", role : 'tree'}, + TBODY({role : 'presentation'}, + SizerRow + ) + ), + + rowTag: + FOR("member", "$members", RowTag), + + memberIterator: function(object, level) + { + return getMembers(object, level); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + onClick: function(event) + { + if (!isLeftClick(event)) + return; + + var target = event.target || event.srcElement; + + var row = getAncestorByClass(target, "memberRow"); + var label = getAncestorByClass(target, "memberLabel"); + if (label && hasClass(row, "hasChildren")) + { + var row = label.parentNode.parentNode; + this.toggleRow(row); + } + else + { + var object = Firebug.getRepObject(target); + if (typeof(object) == "function") + { + Firebug.chrome.select(object, "script"); + cancelEvent(event); + } + else if (event.detail == 2 && !object) + { + var panel = row.parentNode.parentNode.domPanel; + if (panel) + { + var rowValue = panel.getRowPropertyValue(row); + if (typeof(rowValue) == "boolean") + panel.setPropertyValue(row, !rowValue); + else + panel.editProperty(row); + + cancelEvent(event); + } + } + } + + return false; + }, + + toggleRow: function(row) + { + var level = parseInt(row.getAttribute("level")); + var toggles = row.parentNode.parentNode.toggles; + + if (hasClass(row, "opened")) + { + removeClass(row, "opened"); + + if (toggles) + { + var path = getPath(row); + + // Remove the path from the toggle tree + for (var i = 0; i < path.length; ++i) + { + if (i == path.length-1) + delete toggles[path[i]]; + else + toggles = toggles[path[i]]; + } + } + + var rowTag = this.rowTag; + var tbody = row.parentNode; + + setTimeout(function() + { + for (var firstRow = row.nextSibling; firstRow; firstRow = row.nextSibling) + { + if (parseInt(firstRow.getAttribute("level")) <= level) + break; + + tbody.removeChild(firstRow); + } + }, row.insertTimeout ? row.insertTimeout : 0); + } + else + { + setClass(row, "opened"); + + if (toggles) + { + var path = getPath(row); + + // Mark the path in the toggle tree + for (var i = 0; i < path.length; ++i) + { + var name = path[i]; + if (toggles.hasOwnProperty(name)) + toggles = toggles[name]; + else + toggles = toggles[name] = {}; + } + } + + var value = row.lastChild.firstChild.repObject; + var members = getMembers(value, level+1); + + var rowTag = this.rowTag; + var lastRow = row; + + var delay = 0; + //var setSize = members.length; + //var rowCount = 1; + while (members.length) + { + with({slice: members.splice(0, insertSliceSize), isLast: !members.length}) + { + setTimeout(function() + { + if (lastRow.parentNode) + { + var result = rowTag.insertRows({members: slice}, lastRow); + lastRow = result[1]; + //dispatch([Firebug.A11yModel], 'onMemberRowSliceAdded', [null, result, rowCount, setSize]); + //rowCount += insertSliceSize; + } + if (isLast) + row.removeAttribute("insertTimeout"); + }, delay); + } + + delay += insertInterval; + } + + row.insertTimeout = delay; + } + } +}); + + + +// ************************************************************************************************ + +Firebug.DOMBasePanel = function() {}; + +Firebug.DOMBasePanel.prototype = extend(Firebug.Panel, +{ + tag: DirTablePlate.tableTag, + + getRealObject: function(object) + { + // TODO: Move this to some global location + // TODO: Unwrapping should be centralized rather than sprinkling it around ad hoc. + // TODO: We might be able to make this check more authoritative with QueryInterface. + if (!object) return object; + if (object.wrappedJSObject) return object.wrappedJSObject; + return object; + }, + + rebuild: function(update, scrollTop) + { + //dispatch([Firebug.A11yModel], 'onBeforeDomUpdateSelection', [this]); + var members = getMembers(this.selection); + expandMembers(members, this.toggles, 0, 0); + + this.showMembers(members, update, scrollTop); + + //TODO: xxxpedro statusbar + if (!this.parentPanel) + updateStatusBar(this); + }, + + showMembers: function(members, update, scrollTop) + { + // If we are still in the midst of inserting rows, cancel all pending + // insertions here - this is a big speedup when stepping in the debugger + if (this.timeouts) + { + for (var i = 0; i < this.timeouts.length; ++i) + this.context.clearTimeout(this.timeouts[i]); + delete this.timeouts; + } + + if (!members.length) + return this.showEmptyMembers(); + + var panelNode = this.panelNode; + var priorScrollTop = scrollTop == undefined ? panelNode.scrollTop : scrollTop; + + // If we are asked to "update" the current view, then build the new table + // offscreen and swap it in when it's done + var offscreen = update && panelNode.firstChild; + var dest = offscreen ? panelNode.ownerDocument : panelNode; + + var table = this.tag.replace({domPanel: this, toggles: this.toggles}, dest); + var tbody = table.lastChild; + var rowTag = DirTablePlate.rowTag; + + // Insert the first slice immediately + //var slice = members.splice(0, insertSliceSize); + //var result = rowTag.insertRows({members: slice}, tbody.lastChild); + + //var setSize = members.length; + //var rowCount = 1; + + var panel = this; + var result; + + //dispatch([Firebug.A11yModel], 'onMemberRowSliceAdded', [panel, result, rowCount, setSize]); + var timeouts = []; + + var delay = 0; + + // enable to measure rendering performance + var renderStart = new Date().getTime(); + while (members.length) + { + with({slice: members.splice(0, insertSliceSize), isLast: !members.length}) + { + timeouts.push(this.context.setTimeout(function() + { + // TODO: xxxpedro can this be a timing error related to the + // "iteration number" approach insted of "duration time"? + // avoid error in IE8 + if (!tbody.lastChild) return; + + result = rowTag.insertRows({members: slice}, tbody.lastChild); + + //rowCount += insertSliceSize; + //dispatch([Firebug.A11yModel], 'onMemberRowSliceAdded', [panel, result, rowCount, setSize]); + + if ((panelNode.scrollHeight+panelNode.offsetHeight) >= priorScrollTop) + panelNode.scrollTop = priorScrollTop; + + + // enable to measure rendering performance + //if (isLast) alert(new Date().getTime() - renderStart + "ms"); + + + }, delay)); + + delay += insertInterval; + } + } + + if (offscreen) + { + timeouts.push(this.context.setTimeout(function() + { + if (panelNode.firstChild) + panelNode.replaceChild(table, panelNode.firstChild); + else + panelNode.appendChild(table); + + // Scroll back to where we were before + panelNode.scrollTop = priorScrollTop; + }, delay)); + } + else + { + timeouts.push(this.context.setTimeout(function() + { + panelNode.scrollTop = scrollTop == undefined ? 0 : scrollTop; + }, delay)); + } + this.timeouts = timeouts; + }, + + /* + // new + showMembers: function(members, update, scrollTop) + { + // If we are still in the midst of inserting rows, cancel all pending + // insertions here - this is a big speedup when stepping in the debugger + if (this.timeouts) + { + for (var i = 0; i < this.timeouts.length; ++i) + this.context.clearTimeout(this.timeouts[i]); + delete this.timeouts; + } + + if (!members.length) + return this.showEmptyMembers(); + + var panelNode = this.panelNode; + var priorScrollTop = scrollTop == undefined ? panelNode.scrollTop : scrollTop; + + // If we are asked to "update" the current view, then build the new table + // offscreen and swap it in when it's done + var offscreen = update && panelNode.firstChild; + var dest = offscreen ? panelNode.ownerDocument : panelNode; + + var table = this.tag.replace({domPanel: this, toggles: this.toggles}, dest); + var tbody = table.lastChild; + var rowTag = DirTablePlate.rowTag; + + // Insert the first slice immediately + //var slice = members.splice(0, insertSliceSize); + //var result = rowTag.insertRows({members: slice}, tbody.lastChild); + + //var setSize = members.length; + //var rowCount = 1; + + var panel = this; + var result; + + //dispatch([Firebug.A11yModel], 'onMemberRowSliceAdded', [panel, result, rowCount, setSize]); + var timeouts = []; + + var delay = 0; + var _insertSliceSize = insertSliceSize; + var _insertInterval = insertInterval; + + // enable to measure rendering performance + var renderStart = new Date().getTime(); + var lastSkip = renderStart, now; + + while (members.length) + { + with({slice: members.splice(0, _insertSliceSize), isLast: !members.length}) + { + var _tbody = tbody; + var _rowTag = rowTag; + var _panelNode = panelNode; + var _priorScrollTop = priorScrollTop; + + timeouts.push(this.context.setTimeout(function() + { + // TODO: xxxpedro can this be a timing error related to the + // "iteration number" approach insted of "duration time"? + // avoid error in IE8 + if (!_tbody.lastChild) return; + + result = _rowTag.insertRows({members: slice}, _tbody.lastChild); + + //rowCount += _insertSliceSize; + //dispatch([Firebug.A11yModel], 'onMemberRowSliceAdded', [panel, result, rowCount, setSize]); + + if ((_panelNode.scrollHeight + _panelNode.offsetHeight) >= _priorScrollTop) + _panelNode.scrollTop = _priorScrollTop; + + + // enable to measure rendering performance + //alert("gap: " + (new Date().getTime() - lastSkip)); + //lastSkip = new Date().getTime(); + + //if (isLast) alert("new: " + (new Date().getTime() - renderStart) + "ms"); + + }, delay)); + + delay += _insertInterval; + } + } + + if (offscreen) + { + timeouts.push(this.context.setTimeout(function() + { + if (panelNode.firstChild) + panelNode.replaceChild(table, panelNode.firstChild); + else + panelNode.appendChild(table); + + // Scroll back to where we were before + panelNode.scrollTop = priorScrollTop; + }, delay)); + } + else + { + timeouts.push(this.context.setTimeout(function() + { + panelNode.scrollTop = scrollTop == undefined ? 0 : scrollTop; + }, delay)); + } + this.timeouts = timeouts; + }, + /**/ + + showEmptyMembers: function() + { + FirebugReps.Warning.tag.replace({object: "NoMembersWarning"}, this.panelNode); + }, + + findPathObject: function(object) + { + var pathIndex = -1; + for (var i = 0; i < this.objectPath.length; ++i) + { + // IE needs === instead of == or otherwise some objects will + // be considered equal to different objects, returning the + // wrong index of the objectPath array + if (this.getPathObject(i) === object) + return i; + } + + return -1; + }, + + getPathObject: function(index) + { + var object = this.objectPath[index]; + + if (object instanceof Property) + return object.getObject(); + else + return object; + }, + + getRowObject: function(row) + { + var object = getRowOwnerObject(row); + return object ? object : this.selection; + }, + + getRowPropertyValue: function(row) + { + var object = this.getRowObject(row); + object = this.getRealObject(object); + if (object) + { + var propName = getRowName(row); + + if (object instanceof jsdIStackFrame) + return Firebug.Debugger.evaluate(propName, this.context); + else + return object[propName]; + } + }, + /* + copyProperty: function(row) + { + var value = this.getRowPropertyValue(row); + copyToClipboard(value); + }, + + editProperty: function(row, editValue) + { + if (hasClass(row, "watchNewRow")) + { + if (this.context.stopped) + Firebug.Editor.startEditing(row, ""); + else if (Firebug.Console.isAlwaysEnabled()) // not stopped in debugger, need command line + { + if (Firebug.CommandLine.onCommandLineFocus()) + Firebug.Editor.startEditing(row, ""); + else + row.innerHTML = $STR("warning.Command line blocked?"); + } + else + row.innerHTML = $STR("warning.Console must be enabled"); + } + else if (hasClass(row, "watchRow")) + Firebug.Editor.startEditing(row, getRowName(row)); + else + { + var object = this.getRowObject(row); + this.context.thisValue = object; + + if (!editValue) + { + var propValue = this.getRowPropertyValue(row); + + var type = typeof(propValue); + if (type == "undefined" || type == "number" || type == "boolean") + editValue = propValue; + else if (type == "string") + editValue = "\"" + escapeJS(propValue) + "\""; + else if (propValue == null) + editValue = "null"; + else if (object instanceof Window || object instanceof jsdIStackFrame) + editValue = getRowName(row); + else + editValue = "this." + getRowName(row); + } + + + Firebug.Editor.startEditing(row, editValue); + } + }, + + deleteProperty: function(row) + { + if (hasClass(row, "watchRow")) + this.deleteWatch(row); + else + { + var object = getRowOwnerObject(row); + if (!object) + object = this.selection; + object = this.getRealObject(object); + + if (object) + { + var name = getRowName(row); + try + { + delete object[name]; + } + catch (exc) + { + return; + } + + this.rebuild(true); + this.markChange(); + } + } + }, + + setPropertyValue: function(row, value) // value must be string + { + if(FBTrace.DBG_DOM) + { + FBTrace.sysout("row: "+row); + FBTrace.sysout("value: "+value+" type "+typeof(value), value); + } + + var name = getRowName(row); + if (name == "this") + return; + + var object = this.getRowObject(row); + object = this.getRealObject(object); + if (object && !(object instanceof jsdIStackFrame)) + { + // unwrappedJSObject.property = unwrappedJSObject + Firebug.CommandLine.evaluate(value, this.context, object, this.context.getGlobalScope(), + function success(result, context) + { + if (FBTrace.DBG_DOM) + FBTrace.sysout("setPropertyValue evaluate success object["+name+"]="+result+" type "+typeof(result), result); + object[name] = result; + }, + function failed(exc, context) + { + try + { + if (FBTrace.DBG_DOM) + FBTrace.sysout("setPropertyValue evaluate failed with exc:"+exc+" object["+name+"]="+value+" type "+typeof(value), exc); + // If the value doesn't parse, then just store it as a string. Some users will + // not realize they're supposed to enter a JavaScript expression and just type + // literal text + object[name] = String(value); // unwrappedJSobject.property = string + } + catch (exc) + { + return; + } + } + ); + } + else if (this.context.stopped) + { + try + { + Firebug.CommandLine.evaluate(name+"="+value, this.context); + } + catch (exc) + { + try + { + // See catch block above... + object[name] = String(value); // unwrappedJSobject.property = string + } + catch (exc) + { + return; + } + } + } + + this.rebuild(true); + this.markChange(); + }, + + highlightRow: function(row) + { + if (this.highlightedRow) + cancelClassTimed(this.highlightedRow, "jumpHighlight", this.context); + + this.highlightedRow = row; + + if (row) + setClassTimed(row, "jumpHighlight", this.context); + },/**/ + + onMouseMove: function(event) + { + var target = event.srcElement || event.target; + + var object = getAncestorByClass(target, "objectLink-element"); + object = object ? object.repObject : null; + + if(object && instanceOf(object, "Element") && object.nodeType == 1) + { + if(object != lastHighlightedObject) + { + Firebug.Inspector.drawBoxModel(object); + object = lastHighlightedObject; + } + } + else + Firebug.Inspector.hideBoxModel(); + + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // extends Panel + + create: function() + { + // TODO: xxxpedro + this.context = Firebug.browser; + + this.objectPath = []; + this.propertyPath = []; + this.viewPath = []; + this.pathIndex = -1; + this.toggles = {}; + + Firebug.Panel.create.apply(this, arguments); + + this.panelNode.style.padding = "0 1px"; + }, + + initialize: function(){ + Firebug.Panel.initialize.apply(this, arguments); + + addEvent(this.panelNode, "mousemove", this.onMouseMove); + }, + + shutdown: function() + { + removeEvent(this.panelNode, "mousemove", this.onMouseMove); + + Firebug.Panel.shutdown.apply(this, arguments); + }, + + /* + destroy: function(state) + { + var view = this.viewPath[this.pathIndex]; + if (view && this.panelNode.scrollTop) + view.scrollTop = this.panelNode.scrollTop; + + if (this.pathIndex) + state.pathIndex = this.pathIndex; + if (this.viewPath) + state.viewPath = this.viewPath; + if (this.propertyPath) + state.propertyPath = this.propertyPath; + + if (this.propertyPath.length > 0 && !this.propertyPath[1]) + state.firstSelection = persistObject(this.getPathObject(1), this.context); + + Firebug.Panel.destroy.apply(this, arguments); + }, + /**/ + + ishow: function(state) + { + if (this.context.loaded && !this.selection) + { + if (!state) + { + this.select(null); + return; + } + if (state.viewPath) + this.viewPath = state.viewPath; + if (state.propertyPath) + this.propertyPath = state.propertyPath; + + var defaultObject = this.getDefaultSelection(this.context); + var selectObject = defaultObject; + + if (state.firstSelection) + { + var restored = state.firstSelection(this.context); + if (restored) + { + selectObject = restored; + this.objectPath = [defaultObject, restored]; + } + else + this.objectPath = [defaultObject]; + } + else + this.objectPath = [defaultObject]; + + if (this.propertyPath.length > 1) + { + for (var i = 1; i < this.propertyPath.length; ++i) + { + var name = this.propertyPath[i]; + if (!name) + continue; + + var object = selectObject; + try + { + selectObject = object[name]; + } + catch (exc) + { + selectObject = null; + } + + if (selectObject) + { + this.objectPath.push(new Property(object, name)); + } + else + { + // If we can't access a property, just stop + this.viewPath.splice(i); + this.propertyPath.splice(i); + this.objectPath.splice(i); + selectObject = this.getPathObject(this.objectPath.length-1); + break; + } + } + } + + var selection = state.pathIndex <= this.objectPath.length-1 + ? this.getPathObject(state.pathIndex) + : this.getPathObject(this.objectPath.length-1); + + this.select(selection); + } + }, + /* + hide: function() + { + var view = this.viewPath[this.pathIndex]; + if (view && this.panelNode.scrollTop) + view.scrollTop = this.panelNode.scrollTop; + }, + /**/ + + supportsObject: function(object) + { + if (object == null) + return 1000; + + if (typeof(object) == "undefined") + return 1000; + else if (object instanceof SourceLink) + return 0; + else + return 1; // just agree to support everything but not agressively. + }, + + refresh: function() + { + this.rebuild(true); + }, + + updateSelection: function(object) + { + var previousIndex = this.pathIndex; + var previousView = previousIndex == -1 ? null : this.viewPath[previousIndex]; + + var newPath = this.pathToAppend; + delete this.pathToAppend; + + var pathIndex = this.findPathObject(object); + if (newPath || pathIndex == -1) + { + this.toggles = {}; + + if (newPath) + { + // Remove everything after the point where we are inserting, so we + // essentially replace it with the new path + if (previousView) + { + if (this.panelNode.scrollTop) + previousView.scrollTop = this.panelNode.scrollTop; + + var start = previousIndex + 1, + // Opera needs the length argument in splice(), otherwise + // it will consider that only one element should be removed + length = this.objectPath.length - start; + + this.objectPath.splice(start, length); + this.propertyPath.splice(start, length); + this.viewPath.splice(start, length); + } + + var value = this.getPathObject(previousIndex); + if (!value) + { + if (FBTrace.DBG_ERRORS) + FBTrace.sysout("dom.updateSelection no pathObject for "+previousIndex+"\n"); + return; + } + + for (var i = 0, length = newPath.length; i < length; ++i) + { + var name = newPath[i]; + var object = value; + try + { + value = value[name]; + } + catch(exc) + { + if (FBTrace.DBG_ERRORS) + FBTrace.sysout("dom.updateSelection FAILS at path_i="+i+" for name:"+name+"\n"); + return; + } + + ++this.pathIndex; + this.objectPath.push(new Property(object, name)); + this.propertyPath.push(name); + this.viewPath.push({toggles: this.toggles, scrollTop: 0}); + } + } + else + { + this.toggles = {}; + + var win = Firebug.browser.window; + //var win = this.context.getGlobalScope(); + if (object === win) + { + this.pathIndex = 0; + this.objectPath = [win]; + this.propertyPath = [null]; + this.viewPath = [{toggles: this.toggles, scrollTop: 0}]; + } + else + { + this.pathIndex = 1; + this.objectPath = [win, object]; + this.propertyPath = [null, null]; + this.viewPath = [ + {toggles: {}, scrollTop: 0}, + {toggles: this.toggles, scrollTop: 0} + ]; + } + } + + this.panelNode.scrollTop = 0; + this.rebuild(); + } + else + { + this.pathIndex = pathIndex; + + var view = this.viewPath[pathIndex]; + this.toggles = view.toggles; + + // Persist the current scroll location + if (previousView && this.panelNode.scrollTop) + previousView.scrollTop = this.panelNode.scrollTop; + + this.rebuild(false, view.scrollTop); + } + }, + + getObjectPath: function(object) + { + return this.objectPath; + }, + + getDefaultSelection: function() + { + return Firebug.browser.window; + //return this.context.getGlobalScope(); + }/*, + + updateOption: function(name, value) + { + const optionMap = {showUserProps: 1, showUserFuncs: 1, showDOMProps: 1, + showDOMFuncs: 1, showDOMConstants: 1}; + if ( optionMap.hasOwnProperty(name) ) + this.rebuild(true); + }, + + getOptionsMenuItems: function() + { + return [ + optionMenu("ShowUserProps", "showUserProps"), + optionMenu("ShowUserFuncs", "showUserFuncs"), + optionMenu("ShowDOMProps", "showDOMProps"), + optionMenu("ShowDOMFuncs", "showDOMFuncs"), + optionMenu("ShowDOMConstants", "showDOMConstants"), + "-", + {label: "Refresh", command: bindFixed(this.rebuild, this, true) } + ]; + }, + + getContextMenuItems: function(object, target) + { + var row = getAncestorByClass(target, "memberRow"); + + var items = []; + + if (row) + { + var rowName = getRowName(row); + var rowObject = this.getRowObject(row); + var rowValue = this.getRowPropertyValue(row); + + var isWatch = hasClass(row, "watchRow"); + var isStackFrame = rowObject instanceof jsdIStackFrame; + + if (typeof(rowValue) == "string" || typeof(rowValue) == "number") + { + // Functions already have a copy item in their context menu + items.push( + "-", + {label: "CopyValue", + command: bindFixed(this.copyProperty, this, row) } + ); + } + + items.push( + "-", + {label: isWatch ? "EditWatch" : (isStackFrame ? "EditVariable" : "EditProperty"), + command: bindFixed(this.editProperty, this, row) } + ); + + if (isWatch || (!isStackFrame && !isDOMMember(rowObject, rowName))) + { + items.push( + {label: isWatch ? "DeleteWatch" : "DeleteProperty", + command: bindFixed(this.deleteProperty, this, row) } + ); + } + } + + items.push( + "-", + {label: "Refresh", command: bindFixed(this.rebuild, this, true) } + ); + + return items; + }, + + getEditor: function(target, value) + { + if (!this.editor) + this.editor = new DOMEditor(this.document); + + return this.editor; + }/**/ +}); + +// ************************************************************************************************ + +// TODO: xxxpedro statusbar +var updateStatusBar = function(panel) +{ + var path = panel.propertyPath; + var index = panel.pathIndex; + + var r = []; + + for (var i=0, l=path.length; i<l; i++) + { + r.push(i==index ? '<a class="fbHover fbButton fbBtnSelected" ' : '<a class="fbHover fbButton" '); + r.push('pathIndex='); + r.push(i); + + if(isIE6) + r.push(' href="javascript:void(0)"'); + + r.push('>'); + r.push(i==0 ? "window" : path[i] || "Object"); + r.push('</a>'); + + if(i < l-1) + r.push('<span class="fbStatusSeparator">></span>'); + } + panel.statusBarNode.innerHTML = r.join(""); +}; + + +var DOMMainPanel = Firebug.DOMPanel = function () {}; + +Firebug.DOMPanel.DirTable = DirTablePlate; + +DOMMainPanel.prototype = extend(Firebug.DOMBasePanel.prototype, +{ + onClickStatusBar: function(event) + { + var target = event.srcElement || event.target; + var element = getAncestorByClass(target, "fbHover"); + + if(element) + { + var pathIndex = element.getAttribute("pathIndex"); + + if(pathIndex) + { + this.select(this.getPathObject(pathIndex)); + } + } + }, + + selectRow: function(row, target) + { + if (!target) + target = row.lastChild.firstChild; + + if (!target || !target.repObject) + return; + + this.pathToAppend = getPath(row); + + // If the object is inside an array, look up its index + var valueBox = row.lastChild.firstChild; + if (hasClass(valueBox, "objectBox-array")) + { + var arrayIndex = FirebugReps.Arr.getItemIndex(target); + this.pathToAppend.push(arrayIndex); + } + + // Make sure we get a fresh status path for the object, since otherwise + // it might find the object in the existing path and not refresh it + //Firebug.chrome.clearStatusPath(); + + this.select(target.repObject, true); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + onClick: function(event) + { + var target = event.srcElement || event.target; + var repNode = Firebug.getRepNode(target); + if (repNode) + { + var row = getAncestorByClass(target, "memberRow"); + if (row) + { + this.selectRow(row, repNode); + cancelEvent(event); + } + } + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // extends Panel + + name: "DOM", + title: "DOM", + searchable: true, + statusSeparator: ">", + + options: { + hasToolButtons: true, + hasStatusBar: true + }, + + create: function() + { + Firebug.DOMBasePanel.prototype.create.apply(this, arguments); + + this.onClick = bind(this.onClick, this); + + //TODO: xxxpedro + this.onClickStatusBar = bind(this.onClickStatusBar, this); + + this.panelNode.style.padding = "0 1px"; + }, + + initialize: function(oldPanelNode) + { + //this.panelNode.addEventListener("click", this.onClick, false); + //dispatch([Firebug.A11yModel], 'onInitializeNode', [this, 'console']); + + Firebug.DOMBasePanel.prototype.initialize.apply(this, arguments); + + addEvent(this.panelNode, "click", this.onClick); + + // TODO: xxxpedro dom + this.ishow(); + + //TODO: xxxpedro + addEvent(this.statusBarNode, "click", this.onClickStatusBar); + }, + + shutdown: function() + { + //this.panelNode.removeEventListener("click", this.onClick, false); + //dispatch([Firebug.A11yModel], 'onDestroyNode', [this, 'console']); + + removeEvent(this.panelNode, "click", this.onClick); + + Firebug.DOMBasePanel.prototype.shutdown.apply(this, arguments); + }/*, + + search: function(text, reverse) + { + if (!text) + { + delete this.currentSearch; + this.highlightRow(null); + return false; + } + + var row; + if (this.currentSearch && text == this.currentSearch.text) + row = this.currentSearch.findNext(true, undefined, reverse, Firebug.searchCaseSensitive); + else + { + function findRow(node) { return getAncestorByClass(node, "memberRow"); } + this.currentSearch = new TextSearch(this.panelNode, findRow); + row = this.currentSearch.find(text, reverse, Firebug.searchCaseSensitive); + } + + if (row) + { + var sel = this.document.defaultView.getSelection(); + sel.removeAllRanges(); + sel.addRange(this.currentSearch.range); + + scrollIntoCenterView(row, this.panelNode); + + this.highlightRow(row); + dispatch([Firebug.A11yModel], 'onDomSearchMatchFound', [this, text, row]); + return true; + } + else + { + dispatch([Firebug.A11yModel], 'onDomSearchMatchFound', [this, text, null]); + return false; + } + }/**/ +}); + +Firebug.registerPanel(DOMMainPanel); + + +// ************************************************************************************************ + + + +// ************************************************************************************************ +// Local Helpers + +var getMembers = function getMembers(object, level) // we expect object to be user-level object wrapped in security blanket +{ + if (!level) + level = 0; + + var ordinals = [], userProps = [], userClasses = [], userFuncs = [], + domProps = [], domFuncs = [], domConstants = []; + + try + { + var domMembers = getDOMMembers(object); + //var domMembers = {}; // TODO: xxxpedro + //var domConstantMap = {}; // TODO: xxxpedro + + if (object.wrappedJSObject) + var insecureObject = object.wrappedJSObject; + else + var insecureObject = object; + + // IE function prototype is not listed in (for..in) + if (isIE && isFunction(object)) + addMember("user", userProps, "prototype", object.prototype, level); + + for (var name in insecureObject) // enumeration is safe + { + if (ignoreVars[name] == 1) // javascript.options.strict says ignoreVars is undefined. + continue; + + var val; + try + { + val = insecureObject[name]; // getter is safe + } + catch (exc) + { + // Sometimes we get exceptions trying to access certain members + if (FBTrace.DBG_ERRORS && FBTrace.DBG_DOM) + FBTrace.sysout("dom.getMembers cannot access "+name, exc); + } + + var ordinal = parseInt(name); + if (ordinal || ordinal == 0) + { + addMember("ordinal", ordinals, name, val, level); + } + else if (isFunction(val)) + { + if (isClassFunction(val) && !(name in domMembers)) + addMember("userClass", userClasses, name, val, level); + else if (name in domMembers) + addMember("domFunction", domFuncs, name, val, level, domMembers[name]); + else + addMember("userFunction", userFuncs, name, val, level); + } + else + { + //TODO: xxxpedro + /* + var getterFunction = insecureObject.__lookupGetter__(name), + setterFunction = insecureObject.__lookupSetter__(name), + prefix = ""; + + if(getterFunction && !setterFunction) + prefix = "get "; + /**/ + + var prefix = ""; + + if (name in domMembers && !(name in domConstantMap)) + addMember("dom", domProps, (prefix+name), val, level, domMembers[name]); + else if (name in domConstantMap) + addMember("dom", domConstants, (prefix+name), val, level); + else + addMember("user", userProps, (prefix+name), val, level); + } + } + } + catch (exc) + { + // Sometimes we get exceptions just from trying to iterate the members + // of certain objects, like StorageList, but don't let that gum up the works + throw exc; + if (FBTrace.DBG_ERRORS && FBTrace.DBG_DOM) + FBTrace.sysout("dom.getMembers FAILS: ", exc); + //throw exc; + } + + function sortName(a, b) { return a.name > b.name ? 1 : -1; } + function sortOrder(a, b) { return a.order > b.order ? 1 : -1; } + + var members = []; + + members.push.apply(members, ordinals); + + Firebug.showUserProps = true; // TODO: xxxpedro + Firebug.showUserFuncs = true; // TODO: xxxpedro + Firebug.showDOMProps = true; + Firebug.showDOMFuncs = true; + Firebug.showDOMConstants = true; + + if (Firebug.showUserProps) + { + userProps.sort(sortName); + members.push.apply(members, userProps); + } + + if (Firebug.showUserFuncs) + { + userClasses.sort(sortName); + members.push.apply(members, userClasses); + + userFuncs.sort(sortName); + members.push.apply(members, userFuncs); + } + + if (Firebug.showDOMProps) + { + domProps.sort(sortName); + members.push.apply(members, domProps); + } + + if (Firebug.showDOMFuncs) + { + domFuncs.sort(sortName); + members.push.apply(members, domFuncs); + } + + if (Firebug.showDOMConstants) + members.push.apply(members, domConstants); + + return members; +}; + +function expandMembers(members, toggles, offset, level) // recursion starts with offset=0, level=0 +{ + var expanded = 0; + for (var i = offset; i < members.length; ++i) + { + var member = members[i]; + if (member.level > level) + break; + + if ( toggles.hasOwnProperty(member.name) ) + { + member.open = "opened"; // member.level <= level && member.name in toggles. + + var newMembers = getMembers(member.value, level+1); // sets newMembers.level to level+1 + + var args = [i+1, 0]; + args.push.apply(args, newMembers); + members.splice.apply(members, args); + + /* + if (FBTrace.DBG_DOM) + { + FBTrace.sysout("expandMembers member.name", member.name); + FBTrace.sysout("expandMembers toggles", toggles); + FBTrace.sysout("expandMembers toggles[member.name]", toggles[member.name]); + FBTrace.sysout("dom.expandedMembers level: "+level+" member", member); + } + /**/ + + expanded += newMembers.length; + i += newMembers.length + expandMembers(members, toggles[member.name], i+1, level+1); + } + } + + return expanded; +} + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + +function isClassFunction(fn) +{ + try + { + for (var name in fn.prototype) + return true; + } catch (exc) {} + return false; +} + +// FIXME: xxxpedro This function is already defined in Lib. If we keep this definition here, it +// will crash IE9 when not running the IE Developer Tool with JavaScript Debugging enabled!!! +// Check if this function is in fact defined in Firebug for Firefox. If so, we should remove +// this from here. The only difference of this function is the IE hack to show up the prototype +// of functions, but Firebug no longer shows the prototype for simple functions. +//var hasProperties = function hasProperties(ob) +//{ +// try +// { +// for (var name in ob) +// return true; +// } catch (exc) {} +// +// // IE function prototype is not listed in (for..in) +// if (isFunction(ob)) return true; +// +// return false; +//}; + +FBL.ErrorCopy = function(message) +{ + this.message = message; +}; + +var addMember = function addMember(type, props, name, value, level, order) +{ + var rep = Firebug.getRep(value); // do this first in case a call to instanceof reveals contents + var tag = rep.shortTag ? rep.shortTag : rep.tag; + + var ErrorCopy = function(){}; //TODO: xxxpedro + + var valueType = typeof(value); + var hasChildren = hasProperties(value) && !(value instanceof ErrorCopy) && + (isFunction(value) || (valueType == "object" && value != null) + || (valueType == "string" && value.length > Firebug.stringCropLength)); + + props.push({ + name: name, + value: value, + type: type, + rowClass: "memberRow-"+type, + open: "", + order: order, + level: level, + indent: level*16, + hasChildren: hasChildren, + tag: tag + }); +}; + +var getWatchRowIndex = function getWatchRowIndex(row) +{ + var index = -1; + for (; row && hasClass(row, "watchRow"); row = row.previousSibling) + ++index; + return index; +}; + +var getRowName = function getRowName(row) +{ + var node = row.firstChild; + return node.textContent ? node.textContent : node.innerText; +}; + +var getRowValue = function getRowValue(row) +{ + return row.lastChild.firstChild.repObject; +}; + +var getRowOwnerObject = function getRowOwnerObject(row) +{ + var parentRow = getParentRow(row); + if (parentRow) + return getRowValue(parentRow); +}; + +var getParentRow = function getParentRow(row) +{ + var level = parseInt(row.getAttribute("level"))-1; + for (row = row.previousSibling; row; row = row.previousSibling) + { + if (parseInt(row.getAttribute("level")) == level) + return row; + } +}; + +var getPath = function getPath(row) +{ + var name = getRowName(row); + var path = [name]; + + var level = parseInt(row.getAttribute("level"))-1; + for (row = row.previousSibling; row; row = row.previousSibling) + { + if (parseInt(row.getAttribute("level")) == level) + { + var name = getRowName(row); + path.splice(0, 0, name); + + --level; + } + } + + return path; +}; + +// ************************************************************************************************ + + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + +// ************************************************************************************************ +// DOM Module + +Firebug.DOM = extend(Firebug.Module, +{ + getPanel: function() + { + return Firebug.chrome ? Firebug.chrome.getPanel("DOM") : null; + } +}); + +Firebug.registerModule(Firebug.DOM); + + +// ************************************************************************************************ +// DOM Panel + +var lastHighlightedObject; + +function DOMSidePanel(){}; + +DOMSidePanel.prototype = extend(Firebug.DOMBasePanel.prototype, +{ + selectRow: function(row, target) + { + if (!target) + target = row.lastChild.firstChild; + + if (!target || !target.repObject) + return; + + this.pathToAppend = getPath(row); + + // If the object is inside an array, look up its index + var valueBox = row.lastChild.firstChild; + if (hasClass(valueBox, "objectBox-array")) + { + var arrayIndex = FirebugReps.Arr.getItemIndex(target); + this.pathToAppend.push(arrayIndex); + } + + // Make sure we get a fresh status path for the object, since otherwise + // it might find the object in the existing path and not refresh it + //Firebug.chrome.clearStatusPath(); + + var object = target.repObject; + + if (instanceOf(object, "Element")) + { + Firebug.HTML.selectTreeNode(ElementCache(object)); + } + else + { + Firebug.chrome.selectPanel("DOM"); + Firebug.chrome.getPanel("DOM").select(object, true); + } + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + onClick: function(event) + { + /* + var target = event.srcElement || event.target; + + var object = getAncestorByClass(target, "objectLink"); + object = object ? object.repObject : null; + + if(!object) return; + + if (instanceOf(object, "Element")) + { + Firebug.HTML.selectTreeNode(ElementCache(object)); + } + else + { + Firebug.chrome.selectPanel("DOM"); + Firebug.chrome.getPanel("DOM").select(object, true); + } + /**/ + + + var target = event.srcElement || event.target; + var repNode = Firebug.getRepNode(target); + if (repNode) + { + var row = getAncestorByClass(target, "memberRow"); + if (row) + { + this.selectRow(row, repNode); + cancelEvent(event); + } + } + /**/ + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // extends Panel + + name: "DOMSidePanel", + parentPanel: "HTML", + title: "DOM", + + options: { + hasToolButtons: true + }, + + isInitialized: false, + + create: function() + { + Firebug.DOMBasePanel.prototype.create.apply(this, arguments); + + this.onClick = bind(this.onClick, this); + }, + + initialize: function(){ + Firebug.DOMBasePanel.prototype.initialize.apply(this, arguments); + + addEvent(this.panelNode, "click", this.onClick); + + // TODO: xxxpedro css2 + var selection = ElementCache.get(Firebug.context.persistedState.selectedHTMLElementId); + if (selection) + this.select(selection, true); + }, + + shutdown: function() + { + removeEvent(this.panelNode, "click", this.onClick); + + Firebug.DOMBasePanel.prototype.shutdown.apply(this, arguments); + }, + + reattach: function(oldChrome) + { + //this.isInitialized = oldChrome.getPanel("DOM").isInitialized; + this.toggles = oldChrome.getPanel("DOMSidePanel").toggles; + } + +}); + +Firebug.registerPanel(DOMSidePanel); + + +// ************************************************************************************************ +}}); + +/* See license.txt for terms of usage */ + +FBL.FBTrace = {}; + +(function() { +// ************************************************************************************************ + +var traceOptions = { + DBG_TIMESTAMP: 1, + DBG_INITIALIZE: 1, + DBG_CHROME: 1, + DBG_ERRORS: 1, + DBG_DISPATCH: 1, + DBG_CSS: 1 +}; + +this.module = null; + +this.initialize = function() +{ + if (!this.messageQueue) + this.messageQueue = []; + + for (var name in traceOptions) + this[name] = traceOptions[name]; +}; + +// ************************************************************************************************ +// FBTrace API + +this.sysout = function() +{ + return this.logFormatted(arguments, ""); +}; + +this.dumpProperties = function(title, object) +{ + return this.logFormatted("dumpProperties() not supported.", "warning"); +}; + +this.dumpStack = function() +{ + return this.logFormatted("dumpStack() not supported.", "warning"); +}; + +this.flush = function(module) +{ + this.module = module; + + var queue = this.messageQueue; + this.messageQueue = []; + + for (var i = 0; i < queue.length; ++i) + this.writeMessage(queue[i][0], queue[i][1], queue[i][2]); +}; + +this.getPanel = function() +{ + return this.module ? this.module.getPanel() : null; +}; + +//************************************************************************************************* + +this.logFormatted = function(objects, className) +{ + var html = this.DBG_TIMESTAMP ? [getTimestamp(), " | "] : []; + var length = objects.length; + + for (var i = 0; i < length; ++i) + { + appendText(" ", html); + + var object = objects[i]; + + if (i == 0) + { + html.push("<b>"); + appendText(object, html); + html.push("</b>"); + } + else + appendText(object, html); + } + + return this.logRow(html, className); +}; + +this.logRow = function(message, className) +{ + var panel = this.getPanel(); + + if (panel && panel.panelNode) + this.writeMessage(message, className); + else + { + this.messageQueue.push([message, className]); + } + + return this.LOG_COMMAND; +}; + +this.writeMessage = function(message, className) +{ + var container = this.getPanel().containerNode; + var isScrolledToBottom = + container.scrollTop + container.offsetHeight >= container.scrollHeight; + + this.writeRow.call(this, message, className); + + if (isScrolledToBottom) + container.scrollTop = container.scrollHeight - container.offsetHeight; +}; + +this.appendRow = function(row) +{ + var container = this.getPanel().panelNode; + container.appendChild(row); +}; + +this.writeRow = function(message, className) +{ + var row = this.getPanel().panelNode.ownerDocument.createElement("div"); + row.className = "logRow" + (className ? " logRow-"+className : ""); + row.innerHTML = message.join(""); + this.appendRow(row); +}; + +//************************************************************************************************* + +function appendText(object, html) +{ + html.push(escapeHTML(objectToString(object))); +}; + +function getTimestamp() +{ + var now = new Date(); + var ms = "" + (now.getMilliseconds() / 1000).toFixed(3); + ms = ms.substr(2); + + return now.toLocaleTimeString() + "." + ms; +}; + +//************************************************************************************************* + +var HTMLtoEntity = +{ + "<": "<", + ">": ">", + "&": "&", + "'": "'", + '"': """ +}; + +function replaceChars(ch) +{ + return HTMLtoEntity[ch]; +}; + +function escapeHTML(value) +{ + return (value+"").replace(/[<>&"']/g, replaceChars); +}; + +//************************************************************************************************* + +function objectToString(object) +{ + try + { + return object+""; + } + catch (exc) + { + return null; + } +}; + +// ************************************************************************************************ +}).apply(FBL.FBTrace); + +/* See license.txt for terms of usage */ + +FBL.ns(function() { with (FBL) { +// ************************************************************************************************ + +// If application isn't in trace mode, the FBTrace panel won't be loaded +if (!Env.Options.enableTrace) return; + +// ************************************************************************************************ +// FBTrace Module + +Firebug.Trace = extend(Firebug.Module, +{ + getPanel: function() + { + return Firebug.chrome ? Firebug.chrome.getPanel("Trace") : null; + }, + + clear: function() + { + this.getPanel().panelNode.innerHTML = ""; + } +}); + +Firebug.registerModule(Firebug.Trace); + + +// ************************************************************************************************ +// FBTrace Panel + +function TracePanel(){}; + +TracePanel.prototype = extend(Firebug.Panel, +{ + name: "Trace", + title: "Trace", + + options: { + hasToolButtons: true, + innerHTMLSync: true + }, + + create: function(){ + Firebug.Panel.create.apply(this, arguments); + + this.clearButton = new Button({ + caption: "Clear", + title: "Clear FBTrace logs", + owner: Firebug.Trace, + onClick: Firebug.Trace.clear + }); + }, + + initialize: function(){ + Firebug.Panel.initialize.apply(this, arguments); + + this.clearButton.initialize(); + }, + + shutdown: function() + { + this.clearButton.shutdown(); + + Firebug.Panel.shutdown.apply(this, arguments); + } + +}); + +Firebug.registerPanel(TracePanel); + +// ************************************************************************************************ +}}); + +/* See license.txt for terms of usage */ + +FBL.ns(function() { with (FBL) { +// ************************************************************************************************ + +// ************************************************************************************************ +// Globals + +var modules = []; +var panelTypes = []; +var panelTypeMap = {}; + +var parentPanelMap = {}; + + +var registerModule = Firebug.registerModule; +var registerPanel = Firebug.registerPanel; + +// ************************************************************************************************ +append(Firebug, +{ + extend: function(fn) + { + if (Firebug.chrome && Firebug.chrome.addPanel) + { + var namespace = ns(fn); + fn.call(namespace, FBL); + } + else + { + setTimeout(function(){Firebug.extend(fn);},100); + } + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Registration + + registerModule: function() + { + registerModule.apply(Firebug, arguments); + + modules.push.apply(modules, arguments); + + dispatch(modules, "initialize", []); + + if (FBTrace.DBG_INITIALIZE) FBTrace.sysout("Firebug.registerModule"); + }, + + registerPanel: function() + { + registerPanel.apply(Firebug, arguments); + + panelTypes.push.apply(panelTypes, arguments); + + for (var i = 0, panelType; panelType = arguments[i]; ++i) + { + // TODO: xxxpedro investigate why Dev Panel throws an error + if (panelType.prototype.name == "Dev") continue; + + panelTypeMap[panelType.prototype.name] = arguments[i]; + + var parentPanelName = panelType.prototype.parentPanel; + if (parentPanelName) + { + parentPanelMap[parentPanelName] = 1; + } + else + { + var panelName = panelType.prototype.name; + var chrome = Firebug.chrome; + chrome.addPanel(panelName); + + // tab click handler + var onTabClick = function onTabClick() + { + chrome.selectPanel(panelName); + return false; + }; + + chrome.addController([chrome.panelMap[panelName].tabNode, "mousedown", onTabClick]); + } + } + + if (FBTrace.DBG_INITIALIZE) + for (var i = 0; i < arguments.length; ++i) + FBTrace.sysout("Firebug.registerPanel", arguments[i].prototype.name); + } + +}); + + + + +// ************************************************************************************************ +}}); + +FBL.ns(function() { with (FBL) { +// ************************************************************************************************ + +FirebugChrome.Skin = +{ + CSS: '.obscured{left:-999999px !important;}.collapsed{display:none;}[collapsed="true"]{display:none;}#fbCSS{padding:0 !important;}.cssPropDisable{float:left;display:block;width:2em;cursor:default;}.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(https://getfirebug.com/releases/lite/latest/skin/xp/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(https://getfirebug.com/releases/lite/latest/skin/xp/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;}.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;}.textEditorInner1{padding-left:11px;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/textEditorBorders.png) repeat-y;_background:url(https://getfirebug.com/releases/lite/latest/skin/xp/textEditorBorders.gif) repeat-y;_overflow:hidden;}.textEditorInner2{position:relative;padding-right:2px;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/textEditorBorders.png) repeat-y 100% 0;_background:url(https://getfirebug.com/releases/lite/latest/skin/xp/textEditorBorders.gif) repeat-y 100% 0;_position:fixed;}.textEditorTop1{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/textEditorCorners.png) no-repeat 100% 0;margin-left:11px;height:10px;_background:url(https://getfirebug.com/releases/lite/latest/skin/xp/textEditorCorners.gif) no-repeat 100% 0;_overflow:hidden;}.textEditorTop2{position:relative;left:-11px;width:11px;height:10px;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/textEditorCorners.png) no-repeat;_background:url(https://getfirebug.com/releases/lite/latest/skin/xp/textEditorCorners.gif) no-repeat;}.textEditorBottom1{position:relative;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/textEditorCorners.png) no-repeat 100% 100%;margin-left:11px;height:12px;_background:url(https://getfirebug.com/releases/lite/latest/skin/xp/textEditorCorners.gif) no-repeat 100% 100%;}.textEditorBottom2{position:relative;left:-11px;width:11px;height:12px;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/textEditorCorners.png) no-repeat 0 100%;_background:url(https://getfirebug.com/releases/lite/latest/skin/xp/textEditorCorners.gif) no-repeat 0 100%;}.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{}.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(https://getfirebug.com/releases/lite/latest/skin/xp/disable.png) no-repeat 2px 1px;_background:url(https://getfirebug.com/releases/lite/latest/skin/xp/disable.gif) no-repeat 2px 1px;}.cssProp.editGroup.editing{background:none;}.cssProp.disabledStyle{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/disableHover.png) no-repeat 2px 1px;_background:url(https://getfirebug.com/releases/lite/latest/skin/xp/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%;}.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(https://getfirebug.com/releases/lite/latest/skin/xp/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;}.netHeaderRow{height:16px;}.netHeaderCell{cursor:pointer;-moz-user-select:none;border-bottom:1px solid #9C9C9C;padding:0 !important;font-weight:bold;background:#BBBBBB url(https://getfirebug.com/releases/lite/latest/skin/xp/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(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/tableHeaderActive.gif) repeat-x;}.netHeaderSorted{background:#7D93B2 url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/tableHeaderSorted.gif) repeat-x;}.netHeaderSorted > .netHeaderCellBox{border-right-color:#6B7C93;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/arrowDown.png) no-repeat right;}.netHeaderSorted.sortedAscending > .netHeaderCellBox{background-image:url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/arrowUp.png);}.netHeaderSorted:hover:active{background:#536B90 url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/tableHeaderSortedActive.gif) repeat-x;}.panelNode-net .netRowHeader{display:block;}.netRowHeader{cursor:pointer;display:none;height:15px;margin-right:0 !important;}.netRow .netRowHeader{background-position:5px 1px;}.netRow[breakpoint="true"] .netRowHeader{background-image:url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/breakpoint.png);}.netRow[breakpoint="true"][disabledBreakpoint="true"] .netRowHeader{background-image:url(https://getfirebug.com/releases/lite/latest/skin/xp/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;}.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(https://getfirebug.com/releases/lite/latest/skin/xp/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(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/netBarResolving.gif) repeat-x;z-index:60;}.netConnectingBar{position:absolute;left:0;top:0;bottom:0;background:#FFFFFF url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/netBarConnecting.gif) repeat-x;z-index:50;}.netBlockingBar{position:absolute;left:0;top:0;bottom:0;background:#FFFFFF url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/netBarWaiting.gif) repeat-x;z-index:40;}.netSendingBar{position:absolute;left:0;top:0;bottom:0;background:#FFFFFF url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/netBarSending.gif) repeat-x;z-index:30;}.netWaitingBar{position:absolute;left:0;top:0;bottom:0;background:#FFFFFF url(https://getfirebug.com/releases/lite/latest/skin/xp/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(https://getfirebug.com/releases/lite/latest/skin/xp/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;}.loaded .netReceivingBar,.loaded.netReceivingBar{background:#B6B6B6 url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/netBarLoaded.gif) repeat-x;border-color:#B6B6B6;}.fromCache .netReceivingBar,.fromCache.netReceivingBar{background:#D6D6D6 url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/netBarCached.gif) repeat-x;border-color:#D6D6D6;}.netSummaryRow .netTimeLabel,.loaded .netTimeLabel{background:transparent;}.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;}.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;}.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(https://getfirebug.com/releases/lite/latest/skin/xp/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;}.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;}.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;}.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%;}.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(https://getfirebug.com/releases/lite/latest/skin/xp/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;}.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(https://getfirebug.com/releases/lite/latest/skin/xp/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(https://getfirebug.com/releases/lite/latest/skin/xp/tree_close.gif);}.twisty{background-position:4px 4px;}* html .logRow-spy .spyHead .spyTitle,* html .logGroup .logGroupLabel,* html .hasChildren .memberLabelCell .memberLabel,* html .hasHeaders .netHrefLabel{background-image:url(https://getfirebug.com/releases/lite/latest/skin/xp/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(https://getfirebug.com/releases/lite/latest/skin/xp/tree_close.gif);background-repeat:no-repeat;background-position:2px 2px;}.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;}.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(https://getfirebug.com/releases/lite/latest/skin/xp/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{}.logRow-warn,.logRow-warningMessage{}.logRow-error,.logRow-assert,.logRow-errorMessage{}.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;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;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{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;}.headerCellBox{padding:2px 4px;border-left:1px solid #D9D9D9;border-right:1px solid #9C9C9C;}.headerCell:hover:active{}.headerSorted{}.headerSorted > .headerCellBox{border-right-color:#6B7C93;}.headerSorted.sortedAscending > .headerCellBox{}.headerSorted:hover:active{}.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;}.objectProp-object{color:DarkGreen;}.objectProps{color:#000;font-weight:normal;}.objectPropName{color:#777;}.objectProps .objectProp-string{color:#f55;}.objectProps .objectProp-number{color:#55a;}.objectProps .objectProp-object{color:#585;}.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{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(https://getfirebug.com/releases/lite/latest/skin/xp/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(https://getfirebug.com/releases/lite/latest/skin/xp/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(https://getfirebug.com/releases/lite/latest/skin/xp/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;}.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;padding-left:18px;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;}#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;}#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(https://getfirebug.com/releases/lite/latest/skin/xp/tabMenuTarget.png);}.fbTabMenuTarget:hover{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/tabMenuTargetHover.png);}.fbShadow{float:left;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/shadowAlpha.png) no-repeat bottom right !important;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/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(https://getfirebug.com/releases/lite/latest/skin/xp/tabMenuPin.png) no-repeat right 0;}.fbMenuGroup:hover{background:#316AC5 url(https://getfirebug.com/releases/lite/latest/skin/xp/tabMenuPin.png) no-repeat right -17px;}.fbMenuGroupSelected{color:#fff;background:#316AC5 url(https://getfirebug.com/releases/lite/latest/skin/xp/tabMenuPin.png) no-repeat right -17px;}.fbMenuChecked{background:transparent url(https://getfirebug.com/releases/lite/latest/skin/xp/tabMenuCheckbox.png) no-repeat 4px 0;}.fbMenuChecked:hover{background:#316AC5 url(https://getfirebug.com/releases/lite/latest/skin/xp/tabMenuCheckbox.png) no-repeat 4px -17px;}.fbMenuRadioSelected{background:transparent url(https://getfirebug.com/releases/lite/latest/skin/xp/tabMenuRadio.png) no-repeat 4px 0;}.fbMenuRadioSelected:hover{background:#316AC5 url(https://getfirebug.com/releases/lite/latest/skin/xp/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;}#fbErrorPopup{position:absolute;right:0;bottom:0;height:19px;width:75px;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/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;}.fbBody{margin:0;padding:0;overflow:hidden;font-family:Lucida Grande,Tahoma,sans-serif;font-size:11px;background:#fff;}.clear{clear:both;}#fbMiniChrome{display:none;right:0;height:27px;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/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(https://getfirebug.com/releases/lite/latest/skin/xp/search.png) no-repeat 4px 2px !important;background:#fff url(https://getfirebug.com/releases/lite/latest/skin/xp/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(https://getfirebug.com/releases/lite/latest/skin/xp/errorIcon.png) no-repeat !important;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/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(https://getfirebug.com/releases/lite/latest/skin/xp/sprite.png) 0 -135px;cursor:pointer;}#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(https://getfirebug.com/releases/lite/latest/skin/xp/sprite.png) #f1f2ee 0 0;height:27px;font-size:11px;line-height:13px;}#fbPanelBarBox{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/sprite.png) #dbd9c9 0 -27px;height:22px;}#fbContent{height:100%;vertical-align:top;}#fbBottom{height:18px;background:#fff;}#fbToolbarIcon{float:left;padding:0 5px 0;}#fbToolbarIcon a{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/sprite.png) 0 -135px;}#fbToolbarButtons{padding:0 2px 0 5px;}#fbToolbarButtons{padding:0 2px 0 5px;}.fbButton{text-decoration:none;display:block;float:left;color:#000;padding:4px 6px 4px 7px;cursor:default;}.fbButton:hover{color:#333;background:#f5f5ef url(https://getfirebug.com/releases/lite/latest/skin/xp/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(https://getfirebug.com/releases/lite/latest/skin/xp/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(https://getfirebug.com/releases/lite/latest/skin/xp/sprite.png) #f1f2ee 0 0;}#fbPanelBar1{width:1024px; z-index:8;left:0;white-space:nowrap;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/sprite.png) #dbd9c9 0 -27px;position:absolute;left:4px;}#fbPanelBar2Box{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/sprite.png) #dbd9c9 0 -27px;position:absolute;height:22px;width:300px; z-index:9;right:0;}#fbPanelBar2{position:absolute;width:290px; height:22px;padding-left:4px;}.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(https://getfirebug.com/releases/lite/latest/skin/xp/down.png) no-repeat;position:absolute;right:2px;bottom:3px;z-index:99;}#fbSmallCommandLineIcon:hover{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/downHover.png) no-repeat;}.hide{overflow:hidden !important;position:fixed !important;display:none !important;visibility:hidden !important;}#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(https://getfirebug.com/releases/lite/latest/skin/xp/up.png) no-repeat;position:absolute;right:1px;bottom:1px;z-index:10;}#fbLargeCommandLineIcon:hover{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/upHover.png) no-repeat;}div.fbFitHeight{overflow:auto;position:relative;}.fbSmallButton{overflow:hidden;width:16px;height:16px;display:block;text-decoration:none;cursor:default;}#fbWindowButtons .fbSmallButton{float:right;}#fbWindow_btClose{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/min.png);}#fbWindow_btClose:hover{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/minHover.png);}#fbWindow_btDetach{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/detach.png);}#fbWindow_btDetach:hover{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/detachHover.png);}#fbWindow_btDeactivate{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/off.png);}#fbWindow_btDeactivate:hover{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/offHover.png);}.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{float:left;}.fbPanelBar .fbTabL,.fbPanelBar .fbTabR{height:22px;width:8px;}.fbPanelBar .fbTabText{padding:4px 1px 0;}a.fbTab:hover{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/sprite.png) 0 -73px;}a.fbTab:hover .fbTabL{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/sprite.png) -16px -96px;}a.fbTab:hover .fbTabR{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/sprite.png) -24px -96px;}.fbSelectedTab{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/sprite.png) #f1f2ee 0 -50px !important;color:#000;}.fbSelectedTab .fbTabL{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/sprite.png) 0 -96px !important;}.fbSelectedTab .fbTabR{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/sprite.png) -8px -96px !important;}#fbHSplitter{position:fixed;_position:absolute;left:0;top:0;width:100%;height:5px;overflow:hidden;cursor:n-resize !important;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/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(https://getfirebug.com/releases/lite/latest/skin/xp/tree_open.gif);_float:none;_display:inline;_position:absolute;}div.nodeMaximized{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/tree_close.gif);}div.objectBox-element{padding:1px 3px;}.objectBox-selector{cursor:default;}.selectedElement{background:highlight;color:#fff !important;}.selectedElement span{color:#fff !important;}* html .selectedElement{position:relative;}@media screen and (-webkit-min-device-pixel-ratio:0){.selectedElement{background:#316AC5;color:#fff !important;}}.logRow *{font-size:1em;}.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;}.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(https://getfirebug.com/releases/lite/latest/skin/xp/infoIcon.png) !important;background-image:url(https://getfirebug.com/releases/lite/latest/skin/xp/infoIcon.gif);}.logRow-warn{background-color:cyan;background-image:url(https://getfirebug.com/releases/lite/latest/skin/xp/warningIcon.png) !important;background-image:url(https://getfirebug.com/releases/lite/latest/skin/xp/warningIcon.gif);}.logRow-error{background-color:LightYellow;background-image:url(https://getfirebug.com/releases/lite/latest/skin/xp/errorIcon.png) !important;background-image:url(https://getfirebug.com/releases/lite/latest/skin/xp/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;}.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{}.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;}.propertyName{font-weight:bold;}.FirebugPopup{height:100% !important;}.FirebugPopup #fbWindowButtons{display:none !important;}.FirebugPopup #fbHSplitter{display:none !important;}', + HTML: '<table id="fbChrome" cellpadding="0" cellspacing="0" border="0"><tbody><tr><td id="fbTop" colspan="2"><div id="fbWindowButtons"><a id="fbWindow_btDeactivate" class="fbSmallButton fbHover" title="Deactivate Firebug for this web page"> </a><a id="fbWindow_btDetach" class="fbSmallButton fbHover" title="Open Firebug in popup window"> </a><a id="fbWindow_btClose" class="fbSmallButton fbHover" title="Minimize Firebug"> </a></div><div id="fbToolbar"><div id="fbToolbarContent"><span id="fbToolbarIcon"><a id="fbFirebugButton" class="fbIconButton" class="fbHover" target="_blank"> </a></span><span id="fbToolbarButtons"><span id="fbFixedButtons"><a id="fbChrome_btInspect" class="fbButton fbHover" title="Click an element in the page to inspect">Inspect</a></span><span id="fbConsoleButtons" class="fbToolbarButtons"><a id="fbConsole_btClear" class="fbButton fbHover" title="Clear the console">Clear</a></span></span><span id="fbStatusBarBox"><span class="fbToolbarSeparator"></span></span></div></div><div id="fbPanelBarBox"><div id="fbPanelBar1" class="fbPanelBar"><a id="fbConsoleTab" class="fbTab fbHover"><span class="fbTabL"></span><span class="fbTabText">Console</span><span class="fbTabMenuTarget"></span><span class="fbTabR"></span></a><a id="fbHTMLTab" class="fbTab fbHover"><span class="fbTabL"></span><span class="fbTabText">HTML</span><span class="fbTabR"></span></a><a class="fbTab fbHover"><span class="fbTabL"></span><span class="fbTabText">CSS</span><span class="fbTabR"></span></a><a class="fbTab fbHover"><span class="fbTabL"></span><span class="fbTabText">Script</span><span class="fbTabR"></span></a><a class="fbTab fbHover"><span class="fbTabL"></span><span class="fbTabText">DOM</span><span class="fbTabR"></span></a></div><div id="fbPanelBar2Box" class="hide"><div id="fbPanelBar2" class="fbPanelBar"></div></div></div><div id="fbHSplitter"> </div></td></tr><tr id="fbContent"><td id="fbPanelBox1"><div id="fbPanel1" class="fbFitHeight"><div id="fbConsole" class="fbPanel"></div><div id="fbHTML" class="fbPanel"></div></div></td><td id="fbPanelBox2" class="hide"><div id="fbVSplitter" class="fbVSplitter"> </div><div id="fbPanel2" class="fbFitHeight"><div id="fbHTML_Style" class="fbPanel"></div><div id="fbHTML_Layout" class="fbPanel"></div><div id="fbHTML_DOM" class="fbPanel"></div></div><textarea id="fbLargeCommandLine" class="fbFitHeight"></textarea><div id="fbLargeCommandButtons"><a id="fbCommand_btRun" class="fbButton fbHover">Run</a><a id="fbCommand_btClear" class="fbButton fbHover">Clear</a><a id="fbSmallCommandLineIcon" class="fbSmallButton fbHover"></a></div></td></tr><tr id="fbBottom" class="hide"><td id="fbCommand" colspan="2"><div id="fbCommandBox"><div id="fbCommandIcon">>>></div><input id="fbCommandLine" name="fbCommandLine" type="text"/><a id="fbLargeCommandLineIcon" class="fbSmallButton fbHover"></a></div></td></tr></tbody></table><span id="fbMiniChrome"><span id="fbMiniContent"><span id="fbMiniIcon" title="Open Firebug Lite"></span><span id="fbMiniErrors" class="fbErrors"></span></span></span>' +}; + +// ************************************************************************************************ +}}); + +// ************************************************************************************************ +FBL.initialize(); +// ************************************************************************************************ + +})();
\ No newline at end of file diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/json-js/json2.js b/ecomp-portal-FE/client/bower_components/lodash/vendor/json-js/json2.js new file mode 100644 index 00000000..58384577 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/json-js/json2.js @@ -0,0 +1,519 @@ +/* + json2.js + 2015-05-03 + + 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. This file is provides the ES5 JSON capability to ES3 systems. + If a project might run on IE8 or earlier, then this file should be included. + This file does nothing on ES5 systems. + + 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 + eval, for, this +*/ + +/*property + 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. + +if (typeof JSON !== 'object') { + JSON = {}; +} + +(function () { + 'use strict'; + + var rx_one = /^[\],:{}\s]*$/, + rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, + rx_three = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, + rx_four = /(?:^|:|,)(?:\s*\[)+/g, + rx_escapable = /[\\\"\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + rx_dangerous = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; + + function f(n) { + // Format integers to have at least two digits. + return n < 10 + ? '0' + n + : n; + } + + function this_value() { + return this.valueOf(); + } + + if (typeof Date.prototype.toJSON !== 'function') { + + Date.prototype.toJSON = function () { + + 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; + }; + + Boolean.prototype.toJSON = this_value; + Number.prototype.toJSON = this_value; + String.prototype.toJSON = this_value; + } + + var gap, + indent, + meta, + 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. + + rx_escapable.lastIndex = 0; + return rx_escapable.test(string) + ? '"' + string.replace(rx_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) { + if (typeof rep[i] === 'string') { + k = rep[i]; + 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.prototype.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') { + meta = { // table of character substitutions + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"': '\\"', + '\\': '\\\\' + }; + 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.prototype.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); + rx_dangerous.lastIndex = 0; + if (rx_dangerous.test(text)) { + text = text.replace(rx_dangerous, 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 ( + rx_one.test( + text + .replace(rx_two, '@') + .replace(rx_three, ']') + .replace(rx_four, '') + ) + ) { + +// 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'); + }; + } +}()); diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/underscore/LICENSE b/ecomp-portal-FE/client/bower_components/lodash/vendor/underscore/LICENSE new file mode 100644 index 00000000..447239f3 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/underscore/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2009-2016 Jeremy Ashkenas, DocumentCloud and Investigative +Reporters & Editors + +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. diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/underscore/test/arrays.js b/ecomp-portal-FE/client/bower_components/lodash/vendor/underscore/test/arrays.js new file mode 100644 index 00000000..748edea4 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/underscore/test/arrays.js @@ -0,0 +1,555 @@ +(function() { + var _ = typeof require == 'function' ? require('..') : window._; + + QUnit.module('Arrays'); + + QUnit.test('first', function(assert) { + assert.equal(_.first([1, 2, 3]), 1, 'can pull out the first element of an array'); + assert.equal(_([1, 2, 3]).first(), 1, 'can perform OO-style "first()"'); + assert.deepEqual(_.first([1, 2, 3], 0), [], 'returns an empty array when n <= 0 (0 case)'); + assert.deepEqual(_.first([1, 2, 3], -1), [], 'returns an empty array when n <= 0 (negative case)'); + assert.deepEqual(_.first([1, 2, 3], 2), [1, 2], 'can fetch the first n elements'); + assert.deepEqual(_.first([1, 2, 3], 5), [1, 2, 3], 'returns the whole array if n > length'); + var result = (function(){ return _.first(arguments); }(4, 3, 2, 1)); + assert.equal(result, 4, 'works on an arguments object'); + result = _.map([[1, 2, 3], [1, 2, 3]], _.first); + assert.deepEqual(result, [1, 1], 'works well with _.map'); + assert.equal(_.first(null), void 0, 'returns undefined when called on null'); + }); + + QUnit.test('head', function(assert) { + assert.strictEqual(_.head, _.first, 'is an alias for first'); + }); + + QUnit.test('take', function(assert) { + assert.strictEqual(_.take, _.first, 'is an alias for first'); + }); + + QUnit.test('rest', function(assert) { + var numbers = [1, 2, 3, 4]; + assert.deepEqual(_.rest(numbers), [2, 3, 4], 'fetches all but the first element'); + assert.deepEqual(_.rest(numbers, 0), [1, 2, 3, 4], 'returns the whole array when index is 0'); + assert.deepEqual(_.rest(numbers, 2), [3, 4], 'returns elements starting at the given index'); + var result = (function(){ return _(arguments).rest(); }(1, 2, 3, 4)); + assert.deepEqual(result, [2, 3, 4], 'works on an arguments object'); + result = _.map([[1, 2, 3], [1, 2, 3]], _.rest); + assert.deepEqual(_.flatten(result), [2, 3, 2, 3], 'works well with _.map'); + }); + + QUnit.test('tail', function(assert) { + assert.strictEqual(_.tail, _.rest, 'is an alias for rest'); + }); + + QUnit.test('drop', function(assert) { + assert.strictEqual(_.drop, _.rest, 'is an alias for rest'); + }); + + QUnit.test('initial', function(assert) { + assert.deepEqual(_.initial([1, 2, 3, 4, 5]), [1, 2, 3, 4], 'returns all but the last element'); + assert.deepEqual(_.initial([1, 2, 3, 4], 2), [1, 2], 'returns all but the last n elements'); + assert.deepEqual(_.initial([1, 2, 3, 4], 6), [], 'returns an empty array when n > length'); + var result = (function(){ return _(arguments).initial(); }(1, 2, 3, 4)); + assert.deepEqual(result, [1, 2, 3], 'works on an arguments object'); + result = _.map([[1, 2, 3], [1, 2, 3]], _.initial); + assert.deepEqual(_.flatten(result), [1, 2, 1, 2], 'works well with _.map'); + }); + + QUnit.test('last', function(assert) { + assert.equal(_.last([1, 2, 3]), 3, 'can pull out the last element of an array'); + assert.equal(_([1, 2, 3]).last(), 3, 'can perform OO-style "last()"'); + assert.deepEqual(_.last([1, 2, 3], 0), [], 'returns an empty array when n <= 0 (0 case)'); + assert.deepEqual(_.last([1, 2, 3], -1), [], 'returns an empty array when n <= 0 (negative case)'); + assert.deepEqual(_.last([1, 2, 3], 2), [2, 3], 'can fetch the last n elements'); + assert.deepEqual(_.last([1, 2, 3], 5), [1, 2, 3], 'returns the whole array if n > length'); + var result = (function(){ return _(arguments).last(); }(1, 2, 3, 4)); + assert.equal(result, 4, 'works on an arguments object'); + result = _.map([[1, 2, 3], [1, 2, 3]], _.last); + assert.deepEqual(result, [3, 3], 'works well with _.map'); + assert.equal(_.last(null), void 0, 'returns undefined when called on null'); + }); + + QUnit.test('compact', function(assert) { + assert.deepEqual(_.compact([1, false, null, 0, '', void 0, NaN, 2]), [1, 2], 'removes all falsy values'); + var result = (function(){ return _.compact(arguments); }(0, 1, false, 2, false, 3)); + assert.deepEqual(result, [1, 2, 3], 'works on an arguments object'); + result = _.map([[1, false, false], [false, false, 3]], _.compact); + assert.deepEqual(result, [[1], [3]], 'works well with _.map'); + }); + + QUnit.test('flatten', function(assert) { + assert.deepEqual(_.flatten(null), [], 'supports null'); + assert.deepEqual(_.flatten(void 0), [], 'supports undefined'); + + assert.deepEqual(_.flatten([[], [[]], []]), [], 'supports empty arrays'); + assert.deepEqual(_.flatten([[], [[]], []], true), [[]], 'can shallowly flatten empty arrays'); + + var list = [1, [2], [3, [[[4]]]]]; + assert.deepEqual(_.flatten(list), [1, 2, 3, 4], 'can flatten nested arrays'); + assert.deepEqual(_.flatten(list, true), [1, 2, 3, [[[4]]]], 'can shallowly flatten nested arrays'); + var result = (function(){ return _.flatten(arguments); }(1, [2], [3, [[[4]]]])); + assert.deepEqual(result, [1, 2, 3, 4], 'works on an arguments object'); + list = [[1], [2], [3], [[4]]]; + assert.deepEqual(_.flatten(list, true), [1, 2, 3, [4]], 'can shallowly flatten arrays containing only other arrays'); + + assert.equal(_.flatten([_.range(10), _.range(10), 5, 1, 3], true).length, 23, 'can flatten medium length arrays'); + assert.equal(_.flatten([_.range(10), _.range(10), 5, 1, 3]).length, 23, 'can shallowly flatten medium length arrays'); + assert.equal(_.flatten([new Array(1000000), _.range(56000), 5, 1, 3]).length, 1056003, 'can handle massive arrays'); + assert.equal(_.flatten([new Array(1000000), _.range(56000), 5, 1, 3], true).length, 1056003, 'can handle massive arrays in shallow mode'); + + var x = _.range(100000); + for (var i = 0; i < 1000; i++) x = [x]; + assert.deepEqual(_.flatten(x), _.range(100000), 'can handle very deep arrays'); + assert.deepEqual(_.flatten(x, true), x[0], 'can handle very deep arrays in shallow mode'); + }); + + QUnit.test('without', function(assert) { + var list = [1, 2, 1, 0, 3, 1, 4]; + assert.deepEqual(_.without(list, 0, 1), [2, 3, 4], 'removes all instances of the given values'); + var result = (function(){ return _.without(arguments, 0, 1); }(1, 2, 1, 0, 3, 1, 4)); + assert.deepEqual(result, [2, 3, 4], 'works on an arguments object'); + + list = [{one: 1}, {two: 2}]; + assert.deepEqual(_.without(list, {one: 1}), list, 'compares objects by reference (value case)'); + assert.deepEqual(_.without(list, list[0]), [{two: 2}], 'compares objects by reference (reference case)'); + }); + + QUnit.test('sortedIndex', function(assert) { + var numbers = [10, 20, 30, 40, 50]; + var indexFor35 = _.sortedIndex(numbers, 35); + assert.equal(indexFor35, 3, 'finds the index at which a value should be inserted to retain order'); + var indexFor30 = _.sortedIndex(numbers, 30); + assert.equal(indexFor30, 2, 'finds the smallest index at which a value could be inserted to retain order'); + + var objects = [{x: 10}, {x: 20}, {x: 30}, {x: 40}]; + var iterator = function(obj){ return obj.x; }; + assert.strictEqual(_.sortedIndex(objects, {x: 25}, iterator), 2, 'uses the result of `iterator` for order comparisons'); + assert.strictEqual(_.sortedIndex(objects, {x: 35}, 'x'), 3, 'when `iterator` is a string, uses that key for order comparisons'); + + var context = {1: 2, 2: 3, 3: 4}; + iterator = function(obj){ return this[obj]; }; + assert.strictEqual(_.sortedIndex([1, 3], 2, iterator, context), 1, 'can execute its iterator in the given context'); + + var values = [0, 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095, 8191, 16383, 32767, 65535, 131071, 262143, 524287, + 1048575, 2097151, 4194303, 8388607, 16777215, 33554431, 67108863, 134217727, 268435455, 536870911, 1073741823, 2147483647]; + var largeArray = Array(Math.pow(2, 32) - 1); + var length = values.length; + // Sparsely populate `array` + while (length--) { + largeArray[values[length]] = values[length]; + } + assert.equal(_.sortedIndex(largeArray, 2147483648), 2147483648, 'works with large indexes'); + }); + + QUnit.test('uniq', function(assert) { + var list = [1, 2, 1, 3, 1, 4]; + assert.deepEqual(_.uniq(list), [1, 2, 3, 4], 'can find the unique values of an unsorted array'); + list = [1, 1, 1, 2, 2, 3]; + assert.deepEqual(_.uniq(list, true), [1, 2, 3], 'can find the unique values of a sorted array faster'); + + list = [{name: 'Moe'}, {name: 'Curly'}, {name: 'Larry'}, {name: 'Curly'}]; + var expected = [{name: 'Moe'}, {name: 'Curly'}, {name: 'Larry'}]; + var iterator = function(stooge) { return stooge.name; }; + assert.deepEqual(_.uniq(list, false, iterator), expected, 'uses the result of `iterator` for uniqueness comparisons (unsorted case)'); + assert.deepEqual(_.uniq(list, iterator), expected, '`sorted` argument defaults to false when omitted'); + assert.deepEqual(_.uniq(list, 'name'), expected, 'when `iterator` is a string, uses that key for comparisons (unsorted case)'); + + list = [{score: 8}, {score: 10}, {score: 10}]; + expected = [{score: 8}, {score: 10}]; + iterator = function(item) { return item.score; }; + assert.deepEqual(_.uniq(list, true, iterator), expected, 'uses the result of `iterator` for uniqueness comparisons (sorted case)'); + assert.deepEqual(_.uniq(list, true, 'score'), expected, 'when `iterator` is a string, uses that key for comparisons (sorted case)'); + + assert.deepEqual(_.uniq([{0: 1}, {0: 1}, {0: 1}, {0: 2}], 0), [{0: 1}, {0: 2}], 'can use falsey pluck like iterator'); + + var result = (function(){ return _.uniq(arguments); }(1, 2, 1, 3, 1, 4)); + assert.deepEqual(result, [1, 2, 3, 4], 'works on an arguments object'); + + var a = {}, b = {}, c = {}; + assert.deepEqual(_.uniq([a, b, a, b, c]), [a, b, c], 'works on values that can be tested for equivalency but not ordered'); + + assert.deepEqual(_.uniq(null), [], 'returns an empty array when `array` is not iterable'); + + var context = {}; + list = [3]; + _.uniq(list, function(value, index, array) { + assert.strictEqual(this, context, 'executes its iterator in the given context'); + assert.strictEqual(value, 3, 'passes its iterator the value'); + assert.strictEqual(index, 0, 'passes its iterator the index'); + assert.strictEqual(array, list, 'passes its iterator the entire array'); + }, context); + + }); + + QUnit.test('unique', function(assert) { + assert.strictEqual(_.unique, _.uniq, 'is an alias for uniq'); + }); + + QUnit.test('intersection', function(assert) { + var stooges = ['moe', 'curly', 'larry'], leaders = ['moe', 'groucho']; + assert.deepEqual(_.intersection(stooges, leaders), ['moe'], 'can find the set intersection of two arrays'); + assert.deepEqual(_(stooges).intersection(leaders), ['moe'], 'can perform an OO-style intersection'); + var result = (function(){ return _.intersection(arguments, leaders); }('moe', 'curly', 'larry')); + assert.deepEqual(result, ['moe'], 'works on an arguments object'); + var theSixStooges = ['moe', 'moe', 'curly', 'curly', 'larry', 'larry']; + assert.deepEqual(_.intersection(theSixStooges, leaders), ['moe'], 'returns a duplicate-free array'); + result = _.intersection([2, 4, 3, 1], [1, 2, 3]); + assert.deepEqual(result, [2, 3, 1], 'preserves the order of the first array'); + result = _.intersection(null, [1, 2, 3]); + assert.deepEqual(result, [], 'returns an empty array when passed null as the first argument'); + result = _.intersection([1, 2, 3], null); + assert.deepEqual(result, [], 'returns an empty array when passed null as an argument beyond the first'); + }); + + QUnit.test('union', function(assert) { + var result = _.union([1, 2, 3], [2, 30, 1], [1, 40]); + assert.deepEqual(result, [1, 2, 3, 30, 40], 'can find the union of a list of arrays'); + + result = _([1, 2, 3]).union([2, 30, 1], [1, 40]); + assert.deepEqual(result, [1, 2, 3, 30, 40], 'can perform an OO-style union'); + + result = _.union([1, 2, 3], [2, 30, 1], [1, 40, [1]]); + assert.deepEqual(result, [1, 2, 3, 30, 40, [1]], 'can find the union of a list of nested arrays'); + + result = _.union([10, 20], [1, 30, 10], [0, 40]); + assert.deepEqual(result, [10, 20, 1, 30, 0, 40], 'orders values by their first encounter'); + + result = (function(){ return _.union(arguments, [2, 30, 1], [1, 40]); }(1, 2, 3)); + assert.deepEqual(result, [1, 2, 3, 30, 40], 'works on an arguments object'); + + assert.deepEqual(_.union([1, 2, 3], 4), [1, 2, 3], 'restricts the union to arrays only'); + }); + + QUnit.test('difference', function(assert) { + var result = _.difference([1, 2, 3], [2, 30, 40]); + assert.deepEqual(result, [1, 3], 'can find the difference of two arrays'); + + result = _([1, 2, 3]).difference([2, 30, 40]); + assert.deepEqual(result, [1, 3], 'can perform an OO-style difference'); + + result = _.difference([1, 2, 3, 4], [2, 30, 40], [1, 11, 111]); + assert.deepEqual(result, [3, 4], 'can find the difference of three arrays'); + + result = _.difference([8, 9, 3, 1], [3, 8]); + assert.deepEqual(result, [9, 1], 'preserves the order of the first array'); + + result = (function(){ return _.difference(arguments, [2, 30, 40]); }(1, 2, 3)); + assert.deepEqual(result, [1, 3], 'works on an arguments object'); + + result = _.difference([1, 2, 3], 1); + assert.deepEqual(result, [1, 2, 3], 'restrict the difference to arrays only'); + }); + + QUnit.test('zip', function(assert) { + var names = ['moe', 'larry', 'curly'], ages = [30, 40, 50], leaders = [true]; + assert.deepEqual(_.zip(names, ages, leaders), [ + ['moe', 30, true], + ['larry', 40, void 0], + ['curly', 50, void 0] + ], 'zipped together arrays of different lengths'); + + var stooges = _.zip(['moe', 30, 'stooge 1'], ['larry', 40, 'stooge 2'], ['curly', 50, 'stooge 3']); + assert.deepEqual(stooges, [['moe', 'larry', 'curly'], [30, 40, 50], ['stooge 1', 'stooge 2', 'stooge 3']], 'zipped pairs'); + + // In the case of different lengths of the tuples, undefined values + // should be used as placeholder + stooges = _.zip(['moe', 30], ['larry', 40], ['curly', 50, 'extra data']); + assert.deepEqual(stooges, [['moe', 'larry', 'curly'], [30, 40, 50], [void 0, void 0, 'extra data']], 'zipped pairs with empties'); + + var empty = _.zip([]); + assert.deepEqual(empty, [], 'unzipped empty'); + + assert.deepEqual(_.zip(null), [], 'handles null'); + assert.deepEqual(_.zip(), [], '_.zip() returns []'); + }); + + QUnit.test('unzip', function(assert) { + assert.deepEqual(_.unzip(null), [], 'handles null'); + + assert.deepEqual(_.unzip([['a', 'b'], [1, 2]]), [['a', 1], ['b', 2]]); + + // complements zip + var zipped = _.zip(['fred', 'barney'], [30, 40], [true, false]); + assert.deepEqual(_.unzip(zipped), [['fred', 'barney'], [30, 40], [true, false]]); + + zipped = _.zip(['moe', 30], ['larry', 40], ['curly', 50, 'extra data']); + assert.deepEqual(_.unzip(zipped), [['moe', 30, void 0], ['larry', 40, void 0], ['curly', 50, 'extra data']], 'Uses length of largest array'); + }); + + QUnit.test('object', function(assert) { + var result = _.object(['moe', 'larry', 'curly'], [30, 40, 50]); + var shouldBe = {moe: 30, larry: 40, curly: 50}; + assert.deepEqual(result, shouldBe, 'two arrays zipped together into an object'); + + result = _.object([['one', 1], ['two', 2], ['three', 3]]); + shouldBe = {one: 1, two: 2, three: 3}; + assert.deepEqual(result, shouldBe, 'an array of pairs zipped together into an object'); + + var stooges = {moe: 30, larry: 40, curly: 50}; + assert.deepEqual(_.object(_.pairs(stooges)), stooges, 'an object converted to pairs and back to an object'); + + assert.deepEqual(_.object(null), {}, 'handles nulls'); + }); + + QUnit.test('indexOf', function(assert) { + var numbers = [1, 2, 3]; + assert.equal(_.indexOf(numbers, 2), 1, 'can compute indexOf'); + var result = (function(){ return _.indexOf(arguments, 2); }(1, 2, 3)); + assert.equal(result, 1, 'works on an arguments object'); + + _.each([null, void 0, [], false], function(val) { + var msg = 'Handles: ' + (_.isArray(val) ? '[]' : val); + assert.equal(_.indexOf(val, 2), -1, msg); + assert.equal(_.indexOf(val, 2, -1), -1, msg); + assert.equal(_.indexOf(val, 2, -20), -1, msg); + assert.equal(_.indexOf(val, 2, 15), -1, msg); + }); + + var num = 35; + numbers = [10, 20, 30, 40, 50]; + var index = _.indexOf(numbers, num, true); + assert.equal(index, -1, '35 is not in the list'); + + numbers = [10, 20, 30, 40, 50]; num = 40; + index = _.indexOf(numbers, num, true); + assert.equal(index, 3, '40 is in the list'); + + numbers = [1, 40, 40, 40, 40, 40, 40, 40, 50, 60, 70]; num = 40; + assert.equal(_.indexOf(numbers, num, true), 1, '40 is in the list'); + assert.equal(_.indexOf(numbers, 6, true), -1, '6 isnt in the list'); + assert.equal(_.indexOf([1, 2, 5, 4, 6, 7], 5, true), -1, 'sorted indexOf doesn\'t uses binary search'); + assert.ok(_.every(['1', [], {}, null], function() { + return _.indexOf(numbers, num, {}) === 1; + }), 'non-nums as fromIndex make indexOf assume sorted'); + + numbers = [1, 2, 3, 1, 2, 3, 1, 2, 3]; + index = _.indexOf(numbers, 2, 5); + assert.equal(index, 7, 'supports the fromIndex argument'); + + index = _.indexOf([,,, 0], void 0); + assert.equal(index, 0, 'treats sparse arrays as if they were dense'); + + var array = [1, 2, 3, 1, 2, 3]; + assert.strictEqual(_.indexOf(array, 1, -3), 3, 'neg `fromIndex` starts at the right index'); + assert.strictEqual(_.indexOf(array, 1, -2), -1, 'neg `fromIndex` starts at the right index'); + assert.strictEqual(_.indexOf(array, 2, -3), 4); + _.each([-6, -8, -Infinity], function(fromIndex) { + assert.strictEqual(_.indexOf(array, 1, fromIndex), 0); + }); + assert.strictEqual(_.indexOf([1, 2, 3], 1, true), 0); + + index = _.indexOf([], void 0, true); + assert.equal(index, -1, 'empty array with truthy `isSorted` returns -1'); + }); + + QUnit.test('indexOf with NaN', function(assert) { + assert.strictEqual(_.indexOf([1, 2, NaN, NaN], NaN), 2, 'Expected [1, 2, NaN] to contain NaN'); + assert.strictEqual(_.indexOf([1, 2, Infinity], NaN), -1, 'Expected [1, 2, NaN] to contain NaN'); + + assert.strictEqual(_.indexOf([1, 2, NaN, NaN], NaN, 1), 2, 'startIndex does not affect result'); + assert.strictEqual(_.indexOf([1, 2, NaN, NaN], NaN, -2), 2, 'startIndex does not affect result'); + + (function() { + assert.strictEqual(_.indexOf(arguments, NaN), 2, 'Expected arguments [1, 2, NaN] to contain NaN'); + }(1, 2, NaN, NaN)); + }); + + QUnit.test('indexOf with +- 0', function(assert) { + _.each([-0, +0], function(val) { + assert.strictEqual(_.indexOf([1, 2, val, val], val), 2); + assert.strictEqual(_.indexOf([1, 2, val, val], -val), 2); + }); + }); + + QUnit.test('lastIndexOf', function(assert) { + var numbers = [1, 0, 1]; + var falsey = [void 0, '', 0, false, NaN, null, void 0]; + assert.equal(_.lastIndexOf(numbers, 1), 2); + + numbers = [1, 0, 1, 0, 0, 1, 0, 0, 0]; + numbers.lastIndexOf = null; + assert.equal(_.lastIndexOf(numbers, 1), 5, 'can compute lastIndexOf, even without the native function'); + assert.equal(_.lastIndexOf(numbers, 0), 8, 'lastIndexOf the other element'); + var result = (function(){ return _.lastIndexOf(arguments, 1); }(1, 0, 1, 0, 0, 1, 0, 0, 0)); + assert.equal(result, 5, 'works on an arguments object'); + + _.each([null, void 0, [], false], function(val) { + var msg = 'Handles: ' + (_.isArray(val) ? '[]' : val); + assert.equal(_.lastIndexOf(val, 2), -1, msg); + assert.equal(_.lastIndexOf(val, 2, -1), -1, msg); + assert.equal(_.lastIndexOf(val, 2, -20), -1, msg); + assert.equal(_.lastIndexOf(val, 2, 15), -1, msg); + }); + + numbers = [1, 2, 3, 1, 2, 3, 1, 2, 3]; + var index = _.lastIndexOf(numbers, 2, 2); + assert.equal(index, 1, 'supports the fromIndex argument'); + + var array = [1, 2, 3, 1, 2, 3]; + + assert.strictEqual(_.lastIndexOf(array, 1, 0), 0, 'starts at the correct from idx'); + assert.strictEqual(_.lastIndexOf(array, 3), 5, 'should return the index of the last matched value'); + assert.strictEqual(_.lastIndexOf(array, 4), -1, 'should return `-1` for an unmatched value'); + + assert.strictEqual(_.lastIndexOf(array, 1, 2), 0, 'should work with a positive `fromIndex`'); + + _.each([6, 8, Math.pow(2, 32), Infinity], function(fromIndex) { + assert.strictEqual(_.lastIndexOf(array, void 0, fromIndex), -1); + assert.strictEqual(_.lastIndexOf(array, 1, fromIndex), 3); + assert.strictEqual(_.lastIndexOf(array, '', fromIndex), -1); + }); + + var expected = _.map(falsey, function(value) { + return typeof value == 'number' ? -1 : 5; + }); + + var actual = _.map(falsey, function(fromIndex) { + return _.lastIndexOf(array, 3, fromIndex); + }); + + assert.deepEqual(actual, expected, 'should treat falsey `fromIndex` values, except `0` and `NaN`, as `array.length`'); + assert.strictEqual(_.lastIndexOf(array, 3, '1'), 5, 'should treat non-number `fromIndex` values as `array.length`'); + assert.strictEqual(_.lastIndexOf(array, 3, true), 5, 'should treat non-number `fromIndex` values as `array.length`'); + + assert.strictEqual(_.lastIndexOf(array, 2, -3), 1, 'should work with a negative `fromIndex`'); + assert.strictEqual(_.lastIndexOf(array, 1, -3), 3, 'neg `fromIndex` starts at the right index'); + + assert.deepEqual(_.map([-6, -8, -Infinity], function(fromIndex) { + return _.lastIndexOf(array, 1, fromIndex); + }), [0, -1, -1]); + }); + + QUnit.test('lastIndexOf with NaN', function(assert) { + assert.strictEqual(_.lastIndexOf([1, 2, NaN, NaN], NaN), 3, 'Expected [1, 2, NaN] to contain NaN'); + assert.strictEqual(_.lastIndexOf([1, 2, Infinity], NaN), -1, 'Expected [1, 2, NaN] to contain NaN'); + + assert.strictEqual(_.lastIndexOf([1, 2, NaN, NaN], NaN, 2), 2, 'fromIndex does not affect result'); + assert.strictEqual(_.lastIndexOf([1, 2, NaN, NaN], NaN, -2), 2, 'fromIndex does not affect result'); + + (function() { + assert.strictEqual(_.lastIndexOf(arguments, NaN), 3, 'Expected arguments [1, 2, NaN] to contain NaN'); + }(1, 2, NaN, NaN)); + }); + + QUnit.test('lastIndexOf with +- 0', function(assert) { + _.each([-0, +0], function(val) { + assert.strictEqual(_.lastIndexOf([1, 2, val, val], val), 3); + assert.strictEqual(_.lastIndexOf([1, 2, val, val], -val), 3); + assert.strictEqual(_.lastIndexOf([-1, 1, 2], -val), -1); + }); + }); + + QUnit.test('findIndex', function(assert) { + var objects = [ + {a: 0, b: 0}, + {a: 1, b: 1}, + {a: 2, b: 2}, + {a: 0, b: 0} + ]; + + assert.equal(_.findIndex(objects, function(obj) { + return obj.a === 0; + }), 0); + + assert.equal(_.findIndex(objects, function(obj) { + return obj.b * obj.a === 4; + }), 2); + + assert.equal(_.findIndex(objects, 'a'), 1, 'Uses lookupIterator'); + + assert.equal(_.findIndex(objects, function(obj) { + return obj.b * obj.a === 5; + }), -1); + + assert.equal(_.findIndex(null, _.noop), -1); + assert.strictEqual(_.findIndex(objects, function(a) { + return a.foo === null; + }), -1); + _.findIndex([{a: 1}], function(a, key, obj) { + assert.equal(key, 0); + assert.deepEqual(obj, [{a: 1}]); + assert.strictEqual(this, objects, 'called with context'); + }, objects); + + var sparse = []; + sparse[20] = {a: 2, b: 2}; + assert.equal(_.findIndex(sparse, function(obj) { + return obj && obj.b * obj.a === 4; + }), 20, 'Works with sparse arrays'); + + var array = [1, 2, 3, 4]; + array.match = 55; + assert.strictEqual(_.findIndex(array, function(x) { return x === 55; }), -1, 'doesn\'t match array-likes keys'); + }); + + QUnit.test('findLastIndex', function(assert) { + var objects = [ + {a: 0, b: 0}, + {a: 1, b: 1}, + {a: 2, b: 2}, + {a: 0, b: 0} + ]; + + assert.equal(_.findLastIndex(objects, function(obj) { + return obj.a === 0; + }), 3); + + assert.equal(_.findLastIndex(objects, function(obj) { + return obj.b * obj.a === 4; + }), 2); + + assert.equal(_.findLastIndex(objects, 'a'), 2, 'Uses lookupIterator'); + + assert.equal(_.findLastIndex(objects, function(obj) { + return obj.b * obj.a === 5; + }), -1); + + assert.equal(_.findLastIndex(null, _.noop), -1); + assert.strictEqual(_.findLastIndex(objects, function(a) { + return a.foo === null; + }), -1); + _.findLastIndex([{a: 1}], function(a, key, obj) { + assert.equal(key, 0); + assert.deepEqual(obj, [{a: 1}]); + assert.strictEqual(this, objects, 'called with context'); + }, objects); + + var sparse = []; + sparse[20] = {a: 2, b: 2}; + assert.equal(_.findLastIndex(sparse, function(obj) { + return obj && obj.b * obj.a === 4; + }), 20, 'Works with sparse arrays'); + + var array = [1, 2, 3, 4]; + array.match = 55; + assert.strictEqual(_.findLastIndex(array, function(x) { return x === 55; }), -1, 'doesn\'t match array-likes keys'); + }); + + QUnit.test('range', function(assert) { + assert.deepEqual(_.range(0), [], 'range with 0 as a first argument generates an empty array'); + assert.deepEqual(_.range(4), [0, 1, 2, 3], 'range with a single positive argument generates an array of elements 0,1,2,...,n-1'); + assert.deepEqual(_.range(5, 8), [5, 6, 7], 'range with two arguments a & b, a<b generates an array of elements a,a+1,a+2,...,b-2,b-1'); + assert.deepEqual(_.range(3, 10, 3), [3, 6, 9], 'range with three arguments a & b & c, c < b-a, a < b generates an array of elements a,a+c,a+2c,...,b - (multiplier of a) < c'); + assert.deepEqual(_.range(3, 10, 15), [3], 'range with three arguments a & b & c, c > b-a, a < b generates an array with a single element, equal to a'); + assert.deepEqual(_.range(12, 7, -2), [12, 10, 8], 'range with three arguments a & b & c, a > b, c < 0 generates an array of elements a,a-c,a-2c and ends with the number not less than b'); + assert.deepEqual(_.range(0, -10, -1), [0, -1, -2, -3, -4, -5, -6, -7, -8, -9], 'final example in the Python docs'); + assert.strictEqual(1 / _.range(-0, 1)[0], -Infinity, 'should preserve -0'); + assert.deepEqual(_.range(8, 5), [8, 7, 6], 'negative range generates descending array'); + assert.deepEqual(_.range(-3), [0, -1, -2], 'negative range generates descending array'); + }); + + QUnit.test('chunk', function(assert) { + assert.deepEqual(_.chunk([], 2), [], 'chunk for empty array returns an empty array'); + + assert.deepEqual(_.chunk([1, 2, 3], 0), [], 'chunk into parts of 0 elements returns empty array'); + assert.deepEqual(_.chunk([1, 2, 3], -1), [], 'chunk into parts of negative amount of elements returns an empty array'); + assert.deepEqual(_.chunk([1, 2, 3]), [], 'defaults to empty array (chunk size 0)'); + + assert.deepEqual(_.chunk([1, 2, 3], 1), [[1], [2], [3]], 'chunk into parts of 1 elements returns original array'); + + assert.deepEqual(_.chunk([1, 2, 3], 3), [[1, 2, 3]], 'chunk into parts of current array length elements returns the original array'); + assert.deepEqual(_.chunk([1, 2, 3], 5), [[1, 2, 3]], 'chunk into parts of more then current array length elements returns the original array'); + + assert.deepEqual(_.chunk([10, 20, 30, 40, 50, 60, 70], 2), [[10, 20], [30, 40], [50, 60], [70]], 'chunk into parts of less then current array length elements'); + assert.deepEqual(_.chunk([10, 20, 30, 40, 50, 60, 70], 3), [[10, 20, 30], [40, 50, 60], [70]], 'chunk into parts of less then current array length elements'); + }); +}()); diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/underscore/test/chaining.js b/ecomp-portal-FE/client/bower_components/lodash/vendor/underscore/test/chaining.js new file mode 100644 index 00000000..6ad21dcf --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/underscore/test/chaining.js @@ -0,0 +1,99 @@ +(function() { + var _ = typeof require == 'function' ? require('..') : window._; + + QUnit.module('Chaining'); + + QUnit.test('map/flatten/reduce', function(assert) { + var lyrics = [ + 'I\'m a lumberjack and I\'m okay', + 'I sleep all night and I work all day', + 'He\'s a lumberjack and he\'s okay', + 'He sleeps all night and he works all day' + ]; + var counts = _(lyrics).chain() + .map(function(line) { return line.split(''); }) + .flatten() + .reduce(function(hash, l) { + hash[l] = hash[l] || 0; + hash[l]++; + return hash; + }, {}) + .value(); + assert.equal(counts.a, 16, 'counted all the letters in the song'); + assert.equal(counts.e, 10, 'counted all the letters in the song'); + }); + + QUnit.test('select/reject/sortBy', function(assert) { + var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + numbers = _(numbers).chain().select(function(n) { + return n % 2 === 0; + }).reject(function(n) { + return n % 4 === 0; + }).sortBy(function(n) { + return -n; + }).value(); + assert.deepEqual(numbers, [10, 6, 2], 'filtered and reversed the numbers'); + }); + + QUnit.test('select/reject/sortBy in functional style', function(assert) { + var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + numbers = _.chain(numbers).select(function(n) { + return n % 2 === 0; + }).reject(function(n) { + return n % 4 === 0; + }).sortBy(function(n) { + return -n; + }).value(); + assert.deepEqual(numbers, [10, 6, 2], 'filtered and reversed the numbers'); + }); + + QUnit.test('reverse/concat/unshift/pop/map', function(assert) { + var numbers = [1, 2, 3, 4, 5]; + numbers = _(numbers).chain() + .reverse() + .concat([5, 5, 5]) + .unshift(17) + .pop() + .map(function(n){ return n * 2; }) + .value(); + assert.deepEqual(numbers, [34, 10, 8, 6, 4, 2, 10, 10], 'can chain together array functions.'); + }); + + QUnit.test('splice', function(assert) { + var instance = _([1, 2, 3, 4, 5]).chain(); + assert.deepEqual(instance.splice(1, 3).value(), [1, 5]); + assert.deepEqual(instance.splice(1, 0).value(), [1, 5]); + assert.deepEqual(instance.splice(1, 1).value(), [1]); + assert.deepEqual(instance.splice(0, 1).value(), [], '#397 Can create empty array'); + }); + + QUnit.test('shift', function(assert) { + var instance = _([1, 2, 3]).chain(); + assert.deepEqual(instance.shift().value(), [2, 3]); + assert.deepEqual(instance.shift().value(), [3]); + assert.deepEqual(instance.shift().value(), [], '#397 Can create empty array'); + }); + + QUnit.test('pop', function(assert) { + var instance = _([1, 2, 3]).chain(); + assert.deepEqual(instance.pop().value(), [1, 2]); + assert.deepEqual(instance.pop().value(), [1]); + assert.deepEqual(instance.pop().value(), [], '#397 Can create empty array'); + }); + + QUnit.test('chaining works in small stages', function(assert) { + var o = _([1, 2, 3, 4]).chain(); + assert.deepEqual(o.filter(function(i) { return i < 3; }).value(), [1, 2]); + assert.deepEqual(o.filter(function(i) { return i > 2; }).value(), [3, 4]); + }); + + QUnit.test('#1562: Engine proxies for chained functions', function(assert) { + var wrapped = _(512); + assert.strictEqual(wrapped.toJSON(), 512); + assert.strictEqual(wrapped.valueOf(), 512); + assert.strictEqual(+wrapped, 512); + assert.strictEqual(wrapped.toString(), '512'); + assert.strictEqual('' + wrapped, '512'); + }); + +}()); diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/underscore/test/collections.js b/ecomp-portal-FE/client/bower_components/lodash/vendor/underscore/test/collections.js new file mode 100644 index 00000000..182f7a21 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/underscore/test/collections.js @@ -0,0 +1,896 @@ +(function() { + var _ = typeof require == 'function' ? require('..') : window._; + + QUnit.module('Collections'); + + QUnit.test('each', function(assert) { + _.each([1, 2, 3], function(num, i) { + assert.equal(num, i + 1, 'each iterators provide value and iteration count'); + }); + + var answers = []; + _.each([1, 2, 3], function(num){ answers.push(num * this.multiplier); }, {multiplier: 5}); + assert.deepEqual(answers, [5, 10, 15], 'context object property accessed'); + + answers = []; + _.each([1, 2, 3], function(num){ answers.push(num); }); + assert.deepEqual(answers, [1, 2, 3], 'can iterate a simple array'); + + answers = []; + var obj = {one: 1, two: 2, three: 3}; + obj.constructor.prototype.four = 4; + _.each(obj, function(value, key){ answers.push(key); }); + assert.deepEqual(answers, ['one', 'two', 'three'], 'iterating over objects works, and ignores the object prototype.'); + delete obj.constructor.prototype.four; + + // ensure the each function is JITed + _(1000).times(function() { _.each([], function(){}); }); + var count = 0; + obj = {1: 'foo', 2: 'bar', 3: 'baz'}; + _.each(obj, function(){ count++; }); + assert.equal(count, 3, 'the fun should be called only 3 times'); + + var answer = null; + _.each([1, 2, 3], function(num, index, arr){ if (_.include(arr, num)) answer = true; }); + assert.ok(answer, 'can reference the original collection from inside the iterator'); + + answers = 0; + _.each(null, function(){ ++answers; }); + assert.equal(answers, 0, 'handles a null properly'); + + _.each(false, function(){}); + + var a = [1, 2, 3]; + assert.strictEqual(_.each(a, function(){}), a); + assert.strictEqual(_.each(null, function(){}), null); + }); + + QUnit.test('forEach', function(assert) { + assert.strictEqual(_.forEach, _.each, 'is an alias for each'); + }); + + QUnit.test('lookupIterator with contexts', function(assert) { + _.each([true, false, 'yes', '', 0, 1, {}], function(context) { + _.each([1], function() { + assert.equal(this, context); + }, context); + }); + }); + + QUnit.test('Iterating objects with sketchy length properties', function(assert) { + var functions = [ + 'each', 'map', 'filter', 'find', + 'some', 'every', 'max', 'min', + 'groupBy', 'countBy', 'partition', 'indexBy' + ]; + var reducers = ['reduce', 'reduceRight']; + + var tricks = [ + {length: '5'}, + {length: {valueOf: _.constant(5)}}, + {length: Math.pow(2, 53) + 1}, + {length: Math.pow(2, 53)}, + {length: null}, + {length: -2}, + {length: new Number(15)} + ]; + + assert.expect(tricks.length * (functions.length + reducers.length + 4)); + + _.each(tricks, function(trick) { + var length = trick.length; + assert.strictEqual(_.size(trick), 1, 'size on obj with length: ' + length); + assert.deepEqual(_.toArray(trick), [length], 'toArray on obj with length: ' + length); + assert.deepEqual(_.shuffle(trick), [length], 'shuffle on obj with length: ' + length); + assert.deepEqual(_.sample(trick), length, 'sample on obj with length: ' + length); + + + _.each(functions, function(method) { + _[method](trick, function(val, key) { + assert.strictEqual(key, 'length', method + ': ran with length = ' + val); + }); + }); + + _.each(reducers, function(method) { + assert.strictEqual(_[method](trick), trick.length, method); + }); + }); + }); + + QUnit.test('Resistant to collection length and properties changing while iterating', function(assert) { + + var collection = [ + 'each', 'map', 'filter', 'find', + 'some', 'every', 'max', 'min', 'reject', + 'groupBy', 'countBy', 'partition', 'indexBy', + 'reduce', 'reduceRight' + ]; + var array = [ + 'findIndex', 'findLastIndex' + ]; + var object = [ + 'mapObject', 'findKey', 'pick', 'omit' + ]; + + _.each(collection.concat(array), function(method) { + var sparseArray = [1, 2, 3]; + sparseArray.length = 100; + var answers = 0; + _[method](sparseArray, function(){ + ++answers; + return method === 'every' ? true : null; + }, {}); + assert.equal(answers, 100, method + ' enumerates [0, length)'); + + var growingCollection = [1, 2, 3], count = 0; + _[method](growingCollection, function() { + if (count < 10) growingCollection.push(count++); + return method === 'every' ? true : null; + }, {}); + assert.equal(count, 3, method + ' is resistant to length changes'); + }); + + _.each(collection.concat(object), function(method) { + var changingObject = {0: 0, 1: 1}, count = 0; + _[method](changingObject, function(val) { + if (count < 10) changingObject[++count] = val + 1; + return method === 'every' ? true : null; + }, {}); + + assert.equal(count, 2, method + ' is resistant to property changes'); + }); + }); + + QUnit.test('map', function(assert) { + var doubled = _.map([1, 2, 3], function(num){ return num * 2; }); + assert.deepEqual(doubled, [2, 4, 6], 'doubled numbers'); + + var tripled = _.map([1, 2, 3], function(num){ return num * this.multiplier; }, {multiplier: 3}); + assert.deepEqual(tripled, [3, 6, 9], 'tripled numbers with context'); + + doubled = _([1, 2, 3]).map(function(num){ return num * 2; }); + assert.deepEqual(doubled, [2, 4, 6], 'OO-style doubled numbers'); + + var ids = _.map({length: 2, 0: {id: '1'}, 1: {id: '2'}}, function(n){ + return n.id; + }); + assert.deepEqual(ids, ['1', '2'], 'Can use collection methods on Array-likes.'); + + assert.deepEqual(_.map(null, _.noop), [], 'handles a null properly'); + + assert.deepEqual(_.map([1], function() { + return this.length; + }, [5]), [1], 'called with context'); + + // Passing a property name like _.pluck. + var people = [{name: 'moe', age: 30}, {name: 'curly', age: 50}]; + assert.deepEqual(_.map(people, 'name'), ['moe', 'curly'], 'predicate string map to object properties'); + }); + + QUnit.test('collect', function(assert) { + assert.strictEqual(_.collect, _.map, 'is an alias for map'); + }); + + QUnit.test('reduce', function(assert) { + var sum = _.reduce([1, 2, 3], function(memo, num){ return memo + num; }, 0); + assert.equal(sum, 6, 'can sum up an array'); + + var context = {multiplier: 3}; + sum = _.reduce([1, 2, 3], function(memo, num){ return memo + num * this.multiplier; }, 0, context); + assert.equal(sum, 18, 'can reduce with a context object'); + + sum = _([1, 2, 3]).reduce(function(memo, num){ return memo + num; }, 0); + assert.equal(sum, 6, 'OO-style reduce'); + + sum = _.reduce([1, 2, 3], function(memo, num){ return memo + num; }); + assert.equal(sum, 6, 'default initial value'); + + var prod = _.reduce([1, 2, 3, 4], function(memo, num){ return memo * num; }); + assert.equal(prod, 24, 'can reduce via multiplication'); + + assert.ok(_.reduce(null, _.noop, 138) === 138, 'handles a null (with initial value) properly'); + assert.equal(_.reduce([], _.noop, void 0), void 0, 'undefined can be passed as a special case'); + assert.equal(_.reduce([_], _.noop), _, 'collection of length one with no initial value returns the first item'); + assert.equal(_.reduce([], _.noop), void 0, 'returns undefined when collection is empty and no initial value'); + }); + + QUnit.test('foldl', function(assert) { + assert.strictEqual(_.foldl, _.reduce, 'is an alias for reduce'); + }); + + QUnit.test('inject', function(assert) { + assert.strictEqual(_.inject, _.reduce, 'is an alias for reduce'); + }); + + QUnit.test('reduceRight', function(assert) { + var list = _.reduceRight(['foo', 'bar', 'baz'], function(memo, str){ return memo + str; }, ''); + assert.equal(list, 'bazbarfoo', 'can perform right folds'); + + list = _.reduceRight(['foo', 'bar', 'baz'], function(memo, str){ return memo + str; }); + assert.equal(list, 'bazbarfoo', 'default initial value'); + + var sum = _.reduceRight({a: 1, b: 2, c: 3}, function(memo, num){ return memo + num; }); + assert.equal(sum, 6, 'default initial value on object'); + + assert.ok(_.reduceRight(null, _.noop, 138) === 138, 'handles a null (with initial value) properly'); + assert.equal(_.reduceRight([_], _.noop), _, 'collection of length one with no initial value returns the first item'); + + assert.equal(_.reduceRight([], _.noop, void 0), void 0, 'undefined can be passed as a special case'); + assert.equal(_.reduceRight([], _.noop), void 0, 'returns undefined when collection is empty and no initial value'); + + // Assert that the correct arguments are being passed. + + var args, + init = {}, + object = {a: 1, b: 2}, + lastKey = _.keys(object).pop(); + + var expected = lastKey === 'a' + ? [init, 1, 'a', object] + : [init, 2, 'b', object]; + + _.reduceRight(object, function() { + if (!args) args = _.toArray(arguments); + }, init); + + assert.deepEqual(args, expected); + + // And again, with numeric keys. + + object = {2: 'a', 1: 'b'}; + lastKey = _.keys(object).pop(); + args = null; + + expected = lastKey === '2' + ? [init, 'a', '2', object] + : [init, 'b', '1', object]; + + _.reduceRight(object, function() { + if (!args) args = _.toArray(arguments); + }, init); + + assert.deepEqual(args, expected); + }); + + QUnit.test('foldr', function(assert) { + assert.strictEqual(_.foldr, _.reduceRight, 'is an alias for reduceRight'); + }); + + QUnit.test('find', function(assert) { + var array = [1, 2, 3, 4]; + assert.strictEqual(_.find(array, function(n) { return n > 2; }), 3, 'should return first found `value`'); + assert.strictEqual(_.find(array, function() { return false; }), void 0, 'should return `undefined` if `value` is not found'); + + array.dontmatch = 55; + assert.strictEqual(_.find(array, function(x) { return x === 55; }), void 0, 'iterates array-likes correctly'); + + // Matching an object like _.findWhere. + var list = [{a: 1, b: 2}, {a: 2, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}, {a: 2, b: 4}]; + assert.deepEqual(_.find(list, {a: 1}), {a: 1, b: 2}, 'can be used as findWhere'); + assert.deepEqual(_.find(list, {b: 4}), {a: 1, b: 4}); + assert.ok(!_.find(list, {c: 1}), 'undefined when not found'); + assert.ok(!_.find([], {c: 1}), 'undefined when searching empty list'); + + var result = _.find([1, 2, 3], function(num){ return num * 2 === 4; }); + assert.equal(result, 2, 'found the first "2" and broke the loop'); + + var obj = { + a: {x: 1, z: 3}, + b: {x: 2, z: 2}, + c: {x: 3, z: 4}, + d: {x: 4, z: 1} + }; + + assert.deepEqual(_.find(obj, {x: 2}), {x: 2, z: 2}, 'works on objects'); + assert.deepEqual(_.find(obj, {x: 2, z: 1}), void 0); + assert.deepEqual(_.find(obj, function(x) { + return x.x === 4; + }), {x: 4, z: 1}); + + _.findIndex([{a: 1}], function(a, key, o) { + assert.equal(key, 0); + assert.deepEqual(o, [{a: 1}]); + assert.strictEqual(this, _, 'called with context'); + }, _); + }); + + QUnit.test('detect', function(assert) { + assert.strictEqual(_.detect, _.find, 'is an alias for find'); + }); + + QUnit.test('filter', function(assert) { + var evenArray = [1, 2, 3, 4, 5, 6]; + var evenObject = {one: 1, two: 2, three: 3}; + var isEven = function(num){ return num % 2 === 0; }; + + assert.deepEqual(_.filter(evenArray, isEven), [2, 4, 6]); + assert.deepEqual(_.filter(evenObject, isEven), [2], 'can filter objects'); + assert.deepEqual(_.filter([{}, evenObject, []], 'two'), [evenObject], 'predicate string map to object properties'); + + _.filter([1], function() { + assert.equal(this, evenObject, 'given context'); + }, evenObject); + + // Can be used like _.where. + var list = [{a: 1, b: 2}, {a: 2, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}]; + assert.deepEqual(_.filter(list, {a: 1}), [{a: 1, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}]); + assert.deepEqual(_.filter(list, {b: 2}), [{a: 1, b: 2}, {a: 2, b: 2}]); + assert.deepEqual(_.filter(list, {}), list, 'Empty object accepts all items'); + assert.deepEqual(_(list).filter({}), list, 'OO-filter'); + }); + + QUnit.test('select', function(assert) { + assert.strictEqual(_.select, _.filter, 'is an alias for filter'); + }); + + QUnit.test('reject', function(assert) { + var odds = _.reject([1, 2, 3, 4, 5, 6], function(num){ return num % 2 === 0; }); + assert.deepEqual(odds, [1, 3, 5], 'rejected each even number'); + + var context = 'obj'; + + var evens = _.reject([1, 2, 3, 4, 5, 6], function(num){ + assert.equal(context, 'obj'); + return num % 2 !== 0; + }, context); + assert.deepEqual(evens, [2, 4, 6], 'rejected each odd number'); + + assert.deepEqual(_.reject([odds, {one: 1, two: 2, three: 3}], 'two'), [odds], 'predicate string map to object properties'); + + // Can be used like _.where. + var list = [{a: 1, b: 2}, {a: 2, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}]; + assert.deepEqual(_.reject(list, {a: 1}), [{a: 2, b: 2}]); + assert.deepEqual(_.reject(list, {b: 2}), [{a: 1, b: 3}, {a: 1, b: 4}]); + assert.deepEqual(_.reject(list, {}), [], 'Returns empty list given empty object'); + assert.deepEqual(_.reject(list, []), [], 'Returns empty list given empty array'); + }); + + QUnit.test('every', function(assert) { + assert.ok(_.every([], _.identity), 'the empty set'); + assert.ok(_.every([true, true, true], _.identity), 'every true values'); + assert.ok(!_.every([true, false, true], _.identity), 'one false value'); + assert.ok(_.every([0, 10, 28], function(num){ return num % 2 === 0; }), 'even numbers'); + assert.ok(!_.every([0, 11, 28], function(num){ return num % 2 === 0; }), 'an odd number'); + assert.ok(_.every([1], _.identity) === true, 'cast to boolean - true'); + assert.ok(_.every([0], _.identity) === false, 'cast to boolean - false'); + assert.ok(!_.every([void 0, void 0, void 0], _.identity), 'works with arrays of undefined'); + + var list = [{a: 1, b: 2}, {a: 2, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}]; + assert.ok(!_.every(list, {a: 1, b: 2}), 'Can be called with object'); + assert.ok(_.every(list, 'a'), 'String mapped to object property'); + + list = [{a: 1, b: 2}, {a: 2, b: 2, c: true}]; + assert.ok(_.every(list, {b: 2}), 'Can be called with object'); + assert.ok(!_.every(list, 'c'), 'String mapped to object property'); + + assert.ok(_.every({a: 1, b: 2, c: 3, d: 4}, _.isNumber), 'takes objects'); + assert.ok(!_.every({a: 1, b: 2, c: 3, d: 4}, _.isObject), 'takes objects'); + assert.ok(_.every(['a', 'b', 'c', 'd'], _.hasOwnProperty, {a: 1, b: 2, c: 3, d: 4}), 'context works'); + assert.ok(!_.every(['a', 'b', 'c', 'd', 'f'], _.hasOwnProperty, {a: 1, b: 2, c: 3, d: 4}), 'context works'); + }); + + QUnit.test('all', function(assert) { + assert.strictEqual(_.all, _.every, 'is an alias for every'); + }); + + QUnit.test('some', function(assert) { + assert.ok(!_.some([]), 'the empty set'); + assert.ok(!_.some([false, false, false]), 'all false values'); + assert.ok(_.some([false, false, true]), 'one true value'); + assert.ok(_.some([null, 0, 'yes', false]), 'a string'); + assert.ok(!_.some([null, 0, '', false]), 'falsy values'); + assert.ok(!_.some([1, 11, 29], function(num){ return num % 2 === 0; }), 'all odd numbers'); + assert.ok(_.some([1, 10, 29], function(num){ return num % 2 === 0; }), 'an even number'); + assert.ok(_.some([1], _.identity) === true, 'cast to boolean - true'); + assert.ok(_.some([0], _.identity) === false, 'cast to boolean - false'); + assert.ok(_.some([false, false, true])); + + var list = [{a: 1, b: 2}, {a: 2, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}]; + assert.ok(!_.some(list, {a: 5, b: 2}), 'Can be called with object'); + assert.ok(_.some(list, 'a'), 'String mapped to object property'); + + list = [{a: 1, b: 2}, {a: 2, b: 2, c: true}]; + assert.ok(_.some(list, {b: 2}), 'Can be called with object'); + assert.ok(!_.some(list, 'd'), 'String mapped to object property'); + + assert.ok(_.some({a: '1', b: '2', c: '3', d: '4', e: 6}, _.isNumber), 'takes objects'); + assert.ok(!_.some({a: 1, b: 2, c: 3, d: 4}, _.isObject), 'takes objects'); + assert.ok(_.some(['a', 'b', 'c', 'd'], _.hasOwnProperty, {a: 1, b: 2, c: 3, d: 4}), 'context works'); + assert.ok(!_.some(['x', 'y', 'z'], _.hasOwnProperty, {a: 1, b: 2, c: 3, d: 4}), 'context works'); + }); + + QUnit.test('any', function(assert) { + assert.strictEqual(_.any, _.some, 'is an alias for some'); + }); + + QUnit.test('includes', function(assert) { + _.each([null, void 0, 0, 1, NaN, {}, []], function(val) { + assert.strictEqual(_.includes(val, 'hasOwnProperty'), false); + }); + assert.strictEqual(_.includes([1, 2, 3], 2), true, 'two is in the array'); + assert.ok(!_.includes([1, 3, 9], 2), 'two is not in the array'); + + assert.strictEqual(_.includes([5, 4, 3, 2, 1], 5, true), true, 'doesn\'t delegate to binary search'); + + assert.ok(_.includes({moe: 1, larry: 3, curly: 9}, 3) === true, '_.includes on objects checks their values'); + assert.ok(_([1, 2, 3]).includes(2), 'OO-style includes'); + + var numbers = [1, 2, 3, 1, 2, 3, 1, 2, 3]; + assert.strictEqual(_.includes(numbers, 1, 1), true, 'takes a fromIndex'); + assert.strictEqual(_.includes(numbers, 1, -1), false, 'takes a fromIndex'); + assert.strictEqual(_.includes(numbers, 1, -2), false, 'takes a fromIndex'); + assert.strictEqual(_.includes(numbers, 1, -3), true, 'takes a fromIndex'); + assert.strictEqual(_.includes(numbers, 1, 6), true, 'takes a fromIndex'); + assert.strictEqual(_.includes(numbers, 1, 7), false, 'takes a fromIndex'); + + assert.ok(_.every([1, 2, 3], _.partial(_.includes, numbers)), 'fromIndex is guarded'); + }); + + QUnit.test('include', function(assert) { + assert.strictEqual(_.include, _.includes, 'is an alias for includes'); + }); + + QUnit.test('contains', function(assert) { + assert.strictEqual(_.contains, _.includes, 'is an alias for includes'); + + }); + + QUnit.test('includes with NaN', function(assert) { + assert.strictEqual(_.includes([1, 2, NaN, NaN], NaN), true, 'Expected [1, 2, NaN] to contain NaN'); + assert.strictEqual(_.includes([1, 2, Infinity], NaN), false, 'Expected [1, 2, NaN] to contain NaN'); + }); + + QUnit.test('includes with +- 0', function(assert) { + _.each([-0, +0], function(val) { + assert.strictEqual(_.includes([1, 2, val, val], val), true); + assert.strictEqual(_.includes([1, 2, val, val], -val), true); + assert.strictEqual(_.includes([-1, 1, 2], -val), false); + }); + }); + + + QUnit.test('invoke', function(assert) { + assert.expect(5); + var list = [[5, 1, 7], [3, 2, 1]]; + var result = _.invoke(list, 'sort'); + assert.deepEqual(result[0], [1, 5, 7], 'first array sorted'); + assert.deepEqual(result[1], [1, 2, 3], 'second array sorted'); + + _.invoke([{ + method: function() { + assert.deepEqual(_.toArray(arguments), [1, 2, 3], 'called with arguments'); + } + }], 'method', 1, 2, 3); + + assert.deepEqual(_.invoke([{a: null}, {}, {a: _.constant(1)}], 'a'), [null, void 0, 1], 'handles null & undefined'); + + assert.raises(function() { + _.invoke([{a: 1}], 'a'); + }, TypeError, 'throws for non-functions'); + }); + + QUnit.test('invoke w/ function reference', function(assert) { + var list = [[5, 1, 7], [3, 2, 1]]; + var result = _.invoke(list, Array.prototype.sort); + assert.deepEqual(result[0], [1, 5, 7], 'first array sorted'); + assert.deepEqual(result[1], [1, 2, 3], 'second array sorted'); + + assert.deepEqual(_.invoke([1, 2, 3], function(a) { + return a + this; + }, 5), [6, 7, 8], 'receives params from invoke'); + }); + + // Relevant when using ClojureScript + QUnit.test('invoke when strings have a call method', function(assert) { + String.prototype.call = function() { + return 42; + }; + var list = [[5, 1, 7], [3, 2, 1]]; + var s = 'foo'; + assert.equal(s.call(), 42, 'call function exists'); + var result = _.invoke(list, 'sort'); + assert.deepEqual(result[0], [1, 5, 7], 'first array sorted'); + assert.deepEqual(result[1], [1, 2, 3], 'second array sorted'); + delete String.prototype.call; + assert.equal(s.call, void 0, 'call function removed'); + }); + + QUnit.test('pluck', function(assert) { + var people = [{name: 'moe', age: 30}, {name: 'curly', age: 50}]; + assert.deepEqual(_.pluck(people, 'name'), ['moe', 'curly'], 'pulls names out of objects'); + assert.deepEqual(_.pluck(people, 'address'), [void 0, void 0], 'missing properties are returned as undefined'); + //compat: most flexible handling of edge cases + assert.deepEqual(_.pluck([{'[object Object]': 1}], {}), [1]); + }); + + QUnit.test('where', function(assert) { + var list = [{a: 1, b: 2}, {a: 2, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}]; + var result = _.where(list, {a: 1}); + assert.equal(result.length, 3); + assert.equal(result[result.length - 1].b, 4); + result = _.where(list, {b: 2}); + assert.equal(result.length, 2); + assert.equal(result[0].a, 1); + result = _.where(list, {}); + assert.equal(result.length, list.length); + + function test() {} + test.map = _.map; + assert.deepEqual(_.where([_, {a: 1, b: 2}, _], test), [_, _], 'checks properties given function'); + }); + + QUnit.test('findWhere', function(assert) { + var list = [{a: 1, b: 2}, {a: 2, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}, {a: 2, b: 4}]; + var result = _.findWhere(list, {a: 1}); + assert.deepEqual(result, {a: 1, b: 2}); + result = _.findWhere(list, {b: 4}); + assert.deepEqual(result, {a: 1, b: 4}); + + result = _.findWhere(list, {c: 1}); + assert.ok(_.isUndefined(result), 'undefined when not found'); + + result = _.findWhere([], {c: 1}); + assert.ok(_.isUndefined(result), 'undefined when searching empty list'); + + function test() {} + test.map = _.map; + assert.equal(_.findWhere([_, {a: 1, b: 2}, _], test), _, 'checks properties given function'); + + function TestClass() { + this.y = 5; + this.x = 'foo'; + } + var expect = {c: 1, x: 'foo', y: 5}; + assert.deepEqual(_.findWhere([{y: 5, b: 6}, expect], new TestClass()), expect, 'uses class instance properties'); + }); + + QUnit.test('max', function(assert) { + assert.equal(-Infinity, _.max(null), 'can handle null/undefined'); + assert.equal(-Infinity, _.max(void 0), 'can handle null/undefined'); + assert.equal(-Infinity, _.max(null, _.identity), 'can handle null/undefined'); + + assert.equal(3, _.max([1, 2, 3]), 'can perform a regular Math.max'); + + var neg = _.max([1, 2, 3], function(num){ return -num; }); + assert.equal(neg, 1, 'can perform a computation-based max'); + + assert.equal(-Infinity, _.max({}), 'Maximum value of an empty object'); + assert.equal(-Infinity, _.max([]), 'Maximum value of an empty array'); + assert.equal(_.max({a: 'a'}), -Infinity, 'Maximum value of a non-numeric collection'); + + assert.equal(299999, _.max(_.range(1, 300000)), 'Maximum value of a too-big array'); + + assert.equal(3, _.max([1, 2, 3, 'test']), 'Finds correct max in array starting with num and containing a NaN'); + assert.equal(3, _.max(['test', 1, 2, 3]), 'Finds correct max in array starting with NaN'); + + assert.equal(3, _.max([1, 2, 3, null]), 'Finds correct max in array starting with num and containing a `null`'); + assert.equal(3, _.max([null, 1, 2, 3]), 'Finds correct max in array starting with a `null`'); + + assert.equal(3, _.max([1, 2, 3, '']), 'Finds correct max in array starting with num and containing an empty string'); + assert.equal(3, _.max(['', 1, 2, 3]), 'Finds correct max in array starting with an empty string'); + + assert.equal(3, _.max([1, 2, 3, false]), 'Finds correct max in array starting with num and containing a false'); + assert.equal(3, _.max([false, 1, 2, 3]), 'Finds correct max in array starting with a false'); + + assert.equal(4, _.max([0, 1, 2, 3, 4]), 'Finds correct max in array containing a zero'); + assert.equal(0, _.max([-3, -2, -1, 0]), 'Finds correct max in array containing negative numbers'); + + assert.deepEqual([3, 6], _.map([[1, 2, 3], [4, 5, 6]], _.max), 'Finds correct max in array when mapping through multiple arrays'); + + var a = {x: -Infinity}; + var b = {x: -Infinity}; + var iterator = function(o){ return o.x; }; + assert.equal(_.max([a, b], iterator), a, 'Respects iterator return value of -Infinity'); + + assert.deepEqual(_.max([{a: 1}, {a: 0, b: 3}, {a: 4}, {a: 2}], 'a'), {a: 4}, 'String keys use property iterator'); + + assert.deepEqual(_.max([0, 2], function(c){ return c * this.x; }, {x: 1}), 2, 'Iterator context'); + assert.deepEqual(_.max([[1], [2, 3], [-1, 4], [5]], 0), [5], 'Lookup falsy iterator'); + assert.deepEqual(_.max([{0: 1}, {0: 2}, {0: -1}, {a: 1}], 0), {0: 2}, 'Lookup falsy iterator'); + }); + + QUnit.test('min', function(assert) { + assert.equal(Infinity, _.min(null), 'can handle null/undefined'); + assert.equal(Infinity, _.min(void 0), 'can handle null/undefined'); + assert.equal(Infinity, _.min(null, _.identity), 'can handle null/undefined'); + + assert.equal(1, _.min([1, 2, 3]), 'can perform a regular Math.min'); + + var neg = _.min([1, 2, 3], function(num){ return -num; }); + assert.equal(neg, 3, 'can perform a computation-based min'); + + assert.equal(Infinity, _.min({}), 'Minimum value of an empty object'); + assert.equal(Infinity, _.min([]), 'Minimum value of an empty array'); + assert.equal(_.min({a: 'a'}), Infinity, 'Minimum value of a non-numeric collection'); + + assert.deepEqual([1, 4], _.map([[1, 2, 3], [4, 5, 6]], _.min), 'Finds correct min in array when mapping through multiple arrays'); + + var now = new Date(9999999999); + var then = new Date(0); + assert.equal(_.min([now, then]), then); + + assert.equal(1, _.min(_.range(1, 300000)), 'Minimum value of a too-big array'); + + assert.equal(1, _.min([1, 2, 3, 'test']), 'Finds correct min in array starting with num and containing a NaN'); + assert.equal(1, _.min(['test', 1, 2, 3]), 'Finds correct min in array starting with NaN'); + + assert.equal(1, _.min([1, 2, 3, null]), 'Finds correct min in array starting with num and containing a `null`'); + assert.equal(1, _.min([null, 1, 2, 3]), 'Finds correct min in array starting with a `null`'); + + assert.equal(0, _.min([0, 1, 2, 3, 4]), 'Finds correct min in array containing a zero'); + assert.equal(-3, _.min([-3, -2, -1, 0]), 'Finds correct min in array containing negative numbers'); + + var a = {x: Infinity}; + var b = {x: Infinity}; + var iterator = function(o){ return o.x; }; + assert.equal(_.min([a, b], iterator), a, 'Respects iterator return value of Infinity'); + + assert.deepEqual(_.min([{a: 1}, {a: 0, b: 3}, {a: 4}, {a: 2}], 'a'), {a: 0, b: 3}, 'String keys use property iterator'); + + assert.deepEqual(_.min([0, 2], function(c){ return c * this.x; }, {x: -1}), 2, 'Iterator context'); + assert.deepEqual(_.min([[1], [2, 3], [-1, 4], [5]], 0), [-1, 4], 'Lookup falsy iterator'); + assert.deepEqual(_.min([{0: 1}, {0: 2}, {0: -1}, {a: 1}], 0), {0: -1}, 'Lookup falsy iterator'); + }); + + QUnit.test('sortBy', function(assert) { + var people = [{name: 'curly', age: 50}, {name: 'moe', age: 30}]; + people = _.sortBy(people, function(person){ return person.age; }); + assert.deepEqual(_.pluck(people, 'name'), ['moe', 'curly'], 'stooges sorted by age'); + + var list = [void 0, 4, 1, void 0, 3, 2]; + assert.deepEqual(_.sortBy(list, _.identity), [1, 2, 3, 4, void 0, void 0], 'sortBy with undefined values'); + + list = ['one', 'two', 'three', 'four', 'five']; + var sorted = _.sortBy(list, 'length'); + assert.deepEqual(sorted, ['one', 'two', 'four', 'five', 'three'], 'sorted by length'); + + function Pair(x, y) { + this.x = x; + this.y = y; + } + + var stableArray = [ + new Pair(1, 1), new Pair(1, 2), + new Pair(1, 3), new Pair(1, 4), + new Pair(1, 5), new Pair(1, 6), + new Pair(2, 1), new Pair(2, 2), + new Pair(2, 3), new Pair(2, 4), + new Pair(2, 5), new Pair(2, 6), + new Pair(void 0, 1), new Pair(void 0, 2), + new Pair(void 0, 3), new Pair(void 0, 4), + new Pair(void 0, 5), new Pair(void 0, 6) + ]; + + var stableObject = _.object('abcdefghijklmnopqr'.split(''), stableArray); + + var actual = _.sortBy(stableArray, function(pair) { + return pair.x; + }); + + assert.deepEqual(actual, stableArray, 'sortBy should be stable for arrays'); + assert.deepEqual(_.sortBy(stableArray, 'x'), stableArray, 'sortBy accepts property string'); + + actual = _.sortBy(stableObject, function(pair) { + return pair.x; + }); + + assert.deepEqual(actual, stableArray, 'sortBy should be stable for objects'); + + list = ['q', 'w', 'e', 'r', 't', 'y']; + assert.deepEqual(_.sortBy(list), ['e', 'q', 'r', 't', 'w', 'y'], 'uses _.identity if iterator is not specified'); + }); + + QUnit.test('groupBy', function(assert) { + var parity = _.groupBy([1, 2, 3, 4, 5, 6], function(num){ return num % 2; }); + assert.ok('0' in parity && '1' in parity, 'created a group for each value'); + assert.deepEqual(parity[0], [2, 4, 6], 'put each even number in the right group'); + + var list = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten']; + var grouped = _.groupBy(list, 'length'); + assert.deepEqual(grouped['3'], ['one', 'two', 'six', 'ten']); + assert.deepEqual(grouped['4'], ['four', 'five', 'nine']); + assert.deepEqual(grouped['5'], ['three', 'seven', 'eight']); + + var context = {}; + _.groupBy([{}], function(){ assert.ok(this === context); }, context); + + grouped = _.groupBy([4.2, 6.1, 6.4], function(num) { + return Math.floor(num) > 4 ? 'hasOwnProperty' : 'constructor'; + }); + assert.equal(grouped.constructor.length, 1); + assert.equal(grouped.hasOwnProperty.length, 2); + + var array = [{}]; + _.groupBy(array, function(value, index, obj){ assert.ok(obj === array); }); + + array = [1, 2, 1, 2, 3]; + grouped = _.groupBy(array); + assert.equal(grouped['1'].length, 2); + assert.equal(grouped['3'].length, 1); + + var matrix = [ + [1, 2], + [1, 3], + [2, 3] + ]; + assert.deepEqual(_.groupBy(matrix, 0), {1: [[1, 2], [1, 3]], 2: [[2, 3]]}); + assert.deepEqual(_.groupBy(matrix, 1), {2: [[1, 2]], 3: [[1, 3], [2, 3]]}); + }); + + QUnit.test('indexBy', function(assert) { + var parity = _.indexBy([1, 2, 3, 4, 5], function(num){ return num % 2 === 0; }); + assert.equal(parity['true'], 4); + assert.equal(parity['false'], 5); + + var list = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten']; + var grouped = _.indexBy(list, 'length'); + assert.equal(grouped['3'], 'ten'); + assert.equal(grouped['4'], 'nine'); + assert.equal(grouped['5'], 'eight'); + + var array = [1, 2, 1, 2, 3]; + grouped = _.indexBy(array); + assert.equal(grouped['1'], 1); + assert.equal(grouped['2'], 2); + assert.equal(grouped['3'], 3); + }); + + QUnit.test('countBy', function(assert) { + var parity = _.countBy([1, 2, 3, 4, 5], function(num){ return num % 2 === 0; }); + assert.equal(parity['true'], 2); + assert.equal(parity['false'], 3); + + var list = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten']; + var grouped = _.countBy(list, 'length'); + assert.equal(grouped['3'], 4); + assert.equal(grouped['4'], 3); + assert.equal(grouped['5'], 3); + + var context = {}; + _.countBy([{}], function(){ assert.ok(this === context); }, context); + + grouped = _.countBy([4.2, 6.1, 6.4], function(num) { + return Math.floor(num) > 4 ? 'hasOwnProperty' : 'constructor'; + }); + assert.equal(grouped.constructor, 1); + assert.equal(grouped.hasOwnProperty, 2); + + var array = [{}]; + _.countBy(array, function(value, index, obj){ assert.ok(obj === array); }); + + array = [1, 2, 1, 2, 3]; + grouped = _.countBy(array); + assert.equal(grouped['1'], 2); + assert.equal(grouped['3'], 1); + }); + + QUnit.test('shuffle', function(assert) { + assert.deepEqual(_.shuffle([1]), [1], 'behaves correctly on size 1 arrays'); + var numbers = _.range(20); + var shuffled = _.shuffle(numbers); + assert.notDeepEqual(numbers, shuffled, 'does change the order'); // Chance of false negative: 1 in ~2.4*10^18 + assert.notStrictEqual(numbers, shuffled, 'original object is unmodified'); + assert.deepEqual(numbers, _.sortBy(shuffled), 'contains the same members before and after shuffle'); + + shuffled = _.shuffle({a: 1, b: 2, c: 3, d: 4}); + assert.equal(shuffled.length, 4); + assert.deepEqual(shuffled.sort(), [1, 2, 3, 4], 'works on objects'); + }); + + QUnit.test('sample', function(assert) { + assert.strictEqual(_.sample([1]), 1, 'behaves correctly when no second parameter is given'); + assert.deepEqual(_.sample([1, 2, 3], -2), [], 'behaves correctly on negative n'); + var numbers = _.range(10); + var allSampled = _.sample(numbers, 10).sort(); + assert.deepEqual(allSampled, numbers, 'contains the same members before and after sample'); + allSampled = _.sample(numbers, 20).sort(); + assert.deepEqual(allSampled, numbers, 'also works when sampling more objects than are present'); + assert.ok(_.contains(numbers, _.sample(numbers)), 'sampling a single element returns something from the array'); + assert.strictEqual(_.sample([]), void 0, 'sampling empty array with no number returns undefined'); + assert.notStrictEqual(_.sample([], 5), [], 'sampling empty array with a number returns an empty array'); + assert.notStrictEqual(_.sample([1, 2, 3], 0), [], 'sampling an array with 0 picks returns an empty array'); + assert.deepEqual(_.sample([1, 2], -1), [], 'sampling a negative number of picks returns an empty array'); + assert.ok(_.contains([1, 2, 3], _.sample({a: 1, b: 2, c: 3})), 'sample one value from an object'); + var partialSample = _.sample(_.range(1000), 10); + var partialSampleSorted = partialSample.sort(); + assert.notDeepEqual(partialSampleSorted, _.range(10), 'samples from the whole array, not just the beginning'); + }); + + QUnit.test('toArray', function(assert) { + assert.ok(!_.isArray(arguments), 'arguments object is not an array'); + assert.ok(_.isArray(_.toArray(arguments)), 'arguments object converted into array'); + var a = [1, 2, 3]; + assert.ok(_.toArray(a) !== a, 'array is cloned'); + assert.deepEqual(_.toArray(a), [1, 2, 3], 'cloned array contains same elements'); + + var numbers = _.toArray({one: 1, two: 2, three: 3}); + assert.deepEqual(numbers, [1, 2, 3], 'object flattened into array'); + + var hearts = '\uD83D\uDC95'; + var pair = hearts.split(''); + var expected = [pair[0], hearts, '&', hearts, pair[1]]; + assert.deepEqual(_.toArray(expected.join('')), expected, 'maintains astral characters'); + assert.deepEqual(_.toArray(''), [], 'empty string into empty array'); + + if (typeof document != 'undefined') { + // test in IE < 9 + var actual; + try { + actual = _.toArray(document.childNodes); + } catch (e) { /* ignored */ } + assert.deepEqual(actual, _.map(document.childNodes, _.identity), 'works on NodeList'); + } + }); + + QUnit.test('size', function(assert) { + assert.equal(_.size({one: 1, two: 2, three: 3}), 3, 'can compute the size of an object'); + assert.equal(_.size([1, 2, 3]), 3, 'can compute the size of an array'); + assert.equal(_.size({length: 3, 0: 0, 1: 0, 2: 0}), 3, 'can compute the size of Array-likes'); + + var func = function() { + return _.size(arguments); + }; + + assert.equal(func(1, 2, 3, 4), 4, 'can test the size of the arguments object'); + + assert.equal(_.size('hello'), 5, 'can compute the size of a string literal'); + assert.equal(_.size(new String('hello')), 5, 'can compute the size of string object'); + + assert.equal(_.size(null), 0, 'handles nulls'); + assert.equal(_.size(0), 0, 'handles numbers'); + }); + + QUnit.test('partition', function(assert) { + var list = [0, 1, 2, 3, 4, 5]; + assert.deepEqual(_.partition(list, function(x) { return x < 4; }), [[0, 1, 2, 3], [4, 5]], 'handles bool return values'); + assert.deepEqual(_.partition(list, function(x) { return x & 1; }), [[1, 3, 5], [0, 2, 4]], 'handles 0 and 1 return values'); + assert.deepEqual(_.partition(list, function(x) { return x - 3; }), [[0, 1, 2, 4, 5], [3]], 'handles other numeric return values'); + assert.deepEqual(_.partition(list, function(x) { return x > 1 ? null : true; }), [[0, 1], [2, 3, 4, 5]], 'handles null return values'); + assert.deepEqual(_.partition(list, function(x) { if (x < 2) return true; }), [[0, 1], [2, 3, 4, 5]], 'handles undefined return values'); + assert.deepEqual(_.partition({a: 1, b: 2, c: 3}, function(x) { return x > 1; }), [[2, 3], [1]], 'handles objects'); + + assert.deepEqual(_.partition(list, function(x, index) { return index % 2; }), [[1, 3, 5], [0, 2, 4]], 'can reference the array index'); + assert.deepEqual(_.partition(list, function(x, index, arr) { return x === arr.length - 1; }), [[5], [0, 1, 2, 3, 4]], 'can reference the collection'); + + // Default iterator + assert.deepEqual(_.partition([1, false, true, '']), [[1, true], [false, '']], 'Default iterator'); + assert.deepEqual(_.partition([{x: 1}, {x: 0}, {x: 1}], 'x'), [[{x: 1}, {x: 1}], [{x: 0}]], 'Takes a string'); + + // Context + var predicate = function(x){ return x === this.x; }; + assert.deepEqual(_.partition([1, 2, 3], predicate, {x: 2}), [[2], [1, 3]], 'partition takes a context argument'); + + assert.deepEqual(_.partition([{a: 1}, {b: 2}, {a: 1, b: 2}], {a: 1}), [[{a: 1}, {a: 1, b: 2}], [{b: 2}]], 'predicate can be object'); + + var object = {a: 1}; + _.partition(object, function(val, key, obj) { + assert.equal(val, 1); + assert.equal(key, 'a'); + assert.equal(obj, object); + assert.equal(this, predicate); + }, predicate); + }); + + if (typeof document != 'undefined') { + QUnit.test('Can use various collection methods on NodeLists', function(assert) { + var parent = document.createElement('div'); + parent.innerHTML = '<span id=id1></span>textnode<span id=id2></span>'; + + var elementChildren = _.filter(parent.childNodes, _.isElement); + assert.equal(elementChildren.length, 2); + + assert.deepEqual(_.map(elementChildren, 'id'), ['id1', 'id2']); + assert.deepEqual(_.map(parent.childNodes, 'nodeType'), [1, 3, 1]); + + assert.ok(!_.every(parent.childNodes, _.isElement)); + assert.ok(_.some(parent.childNodes, _.isElement)); + + function compareNode(node) { + return _.isElement(node) ? node.id.charAt(2) : void 0; + } + assert.equal(_.max(parent.childNodes, compareNode), _.last(parent.childNodes)); + assert.equal(_.min(parent.childNodes, compareNode), _.first(parent.childNodes)); + }); + } + +}()); diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/underscore/test/cross-document.js b/ecomp-portal-FE/client/bower_components/lodash/vendor/underscore/test/cross-document.js new file mode 100644 index 00000000..cb68a3d9 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/underscore/test/cross-document.js @@ -0,0 +1,141 @@ +(function() { + if (typeof document == 'undefined') return; + + var _ = typeof require == 'function' ? require('..') : window._; + + QUnit.module('Cross Document'); + /* global iObject, iElement, iArguments, iFunction, iArray, iError, iString, iNumber, iBoolean, iDate, iRegExp, iNaN, iNull, iUndefined, ActiveXObject */ + + // Setup remote variables for iFrame tests. + var iframe = document.createElement('iframe'); + iframe.frameBorder = iframe.height = iframe.width = 0; + document.body.appendChild(iframe); + var iDoc = (iDoc = iframe.contentDocument || iframe.contentWindow).document || iDoc; + iDoc.write( + [ + '<script>', + 'parent.iElement = document.createElement("div");', + 'parent.iArguments = (function(){ return arguments; })(1, 2, 3);', + 'parent.iArray = [1, 2, 3];', + 'parent.iString = new String("hello");', + 'parent.iNumber = new Number(100);', + 'parent.iFunction = (function(){});', + 'parent.iDate = new Date();', + 'parent.iRegExp = /hi/;', + 'parent.iNaN = NaN;', + 'parent.iNull = null;', + 'parent.iBoolean = new Boolean(false);', + 'parent.iUndefined = undefined;', + 'parent.iObject = {};', + 'parent.iError = new Error();', + '</script>' + ].join('\n') + ); + iDoc.close(); + + QUnit.test('isEqual', function(assert) { + + assert.ok(!_.isEqual(iNumber, 101)); + assert.ok(_.isEqual(iNumber, 100)); + + // Objects from another frame. + assert.ok(_.isEqual({}, iObject), 'Objects with equivalent members created in different documents are equal'); + + // Array from another frame. + assert.ok(_.isEqual([1, 2, 3], iArray), 'Arrays with equivalent elements created in different documents are equal'); + }); + + QUnit.test('isEmpty', function(assert) { + assert.ok(!_([iNumber]).isEmpty(), '[1] is not empty'); + assert.ok(!_.isEmpty(iArray), '[] is empty'); + assert.ok(_.isEmpty(iObject), '{} is empty'); + }); + + QUnit.test('isElement', function(assert) { + assert.ok(!_.isElement('div'), 'strings are not dom elements'); + assert.ok(_.isElement(document.body), 'the body tag is a DOM element'); + assert.ok(_.isElement(iElement), 'even from another frame'); + }); + + QUnit.test('isArguments', function(assert) { + assert.ok(_.isArguments(iArguments), 'even from another frame'); + }); + + QUnit.test('isObject', function(assert) { + assert.ok(_.isObject(iElement), 'even from another frame'); + assert.ok(_.isObject(iFunction), 'even from another frame'); + }); + + QUnit.test('isArray', function(assert) { + assert.ok(_.isArray(iArray), 'even from another frame'); + }); + + QUnit.test('isString', function(assert) { + assert.ok(_.isString(iString), 'even from another frame'); + }); + + QUnit.test('isNumber', function(assert) { + assert.ok(_.isNumber(iNumber), 'even from another frame'); + }); + + QUnit.test('isBoolean', function(assert) { + assert.ok(_.isBoolean(iBoolean), 'even from another frame'); + }); + + QUnit.test('isFunction', function(assert) { + assert.ok(_.isFunction(iFunction), 'even from another frame'); + }); + + QUnit.test('isDate', function(assert) { + assert.ok(_.isDate(iDate), 'even from another frame'); + }); + + QUnit.test('isRegExp', function(assert) { + assert.ok(_.isRegExp(iRegExp), 'even from another frame'); + }); + + QUnit.test('isNaN', function(assert) { + assert.ok(_.isNaN(iNaN), 'even from another frame'); + }); + + QUnit.test('isNull', function(assert) { + assert.ok(_.isNull(iNull), 'even from another frame'); + }); + + QUnit.test('isUndefined', function(assert) { + assert.ok(_.isUndefined(iUndefined), 'even from another frame'); + }); + + QUnit.test('isError', function(assert) { + assert.ok(_.isError(iError), 'even from another frame'); + }); + + if (typeof ActiveXObject != 'undefined') { + QUnit.test('IE host objects', function(assert) { + var xml = new ActiveXObject('Msxml2.DOMDocument.3.0'); + assert.ok(!_.isNumber(xml)); + assert.ok(!_.isBoolean(xml)); + assert.ok(!_.isNaN(xml)); + assert.ok(!_.isFunction(xml)); + assert.ok(!_.isNull(xml)); + assert.ok(!_.isUndefined(xml)); + }); + + QUnit.test('#1621 IE 11 compat mode DOM elements are not functions', function(assert) { + var fn = function() {}; + var xml = new ActiveXObject('Msxml2.DOMDocument.3.0'); + var div = document.createElement('div'); + + // JIT the function + var count = 200; + while (count--) { + _.isFunction(fn); + } + + assert.equal(_.isFunction(xml), false); + assert.equal(_.isFunction(div), false); + assert.equal(_.isFunction(fn), true); + }); + } + +}()); diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/underscore/test/functions.js b/ecomp-portal-FE/client/bower_components/lodash/vendor/underscore/test/functions.js new file mode 100644 index 00000000..f696bd64 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/underscore/test/functions.js @@ -0,0 +1,728 @@ +(function() { + var _ = typeof require == 'function' ? require('..') : window._; + + QUnit.module('Functions'); + QUnit.config.asyncRetries = 3; + + QUnit.test('bind', function(assert) { + var context = {name: 'moe'}; + var func = function(arg) { return 'name: ' + (this.name || arg); }; + var bound = _.bind(func, context); + assert.equal(bound(), 'name: moe', 'can bind a function to a context'); + + bound = _(func).bind(context); + assert.equal(bound(), 'name: moe', 'can do OO-style binding'); + + bound = _.bind(func, null, 'curly'); + var result = bound(); + // Work around a PhantomJS bug when applying a function with null|undefined. + assert.ok(result === 'name: curly' || result === 'name: ' + window.name, 'can bind without specifying a context'); + + func = function(salutation, name) { return salutation + ': ' + name; }; + func = _.bind(func, this, 'hello'); + assert.equal(func('moe'), 'hello: moe', 'the function was partially applied in advance'); + + func = _.bind(func, this, 'curly'); + assert.equal(func(), 'hello: curly', 'the function was completely applied in advance'); + + func = function(salutation, firstname, lastname) { return salutation + ': ' + firstname + ' ' + lastname; }; + func = _.bind(func, this, 'hello', 'moe', 'curly'); + assert.equal(func(), 'hello: moe curly', 'the function was partially applied in advance and can accept multiple arguments'); + + func = function(ctx, message) { assert.equal(this, ctx, message); }; + _.bind(func, 0, 0, 'can bind a function to `0`')(); + _.bind(func, '', '', 'can bind a function to an empty string')(); + _.bind(func, false, false, 'can bind a function to `false`')(); + + // These tests are only meaningful when using a browser without a native bind function + // To test this with a modern browser, set underscore's nativeBind to undefined + var F = function() { return this; }; + var boundf = _.bind(F, {hello: 'moe curly'}); + var Boundf = boundf; // make eslint happy. + var newBoundf = new Boundf(); + assert.equal(newBoundf.hello, void 0, 'function should not be bound to the context, to comply with ECMAScript 5'); + assert.equal(boundf().hello, 'moe curly', "When called without the new operator, it's OK to be bound to the context"); + assert.ok(newBoundf instanceof F, 'a bound instance is an instance of the original function'); + + assert.raises(function() { _.bind('notafunction'); }, TypeError, 'throws an error when binding to a non-function'); + }); + + QUnit.test('partial', function(assert) { + var obj = {name: 'moe'}; + var func = function() { return this.name + ' ' + _.toArray(arguments).join(' '); }; + + obj.func = _.partial(func, 'a', 'b'); + assert.equal(obj.func('c', 'd'), 'moe a b c d', 'can partially apply'); + + obj.func = _.partial(func, _, 'b', _, 'd'); + assert.equal(obj.func('a', 'c'), 'moe a b c d', 'can partially apply with placeholders'); + + func = _.partial(function() { return arguments.length; }, _, 'b', _, 'd'); + assert.equal(func('a', 'c', 'e'), 5, 'accepts more arguments than the number of placeholders'); + assert.equal(func('a'), 4, 'accepts fewer arguments than the number of placeholders'); + + func = _.partial(function() { return typeof arguments[2]; }, _, 'b', _, 'd'); + assert.equal(func('a'), 'undefined', 'unfilled placeholders are undefined'); + + // passes context + function MyWidget(name, options) { + this.name = name; + this.options = options; + } + MyWidget.prototype.get = function() { + return this.name; + }; + var MyWidgetWithCoolOpts = _.partial(MyWidget, _, {a: 1}); + var widget = new MyWidgetWithCoolOpts('foo'); + assert.ok(widget instanceof MyWidget, 'Can partially bind a constructor'); + assert.equal(widget.get(), 'foo', 'keeps prototype'); + assert.deepEqual(widget.options, {a: 1}); + + _.partial.placeholder = obj; + func = _.partial(function() { return arguments.length; }, obj, 'b', obj, 'd'); + assert.equal(func('a'), 4, 'allows the placeholder to be swapped out'); + + _.partial.placeholder = {}; + func = _.partial(function() { return arguments.length; }, obj, 'b', obj, 'd'); + assert.equal(func('a'), 5, 'swapping the placeholder preserves previously bound arguments'); + + _.partial.placeholder = _; + }); + + QUnit.test('bindAll', function(assert) { + var curly = {name: 'curly'}; + var moe = { + name: 'moe', + getName: function() { return 'name: ' + this.name; }, + sayHi: function() { return 'hi: ' + this.name; } + }; + curly.getName = moe.getName; + _.bindAll(moe, 'getName', 'sayHi'); + curly.sayHi = moe.sayHi; + assert.equal(curly.getName(), 'name: curly', 'unbound function is bound to current object'); + assert.equal(curly.sayHi(), 'hi: moe', 'bound function is still bound to original object'); + + curly = {name: 'curly'}; + moe = { + name: 'moe', + getName: function() { return 'name: ' + this.name; }, + sayHi: function() { return 'hi: ' + this.name; }, + sayLast: function() { return this.sayHi(_.last(arguments)); } + }; + + assert.raises(function() { _.bindAll(moe); }, Error, 'throws an error for bindAll with no functions named'); + assert.raises(function() { _.bindAll(moe, 'sayBye'); }, TypeError, 'throws an error for bindAll if the given key is undefined'); + assert.raises(function() { _.bindAll(moe, 'name'); }, TypeError, 'throws an error for bindAll if the given key is not a function'); + + _.bindAll(moe, 'sayHi', 'sayLast'); + curly.sayHi = moe.sayHi; + assert.equal(curly.sayHi(), 'hi: moe'); + + var sayLast = moe.sayLast; + assert.equal(sayLast(1, 2, 3, 4, 5, 6, 7, 'Tom'), 'hi: moe', 'createCallback works with any number of arguments'); + + _.bindAll(moe, ['getName']); + var getName = moe.getName; + assert.equal(getName(), 'name: moe', 'flattens arguments into a single list'); + }); + + QUnit.test('memoize', function(assert) { + var fib = function(n) { + return n < 2 ? n : fib(n - 1) + fib(n - 2); + }; + assert.equal(fib(10), 55, 'a memoized version of fibonacci produces identical results'); + fib = _.memoize(fib); // Redefine `fib` for memoization + assert.equal(fib(10), 55, 'a memoized version of fibonacci produces identical results'); + + var o = function(str) { + return str; + }; + var fastO = _.memoize(o); + assert.equal(o('toString'), 'toString', 'checks hasOwnProperty'); + assert.equal(fastO('toString'), 'toString', 'checks hasOwnProperty'); + + // Expose the cache. + var upper = _.memoize(function(s) { + return s.toUpperCase(); + }); + assert.equal(upper('foo'), 'FOO'); + assert.equal(upper('bar'), 'BAR'); + assert.deepEqual(upper.cache, {foo: 'FOO', bar: 'BAR'}); + upper.cache = {foo: 'BAR', bar: 'FOO'}; + assert.equal(upper('foo'), 'BAR'); + assert.equal(upper('bar'), 'FOO'); + + var hashed = _.memoize(function(key) { + //https://github.com/jashkenas/underscore/pull/1679#discussion_r13736209 + assert.ok(/[a-z]+/.test(key), 'hasher doesn\'t change keys'); + return key; + }, function(key) { + return key.toUpperCase(); + }); + hashed('yep'); + assert.deepEqual(hashed.cache, {YEP: 'yep'}, 'takes a hasher'); + + // Test that the hash function can be used to swizzle the key. + var objCacher = _.memoize(function(value, key) { + return {key: key, value: value}; + }, function(value, key) { + return key; + }); + var myObj = objCacher('a', 'alpha'); + var myObjAlias = objCacher('b', 'alpha'); + assert.notStrictEqual(myObj, void 0, 'object is created if second argument used as key'); + assert.strictEqual(myObj, myObjAlias, 'object is cached if second argument used as key'); + assert.strictEqual(myObj.value, 'a', 'object is not modified if second argument used as key'); + }); + + QUnit.test('delay', function(assert) { + assert.expect(2); + var done = assert.async(); + var delayed = false; + _.delay(function(){ delayed = true; }, 100); + setTimeout(function(){ assert.ok(!delayed, "didn't delay the function quite yet"); }, 50); + setTimeout(function(){ assert.ok(delayed, 'delayed the function'); done(); }, 150); + }); + + QUnit.test('defer', function(assert) { + assert.expect(1); + var done = assert.async(); + var deferred = false; + _.defer(function(bool){ deferred = bool; }, true); + _.delay(function(){ assert.ok(deferred, 'deferred the function'); done(); }, 50); + }); + + QUnit.test('throttle', function(assert) { + assert.expect(2); + var done = assert.async(); + var counter = 0; + var incr = function(){ counter++; }; + var throttledIncr = _.throttle(incr, 32); + throttledIncr(); throttledIncr(); + + assert.equal(counter, 1, 'incr was called immediately'); + _.delay(function(){ assert.equal(counter, 2, 'incr was throttled'); done(); }, 64); + }); + + QUnit.test('throttle arguments', function(assert) { + assert.expect(2); + var done = assert.async(); + var value = 0; + var update = function(val){ value = val; }; + var throttledUpdate = _.throttle(update, 32); + throttledUpdate(1); throttledUpdate(2); + _.delay(function(){ throttledUpdate(3); }, 64); + assert.equal(value, 1, 'updated to latest value'); + _.delay(function(){ assert.equal(value, 3, 'updated to latest value'); done(); }, 96); + }); + + QUnit.test('throttle once', function(assert) { + assert.expect(2); + var done = assert.async(); + var counter = 0; + var incr = function(){ return ++counter; }; + var throttledIncr = _.throttle(incr, 32); + var result = throttledIncr(); + _.delay(function(){ + assert.equal(result, 1, 'throttled functions return their value'); + assert.equal(counter, 1, 'incr was called once'); done(); + }, 64); + }); + + QUnit.test('throttle twice', function(assert) { + assert.expect(1); + var done = assert.async(); + var counter = 0; + var incr = function(){ counter++; }; + var throttledIncr = _.throttle(incr, 32); + throttledIncr(); throttledIncr(); + _.delay(function(){ assert.equal(counter, 2, 'incr was called twice'); done(); }, 64); + }); + + QUnit.test('more throttling', function(assert) { + assert.expect(3); + var done = assert.async(); + var counter = 0; + var incr = function(){ counter++; }; + var throttledIncr = _.throttle(incr, 30); + throttledIncr(); throttledIncr(); + assert.equal(counter, 1); + _.delay(function(){ + assert.equal(counter, 2); + throttledIncr(); + assert.equal(counter, 3); + done(); + }, 85); + }); + + QUnit.test('throttle repeatedly with results', function(assert) { + assert.expect(6); + var done = assert.async(); + var counter = 0; + var incr = function(){ return ++counter; }; + var throttledIncr = _.throttle(incr, 100); + var results = []; + var saveResult = function() { results.push(throttledIncr()); }; + saveResult(); saveResult(); + _.delay(saveResult, 50); + _.delay(saveResult, 150); + _.delay(saveResult, 160); + _.delay(saveResult, 230); + _.delay(function() { + assert.equal(results[0], 1, 'incr was called once'); + assert.equal(results[1], 1, 'incr was throttled'); + assert.equal(results[2], 1, 'incr was throttled'); + assert.equal(results[3], 2, 'incr was called twice'); + assert.equal(results[4], 2, 'incr was throttled'); + assert.equal(results[5], 3, 'incr was called trailing'); + done(); + }, 300); + }); + + QUnit.test('throttle triggers trailing call when invoked repeatedly', function(assert) { + assert.expect(2); + var done = assert.async(); + var counter = 0; + var limit = 48; + var incr = function(){ counter++; }; + var throttledIncr = _.throttle(incr, 32); + + var stamp = new Date; + while (new Date - stamp < limit) { + throttledIncr(); + } + var lastCount = counter; + assert.ok(counter > 1); + + _.delay(function() { + assert.ok(counter > lastCount); + done(); + }, 96); + }); + + QUnit.test('throttle does not trigger leading call when leading is set to false', function(assert) { + assert.expect(2); + var done = assert.async(); + var counter = 0; + var incr = function(){ counter++; }; + var throttledIncr = _.throttle(incr, 60, {leading: false}); + + throttledIncr(); throttledIncr(); + assert.equal(counter, 0); + + _.delay(function() { + assert.equal(counter, 1); + done(); + }, 96); + }); + + QUnit.test('more throttle does not trigger leading call when leading is set to false', function(assert) { + assert.expect(3); + var done = assert.async(); + var counter = 0; + var incr = function(){ counter++; }; + var throttledIncr = _.throttle(incr, 100, {leading: false}); + + throttledIncr(); + _.delay(throttledIncr, 50); + _.delay(throttledIncr, 60); + _.delay(throttledIncr, 200); + assert.equal(counter, 0); + + _.delay(function() { + assert.equal(counter, 1); + }, 250); + + _.delay(function() { + assert.equal(counter, 2); + done(); + }, 350); + }); + + QUnit.test('one more throttle with leading: false test', function(assert) { + assert.expect(2); + var done = assert.async(); + var counter = 0; + var incr = function(){ counter++; }; + var throttledIncr = _.throttle(incr, 100, {leading: false}); + + var time = new Date; + while (new Date - time < 350) throttledIncr(); + assert.ok(counter <= 3); + + _.delay(function() { + assert.ok(counter <= 4); + done(); + }, 200); + }); + + QUnit.test('throttle does not trigger trailing call when trailing is set to false', function(assert) { + assert.expect(4); + var done = assert.async(); + var counter = 0; + var incr = function(){ counter++; }; + var throttledIncr = _.throttle(incr, 60, {trailing: false}); + + throttledIncr(); throttledIncr(); throttledIncr(); + assert.equal(counter, 1); + + _.delay(function() { + assert.equal(counter, 1); + + throttledIncr(); throttledIncr(); + assert.equal(counter, 2); + + _.delay(function() { + assert.equal(counter, 2); + done(); + }, 96); + }, 96); + }); + + QUnit.test('throttle continues to function after system time is set backwards', function(assert) { + assert.expect(2); + var done = assert.async(); + var counter = 0; + var incr = function(){ counter++; }; + var throttledIncr = _.throttle(incr, 100); + var origNowFunc = _.now; + + throttledIncr(); + assert.equal(counter, 1); + _.now = function() { + return new Date(2013, 0, 1, 1, 1, 1); + }; + + _.delay(function() { + throttledIncr(); + assert.equal(counter, 2); + done(); + _.now = origNowFunc; + }, 200); + }); + + QUnit.test('throttle re-entrant', function(assert) { + assert.expect(2); + var done = assert.async(); + var sequence = [ + ['b1', 'b2'], + ['c1', 'c2'] + ]; + var value = ''; + var throttledAppend; + var append = function(arg){ + value += this + arg; + var args = sequence.pop(); + if (args) { + throttledAppend.call(args[0], args[1]); + } + }; + throttledAppend = _.throttle(append, 32); + throttledAppend.call('a1', 'a2'); + assert.equal(value, 'a1a2'); + _.delay(function(){ + assert.equal(value, 'a1a2c1c2b1b2', 'append was throttled successfully'); + done(); + }, 100); + }); + + QUnit.test('throttle cancel', function(assert) { + var done = assert.async(); + var counter = 0; + var incr = function(){ counter++; }; + var throttledIncr = _.throttle(incr, 32); + throttledIncr(); + throttledIncr.cancel(); + throttledIncr(); + throttledIncr(); + + assert.equal(counter, 2, 'incr was called immediately'); + _.delay(function(){ assert.equal(counter, 3, 'incr was throttled'); done(); }, 64); + }); + + QUnit.test('throttle cancel with leading: false', function(assert) { + var done = assert.async(); + var counter = 0; + var incr = function(){ counter++; }; + var throttledIncr = _.throttle(incr, 32, {leading: false}); + throttledIncr(); + throttledIncr.cancel(); + + assert.equal(counter, 0, 'incr was throttled'); + _.delay(function(){ assert.equal(counter, 0, 'incr was throttled'); done(); }, 64); + }); + + QUnit.test('debounce', function(assert) { + assert.expect(1); + var done = assert.async(); + var counter = 0; + var incr = function(){ counter++; }; + var debouncedIncr = _.debounce(incr, 32); + debouncedIncr(); debouncedIncr(); + _.delay(debouncedIncr, 16); + _.delay(function(){ assert.equal(counter, 1, 'incr was debounced'); done(); }, 96); + }); + + QUnit.test('debounce cancel', function(assert) { + assert.expect(1); + var done = assert.async(); + var counter = 0; + var incr = function(){ counter++; }; + var debouncedIncr = _.debounce(incr, 32); + debouncedIncr(); + debouncedIncr.cancel(); + _.delay(function(){ assert.equal(counter, 0, 'incr was not called'); done(); }, 96); + }); + + QUnit.test('debounce asap', function(assert) { + assert.expect(6); + var done = assert.async(); + var a, b, c; + var counter = 0; + var incr = function(){ return ++counter; }; + var debouncedIncr = _.debounce(incr, 64, true); + a = debouncedIncr(); + b = debouncedIncr(); + assert.equal(a, 1); + assert.equal(b, 1); + assert.equal(counter, 1, 'incr was called immediately'); + _.delay(debouncedIncr, 16); + _.delay(debouncedIncr, 32); + _.delay(debouncedIncr, 48); + _.delay(function(){ + assert.equal(counter, 1, 'incr was debounced'); + c = debouncedIncr(); + assert.equal(c, 2); + assert.equal(counter, 2, 'incr was called again'); + done(); + }, 128); + }); + + QUnit.test('debounce asap cancel', function(assert) { + assert.expect(4); + var done = assert.async(); + var a, b; + var counter = 0; + var incr = function(){ return ++counter; }; + var debouncedIncr = _.debounce(incr, 64, true); + a = debouncedIncr(); + debouncedIncr.cancel(); + b = debouncedIncr(); + assert.equal(a, 1); + assert.equal(b, 2); + assert.equal(counter, 2, 'incr was called immediately'); + _.delay(debouncedIncr, 16); + _.delay(debouncedIncr, 32); + _.delay(debouncedIncr, 48); + _.delay(function(){ assert.equal(counter, 2, 'incr was debounced'); done(); }, 128); + }); + + QUnit.test('debounce asap recursively', function(assert) { + assert.expect(2); + var done = assert.async(); + var counter = 0; + var debouncedIncr = _.debounce(function(){ + counter++; + if (counter < 10) debouncedIncr(); + }, 32, true); + debouncedIncr(); + assert.equal(counter, 1, 'incr was called immediately'); + _.delay(function(){ assert.equal(counter, 1, 'incr was debounced'); done(); }, 96); + }); + + QUnit.test('debounce after system time is set backwards', function(assert) { + assert.expect(2); + var done = assert.async(); + var counter = 0; + var origNowFunc = _.now; + var debouncedIncr = _.debounce(function(){ + counter++; + }, 100, true); + + debouncedIncr(); + assert.equal(counter, 1, 'incr was called immediately'); + + _.now = function() { + return new Date(2013, 0, 1, 1, 1, 1); + }; + + _.delay(function() { + debouncedIncr(); + assert.equal(counter, 2, 'incr was debounced successfully'); + done(); + _.now = origNowFunc; + }, 200); + }); + + QUnit.test('debounce re-entrant', function(assert) { + assert.expect(2); + var done = assert.async(); + var sequence = [ + ['b1', 'b2'] + ]; + var value = ''; + var debouncedAppend; + var append = function(arg){ + value += this + arg; + var args = sequence.pop(); + if (args) { + debouncedAppend.call(args[0], args[1]); + } + }; + debouncedAppend = _.debounce(append, 32); + debouncedAppend.call('a1', 'a2'); + assert.equal(value, ''); + _.delay(function(){ + assert.equal(value, 'a1a2b1b2', 'append was debounced successfully'); + done(); + }, 100); + }); + + QUnit.test('once', function(assert) { + var num = 0; + var increment = _.once(function(){ return ++num; }); + increment(); + increment(); + assert.equal(num, 1); + + assert.equal(increment(), 1, 'stores a memo to the last value'); + }); + + QUnit.test('Recursive onced function.', function(assert) { + assert.expect(1); + var f = _.once(function(){ + assert.ok(true); + f(); + }); + f(); + }); + + QUnit.test('wrap', function(assert) { + var greet = function(name){ return 'hi: ' + name; }; + var backwards = _.wrap(greet, function(func, name){ return func(name) + ' ' + name.split('').reverse().join(''); }); + assert.equal(backwards('moe'), 'hi: moe eom', 'wrapped the salutation function'); + + var inner = function(){ return 'Hello '; }; + var obj = {name: 'Moe'}; + obj.hi = _.wrap(inner, function(fn){ return fn() + this.name; }); + assert.equal(obj.hi(), 'Hello Moe'); + + var noop = function(){}; + var wrapped = _.wrap(noop, function(){ return Array.prototype.slice.call(arguments, 0); }); + var ret = wrapped(['whats', 'your'], 'vector', 'victor'); + assert.deepEqual(ret, [noop, ['whats', 'your'], 'vector', 'victor']); + }); + + QUnit.test('negate', function(assert) { + var isOdd = function(n){ return n & 1; }; + assert.equal(_.negate(isOdd)(2), true, 'should return the complement of the given function'); + assert.equal(_.negate(isOdd)(3), false, 'should return the complement of the given function'); + }); + + QUnit.test('compose', function(assert) { + var greet = function(name){ return 'hi: ' + name; }; + var exclaim = function(sentence){ return sentence + '!'; }; + var composed = _.compose(exclaim, greet); + assert.equal(composed('moe'), 'hi: moe!', 'can compose a function that takes another'); + + composed = _.compose(greet, exclaim); + assert.equal(composed('moe'), 'hi: moe!', 'in this case, the functions are also commutative'); + + // f(g(h(x, y, z))) + function h(x, y, z) { + assert.equal(arguments.length, 3, 'First function called with multiple args'); + return z * y; + } + function g(x) { + assert.equal(arguments.length, 1, 'Composed function is called with 1 argument'); + return x; + } + function f(x) { + assert.equal(arguments.length, 1, 'Composed function is called with 1 argument'); + return x * 2; + } + composed = _.compose(f, g, h); + assert.equal(composed(1, 2, 3), 12); + }); + + QUnit.test('after', function(assert) { + var testAfter = function(afterAmount, timesCalled) { + var afterCalled = 0; + var after = _.after(afterAmount, function() { + afterCalled++; + }); + while (timesCalled--) after(); + return afterCalled; + }; + + assert.equal(testAfter(5, 5), 1, 'after(N) should fire after being called N times'); + assert.equal(testAfter(5, 4), 0, 'after(N) should not fire unless called N times'); + assert.equal(testAfter(0, 0), 0, 'after(0) should not fire immediately'); + assert.equal(testAfter(0, 1), 1, 'after(0) should fire when first invoked'); + }); + + QUnit.test('before', function(assert) { + var testBefore = function(beforeAmount, timesCalled) { + var beforeCalled = 0; + var before = _.before(beforeAmount, function() { beforeCalled++; }); + while (timesCalled--) before(); + return beforeCalled; + }; + + assert.equal(testBefore(5, 5), 4, 'before(N) should not fire after being called N times'); + assert.equal(testBefore(5, 4), 4, 'before(N) should fire before being called N times'); + assert.equal(testBefore(0, 0), 0, 'before(0) should not fire immediately'); + assert.equal(testBefore(0, 1), 0, 'before(0) should not fire when first invoked'); + + var context = {num: 0}; + var increment = _.before(3, function(){ return ++this.num; }); + _.times(10, increment, context); + assert.equal(increment(), 2, 'stores a memo to the last value'); + assert.equal(context.num, 2, 'provides context'); + }); + + QUnit.test('iteratee', function(assert) { + var identity = _.iteratee(); + assert.equal(identity, _.identity, '_.iteratee is exposed as an external function.'); + + function fn() { + return arguments; + } + _.each([_.iteratee(fn), _.iteratee(fn, {})], function(cb) { + assert.equal(cb().length, 0); + assert.deepEqual(_.toArray(cb(1, 2, 3)), _.range(1, 4)); + assert.deepEqual(_.toArray(cb(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)), _.range(1, 11)); + }); + + }); + + QUnit.test('restArgs', function(assert) { + assert.expect(10); + _.restArgs(function(a, args) { + assert.strictEqual(a, 1); + assert.deepEqual(args, [2, 3], 'collects rest arguments into an array'); + })(1, 2, 3); + + _.restArgs(function(a, args) { + assert.strictEqual(a, void 0); + assert.deepEqual(args, [], 'passes empty array if there are not enough arguments'); + })(); + + _.restArgs(function(a, b, c, args) { + assert.strictEqual(arguments.length, 4); + assert.deepEqual(args, [4, 5], 'works on functions with many named parameters'); + })(1, 2, 3, 4, 5); + + var obj = {}; + _.restArgs(function() { + assert.strictEqual(this, obj, 'invokes function with this context'); + }).call(obj); + + _.restArgs(function(array, iteratee, context) { + assert.deepEqual(array, [1, 2, 3, 4], 'startIndex can be used manually specify index of rest parameter'); + assert.strictEqual(iteratee, void 0); + assert.strictEqual(context, void 0); + }, 0)(1, 2, 3, 4); + }); + +}()); diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/underscore/test/objects.js b/ecomp-portal-FE/client/bower_components/lodash/vendor/underscore/test/objects.js new file mode 100644 index 00000000..fa1d9e3e --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/underscore/test/objects.js @@ -0,0 +1,1102 @@ +(function() { + var _ = typeof require == 'function' ? require('..') : window._; + + QUnit.module('Objects'); + + var testElement = typeof document === 'object' ? document.createElement('div') : void 0; + + QUnit.test('keys', function(assert) { + assert.deepEqual(_.keys({one: 1, two: 2}), ['one', 'two'], 'can extract the keys from an object'); + // the test above is not safe because it relies on for-in enumeration order + var a = []; a[1] = 0; + assert.deepEqual(_.keys(a), ['1'], 'is not fooled by sparse arrays; see issue #95'); + assert.deepEqual(_.keys(null), []); + assert.deepEqual(_.keys(void 0), []); + assert.deepEqual(_.keys(1), []); + assert.deepEqual(_.keys('a'), []); + assert.deepEqual(_.keys(true), []); + + // keys that may be missed if the implementation isn't careful + var trouble = { + constructor: Object, + valueOf: _.noop, + hasOwnProperty: null, + toString: 5, + toLocaleString: void 0, + propertyIsEnumerable: /a/, + isPrototypeOf: this, + __defineGetter__: Boolean, + __defineSetter__: {}, + __lookupSetter__: false, + __lookupGetter__: [] + }; + var troubleKeys = ['constructor', 'valueOf', 'hasOwnProperty', 'toString', 'toLocaleString', 'propertyIsEnumerable', + 'isPrototypeOf', '__defineGetter__', '__defineSetter__', '__lookupSetter__', '__lookupGetter__'].sort(); + assert.deepEqual(_.keys(trouble).sort(), troubleKeys, 'matches non-enumerable properties'); + }); + + QUnit.test('allKeys', function(assert) { + assert.deepEqual(_.allKeys({one: 1, two: 2}), ['one', 'two'], 'can extract the allKeys from an object'); + // the test above is not safe because it relies on for-in enumeration order + var a = []; a[1] = 0; + assert.deepEqual(_.allKeys(a), ['1'], 'is not fooled by sparse arrays; see issue #95'); + + a.a = a; + assert.deepEqual(_.allKeys(a), ['1', 'a'], 'is not fooled by sparse arrays with additional properties'); + + _.each([null, void 0, 1, 'a', true, NaN, {}, [], new Number(5), new Date(0)], function(val) { + assert.deepEqual(_.allKeys(val), []); + }); + + // allKeys that may be missed if the implementation isn't careful + var trouble = { + constructor: Object, + valueOf: _.noop, + hasOwnProperty: null, + toString: 5, + toLocaleString: void 0, + propertyIsEnumerable: /a/, + isPrototypeOf: this + }; + var troubleKeys = ['constructor', 'valueOf', 'hasOwnProperty', 'toString', 'toLocaleString', 'propertyIsEnumerable', + 'isPrototypeOf'].sort(); + assert.deepEqual(_.allKeys(trouble).sort(), troubleKeys, 'matches non-enumerable properties'); + + function A() {} + A.prototype.foo = 'foo'; + var b = new A(); + b.bar = 'bar'; + assert.deepEqual(_.allKeys(b).sort(), ['bar', 'foo'], 'should include inherited keys'); + + function y() {} + y.x = 'z'; + assert.deepEqual(_.allKeys(y), ['x'], 'should get keys from constructor'); + }); + + QUnit.test('values', function(assert) { + assert.deepEqual(_.values({one: 1, two: 2}), [1, 2], 'can extract the values from an object'); + assert.deepEqual(_.values({one: 1, two: 2, length: 3}), [1, 2, 3], '... even when one of them is "length"'); + }); + + QUnit.test('pairs', function(assert) { + assert.deepEqual(_.pairs({one: 1, two: 2}), [['one', 1], ['two', 2]], 'can convert an object into pairs'); + assert.deepEqual(_.pairs({one: 1, two: 2, length: 3}), [['one', 1], ['two', 2], ['length', 3]], '... even when one of them is "length"'); + }); + + QUnit.test('invert', function(assert) { + var obj = {first: 'Moe', second: 'Larry', third: 'Curly'}; + assert.deepEqual(_.keys(_.invert(obj)), ['Moe', 'Larry', 'Curly'], 'can invert an object'); + assert.deepEqual(_.invert(_.invert(obj)), obj, 'two inverts gets you back where you started'); + + obj = {length: 3}; + assert.equal(_.invert(obj)['3'], 'length', 'can invert an object with "length"'); + }); + + QUnit.test('functions', function(assert) { + var obj = {a: 'dash', b: _.map, c: /yo/, d: _.reduce}; + assert.deepEqual(['b', 'd'], _.functions(obj), 'can grab the function names of any passed-in object'); + + var Animal = function(){}; + Animal.prototype.run = function(){}; + assert.deepEqual(_.functions(new Animal), ['run'], 'also looks up functions on the prototype'); + }); + + QUnit.test('methods', function(assert) { + assert.strictEqual(_.methods, _.functions, 'is an alias for functions'); + }); + + QUnit.test('extend', function(assert) { + var result; + assert.equal(_.extend({}, {a: 'b'}).a, 'b', 'can extend an object with the attributes of another'); + assert.equal(_.extend({a: 'x'}, {a: 'b'}).a, 'b', 'properties in source override destination'); + assert.equal(_.extend({x: 'x'}, {a: 'b'}).x, 'x', "properties not in source don't get overriden"); + result = _.extend({x: 'x'}, {a: 'a'}, {b: 'b'}); + assert.deepEqual(result, {x: 'x', a: 'a', b: 'b'}, 'can extend from multiple source objects'); + result = _.extend({x: 'x'}, {a: 'a', x: 2}, {a: 'b'}); + assert.deepEqual(result, {x: 2, a: 'b'}, 'extending from multiple source objects last property trumps'); + result = _.extend({}, {a: void 0, b: null}); + assert.deepEqual(_.keys(result), ['a', 'b'], 'extend copies undefined values'); + + var F = function() {}; + F.prototype = {a: 'b'}; + var subObj = new F(); + subObj.c = 'd'; + assert.deepEqual(_.extend({}, subObj), {a: 'b', c: 'd'}, 'extend copies all properties from source'); + _.extend(subObj, {}); + assert.ok(!subObj.hasOwnProperty('a'), "extend does not convert destination object's 'in' properties to 'own' properties"); + + try { + result = {}; + _.extend(result, null, void 0, {a: 1}); + } catch (e) { /* ignored */ } + + assert.equal(result.a, 1, 'should not error on `null` or `undefined` sources'); + + assert.strictEqual(_.extend(null, {a: 1}), null, 'extending null results in null'); + assert.strictEqual(_.extend(void 0, {a: 1}), void 0, 'extending undefined results in undefined'); + }); + + QUnit.test('extendOwn', function(assert) { + var result; + assert.equal(_.extendOwn({}, {a: 'b'}).a, 'b', 'can extend an object with the attributes of another'); + assert.equal(_.extendOwn({a: 'x'}, {a: 'b'}).a, 'b', 'properties in source override destination'); + assert.equal(_.extendOwn({x: 'x'}, {a: 'b'}).x, 'x', "properties not in source don't get overriden"); + result = _.extendOwn({x: 'x'}, {a: 'a'}, {b: 'b'}); + assert.deepEqual(result, {x: 'x', a: 'a', b: 'b'}, 'can extend from multiple source objects'); + result = _.extendOwn({x: 'x'}, {a: 'a', x: 2}, {a: 'b'}); + assert.deepEqual(result, {x: 2, a: 'b'}, 'extending from multiple source objects last property trumps'); + assert.deepEqual(_.extendOwn({}, {a: void 0, b: null}), {a: void 0, b: null}, 'copies undefined values'); + + var F = function() {}; + F.prototype = {a: 'b'}; + var subObj = new F(); + subObj.c = 'd'; + assert.deepEqual(_.extendOwn({}, subObj), {c: 'd'}, 'copies own properties from source'); + + result = {}; + assert.deepEqual(_.extendOwn(result, null, void 0, {a: 1}), {a: 1}, 'should not error on `null` or `undefined` sources'); + + _.each(['a', 5, null, false], function(val) { + assert.strictEqual(_.extendOwn(val, {a: 1}), val, 'extending non-objects results in returning the non-object value'); + }); + + assert.strictEqual(_.extendOwn(void 0, {a: 1}), void 0, 'extending undefined results in undefined'); + + result = _.extendOwn({a: 1, 0: 2, 1: '5', length: 6}, {0: 1, 1: 2, length: 2}); + assert.deepEqual(result, {a: 1, 0: 1, 1: 2, length: 2}, 'should treat array-like objects like normal objects'); + }); + + QUnit.test('assign', function(assert) { + assert.strictEqual(_.assign, _.extendOwn, 'is an alias for extendOwn'); + }); + + QUnit.test('pick', function(assert) { + var result; + result = _.pick({a: 1, b: 2, c: 3}, 'a', 'c'); + assert.deepEqual(result, {a: 1, c: 3}, 'can restrict properties to those named'); + result = _.pick({a: 1, b: 2, c: 3}, ['b', 'c']); + assert.deepEqual(result, {b: 2, c: 3}, 'can restrict properties to those named in an array'); + result = _.pick({a: 1, b: 2, c: 3}, ['a'], 'b'); + assert.deepEqual(result, {a: 1, b: 2}, 'can restrict properties to those named in mixed args'); + result = _.pick(['a', 'b'], 1); + assert.deepEqual(result, {1: 'b'}, 'can pick numeric properties'); + + _.each([null, void 0], function(val) { + assert.deepEqual(_.pick(val, 'hasOwnProperty'), {}, 'Called with null/undefined'); + assert.deepEqual(_.pick(val, _.constant(true)), {}); + }); + assert.deepEqual(_.pick(5, 'toString', 'b'), {toString: Number.prototype.toString}, 'can iterate primitives'); + + var data = {a: 1, b: 2, c: 3}; + var callback = function(value, key, object) { + assert.strictEqual(key, {1: 'a', 2: 'b', 3: 'c'}[value]); + assert.strictEqual(object, data); + return value !== this.value; + }; + result = _.pick(data, callback, {value: 2}); + assert.deepEqual(result, {a: 1, c: 3}, 'can accept a predicate and context'); + + var Obj = function(){}; + Obj.prototype = {a: 1, b: 2, c: 3}; + var instance = new Obj(); + assert.deepEqual(_.pick(instance, 'a', 'c'), {a: 1, c: 3}, 'include prototype props'); + + assert.deepEqual(_.pick(data, function(val, key) { + return this[key] === 3 && this === instance; + }, instance), {c: 3}, 'function is given context'); + + assert.ok(!_.has(_.pick({}, 'foo'), 'foo'), 'does not set own property if property not in object'); + _.pick(data, function(value, key, obj) { + assert.equal(obj, data, 'passes same object as third parameter of iteratee'); + }); + }); + + QUnit.test('omit', function(assert) { + var result; + result = _.omit({a: 1, b: 2, c: 3}, 'b'); + assert.deepEqual(result, {a: 1, c: 3}, 'can omit a single named property'); + result = _.omit({a: 1, b: 2, c: 3}, 'a', 'c'); + assert.deepEqual(result, {b: 2}, 'can omit several named properties'); + result = _.omit({a: 1, b: 2, c: 3}, ['b', 'c']); + assert.deepEqual(result, {a: 1}, 'can omit properties named in an array'); + result = _.omit(['a', 'b'], 0); + assert.deepEqual(result, {1: 'b'}, 'can omit numeric properties'); + + assert.deepEqual(_.omit(null, 'a', 'b'), {}, 'non objects return empty object'); + assert.deepEqual(_.omit(void 0, 'toString'), {}, 'null/undefined return empty object'); + assert.deepEqual(_.omit(5, 'toString', 'b'), {}, 'returns empty object for primitives'); + + var data = {a: 1, b: 2, c: 3}; + var callback = function(value, key, object) { + assert.strictEqual(key, {1: 'a', 2: 'b', 3: 'c'}[value]); + assert.strictEqual(object, data); + return value !== this.value; + }; + result = _.omit(data, callback, {value: 2}); + assert.deepEqual(result, {b: 2}, 'can accept a predicate'); + + var Obj = function(){}; + Obj.prototype = {a: 1, b: 2, c: 3}; + var instance = new Obj(); + assert.deepEqual(_.omit(instance, 'b'), {a: 1, c: 3}, 'include prototype props'); + + assert.deepEqual(_.omit(data, function(val, key) { + return this[key] === 3 && this === instance; + }, instance), {a: 1, b: 2}, 'function is given context'); + }); + + QUnit.test('defaults', function(assert) { + var options = {zero: 0, one: 1, empty: '', nan: NaN, nothing: null}; + + _.defaults(options, {zero: 1, one: 10, twenty: 20, nothing: 'str'}); + assert.equal(options.zero, 0, 'value exists'); + assert.equal(options.one, 1, 'value exists'); + assert.equal(options.twenty, 20, 'default applied'); + assert.equal(options.nothing, null, "null isn't overridden"); + + _.defaults(options, {empty: 'full'}, {nan: 'nan'}, {word: 'word'}, {word: 'dog'}); + assert.equal(options.empty, '', 'value exists'); + assert.ok(_.isNaN(options.nan), "NaN isn't overridden"); + assert.equal(options.word, 'word', 'new value is added, first one wins'); + + try { + options = {}; + _.defaults(options, null, void 0, {a: 1}); + } catch (e) { /* ignored */ } + + assert.equal(options.a, 1, 'should not error on `null` or `undefined` sources'); + + assert.deepEqual(_.defaults(null, {a: 1}), {a: 1}, 'defaults skips nulls'); + assert.deepEqual(_.defaults(void 0, {a: 1}), {a: 1}, 'defaults skips undefined'); + }); + + QUnit.test('clone', function(assert) { + var moe = {name: 'moe', lucky: [13, 27, 34]}; + var clone = _.clone(moe); + assert.equal(clone.name, 'moe', 'the clone as the attributes of the original'); + + clone.name = 'curly'; + assert.ok(clone.name === 'curly' && moe.name === 'moe', 'clones can change shallow attributes without affecting the original'); + + clone.lucky.push(101); + assert.equal(_.last(moe.lucky), 101, 'changes to deep attributes are shared with the original'); + + assert.equal(_.clone(void 0), void 0, 'non objects should not be changed by clone'); + assert.equal(_.clone(1), 1, 'non objects should not be changed by clone'); + assert.equal(_.clone(null), null, 'non objects should not be changed by clone'); + }); + + QUnit.test('create', function(assert) { + var Parent = function() {}; + Parent.prototype = {foo: function() {}, bar: 2}; + + _.each(['foo', null, void 0, 1], function(val) { + assert.deepEqual(_.create(val), {}, 'should return empty object when a non-object is provided'); + }); + + assert.ok(_.create([]) instanceof Array, 'should return new instance of array when array is provided'); + + var Child = function() {}; + Child.prototype = _.create(Parent.prototype); + assert.ok(new Child instanceof Parent, 'object should inherit prototype'); + + var func = function() {}; + Child.prototype = _.create(Parent.prototype, {func: func}); + assert.strictEqual(Child.prototype.func, func, 'properties should be added to object'); + + Child.prototype = _.create(Parent.prototype, {constructor: Child}); + assert.strictEqual(Child.prototype.constructor, Child); + + Child.prototype.foo = 'foo'; + var created = _.create(Child.prototype, new Child); + assert.ok(!created.hasOwnProperty('foo'), 'should only add own properties'); + }); + + QUnit.test('isEqual', function(assert) { + function First() { + this.value = 1; + } + First.prototype.value = 1; + function Second() { + this.value = 1; + } + Second.prototype.value = 2; + + // Basic equality and identity comparisons. + assert.ok(_.isEqual(null, null), '`null` is equal to `null`'); + assert.ok(_.isEqual(), '`undefined` is equal to `undefined`'); + + assert.ok(!_.isEqual(0, -0), '`0` is not equal to `-0`'); + assert.ok(!_.isEqual(-0, 0), 'Commutative equality is implemented for `0` and `-0`'); + assert.ok(!_.isEqual(null, void 0), '`null` is not equal to `undefined`'); + assert.ok(!_.isEqual(void 0, null), 'Commutative equality is implemented for `null` and `undefined`'); + + // String object and primitive comparisons. + assert.ok(_.isEqual('Curly', 'Curly'), 'Identical string primitives are equal'); + assert.ok(_.isEqual(new String('Curly'), new String('Curly')), 'String objects with identical primitive values are equal'); + assert.ok(_.isEqual(new String('Curly'), 'Curly'), 'String primitives and their corresponding object wrappers are equal'); + assert.ok(_.isEqual('Curly', new String('Curly')), 'Commutative equality is implemented for string objects and primitives'); + + assert.ok(!_.isEqual('Curly', 'Larry'), 'String primitives with different values are not equal'); + assert.ok(!_.isEqual(new String('Curly'), new String('Larry')), 'String objects with different primitive values are not equal'); + assert.ok(!_.isEqual(new String('Curly'), {toString: function(){ return 'Curly'; }}), 'String objects and objects with a custom `toString` method are not equal'); + + // Number object and primitive comparisons. + assert.ok(_.isEqual(75, 75), 'Identical number primitives are equal'); + assert.ok(_.isEqual(new Number(75), new Number(75)), 'Number objects with identical primitive values are equal'); + assert.ok(_.isEqual(75, new Number(75)), 'Number primitives and their corresponding object wrappers are equal'); + assert.ok(_.isEqual(new Number(75), 75), 'Commutative equality is implemented for number objects and primitives'); + assert.ok(!_.isEqual(new Number(0), -0), '`new Number(0)` and `-0` are not equal'); + assert.ok(!_.isEqual(0, new Number(-0)), 'Commutative equality is implemented for `new Number(0)` and `-0`'); + + assert.ok(!_.isEqual(new Number(75), new Number(63)), 'Number objects with different primitive values are not equal'); + assert.ok(!_.isEqual(new Number(63), {valueOf: function(){ return 63; }}), 'Number objects and objects with a `valueOf` method are not equal'); + + // Comparisons involving `NaN`. + assert.ok(_.isEqual(NaN, NaN), '`NaN` is equal to `NaN`'); + assert.ok(_.isEqual(new Number(NaN), NaN), 'Object(`NaN`) is equal to `NaN`'); + assert.ok(!_.isEqual(61, NaN), 'A number primitive is not equal to `NaN`'); + assert.ok(!_.isEqual(new Number(79), NaN), 'A number object is not equal to `NaN`'); + assert.ok(!_.isEqual(Infinity, NaN), '`Infinity` is not equal to `NaN`'); + + // Boolean object and primitive comparisons. + assert.ok(_.isEqual(true, true), 'Identical boolean primitives are equal'); + assert.ok(_.isEqual(new Boolean, new Boolean), 'Boolean objects with identical primitive values are equal'); + assert.ok(_.isEqual(true, new Boolean(true)), 'Boolean primitives and their corresponding object wrappers are equal'); + assert.ok(_.isEqual(new Boolean(true), true), 'Commutative equality is implemented for booleans'); + assert.ok(!_.isEqual(new Boolean(true), new Boolean), 'Boolean objects with different primitive values are not equal'); + + // Common type coercions. + assert.ok(!_.isEqual(new Boolean(false), true), '`new Boolean(false)` is not equal to `true`'); + assert.ok(!_.isEqual('75', 75), 'String and number primitives with like values are not equal'); + assert.ok(!_.isEqual(new Number(63), new String(63)), 'String and number objects with like values are not equal'); + assert.ok(!_.isEqual(75, '75'), 'Commutative equality is implemented for like string and number values'); + assert.ok(!_.isEqual(0, ''), 'Number and string primitives with like values are not equal'); + assert.ok(!_.isEqual(1, true), 'Number and boolean primitives with like values are not equal'); + assert.ok(!_.isEqual(new Boolean(false), new Number(0)), 'Boolean and number objects with like values are not equal'); + assert.ok(!_.isEqual(false, new String('')), 'Boolean primitives and string objects with like values are not equal'); + assert.ok(!_.isEqual(12564504e5, new Date(2009, 9, 25)), 'Dates and their corresponding numeric primitive values are not equal'); + + // Dates. + assert.ok(_.isEqual(new Date(2009, 9, 25), new Date(2009, 9, 25)), 'Date objects referencing identical times are equal'); + assert.ok(!_.isEqual(new Date(2009, 9, 25), new Date(2009, 11, 13)), 'Date objects referencing different times are not equal'); + assert.ok(!_.isEqual(new Date(2009, 11, 13), { + getTime: function(){ + return 12606876e5; + } + }), 'Date objects and objects with a `getTime` method are not equal'); + assert.ok(!_.isEqual(new Date('Curly'), new Date('Curly')), 'Invalid dates are not equal'); + + // Functions. + assert.ok(!_.isEqual(First, Second), 'Different functions with identical bodies and source code representations are not equal'); + + // RegExps. + assert.ok(_.isEqual(/(?:)/gim, /(?:)/gim), 'RegExps with equivalent patterns and flags are equal'); + assert.ok(_.isEqual(/(?:)/gi, /(?:)/ig), 'Flag order is not significant'); + assert.ok(!_.isEqual(/(?:)/g, /(?:)/gi), 'RegExps with equivalent patterns and different flags are not equal'); + assert.ok(!_.isEqual(/Moe/gim, /Curly/gim), 'RegExps with different patterns and equivalent flags are not equal'); + assert.ok(!_.isEqual(/(?:)/gi, /(?:)/g), 'Commutative equality is implemented for RegExps'); + assert.ok(!_.isEqual(/Curly/g, {source: 'Larry', global: true, ignoreCase: false, multiline: false}), 'RegExps and RegExp-like objects are not equal'); + + // Empty arrays, array-like objects, and object literals. + assert.ok(_.isEqual({}, {}), 'Empty object literals are equal'); + assert.ok(_.isEqual([], []), 'Empty array literals are equal'); + assert.ok(_.isEqual([{}], [{}]), 'Empty nested arrays and objects are equal'); + assert.ok(!_.isEqual({length: 0}, []), 'Array-like objects and arrays are not equal.'); + assert.ok(!_.isEqual([], {length: 0}), 'Commutative equality is implemented for array-like objects'); + + assert.ok(!_.isEqual({}, []), 'Object literals and array literals are not equal'); + assert.ok(!_.isEqual([], {}), 'Commutative equality is implemented for objects and arrays'); + + // Arrays with primitive and object values. + assert.ok(_.isEqual([1, 'Larry', true], [1, 'Larry', true]), 'Arrays containing identical primitives are equal'); + assert.ok(_.isEqual([/Moe/g, new Date(2009, 9, 25)], [/Moe/g, new Date(2009, 9, 25)]), 'Arrays containing equivalent elements are equal'); + + // Multi-dimensional arrays. + var a = [new Number(47), false, 'Larry', /Moe/, new Date(2009, 11, 13), ['running', 'biking', new String('programming')], {a: 47}]; + var b = [new Number(47), false, 'Larry', /Moe/, new Date(2009, 11, 13), ['running', 'biking', new String('programming')], {a: 47}]; + assert.ok(_.isEqual(a, b), 'Arrays containing nested arrays and objects are recursively compared'); + + // Overwrite the methods defined in ES 5.1 section 15.4.4. + a.forEach = a.map = a.filter = a.every = a.indexOf = a.lastIndexOf = a.some = a.reduce = a.reduceRight = null; + b.join = b.pop = b.reverse = b.shift = b.slice = b.splice = b.concat = b.sort = b.unshift = null; + + // Array elements and properties. + assert.ok(_.isEqual(a, b), 'Arrays containing equivalent elements and different non-numeric properties are equal'); + a.push('White Rocks'); + assert.ok(!_.isEqual(a, b), 'Arrays of different lengths are not equal'); + a.push('East Boulder'); + b.push('Gunbarrel Ranch', 'Teller Farm'); + assert.ok(!_.isEqual(a, b), 'Arrays of identical lengths containing different elements are not equal'); + + // Sparse arrays. + assert.ok(_.isEqual(Array(3), Array(3)), 'Sparse arrays of identical lengths are equal'); + assert.ok(!_.isEqual(Array(3), Array(6)), 'Sparse arrays of different lengths are not equal when both are empty'); + + var sparse = []; + sparse[1] = 5; + assert.ok(_.isEqual(sparse, [void 0, 5]), 'Handles sparse arrays as dense'); + + // Simple objects. + assert.ok(_.isEqual({a: 'Curly', b: 1, c: true}, {a: 'Curly', b: 1, c: true}), 'Objects containing identical primitives are equal'); + assert.ok(_.isEqual({a: /Curly/g, b: new Date(2009, 11, 13)}, {a: /Curly/g, b: new Date(2009, 11, 13)}), 'Objects containing equivalent members are equal'); + assert.ok(!_.isEqual({a: 63, b: 75}, {a: 61, b: 55}), 'Objects of identical sizes with different values are not equal'); + assert.ok(!_.isEqual({a: 63, b: 75}, {a: 61, c: 55}), 'Objects of identical sizes with different property names are not equal'); + assert.ok(!_.isEqual({a: 1, b: 2}, {a: 1}), 'Objects of different sizes are not equal'); + assert.ok(!_.isEqual({a: 1}, {a: 1, b: 2}), 'Commutative equality is implemented for objects'); + assert.ok(!_.isEqual({x: 1, y: void 0}, {x: 1, z: 2}), 'Objects with identical keys and different values are not equivalent'); + + // `A` contains nested objects and arrays. + a = { + name: new String('Moe Howard'), + age: new Number(77), + stooge: true, + hobbies: ['acting'], + film: { + name: 'Sing a Song of Six Pants', + release: new Date(1947, 9, 30), + stars: [new String('Larry Fine'), 'Shemp Howard'], + minutes: new Number(16), + seconds: 54 + } + }; + + // `B` contains equivalent nested objects and arrays. + b = { + name: new String('Moe Howard'), + age: new Number(77), + stooge: true, + hobbies: ['acting'], + film: { + name: 'Sing a Song of Six Pants', + release: new Date(1947, 9, 30), + stars: [new String('Larry Fine'), 'Shemp Howard'], + minutes: new Number(16), + seconds: 54 + } + }; + assert.ok(_.isEqual(a, b), 'Objects with nested equivalent members are recursively compared'); + + // Instances. + assert.ok(_.isEqual(new First, new First), 'Object instances are equal'); + assert.ok(!_.isEqual(new First, new Second), 'Objects with different constructors and identical own properties are not equal'); + assert.ok(!_.isEqual({value: 1}, new First), 'Object instances and objects sharing equivalent properties are not equal'); + assert.ok(!_.isEqual({value: 2}, new Second), 'The prototype chain of objects should not be examined'); + + // Circular Arrays. + (a = []).push(a); + (b = []).push(b); + assert.ok(_.isEqual(a, b), 'Arrays containing circular references are equal'); + a.push(new String('Larry')); + b.push(new String('Larry')); + assert.ok(_.isEqual(a, b), 'Arrays containing circular references and equivalent properties are equal'); + a.push('Shemp'); + b.push('Curly'); + assert.ok(!_.isEqual(a, b), 'Arrays containing circular references and different properties are not equal'); + + // More circular arrays #767. + a = ['everything is checked but', 'this', 'is not']; + a[1] = a; + b = ['everything is checked but', ['this', 'array'], 'is not']; + assert.ok(!_.isEqual(a, b), 'Comparison of circular references with non-circular references are not equal'); + + // Circular Objects. + a = {abc: null}; + b = {abc: null}; + a.abc = a; + b.abc = b; + assert.ok(_.isEqual(a, b), 'Objects containing circular references are equal'); + a.def = 75; + b.def = 75; + assert.ok(_.isEqual(a, b), 'Objects containing circular references and equivalent properties are equal'); + a.def = new Number(75); + b.def = new Number(63); + assert.ok(!_.isEqual(a, b), 'Objects containing circular references and different properties are not equal'); + + // More circular objects #767. + a = {everything: 'is checked', but: 'this', is: 'not'}; + a.but = a; + b = {everything: 'is checked', but: {that: 'object'}, is: 'not'}; + assert.ok(!_.isEqual(a, b), 'Comparison of circular references with non-circular object references are not equal'); + + // Cyclic Structures. + a = [{abc: null}]; + b = [{abc: null}]; + (a[0].abc = a).push(a); + (b[0].abc = b).push(b); + assert.ok(_.isEqual(a, b), 'Cyclic structures are equal'); + a[0].def = 'Larry'; + b[0].def = 'Larry'; + assert.ok(_.isEqual(a, b), 'Cyclic structures containing equivalent properties are equal'); + a[0].def = new String('Larry'); + b[0].def = new String('Curly'); + assert.ok(!_.isEqual(a, b), 'Cyclic structures containing different properties are not equal'); + + // Complex Circular References. + a = {foo: {b: {foo: {c: {foo: null}}}}}; + b = {foo: {b: {foo: {c: {foo: null}}}}}; + a.foo.b.foo.c.foo = a; + b.foo.b.foo.c.foo = b; + assert.ok(_.isEqual(a, b), 'Cyclic structures with nested and identically-named properties are equal'); + + // Chaining. + assert.ok(!_.isEqual(_({x: 1, y: void 0}).chain(), _({x: 1, z: 2}).chain()), 'Chained objects containing different values are not equal'); + + a = _({x: 1, y: 2}).chain(); + b = _({x: 1, y: 2}).chain(); + assert.equal(_.isEqual(a.isEqual(b), _(true)), true, '`isEqual` can be chained'); + + // Objects without a `constructor` property + if (Object.create) { + a = Object.create(null, {x: {value: 1, enumerable: true}}); + b = {x: 1}; + assert.ok(_.isEqual(a, b), 'Handles objects without a constructor (e.g. from Object.create'); + } + + function Foo() { this.a = 1; } + Foo.prototype.constructor = null; + + var other = {a: 1}; + assert.strictEqual(_.isEqual(new Foo, other), false, 'Objects from different constructors are not equal'); + + + // Tricky object cases val comparisions + assert.equal(_.isEqual([0], [-0]), false); + assert.equal(_.isEqual({a: 0}, {a: -0}), false); + assert.equal(_.isEqual([NaN], [NaN]), true); + assert.equal(_.isEqual({a: NaN}, {a: NaN}), true); + + if (typeof Symbol !== 'undefined') { + var symbol = Symbol('x'); + assert.strictEqual(_.isEqual(symbol, symbol), true, 'A symbol is equal to itself'); + assert.strictEqual(_.isEqual(symbol, Object(symbol)), true, 'Even when wrapped in Object()'); + assert.strictEqual(_.isEqual(symbol, null), false, 'Different types are not equal'); + } + + }); + + QUnit.test('isEmpty', function(assert) { + assert.ok(!_([1]).isEmpty(), '[1] is not empty'); + assert.ok(_.isEmpty([]), '[] is empty'); + assert.ok(!_.isEmpty({one: 1}), '{one: 1} is not empty'); + assert.ok(_.isEmpty({}), '{} is empty'); + assert.ok(_.isEmpty(new RegExp('')), 'objects with prototype properties are empty'); + assert.ok(_.isEmpty(null), 'null is empty'); + assert.ok(_.isEmpty(), 'undefined is empty'); + assert.ok(_.isEmpty(''), 'the empty string is empty'); + assert.ok(!_.isEmpty('moe'), 'but other strings are not'); + + var obj = {one: 1}; + delete obj.one; + assert.ok(_.isEmpty(obj), 'deleting all the keys from an object empties it'); + + var args = function(){ return arguments; }; + assert.ok(_.isEmpty(args()), 'empty arguments object is empty'); + assert.ok(!_.isEmpty(args('')), 'non-empty arguments object is not empty'); + + // covers collecting non-enumerable properties in IE < 9 + var nonEnumProp = {toString: 5}; + assert.ok(!_.isEmpty(nonEnumProp), 'non-enumerable property is not empty'); + }); + + if (typeof document === 'object') { + QUnit.test('isElement', function(assert) { + assert.ok(!_.isElement('div'), 'strings are not dom elements'); + assert.ok(_.isElement(testElement), 'an element is a DOM element'); + }); + } + + QUnit.test('isArguments', function(assert) { + var args = (function(){ return arguments; }(1, 2, 3)); + assert.ok(!_.isArguments('string'), 'a string is not an arguments object'); + assert.ok(!_.isArguments(_.isArguments), 'a function is not an arguments object'); + assert.ok(_.isArguments(args), 'but the arguments object is an arguments object'); + assert.ok(!_.isArguments(_.toArray(args)), 'but not when it\'s converted into an array'); + assert.ok(!_.isArguments([1, 2, 3]), 'and not vanilla arrays.'); + }); + + QUnit.test('isObject', function(assert) { + assert.ok(_.isObject(arguments), 'the arguments object is object'); + assert.ok(_.isObject([1, 2, 3]), 'and arrays'); + if (testElement) { + assert.ok(_.isObject(testElement), 'and DOM element'); + } + assert.ok(_.isObject(function() {}), 'and functions'); + assert.ok(!_.isObject(null), 'but not null'); + assert.ok(!_.isObject(void 0), 'and not undefined'); + assert.ok(!_.isObject('string'), 'and not string'); + assert.ok(!_.isObject(12), 'and not number'); + assert.ok(!_.isObject(true), 'and not boolean'); + assert.ok(_.isObject(new String('string')), 'but new String()'); + }); + + QUnit.test('isArray', function(assert) { + assert.ok(!_.isArray(void 0), 'undefined vars are not arrays'); + assert.ok(!_.isArray(arguments), 'the arguments object is not an array'); + assert.ok(_.isArray([1, 2, 3]), 'but arrays are'); + }); + + QUnit.test('isString', function(assert) { + var obj = new String('I am a string object'); + if (testElement) { + assert.ok(!_.isString(testElement), 'an element is not a string'); + } + assert.ok(_.isString([1, 2, 3].join(', ')), 'but strings are'); + assert.strictEqual(_.isString('I am a string literal'), true, 'string literals are'); + assert.ok(_.isString(obj), 'so are String objects'); + assert.strictEqual(_.isString(1), false); + }); + + QUnit.test('isSymbol', function(assert) { + assert.ok(!_.isSymbol(0), 'numbers are not symbols'); + assert.ok(!_.isSymbol(''), 'strings are not symbols'); + assert.ok(!_.isSymbol(_.isSymbol), 'functions are not symbols'); + if (typeof Symbol === 'function') { + assert.ok(_.isSymbol(Symbol()), 'symbols are symbols'); + assert.ok(_.isSymbol(Symbol('description')), 'described symbols are symbols'); + assert.ok(_.isSymbol(Object(Symbol())), 'boxed symbols are symbols'); + } + }); + + QUnit.test('isNumber', function(assert) { + assert.ok(!_.isNumber('string'), 'a string is not a number'); + assert.ok(!_.isNumber(arguments), 'the arguments object is not a number'); + assert.ok(!_.isNumber(void 0), 'undefined is not a number'); + assert.ok(_.isNumber(3 * 4 - 7 / 10), 'but numbers are'); + assert.ok(_.isNumber(NaN), 'NaN *is* a number'); + assert.ok(_.isNumber(Infinity), 'Infinity is a number'); + assert.ok(!_.isNumber('1'), 'numeric strings are not numbers'); + }); + + QUnit.test('isBoolean', function(assert) { + assert.ok(!_.isBoolean(2), 'a number is not a boolean'); + assert.ok(!_.isBoolean('string'), 'a string is not a boolean'); + assert.ok(!_.isBoolean('false'), 'the string "false" is not a boolean'); + assert.ok(!_.isBoolean('true'), 'the string "true" is not a boolean'); + assert.ok(!_.isBoolean(arguments), 'the arguments object is not a boolean'); + assert.ok(!_.isBoolean(void 0), 'undefined is not a boolean'); + assert.ok(!_.isBoolean(NaN), 'NaN is not a boolean'); + assert.ok(!_.isBoolean(null), 'null is not a boolean'); + assert.ok(_.isBoolean(true), 'but true is'); + assert.ok(_.isBoolean(false), 'and so is false'); + }); + + QUnit.test('isMap', function(assert) { + assert.ok(!_.isMap('string'), 'a string is not a map'); + assert.ok(!_.isMap(2), 'a number is not a map'); + assert.ok(!_.isMap({}), 'an object is not a map'); + assert.ok(!_.isMap(false), 'a boolean is not a map'); + assert.ok(!_.isMap(void 0), 'undefined is not a map'); + assert.ok(!_.isMap([1, 2, 3]), 'an array is not a map'); + if (typeof Set === 'function') { + assert.ok(!_.isMap(new Set()), 'a set is not a map'); + } + if (typeof WeakSet === 'function') { + assert.ok(!_.isMap(new WeakSet()), 'a weakset is not a map'); + } + if (typeof WeakMap === 'function') { + assert.ok(!_.isMap(new WeakMap()), 'a weakmap is not a map'); + } + if (typeof Map === 'function') { + var keyString = 'a string'; + var obj = new Map(); + obj.set(keyString, 'value'); + assert.ok(_.isMap(obj), 'but a map is'); + } + }); + + QUnit.test('isWeakMap', function(assert) { + assert.ok(!_.isWeakMap('string'), 'a string is not a weakmap'); + assert.ok(!_.isWeakMap(2), 'a number is not a weakmap'); + assert.ok(!_.isWeakMap({}), 'an object is not a weakmap'); + assert.ok(!_.isWeakMap(false), 'a boolean is not a weakmap'); + assert.ok(!_.isWeakMap(void 0), 'undefined is not a weakmap'); + assert.ok(!_.isWeakMap([1, 2, 3]), 'an array is not a weakmap'); + if (typeof Set === 'function') { + assert.ok(!_.isWeakMap(new Set()), 'a set is not a weakmap'); + } + if (typeof WeakSet === 'function') { + assert.ok(!_.isWeakMap(new WeakSet()), 'a weakset is not a weakmap'); + } + if (typeof Map === 'function') { + assert.ok(!_.isWeakMap(new Map()), 'a map is not a weakmap'); + } + if (typeof WeakMap === 'function') { + var keyObj = {}, obj = new WeakMap(); + obj.set(keyObj, 'value'); + assert.ok(_.isWeakMap(obj), 'but a weakmap is'); + } + }); + + QUnit.test('isSet', function(assert) { + assert.ok(!_.isSet('string'), 'a string is not a set'); + assert.ok(!_.isSet(2), 'a number is not a set'); + assert.ok(!_.isSet({}), 'an object is not a set'); + assert.ok(!_.isSet(false), 'a boolean is not a set'); + assert.ok(!_.isSet(void 0), 'undefined is not a set'); + assert.ok(!_.isSet([1, 2, 3]), 'an array is not a set'); + if (typeof Map === 'function') { + assert.ok(!_.isSet(new Map()), 'a map is not a set'); + } + if (typeof WeakMap === 'function') { + assert.ok(!_.isSet(new WeakMap()), 'a weakmap is not a set'); + } + if (typeof WeakSet === 'function') { + assert.ok(!_.isSet(new WeakSet()), 'a weakset is not a set'); + } + if (typeof Set === 'function') { + var obj = new Set(); + obj.add(1).add('string').add(false).add({}); + assert.ok(_.isSet(obj), 'but a set is'); + } + }); + + QUnit.test('isWeakSet', function(assert) { + + assert.ok(!_.isWeakSet('string'), 'a string is not a weakset'); + assert.ok(!_.isWeakSet(2), 'a number is not a weakset'); + assert.ok(!_.isWeakSet({}), 'an object is not a weakset'); + assert.ok(!_.isWeakSet(false), 'a boolean is not a weakset'); + assert.ok(!_.isWeakSet(void 0), 'undefined is not a weakset'); + assert.ok(!_.isWeakSet([1, 2, 3]), 'an array is not a weakset'); + if (typeof Map === 'function') { + assert.ok(!_.isWeakSet(new Map()), 'a map is not a weakset'); + } + if (typeof WeakMap === 'function') { + assert.ok(!_.isWeakSet(new WeakMap()), 'a weakmap is not a weakset'); + } + if (typeof Set === 'function') { + assert.ok(!_.isWeakSet(new Set()), 'a set is not a weakset'); + } + if (typeof WeakSet === 'function') { + var obj = new WeakSet(); + obj.add({x: 1}, {y: 'string'}).add({y: 'string'}).add({z: [1, 2, 3]}); + assert.ok(_.isWeakSet(obj), 'but a weakset is'); + } + }); + + QUnit.test('isFunction', function(assert) { + assert.ok(!_.isFunction(void 0), 'undefined vars are not functions'); + assert.ok(!_.isFunction([1, 2, 3]), 'arrays are not functions'); + assert.ok(!_.isFunction('moe'), 'strings are not functions'); + assert.ok(_.isFunction(_.isFunction), 'but functions are'); + assert.ok(_.isFunction(function(){}), 'even anonymous ones'); + + if (testElement) { + assert.ok(!_.isFunction(testElement), 'elements are not functions'); + } + + var nodelist = typeof document != 'undefined' && document.childNodes; + if (nodelist) { + assert.ok(!_.isFunction(nodelist)); + } + }); + + if (typeof Int8Array !== 'undefined') { + QUnit.test('#1929 Typed Array constructors are functions', function(assert) { + _.chain(['Float32Array', 'Float64Array', 'Int8Array', 'Int16Array', 'Int32Array', 'Uint8Array', 'Uint8ClampedArray', 'Uint16Array', 'Uint32Array']) + .map(_.propertyOf(typeof GLOBAL != 'undefined' ? GLOBAL : window)) + .compact() + .each(function(TypedArray) { + // PhantomJS reports `typeof UInt8Array == 'object'` and doesn't report toString TypeArray + // as a function + assert.strictEqual(_.isFunction(TypedArray), Object.prototype.toString.call(TypedArray) === '[object Function]'); + }); + }); + } + + QUnit.test('isDate', function(assert) { + assert.ok(!_.isDate(100), 'numbers are not dates'); + assert.ok(!_.isDate({}), 'objects are not dates'); + assert.ok(_.isDate(new Date()), 'but dates are'); + }); + + QUnit.test('isRegExp', function(assert) { + assert.ok(!_.isRegExp(_.identity), 'functions are not RegExps'); + assert.ok(_.isRegExp(/identity/), 'but RegExps are'); + }); + + QUnit.test('isFinite', function(assert) { + assert.ok(!_.isFinite(void 0), 'undefined is not finite'); + assert.ok(!_.isFinite(null), 'null is not finite'); + assert.ok(!_.isFinite(NaN), 'NaN is not finite'); + assert.ok(!_.isFinite(Infinity), 'Infinity is not finite'); + assert.ok(!_.isFinite(-Infinity), '-Infinity is not finite'); + assert.ok(_.isFinite('12'), 'Numeric strings are numbers'); + assert.ok(!_.isFinite('1a'), 'Non numeric strings are not numbers'); + assert.ok(!_.isFinite(''), 'Empty strings are not numbers'); + var obj = new Number(5); + assert.ok(_.isFinite(obj), 'Number instances can be finite'); + assert.ok(_.isFinite(0), '0 is finite'); + assert.ok(_.isFinite(123), 'Ints are finite'); + assert.ok(_.isFinite(-12.44), 'Floats are finite'); + if (typeof Symbol === 'function') { + assert.ok(!_.isFinite(Symbol()), 'symbols are not numbers'); + assert.ok(!_.isFinite(Symbol('description')), 'described symbols are not numbers'); + assert.ok(!_.isFinite(Object(Symbol())), 'boxed symbols are not numbers'); + } + }); + + QUnit.test('isNaN', function(assert) { + assert.ok(!_.isNaN(void 0), 'undefined is not NaN'); + assert.ok(!_.isNaN(null), 'null is not NaN'); + assert.ok(!_.isNaN(0), '0 is not NaN'); + assert.ok(!_.isNaN(new Number(0)), 'wrapped 0 is not NaN'); + assert.ok(_.isNaN(NaN), 'but NaN is'); + assert.ok(_.isNaN(new Number(NaN)), 'wrapped NaN is still NaN'); + }); + + QUnit.test('isNull', function(assert) { + assert.ok(!_.isNull(void 0), 'undefined is not null'); + assert.ok(!_.isNull(NaN), 'NaN is not null'); + assert.ok(_.isNull(null), 'but null is'); + }); + + QUnit.test('isUndefined', function(assert) { + assert.ok(!_.isUndefined(1), 'numbers are defined'); + assert.ok(!_.isUndefined(null), 'null is defined'); + assert.ok(!_.isUndefined(false), 'false is defined'); + assert.ok(!_.isUndefined(NaN), 'NaN is defined'); + assert.ok(_.isUndefined(), 'nothing is undefined'); + assert.ok(_.isUndefined(void 0), 'undefined is undefined'); + }); + + QUnit.test('isError', function(assert) { + assert.ok(!_.isError(1), 'numbers are not Errors'); + assert.ok(!_.isError(null), 'null is not an Error'); + assert.ok(!_.isError(Error), 'functions are not Errors'); + assert.ok(_.isError(new Error()), 'Errors are Errors'); + assert.ok(_.isError(new EvalError()), 'EvalErrors are Errors'); + assert.ok(_.isError(new RangeError()), 'RangeErrors are Errors'); + assert.ok(_.isError(new ReferenceError()), 'ReferenceErrors are Errors'); + assert.ok(_.isError(new SyntaxError()), 'SyntaxErrors are Errors'); + assert.ok(_.isError(new TypeError()), 'TypeErrors are Errors'); + assert.ok(_.isError(new URIError()), 'URIErrors are Errors'); + }); + + QUnit.test('tap', function(assert) { + var intercepted = null; + var interceptor = function(obj) { intercepted = obj; }; + var returned = _.tap(1, interceptor); + assert.equal(intercepted, 1, 'passes tapped object to interceptor'); + assert.equal(returned, 1, 'returns tapped object'); + + returned = _([1, 2, 3]).chain(). + map(function(n){ return n * 2; }). + max(). + tap(interceptor). + value(); + assert.equal(returned, 6, 'can use tapped objects in a chain'); + assert.equal(intercepted, returned, 'can use tapped objects in a chain'); + }); + + QUnit.test('has', function(assert) { + var obj = {foo: 'bar', func: function(){}}; + assert.ok(_.has(obj, 'foo'), 'has() checks that the object has a property.'); + assert.ok(!_.has(obj, 'baz'), "has() returns false if the object doesn't have the property."); + assert.ok(_.has(obj, 'func'), 'has() works for functions too.'); + obj.hasOwnProperty = null; + assert.ok(_.has(obj, 'foo'), 'has() works even when the hasOwnProperty method is deleted.'); + var child = {}; + child.prototype = obj; + assert.ok(!_.has(child, 'foo'), 'has() does not check the prototype chain for a property.'); + assert.strictEqual(_.has(null, 'foo'), false, 'has() returns false for null'); + assert.strictEqual(_.has(void 0, 'foo'), false, 'has() returns false for undefined'); + }); + + QUnit.test('isMatch', function(assert) { + var moe = {name: 'Moe Howard', hair: true}; + var curly = {name: 'Curly Howard', hair: false}; + + assert.equal(_.isMatch(moe, {hair: true}), true, 'Returns a boolean'); + assert.equal(_.isMatch(curly, {hair: true}), false, 'Returns a boolean'); + + assert.equal(_.isMatch(5, {__x__: void 0}), false, 'can match undefined props on primitives'); + assert.equal(_.isMatch({__x__: void 0}, {__x__: void 0}), true, 'can match undefined props'); + + assert.equal(_.isMatch(null, {}), true, 'Empty spec called with null object returns true'); + assert.equal(_.isMatch(null, {a: 1}), false, 'Non-empty spec called with null object returns false'); + + _.each([null, void 0], function(item) { assert.strictEqual(_.isMatch(item, null), true, 'null matches null'); }); + _.each([null, void 0], function(item) { assert.strictEqual(_.isMatch(item, null), true, 'null matches {}'); }); + assert.strictEqual(_.isMatch({b: 1}, {a: void 0}), false, 'handles undefined values (1683)'); + + _.each([true, 5, NaN, null, void 0], function(item) { + assert.strictEqual(_.isMatch({a: 1}, item), true, 'treats primitives as empty'); + }); + + function Prototest() {} + Prototest.prototype.x = 1; + var specObj = new Prototest; + assert.equal(_.isMatch({x: 2}, specObj), true, 'spec is restricted to own properties'); + + specObj.y = 5; + assert.equal(_.isMatch({x: 1, y: 5}, specObj), true); + assert.equal(_.isMatch({x: 1, y: 4}, specObj), false); + + assert.ok(_.isMatch(specObj, {x: 1, y: 5}), 'inherited and own properties are checked on the test object'); + + Prototest.x = 5; + assert.ok(_.isMatch({x: 5, y: 1}, Prototest), 'spec can be a function'); + + //null edge cases + var oCon = {constructor: Object}; + assert.deepEqual(_.map([null, void 0, 5, {}], _.partial(_.isMatch, _, oCon)), [false, false, false, true], 'doesnt falsey match constructor on undefined/null'); + }); + + QUnit.test('matcher', function(assert) { + var moe = {name: 'Moe Howard', hair: true}; + var curly = {name: 'Curly Howard', hair: false}; + var stooges = [moe, curly]; + + assert.equal(_.matcher({hair: true})(moe), true, 'Returns a boolean'); + assert.equal(_.matcher({hair: true})(curly), false, 'Returns a boolean'); + + assert.equal(_.matcher({__x__: void 0})(5), false, 'can match undefined props on primitives'); + assert.equal(_.matcher({__x__: void 0})({__x__: void 0}), true, 'can match undefined props'); + + assert.equal(_.matcher({})(null), true, 'Empty spec called with null object returns true'); + assert.equal(_.matcher({a: 1})(null), false, 'Non-empty spec called with null object returns false'); + + assert.ok(_.find(stooges, _.matcher({hair: false})) === curly, 'returns a predicate that can be used by finding functions.'); + assert.ok(_.find(stooges, _.matcher(moe)) === moe, 'can be used to locate an object exists in a collection.'); + assert.deepEqual(_.filter([null, void 0], _.matcher({a: 1})), [], 'Do not throw on null values.'); + + assert.deepEqual(_.filter([null, void 0], _.matcher(null)), [null, void 0], 'null matches null'); + assert.deepEqual(_.filter([null, void 0], _.matcher({})), [null, void 0], 'null matches {}'); + assert.deepEqual(_.filter([{b: 1}], _.matcher({a: void 0})), [], 'handles undefined values (1683)'); + + _.each([true, 5, NaN, null, void 0], function(item) { + assert.equal(_.matcher(item)({a: 1}), true, 'treats primitives as empty'); + }); + + function Prototest() {} + Prototest.prototype.x = 1; + var specObj = new Prototest; + var protospec = _.matcher(specObj); + assert.equal(protospec({x: 2}), true, 'spec is restricted to own properties'); + + specObj.y = 5; + protospec = _.matcher(specObj); + assert.equal(protospec({x: 1, y: 5}), true); + assert.equal(protospec({x: 1, y: 4}), false); + + assert.ok(_.matcher({x: 1, y: 5})(specObj), 'inherited and own properties are checked on the test object'); + + Prototest.x = 5; + assert.ok(_.matcher(Prototest)({x: 5, y: 1}), 'spec can be a function'); + + // #1729 + var o = {b: 1}; + var m = _.matcher(o); + + assert.equal(m({b: 1}), true); + o.b = 2; + o.a = 1; + assert.equal(m({b: 1}), true, 'changing spec object doesnt change matches result'); + + + //null edge cases + var oCon = _.matcher({constructor: Object}); + assert.deepEqual(_.map([null, void 0, 5, {}], oCon), [false, false, false, true], 'doesnt falsey match constructor on undefined/null'); + }); + + QUnit.test('matches', function(assert) { + assert.strictEqual(_.matches, _.matcher, 'is an alias for matcher'); + }); + + QUnit.test('findKey', function(assert) { + var objects = { + a: {a: 0, b: 0}, + b: {a: 1, b: 1}, + c: {a: 2, b: 2} + }; + + assert.equal(_.findKey(objects, function(obj) { + return obj.a === 0; + }), 'a'); + + assert.equal(_.findKey(objects, function(obj) { + return obj.b * obj.a === 4; + }), 'c'); + + assert.equal(_.findKey(objects, 'a'), 'b', 'Uses lookupIterator'); + + assert.equal(_.findKey(objects, function(obj) { + return obj.b * obj.a === 5; + }), void 0); + + assert.strictEqual(_.findKey([1, 2, 3, 4, 5, 6], function(obj) { + return obj === 3; + }), '2', 'Keys are strings'); + + assert.strictEqual(_.findKey(objects, function(a) { + return a.foo === null; + }), void 0); + + _.findKey({a: {a: 1}}, function(a, key, obj) { + assert.equal(key, 'a'); + assert.deepEqual(obj, {a: {a: 1}}); + assert.strictEqual(this, objects, 'called with context'); + }, objects); + + var array = [1, 2, 3, 4]; + array.match = 55; + assert.strictEqual(_.findKey(array, function(x) { return x === 55; }), 'match', 'matches array-likes keys'); + }); + + + QUnit.test('mapObject', function(assert) { + var obj = {a: 1, b: 2}; + var objects = { + a: {a: 0, b: 0}, + b: {a: 1, b: 1}, + c: {a: 2, b: 2} + }; + + assert.deepEqual(_.mapObject(obj, function(val) { + return val * 2; + }), {a: 2, b: 4}, 'simple objects'); + + assert.deepEqual(_.mapObject(objects, function(val) { + return _.reduce(val, function(memo, v){ + return memo + v; + }, 0); + }), {a: 0, b: 2, c: 4}, 'nested objects'); + + assert.deepEqual(_.mapObject(obj, function(val, key, o) { + return o[key] * 2; + }), {a: 2, b: 4}, 'correct keys'); + + assert.deepEqual(_.mapObject([1, 2], function(val) { + return val * 2; + }), {0: 2, 1: 4}, 'check behavior for arrays'); + + assert.deepEqual(_.mapObject(obj, function(val) { + return val * this.multiplier; + }, {multiplier: 3}), {a: 3, b: 6}, 'keep context'); + + assert.deepEqual(_.mapObject({a: 1}, function() { + return this.length; + }, [1, 2]), {a: 2}, 'called with context'); + + var ids = _.mapObject({length: 2, 0: {id: '1'}, 1: {id: '2'}}, function(n){ + return n.id; + }); + assert.deepEqual(ids, {length: void 0, 0: '1', 1: '2'}, 'Check with array-like objects'); + + // Passing a property name like _.pluck. + var people = {a: {name: 'moe', age: 30}, b: {name: 'curly', age: 50}}; + assert.deepEqual(_.mapObject(people, 'name'), {a: 'moe', b: 'curly'}, 'predicate string map to object properties'); + + _.each([null, void 0, 1, 'abc', [], {}, void 0], function(val){ + assert.deepEqual(_.mapObject(val, _.identity), {}, 'mapValue identity'); + }); + + var Proto = function(){ this.a = 1; }; + Proto.prototype.b = 1; + var protoObj = new Proto(); + assert.deepEqual(_.mapObject(protoObj, _.identity), {a: 1}, 'ignore inherited values from prototypes'); + + }); +}()); diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/underscore/test/utility.js b/ecomp-portal-FE/client/bower_components/lodash/vendor/underscore/test/utility.js new file mode 100644 index 00000000..fbd54df3 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/underscore/test/utility.js @@ -0,0 +1,420 @@ +(function() { + var _ = typeof require == 'function' ? require('..') : window._; + var templateSettings; + + QUnit.module('Utility', { + + beforeEach: function() { + templateSettings = _.clone(_.templateSettings); + }, + + afterEach: function() { + _.templateSettings = templateSettings; + } + + }); + + if (typeof this == 'object') { + QUnit.test('noConflict', function(assert) { + var underscore = _.noConflict(); + assert.equal(underscore.identity(1), 1); + if (typeof require != 'function') { + assert.equal(this._, void 0, 'global underscore is removed'); + this._ = underscore; + } else if (typeof global !== 'undefined') { + delete global._; + } + }); + } + + if (typeof require == 'function') { + QUnit.test('noConflict (node vm)', function(assert) { + assert.expect(2); + var done = assert.async(); + var fs = require('fs'); + var vm = require('vm'); + var filename = __dirname + '/../underscore.js'; + fs.readFile(filename, function(err, content){ + var sandbox = vm.createScript( + content + 'this.underscore = this._.noConflict();', + filename + ); + var context = {_: 'oldvalue'}; + sandbox.runInNewContext(context); + assert.equal(context._, 'oldvalue'); + assert.equal(context.underscore.VERSION, _.VERSION); + + done(); + }); + }); + } + + QUnit.test('#750 - Return _ instance.', function(assert) { + assert.expect(2); + var instance = _([]); + assert.ok(_(instance) === instance); + assert.ok(new _(instance) === instance); + }); + + QUnit.test('identity', function(assert) { + var stooge = {name: 'moe'}; + assert.equal(_.identity(stooge), stooge, 'stooge is the same as his identity'); + }); + + QUnit.test('constant', function(assert) { + var stooge = {name: 'moe'}; + assert.equal(_.constant(stooge)(), stooge, 'should create a function that returns stooge'); + }); + + QUnit.test('noop', function(assert) { + assert.strictEqual(_.noop('curly', 'larry', 'moe'), void 0, 'should always return undefined'); + }); + + QUnit.test('property', function(assert) { + var stooge = {name: 'moe'}; + assert.equal(_.property('name')(stooge), 'moe', 'should return the property with the given name'); + assert.equal(_.property('name')(null), void 0, 'should return undefined for null values'); + assert.equal(_.property('name')(void 0), void 0, 'should return undefined for undefined values'); + }); + + QUnit.test('propertyOf', function(assert) { + var stoogeRanks = _.propertyOf({curly: 2, moe: 1, larry: 3}); + assert.equal(stoogeRanks('curly'), 2, 'should return the property with the given name'); + assert.equal(stoogeRanks(null), void 0, 'should return undefined for null values'); + assert.equal(stoogeRanks(void 0), void 0, 'should return undefined for undefined values'); + + function MoreStooges() { this.shemp = 87; } + MoreStooges.prototype = {curly: 2, moe: 1, larry: 3}; + var moreStoogeRanks = _.propertyOf(new MoreStooges()); + assert.equal(moreStoogeRanks('curly'), 2, 'should return properties from further up the prototype chain'); + + var nullPropertyOf = _.propertyOf(null); + assert.equal(nullPropertyOf('curly'), void 0, 'should return undefined when obj is null'); + + var undefPropertyOf = _.propertyOf(void 0); + assert.equal(undefPropertyOf('curly'), void 0, 'should return undefined when obj is undefined'); + }); + + QUnit.test('random', function(assert) { + var array = _.range(1000); + var min = Math.pow(2, 31); + var max = Math.pow(2, 62); + + assert.ok(_.every(array, function() { + return _.random(min, max) >= min; + }), 'should produce a random number greater than or equal to the minimum number'); + + assert.ok(_.some(array, function() { + return _.random(Number.MAX_VALUE) > 0; + }), 'should produce a random number when passed `Number.MAX_VALUE`'); + }); + + QUnit.test('now', function(assert) { + var diff = _.now() - new Date().getTime(); + assert.ok(diff <= 0 && diff > -5, 'Produces the correct time in milliseconds');//within 5ms + }); + + QUnit.test('uniqueId', function(assert) { + var ids = [], i = 0; + while (i++ < 100) ids.push(_.uniqueId()); + assert.equal(_.uniq(ids).length, ids.length, 'can generate a globally-unique stream of ids'); + }); + + QUnit.test('times', function(assert) { + var vals = []; + _.times(3, function(i) { vals.push(i); }); + assert.deepEqual(vals, [0, 1, 2], 'is 0 indexed'); + // + vals = []; + _(3).times(function(i) { vals.push(i); }); + assert.deepEqual(vals, [0, 1, 2], 'works as a wrapper'); + // collects return values + assert.deepEqual([0, 1, 2], _.times(3, function(i) { return i; }), 'collects return values'); + + assert.deepEqual(_.times(0, _.identity), []); + assert.deepEqual(_.times(-1, _.identity), []); + assert.deepEqual(_.times(parseFloat('-Infinity'), _.identity), []); + }); + + QUnit.test('mixin', function(assert) { + _.mixin({ + myReverse: function(string) { + return string.split('').reverse().join(''); + } + }); + assert.equal(_.myReverse('panacea'), 'aecanap', 'mixed in a function to _'); + assert.equal(_('champ').myReverse(), 'pmahc', 'mixed in a function to the OOP wrapper'); + }); + + QUnit.test('_.escape', function(assert) { + assert.equal(_.escape(null), ''); + }); + + QUnit.test('_.unescape', function(assert) { + var string = 'Curly & Moe'; + assert.equal(_.unescape(null), ''); + assert.equal(_.unescape(_.escape(string)), string); + assert.equal(_.unescape(string), string, 'don\'t unescape unnecessarily'); + }); + + // Don't care what they escape them to just that they're escaped and can be unescaped + QUnit.test('_.escape & unescape', function(assert) { + // test & (&) seperately obviously + var escapeCharacters = ['<', '>', '"', '\'', '`']; + + _.each(escapeCharacters, function(escapeChar) { + var s = 'a ' + escapeChar + ' string escaped'; + var e = _.escape(s); + assert.notEqual(s, e, escapeChar + ' is escaped'); + assert.equal(s, _.unescape(e), escapeChar + ' can be unescaped'); + + s = 'a ' + escapeChar + escapeChar + escapeChar + 'some more string' + escapeChar; + e = _.escape(s); + + assert.equal(e.indexOf(escapeChar), -1, 'can escape multiple occurances of ' + escapeChar); + assert.equal(_.unescape(e), s, 'multiple occurrences of ' + escapeChar + ' can be unescaped'); + }); + + // handles multiple escape characters at once + var joiner = ' other stuff '; + var allEscaped = escapeCharacters.join(joiner); + allEscaped += allEscaped; + assert.ok(_.every(escapeCharacters, function(escapeChar) { + return allEscaped.indexOf(escapeChar) !== -1; + }), 'handles multiple characters'); + assert.ok(allEscaped.indexOf(joiner) >= 0, 'can escape multiple escape characters at the same time'); + + // test & -> & + var str = 'some string & another string & yet another'; + var escaped = _.escape(str); + + assert.ok(escaped.indexOf('&') !== -1, 'handles & aka &'); + assert.equal(_.unescape(str), str, 'can unescape &'); + }); + + QUnit.test('template', function(assert) { + var basicTemplate = _.template("<%= thing %> is gettin' on my noives!"); + var result = basicTemplate({thing: 'This'}); + assert.equal(result, "This is gettin' on my noives!", 'can do basic attribute interpolation'); + + var sansSemicolonTemplate = _.template('A <% this %> B'); + assert.equal(sansSemicolonTemplate(), 'A B'); + + var backslashTemplate = _.template('<%= thing %> is \\ridanculous'); + assert.equal(backslashTemplate({thing: 'This'}), 'This is \\ridanculous'); + + var escapeTemplate = _.template('<%= a ? "checked=\\"checked\\"" : "" %>'); + assert.equal(escapeTemplate({a: true}), 'checked="checked"', 'can handle slash escapes in interpolations.'); + + var fancyTemplate = _.template('<ul><% ' + + ' for (var key in people) { ' + + '%><li><%= people[key] %></li><% } %></ul>'); + result = fancyTemplate({people: {moe: 'Moe', larry: 'Larry', curly: 'Curly'}}); + assert.equal(result, '<ul><li>Moe</li><li>Larry</li><li>Curly</li></ul>', 'can run arbitrary javascript in templates'); + + var escapedCharsInJavascriptTemplate = _.template('<ul><% _.each(numbers.split("\\n"), function(item) { %><li><%= item %></li><% }) %></ul>'); + result = escapedCharsInJavascriptTemplate({numbers: 'one\ntwo\nthree\nfour'}); + assert.equal(result, '<ul><li>one</li><li>two</li><li>three</li><li>four</li></ul>', 'Can use escaped characters (e.g. \\n) in JavaScript'); + + var namespaceCollisionTemplate = _.template('<%= pageCount %> <%= thumbnails[pageCount] %> <% _.each(thumbnails, function(p) { %><div class="thumbnail" rel="<%= p %>"></div><% }); %>'); + result = namespaceCollisionTemplate({ + pageCount: 3, + thumbnails: { + 1: 'p1-thumbnail.gif', + 2: 'p2-thumbnail.gif', + 3: 'p3-thumbnail.gif' + } + }); + assert.equal(result, '3 p3-thumbnail.gif <div class="thumbnail" rel="p1-thumbnail.gif"></div><div class="thumbnail" rel="p2-thumbnail.gif"></div><div class="thumbnail" rel="p3-thumbnail.gif"></div>'); + + var noInterpolateTemplate = _.template('<div><p>Just some text. Hey, I know this is silly but it aids consistency.</p></div>'); + result = noInterpolateTemplate(); + assert.equal(result, '<div><p>Just some text. Hey, I know this is silly but it aids consistency.</p></div>'); + + var quoteTemplate = _.template("It's its, not it's"); + assert.equal(quoteTemplate({}), "It's its, not it's"); + + var quoteInStatementAndBody = _.template('<% ' + + " if(foo == 'bar'){ " + + "%>Statement quotes and 'quotes'.<% } %>"); + assert.equal(quoteInStatementAndBody({foo: 'bar'}), "Statement quotes and 'quotes'."); + + var withNewlinesAndTabs = _.template('This\n\t\tis: <%= x %>.\n\tok.\nend.'); + assert.equal(withNewlinesAndTabs({x: 'that'}), 'This\n\t\tis: that.\n\tok.\nend.'); + + var template = _.template('<i><%- value %></i>'); + result = template({value: '<script>'}); + assert.equal(result, '<i><script></i>'); + + var stooge = { + name: 'Moe', + template: _.template("I'm <%= this.name %>") + }; + assert.equal(stooge.template(), "I'm Moe"); + + template = _.template('\n ' + + ' <%\n ' + + ' // a comment\n ' + + ' if (data) { data += 12345; }; %>\n ' + + ' <li><%= data %></li>\n ' + ); + assert.equal(template({data: 12345}).replace(/\s/g, ''), '<li>24690</li>'); + + _.templateSettings = { + evaluate: /\{\{([\s\S]+?)\}\}/g, + interpolate: /\{\{=([\s\S]+?)\}\}/g + }; + + var custom = _.template('<ul>{{ for (var key in people) { }}<li>{{= people[key] }}</li>{{ } }}</ul>'); + result = custom({people: {moe: 'Moe', larry: 'Larry', curly: 'Curly'}}); + assert.equal(result, '<ul><li>Moe</li><li>Larry</li><li>Curly</li></ul>', 'can run arbitrary javascript in templates'); + + var customQuote = _.template("It's its, not it's"); + assert.equal(customQuote({}), "It's its, not it's"); + + quoteInStatementAndBody = _.template("{{ if(foo == 'bar'){ }}Statement quotes and 'quotes'.{{ } }}"); + assert.equal(quoteInStatementAndBody({foo: 'bar'}), "Statement quotes and 'quotes'."); + + _.templateSettings = { + evaluate: /<\?([\s\S]+?)\?>/g, + interpolate: /<\?=([\s\S]+?)\?>/g + }; + + var customWithSpecialChars = _.template('<ul><? for (var key in people) { ?><li><?= people[key] ?></li><? } ?></ul>'); + result = customWithSpecialChars({people: {moe: 'Moe', larry: 'Larry', curly: 'Curly'}}); + assert.equal(result, '<ul><li>Moe</li><li>Larry</li><li>Curly</li></ul>', 'can run arbitrary javascript in templates'); + + var customWithSpecialCharsQuote = _.template("It's its, not it's"); + assert.equal(customWithSpecialCharsQuote({}), "It's its, not it's"); + + quoteInStatementAndBody = _.template("<? if(foo == 'bar'){ ?>Statement quotes and 'quotes'.<? } ?>"); + assert.equal(quoteInStatementAndBody({foo: 'bar'}), "Statement quotes and 'quotes'."); + + _.templateSettings = { + interpolate: /\{\{(.+?)\}\}/g + }; + + var mustache = _.template('Hello {{planet}}!'); + assert.equal(mustache({planet: 'World'}), 'Hello World!', 'can mimic mustache.js'); + + var templateWithNull = _.template('a null undefined {{planet}}'); + assert.equal(templateWithNull({planet: 'world'}), 'a null undefined world', 'can handle missing escape and evaluate settings'); + }); + + QUnit.test('_.template provides the generated function source, when a SyntaxError occurs', function(assert) { + var source; + try { + _.template('<b><%= if x %></b>'); + } catch (ex) { + source = ex.source; + } + assert.ok(/__p/.test(source)); + }); + + QUnit.test('_.template handles \\u2028 & \\u2029', function(assert) { + var tmpl = _.template('<p>\u2028<%= "\\u2028\\u2029" %>\u2029</p>'); + assert.strictEqual(tmpl(), '<p>\u2028\u2028\u2029\u2029</p>'); + }); + + QUnit.test('result calls functions and returns primitives', function(assert) { + var obj = {w: '', x: 'x', y: function(){ return this.x; }}; + assert.strictEqual(_.result(obj, 'w'), ''); + assert.strictEqual(_.result(obj, 'x'), 'x'); + assert.strictEqual(_.result(obj, 'y'), 'x'); + assert.strictEqual(_.result(obj, 'z'), void 0); + assert.strictEqual(_.result(null, 'x'), void 0); + }); + + QUnit.test('result returns a default value if object is null or undefined', function(assert) { + assert.strictEqual(_.result(null, 'b', 'default'), 'default'); + assert.strictEqual(_.result(void 0, 'c', 'default'), 'default'); + assert.strictEqual(_.result(''.match('missing'), 1, 'default'), 'default'); + }); + + QUnit.test('result returns a default value if property of object is missing', function(assert) { + assert.strictEqual(_.result({d: null}, 'd', 'default'), null); + assert.strictEqual(_.result({e: false}, 'e', 'default'), false); + }); + + QUnit.test('result only returns the default value if the object does not have the property or is undefined', function(assert) { + assert.strictEqual(_.result({}, 'b', 'default'), 'default'); + assert.strictEqual(_.result({d: void 0}, 'd', 'default'), 'default'); + }); + + QUnit.test('result does not return the default if the property of an object is found in the prototype', function(assert) { + var Foo = function(){}; + Foo.prototype.bar = 1; + assert.strictEqual(_.result(new Foo, 'bar', 2), 1); + }); + + QUnit.test('result does use the fallback when the result of invoking the property is undefined', function(assert) { + var obj = {a: function() {}}; + assert.strictEqual(_.result(obj, 'a', 'failed'), void 0); + }); + + QUnit.test('result fallback can use a function', function(assert) { + var obj = {a: [1, 2, 3]}; + assert.strictEqual(_.result(obj, 'b', _.constant(5)), 5); + assert.strictEqual(_.result(obj, 'b', function() { + return this.a; + }), obj.a, 'called with context'); + }); + + QUnit.test('_.templateSettings.variable', function(assert) { + var s = '<%=data.x%>'; + var data = {x: 'x'}; + var tmp = _.template(s, {variable: 'data'}); + assert.strictEqual(tmp(data), 'x'); + _.templateSettings.variable = 'data'; + assert.strictEqual(_.template(s)(data), 'x'); + }); + + QUnit.test('#547 - _.templateSettings is unchanged by custom settings.', function(assert) { + assert.ok(!_.templateSettings.variable); + _.template('', {}, {variable: 'x'}); + assert.ok(!_.templateSettings.variable); + }); + + QUnit.test('#556 - undefined template variables.', function(assert) { + var template = _.template('<%=x%>'); + assert.strictEqual(template({x: null}), ''); + assert.strictEqual(template({x: void 0}), ''); + + var templateEscaped = _.template('<%-x%>'); + assert.strictEqual(templateEscaped({x: null}), ''); + assert.strictEqual(templateEscaped({x: void 0}), ''); + + var templateWithProperty = _.template('<%=x.foo%>'); + assert.strictEqual(templateWithProperty({x: {}}), ''); + assert.strictEqual(templateWithProperty({x: {}}), ''); + + var templateWithPropertyEscaped = _.template('<%-x.foo%>'); + assert.strictEqual(templateWithPropertyEscaped({x: {}}), ''); + assert.strictEqual(templateWithPropertyEscaped({x: {}}), ''); + }); + + QUnit.test('interpolate evaluates code only once.', function(assert) { + assert.expect(2); + var count = 0; + var template = _.template('<%= f() %>'); + template({f: function(){ assert.ok(!count++); }}); + + var countEscaped = 0; + var templateEscaped = _.template('<%- f() %>'); + templateEscaped({f: function(){ assert.ok(!countEscaped++); }}); + }); + + QUnit.test('#746 - _.template settings are not modified.', function(assert) { + assert.expect(1); + var settings = {}; + _.template('', null, settings); + assert.deepEqual(settings, {}); + }); + + QUnit.test('#779 - delimeters are applied to unescaped text.', function(assert) { + assert.expect(1); + var template = _.template('<<\nx\n>>', null, {evaluate: /<<(.*?)>>/g}); + assert.strictEqual(template(), '<<\nx\n>>'); + }); + +}()); diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/underscore/underscore-min.js b/ecomp-portal-FE/client/bower_components/lodash/vendor/underscore/underscore-min.js new file mode 100644 index 00000000..f01025b7 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/underscore/underscore-min.js @@ -0,0 +1,6 @@ +// Underscore.js 1.8.3 +// http://underscorejs.org +// (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors +// Underscore may be freely distributed under the MIT license. +(function(){function n(n){function t(t,r,e,u,i,o){for(;i>=0&&o>i;i+=n){var a=u?u[i]:i;e=r(e,t[a],a,t)}return e}return function(r,e,u,i){e=b(e,i,4);var o=!k(r)&&m.keys(r),a=(o||r).length,c=n>0?0:a-1;return arguments.length<3&&(u=r[o?o[c]:c],c+=n),t(r,e,u,o,c,a)}}function t(n){return function(t,r,e){r=x(r,e);for(var u=O(t),i=n>0?0:u-1;i>=0&&u>i;i+=n)if(r(t[i],i,t))return i;return-1}}function r(n,t,r){return function(e,u,i){var o=0,a=O(e);if("number"==typeof i)n>0?o=i>=0?i:Math.max(i+a,o):a=i>=0?Math.min(i+1,a):i+a+1;else if(r&&i&&a)return i=r(e,u),e[i]===u?i:-1;if(u!==u)return i=t(l.call(e,o,a),m.isNaN),i>=0?i+o:-1;for(i=n>0?o:a-1;i>=0&&a>i;i+=n)if(e[i]===u)return i;return-1}}function e(n,t){var r=I.length,e=n.constructor,u=m.isFunction(e)&&e.prototype||a,i="constructor";for(m.has(n,i)&&!m.contains(t,i)&&t.push(i);r--;)i=I[r],i in n&&n[i]!==u[i]&&!m.contains(t,i)&&t.push(i)}var u=this,i=u._,o=Array.prototype,a=Object.prototype,c=Function.prototype,f=o.push,l=o.slice,s=a.toString,p=a.hasOwnProperty,h=Array.isArray,v=Object.keys,g=c.bind,y=Object.create,d=function(){},m=function(n){return n instanceof m?n:this instanceof m?void(this._wrapped=n):new m(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=m),exports._=m):u._=m,m.VERSION="1.8.3";var b=function(n,t,r){if(t===void 0)return n;switch(null==r?3:r){case 1:return function(r){return n.call(t,r)};case 2:return function(r,e){return n.call(t,r,e)};case 3:return function(r,e,u){return n.call(t,r,e,u)};case 4:return function(r,e,u,i){return n.call(t,r,e,u,i)}}return function(){return n.apply(t,arguments)}},x=function(n,t,r){return null==n?m.identity:m.isFunction(n)?b(n,t,r):m.isObject(n)?m.matcher(n):m.property(n)};m.iteratee=function(n,t){return x(n,t,1/0)};var _=function(n,t){return function(r){var e=arguments.length;if(2>e||null==r)return r;for(var u=1;e>u;u++)for(var i=arguments[u],o=n(i),a=o.length,c=0;a>c;c++){var f=o[c];t&&r[f]!==void 0||(r[f]=i[f])}return r}},j=function(n){if(!m.isObject(n))return{};if(y)return y(n);d.prototype=n;var t=new d;return d.prototype=null,t},w=function(n){return function(t){return null==t?void 0:t[n]}},A=Math.pow(2,53)-1,O=w("length"),k=function(n){var t=O(n);return"number"==typeof t&&t>=0&&A>=t};m.each=m.forEach=function(n,t,r){t=b(t,r);var e,u;if(k(n))for(e=0,u=n.length;u>e;e++)t(n[e],e,n);else{var i=m.keys(n);for(e=0,u=i.length;u>e;e++)t(n[i[e]],i[e],n)}return n},m.map=m.collect=function(n,t,r){t=x(t,r);for(var e=!k(n)&&m.keys(n),u=(e||n).length,i=Array(u),o=0;u>o;o++){var a=e?e[o]:o;i[o]=t(n[a],a,n)}return i},m.reduce=m.foldl=m.inject=n(1),m.reduceRight=m.foldr=n(-1),m.find=m.detect=function(n,t,r){var e;return e=k(n)?m.findIndex(n,t,r):m.findKey(n,t,r),e!==void 0&&e!==-1?n[e]:void 0},m.filter=m.select=function(n,t,r){var e=[];return t=x(t,r),m.each(n,function(n,r,u){t(n,r,u)&&e.push(n)}),e},m.reject=function(n,t,r){return m.filter(n,m.negate(x(t)),r)},m.every=m.all=function(n,t,r){t=x(t,r);for(var e=!k(n)&&m.keys(n),u=(e||n).length,i=0;u>i;i++){var o=e?e[i]:i;if(!t(n[o],o,n))return!1}return!0},m.some=m.any=function(n,t,r){t=x(t,r);for(var e=!k(n)&&m.keys(n),u=(e||n).length,i=0;u>i;i++){var o=e?e[i]:i;if(t(n[o],o,n))return!0}return!1},m.contains=m.includes=m.include=function(n,t,r,e){return k(n)||(n=m.values(n)),("number"!=typeof r||e)&&(r=0),m.indexOf(n,t,r)>=0},m.invoke=function(n,t){var r=l.call(arguments,2),e=m.isFunction(t);return m.map(n,function(n){var u=e?t:n[t];return null==u?u:u.apply(n,r)})},m.pluck=function(n,t){return m.map(n,m.property(t))},m.where=function(n,t){return m.filter(n,m.matcher(t))},m.findWhere=function(n,t){return m.find(n,m.matcher(t))},m.max=function(n,t,r){var e,u,i=-1/0,o=-1/0;if(null==t&&null!=n){n=k(n)?n:m.values(n);for(var a=0,c=n.length;c>a;a++)e=n[a],e>i&&(i=e)}else t=x(t,r),m.each(n,function(n,r,e){u=t(n,r,e),(u>o||u===-1/0&&i===-1/0)&&(i=n,o=u)});return i},m.min=function(n,t,r){var e,u,i=1/0,o=1/0;if(null==t&&null!=n){n=k(n)?n:m.values(n);for(var a=0,c=n.length;c>a;a++)e=n[a],i>e&&(i=e)}else t=x(t,r),m.each(n,function(n,r,e){u=t(n,r,e),(o>u||1/0===u&&1/0===i)&&(i=n,o=u)});return i},m.shuffle=function(n){for(var t,r=k(n)?n:m.values(n),e=r.length,u=Array(e),i=0;e>i;i++)t=m.random(0,i),t!==i&&(u[i]=u[t]),u[t]=r[i];return u},m.sample=function(n,t,r){return null==t||r?(k(n)||(n=m.values(n)),n[m.random(n.length-1)]):m.shuffle(n).slice(0,Math.max(0,t))},m.sortBy=function(n,t,r){return t=x(t,r),m.pluck(m.map(n,function(n,r,e){return{value:n,index:r,criteria:t(n,r,e)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.index-t.index}),"value")};var F=function(n){return function(t,r,e){var u={};return r=x(r,e),m.each(t,function(e,i){var o=r(e,i,t);n(u,e,o)}),u}};m.groupBy=F(function(n,t,r){m.has(n,r)?n[r].push(t):n[r]=[t]}),m.indexBy=F(function(n,t,r){n[r]=t}),m.countBy=F(function(n,t,r){m.has(n,r)?n[r]++:n[r]=1}),m.toArray=function(n){return n?m.isArray(n)?l.call(n):k(n)?m.map(n,m.identity):m.values(n):[]},m.size=function(n){return null==n?0:k(n)?n.length:m.keys(n).length},m.partition=function(n,t,r){t=x(t,r);var e=[],u=[];return m.each(n,function(n,r,i){(t(n,r,i)?e:u).push(n)}),[e,u]},m.first=m.head=m.take=function(n,t,r){return null==n?void 0:null==t||r?n[0]:m.initial(n,n.length-t)},m.initial=function(n,t,r){return l.call(n,0,Math.max(0,n.length-(null==t||r?1:t)))},m.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:m.rest(n,Math.max(0,n.length-t))},m.rest=m.tail=m.drop=function(n,t,r){return l.call(n,null==t||r?1:t)},m.compact=function(n){return m.filter(n,m.identity)};var S=function(n,t,r,e){for(var u=[],i=0,o=e||0,a=O(n);a>o;o++){var c=n[o];if(k(c)&&(m.isArray(c)||m.isArguments(c))){t||(c=S(c,t,r));var f=0,l=c.length;for(u.length+=l;l>f;)u[i++]=c[f++]}else r||(u[i++]=c)}return u};m.flatten=function(n,t){return S(n,t,!1)},m.without=function(n){return m.difference(n,l.call(arguments,1))},m.uniq=m.unique=function(n,t,r,e){m.isBoolean(t)||(e=r,r=t,t=!1),null!=r&&(r=x(r,e));for(var u=[],i=[],o=0,a=O(n);a>o;o++){var c=n[o],f=r?r(c,o,n):c;t?(o&&i===f||u.push(c),i=f):r?m.contains(i,f)||(i.push(f),u.push(c)):m.contains(u,c)||u.push(c)}return u},m.union=function(){return m.uniq(S(arguments,!0,!0))},m.intersection=function(n){for(var t=[],r=arguments.length,e=0,u=O(n);u>e;e++){var i=n[e];if(!m.contains(t,i)){for(var o=1;r>o&&m.contains(arguments[o],i);o++);o===r&&t.push(i)}}return t},m.difference=function(n){var t=S(arguments,!0,!0,1);return m.filter(n,function(n){return!m.contains(t,n)})},m.zip=function(){return m.unzip(arguments)},m.unzip=function(n){for(var t=n&&m.max(n,O).length||0,r=Array(t),e=0;t>e;e++)r[e]=m.pluck(n,e);return r},m.object=function(n,t){for(var r={},e=0,u=O(n);u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},m.findIndex=t(1),m.findLastIndex=t(-1),m.sortedIndex=function(n,t,r,e){r=x(r,e,1);for(var u=r(t),i=0,o=O(n);o>i;){var a=Math.floor((i+o)/2);r(n[a])<u?i=a+1:o=a}return i},m.indexOf=r(1,m.findIndex,m.sortedIndex),m.lastIndexOf=r(-1,m.findLastIndex),m.range=function(n,t,r){null==t&&(t=n||0,n=0),r=r||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=Array(e),i=0;e>i;i++,n+=r)u[i]=n;return u};var E=function(n,t,r,e,u){if(!(e instanceof t))return n.apply(r,u);var i=j(n.prototype),o=n.apply(i,u);return m.isObject(o)?o:i};m.bind=function(n,t){if(g&&n.bind===g)return g.apply(n,l.call(arguments,1));if(!m.isFunction(n))throw new TypeError("Bind must be called on a function");var r=l.call(arguments,2),e=function(){return E(n,e,t,this,r.concat(l.call(arguments)))};return e},m.partial=function(n){var t=l.call(arguments,1),r=function(){for(var e=0,u=t.length,i=Array(u),o=0;u>o;o++)i[o]=t[o]===m?arguments[e++]:t[o];for(;e<arguments.length;)i.push(arguments[e++]);return E(n,r,this,this,i)};return r},m.bindAll=function(n){var t,r,e=arguments.length;if(1>=e)throw new Error("bindAll must be passed function names");for(t=1;e>t;t++)r=arguments[t],n[r]=m.bind(n[r],n);return n},m.memoize=function(n,t){var r=function(e){var u=r.cache,i=""+(t?t.apply(this,arguments):e);return m.has(u,i)||(u[i]=n.apply(this,arguments)),u[i]};return r.cache={},r},m.delay=function(n,t){var r=l.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},m.defer=m.partial(m.delay,m,1),m.throttle=function(n,t,r){var e,u,i,o=null,a=0;r||(r={});var c=function(){a=r.leading===!1?0:m.now(),o=null,i=n.apply(e,u),o||(e=u=null)};return function(){var f=m.now();a||r.leading!==!1||(a=f);var l=t-(f-a);return e=this,u=arguments,0>=l||l>t?(o&&(clearTimeout(o),o=null),a=f,i=n.apply(e,u),o||(e=u=null)):o||r.trailing===!1||(o=setTimeout(c,l)),i}},m.debounce=function(n,t,r){var e,u,i,o,a,c=function(){var f=m.now()-o;t>f&&f>=0?e=setTimeout(c,t-f):(e=null,r||(a=n.apply(i,u),e||(i=u=null)))};return function(){i=this,u=arguments,o=m.now();var f=r&&!e;return e||(e=setTimeout(c,t)),f&&(a=n.apply(i,u),i=u=null),a}},m.wrap=function(n,t){return m.partial(t,n)},m.negate=function(n){return function(){return!n.apply(this,arguments)}},m.compose=function(){var n=arguments,t=n.length-1;return function(){for(var r=t,e=n[t].apply(this,arguments);r--;)e=n[r].call(this,e);return e}},m.after=function(n,t){return function(){return--n<1?t.apply(this,arguments):void 0}},m.before=function(n,t){var r;return function(){return--n>0&&(r=t.apply(this,arguments)),1>=n&&(t=null),r}},m.once=m.partial(m.before,2);var M=!{toString:null}.propertyIsEnumerable("toString"),I=["valueOf","isPrototypeOf","toString","propertyIsEnumerable","hasOwnProperty","toLocaleString"];m.keys=function(n){if(!m.isObject(n))return[];if(v)return v(n);var t=[];for(var r in n)m.has(n,r)&&t.push(r);return M&&e(n,t),t},m.allKeys=function(n){if(!m.isObject(n))return[];var t=[];for(var r in n)t.push(r);return M&&e(n,t),t},m.values=function(n){for(var t=m.keys(n),r=t.length,e=Array(r),u=0;r>u;u++)e[u]=n[t[u]];return e},m.mapObject=function(n,t,r){t=x(t,r);for(var e,u=m.keys(n),i=u.length,o={},a=0;i>a;a++)e=u[a],o[e]=t(n[e],e,n);return o},m.pairs=function(n){for(var t=m.keys(n),r=t.length,e=Array(r),u=0;r>u;u++)e[u]=[t[u],n[t[u]]];return e},m.invert=function(n){for(var t={},r=m.keys(n),e=0,u=r.length;u>e;e++)t[n[r[e]]]=r[e];return t},m.functions=m.methods=function(n){var t=[];for(var r in n)m.isFunction(n[r])&&t.push(r);return t.sort()},m.extend=_(m.allKeys),m.extendOwn=m.assign=_(m.keys),m.findKey=function(n,t,r){t=x(t,r);for(var e,u=m.keys(n),i=0,o=u.length;o>i;i++)if(e=u[i],t(n[e],e,n))return e},m.pick=function(n,t,r){var e,u,i={},o=n;if(null==o)return i;m.isFunction(t)?(u=m.allKeys(o),e=b(t,r)):(u=S(arguments,!1,!1,1),e=function(n,t,r){return t in r},o=Object(o));for(var a=0,c=u.length;c>a;a++){var f=u[a],l=o[f];e(l,f,o)&&(i[f]=l)}return i},m.omit=function(n,t,r){if(m.isFunction(t))t=m.negate(t);else{var e=m.map(S(arguments,!1,!1,1),String);t=function(n,t){return!m.contains(e,t)}}return m.pick(n,t,r)},m.defaults=_(m.allKeys,!0),m.create=function(n,t){var r=j(n);return t&&m.extendOwn(r,t),r},m.clone=function(n){return m.isObject(n)?m.isArray(n)?n.slice():m.extend({},n):n},m.tap=function(n,t){return t(n),n},m.isMatch=function(n,t){var r=m.keys(t),e=r.length;if(null==n)return!e;for(var u=Object(n),i=0;e>i;i++){var o=r[i];if(t[o]!==u[o]||!(o in u))return!1}return!0};var N=function(n,t,r,e){if(n===t)return 0!==n||1/n===1/t;if(null==n||null==t)return n===t;n instanceof m&&(n=n._wrapped),t instanceof m&&(t=t._wrapped);var u=s.call(n);if(u!==s.call(t))return!1;switch(u){case"[object RegExp]":case"[object String]":return""+n==""+t;case"[object Number]":return+n!==+n?+t!==+t:0===+n?1/+n===1/t:+n===+t;case"[object Date]":case"[object Boolean]":return+n===+t}var i="[object Array]"===u;if(!i){if("object"!=typeof n||"object"!=typeof t)return!1;var o=n.constructor,a=t.constructor;if(o!==a&&!(m.isFunction(o)&&o instanceof o&&m.isFunction(a)&&a instanceof a)&&"constructor"in n&&"constructor"in t)return!1}r=r||[],e=e||[];for(var c=r.length;c--;)if(r[c]===n)return e[c]===t;if(r.push(n),e.push(t),i){if(c=n.length,c!==t.length)return!1;for(;c--;)if(!N(n[c],t[c],r,e))return!1}else{var f,l=m.keys(n);if(c=l.length,m.keys(t).length!==c)return!1;for(;c--;)if(f=l[c],!m.has(t,f)||!N(n[f],t[f],r,e))return!1}return r.pop(),e.pop(),!0};m.isEqual=function(n,t){return N(n,t)},m.isEmpty=function(n){return null==n?!0:k(n)&&(m.isArray(n)||m.isString(n)||m.isArguments(n))?0===n.length:0===m.keys(n).length},m.isElement=function(n){return!(!n||1!==n.nodeType)},m.isArray=h||function(n){return"[object Array]"===s.call(n)},m.isObject=function(n){var t=typeof n;return"function"===t||"object"===t&&!!n},m.each(["Arguments","Function","String","Number","Date","RegExp","Error"],function(n){m["is"+n]=function(t){return s.call(t)==="[object "+n+"]"}}),m.isArguments(arguments)||(m.isArguments=function(n){return m.has(n,"callee")}),"function"!=typeof/./&&"object"!=typeof Int8Array&&(m.isFunction=function(n){return"function"==typeof n||!1}),m.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},m.isNaN=function(n){return m.isNumber(n)&&n!==+n},m.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"===s.call(n)},m.isNull=function(n){return null===n},m.isUndefined=function(n){return n===void 0},m.has=function(n,t){return null!=n&&p.call(n,t)},m.noConflict=function(){return u._=i,this},m.identity=function(n){return n},m.constant=function(n){return function(){return n}},m.noop=function(){},m.property=w,m.propertyOf=function(n){return null==n?function(){}:function(t){return n[t]}},m.matcher=m.matches=function(n){return n=m.extendOwn({},n),function(t){return m.isMatch(t,n)}},m.times=function(n,t,r){var e=Array(Math.max(0,n));t=b(t,r,1);for(var u=0;n>u;u++)e[u]=t(u);return e},m.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))},m.now=Date.now||function(){return(new Date).getTime()};var B={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`"},T=m.invert(B),R=function(n){var t=function(t){return n[t]},r="(?:"+m.keys(n).join("|")+")",e=RegExp(r),u=RegExp(r,"g");return function(n){return n=null==n?"":""+n,e.test(n)?n.replace(u,t):n}};m.escape=R(B),m.unescape=R(T),m.result=function(n,t,r){var e=null==n?void 0:n[t];return e===void 0&&(e=r),m.isFunction(e)?e.call(n):e};var q=0;m.uniqueId=function(n){var t=++q+"";return n?n+t:t},m.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var K=/(.)^/,z={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},D=/\\|'|\r|\n|\u2028|\u2029/g,L=function(n){return"\\"+z[n]};m.template=function(n,t,r){!t&&r&&(t=r),t=m.defaults({},t,m.templateSettings);var e=RegExp([(t.escape||K).source,(t.interpolate||K).source,(t.evaluate||K).source].join("|")+"|$","g"),u=0,i="__p+='";n.replace(e,function(t,r,e,o,a){return i+=n.slice(u,a).replace(D,L),u=a+t.length,r?i+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'":e?i+="'+\n((__t=("+e+"))==null?'':__t)+\n'":o&&(i+="';\n"+o+"\n__p+='"),t}),i+="';\n",t.variable||(i="with(obj||{}){\n"+i+"}\n"),i="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+i+"return __p;\n";try{var o=new Function(t.variable||"obj","_",i)}catch(a){throw a.source=i,a}var c=function(n){return o.call(this,n,m)},f=t.variable||"obj";return c.source="function("+f+"){\n"+i+"}",c},m.chain=function(n){var t=m(n);return t._chain=!0,t};var P=function(n,t){return n._chain?m(t).chain():t};m.mixin=function(n){m.each(m.functions(n),function(t){var r=m[t]=n[t];m.prototype[t]=function(){var n=[this._wrapped];return f.apply(n,arguments),P(this,r.apply(m,n))}})},m.mixin(m),m.each(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=o[n];m.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!==n&&"splice"!==n||0!==r.length||delete r[0],P(this,r)}}),m.each(["concat","join","slice"],function(n){var t=o[n];m.prototype[n]=function(){return P(this,t.apply(this._wrapped,arguments))}}),m.prototype.value=function(){return this._wrapped},m.prototype.valueOf=m.prototype.toJSON=m.prototype.value,m.prototype.toString=function(){return""+this._wrapped},"function"==typeof define&&define.amd&&define("underscore",[],function(){return m})}).call(this); +//# sourceMappingURL=underscore-min.map
\ No newline at end of file diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/underscore/underscore.js b/ecomp-portal-FE/client/bower_components/lodash/vendor/underscore/underscore.js new file mode 100644 index 00000000..bddfdc9f --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/underscore/underscore.js @@ -0,0 +1,1620 @@ +// Underscore.js 1.8.3 +// http://underscorejs.org +// (c) 2009-2016 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors +// Underscore may be freely distributed under the MIT license. + +(function() { + + // Baseline setup + // -------------- + + // Establish the root object, `window` (`self`) in the browser, `global` + // on the server, or `this` in some virtual machines. We use `self` + // instead of `window` for `WebWorker` support. + var root = typeof self == 'object' && self.self === self && self || + typeof global == 'object' && global.global === global && global || + this; + + // Save the previous value of the `_` variable. + var previousUnderscore = root._; + + // Save bytes in the minified (but not gzipped) version: + var ArrayProto = Array.prototype, ObjProto = Object.prototype; + var SymbolProto = typeof Symbol !== 'undefined' ? Symbol.prototype : null; + + // Create quick reference variables for speed access to core prototypes. + var push = ArrayProto.push, + slice = ArrayProto.slice, + toString = ObjProto.toString, + hasOwnProperty = ObjProto.hasOwnProperty; + + // All **ECMAScript 5** native function implementations that we hope to use + // are declared here. + var nativeIsArray = Array.isArray, + nativeKeys = Object.keys, + nativeCreate = Object.create; + + // Naked function reference for surrogate-prototype-swapping. + var Ctor = function(){}; + + // Create a safe reference to the Underscore object for use below. + var _ = function(obj) { + if (obj instanceof _) return obj; + if (!(this instanceof _)) return new _(obj); + this._wrapped = obj; + }; + + // Export the Underscore object for **Node.js**, with + // backwards-compatibility for their old module API. If we're in + // the browser, add `_` as a global object. + // (`nodeType` is checked to ensure that `module` + // and `exports` are not HTML elements.) + if (typeof exports != 'undefined' && !exports.nodeType) { + if (typeof module != 'undefined' && !module.nodeType && module.exports) { + exports = module.exports = _; + } + exports._ = _; + } else { + root._ = _; + } + + // Current version. + _.VERSION = '1.8.3'; + + // Internal function that returns an efficient (for current engines) version + // of the passed-in callback, to be repeatedly applied in other Underscore + // functions. + var optimizeCb = function(func, context, argCount) { + if (context === void 0) return func; + switch (argCount == null ? 3 : argCount) { + case 1: return function(value) { + return func.call(context, value); + }; + // The 2-parameter case has been omitted only because no current consumers + // made use of it. + case 3: return function(value, index, collection) { + return func.call(context, value, index, collection); + }; + case 4: return function(accumulator, value, index, collection) { + return func.call(context, accumulator, value, index, collection); + }; + } + return function() { + return func.apply(context, arguments); + }; + }; + + // An internal function to generate callbacks that can be applied to each + // element in a collection, returning the desired result — either `identity`, + // an arbitrary callback, a property matcher, or a property accessor. + var cb = function(value, context, argCount) { + if (value == null) return _.identity; + if (_.isFunction(value)) return optimizeCb(value, context, argCount); + if (_.isObject(value)) return _.matcher(value); + return _.property(value); + }; + + // An external wrapper for the internal callback generator. + _.iteratee = function(value, context) { + return cb(value, context, Infinity); + }; + + // Similar to ES6's rest param (http://ariya.ofilabs.com/2013/03/es6-and-rest-parameter.html) + // This accumulates the arguments passed into an array, after a given index. + var restArgs = function(func, startIndex) { + startIndex = startIndex == null ? func.length - 1 : +startIndex; + return function() { + var length = Math.max(arguments.length - startIndex, 0); + var rest = Array(length); + for (var index = 0; index < length; index++) { + rest[index] = arguments[index + startIndex]; + } + switch (startIndex) { + case 0: return func.call(this, rest); + case 1: return func.call(this, arguments[0], rest); + case 2: return func.call(this, arguments[0], arguments[1], rest); + } + var args = Array(startIndex + 1); + for (index = 0; index < startIndex; index++) { + args[index] = arguments[index]; + } + args[startIndex] = rest; + return func.apply(this, args); + }; + }; + + // An internal function for creating a new object that inherits from another. + var baseCreate = function(prototype) { + if (!_.isObject(prototype)) return {}; + if (nativeCreate) return nativeCreate(prototype); + Ctor.prototype = prototype; + var result = new Ctor; + Ctor.prototype = null; + return result; + }; + + var property = function(key) { + return function(obj) { + return obj == null ? void 0 : obj[key]; + }; + }; + + // Helper for collection methods to determine whether a collection + // should be iterated as an array or as an object. + // Related: http://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength + // Avoids a very nasty iOS 8 JIT bug on ARM-64. #2094 + var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1; + var getLength = property('length'); + var isArrayLike = function(collection) { + var length = getLength(collection); + return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX; + }; + + // Collection Functions + // -------------------- + + // The cornerstone, an `each` implementation, aka `forEach`. + // Handles raw objects in addition to array-likes. Treats all + // sparse array-likes as if they were dense. + _.each = _.forEach = function(obj, iteratee, context) { + iteratee = optimizeCb(iteratee, context); + var i, length; + if (isArrayLike(obj)) { + for (i = 0, length = obj.length; i < length; i++) { + iteratee(obj[i], i, obj); + } + } else { + var keys = _.keys(obj); + for (i = 0, length = keys.length; i < length; i++) { + iteratee(obj[keys[i]], keys[i], obj); + } + } + return obj; + }; + + // Return the results of applying the iteratee to each element. + _.map = _.collect = function(obj, iteratee, context) { + iteratee = cb(iteratee, context); + var keys = !isArrayLike(obj) && _.keys(obj), + length = (keys || obj).length, + results = Array(length); + for (var index = 0; index < length; index++) { + var currentKey = keys ? keys[index] : index; + results[index] = iteratee(obj[currentKey], currentKey, obj); + } + return results; + }; + + // Create a reducing function iterating left or right. + var createReduce = function(dir) { + // Wrap code that reassigns argument variables in a separate function than + // the one that accesses `arguments.length` to avoid a perf hit. (#1991) + var reducer = function(obj, iteratee, memo, initial) { + var keys = !isArrayLike(obj) && _.keys(obj), + length = (keys || obj).length, + index = dir > 0 ? 0 : length - 1; + if (!initial) { + memo = obj[keys ? keys[index] : index]; + index += dir; + } + for (; index >= 0 && index < length; index += dir) { + var currentKey = keys ? keys[index] : index; + memo = iteratee(memo, obj[currentKey], currentKey, obj); + } + return memo; + }; + + return function(obj, iteratee, memo, context) { + var initial = arguments.length >= 3; + return reducer(obj, optimizeCb(iteratee, context, 4), memo, initial); + }; + }; + + // **Reduce** builds up a single result from a list of values, aka `inject`, + // or `foldl`. + _.reduce = _.foldl = _.inject = createReduce(1); + + // The right-associative version of reduce, also known as `foldr`. + _.reduceRight = _.foldr = createReduce(-1); + + // Return the first value which passes a truth test. Aliased as `detect`. + _.find = _.detect = function(obj, predicate, context) { + var keyFinder = isArrayLike(obj) ? _.findIndex : _.findKey; + var key = keyFinder(obj, predicate, context); + if (key !== void 0 && key !== -1) return obj[key]; + }; + + // Return all the elements that pass a truth test. + // Aliased as `select`. + _.filter = _.select = function(obj, predicate, context) { + var results = []; + predicate = cb(predicate, context); + _.each(obj, function(value, index, list) { + if (predicate(value, index, list)) results.push(value); + }); + return results; + }; + + // Return all the elements for which a truth test fails. + _.reject = function(obj, predicate, context) { + return _.filter(obj, _.negate(cb(predicate)), context); + }; + + // Determine whether all of the elements match a truth test. + // Aliased as `all`. + _.every = _.all = function(obj, predicate, context) { + predicate = cb(predicate, context); + var keys = !isArrayLike(obj) && _.keys(obj), + length = (keys || obj).length; + for (var index = 0; index < length; index++) { + var currentKey = keys ? keys[index] : index; + if (!predicate(obj[currentKey], currentKey, obj)) return false; + } + return true; + }; + + // Determine if at least one element in the object matches a truth test. + // Aliased as `any`. + _.some = _.any = function(obj, predicate, context) { + predicate = cb(predicate, context); + var keys = !isArrayLike(obj) && _.keys(obj), + length = (keys || obj).length; + for (var index = 0; index < length; index++) { + var currentKey = keys ? keys[index] : index; + if (predicate(obj[currentKey], currentKey, obj)) return true; + } + return false; + }; + + // Determine if the array or object contains a given item (using `===`). + // Aliased as `includes` and `include`. + _.contains = _.includes = _.include = function(obj, item, fromIndex, guard) { + if (!isArrayLike(obj)) obj = _.values(obj); + if (typeof fromIndex != 'number' || guard) fromIndex = 0; + return _.indexOf(obj, item, fromIndex) >= 0; + }; + + // Invoke a method (with arguments) on every item in a collection. + _.invoke = restArgs(function(obj, method, args) { + var isFunc = _.isFunction(method); + return _.map(obj, function(value) { + var func = isFunc ? method : value[method]; + return func == null ? func : func.apply(value, args); + }); + }); + + // Convenience version of a common use case of `map`: fetching a property. + _.pluck = function(obj, key) { + return _.map(obj, _.property(key)); + }; + + // Convenience version of a common use case of `filter`: selecting only objects + // containing specific `key:value` pairs. + _.where = function(obj, attrs) { + return _.filter(obj, _.matcher(attrs)); + }; + + // Convenience version of a common use case of `find`: getting the first object + // containing specific `key:value` pairs. + _.findWhere = function(obj, attrs) { + return _.find(obj, _.matcher(attrs)); + }; + + // Return the maximum element (or element-based computation). + _.max = function(obj, iteratee, context) { + var result = -Infinity, lastComputed = -Infinity, + value, computed; + if (iteratee == null || (typeof iteratee == 'number' && typeof obj[0] != 'object') && obj != null) { + obj = isArrayLike(obj) ? obj : _.values(obj); + for (var i = 0, length = obj.length; i < length; i++) { + value = obj[i]; + if (value != null && value > result) { + result = value; + } + } + } else { + iteratee = cb(iteratee, context); + _.each(obj, function(v, index, list) { + computed = iteratee(v, index, list); + if (computed > lastComputed || computed === -Infinity && result === -Infinity) { + result = v; + lastComputed = computed; + } + }); + } + return result; + }; + + // Return the minimum element (or element-based computation). + _.min = function(obj, iteratee, context) { + var result = Infinity, lastComputed = Infinity, + value, computed; + if (iteratee == null || (typeof iteratee == 'number' && typeof obj[0] != 'object') && obj != null) { + obj = isArrayLike(obj) ? obj : _.values(obj); + for (var i = 0, length = obj.length; i < length; i++) { + value = obj[i]; + if (value != null && value < result) { + result = value; + } + } + } else { + iteratee = cb(iteratee, context); + _.each(obj, function(v, index, list) { + computed = iteratee(v, index, list); + if (computed < lastComputed || computed === Infinity && result === Infinity) { + result = v; + lastComputed = computed; + } + }); + } + return result; + }; + + // Shuffle a collection. + _.shuffle = function(obj) { + return _.sample(obj, Infinity); + }; + + // Sample **n** random values from a collection using the modern version of the + // [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle). + // If **n** is not specified, returns a single random element. + // The internal `guard` argument allows it to work with `map`. + _.sample = function(obj, n, guard) { + if (n == null || guard) { + if (!isArrayLike(obj)) obj = _.values(obj); + return obj[_.random(obj.length - 1)]; + } + var sample = isArrayLike(obj) ? _.clone(obj) : _.values(obj); + var length = getLength(sample); + n = Math.max(Math.min(n, length), 0); + var last = length - 1; + for (var index = 0; index < n; index++) { + var rand = _.random(index, last); + var temp = sample[index]; + sample[index] = sample[rand]; + sample[rand] = temp; + } + return sample.slice(0, n); + }; + + // Sort the object's values by a criterion produced by an iteratee. + _.sortBy = function(obj, iteratee, context) { + var index = 0; + iteratee = cb(iteratee, context); + return _.pluck(_.map(obj, function(value, key, list) { + return { + value: value, + index: index++, + criteria: iteratee(value, key, list) + }; + }).sort(function(left, right) { + var a = left.criteria; + var b = right.criteria; + if (a !== b) { + if (a > b || a === void 0) return 1; + if (a < b || b === void 0) return -1; + } + return left.index - right.index; + }), 'value'); + }; + + // An internal function used for aggregate "group by" operations. + var group = function(behavior, partition) { + return function(obj, iteratee, context) { + var result = partition ? [[], []] : {}; + iteratee = cb(iteratee, context); + _.each(obj, function(value, index) { + var key = iteratee(value, index, obj); + behavior(result, value, key); + }); + return result; + }; + }; + + // Groups the object's values by a criterion. Pass either a string attribute + // to group by, or a function that returns the criterion. + _.groupBy = group(function(result, value, key) { + if (_.has(result, key)) result[key].push(value); else result[key] = [value]; + }); + + // Indexes the object's values by a criterion, similar to `groupBy`, but for + // when you know that your index values will be unique. + _.indexBy = group(function(result, value, key) { + result[key] = value; + }); + + // Counts instances of an object that group by a certain criterion. Pass + // either a string attribute to count by, or a function that returns the + // criterion. + _.countBy = group(function(result, value, key) { + if (_.has(result, key)) result[key]++; else result[key] = 1; + }); + + var reStrSymbol = /[^\ud800-\udfff]|[\ud800-\udbff][\udc00-\udfff]|[\ud800-\udfff]/g; + // Safely create a real, live array from anything iterable. + _.toArray = function(obj) { + if (!obj) return []; + if (_.isArray(obj)) return slice.call(obj); + if (_.isString(obj)) { + // Keep surrogate pair characters together + return obj.match(reStrSymbol); + } + if (isArrayLike(obj)) return _.map(obj); + return _.values(obj); + }; + + // Return the number of elements in an object. + _.size = function(obj) { + if (obj == null) return 0; + return isArrayLike(obj) ? obj.length : _.keys(obj).length; + }; + + // Split a collection into two arrays: one whose elements all satisfy the given + // predicate, and one whose elements all do not satisfy the predicate. + _.partition = group(function(result, value, pass) { + result[pass ? 0 : 1].push(value); + }, true); + + // Array Functions + // --------------- + + // Get the first element of an array. Passing **n** will return the first N + // values in the array. Aliased as `head` and `take`. The **guard** check + // allows it to work with `_.map`. + _.first = _.head = _.take = function(array, n, guard) { + if (array == null) return void 0; + if (n == null || guard) return array[0]; + return _.initial(array, array.length - n); + }; + + // Returns everything but the last entry of the array. Especially useful on + // the arguments object. Passing **n** will return all the values in + // the array, excluding the last N. + _.initial = function(array, n, guard) { + return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n))); + }; + + // Get the last element of an array. Passing **n** will return the last N + // values in the array. + _.last = function(array, n, guard) { + if (array == null) return void 0; + if (n == null || guard) return array[array.length - 1]; + return _.rest(array, Math.max(0, array.length - n)); + }; + + // Returns everything but the first entry of the array. Aliased as `tail` and `drop`. + // Especially useful on the arguments object. Passing an **n** will return + // the rest N values in the array. + _.rest = _.tail = _.drop = function(array, n, guard) { + return slice.call(array, n == null || guard ? 1 : n); + }; + + // Trim out all falsy values from an array. + _.compact = function(array) { + return _.filter(array); + }; + + // Internal implementation of a recursive `flatten` function. + var flatten = function(input, shallow, strict, output) { + output = output || []; + var idx = output.length; + for (var i = 0, length = getLength(input); i < length; i++) { + var value = input[i]; + if (isArrayLike(value) && (_.isArray(value) || _.isArguments(value))) { + // Flatten current level of array or arguments object. + if (shallow) { + var j = 0, len = value.length; + while (j < len) output[idx++] = value[j++]; + } else { + flatten(value, shallow, strict, output); + idx = output.length; + } + } else if (!strict) { + output[idx++] = value; + } + } + return output; + }; + + // Flatten out an array, either recursively (by default), or just one level. + _.flatten = function(array, shallow) { + return flatten(array, shallow, false); + }; + + // Return a version of the array that does not contain the specified value(s). + _.without = restArgs(function(array, otherArrays) { + return _.difference(array, otherArrays); + }); + + // Produce a duplicate-free version of the array. If the array has already + // been sorted, you have the option of using a faster algorithm. + // Aliased as `unique`. + _.uniq = _.unique = function(array, isSorted, iteratee, context) { + if (!_.isBoolean(isSorted)) { + context = iteratee; + iteratee = isSorted; + isSorted = false; + } + if (iteratee != null) iteratee = cb(iteratee, context); + var result = []; + var seen = []; + for (var i = 0, length = getLength(array); i < length; i++) { + var value = array[i], + computed = iteratee ? iteratee(value, i, array) : value; + if (isSorted) { + if (!i || seen !== computed) result.push(value); + seen = computed; + } else if (iteratee) { + if (!_.contains(seen, computed)) { + seen.push(computed); + result.push(value); + } + } else if (!_.contains(result, value)) { + result.push(value); + } + } + return result; + }; + + // Produce an array that contains the union: each distinct element from all of + // the passed-in arrays. + _.union = restArgs(function(arrays) { + return _.uniq(flatten(arrays, true, true)); + }); + + // Produce an array that contains every item shared between all the + // passed-in arrays. + _.intersection = function(array) { + var result = []; + var argsLength = arguments.length; + for (var i = 0, length = getLength(array); i < length; i++) { + var item = array[i]; + if (_.contains(result, item)) continue; + var j; + for (j = 1; j < argsLength; j++) { + if (!_.contains(arguments[j], item)) break; + } + if (j === argsLength) result.push(item); + } + return result; + }; + + // Take the difference between one array and a number of other arrays. + // Only the elements present in just the first array will remain. + _.difference = restArgs(function(array, rest) { + rest = flatten(rest, true, true); + return _.filter(array, function(value){ + return !_.contains(rest, value); + }); + }); + + // Complement of _.zip. Unzip accepts an array of arrays and groups + // each array's elements on shared indices. + _.unzip = function(array) { + var length = array && _.max(array, getLength).length || 0; + var result = Array(length); + + for (var index = 0; index < length; index++) { + result[index] = _.pluck(array, index); + } + return result; + }; + + // Zip together multiple lists into a single array -- elements that share + // an index go together. + _.zip = restArgs(_.unzip); + + // Converts lists into objects. Pass either a single array of `[key, value]` + // pairs, or two parallel arrays of the same length -- one of keys, and one of + // the corresponding values. + _.object = function(list, values) { + var result = {}; + for (var i = 0, length = getLength(list); i < length; i++) { + if (values) { + result[list[i]] = values[i]; + } else { + result[list[i][0]] = list[i][1]; + } + } + return result; + }; + + // Generator function to create the findIndex and findLastIndex functions. + var createPredicateIndexFinder = function(dir) { + return function(array, predicate, context) { + predicate = cb(predicate, context); + var length = getLength(array); + var index = dir > 0 ? 0 : length - 1; + for (; index >= 0 && index < length; index += dir) { + if (predicate(array[index], index, array)) return index; + } + return -1; + }; + }; + + // Returns the first index on an array-like that passes a predicate test. + _.findIndex = createPredicateIndexFinder(1); + _.findLastIndex = createPredicateIndexFinder(-1); + + // Use a comparator function to figure out the smallest index at which + // an object should be inserted so as to maintain order. Uses binary search. + _.sortedIndex = function(array, obj, iteratee, context) { + iteratee = cb(iteratee, context, 1); + var value = iteratee(obj); + var low = 0, high = getLength(array); + while (low < high) { + var mid = Math.floor((low + high) / 2); + if (iteratee(array[mid]) < value) low = mid + 1; else high = mid; + } + return low; + }; + + // Generator function to create the indexOf and lastIndexOf functions. + var createIndexFinder = function(dir, predicateFind, sortedIndex) { + return function(array, item, idx) { + var i = 0, length = getLength(array); + if (typeof idx == 'number') { + if (dir > 0) { + i = idx >= 0 ? idx : Math.max(idx + length, i); + } else { + length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1; + } + } else if (sortedIndex && idx && length) { + idx = sortedIndex(array, item); + return array[idx] === item ? idx : -1; + } + if (item !== item) { + idx = predicateFind(slice.call(array, i, length), _.isNaN); + return idx >= 0 ? idx + i : -1; + } + for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) { + if (array[idx] === item) return idx; + } + return -1; + }; + }; + + // Return the position of the first occurrence of an item in an array, + // or -1 if the item is not included in the array. + // If the array is large and already in sort order, pass `true` + // for **isSorted** to use binary search. + _.indexOf = createIndexFinder(1, _.findIndex, _.sortedIndex); + _.lastIndexOf = createIndexFinder(-1, _.findLastIndex); + + // Generate an integer Array containing an arithmetic progression. A port of + // the native Python `range()` function. See + // [the Python documentation](http://docs.python.org/library/functions.html#range). + _.range = function(start, stop, step) { + if (stop == null) { + stop = start || 0; + start = 0; + } + if (!step) { + step = stop < start ? -1 : 1; + } + + var length = Math.max(Math.ceil((stop - start) / step), 0); + var range = Array(length); + + for (var idx = 0; idx < length; idx++, start += step) { + range[idx] = start; + } + + return range; + }; + + // Split an **array** into several arrays containing **count** or less elements + // of initial array. + _.chunk = function(array, count) { + if (count == null || count < 1) return []; + + var result = []; + var i = 0, length = array.length; + while (i < length) { + result.push(slice.call(array, i, i += count)); + } + return result; + }; + + // Function (ahem) Functions + // ------------------ + + // Determines whether to execute a function as a constructor + // or a normal function with the provided arguments. + var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) { + if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args); + var self = baseCreate(sourceFunc.prototype); + var result = sourceFunc.apply(self, args); + if (_.isObject(result)) return result; + return self; + }; + + // Create a function bound to a given object (assigning `this`, and arguments, + // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if + // available. + _.bind = restArgs(function(func, context, args) { + if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function'); + var bound = restArgs(function(callArgs) { + return executeBound(func, bound, context, this, args.concat(callArgs)); + }); + return bound; + }); + + // Partially apply a function by creating a version that has had some of its + // arguments pre-filled, without changing its dynamic `this` context. _ acts + // as a placeholder by default, allowing any combination of arguments to be + // pre-filled. Set `_.partial.placeholder` for a custom placeholder argument. + _.partial = restArgs(function(func, boundArgs) { + var placeholder = _.partial.placeholder; + var bound = function() { + var position = 0, length = boundArgs.length; + var args = Array(length); + for (var i = 0; i < length; i++) { + args[i] = boundArgs[i] === placeholder ? arguments[position++] : boundArgs[i]; + } + while (position < arguments.length) args.push(arguments[position++]); + return executeBound(func, bound, this, this, args); + }; + return bound; + }); + + _.partial.placeholder = _; + + // Bind a number of an object's methods to that object. Remaining arguments + // are the method names to be bound. Useful for ensuring that all callbacks + // defined on an object belong to it. + _.bindAll = restArgs(function(obj, keys) { + keys = flatten(keys, false, false); + var index = keys.length; + if (index < 1) throw new Error('bindAll must be passed function names'); + while (index--) { + var key = keys[index]; + obj[key] = _.bind(obj[key], obj); + } + }); + + // Memoize an expensive function by storing its results. + _.memoize = function(func, hasher) { + var memoize = function(key) { + var cache = memoize.cache; + var address = '' + (hasher ? hasher.apply(this, arguments) : key); + if (!_.has(cache, address)) cache[address] = func.apply(this, arguments); + return cache[address]; + }; + memoize.cache = {}; + return memoize; + }; + + // Delays a function for the given number of milliseconds, and then calls + // it with the arguments supplied. + _.delay = restArgs(function(func, wait, args) { + return setTimeout(function() { + return func.apply(null, args); + }, wait); + }); + + // Defers a function, scheduling it to run after the current call stack has + // cleared. + _.defer = _.partial(_.delay, _, 1); + + // Returns a function, that, when invoked, will only be triggered at most once + // during a given window of time. Normally, the throttled function will run + // as much as it can, without ever going more than once per `wait` duration; + // but if you'd like to disable the execution on the leading edge, pass + // `{leading: false}`. To disable execution on the trailing edge, ditto. + _.throttle = function(func, wait, options) { + var timeout, context, args, result; + var previous = 0; + if (!options) options = {}; + + var later = function() { + previous = options.leading === false ? 0 : _.now(); + timeout = null; + result = func.apply(context, args); + if (!timeout) context = args = null; + }; + + var throttled = function() { + var now = _.now(); + if (!previous && options.leading === false) previous = now; + var remaining = wait - (now - previous); + context = this; + args = arguments; + if (remaining <= 0 || remaining > wait) { + if (timeout) { + clearTimeout(timeout); + timeout = null; + } + previous = now; + result = func.apply(context, args); + if (!timeout) context = args = null; + } else if (!timeout && options.trailing !== false) { + timeout = setTimeout(later, remaining); + } + return result; + }; + + throttled.cancel = function() { + clearTimeout(timeout); + previous = 0; + timeout = context = args = null; + }; + + return throttled; + }; + + // Returns a function, that, as long as it continues to be invoked, will not + // be triggered. The function will be called after it stops being called for + // N milliseconds. If `immediate` is passed, trigger the function on the + // leading edge, instead of the trailing. + _.debounce = function(func, wait, immediate) { + var timeout, result; + + var later = function(context, args) { + timeout = null; + if (args) result = func.apply(context, args); + }; + + var debounced = restArgs(function(args) { + if (timeout) clearTimeout(timeout); + if (immediate) { + var callNow = !timeout; + timeout = setTimeout(later, wait); + if (callNow) result = func.apply(this, args); + } else { + timeout = _.delay(later, wait, this, args); + } + + return result; + }); + + debounced.cancel = function() { + clearTimeout(timeout); + timeout = null; + }; + + return debounced; + }; + + // Returns the first function passed as an argument to the second, + // allowing you to adjust arguments, run code before and after, and + // conditionally execute the original function. + _.wrap = function(func, wrapper) { + return _.partial(wrapper, func); + }; + + // Returns a negated version of the passed-in predicate. + _.negate = function(predicate) { + return function() { + return !predicate.apply(this, arguments); + }; + }; + + // Returns a function that is the composition of a list of functions, each + // consuming the return value of the function that follows. + _.compose = function() { + var args = arguments; + var start = args.length - 1; + return function() { + var i = start; + var result = args[start].apply(this, arguments); + while (i--) result = args[i].call(this, result); + return result; + }; + }; + + // Returns a function that will only be executed on and after the Nth call. + _.after = function(times, func) { + return function() { + if (--times < 1) { + return func.apply(this, arguments); + } + }; + }; + + // Returns a function that will only be executed up to (but not including) the Nth call. + _.before = function(times, func) { + var memo; + return function() { + if (--times > 0) { + memo = func.apply(this, arguments); + } + if (times <= 1) func = null; + return memo; + }; + }; + + // Returns a function that will be executed at most one time, no matter how + // often you call it. Useful for lazy initialization. + _.once = _.partial(_.before, 2); + + _.restArgs = restArgs; + + // Object Functions + // ---------------- + + // Keys in IE < 9 that won't be iterated by `for key in ...` and thus missed. + var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString'); + var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString', + 'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString']; + + var collectNonEnumProps = function(obj, keys) { + var nonEnumIdx = nonEnumerableProps.length; + var constructor = obj.constructor; + var proto = _.isFunction(constructor) && constructor.prototype || ObjProto; + + // Constructor is a special case. + var prop = 'constructor'; + if (_.has(obj, prop) && !_.contains(keys, prop)) keys.push(prop); + + while (nonEnumIdx--) { + prop = nonEnumerableProps[nonEnumIdx]; + if (prop in obj && obj[prop] !== proto[prop] && !_.contains(keys, prop)) { + keys.push(prop); + } + } + }; + + // Retrieve the names of an object's own properties. + // Delegates to **ECMAScript 5**'s native `Object.keys`. + _.keys = function(obj) { + if (!_.isObject(obj)) return []; + if (nativeKeys) return nativeKeys(obj); + var keys = []; + for (var key in obj) if (_.has(obj, key)) keys.push(key); + // Ahem, IE < 9. + if (hasEnumBug) collectNonEnumProps(obj, keys); + return keys; + }; + + // Retrieve all the property names of an object. + _.allKeys = function(obj) { + if (!_.isObject(obj)) return []; + var keys = []; + for (var key in obj) keys.push(key); + // Ahem, IE < 9. + if (hasEnumBug) collectNonEnumProps(obj, keys); + return keys; + }; + + // Retrieve the values of an object's properties. + _.values = function(obj) { + var keys = _.keys(obj); + var length = keys.length; + var values = Array(length); + for (var i = 0; i < length; i++) { + values[i] = obj[keys[i]]; + } + return values; + }; + + // Returns the results of applying the iteratee to each element of the object. + // In contrast to _.map it returns an object. + _.mapObject = function(obj, iteratee, context) { + iteratee = cb(iteratee, context); + var keys = _.keys(obj), + length = keys.length, + results = {}; + for (var index = 0; index < length; index++) { + var currentKey = keys[index]; + results[currentKey] = iteratee(obj[currentKey], currentKey, obj); + } + return results; + }; + + // Convert an object into a list of `[key, value]` pairs. + _.pairs = function(obj) { + var keys = _.keys(obj); + var length = keys.length; + var pairs = Array(length); + for (var i = 0; i < length; i++) { + pairs[i] = [keys[i], obj[keys[i]]]; + } + return pairs; + }; + + // Invert the keys and values of an object. The values must be serializable. + _.invert = function(obj) { + var result = {}; + var keys = _.keys(obj); + for (var i = 0, length = keys.length; i < length; i++) { + result[obj[keys[i]]] = keys[i]; + } + return result; + }; + + // Return a sorted list of the function names available on the object. + // Aliased as `methods`. + _.functions = _.methods = function(obj) { + var names = []; + for (var key in obj) { + if (_.isFunction(obj[key])) names.push(key); + } + return names.sort(); + }; + + // An internal function for creating assigner functions. + var createAssigner = function(keysFunc, defaults) { + return function(obj) { + var length = arguments.length; + if (defaults) obj = Object(obj); + if (length < 2 || obj == null) return obj; + for (var index = 1; index < length; index++) { + var source = arguments[index], + keys = keysFunc(source), + l = keys.length; + for (var i = 0; i < l; i++) { + var key = keys[i]; + if (!defaults || obj[key] === void 0) obj[key] = source[key]; + } + } + return obj; + }; + }; + + // Extend a given object with all the properties in passed-in object(s). + _.extend = createAssigner(_.allKeys); + + // Assigns a given object with all the own properties in the passed-in object(s). + // (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) + _.extendOwn = _.assign = createAssigner(_.keys); + + // Returns the first key on an object that passes a predicate test. + _.findKey = function(obj, predicate, context) { + predicate = cb(predicate, context); + var keys = _.keys(obj), key; + for (var i = 0, length = keys.length; i < length; i++) { + key = keys[i]; + if (predicate(obj[key], key, obj)) return key; + } + }; + + // Internal pick helper function to determine if `obj` has key `key`. + var keyInObj = function(value, key, obj) { + return key in obj; + }; + + // Return a copy of the object only containing the whitelisted properties. + _.pick = restArgs(function(obj, keys) { + var result = {}, iteratee = keys[0]; + if (obj == null) return result; + if (_.isFunction(iteratee)) { + if (keys.length > 1) iteratee = optimizeCb(iteratee, keys[1]); + keys = _.allKeys(obj); + } else { + iteratee = keyInObj; + keys = flatten(keys, false, false); + obj = Object(obj); + } + for (var i = 0, length = keys.length; i < length; i++) { + var key = keys[i]; + var value = obj[key]; + if (iteratee(value, key, obj)) result[key] = value; + } + return result; + }); + + // Return a copy of the object without the blacklisted properties. + _.omit = restArgs(function(obj, keys) { + var iteratee = keys[0], context; + if (_.isFunction(iteratee)) { + iteratee = _.negate(iteratee); + if (keys.length > 1) context = keys[1]; + } else { + keys = _.map(flatten(keys, false, false), String); + iteratee = function(value, key) { + return !_.contains(keys, key); + }; + } + return _.pick(obj, iteratee, context); + }); + + // Fill in a given object with default properties. + _.defaults = createAssigner(_.allKeys, true); + + // Creates an object that inherits from the given prototype object. + // If additional properties are provided then they will be added to the + // created object. + _.create = function(prototype, props) { + var result = baseCreate(prototype); + if (props) _.extendOwn(result, props); + return result; + }; + + // Create a (shallow-cloned) duplicate of an object. + _.clone = function(obj) { + if (!_.isObject(obj)) return obj; + return _.isArray(obj) ? obj.slice() : _.extend({}, obj); + }; + + // Invokes interceptor with the obj, and then returns obj. + // The primary purpose of this method is to "tap into" a method chain, in + // order to perform operations on intermediate results within the chain. + _.tap = function(obj, interceptor) { + interceptor(obj); + return obj; + }; + + // Returns whether an object has a given set of `key:value` pairs. + _.isMatch = function(object, attrs) { + var keys = _.keys(attrs), length = keys.length; + if (object == null) return !length; + var obj = Object(object); + for (var i = 0; i < length; i++) { + var key = keys[i]; + if (attrs[key] !== obj[key] || !(key in obj)) return false; + } + return true; + }; + + + // Internal recursive comparison function for `isEqual`. + var eq, deepEq; + eq = function(a, b, aStack, bStack) { + // Identical objects are equal. `0 === -0`, but they aren't identical. + // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal). + if (a === b) return a !== 0 || 1 / a === 1 / b; + // A strict comparison is necessary because `null == undefined`. + if (a == null || b == null) return a === b; + // `NaN`s are equivalent, but non-reflexive. + if (a !== a) return b !== b; + // Exhaust primitive checks + var type = typeof a; + if (type !== 'function' && type !== 'object' && typeof b != 'object') return false; + return deepEq(a, b, aStack, bStack); + }; + + // Internal recursive comparison function for `isEqual`. + deepEq = function(a, b, aStack, bStack) { + // Unwrap any wrapped objects. + if (a instanceof _) a = a._wrapped; + if (b instanceof _) b = b._wrapped; + // Compare `[[Class]]` names. + var className = toString.call(a); + if (className !== toString.call(b)) return false; + switch (className) { + // Strings, numbers, regular expressions, dates, and booleans are compared by value. + case '[object RegExp]': + // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i') + case '[object String]': + // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is + // equivalent to `new String("5")`. + return '' + a === '' + b; + case '[object Number]': + // `NaN`s are equivalent, but non-reflexive. + // Object(NaN) is equivalent to NaN. + if (+a !== +a) return +b !== +b; + // An `egal` comparison is performed for other numeric values. + return +a === 0 ? 1 / +a === 1 / b : +a === +b; + case '[object Date]': + case '[object Boolean]': + // Coerce dates and booleans to numeric primitive values. Dates are compared by their + // millisecond representations. Note that invalid dates with millisecond representations + // of `NaN` are not equivalent. + return +a === +b; + case '[object Symbol]': + return SymbolProto.valueOf.call(a) === SymbolProto.valueOf.call(b); + } + + var areArrays = className === '[object Array]'; + if (!areArrays) { + if (typeof a != 'object' || typeof b != 'object') return false; + + // Objects with different constructors are not equivalent, but `Object`s or `Array`s + // from different frames are. + var aCtor = a.constructor, bCtor = b.constructor; + if (aCtor !== bCtor && !(_.isFunction(aCtor) && aCtor instanceof aCtor && + _.isFunction(bCtor) && bCtor instanceof bCtor) + && ('constructor' in a && 'constructor' in b)) { + return false; + } + } + // Assume equality for cyclic structures. The algorithm for detecting cyclic + // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. + + // Initializing stack of traversed objects. + // It's done here since we only need them for objects and arrays comparison. + aStack = aStack || []; + bStack = bStack || []; + var length = aStack.length; + while (length--) { + // Linear search. Performance is inversely proportional to the number of + // unique nested structures. + if (aStack[length] === a) return bStack[length] === b; + } + + // Add the first object to the stack of traversed objects. + aStack.push(a); + bStack.push(b); + + // Recursively compare objects and arrays. + if (areArrays) { + // Compare array lengths to determine if a deep comparison is necessary. + length = a.length; + if (length !== b.length) return false; + // Deep compare the contents, ignoring non-numeric properties. + while (length--) { + if (!eq(a[length], b[length], aStack, bStack)) return false; + } + } else { + // Deep compare objects. + var keys = _.keys(a), key; + length = keys.length; + // Ensure that both objects contain the same number of properties before comparing deep equality. + if (_.keys(b).length !== length) return false; + while (length--) { + // Deep compare each member + key = keys[length]; + if (!(_.has(b, key) && eq(a[key], b[key], aStack, bStack))) return false; + } + } + // Remove the first object from the stack of traversed objects. + aStack.pop(); + bStack.pop(); + return true; + }; + + // Perform a deep comparison to check if two objects are equal. + _.isEqual = function(a, b) { + return eq(a, b); + }; + + // Is a given array, string, or object empty? + // An "empty" object has no enumerable own-properties. + _.isEmpty = function(obj) { + if (obj == null) return true; + if (isArrayLike(obj) && (_.isArray(obj) || _.isString(obj) || _.isArguments(obj))) return obj.length === 0; + return _.keys(obj).length === 0; + }; + + // Is a given value a DOM element? + _.isElement = function(obj) { + return !!(obj && obj.nodeType === 1); + }; + + // Is a given value an array? + // Delegates to ECMA5's native Array.isArray + _.isArray = nativeIsArray || function(obj) { + return toString.call(obj) === '[object Array]'; + }; + + // Is a given variable an object? + _.isObject = function(obj) { + var type = typeof obj; + return type === 'function' || type === 'object' && !!obj; + }; + + // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp, isError, isMap, isWeakMap, isSet, isWeakSet. + _.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp', 'Error', 'Symbol', 'Map', 'WeakMap', 'Set', 'WeakSet'], function(name) { + _['is' + name] = function(obj) { + return toString.call(obj) === '[object ' + name + ']'; + }; + }); + + // Define a fallback version of the method in browsers (ahem, IE < 9), where + // there isn't any inspectable "Arguments" type. + if (!_.isArguments(arguments)) { + _.isArguments = function(obj) { + return _.has(obj, 'callee'); + }; + } + + // Optimize `isFunction` if appropriate. Work around some typeof bugs in old v8, + // IE 11 (#1621), Safari 8 (#1929), and PhantomJS (#2236). + var nodelist = root.document && root.document.childNodes; + if (typeof /./ != 'function' && typeof Int8Array != 'object' && typeof nodelist != 'function') { + _.isFunction = function(obj) { + return typeof obj == 'function' || false; + }; + } + + // Is a given object a finite number? + _.isFinite = function(obj) { + return !_.isSymbol(obj) && isFinite(obj) && !isNaN(parseFloat(obj)); + }; + + // Is the given value `NaN`? + _.isNaN = function(obj) { + return _.isNumber(obj) && isNaN(obj); + }; + + // Is a given value a boolean? + _.isBoolean = function(obj) { + return obj === true || obj === false || toString.call(obj) === '[object Boolean]'; + }; + + // Is a given value equal to null? + _.isNull = function(obj) { + return obj === null; + }; + + // Is a given variable undefined? + _.isUndefined = function(obj) { + return obj === void 0; + }; + + // Shortcut function for checking if an object has a given property directly + // on itself (in other words, not on a prototype). + _.has = function(obj, key) { + return obj != null && hasOwnProperty.call(obj, key); + }; + + // Utility Functions + // ----------------- + + // Run Underscore.js in *noConflict* mode, returning the `_` variable to its + // previous owner. Returns a reference to the Underscore object. + _.noConflict = function() { + root._ = previousUnderscore; + return this; + }; + + // Keep the identity function around for default iteratees. + _.identity = function(value) { + return value; + }; + + // Predicate-generating functions. Often useful outside of Underscore. + _.constant = function(value) { + return function() { + return value; + }; + }; + + _.noop = function(){}; + + _.property = property; + + // Generates a function for a given object that returns a given property. + _.propertyOf = function(obj) { + return obj == null ? function(){} : function(key) { + return obj[key]; + }; + }; + + // Returns a predicate for checking whether an object has a given set of + // `key:value` pairs. + _.matcher = _.matches = function(attrs) { + attrs = _.extendOwn({}, attrs); + return function(obj) { + return _.isMatch(obj, attrs); + }; + }; + + // Run a function **n** times. + _.times = function(n, iteratee, context) { + var accum = Array(Math.max(0, n)); + iteratee = optimizeCb(iteratee, context, 1); + for (var i = 0; i < n; i++) accum[i] = iteratee(i); + return accum; + }; + + // Return a random integer between min and max (inclusive). + _.random = function(min, max) { + if (max == null) { + max = min; + min = 0; + } + return min + Math.floor(Math.random() * (max - min + 1)); + }; + + // A (possibly faster) way to get the current timestamp as an integer. + _.now = Date.now || function() { + return new Date().getTime(); + }; + + // List of HTML entities for escaping. + var escapeMap = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '`': '`' + }; + var unescapeMap = _.invert(escapeMap); + + // Functions for escaping and unescaping strings to/from HTML interpolation. + var createEscaper = function(map) { + var escaper = function(match) { + return map[match]; + }; + // Regexes for identifying a key that needs to be escaped. + var source = '(?:' + _.keys(map).join('|') + ')'; + var testRegexp = RegExp(source); + var replaceRegexp = RegExp(source, 'g'); + return function(string) { + string = string == null ? '' : '' + string; + return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string; + }; + }; + _.escape = createEscaper(escapeMap); + _.unescape = createEscaper(unescapeMap); + + // If the value of the named `property` is a function then invoke it with the + // `object` as context; otherwise, return it. + _.result = function(object, prop, fallback) { + var value = object == null ? void 0 : object[prop]; + if (value === void 0) { + value = fallback; + } + return _.isFunction(value) ? value.call(object) : value; + }; + + // Generate a unique integer id (unique within the entire client session). + // Useful for temporary DOM ids. + var idCounter = 0; + _.uniqueId = function(prefix) { + var id = ++idCounter + ''; + return prefix ? prefix + id : id; + }; + + // By default, Underscore uses ERB-style template delimiters, change the + // following template settings to use alternative delimiters. + _.templateSettings = { + evaluate: /<%([\s\S]+?)%>/g, + interpolate: /<%=([\s\S]+?)%>/g, + escape: /<%-([\s\S]+?)%>/g + }; + + // When customizing `templateSettings`, if you don't want to define an + // interpolation, evaluation or escaping regex, we need one that is + // guaranteed not to match. + var noMatch = /(.)^/; + + // Certain characters need to be escaped so that they can be put into a + // string literal. + var escapes = { + "'": "'", + '\\': '\\', + '\r': 'r', + '\n': 'n', + '\u2028': 'u2028', + '\u2029': 'u2029' + }; + + var escapeRegExp = /\\|'|\r|\n|\u2028|\u2029/g; + + var escapeChar = function(match) { + return '\\' + escapes[match]; + }; + + // JavaScript micro-templating, similar to John Resig's implementation. + // Underscore templating handles arbitrary delimiters, preserves whitespace, + // and correctly escapes quotes within interpolated code. + // NB: `oldSettings` only exists for backwards compatibility. + _.template = function(text, settings, oldSettings) { + if (!settings && oldSettings) settings = oldSettings; + settings = _.defaults({}, settings, _.templateSettings); + + // Combine delimiters into one regular expression via alternation. + var matcher = RegExp([ + (settings.escape || noMatch).source, + (settings.interpolate || noMatch).source, + (settings.evaluate || noMatch).source + ].join('|') + '|$', 'g'); + + // Compile the template source, escaping string literals appropriately. + var index = 0; + var source = "__p+='"; + text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { + source += text.slice(index, offset).replace(escapeRegExp, escapeChar); + index = offset + match.length; + + if (escape) { + source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; + } else if (interpolate) { + source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; + } else if (evaluate) { + source += "';\n" + evaluate + "\n__p+='"; + } + + // Adobe VMs need the match returned to produce the correct offset. + return match; + }); + source += "';\n"; + + // If a variable is not specified, place data values in local scope. + if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; + + source = "var __t,__p='',__j=Array.prototype.join," + + "print=function(){__p+=__j.call(arguments,'');};\n" + + source + 'return __p;\n'; + + var render; + try { + render = new Function(settings.variable || 'obj', '_', source); + } catch (e) { + e.source = source; + throw e; + } + + var template = function(data) { + return render.call(this, data, _); + }; + + // Provide the compiled source as a convenience for precompilation. + var argument = settings.variable || 'obj'; + template.source = 'function(' + argument + '){\n' + source + '}'; + + return template; + }; + + // Add a "chain" function. Start chaining a wrapped Underscore object. + _.chain = function(obj) { + var instance = _(obj); + instance._chain = true; + return instance; + }; + + // OOP + // --------------- + // If Underscore is called as a function, it returns a wrapped object that + // can be used OO-style. This wrapper holds altered versions of all the + // underscore functions. Wrapped objects may be chained. + + // Helper function to continue chaining intermediate results. + var chainResult = function(instance, obj) { + return instance._chain ? _(obj).chain() : obj; + }; + + // Add your own custom functions to the Underscore object. + _.mixin = function(obj) { + _.each(_.functions(obj), function(name) { + var func = _[name] = obj[name]; + _.prototype[name] = function() { + var args = [this._wrapped]; + push.apply(args, arguments); + return chainResult(this, func.apply(_, args)); + }; + }); + }; + + // Add all of the Underscore functions to the wrapper object. + _.mixin(_); + + // Add all mutator Array functions to the wrapper. + _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { + var method = ArrayProto[name]; + _.prototype[name] = function() { + var obj = this._wrapped; + method.apply(obj, arguments); + if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0]; + return chainResult(this, obj); + }; + }); + + // Add all accessor Array functions to the wrapper. + _.each(['concat', 'join', 'slice'], function(name) { + var method = ArrayProto[name]; + _.prototype[name] = function() { + return chainResult(this, method.apply(this._wrapped, arguments)); + }; + }); + + // Extracts the result from a wrapped and chained object. + _.prototype.value = function() { + return this._wrapped; + }; + + // Provide unwrapping proxy for some methods used in engine operations + // such as arithmetic and JSON stringification. + _.prototype.valueOf = _.prototype.toJSON = _.prototype.value; + + _.prototype.toString = function() { + return '' + this._wrapped; + }; + + // AMD registration happens at the end for compatibility with AMD loaders + // that may not enforce next-turn semantics on modules. Even though general + // practice for AMD registration is to be anonymous, underscore registers + // as a named module because, like jQuery, it is a base library that is + // popular enough to be bundled in a third party lib, but not be part of + // an AMD load request. Those cases could generate an error when an + // anonymous define() is called outside of a loader request. + if (typeof define == 'function' && define.amd) { + define('underscore', [], function() { + return _; + }); + } +}()); |