@qooxdoo/framework
Version:
The JS Framework for Coders
485 lines (436 loc) • 15.5 kB
JavaScript
/* ************************************************************************
qooxdoo - the new era of web development
http://qooxdoo.org
Copyright:
2006, 2007 Derrell Lipman
2011 1&1 Internet AG, Germany, http://www.1und1.de
License:
MIT: https://opensource.org/licenses/MIT
See the LICENSE file in the project's top-level directory for details.
Authors:
* Derrell Lipman (derrell)
* Daniel Wagner (d_wagner)
************************************************************************ */
/**
* Useful debug capabilities
* @ignore(qx.ui.decoration.IDecorator)
* @ignore(qx.theme.manager.Decoration)
* @ignore(qx.ui.core.queue.Dispose)
* @ignore(qx.bom.Font)
* @ignore(qx.theme.manager.Font)
*/
qx.Class.define("qx.dev.Debug",
{
statics :
{
/**
* Flag that shows whether dispose profiling is currently active
* @internal
*/
disposeProfilingActive : false,
/**
* Recursively display an object (as a debug message)
*
*
* @param obj {Object}
* The object to be recursively displayed
*
* @param initialMessage {String|null}
* The initial message to be displayed.
*
* @param maxLevel {Integer ? 10}
* The maximum level of recursion. Objects beyond this level will not
* be displayed.
*
*/
debugObject : function(obj, initialMessage, maxLevel)
{
// We've compiled the complete message. Give 'em what they came for!
qx.log.Logger.debug(this,
qx.dev.Debug.debugObjectToString(obj,
initialMessage,
maxLevel,
false));
},
/**
* Recursively display an object (into a string)
*
*
* @param obj {Object}
* The object to be recursively displayed
*
* @param initialMessage {String|null}
* The initial message to be displayed.
*
* @param maxLevel {Integer ? 10}
* The maximum level of recursion. Objects beyond this level will not
* be displayed.
*
* @param bHtml {Boolean ? false}
* If true, then render the debug message in HTML;
* Otherwise, use spaces for indentation and "\n" for end of line.
*
* @return {String}
* The string containing the recursive display of the object
*
* @lint ignoreUnused(prop)
*/
debugObjectToString : function(obj, initialMessage, maxLevel, bHtml)
{
// If a maximum recursion level was not specified...
if (!maxLevel)
{
// ... then create one arbitrarily
maxLevel = 10;
}
// If they want html, the differences are "<br>" instead of "\n"
// and how we do the indentation. Define the end-of-line string
// and a start-of-line function.
var eol = (bHtml ? "</span><br>" : "\n");
var sol = function(currentLevel)
{
var indentStr;
if (! bHtml)
{
indentStr = "";
for (var i = 0; i < currentLevel; i++)
{
indentStr += " ";
}
}
else
{
indentStr =
"<span style='padding-left:" + (currentLevel * 8) + "px;'>";
}
return indentStr;
};
// Initialize an empty message to be displayed
var message = "";
// Function to recursively display an object
var displayObj = function(obj, level, maxLevel)
{
// If we've exceeded the maximum recursion level...
if (level > maxLevel)
{
// ... then tell 'em so, and get outta dodge.
message += (
sol(level) +
"*** TOO MUCH RECURSION: not displaying ***" +
eol);
return;
}
// Is this an ordinary non-recursive item?
if (typeof (obj) != "object")
{
// Yup. Just add it to the message.
message += sol(level) + obj + eol;
return;
}
// We have an object or array. For each child...
for (var prop in obj)
{
// Is this child a recursive item?
if (typeof (obj[prop]) == "object")
{
try
{
// Yup. Determine the type and add it to the message
if (obj[prop] instanceof Array)
{
message += sol(level) + prop + ": " + "Array" + eol;
}
else if (obj[prop] === null)
{
message += sol(level) + prop + ": " + "null" + eol;
continue;
}
else if (obj[prop] === undefined)
{
message += sol(level) + prop + ": " + "undefined" + eol;
continue;
}
else
{
message += sol(level) + prop + ": " + "Object" + eol;
}
// Recurse into it to display its children.
displayObj(obj[prop], level + 1, maxLevel);
}
catch (e)
{
message +=
sol(level) + prop + ": EXCEPTION expanding property" + eol;
}
}
else
{
// We have an ordinary non-recursive item. Add it to the message.
message += sol(level) + prop + ": " + obj[prop] + eol;
}
}
};
// Was an initial message provided?
if (initialMessage)
{
// Yup. Add it to the displayable message.
message += sol(0) + initialMessage + eol;
}
if (obj instanceof Array)
{
message += sol(0) + "Array, length=" + obj.length + ":" + eol;
}
else if (typeof(obj) == "object")
{
var count = 0;
for (var prop in obj)
{
count++;
}
message += sol(0) + "Object, count=" + count + ":" + eol;
}
message +=
sol(0) +
"------------------------------------------------------------" +
eol;
try
{
// Recursively display this object
displayObj(obj, 0, maxLevel);
}
catch(ex)
{
message += sol(0) + "*** EXCEPTION (" + ex + ") ***" + eol;
}
message +=
sol(0) +
"============================================================" +
eol;
return message;
},
/**
* Get the name of a member/static function or constructor defined using the new style class definition.
* If the function could not be found <code>null</code> is returned.
*
* This function uses a linear search, so don't use it in performance critical
* code.
*
* @param func {Function} member function to get the name of.
* @param functionType {String?"all"} Where to look for the function. Possible values are "members", "statics", "constructor", "all"
* @return {String|null} Name of the function (null if not found).
*/
getFunctionName: function(func, functionType)
{
var clazz = func.self;
if (!clazz) {
return null;
}
// unwrap
while(func.wrapper) {
func = func.wrapper;
}
switch (functionType)
{
case "construct":
return func == clazz ? "construct" : null;
case "members":
return qx.lang.Object.getKeyFromValue(clazz, func);
case "statics":
return qx.lang.Object.getKeyFromValue(clazz.prototype, func);
default:
// constructor
if (func == clazz) {
return "construct";
}
return (
qx.lang.Object.getKeyFromValue(clazz.prototype, func) ||
qx.lang.Object.getKeyFromValue(clazz, func) ||
null
);
}
},
/**
* Returns a string representing the given model. The string will include
* all model objects to a given recursive depth.
*
* @param model {qx.core.Object} The model object.
* @param maxLevel {Number ? 10} The amount of max recursive depth.
* @param html {Boolean ? false} If the returned string should have \n\r as
* newline of <br>.
* @param indent {Number ? 1} The indentation level.
* (Needed for the recursion)
*
* @return {String} A string representation of the given model.
*/
debugProperties: function(model, maxLevel, html, indent) {
// set the default max depth of the recursion
if (maxLevel == null) {
maxLevel = 10;
}
// set the default startin indent
if (indent == null) {
indent = 1;
}
var newLine = "";
html ? newLine = "<br>" : newLine = "\r\n";
var message = "";
if (
qx.lang.Type.isNumber(model)
|| qx.lang.Type.isString(model)
|| qx.lang.Type.isBoolean(model)
|| model == null
|| maxLevel <= 0
) {
return model;
} else if (qx.Class.hasInterface(model.constructor, qx.data.IListData)) {
// go threw the data structure
for (var i = 0; i < model.length; i++) {
// print out the indentation
for (var j = 0; j < indent; j++) {
message += "-";
}
message += "index(" + i + "): "
+ this.debugProperties(model.getItem(i), maxLevel - 1, html, indent + 1)
+ newLine;
}
return message + newLine;
} else if (model.constructor != null) {
// go threw all properties
var properties = model.constructor.$$properties;
for (var key in properties) {
message += newLine;
// print out the indentation
for (var j = 0; j < indent; j++) {
message += "-";
}
message += " " + key + ": " + this.debugProperties(
model["get" + qx.lang.String.firstUp(key)](), maxLevel - 1, html, indent + 1
);
}
return message;
}
return "";
},
/**
* Starts a dispose profiling session. Use {@link #stopDisposeProfiling} to
* get the results
*
* @return {Number|undefined}
* Returns a handle which may be passed to {@link #stopDisposeProfiling}
* indicating the start point for searching for undisposed objects.
*/
startDisposeProfiling : qx.core.Environment.select("qx.debug.dispose", {
"true" : function() {
this.disposeProfilingActive = true;
this.__nextHashFirst = qx.core.ObjectRegistry.getNextHash();
return this.__nextHashFirst;
},
"default" : (function() {})
}),
/**
* Returns a list of any (qx) objects that were created but not disposed
* since {@link #startDisposeProfiling} was called. Also returns a stack
* trace recorded at the time the object was created. The starting point
* of dispose tracking is reset, so to do further dispose profiling, a new
* call to {@link #startDisposeProfile} must be issued.
*
* @signature function(checkFunction)
* @param checkFunction {Function} Custom check function. It is called once
* for each object that was created after dispose profiling was started,
* with the object as the only parameter. If it returns false, the object
* will not be included in the returned list
* @return {Map[]} List of maps. Each map contains two keys:
* <code>object</code> and <code>stackTrace</code>
*/
stopDisposeProfiling : qx.core.Environment.select("qx.debug.dispose", {
"true" : function(checkFunction, startHandle) {
if (!this.__nextHashFirst) {
qx.log.Logger.error("Call " + this.classname + ".startDisposeProfiling first.");
return [];
}
//qx.core.ObjectRegistry.saveStackTraces = false;
this.disposeProfilingActive = false;
var undisposedObjects = this.showDisposeProfiling(checkFunction, startHandle || this.__nextHashFirst);
delete this.__nextHashFirst;
return undisposedObjects;
},
"default" : (function() {})
}),
/**
* Returns a list of any (qx) objects that were created but not disposed
* since {@link #startDisposeProfiling} was called. Also returns a stack
* trace recorded at the time the object was created. Does not restart the
* tracking point, so subsequent calls to this method will continue to
* show undisposed objects since {@link #startDisposeProfiling} was
* called.
*
* @signature function(checkFunction)
* @param checkFunction {Function} Custom check function. It is called once
* for each object that was created after dispose profiling was started,
* with the object as the only parameter. If it returns false, the object
* will not be included in the returned list
* @return {Map[]} List of maps. Each map contains two keys:
* <code>object</code> and <code>stackTrace</code>
*/
showDisposeProfiling : qx.core.Environment.select("qx.debug.dispose", {
"true" : function(checkFunction) {
var undisposedObjects = [];
// If destroy calls another destroy, flushing the queue once is not enough
if (qx.Class.getByName("qx.ui.core.queue.Dispose")) {
while (!qx.ui.core.queue.Dispose.isEmpty()) {
qx.ui.core.queue.Dispose.flush();
}
}
var nextHashLast = qx.core.ObjectRegistry.getNextHash();
var postId = qx.core.ObjectRegistry.getPostId();
var traces = qx.core.ObjectRegistry.getStackTraces();
for (var hash = this.__nextHashFirst; hash<nextHashLast; hash++) {
var obj = qx.core.ObjectRegistry.fromHashCode(hash + postId);
if (obj && obj.isDisposed && !obj.isDisposed()) {
// User-defined check
if (checkFunction && typeof checkFunction == "function" &&
!checkFunction(obj)) {
continue;
}
// Singleton instances
if (obj.constructor.$$instance === obj) {
continue;
}
// Event handlers
if (qx.Class.implementsInterface(obj, qx.event.IEventHandler)) {
continue;
}
// Pooled Decorators
if (obj.$$pooled) {
continue;
}
// Dynamic decorators
if (qx.Interface.getByName("qx.ui.decoration.IDecorator") &&
qx.Class.getByName("qx.theme.manager.Decoration") &&
qx.Class.implementsInterface(obj, qx.ui.decoration.IDecorator) &&
qx.theme.manager.Decoration.getInstance().isCached(obj)) {
continue;
}
// ignored objects
if (obj.$$ignoreDisposeWarning) {
continue;
}
// Dynamic fonts
if (qx.Class.getByName("qx.bom.Font") &&
obj instanceof qx.bom.Font &&
qx.Class.getByName("qx.theme.manager.Font") &&
qx.theme.manager.Font.getInstance().isDynamic(obj)) {
continue;
}
undisposedObjects.push({
object : obj,
stackTrace : traces[hash + postId] ? traces[hash + postId] : null
});
}
}
return undisposedObjects;
},
"default" : (function() {})
})
}
});