1 /* See license.txt for terms of usage */
  2 
  3 define([
  4     "firebug/lib/trace",
  5     "firebug/lib/string",
  6     "firebug/lib/deprecated",
  7     "firebug/lib/function",
  8 ],
  9 function(FBTrace, Str, Deprecated, Func) {
 10 "use strict";
 11 // xxxFlorent: TODO add that specific tag in jsdoc...
 12 
 13 // ********************************************************************************************* //
 14 // Constants
 15 
 16 var Cu = Components.utils;
 17 
 18 /**
 19  * @name Obj
 20  * @lib Utility for objects
 21  */
 22 var Obj = {};
 23 
 24 // ********************************************************************************************* //
 25 
 26 // xxxFlorent: TODO: [REST]
 27 
 28 Obj.bind = Deprecated.deprecated("Use either (depending on the case): Func.bindRight, "+
 29 "Func.bindFixed or Function.prototype.bind instead", Func.bindRight);
 30 
 31 Obj.bindFixed = Deprecated.deprecated("Please, use Func.bindFixed instead now", Func.bindFixed);
 32 
 33 // xxxFlorent: [ES6-REST]
 34 /**
 35  * Clones and extend the object (first parameters) with other objects (following parameters).
 36  *
 37  * @param {Object} parentObject The object to clone and extend
 38  * @param {Object} ...extensions the extensions
 39  *
 40  * @example
 41  * var parentObj = {foo: "foo" };
 42  * var newObj = Obj.extend(parentObj, {bar: "bar"}); // => {foo: "foo", bar: "bar"}
 43  */
 44 Obj.extend = function(parentObject/*, ...extensions*/)
 45 {
 46     if (arguments.length < 2)
 47     {
 48         FBTrace.sysout("object.extend; ERROR", arguments);
 49         throw new Error("Obj.extend on undefined object");
 50     }
 51 
 52     var extensions = Array.prototype.slice.call(arguments, 1);
 53     var newOb = Object.create(parentObject);
 54 
 55     for (var i = 0, len = arguments.length; i < len; ++i)
 56     {
 57         for (var prop in arguments[i])
 58             newOb[prop] = arguments[i][prop];
 59     }
 60 
 61     return newOb;
 62 };
 63 
 64 /**
 65  * Creates a new instance inheriting from a parent "class".
 66  * That class is then extended with child properties.
 67  *
 68  * @param {Object} protototypeParent The parent "class" prototype
 69  * @param {Object} childProperties The properties extending the new object
 70  *
 71  * @return {Object} the new object
 72  */
 73 Obj.descend = function(prototypeParent, childProperties)
 74 {
 75     function protoSetter() {};
 76     protoSetter.prototype = prototypeParent;
 77     var newOb = new protoSetter();
 78     for (var n in childProperties)
 79         newOb[n] = childProperties[n];
 80     return newOb;
 81 };
 82 
 83 // ************************************************************************************************
 84 
 85 /**
 86  * Returns true if the passed object has any properties, otherwise returns false.
 87  *
 88  * @param {Object} ob Inspected object
 89  * @param {Object} nonEnumProps If set to true, check also non-enumerable properties (optional)
 90  * @param {Object} ownPropsOnly If set to true, only check own properties not inherited (optional)
 91  */
 92 Obj.hasProperties = function(ob, nonEnumProps, ownPropsOnly)
 93 {
 94     try
 95     {
 96         if (!ob)
 97             return false;
 98 
 99         var obString = Str.safeToString(ob);
100         if (obString === "[xpconnect wrapped native prototype]")
101         {
102             return true;
103         }
104 
105         // The default case (both options false) is relatively simple.
106         // Just use for..in loop.
107         if (!nonEnumProps && !ownPropsOnly)
108         {
109             for (var name in ob)
110             {
111                 // Try to access the property before declaring existing properties.
112                 // It's because some properties can't be read see:
113                 // issue 3843, https://bugzilla.mozilla.org/show_bug.cgi?id=455013
114                 var value = ob[name];
115                 return true;
116             }
117             return false;
118         }
119 
120         var type = typeof(ob);
121         if (type == "string" && ob.length)
122             return true;
123 
124         if (type === "number" || type === "boolean" || type === "undefined" || ob === null)
125             return false;
126 
127         if (nonEnumProps)
128             props = Object.getOwnPropertyNames(ob);
129         else
130             props = Object.keys(ob);
131 
132         if (props.length)
133         {
134             // Try to access the property before declaring existing properties.
135             // It's because some properties can't be read see:
136             // issue 3843, https://bugzilla.mozilla.org/show_bug.cgi?id=455013
137             var value = ob[props[0]];
138             return true;
139         }
140 
141         // Not interested in inherited properties, bail out.
142         if (ownPropsOnly)
143             return false;
144 
145         // Climb prototype chain.
146         var inheritedProps = [];
147         var parent = Object.getPrototypeOf(ob);
148         if (parent)
149             return this.hasProperties(parent, nonEnumProps, ownPropsOnly);
150     }
151     catch (exc)
152     {
153         // Primitive (non string) objects will throw an exception when passed into
154         // Object.keys or Object.getOwnPropertyNames APIs.
155         // There are also many "security error" exceptions I guess none of which are really
156         // necessary to display in the FBTrace console, so, remove the tracing for now.
157         // if (FBTrace.DBG_ERRORS)
158         //     FBTrace.sysout("lib.hasProperties(" + Str.safeToString(ob) + ") ERROR " + exc, exc);
159 
160         // workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=648560
161         if (ob.wrappedJSObject)
162             return true;
163     }
164 
165     return false;
166 };
167 
168 /**
169  * Returns the prototype of an object, or null if the function fails to do so.
170  *
171  * @deprecated use <code>myObj.prototype</code> instead (plus clever checks before)
172  */
173 Obj.getPrototype = Deprecated.deprecated("use myObj.prototype instead (+ clever checks before)",
174 function(ob)
175 {
176     try
177     {
178         return ob.prototype;
179     } catch (exc) {}
180     return null;
181 });
182 
183 /**
184  * Returns a unique ID (random integer between 0 and 65536)
185  *
186  * @return {Number} the random number
187  */
188 Obj.getUniqueId = function()
189 {
190     return this.getRandomInt(0,65536);
191 }
192 
193 /**
194  * Returns a random integer between min and max
195  *
196  * @param {Number} min The minimum
197  * @param {Number} max The maximum
198  *
199  * @return {Number} the random number
200  */
201 Obj.getRandomInt = function(min, max)
202 {
203     return Math.floor(Math.random() * (max - min + 1) + min);
204 }
205 
206 // xxxFlorent: not sure it is true... But I couldn't find any case where `instanceof` 
207 //             didn't work correctly cross-window
208 /**
209  * Cross Window instanceof
210  *
211  * @param {Object} obj The object to test
212  * @param {*} type The type (local to this window)
213  *
214  * @returns {Boolean} true if the test succeeded, false otherwise
215  *
216  * @deprecated use <code>instanceof</code> instead
217  */
218 Obj.XW_instanceof = Deprecated.deprecated("use `instanceof` instead", function(obj, type)
219 {
220     if (obj instanceof type)
221         return true;  // within-window test
222 
223     if (!type)
224         return false;
225 
226     if (!obj)
227         return (type == "undefined");
228 
229     // compare strings: obj constructor.name to type.name.
230     // This is not perfect, we should compare type.prototype to object.__proto__,
231     // but mostly code does not change the constructor object.
232     do
233     {
234         // then the function that constructed us is the argument
235         if (obj.constructor && obj.constructor.name == type.name)
236             return true;
237     }
238     while(obj = obj.__proto__);  // walk the prototype chain.
239 
240     return false;
241 
242     // https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Property_Inheritance_Revisited
243     // /Determining_Instance_Relationships
244 });
245 
246 /**
247  * Tells if the given property of the provided object is a non-native getter or not.
248  * This method depends on PropertyPanel.jsm module available in Firefox 5+
249  * isNonNativeGetter has been introduced in Firefox 7
250  * The method has been moved to WebConsoleUtils.jsm in Fx 18
251  *
252  * @param {object} obj The object that contains the property.
253  * @param {string} propName The property you want to check if it is a getter or not.
254  * @return {boolean} True if the given property is a getter, false otherwise.
255  */
256 Obj.isNonNativeGetter = function(obj, propName)
257 {
258     try
259     {
260         var scope = {};
261         Cu.import("resource://gre/modules/devtools/WebConsoleUtils.jsm", scope);
262 
263         if (scope.WebConsoleUtils.isNonNativeGetter)
264         {
265             Obj.isNonNativeGetter = function(obj, propName)
266             {
267                 return scope.WebConsoleUtils.isNonNativeGetter(obj, propName);
268             }
269 
270             return Obj.isNonNativeGetter(obj, propName);
271         }
272     }
273     catch (err)
274     {
275         if (FBTrace.DBG_ERRORS)
276             FBTrace.sysout("Obj.isNonNativeGetter; EXCEPTION " + err, err);
277     }
278 
279     // OK, the method isn't available let's use an empty implementation
280     Obj.isNonNativeGetter = function()
281     {
282         if (FBTrace.DBG_ERRORS)
283             FBTrace.sysout("Obj.isNonNativeGetter; ERROR built-in method not found!");
284         return true;
285     }
286 
287     return true;
288 }
289 
290 // ********************************************************************************************* //
291 
292 return Obj;
293 
294 // ********************************************************************************************* //
295 });
296