31176 lines
No EOL
938 KiB
JavaScript
31176 lines
No EOL
938 KiB
JavaScript
(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();
|
|
// ************************************************************************************************
|
|
|
|
})(); |