diff options
author | 2017-02-07 15:03:57 -0500 | |
---|---|---|
committer | 2017-02-07 15:05:15 -0500 | |
commit | 4ad39a5c96dd99acf819ce189b13fec946d7506b (patch) | |
tree | a1449286441947cc3d07a45227fa0d6f978e1a7d /ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/src/firebug-lite-debug.js | |
parent | 5500448cbd1f374d0ac743ee2fd636fe2d3c0027 (diff) |
Initial OpenECOMP Portal commit
Change-Id: I804b80e0830c092e307da1599bd9fbb5c3e2da77
Signed-off-by: talasila <talasila@research.att.com>
Diffstat (limited to 'ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/src/firebug-lite-debug.js')
-rw-r--r-- | ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/src/firebug-lite-debug.js | 31176 |
1 files changed, 31176 insertions, 0 deletions
diff --git a/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/src/firebug-lite-debug.js b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/src/firebug-lite-debug.js new file mode 100644 index 00000000..40b1ae70 --- /dev/null +++ b/ecomp-portal-FE/client/bower_components/lodash/vendor/firebug-lite/src/firebug-lite-debug.js @@ -0,0 +1,31176 @@ +(function(){ + +/*!************************************************************* + * + * Firebug Lite 1.4.0 + * + * Copyright (c) 2007, Parakey Inc. + * Released under BSD license. + * More information: http://getfirebug.com/firebuglite + * + **************************************************************/ + +/*! + * CSS selectors powered by: + * + * Sizzle CSS Selector Engine - v1.0 + * Copyright 2009, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ + +/** @namespace describe lib */ + +// FIXME: xxxpedro if we use "var FBL = {}" the FBL won't appear in the DOM Panel in IE +var FBL = {}; + +( /** @scope s_lib @this FBL */ function() { +// ************************************************************************************************ + +// ************************************************************************************************ +// Constants + +var productionDir = "http://getfirebug.com/releases/lite/"; +var bookmarkletVersion = 4; + +// ************************************************************************************************ + +var reNotWhitespace = /[^\s]/; +var reSplitFile = /:\/{1,3}(.*?)\/([^\/]*?)\/?($|\?.*)/; + +// Globals +this.reJavascript = /\s*javascript:\s*(.*)/; +this.reChrome = /chrome:\/\/([^\/]*)\//; +this.reFile = /file:\/\/([^\/]*)\//; + + +// ************************************************************************************************ +// properties + +var userAgent = navigator.userAgent.toLowerCase(); +this.isFirefox = /firefox/.test(userAgent); +this.isOpera = /opera/.test(userAgent); +this.isSafari = /webkit/.test(userAgent); +this.isIE = /msie/.test(userAgent) && !/opera/.test(userAgent); +this.isIE6 = /msie 6/i.test(navigator.appVersion); +this.browserVersion = (userAgent.match( /.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [0,'0'])[1]; +this.isIElt8 = this.isIE && (this.browserVersion-0 < 8); + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +this.NS = null; +this.pixelsPerInch = null; + + +// ************************************************************************************************ +// Namespaces + +var namespaces = []; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +this.ns = function(fn) +{ + var ns = {}; + namespaces.push(fn, ns); + return ns; +}; + +var FBTrace = null; + +this.initialize = function() +{ + // Firebug Lite is already running in persistent mode so we just quit + if (window.firebug && firebug.firebuglite || window.console && console.firebuglite) + return; + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // initialize environment + + // point the FBTrace object to the local variable + if (FBL.FBTrace) + FBTrace = FBL.FBTrace; + else + FBTrace = FBL.FBTrace = {}; + + // check if the actual window is a persisted chrome context + var isChromeContext = window.Firebug && typeof window.Firebug.SharedEnv == "object"; + + // chrome context of the persistent application + if (isChromeContext) + { + // TODO: xxxpedro persist - make a better synchronization + sharedEnv = window.Firebug.SharedEnv; + delete window.Firebug.SharedEnv; + + FBL.Env = sharedEnv; + FBL.Env.isChromeContext = true; + FBTrace.messageQueue = FBL.Env.traceMessageQueue; + } + // non-persistent application + else + { + FBL.NS = document.documentElement.namespaceURI; + FBL.Env.browser = window; + FBL.Env.destroy = destroyEnvironment; + + if (document.documentElement.getAttribute("debug") == "true") + FBL.Env.Options.startOpened = true; + + // find the URL location of the loaded application + findLocation(); + + // TODO: get preferences here... + // The problem is that we don't have the Firebug object yet, so we can't use + // Firebug.loadPrefs. We're using the Store module directly instead. + var prefs = FBL.Store.get("FirebugLite") || {}; + FBL.Env.DefaultOptions = FBL.Env.Options; + FBL.Env.Options = FBL.extend(FBL.Env.Options, prefs.options || {}); + + if (FBL.isFirefox && + typeof FBL.Env.browser.console == "object" && + FBL.Env.browser.console.firebug && + FBL.Env.Options.disableWhenFirebugActive) + return; + } + + // exposes the FBL to the global namespace when in debug mode + if (FBL.Env.isDebugMode) + { + FBL.Env.browser.FBL = FBL; + } + + // check browser compatibilities + this.isQuiksMode = FBL.Env.browser.document.compatMode == "BackCompat"; + this.isIEQuiksMode = this.isIE && this.isQuiksMode; + this.isIEStantandMode = this.isIE && !this.isQuiksMode; + + this.noFixedPosition = this.isIE6 || this.isIEQuiksMode; + + // after creating/synchronizing the environment, initialize the FBTrace module + if (FBL.Env.Options.enableTrace) FBTrace.initialize(); + + if (FBTrace.DBG_INITIALIZE && isChromeContext) FBTrace.sysout("FBL.initialize - persistent application", "initialize chrome context"); + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // initialize namespaces + + if (FBTrace.DBG_INITIALIZE) FBTrace.sysout("FBL.initialize", namespaces.length/2+" namespaces BEGIN"); + + for (var i = 0; i < namespaces.length; i += 2) + { + var fn = namespaces[i]; + var ns = namespaces[i+1]; + fn.apply(ns); + } + + if (FBTrace.DBG_INITIALIZE) { + FBTrace.sysout("FBL.initialize", namespaces.length/2+" namespaces END"); + FBTrace.sysout("FBL waitForDocument", "waiting document load"); + } + + FBL.Ajax.initialize(); + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // finish environment initialization + FBL.Firebug.loadPrefs(); + + if (FBL.Env.Options.enablePersistent) + { + // TODO: xxxpedro persist - make a better synchronization + if (isChromeContext) + { + FBL.FirebugChrome.clone(FBL.Env.FirebugChrome); + } + else + { + FBL.Env.FirebugChrome = FBL.FirebugChrome; + FBL.Env.traceMessageQueue = FBTrace.messageQueue; + } + } + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // wait document load + + waitForDocument(); +}; + +var waitForDocument = function waitForDocument() +{ + // document.body not available in XML+XSL documents in Firefox + var doc = FBL.Env.browser.document; + var body = doc.getElementsByTagName("body")[0]; + + if (body) + { + calculatePixelsPerInch(doc, body); + onDocumentLoad(); + } + else + setTimeout(waitForDocument, 50); +}; + +var onDocumentLoad = function onDocumentLoad() +{ + if (FBTrace.DBG_INITIALIZE) FBTrace.sysout("FBL onDocumentLoad", "document loaded"); + + // fix IE6 problem with cache of background images, causing a lot of flickering + if (FBL.isIE6) + fixIE6BackgroundImageCache(); + + // chrome context of the persistent application + if (FBL.Env.Options.enablePersistent && FBL.Env.isChromeContext) + { + // finally, start the application in the chrome context + FBL.Firebug.initialize(); + + // if is not development mode, remove the shared environment cache object + // used to synchronize the both persistent contexts + if (!FBL.Env.isDevelopmentMode) + { + sharedEnv.destroy(); + sharedEnv = null; + } + } + // non-persistent application + else + { + FBL.FirebugChrome.create(); + } +}; + +// ************************************************************************************************ +// Env + +var sharedEnv; + +this.Env = +{ + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Env Options (will be transported to Firebug options) + Options: + { + saveCookies: true, + + saveWindowPosition: false, + saveCommandLineHistory: false, + + startOpened: false, + startInNewWindow: false, + showIconWhenHidden: true, + + overrideConsole: true, + ignoreFirebugElements: true, + disableWhenFirebugActive: true, + + disableXHRListener: false, + disableResourceFetching: false, + + enableTrace: false, + enablePersistent: false + + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Library location + Location: + { + sourceDir: null, + baseDir: null, + skinDir: null, + skin: null, + app: null + }, + + skin: "xp", + useLocalSkin: false, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Env states + isDevelopmentMode: false, + isDebugMode: false, + isChromeContext: false, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Env references + browser: null, + chrome: null +}; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +var destroyEnvironment = function destroyEnvironment() +{ + setTimeout(function() + { + FBL = null; + }, 100); +}; + +// ************************************************************************************************ +// Library location + +var findLocation = function findLocation() +{ + var reFirebugFile = /(firebug-lite(?:-\w+)?(?:\.js|\.jgz))(?:#(.+))?$/; + var reGetFirebugSite = /(?:http|https):\/\/getfirebug.com\//; + var isGetFirebugSite; + + var rePath = /^(.*\/)/; + var reProtocol = /^\w+:\/\//; + var path = null; + var doc = document; + + // Firebug Lite 1.3.0 bookmarklet identification + var script = doc.getElementById("FirebugLite"); + + var scriptSrc; + var hasSrcAttribute = true; + + // If the script was loaded via bookmarklet, we already have the script tag + if (script) + { + scriptSrc = script.src; + file = reFirebugFile.exec(scriptSrc); + + var version = script.getAttribute("FirebugLite"); + var number = version ? parseInt(version) : 0; + + if (!version || !number || number < bookmarkletVersion) + { + FBL.Env.bookmarkletOutdated = true; + } + } + // otherwise we must search for the correct script tag + else + { + for(var i=0, s=doc.getElementsByTagName("script"), si; si=s[i]; i++) + { + var file = null; + if ( si.nodeName.toLowerCase() == "script" ) + { + if (file = reFirebugFile.exec(si.getAttribute("firebugSrc"))) + { + scriptSrc = si.getAttribute("firebugSrc"); + hasSrcAttribute = false; + } + else if (file = reFirebugFile.exec(si.src)) + { + scriptSrc = si.src; + } + else + continue; + + script = si; + break; + } + } + } + + // mark the script tag to be ignored by Firebug Lite + if (script) + script.firebugIgnore = true; + + if (file) + { + var fileName = file[1]; + var fileOptions = file[2]; + + // absolute path + if (reProtocol.test(scriptSrc)) { + path = rePath.exec(scriptSrc)[1]; + + } + // relative path + else + { + var r = rePath.exec(scriptSrc); + var src = r ? r[1] : scriptSrc; + var backDir = /^((?:\.\.\/)+)(.*)/.exec(src); + var reLastDir = /^(.*\/)[^\/]+\/$/; + path = rePath.exec(location.href)[1]; + + // "../some/path" + if (backDir) + { + var j = backDir[1].length/3; + var p; + while (j-- > 0) + path = reLastDir.exec(path)[1]; + + path += backDir[2]; + } + + else if(src.indexOf("/") != -1) + { + // "./some/path" + if(/^\.\/./.test(src)) + { + path += src.substring(2); + } + // "/some/path" + else if(/^\/./.test(src)) + { + var domain = /^(\w+:\/\/[^\/]+)/.exec(path); + path = domain[1] + src; + } + // "some/path" + else + { + path += src; + } + } + } + } + + FBL.Env.isChromeExtension = script && script.getAttribute("extension") == "Chrome"; + if (FBL.Env.isChromeExtension) + { + path = productionDir; + FBL.Env.bookmarkletOutdated = false; + script = {innerHTML: "{showIconWhenHidden:false}"}; + } + + isGetFirebugSite = reGetFirebugSite.test(path); + + if (isGetFirebugSite && path.indexOf("/releases/lite/") == -1) + { + // See Issue 4587 - If we are loading the script from getfirebug.com shortcut, like + // https://getfirebug.com/firebug-lite.js, then we must manually add the full path, + // otherwise the Env.Location will hold the wrong path, which will in turn lead to + // undesirable effects like the problem in Issue 4587 + path += "releases/lite/" + (fileName == "firebug-lite-beta.js" ? "beta/" : "latest/"); + } + + var m = path && path.match(/([^\/]+)\/$/) || null; + + if (path && m) + { + var Env = FBL.Env; + + // Always use the local skin when running in the same domain + // See Issue 3554: Firebug Lite should use local images when loaded locally + Env.useLocalSkin = path.indexOf(location.protocol + "//" + location.host + "/") == 0 && + // but we cannot use the locan skin when loaded from getfirebug.com, otherwise + // the bookmarklet won't work when visiting getfirebug.com + !isGetFirebugSite; + + // detecting development and debug modes via file name + if (fileName == "firebug-lite-dev.js") + { + Env.isDevelopmentMode = true; + Env.isDebugMode = true; + } + else if (fileName == "firebug-lite-debug.js") + { + Env.isDebugMode = true; + } + + // process the <html debug="true"> + if (Env.browser.document.documentElement.getAttribute("debug") == "true") + { + Env.Options.startOpened = true; + } + + // process the Script URL Options + if (fileOptions) + { + var options = fileOptions.split(","); + + for (var i = 0, length = options.length; i < length; i++) + { + var option = options[i]; + var name, value; + + if (option.indexOf("=") != -1) + { + var parts = option.split("="); + name = parts[0]; + value = eval(unescape(parts[1])); + } + else + { + name = option; + value = true; + } + + if (name == "debug") + { + Env.isDebugMode = !!value; + } + else if (name in Env.Options) + { + Env.Options[name] = value; + } + else + { + Env[name] = value; + } + } + } + + // process the Script JSON Options + if (hasSrcAttribute) + { + var innerOptions = FBL.trim(script.innerHTML); + if (innerOptions) + { + var innerOptionsObject = eval("(" + innerOptions + ")"); + + for (var name in innerOptionsObject) + { + var value = innerOptionsObject[name]; + + if (name == "debug") + { + Env.isDebugMode = !!value; + } + else if (name in Env.Options) + { + Env.Options[name] = value; + } + else + { + Env[name] = value; + } + } + } + } + + if (!Env.Options.saveCookies) + FBL.Store.remove("FirebugLite"); + + // process the Debug Mode + if (Env.isDebugMode) + { + Env.Options.startOpened = true; + Env.Options.enableTrace = true; + Env.Options.disableWhenFirebugActive = false; + } + + var loc = Env.Location; + var isProductionRelease = path.indexOf(productionDir) != -1; + + loc.sourceDir = path; + loc.baseDir = path.substr(0, path.length - m[1].length - 1); + loc.skinDir = (isProductionRelease ? path : loc.baseDir) + "skin/" + Env.skin + "/"; + loc.skin = loc.skinDir + "firebug.html"; + loc.app = path + fileName; + } + else + { + throw new Error("Firebug Error: Library path not found"); + } +}; + +// ************************************************************************************************ +// Basics + +this.bind = function() // fn, thisObject, args => thisObject.fn(args, arguments); +{ + var args = cloneArray(arguments), fn = args.shift(), object = args.shift(); + return function() { return fn.apply(object, arrayInsert(cloneArray(args), 0, arguments)); }; +}; + +this.bindFixed = function() // fn, thisObject, args => thisObject.fn(args); +{ + var args = cloneArray(arguments), fn = args.shift(), object = args.shift(); + return function() { return fn.apply(object, args); }; +}; + +this.extend = function(l, r) +{ + var newOb = {}; + for (var n in l) + newOb[n] = l[n]; + for (var n in r) + newOb[n] = r[n]; + return newOb; +}; + +this.descend = function(prototypeParent, childProperties) +{ + function protoSetter() {}; + protoSetter.prototype = prototypeParent; + var newOb = new protoSetter(); + for (var n in childProperties) + newOb[n] = childProperties[n]; + return newOb; +}; + +this.append = function(l, r) +{ + for (var n in r) + l[n] = r[n]; + + return l; +}; + +this.keys = function(map) // At least sometimes the keys will be on user-level window objects +{ + var keys = []; + try + { + for (var name in map) // enumeration is safe + keys.push(name); // name is string, safe + } + catch (exc) + { + // Sometimes we get exceptions trying to iterate properties + } + + return keys; // return is safe +}; + +this.values = function(map) +{ + var values = []; + try + { + for (var name in map) + { + try + { + values.push(map[name]); + } + catch (exc) + { + // Sometimes we get exceptions trying to access properties + if (FBTrace.DBG_ERRORS) + FBTrace.sysout("lib.values FAILED ", exc); + } + + } + } + catch (exc) + { + // Sometimes we get exceptions trying to iterate properties + if (FBTrace.DBG_ERRORS) + FBTrace.sysout("lib.values FAILED ", exc); + } + + return values; +}; + +this.remove = function(list, item) +{ + for (var i = 0; i < list.length; ++i) + { + if (list[i] == item) + { + list.splice(i, 1); + break; + } + } +}; + +this.sliceArray = function(array, index) +{ + var slice = []; + for (var i = index; i < array.length; ++i) + slice.push(array[i]); + + return slice; +}; + +function cloneArray(array, fn) +{ + var newArray = []; + + if (fn) + for (var i = 0; i < array.length; ++i) + newArray.push(fn(array[i])); + else + for (var i = 0; i < array.length; ++i) + newArray.push(array[i]); + + return newArray; +} + +function extendArray(array, array2) +{ + var newArray = []; + newArray.push.apply(newArray, array); + newArray.push.apply(newArray, array2); + return newArray; +} + +this.extendArray = extendArray; +this.cloneArray = cloneArray; + +function arrayInsert(array, index, other) +{ + for (var i = 0; i < other.length; ++i) + array.splice(i+index, 0, other[i]); + + return array; +} + +// ************************************************************************************************ + +this.createStyleSheet = function(doc, url) +{ + //TODO: xxxpedro + //var style = doc.createElementNS("http://www.w3.org/1999/xhtml", "style"); + var style = this.createElement("link"); + style.setAttribute("charset","utf-8"); + style.firebugIgnore = true; + style.setAttribute("rel", "stylesheet"); + style.setAttribute("type", "text/css"); + style.setAttribute("href", url); + + //TODO: xxxpedro + //style.innerHTML = this.getResource(url); + return style; +}; + +this.addStyleSheet = function(doc, style) +{ + var heads = doc.getElementsByTagName("head"); + if (heads.length) + heads[0].appendChild(style); + else + doc.documentElement.appendChild(style); +}; + +this.appendStylesheet = function(doc, uri) +{ + // Make sure the stylesheet is not appended twice. + if (this.$(uri, doc)) + return; + + var styleSheet = this.createStyleSheet(doc, uri); + styleSheet.setAttribute("id", uri); + this.addStyleSheet(doc, styleSheet); +}; + +this.addScript = function(doc, id, src) +{ + var element = doc.createElementNS("http://www.w3.org/1999/xhtml", "html:script"); + element.setAttribute("type", "text/javascript"); + element.setAttribute("id", id); + if (!FBTrace.DBG_CONSOLE) + FBL.unwrapObject(element).firebugIgnore = true; + + element.innerHTML = src; + if (doc.documentElement) + doc.documentElement.appendChild(element); + else + { + // See issue 1079, the svg test case gives this error + if (FBTrace.DBG_ERRORS) + FBTrace.sysout("lib.addScript doc has no documentElement:", doc); + } + return element; +}; + + +// ************************************************************************************************ + +this.getStyle = this.isIE ? + function(el, name) + { + return el.currentStyle[name] || el.style[name] || undefined; + } + : + function(el, name) + { + return el.ownerDocument.defaultView.getComputedStyle(el,null)[name] + || el.style[name] || undefined; + }; + + +// ************************************************************************************************ +// Whitespace and Entity conversions + +var entityConversionLists = this.entityConversionLists = { + normal : { + whitespace : { + '\t' : '\u200c\u2192', + '\n' : '\u200c\u00b6', + '\r' : '\u200c\u00ac', + ' ' : '\u200c\u00b7' + } + }, + reverse : { + whitespace : { + '	' : '\t', + '
' : '\n', + '\u200c\u2192' : '\t', + '\u200c\u00b6' : '\n', + '\u200c\u00ac' : '\r', + '\u200c\u00b7' : ' ' + } + } +}; + +var normal = entityConversionLists.normal, + reverse = entityConversionLists.reverse; + +function addEntityMapToList(ccode, entity) +{ + var lists = Array.prototype.slice.call(arguments, 2), + len = lists.length, + ch = String.fromCharCode(ccode); + for (var i = 0; i < len; i++) + { + var list = lists[i]; + normal[list]=normal[list] || {}; + normal[list][ch] = '&' + entity + ';'; + reverse[list]=reverse[list] || {}; + reverse[list]['&' + entity + ';'] = ch; + } +}; + +var e = addEntityMapToList, + white = 'whitespace', + text = 'text', + attr = 'attributes', + css = 'css', + editor = 'editor'; + +e(0x0022, 'quot', attr, css); +e(0x0026, 'amp', attr, text, css); +e(0x0027, 'apos', css); +e(0x003c, 'lt', attr, text, css); +e(0x003e, 'gt', attr, text, css); +e(0xa9, 'copy', text, editor); +e(0xae, 'reg', text, editor); +e(0x2122, 'trade', text, editor); + +// See http://en.wikipedia.org/wiki/Dash +e(0x2012, '#8210', attr, text, editor); // figure dash +e(0x2013, 'ndash', attr, text, editor); // en dash +e(0x2014, 'mdash', attr, text, editor); // em dash +e(0x2015, '#8213', attr, text, editor); // horizontal bar + +e(0x00a0, 'nbsp', attr, text, white, editor); +e(0x2002, 'ensp', attr, text, white, editor); +e(0x2003, 'emsp', attr, text, white, editor); +e(0x2009, 'thinsp', attr, text, white, editor); +e(0x200c, 'zwnj', attr, text, white, editor); +e(0x200d, 'zwj', attr, text, white, editor); +e(0x200e, 'lrm', attr, text, white, editor); +e(0x200f, 'rlm', attr, text, white, editor); +e(0x200b, '#8203', attr, text, white, editor); // zero-width space (ZWSP) + +//************************************************************************************************ +// Entity escaping + +var entityConversionRegexes = { + normal : {}, + reverse : {} + }; + +var escapeEntitiesRegEx = { + normal : function(list) + { + var chars = []; + for ( var ch in list) + { + chars.push(ch); + } + return new RegExp('([' + chars.join('') + '])', 'gm'); + }, + reverse : function(list) + { + var chars = []; + for ( var ch in list) + { + chars.push(ch); + } + return new RegExp('(' + chars.join('|') + ')', 'gm'); + } +}; + +function getEscapeRegexp(direction, lists) +{ + var name = '', re; + var groups = [].concat(lists); + for (i = 0; i < groups.length; i++) + { + name += groups[i].group; + } + re = entityConversionRegexes[direction][name]; + if (!re) + { + var list = {}; + if (groups.length > 1) + { + for ( var i = 0; i < groups.length; i++) + { + var aList = entityConversionLists[direction][groups[i].group]; + for ( var item in aList) + list[item] = aList[item]; + } + } else if (groups.length==1) + { + list = entityConversionLists[direction][groups[0].group]; // faster for special case + } else { + list = {}; // perhaps should print out an error here? + } + re = entityConversionRegexes[direction][name] = escapeEntitiesRegEx[direction](list); + } + return re; +}; + +function createSimpleEscape(name, direction) +{ + return function(value) + { + var list = entityConversionLists[direction][name]; + return String(value).replace( + getEscapeRegexp(direction, { + group : name, + list : list + }), + function(ch) + { + return list[ch]; + } + ); + }; +}; + +function escapeGroupsForEntities(str, lists) +{ + lists = [].concat(lists); + var re = getEscapeRegexp('normal', lists), + split = String(str).split(re), + len = split.length, + results = [], + cur, r, i, ri = 0, l, list, last = ''; + if (!len) + return [ { + str : String(str), + group : '', + name : '' + } ]; + for (i = 0; i < len; i++) + { + cur = split[i]; + if (cur == '') + continue; + for (l = 0; l < lists.length; l++) + { + list = lists[l]; + r = entityConversionLists.normal[list.group][cur]; + // if (cur == ' ' && list.group == 'whitespace' && last == ' ') // only show for runs of more than one space + // r = ' '; + if (r) + { + results[ri] = { + 'str' : r, + 'class' : list['class'], + 'extra' : list.extra[cur] ? list['class'] + + list.extra[cur] : '' + }; + break; + } + } + // last=cur; + if (!r) + results[ri] = { + 'str' : cur, + 'class' : '', + 'extra' : '' + }; + ri++; + } + return results; +}; + +this.escapeGroupsForEntities = escapeGroupsForEntities; + + +function unescapeEntities(str, lists) +{ + var re = getEscapeRegexp('reverse', lists), + split = String(str).split(re), + len = split.length, + results = [], + cur, r, i, ri = 0, l, list; + if (!len) + return str; + lists = [].concat(lists); + for (i = 0; i < len; i++) + { + cur = split[i]; + if (cur == '') + continue; + for (l = 0; l < lists.length; l++) + { + list = lists[l]; + r = entityConversionLists.reverse[list.group][cur]; + if (r) + { + results[ri] = r; + break; + } + } + if (!r) + results[ri] = cur; + ri++; + } + return results.join('') || ''; +}; + + +// ************************************************************************************************ +// String escaping + +var escapeForTextNode = this.escapeForTextNode = createSimpleEscape('text', 'normal'); +var escapeForHtmlEditor = this.escapeForHtmlEditor = createSimpleEscape('editor', 'normal'); +var escapeForElementAttribute = this.escapeForElementAttribute = createSimpleEscape('attributes', 'normal'); +var escapeForCss = this.escapeForCss = createSimpleEscape('css', 'normal'); + +// deprecated compatibility functions +//this.deprecateEscapeHTML = createSimpleEscape('text', 'normal'); +//this.deprecatedUnescapeHTML = createSimpleEscape('text', 'reverse'); +//this.escapeHTML = deprecated("use appropriate escapeFor... function", this.deprecateEscapeHTML); +//this.unescapeHTML = deprecated("use appropriate unescapeFor... function", this.deprecatedUnescapeHTML); + +var escapeForSourceLine = this.escapeForSourceLine = createSimpleEscape('text', 'normal'); + +var unescapeWhitespace = createSimpleEscape('whitespace', 'reverse'); + +this.unescapeForTextNode = function(str) +{ + if (Firebug.showTextNodesWithWhitespace) + str = unescapeWhitespace(str); + if (!Firebug.showTextNodesWithEntities) + str = escapeForElementAttribute(str); + return str; +}; + +this.escapeNewLines = function(value) +{ + return value.replace(/\r/g, "\\r").replace(/\n/g, "\\n"); +}; + +this.stripNewLines = function(value) +{ + return typeof(value) == "string" ? value.replace(/[\r\n]/g, " ") : value; +}; + +this.escapeJS = function(value) +{ + return value.replace(/\r/g, "\\r").replace(/\n/g, "\\n").replace('"', '\\"', "g"); +}; + +function escapeHTMLAttribute(value) +{ + function replaceChars(ch) + { + switch (ch) + { + case "&": + return "&"; + case "'": + return apos; + case '"': + return quot; + } + return "?"; + }; + var apos = "'", quot = """, around = '"'; + if( value.indexOf('"') == -1 ) { + quot = '"'; + apos = "'"; + } else if( value.indexOf("'") == -1 ) { + quot = '"'; + around = "'"; + } + return around + (String(value).replace(/[&'"]/g, replaceChars)) + around; +} + + +function escapeHTML(value) +{ + function replaceChars(ch) + { + switch (ch) + { + case "<": + return "<"; + case ">": + return ">"; + case "&": + return "&"; + case "'": + return "'"; + case '"': + return """; + } + return "?"; + }; + return String(value).replace(/[<>&"']/g, replaceChars); +} + +this.escapeHTML = escapeHTML; + +this.cropString = function(text, limit) +{ + text = text + ""; + + if (!limit) + var halfLimit = 50; + else + var halfLimit = limit / 2; + + if (text.length > limit) + return this.escapeNewLines(text.substr(0, halfLimit) + "..." + text.substr(text.length-halfLimit)); + else + return this.escapeNewLines(text); +}; + +this.isWhitespace = function(text) +{ + return !reNotWhitespace.exec(text); +}; + +this.splitLines = function(text) +{ + var reSplitLines2 = /.*(:?\r\n|\n|\r)?/mg; + var lines; + if (text.match) + { + lines = text.match(reSplitLines2); + } + else + { + var str = text+""; + lines = str.match(reSplitLines2); + } + lines.pop(); + return lines; +}; + + +// ************************************************************************************************ + +this.safeToString = function(ob) +{ + if (this.isIE) + { + try + { + // FIXME: xxxpedro this is failing in IE for the global "external" object + return ob + ""; + } + catch(E) + { + FBTrace.sysout("Lib.safeToString() failed for ", ob); + return ""; + } + } + + try + { + if (ob && "toString" in ob && typeof ob.toString == "function") + return ob.toString(); + } + catch (exc) + { + // xxxpedro it is not safe to use ob+""? + return ob + ""; + ///return "[an object with no toString() function]"; + } +}; + +// ************************************************************************************************ + +this.hasProperties = function(ob) +{ + try + { + for (var name in ob) + return true; + } catch (exc) {} + return false; +}; + +// ************************************************************************************************ +// String Util + +var reTrim = /^\s+|\s+$/g; +this.trim = function(s) +{ + return s.replace(reTrim, ""); +}; + + +// ************************************************************************************************ +// Empty + +this.emptyFn = function(){}; + + + +// ************************************************************************************************ +// Visibility + +this.isVisible = function(elt) +{ + /* + if (elt instanceof XULElement) + { + //FBTrace.sysout("isVisible elt.offsetWidth: "+elt.offsetWidth+" offsetHeight:"+ elt.offsetHeight+" localName:"+ elt.localName+" nameSpace:"+elt.nameSpaceURI+"\n"); + return (!elt.hidden && !elt.collapsed); + } + /**/ + + return this.getStyle(elt, "visibility") != "hidden" && + ( elt.offsetWidth > 0 || elt.offsetHeight > 0 + || elt.tagName in invisibleTags + || elt.namespaceURI == "http://www.w3.org/2000/svg" + || elt.namespaceURI == "http://www.w3.org/1998/Math/MathML" ); +}; + +this.collapse = function(elt, collapsed) +{ + // IE6 doesn't support the [collapsed] CSS selector. IE7 does support the selector, + // but it is causing a bug (the element disappears when you set the "collapsed" + // attribute, but it doesn't appear when you remove the attribute. So, for those + // cases, we need to use the class attribute. + if (this.isIElt8) + { + if (collapsed) + this.setClass(elt, "collapsed"); + else + this.removeClass(elt, "collapsed"); + } + else + elt.setAttribute("collapsed", collapsed ? "true" : "false"); +}; + +this.obscure = function(elt, obscured) +{ + if (obscured) + this.setClass(elt, "obscured"); + else + this.removeClass(elt, "obscured"); +}; + +this.hide = function(elt, hidden) +{ + elt.style.visibility = hidden ? "hidden" : "visible"; +}; + +this.clearNode = function(node) +{ + var nodeName = " " + node.nodeName.toLowerCase() + " "; + var ignoreTags = " table tbody thead tfoot th tr td "; + + // IE can't use innerHTML of table elements + if (this.isIE && ignoreTags.indexOf(nodeName) != -1) + this.eraseNode(node); + else + node.innerHTML = ""; +}; + +this.eraseNode = function(node) +{ + while (node.lastChild) + node.removeChild(node.lastChild); +}; + +// ************************************************************************************************ +// Window iteration + +this.iterateWindows = function(win, handler) +{ + if (!win || !win.document) + return; + + handler(win); + + if (win == top || !win.frames) return; // XXXjjb hack for chromeBug + + for (var i = 0; i < win.frames.length; ++i) + { + var subWin = win.frames[i]; + if (subWin != win) + this.iterateWindows(subWin, handler); + } +}; + +this.getRootWindow = function(win) +{ + for (; win; win = win.parent) + { + if (!win.parent || win == win.parent || !this.instanceOf(win.parent, "Window")) + return win; + } + return null; +}; + +// ************************************************************************************************ +// Graphics + +this.getClientOffset = function(elt) +{ + var addOffset = function addOffset(elt, coords, view) + { + var p = elt.offsetParent; + + ///var style = isIE ? elt.currentStyle : view.getComputedStyle(elt, ""); + var chrome = Firebug.chrome; + + if (elt.offsetLeft) + ///coords.x += elt.offsetLeft + parseInt(style.borderLeftWidth); + coords.x += elt.offsetLeft + chrome.getMeasurementInPixels(elt, "borderLeft"); + if (elt.offsetTop) + ///coords.y += elt.offsetTop + parseInt(style.borderTopWidth); + coords.y += elt.offsetTop + chrome.getMeasurementInPixels(elt, "borderTop"); + + if (p) + { + if (p.nodeType == 1) + addOffset(p, coords, view); + } + else + { + var otherView = isIE ? elt.ownerDocument.parentWindow : elt.ownerDocument.defaultView; + // IE will fail when reading the frameElement property of a popup window. + // We don't need it anyway once it is outside the (popup) viewport, so we're + // ignoring the frameElement check when the window is a popup + if (!otherView.opener && otherView.frameElement) + addOffset(otherView.frameElement, coords, otherView); + } + }; + + var isIE = this.isIE; + var coords = {x: 0, y: 0}; + if (elt) + { + var view = isIE ? elt.ownerDocument.parentWindow : elt.ownerDocument.defaultView; + addOffset(elt, coords, view); + } + + return coords; +}; + +this.getViewOffset = function(elt, singleFrame) +{ + function addOffset(elt, coords, view) + { + var p = elt.offsetParent; + coords.x += elt.offsetLeft - (p ? p.scrollLeft : 0); + coords.y += elt.offsetTop - (p ? p.scrollTop : 0); + + if (p) + { + if (p.nodeType == 1) + { + var parentStyle = view.getComputedStyle(p, ""); + if (parentStyle.position != "static") + { + coords.x += parseInt(parentStyle.borderLeftWidth); + coords.y += parseInt(parentStyle.borderTopWidth); + + if (p.localName == "TABLE") + { + coords.x += parseInt(parentStyle.paddingLeft); + coords.y += parseInt(parentStyle.paddingTop); + } + else if (p.localName == "BODY") + { + var style = view.getComputedStyle(elt, ""); + coords.x += parseInt(style.marginLeft); + coords.y += parseInt(style.marginTop); + } + } + else if (p.localName == "BODY") + { + coords.x += parseInt(parentStyle.borderLeftWidth); + coords.y += parseInt(parentStyle.borderTopWidth); + } + + var parent = elt.parentNode; + while (p != parent) + { + coords.x -= parent.scrollLeft; + coords.y -= parent.scrollTop; + parent = parent.parentNode; + } + addOffset(p, coords, view); + } + } + else + { + if (elt.localName == "BODY") + { + var style = view.getComputedStyle(elt, ""); + coords.x += parseInt(style.borderLeftWidth); + coords.y += parseInt(style.borderTopWidth); + + var htmlStyle = view.getComputedStyle(elt.parentNode, ""); + coords.x -= parseInt(htmlStyle.paddingLeft); + coords.y -= parseInt(htmlStyle.paddingTop); + } + + if (elt.scrollLeft) + coords.x += elt.scrollLeft; + if (elt.scrollTop) + coords.y += elt.scrollTop; + + var win = elt.ownerDocument.defaultView; + if (win && (!singleFrame && win.frameElement)) + addOffset(win.frameElement, coords, win); + } + + } + + var coords = {x: 0, y: 0}; + if (elt) + addOffset(elt, coords, elt.ownerDocument.defaultView); + + return coords; +}; + +this.getLTRBWH = function(elt) +{ + var bcrect, + dims = {"left": 0, "top": 0, "right": 0, "bottom": 0, "width": 0, "height": 0}; + + if (elt) + { + bcrect = elt.getBoundingClientRect(); + dims.left = bcrect.left; + dims.top = bcrect.top; + dims.right = bcrect.right; + dims.bottom = bcrect.bottom; + + if(bcrect.width) + { + dims.width = bcrect.width; + dims.height = bcrect.height; + } + else + { + dims.width = dims.right - dims.left; + dims.height = dims.bottom - dims.top; + } + } + return dims; +}; + +this.applyBodyOffsets = function(elt, clientRect) +{ + var od = elt.ownerDocument; + if (!od.body) + return clientRect; + + var style = od.defaultView.getComputedStyle(od.body, null); + + var pos = style.getPropertyValue('position'); + if(pos === 'absolute' || pos === 'relative') + { + var borderLeft = parseInt(style.getPropertyValue('border-left-width').replace('px', ''),10) || 0; + var borderTop = parseInt(style.getPropertyValue('border-top-width').replace('px', ''),10) || 0; + var paddingLeft = parseInt(style.getPropertyValue('padding-left').replace('px', ''),10) || 0; + var paddingTop = parseInt(style.getPropertyValue('padding-top').replace('px', ''),10) || 0; + var marginLeft = parseInt(style.getPropertyValue('margin-left').replace('px', ''),10) || 0; + var marginTop = parseInt(style.getPropertyValue('margin-top').replace('px', ''),10) || 0; + + var offsetX = borderLeft + paddingLeft + marginLeft; + var offsetY = borderTop + paddingTop + marginTop; + + clientRect.left -= offsetX; + clientRect.top -= offsetY; + clientRect.right -= offsetX; + clientRect.bottom -= offsetY; + } + + return clientRect; +}; + +this.getOffsetSize = function(elt) +{ + return {width: elt.offsetWidth, height: elt.offsetHeight}; +}; + +this.getOverflowParent = function(element) +{ + for (var scrollParent = element.parentNode; scrollParent; scrollParent = scrollParent.offsetParent) + { + if (scrollParent.scrollHeight > scrollParent.offsetHeight) + return scrollParent; + } +}; + +this.isScrolledToBottom = function(element) +{ + var onBottom = (element.scrollTop + element.offsetHeight) == element.scrollHeight; + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("isScrolledToBottom offsetHeight: "+element.offsetHeight +" onBottom:"+onBottom); + return onBottom; +}; + +this.scrollToBottom = function(element) +{ + element.scrollTop = element.scrollHeight; + + if (FBTrace.DBG_CONSOLE) + { + FBTrace.sysout("scrollToBottom reset scrollTop "+element.scrollTop+" = "+element.scrollHeight); + if (element.scrollHeight == element.offsetHeight) + FBTrace.sysout("scrollToBottom attempt to scroll non-scrollable element "+element, element); + } + + return (element.scrollTop == element.scrollHeight); +}; + +this.move = function(element, x, y) +{ + element.style.left = x + "px"; + element.style.top = y + "px"; +}; + +this.resize = function(element, w, h) +{ + element.style.width = w + "px"; + element.style.height = h + "px"; +}; + +this.linesIntoCenterView = function(element, scrollBox) // {before: int, after: int} +{ + if (!scrollBox) + scrollBox = this.getOverflowParent(element); + + if (!scrollBox) + return; + + var offset = this.getClientOffset(element); + + var topSpace = offset.y - scrollBox.scrollTop; + var bottomSpace = (scrollBox.scrollTop + scrollBox.clientHeight) + - (offset.y + element.offsetHeight); + + if (topSpace < 0 || bottomSpace < 0) + { + var split = (scrollBox.clientHeight/2); + var centerY = offset.y - split; + scrollBox.scrollTop = centerY; + topSpace = split; + bottomSpace = split - element.offsetHeight; + } + + return {before: Math.round((topSpace/element.offsetHeight) + 0.5), + after: Math.round((bottomSpace/element.offsetHeight) + 0.5) }; +}; + +this.scrollIntoCenterView = function(element, scrollBox, notX, notY) +{ + if (!element) + return; + + if (!scrollBox) + scrollBox = this.getOverflowParent(element); + + if (!scrollBox) + return; + + var offset = this.getClientOffset(element); + + if (!notY) + { + var topSpace = offset.y - scrollBox.scrollTop; + var bottomSpace = (scrollBox.scrollTop + scrollBox.clientHeight) + - (offset.y + element.offsetHeight); + + if (topSpace < 0 || bottomSpace < 0) + { + var centerY = offset.y - (scrollBox.clientHeight/2); + scrollBox.scrollTop = centerY; + } + } + + if (!notX) + { + var leftSpace = offset.x - scrollBox.scrollLeft; + var rightSpace = (scrollBox.scrollLeft + scrollBox.clientWidth) + - (offset.x + element.clientWidth); + + if (leftSpace < 0 || rightSpace < 0) + { + var centerX = offset.x - (scrollBox.clientWidth/2); + scrollBox.scrollLeft = centerX; + } + } + if (FBTrace.DBG_SOURCEFILES) + FBTrace.sysout("lib.scrollIntoCenterView ","Element:"+element.innerHTML); +}; + + +// ************************************************************************************************ +// CSS + +var cssKeywordMap = null; +var cssPropNames = null; +var cssColorNames = null; +var imageRules = null; + +this.getCSSKeywordsByProperty = function(propName) +{ + if (!cssKeywordMap) + { + cssKeywordMap = {}; + + for (var name in this.cssInfo) + { + var list = []; + + var types = this.cssInfo[name]; + for (var i = 0; i < types.length; ++i) + { + var keywords = this.cssKeywords[types[i]]; + if (keywords) + list.push.apply(list, keywords); + } + + cssKeywordMap[name] = list; + } + } + + return propName in cssKeywordMap ? cssKeywordMap[propName] : []; +}; + +this.getCSSPropertyNames = function() +{ + if (!cssPropNames) + { + cssPropNames = []; + + for (var name in this.cssInfo) + cssPropNames.push(name); + } + + return cssPropNames; +}; + +this.isColorKeyword = function(keyword) +{ + if (keyword == "transparent") + return false; + + if (!cssColorNames) + { + cssColorNames = []; + + var colors = this.cssKeywords["color"]; + for (var i = 0; i < colors.length; ++i) + cssColorNames.push(colors[i].toLowerCase()); + + var systemColors = this.cssKeywords["systemColor"]; + for (var i = 0; i < systemColors.length; ++i) + cssColorNames.push(systemColors[i].toLowerCase()); + } + + return cssColorNames.indexOf ? // Array.indexOf is not available in IE + cssColorNames.indexOf(keyword.toLowerCase()) != -1 : + (" " + cssColorNames.join(" ") + " ").indexOf(" " + keyword.toLowerCase() + " ") != -1; +}; + +this.isImageRule = function(rule) +{ + if (!imageRules) + { + imageRules = []; + + for (var i in this.cssInfo) + { + var r = i.toLowerCase(); + var suffix = "image"; + if (r.match(suffix + "$") == suffix || r == "background") + imageRules.push(r); + } + } + + return imageRules.indexOf ? // Array.indexOf is not available in IE + imageRules.indexOf(rule.toLowerCase()) != -1 : + (" " + imageRules.join(" ") + " ").indexOf(" " + rule.toLowerCase() + " ") != -1; +}; + +this.copyTextStyles = function(fromNode, toNode, style) +{ + var view = this.isIE ? + fromNode.ownerDocument.parentWindow : + fromNode.ownerDocument.defaultView; + + if (view) + { + if (!style) + style = this.isIE ? fromNode.currentStyle : view.getComputedStyle(fromNode, ""); + + toNode.style.fontFamily = style.fontFamily; + + // TODO: xxxpedro need to create a FBL.getComputedStyle() because IE + // returns wrong computed styles for inherited properties (like font-*) + // + // Also would be good to create a FBL.getStyle() + toNode.style.fontSize = style.fontSize; + toNode.style.fontWeight = style.fontWeight; + toNode.style.fontStyle = style.fontStyle; + + return style; + } +}; + +this.copyBoxStyles = function(fromNode, toNode, style) +{ + var view = this.isIE ? + fromNode.ownerDocument.parentWindow : + fromNode.ownerDocument.defaultView; + + if (view) + { + if (!style) + style = this.isIE ? fromNode.currentStyle : view.getComputedStyle(fromNode, ""); + + toNode.style.marginTop = style.marginTop; + toNode.style.marginRight = style.marginRight; + toNode.style.marginBottom = style.marginBottom; + toNode.style.marginLeft = style.marginLeft; + toNode.style.borderTopWidth = style.borderTopWidth; + toNode.style.borderRightWidth = style.borderRightWidth; + toNode.style.borderBottomWidth = style.borderBottomWidth; + toNode.style.borderLeftWidth = style.borderLeftWidth; + + return style; + } +}; + +this.readBoxStyles = function(style) +{ + var styleNames = { + "margin-top": "marginTop", "margin-right": "marginRight", + "margin-left": "marginLeft", "margin-bottom": "marginBottom", + "border-top-width": "borderTop", "border-right-width": "borderRight", + "border-left-width": "borderLeft", "border-bottom-width": "borderBottom", + "padding-top": "paddingTop", "padding-right": "paddingRight", + "padding-left": "paddingLeft", "padding-bottom": "paddingBottom", + "z-index": "zIndex" + }; + + var styles = {}; + for (var styleName in styleNames) + styles[styleNames[styleName]] = parseInt(style.getPropertyCSSValue(styleName).cssText) || 0; + if (FBTrace.DBG_INSPECT) + FBTrace.sysout("readBoxStyles ", styles); + return styles; +}; + +this.getBoxFromStyles = function(style, element) +{ + var args = this.readBoxStyles(style); + args.width = element.offsetWidth + - (args.paddingLeft+args.paddingRight+args.borderLeft+args.borderRight); + args.height = element.offsetHeight + - (args.paddingTop+args.paddingBottom+args.borderTop+args.borderBottom); + return args; +}; + +this.getElementCSSSelector = function(element) +{ + var label = element.localName.toLowerCase(); + if (element.id) + label += "#" + element.id; + if (element.hasAttribute("class")) + label += "." + element.getAttribute("class").split(" ")[0]; + + return label; +}; + +this.getURLForStyleSheet= function(styleSheet) +{ + //http://www.w3.org/TR/DOM-Level-2-Style/stylesheets.html#StyleSheets-StyleSheet. For inline style sheets, the value of this attribute is null. + return (styleSheet.href ? styleSheet.href : styleSheet.ownerNode.ownerDocument.URL); +}; + +this.getDocumentForStyleSheet = function(styleSheet) +{ + while (styleSheet.parentStyleSheet && !styleSheet.ownerNode) + { + styleSheet = styleSheet.parentStyleSheet; + } + if (styleSheet.ownerNode) + return styleSheet.ownerNode.ownerDocument; +}; + +/** + * Retrieves the instance number for a given style sheet. The instance number + * is sheet's index within the set of all other sheets whose URL is the same. + */ +this.getInstanceForStyleSheet = function(styleSheet, ownerDocument) +{ + // System URLs are always unique (or at least we are making this assumption) + if (FBL.isSystemStyleSheet(styleSheet)) + return 0; + + // ownerDocument is an optional hint for performance + if (FBTrace.DBG_CSS) FBTrace.sysout("getInstanceForStyleSheet: " + styleSheet.href + " " + styleSheet.media.mediaText + " " + (styleSheet.ownerNode && FBL.getElementXPath(styleSheet.ownerNode)), ownerDocument); + ownerDocument = ownerDocument || FBL.getDocumentForStyleSheet(styleSheet); + + var ret = 0, + styleSheets = ownerDocument.styleSheets, + href = styleSheet.href; + for (var i = 0; i < styleSheets.length; i++) + { + var curSheet = styleSheets[i]; + if (FBTrace.DBG_CSS) FBTrace.sysout("getInstanceForStyleSheet: compare href " + i + " " + curSheet.href + " " + curSheet.media.mediaText + " " + (curSheet.ownerNode && FBL.getElementXPath(curSheet.ownerNode))); + if (curSheet == styleSheet) + break; + if (curSheet.href == href) + ret++; + } + return ret; +}; + +// ************************************************************************************************ +// HTML and XML Serialization + + +var getElementType = this.getElementType = function(node) +{ + if (isElementXUL(node)) + return 'xul'; + else if (isElementSVG(node)) + return 'svg'; + else if (isElementMathML(node)) + return 'mathml'; + else if (isElementXHTML(node)) + return 'xhtml'; + else if (isElementHTML(node)) + return 'html'; +}; + +var getElementSimpleType = this.getElementSimpleType = function(node) +{ + if (isElementSVG(node)) + return 'svg'; + else if (isElementMathML(node)) + return 'mathml'; + else + return 'html'; +}; + +var isElementHTML = this.isElementHTML = function(node) +{ + return node.nodeName == node.nodeName.toUpperCase(); +}; + +var isElementXHTML = this.isElementXHTML = function(node) +{ + return node.nodeName == node.nodeName.toLowerCase(); +}; + +var isElementMathML = this.isElementMathML = function(node) +{ + return node.namespaceURI == 'http://www.w3.org/1998/Math/MathML'; +}; + +var isElementSVG = this.isElementSVG = function(node) +{ + return node.namespaceURI == 'http://www.w3.org/2000/svg'; +}; + +var isElementXUL = this.isElementXUL = function(node) +{ + return node instanceof XULElement; +}; + +this.isSelfClosing = function(element) +{ + if (isElementSVG(element) || isElementMathML(element)) + return true; + var tag = element.localName.toLowerCase(); + return (this.selfClosingTags.hasOwnProperty(tag)); +}; + +this.getElementHTML = function(element) +{ + var self=this; + function toHTML(elt) + { + if (elt.nodeType == Node.ELEMENT_NODE) + { + if (unwrapObject(elt).firebugIgnore) + return; + + html.push('<', elt.nodeName.toLowerCase()); + + for (var i = 0; i < elt.attributes.length; ++i) + { + var attr = elt.attributes[i]; + + // Hide attributes set by Firebug + if (attr.localName.indexOf("firebug-") == 0) + continue; + + // MathML + if (attr.localName.indexOf("-moz-math") == 0) + { + // just hide for now + continue; + } + + html.push(' ', attr.nodeName, '="', escapeForElementAttribute(attr.nodeValue),'"'); + } + + if (elt.firstChild) + { + html.push('>'); + + var pureText=true; + for (var child = element.firstChild; child; child = child.nextSibling) + pureText=pureText && (child.nodeType == Node.TEXT_NODE); + + if (pureText) + html.push(escapeForHtmlEditor(elt.textContent)); + else { + for (var child = elt.firstChild; child; child = child.nextSibling) + toHTML(child); + } + + html.push('</', elt.nodeName.toLowerCase(), '>'); + } + else if (isElementSVG(elt) || isElementMathML(elt)) + { + html.push('/>'); + } + else if (self.isSelfClosing(elt)) + { + html.push((isElementXHTML(elt))?'/>':'>'); + } + else + { + html.push('></', elt.nodeName.toLowerCase(), '>'); + } + } + else if (elt.nodeType == Node.TEXT_NODE) + html.push(escapeForTextNode(elt.textContent)); + else if (elt.nodeType == Node.CDATA_SECTION_NODE) + html.push('<![CDATA[', elt.nodeValue, ']]>'); + else if (elt.nodeType == Node.COMMENT_NODE) + html.push('<!--', elt.nodeValue, '-->'); + } + + var html = []; + toHTML(element); + return html.join(""); +}; + +this.getElementXML = function(element) +{ + function toXML(elt) + { + if (elt.nodeType == Node.ELEMENT_NODE) + { + if (unwrapObject(elt).firebugIgnore) + return; + + xml.push('<', elt.nodeName.toLowerCase()); + + for (var i = 0; i < elt.attributes.length; ++i) + { + var attr = elt.attributes[i]; + + // Hide attributes set by Firebug + if (attr.localName.indexOf("firebug-") == 0) + continue; + + // MathML + if (attr.localName.indexOf("-moz-math") == 0) + { + // just hide for now + continue; + } + + xml.push(' ', attr.nodeName, '="', escapeForElementAttribute(attr.nodeValue),'"'); + } + + if (elt.firstChild) + { + xml.push('>'); + + for (var child = elt.firstChild; child; child = child.nextSibling) + toXML(child); + + xml.push('</', elt.nodeName.toLowerCase(), '>'); + } + else + xml.push('/>'); + } + else if (elt.nodeType == Node.TEXT_NODE) + xml.push(elt.nodeValue); + else if (elt.nodeType == Node.CDATA_SECTION_NODE) + xml.push('<![CDATA[', elt.nodeValue, ']]>'); + else if (elt.nodeType == Node.COMMENT_NODE) + xml.push('<!--', elt.nodeValue, '-->'); + } + + var xml = []; + toXML(element); + return xml.join(""); +}; + + +// ************************************************************************************************ +// CSS classes + +this.hasClass = function(node, name) // className, className, ... +{ + // TODO: xxxpedro when lib.hasClass is called with more than 2 arguments? + // this function can be optimized a lot if assumed 2 arguments only, + // which seems to be what happens 99% of the time + if (arguments.length == 2) + return (' '+node.className+' ').indexOf(' '+name+' ') != -1; + + if (!node || node.nodeType != 1) + return false; + else + { + for (var i=1; i<arguments.length; ++i) + { + var name = arguments[i]; + var re = new RegExp("(^|\\s)"+name+"($|\\s)"); + if (!re.exec(node.className)) + return false; + } + + return true; + } +}; + +this.old_hasClass = function(node, name) // className, className, ... +{ + if (!node || node.nodeType != 1) + return false; + else + { + for (var i=1; i<arguments.length; ++i) + { + var name = arguments[i]; + var re = new RegExp("(^|\\s)"+name+"($|\\s)"); + if (!re.exec(node.className)) + return false; + } + + return true; + } +}; + +this.setClass = function(node, name) +{ + if (node && (' '+node.className+' ').indexOf(' '+name+' ') == -1) + ///if (node && !this.hasClass(node, name)) + node.className += " " + name; +}; + +this.getClassValue = function(node, name) +{ + var re = new RegExp(name+"-([^ ]+)"); + var m = re.exec(node.className); + return m ? m[1] : ""; +}; + +this.removeClass = function(node, name) +{ + if (node && node.className) + { + var index = node.className.indexOf(name); + if (index >= 0) + { + var size = name.length; + node.className = node.className.substr(0,index-1) + node.className.substr(index+size); + } + } +}; + +this.toggleClass = function(elt, name) +{ + if ((' '+elt.className+' ').indexOf(' '+name+' ') != -1) + ///if (this.hasClass(elt, name)) + this.removeClass(elt, name); + else + this.setClass(elt, name); +}; + +this.setClassTimed = function(elt, name, context, timeout) +{ + if (!timeout) + timeout = 1300; + + if (elt.__setClassTimeout) + context.clearTimeout(elt.__setClassTimeout); + else + this.setClass(elt, name); + + elt.__setClassTimeout = context.setTimeout(function() + { + delete elt.__setClassTimeout; + + FBL.removeClass(elt, name); + }, timeout); +}; + +this.cancelClassTimed = function(elt, name, context) +{ + if (elt.__setClassTimeout) + { + FBL.removeClass(elt, name); + context.clearTimeout(elt.__setClassTimeout); + delete elt.__setClassTimeout; + } +}; + + +// ************************************************************************************************ +// DOM queries + +this.$ = function(id, doc) +{ + if (doc) + return doc.getElementById(id); + else + { + return FBL.Firebug.chrome.document.getElementById(id); + } +}; + +this.$$ = function(selector, doc) +{ + if (doc || !FBL.Firebug.chrome) + return FBL.Firebug.Selector(selector, doc); + else + { + return FBL.Firebug.Selector(selector, FBL.Firebug.chrome.document); + } +}; + +this.getChildByClass = function(node) // ,classname, classname, classname... +{ + for (var i = 1; i < arguments.length; ++i) + { + var className = arguments[i]; + var child = node.firstChild; + node = null; + for (; child; child = child.nextSibling) + { + if (this.hasClass(child, className)) + { + node = child; + break; + } + } + } + + return node; +}; + +this.getAncestorByClass = function(node, className) +{ + for (var parent = node; parent; parent = parent.parentNode) + { + if (this.hasClass(parent, className)) + return parent; + } + + return null; +}; + + +this.getElementsByClass = function(node, className) +{ + var result = []; + + for (var child = node.firstChild; child; child = child.nextSibling) + { + if (this.hasClass(child, className)) + result.push(child); + } + + return result; +}; + +this.getElementByClass = function(node, className) // className, className, ... +{ + var args = cloneArray(arguments); args.splice(0, 1); + for (var child = node.firstChild; child; child = child.nextSibling) + { + var args1 = cloneArray(args); args1.unshift(child); + if (FBL.hasClass.apply(null, args1)) + return child; + else + { + var found = FBL.getElementByClass.apply(null, args1); + if (found) + return found; + } + } + + return null; +}; + +this.isAncestor = function(node, potentialAncestor) +{ + for (var parent = node; parent; parent = parent.parentNode) + { + if (parent == potentialAncestor) + return true; + } + + return false; +}; + +this.getNextElement = function(node) +{ + while (node && node.nodeType != 1) + node = node.nextSibling; + + return node; +}; + +this.getPreviousElement = function(node) +{ + while (node && node.nodeType != 1) + node = node.previousSibling; + + return node; +}; + +this.getBody = function(doc) +{ + if (doc.body) + return doc.body; + + var body = doc.getElementsByTagName("body")[0]; + if (body) + return body; + + return doc.firstChild; // For non-HTML docs +}; + +this.findNextDown = function(node, criteria) +{ + if (!node) + return null; + + for (var child = node.firstChild; child; child = child.nextSibling) + { + if (criteria(child)) + return child; + + var next = this.findNextDown(child, criteria); + if (next) + return next; + } +}; + +this.findPreviousUp = function(node, criteria) +{ + if (!node) + return null; + + for (var child = node.lastChild; child; child = child.previousSibling) + { + var next = this.findPreviousUp(child, criteria); + if (next) + return next; + + if (criteria(child)) + return child; + } +}; + +this.findNext = function(node, criteria, upOnly, maxRoot) +{ + if (!node) + return null; + + if (!upOnly) + { + var next = this.findNextDown(node, criteria); + if (next) + return next; + } + + for (var sib = node.nextSibling; sib; sib = sib.nextSibling) + { + if (criteria(sib)) + return sib; + + var next = this.findNextDown(sib, criteria); + if (next) + return next; + } + + if (node.parentNode && node.parentNode != maxRoot) + return this.findNext(node.parentNode, criteria, true); +}; + +this.findPrevious = function(node, criteria, downOnly, maxRoot) +{ + if (!node) + return null; + + for (var sib = node.previousSibling; sib; sib = sib.previousSibling) + { + var prev = this.findPreviousUp(sib, criteria); + if (prev) + return prev; + + if (criteria(sib)) + return sib; + } + + if (!downOnly) + { + var next = this.findPreviousUp(node, criteria); + if (next) + return next; + } + + if (node.parentNode && node.parentNode != maxRoot) + { + if (criteria(node.parentNode)) + return node.parentNode; + + return this.findPrevious(node.parentNode, criteria, true); + } +}; + +this.getNextByClass = function(root, state) +{ + var iter = function iter(node) { return node.nodeType == 1 && FBL.hasClass(node, state); }; + return this.findNext(root, iter); +}; + +this.getPreviousByClass = function(root, state) +{ + var iter = function iter(node) { return node.nodeType == 1 && FBL.hasClass(node, state); }; + return this.findPrevious(root, iter); +}; + +this.isElement = function(o) +{ + try { + return o && this.instanceOf(o, "Element"); + } + catch (ex) { + return false; + } +}; + + +// ************************************************************************************************ +// DOM Modification + +// TODO: xxxpedro use doc fragments in Context API +var appendFragment = null; + +this.appendInnerHTML = function(element, html, referenceElement) +{ + // if undefined, we must convert it to null otherwise it will throw an error in IE + // when executing element.insertBefore(firstChild, referenceElement) + referenceElement = referenceElement || null; + + var doc = element.ownerDocument; + + // doc.createRange not available in IE + if (doc.createRange) + { + var range = doc.createRange(); // a helper object + range.selectNodeContents(element); // the environment to interpret the html + + var fragment = range.createContextualFragment(html); // parse + var firstChild = fragment.firstChild; + element.insertBefore(fragment, referenceElement); + } + else + { + if (!appendFragment || appendFragment.ownerDocument != doc) + appendFragment = doc.createDocumentFragment(); + + var div = doc.createElement("div"); + div.innerHTML = html; + + var firstChild = div.firstChild; + while (div.firstChild) + appendFragment.appendChild(div.firstChild); + + element.insertBefore(appendFragment, referenceElement); + + div = null; + } + + return firstChild; +}; + + +// ************************************************************************************************ +// DOM creation + +this.createElement = function(tagName, properties) +{ + properties = properties || {}; + var doc = properties.document || FBL.Firebug.chrome.document; + + var element = doc.createElement(tagName); + + for(var name in properties) + { + if (name != "document") + { + element[name] = properties[name]; + } + } + + return element; +}; + +this.createGlobalElement = function(tagName, properties) +{ + properties = properties || {}; + var doc = FBL.Env.browser.document; + + var element = this.NS && doc.createElementNS ? + doc.createElementNS(FBL.NS, tagName) : + doc.createElement(tagName); + + for(var name in properties) + { + var propname = name; + if (FBL.isIE && name == "class") propname = "className"; + + if (name != "document") + { + element.setAttribute(propname, properties[name]); + } + } + + return element; +}; + +//************************************************************************************************ + +this.safeGetWindowLocation = function(window) +{ + try + { + if (window) + { + if (window.closed) + return "(window.closed)"; + if ("location" in window) + return window.location+""; + else + return "(no window.location)"; + } + else + return "(no context.window)"; + } + catch(exc) + { + if (FBTrace.DBG_WINDOWS || FBTrace.DBG_ERRORS) + FBTrace.sysout("TabContext.getWindowLocation failed "+exc, exc); + FBTrace.sysout("TabContext.getWindowLocation failed window:", window); + return "(getWindowLocation: "+exc+")"; + } +}; + +// ************************************************************************************************ +// Events + +this.isLeftClick = function(event) +{ + return (this.isIE && event.type != "click" && event.type != "dblclick" ? + event.button == 1 : // IE "click" and "dblclick" button model + event.button == 0) && // others + this.noKeyModifiers(event); +}; + +this.isMiddleClick = function(event) +{ + return (this.isIE && event.type != "click" && event.type != "dblclick" ? + event.button == 4 : // IE "click" and "dblclick" button model + event.button == 1) && + this.noKeyModifiers(event); +}; + +this.isRightClick = function(event) +{ + return (this.isIE && event.type != "click" && event.type != "dblclick" ? + event.button == 2 : // IE "click" and "dblclick" button model + event.button == 2) && + this.noKeyModifiers(event); +}; + +this.noKeyModifiers = function(event) +{ + return !event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey; +}; + +this.isControlClick = function(event) +{ + return (this.isIE && event.type != "click" && event.type != "dblclick" ? + event.button == 1 : // IE "click" and "dblclick" button model + event.button == 0) && + this.isControl(event); +}; + +this.isShiftClick = function(event) +{ + return (this.isIE && event.type != "click" && event.type != "dblclick" ? + event.button == 1 : // IE "click" and "dblclick" button model + event.button == 0) && + this.isShift(event); +}; + +this.isControl = function(event) +{ + return (event.metaKey || event.ctrlKey) && !event.shiftKey && !event.altKey; +}; + +this.isAlt = function(event) +{ + return event.altKey && !event.ctrlKey && !event.shiftKey && !event.metaKey; +}; + +this.isAltClick = function(event) +{ + return (this.isIE && event.type != "click" && event.type != "dblclick" ? + event.button == 1 : // IE "click" and "dblclick" button model + event.button == 0) && + this.isAlt(event); +}; + +this.isControlShift = function(event) +{ + return (event.metaKey || event.ctrlKey) && event.shiftKey && !event.altKey; +}; + +this.isShift = function(event) +{ + return event.shiftKey && !event.metaKey && !event.ctrlKey && !event.altKey; +}; + +this.addEvent = function(object, name, handler, useCapture) +{ + if (object.addEventListener) + object.addEventListener(name, handler, useCapture); + else + object.attachEvent("on"+name, handler); +}; + +this.removeEvent = function(object, name, handler, useCapture) +{ + try + { + if (object.removeEventListener) + object.removeEventListener(name, handler, useCapture); + else + object.detachEvent("on"+name, handler); + } + catch(e) + { + if (FBTrace.DBG_ERRORS) + FBTrace.sysout("FBL.removeEvent error: ", object, name); + } +}; + +this.cancelEvent = function(e, preventDefault) +{ + if (!e) return; + + if (preventDefault) + { + if (e.preventDefault) + e.preventDefault(); + else + e.returnValue = false; + } + + if (e.stopPropagation) + e.stopPropagation(); + else + e.cancelBubble = true; +}; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +this.addGlobalEvent = function(name, handler) +{ + var doc = this.Firebug.browser.document; + var frames = this.Firebug.browser.window.frames; + + this.addEvent(doc, name, handler); + + if (this.Firebug.chrome.type == "popup") + this.addEvent(this.Firebug.chrome.document, name, handler); + + for (var i = 0, frame; frame = frames[i]; i++) + { + try + { + this.addEvent(frame.document, name, handler); + } + catch(E) + { + // Avoid acess denied + } + } +}; + +this.removeGlobalEvent = function(name, handler) +{ + var doc = this.Firebug.browser.document; + var frames = this.Firebug.browser.window.frames; + + this.removeEvent(doc, name, handler); + + if (this.Firebug.chrome.type == "popup") + this.removeEvent(this.Firebug.chrome.document, name, handler); + + for (var i = 0, frame; frame = frames[i]; i++) + { + try + { + this.removeEvent(frame.document, name, handler); + } + catch(E) + { + // Avoid acess denied + } + } +}; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +this.dispatch = function(listeners, name, args) +{ + if (!listeners) return; + + try + {/**/ + if (typeof listeners.length != "undefined") + { + if (FBTrace.DBG_DISPATCH) FBTrace.sysout("FBL.dispatch", name+" to "+listeners.length+" listeners"); + + for (var i = 0; i < listeners.length; ++i) + { + var listener = listeners[i]; + if ( listener[name] ) + listener[name].apply(listener, args); + } + } + else + { + if (FBTrace.DBG_DISPATCH) FBTrace.sysout("FBL.dispatch", name+" to listeners of an object"); + + for (var prop in listeners) + { + var listener = listeners[prop]; + if ( listener[name] ) + listener[name].apply(listener, args); + } + } + } + catch (exc) + { + if (FBTrace.DBG_ERRORS) + { + FBTrace.sysout(" Exception in lib.dispatch "+ name, exc); + //FBTrace.dumpProperties(" Exception in lib.dispatch listener", listener); + } + } + /**/ +}; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +var disableTextSelectionHandler = function(event) +{ + FBL.cancelEvent(event, true); + + return false; +}; + +this.disableTextSelection = function(e) +{ + if (typeof e.onselectstart != "undefined") // IE + this.addEvent(e, "selectstart", disableTextSelectionHandler); + + else // others + { + e.style.cssText = "user-select: none; -khtml-user-select: none; -moz-user-select: none;"; + + // canceling the event in FF will prevent the menu popups to close when clicking over + // text-disabled elements + if (!this.isFirefox) + this.addEvent(e, "mousedown", disableTextSelectionHandler); + } + + e.style.cursor = "default"; +}; + +this.restoreTextSelection = function(e) +{ + if (typeof e.onselectstart != "undefined") // IE + this.removeEvent(e, "selectstart", disableTextSelectionHandler); + + else // others + { + e.style.cssText = "cursor: default;"; + + // canceling the event in FF will prevent the menu popups to close when clicking over + // text-disabled elements + if (!this.isFirefox) + this.removeEvent(e, "mousedown", disableTextSelectionHandler); + } +}; + +// ************************************************************************************************ +// DOM Events + +var eventTypes = +{ + composition: [ + "composition", + "compositionstart", + "compositionend" ], + contextmenu: [ + "contextmenu" ], + drag: [ + "dragenter", + "dragover", + "dragexit", + "dragdrop", + "draggesture" ], + focus: [ + "focus", + "blur" ], + form: [ + "submit", + "reset", + "change", + "select", + "input" ], + key: [ + "keydown", + "keyup", + "keypress" ], + load: [ + "load", + "beforeunload", + "unload", + "abort", + "error" ], + mouse: [ + "mousedown", + "mouseup", + "click", + "dblclick", + "mouseover", + "mouseout", + "mousemove" ], + mutation: [ + "DOMSubtreeModified", + "DOMNodeInserted", + "DOMNodeRemoved", + "DOMNodeRemovedFromDocument", + "DOMNodeInsertedIntoDocument", + "DOMAttrModified", + "DOMCharacterDataModified" ], + paint: [ + "paint", + "resize", + "scroll" ], + scroll: [ + "overflow", + "underflow", + "overflowchanged" ], + text: [ + "text" ], + ui: [ + "DOMActivate", + "DOMFocusIn", + "DOMFocusOut" ], + xul: [ + "popupshowing", + "popupshown", + "popuphiding", + "popuphidden", + "close", + "command", + "broadcast", + "commandupdate" ] +}; + +this.getEventFamily = function(eventType) +{ + if (!this.families) + { + this.families = {}; + + for (var family in eventTypes) + { + var types = eventTypes[family]; + for (var i = 0; i < types.length; ++i) + this.families[types[i]] = family; + } + } + + return this.families[eventType]; +}; + + +// ************************************************************************************************ +// URLs + +this.getFileName = function(url) +{ + var split = this.splitURLBase(url); + return split.name; +}; + +this.splitURLBase = function(url) +{ + if (this.isDataURL(url)) + return this.splitDataURL(url); + return this.splitURLTrue(url); +}; + +this.splitDataURL = function(url) +{ + var mark = url.indexOf(':', 3); + if (mark != 4) + return false; // the first 5 chars must be 'data:' + + var point = url.indexOf(',', mark+1); + if (point < mark) + return false; // syntax error + + var props = { encodedContent: url.substr(point+1) }; + + var metadataBuffer = url.substr(mark+1, point); + var metadata = metadataBuffer.split(';'); + for (var i = 0; i < metadata.length; i++) + { + var nv = metadata[i].split('='); + if (nv.length == 2) + props[nv[0]] = nv[1]; + } + + // Additional Firebug-specific properties + if (props.hasOwnProperty('fileName')) + { + var caller_URL = decodeURIComponent(props['fileName']); + var caller_split = this.splitURLTrue(caller_URL); + + if (props.hasOwnProperty('baseLineNumber')) // this means it's probably an eval() + { + props['path'] = caller_split.path; + props['line'] = props['baseLineNumber']; + var hint = decodeURIComponent(props['encodedContent'].substr(0,200)).replace(/\s*$/, ""); + props['name'] = 'eval->'+hint; + } + else + { + props['name'] = caller_split.name; + props['path'] = caller_split.path; + } + } + else + { + if (!props.hasOwnProperty('path')) + props['path'] = "data:"; + if (!props.hasOwnProperty('name')) + props['name'] = decodeURIComponent(props['encodedContent'].substr(0,200)).replace(/\s*$/, ""); + } + + return props; +}; + +this.splitURLTrue = function(url) +{ + var m = reSplitFile.exec(url); + if (!m) + return {name: url, path: url}; + else if (!m[2]) + return {path: m[1], name: m[1]}; + else + return {path: m[1], name: m[2]+m[3]}; +}; + +this.getFileExtension = function(url) +{ + if (!url) + return null; + + // Remove query string from the URL if any. + var queryString = url.indexOf("?"); + if (queryString != -1) + url = url.substr(0, queryString); + + // Now get the file extension. + var lastDot = url.lastIndexOf("."); + return url.substr(lastDot+1); +}; + +this.isSystemURL = function(url) +{ + if (!url) return true; + if (url.length == 0) return true; + if (url[0] == 'h') return false; + if (url.substr(0, 9) == "resource:") + return true; + else if (url.substr(0, 16) == "chrome://firebug") + return true; + else if (url == "XPCSafeJSObjectWrapper.cpp") + return true; + else if (url.substr(0, 6) == "about:") + return true; + else if (url.indexOf("firebug-service.js") != -1) + return true; + else + return false; +}; + +this.isSystemPage = function(win) +{ + try + { + var doc = win.document; + if (!doc) + return false; + + // Detect pages for pretty printed XML + if ((doc.styleSheets.length && doc.styleSheets[0].href + == "chrome://global/content/xml/XMLPrettyPrint.css") + || (doc.styleSheets.length > 1 && doc.styleSheets[1].href + == "chrome://browser/skin/feeds/subscribe.css")) + return true; + + return FBL.isSystemURL(win.location.href); + } + catch (exc) + { + // Sometimes documents just aren't ready to be manipulated here, but don't let that + // gum up the works + ERROR("tabWatcher.isSystemPage document not ready:"+ exc); + return false; + } +}; + +this.isSystemStyleSheet = function(sheet) +{ + var href = sheet && sheet.href; + return href && FBL.isSystemURL(href); +}; + +this.getURIHost = function(uri) +{ + try + { + if (uri) + return uri.host; + else + return ""; + } + catch (exc) + { + return ""; + } +}; + +this.isLocalURL = function(url) +{ + if (url.substr(0, 5) == "file:") + return true; + else if (url.substr(0, 8) == "wyciwyg:") + return true; + else + return false; +}; + +this.isDataURL = function(url) +{ + return (url && url.substr(0,5) == "data:"); +}; + +this.getLocalPath = function(url) +{ + if (this.isLocalURL(url)) + { + var fileHandler = ioService.getProtocolHandler("file").QueryInterface(Ci.nsIFileProtocolHandler); + var file = fileHandler.getFileFromURLSpec(url); + return file.path; + } +}; + +this.getURLFromLocalFile = function(file) +{ + var fileHandler = ioService.getProtocolHandler("file").QueryInterface(Ci.nsIFileProtocolHandler); + var URL = fileHandler.getURLSpecFromFile(file); + return URL; +}; + +this.getDataURLForContent = function(content, url) +{ + // data:text/javascript;fileName=x%2Cy.js;baseLineNumber=10,<the-url-encoded-data> + var uri = "data:text/html;"; + uri += "fileName="+encodeURIComponent(url)+ ","; + uri += encodeURIComponent(content); + return uri; +}, + +this.getDomain = function(url) +{ + var m = /[^:]+:\/{1,3}([^\/]+)/.exec(url); + return m ? m[1] : ""; +}; + +this.getURLPath = function(url) +{ + var m = /[^:]+:\/{1,3}[^\/]+(\/.*?)$/.exec(url); + return m ? m[1] : ""; +}; + +this.getPrettyDomain = function(url) +{ + var m = /[^:]+:\/{1,3}(www\.)?([^\/]+)/.exec(url); + return m ? m[2] : ""; +}; + +this.absoluteURL = function(url, baseURL) +{ + return this.absoluteURLWithDots(url, baseURL).replace("/./", "/", "g"); +}; + +this.absoluteURLWithDots = function(url, baseURL) +{ + if (url[0] == "?") + return baseURL + url; + + var reURL = /(([^:]+:)\/{1,2}[^\/]*)(.*?)$/; + var m = reURL.exec(url); + if (m) + return url; + + var m = reURL.exec(baseURL); + if (!m) + return ""; + + var head = m[1]; + var tail = m[3]; + if (url.substr(0, 2) == "//") + return m[2] + url; + else if (url[0] == "/") + { + return head + url; + } + else if (tail[tail.length-1] == "/") + return baseURL + url; + else + { + var parts = tail.split("/"); + return head + parts.slice(0, parts.length-1).join("/") + "/" + url; + } +}; + +this.normalizeURL = function(url) // this gets called a lot, any performance improvement welcome +{ + if (!url) + return ""; + // Replace one or more characters that are not forward-slash followed by /.., by space. + if (url.length < 255) // guard against monsters. + { + // Replace one or more characters that are not forward-slash followed by /.., by space. + url = url.replace(/[^\/]+\/\.\.\//, "", "g"); + // Issue 1496, avoid # + url = url.replace(/#.*/,""); + // For some reason, JSDS reports file URLs like "file:/" instead of "file:///", so they + // don't match up with the URLs we get back from the DOM + url = url.replace(/file:\/([^\/])/g, "file:///$1"); + if (url.indexOf('chrome:')==0) + { + var m = reChromeCase.exec(url); // 1 is package name, 2 is path + if (m) + { + url = "chrome://"+m[1].toLowerCase()+"/"+m[2]; + } + } + } + return url; +}; + +this.denormalizeURL = function(url) +{ + return url.replace(/file:\/\/\//g, "file:/"); +}; + +this.parseURLParams = function(url) +{ + var q = url ? url.indexOf("?") : -1; + if (q == -1) + return []; + + var search = url.substr(q+1); + var h = search.lastIndexOf("#"); + if (h != -1) + search = search.substr(0, h); + + if (!search) + return []; + + return this.parseURLEncodedText(search); +}; + +this.parseURLEncodedText = function(text) +{ + var maxValueLength = 25000; + + var params = []; + + // Unescape '+' characters that are used to encode a space. + // See section 2.2.in RFC 3986: http://www.ietf.org/rfc/rfc3986.txt + text = text.replace(/\+/g, " "); + + var args = text.split("&"); + for (var i = 0; i < args.length; ++i) + { + try { + var parts = args[i].split("="); + if (parts.length == 2) + { + if (parts[1].length > maxValueLength) + parts[1] = this.$STR("LargeData"); + + params.push({name: decodeURIComponent(parts[0]), value: decodeURIComponent(parts[1])}); + } + else + params.push({name: decodeURIComponent(parts[0]), value: ""}); + } + catch (e) + { + if (FBTrace.DBG_ERRORS) + { + FBTrace.sysout("parseURLEncodedText EXCEPTION ", e); + FBTrace.sysout("parseURLEncodedText EXCEPTION URI", args[i]); + } + } + } + + params.sort(function(a, b) { return a.name <= b.name ? -1 : 1; }); + + return params; +}; + +// TODO: xxxpedro lib. why loops in domplate are requiring array in parameters +// as in response/request headers and get/post parameters in Net module? +this.parseURLParamsArray = function(url) +{ + var q = url ? url.indexOf("?") : -1; + if (q == -1) + return []; + + var search = url.substr(q+1); + var h = search.lastIndexOf("#"); + if (h != -1) + search = search.substr(0, h); + + if (!search) + return []; + + return this.parseURLEncodedTextArray(search); +}; + +this.parseURLEncodedTextArray = function(text) +{ + var maxValueLength = 25000; + + var params = []; + + // Unescape '+' characters that are used to encode a space. + // See section 2.2.in RFC 3986: http://www.ietf.org/rfc/rfc3986.txt + text = text.replace(/\+/g, " "); + + var args = text.split("&"); + for (var i = 0; i < args.length; ++i) + { + try { + var parts = args[i].split("="); + if (parts.length == 2) + { + if (parts[1].length > maxValueLength) + parts[1] = this.$STR("LargeData"); + + params.push({name: decodeURIComponent(parts[0]), value: [decodeURIComponent(parts[1])]}); + } + else + params.push({name: decodeURIComponent(parts[0]), value: [""]}); + } + catch (e) + { + if (FBTrace.DBG_ERRORS) + { + FBTrace.sysout("parseURLEncodedText EXCEPTION ", e); + FBTrace.sysout("parseURLEncodedText EXCEPTION URI", args[i]); + } + } + } + + params.sort(function(a, b) { return a.name <= b.name ? -1 : 1; }); + + return params; +}; + +this.reEncodeURL = function(file, text) +{ + var lines = text.split("\n"); + var params = this.parseURLEncodedText(lines[lines.length-1]); + + var args = []; + for (var i = 0; i < params.length; ++i) + args.push(encodeURIComponent(params[i].name)+"="+encodeURIComponent(params[i].value)); + + var url = file.href; + url += (url.indexOf("?") == -1 ? "?" : "&") + args.join("&"); + + return url; +}; + +this.getResource = function(aURL) +{ + try + { + var channel=ioService.newChannel(aURL,null,null); + var input=channel.open(); + return FBL.readFromStream(input); + } + catch (e) + { + if (FBTrace.DBG_ERRORS) + FBTrace.sysout("lib.getResource FAILS for "+aURL, e); + } +}; + +this.parseJSONString = function(jsonString, originURL) +{ + // See if this is a Prototype style *-secure request. + var regex = new RegExp(/^\/\*-secure-([\s\S]*)\*\/\s*$/); + var matches = regex.exec(jsonString); + + if (matches) + { + jsonString = matches[1]; + + if (jsonString[0] == "\\" && jsonString[1] == "n") + jsonString = jsonString.substr(2); + + if (jsonString[jsonString.length-2] == "\\" && jsonString[jsonString.length-1] == "n") + jsonString = jsonString.substr(0, jsonString.length-2); + } + + if (jsonString.indexOf("&&&START&&&")) + { + regex = new RegExp(/&&&START&&& (.+) &&&END&&&/); + matches = regex.exec(jsonString); + if (matches) + jsonString = matches[1]; + } + + // throw on the extra parentheses + jsonString = "(" + jsonString + ")"; + + ///var s = Components.utils.Sandbox(originURL); + var jsonObject = null; + + try + { + ///jsonObject = Components.utils.evalInSandbox(jsonString, s); + + //jsonObject = Firebug.context.eval(jsonString); + jsonObject = Firebug.context.evaluate(jsonString, null, null, function(){return null;}); + } + catch(e) + { + /*** + if (e.message.indexOf("is not defined")) + { + var parts = e.message.split(" "); + s[parts[0]] = function(str){ return str; }; + try { + jsonObject = Components.utils.evalInSandbox(jsonString, s); + } catch(ex) { + if (FBTrace.DBG_ERRORS || FBTrace.DBG_JSONVIEWER) + FBTrace.sysout("jsonviewer.parseJSON EXCEPTION", e); + return null; + } + } + else + {/**/ + if (FBTrace.DBG_ERRORS || FBTrace.DBG_JSONVIEWER) + FBTrace.sysout("jsonviewer.parseJSON EXCEPTION", e); + return null; + ///} + } + + return jsonObject; +}; + +// ************************************************************************************************ + +this.objectToString = function(object) +{ + try + { + return object+""; + } + catch (exc) + { + return null; + } +}; + +// ************************************************************************************************ +// Input Caret Position + +this.setSelectionRange = function(input, start, length) +{ + if (input.createTextRange) + { + var range = input.createTextRange(); + range.moveStart("character", start); + range.moveEnd("character", length - input.value.length); + range.select(); + } + else if (input.setSelectionRange) + { + input.setSelectionRange(start, length); + input.focus(); + } +}; + +// ************************************************************************************************ +// Input Selection Start / Caret Position + +this.getInputSelectionStart = function(input) +{ + if (document.selection) + { + var range = input.ownerDocument.selection.createRange(); + var text = range.text; + + //console.log("range", range.text); + + // if there is a selection, find the start position + if (text) + { + return input.value.indexOf(text); + } + // if there is no selection, find the caret position + else + { + range.moveStart("character", -input.value.length); + + return range.text.length; + } + } + else if (typeof input.selectionStart != "undefined") + return input.selectionStart; + + return 0; +}; + +// ************************************************************************************************ +// Opera Tab Fix + +function onOperaTabBlur(e) +{ + if (this.lastKey == 9) + this.focus(); +}; + +function onOperaTabKeyDown(e) +{ + this.lastKey = e.keyCode; +}; + +function onOperaTabFocus(e) +{ + this.lastKey = null; +}; + +this.fixOperaTabKey = function(el) +{ + el.onfocus = onOperaTabFocus; + el.onblur = onOperaTabBlur; + el.onkeydown = onOperaTabKeyDown; +}; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +this.Property = function(object, name) +{ + this.object = object; + this.name = name; + + this.getObject = function() + { + return object[name]; + }; +}; + +this.ErrorCopy = function(message) +{ + this.message = message; +}; + +function EventCopy(event) +{ + // Because event objects are destroyed arbitrarily by Gecko, we must make a copy of them to + // represent them long term in the inspector. + for (var name in event) + { + try { + this[name] = event[name]; + } catch (exc) { } + } +} + +this.EventCopy = EventCopy; + + +// ************************************************************************************************ +// Type Checking + +var toString = Object.prototype.toString; +var reFunction = /^\s*function(\s+[\w_$][\w\d_$]*)?\s*\(/; + +this.isArray = function(object) { + return toString.call(object) === '[object Array]'; +}; + +this.isFunction = function(object) { + if (!object) return false; + + try + { + // FIXME: xxxpedro this is failing in IE for the global "external" object + return toString.call(object) === "[object Function]" || + this.isIE && typeof object != "string" && reFunction.test(""+object); + } + catch (E) + { + FBTrace.sysout("Lib.isFunction() failed for ", object); + return false; + } +}; + + +// ************************************************************************************************ +// Instance Checking + +this.instanceOf = function(object, className) +{ + if (!object || typeof object != "object") + return false; + + // Try to use the native instanceof operator. We can only use it when we know + // exactly the window where the object is located at + if (object.ownerDocument) + { + // find the correct window of the object + var win = object.ownerDocument.defaultView || object.ownerDocument.parentWindow; + + // if the class is accessible in the window, uses the native instanceof operator + // if the instanceof evaluates to "true" we can assume it is a instance, but if it + // evaluates to "false" we must continue with the duck type detection below because + // the native object may be extended, thus breaking the instanceof result + // See Issue 3524: Firebug Lite Style Panel doesn't work if the native Element is extended + if (className in win && object instanceof win[className]) + return true; + } + // If the object doesn't have the ownerDocument property, we'll try to look at + // the current context's window + else + { + // TODO: xxxpedro context + // Since we're not using yet a Firebug.context, we'll just use the top window + // (browser) as a reference + var win = Firebug.browser.window; + if (className in win) + return object instanceof win[className]; + } + + // get the duck type model from the cache + var cache = instanceCheckMap[className]; + if (!cache) + return false; + + // starts the hacky duck type detection + for(var n in cache) + { + var obj = cache[n]; + var type = typeof obj; + obj = type == "object" ? obj : [obj]; + + for(var name in obj) + { + // avoid problems with extended native objects + // See Issue 3524: Firebug Lite Style Panel doesn't work if the native Element is extended + if (!obj.hasOwnProperty(name)) + continue; + + var value = obj[name]; + + if( n == "property" && !(value in object) || + n == "method" && !this.isFunction(object[value]) || + n == "value" && (""+object[name]).toLowerCase() != (""+value).toLowerCase() ) + return false; + } + } + + return true; +}; + +var instanceCheckMap = +{ + // DuckTypeCheck: + // { + // property: ["window", "document"], + // method: "setTimeout", + // value: {nodeType: 1} + // }, + + Window: + { + property: ["window", "document"], + method: "setTimeout" + }, + + Document: + { + property: ["body", "cookie"], + method: "getElementById" + }, + + Node: + { + property: "ownerDocument", + method: "appendChild" + }, + + Element: + { + property: "tagName", + value: {nodeType: 1} + }, + + Location: + { + property: ["hostname", "protocol"], + method: "assign" + }, + + HTMLImageElement: + { + property: "useMap", + value: + { + nodeType: 1, + tagName: "img" + } + }, + + HTMLAnchorElement: + { + property: "hreflang", + value: + { + nodeType: 1, + tagName: "a" + } + }, + + HTMLInputElement: + { + property: "form", + value: + { + nodeType: 1, + tagName: "input" + } + }, + + HTMLButtonElement: + { + // ? + }, + + HTMLFormElement: + { + method: "submit", + value: + { + nodeType: 1, + tagName: "form" + } + }, + + HTMLBodyElement: + { + + }, + + HTMLHtmlElement: + { + + }, + + CSSStyleRule: + { + property: ["selectorText", "style"] + } + +}; + + +// ************************************************************************************************ +// DOM Constants + +/* + +Problems: + + - IE does not have window.Node, window.Element, etc + - for (var name in Node.prototype) return nothing on FF + +*/ + + +var domMemberMap2 = {}; + +var domMemberMap2Sandbox = null; + +var getDomMemberMap2 = function(name) +{ + if (!domMemberMap2Sandbox) + { + var doc = Firebug.chrome.document; + var frame = doc.createElement("iframe"); + + frame.id = "FirebugSandbox"; + frame.style.display = "none"; + frame.src = "about:blank"; + + doc.body.appendChild(frame); + + domMemberMap2Sandbox = frame.window || frame.contentWindow; + } + + var props = []; + + //var object = domMemberMap2Sandbox[name]; + //object = object.prototype || object; + + var object = null; + + if (name == "Window") + object = domMemberMap2Sandbox.window; + + else if (name == "Document") + object = domMemberMap2Sandbox.document; + + else if (name == "HTMLScriptElement") + object = domMemberMap2Sandbox.document.createElement("script"); + + else if (name == "HTMLAnchorElement") + object = domMemberMap2Sandbox.document.createElement("a"); + + else if (name.indexOf("Element") != -1) + { + object = domMemberMap2Sandbox.document.createElement("div"); + } + + if (object) + { + //object = object.prototype || object; + + //props = 'addEventListener,document,location,navigator,window'.split(','); + + for (var n in object) + props.push(n); + } + /**/ + + return props; + return extendArray(props, domMemberMap[name]); +}; + +// xxxpedro experimental get DOM members +this.getDOMMembers = function(object) +{ + if (!domMemberCache) + { + FBL.domMemberCache = domMemberCache = {}; + + for (var name in domMemberMap) + { + var builtins = getDomMemberMap2(name); + var cache = domMemberCache[name] = {}; + + /* + if (name.indexOf("Element") != -1) + { + this.append(cache, this.getDOMMembers("Node")); + this.append(cache, this.getDOMMembers("Element")); + } + /**/ + + for (var i = 0; i < builtins.length; ++i) + cache[builtins[i]] = i; + } + } + + try + { + if (this.instanceOf(object, "Window")) + { return domMemberCache.Window; } + else if (this.instanceOf(object, "Document") || this.instanceOf(object, "XMLDocument")) + { return domMemberCache.Document; } + else if (this.instanceOf(object, "Location")) + { return domMemberCache.Location; } + else if (this.instanceOf(object, "HTMLImageElement")) + { return domMemberCache.HTMLImageElement; } + else if (this.instanceOf(object, "HTMLAnchorElement")) + { return domMemberCache.HTMLAnchorElement; } + else if (this.instanceOf(object, "HTMLInputElement")) + { return domMemberCache.HTMLInputElement; } + else if (this.instanceOf(object, "HTMLButtonElement")) + { return domMemberCache.HTMLButtonElement; } + else if (this.instanceOf(object, "HTMLFormElement")) + { return domMemberCache.HTMLFormElement; } + else if (this.instanceOf(object, "HTMLBodyElement")) + { return domMemberCache.HTMLBodyElement; } + else if (this.instanceOf(object, "HTMLHtmlElement")) + { return domMemberCache.HTMLHtmlElement; } + else if (this.instanceOf(object, "HTMLScriptElement")) + { return domMemberCache.HTMLScriptElement; } + else if (this.instanceOf(object, "HTMLTableElement")) + { return domMemberCache.HTMLTableElement; } + else if (this.instanceOf(object, "HTMLTableRowElement")) + { return domMemberCache.HTMLTableRowElement; } + else if (this.instanceOf(object, "HTMLTableCellElement")) + { return domMemberCache.HTMLTableCellElement; } + else if (this.instanceOf(object, "HTMLIFrameElement")) + { return domMemberCache.HTMLIFrameElement; } + else if (this.instanceOf(object, "SVGSVGElement")) + { return domMemberCache.SVGSVGElement; } + else if (this.instanceOf(object, "SVGElement")) + { return domMemberCache.SVGElement; } + else if (this.instanceOf(object, "Element")) + { return domMemberCache.Element; } + else if (this.instanceOf(object, "Text") || this.instanceOf(object, "CDATASection")) + { return domMemberCache.Text; } + else if (this.instanceOf(object, "Attr")) + { return domMemberCache.Attr; } + else if (this.instanceOf(object, "Node")) + { return domMemberCache.Node; } + else if (this.instanceOf(object, "Event") || this.instanceOf(object, "EventCopy")) + { return domMemberCache.Event; } + else + return {}; + } + catch(E) + { + if (FBTrace.DBG_ERRORS) + FBTrace.sysout("lib.getDOMMembers FAILED ", E); + + return {}; + } +}; + + +/* +this.getDOMMembers = function(object) +{ + if (!domMemberCache) + { + domMemberCache = {}; + + for (var name in domMemberMap) + { + var builtins = domMemberMap[name]; + var cache = domMemberCache[name] = {}; + + for (var i = 0; i < builtins.length; ++i) + cache[builtins[i]] = i; + } + } + + try + { + if (this.instanceOf(object, "Window")) + { return domMemberCache.Window; } + else if (object instanceof Document || object instanceof XMLDocument) + { return domMemberCache.Document; } + else if (object instanceof Location) + { return domMemberCache.Location; } + else if (object instanceof HTMLImageElement) + { return domMemberCache.HTMLImageElement; } + else if (object instanceof HTMLAnchorElement) + { return domMemberCache.HTMLAnchorElement; } + else if (object instanceof HTMLInputElement) + { return domMemberCache.HTMLInputElement; } + else if (object instanceof HTMLButtonElement) + { return domMemberCache.HTMLButtonElement; } + else if (object instanceof HTMLFormElement) + { return domMemberCache.HTMLFormElement; } + else if (object instanceof HTMLBodyElement) + { return domMemberCache.HTMLBodyElement; } + else if (object instanceof HTMLHtmlElement) + { return domMemberCache.HTMLHtmlElement; } + else if (object instanceof HTMLScriptElement) + { return domMemberCache.HTMLScriptElement; } + else if (object instanceof HTMLTableElement) + { return domMemberCache.HTMLTableElement; } + else if (object instanceof HTMLTableRowElement) + { return domMemberCache.HTMLTableRowElement; } + else if (object instanceof HTMLTableCellElement) + { return domMemberCache.HTMLTableCellElement; } + else if (object instanceof HTMLIFrameElement) + { return domMemberCache.HTMLIFrameElement; } + else if (object instanceof SVGSVGElement) + { return domMemberCache.SVGSVGElement; } + else if (object instanceof SVGElement) + { return domMemberCache.SVGElement; } + else if (object instanceof Element) + { return domMemberCache.Element; } + else if (object instanceof Text || object instanceof CDATASection) + { return domMemberCache.Text; } + else if (object instanceof Attr) + { return domMemberCache.Attr; } + else if (object instanceof Node) + { return domMemberCache.Node; } + else if (object instanceof Event || object instanceof EventCopy) + { return domMemberCache.Event; } + else + return {}; + } + catch(E) + { + return {}; + } +}; +/**/ + +this.isDOMMember = function(object, propName) +{ + var members = this.getDOMMembers(object); + return members && propName in members; +}; + +var domMemberCache = null; +var domMemberMap = {}; + +domMemberMap.Window = +[ + "document", + "frameElement", + + "innerWidth", + "innerHeight", + "outerWidth", + "outerHeight", + "screenX", + "screenY", + "pageXOffset", + "pageYOffset", + "scrollX", + "scrollY", + "scrollMaxX", + "scrollMaxY", + + "status", + "defaultStatus", + + "parent", + "opener", + "top", + "window", + "content", + "self", + + "location", + "history", + "frames", + "navigator", + "screen", + "menubar", + "toolbar", + "locationbar", + "personalbar", + "statusbar", + "directories", + "scrollbars", + "fullScreen", + "netscape", + "java", + "console", + "Components", + "controllers", + "closed", + "crypto", + "pkcs11", + + "name", + "property", + "length", + + "sessionStorage", + "globalStorage", + + "setTimeout", + "setInterval", + "clearTimeout", + "clearInterval", + "addEventListener", + "removeEventListener", + "dispatchEvent", + "getComputedStyle", + "captureEvents", + "releaseEvents", + "routeEvent", + "enableExternalCapture", + "disableExternalCapture", + "moveTo", + "moveBy", + "resizeTo", + "resizeBy", + "scroll", + "scrollTo", + "scrollBy", + "scrollByLines", + "scrollByPages", + "sizeToContent", + "setResizable", + "getSelection", + "open", + "openDialog", + "close", + "alert", + "confirm", + "prompt", + "dump", + "focus", + "blur", + "find", + "back", + "forward", + "home", + "stop", + "print", + "atob", + "btoa", + "updateCommands", + "XPCNativeWrapper", + "GeckoActiveXObject", + "applicationCache" // FF3 +]; + +domMemberMap.Location = +[ + "href", + "protocol", + "host", + "hostname", + "port", + "pathname", + "search", + "hash", + + "assign", + "reload", + "replace" +]; + +domMemberMap.Node = +[ + "id", + "className", + + "nodeType", + "tagName", + "nodeName", + "localName", + "prefix", + "namespaceURI", + "nodeValue", + + "ownerDocument", + "parentNode", + "offsetParent", + "nextSibling", + "previousSibling", + "firstChild", + "lastChild", + "childNodes", + "attributes", + + "dir", + "baseURI", + "textContent", + "innerHTML", + + "addEventListener", + "removeEventListener", + "dispatchEvent", + "cloneNode", + "appendChild", + "insertBefore", + "replaceChild", + "removeChild", + "compareDocumentPosition", + "hasAttributes", + "hasChildNodes", + "lookupNamespaceURI", + "lookupPrefix", + "normalize", + "isDefaultNamespace", + "isEqualNode", + "isSameNode", + "isSupported", + "getFeature", + "getUserData", + "setUserData" +]; + +domMemberMap.Document = extendArray(domMemberMap.Node, +[ + "documentElement", + "body", + "title", + "location", + "referrer", + "cookie", + "contentType", + "lastModified", + "characterSet", + "inputEncoding", + "xmlEncoding", + "xmlStandalone", + "xmlVersion", + "strictErrorChecking", + "documentURI", + "URL", + + "defaultView", + "doctype", + "implementation", + "styleSheets", + "images", + "links", + "forms", + "anchors", + "embeds", + "plugins", + "applets", + + "width", + "height", + + "designMode", + "compatMode", + "async", + "preferredStylesheetSet", + + "alinkColor", + "linkColor", + "vlinkColor", + "bgColor", + "fgColor", + "domain", + + "addEventListener", + "removeEventListener", + "dispatchEvent", + "captureEvents", + "releaseEvents", + "routeEvent", + "clear", + "open", + "close", + "execCommand", + "execCommandShowHelp", + "getElementsByName", + "getSelection", + "queryCommandEnabled", + "queryCommandIndeterm", + "queryCommandState", + "queryCommandSupported", + "queryCommandText", + "queryCommandValue", + "write", + "writeln", + "adoptNode", + "appendChild", + "removeChild", + "renameNode", + "cloneNode", + "compareDocumentPosition", + "createAttribute", + "createAttributeNS", + "createCDATASection", + "createComment", + "createDocumentFragment", + "createElement", + "createElementNS", + "createEntityReference", + "createEvent", + "createExpression", + "createNSResolver", + "createNodeIterator", + "createProcessingInstruction", + "createRange", + "createTextNode", + "createTreeWalker", + "domConfig", + "evaluate", + "evaluateFIXptr", + "evaluateXPointer", + "getAnonymousElementByAttribute", + "getAnonymousNodes", + "addBinding", + "removeBinding", + "getBindingParent", + "getBoxObjectFor", + "setBoxObjectFor", + "getElementById", + "getElementsByTagName", + "getElementsByTagNameNS", + "hasAttributes", + "hasChildNodes", + "importNode", + "insertBefore", + "isDefaultNamespace", + "isEqualNode", + "isSameNode", + "isSupported", + "load", + "loadBindingDocument", + "lookupNamespaceURI", + "lookupPrefix", + "normalize", + "normalizeDocument", + "getFeature", + "getUserData", + "setUserData" +]); + +domMemberMap.Element = extendArray(domMemberMap.Node, +[ + "clientWidth", + "clientHeight", + "offsetLeft", + "offsetTop", + "offsetWidth", + "offsetHeight", + "scrollLeft", + "scrollTop", + "scrollWidth", + "scrollHeight", + + "style", + + "tabIndex", + "title", + "lang", + "align", + "spellcheck", + + "addEventListener", + "removeEventListener", + "dispatchEvent", + "focus", + "blur", + "cloneNode", + "appendChild", + "insertBefore", + "replaceChild", + "removeChild", + "compareDocumentPosition", + "getElementsByTagName", + "getElementsByTagNameNS", + "getAttribute", + "getAttributeNS", + "getAttributeNode", + "getAttributeNodeNS", + "setAttribute", + "setAttributeNS", + "setAttributeNode", + "setAttributeNodeNS", + "removeAttribute", + "removeAttributeNS", + "removeAttributeNode", + "hasAttribute", + "hasAttributeNS", + "hasAttributes", + "hasChildNodes", + "lookupNamespaceURI", + "lookupPrefix", + "normalize", + "isDefaultNamespace", + "isEqualNode", + "isSameNode", + "isSupported", + "getFeature", + "getUserData", + "setUserData" +]); + +domMemberMap.SVGElement = extendArray(domMemberMap.Element, +[ + "x", + "y", + "width", + "height", + "rx", + "ry", + "transform", + "href", + + "ownerSVGElement", + "viewportElement", + "farthestViewportElement", + "nearestViewportElement", + + "getBBox", + "getCTM", + "getScreenCTM", + "getTransformToElement", + "getPresentationAttribute", + "preserveAspectRatio" +]); + +domMemberMap.SVGSVGElement = extendArray(domMemberMap.Element, +[ + "x", + "y", + "width", + "height", + "rx", + "ry", + "transform", + + "viewBox", + "viewport", + "currentView", + "useCurrentView", + "pixelUnitToMillimeterX", + "pixelUnitToMillimeterY", + "screenPixelToMillimeterX", + "screenPixelToMillimeterY", + "currentScale", + "currentTranslate", + "zoomAndPan", + + "ownerSVGElement", + "viewportElement", + "farthestViewportElement", + "nearestViewportElement", + "contentScriptType", + "contentStyleType", + + "getBBox", + "getCTM", + "getScreenCTM", + "getTransformToElement", + "getEnclosureList", + "getIntersectionList", + "getViewboxToViewportTransform", + "getPresentationAttribute", + "getElementById", + "checkEnclosure", + "checkIntersection", + "createSVGAngle", + "createSVGLength", + "createSVGMatrix", + "createSVGNumber", + "createSVGPoint", + "createSVGRect", + "createSVGString", + "createSVGTransform", + "createSVGTransformFromMatrix", + "deSelectAll", + "preserveAspectRatio", + "forceRedraw", + "suspendRedraw", + "unsuspendRedraw", + "unsuspendRedrawAll", + "getCurrentTime", + "setCurrentTime", + "animationsPaused", + "pauseAnimations", + "unpauseAnimations" +]); + +domMemberMap.HTMLImageElement = extendArray(domMemberMap.Element, +[ + "src", + "naturalWidth", + "naturalHeight", + "width", + "height", + "x", + "y", + "name", + "alt", + "longDesc", + "lowsrc", + "border", + "complete", + "hspace", + "vspace", + "isMap", + "useMap" +]); + +domMemberMap.HTMLAnchorElement = extendArray(domMemberMap.Element, +[ + "name", + "target", + "accessKey", + "href", + "protocol", + "host", + "hostname", + "port", + "pathname", + "search", + "hash", + "hreflang", + "coords", + "shape", + "text", + "type", + "rel", + "rev", + "charset" +]); + +domMemberMap.HTMLIFrameElement = extendArray(domMemberMap.Element, +[ + "contentDocument", + "contentWindow", + "frameBorder", + "height", + "longDesc", + "marginHeight", + "marginWidth", + "name", + "scrolling", + "src", + "width" +]); + +domMemberMap.HTMLTableElement = extendArray(domMemberMap.Element, +[ + "bgColor", + "border", + "caption", + "cellPadding", + "cellSpacing", + "frame", + "rows", + "rules", + "summary", + "tBodies", + "tFoot", + "tHead", + "width", + + "createCaption", + "createTFoot", + "createTHead", + "deleteCaption", + "deleteRow", + "deleteTFoot", + "deleteTHead", + "insertRow" +]); + +domMemberMap.HTMLTableRowElement = extendArray(domMemberMap.Element, +[ + "bgColor", + "cells", + "ch", + "chOff", + "rowIndex", + "sectionRowIndex", + "vAlign", + + "deleteCell", + "insertCell" +]); + +domMemberMap.HTMLTableCellElement = extendArray(domMemberMap.Element, +[ + "abbr", + "axis", + "bgColor", + "cellIndex", + "ch", + "chOff", + "colSpan", + "headers", + "height", + "noWrap", + "rowSpan", + "scope", + "vAlign", + "width" + +]); + +domMemberMap.HTMLScriptElement = extendArray(domMemberMap.Element, +[ + "src" +]); + +domMemberMap.HTMLButtonElement = extendArray(domMemberMap.Element, +[ + "accessKey", + "disabled", + "form", + "name", + "type", + "value", + + "click" +]); + +domMemberMap.HTMLInputElement = extendArray(domMemberMap.Element, +[ + "type", + "value", + "checked", + "accept", + "accessKey", + "alt", + "controllers", + "defaultChecked", + "defaultValue", + "disabled", + "form", + "maxLength", + "name", + "readOnly", + "selectionEnd", + "selectionStart", + "size", + "src", + "textLength", + "useMap", + + "click", + "select", + "setSelectionRange" +]); + +domMemberMap.HTMLFormElement = extendArray(domMemberMap.Element, +[ + "acceptCharset", + "action", + "author", + "elements", + "encoding", + "enctype", + "entry_id", + "length", + "method", + "name", + "post", + "target", + "text", + "url", + + "reset", + "submit" +]); + +domMemberMap.HTMLBodyElement = extendArray(domMemberMap.Element, +[ + "aLink", + "background", + "bgColor", + "link", + "text", + "vLink" +]); + +domMemberMap.HTMLHtmlElement = extendArray(domMemberMap.Element, +[ + "version" +]); + +domMemberMap.Text = extendArray(domMemberMap.Node, +[ + "data", + "length", + + "appendData", + "deleteData", + "insertData", + "replaceData", + "splitText", + "substringData" +]); + +domMemberMap.Attr = extendArray(domMemberMap.Node, +[ + "name", + "value", + "specified", + "ownerElement" +]); + +domMemberMap.Event = +[ + "type", + "target", + "currentTarget", + "originalTarget", + "explicitOriginalTarget", + "relatedTarget", + "rangeParent", + "rangeOffset", + "view", + + "keyCode", + "charCode", + "screenX", + "screenY", + "clientX", + "clientY", + "layerX", + "layerY", + "pageX", + "pageY", + + "detail", + "button", + "which", + "ctrlKey", + "shiftKey", + "altKey", + "metaKey", + + "eventPhase", + "timeStamp", + "bubbles", + "cancelable", + "cancelBubble", + + "isTrusted", + "isChar", + + "getPreventDefault", + "initEvent", + "initMouseEvent", + "initKeyEvent", + "initUIEvent", + "preventBubble", + "preventCapture", + "preventDefault", + "stopPropagation" +]; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +this.domConstantMap = +{ + "ELEMENT_NODE": 1, + "ATTRIBUTE_NODE": 1, + "TEXT_NODE": 1, + "CDATA_SECTION_NODE": 1, + "ENTITY_REFERENCE_NODE": 1, + "ENTITY_NODE": 1, + "PROCESSING_INSTRUCTION_NODE": 1, + "COMMENT_NODE": 1, + "DOCUMENT_NODE": 1, + "DOCUMENT_TYPE_NODE": 1, + "DOCUMENT_FRAGMENT_NODE": 1, + "NOTATION_NODE": 1, + + "DOCUMENT_POSITION_DISCONNECTED": 1, + "DOCUMENT_POSITION_PRECEDING": 1, + "DOCUMENT_POSITION_FOLLOWING": 1, + "DOCUMENT_POSITION_CONTAINS": 1, + "DOCUMENT_POSITION_CONTAINED_BY": 1, + "DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC": 1, + + "UNKNOWN_RULE": 1, + "STYLE_RULE": 1, + "CHARSET_RULE": 1, + "IMPORT_RULE": 1, + "MEDIA_RULE": 1, + "FONT_FACE_RULE": 1, + "PAGE_RULE": 1, + + "CAPTURING_PHASE": 1, + "AT_TARGET": 1, + "BUBBLING_PHASE": 1, + + "SCROLL_PAGE_UP": 1, + "SCROLL_PAGE_DOWN": 1, + + "MOUSEUP": 1, + "MOUSEDOWN": 1, + "MOUSEOVER": 1, + "MOUSEOUT": 1, + "MOUSEMOVE": 1, + "MOUSEDRAG": 1, + "CLICK": 1, + "DBLCLICK": 1, + "KEYDOWN": 1, + "KEYUP": 1, + "KEYPRESS": 1, + "DRAGDROP": 1, + "FOCUS": 1, + "BLUR": 1, + "SELECT": 1, + "CHANGE": 1, + "RESET": 1, + "SUBMIT": 1, + "SCROLL": 1, + "LOAD": 1, + "UNLOAD": 1, + "XFER_DONE": 1, + "ABORT": 1, + "ERROR": 1, + "LOCATE": 1, + "MOVE": 1, + "RESIZE": 1, + "FORWARD": 1, + "HELP": 1, + "BACK": 1, + "TEXT": 1, + + "ALT_MASK": 1, + "CONTROL_MASK": 1, + "SHIFT_MASK": 1, + "META_MASK": 1, + + "DOM_VK_TAB": 1, + "DOM_VK_PAGE_UP": 1, + "DOM_VK_PAGE_DOWN": 1, + "DOM_VK_UP": 1, + "DOM_VK_DOWN": 1, + "DOM_VK_LEFT": 1, + "DOM_VK_RIGHT": 1, + "DOM_VK_CANCEL": 1, + "DOM_VK_HELP": 1, + "DOM_VK_BACK_SPACE": 1, + "DOM_VK_CLEAR": 1, + "DOM_VK_RETURN": 1, + "DOM_VK_ENTER": 1, + "DOM_VK_SHIFT": 1, + "DOM_VK_CONTROL": 1, + "DOM_VK_ALT": 1, + "DOM_VK_PAUSE": 1, + "DOM_VK_CAPS_LOCK": 1, + "DOM_VK_ESCAPE": 1, + "DOM_VK_SPACE": 1, + "DOM_VK_END": 1, + "DOM_VK_HOME": 1, + "DOM_VK_PRINTSCREEN": 1, + "DOM_VK_INSERT": 1, + "DOM_VK_DELETE": 1, + "DOM_VK_0": 1, + "DOM_VK_1": 1, + "DOM_VK_2": 1, + "DOM_VK_3": 1, + "DOM_VK_4": 1, + "DOM_VK_5": 1, + "DOM_VK_6": 1, + "DOM_VK_7": 1, + "DOM_VK_8": 1, + "DOM_VK_9": 1, + "DOM_VK_SEMICOLON": 1, + "DOM_VK_EQUALS": 1, + "DOM_VK_A": 1, + "DOM_VK_B": 1, + "DOM_VK_C": 1, + "DOM_VK_D": 1, + "DOM_VK_E": 1, + "DOM_VK_F": 1, + "DOM_VK_G": 1, + "DOM_VK_H": 1, + "DOM_VK_I": 1, + "DOM_VK_J": 1, + "DOM_VK_K": 1, + "DOM_VK_L": 1, + "DOM_VK_M": 1, + "DOM_VK_N": 1, + "DOM_VK_O": 1, + "DOM_VK_P": 1, + "DOM_VK_Q": 1, + "DOM_VK_R": 1, + "DOM_VK_S": 1, + "DOM_VK_T": 1, + "DOM_VK_U": 1, + "DOM_VK_V": 1, + "DOM_VK_W": 1, + "DOM_VK_X": 1, + "DOM_VK_Y": 1, + "DOM_VK_Z": 1, + "DOM_VK_CONTEXT_MENU": 1, + "DOM_VK_NUMPAD0": 1, + "DOM_VK_NUMPAD1": 1, + "DOM_VK_NUMPAD2": 1, + "DOM_VK_NUMPAD3": 1, + "DOM_VK_NUMPAD4": 1, + "DOM_VK_NUMPAD5": 1, + "DOM_VK_NUMPAD6": 1, + "DOM_VK_NUMPAD7": 1, + "DOM_VK_NUMPAD8": 1, + "DOM_VK_NUMPAD9": 1, + "DOM_VK_MULTIPLY": 1, + "DOM_VK_ADD": 1, + "DOM_VK_SEPARATOR": 1, + "DOM_VK_SUBTRACT": 1, + "DOM_VK_DECIMAL": 1, + "DOM_VK_DIVIDE": 1, + "DOM_VK_F1": 1, + "DOM_VK_F2": 1, + "DOM_VK_F3": 1, + "DOM_VK_F4": 1, + "DOM_VK_F5": 1, + "DOM_VK_F6": 1, + "DOM_VK_F7": 1, + "DOM_VK_F8": 1, + "DOM_VK_F9": 1, + "DOM_VK_F10": 1, + "DOM_VK_F11": 1, + "DOM_VK_F12": 1, + "DOM_VK_F13": 1, + "DOM_VK_F14": 1, + "DOM_VK_F15": 1, + "DOM_VK_F16": 1, + "DOM_VK_F17": 1, + "DOM_VK_F18": 1, + "DOM_VK_F19": 1, + "DOM_VK_F20": 1, + "DOM_VK_F21": 1, + "DOM_VK_F22": 1, + "DOM_VK_F23": 1, + "DOM_VK_F24": 1, + "DOM_VK_NUM_LOCK": 1, + "DOM_VK_SCROLL_LOCK": 1, + "DOM_VK_COMMA": 1, + "DOM_VK_PERIOD": 1, + "DOM_VK_SLASH": 1, + "DOM_VK_BACK_QUOTE": 1, + "DOM_VK_OPEN_BRACKET": 1, + "DOM_VK_BACK_SLASH": 1, + "DOM_VK_CLOSE_BRACKET": 1, + "DOM_VK_QUOTE": 1, + "DOM_VK_META": 1, + + "SVG_ZOOMANDPAN_DISABLE": 1, + "SVG_ZOOMANDPAN_MAGNIFY": 1, + "SVG_ZOOMANDPAN_UNKNOWN": 1 +}; + +this.cssInfo = +{ + "background": ["bgRepeat", "bgAttachment", "bgPosition", "color", "systemColor", "none"], + "background-attachment": ["bgAttachment"], + "background-color": ["color", "systemColor"], + "background-image": ["none"], + "background-position": ["bgPosition"], + "background-repeat": ["bgRepeat"], + + "border": ["borderStyle", "thickness", "color", "systemColor", "none"], + "border-top": ["borderStyle", "borderCollapse", "color", "systemColor", "none"], + "border-right": ["borderStyle", "borderCollapse", "color", "systemColor", "none"], + "border-bottom": ["borderStyle", "borderCollapse", "color", "systemColor", "none"], + "border-left": ["borderStyle", "borderCollapse", "color", "systemColor", "none"], + "border-collapse": ["borderCollapse"], + "border-color": ["color", "systemColor"], + "border-top-color": ["color", "systemColor"], + "border-right-color": ["color", "systemColor"], + "border-bottom-color": ["color", "systemColor"], + "border-left-color": ["color", "systemColor"], + "border-spacing": [], + "border-style": ["borderStyle"], + "border-top-style": ["borderStyle"], + "border-right-style": ["borderStyle"], + "border-bottom-style": ["borderStyle"], + "border-left-style": ["borderStyle"], + "border-width": ["thickness"], + "border-top-width": ["thickness"], + "border-right-width": ["thickness"], + "border-bottom-width": ["thickness"], + "border-left-width": ["thickness"], + + "bottom": ["auto"], + "caption-side": ["captionSide"], + "clear": ["clear", "none"], + "clip": ["auto"], + "color": ["color", "systemColor"], + "content": ["content"], + "counter-increment": ["none"], + "counter-reset": ["none"], + "cursor": ["cursor", "none"], + "direction": ["direction"], + "display": ["display", "none"], + "empty-cells": [], + "float": ["float", "none"], + "font": ["fontStyle", "fontVariant", "fontWeight", "fontFamily"], + + "font-family": ["fontFamily"], + "font-size": ["fontSize"], + "font-size-adjust": [], + "font-stretch": [], + "font-style": ["fontStyle"], + "font-variant": ["fontVariant"], + "font-weight": ["fontWeight"], + + "height": ["auto"], + "left": ["auto"], + "letter-spacing": [], + "line-height": [], + + "list-style": ["listStyleType", "listStylePosition", "none"], + "list-style-image": ["none"], + "list-style-position": ["listStylePosition"], + "list-style-type": ["listStyleType", "none"], + + "margin": [], + "margin-top": [], + "margin-right": [], + "margin-bottom": [], + "margin-left": [], + + "marker-offset": ["auto"], + "min-height": ["none"], + "max-height": ["none"], + "min-width": ["none"], + "max-width": ["none"], + + "outline": ["borderStyle", "color", "systemColor", "none"], + "outline-color": ["color", "systemColor"], + "outline-style": ["borderStyle"], + "outline-width": [], + + "overflow": ["overflow", "auto"], + "overflow-x": ["overflow", "auto"], + "overflow-y": ["overflow", "auto"], + + "padding": [], + "padding-top": [], + "padding-right": [], + "padding-bottom": [], + "padding-left": [], + + "position": ["position"], + "quotes": ["none"], + "right": ["auto"], + "table-layout": ["tableLayout", "auto"], + "text-align": ["textAlign"], + "text-decoration": ["textDecoration", "none"], + "text-indent": [], + "text-shadow": [], + "text-transform": ["textTransform", "none"], + "top": ["auto"], + "unicode-bidi": [], + "vertical-align": ["verticalAlign"], + "white-space": ["whiteSpace"], + "width": ["auto"], + "word-spacing": [], + "z-index": [], + + "-moz-appearance": ["mozAppearance"], + "-moz-border-radius": [], + "-moz-border-radius-bottomleft": [], + "-moz-border-radius-bottomright": [], + "-moz-border-radius-topleft": [], + "-moz-border-radius-topright": [], + "-moz-border-top-colors": ["color", "systemColor"], + "-moz-border-right-colors": ["color", "systemColor"], + "-moz-border-bottom-colors": ["color", "systemColor"], + "-moz-border-left-colors": ["color", "systemColor"], + "-moz-box-align": ["mozBoxAlign"], + "-moz-box-direction": ["mozBoxDirection"], + "-moz-box-flex": [], + "-moz-box-ordinal-group": [], + "-moz-box-orient": ["mozBoxOrient"], + "-moz-box-pack": ["mozBoxPack"], + "-moz-box-sizing": ["mozBoxSizing"], + "-moz-opacity": [], + "-moz-user-focus": ["userFocus", "none"], + "-moz-user-input": ["userInput"], + "-moz-user-modify": [], + "-moz-user-select": ["userSelect", "none"], + "-moz-background-clip": [], + "-moz-background-inline-policy": [], + "-moz-background-origin": [], + "-moz-binding": [], + "-moz-column-count": [], + "-moz-column-gap": [], + "-moz-column-width": [], + "-moz-image-region": [] +}; + +this.inheritedStyleNames = +{ + "border-collapse": 1, + "border-spacing": 1, + "border-style": 1, + "caption-side": 1, + "color": 1, + "cursor": 1, + "direction": 1, + "empty-cells": 1, + "font": 1, + "font-family": 1, + "font-size-adjust": 1, + "font-size": 1, + "font-style": 1, + "font-variant": 1, + "font-weight": 1, + "letter-spacing": 1, + "line-height": 1, + "list-style": 1, + "list-style-image": 1, + "list-style-position": 1, + "list-style-type": 1, + "quotes": 1, + "text-align": 1, + "text-decoration": 1, + "text-indent": 1, + "text-shadow": 1, + "text-transform": 1, + "white-space": 1, + "word-spacing": 1 +}; + +this.cssKeywords = +{ + "appearance": + [ + "button", + "button-small", + "checkbox", + "checkbox-container", + "checkbox-small", + "dialog", + "listbox", + "menuitem", + "menulist", + "menulist-button", + "menulist-textfield", + "menupopup", + "progressbar", + "radio", + "radio-container", + "radio-small", + "resizer", + "scrollbar", + "scrollbarbutton-down", + "scrollbarbutton-left", + "scrollbarbutton-right", + "scrollbarbutton-up", + "scrollbartrack-horizontal", + "scrollbartrack-vertical", + "separator", + "statusbar", + "tab", + "tab-left-edge", + "tabpanels", + "textfield", + "toolbar", + "toolbarbutton", + "toolbox", + "tooltip", + "treeheadercell", + "treeheadersortarrow", + "treeitem", + "treetwisty", + "treetwistyopen", + "treeview", + "window" + ], + + "systemColor": + [ + "ActiveBorder", + "ActiveCaption", + "AppWorkspace", + "Background", + "ButtonFace", + "ButtonHighlight", + "ButtonShadow", + "ButtonText", + "CaptionText", + "GrayText", + "Highlight", + "HighlightText", + "InactiveBorder", + "InactiveCaption", + "InactiveCaptionText", + "InfoBackground", + "InfoText", + "Menu", + "MenuText", + "Scrollbar", + "ThreeDDarkShadow", + "ThreeDFace", + "ThreeDHighlight", + "ThreeDLightShadow", + "ThreeDShadow", + "Window", + "WindowFrame", + "WindowText", + "-moz-field", + "-moz-fieldtext", + "-moz-workspace", + "-moz-visitedhyperlinktext", + "-moz-use-text-color" + ], + + "color": + [ + "AliceBlue", + "AntiqueWhite", + "Aqua", + "Aquamarine", + "Azure", + "Beige", + "Bisque", + "Black", + "BlanchedAlmond", + "Blue", + "BlueViolet", + "Brown", + "BurlyWood", + "CadetBlue", + "Chartreuse", + "Chocolate", + "Coral", + "CornflowerBlue", + "Cornsilk", + "Crimson", + "Cyan", + "DarkBlue", + "DarkCyan", + "DarkGoldenRod", + "DarkGray", + "DarkGreen", + "DarkKhaki", + "DarkMagenta", + "DarkOliveGreen", + "DarkOrange", + "DarkOrchid", + "DarkRed", + "DarkSalmon", + "DarkSeaGreen", + "DarkSlateBlue", + "DarkSlateGray", + "DarkTurquoise", + "DarkViolet", + "DeepPink", + "DarkSkyBlue", + "DimGray", + "DodgerBlue", + "Feldspar", + "FireBrick", + "FloralWhite", + "ForestGreen", + "Fuchsia", + "Gainsboro", + "GhostWhite", + "Gold", + "GoldenRod", + "Gray", + "Green", + "GreenYellow", + "HoneyDew", + "HotPink", + "IndianRed", + "Indigo", + "Ivory", + "Khaki", + "Lavender", + "LavenderBlush", + "LawnGreen", + "LemonChiffon", + "LightBlue", + "LightCoral", + "LightCyan", + "LightGoldenRodYellow", + "LightGrey", + "LightGreen", + "LightPink", + "LightSalmon", + "LightSeaGreen", + "LightSkyBlue", + "LightSlateBlue", + "LightSlateGray", + "LightSteelBlue", + "LightYellow", + "Lime", + "LimeGreen", + "Linen", + "Magenta", + "Maroon", + "MediumAquaMarine", + "MediumBlue", + "MediumOrchid", + "MediumPurple", + "MediumSeaGreen", + "MediumSlateBlue", + "MediumSpringGreen", + "MediumTurquoise", + "MediumVioletRed", + "MidnightBlue", + "MintCream", + "MistyRose", + "Moccasin", + "NavajoWhite", + "Navy", + "OldLace", + "Olive", + "OliveDrab", + "Orange", + "OrangeRed", + "Orchid", + "PaleGoldenRod", + "PaleGreen", + "PaleTurquoise", + "PaleVioletRed", + "PapayaWhip", + "PeachPuff", + "Peru", + "Pink", + "Plum", + "PowderBlue", + "Purple", + "Red", + "RosyBrown", + "RoyalBlue", + "SaddleBrown", + "Salmon", + "SandyBrown", + "SeaGreen", + "SeaShell", + "Sienna", + "Silver", + "SkyBlue", + "SlateBlue", + "SlateGray", + "Snow", + "SpringGreen", + "SteelBlue", + "Tan", + "Teal", + "Thistle", + "Tomato", + "Turquoise", + "Violet", + "VioletRed", + "Wheat", + "White", + "WhiteSmoke", + "Yellow", + "YellowGreen", + "transparent", + "invert" + ], + + "auto": + [ + "auto" + ], + + "none": + [ + "none" + ], + + "captionSide": + [ + "top", + "bottom", + "left", + "right" + ], + + "clear": + [ + "left", + "right", + "both" + ], + + "cursor": + [ + "auto", + "cell", + "context-menu", + "crosshair", + "default", + "help", + "pointer", + "progress", + "move", + "e-resize", + "all-scroll", + "ne-resize", + "nw-resize", + "n-resize", + "se-resize", + "sw-resize", + "s-resize", + "w-resize", + "ew-resize", + "ns-resize", + "nesw-resize", + "nwse-resize", + "col-resize", + "row-resize", + "text", + "vertical-text", + "wait", + "alias", + "copy", + "move", + "no-drop", + "not-allowed", + "-moz-alias", + "-moz-cell", + "-moz-copy", + "-moz-grab", + "-moz-grabbing", + "-moz-contextmenu", + "-moz-zoom-in", + "-moz-zoom-out", + "-moz-spinning" + ], + + "direction": + [ + "ltr", + "rtl" + ], + + "bgAttachment": + [ + "scroll", + "fixed" + ], + + "bgPosition": + [ + "top", + "center", + "bottom", + "left", + "right" + ], + + "bgRepeat": + [ + "repeat", + "repeat-x", + "repeat-y", + "no-repeat" + ], + + "borderStyle": + [ + "hidden", + "dotted", + "dashed", + "solid", + "double", + "groove", + "ridge", + "inset", + "outset", + "-moz-bg-inset", + "-moz-bg-outset", + "-moz-bg-solid" + ], + + "borderCollapse": + [ + "collapse", + "separate" + ], + + "overflow": + [ + "visible", + "hidden", + "scroll", + "-moz-scrollbars-horizontal", + "-moz-scrollbars-none", + "-moz-scrollbars-vertical" + ], + + "listStyleType": + [ + "disc", + "circle", + "square", + "decimal", + "decimal-leading-zero", + "lower-roman", + "upper-roman", + "lower-greek", + "lower-alpha", + "lower-latin", + "upper-alpha", + "upper-latin", + "hebrew", + "armenian", + "georgian", + "cjk-ideographic", + "hiragana", + "katakana", + "hiragana-iroha", + "katakana-iroha", + "inherit" + ], + + "listStylePosition": + [ + "inside", + "outside" + ], + + "content": + [ + "open-quote", + "close-quote", + "no-open-quote", + "no-close-quote", + "inherit" + ], + + "fontStyle": + [ + "normal", + "italic", + "oblique", + "inherit" + ], + + "fontVariant": + [ + "normal", + "small-caps", + "inherit" + ], + + "fontWeight": + [ + "normal", + "bold", + "bolder", + "lighter", + "inherit" + ], + + "fontSize": + [ + "xx-small", + "x-small", + "small", + "medium", + "large", + "x-large", + "xx-large", + "smaller", + "larger" + ], + + "fontFamily": + [ + "Arial", + "Comic Sans MS", + "Georgia", + "Tahoma", + "Verdana", + "Times New Roman", + "Trebuchet MS", + "Lucida Grande", + "Helvetica", + "serif", + "sans-serif", + "cursive", + "fantasy", + "monospace", + "caption", + "icon", + "menu", + "message-box", + "small-caption", + "status-bar", + "inherit" + ], + + "display": + [ + "block", + "inline", + "inline-block", + "list-item", + "marker", + "run-in", + "compact", + "table", + "inline-table", + "table-row-group", + "table-column", + "table-column-group", + "table-header-group", + "table-footer-group", + "table-row", + "table-cell", + "table-caption", + "-moz-box", + "-moz-compact", + "-moz-deck", + "-moz-grid", + "-moz-grid-group", + "-moz-grid-line", + "-moz-groupbox", + "-moz-inline-block", + "-moz-inline-box", + "-moz-inline-grid", + "-moz-inline-stack", + "-moz-inline-table", + "-moz-marker", + "-moz-popup", + "-moz-runin", + "-moz-stack" + ], + + "position": + [ + "static", + "relative", + "absolute", + "fixed", + "inherit" + ], + + "float": + [ + "left", + "right" + ], + + "textAlign": + [ + "left", + "right", + "center", + "justify" + ], + + "tableLayout": + [ + "fixed" + ], + + "textDecoration": + [ + "underline", + "overline", + "line-through", + "blink" + ], + + "textTransform": + [ + "capitalize", + "lowercase", + "uppercase", + "inherit" + ], + + "unicodeBidi": + [ + "normal", + "embed", + "bidi-override" + ], + + "whiteSpace": + [ + "normal", + "pre", + "nowrap" + ], + + "verticalAlign": + [ + "baseline", + "sub", + "super", + "top", + "text-top", + "middle", + "bottom", + "text-bottom", + "inherit" + ], + + "thickness": + [ + "thin", + "medium", + "thick" + ], + + "userFocus": + [ + "ignore", + "normal" + ], + + "userInput": + [ + "disabled", + "enabled" + ], + + "userSelect": + [ + "normal" + ], + + "mozBoxSizing": + [ + "content-box", + "padding-box", + "border-box" + ], + + "mozBoxAlign": + [ + "start", + "center", + "end", + "baseline", + "stretch" + ], + + "mozBoxDirection": + [ + "normal", + "reverse" + ], + + "mozBoxOrient": + [ + "horizontal", + "vertical" + ], + + "mozBoxPack": + [ + "start", + "center", + "end" + ] +}; + +this.nonEditableTags = +{ + "HTML": 1, + "HEAD": 1, + "html": 1, + "head": 1 +}; + +this.innerEditableTags = +{ + "BODY": 1, + "body": 1 +}; + +this.selfClosingTags = +{ // End tags for void elements are forbidden http://wiki.whatwg.org/wiki/HTML_vs._XHTML + "meta": 1, + "link": 1, + "area": 1, + "base": 1, + "col": 1, + "input": 1, + "img": 1, + "br": 1, + "hr": 1, + "param":1, + "embed":1 +}; + +var invisibleTags = this.invisibleTags = +{ + "HTML": 1, + "HEAD": 1, + "TITLE": 1, + "META": 1, + "LINK": 1, + "STYLE": 1, + "SCRIPT": 1, + "NOSCRIPT": 1, + "BR": 1, + "PARAM": 1, + "COL": 1, + + "html": 1, + "head": 1, + "title": 1, + "meta": 1, + "link": 1, + "style": 1, + "script": 1, + "noscript": 1, + "br": 1, + "param": 1, + "col": 1 + /* + "window": 1, + "browser": 1, + "frame": 1, + "tabbrowser": 1, + "WINDOW": 1, + "BROWSER": 1, + "FRAME": 1, + "TABBROWSER": 1, + */ +}; + + +if (typeof KeyEvent == "undefined") { + this.KeyEvent = { + DOM_VK_CANCEL: 3, + DOM_VK_HELP: 6, + DOM_VK_BACK_SPACE: 8, + DOM_VK_TAB: 9, + DOM_VK_CLEAR: 12, + DOM_VK_RETURN: 13, + DOM_VK_ENTER: 14, + DOM_VK_SHIFT: 16, + DOM_VK_CONTROL: 17, + DOM_VK_ALT: 18, + DOM_VK_PAUSE: 19, + DOM_VK_CAPS_LOCK: 20, + DOM_VK_ESCAPE: 27, + DOM_VK_SPACE: 32, + DOM_VK_PAGE_UP: 33, + DOM_VK_PAGE_DOWN: 34, + DOM_VK_END: 35, + DOM_VK_HOME: 36, + DOM_VK_LEFT: 37, + DOM_VK_UP: 38, + DOM_VK_RIGHT: 39, + DOM_VK_DOWN: 40, + DOM_VK_PRINTSCREEN: 44, + DOM_VK_INSERT: 45, + DOM_VK_DELETE: 46, + DOM_VK_0: 48, + DOM_VK_1: 49, + DOM_VK_2: 50, + DOM_VK_3: 51, + DOM_VK_4: 52, + DOM_VK_5: 53, + DOM_VK_6: 54, + DOM_VK_7: 55, + DOM_VK_8: 56, + DOM_VK_9: 57, + DOM_VK_SEMICOLON: 59, + DOM_VK_EQUALS: 61, + DOM_VK_A: 65, + DOM_VK_B: 66, + DOM_VK_C: 67, + DOM_VK_D: 68, + DOM_VK_E: 69, + DOM_VK_F: 70, + DOM_VK_G: 71, + DOM_VK_H: 72, + DOM_VK_I: 73, + DOM_VK_J: 74, + DOM_VK_K: 75, + DOM_VK_L: 76, + DOM_VK_M: 77, + DOM_VK_N: 78, + DOM_VK_O: 79, + DOM_VK_P: 80, + DOM_VK_Q: 81, + DOM_VK_R: 82, + DOM_VK_S: 83, + DOM_VK_T: 84, + DOM_VK_U: 85, + DOM_VK_V: 86, + DOM_VK_W: 87, + DOM_VK_X: 88, + DOM_VK_Y: 89, + DOM_VK_Z: 90, + DOM_VK_CONTEXT_MENU: 93, + DOM_VK_NUMPAD0: 96, + DOM_VK_NUMPAD1: 97, + DOM_VK_NUMPAD2: 98, + DOM_VK_NUMPAD3: 99, + DOM_VK_NUMPAD4: 100, + DOM_VK_NUMPAD5: 101, + DOM_VK_NUMPAD6: 102, + DOM_VK_NUMPAD7: 103, + DOM_VK_NUMPAD8: 104, + DOM_VK_NUMPAD9: 105, + DOM_VK_MULTIPLY: 106, + DOM_VK_ADD: 107, + DOM_VK_SEPARATOR: 108, + DOM_VK_SUBTRACT: 109, + DOM_VK_DECIMAL: 110, + DOM_VK_DIVIDE: 111, + DOM_VK_F1: 112, + DOM_VK_F2: 113, + DOM_VK_F3: 114, + DOM_VK_F4: 115, + DOM_VK_F5: 116, + DOM_VK_F6: 117, + DOM_VK_F7: 118, + DOM_VK_F8: 119, + DOM_VK_F9: 120, + DOM_VK_F10: 121, + DOM_VK_F11: 122, + DOM_VK_F12: 123, + DOM_VK_F13: 124, + DOM_VK_F14: 125, + DOM_VK_F15: 126, + DOM_VK_F16: 127, + DOM_VK_F17: 128, + DOM_VK_F18: 129, + DOM_VK_F19: 130, + DOM_VK_F20: 131, + DOM_VK_F21: 132, + DOM_VK_F22: 133, + DOM_VK_F23: 134, + DOM_VK_F24: 135, + DOM_VK_NUM_LOCK: 144, + DOM_VK_SCROLL_LOCK: 145, + DOM_VK_COMMA: 188, + DOM_VK_PERIOD: 190, + DOM_VK_SLASH: 191, + DOM_VK_BACK_QUOTE: 192, + DOM_VK_OPEN_BRACKET: 219, + DOM_VK_BACK_SLASH: 220, + DOM_VK_CLOSE_BRACKET: 221, + DOM_VK_QUOTE: 222, + DOM_VK_META: 224 + }; +} + + +// ************************************************************************************************ +// Ajax + +/** + * @namespace + */ +this.Ajax = +{ + + requests: [], + transport: null, + states: ["Uninitialized","Loading","Loaded","Interactive","Complete"], + + initialize: function() + { + this.transport = FBL.getNativeXHRObject(); + }, + + getXHRObject: function() + { + var xhrObj = false; + try + { + xhrObj = new XMLHttpRequest(); + } + catch(e) + { + var progid = [ + "MSXML2.XMLHTTP.5.0", "MSXML2.XMLHTTP.4.0", + "MSXML2.XMLHTTP.3.0", "MSXML2.XMLHTTP", "Microsoft.XMLHTTP" + ]; + + for ( var i=0; i < progid.length; ++i ) { + try + { + xhrObj = new ActiveXObject(progid[i]); + } + catch(e) + { + continue; + } + break; + } + } + finally + { + return xhrObj; + } + }, + + + /** + * Create a AJAX request. + * + * @name request + * @param {Object} options request options + * @param {String} options.url URL to be requested + * @param {String} options.type Request type ("get" ou "post"). Default is "get". + * @param {Boolean} options.async Asynchronous flag. Default is "true". + * @param {String} options.dataType Data type ("text", "html", "xml" or "json"). Default is "text". + * @param {String} options.contentType Content-type of the data being sent. Default is "application/x-www-form-urlencoded". + * @param {Function} options.onLoading onLoading callback + * @param {Function} options.onLoaded onLoaded callback + * @param {Function} options.onInteractive onInteractive callback + * @param {Function} options.onComplete onComplete callback + * @param {Function} options.onUpdate onUpdate callback + * @param {Function} options.onSuccess onSuccess callback + * @param {Function} options.onFailure onFailure callback + */ + request: function(options) + { + // process options + var o = FBL.extend( + { + // default values + type: "get", + async: true, + dataType: "text", + contentType: "application/x-www-form-urlencoded" + }, + options || {} + ); + + this.requests.push(o); + + var s = this.getState(); + if (s == "Uninitialized" || s == "Complete" || s == "Loaded") + this.sendRequest(); + }, + + serialize: function(data) + { + var r = [""], rl = 0; + if (data) { + if (typeof data == "string") r[rl++] = data; + + else if (data.innerHTML && data.elements) { + for (var i=0,el,l=(el=data.elements).length; i < l; i++) + if (el[i].name) { + r[rl++] = encodeURIComponent(el[i].name); + r[rl++] = "="; + r[rl++] = encodeURIComponent(el[i].value); + r[rl++] = "&"; + } + + } else + for(var param in data) { + r[rl++] = encodeURIComponent(param); + r[rl++] = "="; + r[rl++] = encodeURIComponent(data[param]); + r[rl++] = "&"; + } + } + return r.join("").replace(/&$/, ""); + }, + + sendRequest: function() + { + var t = FBL.Ajax.transport, r = FBL.Ajax.requests.shift(), data; + + // open XHR object + t.open(r.type, r.url, r.async); + + //setRequestHeaders(); + + // indicates that it is a XHR request to the server + t.setRequestHeader("X-Requested-With", "XMLHttpRequest"); + + // if data is being sent, sets the appropriate content-type + if (data = FBL.Ajax.serialize(r.data)) + t.setRequestHeader("Content-Type", r.contentType); + + /** @ignore */ + // onreadystatechange handler + t.onreadystatechange = function() + { + FBL.Ajax.onStateChange(r); + }; + + // send the request + t.send(data); + }, + + /** + * Handles the state change + */ + onStateChange: function(options) + { + var fn, o = options, t = this.transport; + var state = this.getState(t); + + if (fn = o["on" + state]) fn(this.getResponse(o), o); + + if (state == "Complete") + { + var success = t.status == 200, response = this.getResponse(o); + + if (fn = o["onUpdate"]) + fn(response, o); + + if (fn = o["on" + (success ? "Success" : "Failure")]) + fn(response, o); + + t.onreadystatechange = FBL.emptyFn; + + if (this.requests.length > 0) + setTimeout(this.sendRequest, 10); + } + }, + + /** + * gets the appropriate response value according the type + */ + getResponse: function(options) + { + var t = this.transport, type = options.dataType; + + if (t.status != 200) return t.statusText; + else if (type == "text") return t.responseText; + else if (type == "html") return t.responseText; + else if (type == "xml") return t.responseXML; + else if (type == "json") return eval("(" + t.responseText + ")"); + }, + + /** + * returns the current state of the XHR object + */ + getState: function() + { + return this.states[this.transport.readyState]; + } + +}; + + +// ************************************************************************************************ +// Cookie, from http://www.quirksmode.org/js/cookies.html + +this.createCookie = function(name,value,days) +{ + if ('cookie' in document) + { + if (days) + { + var date = new Date(); + date.setTime(date.getTime()+(days*24*60*60*1000)); + var expires = "; expires="+date.toGMTString(); + } + else + var expires = ""; + + document.cookie = name+"="+value+expires+"; path=/"; + } +}; + +this.readCookie = function (name) +{ + if ('cookie' in document) + { + var nameEQ = name + "="; + var ca = document.cookie.split(';'); + + for(var i=0; i < ca.length; i++) + { + var c = ca[i]; + while (c.charAt(0)==' ') c = c.substring(1,c.length); + if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length); + } + } + + return null; +}; + +this.removeCookie = function(name) +{ + this.createCookie(name, "", -1); +}; + + +// ************************************************************************************************ +// http://www.mister-pixel.com/#Content__state=is_that_simple +var fixIE6BackgroundImageCache = function(doc) +{ + doc = doc || document; + try + { + doc.execCommand("BackgroundImageCache", false, true); + } + catch(E) + { + + } +}; + +// ************************************************************************************************ +// calculatePixelsPerInch + +var resetStyle = "margin:0; padding:0; border:0; position:absolute; overflow:hidden; display:block;"; + +var calculatePixelsPerInch = function calculatePixelsPerInch(doc, body) +{ + var inch = FBL.createGlobalElement("div"); + inch.style.cssText = resetStyle + "width:1in; height:1in; position:absolute; top:-1234px; left:-1234px;"; + body.appendChild(inch); + + FBL.pixelsPerInch = { + x: inch.offsetWidth, + y: inch.offsetHeight + }; + + body.removeChild(inch); +}; + + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +this.SourceLink = function(url, line, type, object, instance) +{ + this.href = url; + this.instance = instance; + this.line = line; + this.type = type; + this.object = object; +}; + +this.SourceLink.prototype = +{ + toString: function() + { + return this.href; + }, + toJSON: function() // until 3.1... + { + return "{\"href\":\""+this.href+"\", "+ + (this.line?("\"line\":"+this.line+","):"")+ + (this.type?(" \"type\":\""+this.type+"\","):"")+ + "}"; + } + +}; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +this.SourceText = function(lines, owner) +{ + this.lines = lines; + this.owner = owner; +}; + +this.SourceText.getLineAsHTML = function(lineNo) +{ + return escapeForSourceLine(this.lines[lineNo-1]); +}; + + +// ************************************************************************************************ +}).apply(FBL); + +/* See license.txt for terms of usage */ + +FBL.ns( /** @scope s_i18n */ function() { with (FBL) { +// ************************************************************************************************ + +// TODO: xxxpedro localization +var oSTR = +{ + "NoMembersWarning": "There are no properties to show for this object.", + + "EmptyStyleSheet": "There are no rules in this stylesheet.", + "EmptyElementCSS": "This element has no style rules.", + "AccessRestricted": "Access to restricted URI denied.", + + "net.label.Parameters": "Parameters", + "net.label.Source": "Source", + "URLParameters": "Params", + + "EditStyle": "Edit Element Style...", + "NewRule": "New Rule...", + + "NewProp": "New Property...", + "EditProp": 'Edit "%s"', + "DeleteProp": 'Delete "%s"', + "DisableProp": 'Disable "%s"' +}; + +// ************************************************************************************************ + +FBL.$STR = function(name) +{ + return oSTR.hasOwnProperty(name) ? oSTR[name] : name; +}; + +FBL.$STRF = function(name, args) +{ + if (!oSTR.hasOwnProperty(name)) return name; + + var format = oSTR[name]; + var objIndex = 0; + + var parts = parseFormat(format); + var trialIndex = objIndex; + var objects = args; + + for (var i= 0; i < parts.length; i++) + { + var part = parts[i]; + if (part && typeof(part) == "object") + { + if (++trialIndex > objects.length) // then too few parameters for format, assume unformatted. + { + format = ""; + objIndex = -1; + parts.length = 0; + break; + } + } + + } + + var result = []; + for (var i = 0; i < parts.length; ++i) + { + var part = parts[i]; + if (part && typeof(part) == "object") + { + result.push(""+args.shift()); + } + else + result.push(part); + } + + return result.join(""); +}; + +// ************************************************************************************************ + +var parseFormat = function parseFormat(format) +{ + var parts = []; + if (format.length <= 0) + return parts; + + var reg = /((^%|.%)(\d+)?(\.)([a-zA-Z]))|((^%|.%)([a-zA-Z]))/; + for (var m = reg.exec(format); m; m = reg.exec(format)) + { + if (m[0].substr(0, 2) == "%%") + { + parts.push(format.substr(0, m.index)); + parts.push(m[0].substr(1)); + } + else + { + var type = m[8] ? m[8] : m[5]; + var precision = m[3] ? parseInt(m[3]) : (m[4] == "." ? -1 : 0); + + var rep = null; + switch (type) + { + case "s": + rep = FirebugReps.Text; + break; + case "f": + case "i": + case "d": + rep = FirebugReps.Number; + break; + case "o": + rep = null; + break; + } + + parts.push(format.substr(0, m[0][0] == "%" ? m.index : m.index+1)); + parts.push({rep: rep, precision: precision, type: ("%" + type)}); + } + + format = format.substr(m.index+m[0].length); + } + + parts.push(format); + return parts; +}; + +// ************************************************************************************************ +}}); + +/* See license.txt for terms of usage */ + +FBL.ns( /** @scope s_firebug */ function() { with (FBL) { +// ************************************************************************************************ + +// ************************************************************************************************ +// Globals + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// Internals + +var modules = []; +var panelTypes = []; +var panelTypeMap = {}; +var reps = []; + +var parentPanelMap = {}; + + +// ************************************************************************************************ +// Firebug + +/** + * @namespace describe Firebug + * @exports FBL.Firebug as Firebug + */ +FBL.Firebug = +{ + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + version: "Firebug Lite 1.4.0", + revision: "$Revision$", + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + modules: modules, + panelTypes: panelTypes, + panelTypeMap: panelTypeMap, + reps: reps, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Initialization + + initialize: function() + { + if (FBTrace.DBG_INITIALIZE) FBTrace.sysout("Firebug.initialize", "initializing application"); + + Firebug.browser = new Context(Env.browser); + Firebug.context = Firebug.browser; + + Firebug.loadPrefs(); + Firebug.context.persistedState.isOpen = false; + + // Document must be cached before chrome initialization + cacheDocument(); + + if (Firebug.Inspector && Firebug.Inspector.create) + Firebug.Inspector.create(); + + if (FBL.CssAnalyzer && FBL.CssAnalyzer.processAllStyleSheets) + FBL.CssAnalyzer.processAllStyleSheets(Firebug.browser.document); + + FirebugChrome.initialize(); + + dispatch(modules, "initialize", []); + + if (Firebug.disableResourceFetching) + Firebug.Console.logFormatted(["Some Firebug Lite features are not working because " + + "resource fetching is disabled. To enabled it set the Firebug Lite option " + + "\"disableResourceFetching\" to \"false\". More info at " + + "http://getfirebug.com/firebuglite#Options"], + Firebug.context, "warn"); + + if (Env.onLoad) + { + var onLoad = Env.onLoad; + delete Env.onLoad; + + setTimeout(onLoad, 200); + } + }, + + shutdown: function() + { + if (Firebug.saveCookies) + Firebug.savePrefs(); + + if (Firebug.Inspector) + Firebug.Inspector.destroy(); + + dispatch(modules, "shutdown", []); + + var chromeMap = FirebugChrome.chromeMap; + + for (var name in chromeMap) + { + if (chromeMap.hasOwnProperty(name)) + { + try + { + chromeMap[name].destroy(); + } + catch(E) + { + if (FBTrace.DBG_ERRORS) FBTrace.sysout("chrome.destroy() failed to: " + name); + } + } + } + + Firebug.Lite.Cache.Element.clear(); + Firebug.Lite.Cache.StyleSheet.clear(); + + Firebug.browser = null; + Firebug.context = null; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Registration + + registerModule: function() + { + modules.push.apply(modules, arguments); + + if (FBTrace.DBG_INITIALIZE) FBTrace.sysout("Firebug.registerModule"); + }, + + registerPanel: function() + { + panelTypes.push.apply(panelTypes, arguments); + + for (var i = 0, panelType; panelType = arguments[i]; ++i) + { + panelTypeMap[panelType.prototype.name] = arguments[i]; + + if (panelType.prototype.parentPanel) + parentPanelMap[panelType.prototype.parentPanel] = 1; + } + + if (FBTrace.DBG_INITIALIZE) + for (var i = 0; i < arguments.length; ++i) + FBTrace.sysout("Firebug.registerPanel", arguments[i].prototype.name); + }, + + registerRep: function() + { + reps.push.apply(reps, arguments); + }, + + unregisterRep: function() + { + for (var i = 0; i < arguments.length; ++i) + remove(reps, arguments[i]); + }, + + setDefaultReps: function(funcRep, rep) + { + FBL.defaultRep = rep; + FBL.defaultFuncRep = funcRep; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Reps + + getRep: function(object) + { + var type = typeof object; + if (isIE && isFunction(object)) + type = "function"; + + for (var i = 0; i < reps.length; ++i) + { + var rep = reps[i]; + try + { + if (rep.supportsObject(object, type)) + { + if (FBTrace.DBG_DOM) + FBTrace.sysout("getRep type: "+type+" object: "+object, rep); + return rep; + } + } + catch (exc) + { + if (FBTrace.DBG_ERRORS) + { + FBTrace.sysout("firebug.getRep FAILS: ", exc.message || exc); + FBTrace.sysout("firebug.getRep reps["+i+"/"+reps.length+"]: Rep="+reps[i].className); + // TODO: xxxpedro add trace to FBTrace logs like in Firebug + //firebug.trace(); + } + } + } + + return (type == 'function') ? defaultFuncRep : defaultRep; + }, + + getRepObject: function(node) + { + var target = null; + for (var child = node; child; child = child.parentNode) + { + if (hasClass(child, "repTarget")) + target = child; + + if (child.repObject) + { + if (!target && hasClass(child, "repIgnore")) + break; + else + return child.repObject; + } + } + }, + + getRepNode: function(node) + { + for (var child = node; child; child = child.parentNode) + { + if (child.repObject) + return child; + } + }, + + getElementByRepObject: function(element, object) + { + for (var child = element.firstChild; child; child = child.nextSibling) + { + if (child.repObject == object) + return child; + } + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Preferences + + getPref: function(name) + { + return Firebug[name]; + }, + + setPref: function(name, value) + { + Firebug[name] = value; + + Firebug.savePrefs(); + }, + + setPrefs: function(prefs) + { + for (var name in prefs) + { + if (prefs.hasOwnProperty(name)) + Firebug[name] = prefs[name]; + } + + Firebug.savePrefs(); + }, + + restorePrefs: function() + { + var Options = Env.DefaultOptions; + + for (var name in Options) + { + Firebug[name] = Options[name]; + } + }, + + loadPrefs: function() + { + this.restorePrefs(); + + var prefs = Store.get("FirebugLite") || {}; + var options = prefs.options; + var persistedState = prefs.persistedState || FBL.defaultPersistedState; + + for (var name in options) + { + if (options.hasOwnProperty(name)) + Firebug[name] = options[name]; + } + + if (Firebug.context && persistedState) + Firebug.context.persistedState = persistedState; + }, + + savePrefs: function() + { + var prefs = { + options: {} + }; + + var EnvOptions = Env.Options; + var options = prefs.options; + for (var name in EnvOptions) + { + if (EnvOptions.hasOwnProperty(name)) + { + options[name] = Firebug[name]; + } + } + + var persistedState = Firebug.context.persistedState; + if (!persistedState) + { + persistedState = Firebug.context.persistedState = FBL.defaultPersistedState; + } + + prefs.persistedState = persistedState; + + Store.set("FirebugLite", prefs); + }, + + erasePrefs: function() + { + Store.remove("FirebugLite"); + this.restorePrefs(); + } +}; + +Firebug.restorePrefs(); + +// xxxpedro should we remove this? +window.Firebug = FBL.Firebug; + +if (!Env.Options.enablePersistent || + Env.Options.enablePersistent && Env.isChromeContext || + Env.isDebugMode) + Env.browser.window.Firebug = FBL.Firebug; + + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// Other methods + +FBL.cacheDocument = function cacheDocument() +{ + var ElementCache = Firebug.Lite.Cache.Element; + var els = Firebug.browser.document.getElementsByTagName("*"); + for (var i=0, l=els.length, el; i<l; i++) + { + el = els[i]; + ElementCache(el); + } +}; + +// ************************************************************************************************ + +/** + * @class + * + * Support for listeners registration. This object also extended by Firebug.Module so, + * all modules supports listening automatically. Notice that array of listeners + * is created for each intance of a module within initialize method. Thus all derived + * module classes must ensure that Firebug.Module.initialize method is called for the + * super class. + */ +Firebug.Listener = function() +{ + // The array is created when the first listeners is added. + // It can't be created here since derived objects would share + // the same array. + this.fbListeners = null; +}; + +Firebug.Listener.prototype = +{ + addListener: function(listener) + { + if (!this.fbListeners) + this.fbListeners = []; // delay the creation until the objects are created so 'this' causes new array for each module + + this.fbListeners.push(listener); + }, + + removeListener: function(listener) + { + remove(this.fbListeners, listener); // if this.fbListeners is null, remove is being called with no add + } +}; + +// ************************************************************************************************ + + +// ************************************************************************************************ +// Module + +/** + * @module Base class for all modules. Every derived module object must be registered using + * <code>Firebug.registerModule</code> method. There is always one instance of a module object + * per browser window. + * @extends Firebug.Listener + */ +Firebug.Module = extend(new Firebug.Listener(), +/** @extend Firebug.Module */ +{ + /** + * Called when the window is opened. + */ + initialize: function() + { + }, + + /** + * Called when the window is closed. + */ + shutdown: function() + { + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + /** + * Called when a new context is created but before the page is loaded. + */ + initContext: function(context) + { + }, + + /** + * Called after a context is detached to a separate window; + */ + reattachContext: function(browser, context) + { + }, + + /** + * Called when a context is destroyed. Module may store info on persistedState for reloaded pages. + */ + destroyContext: function(context, persistedState) + { + }, + + // Called when a FF tab is create or activated (user changes FF tab) + // Called after context is created or with context == null (to abort?) + showContext: function(browser, context) + { + }, + + /** + * Called after a context's page gets DOMContentLoaded + */ + loadedContext: function(context) + { + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + showPanel: function(browser, panel) + { + }, + + showSidePanel: function(browser, panel) + { + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + updateOption: function(name, value) + { + }, + + getObjectByURL: function(context, url) + { + } +}); + +// ************************************************************************************************ +// Panel + +/** + * @panel Base class for all panels. Every derived panel must define a constructor and + * register with "Firebug.registerPanel" method. An instance of the panel + * object is created by the framework for each browser tab where Firebug is activated. + */ +Firebug.Panel = +{ + name: "HelloWorld", + title: "Hello World!", + + parentPanel: null, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + options: { + hasCommandLine: false, + hasStatusBar: false, + hasToolButtons: false, + + // Pre-rendered panels are those included in the skin file (firebug.html) + isPreRendered: false, + innerHTMLSync: false + + /* + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // To be used by external extensions + panelHTML: "", + panelCSS: "", + + toolButtonsHTML: "" + /**/ + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + tabNode: null, + panelNode: null, + sidePanelNode: null, + statusBarNode: null, + toolButtonsNode: null, + + panelBarNode: null, + + sidePanelBarBoxNode: null, + sidePanelBarNode: null, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + sidePanelBar: null, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + searchable: false, + editable: true, + order: 2147483647, + statusSeparator: "<", + + create: function(context, doc) + { + this.hasSidePanel = parentPanelMap.hasOwnProperty(this.name); + + this.panelBarNode = $("fbPanelBar1"); + this.sidePanelBarBoxNode = $("fbPanelBar2"); + + if (this.hasSidePanel) + { + this.sidePanelBar = extend({}, PanelBar); + this.sidePanelBar.create(this); + } + + var options = this.options = extend(Firebug.Panel.options, this.options); + var panelId = "fb" + this.name; + + if (options.isPreRendered) + { + this.panelNode = $(panelId); + + this.tabNode = $(panelId + "Tab"); + this.tabNode.style.display = "block"; + + if (options.hasToolButtons) + { + this.toolButtonsNode = $(panelId + "Buttons"); + } + + if (options.hasStatusBar) + { + this.statusBarBox = $("fbStatusBarBox"); + this.statusBarNode = $(panelId + "StatusBar"); + } + } + else + { + var containerSufix = this.parentPanel ? "2" : "1"; + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Create Panel + var panelNode = this.panelNode = createElement("div", { + id: panelId, + className: "fbPanel" + }); + + $("fbPanel" + containerSufix).appendChild(panelNode); + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Create Panel Tab + var tabHTML = '<span class="fbTabL"></span><span class="fbTabText">' + + this.title + '</span><span class="fbTabR"></span>'; + + var tabNode = this.tabNode = createElement("a", { + id: panelId + "Tab", + className: "fbTab fbHover", + innerHTML: tabHTML + }); + + if (isIE6) + { + tabNode.href = "javascript:void(0)"; + } + + var panelBarNode = this.parentPanel ? + Firebug.chrome.getPanel(this.parentPanel).sidePanelBarNode : + this.panelBarNode; + + panelBarNode.appendChild(tabNode); + tabNode.style.display = "block"; + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // create ToolButtons + if (options.hasToolButtons) + { + this.toolButtonsNode = createElement("span", { + id: panelId + "Buttons", + className: "fbToolbarButtons" + }); + + $("fbToolbarButtons").appendChild(this.toolButtonsNode); + } + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // create StatusBar + if (options.hasStatusBar) + { + this.statusBarBox = $("fbStatusBarBox"); + + this.statusBarNode = createElement("span", { + id: panelId + "StatusBar", + className: "fbToolbarButtons fbStatusBar" + }); + + this.statusBarBox.appendChild(this.statusBarNode); + } + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // create SidePanel + } + + this.containerNode = this.panelNode.parentNode; + + if (FBTrace.DBG_INITIALIZE) FBTrace.sysout("Firebug.Panel.create", this.name); + + // xxxpedro contextMenu + this.onContextMenu = bind(this.onContextMenu, this); + + /* + this.context = context; + this.document = doc; + + this.panelNode = doc.createElement("div"); + this.panelNode.ownerPanel = this; + + setClass(this.panelNode, "panelNode panelNode-"+this.name+" contextUID="+context.uid); + doc.body.appendChild(this.panelNode); + + if (FBTrace.DBG_INITIALIZE) + FBTrace.sysout("firebug.initialize panelNode for "+this.name+"\n"); + + this.initializeNode(this.panelNode); + /**/ + }, + + destroy: function(state) // Panel may store info on state + { + if (FBTrace.DBG_INITIALIZE) FBTrace.sysout("Firebug.Panel.destroy", this.name); + + if (this.hasSidePanel) + { + this.sidePanelBar.destroy(); + this.sidePanelBar = null; + } + + this.options = null; + this.name = null; + this.parentPanel = null; + + this.tabNode = null; + this.panelNode = null; + this.containerNode = null; + + this.toolButtonsNode = null; + this.statusBarBox = null; + this.statusBarNode = null; + + //if (this.panelNode) + // delete this.panelNode.ownerPanel; + + //this.destroyNode(); + }, + + initialize: function() + { + if (FBTrace.DBG_INITIALIZE) FBTrace.sysout("Firebug.Panel.initialize", this.name); + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + if (this.hasSidePanel) + { + this.sidePanelBar.initialize(); + } + + var options = this.options = extend(Firebug.Panel.options, this.options); + var panelId = "fb" + this.name; + + this.panelNode = $(panelId); + + this.tabNode = $(panelId + "Tab"); + this.tabNode.style.display = "block"; + + if (options.hasStatusBar) + { + this.statusBarBox = $("fbStatusBarBox"); + this.statusBarNode = $(panelId + "StatusBar"); + } + + if (options.hasToolButtons) + { + this.toolButtonsNode = $(panelId + "Buttons"); + } + + this.containerNode = this.panelNode.parentNode; + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // restore persistent state + this.containerNode.scrollTop = this.lastScrollTop; + + // xxxpedro contextMenu + addEvent(this.containerNode, "contextmenu", this.onContextMenu); + + + /// TODO: xxxpedro infoTip Hack + Firebug.chrome.currentPanel = + Firebug.chrome.selectedPanel && Firebug.chrome.selectedPanel.sidePanelBar ? + Firebug.chrome.selectedPanel.sidePanelBar.selectedPanel : + Firebug.chrome.selectedPanel; + + Firebug.showInfoTips = true; + if (Firebug.InfoTip) + Firebug.InfoTip.initializeBrowser(Firebug.chrome); + }, + + shutdown: function() + { + if (FBTrace.DBG_INITIALIZE) FBTrace.sysout("Firebug.Panel.shutdown", this.name); + + /// TODO: xxxpedro infoTip Hack + if (Firebug.InfoTip) + Firebug.InfoTip.uninitializeBrowser(Firebug.chrome); + + if (Firebug.chrome.largeCommandLineVisible) + Firebug.chrome.hideLargeCommandLine(); + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + if (this.hasSidePanel) + { + // TODO: xxxpedro firebug1.3a6 + // new PanelBar mechanism will need to call shutdown to hide the panels (so it + // doesn't appears in other panel's sidePanelBar. Therefore, we need to implement + // a "remember selected panel" feature in the sidePanelBar + //this.sidePanelBar.shutdown(); + } + + // store persistent state + this.lastScrollTop = this.containerNode.scrollTop; + + // xxxpedro contextMenu + removeEvent(this.containerNode, "contextmenu", this.onContextMenu); + }, + + detach: function(oldChrome, newChrome) + { + if (oldChrome && oldChrome.selectedPanel && oldChrome.selectedPanel.name == this.name) + this.lastScrollTop = oldChrome.selectedPanel.containerNode.scrollTop; + }, + + reattach: function(doc) + { + if (this.options.innerHTMLSync) + this.synchronizeUI(); + }, + + synchronizeUI: function() + { + this.containerNode.scrollTop = this.lastScrollTop || 0; + }, + + show: function(state) + { + var options = this.options; + + if (options.hasStatusBar) + { + this.statusBarBox.style.display = "inline"; + this.statusBarNode.style.display = "inline"; + } + + if (options.hasToolButtons) + { + this.toolButtonsNode.style.display = "inline"; + } + + this.panelNode.style.display = "block"; + + this.visible = true; + + if (!this.parentPanel) + Firebug.chrome.layout(this); + }, + + hide: function(state) + { + var options = this.options; + + if (options.hasStatusBar) + { + this.statusBarBox.style.display = "none"; + this.statusBarNode.style.display = "none"; + } + + if (options.hasToolButtons) + { + this.toolButtonsNode.style.display = "none"; + } + + this.panelNode.style.display = "none"; + + this.visible = false; + }, + + watchWindow: function(win) + { + }, + + unwatchWindow: function(win) + { + }, + + updateOption: function(name, value) + { + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + /** + * Toolbar helpers + */ + showToolbarButtons: function(buttonsId, show) + { + try + { + if (!this.context.browser) // XXXjjb this is bug. Somehow the panel context is not FirebugContext. + { + if (FBTrace.DBG_ERRORS) + FBTrace.sysout("firebug.Panel showToolbarButtons this.context has no browser, this:", this); + + return; + } + var buttons = this.context.browser.chrome.$(buttonsId); + if (buttons) + collapse(buttons, show ? "false" : "true"); + } + catch (exc) + { + if (FBTrace.DBG_ERRORS) + { + FBTrace.dumpProperties("firebug.Panel showToolbarButtons FAILS", exc); + if (!this.context.browser)FBTrace.dumpStack("firebug.Panel showToolbarButtons no browser"); + } + } + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + /** + * Returns a number indicating the view's ability to inspect the object. + * + * Zero means not supported, and higher numbers indicate specificity. + */ + supportsObject: function(object) + { + return 0; + }, + + hasObject: function(object) // beyond type testing, is this object selectable? + { + return false; + }, + + select: function(object, forceUpdate) + { + if (!object) + object = this.getDefaultSelection(this.context); + + if(FBTrace.DBG_PANELS) + FBTrace.sysout("firebug.select "+this.name+" forceUpdate: "+forceUpdate+" "+object+((object==this.selection)?"==":"!=")+this.selection); + + if (forceUpdate || object != this.selection) + { + this.selection = object; + this.updateSelection(object); + + // TODO: xxxpedro + // XXXjoe This is kind of cheating, but, feh. + //Firebug.chrome.onPanelSelect(object, this); + //if (uiListeners.length > 0) + // dispatch(uiListeners, "onPanelSelect", [object, this]); // TODO: make Firebug.chrome a uiListener + } + }, + + updateSelection: function(object) + { + }, + + markChange: function(skipSelf) + { + if (this.dependents) + { + if (skipSelf) + { + for (var i = 0; i < this.dependents.length; ++i) + { + var panelName = this.dependents[i]; + if (panelName != this.name) + this.context.invalidatePanels(panelName); + } + } + else + this.context.invalidatePanels.apply(this.context, this.dependents); + } + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + startInspecting: function() + { + }, + + stopInspecting: function(object, cancelled) + { + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + search: function(text, reverse) + { + }, + + /** + * Retrieves the search options that this modules supports. + * This is used by the search UI to present the proper options. + */ + getSearchOptionsMenuItems: function() + { + return [ + Firebug.Search.searchOptionMenu("search.Case Sensitive", "searchCaseSensitive") + ]; + }, + + /** + * Navigates to the next document whose match parameter returns true. + */ + navigateToNextDocument: function(match, reverse) + { + // This is an approximation of the UI that is displayed by the location + // selector. This should be close enough, although it may be better + // to simply generate the sorted list within the module, rather than + // sorting within the UI. + var self = this; + function compare(a, b) { + var locA = self.getObjectDescription(a); + var locB = self.getObjectDescription(b); + if(locA.path > locB.path) + return 1; + if(locA.path < locB.path) + return -1; + if(locA.name > locB.name) + return 1; + if(locA.name < locB.name) + return -1; + return 0; + } + var allLocs = this.getLocationList().sort(compare); + for (var curPos = 0; curPos < allLocs.length && allLocs[curPos] != this.location; curPos++); + + function transformIndex(index) { + if (reverse) { + // For the reverse case we need to implement wrap around. + var intermediate = curPos - index - 1; + return (intermediate < 0 ? allLocs.length : 0) + intermediate; + } else { + return (curPos + index + 1) % allLocs.length; + } + }; + + for (var next = 0; next < allLocs.length - 1; next++) + { + var object = allLocs[transformIndex(next)]; + + if (match(object)) + { + this.navigate(object); + return object; + } + } + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + // Called when "Options" clicked. Return array of + // {label: 'name', nol10n: true, type: "checkbox", checked: <value>, command:function to set <value>} + getOptionsMenuItems: function() + { + return null; + }, + + /* + * Called by chrome.onContextMenu to build the context menu when this panel has focus. + * See also FirebugRep for a similar function also called by onContextMenu + * Extensions may monkey patch and chain off this call + * @param object: the 'realObject', a model value, eg a DOM property + * @param target: the HTML element clicked on. + * @return an array of menu items. + */ + getContextMenuItems: function(object, target) + { + return []; + }, + + getBreakOnMenuItems: function() + { + return []; + }, + + getEditor: function(target, value) + { + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + getDefaultSelection: function() + { + return null; + }, + + browseObject: function(object) + { + }, + + getPopupObject: function(target) + { + return Firebug.getRepObject(target); + }, + + getTooltipObject: function(target) + { + return Firebug.getRepObject(target); + }, + + showInfoTip: function(infoTip, x, y) + { + + }, + + getObjectPath: function(object) + { + return null; + }, + + // An array of objects that can be passed to getObjectLocation. + // The list of things a panel can show, eg sourceFiles. + // Only shown if panel.location defined and supportsObject true + getLocationList: function() + { + return null; + }, + + getDefaultLocation: function() + { + return null; + }, + + getObjectLocation: function(object) + { + return ""; + }, + + // Text for the location list menu eg script panel source file list + // return.path: group/category label, return.name: item label + getObjectDescription: function(object) + { + var url = this.getObjectLocation(object); + return FBL.splitURLBase(url); + }, + + /* + * UI signal that a tab needs attention, eg Script panel is currently stopped on a breakpoint + * @param: show boolean, true turns on. + */ + highlight: function(show) + { + var tab = this.getTab(); + if (!tab) + return; + + if (show) + tab.setAttribute("highlight", "true"); + else + tab.removeAttribute("highlight"); + }, + + getTab: function() + { + var chrome = Firebug.chrome; + + var tab = chrome.$("fbPanelBar2").getTab(this.name); + if (!tab) + tab = chrome.$("fbPanelBar1").getTab(this.name); + return tab; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Support for Break On Next + + /** + * Called by the framework when the user clicks on the Break On Next button. + * @param {Boolean} armed Set to true if the Break On Next feature is + * to be armed for action and set to false if the Break On Next should be disarmed. + * If 'armed' is true, then the next call to shouldBreakOnNext should be |true|. + */ + breakOnNext: function(armed) + { + }, + + /** + * Called when a panel is selected/displayed. The method should return true + * if the Break On Next feature is currently armed for this panel. + */ + shouldBreakOnNext: function() + { + return false; + }, + + /** + * Returns labels for Break On Next tooltip (one for enabled and one for disabled state). + * @param {Boolean} enabled Set to true if the Break On Next feature is + * currently activated for this panel. + */ + getBreakOnNextTooltip: function(enabled) + { + return null; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + // xxxpedro contextMenu + onContextMenu: function(event) + { + if (!this.getContextMenuItems) + return; + + cancelEvent(event, true); + + var target = event.target || event.srcElement; + + var menu = this.getContextMenuItems(this.selection, target); + if (!menu) + return; + + var contextMenu = new Menu( + { + id: "fbPanelContextMenu", + + items: menu + }); + + contextMenu.show(event.clientX, event.clientY); + + return true; + + /* + // TODO: xxxpedro move code to somewhere. code to get cross-browser + // window to screen coordinates + var box = Firebug.browser.getElementPosition(Firebug.chrome.node); + + var screenY = 0; + + // Firefox + if (typeof window.mozInnerScreenY != "undefined") + { + screenY = window.mozInnerScreenY; + } + // Chrome + else if (typeof window.innerHeight != "undefined") + { + screenY = window.outerHeight - window.innerHeight; + } + // IE + else if (typeof window.screenTop != "undefined") + { + screenY = window.screenTop; + } + + contextMenu.show(event.screenX-box.left, event.screenY-screenY-box.top); + /**/ + } + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +}; + + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +/** + * MeasureBox + * To get pixels size.width and size.height: + * <ul><li> this.startMeasuring(view); </li> + * <li> var size = this.measureText(lineNoCharsSpacer); </li> + * <li> this.stopMeasuring(); </li> + * </ul> + * + * @namespace + */ +Firebug.MeasureBox = +{ + startMeasuring: function(target) + { + if (!this.measureBox) + { + this.measureBox = target.ownerDocument.createElement("span"); + this.measureBox.className = "measureBox"; + } + + copyTextStyles(target, this.measureBox); + target.ownerDocument.body.appendChild(this.measureBox); + }, + + getMeasuringElement: function() + { + return this.measureBox; + }, + + measureText: function(value) + { + this.measureBox.innerHTML = value ? escapeForSourceLine(value) : "m"; + return {width: this.measureBox.offsetWidth, height: this.measureBox.offsetHeight-1}; + }, + + measureInputText: function(value) + { + value = value ? escapeForTextNode(value) : "m"; + if (!Firebug.showTextNodesWithWhitespace) + value = value.replace(/\t/g,'mmmmmm').replace(/\ /g,'m'); + this.measureBox.innerHTML = value; + return {width: this.measureBox.offsetWidth, height: this.measureBox.offsetHeight-1}; + }, + + getBox: function(target) + { + var style = this.measureBox.ownerDocument.defaultView.getComputedStyle(this.measureBox, ""); + var box = getBoxFromStyles(style, this.measureBox); + return box; + }, + + stopMeasuring: function() + { + this.measureBox.parentNode.removeChild(this.measureBox); + } +}; + + +// ************************************************************************************************ +if (FBL.domplate) Firebug.Rep = domplate( +{ + className: "", + inspectable: true, + + supportsObject: function(object, type) + { + return false; + }, + + inspectObject: function(object, context) + { + Firebug.chrome.select(object); + }, + + browseObject: function(object, context) + { + }, + + persistObject: function(object, context) + { + }, + + getRealObject: function(object, context) + { + return object; + }, + + getTitle: function(object) + { + var label = safeToString(object); + + var re = /\[object (.*?)\]/; + var m = re.exec(label); + + ///return m ? m[1] : label; + + // if the label is in the "[object TYPE]" format return its type + if (m) + { + return m[1]; + } + // if it is IE we need to handle some special cases + else if ( + // safeToString() fails to recognize some objects in IE + isIE && + // safeToString() returns "[object]" for some objects like window.Image + (label == "[object]" || + // safeToString() returns undefined for some objects like window.clientInformation + typeof object == "object" && typeof label == "undefined") + ) + { + return "Object"; + } + else + { + return label; + } + }, + + getTooltip: function(object) + { + return null; + }, + + getContextMenuItems: function(object, target, context) + { + return []; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Convenience for domplates + + STR: function(name) + { + return $STR(name); + }, + + cropString: function(text) + { + return cropString(text); + }, + + cropMultipleLines: function(text, limit) + { + return cropMultipleLines(text, limit); + }, + + toLowerCase: function(text) + { + return text ? text.toLowerCase() : text; + }, + + plural: function(n) + { + return n == 1 ? "" : "s"; + } +}); + +// ************************************************************************************************ + + +// ************************************************************************************************ +}}); + +/* See license.txt for terms of usage */ + +FBL.ns( /** @scope s_gui */ function() { with (FBL) { +// ************************************************************************************************ + +// ************************************************************************************************ +// Controller + +/**@namespace*/ +FBL.Controller = { + + controllers: null, + controllerContext: null, + + initialize: function(context) + { + this.controllers = []; + this.controllerContext = context || Firebug.chrome; + }, + + shutdown: function() + { + this.removeControllers(); + + //this.controllers = null; + //this.controllerContext = null; + }, + + addController: function() + { + for (var i=0, arg; arg=arguments[i]; i++) + { + // If the first argument is a string, make a selector query + // within the controller node context + if (typeof arg[0] == "string") + { + arg[0] = $$(arg[0], this.controllerContext); + } + + // bind the handler to the proper context + var handler = arg[2]; + arg[2] = bind(handler, this); + // save the original handler as an extra-argument, so we can + // look for it later, when removing a particular controller + arg[3] = handler; + + this.controllers.push(arg); + addEvent.apply(this, arg); + } + }, + + removeController: function() + { + for (var i=0, arg; arg=arguments[i]; i++) + { + for (var j=0, c; c=this.controllers[j]; j++) + { + if (arg[0] == c[0] && arg[1] == c[1] && arg[2] == c[3]) + removeEvent.apply(this, c); + } + } + }, + + removeControllers: function() + { + for (var i=0, c; c=this.controllers[i]; i++) + { + removeEvent.apply(this, c); + } + } +}; + + +// ************************************************************************************************ +// PanelBar + +/**@namespace*/ +FBL.PanelBar = +{ + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + panelMap: null, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + selectedPanel: null, + parentPanelName: null, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + create: function(ownerPanel) + { + this.panelMap = {}; + this.ownerPanel = ownerPanel; + + if (ownerPanel) + { + ownerPanel.sidePanelBarNode = createElement("span"); + ownerPanel.sidePanelBarNode.style.display = "none"; + ownerPanel.sidePanelBarBoxNode.appendChild(ownerPanel.sidePanelBarNode); + } + + var panels = Firebug.panelTypes; + for (var i=0, p; p=panels[i]; i++) + { + if ( // normal Panel of the Chrome's PanelBar + !ownerPanel && !p.prototype.parentPanel || + // Child Panel of the current Panel's SidePanelBar + ownerPanel && p.prototype.parentPanel && + ownerPanel.name == p.prototype.parentPanel) + { + this.addPanel(p.prototype.name); + } + } + }, + + destroy: function() + { + PanelBar.shutdown.call(this); + + for (var name in this.panelMap) + { + this.removePanel(name); + + var panel = this.panelMap[name]; + panel.destroy(); + + this.panelMap[name] = null; + delete this.panelMap[name]; + } + + this.panelMap = null; + this.ownerPanel = null; + }, + + initialize: function() + { + if (this.ownerPanel) + this.ownerPanel.sidePanelBarNode.style.display = "inline"; + + for(var name in this.panelMap) + { + (function(self, name){ + + // tab click handler + var onTabClick = function onTabClick() + { + self.selectPanel(name); + return false; + }; + + Firebug.chrome.addController([self.panelMap[name].tabNode, "mousedown", onTabClick]); + + })(this, name); + } + }, + + shutdown: function() + { + var selectedPanel = this.selectedPanel; + + if (selectedPanel) + { + removeClass(selectedPanel.tabNode, "fbSelectedTab"); + selectedPanel.hide(); + selectedPanel.shutdown(); + } + + if (this.ownerPanel) + this.ownerPanel.sidePanelBarNode.style.display = "none"; + + this.selectedPanel = null; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + addPanel: function(panelName, parentPanel) + { + var PanelType = Firebug.panelTypeMap[panelName]; + var panel = this.panelMap[panelName] = new PanelType(); + + panel.create(); + }, + + removePanel: function(panelName) + { + var panel = this.panelMap[panelName]; + if (panel.hasOwnProperty(panelName)) + panel.destroy(); + }, + + selectPanel: function(panelName) + { + var selectedPanel = this.selectedPanel; + var panel = this.panelMap[panelName]; + + if (panel && selectedPanel != panel) + { + if (selectedPanel) + { + removeClass(selectedPanel.tabNode, "fbSelectedTab"); + selectedPanel.shutdown(); + selectedPanel.hide(); + } + + if (!panel.parentPanel) + Firebug.context.persistedState.selectedPanelName = panelName; + + this.selectedPanel = panel; + + setClass(panel.tabNode, "fbSelectedTab"); + panel.show(); + panel.initialize(); + } + }, + + getPanel: function(panelName) + { + var panel = this.panelMap[panelName]; + + return panel; + } + +}; + +//************************************************************************************************ +// Button + +/** + * options.element + * options.caption + * options.title + * + * options.owner + * options.className + * options.pressedClassName + * + * options.onPress + * options.onUnpress + * options.onClick + * + * @class + * @extends FBL.Controller + * + */ + +FBL.Button = function(options) +{ + options = options || {}; + + append(this, options); + + this.state = "unpressed"; + this.display = "unpressed"; + + if (this.element) + { + this.container = this.element.parentNode; + } + else + { + this.shouldDestroy = true; + + this.container = this.owner.getPanel().toolButtonsNode; + + this.element = createElement("a", { + className: this.baseClassName + " " + this.className + " fbHover", + innerHTML: this.caption + }); + + if (this.title) + this.element.title = this.title; + + this.container.appendChild(this.element); + } +}; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +Button.prototype = extend(Controller, +/**@extend FBL.Button.prototype*/ +{ + type: "normal", + caption: "caption", + title: null, + + className: "", // custom class + baseClassName: "fbButton", // control class + pressedClassName: "fbBtnPressed", // control pressed class + + element: null, + container: null, + owner: null, + + state: null, + display: null, + + destroy: function() + { + this.shutdown(); + + // only remove if it is a dynamically generated button (not pre-rendered) + if (this.shouldDestroy) + this.container.removeChild(this.element); + + this.element = null; + this.container = null; + this.owner = null; + }, + + initialize: function() + { + Controller.initialize.apply(this); + + var element = this.element; + + this.addController([element, "mousedown", this.handlePress]); + + if (this.type == "normal") + this.addController( + [element, "mouseup", this.handleUnpress], + [element, "mouseout", this.handleUnpress], + [element, "click", this.handleClick] + ); + }, + + shutdown: function() + { + Controller.shutdown.apply(this); + }, + + restore: function() + { + this.changeState("unpressed"); + }, + + changeState: function(state) + { + this.state = state; + this.changeDisplay(state); + }, + + changeDisplay: function(display) + { + if (display != this.display) + { + if (display == "pressed") + { + setClass(this.element, this.pressedClassName); + } + else if (display == "unpressed") + { + removeClass(this.element, this.pressedClassName); + } + this.display = display; + } + }, + + handlePress: function(event) + { + cancelEvent(event, true); + + if (this.type == "normal") + { + this.changeDisplay("pressed"); + this.beforeClick = true; + } + else if (this.type == "toggle") + { + if (this.state == "pressed") + { + this.changeState("unpressed"); + + if (this.onUnpress) + this.onUnpress.apply(this.owner, arguments); + } + else + { + this.changeState("pressed"); + + if (this.onPress) + this.onPress.apply(this.owner, arguments); + } + + if (this.onClick) + this.onClick.apply(this.owner, arguments); + } + + return false; + }, + + handleUnpress: function(event) + { + cancelEvent(event, true); + + if (this.beforeClick) + this.changeDisplay("unpressed"); + + return false; + }, + + handleClick: function(event) + { + cancelEvent(event, true); + + if (this.type == "normal") + { + if (this.onClick) + this.onClick.apply(this.owner); + + this.changeState("unpressed"); + } + + this.beforeClick = false; + + return false; + } +}); + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +/** + * @class + * @extends FBL.Button + */ +FBL.IconButton = function() +{ + Button.apply(this, arguments); +}; + +IconButton.prototype = extend(Button.prototype, +/**@extend FBL.IconButton.prototype*/ +{ + baseClassName: "fbIconButton", + pressedClassName: "fbIconPressed" +}); + + +//************************************************************************************************ +// Menu + +var menuItemProps = {"class": "$item.className", type: "$item.type", value: "$item.value", + _command: "$item.command"}; + +if (isIE6) + menuItemProps.href = "javascript:void(0)"; + +// Allow GUI to be loaded even when Domplate module is not installed. +if (FBL.domplate) +var MenuPlate = domplate(Firebug.Rep, +{ + tag: + DIV({"class": "fbMenu fbShadow"}, + DIV({"class": "fbMenuContent fbShadowContent"}, + FOR("item", "$object.items|memberIterator", + TAG("$item.tag", {item: "$item"}) + ) + ) + ), + + itemTag: + A(menuItemProps, + "$item.label" + ), + + checkBoxTag: + A(extend(menuItemProps, {checked : "$item.checked"}), + + "$item.label" + ), + + radioButtonTag: + A(extend(menuItemProps, {selected : "$item.selected"}), + + "$item.label" + ), + + groupTag: + A(extend(menuItemProps, {child: "$item.child"}), + "$item.label" + ), + + shortcutTag: + A(menuItemProps, + "$item.label", + SPAN({"class": "fbMenuShortcutKey"}, + "$item.key" + ) + ), + + separatorTag: + SPAN({"class": "fbMenuSeparator"}), + + memberIterator: function(items) + { + var result = []; + + for (var i=0, length=items.length; i<length; i++) + { + var item = items[i]; + + // separator representation + if (typeof item == "string" && item.indexOf("-") == 0) + { + result.push({tag: this.separatorTag}); + continue; + } + + item = extend(item, {}); + + item.type = item.type || ""; + item.value = item.value || ""; + + var type = item.type; + + // default item representation + item.tag = this.itemTag; + + var className = item.className || ""; + + className += "fbMenuOption fbHover "; + + // specific representations + if (type == "checkbox") + { + className += "fbMenuCheckBox "; + item.tag = this.checkBoxTag; + } + else if (type == "radiobutton") + { + className += "fbMenuRadioButton "; + item.tag = this.radioButtonTag; + } + else if (type == "group") + { + className += "fbMenuGroup "; + item.tag = this.groupTag; + } + else if (type == "shortcut") + { + className += "fbMenuShortcut "; + item.tag = this.shortcutTag; + } + + if (item.checked) + className += "fbMenuChecked "; + else if (item.selected) + className += "fbMenuRadioSelected "; + + if (item.disabled) + className += "fbMenuDisabled "; + + item.className = className; + + item.label = $STR(item.label); + + result.push(item); + } + + return result; + } +}); + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +/** + * options + * options.element + * options.id + * options.items + * + * item.label + * item.className + * item.type + * item.value + * item.disabled + * item.checked + * item.selected + * item.command + * item.child + * + * + * @class + * @extends FBL.Controller + * + */ +FBL.Menu = function(options) +{ + // if element is not pre-rendered, we must render it now + if (!options.element) + { + if (options.getItems) + options.items = options.getItems(); + + options.element = MenuPlate.tag.append( + {object: options}, + getElementByClass(Firebug.chrome.document, "fbBody"), + MenuPlate + ); + } + + // extend itself with the provided options + append(this, options); + + if (typeof this.element == "string") + { + this.id = this.element; + this.element = $(this.id); + } + else if (this.id) + { + this.element.id = this.id; + } + + this.element.firebugIgnore = true; + this.elementStyle = this.element.style; + + this.isVisible = false; + + this.handleMouseDown = bind(this.handleMouseDown, this); + this.handleMouseOver = bind(this.handleMouseOver, this); + this.handleMouseOut = bind(this.handleMouseOut, this); + + this.handleWindowMouseDown = bind(this.handleWindowMouseDown, this); +}; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +var menuMap = {}; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +Menu.prototype = extend(Controller, +/**@extend FBL.Menu.prototype*/ +{ + destroy: function() + { + //if (this.element) console.log("destroy", this.element.id); + + this.hide(); + + // if it is a childMenu, remove its reference from the parentMenu + if (this.parentMenu) + this.parentMenu.childMenu = null; + + // remove the element from the document + this.element.parentNode.removeChild(this.element); + + // clear references + this.element = null; + this.elementStyle = null; + this.parentMenu = null; + this.parentTarget = null; + }, + + initialize: function() + { + Controller.initialize.call(this); + + this.addController( + [this.element, "mousedown", this.handleMouseDown], + [this.element, "mouseover", this.handleMouseOver] + ); + }, + + shutdown: function() + { + Controller.shutdown.call(this); + }, + + show: function(x, y) + { + this.initialize(); + + if (this.isVisible) return; + + //console.log("show", this.element.id); + + x = x || 0; + y = y || 0; + + if (this.parentMenu) + { + var oldChildMenu = this.parentMenu.childMenu; + if (oldChildMenu && oldChildMenu != this) + { + oldChildMenu.destroy(); + } + + this.parentMenu.childMenu = this; + } + else + addEvent(Firebug.chrome.document, "mousedown", this.handleWindowMouseDown); + + this.elementStyle.display = "block"; + this.elementStyle.visibility = "hidden"; + + var size = Firebug.chrome.getSize(); + + x = Math.min(x, size.width - this.element.clientWidth - 10); + x = Math.max(x, 0); + + y = Math.min(y, size.height - this.element.clientHeight - 10); + y = Math.max(y, 0); + + this.elementStyle.left = x + "px"; + this.elementStyle.top = y + "px"; + + this.elementStyle.visibility = "visible"; + + this.isVisible = true; + + if (isFunction(this.onShow)) + this.onShow.apply(this, arguments); + }, + + hide: function() + { + this.clearHideTimeout(); + this.clearShowChildTimeout(); + + if (!this.isVisible) return; + + //console.log("hide", this.element.id); + + this.elementStyle.display = "none"; + + if(this.childMenu) + { + this.childMenu.destroy(); + this.childMenu = null; + } + + if(this.parentTarget) + removeClass(this.parentTarget, "fbMenuGroupSelected"); + + this.isVisible = false; + + this.shutdown(); + + if (isFunction(this.onHide)) + this.onHide.apply(this, arguments); + }, + + showChildMenu: function(target) + { + var id = target.getAttribute("child"); + + var parent = this; + var target = target; + + this.showChildTimeout = Firebug.chrome.window.setTimeout(function(){ + + //if (!parent.isVisible) return; + + var box = Firebug.chrome.getElementBox(target); + + var childMenuObject = menuMap.hasOwnProperty(id) ? + menuMap[id] : {element: $(id)}; + + var childMenu = new Menu(extend(childMenuObject, + { + parentMenu: parent, + parentTarget: target + })); + + var offsetLeft = isIE6 ? -1 : -6; // IE6 problem with fixed position + childMenu.show(box.left + box.width + offsetLeft, box.top -6); + setClass(target, "fbMenuGroupSelected"); + + },350); + }, + + clearHideTimeout: function() + { + if (this.hideTimeout) + { + Firebug.chrome.window.clearTimeout(this.hideTimeout); + delete this.hideTimeout; + } + }, + + clearShowChildTimeout: function() + { + if(this.showChildTimeout) + { + Firebug.chrome.window.clearTimeout(this.showChildTimeout); + this.showChildTimeout = null; + } + }, + + handleMouseDown: function(event) + { + cancelEvent(event, true); + + var topParent = this; + while (topParent.parentMenu) + topParent = topParent.parentMenu; + + var target = event.target || event.srcElement; + + target = getAncestorByClass(target, "fbMenuOption"); + + if(!target || hasClass(target, "fbMenuGroup")) + return false; + + if (target && !hasClass(target, "fbMenuDisabled")) + { + var type = target.getAttribute("type"); + + if (type == "checkbox") + { + var checked = target.getAttribute("checked"); + var value = target.getAttribute("value"); + var wasChecked = hasClass(target, "fbMenuChecked"); + + if (wasChecked) + { + removeClass(target, "fbMenuChecked"); + target.setAttribute("checked", ""); + } + else + { + setClass(target, "fbMenuChecked"); + target.setAttribute("checked", "true"); + } + + if (isFunction(this.onCheck)) + this.onCheck.call(this, target, value, !wasChecked); + } + + if (type == "radiobutton") + { + var selectedRadios = getElementsByClass(target.parentNode, "fbMenuRadioSelected"); + + var group = target.getAttribute("group"); + + for (var i = 0, length = selectedRadios.length; i < length; i++) + { + radio = selectedRadios[i]; + + if (radio.getAttribute("group") == group) + { + removeClass(radio, "fbMenuRadioSelected"); + radio.setAttribute("selected", ""); + } + } + + setClass(target, "fbMenuRadioSelected"); + target.setAttribute("selected", "true"); + } + + var handler = null; + + // target.command can be a function or a string. + var cmd = target.command; + + // If it is a function it will be used as the handler + if (isFunction(cmd)) + handler = cmd; + // If it is a string it the property of the current menu object + // will be used as the handler + else if (typeof cmd == "string") + handler = this[cmd]; + + var closeMenu = true; + + if (handler) + closeMenu = handler.call(this, target) !== false; + + if (closeMenu) + topParent.hide(); + } + + return false; + }, + + handleWindowMouseDown: function(event) + { + //console.log("handleWindowMouseDown"); + + var target = event.target || event.srcElement; + + target = getAncestorByClass(target, "fbMenu"); + + if (!target) + { + removeEvent(Firebug.chrome.document, "mousedown", this.handleWindowMouseDown); + this.hide(); + } + }, + + handleMouseOver: function(event) + { + //console.log("handleMouseOver", this.element.id); + + this.clearHideTimeout(); + this.clearShowChildTimeout(); + + var target = event.target || event.srcElement; + + target = getAncestorByClass(target, "fbMenuOption"); + + if(!target) + return; + + var childMenu = this.childMenu; + if(childMenu) + { + removeClass(childMenu.parentTarget, "fbMenuGroupSelected"); + + if (childMenu.parentTarget != target && childMenu.isVisible) + { + childMenu.clearHideTimeout(); + childMenu.hideTimeout = Firebug.chrome.window.setTimeout(function(){ + childMenu.destroy(); + },300); + } + } + + if(hasClass(target, "fbMenuGroup")) + { + this.showChildMenu(target); + } + } +}); + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +append(Menu, +/**@extend FBL.Menu*/ +{ + register: function(object) + { + menuMap[object.id] = object; + }, + + check: function(element) + { + setClass(element, "fbMenuChecked"); + element.setAttribute("checked", "true"); + }, + + uncheck: function(element) + { + removeClass(element, "fbMenuChecked"); + element.setAttribute("checked", ""); + }, + + disable: function(element) + { + setClass(element, "fbMenuDisabled"); + }, + + enable: function(element) + { + removeClass(element, "fbMenuDisabled"); + } +}); + + +//************************************************************************************************ +// Status Bar + +/**@class*/ +function StatusBar(){}; + +StatusBar.prototype = extend(Controller, { + +}); + +// ************************************************************************************************ + + +// ************************************************************************************************ +}}); + +/* See license.txt for terms of usage */ + +FBL.ns( /**@scope s_context*/ function() { with (FBL) { +// ************************************************************************************************ + +// ************************************************************************************************ +// Globals + +var refreshDelay = 300; + +// Opera and some versions of webkit returns the wrong value of document.elementFromPoint() +// function, without taking into account the scroll position. Safari 4 (webkit/531.21.8) +// still have this issue. Google Chrome 4 (webkit/532.5) does not. So, we're assuming this +// issue was fixed in the 532 version +var shouldFixElementFromPoint = isOpera || isSafari && browserVersion < "532"; + +var evalError = "___firebug_evaluation_error___"; +var pixelsPerInch; + +var resetStyle = "margin:0; padding:0; border:0; position:absolute; overflow:hidden; display:block;"; +var offscreenStyle = resetStyle + "top:-1234px; left:-1234px;"; + + +// ************************************************************************************************ +// Context + +/** @class */ +FBL.Context = function(win) +{ + this.window = win.window; + this.document = win.document; + + this.browser = Env.browser; + + // Some windows in IE, like iframe, doesn't have the eval() method + if (isIE && !this.window.eval) + { + // But after executing the following line the method magically appears! + this.window.execScript("null"); + // Just to make sure the "magic" really happened + if (!this.window.eval) + throw new Error("Firebug Error: eval() method not found in this window"); + } + + // Create a new "black-box" eval() method that runs in the global namespace + // of the context window, without exposing the local variables declared + // by the function that calls it + this.eval = this.window.eval("new Function('" + + "try{ return window.eval.apply(window,arguments) }catch(E){ E."+evalError+"=true; return E }" + + "')"); +}; + +FBL.Context.prototype = +{ + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // partial-port of Firebug tabContext.js + + browser: null, + loaded: true, + + setTimeout: function(fn, delay) + { + var win = this.window; + + if (win.setTimeout == this.setTimeout) + throw new Error("setTimeout recursion"); + + var timeout = win.setTimeout.apply ? // IE doesn't have apply method on setTimeout + win.setTimeout.apply(win, arguments) : + win.setTimeout(fn, delay); + + if (!this.timeouts) + this.timeouts = {}; + + this.timeouts[timeout] = 1; + + return timeout; + }, + + clearTimeout: function(timeout) + { + clearTimeout(timeout); + + if (this.timeouts) + delete this.timeouts[timeout]; + }, + + setInterval: function(fn, delay) + { + var win = this.window; + + var timeout = win.setInterval.apply ? // IE doesn't have apply method on setTimeout + win.setInterval.apply(win, arguments) : + win.setInterval(fn, delay); + + if (!this.intervals) + this.intervals = {}; + + this.intervals[timeout] = 1; + + return timeout; + }, + + clearInterval: function(timeout) + { + clearInterval(timeout); + + if (this.intervals) + delete this.intervals[timeout]; + }, + + invalidatePanels: function() + { + if (!this.invalidPanels) + this.invalidPanels = {}; + + for (var i = 0; i < arguments.length; ++i) + { + var panelName = arguments[i]; + + // avoid error. need to create a better getPanel() function as explained below + if (!Firebug.chrome || !Firebug.chrome.selectedPanel) + return; + + //var panel = this.getPanel(panelName, true); + //TODO: xxxpedro context how to get all panels using a single function? + // the current workaround to make the invalidation works is invalidating + // only sidePanels. There's also a problem with panel name (LowerCase in Firebug Lite) + var panel = Firebug.chrome.selectedPanel.sidePanelBar ? + Firebug.chrome.selectedPanel.sidePanelBar.getPanel(panelName, true) : + null; + + if (panel && !panel.noRefresh) + this.invalidPanels[panelName] = 1; + } + + if (this.refreshTimeout) + { + this.clearTimeout(this.refreshTimeout); + delete this.refreshTimeout; + } + + this.refreshTimeout = this.setTimeout(bindFixed(function() + { + var invalids = []; + + for (var panelName in this.invalidPanels) + { + //var panel = this.getPanel(panelName, true); + //TODO: xxxpedro context how to get all panels using a single function? + // the current workaround to make the invalidation works is invalidating + // only sidePanels. There's also a problem with panel name (LowerCase in Firebug Lite) + var panel = Firebug.chrome.selectedPanel.sidePanelBar ? + Firebug.chrome.selectedPanel.sidePanelBar.getPanel(panelName, true) : + null; + + if (panel) + { + if (panel.visible && !panel.editing) + panel.refresh(); + else + panel.needsRefresh = true; + + // If the panel is being edited, we'll keep trying to + // refresh it until editing is done + if (panel.editing) + invalids.push(panelName); + } + } + + delete this.invalidPanels; + delete this.refreshTimeout; + + // Keep looping until every tab is valid + if (invalids.length) + this.invalidatePanels.apply(this, invalids); + }, this), refreshDelay); + }, + + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Evalutation Method + + /** + * Evaluates an expression in the current context window. + * + * @param {String} expr expression to be evaluated + * + * @param {String} context string indicating the global location + * of the object that will be used as the + * context. The context is referred in + * the expression as the "this" keyword. + * If no context is informed, the "window" + * context is used. + * + * @param {String} api string indicating the global location + * of the object that will be used as the + * api of the evaluation. + * + * @param {Function} errorHandler(message) error handler to be called + * if the evaluation fails. + */ + evaluate: function(expr, context, api, errorHandler) + { + // the default context is the "window" object. It can be any string that represents + // a global accessible element as: "my.namespaced.object" + context = context || "window"; + + var isObjectLiteral = trim(expr).indexOf("{") == 0, + cmd, + result; + + // if the context is the "window" object, we don't need a closure + if (context == "window") + { + // If it is an object literal, then wrap the expression with parenthesis so we can + // capture the return value + if (isObjectLiteral) + { + cmd = api ? + "with("+api+"){ ("+expr+") }" : + "(" + expr + ")"; + } + else + { + cmd = api ? + "with("+api+"){ "+expr+" }" : + expr; + } + } + else + { + cmd = api ? + // with API and context, no return value + "(function(arguments){ with(" + api + "){ " + + expr + + " } }).call(" + context + ",undefined)" + : + // with context only, no return value + "(function(arguments){ " + + expr + + " }).call(" + context + ",undefined)"; + } + + result = this.eval(cmd); + + if (result && result[evalError]) + { + var msg = result.name ? (result.name + ": ") : ""; + msg += result.message || result; + + if (errorHandler) + result = errorHandler(msg); + else + result = msg; + } + + return result; + }, + + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Window Methods + + getWindowSize: function() + { + var width=0, height=0, el; + + if (typeof this.window.innerWidth == "number") + { + width = this.window.innerWidth; + height = this.window.innerHeight; + } + else if ((el=this.document.documentElement) && (el.clientHeight || el.clientWidth)) + { + width = el.clientWidth; + height = el.clientHeight; + } + else if ((el=this.document.body) && (el.clientHeight || el.clientWidth)) + { + width = el.clientWidth; + height = el.clientHeight; + } + + return {width: width, height: height}; + }, + + getWindowScrollSize: function() + { + var width=0, height=0, el; + + // first try the document.documentElement scroll size + if (!isIEQuiksMode && (el=this.document.documentElement) && + (el.scrollHeight || el.scrollWidth)) + { + width = el.scrollWidth; + height = el.scrollHeight; + } + + // then we need to check if document.body has a bigger scroll size value + // because sometimes depending on the browser and the page, the document.body + // scroll size returns a smaller (and wrong) measure + if ((el=this.document.body) && (el.scrollHeight || el.scrollWidth) && + (el.scrollWidth > width || el.scrollHeight > height)) + { + width = el.scrollWidth; + height = el.scrollHeight; + } + + return {width: width, height: height}; + }, + + getWindowScrollPosition: function() + { + var top=0, left=0, el; + + if(typeof this.window.pageYOffset == "number") + { + top = this.window.pageYOffset; + left = this.window.pageXOffset; + } + else if((el=this.document.body) && (el.scrollTop || el.scrollLeft)) + { + top = el.scrollTop; + left = el.scrollLeft; + } + else if((el=this.document.documentElement) && (el.scrollTop || el.scrollLeft)) + { + top = el.scrollTop; + left = el.scrollLeft; + } + + return {top:top, left:left}; + }, + + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Element Methods + + getElementFromPoint: function(x, y) + { + if (shouldFixElementFromPoint) + { + var scroll = this.getWindowScrollPosition(); + return this.document.elementFromPoint(x + scroll.left, y + scroll.top); + } + else + return this.document.elementFromPoint(x, y); + }, + + getElementPosition: function(el) + { + var left = 0; + var top = 0; + + do + { + left += el.offsetLeft; + top += el.offsetTop; + } + while (el = el.offsetParent); + + return {left:left, top:top}; + }, + + getElementBox: function(el) + { + var result = {}; + + if (el.getBoundingClientRect) + { + var rect = el.getBoundingClientRect(); + + // fix IE problem with offset when not in fullscreen mode + var offset = isIE ? this.document.body.clientTop || this.document.documentElement.clientTop: 0; + + var scroll = this.getWindowScrollPosition(); + + result.top = Math.round(rect.top - offset + scroll.top); + result.left = Math.round(rect.left - offset + scroll.left); + result.height = Math.round(rect.bottom - rect.top); + result.width = Math.round(rect.right - rect.left); + } + else + { + var position = this.getElementPosition(el); + + result.top = position.top; + result.left = position.left; + result.height = el.offsetHeight; + result.width = el.offsetWidth; + } + + return result; + }, + + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Measurement Methods + + getMeasurement: function(el, name) + { + var result = {value: 0, unit: "px"}; + + var cssValue = this.getStyle(el, name); + + if (!cssValue) return result; + if (cssValue.toLowerCase() == "auto") return result; + + var reMeasure = /(\d+\.?\d*)(.*)/; + var m = cssValue.match(reMeasure); + + if (m) + { + result.value = m[1]-0; + result.unit = m[2].toLowerCase(); + } + + return result; + }, + + getMeasurementInPixels: function(el, name) + { + if (!el) return null; + + var m = this.getMeasurement(el, name); + var value = m.value; + var unit = m.unit; + + if (unit == "px") + return value; + + else if (unit == "pt") + return this.pointsToPixels(name, value); + + else if (unit == "em") + return this.emToPixels(el, value); + + else if (unit == "%") + return this.percentToPixels(el, value); + + else if (unit == "ex") + return this.exToPixels(el, value); + + // TODO: add other units. Maybe create a better general way + // to calculate measurements in different units. + }, + + getMeasurementBox1: function(el, name) + { + var sufixes = ["Top", "Left", "Bottom", "Right"]; + var result = []; + + for(var i=0, sufix; sufix=sufixes[i]; i++) + result[i] = Math.round(this.getMeasurementInPixels(el, name + sufix)); + + return {top:result[0], left:result[1], bottom:result[2], right:result[3]}; + }, + + getMeasurementBox: function(el, name) + { + var result = []; + var sufixes = name == "border" ? + ["TopWidth", "LeftWidth", "BottomWidth", "RightWidth"] : + ["Top", "Left", "Bottom", "Right"]; + + if (isIE) + { + var propName, cssValue; + var autoMargin = null; + + for(var i=0, sufix; sufix=sufixes[i]; i++) + { + propName = name + sufix; + + cssValue = el.currentStyle[propName] || el.style[propName]; + + if (cssValue == "auto") + { + if (!autoMargin) + autoMargin = this.getCSSAutoMarginBox(el); + + result[i] = autoMargin[sufix.toLowerCase()]; + } + else + result[i] = this.getMeasurementInPixels(el, propName); + + } + + } + else + { + for(var i=0, sufix; sufix=sufixes[i]; i++) + result[i] = this.getMeasurementInPixels(el, name + sufix); + } + + return {top:result[0], left:result[1], bottom:result[2], right:result[3]}; + }, + + getCSSAutoMarginBox: function(el) + { + if (isIE && " meta title input script link a ".indexOf(" "+el.nodeName.toLowerCase()+" ") != -1) + return {top:0, left:0, bottom:0, right:0}; + /**/ + + if (isIE && " h1 h2 h3 h4 h5 h6 h7 ul p ".indexOf(" "+el.nodeName.toLowerCase()+" ") == -1) + return {top:0, left:0, bottom:0, right:0}; + /**/ + + var offsetTop = 0; + if (false && isIEStantandMode) + { + var scrollSize = Firebug.browser.getWindowScrollSize(); + offsetTop = scrollSize.height; + } + + var box = this.document.createElement("div"); + //box.style.cssText = "margin:0; padding:1px; border: 0; position:static; overflow:hidden; visibility: hidden;"; + box.style.cssText = "margin:0; padding:1px; border: 0; visibility: hidden;"; + + var clone = el.cloneNode(false); + var text = this.document.createTextNode(" "); + clone.appendChild(text); + + box.appendChild(clone); + + this.document.body.appendChild(box); + + var marginTop = clone.offsetTop - box.offsetTop - 1; + var marginBottom = box.offsetHeight - clone.offsetHeight - 2 - marginTop; + + var marginLeft = clone.offsetLeft - box.offsetLeft - 1; + var marginRight = box.offsetWidth - clone.offsetWidth - 2 - marginLeft; + + this.document.body.removeChild(box); + + return {top:marginTop+offsetTop, left:marginLeft, bottom:marginBottom-offsetTop, right:marginRight}; + }, + + getFontSizeInPixels: function(el) + { + var size = this.getMeasurement(el, "fontSize"); + + if (size.unit == "px") return size.value; + + // get font size, the dirty way + var computeDirtyFontSize = function(el, calibration) + { + var div = this.document.createElement("div"); + var divStyle = offscreenStyle; + + if (calibration) + divStyle += " font-size:"+calibration+"px;"; + + div.style.cssText = divStyle; + div.innerHTML = "A"; + el.appendChild(div); + + var value = div.offsetHeight; + el.removeChild(div); + return value; + }; + + /* + var calibrationBase = 200; + var calibrationValue = computeDirtyFontSize(el, calibrationBase); + var rate = calibrationBase / calibrationValue; + /**/ + + // the "dirty technique" fails in some environments, so we're using a static value + // based in some tests. + var rate = 200 / 225; + + var value = computeDirtyFontSize(el); + + return value * rate; + }, + + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Unit Funtions + + pointsToPixels: function(name, value, returnFloat) + { + var axis = /Top$|Bottom$/.test(name) ? "y" : "x"; + + var result = value * pixelsPerInch[axis] / 72; + + return returnFloat ? result : Math.round(result); + }, + + emToPixels: function(el, value) + { + if (!el) return null; + + var fontSize = this.getFontSizeInPixels(el); + + return Math.round(value * fontSize); + }, + + exToPixels: function(el, value) + { + if (!el) return null; + + // get ex value, the dirty way + var div = this.document.createElement("div"); + div.style.cssText = offscreenStyle + "width:"+value + "ex;"; + + el.appendChild(div); + var value = div.offsetWidth; + el.removeChild(div); + + return value; + }, + + percentToPixels: function(el, value) + { + if (!el) return null; + + // get % value, the dirty way + var div = this.document.createElement("div"); + div.style.cssText = offscreenStyle + "width:"+value + "%;"; + + el.appendChild(div); + var value = div.offsetWidth; + el.removeChild(div); + + return value; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + getStyle: isIE ? function(el, name) + { + return el.currentStyle[name] || el.style[name] || undefined; + } + : function(el, name) + { + return this.document.defaultView.getComputedStyle(el,null)[name] + || el.style[name] || undefined; + } + +}; + + +// ************************************************************************************************ +}}); + +/* See license.txt for terms of usage */ + +FBL.ns( /**@scope ns-chrome*/ function() { with (FBL) { +// ************************************************************************************************ + +// ************************************************************************************************ +// Globals + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// Window Options + +var WindowDefaultOptions = + { + type: "frame", + id: "FirebugUI" + //height: 350 // obsolete + }, + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// Instantiated objects + + commandLine, + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// Interface Elements Cache + + fbTop, + fbContent, + fbContentStyle, + fbBottom, + fbBtnInspect, + + fbToolbar, + + fbPanelBox1, + fbPanelBox1Style, + fbPanelBox2, + fbPanelBox2Style, + fbPanelBar2Box, + fbPanelBar2BoxStyle, + + fbHSplitter, + fbVSplitter, + fbVSplitterStyle, + + fbPanel1, + fbPanel1Style, + fbPanel2, + fbPanel2Style, + + fbConsole, + fbConsoleStyle, + fbHTML, + + fbCommandLine, + fbLargeCommandLine, + fbLargeCommandButtons, + +//* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// Cached size values + + topHeight, + topPartialHeight, + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + chromeRedrawSkipRate = isIE ? 75 : isOpera ? 80 : 75, + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + lastSelectedPanelName, + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + focusCommandLineState = 0, + lastFocusedPanelName, + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + lastHSplitterMouseMove = 0, + onHSplitterMouseMoveBuffer = null, + onHSplitterMouseMoveTimer = null, + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + lastVSplitterMouseMove = 0; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + +// ************************************************************************************************ +// FirebugChrome + +FBL.defaultPersistedState = +{ + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + isOpen: false, + height: 300, + sidePanelWidth: 350, + + selectedPanelName: "Console", + selectedHTMLElementId: null, + + htmlSelectionStack: [] + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +}; + +/**@namespace*/ +FBL.FirebugChrome = +{ + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + //isOpen: false, + //height: 300, + //sidePanelWidth: 350, + + //selectedPanelName: "Console", + //selectedHTMLElementId: null, + + chromeMap: {}, + + htmlSelectionStack: [], + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + create: function() + { + if (FBTrace.DBG_INITIALIZE) FBTrace.sysout("FirebugChrome.create", "creating chrome window"); + + createChromeWindow(); + }, + + initialize: function() + { + if (FBTrace.DBG_INITIALIZE) FBTrace.sysout("FirebugChrome.initialize", "initializing chrome window"); + + if (Env.chrome.type == "frame" || Env.chrome.type == "div") + ChromeMini.create(Env.chrome); + + var chrome = Firebug.chrome = new Chrome(Env.chrome); + FirebugChrome.chromeMap[chrome.type] = chrome; + + addGlobalEvent("keydown", onGlobalKeyDown); + + if (Env.Options.enablePersistent && chrome.type == "popup") + { + // TODO: xxxpedro persist - revise chrome synchronization when in persistent mode + var frame = FirebugChrome.chromeMap.frame; + if (frame) + frame.close(); + + //chrome.reattach(frame, chrome); + //TODO: xxxpedro persist synchronize? + chrome.initialize(); + } + }, + + clone: function(FBChrome) + { + for (var name in FBChrome) + { + var prop = FBChrome[name]; + if (FBChrome.hasOwnProperty(name) && !isFunction(prop)) + { + this[name] = prop; + } + } + } +}; + + + +// ************************************************************************************************ +// Chrome Window Creation + +var createChromeWindow = function(options) +{ + options = extend(WindowDefaultOptions, options || {}); + + //* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Locals + + var browserWin = Env.browser.window; + var browserContext = new Context(browserWin); + var prefs = Store.get("FirebugLite"); + var persistedState = prefs && prefs.persistedState || defaultPersistedState; + + var chrome = {}, + + context = options.context || Env.browser, + + type = chrome.type = Env.Options.enablePersistent ? + "popup" : + options.type, + + isChromeFrame = type == "frame", + + useLocalSkin = Env.useLocalSkin, + + url = useLocalSkin ? + Env.Location.skin : + "about:blank", + + // document.body not available in XML+XSL documents in Firefox + body = context.document.getElementsByTagName("body")[0], + + formatNode = function(node) + { + if (!Env.isDebugMode) + { + node.firebugIgnore = true; + } + + var browserWinSize = browserContext.getWindowSize(); + var height = persistedState.height || 300; + + height = Math.min(browserWinSize.height, height); + height = Math.max(200, height); + + node.style.border = "0"; + node.style.visibility = "hidden"; + node.style.zIndex = "2147483647"; // MAX z-index = 2147483647 + node.style.position = noFixedPosition ? "absolute" : "fixed"; + node.style.width = "100%"; // "102%"; IE auto margin bug + node.style.left = "0"; + node.style.bottom = noFixedPosition ? "-1px" : "0"; + node.style.height = height + "px"; + + // avoid flickering during chrome rendering + //if (isFirefox) + // node.style.display = "none"; + }, + + createChromeDiv = function() + { + //Firebug.Console.warn("Firebug Lite GUI is working in 'windowless mode'. It may behave slower and receive interferences from the page in which it is installed."); + + var node = chrome.node = createGlobalElement("div"), + style = createGlobalElement("style"), + + css = FirebugChrome.Skin.CSS + /* + .replace(/;/g, " !important;") + .replace(/!important\s!important/g, "!important") + .replace(/display\s*:\s*(\w+)\s*!important;/g, "display:$1;")*/, + + // reset some styles to minimize interference from the main page's style + rules = ".fbBody *{margin:0;padding:0;font-size:11px;line-height:13px;color:inherit;}" + + // load the chrome styles + css + + // adjust some remaining styles + ".fbBody #fbHSplitter{position:absolute !important;} .fbBody #fbHTML span{line-height:14px;} .fbBody .lineNo div{line-height:inherit !important;}"; + /* + if (isIE) + { + // IE7 CSS bug (FbChrome table bigger than its parent div) + rules += ".fbBody table.fbChrome{position: static !important;}"; + }/**/ + + style.type = "text/css"; + + if (style.styleSheet) + style.styleSheet.cssText = rules; + else + style.appendChild(context.document.createTextNode(rules)); + + document.getElementsByTagName("head")[0].appendChild(style); + + node.className = "fbBody"; + node.style.overflow = "hidden"; + node.innerHTML = getChromeDivTemplate(); + + if (isIE) + { + // IE7 CSS bug (FbChrome table bigger than its parent div) + setTimeout(function(){ + node.firstChild.style.height = "1px"; + node.firstChild.style.position = "static"; + },0); + /**/ + } + + formatNode(node); + + body.appendChild(node); + + chrome.window = window; + chrome.document = document; + onChromeLoad(chrome); + }; + + //* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + try + { + //* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // create the Chrome as a "div" (windowless mode) + if (type == "div") + { + createChromeDiv(); + return; + } + + //* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // cretate the Chrome as an "iframe" + else if (isChromeFrame) + { + // Create the Chrome Frame + var node = chrome.node = createGlobalElement("iframe"); + node.setAttribute("src", url); + node.setAttribute("frameBorder", "0"); + + formatNode(node); + + body.appendChild(node); + + // must set the id after appending to the document, otherwise will cause an + // strange error in IE, making the iframe load the page in which the bookmarklet + // was created (like getfirebug.com), before loading the injected UI HTML, + // generating an "Access Denied" error. + node.id = options.id; + } + + //* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // create the Chrome as a "popup" + else + { + var height = persistedState.popupHeight || 300; + var browserWinSize = browserContext.getWindowSize(); + + var browserWinLeft = typeof browserWin.screenX == "number" ? + browserWin.screenX : browserWin.screenLeft; + + var popupLeft = typeof persistedState.popupLeft == "number" ? + persistedState.popupLeft : browserWinLeft; + + var browserWinTop = typeof browserWin.screenY == "number" ? + browserWin.screenY : browserWin.screenTop; + + var popupTop = typeof persistedState.popupTop == "number" ? + persistedState.popupTop : + Math.max( + 0, + Math.min( + browserWinTop + browserWinSize.height - height, + // Google Chrome bug + screen.availHeight - height - 61 + ) + ); + + var popupWidth = typeof persistedState.popupWidth == "number" ? + persistedState.popupWidth : + Math.max( + 0, + Math.min( + browserWinSize.width, + // Opera opens popup in a new tab if it's too big! + screen.availWidth-10 + ) + ); + + var popupHeight = typeof persistedState.popupHeight == "number" ? + persistedState.popupHeight : 300; + + var options = [ + "true,top=", popupTop, + ",left=", popupLeft, + ",height=", popupHeight, + ",width=", popupWidth, + ",resizable" + ].join(""), + + node = chrome.node = context.window.open( + url, + "popup", + options + ); + + if (node) + { + try + { + node.focus(); + } + catch(E) + { + alert("Firebug Error: Firebug popup was blocked."); + return; + } + } + else + { + alert("Firebug Error: Firebug popup was blocked."); + return; + } + } + + //* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Inject the interface HTML if it is not using the local skin + + if (!useLocalSkin) + { + var tpl = getChromeTemplate(!isChromeFrame), + doc = isChromeFrame ? node.contentWindow.document : node.document; + + doc.write(tpl); + doc.close(); + } + + //* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Wait the Window to be loaded + + var win, + + waitDelay = useLocalSkin ? isChromeFrame ? 200 : 300 : 100, + + waitForWindow = function() + { + if ( // Frame loaded... OR + isChromeFrame && (win=node.contentWindow) && + node.contentWindow.document.getElementById("fbCommandLine") || + + // Popup loaded + !isChromeFrame && (win=node.window) && node.document && + node.document.getElementById("fbCommandLine") ) + { + chrome.window = win.window; + chrome.document = win.document; + + // Prevent getting the wrong chrome height in FF when opening a popup + setTimeout(function(){ + onChromeLoad(chrome); + }, useLocalSkin ? 200 : 0); + } + else + setTimeout(waitForWindow, waitDelay); + }; + + waitForWindow(); + } + catch(e) + { + var msg = e.message || e; + + if (/access/i.test(msg)) + { + // Firebug Lite could not create a window for its Graphical User Interface due to + // a access restriction. This happens in some pages, when loading via bookmarklet. + // In such cases, the only way is to load the GUI in a "windowless mode". + + if (isChromeFrame) + body.removeChild(node); + else if(type == "popup") + node.close(); + + // Load the GUI in a "windowless mode" + createChromeDiv(); + } + else + { + alert("Firebug Error: Firebug GUI could not be created."); + } + } +}; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +var onChromeLoad = function onChromeLoad(chrome) +{ + Env.chrome = chrome; + + if (FBTrace.DBG_INITIALIZE) FBTrace.sysout("Chrome onChromeLoad", "chrome window loaded"); + + if (Env.Options.enablePersistent) + { + // TODO: xxxpedro persist - make better chrome synchronization when in persistent mode + Env.FirebugChrome = FirebugChrome; + + chrome.window.Firebug = chrome.window.Firebug || {}; + chrome.window.Firebug.SharedEnv = Env; + + if (Env.isDevelopmentMode) + { + Env.browser.window.FBDev.loadChromeApplication(chrome); + } + else + { + var doc = chrome.document; + var script = doc.createElement("script"); + script.src = Env.Location.app + "#remote,persist"; + doc.getElementsByTagName("head")[0].appendChild(script); + } + } + else + { + if (chrome.type == "frame" || chrome.type == "div") + { + // initialize the chrome application + setTimeout(function(){ + FBL.Firebug.initialize(); + },0); + } + else if (chrome.type == "popup") + { + var oldChrome = FirebugChrome.chromeMap.frame; + + var newChrome = new Chrome(chrome); + + // TODO: xxxpedro sync detach reattach attach + dispatch(newChrome.panelMap, "detach", [oldChrome, newChrome]); + + newChrome.reattach(oldChrome, newChrome); + } + } +}; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +var getChromeDivTemplate = function() +{ + return FirebugChrome.Skin.HTML; +}; + +var getChromeTemplate = function(isPopup) +{ + var tpl = FirebugChrome.Skin; + var r = [], i = -1; + + r[++i] = '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/DTD/strict.dtd">'; + r[++i] = '<html><head><title>'; + r[++i] = Firebug.version; + + /* + r[++i] = '</title><link href="'; + r[++i] = Env.Location.skinDir + 'firebug.css'; + r[++i] = '" rel="stylesheet" type="text/css" />'; + /**/ + + r[++i] = '</title><style>html,body{margin:0;padding:0;overflow:hidden;}'; + r[++i] = tpl.CSS; + r[++i] = '</style>'; + /**/ + + r[++i] = '</head><body class="fbBody' + (isPopup ? ' FirebugPopup' : '') + '">'; + r[++i] = tpl.HTML; + r[++i] = '</body></html>'; + + return r.join(""); +}; + + +// ************************************************************************************************ +// Chrome Class + +/**@class*/ +var Chrome = function Chrome(chrome) +{ + var type = chrome.type; + var Base = type == "frame" || type == "div" ? ChromeFrameBase : ChromePopupBase; + + append(this, Base); // inherit from base class (ChromeFrameBase or ChromePopupBase) + append(this, chrome); // inherit chrome window properties + append(this, new Context(chrome.window)); // inherit from Context class + + FirebugChrome.chromeMap[type] = this; + Firebug.chrome = this; + Env.chrome = chrome.window; + + this.commandLineVisible = false; + this.sidePanelVisible = false; + + this.create(); + + return this; +}; + +// ************************************************************************************************ +// ChromeBase + +/** + * @namespace + * @extends FBL.Controller + * @extends FBL.PanelBar + **/ +var ChromeBase = {}; +append(ChromeBase, Controller); +append(ChromeBase, PanelBar); +append(ChromeBase, +/**@extend ns-chrome-ChromeBase*/ +{ + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // inherited properties + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // inherited from createChrome function + + node: null, + type: null, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // inherited from Context.prototype + + document: null, + window: null, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // value properties + + sidePanelVisible: false, + commandLineVisible: false, + largeCommandLineVisible: false, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // object properties + + inspectButton: null, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + create: function() + { + PanelBar.create.call(this); + + if (Firebug.Inspector) + this.inspectButton = new Button({ + type: "toggle", + element: $("fbChrome_btInspect"), + owner: Firebug.Inspector, + + onPress: Firebug.Inspector.startInspecting, + onUnpress: Firebug.Inspector.stopInspecting + }); + }, + + destroy: function() + { + if(Firebug.Inspector) + this.inspectButton.destroy(); + + PanelBar.destroy.call(this); + + this.shutdown(); + }, + + testMenu: function() + { + var firebugMenu = new Menu( + { + id: "fbFirebugMenu", + + items: + [ + { + label: "Open Firebug", + type: "shortcut", + key: isFirefox ? "Shift+F12" : "F12", + checked: true, + command: "toggleChrome" + }, + { + label: "Open Firebug in New Window", + type: "shortcut", + key: isFirefox ? "Ctrl+Shift+F12" : "Ctrl+F12", + command: "openPopup" + }, + { + label: "Inspect Element", + type: "shortcut", + key: "Ctrl+Shift+C", + command: "toggleInspect" + }, + { + label: "Command Line", + type: "shortcut", + key: "Ctrl+Shift+L", + command: "focusCommandLine" + }, + "-", + { + label: "Options", + type: "group", + child: "fbFirebugOptionsMenu" + }, + "-", + { + label: "Firebug Lite Website...", + command: "visitWebsite" + }, + { + label: "Discussion Group...", + command: "visitDiscussionGroup" + }, + { + label: "Issue Tracker...", + command: "visitIssueTracker" + } + ], + + onHide: function() + { + iconButton.restore(); + }, + + toggleChrome: function() + { + Firebug.chrome.toggle(); + }, + + openPopup: function() + { + Firebug.chrome.toggle(true, true); + }, + + toggleInspect: function() + { + Firebug.Inspector.toggleInspect(); + }, + + focusCommandLine: function() + { + Firebug.chrome.focusCommandLine(); + }, + + visitWebsite: function() + { + this.visit("http://getfirebug.com/lite.html"); + }, + + visitDiscussionGroup: function() + { + this.visit("http://groups.google.com/group/firebug"); + }, + + visitIssueTracker: function() + { + this.visit("http://code.google.com/p/fbug/issues/list"); + }, + + visit: function(url) + { + window.open(url); + } + + }); + + /**@private*/ + var firebugOptionsMenu = + { + id: "fbFirebugOptionsMenu", + + getItems: function() + { + var cookiesDisabled = !Firebug.saveCookies; + + return [ + { + label: "Start Opened", + type: "checkbox", + value: "startOpened", + checked: Firebug.startOpened, + disabled: cookiesDisabled + }, + { + label: "Start in New Window", + type: "checkbox", + value: "startInNewWindow", + checked: Firebug.startInNewWindow, + disabled: cookiesDisabled + }, + { + label: "Show Icon When Hidden", + type: "checkbox", + value: "showIconWhenHidden", + checked: Firebug.showIconWhenHidden, + disabled: cookiesDisabled + }, + { + label: "Override Console Object", + type: "checkbox", + value: "overrideConsole", + checked: Firebug.overrideConsole, + disabled: cookiesDisabled + }, + { + label: "Ignore Firebug Elements", + type: "checkbox", + value: "ignoreFirebugElements", + checked: Firebug.ignoreFirebugElements, + disabled: cookiesDisabled + }, + { + label: "Disable When Firebug Active", + type: "checkbox", + value: "disableWhenFirebugActive", + checked: Firebug.disableWhenFirebugActive, + disabled: cookiesDisabled + }, + { + label: "Disable XHR Listener", + type: "checkbox", + value: "disableXHRListener", + checked: Firebug.disableXHRListener, + disabled: cookiesDisabled + }, + { + label: "Disable Resource Fetching", + type: "checkbox", + value: "disableResourceFetching", + checked: Firebug.disableResourceFetching, + disabled: cookiesDisabled + }, + { + label: "Enable Trace Mode", + type: "checkbox", + value: "enableTrace", + checked: Firebug.enableTrace, + disabled: cookiesDisabled + }, + { + label: "Enable Persistent Mode (experimental)", + type: "checkbox", + value: "enablePersistent", + checked: Firebug.enablePersistent, + disabled: cookiesDisabled + }, + "-", + { + label: "Reset All Firebug Options", + command: "restorePrefs", + disabled: cookiesDisabled + } + ]; + }, + + onCheck: function(target, value, checked) + { + Firebug.setPref(value, checked); + }, + + restorePrefs: function(target) + { + Firebug.erasePrefs(); + + if (target) + this.updateMenu(target); + }, + + updateMenu: function(target) + { + var options = getElementsByClass(target.parentNode, "fbMenuOption"); + + var firstOption = options[0]; + var enabled = Firebug.saveCookies; + if (enabled) + Menu.check(firstOption); + else + Menu.uncheck(firstOption); + + if (enabled) + Menu.check(options[0]); + else + Menu.uncheck(options[0]); + + for (var i = 1, length = options.length; i < length; i++) + { + var option = options[i]; + + var value = option.getAttribute("value"); + var pref = Firebug[value]; + + if (pref) + Menu.check(option); + else + Menu.uncheck(option); + + if (enabled) + Menu.enable(option); + else + Menu.disable(option); + } + } + }; + + Menu.register(firebugOptionsMenu); + + var menu = firebugMenu; + + var testMenuClick = function(event) + { + //console.log("testMenuClick"); + cancelEvent(event, true); + + var target = event.target || event.srcElement; + + if (menu.isVisible) + menu.hide(); + else + { + var offsetLeft = isIE6 ? 1 : -4, // IE6 problem with fixed position + + chrome = Firebug.chrome, + + box = chrome.getElementBox(target), + + offset = chrome.type == "div" ? + chrome.getElementPosition(chrome.node) : + {top: 0, left: 0}; + + menu.show( + box.left + offsetLeft - offset.left, + box.top + box.height -5 - offset.top + ); + } + + return false; + }; + + var iconButton = new IconButton({ + type: "toggle", + element: $("fbFirebugButton"), + + onClick: testMenuClick + }); + + iconButton.initialize(); + + //addEvent($("fbToolbarIcon"), "click", testMenuClick); + }, + + initialize: function() + { + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + if (Env.bookmarkletOutdated) + Firebug.Console.logFormatted([ + "A new bookmarklet version is available. " + + "Please visit http://getfirebug.com/firebuglite#Install and update it." + ], Firebug.context, "warn"); + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + if (Firebug.Console) + Firebug.Console.flush(); + + if (Firebug.Trace) + FBTrace.flush(Firebug.Trace); + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + if (FBTrace.DBG_INITIALIZE) FBTrace.sysout("Firebug.chrome.initialize", "initializing chrome application"); + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // initialize inherited classes + Controller.initialize.call(this); + PanelBar.initialize.call(this); + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // create the interface elements cache + + fbTop = $("fbTop"); + fbContent = $("fbContent"); + fbContentStyle = fbContent.style; + fbBottom = $("fbBottom"); + fbBtnInspect = $("fbBtnInspect"); + + fbToolbar = $("fbToolbar"); + + fbPanelBox1 = $("fbPanelBox1"); + fbPanelBox1Style = fbPanelBox1.style; + fbPanelBox2 = $("fbPanelBox2"); + fbPanelBox2Style = fbPanelBox2.style; + fbPanelBar2Box = $("fbPanelBar2Box"); + fbPanelBar2BoxStyle = fbPanelBar2Box.style; + + fbHSplitter = $("fbHSplitter"); + fbVSplitter = $("fbVSplitter"); + fbVSplitterStyle = fbVSplitter.style; + + fbPanel1 = $("fbPanel1"); + fbPanel1Style = fbPanel1.style; + fbPanel2 = $("fbPanel2"); + fbPanel2Style = fbPanel2.style; + + fbConsole = $("fbConsole"); + fbConsoleStyle = fbConsole.style; + fbHTML = $("fbHTML"); + + fbCommandLine = $("fbCommandLine"); + fbLargeCommandLine = $("fbLargeCommandLine"); + fbLargeCommandButtons = $("fbLargeCommandButtons"); + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // static values cache + topHeight = fbTop.offsetHeight; + topPartialHeight = fbToolbar.offsetHeight; + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + disableTextSelection($("fbToolbar")); + disableTextSelection($("fbPanelBarBox")); + disableTextSelection($("fbPanelBar1")); + disableTextSelection($("fbPanelBar2")); + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Add the "javascript:void(0)" href attributes used to make the hover effect in IE6 + if (isIE6 && Firebug.Selector) + { + // TODO: xxxpedro change to getElementsByClass + var as = $$(".fbHover"); + for (var i=0, a; a=as[i]; i++) + { + a.setAttribute("href", "javascript:void(0)"); + } + } + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // initialize all panels + /* + var panelMap = Firebug.panelTypes; + for (var i=0, p; p=panelMap[i]; i++) + { + if (!p.parentPanel) + { + this.addPanel(p.prototype.name); + } + } + /**/ + + // ************************************************************************************************ + // ************************************************************************************************ + // ************************************************************************************************ + // ************************************************************************************************ + + if(Firebug.Inspector) + this.inspectButton.initialize(); + + // ************************************************************************************************ + // ************************************************************************************************ + // ************************************************************************************************ + // ************************************************************************************************ + + this.addController( + [$("fbLargeCommandLineIcon"), "click", this.showLargeCommandLine] + ); + + // ************************************************************************************************ + + // Select the first registered panel + // TODO: BUG IE7 + var self = this; + setTimeout(function(){ + self.selectPanel(Firebug.context.persistedState.selectedPanelName); + + if (Firebug.context.persistedState.selectedPanelName == "Console" && Firebug.CommandLine) + Firebug.chrome.focusCommandLine(); + },0); + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + //this.draw(); + + + + + + + + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + var onPanelMouseDown = function onPanelMouseDown(event) + { + //console.log("onPanelMouseDown", event.target || event.srcElement, event); + + var target = event.target || event.srcElement; + + if (FBL.isLeftClick(event)) + { + var editable = FBL.getAncestorByClass(target, "editable"); + + // if an editable element has been clicked then start editing + if (editable) + { + Firebug.Editor.startEditing(editable); + FBL.cancelEvent(event); + } + // if any other element has been clicked then stop editing + else + { + if (!hasClass(target, "textEditorInner")) + Firebug.Editor.stopEditing(); + } + } + else if (FBL.isMiddleClick(event) && Firebug.getRepNode(target)) + { + // Prevent auto-scroll when middle-clicking a rep object + FBL.cancelEvent(event); + } + }; + + Firebug.getElementPanel = function(element) + { + var panelNode = getAncestorByClass(element, "fbPanel"); + var id = panelNode.id.substr(2); + + var panel = Firebug.chrome.panelMap[id]; + + if (!panel) + { + if (Firebug.chrome.selectedPanel.sidePanelBar) + panel = Firebug.chrome.selectedPanel.sidePanelBar.panelMap[id]; + } + + return panel; + }; + + + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + // TODO: xxxpedro port to Firebug + + // Improved window key code event listener. Only one "keydown" event will be attached + // to the window, and the onKeyCodeListen() function will delegate which listeners + // should be called according to the event.keyCode fired. + var onKeyCodeListenersMap = []; + var onKeyCodeListen = function(event) + { + for (var keyCode in onKeyCodeListenersMap) + { + var listeners = onKeyCodeListenersMap[keyCode]; + + for (var i = 0, listener; listener = listeners[i]; i++) + { + var filter = listener.filter || FBL.noKeyModifiers; + + if (event.keyCode == keyCode && (!filter || filter(event))) + { + listener.listener(); + FBL.cancelEvent(event, true); + return false; + } + } + } + }; + + addEvent(Firebug.chrome.document, "keydown", onKeyCodeListen); + + /** + * @name keyCodeListen + * @memberOf FBL.FirebugChrome + */ + Firebug.chrome.keyCodeListen = function(key, filter, listener, capture) + { + var keyCode = KeyEvent["DOM_VK_"+key]; + + if (!onKeyCodeListenersMap[keyCode]) + onKeyCodeListenersMap[keyCode] = []; + + onKeyCodeListenersMap[keyCode].push({ + filter: filter, + listener: listener + }); + + return keyCode; + }; + + /** + * @name keyIgnore + * @memberOf FBL.FirebugChrome + */ + Firebug.chrome.keyIgnore = function(keyCode) + { + onKeyCodeListenersMap[keyCode] = null; + delete onKeyCodeListenersMap[keyCode]; + }; + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + /**/ + // move to shutdown + //removeEvent(Firebug.chrome.document, "keydown", listener[0]); + + + /* + Firebug.chrome.keyCodeListen = function(key, filter, listener, capture) + { + if (!filter) + filter = FBL.noKeyModifiers; + + var keyCode = KeyEvent["DOM_VK_"+key]; + + var fn = function fn(event) + { + if (event.keyCode == keyCode && (!filter || filter(event))) + { + listener(); + FBL.cancelEvent(event, true); + return false; + } + } + + addEvent(Firebug.chrome.document, "keydown", fn); + + return [fn, capture]; + }; + + Firebug.chrome.keyIgnore = function(listener) + { + removeEvent(Firebug.chrome.document, "keydown", listener[0]); + }; + /**/ + + + this.addController( + [fbPanel1, "mousedown", onPanelMouseDown], + [fbPanel2, "mousedown", onPanelMouseDown] + ); +/**/ + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + + // menus can be used without domplate + if (FBL.domplate) + this.testMenu(); + /**/ + + //test XHR + /* + setTimeout(function(){ + + FBL.Ajax.request({url: "../content/firebug/boot.js"}); + FBL.Ajax.request({url: "../content/firebug/boot.js.invalid"}); + + },1000); + /**/ + }, + + shutdown: function() + { + // ************************************************************************************************ + // ************************************************************************************************ + // ************************************************************************************************ + // ************************************************************************************************ + + if(Firebug.Inspector) + this.inspectButton.shutdown(); + + // ************************************************************************************************ + // ************************************************************************************************ + // ************************************************************************************************ + // ************************************************************************************************ + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + // remove disableTextSelection event handlers + restoreTextSelection($("fbToolbar")); + restoreTextSelection($("fbPanelBarBox")); + restoreTextSelection($("fbPanelBar1")); + restoreTextSelection($("fbPanelBar2")); + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // shutdown inherited classes + Controller.shutdown.call(this); + PanelBar.shutdown.call(this); + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Remove the interface elements cache (this must happen after calling + // the shutdown method of all dependent components to avoid errors) + + fbTop = null; + fbContent = null; + fbContentStyle = null; + fbBottom = null; + fbBtnInspect = null; + + fbToolbar = null; + + fbPanelBox1 = null; + fbPanelBox1Style = null; + fbPanelBox2 = null; + fbPanelBox2Style = null; + fbPanelBar2Box = null; + fbPanelBar2BoxStyle = null; + + fbHSplitter = null; + fbVSplitter = null; + fbVSplitterStyle = null; + + fbPanel1 = null; + fbPanel1Style = null; + fbPanel2 = null; + + fbConsole = null; + fbConsoleStyle = null; + fbHTML = null; + + fbCommandLine = null; + fbLargeCommandLine = null; + fbLargeCommandButtons = null; + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // static values cache + + topHeight = null; + topPartialHeight = null; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + toggle: function(forceOpen, popup) + { + if(popup) + { + this.detach(); + } + else + { + if (isOpera && Firebug.chrome.type == "popup" && Firebug.chrome.node.closed) + { + var frame = FirebugChrome.chromeMap.frame; + frame.reattach(); + + FirebugChrome.chromeMap.popup = null; + + frame.open(); + + return; + } + + // If the context is a popup, ignores the toggle process + if (Firebug.chrome.type == "popup") return; + + var shouldOpen = forceOpen || !Firebug.context.persistedState.isOpen; + + if(shouldOpen) + this.open(); + else + this.close(); + } + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + detach: function() + { + if(!FirebugChrome.chromeMap.popup) + { + this.close(); + createChromeWindow({type: "popup"}); + } + }, + + reattach: function(oldChrome, newChrome) + { + Firebug.browser.window.Firebug = Firebug; + + // chrome synchronization + var newPanelMap = newChrome.panelMap; + var oldPanelMap = oldChrome.panelMap; + + var panel; + for(var name in newPanelMap) + { + // TODO: xxxpedro innerHTML + panel = newPanelMap[name]; + if (panel.options.innerHTMLSync) + panel.panelNode.innerHTML = oldPanelMap[name].panelNode.innerHTML; + } + + Firebug.chrome = newChrome; + + // TODO: xxxpedro sync detach reattach attach + //dispatch(Firebug.chrome.panelMap, "detach", [oldChrome, newChrome]); + + if (newChrome.type == "popup") + { + newChrome.initialize(); + //dispatch(Firebug.modules, "initialize", []); + } + else + { + // TODO: xxxpedro only needed in persistent + // should use FirebugChrome.clone, but popup FBChrome + // isn't acessible + Firebug.context.persistedState.selectedPanelName = oldChrome.selectedPanel.name; + } + + dispatch(newPanelMap, "reattach", [oldChrome, newChrome]); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + draw: function() + { + var size = this.getSize(); + + // Height related values + var commandLineHeight = Firebug.chrome.commandLineVisible ? fbCommandLine.offsetHeight : 0, + + y = Math.max(size.height /* chrome height */, topHeight), + + heightValue = Math.max(y - topHeight - commandLineHeight /* fixed height */, 0), + + height = heightValue + "px", + + // Width related values + sideWidthValue = Firebug.chrome.sidePanelVisible ? Firebug.context.persistedState.sidePanelWidth : 0, + + width = Math.max(size.width /* chrome width */ - sideWidthValue, 0) + "px"; + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Height related rendering + fbPanelBox1Style.height = height; + fbPanel1Style.height = height; + + if (isIE || isOpera) + { + // Fix IE and Opera problems with auto resizing the verticall splitter + fbVSplitterStyle.height = Math.max(y - topPartialHeight - commandLineHeight, 0) + "px"; + } + //xxxpedro FF2 only? + /* + else if (isFirefox) + { + // Fix Firefox problem with table rows with 100% height (fit height) + fbContentStyle.maxHeight = Math.max(y - fixedHeight, 0)+ "px"; + }/**/ + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Width related rendering + fbPanelBox1Style.width = width; + fbPanel1Style.width = width; + + // SidePanel rendering + if (Firebug.chrome.sidePanelVisible) + { + sideWidthValue = Math.max(sideWidthValue - 6, 0); + + var sideWidth = sideWidthValue + "px"; + + fbPanelBox2Style.width = sideWidth; + + fbVSplitterStyle.right = sideWidth; + + if (Firebug.chrome.largeCommandLineVisible) + { + fbLargeCommandLine = $("fbLargeCommandLine"); + + fbLargeCommandLine.style.height = heightValue - 4 + "px"; + fbLargeCommandLine.style.width = sideWidthValue - 2 + "px"; + + fbLargeCommandButtons = $("fbLargeCommandButtons"); + fbLargeCommandButtons.style.width = sideWidth; + } + else + { + fbPanel2Style.height = height; + fbPanel2Style.width = sideWidth; + + fbPanelBar2BoxStyle.width = sideWidth; + } + } + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + getSize: function() + { + return this.type == "div" ? + { + height: this.node.offsetHeight, + width: this.node.offsetWidth + } + : + this.getWindowSize(); + }, + + resize: function() + { + var self = this; + + // avoid partial resize when maximizing window + setTimeout(function(){ + self.draw(); + + if (noFixedPosition && (self.type == "frame" || self.type == "div")) + self.fixIEPosition(); + }, 0); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + layout: function(panel) + { + if (FBTrace.DBG_CHROME) FBTrace.sysout("Chrome.layout", ""); + + var options = panel.options; + + changeCommandLineVisibility(options.hasCommandLine); + changeSidePanelVisibility(panel.hasSidePanel); + + Firebug.chrome.draw(); + }, + + showLargeCommandLine: function(hideToggleIcon) + { + var chrome = Firebug.chrome; + + if (!chrome.largeCommandLineVisible) + { + chrome.largeCommandLineVisible = true; + + if (chrome.selectedPanel.options.hasCommandLine) + { + if (Firebug.CommandLine) + Firebug.CommandLine.blur(); + + changeCommandLineVisibility(false); + } + + changeSidePanelVisibility(true); + + fbLargeCommandLine.style.display = "block"; + fbLargeCommandButtons.style.display = "block"; + + fbPanel2Style.display = "none"; + fbPanelBar2BoxStyle.display = "none"; + + chrome.draw(); + + fbLargeCommandLine.focus(); + + if (Firebug.CommandLine) + Firebug.CommandLine.setMultiLine(true); + } + }, + + hideLargeCommandLine: function() + { + if (Firebug.chrome.largeCommandLineVisible) + { + Firebug.chrome.largeCommandLineVisible = false; + + if (Firebug.CommandLine) + Firebug.CommandLine.setMultiLine(false); + + fbLargeCommandLine.blur(); + + fbPanel2Style.display = "block"; + fbPanelBar2BoxStyle.display = "block"; + + fbLargeCommandLine.style.display = "none"; + fbLargeCommandButtons.style.display = "none"; + + changeSidePanelVisibility(false); + + if (Firebug.chrome.selectedPanel.options.hasCommandLine) + changeCommandLineVisibility(true); + + Firebug.chrome.draw(); + + } + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + focusCommandLine: function() + { + var selectedPanelName = this.selectedPanel.name, panelToSelect; + + if (focusCommandLineState == 0 || selectedPanelName != "Console") + { + focusCommandLineState = 0; + lastFocusedPanelName = selectedPanelName; + + panelToSelect = "Console"; + } + if (focusCommandLineState == 1) + { + panelToSelect = lastFocusedPanelName; + } + + this.selectPanel(panelToSelect); + + try + { + if (Firebug.CommandLine) + { + if (panelToSelect == "Console") + Firebug.CommandLine.focus(); + else + Firebug.CommandLine.blur(); + } + } + catch(e) + { + //TODO: xxxpedro trace error + } + + focusCommandLineState = ++focusCommandLineState % 2; + } + +}); + +// ************************************************************************************************ +// ChromeFrameBase + +/** + * @namespace + * @extends ns-chrome-ChromeBase + */ +var ChromeFrameBase = extend(ChromeBase, +/**@extend ns-chrome-ChromeFrameBase*/ +{ + create: function() + { + ChromeBase.create.call(this); + + // restore display for the anti-flicker trick + if (isFirefox) + this.node.style.display = "block"; + + if (Env.Options.startInNewWindow) + { + this.close(); + this.toggle(true, true); + return; + } + + if (Env.Options.startOpened) + this.open(); + else + this.close(); + }, + + destroy: function() + { + var size = Firebug.chrome.getWindowSize(); + + Firebug.context.persistedState.height = size.height; + + if (Firebug.saveCookies) + Firebug.savePrefs(); + + removeGlobalEvent("keydown", onGlobalKeyDown); + + ChromeBase.destroy.call(this); + + this.document = null; + delete this.document; + + this.window = null; + delete this.window; + + this.node.parentNode.removeChild(this.node); + this.node = null; + delete this.node; + }, + + initialize: function() + { + //FBTrace.sysout("Frame", "initialize();") + ChromeBase.initialize.call(this); + + this.addController( + [Firebug.browser.window, "resize", this.resize], + [$("fbWindow_btClose"), "click", this.close], + [$("fbWindow_btDetach"), "click", this.detach], + [$("fbWindow_btDeactivate"), "click", this.deactivate] + ); + + if (!Env.Options.enablePersistent) + this.addController([Firebug.browser.window, "unload", Firebug.shutdown]); + + if (noFixedPosition) + { + this.addController( + [Firebug.browser.window, "scroll", this.fixIEPosition] + ); + } + + fbVSplitter.onmousedown = onVSplitterMouseDown; + fbHSplitter.onmousedown = onHSplitterMouseDown; + + this.isInitialized = true; + }, + + shutdown: function() + { + fbVSplitter.onmousedown = null; + fbHSplitter.onmousedown = null; + + ChromeBase.shutdown.apply(this); + + this.isInitialized = false; + }, + + reattach: function() + { + var frame = FirebugChrome.chromeMap.frame; + + ChromeBase.reattach(FirebugChrome.chromeMap.popup, this); + }, + + open: function() + { + if (!Firebug.context.persistedState.isOpen) + { + Firebug.context.persistedState.isOpen = true; + + if (Env.isChromeExtension) + localStorage.setItem("Firebug", "1,1"); + + var node = this.node; + + node.style.visibility = "hidden"; // Avoid flickering + + if (Firebug.showIconWhenHidden) + { + if (ChromeMini.isInitialized) + { + ChromeMini.shutdown(); + } + + } + else + node.style.display = "block"; + + var main = $("fbChrome"); + + // IE6 throws an error when setting this property! why? + //main.style.display = "table"; + main.style.display = ""; + + var self = this; + /// TODO: xxxpedro FOUC + node.style.visibility = "visible"; + setTimeout(function(){ + ///node.style.visibility = "visible"; + + //dispatch(Firebug.modules, "initialize", []); + self.initialize(); + + if (noFixedPosition) + self.fixIEPosition(); + + self.draw(); + + }, 10); + } + }, + + close: function() + { + if (Firebug.context.persistedState.isOpen) + { + if (this.isInitialized) + { + //dispatch(Firebug.modules, "shutdown", []); + this.shutdown(); + } + + Firebug.context.persistedState.isOpen = false; + + if (Env.isChromeExtension) + localStorage.setItem("Firebug", "1,0"); + + var node = this.node; + + if (Firebug.showIconWhenHidden) + { + node.style.visibility = "hidden"; // Avoid flickering + + // TODO: xxxpedro - persist IE fixed? + var main = $("fbChrome", FirebugChrome.chromeMap.frame.document); + main.style.display = "none"; + + ChromeMini.initialize(); + + node.style.visibility = "visible"; + } + else + node.style.display = "none"; + } + }, + + deactivate: function() + { + // if it is running as a Chrome extension, dispatch a message to the extension signaling + // that Firebug should be deactivated for the current tab + if (Env.isChromeExtension) + { + localStorage.removeItem("Firebug"); + Firebug.GoogleChrome.dispatch("FB_deactivate"); + + // xxxpedro problem here regarding Chrome extension. We can't deactivate the whole + // app, otherwise it won't be able to be reactivated without reloading the page. + // but we need to stop listening global keys, otherwise the key activation won't work. + Firebug.chrome.close(); + } + else + { + Firebug.shutdown(); + } + }, + + fixIEPosition: function() + { + // fix IE problem with offset when not in fullscreen mode + var doc = this.document; + var offset = isIE ? doc.body.clientTop || doc.documentElement.clientTop: 0; + + var size = Firebug.browser.getWindowSize(); + var scroll = Firebug.browser.getWindowScrollPosition(); + var maxHeight = size.height; + var height = this.node.offsetHeight; + + var bodyStyle = doc.body.currentStyle; + + this.node.style.top = maxHeight - height + scroll.top + "px"; + + if ((this.type == "frame" || this.type == "div") && + (bodyStyle.marginLeft || bodyStyle.marginRight)) + { + this.node.style.width = size.width + "px"; + } + + if (fbVSplitterStyle) + fbVSplitterStyle.right = Firebug.context.persistedState.sidePanelWidth + "px"; + + this.draw(); + } + +}); + + +// ************************************************************************************************ +// ChromeMini + +/** + * @namespace + * @extends FBL.Controller + */ +var ChromeMini = extend(Controller, +/**@extend ns-chrome-ChromeMini*/ +{ + create: function(chrome) + { + append(this, chrome); + this.type = "mini"; + }, + + initialize: function() + { + Controller.initialize.apply(this); + + var doc = FirebugChrome.chromeMap.frame.document; + + var mini = $("fbMiniChrome", doc); + mini.style.display = "block"; + + var miniIcon = $("fbMiniIcon", doc); + var width = miniIcon.offsetWidth + 10; + miniIcon.title = "Open " + Firebug.version; + + var errors = $("fbMiniErrors", doc); + if (errors.offsetWidth) + width += errors.offsetWidth + 10; + + var node = this.node; + node.style.height = "27px"; + node.style.width = width + "px"; + node.style.left = ""; + node.style.right = 0; + + if (this.node.nodeName.toLowerCase() == "iframe") + { + node.setAttribute("allowTransparency", "true"); + this.document.body.style.backgroundColor = "transparent"; + } + else + node.style.background = "transparent"; + + if (noFixedPosition) + this.fixIEPosition(); + + this.addController( + [$("fbMiniIcon", doc), "click", onMiniIconClick] + ); + + if (noFixedPosition) + { + this.addController( + [Firebug.browser.window, "scroll", this.fixIEPosition] + ); + } + + this.isInitialized = true; + }, + + shutdown: function() + { + var node = this.node; + node.style.height = Firebug.context.persistedState.height + "px"; + node.style.width = "100%"; + node.style.left = 0; + node.style.right = ""; + + if (this.node.nodeName.toLowerCase() == "iframe") + { + node.setAttribute("allowTransparency", "false"); + this.document.body.style.backgroundColor = "#fff"; + } + else + node.style.background = "#fff"; + + if (noFixedPosition) + this.fixIEPosition(); + + var doc = FirebugChrome.chromeMap.frame.document; + + var mini = $("fbMiniChrome", doc); + mini.style.display = "none"; + + Controller.shutdown.apply(this); + + this.isInitialized = false; + }, + + draw: function() + { + + }, + + fixIEPosition: ChromeFrameBase.fixIEPosition + +}); + + +// ************************************************************************************************ +// ChromePopupBase + +/** + * @namespace + * @extends ns-chrome-ChromeBase + */ +var ChromePopupBase = extend(ChromeBase, +/**@extend ns-chrome-ChromePopupBase*/ +{ + + initialize: function() + { + setClass(this.document.body, "FirebugPopup"); + + ChromeBase.initialize.call(this); + + this.addController( + [Firebug.chrome.window, "resize", this.resize], + [Firebug.chrome.window, "unload", this.destroy] + //[Firebug.chrome.window, "beforeunload", this.destroy] + ); + + if (Env.Options.enablePersistent) + { + this.persist = bind(this.persist, this); + addEvent(Firebug.browser.window, "unload", this.persist); + } + else + this.addController( + [Firebug.browser.window, "unload", this.close] + ); + + fbVSplitter.onmousedown = onVSplitterMouseDown; + }, + + destroy: function() + { + var chromeWin = Firebug.chrome.window; + var left = chromeWin.screenX || chromeWin.screenLeft; + var top = chromeWin.screenY || chromeWin.screenTop; + var size = Firebug.chrome.getWindowSize(); + + Firebug.context.persistedState.popupTop = top; + Firebug.context.persistedState.popupLeft = left; + Firebug.context.persistedState.popupWidth = size.width; + Firebug.context.persistedState.popupHeight = size.height; + + if (Firebug.saveCookies) + Firebug.savePrefs(); + + // TODO: xxxpedro sync detach reattach attach + var frame = FirebugChrome.chromeMap.frame; + + if(frame) + { + dispatch(frame.panelMap, "detach", [this, frame]); + + frame.reattach(this, frame); + } + + if (Env.Options.enablePersistent) + { + removeEvent(Firebug.browser.window, "unload", this.persist); + } + + ChromeBase.destroy.apply(this); + + FirebugChrome.chromeMap.popup = null; + + this.node.close(); + }, + + persist: function() + { + persistTimeStart = new Date().getTime(); + + removeEvent(Firebug.browser.window, "unload", this.persist); + + Firebug.Inspector.destroy(); + Firebug.browser.window.FirebugOldBrowser = true; + + var persistTimeStart = new Date().getTime(); + + var waitMainWindow = function() + { + var doc, head; + + try + { + if (window.opener && !window.opener.FirebugOldBrowser && (doc = window.opener.document)/* && + doc.documentElement && (head = doc.documentElement.firstChild)*/) + { + + try + { + // exposes the FBL to the global namespace when in debug mode + if (Env.isDebugMode) + { + window.FBL = FBL; + } + + window.Firebug = Firebug; + window.opener.Firebug = Firebug; + + Env.browser = window.opener; + Firebug.browser = Firebug.context = new Context(Env.browser); + Firebug.loadPrefs(); + + registerConsole(); + + // the delay time should be calculated right after registering the + // console, once right after the console registration, call log messages + // will be properly handled + var persistDelay = new Date().getTime() - persistTimeStart; + + var chrome = Firebug.chrome; + addEvent(Firebug.browser.window, "unload", chrome.persist); + + FBL.cacheDocument(); + Firebug.Inspector.create(); + + Firebug.Console.logFormatted( + ["Firebug could not capture console calls during " + + persistDelay + "ms"], + Firebug.context, + "info" + ); + + setTimeout(function(){ + var htmlPanel = chrome.getPanel("HTML"); + htmlPanel.createUI(); + },50); + + } + catch(pE) + { + alert("persist error: " + (pE.message || pE)); + } + + } + else + { + window.setTimeout(waitMainWindow, 0); + } + + } catch (E) { + window.close(); + } + }; + + waitMainWindow(); + }, + + close: function() + { + this.destroy(); + } + +}); + + +//************************************************************************************************ +// UI helpers + +var changeCommandLineVisibility = function changeCommandLineVisibility(visibility) +{ + var last = Firebug.chrome.commandLineVisible; + var visible = Firebug.chrome.commandLineVisible = + typeof visibility == "boolean" ? visibility : !Firebug.chrome.commandLineVisible; + + if (visible != last) + { + if (visible) + { + fbBottom.className = ""; + + if (Firebug.CommandLine) + Firebug.CommandLine.activate(); + } + else + { + if (Firebug.CommandLine) + Firebug.CommandLine.deactivate(); + + fbBottom.className = "hide"; + } + } +}; + +var changeSidePanelVisibility = function changeSidePanelVisibility(visibility) +{ + var last = Firebug.chrome.sidePanelVisible; + Firebug.chrome.sidePanelVisible = + typeof visibility == "boolean" ? visibility : !Firebug.chrome.sidePanelVisible; + + if (Firebug.chrome.sidePanelVisible != last) + { + fbPanelBox2.className = Firebug.chrome.sidePanelVisible ? "" : "hide"; + fbPanelBar2Box.className = Firebug.chrome.sidePanelVisible ? "" : "hide"; + } +}; + + +// ************************************************************************************************ +// F12 Handler + +var onGlobalKeyDown = function onGlobalKeyDown(event) +{ + var keyCode = event.keyCode; + var shiftKey = event.shiftKey; + var ctrlKey = event.ctrlKey; + + if (keyCode == 123 /* F12 */ && (!isFirefox && !shiftKey || shiftKey && isFirefox)) + { + Firebug.chrome.toggle(false, ctrlKey); + cancelEvent(event, true); + + // TODO: xxxpedro replace with a better solution. we're doing this + // to allow reactivating with the F12 key after being deactivated + if (Env.isChromeExtension) + { + Firebug.GoogleChrome.dispatch("FB_enableIcon"); + } + } + else if (keyCode == 67 /* C */ && ctrlKey && shiftKey) + { + Firebug.Inspector.toggleInspect(); + cancelEvent(event, true); + } + else if (keyCode == 76 /* L */ && ctrlKey && shiftKey) + { + Firebug.chrome.focusCommandLine(); + cancelEvent(event, true); + } +}; + +var onMiniIconClick = function onMiniIconClick(event) +{ + Firebug.chrome.toggle(false, event.ctrlKey); + cancelEvent(event, true); +}; + + +// ************************************************************************************************ +// Horizontal Splitter Handling + +var onHSplitterMouseDown = function onHSplitterMouseDown(event) +{ + addGlobalEvent("mousemove", onHSplitterMouseMove); + addGlobalEvent("mouseup", onHSplitterMouseUp); + + if (isIE) + addEvent(Firebug.browser.document.documentElement, "mouseleave", onHSplitterMouseUp); + + fbHSplitter.className = "fbOnMovingHSplitter"; + + return false; +}; + +var onHSplitterMouseMove = function onHSplitterMouseMove(event) +{ + cancelEvent(event, true); + + var clientY = event.clientY; + var win = isIE + ? event.srcElement.ownerDocument.parentWindow + : event.target.defaultView || event.target.ownerDocument && event.target.ownerDocument.defaultView; + + if (!win) + return; + + if (win != win.parent) + { + var frameElement = win.frameElement; + if (frameElement) + { + var framePos = Firebug.browser.getElementPosition(frameElement).top; + clientY += framePos; + + if (frameElement.style.position != "fixed") + clientY -= Firebug.browser.getWindowScrollPosition().top; + } + } + + if (isOpera && isQuiksMode && win.frameElement.id == "FirebugUI") + { + clientY = Firebug.browser.getWindowSize().height - win.frameElement.offsetHeight + clientY; + } + + /* + console.log( + typeof win.FBL != "undefined" ? "no-Chrome" : "Chrome", + //win.frameElement.id, + event.target, + clientY + );/**/ + + onHSplitterMouseMoveBuffer = clientY; // buffer + + if (new Date().getTime() - lastHSplitterMouseMove > chromeRedrawSkipRate) // frame skipping + { + lastHSplitterMouseMove = new Date().getTime(); + handleHSplitterMouseMove(); + } + else + if (!onHSplitterMouseMoveTimer) + onHSplitterMouseMoveTimer = setTimeout(handleHSplitterMouseMove, chromeRedrawSkipRate); + + // improving the resizing performance by canceling the mouse event. + // canceling events will prevent the page to receive such events, which would imply + // in more processing being expended. + cancelEvent(event, true); + return false; +}; + +var handleHSplitterMouseMove = function() +{ + if (onHSplitterMouseMoveTimer) + { + clearTimeout(onHSplitterMouseMoveTimer); + onHSplitterMouseMoveTimer = null; + } + + var clientY = onHSplitterMouseMoveBuffer; + + var windowSize = Firebug.browser.getWindowSize(); + var scrollSize = Firebug.browser.getWindowScrollSize(); + + // compute chrome fixed size (top bar and command line) + var commandLineHeight = Firebug.chrome.commandLineVisible ? fbCommandLine.offsetHeight : 0; + var fixedHeight = topHeight + commandLineHeight; + var chromeNode = Firebug.chrome.node; + + var scrollbarSize = !isIE && (scrollSize.width > windowSize.width) ? 17 : 0; + + //var height = !isOpera ? chromeNode.offsetTop + chromeNode.clientHeight : windowSize.height; + var height = windowSize.height; + + // compute the min and max size of the chrome + var chromeHeight = Math.max(height - clientY + 5 - scrollbarSize, fixedHeight); + chromeHeight = Math.min(chromeHeight, windowSize.height - scrollbarSize); + + Firebug.context.persistedState.height = chromeHeight; + chromeNode.style.height = chromeHeight + "px"; + + if (noFixedPosition) + Firebug.chrome.fixIEPosition(); + + Firebug.chrome.draw(); +}; + +var onHSplitterMouseUp = function onHSplitterMouseUp(event) +{ + removeGlobalEvent("mousemove", onHSplitterMouseMove); + removeGlobalEvent("mouseup", onHSplitterMouseUp); + + if (isIE) + removeEvent(Firebug.browser.document.documentElement, "mouseleave", onHSplitterMouseUp); + + fbHSplitter.className = ""; + + Firebug.chrome.draw(); + + // avoid text selection in IE when returning to the document + // after the mouse leaves the document during the resizing + return false; +}; + + +// ************************************************************************************************ +// Vertical Splitter Handling + +var onVSplitterMouseDown = function onVSplitterMouseDown(event) +{ + addGlobalEvent("mousemove", onVSplitterMouseMove); + addGlobalEvent("mouseup", onVSplitterMouseUp); + + return false; +}; + +var onVSplitterMouseMove = function onVSplitterMouseMove(event) +{ + if (new Date().getTime() - lastVSplitterMouseMove > chromeRedrawSkipRate) // frame skipping + { + var target = event.target || event.srcElement; + if (target && target.ownerDocument) // avoid error when cursor reaches out of the chrome + { + var clientX = event.clientX; + var win = document.all + ? event.srcElement.ownerDocument.parentWindow + : event.target.ownerDocument.defaultView; + + if (win != win.parent) + clientX += win.frameElement ? win.frameElement.offsetLeft : 0; + + var size = Firebug.chrome.getSize(); + var x = Math.max(size.width - clientX + 3, 6); + + Firebug.context.persistedState.sidePanelWidth = x; + Firebug.chrome.draw(); + } + + lastVSplitterMouseMove = new Date().getTime(); + } + + cancelEvent(event, true); + return false; +}; + +var onVSplitterMouseUp = function onVSplitterMouseUp(event) +{ + removeGlobalEvent("mousemove", onVSplitterMouseMove); + removeGlobalEvent("mouseup", onVSplitterMouseUp); + + Firebug.chrome.draw(); +}; + + +// ************************************************************************************************ +}}); + +/* See license.txt for terms of usage */ + +FBL.ns(function() { with (FBL) { +// ************************************************************************************************ + +Firebug.Lite = +{ +}; + +// ************************************************************************************************ +}}); + + +/* See license.txt for terms of usage */ + +FBL.ns(function() { with (FBL) { +// ************************************************************************************************ + +Firebug.Lite.Cache = +{ + ID: "firebug-" + new Date().getTime() +}; + +// ************************************************************************************************ + +/** + * TODO: if a cached element is cloned, the expando property will be cloned too in IE + * which will result in a bug. Firebug Lite will think the new cloned node is the old + * one. + * + * TODO: Investigate a possibility of cache validation, to be customized by each + * kind of cache. For ElementCache it should validate if the element still is + * inserted at the DOM. + */ +var cacheUID = 0; +var createCache = function() +{ + var map = {}; + var data = {}; + + var CID = Firebug.Lite.Cache.ID; + + // better detection + var supportsDeleteExpando = !document.all; + + var cacheFunction = function(element) + { + return cacheAPI.set(element); + }; + + var cacheAPI = + { + get: function(key) + { + return map.hasOwnProperty(key) ? + map[key] : + null; + }, + + set: function(element) + { + var id = getValidatedKey(element); + + if (!id) + { + id = ++cacheUID; + element[CID] = id; + } + + if (!map.hasOwnProperty(id)) + { + map[id] = element; + data[id] = {}; + } + + return id; + }, + + unset: function(element) + { + var id = getValidatedKey(element); + + if (!id) return; + + if (supportsDeleteExpando) + { + delete element[CID]; + } + else if (element.removeAttribute) + { + element.removeAttribute(CID); + } + + delete map[id]; + delete data[id]; + + }, + + key: function(element) + { + return getValidatedKey(element); + }, + + has: function(element) + { + var id = getValidatedKey(element); + return id && map.hasOwnProperty(id); + }, + + each: function(callback) + { + for (var key in map) + { + if (map.hasOwnProperty(key)) + { + callback(key, map[key]); + } + } + }, + + data: function(element, name, value) + { + // set data + if (value) + { + if (!name) return null; + + var id = cacheAPI.set(element); + + return data[id][name] = value; + } + // get data + else + { + var id = cacheAPI.key(element); + + return data.hasOwnProperty(id) && data[id].hasOwnProperty(name) ? + data[id][name] : + null; + } + }, + + clear: function() + { + for (var id in map) + { + var element = map[id]; + cacheAPI.unset(element); + } + } + }; + + var getValidatedKey = function(element) + { + var id = element[CID]; + + // If a cached element is cloned in IE, the expando property CID will be also + // cloned (differently than other browsers) resulting in a bug: Firebug Lite + // will think the new cloned node is the old one. To prevent this problem we're + // checking if the cached element matches the given element. + if ( + !supportsDeleteExpando && // the problem happens when supportsDeleteExpando is false + id && // the element has the expando property + map.hasOwnProperty(id) && // there is a cached element with the same id + map[id] != element // but it is a different element than the current one + ) + { + // remove the problematic property + element.removeAttribute(CID); + + id = null; + } + + return id; + }; + + FBL.append(cacheFunction, cacheAPI); + + return cacheFunction; +}; + +// ************************************************************************************************ + +// TODO: xxxpedro : check if we need really this on FBL scope +Firebug.Lite.Cache.StyleSheet = createCache(); +Firebug.Lite.Cache.Element = createCache(); + +// TODO: xxxpedro +Firebug.Lite.Cache.Event = createCache(); + + +// ************************************************************************************************ +}}); + + +/* See license.txt for terms of usage */ + +FBL.ns(function() { with (FBL) { +// ************************************************************************************************ + +// ************************************************************************************************ +var sourceMap = {}; + +// ************************************************************************************************ +Firebug.Lite.Proxy = +{ + // jsonp callbacks + _callbacks: {}, + + /** + * Load a resource, either locally (directly) or externally (via proxy) using + * synchronous XHR calls. Loading external resources requires the proxy plugin to + * be installed and configured (see /plugin/proxy/proxy.php). + */ + load: function(url) + { + var resourceDomain = getDomain(url); + var isLocalResource = + // empty domain means local URL + !resourceDomain || + // same domain means local too + resourceDomain == Firebug.context.window.location.host; // TODO: xxxpedro context + + return isLocalResource ? fetchResource(url) : fetchProxyResource(url); + }, + + /** + * Load a resource using JSONP technique. + */ + loadJSONP: function(url, callback) + { + var script = createGlobalElement("script"), + doc = Firebug.context.document, + + uid = "" + new Date().getTime(), + callbackName = "callback=Firebug.Lite.Proxy._callbacks." + uid, + + jsonpURL = url.indexOf("?") != -1 ? + url + "&" + callbackName : + url + "?" + callbackName; + + Firebug.Lite.Proxy._callbacks[uid] = function(data) + { + if (callback) + callback(data); + + script.parentNode.removeChild(script); + delete Firebug.Lite.Proxy._callbacks[uid]; + }; + + script.src = jsonpURL; + + if (doc.documentElement) + doc.documentElement.appendChild(script); + }, + + /** + * Load a resource using YQL (not reliable). + */ + YQL: function(url, callback) + { + var yql = "http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20html%20where%20url%3D%22" + + encodeURIComponent(url) + "%22&format=xml"; + + this.loadJSONP(yql, function(data) + { + var source = data.results[0]; + + // clean up YQL bogus elements + var match = /<body>\s+<p>([\s\S]+)<\/p>\s+<\/body>$/.exec(source); + if (match) + source = match[1]; + + console.log(source); + }); + } +}; + +// ************************************************************************************************ + +Firebug.Lite.Proxy.fetchResourceDisabledMessage = + "/* Firebug Lite resource fetching is disabled.\n" + + "To enabled it set the Firebug Lite option \"disableResourceFetching\" to \"false\".\n" + + "More info at http://getfirebug.com/firebuglite#Options */"; + +var fetchResource = function(url) +{ + if (Firebug.disableResourceFetching) + { + var source = sourceMap[url] = Firebug.Lite.Proxy.fetchResourceDisabledMessage; + return source; + } + + if (sourceMap.hasOwnProperty(url)) + return sourceMap[url]; + + // Getting the native XHR object so our calls won't be logged in the Console Panel + var xhr = FBL.getNativeXHRObject(); + xhr.open("get", url, false); + xhr.send(); + + var source = sourceMap[url] = xhr.responseText; + return source; +}; + +var fetchProxyResource = function(url) +{ + if (sourceMap.hasOwnProperty(url)) + return sourceMap[url]; + + var proxyURL = Env.Location.baseDir + "plugin/proxy/proxy.php?url=" + encodeURIComponent(url); + var response = fetchResource(proxyURL); + + try + { + var data = eval("(" + response + ")"); + } + catch(E) + { + return "ERROR: Firebug Lite Proxy plugin returned an invalid response."; + } + + var source = data ? data.contents : ""; + return source; +}; + + +// ************************************************************************************************ +}}); + + +/* See license.txt for terms of usage */ + +FBL.ns(function() { with (FBL) { +// ************************************************************************************************ + +Firebug.Lite.Style = +{ +}; + +// ************************************************************************************************ +}}); + + +/* See license.txt for terms of usage */ + +FBL.ns(function() { with (FBL) { +// ************************************************************************************************ + +Firebug.Lite.Script = function(window) +{ + this.fileName = null; + this.isValid = null; + this.baseLineNumber = null; + this.lineExtent = null; + this.tag = null; + + this.functionName = null; + this.functionSource = null; +}; + +Firebug.Lite.Script.prototype = +{ + isLineExecutable: function(){}, + pcToLine: function(){}, + lineToPc: function(){}, + + toString: function() + { + return "Firebug.Lite.Script"; + } +}; + +// ************************************************************************************************ +}}); + + +/* See license.txt for terms of usage */ + +FBL.ns(function() { with (FBL) { +// ************************************************************************************************ + + +Firebug.Lite.Browser = function(window) +{ + this.contentWindow = window; + this.contentDocument = window.document; + this.currentURI = + { + spec: window.location.href + }; +}; + +Firebug.Lite.Browser.prototype = +{ + toString: function() + { + return "Firebug.Lite.Browser"; + } +}; + + +// ************************************************************************************************ +}}); + + +/* See license.txt for terms of usage */ + +/* + http://www.JSON.org/json2.js + 2010-03-20 + + Public Domain. + + NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. + + See http://www.JSON.org/js.html + + + This code should be minified before deployment. + See http://javascript.crockford.com/jsmin.html + + USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO + NOT CONTROL. + + + This file creates a global JSON object containing two methods: stringify + and parse. + + JSON.stringify(value, replacer, space) + value any JavaScript value, usually an object or array. + + replacer an optional parameter that determines how object + values are stringified for objects. It can be a + function or an array of strings. + + space an optional parameter that specifies the indentation + of nested structures. If it is omitted, the text will + be packed without extra whitespace. If it is a number, + it will specify the number of spaces to indent at each + level. If it is a string (such as '\t' or ' '), + it contains the characters used to indent at each level. + + This method produces a JSON text from a JavaScript value. + + When an object value is found, if the object contains a toJSON + method, its toJSON method will be called and the result will be + stringified. A toJSON method does not serialize: it returns the + value represented by the name/value pair that should be serialized, + or undefined if nothing should be serialized. The toJSON method + will be passed the key associated with the value, and this will be + bound to the value + + For example, this would serialize Dates as ISO strings. + + Date.prototype.toJSON = function (key) { + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } + + return this.getUTCFullYear() + '-' + + f(this.getUTCMonth() + 1) + '-' + + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z'; + }; + + You can provide an optional replacer method. It will be passed the + key and value of each member, with this bound to the containing + object. The value that is returned from your method will be + serialized. If your method returns undefined, then the member will + be excluded from the serialization. + + If the replacer parameter is an array of strings, then it will be + used to select the members to be serialized. It filters the results + such that only members with keys listed in the replacer array are + stringified. + + Values that do not have JSON representations, such as undefined or + functions, will not be serialized. Such values in objects will be + dropped; in arrays they will be replaced with null. You can use + a replacer function to replace those with JSON values. + JSON.stringify(undefined) returns undefined. + + The optional space parameter produces a stringification of the + value that is filled with line breaks and indentation to make it + easier to read. + + If the space parameter is a non-empty string, then that string will + be used for indentation. If the space parameter is a number, then + the indentation will be that many spaces. + + Example: + + text = JSON.stringify(['e', {pluribus: 'unum'}]); + // text is '["e",{"pluribus":"unum"}]' + + + text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); + // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' + + text = JSON.stringify([new Date()], function (key, value) { + return this[key] instanceof Date ? + 'Date(' + this[key] + ')' : value; + }); + // text is '["Date(---current time---)"]' + + + JSON.parse(text, reviver) + This method parses a JSON text to produce an object or array. + It can throw a SyntaxError exception. + + The optional reviver parameter is a function that can filter and + transform the results. It receives each of the keys and values, + and its return value is used instead of the original value. + If it returns what it received, then the structure is not modified. + If it returns undefined then the member is deleted. + + Example: + + // Parse the text. Values that look like ISO date strings will + // be converted to Date objects. + + myData = JSON.parse(text, function (key, value) { + var a; + if (typeof value === 'string') { + a = +/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); + if (a) { + return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], + +a[5], +a[6])); + } + } + return value; + }); + + myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { + var d; + if (typeof value === 'string' && + value.slice(0, 5) === 'Date(' && + value.slice(-1) === ')') { + d = new Date(value.slice(5, -1)); + if (d) { + return d; + } + } + return value; + }); + + + This is a reference implementation. You are free to copy, modify, or + redistribute. +*/ + +/*jslint evil: true, strict: false */ + +/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, + call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, + getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, + lastIndex, length, parse, prototype, push, replace, slice, stringify, + test, toJSON, toString, valueOf +*/ + + +// Create a JSON object only if one does not already exist. We create the +// methods in a closure to avoid creating global variables. + +// ************************************************************************************************ + +var JSON = window.JSON || {}; + +// ************************************************************************************************ + +(function () { + + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } + + if (typeof Date.prototype.toJSON !== 'function') { + + Date.prototype.toJSON = function (key) { + + return isFinite(this.valueOf()) ? + this.getUTCFullYear() + '-' + + f(this.getUTCMonth() + 1) + '-' + + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z' : null; + }; + + String.prototype.toJSON = + Number.prototype.toJSON = + Boolean.prototype.toJSON = function (key) { + return this.valueOf(); + }; + } + + var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + gap, + indent, + meta = { // table of character substitutions + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' + }, + rep; + + + function quote(string) { + +// If the string contains no control characters, no quote characters, and no +// backslash characters, then we can safely slap some quotes around it. +// Otherwise we must also replace the offending characters with safe escape +// sequences. + + escapable.lastIndex = 0; + return escapable.test(string) ? + '"' + string.replace(escapable, function (a) { + var c = meta[a]; + return typeof c === 'string' ? c : + '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }) + '"' : + '"' + string + '"'; + } + + + function str(key, holder) { + +// Produce a string from holder[key]. + + var i, // The loop counter. + k, // The member key. + v, // The member value. + length, + mind = gap, + partial, + value = holder[key]; + +// If the value has a toJSON method, call it to obtain a replacement value. + + if (value && typeof value === 'object' && + typeof value.toJSON === 'function') { + value = value.toJSON(key); + } + +// If we were called with a replacer function, then call the replacer to +// obtain a replacement value. + + if (typeof rep === 'function') { + value = rep.call(holder, key, value); + } + +// What happens next depends on the value's type. + + switch (typeof value) { + case 'string': + return quote(value); + + case 'number': + +// JSON numbers must be finite. Encode non-finite numbers as null. + + return isFinite(value) ? String(value) : 'null'; + + case 'boolean': + case 'null': + +// If the value is a boolean or null, convert it to a string. Note: +// typeof null does not produce 'null'. The case is included here in +// the remote chance that this gets fixed someday. + + return String(value); + +// If the type is 'object', we might be dealing with an object or an array or +// null. + + case 'object': + +// Due to a specification blunder in ECMAScript, typeof null is 'object', +// so watch out for that case. + + if (!value) { + return 'null'; + } + +// Make an array to hold the partial results of stringifying this object value. + + gap += indent; + partial = []; + +// Is the value an array? + + if (Object.prototype.toString.apply(value) === '[object Array]') { + +// The value is an array. Stringify every element. Use null as a placeholder +// for non-JSON values. + + length = value.length; + for (i = 0; i < length; i += 1) { + partial[i] = str(i, value) || 'null'; + } + +// Join all of the elements together, separated with commas, and wrap them in +// brackets. + + v = partial.length === 0 ? '[]' : + gap ? '[\n' + gap + + partial.join(',\n' + gap) + '\n' + + mind + ']' : + '[' + partial.join(',') + ']'; + gap = mind; + return v; + } + +// If the replacer is an array, use it to select the members to be stringified. + + if (rep && typeof rep === 'object') { + length = rep.length; + for (i = 0; i < length; i += 1) { + k = rep[i]; + if (typeof k === 'string') { + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + } else { + +// Otherwise, iterate through all of the keys in the object. + + for (k in value) { + if (Object.hasOwnProperty.call(value, k)) { + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + } + +// Join all of the member texts together, separated with commas, +// and wrap them in braces. + + v = partial.length === 0 ? '{}' : + gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + + mind + '}' : '{' + partial.join(',') + '}'; + gap = mind; + return v; + } + } + +// If the JSON object does not yet have a stringify method, give it one. + + if (typeof JSON.stringify !== 'function') { + JSON.stringify = function (value, replacer, space) { + +// The stringify method takes a value and an optional replacer, and an optional +// space parameter, and returns a JSON text. The replacer can be a function +// that can replace values, or an array of strings that will select the keys. +// A default replacer method can be provided. Use of the space parameter can +// produce text that is more easily readable. + + var i; + gap = ''; + indent = ''; + +// If the space parameter is a number, make an indent string containing that +// many spaces. + + if (typeof space === 'number') { + for (i = 0; i < space; i += 1) { + indent += ' '; + } + +// If the space parameter is a string, it will be used as the indent string. + + } else if (typeof space === 'string') { + indent = space; + } + +// If there is a replacer, it must be a function or an array. +// Otherwise, throw an error. + + rep = replacer; + if (replacer && typeof replacer !== 'function' && + (typeof replacer !== 'object' || + typeof replacer.length !== 'number')) { + throw new Error('JSON.stringify'); + } + +// Make a fake root object containing our value under the key of ''. +// Return the result of stringifying the value. + + return str('', {'': value}); + }; + } + + +// If the JSON object does not yet have a parse method, give it one. + + if (typeof JSON.parse !== 'function') { + JSON.parse = function (text, reviver) { + +// The parse method takes a text and an optional reviver function, and returns +// a JavaScript value if the text is a valid JSON text. + + var j; + + function walk(holder, key) { + +// The walk method is used to recursively walk the resulting structure so +// that modifications can be made. + + var k, v, value = holder[key]; + if (value && typeof value === 'object') { + for (k in value) { + if (Object.hasOwnProperty.call(value, k)) { + v = walk(value, k); + if (v !== undefined) { + value[k] = v; + } else { + delete value[k]; + } + } + } + } + return reviver.call(holder, key, value); + } + + +// Parsing happens in four stages. In the first stage, we replace certain +// Unicode characters with escape sequences. JavaScript handles many characters +// incorrectly, either silently deleting them, or treating them as line endings. + + text = String(text); + cx.lastIndex = 0; + if (cx.test(text)) { + text = text.replace(cx, function (a) { + return '\\u' + + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }); + } + +// In the second stage, we run the text against regular expressions that look +// for non-JSON patterns. We are especially concerned with '()' and 'new' +// because they can cause invocation, and '=' because it can cause mutation. +// But just to be safe, we want to reject all unexpected forms. + +// We split the second stage into 4 regexp operations in order to work around +// crippling inefficiencies in IE's and Safari's regexp engines. First we +// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we +// replace all simple value tokens with ']' characters. Third, we delete all +// open brackets that follow a colon or comma or that begin the text. Finally, +// we look to see that the remaining characters are only whitespace or ']' or +// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. + + if (/^[\],:{}\s]*$/. +test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@'). +replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'). +replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { + +// In the third stage we use the eval function to compile the text into a +// JavaScript structure. The '{' operator is subject to a syntactic ambiguity +// in JavaScript: it can begin a block or an object literal. We wrap the text +// in parens to eliminate the ambiguity. + + j = eval('(' + text + ')'); + +// In the optional fourth stage, we recursively walk the new structure, passing +// each name/value pair to a reviver function for possible transformation. + + return typeof reviver === 'function' ? + walk({'': j}, '') : j; + } + +// If the text is not JSON parseable, then a SyntaxError is thrown. + + throw new SyntaxError('JSON.parse'); + }; + } + +// ************************************************************************************************ +// registration + +FBL.JSON = JSON; + +// ************************************************************************************************ +}()); + +/* See license.txt for terms of usage */ + +(function(){ +// ************************************************************************************************ + +/* Copyright (c) 2010-2011 Marcus Westin + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +var store = (function(){ + var api = {}, + win = window, + doc = win.document, + localStorageName = 'localStorage', + globalStorageName = 'globalStorage', + namespace = '__firebug__storejs__', + storage + + api.disabled = false + api.set = function(key, value) {} + api.get = function(key) {} + api.remove = function(key) {} + api.clear = function() {} + api.transact = function(key, transactionFn) { + var val = api.get(key) + if (typeof val == 'undefined') { val = {} } + transactionFn(val) + api.set(key, val) + } + + api.serialize = function(value) { + return JSON.stringify(value) + } + api.deserialize = function(value) { + if (typeof value != 'string') { return undefined } + return JSON.parse(value) + } + + // Functions to encapsulate questionable FireFox 3.6.13 behavior + // when about.config::dom.storage.enabled === false + // See https://github.com/marcuswestin/store.js/issues#issue/13 + function isLocalStorageNameSupported() { + try { return (localStorageName in win && win[localStorageName]) } + catch(err) { return false } + } + + function isGlobalStorageNameSupported() { + try { return (globalStorageName in win && win[globalStorageName] && win[globalStorageName][win.location.hostname]) } + catch(err) { return false } + } + + if (isLocalStorageNameSupported()) { + storage = win[localStorageName] + api.set = function(key, val) { storage.setItem(key, api.serialize(val)) } + api.get = function(key) { return api.deserialize(storage.getItem(key)) } + api.remove = function(key) { storage.removeItem(key) } + api.clear = function() { storage.clear() } + + } else if (isGlobalStorageNameSupported()) { + storage = win[globalStorageName][win.location.hostname] + api.set = function(key, val) { storage[key] = api.serialize(val) } + api.get = function(key) { return api.deserialize(storage[key] && storage[key].value) } + api.remove = function(key) { delete storage[key] } + api.clear = function() { for (var key in storage ) { delete storage[key] } } + + } else if (doc.documentElement.addBehavior) { + var storage = doc.createElement('div') + function withIEStorage(storeFunction) { + return function() { + var args = Array.prototype.slice.call(arguments, 0) + args.unshift(storage) + // See http://msdn.microsoft.com/en-us/library/ms531081(v=VS.85).aspx + // and http://msdn.microsoft.com/en-us/library/ms531424(v=VS.85).aspx + // TODO: xxxpedro doc.body is not always available so we must use doc.documentElement. + // We need to make sure this change won't affect the behavior of this library. + doc.documentElement.appendChild(storage) + storage.addBehavior('#default#userData') + storage.load(localStorageName) + var result = storeFunction.apply(api, args) + doc.documentElement.removeChild(storage) + return result + } + } + api.set = withIEStorage(function(storage, key, val) { + storage.setAttribute(key, api.serialize(val)) + storage.save(localStorageName) + }) + api.get = withIEStorage(function(storage, key) { + return api.deserialize(storage.getAttribute(key)) + }) + api.remove = withIEStorage(function(storage, key) { + storage.removeAttribute(key) + storage.save(localStorageName) + }) + api.clear = withIEStorage(function(storage) { + var attributes = storage.XMLDocument.documentElement.attributes + storage.load(localStorageName) + for (var i=0, attr; attr = attributes[i]; i++) { + storage.removeAttribute(attr.name) + } + storage.save(localStorageName) + }) + } + + try { + api.set(namespace, namespace) + if (api.get(namespace) != namespace) { api.disabled = true } + api.remove(namespace) + } catch(e) { + api.disabled = true + } + + return api +})(); + +if (typeof module != 'undefined') { module.exports = store } + + +// ************************************************************************************************ +// registration + +FBL.Store = store; + +// ************************************************************************************************ +})(); + +/* See license.txt for terms of usage */ + +FBL.ns( /**@scope s_selector*/ function() { with (FBL) { +// ************************************************************************************************ + +/* + * Sizzle CSS Selector Engine - v1.0 + * Copyright 2009, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ + +var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, + done = 0, + toString = Object.prototype.toString, + hasDuplicate = false, + baseHasDuplicate = true; + +// Here we check if the JavaScript engine is using some sort of +// optimization where it does not always call our comparision +// function. If that is the case, discard the hasDuplicate value. +// Thus far that includes Google Chrome. +[0, 0].sort(function(){ + baseHasDuplicate = false; + return 0; +}); + +/** + * @name Firebug.Selector + * @namespace + */ + +/** + * @exports Sizzle as Firebug.Selector + */ +var Sizzle = function(selector, context, results, seed) { + results = results || []; + var origContext = context = context || document; + + if ( context.nodeType !== 1 && context.nodeType !== 9 ) { + return []; + } + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + var parts = [], m, set, checkSet, check, mode, extra, prune = true, contextXML = isXML(context), + soFar = selector; + + // Reset the position of the chunker regexp (start from head) + while ( (chunker.exec(""), m = chunker.exec(soFar)) !== null ) { + soFar = m[3]; + + parts.push( m[1] ); + + if ( m[2] ) { + extra = m[3]; + break; + } + } + + if ( parts.length > 1 && origPOS.exec( selector ) ) { + if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { + set = posProcess( parts[0] + parts[1], context ); + } else { + set = Expr.relative[ parts[0] ] ? + [ context ] : + Sizzle( parts.shift(), context ); + + while ( parts.length ) { + selector = parts.shift(); + + if ( Expr.relative[ selector ] ) + selector += parts.shift(); + + set = posProcess( selector, set ); + } + } + } else { + // Take a shortcut and set the context if the root selector is an ID + // (but not if it'll be faster if the inner selector is an ID) + if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && + Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { + var ret = Sizzle.find( parts.shift(), context, contextXML ); + context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0]; + } + + if ( context ) { + var ret = seed ? + { expr: parts.pop(), set: makeArray(seed) } : + Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); + set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set; + + if ( parts.length > 0 ) { + checkSet = makeArray(set); + } else { + prune = false; + } + + while ( parts.length ) { + var cur = parts.pop(), pop = cur; + + if ( !Expr.relative[ cur ] ) { + cur = ""; + } else { + pop = parts.pop(); + } + + if ( pop == null ) { + pop = context; + } + + Expr.relative[ cur ]( checkSet, pop, contextXML ); + } + } else { + checkSet = parts = []; + } + } + + if ( !checkSet ) { + checkSet = set; + } + + if ( !checkSet ) { + throw "Syntax error, unrecognized expression: " + (cur || selector); + } + + if ( toString.call(checkSet) === "[object Array]" ) { + if ( !prune ) { + results.push.apply( results, checkSet ); + } else if ( context && context.nodeType === 1 ) { + for ( var i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) { + results.push( set[i] ); + } + } + } else { + for ( var i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && checkSet[i].nodeType === 1 ) { + results.push( set[i] ); + } + } + } + } else { + makeArray( checkSet, results ); + } + + if ( extra ) { + Sizzle( extra, origContext, results, seed ); + Sizzle.uniqueSort( results ); + } + + return results; +}; + +Sizzle.uniqueSort = function(results){ + if ( sortOrder ) { + hasDuplicate = baseHasDuplicate; + results.sort(sortOrder); + + if ( hasDuplicate ) { + for ( var i = 1; i < results.length; i++ ) { + if ( results[i] === results[i-1] ) { + results.splice(i--, 1); + } + } + } + } + + return results; +}; + +Sizzle.matches = function(expr, set){ + return Sizzle(expr, null, null, set); +}; + +Sizzle.find = function(expr, context, isXML){ + var set, match; + + if ( !expr ) { + return []; + } + + for ( var i = 0, l = Expr.order.length; i < l; i++ ) { + var type = Expr.order[i], match; + + if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { + var left = match[1]; + match.splice(1,1); + + if ( left.substr( left.length - 1 ) !== "\\" ) { + match[1] = (match[1] || "").replace(/\\/g, ""); + set = Expr.find[ type ]( match, context, isXML ); + if ( set != null ) { + expr = expr.replace( Expr.match[ type ], "" ); + break; + } + } + } + } + + if ( !set ) { + set = context.getElementsByTagName("*"); + } + + return {set: set, expr: expr}; +}; + +Sizzle.filter = function(expr, set, inplace, not){ + var old = expr, result = [], curLoop = set, match, anyFound, + isXMLFilter = set && set[0] && isXML(set[0]); + + while ( expr && set.length ) { + for ( var type in Expr.filter ) { + if ( (match = Expr.match[ type ].exec( expr )) != null ) { + var filter = Expr.filter[ type ], found, item; + anyFound = false; + + if ( curLoop == result ) { + result = []; + } + + if ( Expr.preFilter[ type ] ) { + match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); + + if ( !match ) { + anyFound = found = true; + } else if ( match === true ) { + continue; + } + } + + if ( match ) { + for ( var i = 0; (item = curLoop[i]) != null; i++ ) { + if ( item ) { + found = filter( item, match, i, curLoop ); + var pass = not ^ !!found; + + if ( inplace && found != null ) { + if ( pass ) { + anyFound = true; + } else { + curLoop[i] = false; + } + } else if ( pass ) { + result.push( item ); + anyFound = true; + } + } + } + } + + if ( found !== undefined ) { + if ( !inplace ) { + curLoop = result; + } + + expr = expr.replace( Expr.match[ type ], "" ); + + if ( !anyFound ) { + return []; + } + + break; + } + } + } + + // Improper expression + if ( expr == old ) { + if ( anyFound == null ) { + throw "Syntax error, unrecognized expression: " + expr; + } else { + break; + } + } + + old = expr; + } + + return curLoop; +}; + +/**#@+ @ignore */ +var Expr = Sizzle.selectors = { + order: [ "ID", "NAME", "TAG" ], + match: { + ID: /#((?:[\w\u00c0-\uFFFF-]|\\.)+)/, + CLASS: /\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/, + NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/, + ATTR: /\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/, + TAG: /^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/, + CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/, + POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/, + PSEUDO: /:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/ + }, + leftMatch: {}, + attrMap: { + "class": "className", + "for": "htmlFor" + }, + attrHandle: { + href: function(elem){ + return elem.getAttribute("href"); + } + }, + relative: { + "+": function(checkSet, part, isXML){ + var isPartStr = typeof part === "string", + isTag = isPartStr && !/\W/.test(part), + isPartStrNotTag = isPartStr && !isTag; + + if ( isTag && !isXML ) { + part = part.toUpperCase(); + } + + for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { + if ( (elem = checkSet[i]) ) { + while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} + + checkSet[i] = isPartStrNotTag || elem && elem.nodeName === part ? + elem || false : + elem === part; + } + } + + if ( isPartStrNotTag ) { + Sizzle.filter( part, checkSet, true ); + } + }, + ">": function(checkSet, part, isXML){ + var isPartStr = typeof part === "string"; + + if ( isPartStr && !/\W/.test(part) ) { + part = isXML ? part : part.toUpperCase(); + + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + var parent = elem.parentNode; + checkSet[i] = parent.nodeName === part ? parent : false; + } + } + } else { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + checkSet[i] = isPartStr ? + elem.parentNode : + elem.parentNode === part; + } + } + + if ( isPartStr ) { + Sizzle.filter( part, checkSet, true ); + } + } + }, + "": function(checkSet, part, isXML){ + var doneName = done++, checkFn = dirCheck; + + if ( !/\W/.test(part) ) { + var nodeCheck = part = isXML ? part : part.toUpperCase(); + checkFn = dirNodeCheck; + } + + checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML); + }, + "~": function(checkSet, part, isXML){ + var doneName = done++, checkFn = dirCheck; + + if ( typeof part === "string" && !/\W/.test(part) ) { + var nodeCheck = part = isXML ? part : part.toUpperCase(); + checkFn = dirNodeCheck; + } + + checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML); + } + }, + find: { + ID: function(match, context, isXML){ + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + return m ? [m] : []; + } + }, + NAME: function(match, context, isXML){ + if ( typeof context.getElementsByName !== "undefined" ) { + var ret = [], results = context.getElementsByName(match[1]); + + for ( var i = 0, l = results.length; i < l; i++ ) { + if ( results[i].getAttribute("name") === match[1] ) { + ret.push( results[i] ); + } + } + + return ret.length === 0 ? null : ret; + } + }, + TAG: function(match, context){ + return context.getElementsByTagName(match[1]); + } + }, + preFilter: { + CLASS: function(match, curLoop, inplace, result, not, isXML){ + match = " " + match[1].replace(/\\/g, "") + " "; + + if ( isXML ) { + return match; + } + + for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { + if ( elem ) { + if ( not ^ (elem.className && (" " + elem.className + " ").indexOf(match) >= 0) ) { + if ( !inplace ) + result.push( elem ); + } else if ( inplace ) { + curLoop[i] = false; + } + } + } + + return false; + }, + ID: function(match){ + return match[1].replace(/\\/g, ""); + }, + TAG: function(match, curLoop){ + for ( var i = 0; curLoop[i] === false; i++ ){} + return curLoop[i] && isXML(curLoop[i]) ? match[1] : match[1].toUpperCase(); + }, + CHILD: function(match){ + if ( match[1] == "nth" ) { + // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' + var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec( + match[2] == "even" && "2n" || match[2] == "odd" && "2n+1" || + !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); + + // calculate the numbers (first)n+(last) including if they are negative + match[2] = (test[1] + (test[2] || 1)) - 0; + match[3] = test[3] - 0; + } + + // TODO: Move to normal caching system + match[0] = done++; + + return match; + }, + ATTR: function(match, curLoop, inplace, result, not, isXML){ + var name = match[1].replace(/\\/g, ""); + + if ( !isXML && Expr.attrMap[name] ) { + match[1] = Expr.attrMap[name]; + } + + if ( match[2] === "~=" ) { + match[4] = " " + match[4] + " "; + } + + return match; + }, + PSEUDO: function(match, curLoop, inplace, result, not){ + if ( match[1] === "not" ) { + // If we're dealing with a complex expression, or a simple one + if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { + match[3] = Sizzle(match[3], null, null, curLoop); + } else { + var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); + if ( !inplace ) { + result.push.apply( result, ret ); + } + return false; + } + } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { + return true; + } + + return match; + }, + POS: function(match){ + match.unshift( true ); + return match; + } + }, + filters: { + enabled: function(elem){ + return elem.disabled === false && elem.type !== "hidden"; + }, + disabled: function(elem){ + return elem.disabled === true; + }, + checked: function(elem){ + return elem.checked === true; + }, + selected: function(elem){ + // Accessing this property makes selected-by-default + // options in Safari work properly + elem.parentNode.selectedIndex; + return elem.selected === true; + }, + parent: function(elem){ + return !!elem.firstChild; + }, + empty: function(elem){ + return !elem.firstChild; + }, + has: function(elem, i, match){ + return !!Sizzle( match[3], elem ).length; + }, + header: function(elem){ + return /h\d/i.test( elem.nodeName ); + }, + text: function(elem){ + return "text" === elem.type; + }, + radio: function(elem){ + return "radio" === elem.type; + }, + checkbox: function(elem){ + return "checkbox" === elem.type; + }, + file: function(elem){ + return "file" === elem.type; + }, + password: function(elem){ + return "password" === elem.type; + }, + submit: function(elem){ + return "submit" === elem.type; + }, + image: function(elem){ + return "image" === elem.type; + }, + reset: function(elem){ + return "reset" === elem.type; + }, + button: function(elem){ + return "button" === elem.type || elem.nodeName.toUpperCase() === "BUTTON"; + }, + input: function(elem){ + return /input|select|textarea|button/i.test(elem.nodeName); + } + }, + setFilters: { + first: function(elem, i){ + return i === 0; + }, + last: function(elem, i, match, array){ + return i === array.length - 1; + }, + even: function(elem, i){ + return i % 2 === 0; + }, + odd: function(elem, i){ + return i % 2 === 1; + }, + lt: function(elem, i, match){ + return i < match[3] - 0; + }, + gt: function(elem, i, match){ + return i > match[3] - 0; + }, + nth: function(elem, i, match){ + return match[3] - 0 == i; + }, + eq: function(elem, i, match){ + return match[3] - 0 == i; + } + }, + filter: { + PSEUDO: function(elem, match, i, array){ + var name = match[1], filter = Expr.filters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } else if ( name === "contains" ) { + return (elem.textContent || elem.innerText || "").indexOf(match[3]) >= 0; + } else if ( name === "not" ) { + var not = match[3]; + + for ( var i = 0, l = not.length; i < l; i++ ) { + if ( not[i] === elem ) { + return false; + } + } + + return true; + } + }, + CHILD: function(elem, match){ + var type = match[1], node = elem; + switch (type) { + case 'only': + case 'first': + while ( (node = node.previousSibling) ) { + if ( node.nodeType === 1 ) return false; + } + if ( type == 'first') return true; + node = elem; + case 'last': + while ( (node = node.nextSibling) ) { + if ( node.nodeType === 1 ) return false; + } + return true; + case 'nth': + var first = match[2], last = match[3]; + + if ( first == 1 && last == 0 ) { + return true; + } + + var doneName = match[0], + parent = elem.parentNode; + + if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { + var count = 0; + for ( node = parent.firstChild; node; node = node.nextSibling ) { + if ( node.nodeType === 1 ) { + node.nodeIndex = ++count; + } + } + parent.sizcache = doneName; + } + + var diff = elem.nodeIndex - last; + if ( first == 0 ) { + return diff == 0; + } else { + return ( diff % first == 0 && diff / first >= 0 ); + } + } + }, + ID: function(elem, match){ + return elem.nodeType === 1 && elem.getAttribute("id") === match; + }, + TAG: function(elem, match){ + return (match === "*" && elem.nodeType === 1) || elem.nodeName === match; + }, + CLASS: function(elem, match){ + return (" " + (elem.className || elem.getAttribute("class")) + " ") + .indexOf( match ) > -1; + }, + ATTR: function(elem, match){ + var name = match[1], + result = Expr.attrHandle[ name ] ? + Expr.attrHandle[ name ]( elem ) : + elem[ name ] != null ? + elem[ name ] : + elem.getAttribute( name ), + value = result + "", + type = match[2], + check = match[4]; + + return result == null ? + type === "!=" : + type === "=" ? + value === check : + type === "*=" ? + value.indexOf(check) >= 0 : + type === "~=" ? + (" " + value + " ").indexOf(check) >= 0 : + !check ? + value && result !== false : + type === "!=" ? + value != check : + type === "^=" ? + value.indexOf(check) === 0 : + type === "$=" ? + value.substr(value.length - check.length) === check : + type === "|=" ? + value === check || value.substr(0, check.length + 1) === check + "-" : + false; + }, + POS: function(elem, match, i, array){ + var name = match[2], filter = Expr.setFilters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } + } + } +}; + +var origPOS = Expr.match.POS; + +for ( var type in Expr.match ) { + Expr.match[ type ] = new RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source ); + Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source ); +} + +var makeArray = function(array, results) { + array = Array.prototype.slice.call( array, 0 ); + + if ( results ) { + results.push.apply( results, array ); + return results; + } + + return array; +}; + +// Perform a simple check to determine if the browser is capable of +// converting a NodeList to an array using builtin methods. +try { + Array.prototype.slice.call( document.documentElement.childNodes, 0 ); + +// Provide a fallback method if it does not work +} catch(e){ + makeArray = function(array, results) { + var ret = results || []; + + if ( toString.call(array) === "[object Array]" ) { + Array.prototype.push.apply( ret, array ); + } else { + if ( typeof array.length === "number" ) { + for ( var i = 0, l = array.length; i < l; i++ ) { + ret.push( array[i] ); + } + } else { + for ( var i = 0; array[i]; i++ ) { + ret.push( array[i] ); + } + } + } + + return ret; + }; +} + +var sortOrder; + +if ( document.documentElement.compareDocumentPosition ) { + sortOrder = function( a, b ) { + if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { + if ( a == b ) { + hasDuplicate = true; + } + return 0; + } + + var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1; + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} else if ( "sourceIndex" in document.documentElement ) { + sortOrder = function( a, b ) { + if ( !a.sourceIndex || !b.sourceIndex ) { + if ( a == b ) { + hasDuplicate = true; + } + return 0; + } + + var ret = a.sourceIndex - b.sourceIndex; + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} else if ( document.createRange ) { + sortOrder = function( a, b ) { + if ( !a.ownerDocument || !b.ownerDocument ) { + if ( a == b ) { + hasDuplicate = true; + } + return 0; + } + + var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange(); + aRange.setStart(a, 0); + aRange.setEnd(a, 0); + bRange.setStart(b, 0); + bRange.setEnd(b, 0); + var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange); + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} + +// Check to see if the browser returns elements by name when +// querying by getElementById (and provide a workaround) +(function(){ + // We're going to inject a fake input element with a specified name + var form = document.createElement("div"), + id = "script" + (new Date).getTime(); + form.innerHTML = "<a name='" + id + "'/>"; + + // Inject it into the root element, check its status, and remove it quickly + var root = document.documentElement; + root.insertBefore( form, root.firstChild ); + + // The workaround has to do additional checks after a getElementById + // Which slows things down for other browsers (hence the branching) + if ( !!document.getElementById( id ) ) { + Expr.find.ID = function(match, context, isXML){ + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : []; + } + }; + + Expr.filter.ID = function(elem, match){ + var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); + return elem.nodeType === 1 && node && node.nodeValue === match; + }; + } + + root.removeChild( form ); + root = form = null; // release memory in IE +})(); + +(function(){ + // Check to see if the browser returns only elements + // when doing getElementsByTagName("*") + + // Create a fake element + var div = document.createElement("div"); + div.appendChild( document.createComment("") ); + + // Make sure no comments are found + if ( div.getElementsByTagName("*").length > 0 ) { + Expr.find.TAG = function(match, context){ + var results = context.getElementsByTagName(match[1]); + + // Filter out possible comments + if ( match[1] === "*" ) { + var tmp = []; + + for ( var i = 0; results[i]; i++ ) { + if ( results[i].nodeType === 1 ) { + tmp.push( results[i] ); + } + } + + results = tmp; + } + + return results; + }; + } + + // Check to see if an attribute returns normalized href attributes + div.innerHTML = "<a href='#'></a>"; + if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && + div.firstChild.getAttribute("href") !== "#" ) { + Expr.attrHandle.href = function(elem){ + return elem.getAttribute("href", 2); + }; + } + + div = null; // release memory in IE +})(); + +if ( document.querySelectorAll ) (function(){ + var oldSizzle = Sizzle, div = document.createElement("div"); + div.innerHTML = "<p class='TEST'></p>"; + + // Safari can't handle uppercase or unicode characters when + // in quirks mode. + if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { + return; + } + + Sizzle = function(query, context, extra, seed){ + context = context || document; + + // Only use querySelectorAll on non-XML documents + // (ID selectors don't work in non-HTML documents) + if ( !seed && context.nodeType === 9 && !isXML(context) ) { + try { + return makeArray( context.querySelectorAll(query), extra ); + } catch(e){} + } + + return oldSizzle(query, context, extra, seed); + }; + + for ( var prop in oldSizzle ) { + Sizzle[ prop ] = oldSizzle[ prop ]; + } + + div = null; // release memory in IE +})(); + +if ( document.getElementsByClassName && document.documentElement.getElementsByClassName ) (function(){ + var div = document.createElement("div"); + div.innerHTML = "<div class='test e'></div><div class='test'></div>"; + + // Opera can't find a second classname (in 9.6) + if ( div.getElementsByClassName("e").length === 0 ) + return; + + // Safari caches class attributes, doesn't catch changes (in 3.2) + div.lastChild.className = "e"; + + if ( div.getElementsByClassName("e").length === 1 ) + return; + + Expr.order.splice(1, 0, "CLASS"); + Expr.find.CLASS = function(match, context, isXML) { + if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { + return context.getElementsByClassName(match[1]); + } + }; + + div = null; // release memory in IE +})(); + +function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + var sibDir = dir == "previousSibling" && !isXML; + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + if ( sibDir && elem.nodeType === 1 ){ + elem.sizcache = doneName; + elem.sizset = i; + } + elem = elem[dir]; + var match = false; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 && !isXML ){ + elem.sizcache = doneName; + elem.sizset = i; + } + + if ( elem.nodeName === cur ) { + match = elem; + break; + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + var sibDir = dir == "previousSibling" && !isXML; + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + if ( sibDir && elem.nodeType === 1 ) { + elem.sizcache = doneName; + elem.sizset = i; + } + elem = elem[dir]; + var match = false; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 ) { + if ( !isXML ) { + elem.sizcache = doneName; + elem.sizset = i; + } + if ( typeof cur !== "string" ) { + if ( elem === cur ) { + match = true; + break; + } + + } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { + match = elem; + break; + } + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +var contains = document.compareDocumentPosition ? function(a, b){ + return a.compareDocumentPosition(b) & 16; +} : function(a, b){ + return a !== b && (a.contains ? a.contains(b) : true); +}; + +var isXML = function(elem){ + return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" || + !!elem.ownerDocument && elem.ownerDocument.documentElement.nodeName !== "HTML"; +}; + +var posProcess = function(selector, context){ + var tmpSet = [], later = "", match, + root = context.nodeType ? [context] : context; + + // Position selectors must be done after the filter + // And so must :not(positional) so we move all PSEUDOs to the end + while ( (match = Expr.match.PSEUDO.exec( selector )) ) { + later += match[0]; + selector = selector.replace( Expr.match.PSEUDO, "" ); + } + + selector = Expr.relative[selector] ? selector + "*" : selector; + + for ( var i = 0, l = root.length; i < l; i++ ) { + Sizzle( selector, root[i], tmpSet ); + } + + return Sizzle.filter( later, tmpSet ); +}; + +// EXPOSE + +Firebug.Selector = Sizzle; + +/**#@-*/ + +// ************************************************************************************************ +}}); + +/* See license.txt for terms of usage */ + +FBL.ns(function() { with (FBL) { +// ************************************************************************************************ + +// ************************************************************************************************ +// Inspector Module + +var ElementCache = Firebug.Lite.Cache.Element; + +var inspectorTS, inspectorTimer, isInspecting; + +Firebug.Inspector = +{ + create: function() + { + offlineFragment = Env.browser.document.createDocumentFragment(); + + createBoxModelInspector(); + createOutlineInspector(); + }, + + destroy: function() + { + destroyBoxModelInspector(); + destroyOutlineInspector(); + + offlineFragment = null; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Inspect functions + + toggleInspect: function() + { + if (isInspecting) + { + this.stopInspecting(); + } + else + { + Firebug.chrome.inspectButton.changeState("pressed"); + this.startInspecting(); + } + }, + + startInspecting: function() + { + isInspecting = true; + + Firebug.chrome.selectPanel("HTML"); + + createInspectorFrame(); + + var size = Firebug.browser.getWindowScrollSize(); + + fbInspectFrame.style.width = size.width + "px"; + fbInspectFrame.style.height = size.height + "px"; + + //addEvent(Firebug.browser.document.documentElement, "mousemove", Firebug.Inspector.onInspectingBody); + + addEvent(fbInspectFrame, "mousemove", Firebug.Inspector.onInspecting); + addEvent(fbInspectFrame, "mousedown", Firebug.Inspector.onInspectingClick); + }, + + stopInspecting: function() + { + isInspecting = false; + + if (outlineVisible) this.hideOutline(); + removeEvent(fbInspectFrame, "mousemove", Firebug.Inspector.onInspecting); + removeEvent(fbInspectFrame, "mousedown", Firebug.Inspector.onInspectingClick); + + destroyInspectorFrame(); + + Firebug.chrome.inspectButton.restore(); + + if (Firebug.chrome.type == "popup") + Firebug.chrome.node.focus(); + }, + + onInspectingClick: function(e) + { + fbInspectFrame.style.display = "none"; + var targ = Firebug.browser.getElementFromPoint(e.clientX, e.clientY); + fbInspectFrame.style.display = "block"; + + // Avoid inspecting the outline, and the FirebugUI + var id = targ.id; + if (id && /^fbOutline\w$/.test(id)) return; + if (id == "FirebugUI") return; + + // Avoid looking at text nodes in Opera + while (targ.nodeType != 1) targ = targ.parentNode; + + //Firebug.Console.log(targ); + Firebug.Inspector.stopInspecting(); + }, + + onInspecting: function(e) + { + if (new Date().getTime() - lastInspecting > 30) + { + fbInspectFrame.style.display = "none"; + var targ = Firebug.browser.getElementFromPoint(e.clientX, e.clientY); + fbInspectFrame.style.display = "block"; + + // Avoid inspecting the outline, and the FirebugUI + var id = targ.id; + if (id && /^fbOutline\w$/.test(id)) return; + if (id == "FirebugUI") return; + + // Avoid looking at text nodes in Opera + while (targ.nodeType != 1) targ = targ.parentNode; + + if (targ.nodeName.toLowerCase() == "body") return; + + //Firebug.Console.log(e.clientX, e.clientY, targ); + Firebug.Inspector.drawOutline(targ); + + if (ElementCache(targ)) + { + var target = ""+ElementCache.key(targ); + var lazySelect = function() + { + inspectorTS = new Date().getTime(); + + if (Firebug.HTML) + Firebug.HTML.selectTreeNode(""+ElementCache.key(targ)); + }; + + if (inspectorTimer) + { + clearTimeout(inspectorTimer); + inspectorTimer = null; + } + + if (new Date().getTime() - inspectorTS > 200) + setTimeout(lazySelect, 0); + else + inspectorTimer = setTimeout(lazySelect, 300); + } + + lastInspecting = new Date().getTime(); + } + }, + + // TODO: xxxpedro remove this? + onInspectingBody: function(e) + { + if (new Date().getTime() - lastInspecting > 30) + { + var targ = e.target; + + // Avoid inspecting the outline, and the FirebugUI + var id = targ.id; + if (id && /^fbOutline\w$/.test(id)) return; + if (id == "FirebugUI") return; + + // Avoid looking at text nodes in Opera + while (targ.nodeType != 1) targ = targ.parentNode; + + if (targ.nodeName.toLowerCase() == "body") return; + + //Firebug.Console.log(e.clientX, e.clientY, targ); + Firebug.Inspector.drawOutline(targ); + + if (ElementCache.has(targ)) + FBL.Firebug.HTML.selectTreeNode(""+ElementCache.key(targ)); + + lastInspecting = new Date().getTime(); + } + }, + + /** + * + * llttttttrr + * llttttttrr + * ll rr + * ll rr + * llbbbbbbrr + * llbbbbbbrr + */ + drawOutline: function(el) + { + var border = 2; + var scrollbarSize = 17; + + var windowSize = Firebug.browser.getWindowSize(); + var scrollSize = Firebug.browser.getWindowScrollSize(); + var scrollPosition = Firebug.browser.getWindowScrollPosition(); + + var box = Firebug.browser.getElementBox(el); + + var top = box.top; + var left = box.left; + var height = box.height; + var width = box.width; + + var freeHorizontalSpace = scrollPosition.left + windowSize.width - left - width - + (!isIE && scrollSize.height > windowSize.height ? // is *vertical* scrollbar visible + scrollbarSize : 0); + + var freeVerticalSpace = scrollPosition.top + windowSize.height - top - height - + (!isIE && scrollSize.width > windowSize.width ? // is *horizontal* scrollbar visible + scrollbarSize : 0); + + var numVerticalBorders = freeVerticalSpace > 0 ? 2 : 1; + + var o = outlineElements; + var style; + + style = o.fbOutlineT.style; + style.top = top-border + "px"; + style.left = left + "px"; + style.height = border + "px"; // TODO: on initialize() + style.width = width + "px"; + + style = o.fbOutlineL.style; + style.top = top-border + "px"; + style.left = left-border + "px"; + style.height = height+ numVerticalBorders*border + "px"; + style.width = border + "px"; // TODO: on initialize() + + style = o.fbOutlineB.style; + if (freeVerticalSpace > 0) + { + style.top = top+height + "px"; + style.left = left + "px"; + style.width = width + "px"; + //style.height = border + "px"; // TODO: on initialize() or worst case? + } + else + { + style.top = -2*border + "px"; + style.left = -2*border + "px"; + style.width = border + "px"; + //style.height = border + "px"; + } + + style = o.fbOutlineR.style; + if (freeHorizontalSpace > 0) + { + style.top = top-border + "px"; + style.left = left+width + "px"; + style.height = height + numVerticalBorders*border + "px"; + style.width = (freeHorizontalSpace < border ? freeHorizontalSpace : border) + "px"; + } + else + { + style.top = -2*border + "px"; + style.left = -2*border + "px"; + style.height = border + "px"; + style.width = border + "px"; + } + + if (!outlineVisible) this.showOutline(); + }, + + hideOutline: function() + { + if (!outlineVisible) return; + + for (var name in outline) + offlineFragment.appendChild(outlineElements[name]); + + outlineVisible = false; + }, + + showOutline: function() + { + if (outlineVisible) return; + + if (boxModelVisible) this.hideBoxModel(); + + for (var name in outline) + Firebug.browser.document.getElementsByTagName("body")[0].appendChild(outlineElements[name]); + + outlineVisible = true; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Box Model + + drawBoxModel: function(el) + { + // avoid error when the element is not attached a document + if (!el || !el.parentNode) + return; + + var box = Firebug.browser.getElementBox(el); + + var windowSize = Firebug.browser.getWindowSize(); + var scrollPosition = Firebug.browser.getWindowScrollPosition(); + + // element may be occluded by the chrome, when in frame mode + var offsetHeight = Firebug.chrome.type == "frame" ? Firebug.context.persistedState.height : 0; + + // if element box is not inside the viewport, don't draw the box model + if (box.top > scrollPosition.top + windowSize.height - offsetHeight || + box.left > scrollPosition.left + windowSize.width || + scrollPosition.top > box.top + box.height || + scrollPosition.left > box.left + box.width ) + return; + + var top = box.top; + var left = box.left; + var height = box.height; + var width = box.width; + + var margin = Firebug.browser.getMeasurementBox(el, "margin"); + var padding = Firebug.browser.getMeasurementBox(el, "padding"); + var border = Firebug.browser.getMeasurementBox(el, "border"); + + boxModelStyle.top = top - margin.top + "px"; + boxModelStyle.left = left - margin.left + "px"; + boxModelStyle.height = height + margin.top + margin.bottom + "px"; + boxModelStyle.width = width + margin.left + margin.right + "px"; + + boxBorderStyle.top = margin.top + "px"; + boxBorderStyle.left = margin.left + "px"; + boxBorderStyle.height = height + "px"; + boxBorderStyle.width = width + "px"; + + boxPaddingStyle.top = margin.top + border.top + "px"; + boxPaddingStyle.left = margin.left + border.left + "px"; + boxPaddingStyle.height = height - border.top - border.bottom + "px"; + boxPaddingStyle.width = width - border.left - border.right + "px"; + + boxContentStyle.top = margin.top + border.top + padding.top + "px"; + boxContentStyle.left = margin.left + border.left + padding.left + "px"; + boxContentStyle.height = height - border.top - padding.top - padding.bottom - border.bottom + "px"; + boxContentStyle.width = width - border.left - padding.left - padding.right - border.right + "px"; + + if (!boxModelVisible) this.showBoxModel(); + }, + + hideBoxModel: function() + { + if (!boxModelVisible) return; + + offlineFragment.appendChild(boxModel); + boxModelVisible = false; + }, + + showBoxModel: function() + { + if (boxModelVisible) return; + + if (outlineVisible) this.hideOutline(); + + Firebug.browser.document.getElementsByTagName("body")[0].appendChild(boxModel); + boxModelVisible = true; + } + +}; + +// ************************************************************************************************ +// Inspector Internals + + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// Shared variables + + + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// Internal variables + +var offlineFragment = null; + +var boxModelVisible = false; + +var boxModel, boxModelStyle, + boxMargin, boxMarginStyle, + boxBorder, boxBorderStyle, + boxPadding, boxPaddingStyle, + boxContent, boxContentStyle; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +var resetStyle = "margin:0; padding:0; border:0; position:absolute; overflow:hidden; display:block;"; +var offscreenStyle = resetStyle + "top:-1234px; left:-1234px;"; + +var inspectStyle = resetStyle + "z-index: 2147483500;"; +var inspectFrameStyle = resetStyle + "z-index: 2147483550; top:0; left:0; background:url(" + + Env.Location.skinDir + "pixel_transparent.gif);"; + +//if (Env.Options.enableTrace) inspectFrameStyle = resetStyle + "z-index: 2147483550; top: 0; left: 0; background: #ff0; opacity: 0.05; _filter: alpha(opacity=5);"; + +var inspectModelOpacity = isIE ? "filter:alpha(opacity=80);" : "opacity:0.8;"; +var inspectModelStyle = inspectStyle + inspectModelOpacity; +var inspectMarginStyle = inspectStyle + "background: #EDFF64; height:100%; width:100%;"; +var inspectBorderStyle = inspectStyle + "background: #666;"; +var inspectPaddingStyle = inspectStyle + "background: SlateBlue;"; +var inspectContentStyle = inspectStyle + "background: SkyBlue;"; + + +var outlineStyle = { + fbHorizontalLine: "background: #3875D7;height: 2px;", + fbVerticalLine: "background: #3875D7;width: 2px;" +}; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +var lastInspecting = 0; +var fbInspectFrame = null; + + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +var outlineVisible = false; +var outlineElements = {}; +var outline = { + "fbOutlineT": "fbHorizontalLine", + "fbOutlineL": "fbVerticalLine", + "fbOutlineB": "fbHorizontalLine", + "fbOutlineR": "fbVerticalLine" +}; + + +var getInspectingTarget = function() +{ + +}; + +// ************************************************************************************************ +// Section + +var createInspectorFrame = function createInspectorFrame() +{ + fbInspectFrame = createGlobalElement("div"); + fbInspectFrame.id = "fbInspectFrame"; + fbInspectFrame.firebugIgnore = true; + fbInspectFrame.style.cssText = inspectFrameStyle; + Firebug.browser.document.getElementsByTagName("body")[0].appendChild(fbInspectFrame); +}; + +var destroyInspectorFrame = function destroyInspectorFrame() +{ + if (fbInspectFrame) + { + Firebug.browser.document.getElementsByTagName("body")[0].removeChild(fbInspectFrame); + fbInspectFrame = null; + } +}; + +var createOutlineInspector = function createOutlineInspector() +{ + for (var name in outline) + { + var el = outlineElements[name] = createGlobalElement("div"); + el.id = name; + el.firebugIgnore = true; + el.style.cssText = inspectStyle + outlineStyle[outline[name]]; + offlineFragment.appendChild(el); + } +}; + +var destroyOutlineInspector = function destroyOutlineInspector() +{ + for (var name in outline) + { + var el = outlineElements[name]; + el.parentNode.removeChild(el); + } +}; + +var createBoxModelInspector = function createBoxModelInspector() +{ + boxModel = createGlobalElement("div"); + boxModel.id = "fbBoxModel"; + boxModel.firebugIgnore = true; + boxModelStyle = boxModel.style; + boxModelStyle.cssText = inspectModelStyle; + + boxMargin = createGlobalElement("div"); + boxMargin.id = "fbBoxMargin"; + boxMarginStyle = boxMargin.style; + boxMarginStyle.cssText = inspectMarginStyle; + boxModel.appendChild(boxMargin); + + boxBorder = createGlobalElement("div"); + boxBorder.id = "fbBoxBorder"; + boxBorderStyle = boxBorder.style; + boxBorderStyle.cssText = inspectBorderStyle; + boxModel.appendChild(boxBorder); + + boxPadding = createGlobalElement("div"); + boxPadding.id = "fbBoxPadding"; + boxPaddingStyle = boxPadding.style; + boxPaddingStyle.cssText = inspectPaddingStyle; + boxModel.appendChild(boxPadding); + + boxContent = createGlobalElement("div"); + boxContent.id = "fbBoxContent"; + boxContentStyle = boxContent.style; + boxContentStyle.cssText = inspectContentStyle; + boxModel.appendChild(boxContent); + + offlineFragment.appendChild(boxModel); +}; + +var destroyBoxModelInspector = function destroyBoxModelInspector() +{ + boxModel.parentNode.removeChild(boxModel); +}; + +// ************************************************************************************************ +// Section + + + + +// ************************************************************************************************ +}}); + +// Problems in IE +// FIXED - eval return +// FIXED - addEventListener problem in IE +// FIXED doc.createRange? +// +// class reserved word +// test all honza examples in IE6 and IE7 + + +/* See license.txt for terms of usage */ + +( /** @scope s_domplate */ function() { + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +/** @class */ +FBL.DomplateTag = function DomplateTag(tagName) +{ + this.tagName = tagName; +}; + +/** + * @class + * @extends FBL.DomplateTag + */ +FBL.DomplateEmbed = function DomplateEmbed() +{ +}; + +/** + * @class + * @extends FBL.DomplateTag + */ +FBL.DomplateLoop = function DomplateLoop() +{ +}; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +var DomplateTag = FBL.DomplateTag; +var DomplateEmbed = FBL.DomplateEmbed; +var DomplateLoop = FBL.DomplateLoop; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +var womb = null; + +FBL.domplate = function() +{ + var lastSubject; + for (var i = 0; i < arguments.length; ++i) + lastSubject = lastSubject ? copyObject(lastSubject, arguments[i]) : arguments[i]; + + for (var name in lastSubject) + { + var val = lastSubject[name]; + if (isTag(val)) + val.tag.subject = lastSubject; + } + + return lastSubject; +}; + +var domplate = FBL.domplate; + +FBL.domplate.context = function(context, fn) +{ + var lastContext = domplate.lastContext; + domplate.topContext = context; + fn.apply(context); + domplate.topContext = lastContext; +}; + +FBL.TAG = function() +{ + var embed = new DomplateEmbed(); + return embed.merge(arguments); +}; + +FBL.FOR = function() +{ + var loop = new DomplateLoop(); + return loop.merge(arguments); +}; + +FBL.DomplateTag.prototype = +{ + merge: function(args, oldTag) + { + if (oldTag) + this.tagName = oldTag.tagName; + + this.context = oldTag ? oldTag.context : null; + this.subject = oldTag ? oldTag.subject : null; + this.attrs = oldTag ? copyObject(oldTag.attrs) : {}; + this.classes = oldTag ? copyObject(oldTag.classes) : {}; + this.props = oldTag ? copyObject(oldTag.props) : null; + this.listeners = oldTag ? copyArray(oldTag.listeners) : null; + this.children = oldTag ? copyArray(oldTag.children) : []; + this.vars = oldTag ? copyArray(oldTag.vars) : []; + + var attrs = args.length ? args[0] : null; + var hasAttrs = typeof(attrs) == "object" && !isTag(attrs); + + this.children = []; + + if (domplate.topContext) + this.context = domplate.topContext; + + if (args.length) + parseChildren(args, hasAttrs ? 1 : 0, this.vars, this.children); + + if (hasAttrs) + this.parseAttrs(attrs); + + return creator(this, DomplateTag); + }, + + parseAttrs: function(args) + { + for (var name in args) + { + var val = parseValue(args[name]); + readPartNames(val, this.vars); + + if (name.indexOf("on") == 0) + { + var eventName = name.substr(2); + if (!this.listeners) + this.listeners = []; + this.listeners.push(eventName, val); + } + else if (name.indexOf("_") == 0) + { + var propName = name.substr(1); + if (!this.props) + this.props = {}; + this.props[propName] = val; + } + else if (name.indexOf("$") == 0) + { + var className = name.substr(1); + if (!this.classes) + this.classes = {}; + this.classes[className] = val; + } + else + { + if (name == "class" && this.attrs.hasOwnProperty(name) ) + this.attrs[name] += " " + val; + else + this.attrs[name] = val; + } + } + }, + + compile: function() + { + if (this.renderMarkup) + return; + + this.compileMarkup(); + this.compileDOM(); + + //if (FBTrace.DBG_DOM) FBTrace.sysout("domplate renderMarkup: ", this.renderMarkup); + //if (FBTrace.DBG_DOM) FBTrace.sysout("domplate renderDOM:", this.renderDOM); + //if (FBTrace.DBG_DOM) FBTrace.sysout("domplate domArgs:", this.domArgs); + }, + + compileMarkup: function() + { + this.markupArgs = []; + var topBlock = [], topOuts = [], blocks = [], info = {args: this.markupArgs, argIndex: 0}; + + this.generateMarkup(topBlock, topOuts, blocks, info); + this.addCode(topBlock, topOuts, blocks); + + var fnBlock = ['r=(function (__code__, __context__, __in__, __out__']; + for (var i = 0; i < info.argIndex; ++i) + fnBlock.push(', s', i); + fnBlock.push(') {'); + + if (this.subject) + fnBlock.push('with (this) {'); + if (this.context) + fnBlock.push('with (__context__) {'); + fnBlock.push('with (__in__) {'); + + fnBlock.push.apply(fnBlock, blocks); + + if (this.subject) + fnBlock.push('}'); + if (this.context) + fnBlock.push('}'); + + fnBlock.push('}})'); + + function __link__(tag, code, outputs, args) + { + if (!tag || !tag.tag) + return; + + tag.tag.compile(); + + var tagOutputs = []; + var markupArgs = [code, tag.tag.context, args, tagOutputs]; + markupArgs.push.apply(markupArgs, tag.tag.markupArgs); + tag.tag.renderMarkup.apply(tag.tag.subject, markupArgs); + + outputs.push(tag); + outputs.push(tagOutputs); + } + + function __escape__(value) + { + function replaceChars(ch) + { + switch (ch) + { + case "<": + return "<"; + case ">": + return ">"; + case "&": + return "&"; + case "'": + return "'"; + case '"': + return """; + } + return "?"; + }; + return String(value).replace(/[<>&"']/g, replaceChars); + } + + function __loop__(iter, outputs, fn) + { + var iterOuts = []; + outputs.push(iterOuts); + + if (iter instanceof Array) + iter = new ArrayIterator(iter); + + try + { + while (1) + { + var value = iter.next(); + var itemOuts = [0,0]; + iterOuts.push(itemOuts); + fn.apply(this, [value, itemOuts]); + } + } + catch (exc) + { + if (exc != StopIteration) + throw exc; + } + } + + var js = fnBlock.join(""); + var r = null; + eval(js); + this.renderMarkup = r; + }, + + getVarNames: function(args) + { + if (this.vars) + args.push.apply(args, this.vars); + + for (var i = 0; i < this.children.length; ++i) + { + var child = this.children[i]; + if (isTag(child)) + child.tag.getVarNames(args); + else if (child instanceof Parts) + { + for (var i = 0; i < child.parts.length; ++i) + { + if (child.parts[i] instanceof Variable) + { + var name = child.parts[i].name; + var names = name.split("."); + args.push(names[0]); + } + } + } + } + }, + + generateMarkup: function(topBlock, topOuts, blocks, info) + { + topBlock.push(',"<', this.tagName, '"'); + + for (var name in this.attrs) + { + if (name != "class") + { + var val = this.attrs[name]; + topBlock.push(', " ', name, '=\\""'); + addParts(val, ',', topBlock, info, true); + topBlock.push(', "\\""'); + } + } + + if (this.listeners) + { + for (var i = 0; i < this.listeners.length; i += 2) + readPartNames(this.listeners[i+1], topOuts); + } + + if (this.props) + { + for (var name in this.props) + readPartNames(this.props[name], topOuts); + } + + if ( this.attrs.hasOwnProperty("class") || this.classes) + { + topBlock.push(', " class=\\""'); + if (this.attrs.hasOwnProperty("class")) + addParts(this.attrs["class"], ',', topBlock, info, true); + topBlock.push(', " "'); + for (var name in this.classes) + { + topBlock.push(', ('); + addParts(this.classes[name], '', topBlock, info); + topBlock.push(' ? "', name, '" + " " : "")'); + } + topBlock.push(', "\\""'); + } + topBlock.push(',">"'); + + this.generateChildMarkup(topBlock, topOuts, blocks, info); + topBlock.push(',"</', this.tagName, '>"'); + }, + + generateChildMarkup: function(topBlock, topOuts, blocks, info) + { + for (var i = 0; i < this.children.length; ++i) + { + var child = this.children[i]; + if (isTag(child)) + child.tag.generateMarkup(topBlock, topOuts, blocks, info); + else + addParts(child, ',', topBlock, info, true); + } + }, + + addCode: function(topBlock, topOuts, blocks) + { + if (topBlock.length) + blocks.push('__code__.push(""', topBlock.join(""), ');'); + if (topOuts.length) + blocks.push('__out__.push(', topOuts.join(","), ');'); + topBlock.splice(0, topBlock.length); + topOuts.splice(0, topOuts.length); + }, + + addLocals: function(blocks) + { + var varNames = []; + this.getVarNames(varNames); + + var map = {}; + for (var i = 0; i < varNames.length; ++i) + { + var name = varNames[i]; + if ( map.hasOwnProperty(name) ) + continue; + + map[name] = 1; + var names = name.split("."); + blocks.push('var ', names[0] + ' = ' + '__in__.' + names[0] + ';'); + } + }, + + compileDOM: function() + { + var path = []; + var blocks = []; + this.domArgs = []; + path.embedIndex = 0; + path.loopIndex = 0; + path.staticIndex = 0; + path.renderIndex = 0; + var nodeCount = this.generateDOM(path, blocks, this.domArgs); + + var fnBlock = ['r=(function (root, context, o']; + + for (var i = 0; i < path.staticIndex; ++i) + fnBlock.push(', ', 's'+i); + + for (var i = 0; i < path.renderIndex; ++i) + fnBlock.push(', ', 'd'+i); + + fnBlock.push(') {'); + for (var i = 0; i < path.loopIndex; ++i) + fnBlock.push('var l', i, ' = 0;'); + for (var i = 0; i < path.embedIndex; ++i) + fnBlock.push('var e', i, ' = 0;'); + + if (this.subject) + fnBlock.push('with (this) {'); + if (this.context) + fnBlock.push('with (context) {'); + + fnBlock.push(blocks.join("")); + + if (this.subject) + fnBlock.push('}'); + if (this.context) + fnBlock.push('}'); + + fnBlock.push('return ', nodeCount, ';'); + fnBlock.push('})'); + + function __bind__(object, fn) + { + return function(event) { return fn.apply(object, [event]); }; + } + + function __link__(node, tag, args) + { + if (!tag || !tag.tag) + return; + + tag.tag.compile(); + + var domArgs = [node, tag.tag.context, 0]; + domArgs.push.apply(domArgs, tag.tag.domArgs); + domArgs.push.apply(domArgs, args); + //if (FBTrace.DBG_DOM) FBTrace.dumpProperties("domplate__link__ domArgs:", domArgs); + return tag.tag.renderDOM.apply(tag.tag.subject, domArgs); + } + + var self = this; + function __loop__(iter, fn) + { + var nodeCount = 0; + for (var i = 0; i < iter.length; ++i) + { + iter[i][0] = i; + iter[i][1] = nodeCount; + nodeCount += fn.apply(this, iter[i]); + //if (FBTrace.DBG_DOM) FBTrace.sysout("nodeCount", nodeCount); + } + return nodeCount; + } + + function __path__(parent, offset) + { + //if (FBTrace.DBG_DOM) FBTrace.sysout("domplate __path__ offset: "+ offset+"\n"); + var root = parent; + + for (var i = 2; i < arguments.length; ++i) + { + var index = arguments[i]; + if (i == 3) + index += offset; + + if (index == -1) + parent = parent.parentNode; + else + parent = parent.childNodes[index]; + } + + //if (FBTrace.DBG_DOM) FBTrace.sysout("domplate: "+arguments[2]+", root: "+ root+", parent: "+ parent+"\n"); + return parent; + } + + var js = fnBlock.join(""); + //if (FBTrace.DBG_DOM) FBTrace.sysout(js.replace(/(\;|\{)/g, "$1\n")); + var r = null; + eval(js); + this.renderDOM = r; + }, + + generateDOM: function(path, blocks, args) + { + if (this.listeners || this.props) + this.generateNodePath(path, blocks); + + if (this.listeners) + { + for (var i = 0; i < this.listeners.length; i += 2) + { + var val = this.listeners[i+1]; + var arg = generateArg(val, path, args); + //blocks.push('node.addEventListener("', this.listeners[i], '", __bind__(this, ', arg, '), false);'); + blocks.push('addEvent(node, "', this.listeners[i], '", __bind__(this, ', arg, '), false);'); + } + } + + if (this.props) + { + for (var name in this.props) + { + var val = this.props[name]; + var arg = generateArg(val, path, args); + blocks.push('node.', name, ' = ', arg, ';'); + } + } + + this.generateChildDOM(path, blocks, args); + return 1; + }, + + generateNodePath: function(path, blocks) + { + blocks.push("var node = __path__(root, o"); + for (var i = 0; i < path.length; ++i) + blocks.push(",", path[i]); + blocks.push(");"); + }, + + generateChildDOM: function(path, blocks, args) + { + path.push(0); + for (var i = 0; i < this.children.length; ++i) + { + var child = this.children[i]; + if (isTag(child)) + path[path.length-1] += '+' + child.tag.generateDOM(path, blocks, args); + else + path[path.length-1] += '+1'; + } + path.pop(); + } +}; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +FBL.DomplateEmbed.prototype = copyObject(FBL.DomplateTag.prototype, +/** @lends FBL.DomplateEmbed.prototype */ +{ + merge: function(args, oldTag) + { + this.value = oldTag ? oldTag.value : parseValue(args[0]); + this.attrs = oldTag ? oldTag.attrs : {}; + this.vars = oldTag ? copyArray(oldTag.vars) : []; + + var attrs = args[1]; + for (var name in attrs) + { + var val = parseValue(attrs[name]); + this.attrs[name] = val; + readPartNames(val, this.vars); + } + + return creator(this, DomplateEmbed); + }, + + getVarNames: function(names) + { + if (this.value instanceof Parts) + names.push(this.value.parts[0].name); + + if (this.vars) + names.push.apply(names, this.vars); + }, + + generateMarkup: function(topBlock, topOuts, blocks, info) + { + this.addCode(topBlock, topOuts, blocks); + + blocks.push('__link__('); + addParts(this.value, '', blocks, info); + blocks.push(', __code__, __out__, {'); + + var lastName = null; + for (var name in this.attrs) + { + if (lastName) + blocks.push(','); + lastName = name; + + var val = this.attrs[name]; + blocks.push('"', name, '":'); + addParts(val, '', blocks, info); + } + + blocks.push('});'); + //this.generateChildMarkup(topBlock, topOuts, blocks, info); + }, + + generateDOM: function(path, blocks, args) + { + var embedName = 'e'+path.embedIndex++; + + this.generateNodePath(path, blocks); + + var valueName = 'd' + path.renderIndex++; + var argsName = 'd' + path.renderIndex++; + blocks.push(embedName + ' = __link__(node, ', valueName, ', ', argsName, ');'); + + return embedName; + } +}); + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +FBL.DomplateLoop.prototype = copyObject(FBL.DomplateTag.prototype, +/** @lends FBL.DomplateLoop.prototype */ +{ + merge: function(args, oldTag) + { + this.varName = oldTag ? oldTag.varName : args[0]; + this.iter = oldTag ? oldTag.iter : parseValue(args[1]); + this.vars = []; + + this.children = oldTag ? copyArray(oldTag.children) : []; + + var offset = Math.min(args.length, 2); + parseChildren(args, offset, this.vars, this.children); + + return creator(this, DomplateLoop); + }, + + getVarNames: function(names) + { + if (this.iter instanceof Parts) + names.push(this.iter.parts[0].name); + + DomplateTag.prototype.getVarNames.apply(this, [names]); + }, + + generateMarkup: function(topBlock, topOuts, blocks, info) + { + this.addCode(topBlock, topOuts, blocks); + + var iterName; + if (this.iter instanceof Parts) + { + var part = this.iter.parts[0]; + iterName = part.name; + + if (part.format) + { + for (var i = 0; i < part.format.length; ++i) + iterName = part.format[i] + "(" + iterName + ")"; + } + } + else + iterName = this.iter; + + blocks.push('__loop__.apply(this, [', iterName, ', __out__, function(', this.varName, ', __out__) {'); + this.generateChildMarkup(topBlock, topOuts, blocks, info); + this.addCode(topBlock, topOuts, blocks); + blocks.push('}]);'); + }, + + generateDOM: function(path, blocks, args) + { + var iterName = 'd'+path.renderIndex++; + var counterName = 'i'+path.loopIndex; + var loopName = 'l'+path.loopIndex++; + + if (!path.length) + path.push(-1, 0); + + var preIndex = path.renderIndex; + path.renderIndex = 0; + + var nodeCount = 0; + + var subBlocks = []; + var basePath = path[path.length-1]; + for (var i = 0; i < this.children.length; ++i) + { + path[path.length-1] = basePath+'+'+loopName+'+'+nodeCount; + + var child = this.children[i]; + if (isTag(child)) + nodeCount += '+' + child.tag.generateDOM(path, subBlocks, args); + else + nodeCount += '+1'; + } + + path[path.length-1] = basePath+'+'+loopName; + + blocks.push(loopName,' = __loop__.apply(this, [', iterName, ', function(', counterName,',',loopName); + for (var i = 0; i < path.renderIndex; ++i) + blocks.push(',d'+i); + blocks.push(') {'); + blocks.push(subBlocks.join("")); + blocks.push('return ', nodeCount, ';'); + blocks.push('}]);'); + + path.renderIndex = preIndex; + + return loopName; + } +}); + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +/** @class */ +function Variable(name, format) +{ + this.name = name; + this.format = format; +} + +/** @class */ +function Parts(parts) +{ + this.parts = parts; +} + +// ************************************************************************************************ + +function parseParts(str) +{ + var re = /\$([_A-Za-z][_A-Za-z0-9.|]*)/g; + var index = 0; + var parts = []; + + var m; + while (m = re.exec(str)) + { + var pre = str.substr(index, (re.lastIndex-m[0].length)-index); + if (pre) + parts.push(pre); + + var expr = m[1].split("|"); + parts.push(new Variable(expr[0], expr.slice(1))); + index = re.lastIndex; + } + + if (!index) + return str; + + var post = str.substr(index); + if (post) + parts.push(post); + + return new Parts(parts); +} + +function parseValue(val) +{ + return typeof(val) == 'string' ? parseParts(val) : val; +} + +function parseChildren(args, offset, vars, children) +{ + for (var i = offset; i < args.length; ++i) + { + var val = parseValue(args[i]); + children.push(val); + readPartNames(val, vars); + } +} + +function readPartNames(val, vars) +{ + if (val instanceof Parts) + { + for (var i = 0; i < val.parts.length; ++i) + { + var part = val.parts[i]; + if (part instanceof Variable) + vars.push(part.name); + } + } +} + +function generateArg(val, path, args) +{ + if (val instanceof Parts) + { + var vals = []; + for (var i = 0; i < val.parts.length; ++i) + { + var part = val.parts[i]; + if (part instanceof Variable) + { + var varName = 'd'+path.renderIndex++; + if (part.format) + { + for (var j = 0; j < part.format.length; ++j) + varName = part.format[j] + '(' + varName + ')'; + } + + vals.push(varName); + } + else + vals.push('"'+part.replace(/"/g, '\\"')+'"'); + } + + return vals.join('+'); + } + else + { + args.push(val); + return 's' + path.staticIndex++; + } +} + +function addParts(val, delim, block, info, escapeIt) +{ + var vals = []; + if (val instanceof Parts) + { + for (var i = 0; i < val.parts.length; ++i) + { + var part = val.parts[i]; + if (part instanceof Variable) + { + var partName = part.name; + if (part.format) + { + for (var j = 0; j < part.format.length; ++j) + partName = part.format[j] + "(" + partName + ")"; + } + + if (escapeIt) + vals.push("__escape__(" + partName + ")"); + else + vals.push(partName); + } + else + vals.push('"'+ part + '"'); + } + } + else if (isTag(val)) + { + info.args.push(val); + vals.push('s'+info.argIndex++); + } + else + vals.push('"'+ val + '"'); + + var parts = vals.join(delim); + if (parts) + block.push(delim, parts); +} + +function isTag(obj) +{ + return (typeof(obj) == "function" || obj instanceof Function) && !!obj.tag; +} + +function creator(tag, cons) +{ + var fn = new Function( + "var tag = arguments.callee.tag;" + + "var cons = arguments.callee.cons;" + + "var newTag = new cons();" + + "return newTag.merge(arguments, tag);"); + + fn.tag = tag; + fn.cons = cons; + extend(fn, Renderer); + + return fn; +} + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +function copyArray(oldArray) +{ + var ary = []; + if (oldArray) + for (var i = 0; i < oldArray.length; ++i) + ary.push(oldArray[i]); + return ary; +} + +function copyObject(l, r) +{ + var m = {}; + extend(m, l); + extend(m, r); + return m; +} + +function extend(l, r) +{ + for (var n in r) + l[n] = r[n]; +} + +function addEvent(object, name, handler) +{ + if (document.all) + object.attachEvent("on"+name, handler); + else + object.addEventListener(name, handler, false); +} + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +/** @class */ +function ArrayIterator(array) +{ + var index = -1; + + this.next = function() + { + if (++index >= array.length) + throw StopIteration; + + return array[index]; + }; +} + +/** @class */ +function StopIteration() {} + +FBL.$break = function() +{ + throw StopIteration; +}; + +// ************************************************************************************************ + +/** @namespace */ +var Renderer = +{ + renderHTML: function(args, outputs, self) + { + var code = []; + var markupArgs = [code, this.tag.context, args, outputs]; + markupArgs.push.apply(markupArgs, this.tag.markupArgs); + this.tag.renderMarkup.apply(self ? self : this.tag.subject, markupArgs); + return code.join(""); + }, + + insertRows: function(args, before, self) + { + this.tag.compile(); + + var outputs = []; + var html = this.renderHTML(args, outputs, self); + + var doc = before.ownerDocument; + var div = doc.createElement("div"); + div.innerHTML = "<table><tbody>"+html+"</tbody></table>"; + + var tbody = div.firstChild.firstChild; + var parent = before.tagName == "TR" ? before.parentNode : before; + var after = before.tagName == "TR" ? before.nextSibling : null; + + var firstRow = tbody.firstChild, lastRow; + while (tbody.firstChild) + { + lastRow = tbody.firstChild; + if (after) + parent.insertBefore(lastRow, after); + else + parent.appendChild(lastRow); + } + + var offset = 0; + if (before.tagName == "TR") + { + var node = firstRow.parentNode.firstChild; + for (; node && node != firstRow; node = node.nextSibling) + ++offset; + } + + var domArgs = [firstRow, this.tag.context, offset]; + domArgs.push.apply(domArgs, this.tag.domArgs); + domArgs.push.apply(domArgs, outputs); + + this.tag.renderDOM.apply(self ? self : this.tag.subject, domArgs); + return [firstRow, lastRow]; + }, + + insertBefore: function(args, before, self) + { + return this.insertNode(args, before.ownerDocument, before, false, self); + }, + + insertAfter: function(args, after, self) + { + return this.insertNode(args, after.ownerDocument, after, true, self); + }, + + insertNode: function(args, doc, element, isAfter, self) + { + if (!args) + args = {}; + + this.tag.compile(); + + var outputs = []; + var html = this.renderHTML(args, outputs, self); + + //if (FBTrace.DBG_DOM) + // FBTrace.sysout("domplate.insertNode html: "+html+"\n"); + + var doc = element.ownerDocument; + if (!womb || womb.ownerDocument != doc) + womb = doc.createElement("div"); + + womb.innerHTML = html; + + var root = womb.firstChild; + if (isAfter) + { + while (womb.firstChild) + if (element.nextSibling) + element.parentNode.insertBefore(womb.firstChild, element.nextSibling); + else + element.parentNode.appendChild(womb.firstChild); + } + else + { + while (womb.lastChild) + element.parentNode.insertBefore(womb.lastChild, element); + } + + var domArgs = [root, this.tag.context, 0]; + domArgs.push.apply(domArgs, this.tag.domArgs); + domArgs.push.apply(domArgs, outputs); + + //if (FBTrace.DBG_DOM) + // FBTrace.sysout("domplate.insertNode domArgs:", domArgs); + this.tag.renderDOM.apply(self ? self : this.tag.subject, domArgs); + + return root; + }, + /**/ + + /* + insertAfter: function(args, before, self) + { + this.tag.compile(); + + var outputs = []; + var html = this.renderHTML(args, outputs, self); + + var doc = before.ownerDocument; + if (!womb || womb.ownerDocument != doc) + womb = doc.createElement("div"); + + womb.innerHTML = html; + + var root = womb.firstChild; + while (womb.firstChild) + if (before.nextSibling) + before.parentNode.insertBefore(womb.firstChild, before.nextSibling); + else + before.parentNode.appendChild(womb.firstChild); + + var domArgs = [root, this.tag.context, 0]; + domArgs.push.apply(domArgs, this.tag.domArgs); + domArgs.push.apply(domArgs, outputs); + + this.tag.renderDOM.apply(self ? self : (this.tag.subject ? this.tag.subject : null), + domArgs); + + return root; + }, + /**/ + + replace: function(args, parent, self) + { + this.tag.compile(); + + var outputs = []; + var html = this.renderHTML(args, outputs, self); + + var root; + if (parent.nodeType == 1) + { + parent.innerHTML = html; + root = parent.firstChild; + } + else + { + if (!parent || parent.nodeType != 9) + parent = document; + + if (!womb || womb.ownerDocument != parent) + womb = parent.createElement("div"); + womb.innerHTML = html; + + root = womb.firstChild; + //womb.removeChild(root); + } + + var domArgs = [root, this.tag.context, 0]; + domArgs.push.apply(domArgs, this.tag.domArgs); + domArgs.push.apply(domArgs, outputs); + this.tag.renderDOM.apply(self ? self : this.tag.subject, domArgs); + + return root; + }, + + append: function(args, parent, self) + { + this.tag.compile(); + + var outputs = []; + var html = this.renderHTML(args, outputs, self); + //if (FBTrace.DBG_DOM) FBTrace.sysout("domplate.append html: "+html+"\n"); + + if (!womb || womb.ownerDocument != parent.ownerDocument) + womb = parent.ownerDocument.createElement("div"); + womb.innerHTML = html; + + // TODO: xxxpedro domplate port to Firebug + var root = womb.firstChild; + while (womb.firstChild) + parent.appendChild(womb.firstChild); + + // clearing element reference to avoid reference error in IE8 when switching contexts + womb = null; + + var domArgs = [root, this.tag.context, 0]; + domArgs.push.apply(domArgs, this.tag.domArgs); + domArgs.push.apply(domArgs, outputs); + + //if (FBTrace.DBG_DOM) FBTrace.dumpProperties("domplate append domArgs:", domArgs); + this.tag.renderDOM.apply(self ? self : this.tag.subject, domArgs); + + return root; + } +}; + +// ************************************************************************************************ + +function defineTags() +{ + for (var i = 0; i < arguments.length; ++i) + { + var tagName = arguments[i]; + var fn = new Function("var newTag = new arguments.callee.DomplateTag('"+tagName+"'); return newTag.merge(arguments);"); + fn.DomplateTag = DomplateTag; + + var fnName = tagName.toUpperCase(); + FBL[fnName] = fn; + } +} + +defineTags( + "a", "button", "br", "canvas", "code", "col", "colgroup", "div", "fieldset", "form", "h1", "h2", "h3", "hr", + "img", "input", "label", "legend", "li", "ol", "optgroup", "option", "p", "pre", "select", + "span", "strong", "table", "tbody", "td", "textarea", "tfoot", "th", "thead", "tr", "tt", "ul", "iframe" +); + +})(); + + +/* See license.txt for terms of usage */ + +var FirebugReps = FBL.ns(function() { with (FBL) { + + +// ************************************************************************************************ +// Common Tags + +var OBJECTBOX = this.OBJECTBOX = + SPAN({"class": "objectBox objectBox-$className"}); + +var OBJECTBLOCK = this.OBJECTBLOCK = + DIV({"class": "objectBox objectBox-$className"}); + +var OBJECTLINK = this.OBJECTLINK = isIE6 ? // IE6 object link representation + A({ + "class": "objectLink objectLink-$className a11yFocus", + href: "javascript:void(0)", + // workaround to show XPath (a better approach would use the tooltip on mouseover, + // so the XPath information would be calculated dynamically, but we need to create + // a tooltip class/wrapper around Menu or InfoTip) + title: "$object|FBL.getElementXPath", + _repObject: "$object" + }) + : // Other browsers + A({ + "class": "objectLink objectLink-$className a11yFocus", + // workaround to show XPath (a better approach would use the tooltip on mouseover, + // so the XPath information would be calculated dynamically, but we need to create + // a tooltip class/wrapper around Menu or InfoTip) + title: "$object|FBL.getElementXPath", + _repObject: "$object" + }); + + +// ************************************************************************************************ + +this.Undefined = domplate(Firebug.Rep, +{ + tag: OBJECTBOX("undefined"), + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "undefined", + + supportsObject: function(object, type) + { + return type == "undefined"; + } +}); + +// ************************************************************************************************ + +this.Null = domplate(Firebug.Rep, +{ + tag: OBJECTBOX("null"), + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "null", + + supportsObject: function(object, type) + { + return object == null; + } +}); + +// ************************************************************************************************ + +this.Nada = domplate(Firebug.Rep, +{ + tag: SPAN(""), + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "nada" +}); + +// ************************************************************************************************ + +this.Number = domplate(Firebug.Rep, +{ + tag: OBJECTBOX("$object"), + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "number", + + supportsObject: function(object, type) + { + return type == "boolean" || type == "number"; + } +}); + +// ************************************************************************************************ + +this.String = domplate(Firebug.Rep, +{ + tag: OBJECTBOX(""$object""), + + shortTag: OBJECTBOX(""$object|cropString""), + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "string", + + supportsObject: function(object, type) + { + return type == "string"; + } +}); + +// ************************************************************************************************ + +this.Text = domplate(Firebug.Rep, +{ + tag: OBJECTBOX("$object"), + + shortTag: OBJECTBOX("$object|cropString"), + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "text" +}); + +// ************************************************************************************************ + +this.Caption = domplate(Firebug.Rep, +{ + tag: SPAN({"class": "caption"}, "$object") +}); + +// ************************************************************************************************ + +this.Warning = domplate(Firebug.Rep, +{ + tag: DIV({"class": "warning focusRow", role : 'listitem'}, "$object|STR") +}); + +// ************************************************************************************************ + +this.Func = domplate(Firebug.Rep, +{ + tag: + OBJECTLINK("$object|summarizeFunction"), + + summarizeFunction: function(fn) + { + var fnRegex = /function ([^(]+\([^)]*\)) \{/; + var fnText = safeToString(fn); + + var m = fnRegex.exec(fnText); + return m ? m[1] : "function()"; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + copySource: function(fn) + { + copyToClipboard(safeToString(fn)); + }, + + monitor: function(fn, script, monitored) + { + if (monitored) + Firebug.Debugger.unmonitorScript(fn, script, "monitor"); + else + Firebug.Debugger.monitorScript(fn, script, "monitor"); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "function", + + supportsObject: function(object, type) + { + return isFunction(object); + }, + + inspectObject: function(fn, context) + { + var sourceLink = findSourceForFunction(fn, context); + if (sourceLink) + Firebug.chrome.select(sourceLink); + if (FBTrace.DBG_FUNCTION_NAME) + FBTrace.sysout("reps.function.inspectObject selected sourceLink is ", sourceLink); + }, + + getTooltip: function(fn, context) + { + var script = findScriptForFunctionInContext(context, fn); + if (script) + return $STRF("Line", [normalizeURL(script.fileName), script.baseLineNumber]); + else + if (fn.toString) + return fn.toString(); + }, + + getTitle: function(fn, context) + { + var name = fn.name ? fn.name : "function"; + return name + "()"; + }, + + getContextMenuItems: function(fn, target, context, script) + { + if (!script) + script = findScriptForFunctionInContext(context, fn); + if (!script) + return; + + var scriptInfo = getSourceFileAndLineByScript(context, script); + var monitored = scriptInfo ? fbs.isMonitored(scriptInfo.sourceFile.href, scriptInfo.lineNo) : false; + + var name = script ? getFunctionName(script, context) : fn.name; + return [ + {label: "CopySource", command: bindFixed(this.copySource, this, fn) }, + "-", + {label: $STRF("ShowCallsInConsole", [name]), nol10n: true, + type: "checkbox", checked: monitored, + command: bindFixed(this.monitor, this, fn, script, monitored) } + ]; + } +}); + +// ************************************************************************************************ +/* +this.jsdScript = domplate(Firebug.Rep, +{ + copySource: function(script) + { + var fn = script.functionObject.getWrappedValue(); + return FirebugReps.Func.copySource(fn); + }, + + monitor: function(fn, script, monitored) + { + fn = script.functionObject.getWrappedValue(); + return FirebugReps.Func.monitor(fn, script, monitored); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "jsdScript", + inspectable: false, + + supportsObject: function(object, type) + { + return object instanceof jsdIScript; + }, + + inspectObject: function(script, context) + { + var sourceLink = getSourceLinkForScript(script, context); + if (sourceLink) + Firebug.chrome.select(sourceLink); + }, + + getRealObject: function(script, context) + { + return script; + }, + + getTooltip: function(script) + { + return $STRF("jsdIScript", [script.tag]); + }, + + getTitle: function(script, context) + { + var fn = script.functionObject.getWrappedValue(); + return FirebugReps.Func.getTitle(fn, context); + }, + + getContextMenuItems: function(script, target, context) + { + var fn = script.functionObject.getWrappedValue(); + + var scriptInfo = getSourceFileAndLineByScript(context, script); + var monitored = scriptInfo ? fbs.isMonitored(scriptInfo.sourceFile.href, scriptInfo.lineNo) : false; + + var name = getFunctionName(script, context); + + return [ + {label: "CopySource", command: bindFixed(this.copySource, this, script) }, + "-", + {label: $STRF("ShowCallsInConsole", [name]), nol10n: true, + type: "checkbox", checked: monitored, + command: bindFixed(this.monitor, this, fn, script, monitored) } + ]; + } +}); +/**/ +//************************************************************************************************ + +this.Obj = domplate(Firebug.Rep, +{ + tag: + OBJECTLINK( + SPAN({"class": "objectTitle"}, "$object|getTitle "), + + SPAN({"class": "objectProps"}, + SPAN({"class": "objectLeftBrace", role: "presentation"}, "{"), + FOR("prop", "$object|propIterator", + SPAN({"class": "objectPropName", role: "presentation"}, "$prop.name"), + SPAN({"class": "objectEqual", role: "presentation"}, "$prop.equal"), + TAG("$prop.tag", {object: "$prop.object"}), + SPAN({"class": "objectComma", role: "presentation"}, "$prop.delim") + ), + SPAN({"class": "objectRightBrace"}, "}") + ) + ), + + propNumberTag: + SPAN({"class": "objectProp-number"}, "$object"), + + propStringTag: + SPAN({"class": "objectProp-string"}, ""$object""), + + propObjectTag: + SPAN({"class": "objectProp-object"}, "$object"), + + propIterator: function (object) + { + ///Firebug.ObjectShortIteratorMax; + var maxLength = 55; // default max length for long representation + + if (!object) + return []; + + var props = []; + var length = 0; + + var numProperties = 0; + var numPropertiesShown = 0; + var maxLengthReached = false; + + var lib = this; + + var propRepsMap = + { + "boolean": this.propNumberTag, + "number": this.propNumberTag, + "string": this.propStringTag, + "object": this.propObjectTag + }; + + try + { + var title = Firebug.Rep.getTitle(object); + length += title.length; + + for (var name in object) + { + var value; + try + { + value = object[name]; + } + catch (exc) + { + continue; + } + + var type = typeof(value); + if (type == "boolean" || + type == "number" || + (type == "string" && value) || + (type == "object" && value && value.toString)) + { + var tag = propRepsMap[type]; + + var value = (type == "object") ? + Firebug.getRep(value).getTitle(value) : + value + ""; + + length += name.length + value.length + 4; + + if (length <= maxLength) + { + props.push({ + tag: tag, + name: name, + object: value, + equal: "=", + delim: ", " + }); + + numPropertiesShown++; + } + else + maxLengthReached = true; + + } + + numProperties++; + + if (maxLengthReached && numProperties > numPropertiesShown) + break; + } + + if (numProperties > numPropertiesShown) + { + props.push({ + object: "...", //xxxHonza localization + tag: FirebugReps.Caption.tag, + name: "", + equal:"", + delim:"" + }); + } + else if (props.length > 0) + { + props[props.length-1].delim = ''; + } + } + catch (exc) + { + // Sometimes we get exceptions when trying to read from certain objects, like + // StorageList, but don't let that gum up the works + // XXXjjb also History.previous fails because object is a web-page object which does not have + // permission to read the history + } + return props; + }, + + fb_1_6_propIterator: function (object, max) + { + max = max || 3; + if (!object) + return []; + + var props = []; + var len = 0, count = 0; + + try + { + for (var name in object) + { + var value; + try + { + value = object[name]; + } + catch (exc) + { + continue; + } + + var t = typeof(value); + if (t == "boolean" || t == "number" || (t == "string" && value) + || (t == "object" && value && value.toString)) + { + var rep = Firebug.getRep(value); + var tag = rep.shortTag || rep.tag; + if (t == "object") + { + value = rep.getTitle(value); + tag = rep.titleTag; + } + count++; + if (count <= max) + props.push({tag: tag, name: name, object: value, equal: "=", delim: ", "}); + else + break; + } + } + if (count > max) + { + props[Math.max(1,max-1)] = { + object: "more...", //xxxHonza localization + tag: FirebugReps.Caption.tag, + name: "", + equal:"", + delim:"" + }; + } + else if (props.length > 0) + { + props[props.length-1].delim = ''; + } + } + catch (exc) + { + // Sometimes we get exceptions when trying to read from certain objects, like + // StorageList, but don't let that gum up the works + // XXXjjb also History.previous fails because object is a web-page object which does not have + // permission to read the history + } + return props; + }, + + /* + propIterator: function (object) + { + if (!object) + return []; + + var props = []; + var len = 0; + + try + { + for (var name in object) + { + var val; + try + { + val = object[name]; + } + catch (exc) + { + continue; + } + + var t = typeof val; + if (t == "boolean" || t == "number" || (t == "string" && val) + || (t == "object" && !isFunction(val) && val && val.toString)) + { + var title = (t == "object") + ? Firebug.getRep(val).getTitle(val) + : val+""; + + len += name.length + title.length + 1; + if (len < 50) + props.push({name: name, value: title}); + else + break; + } + } + } + catch (exc) + { + // Sometimes we get exceptions when trying to read from certain objects, like + // StorageList, but don't let that gum up the works + // XXXjjb also History.previous fails because object is a web-page object which does not have + // permission to read the history + } + + return props; + }, + /**/ + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "object", + + supportsObject: function(object, type) + { + return true; + } +}); + + +// ************************************************************************************************ + +this.Arr = domplate(Firebug.Rep, +{ + tag: + OBJECTBOX({_repObject: "$object"}, + SPAN({"class": "arrayLeftBracket", role : "presentation"}, "["), + FOR("item", "$object|arrayIterator", + TAG("$item.tag", {object: "$item.object"}), + SPAN({"class": "arrayComma", role : "presentation"}, "$item.delim") + ), + SPAN({"class": "arrayRightBracket", role : "presentation"}, "]") + ), + + shortTag: + OBJECTBOX({_repObject: "$object"}, + SPAN({"class": "arrayLeftBracket", role : "presentation"}, "["), + FOR("item", "$object|shortArrayIterator", + TAG("$item.tag", {object: "$item.object"}), + SPAN({"class": "arrayComma", role : "presentation"}, "$item.delim") + ), + // TODO: xxxpedro - confirm this on Firebug + //FOR("prop", "$object|shortPropIterator", + // " $prop.name=", + // SPAN({"class": "objectPropValue"}, "$prop.value|cropString") + //), + SPAN({"class": "arrayRightBracket"}, "]") + ), + + arrayIterator: function(array) + { + var items = []; + for (var i = 0; i < array.length; ++i) + { + var value = array[i]; + var rep = Firebug.getRep(value); + var tag = rep.shortTag ? rep.shortTag : rep.tag; + var delim = (i == array.length-1 ? "" : ", "); + + items.push({object: value, tag: tag, delim: delim}); + } + + return items; + }, + + shortArrayIterator: function(array) + { + var items = []; + for (var i = 0; i < array.length && i < 3; ++i) + { + var value = array[i]; + var rep = Firebug.getRep(value); + var tag = rep.shortTag ? rep.shortTag : rep.tag; + var delim = (i == array.length-1 ? "" : ", "); + + items.push({object: value, tag: tag, delim: delim}); + } + + if (array.length > 3) + items.push({object: (array.length-3) + " more...", tag: FirebugReps.Caption.tag, delim: ""}); + + return items; + }, + + shortPropIterator: this.Obj.propIterator, + + getItemIndex: function(child) + { + var arrayIndex = 0; + for (child = child.previousSibling; child; child = child.previousSibling) + { + if (child.repObject) + ++arrayIndex; + } + return arrayIndex; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "array", + + supportsObject: function(object) + { + return this.isArray(object); + }, + + // http://code.google.com/p/fbug/issues/detail?id=874 + // BEGIN Yahoo BSD Source (modified here) YAHOO.lang.isArray, YUI 2.2.2 June 2007 + isArray: function(obj) { + try { + if (!obj) + return false; + else if (isIE && !isFunction(obj) && typeof obj == "object" && isFinite(obj.length) && obj.nodeType != 8) + return true; + else if (isFinite(obj.length) && isFunction(obj.splice)) + return true; + else if (isFinite(obj.length) && isFunction(obj.callee)) // arguments + return true; + else if (instanceOf(obj, "HTMLCollection")) + return true; + else if (instanceOf(obj, "NodeList")) + return true; + else + return false; + } + catch(exc) + { + if (FBTrace.DBG_ERRORS) + { + FBTrace.sysout("isArray FAILS:", exc); /* Something weird: without the try/catch, OOM, with no exception?? */ + FBTrace.sysout("isArray Fails on obj", obj); + } + } + + return false; + }, + // END Yahoo BSD SOURCE See license below. + + getTitle: function(object, context) + { + return "[" + object.length + "]"; + } +}); + +// ************************************************************************************************ + +this.Property = domplate(Firebug.Rep, +{ + supportsObject: function(object) + { + return object instanceof Property; + }, + + getRealObject: function(prop, context) + { + return prop.object[prop.name]; + }, + + getTitle: function(prop, context) + { + return prop.name; + } +}); + +// ************************************************************************************************ + +this.NetFile = domplate(this.Obj, +{ + supportsObject: function(object) + { + return object instanceof Firebug.NetFile; + }, + + browseObject: function(file, context) + { + openNewTab(file.href); + return true; + }, + + getRealObject: function(file, context) + { + return null; + } +}); + +// ************************************************************************************************ + +this.Except = domplate(Firebug.Rep, +{ + tag: + OBJECTBOX({_repObject: "$object"}, "$object.message"), + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "exception", + + supportsObject: function(object) + { + return object instanceof ErrorCopy; + } +}); + + +// ************************************************************************************************ + +this.Element = domplate(Firebug.Rep, +{ + tag: + OBJECTLINK( + "<", + SPAN({"class": "nodeTag"}, "$object.nodeName|toLowerCase"), + FOR("attr", "$object|attrIterator", + " $attr.nodeName="", SPAN({"class": "nodeValue"}, "$attr.nodeValue"), """ + ), + ">" + ), + + shortTag: + OBJECTLINK( + SPAN({"class": "$object|getVisible"}, + SPAN({"class": "selectorTag"}, "$object|getSelectorTag"), + SPAN({"class": "selectorId"}, "$object|getSelectorId"), + SPAN({"class": "selectorClass"}, "$object|getSelectorClass"), + SPAN({"class": "selectorValue"}, "$object|getValue") + ) + ), + + getVisible: function(elt) + { + return isVisible(elt) ? "" : "selectorHidden"; + }, + + getSelectorTag: function(elt) + { + return elt.nodeName.toLowerCase(); + }, + + getSelectorId: function(elt) + { + return elt.id ? "#" + elt.id : ""; + }, + + getSelectorClass: function(elt) + { + return elt.className ? "." + elt.className.split(" ")[0] : ""; + }, + + getValue: function(elt) + { + // TODO: xxxpedro + return ""; + var value; + if (elt instanceof HTMLImageElement) + value = getFileName(elt.src); + else if (elt instanceof HTMLAnchorElement) + value = getFileName(elt.href); + else if (elt instanceof HTMLInputElement) + value = elt.value; + else if (elt instanceof HTMLFormElement) + value = getFileName(elt.action); + else if (elt instanceof HTMLScriptElement) + value = getFileName(elt.src); + + return value ? " " + cropString(value, 20) : ""; + }, + + attrIterator: function(elt) + { + var attrs = []; + var idAttr, classAttr; + if (elt.attributes) + { + for (var i = 0; i < elt.attributes.length; ++i) + { + var attr = elt.attributes[i]; + + // we must check if the attribute is specified otherwise IE will show them + if (!attr.specified || attr.nodeName && attr.nodeName.indexOf("firebug-") != -1) + continue; + else if (attr.nodeName == "id") + idAttr = attr; + else if (attr.nodeName == "class") + classAttr = attr; + else if (attr.nodeName == "style") + attrs.push({ + nodeName: attr.nodeName, + nodeValue: attr.nodeValue || + // IE won't recognize the attr.nodeValue of <style> nodes ... + // and will return CSS property names in upper case, so we need to convert them + elt.style.cssText.replace(/([^\s]+)\s*:/g, + function(m,g){return g.toLowerCase()+":"}) + }); + else + attrs.push(attr); + } + } + if (classAttr) + attrs.splice(0, 0, classAttr); + if (idAttr) + attrs.splice(0, 0, idAttr); + + return attrs; + }, + + shortAttrIterator: function(elt) + { + var attrs = []; + if (elt.attributes) + { + for (var i = 0; i < elt.attributes.length; ++i) + { + var attr = elt.attributes[i]; + if (attr.nodeName == "id" || attr.nodeName == "class") + attrs.push(attr); + } + } + + return attrs; + }, + + getHidden: function(elt) + { + return isVisible(elt) ? "" : "nodeHidden"; + }, + + getXPath: function(elt) + { + return getElementTreeXPath(elt); + }, + + // TODO: xxxpedro remove this? + getNodeText: function(element) + { + var text = element.textContent; + if (Firebug.showFullTextNodes) + return text; + else + return cropString(text, 50); + }, + /**/ + + getNodeTextGroups: function(element) + { + var text = element.textContent; + if (!Firebug.showFullTextNodes) + { + text=cropString(text,50); + } + + var escapeGroups=[]; + + if (Firebug.showTextNodesWithWhitespace) + escapeGroups.push({ + 'group': 'whitespace', + 'class': 'nodeWhiteSpace', + 'extra': { + '\t': '_Tab', + '\n': '_Para', + ' ' : '_Space' + } + }); + if (Firebug.showTextNodesWithEntities) + escapeGroups.push({ + 'group':'text', + 'class':'nodeTextEntity', + 'extra':{} + }); + + if (escapeGroups.length) + return escapeGroupsForEntities(text, escapeGroups); + else + return [{str:text,'class':'',extra:''}]; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + copyHTML: function(elt) + { + var html = getElementXML(elt); + copyToClipboard(html); + }, + + copyInnerHTML: function(elt) + { + copyToClipboard(elt.innerHTML); + }, + + copyXPath: function(elt) + { + var xpath = getElementXPath(elt); + copyToClipboard(xpath); + }, + + persistor: function(context, xpath) + { + var elts = xpath + ? getElementsByXPath(context.window.document, xpath) + : null; + + return elts && elts.length ? elts[0] : null; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "element", + + supportsObject: function(object) + { + //return object instanceof Element || object.nodeType == 1 && typeof object.nodeName == "string"; + return instanceOf(object, "Element"); + }, + + browseObject: function(elt, context) + { + var tag = elt.nodeName.toLowerCase(); + if (tag == "script") + openNewTab(elt.src); + else if (tag == "link") + openNewTab(elt.href); + else if (tag == "a") + openNewTab(elt.href); + else if (tag == "img") + openNewTab(elt.src); + + return true; + }, + + persistObject: function(elt, context) + { + var xpath = getElementXPath(elt); + + return bind(this.persistor, top, xpath); + }, + + getTitle: function(element, context) + { + return getElementCSSSelector(element); + }, + + getTooltip: function(elt) + { + return this.getXPath(elt); + }, + + getContextMenuItems: function(elt, target, context) + { + var monitored = areEventsMonitored(elt, null, context); + + return [ + {label: "CopyHTML", command: bindFixed(this.copyHTML, this, elt) }, + {label: "CopyInnerHTML", command: bindFixed(this.copyInnerHTML, this, elt) }, + {label: "CopyXPath", command: bindFixed(this.copyXPath, this, elt) }, + "-", + {label: "ShowEventsInConsole", type: "checkbox", checked: monitored, + command: bindFixed(toggleMonitorEvents, FBL, elt, null, monitored, context) }, + "-", + {label: "ScrollIntoView", command: bindFixed(elt.scrollIntoView, elt) } + ]; + } +}); + +// ************************************************************************************************ + +this.TextNode = domplate(Firebug.Rep, +{ + tag: + OBJECTLINK( + "<", + SPAN({"class": "nodeTag"}, "TextNode"), + " textContent="", SPAN({"class": "nodeValue"}, "$object.textContent|cropString"), """, + ">" + ), + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "textNode", + + supportsObject: function(object) + { + return object instanceof Text; + } +}); + +// ************************************************************************************************ + +this.Document = domplate(Firebug.Rep, +{ + tag: + OBJECTLINK("Document ", SPAN({"class": "objectPropValue"}, "$object|getLocation")), + + getLocation: function(doc) + { + return doc.location ? getFileName(doc.location.href) : ""; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "object", + + supportsObject: function(object) + { + //return object instanceof Document || object instanceof XMLDocument; + return instanceOf(object, "Document"); + }, + + browseObject: function(doc, context) + { + openNewTab(doc.location.href); + return true; + }, + + persistObject: function(doc, context) + { + return this.persistor; + }, + + persistor: function(context) + { + return context.window.document; + }, + + getTitle: function(win, context) + { + return "document"; + }, + + getTooltip: function(doc) + { + return doc.location.href; + } +}); + +// ************************************************************************************************ + +this.StyleSheet = domplate(Firebug.Rep, +{ + tag: + OBJECTLINK("StyleSheet ", SPAN({"class": "objectPropValue"}, "$object|getLocation")), + + getLocation: function(styleSheet) + { + return getFileName(styleSheet.href); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + copyURL: function(styleSheet) + { + copyToClipboard(styleSheet.href); + }, + + openInTab: function(styleSheet) + { + openNewTab(styleSheet.href); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "object", + + supportsObject: function(object) + { + //return object instanceof CSSStyleSheet; + return instanceOf(object, "CSSStyleSheet"); + }, + + browseObject: function(styleSheet, context) + { + openNewTab(styleSheet.href); + return true; + }, + + persistObject: function(styleSheet, context) + { + return bind(this.persistor, top, styleSheet.href); + }, + + getTooltip: function(styleSheet) + { + return styleSheet.href; + }, + + getContextMenuItems: function(styleSheet, target, context) + { + return [ + {label: "CopyLocation", command: bindFixed(this.copyURL, this, styleSheet) }, + "-", + {label: "OpenInTab", command: bindFixed(this.openInTab, this, styleSheet) } + ]; + }, + + persistor: function(context, href) + { + return getStyleSheetByHref(href, context); + } +}); + +// ************************************************************************************************ + +this.Window = domplate(Firebug.Rep, +{ + tag: + OBJECTLINK("Window ", SPAN({"class": "objectPropValue"}, "$object|getLocation")), + + getLocation: function(win) + { + try + { + return (win && win.location && !win.closed) ? getFileName(win.location.href) : ""; + } + catch (exc) + { + if (FBTrace.DBG_ERRORS) + FBTrace.sysout("reps.Window window closed?"); + } + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "object", + + supportsObject: function(object) + { + return instanceOf(object, "Window"); + }, + + browseObject: function(win, context) + { + openNewTab(win.location.href); + return true; + }, + + persistObject: function(win, context) + { + return this.persistor; + }, + + persistor: function(context) + { + return context.window; + }, + + getTitle: function(win, context) + { + return "window"; + }, + + getTooltip: function(win) + { + if (win && !win.closed) + return win.location.href; + } +}); + +// ************************************************************************************************ + +this.Event = domplate(Firebug.Rep, +{ + tag: TAG("$copyEventTag", {object: "$object|copyEvent"}), + + copyEventTag: + OBJECTLINK("$object|summarizeEvent"), + + summarizeEvent: function(event) + { + var info = [event.type, ' ']; + + var eventFamily = getEventFamily(event.type); + if (eventFamily == "mouse") + info.push("clientX=", event.clientX, ", clientY=", event.clientY); + else if (eventFamily == "key") + info.push("charCode=", event.charCode, ", keyCode=", event.keyCode); + + return info.join(""); + }, + + copyEvent: function(event) + { + return new EventCopy(event); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "object", + + supportsObject: function(object) + { + //return object instanceof Event || object instanceof EventCopy; + return instanceOf(object, "Event") || instanceOf(object, "EventCopy"); + }, + + getTitle: function(event, context) + { + return "Event " + event.type; + } +}); + +// ************************************************************************************************ + +this.SourceLink = domplate(Firebug.Rep, +{ + tag: + OBJECTLINK({$collapsed: "$object|hideSourceLink"}, "$object|getSourceLinkTitle"), + + hideSourceLink: function(sourceLink) + { + return sourceLink ? sourceLink.href.indexOf("XPCSafeJSObjectWrapper") != -1 : true; + }, + + getSourceLinkTitle: function(sourceLink) + { + if (!sourceLink) + return ""; + + try + { + var fileName = getFileName(sourceLink.href); + fileName = decodeURIComponent(fileName); + fileName = cropString(fileName, 17); + } + catch(exc) + { + if (FBTrace.DBG_ERRORS) + FBTrace.sysout("reps.getSourceLinkTitle decodeURIComponent fails for \'"+fileName+"\': "+exc, exc); + } + + return typeof sourceLink.line == "number" ? + fileName + " (line " + sourceLink.line + ")" : + fileName; + + // TODO: xxxpedro + //return $STRF("Line", [fileName, sourceLink.line]); + }, + + copyLink: function(sourceLink) + { + copyToClipboard(sourceLink.href); + }, + + openInTab: function(sourceLink) + { + openNewTab(sourceLink.href); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "sourceLink", + + supportsObject: function(object) + { + return object instanceof SourceLink; + }, + + getTooltip: function(sourceLink) + { + return decodeURI(sourceLink.href); + }, + + inspectObject: function(sourceLink, context) + { + if (sourceLink.type == "js") + { + var scriptFile = getSourceFileByHref(sourceLink.href, context); + if (scriptFile) + return Firebug.chrome.select(sourceLink); + } + else if (sourceLink.type == "css") + { + // If an object is defined, treat it as the highest priority for + // inspect actions + if (sourceLink.object) { + Firebug.chrome.select(sourceLink.object); + return; + } + + var stylesheet = getStyleSheetByHref(sourceLink.href, context); + if (stylesheet) + { + var ownerNode = stylesheet.ownerNode; + if (ownerNode) + { + Firebug.chrome.select(sourceLink, "html"); + return; + } + + var panel = context.getPanel("stylesheet"); + if (panel && panel.getRuleByLine(stylesheet, sourceLink.line)) + return Firebug.chrome.select(sourceLink); + } + } + + // Fallback is to just open the view-source window on the file + viewSource(sourceLink.href, sourceLink.line); + }, + + browseObject: function(sourceLink, context) + { + openNewTab(sourceLink.href); + return true; + }, + + getContextMenuItems: function(sourceLink, target, context) + { + return [ + {label: "CopyLocation", command: bindFixed(this.copyLink, this, sourceLink) }, + "-", + {label: "OpenInTab", command: bindFixed(this.openInTab, this, sourceLink) } + ]; + } +}); + +// ************************************************************************************************ + +this.SourceFile = domplate(this.SourceLink, +{ + tag: + OBJECTLINK({$collapsed: "$object|hideSourceLink"}, "$object|getSourceLinkTitle"), + + persistor: function(context, href) + { + return getSourceFileByHref(href, context); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "sourceFile", + + supportsObject: function(object) + { + return object instanceof SourceFile; + }, + + persistObject: function(sourceFile) + { + return bind(this.persistor, top, sourceFile.href); + }, + + browseObject: function(sourceLink, context) + { + }, + + getTooltip: function(sourceFile) + { + return sourceFile.href; + } +}); + +// ************************************************************************************************ + +this.StackFrame = domplate(Firebug.Rep, // XXXjjb Since the repObject is fn the stack does not have correct line numbers +{ + tag: + OBJECTBLOCK( + A({"class": "objectLink objectLink-function focusRow a11yFocus", _repObject: "$object.fn"}, "$object|getCallName"), + " ( ", + FOR("arg", "$object|argIterator", + TAG("$arg.tag", {object: "$arg.value"}), + SPAN({"class": "arrayComma"}, "$arg.delim") + ), + " )", + SPAN({"class": "objectLink-sourceLink objectLink"}, "$object|getSourceLinkTitle") + ), + + getCallName: function(frame) + { + //TODO: xxxpedro reps StackFrame + return frame.name || "anonymous"; + + //return getFunctionName(frame.script, frame.context); + }, + + getSourceLinkTitle: function(frame) + { + //TODO: xxxpedro reps StackFrame + var fileName = cropString(getFileName(frame.href), 20); + return fileName + (frame.lineNo ? " (line " + frame.lineNo + ")" : ""); + + var fileName = cropString(getFileName(frame.href), 17); + return $STRF("Line", [fileName, frame.lineNo]); + }, + + argIterator: function(frame) + { + if (!frame.args) + return []; + + var items = []; + + for (var i = 0; i < frame.args.length; ++i) + { + var arg = frame.args[i]; + + if (!arg) + break; + + var rep = Firebug.getRep(arg.value); + var tag = rep.shortTag ? rep.shortTag : rep.tag; + + var delim = (i == frame.args.length-1 ? "" : ", "); + + items.push({name: arg.name, value: arg.value, tag: tag, delim: delim}); + } + + return items; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "stackFrame", + + supportsObject: function(object) + { + return object instanceof StackFrame; + }, + + inspectObject: function(stackFrame, context) + { + var sourceLink = new SourceLink(stackFrame.href, stackFrame.lineNo, "js"); + Firebug.chrome.select(sourceLink); + }, + + getTooltip: function(stackFrame, context) + { + return $STRF("Line", [stackFrame.href, stackFrame.lineNo]); + } + +}); + +// ************************************************************************************************ + +this.StackTrace = domplate(Firebug.Rep, +{ + tag: + FOR("frame", "$object.frames focusRow", + TAG(this.StackFrame.tag, {object: "$frame"}) + ), + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "stackTrace", + + supportsObject: function(object) + { + return object instanceof StackTrace; + } +}); + +// ************************************************************************************************ + +this.jsdStackFrame = domplate(Firebug.Rep, +{ + inspectable: false, + + supportsObject: function(object) + { + return (object instanceof jsdIStackFrame) && (object.isValid); + }, + + getTitle: function(frame, context) + { + if (!frame.isValid) return "(invalid frame)"; // XXXjjb avoid frame.script == null + return getFunctionName(frame.script, context); + }, + + getTooltip: function(frame, context) + { + if (!frame.isValid) return "(invalid frame)"; // XXXjjb avoid frame.script == null + var sourceInfo = FBL.getSourceFileAndLineByScript(context, frame.script, frame); + if (sourceInfo) + return $STRF("Line", [sourceInfo.sourceFile.href, sourceInfo.lineNo]); + else + return $STRF("Line", [frame.script.fileName, frame.line]); + }, + + getContextMenuItems: function(frame, target, context) + { + var fn = frame.script.functionObject.getWrappedValue(); + return FirebugReps.Func.getContextMenuItems(fn, target, context, frame.script); + } +}); + +// ************************************************************************************************ + +this.ErrorMessage = domplate(Firebug.Rep, +{ + tag: + OBJECTBOX({ + $hasTwisty: "$object|hasStackTrace", + $hasBreakSwitch: "$object|hasBreakSwitch", + $breakForError: "$object|hasErrorBreak", + _repObject: "$object", + _stackTrace: "$object|getLastErrorStackTrace", + onclick: "$onToggleError"}, + + DIV({"class": "errorTitle a11yFocus", role : 'checkbox', 'aria-checked' : 'false'}, + "$object.message|getMessage" + ), + DIV({"class": "errorTrace"}), + DIV({"class": "errorSourceBox errorSource-$object|getSourceType"}, + IMG({"class": "errorBreak a11yFocus", src:"blank.gif", role : 'checkbox', 'aria-checked':'false', title: "Break on this error"}), + A({"class": "errorSource a11yFocus"}, "$object|getLine") + ), + TAG(this.SourceLink.tag, {object: "$object|getSourceLink"}) + ), + + getLastErrorStackTrace: function(error) + { + return error.trace; + }, + + hasStackTrace: function(error) + { + var url = error.href.toString(); + var fromCommandLine = (url.indexOf("XPCSafeJSObjectWrapper") != -1); + return !fromCommandLine && error.trace; + }, + + hasBreakSwitch: function(error) + { + return error.href && error.lineNo > 0; + }, + + hasErrorBreak: function(error) + { + return fbs.hasErrorBreakpoint(error.href, error.lineNo); + }, + + getMessage: function(message) + { + var re = /\[Exception... "(.*?)" nsresult:/; + var m = re.exec(message); + return m ? m[1] : message; + }, + + getLine: function(error) + { + if (error.category == "js") + { + if (error.source) + return cropString(error.source, 80); + else if (error.href && error.href.indexOf("XPCSafeJSObjectWrapper") == -1) + return cropString(error.getSourceLine(), 80); + } + }, + + getSourceLink: function(error) + { + var ext = error.category == "css" ? "css" : "js"; + return error.lineNo ? new SourceLink(error.href, error.lineNo, ext) : null; + }, + + getSourceType: function(error) + { + // Errors occurring inside of HTML event handlers look like "foo.html (line 1)" + // so let's try to skip those + if (error.source) + return "syntax"; + else if (error.lineNo == 1 && getFileExtension(error.href) != "js") + return "none"; + else if (error.category == "css") + return "none"; + else if (!error.href || !error.lineNo) + return "none"; + else + return "exec"; + }, + + onToggleError: function(event) + { + var target = event.currentTarget; + if (hasClass(event.target, "errorBreak")) + { + this.breakOnThisError(target.repObject); + } + else if (hasClass(event.target, "errorSource")) + { + var panel = Firebug.getElementPanel(event.target); + this.inspectObject(target.repObject, panel.context); + } + else if (hasClass(event.target, "errorTitle")) + { + var traceBox = target.childNodes[1]; + toggleClass(target, "opened"); + event.target.setAttribute('aria-checked', hasClass(target, "opened")); + if (hasClass(target, "opened")) + { + if (target.stackTrace) + var node = FirebugReps.StackTrace.tag.append({object: target.stackTrace}, traceBox); + if (Firebug.A11yModel.enabled) + { + var panel = Firebug.getElementPanel(event.target); + dispatch([Firebug.A11yModel], "onLogRowContentCreated", [panel , traceBox]); + } + } + else + clearNode(traceBox); + } + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + copyError: function(error) + { + var message = [ + this.getMessage(error.message), + error.href, + "Line " + error.lineNo + ]; + copyToClipboard(message.join("\n")); + }, + + breakOnThisError: function(error) + { + if (this.hasErrorBreak(error)) + Firebug.Debugger.clearErrorBreakpoint(error.href, error.lineNo); + else + Firebug.Debugger.setErrorBreakpoint(error.href, error.lineNo); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "errorMessage", + inspectable: false, + + supportsObject: function(object) + { + return object instanceof ErrorMessage; + }, + + inspectObject: function(error, context) + { + var sourceLink = this.getSourceLink(error); + FirebugReps.SourceLink.inspectObject(sourceLink, context); + }, + + getContextMenuItems: function(error, target, context) + { + var breakOnThisError = this.hasErrorBreak(error); + + var items = [ + {label: "CopyError", command: bindFixed(this.copyError, this, error) } + ]; + + if (error.category == "css") + { + items.push( + "-", + {label: "BreakOnThisError", type: "checkbox", checked: breakOnThisError, + command: bindFixed(this.breakOnThisError, this, error) }, + + optionMenu("BreakOnAllErrors", "breakOnErrors") + ); + } + + return items; + } +}); + +// ************************************************************************************************ + +this.Assert = domplate(Firebug.Rep, +{ + tag: + DIV( + DIV({"class": "errorTitle"}), + DIV({"class": "assertDescription"}) + ), + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "assert", + + inspectObject: function(error, context) + { + var sourceLink = this.getSourceLink(error); + Firebug.chrome.select(sourceLink); + }, + + getContextMenuItems: function(error, target, context) + { + var breakOnThisError = this.hasErrorBreak(error); + + return [ + {label: "CopyError", command: bindFixed(this.copyError, this, error) }, + "-", + {label: "BreakOnThisError", type: "checkbox", checked: breakOnThisError, + command: bindFixed(this.breakOnThisError, this, error) }, + {label: "BreakOnAllErrors", type: "checkbox", checked: Firebug.breakOnErrors, + command: bindFixed(this.breakOnAllErrors, this, error) } + ]; + } +}); + +// ************************************************************************************************ + +this.SourceText = domplate(Firebug.Rep, +{ + tag: + DIV( + FOR("line", "$object|lineIterator", + DIV({"class": "sourceRow", role : "presentation"}, + SPAN({"class": "sourceLine", role : "presentation"}, "$line.lineNo"), + SPAN({"class": "sourceRowText", role : "presentation"}, "$line.text") + ) + ) + ), + + lineIterator: function(sourceText) + { + var maxLineNoChars = (sourceText.lines.length + "").length; + var list = []; + + for (var i = 0; i < sourceText.lines.length; ++i) + { + // Make sure all line numbers are the same width (with a fixed-width font) + var lineNo = (i+1) + ""; + while (lineNo.length < maxLineNoChars) + lineNo = " " + lineNo; + + list.push({lineNo: lineNo, text: sourceText.lines[i]}); + } + + return list; + }, + + getHTML: function(sourceText) + { + return getSourceLineRange(sourceText, 1, sourceText.lines.length); + } +}); + +//************************************************************************************************ +this.nsIDOMHistory = domplate(Firebug.Rep, +{ + tag:OBJECTBOX({onclick: "$showHistory"}, + OBJECTLINK("$object|summarizeHistory") + ), + + className: "nsIDOMHistory", + + summarizeHistory: function(history) + { + try + { + var items = history.length; + return items + " history entries"; + } + catch(exc) + { + return "object does not support history (nsIDOMHistory)"; + } + }, + + showHistory: function(history) + { + try + { + var items = history.length; // if this throws, then unsupported + Firebug.chrome.select(history); + } + catch (exc) + { + } + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + supportsObject: function(object, type) + { + return (object instanceof Ci.nsIDOMHistory); + } +}); + +// ************************************************************************************************ +this.ApplicationCache = domplate(Firebug.Rep, +{ + tag:OBJECTBOX({onclick: "$showApplicationCache"}, + OBJECTLINK("$object|summarizeCache") + ), + + summarizeCache: function(applicationCache) + { + try + { + return applicationCache.length + " items in offline cache"; + } + catch(exc) + { + return "https://bugzilla.mozilla.org/show_bug.cgi?id=422264"; + } + }, + + showApplicationCache: function(event) + { + openNewTab("https://bugzilla.mozilla.org/show_bug.cgi?id=422264"); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "applicationCache", + + supportsObject: function(object, type) + { + if (Ci.nsIDOMOfflineResourceList) + return (object instanceof Ci.nsIDOMOfflineResourceList); + } + +}); + +this.Storage = domplate(Firebug.Rep, +{ + tag: OBJECTBOX({onclick: "$show"}, OBJECTLINK("$object|summarize")), + + summarize: function(storage) + { + return storage.length +" items in Storage"; + }, + show: function(storage) + { + openNewTab("http://dev.w3.org/html5/webstorage/#storage-0"); + }, + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "Storage", + + supportsObject: function(object, type) + { + return (object instanceof Storage); + } + +}); + +// ************************************************************************************************ +Firebug.registerRep( + //this.nsIDOMHistory, // make this early to avoid exceptions + this.Undefined, + this.Null, + this.Number, + this.String, + this.Window, + //this.ApplicationCache, // must come before Arr (array) else exceptions. + //this.ErrorMessage, + this.Element, + //this.TextNode, + this.Document, + this.StyleSheet, + this.Event, + //this.SourceLink, + //this.SourceFile, + //this.StackTrace, + //this.StackFrame, + //this.jsdStackFrame, + //this.jsdScript, + //this.NetFile, + this.Property, + this.Except, + this.Arr +); + +Firebug.setDefaultReps(this.Func, this.Obj); + +}}); + +// ************************************************************************************************ +/* + * The following is http://developer.yahoo.com/yui/license.txt and applies to only code labeled "Yahoo BSD Source" + * in only this file reps.js. John J. Barton June 2007. + * +Software License Agreement (BSD License) + +Copyright (c) 2006, Yahoo! Inc. +All rights reserved. + +Redistribution and use of this software in source and binary forms, with or without modification, are +permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of Yahoo! Inc. nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of Yahoo! Inc. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * / + */ + + +/* See license.txt for terms of usage */ + +FBL.ns(function() { with (FBL) { + +// ************************************************************************************************ +// Constants + +var saveTimeout = 400; +var pageAmount = 10; + +// ************************************************************************************************ +// Globals + +var currentTarget = null; +var currentGroup = null; +var currentPanel = null; +var currentEditor = null; + +var defaultEditor = null; + +var originalClassName = null; + +var originalValue = null; +var defaultValue = null; +var previousValue = null; + +var invalidEditor = false; +var ignoreNextInput = false; + +// ************************************************************************************************ + +Firebug.Editor = extend(Firebug.Module, +{ + supportsStopEvent: true, + + dispatchName: "editor", + tabCharacter: " ", + + startEditing: function(target, value, editor) + { + this.stopEditing(); + + if (hasClass(target, "insertBefore") || hasClass(target, "insertAfter")) + return; + + var panel = Firebug.getElementPanel(target); + if (!panel.editable) + return; + + if (FBTrace.DBG_EDITOR) + FBTrace.sysout("editor.startEditing " + value, target); + + defaultValue = target.getAttribute("defaultValue"); + if (value == undefined) + { + var textContent = isIE ? "innerText" : "textContent"; + value = target[textContent]; + if (value == defaultValue) + value = ""; + } + + originalValue = previousValue = value; + + invalidEditor = false; + currentTarget = target; + currentPanel = panel; + currentGroup = getAncestorByClass(target, "editGroup"); + + currentPanel.editing = true; + + var panelEditor = currentPanel.getEditor(target, value); + currentEditor = editor ? editor : panelEditor; + if (!currentEditor) + currentEditor = getDefaultEditor(currentPanel); + + var inlineParent = getInlineParent(target); + var targetSize = getOffsetSize(inlineParent); + + setClass(panel.panelNode, "editing"); + setClass(target, "editing"); + if (currentGroup) + setClass(currentGroup, "editing"); + + currentEditor.show(target, currentPanel, value, targetSize); + //dispatch(this.fbListeners, "onBeginEditing", [currentPanel, currentEditor, target, value]); + currentEditor.beginEditing(target, value); + if (FBTrace.DBG_EDITOR) + FBTrace.sysout("Editor start panel "+currentPanel.name); + this.attachListeners(currentEditor, panel.context); + }, + + stopEditing: function(cancel) + { + if (!currentTarget) + return; + + if (FBTrace.DBG_EDITOR) + FBTrace.sysout("editor.stopEditing cancel:" + cancel+" saveTimeout: "+this.saveTimeout); + + clearTimeout(this.saveTimeout); + delete this.saveTimeout; + + this.detachListeners(currentEditor, currentPanel.context); + + removeClass(currentPanel.panelNode, "editing"); + removeClass(currentTarget, "editing"); + if (currentGroup) + removeClass(currentGroup, "editing"); + + var value = currentEditor.getValue(); + if (value == defaultValue) + value = ""; + + var removeGroup = currentEditor.endEditing(currentTarget, value, cancel); + + try + { + if (cancel) + { + //dispatch([Firebug.A11yModel], 'onInlineEditorClose', [currentPanel, currentTarget, removeGroup && !originalValue]); + if (value != originalValue) + this.saveEditAndNotifyListeners(currentTarget, originalValue, previousValue); + + if (removeGroup && !originalValue && currentGroup) + currentGroup.parentNode.removeChild(currentGroup); + } + else if (!value) + { + this.saveEditAndNotifyListeners(currentTarget, null, previousValue); + + if (removeGroup && currentGroup) + currentGroup.parentNode.removeChild(currentGroup); + } + else + this.save(value); + } + catch (exc) + { + //throw exc.message; + //ERROR(exc); + } + + currentEditor.hide(); + currentPanel.editing = false; + + //dispatch(this.fbListeners, "onStopEdit", [currentPanel, currentEditor, currentTarget]); + //if (FBTrace.DBG_EDITOR) + // FBTrace.sysout("Editor stop panel "+currentPanel.name); + + currentTarget = null; + currentGroup = null; + currentPanel = null; + currentEditor = null; + originalValue = null; + invalidEditor = false; + + return value; + }, + + cancelEditing: function() + { + return this.stopEditing(true); + }, + + update: function(saveNow) + { + if (this.saveTimeout) + clearTimeout(this.saveTimeout); + + invalidEditor = true; + + currentEditor.layout(); + + if (saveNow) + this.save(); + else + { + var context = currentPanel.context; + this.saveTimeout = context.setTimeout(bindFixed(this.save, this), saveTimeout); + if (FBTrace.DBG_EDITOR) + FBTrace.sysout("editor.update saveTimeout: "+this.saveTimeout); + } + }, + + save: function(value) + { + if (!invalidEditor) + return; + + if (value == undefined) + value = currentEditor.getValue(); + if (FBTrace.DBG_EDITOR) + FBTrace.sysout("editor.save saveTimeout: "+this.saveTimeout+" currentPanel: "+(currentPanel?currentPanel.name:"null")); + try + { + this.saveEditAndNotifyListeners(currentTarget, value, previousValue); + + previousValue = value; + invalidEditor = false; + } + catch (exc) + { + if (FBTrace.DBG_ERRORS) + FBTrace.sysout("editor.save FAILS "+exc, exc); + } + }, + + saveEditAndNotifyListeners: function(currentTarget, value, previousValue) + { + currentEditor.saveEdit(currentTarget, value, previousValue); + //dispatch(this.fbListeners, "onSaveEdit", [currentPanel, currentEditor, currentTarget, value, previousValue]); + }, + + setEditTarget: function(element) + { + if (!element) + { + dispatch([Firebug.A11yModel], 'onInlineEditorClose', [currentPanel, currentTarget, true]); + this.stopEditing(); + } + else if (hasClass(element, "insertBefore")) + this.insertRow(element, "before"); + else if (hasClass(element, "insertAfter")) + this.insertRow(element, "after"); + else + this.startEditing(element); + }, + + tabNextEditor: function() + { + if (!currentTarget) + return; + + var value = currentEditor.getValue(); + var nextEditable = currentTarget; + do + { + nextEditable = !value && currentGroup + ? getNextOutsider(nextEditable, currentGroup) + : getNextByClass(nextEditable, "editable"); + } + while (nextEditable && !nextEditable.offsetHeight); + + this.setEditTarget(nextEditable); + }, + + tabPreviousEditor: function() + { + if (!currentTarget) + return; + + var value = currentEditor.getValue(); + var prevEditable = currentTarget; + do + { + prevEditable = !value && currentGroup + ? getPreviousOutsider(prevEditable, currentGroup) + : getPreviousByClass(prevEditable, "editable"); + } + while (prevEditable && !prevEditable.offsetHeight); + + this.setEditTarget(prevEditable); + }, + + insertRow: function(relative, insertWhere) + { + var group = + relative || getAncestorByClass(currentTarget, "editGroup") || currentTarget; + var value = this.stopEditing(); + + currentPanel = Firebug.getElementPanel(group); + + currentEditor = currentPanel.getEditor(group, value); + if (!currentEditor) + currentEditor = getDefaultEditor(currentPanel); + + currentGroup = currentEditor.insertNewRow(group, insertWhere); + if (!currentGroup) + return; + + var editable = hasClass(currentGroup, "editable") + ? currentGroup + : getNextByClass(currentGroup, "editable"); + + if (editable) + this.setEditTarget(editable); + }, + + insertRowForObject: function(relative) + { + var container = getAncestorByClass(relative, "insertInto"); + if (container) + { + relative = getChildByClass(container, "insertBefore"); + if (relative) + this.insertRow(relative, "before"); + } + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + attachListeners: function(editor, context) + { + var win = isIE ? + currentTarget.ownerDocument.parentWindow : + currentTarget.ownerDocument.defaultView; + + addEvent(win, "resize", this.onResize); + addEvent(win, "blur", this.onBlur); + + var chrome = Firebug.chrome; + + this.listeners = [ + chrome.keyCodeListen("ESCAPE", null, bind(this.cancelEditing, this)) + ]; + + if (editor.arrowCompletion) + { + this.listeners.push( + chrome.keyCodeListen("UP", null, bindFixed(editor.completeValue, editor, -1)), + chrome.keyCodeListen("DOWN", null, bindFixed(editor.completeValue, editor, 1)), + chrome.keyCodeListen("PAGE_UP", null, bindFixed(editor.completeValue, editor, -pageAmount)), + chrome.keyCodeListen("PAGE_DOWN", null, bindFixed(editor.completeValue, editor, pageAmount)) + ); + } + + if (currentEditor.tabNavigation) + { + this.listeners.push( + chrome.keyCodeListen("RETURN", null, bind(this.tabNextEditor, this)), + chrome.keyCodeListen("RETURN", isControl, bind(this.insertRow, this, null, "after")), + chrome.keyCodeListen("TAB", null, bind(this.tabNextEditor, this)), + chrome.keyCodeListen("TAB", isShift, bind(this.tabPreviousEditor, this)) + ); + } + else if (currentEditor.multiLine) + { + this.listeners.push( + chrome.keyCodeListen("TAB", null, insertTab) + ); + } + else + { + this.listeners.push( + chrome.keyCodeListen("RETURN", null, bindFixed(this.stopEditing, this)) + ); + + if (currentEditor.tabCompletion) + { + this.listeners.push( + chrome.keyCodeListen("TAB", null, bind(editor.completeValue, editor, 1)), + chrome.keyCodeListen("TAB", isShift, bind(editor.completeValue, editor, -1)) + ); + } + } + }, + + detachListeners: function(editor, context) + { + if (!this.listeners) + return; + + var win = isIE ? + currentTarget.ownerDocument.parentWindow : + currentTarget.ownerDocument.defaultView; + + removeEvent(win, "resize", this.onResize); + removeEvent(win, "blur", this.onBlur); + + var chrome = Firebug.chrome; + if (chrome) + { + for (var i = 0; i < this.listeners.length; ++i) + chrome.keyIgnore(this.listeners[i]); + } + + delete this.listeners; + }, + + onResize: function(event) + { + currentEditor.layout(true); + }, + + onBlur: function(event) + { + if (currentEditor.enterOnBlur && isAncestor(event.target, currentEditor.box)) + this.stopEditing(); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // extends Module + + initialize: function() + { + Firebug.Module.initialize.apply(this, arguments); + + this.onResize = bindFixed(this.onResize, this); + this.onBlur = bind(this.onBlur, this); + }, + + disable: function() + { + this.stopEditing(); + }, + + showContext: function(browser, context) + { + this.stopEditing(); + }, + + showPanel: function(browser, panel) + { + this.stopEditing(); + } +}); + +// ************************************************************************************************ +// BaseEditor + +Firebug.BaseEditor = extend(Firebug.MeasureBox, +{ + getValue: function() + { + }, + + setValue: function(value) + { + }, + + show: function(target, panel, value, textSize, targetSize) + { + }, + + hide: function() + { + }, + + layout: function(forceAll) + { + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Support for context menus within inline editors. + + getContextMenuItems: function(target) + { + var items = []; + items.push({label: "Cut", commandID: "cmd_cut"}); + items.push({label: "Copy", commandID: "cmd_copy"}); + items.push({label: "Paste", commandID: "cmd_paste"}); + return items; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Editor Module listeners will get "onBeginEditing" just before this call + + beginEditing: function(target, value) + { + }, + + // Editor Module listeners will get "onSaveEdit" just after this call + saveEdit: function(target, value, previousValue) + { + }, + + endEditing: function(target, value, cancel) + { + // Remove empty groups by default + return true; + }, + + insertNewRow: function(target, insertWhere) + { + } +}); + +// ************************************************************************************************ +// InlineEditor + +// basic inline editor attributes +var inlineEditorAttributes = { + "class": "textEditorInner", + + type: "text", + spellcheck: "false", + + onkeypress: "$onKeyPress", + + onoverflow: "$onOverflow", + oncontextmenu: "$onContextMenu" +}; + +// IE does not support the oninput event, so we're using the onkeydown to signalize +// the relevant keyboard events, and the onpropertychange to actually handle the +// input event, which should happen after the onkeydown event is fired and after the +// value of the input is updated, but before the onkeyup and before the input (with the +// new value) is rendered +if (isIE) +{ + inlineEditorAttributes.onpropertychange = "$onInput"; + inlineEditorAttributes.onkeydown = "$onKeyDown"; +} +// for other browsers we use the oninput event +else +{ + inlineEditorAttributes.oninput = "$onInput"; +} + +Firebug.InlineEditor = function(doc) +{ + this.initializeInline(doc); +}; + +Firebug.InlineEditor.prototype = domplate(Firebug.BaseEditor, +{ + enterOnBlur: true, + outerMargin: 8, + shadowExpand: 7, + + tag: + DIV({"class": "inlineEditor"}, + DIV({"class": "textEditorTop1"}, + DIV({"class": "textEditorTop2"}) + ), + DIV({"class": "textEditorInner1"}, + DIV({"class": "textEditorInner2"}, + INPUT( + inlineEditorAttributes + ) + ) + ), + DIV({"class": "textEditorBottom1"}, + DIV({"class": "textEditorBottom2"}) + ) + ), + + inputTag : + INPUT({"class": "textEditorInner", type: "text", + /*oninput: "$onInput",*/ onkeypress: "$onKeyPress", onoverflow: "$onOverflow"} + ), + + expanderTag: + IMG({"class": "inlineExpander", src: "blank.gif"}), + + initialize: function() + { + this.fixedWidth = false; + this.completeAsYouType = true; + this.tabNavigation = true; + this.multiLine = false; + this.tabCompletion = false; + this.arrowCompletion = true; + this.noWrap = true; + this.numeric = false; + }, + + destroy: function() + { + this.destroyInput(); + }, + + initializeInline: function(doc) + { + if (FBTrace.DBG_EDITOR) + FBTrace.sysout("Firebug.InlineEditor initializeInline()"); + + //this.box = this.tag.replace({}, doc, this); + this.box = this.tag.append({}, doc.body, this); + + //this.input = this.box.childNodes[1].firstChild.firstChild; // XXXjjb childNode[1] required + this.input = this.box.getElementsByTagName("input")[0]; + + if (isIElt8) + { + this.input.style.top = "-8px"; + } + + this.expander = this.expanderTag.replace({}, doc, this); + this.initialize(); + }, + + destroyInput: function() + { + // XXXjoe Need to remove input/keypress handlers to avoid leaks + }, + + getValue: function() + { + return this.input.value; + }, + + setValue: function(value) + { + // It's only a one-line editor, so new lines shouldn't be allowed + return this.input.value = stripNewLines(value); + }, + + show: function(target, panel, value, targetSize) + { + //dispatch([Firebug.A11yModel], "onInlineEditorShow", [panel, this]); + this.target = target; + this.panel = panel; + + this.targetSize = targetSize; + + // TODO: xxxpedro editor + //this.targetOffset = getClientOffset(target); + + // Some browsers (IE, Google Chrome and Safari) will have problem trying to get the + // offset values of invisible elements, or empty elements. So, in order to get the + // correct values, we temporary inject a character in the innerHTML of the empty element, + // then we get the offset values, and next, we restore the original innerHTML value. + var innerHTML = target.innerHTML; + var isEmptyElement = !innerHTML; + if (isEmptyElement) + target.innerHTML = "."; + + // Get the position of the target element (that is about to be edited) + this.targetOffset = + { + x: target.offsetLeft, + y: target.offsetTop + }; + + // Restore the original innerHTML value of the empty element + if (isEmptyElement) + target.innerHTML = innerHTML; + + this.originalClassName = this.box.className; + + var classNames = target.className.split(" "); + for (var i = 0; i < classNames.length; ++i) + setClass(this.box, "editor-" + classNames[i]); + + // Make the editor match the target's font style + copyTextStyles(target, this.box); + + this.setValue(value); + + if (this.fixedWidth) + this.updateLayout(true); + else + { + this.startMeasuring(target); + this.textSize = this.measureInputText(value); + + // Correct the height of the box to make the funky CSS drop-shadow line up + var parent = this.input.parentNode; + if (hasClass(parent, "textEditorInner2")) + { + var yDiff = this.textSize.height - this.shadowExpand; + + // IE6 height offset + if (isIE6) + yDiff -= 2; + + parent.style.height = yDiff + "px"; + parent.parentNode.style.height = yDiff + "px"; + } + + this.updateLayout(true); + } + + this.getAutoCompleter().reset(); + + if (isIElt8) + panel.panelNode.appendChild(this.box); + else + target.offsetParent.appendChild(this.box); + + //console.log(target); + //this.input.select(); // it's called bellow, with setTimeout + + if (isIE) + { + // reset input style + this.input.style.fontFamily = "Monospace"; + this.input.style.fontSize = "11px"; + } + + // Insert the "expander" to cover the target element with white space + if (!this.fixedWidth) + { + copyBoxStyles(target, this.expander); + + target.parentNode.replaceChild(this.expander, target); + collapse(target, true); + this.expander.parentNode.insertBefore(target, this.expander); + } + + //TODO: xxxpedro + //scrollIntoCenterView(this.box, null, true); + + // Display the editor after change its size and position to avoid flickering + this.box.style.display = "block"; + + // we need to call input.focus() and input.select() with a timeout, + // otherwise it won't work on all browsers due to timing issues + var self = this; + setTimeout(function(){ + self.input.focus(); + self.input.select(); + },0); + }, + + hide: function() + { + this.box.className = this.originalClassName; + + if (!this.fixedWidth) + { + this.stopMeasuring(); + + collapse(this.target, false); + + if (this.expander.parentNode) + this.expander.parentNode.removeChild(this.expander); + } + + if (this.box.parentNode) + { + ///setSelectionRange(this.input, 0, 0); + this.input.blur(); + + this.box.parentNode.removeChild(this.box); + } + + delete this.target; + delete this.panel; + }, + + layout: function(forceAll) + { + if (!this.fixedWidth) + this.textSize = this.measureInputText(this.input.value); + + if (forceAll) + this.targetOffset = getClientOffset(this.expander); + + this.updateLayout(false, forceAll); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + beginEditing: function(target, value) + { + }, + + saveEdit: function(target, value, previousValue) + { + }, + + endEditing: function(target, value, cancel) + { + // Remove empty groups by default + return true; + }, + + insertNewRow: function(target, insertWhere) + { + }, + + advanceToNext: function(target, charCode) + { + return false; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + getAutoCompleteRange: function(value, offset) + { + }, + + getAutoCompleteList: function(preExpr, expr, postExpr) + { + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + getAutoCompleter: function() + { + if (!this.autoCompleter) + { + this.autoCompleter = new Firebug.AutoCompleter(null, + bind(this.getAutoCompleteRange, this), bind(this.getAutoCompleteList, this), + true, false); + } + + return this.autoCompleter; + }, + + completeValue: function(amt) + { + //console.log("completeValue"); + + var selectRangeCallback = this.getAutoCompleter().complete(currentPanel.context, this.input, true, amt < 0); + + if (selectRangeCallback) + { + Firebug.Editor.update(true); + + // We need to select the editor text after calling update in Safari/Chrome, + // otherwise the text won't be selected + if (isSafari) + setTimeout(selectRangeCallback,0); + else + selectRangeCallback(); + } + else + this.incrementValue(amt); + }, + + incrementValue: function(amt) + { + var value = this.input.value; + + // TODO: xxxpedro editor + if (isIE) + var start = getInputSelectionStart(this.input), end = start; + else + var start = this.input.selectionStart, end = this.input.selectionEnd; + + //debugger; + var range = this.getAutoCompleteRange(value, start); + if (!range || range.type != "int") + range = {start: 0, end: value.length-1}; + + var expr = value.substr(range.start, range.end-range.start+1); + preExpr = value.substr(0, range.start); + postExpr = value.substr(range.end+1); + + // See if the value is an integer, and if so increment it + var intValue = parseInt(expr); + if (!!intValue || intValue == 0) + { + var m = /\d+/.exec(expr); + var digitPost = expr.substr(m.index+m[0].length); + + var completion = intValue-amt; + this.input.value = preExpr + completion + digitPost + postExpr; + + setSelectionRange(this.input, start, end); + + Firebug.Editor.update(true); + + return true; + } + else + return false; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + onKeyPress: function(event) + { + //console.log("onKeyPress", event); + if (event.keyCode == 27 && !this.completeAsYouType) + { + var reverted = this.getAutoCompleter().revert(this.input); + if (reverted) + cancelEvent(event); + } + else if (event.charCode && this.advanceToNext(this.target, event.charCode)) + { + Firebug.Editor.tabNextEditor(); + cancelEvent(event); + } + else + { + if (this.numeric && event.charCode && (event.charCode < 48 || event.charCode > 57) + && event.charCode != 45 && event.charCode != 46) + FBL.cancelEvent(event); + else + { + // If the user backspaces, don't autocomplete after the upcoming input event + this.ignoreNextInput = event.keyCode == 8; + } + } + }, + + onOverflow: function() + { + this.updateLayout(false, false, 3); + }, + + onKeyDown: function(event) + { + //console.log("onKeyDown", event.keyCode); + if (event.keyCode > 46 || event.keyCode == 32 || event.keyCode == 8) + { + this.keyDownPressed = true; + } + }, + + onInput: function(event) + { + //debugger; + + // skip not relevant onpropertychange calls on IE + if (isIE) + { + if (event.propertyName != "value" || !isVisible(this.input) || !this.keyDownPressed) + return; + + this.keyDownPressed = false; + } + + //console.log("onInput", event); + //console.trace(); + + var selectRangeCallback; + + if (this.ignoreNextInput) + { + this.ignoreNextInput = false; + this.getAutoCompleter().reset(); + } + else if (this.completeAsYouType) + selectRangeCallback = this.getAutoCompleter().complete(currentPanel.context, this.input, false); + else + this.getAutoCompleter().reset(); + + Firebug.Editor.update(); + + if (selectRangeCallback) + { + // We need to select the editor text after calling update in Safari/Chrome, + // otherwise the text won't be selected + if (isSafari) + setTimeout(selectRangeCallback,0); + else + selectRangeCallback(); + } + }, + + onContextMenu: function(event) + { + cancelEvent(event); + + var popup = $("fbInlineEditorPopup"); + FBL.eraseNode(popup); + + var target = event.target || event.srcElement; + var menu = this.getContextMenuItems(target); + if (menu) + { + for (var i = 0; i < menu.length; ++i) + FBL.createMenuItem(popup, menu[i]); + } + + if (!popup.firstChild) + return false; + + popup.openPopupAtScreen(event.screenX, event.screenY, true); + return true; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + updateLayout: function(initial, forceAll, extraWidth) + { + if (this.fixedWidth) + { + this.box.style.left = (this.targetOffset.x) + "px"; + this.box.style.top = (this.targetOffset.y) + "px"; + + var w = this.target.offsetWidth; + var h = this.target.offsetHeight; + this.input.style.width = w + "px"; + this.input.style.height = (h-3) + "px"; + } + else + { + if (initial || forceAll) + { + this.box.style.left = this.targetOffset.x + "px"; + this.box.style.top = this.targetOffset.y + "px"; + } + + var approxTextWidth = this.textSize.width; + var maxWidth = (currentPanel.panelNode.scrollWidth - this.targetOffset.x) + - this.outerMargin; + + var wrapped = initial + ? this.noWrap && this.targetSize.height > this.textSize.height+3 + : this.noWrap && approxTextWidth > maxWidth; + + if (wrapped) + { + var style = isIE ? + this.target.currentStyle : + this.target.ownerDocument.defaultView.getComputedStyle(this.target, ""); + + targetMargin = parseInt(style.marginLeft) + parseInt(style.marginRight); + + // Make the width fit the remaining x-space from the offset to the far right + approxTextWidth = maxWidth - targetMargin; + + this.input.style.width = "100%"; + this.box.style.width = approxTextWidth + "px"; + } + else + { + // Make the input one character wider than the text value so that + // typing does not ever cause the textbox to scroll + var charWidth = this.measureInputText('m').width; + + // Sometimes we need to make the editor a little wider, specifically when + // an overflow happens, otherwise it will scroll off some text on the left + if (extraWidth) + charWidth *= extraWidth; + + var inputWidth = approxTextWidth + charWidth; + + if (initial) + { + if (isIE) + { + // TODO: xxxpedro + var xDiff = 13; + this.box.style.width = (inputWidth + xDiff) + "px"; + } + else + this.box.style.width = "auto"; + } + else + { + // TODO: xxxpedro + var xDiff = isIE ? 13: this.box.scrollWidth - this.input.offsetWidth; + this.box.style.width = (inputWidth + xDiff) + "px"; + } + + this.input.style.width = inputWidth + "px"; + } + + this.expander.style.width = approxTextWidth + "px"; + this.expander.style.height = Math.max(this.textSize.height-3,0) + "px"; + } + + if (forceAll) + scrollIntoCenterView(this.box, null, true); + } +}); + +// ************************************************************************************************ +// Autocompletion + +Firebug.AutoCompleter = function(getExprOffset, getRange, evaluator, selectMode, caseSensitive) +{ + var candidates = null; + var originalValue = null; + var originalOffset = -1; + var lastExpr = null; + var lastOffset = -1; + var exprOffset = 0; + var lastIndex = 0; + var preParsed = null; + var preExpr = null; + var postExpr = null; + + this.revert = function(textBox) + { + if (originalOffset != -1) + { + textBox.value = originalValue; + + setSelectionRange(textBox, originalOffset, originalOffset); + + this.reset(); + return true; + } + else + { + this.reset(); + return false; + } + }; + + this.reset = function() + { + candidates = null; + originalValue = null; + originalOffset = -1; + lastExpr = null; + lastOffset = 0; + exprOffset = 0; + }; + + this.complete = function(context, textBox, cycle, reverse) + { + //console.log("complete", context, textBox, cycle, reverse); + // TODO: xxxpedro important port to firebug (variable leak) + //var value = lastValue = textBox.value; + var value = textBox.value; + + //var offset = textBox.selectionStart; + var offset = getInputSelectionStart(textBox); + + // The result of selectionStart() in Safari/Chrome is 1 unit less than the result + // in Firefox. Therefore, we need to manually adjust the value here. + if (isSafari && !cycle && offset >= 0) offset++; + + if (!selectMode && originalOffset != -1) + offset = originalOffset; + + if (!candidates || !cycle || offset != lastOffset) + { + originalOffset = offset; + originalValue = value; + + // Find the part of the string that will be parsed + var parseStart = getExprOffset ? getExprOffset(value, offset, context) : 0; + preParsed = value.substr(0, parseStart); + var parsed = value.substr(parseStart); + + // Find the part of the string that is being completed + var range = getRange ? getRange(parsed, offset-parseStart, context) : null; + if (!range) + range = {start: 0, end: parsed.length-1 }; + + var expr = parsed.substr(range.start, range.end-range.start+1); + preExpr = parsed.substr(0, range.start); + postExpr = parsed.substr(range.end+1); + exprOffset = parseStart + range.start; + + if (!cycle) + { + if (!expr) + return; + else if (lastExpr && lastExpr.indexOf(expr) != 0) + { + candidates = null; + } + else if (lastExpr && lastExpr.length >= expr.length) + { + candidates = null; + lastExpr = expr; + return; + } + } + + lastExpr = expr; + lastOffset = offset; + + var searchExpr; + + // Check if the cursor is at the very right edge of the expression, or + // somewhere in the middle of it + if (expr && offset != parseStart+range.end+1) + { + if (cycle) + { + // We are in the middle of the expression, but we can + // complete by cycling to the next item in the values + // list after the expression + offset = range.start; + searchExpr = expr; + expr = ""; + } + else + { + // We can't complete unless we are at the ridge edge + return; + } + } + + var values = evaluator(preExpr, expr, postExpr, context); + if (!values) + return; + + if (expr) + { + // Filter the list of values to those which begin with expr. We + // will then go on to complete the first value in the resulting list + candidates = []; + + if (caseSensitive) + { + for (var i = 0; i < values.length; ++i) + { + var name = values[i]; + if (name.indexOf && name.indexOf(expr) == 0) + candidates.push(name); + } + } + else + { + var lowerExpr = caseSensitive ? expr : expr.toLowerCase(); + for (var i = 0; i < values.length; ++i) + { + var name = values[i]; + if (name.indexOf && name.toLowerCase().indexOf(lowerExpr) == 0) + candidates.push(name); + } + } + + lastIndex = reverse ? candidates.length-1 : 0; + } + else if (searchExpr) + { + var searchIndex = -1; + + // Find the first instance of searchExpr in the values list. We + // will then complete the string that is found + if (caseSensitive) + { + searchIndex = values.indexOf(expr); + } + else + { + var lowerExpr = searchExpr.toLowerCase(); + for (var i = 0; i < values.length; ++i) + { + var name = values[i]; + if (name && name.toLowerCase().indexOf(lowerExpr) == 0) + { + searchIndex = i; + break; + } + } + } + + // Nothing found, so there's nothing to complete to + if (searchIndex == -1) + return this.reset(); + + expr = searchExpr; + candidates = cloneArray(values); + lastIndex = searchIndex; + } + else + { + expr = ""; + candidates = []; + for (var i = 0; i < values.length; ++i) + { + if (values[i].substr) + candidates.push(values[i]); + } + lastIndex = -1; + } + } + + if (cycle) + { + expr = lastExpr; + lastIndex += reverse ? -1 : 1; + } + + if (!candidates.length) + return; + + if (lastIndex >= candidates.length) + lastIndex = 0; + else if (lastIndex < 0) + lastIndex = candidates.length-1; + + var completion = candidates[lastIndex]; + var preCompletion = expr.substr(0, offset-exprOffset); + var postCompletion = completion.substr(offset-exprOffset); + + textBox.value = preParsed + preExpr + preCompletion + postCompletion + postExpr; + var offsetEnd = preParsed.length + preExpr.length + completion.length; + + // TODO: xxxpedro remove the following commented code, if the lib.setSelectionRange() + // is working well. + /* + if (textBox.setSelectionRange) + { + // we must select the range with a timeout, otherwise the text won't + // be properly selected (because after this function executes, the editor's + // input will be resized to fit the whole text) + setTimeout(function(){ + if (selectMode) + textBox.setSelectionRange(offset, offsetEnd); + else + textBox.setSelectionRange(offsetEnd, offsetEnd); + },0); + } + /**/ + + // we must select the range with a timeout, otherwise the text won't + // be properly selected (because after this function executes, the editor's + // input will be resized to fit the whole text) + /* + setTimeout(function(){ + if (selectMode) + setSelectionRange(textBox, offset, offsetEnd); + else + setSelectionRange(textBox, offsetEnd, offsetEnd); + },0); + + return true; + /**/ + + // The editor text should be selected only after calling the editor.update() + // in Safari/Chrome, otherwise the text won't be selected. So, we're returning + // a function to be called later (in the proper time for all browsers). + // + // TODO: xxxpedro see if we can move the editor.update() calls to here, and avoid + // returning a closure. the complete() function seems to be called only twice in + // editor.js. See if this function is called anywhere else (like css.js for example). + return function(){ + //console.log("autocomplete ", textBox, offset, offsetEnd); + + if (selectMode) + setSelectionRange(textBox, offset, offsetEnd); + else + setSelectionRange(textBox, offsetEnd, offsetEnd); + }; + /**/ + }; +}; + +// ************************************************************************************************ +// Local Helpers + +var getDefaultEditor = function getDefaultEditor(panel) +{ + if (!defaultEditor) + { + var doc = panel.document; + defaultEditor = new Firebug.InlineEditor(doc); + } + + return defaultEditor; +} + +/** + * An outsider is the first element matching the stepper element that + * is not an child of group. Elements tagged with insertBefore or insertAfter + * classes are also excluded from these results unless they are the sibling + * of group, relative to group's parent editGroup. This allows for the proper insertion + * rows when groups are nested. + */ +var getOutsider = function getOutsider(element, group, stepper) +{ + var parentGroup = getAncestorByClass(group.parentNode, "editGroup"); + var next; + do + { + next = stepper(next || element); + } + while (isAncestor(next, group) || isGroupInsert(next, parentGroup)); + + return next; +} + +var isGroupInsert = function isGroupInsert(next, group) +{ + return (!group || isAncestor(next, group)) + && (hasClass(next, "insertBefore") || hasClass(next, "insertAfter")); +} + +var getNextOutsider = function getNextOutsider(element, group) +{ + return getOutsider(element, group, bind(getNextByClass, FBL, "editable")); +} + +var getPreviousOutsider = function getPreviousOutsider(element, group) +{ + return getOutsider(element, group, bind(getPreviousByClass, FBL, "editable")); +} + +var getInlineParent = function getInlineParent(element) +{ + var lastInline = element; + for (; element; element = element.parentNode) + { + //var s = element.ownerDocument.defaultView.getComputedStyle(element, ""); + var s = isIE ? + element.currentStyle : + element.ownerDocument.defaultView.getComputedStyle(element, ""); + + if (s.display != "inline") + return lastInline; + else + lastInline = element; + } + return null; +} + +var insertTab = function insertTab() +{ + insertTextIntoElement(currentEditor.input, Firebug.Editor.tabCharacter); +} + +// ************************************************************************************************ + +Firebug.registerModule(Firebug.Editor); + +// ************************************************************************************************ + +}}); + + +/* See license.txt for terms of usage */ + +FBL.ns(function() { with (FBL) { +// ************************************************************************************************ + +if (Env.Options.disableXHRListener) + return; + +// ************************************************************************************************ +// XHRSpy + +var XHRSpy = function() +{ + this.requestHeaders = []; + this.responseHeaders = []; +}; + +XHRSpy.prototype = +{ + method: null, + url: null, + async: null, + + xhrRequest: null, + + href: null, + + loaded: false, + + logRow: null, + + responseText: null, + + requestHeaders: null, + responseHeaders: null, + + sourceLink: null, // {href:"file.html", line: 22} + + getURL: function() + { + return this.href; + } +}; + +// ************************************************************************************************ +// XMLHttpRequestWrapper + +var XMLHttpRequestWrapper = function(activeXObject) +{ + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // XMLHttpRequestWrapper internal variables + + var xhrRequest = typeof activeXObject != "undefined" ? + activeXObject : + new _XMLHttpRequest(), + + spy = new XHRSpy(), + + self = this, + + reqType, + reqUrl, + reqStartTS; + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // XMLHttpRequestWrapper internal methods + + var updateSelfPropertiesIgnore = { + abort: 1, + channel: 1, + getAllResponseHeaders: 1, + getInterface: 1, + getResponseHeader: 1, + mozBackgroundRequest: 1, + multipart: 1, + onreadystatechange: 1, + open: 1, + send: 1, + setRequestHeader: 1 + }; + + var updateSelfProperties = function() + { + if (supportsXHRIterator) + { + for (var propName in xhrRequest) + { + if (propName in updateSelfPropertiesIgnore) + continue; + + try + { + var propValue = xhrRequest[propName]; + + if (propValue && !isFunction(propValue)) + self[propName] = propValue; + } + catch(E) + { + //console.log(propName, E.message); + } + } + } + else + { + // will fail to read these xhrRequest properties if the request is not completed + if (xhrRequest.readyState == 4) + { + self.status = xhrRequest.status; + self.statusText = xhrRequest.statusText; + self.responseText = xhrRequest.responseText; + self.responseXML = xhrRequest.responseXML; + } + } + }; + + var updateXHRPropertiesIgnore = { + channel: 1, + onreadystatechange: 1, + readyState: 1, + responseBody: 1, + responseText: 1, + responseXML: 1, + status: 1, + statusText: 1, + upload: 1 + }; + + var updateXHRProperties = function() + { + for (var propName in self) + { + if (propName in updateXHRPropertiesIgnore) + continue; + + try + { + var propValue = self[propName]; + + if (propValue && !xhrRequest[propName]) + { + xhrRequest[propName] = propValue; + } + } + catch(E) + { + //console.log(propName, E.message); + } + } + }; + + var logXHR = function() + { + var row = Firebug.Console.log(spy, null, "spy", Firebug.Spy.XHR); + + if (row) + { + setClass(row, "loading"); + spy.logRow = row; + } + }; + + var finishXHR = function() + { + var duration = new Date().getTime() - reqStartTS; + var success = xhrRequest.status == 200; + + var responseHeadersText = xhrRequest.getAllResponseHeaders(); + var responses = responseHeadersText ? responseHeadersText.split(/[\n\r]/) : []; + var reHeader = /^(\S+):\s*(.*)/; + + for (var i=0, l=responses.length; i<l; i++) + { + var text = responses[i]; + var match = text.match(reHeader); + + if (match) + { + var name = match[1]; + var value = match[2]; + + // update the spy mimeType property so we can detect when to show + // custom response viewers (such as HTML, XML or JSON viewer) + if (name == "Content-Type") + spy.mimeType = value; + + /* + if (name == "Last Modified") + { + if (!spy.cacheEntry) + spy.cacheEntry = []; + + spy.cacheEntry.push({ + name: [name], + value: [value] + }); + } + /**/ + + spy.responseHeaders.push({ + name: [name], + value: [value] + }); + } + } + + with({ + row: spy.logRow, + status: xhrRequest.status == 0 ? + // if xhrRequest.status == 0 then accessing xhrRequest.statusText + // will cause an error, so we must handle this case (Issue 3504) + "" : xhrRequest.status + " " + xhrRequest.statusText, + time: duration, + success: success + }) + { + setTimeout(function(){ + + spy.responseText = xhrRequest.responseText; + + // update row information to avoid "ethernal spinning gif" bug in IE + row = row || spy.logRow; + + // if chrome document is not loaded, there will be no row yet, so just ignore + if (!row) return; + + // update the XHR representation data + handleRequestStatus(success, status, time); + + },200); + } + + spy.loaded = true; + /* + // commented because they are being updated by the updateSelfProperties() function + self.status = xhrRequest.status; + self.statusText = xhrRequest.statusText; + self.responseText = xhrRequest.responseText; + self.responseXML = xhrRequest.responseXML; + /**/ + updateSelfProperties(); + }; + + var handleStateChange = function() + { + //Firebug.Console.log(["onreadystatechange", xhrRequest.readyState, xhrRequest.readyState == 4 && xhrRequest.status]); + + self.readyState = xhrRequest.readyState; + + if (xhrRequest.readyState == 4) + { + finishXHR(); + + xhrRequest.onreadystatechange = function(){}; + } + + //Firebug.Console.log(spy.url + ": " + xhrRequest.readyState); + + self.onreadystatechange(); + }; + + // update the XHR representation data + var handleRequestStatus = function(success, status, time) + { + var row = spy.logRow; + FBL.removeClass(row, "loading"); + + if (!success) + FBL.setClass(row, "error"); + + var item = FBL.$$(".spyStatus", row)[0]; + item.innerHTML = status; + + if (time) + { + var item = FBL.$$(".spyTime", row)[0]; + item.innerHTML = time + "ms"; + } + }; + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // XMLHttpRequestWrapper public properties and handlers + + this.readyState = 0; + + this.onreadystatechange = function(){}; + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // XMLHttpRequestWrapper public methods + + this.open = function(method, url, async, user, password) + { + //Firebug.Console.log("xhrRequest open"); + + updateSelfProperties(); + + if (spy.loaded) + spy = new XHRSpy(); + + spy.method = method; + spy.url = url; + spy.async = async; + spy.href = url; + spy.xhrRequest = xhrRequest; + spy.urlParams = parseURLParamsArray(url); + + try + { + // xhrRequest.open.apply may not be available in IE + if (supportsApply) + xhrRequest.open.apply(xhrRequest, arguments); + else + xhrRequest.open(method, url, async, user, password); + } + catch(e) + { + } + + xhrRequest.onreadystatechange = handleStateChange; + + }; + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + this.send = function(data) + { + //Firebug.Console.log("xhrRequest send"); + spy.data = data; + + reqStartTS = new Date().getTime(); + + updateXHRProperties(); + + try + { + xhrRequest.send(data); + } + catch(e) + { + // TODO: xxxpedro XHR throws or not? + //throw e; + } + finally + { + logXHR(); + + if (!spy.async) + { + self.readyState = xhrRequest.readyState; + + // sometimes an error happens when calling finishXHR() + // Issue 3422: Firebug Lite breaks Google Instant Search + try + { + finishXHR(); + } + catch(E) + { + } + } + } + }; + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + this.setRequestHeader = function(header, value) + { + spy.requestHeaders.push({name: [header], value: [value]}); + return xhrRequest.setRequestHeader(header, value); + }; + + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + this.abort = function() + { + xhrRequest.abort(); + updateSelfProperties(); + handleRequestStatus(false, "Aborted"); + }; + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + this.getResponseHeader = function(header) + { + return xhrRequest.getResponseHeader(header); + }; + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + this.getAllResponseHeaders = function() + { + return xhrRequest.getAllResponseHeaders(); + }; + + /**/ + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Clone XHR object + + // xhrRequest.open.apply not available in IE and will throw an error in + // IE6 by simply reading xhrRequest.open so we must sniff it + var supportsApply = !isIE6 && + xhrRequest && + xhrRequest.open && + typeof xhrRequest.open.apply != "undefined"; + + var numberOfXHRProperties = 0; + for (var propName in xhrRequest) + { + numberOfXHRProperties++; + + if (propName in updateSelfPropertiesIgnore) + continue; + + try + { + var propValue = xhrRequest[propName]; + + if (isFunction(propValue)) + { + if (typeof self[propName] == "undefined") + { + this[propName] = (function(name, xhr){ + + return supportsApply ? + // if the browser supports apply + function() + { + return xhr[name].apply(xhr, arguments); + } + : + function(a,b,c,d,e) + { + return xhr[name](a,b,c,d,e); + }; + + })(propName, xhrRequest); + } + } + else + this[propName] = propValue; + } + catch(E) + { + //console.log(propName, E.message); + } + } + + // IE6 does not support for (var prop in XHR) + var supportsXHRIterator = numberOfXHRProperties > 0; + + /**/ + + return this; +}; + +// ************************************************************************************************ +// ActiveXObject Wrapper (IE6 only) + +var _ActiveXObject; +var isIE6 = /msie 6/i.test(navigator.appVersion); + +if (isIE6) +{ + _ActiveXObject = window.ActiveXObject; + + var xhrObjects = " MSXML2.XMLHTTP.5.0 MSXML2.XMLHTTP.4.0 MSXML2.XMLHTTP.3.0 MSXML2.XMLHTTP Microsoft.XMLHTTP "; + + window.ActiveXObject = function(name) + { + var error = null; + + try + { + var activeXObject = new _ActiveXObject(name); + } + catch(e) + { + error = e; + } + finally + { + if (!error) + { + if (xhrObjects.indexOf(" " + name + " ") != -1) + return new XMLHttpRequestWrapper(activeXObject); + else + return activeXObject; + } + else + throw error.message; + } + }; +} + +// ************************************************************************************************ + +// Register the XMLHttpRequestWrapper for non-IE6 browsers +if (!isIE6) +{ + var _XMLHttpRequest = XMLHttpRequest; + window.XMLHttpRequest = function() + { + return new XMLHttpRequestWrapper(); + }; +} + +//************************************************************************************************ + +FBL.getNativeXHRObject = function() +{ + var xhrObj = false; + try + { + xhrObj = new _XMLHttpRequest(); + } + catch(e) + { + var progid = [ + "MSXML2.XMLHTTP.5.0", "MSXML2.XMLHTTP.4.0", + "MSXML2.XMLHTTP.3.0", "MSXML2.XMLHTTP", "Microsoft.XMLHTTP" + ]; + + for ( var i=0; i < progid.length; ++i ) { + try + { + xhrObj = new _ActiveXObject(progid[i]); + } + catch(e) + { + continue; + } + break; + } + } + finally + { + return xhrObj; + } +}; + +// ************************************************************************************************ +}}); + + +/* See license.txt for terms of usage */ + +FBL.ns(function() { with (FBL) { +// ************************************************************************************************ + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +var reIgnore = /about:|javascript:|resource:|chrome:|jar:/; +var layoutInterval = 300; +var indentWidth = 18; + +var cacheSession = null; +var contexts = new Array(); +var panelName = "net"; +var maxQueueRequests = 500; +//var panelBar1 = $("fbPanelBar1"); // chrome not available at startup +var activeRequests = []; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +var mimeExtensionMap = +{ + "txt": "text/plain", + "html": "text/html", + "htm": "text/html", + "xhtml": "text/html", + "xml": "text/xml", + "css": "text/css", + "js": "application/x-javascript", + "jss": "application/x-javascript", + "jpg": "image/jpg", + "jpeg": "image/jpeg", + "gif": "image/gif", + "png": "image/png", + "bmp": "image/bmp", + "swf": "application/x-shockwave-flash", + "flv": "video/x-flv" +}; + +var fileCategories = +{ + "undefined": 1, + "html": 1, + "css": 1, + "js": 1, + "xhr": 1, + "image": 1, + "flash": 1, + "txt": 1, + "bin": 1 +}; + +var textFileCategories = +{ + "txt": 1, + "html": 1, + "xhr": 1, + "css": 1, + "js": 1 +}; + +var binaryFileCategories = +{ + "bin": 1, + "flash": 1 +}; + +var mimeCategoryMap = +{ + "text/plain": "txt", + "application/octet-stream": "bin", + "text/html": "html", + "text/xml": "html", + "text/css": "css", + "application/x-javascript": "js", + "text/javascript": "js", + "application/javascript" : "js", + "image/jpeg": "image", + "image/jpg": "image", + "image/gif": "image", + "image/png": "image", + "image/bmp": "image", + "application/x-shockwave-flash": "flash", + "video/x-flv": "flash" +}; + +var binaryCategoryMap = +{ + "image": 1, + "flash" : 1 +}; + +// ************************************************************************************************ + +/** + * @module Represents a module object for the Net panel. This object is derived + * from <code>Firebug.ActivableModule</code> in order to support activation (enable/disable). + * This allows to avoid (performance) expensive features if the functionality is not necessary + * for the user. + */ +Firebug.NetMonitor = extend(Firebug.ActivableModule, +{ + dispatchName: "netMonitor", + + clear: function(context) + { + // The user pressed a Clear button so, remove content of the panel... + var panel = context.getPanel(panelName, true); + if (panel) + panel.clear(); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // extends Module + + initialize: function() + { + return; + + this.panelName = panelName; + + Firebug.ActivableModule.initialize.apply(this, arguments); + + if (Firebug.TraceModule) + Firebug.TraceModule.addListener(this.TraceListener); + + // HTTP observer must be registered now (and not in monitorContext, since if a + // page is opened in a new tab the top document request would be missed otherwise. + NetHttpObserver.registerObserver(); + NetHttpActivityObserver.registerObserver(); + + Firebug.Debugger.addListener(this.DebuggerListener); + }, + + shutdown: function() + { + return; + + prefs.removeObserver(Firebug.prefDomain, this, false); + if (Firebug.TraceModule) + Firebug.TraceModule.removeListener(this.TraceListener); + + NetHttpObserver.unregisterObserver(); + NetHttpActivityObserver.unregisterObserver(); + + Firebug.Debugger.removeListener(this.DebuggerListener); + } +}); + + +/** + * @domplate Represents a template that is used to reneder detailed info about a request. + * This template is rendered when a request is expanded. + */ +Firebug.NetMonitor.NetInfoBody = domplate(Firebug.Rep, new Firebug.Listener(), +{ + tag: + DIV({"class": "netInfoBody", _repObject: "$file"}, + TAG("$infoTabs", {file: "$file"}), + TAG("$infoBodies", {file: "$file"}) + ), + + infoTabs: + DIV({"class": "netInfoTabs focusRow subFocusRow", "role": "tablist"}, + A({"class": "netInfoParamsTab netInfoTab a11yFocus", onclick: "$onClickTab", "role": "tab", + view: "Params", + $collapsed: "$file|hideParams"}, + $STR("URLParameters") + ), + A({"class": "netInfoHeadersTab netInfoTab a11yFocus", onclick: "$onClickTab", "role": "tab", + view: "Headers"}, + $STR("Headers") + ), + A({"class": "netInfoPostTab netInfoTab a11yFocus", onclick: "$onClickTab", "role": "tab", + view: "Post", + $collapsed: "$file|hidePost"}, + $STR("Post") + ), + A({"class": "netInfoPutTab netInfoTab a11yFocus", onclick: "$onClickTab", "role": "tab", + view: "Put", + $collapsed: "$file|hidePut"}, + $STR("Put") + ), + A({"class": "netInfoResponseTab netInfoTab a11yFocus", onclick: "$onClickTab", "role": "tab", + view: "Response", + $collapsed: "$file|hideResponse"}, + $STR("Response") + ), + A({"class": "netInfoCacheTab netInfoTab a11yFocus", onclick: "$onClickTab", "role": "tab", + view: "Cache", + $collapsed: "$file|hideCache"}, + $STR("Cache") + ), + A({"class": "netInfoHtmlTab netInfoTab a11yFocus", onclick: "$onClickTab", "role": "tab", + view: "Html", + $collapsed: "$file|hideHtml"}, + $STR("HTML") + ) + ), + + infoBodies: + DIV({"class": "netInfoBodies outerFocusRow"}, + TABLE({"class": "netInfoParamsText netInfoText netInfoParamsTable", "role": "tabpanel", + cellpadding: 0, cellspacing: 0}, TBODY()), + DIV({"class": "netInfoHeadersText netInfoText", "role": "tabpanel"}), + DIV({"class": "netInfoPostText netInfoText", "role": "tabpanel"}), + DIV({"class": "netInfoPutText netInfoText", "role": "tabpanel"}), + PRE({"class": "netInfoResponseText netInfoText", "role": "tabpanel"}), + DIV({"class": "netInfoCacheText netInfoText", "role": "tabpanel"}, + TABLE({"class": "netInfoCacheTable", cellpadding: 0, cellspacing: 0, "role": "presentation"}, + TBODY({"role": "list", "aria-label": $STR("Cache")}) + ) + ), + DIV({"class": "netInfoHtmlText netInfoText", "role": "tabpanel"}, + IFRAME({"class": "netInfoHtmlPreview", "role": "document"}) + ) + ), + + headerDataTag: + FOR("param", "$headers", + TR({"role": "listitem"}, + TD({"class": "netInfoParamName", "role": "presentation"}, + TAG("$param|getNameTag", {param: "$param"}) + ), + TD({"class": "netInfoParamValue", "role": "list", "aria-label": "$param.name"}, + FOR("line", "$param|getParamValueIterator", + CODE({"class": "focusRow subFocusRow", "role": "listitem"}, "$line") + ) + ) + ) + ), + + customTab: + A({"class": "netInfo$tabId\\Tab netInfoTab", onclick: "$onClickTab", view: "$tabId", "role": "tab"}, + "$tabTitle" + ), + + customBody: + DIV({"class": "netInfo$tabId\\Text netInfoText", "role": "tabpanel"}), + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + nameTag: + SPAN("$param|getParamName"), + + nameWithTooltipTag: + SPAN({title: "$param.name"}, "$param|getParamName"), + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + getNameTag: function(param) + { + return (this.getParamName(param) == param.name) ? this.nameTag : this.nameWithTooltipTag; + }, + + getParamName: function(param) + { + var limit = 25; + var name = param.name; + if (name.length > limit) + name = name.substr(0, limit) + "..."; + return name; + }, + + getParamTitle: function(param) + { + var limit = 25; + var name = param.name; + if (name.length > limit) + return name; + return ""; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + hideParams: function(file) + { + return !file.urlParams || !file.urlParams.length; + }, + + hidePost: function(file) + { + return file.method.toUpperCase() != "POST"; + }, + + hidePut: function(file) + { + return file.method.toUpperCase() != "PUT"; + }, + + hideResponse: function(file) + { + return false; + //return file.category in binaryFileCategories; + }, + + hideCache: function(file) + { + return true; + //xxxHonza: I don't see any reason why not to display the cache also info for images. + return !file.cacheEntry; // || file.category=="image"; + }, + + hideHtml: function(file) + { + return (file.mimeType != "text/html") && (file.mimeType != "application/xhtml+xml"); + }, + + onClickTab: function(event) + { + this.selectTab(event.currentTarget || event.srcElement); + }, + + getParamValueIterator: function(param) + { + // TODO: xxxpedro console2 + return param.value; + + // This value is inserted into CODE element and so, make sure the HTML isn't escaped (1210). + // This is why the second parameter is true. + // The CODE (with style white-space:pre) element preserves whitespaces so they are + // displayed the same, as they come from the server (1194). + // In case of a long header values of post parameters the value must be wrapped (2105). + return wrapText(param.value, true); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + appendTab: function(netInfoBox, tabId, tabTitle) + { + // Create new tab and body. + var args = {tabId: tabId, tabTitle: tabTitle}; + ///this.customTab.append(args, netInfoBox.getElementsByClassName("netInfoTabs").item(0)); + ///this.customBody.append(args, netInfoBox.getElementsByClassName("netInfoBodies").item(0)); + this.customTab.append(args, $$(".netInfoTabs", netInfoBox)[0]); + this.customBody.append(args, $$(".netInfoBodies", netInfoBox)[0]); + }, + + selectTabByName: function(netInfoBox, tabName) + { + var tab = getChildByClass(netInfoBox, "netInfoTabs", "netInfo"+tabName+"Tab"); + if (tab) + this.selectTab(tab); + }, + + selectTab: function(tab) + { + var view = tab.getAttribute("view"); + + var netInfoBox = getAncestorByClass(tab, "netInfoBody"); + + var selectedTab = netInfoBox.selectedTab; + + if (selectedTab) + { + //netInfoBox.selectedText.removeAttribute("selected"); + removeClass(netInfoBox.selectedText, "netInfoTextSelected"); + + removeClass(selectedTab, "netInfoTabSelected"); + //selectedTab.removeAttribute("selected"); + selectedTab.setAttribute("aria-selected", "false"); + } + + var textBodyName = "netInfo" + view + "Text"; + + selectedTab = netInfoBox.selectedTab = tab; + + netInfoBox.selectedText = $$("."+textBodyName, netInfoBox)[0]; + //netInfoBox.selectedText = netInfoBox.getElementsByClassName(textBodyName).item(0); + + //netInfoBox.selectedText.setAttribute("selected", "true"); + setClass(netInfoBox.selectedText, "netInfoTextSelected"); + + setClass(selectedTab, "netInfoTabSelected"); + selectedTab.setAttribute("selected", "true"); + selectedTab.setAttribute("aria-selected", "true"); + + var file = Firebug.getRepObject(netInfoBox); + + //var context = Firebug.getElementPanel(netInfoBox).context; + var context = Firebug.chrome; + + this.updateInfo(netInfoBox, file, context); + }, + + updateInfo: function(netInfoBox, file, context) + { + if (FBTrace.DBG_NET) + FBTrace.sysout("net.updateInfo; file", file); + + if (!netInfoBox) + { + if (FBTrace.DBG_NET || FBTrace.DBG_ERRORS) + FBTrace.sysout("net.updateInfo; ERROR netInfo == null " + file.href, file); + return; + } + + var tab = netInfoBox.selectedTab; + + if (hasClass(tab, "netInfoParamsTab")) + { + if (file.urlParams && !netInfoBox.urlParamsPresented) + { + netInfoBox.urlParamsPresented = true; + this.insertHeaderRows(netInfoBox, file.urlParams, "Params"); + } + } + + else if (hasClass(tab, "netInfoHeadersTab")) + { + var headersText = $$(".netInfoHeadersText", netInfoBox)[0]; + //var headersText = netInfoBox.getElementsByClassName("netInfoHeadersText").item(0); + + if (file.responseHeaders && !netInfoBox.responseHeadersPresented) + { + netInfoBox.responseHeadersPresented = true; + NetInfoHeaders.renderHeaders(headersText, file.responseHeaders, "ResponseHeaders"); + } + + if (file.requestHeaders && !netInfoBox.requestHeadersPresented) + { + netInfoBox.requestHeadersPresented = true; + NetInfoHeaders.renderHeaders(headersText, file.requestHeaders, "RequestHeaders"); + } + } + + else if (hasClass(tab, "netInfoPostTab")) + { + if (!netInfoBox.postPresented) + { + netInfoBox.postPresented = true; + //var postText = netInfoBox.getElementsByClassName("netInfoPostText").item(0); + var postText = $$(".netInfoPostText", netInfoBox)[0]; + NetInfoPostData.render(context, postText, file); + } + } + + else if (hasClass(tab, "netInfoPutTab")) + { + if (!netInfoBox.putPresented) + { + netInfoBox.putPresented = true; + //var putText = netInfoBox.getElementsByClassName("netInfoPutText").item(0); + var putText = $$(".netInfoPutText", netInfoBox)[0]; + NetInfoPostData.render(context, putText, file); + } + } + + else if (hasClass(tab, "netInfoResponseTab") && file.loaded && !netInfoBox.responsePresented) + { + ///var responseTextBox = netInfoBox.getElementsByClassName("netInfoResponseText").item(0); + var responseTextBox = $$(".netInfoResponseText", netInfoBox)[0]; + if (file.category == "image") + { + netInfoBox.responsePresented = true; + + var responseImage = netInfoBox.ownerDocument.createElement("img"); + responseImage.src = file.href; + + clearNode(responseTextBox); + responseTextBox.appendChild(responseImage, responseTextBox); + } + else ///if (!(binaryCategoryMap.hasOwnProperty(file.category))) + { + this.setResponseText(file, netInfoBox, responseTextBox, context); + } + } + + else if (hasClass(tab, "netInfoCacheTab") && file.loaded && !netInfoBox.cachePresented) + { + var responseTextBox = netInfoBox.getElementsByClassName("netInfoCacheText").item(0); + if (file.cacheEntry) { + netInfoBox.cachePresented = true; + this.insertHeaderRows(netInfoBox, file.cacheEntry, "Cache"); + } + } + + else if (hasClass(tab, "netInfoHtmlTab") && file.loaded && !netInfoBox.htmlPresented) + { + netInfoBox.htmlPresented = true; + + var text = Utils.getResponseText(file, context); + + ///var iframe = netInfoBox.getElementsByClassName("netInfoHtmlPreview").item(0); + var iframe = $$(".netInfoHtmlPreview", netInfoBox)[0]; + + ///iframe.contentWindow.document.body.innerHTML = text; + + // TODO: xxxpedro net - remove scripts + var reScript = /<script(.|\s)*?\/script>/gi; + + text = text.replace(reScript, ""); + + iframe.contentWindow.document.write(text); + iframe.contentWindow.document.close(); + } + + // Notify listeners about update so, content of custom tabs can be updated. + dispatch(NetInfoBody.fbListeners, "updateTabBody", [netInfoBox, file, context]); + }, + + setResponseText: function(file, netInfoBox, responseTextBox, context) + { + //********************************************** + //********************************************** + //********************************************** + netInfoBox.responsePresented = true; + // line breaks somehow are different in IE + // make this only once in the initialization? we don't have net panels and modules yet. + if (isIE) + responseTextBox.style.whiteSpace = "nowrap"; + + responseTextBox[ + typeof responseTextBox.textContent != "undefined" ? + "textContent" : + "innerText" + ] = file.responseText; + + return; + //********************************************** + //********************************************** + //********************************************** + + // Get response text and make sure it doesn't exceed the max limit. + var text = Utils.getResponseText(file, context); + var limit = Firebug.netDisplayedResponseLimit + 15; + var limitReached = text ? (text.length > limit) : false; + if (limitReached) + text = text.substr(0, limit) + "..."; + + // Insert the response into the UI. + if (text) + insertWrappedText(text, responseTextBox); + else + insertWrappedText("", responseTextBox); + + // Append a message informing the user that the response isn't fully displayed. + if (limitReached) + { + var object = { + text: $STR("net.responseSizeLimitMessage"), + onClickLink: function() { + var panel = context.getPanel("net", true); + panel.openResponseInTab(file); + } + }; + Firebug.NetMonitor.ResponseSizeLimit.append(object, responseTextBox); + } + + netInfoBox.responsePresented = true; + + if (FBTrace.DBG_NET) + FBTrace.sysout("net.setResponseText; response text updated"); + }, + + insertHeaderRows: function(netInfoBox, headers, tableName, rowName) + { + if (!headers.length) + return; + + var headersTable = $$(".netInfo"+tableName+"Table", netInfoBox)[0]; + //var headersTable = netInfoBox.getElementsByClassName("netInfo"+tableName+"Table").item(0); + var tbody = getChildByClass(headersTable, "netInfo" + rowName + "Body"); + if (!tbody) + tbody = headersTable.firstChild; + var titleRow = getChildByClass(tbody, "netInfo" + rowName + "Title"); + + this.headerDataTag.insertRows({headers: headers}, titleRow ? titleRow : tbody); + removeClass(titleRow, "collapsed"); + } +}); + +var NetInfoBody = Firebug.NetMonitor.NetInfoBody; + +// ************************************************************************************************ + +/** + * @domplate Used within the Net panel to display raw source of request and response headers + * as well as pretty-formatted summary of these headers. + */ +Firebug.NetMonitor.NetInfoHeaders = domplate(Firebug.Rep, //new Firebug.Listener(), +{ + tag: + DIV({"class": "netInfoHeadersTable", "role": "tabpanel"}, + DIV({"class": "netInfoHeadersGroup netInfoResponseHeadersTitle"}, + SPAN($STR("ResponseHeaders")), + SPAN({"class": "netHeadersViewSource response collapsed", onclick: "$onViewSource", + _sourceDisplayed: false, _rowName: "ResponseHeaders"}, + $STR("net.headers.view source") + ) + ), + TABLE({cellpadding: 0, cellspacing: 0}, + TBODY({"class": "netInfoResponseHeadersBody", "role": "list", + "aria-label": $STR("ResponseHeaders")}) + ), + DIV({"class": "netInfoHeadersGroup netInfoRequestHeadersTitle"}, + SPAN($STR("RequestHeaders")), + SPAN({"class": "netHeadersViewSource request collapsed", onclick: "$onViewSource", + _sourceDisplayed: false, _rowName: "RequestHeaders"}, + $STR("net.headers.view source") + ) + ), + TABLE({cellpadding: 0, cellspacing: 0}, + TBODY({"class": "netInfoRequestHeadersBody", "role": "list", + "aria-label": $STR("RequestHeaders")}) + ) + ), + + sourceTag: + TR({"role": "presentation"}, + TD({colspan: 2, "role": "presentation"}, + PRE({"class": "source"}) + ) + ), + + onViewSource: function(event) + { + var target = event.target; + var requestHeaders = (target.rowName == "RequestHeaders"); + + var netInfoBox = getAncestorByClass(target, "netInfoBody"); + var file = netInfoBox.repObject; + + if (target.sourceDisplayed) + { + var headers = requestHeaders ? file.requestHeaders : file.responseHeaders; + this.insertHeaderRows(netInfoBox, headers, target.rowName); + target.innerHTML = $STR("net.headers.view source"); + } + else + { + var source = requestHeaders ? file.requestHeadersText : file.responseHeadersText; + this.insertSource(netInfoBox, source, target.rowName); + target.innerHTML = $STR("net.headers.pretty print"); + } + + target.sourceDisplayed = !target.sourceDisplayed; + + cancelEvent(event); + }, + + insertSource: function(netInfoBox, source, rowName) + { + // This breaks copy to clipboard. + //if (source) + // source = source.replace(/\r\n/gm, "<span style='color:lightgray'>\\r\\n</span>\r\n"); + + ///var tbody = netInfoBox.getElementsByClassName("netInfo" + rowName + "Body").item(0); + var tbody = $$(".netInfo" + rowName + "Body", netInfoBox)[0]; + var node = this.sourceTag.replace({}, tbody); + ///var sourceNode = node.getElementsByClassName("source").item(0); + var sourceNode = $$(".source", node)[0]; + sourceNode.innerHTML = source; + }, + + insertHeaderRows: function(netInfoBox, headers, rowName) + { + var headersTable = $$(".netInfoHeadersTable", netInfoBox)[0]; + var tbody = $$(".netInfo" + rowName + "Body", headersTable)[0]; + + //var headersTable = netInfoBox.getElementsByClassName("netInfoHeadersTable").item(0); + //var tbody = headersTable.getElementsByClassName("netInfo" + rowName + "Body").item(0); + + clearNode(tbody); + + if (!headers.length) + return; + + NetInfoBody.headerDataTag.insertRows({headers: headers}, tbody); + + var titleRow = getChildByClass(headersTable, "netInfo" + rowName + "Title"); + removeClass(titleRow, "collapsed"); + }, + + init: function(parent) + { + var rootNode = this.tag.append({}, parent); + + var netInfoBox = getAncestorByClass(parent, "netInfoBody"); + var file = netInfoBox.repObject; + + var viewSource; + + viewSource = $$(".request", rootNode)[0]; + //viewSource = rootNode.getElementsByClassName("netHeadersViewSource request").item(0); + if (file.requestHeadersText) + removeClass(viewSource, "collapsed"); + + viewSource = $$(".response", rootNode)[0]; + //viewSource = rootNode.getElementsByClassName("netHeadersViewSource response").item(0); + if (file.responseHeadersText) + removeClass(viewSource, "collapsed"); + }, + + renderHeaders: function(parent, headers, rowName) + { + if (!parent.firstChild) + this.init(parent); + + this.insertHeaderRows(parent, headers, rowName); + } +}); + +var NetInfoHeaders = Firebug.NetMonitor.NetInfoHeaders; + +// ************************************************************************************************ + +/** + * @domplate Represents posted data within request info (the info, which is visible when + * a request entry is expanded. This template renders content of the Post tab. + */ +Firebug.NetMonitor.NetInfoPostData = domplate(Firebug.Rep, /*new Firebug.Listener(),*/ +{ + // application/x-www-form-urlencoded + paramsTable: + TABLE({"class": "netInfoPostParamsTable", cellpadding: 0, cellspacing: 0, "role": "presentation"}, + TBODY({"role": "list", "aria-label": $STR("net.label.Parameters")}, + TR({"class": "netInfoPostParamsTitle", "role": "presentation"}, + TD({colspan: 3, "role": "presentation"}, + DIV({"class": "netInfoPostParams"}, + $STR("net.label.Parameters"), + SPAN({"class": "netInfoPostContentType"}, + "application/x-www-form-urlencoded" + ) + ) + ) + ) + ) + ), + + // multipart/form-data + partsTable: + TABLE({"class": "netInfoPostPartsTable", cellpadding: 0, cellspacing: 0, "role": "presentation"}, + TBODY({"role": "list", "aria-label": $STR("net.label.Parts")}, + TR({"class": "netInfoPostPartsTitle", "role": "presentation"}, + TD({colspan: 2, "role":"presentation" }, + DIV({"class": "netInfoPostParams"}, + $STR("net.label.Parts"), + SPAN({"class": "netInfoPostContentType"}, + "multipart/form-data" + ) + ) + ) + ) + ) + ), + + // application/json + jsonTable: + TABLE({"class": "netInfoPostJSONTable", cellpadding: 0, cellspacing: 0, "role": "presentation"}, + ///TBODY({"role": "list", "aria-label": $STR("jsonviewer.tab.JSON")}, + TBODY({"role": "list", "aria-label": $STR("JSON")}, + TR({"class": "netInfoPostJSONTitle", "role": "presentation"}, + TD({"role": "presentation" }, + DIV({"class": "netInfoPostParams"}, + ///$STR("jsonviewer.tab.JSON") + $STR("JSON") + ) + ) + ), + TR( + TD({"class": "netInfoPostJSONBody"}) + ) + ) + ), + + // application/xml + xmlTable: + TABLE({"class": "netInfoPostXMLTable", cellpadding: 0, cellspacing: 0, "role": "presentation"}, + TBODY({"role": "list", "aria-label": $STR("xmlviewer.tab.XML")}, + TR({"class": "netInfoPostXMLTitle", "role": "presentation"}, + TD({"role": "presentation" }, + DIV({"class": "netInfoPostParams"}, + $STR("xmlviewer.tab.XML") + ) + ) + ), + TR( + TD({"class": "netInfoPostXMLBody"}) + ) + ) + ), + + sourceTable: + TABLE({"class": "netInfoPostSourceTable", cellpadding: 0, cellspacing: 0, "role": "presentation"}, + TBODY({"role": "list", "aria-label": $STR("net.label.Source")}, + TR({"class": "netInfoPostSourceTitle", "role": "presentation"}, + TD({colspan: 2, "role": "presentation"}, + DIV({"class": "netInfoPostSource"}, + $STR("net.label.Source") + ) + ) + ) + ) + ), + + sourceBodyTag: + TR({"role": "presentation"}, + TD({colspan: 2, "role": "presentation"}, + FOR("line", "$param|getParamValueIterator", + CODE({"class":"focusRow subFocusRow" , "role": "listitem"},"$line") + ) + ) + ), + + getParamValueIterator: function(param) + { + return NetInfoBody.getParamValueIterator(param); + }, + + render: function(context, parentNode, file) + { + //debugger; + var spy = getAncestorByClass(parentNode, "spyHead"); + var spyObject = spy.repObject; + var data = spyObject.data; + + ///var contentType = Utils.findHeader(file.requestHeaders, "content-type"); + var contentType = file.mimeType; + + ///var text = Utils.getPostText(file, context, true); + ///if (text == undefined) + /// return; + + ///if (Utils.isURLEncodedRequest(file, context)) + // fake Utils.isURLEncodedRequest identification + if (contentType && contentType == "application/x-www-form-urlencoded" || + data && data.indexOf("=") != -1) + { + ///var lines = text.split("\n"); + ///var params = parseURLEncodedText(lines[lines.length-1]); + var params = parseURLEncodedTextArray(data); + if (params) + this.insertParameters(parentNode, params); + } + + ///if (Utils.isMultiPartRequest(file, context)) + ///{ + /// var data = this.parseMultiPartText(file, context); + /// if (data) + /// this.insertParts(parentNode, data); + ///} + + // moved to the top + ///var contentType = Utils.findHeader(file.requestHeaders, "content-type"); + + ///if (Firebug.JSONViewerModel.isJSON(contentType)) + var jsonData = { + responseText: data + }; + + if (Firebug.JSONViewerModel.isJSON(contentType, data)) + ///this.insertJSON(parentNode, file, context); + this.insertJSON(parentNode, jsonData, context); + + ///if (Firebug.XMLViewerModel.isXML(contentType)) + /// this.insertXML(parentNode, file, context); + + ///var postText = Utils.getPostText(file, context); + ///postText = Utils.formatPostText(postText); + var postText = data; + if (postText) + this.insertSource(parentNode, postText); + }, + + insertParameters: function(parentNode, params) + { + if (!params || !params.length) + return; + + var paramTable = this.paramsTable.append({object:{}}, parentNode); + var row = $$(".netInfoPostParamsTitle", paramTable)[0]; + //var paramTable = this.paramsTable.append(null, parentNode); + //var row = paramTable.getElementsByClassName("netInfoPostParamsTitle").item(0); + + var tbody = paramTable.getElementsByTagName("tbody")[0]; + + NetInfoBody.headerDataTag.insertRows({headers: params}, row); + }, + + insertParts: function(parentNode, data) + { + if (!data.params || !data.params.length) + return; + + var partsTable = this.partsTable.append({object:{}}, parentNode); + var row = $$(".netInfoPostPartsTitle", paramTable)[0]; + //var partsTable = this.partsTable.append(null, parentNode); + //var row = partsTable.getElementsByClassName("netInfoPostPartsTitle").item(0); + + NetInfoBody.headerDataTag.insertRows({headers: data.params}, row); + }, + + insertJSON: function(parentNode, file, context) + { + ///var text = Utils.getPostText(file, context); + var text = file.responseText; + ///var data = parseJSONString(text, "http://" + file.request.originalURI.host); + var data = parseJSONString(text); + if (!data) + return; + + ///var jsonTable = this.jsonTable.append(null, parentNode); + var jsonTable = this.jsonTable.append({}, parentNode); + ///var jsonBody = jsonTable.getElementsByClassName("netInfoPostJSONBody").item(0); + var jsonBody = $$(".netInfoPostJSONBody", jsonTable)[0]; + + if (!this.toggles) + this.toggles = {}; + + Firebug.DOMPanel.DirTable.tag.replace( + {object: data, toggles: this.toggles}, jsonBody); + }, + + insertXML: function(parentNode, file, context) + { + var text = Utils.getPostText(file, context); + + var jsonTable = this.xmlTable.append(null, parentNode); + ///var jsonBody = jsonTable.getElementsByClassName("netInfoPostXMLBody").item(0); + var jsonBody = $$(".netInfoPostXMLBody", jsonTable)[0]; + + Firebug.XMLViewerModel.insertXML(jsonBody, text); + }, + + insertSource: function(parentNode, text) + { + var sourceTable = this.sourceTable.append({object:{}}, parentNode); + var row = $$(".netInfoPostSourceTitle", sourceTable)[0]; + //var sourceTable = this.sourceTable.append(null, parentNode); + //var row = sourceTable.getElementsByClassName("netInfoPostSourceTitle").item(0); + + var param = {value: [text]}; + this.sourceBodyTag.insertRows({param: param}, row); + }, + + parseMultiPartText: function(file, context) + { + var text = Utils.getPostText(file, context); + if (text == undefined) + return null; + + FBTrace.sysout("net.parseMultiPartText; boundary: ", text); + + var boundary = text.match(/\s*boundary=\s*(.*)/)[1]; + + var divider = "\r\n\r\n"; + var bodyStart = text.indexOf(divider); + var body = text.substr(bodyStart + divider.length); + + var postData = {}; + postData.mimeType = "multipart/form-data"; + postData.params = []; + + var parts = body.split("--" + boundary); + for (var i=0; i<parts.length; i++) + { + var part = parts[i].split(divider); + if (part.length != 2) + continue; + + var m = part[0].match(/\s*name=\"(.*)\"(;|$)/); + postData.params.push({ + name: (m && m.length > 1) ? m[1] : "", + value: trim(part[1]) + }); + } + + return postData; + } +}); + +var NetInfoPostData = Firebug.NetMonitor.NetInfoPostData; + +// ************************************************************************************************ + + +// TODO: xxxpedro net i18n +var $STRP = function(a){return a;}; + +Firebug.NetMonitor.NetLimit = domplate(Firebug.Rep, +{ + collapsed: true, + + tableTag: + DIV( + TABLE({width: "100%", cellpadding: 0, cellspacing: 0}, + TBODY() + ) + ), + + limitTag: + TR({"class": "netRow netLimitRow", $collapsed: "$isCollapsed"}, + TD({"class": "netCol netLimitCol", colspan: 6}, + TABLE({cellpadding: 0, cellspacing: 0}, + TBODY( + TR( + TD( + SPAN({"class": "netLimitLabel"}, + $STRP("plural.Limit_Exceeded", [0]) + ) + ), + TD({style: "width:100%"}), + TD( + BUTTON({"class": "netLimitButton", title: "$limitPrefsTitle", + onclick: "$onPreferences"}, + $STR("LimitPrefs") + ) + ), + TD(" ") + ) + ) + ) + ) + ), + + isCollapsed: function() + { + return this.collapsed; + }, + + onPreferences: function(event) + { + openNewTab("about:config"); + }, + + updateCounter: function(row) + { + removeClass(row, "collapsed"); + + // Update info within the limit row. + var limitLabel = row.getElementsByClassName("netLimitLabel").item(0); + limitLabel.firstChild.nodeValue = $STRP("plural.Limit_Exceeded", [row.limitInfo.totalCount]); + }, + + createTable: function(parent, limitInfo) + { + var table = this.tableTag.replace({}, parent); + var row = this.createRow(table.firstChild.firstChild, limitInfo); + return [table, row]; + }, + + createRow: function(parent, limitInfo) + { + var row = this.limitTag.insertRows(limitInfo, parent, this)[0]; + row.limitInfo = limitInfo; + return row; + }, + + // nsIPrefObserver + observe: function(subject, topic, data) + { + // We're observing preferences only. + if (topic != "nsPref:changed") + return; + + if (data.indexOf("net.logLimit") != -1) + this.updateMaxLimit(); + }, + + updateMaxLimit: function() + { + var value = Firebug.getPref(Firebug.prefDomain, "net.logLimit"); + maxQueueRequests = value ? value : maxQueueRequests; + } +}); + +var NetLimit = Firebug.NetMonitor.NetLimit; + +// ************************************************************************************************ + +Firebug.NetMonitor.ResponseSizeLimit = domplate(Firebug.Rep, +{ + tag: + DIV({"class": "netInfoResponseSizeLimit"}, + SPAN("$object.beforeLink"), + A({"class": "objectLink", onclick: "$onClickLink"}, + "$object.linkText" + ), + SPAN("$object.afterLink") + ), + + reLink: /^(.*)<a>(.*)<\/a>(.*$)/, + append: function(obj, parent) + { + var m = obj.text.match(this.reLink); + return this.tag.append({onClickLink: obj.onClickLink, + object: { + beforeLink: m[1], + linkText: m[2], + afterLink: m[3] + }}, parent, this); + } +}); + +// ************************************************************************************************ +// ************************************************************************************************ + +Firebug.NetMonitor.Utils = +{ + findHeader: function(headers, name) + { + if (!headers) + return null; + + name = name.toLowerCase(); + for (var i = 0; i < headers.length; ++i) + { + var headerName = headers[i].name.toLowerCase(); + if (headerName == name) + return headers[i].value; + } + }, + + formatPostText: function(text) + { + if (text instanceof XMLDocument) + return getElementXML(text.documentElement); + else + return text; + }, + + getPostText: function(file, context, noLimit) + { + if (!file.postText) + { + file.postText = readPostTextFromRequest(file.request, context); + + if (!file.postText && context) + file.postText = readPostTextFromPage(file.href, context); + } + + if (!file.postText) + return file.postText; + + var limit = Firebug.netDisplayedPostBodyLimit; + if (file.postText.length > limit && !noLimit) + { + return cropString(file.postText, limit, + "\n\n... " + $STR("net.postDataSizeLimitMessage") + " ...\n\n"); + } + + return file.postText; + }, + + getResponseText: function(file, context) + { + // The response can be also empty string so, check agains "undefined". + return (typeof(file.responseText) != "undefined")? file.responseText : + context.sourceCache.loadText(file.href, file.method, file); + }, + + isURLEncodedRequest: function(file, context) + { + var text = Utils.getPostText(file, context); + if (text && text.toLowerCase().indexOf("content-type: application/x-www-form-urlencoded") == 0) + return true; + + // The header value doesn't have to be always exactly "application/x-www-form-urlencoded", + // there can be even charset specified. So, use indexOf rather than just "==". + var headerValue = Utils.findHeader(file.requestHeaders, "content-type"); + if (headerValue && headerValue.indexOf("application/x-www-form-urlencoded") == 0) + return true; + + return false; + }, + + isMultiPartRequest: function(file, context) + { + var text = Utils.getPostText(file, context); + if (text && text.toLowerCase().indexOf("content-type: multipart/form-data") == 0) + return true; + return false; + }, + + getMimeType: function(mimeType, uri) + { + if (!mimeType || !(mimeCategoryMap.hasOwnProperty(mimeType))) + { + var ext = getFileExtension(uri); + if (!ext) + return mimeType; + else + { + var extMimeType = mimeExtensionMap[ext.toLowerCase()]; + return extMimeType ? extMimeType : mimeType; + } + } + else + return mimeType; + }, + + getDateFromSeconds: function(s) + { + var d = new Date(); + d.setTime(s*1000); + return d; + }, + + getHttpHeaders: function(request, file) + { + try + { + var http = QI(request, Ci.nsIHttpChannel); + file.status = request.responseStatus; + + // xxxHonza: is there any problem to do this in requestedFile method? + file.method = http.requestMethod; + file.urlParams = parseURLParams(file.href); + file.mimeType = Utils.getMimeType(request.contentType, request.name); + + if (!file.responseHeaders && Firebug.collectHttpHeaders) + { + var requestHeaders = [], responseHeaders = []; + + http.visitRequestHeaders({ + visitHeader: function(name, value) + { + requestHeaders.push({name: name, value: value}); + } + }); + http.visitResponseHeaders({ + visitHeader: function(name, value) + { + responseHeaders.push({name: name, value: value}); + } + }); + + file.requestHeaders = requestHeaders; + file.responseHeaders = responseHeaders; + } + } + catch (exc) + { + // An exception can be throwed e.g. when the request is aborted and + // request.responseStatus is accessed. + if (FBTrace.DBG_ERRORS) + FBTrace.sysout("net.getHttpHeaders FAILS " + file.href, exc); + } + }, + + isXHR: function(request) + { + try + { + var callbacks = request.notificationCallbacks; + var xhrRequest = callbacks ? callbacks.getInterface(Ci.nsIXMLHttpRequest) : null; + if (FBTrace.DBG_NET) + FBTrace.sysout("net.isXHR; " + (xhrRequest != null) + ", " + safeGetName(request)); + + return (xhrRequest != null); + } + catch (exc) + { + } + + return false; + }, + + getFileCategory: function(file) + { + if (file.category) + { + if (FBTrace.DBG_NET) + FBTrace.sysout("net.getFileCategory; current: " + file.category + " for: " + file.href, file); + return file.category; + } + + if (file.isXHR) + { + if (FBTrace.DBG_NET) + FBTrace.sysout("net.getFileCategory; XHR for: " + file.href, file); + return file.category = "xhr"; + } + + if (!file.mimeType) + { + var ext = getFileExtension(file.href); + if (ext) + file.mimeType = mimeExtensionMap[ext.toLowerCase()]; + } + + /*if (FBTrace.DBG_NET) + FBTrace.sysout("net.getFileCategory; " + mimeCategoryMap[file.mimeType] + + ", mimeType: " + file.mimeType + " for: " + file.href, file);*/ + + if (!file.mimeType) + return ""; + + // Solve cases when charset is also specified, eg "text/html; charset=UTF-8". + var mimeType = file.mimeType; + if (mimeType) + mimeType = mimeType.split(";")[0]; + + return (file.category = mimeCategoryMap[mimeType]); + } +}; + +var Utils = Firebug.NetMonitor.Utils; + +// ************************************************************************************************ + +//Firebug.registerRep(Firebug.NetMonitor.NetRequestTable); +//Firebug.registerActivableModule(Firebug.NetMonitor); +//Firebug.registerPanel(NetPanel); + +Firebug.registerModule(Firebug.NetMonitor); +//Firebug.registerRep(Firebug.NetMonitor.BreakpointRep); + +// ************************************************************************************************ +}}); + + +/* See license.txt for terms of usage */ + +FBL.ns(function() { with (FBL) { + +// ************************************************************************************************ +// Constants + +//const Cc = Components.classes; +//const Ci = Components.interfaces; + +// List of contexts with XHR spy attached. +var contexts = []; + +// ************************************************************************************************ +// Spy Module + +/** + * @module Represents a XHR Spy module. The main purpose of the XHR Spy feature is to monitor + * XHR activity of the current page and create appropriate log into the Console panel. + * This feature can be controlled by an option <i>Show XMLHttpRequests</i> (from within the + * console panel). + * + * The module is responsible for attaching/detaching a HTTP Observers when Firebug is + * activated/deactivated for a site. + */ +Firebug.Spy = extend(Firebug.Module, +/** @lends Firebug.Spy */ +{ + dispatchName: "spy", + + initialize: function() + { + if (Firebug.TraceModule) + Firebug.TraceModule.addListener(this.TraceListener); + + Firebug.Module.initialize.apply(this, arguments); + }, + + shutdown: function() + { + Firebug.Module.shutdown.apply(this, arguments); + + if (Firebug.TraceModule) + Firebug.TraceModule.removeListener(this.TraceListener); + }, + + initContext: function(context) + { + context.spies = []; + + if (Firebug.showXMLHttpRequests && Firebug.Console.isAlwaysEnabled()) + this.attachObserver(context, context.window); + + if (FBTrace.DBG_SPY) + FBTrace.sysout("spy.initContext " + contexts.length + " ", context.getName()); + }, + + destroyContext: function(context) + { + // For any spies that are in progress, remove our listeners so that they don't leak + this.detachObserver(context, null); + + if (FBTrace.DBG_SPY && context.spies.length) + FBTrace.sysout("spy.destroyContext; ERROR There are leaking Spies (" + + context.spies.length + ") " + context.getName()); + + delete context.spies; + + if (FBTrace.DBG_SPY) + FBTrace.sysout("spy.destroyContext " + contexts.length + " ", context.getName()); + }, + + watchWindow: function(context, win) + { + if (Firebug.showXMLHttpRequests && Firebug.Console.isAlwaysEnabled()) + this.attachObserver(context, win); + }, + + unwatchWindow: function(context, win) + { + try + { + // This make sure that the existing context is properly removed from "contexts" array. + this.detachObserver(context, win); + } + catch (ex) + { + // Get exceptions here sometimes, so let's just ignore them + // since the window is going away anyhow + ERROR(ex); + } + }, + + updateOption: function(name, value) + { + // XXXjjb Honza, if Console.isEnabled(context) false, then this can't be called, + // but somehow seems not correct + if (name == "showXMLHttpRequests") + { + var tach = value ? this.attachObserver : this.detachObserver; + for (var i = 0; i < TabWatcher.contexts.length; ++i) + { + var context = TabWatcher.contexts[i]; + iterateWindows(context.window, function(win) + { + tach.apply(this, [context, win]); + }); + } + } + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Attaching Spy to XHR requests. + + /** + * Returns false if Spy should not be attached to XHRs executed by the specified window. + */ + skipSpy: function(win) + { + if (!win) + return true; + + // Don't attach spy to chrome. + var uri = safeGetWindowLocation(win); + if (uri && (uri.indexOf("about:") == 0 || uri.indexOf("chrome:") == 0)) + return true; + }, + + attachObserver: function(context, win) + { + if (Firebug.Spy.skipSpy(win)) + return; + + for (var i=0; i<contexts.length; ++i) + { + if ((contexts[i].context == context) && (contexts[i].win == win)) + return; + } + + // Register HTTP observers only once. + if (contexts.length == 0) + { + httpObserver.addObserver(SpyHttpObserver, "firebug-http-event", false); + SpyHttpActivityObserver.registerObserver(); + } + + contexts.push({context: context, win: win}); + + if (FBTrace.DBG_SPY) + FBTrace.sysout("spy.attachObserver (HTTP) " + contexts.length + " ", context.getName()); + }, + + detachObserver: function(context, win) + { + for (var i=0; i<contexts.length; ++i) + { + if (contexts[i].context == context) + { + if (win && (contexts[i].win != win)) + continue; + + contexts.splice(i, 1); + + // If no context is using spy, remvove the (only one) HTTP observer. + if (contexts.length == 0) + { + httpObserver.removeObserver(SpyHttpObserver, "firebug-http-event"); + SpyHttpActivityObserver.unregisterObserver(); + } + + if (FBTrace.DBG_SPY) + FBTrace.sysout("spy.detachObserver (HTTP) " + contexts.length + " ", + context.getName()); + return; + } + } + }, + + /** + * Return XHR object that is associated with specified request <i>nsIHttpChannel</i>. + * Returns null if the request doesn't represent XHR. + */ + getXHR: function(request) + { + // Does also query-interface for nsIHttpChannel. + if (!(request instanceof Ci.nsIHttpChannel)) + return null; + + try + { + var callbacks = request.notificationCallbacks; + return (callbacks ? callbacks.getInterface(Ci.nsIXMLHttpRequest) : null); + } + catch (exc) + { + if (exc.name == "NS_NOINTERFACE") + { + if (FBTrace.DBG_SPY) + FBTrace.sysout("spy.getXHR; Request is not nsIXMLHttpRequest: " + + safeGetRequestName(request)); + } + } + + return null; + } +}); + + + + + +// ************************************************************************************************ + +/* +function getSpyForXHR(request, xhrRequest, context, noCreate) +{ + var spy = null; + + // Iterate all existing spy objects in this context and look for one that is + // already created for this request. + var length = context.spies.length; + for (var i=0; i<length; i++) + { + spy = context.spies[i]; + if (spy.request == request) + return spy; + } + + if (noCreate) + return null; + + spy = new Firebug.Spy.XMLHttpRequestSpy(request, xhrRequest, context); + context.spies.push(spy); + + var name = request.URI.asciiSpec; + var origName = request.originalURI.asciiSpec; + + // Attach spy only to the original request. Notice that there can be more network requests + // made by the same XHR if redirects are involved. + if (name == origName) + spy.attach(); + + if (FBTrace.DBG_SPY) + FBTrace.sysout("spy.getSpyForXHR; New spy object created (" + + (name == origName ? "new XHR" : "redirected XHR") + ") for: " + name, spy); + + return spy; +} +/**/ + +// ************************************************************************************************ + +/** + * @class This class represents a Spy object that is attached to XHR. This object + * registers various listeners into the XHR in order to monitor various events fired + * during the request process (onLoad, onAbort, etc.) + */ +/* +Firebug.Spy.XMLHttpRequestSpy = function(request, xhrRequest, context) +{ + this.request = request; + this.xhrRequest = xhrRequest; + this.context = context; + this.responseText = ""; + + // For compatibility with the Net templates. + this.isXHR = true; + + // Support for activity-observer + this.transactionStarted = false; + this.transactionClosed = false; +}; +/**/ + +//Firebug.Spy.XMLHttpRequestSpy.prototype = +/** @lends Firebug.Spy.XMLHttpRequestSpy */ +/* +{ + attach: function() + { + var spy = this; + this.onReadyStateChange = function(event) { onHTTPSpyReadyStateChange(spy, event); }; + this.onLoad = function() { onHTTPSpyLoad(spy); }; + this.onError = function() { onHTTPSpyError(spy); }; + this.onAbort = function() { onHTTPSpyAbort(spy); }; + + // xxxHonza: #502959 is still failing on Fx 3.5 + // Use activity distributor to identify 3.6 + if (SpyHttpActivityObserver.getActivityDistributor()) + { + this.onreadystatechange = this.xhrRequest.onreadystatechange; + this.xhrRequest.onreadystatechange = this.onReadyStateChange; + } + + this.xhrRequest.addEventListener("load", this.onLoad, false); + this.xhrRequest.addEventListener("error", this.onError, false); + this.xhrRequest.addEventListener("abort", this.onAbort, false); + + // xxxHonza: should be removed from FB 3.6 + if (!SpyHttpActivityObserver.getActivityDistributor()) + this.context.sourceCache.addListener(this); + }, + + detach: function() + { + // Bubble out if already detached. + if (!this.onLoad) + return; + + // If the activity distributor is available, let's detach it when the XHR + // transaction is closed. Since, in case of multipart XHRs the onLoad method + // (readyState == 4) can be called mutliple times. + // Keep in mind: + // 1) It can happen that that the TRANSACTION_CLOSE event comes before + // the onLoad (if the XHR is made as part of the page load) so, detach if + // it's already closed. + // 2) In case of immediate cache responses, the transaction doesn't have to + // be started at all (or the activity observer is no available in Firefox 3.5). + // So, also detach in this case. + if (this.transactionStarted && !this.transactionClosed) + return; + + if (FBTrace.DBG_SPY) + FBTrace.sysout("spy.detach; " + this.href); + + // Remove itself from the list of active spies. + remove(this.context.spies, this); + + if (this.onreadystatechange) + this.xhrRequest.onreadystatechange = this.onreadystatechange; + + try { this.xhrRequest.removeEventListener("load", this.onLoad, false); } catch (e) {} + try { this.xhrRequest.removeEventListener("error", this.onError, false); } catch (e) {} + try { this.xhrRequest.removeEventListener("abort", this.onAbort, false); } catch (e) {} + + this.onreadystatechange = null; + this.onLoad = null; + this.onError = null; + this.onAbort = null; + + // xxxHonza: shouuld be removed from FB 1.6 + if (!SpyHttpActivityObserver.getActivityDistributor()) + this.context.sourceCache.removeListener(this); + }, + + getURL: function() + { + return this.xhrRequest.channel ? this.xhrRequest.channel.name : this.href; + }, + + // Cache listener + onStopRequest: function(context, request, responseText) + { + if (!responseText) + return; + + if (request == this.request) + this.responseText = responseText; + }, +}; +/**/ +// ************************************************************************************************ +/* +function onHTTPSpyReadyStateChange(spy, event) +{ + if (FBTrace.DBG_SPY) + FBTrace.sysout("spy.onHTTPSpyReadyStateChange " + spy.xhrRequest.readyState + + " (multipart: " + spy.xhrRequest.multipart + ")"); + + // Remember just in case spy is detached (readyState == 4). + var originalHandler = spy.onreadystatechange; + + // Force response text to be updated in the UI (in case the console entry + // has been already expanded and the response tab selected). + if (spy.logRow && spy.xhrRequest.readyState >= 3) + { + var netInfoBox = getChildByClass(spy.logRow, "spyHead", "netInfoBody"); + if (netInfoBox) + { + netInfoBox.htmlPresented = false; + netInfoBox.responsePresented = false; + } + } + + // If the request is loading update the end time. + if (spy.xhrRequest.readyState == 3) + { + spy.responseTime = spy.endTime - spy.sendTime; + updateTime(spy); + } + + // Request loaded. Get all the info from the request now, just in case the + // XHR would be aborted in the original onReadyStateChange handler. + if (spy.xhrRequest.readyState == 4) + { + // Cumulate response so, multipart response content is properly displayed. + if (SpyHttpActivityObserver.getActivityDistributor()) + spy.responseText += spy.xhrRequest.responseText; + else + { + // xxxHonza: remove from FB 1.6 + if (!spy.responseText) + spy.responseText = spy.xhrRequest.responseText; + } + + // The XHR is loaded now (used also by the activity observer). + spy.loaded = true; + + // Update UI. + updateHttpSpyInfo(spy); + + // Notify Net pane about a request beeing loaded. + // xxxHonza: I don't think this is necessary. + var netProgress = spy.context.netProgress; + if (netProgress) + netProgress.post(netProgress.stopFile, [spy.request, spy.endTime, spy.postText, spy.responseText]); + + // Notify registered listeners about finish of the XHR. + dispatch(Firebug.Spy.fbListeners, "onLoad", [spy.context, spy]); + } + + // Pass the event to the original page handler. + callPageHandler(spy, event, originalHandler); +} + +function onHTTPSpyLoad(spy) +{ + if (FBTrace.DBG_SPY) + FBTrace.sysout("spy.onHTTPSpyLoad: " + spy.href, spy); + + // Detach must be done in onLoad (not in onreadystatechange) otherwise + // onAbort would not be handled. + spy.detach(); + + // xxxHonza: Still needed for Fx 3.5 (#502959) + if (!SpyHttpActivityObserver.getActivityDistributor()) + onHTTPSpyReadyStateChange(spy, null); +} + +function onHTTPSpyError(spy) +{ + if (FBTrace.DBG_SPY) + FBTrace.sysout("spy.onHTTPSpyError; " + spy.href, spy); + + spy.detach(); + spy.loaded = true; + + if (spy.logRow) + { + removeClass(spy.logRow, "loading"); + setClass(spy.logRow, "error"); + } +} + +function onHTTPSpyAbort(spy) +{ + if (FBTrace.DBG_SPY) + FBTrace.sysout("spy.onHTTPSpyAbort: " + spy.href, spy); + + spy.detach(); + spy.loaded = true; + + if (spy.logRow) + { + removeClass(spy.logRow, "loading"); + setClass(spy.logRow, "error"); + } + + spy.statusText = "Aborted"; + updateLogRow(spy); + + // Notify Net pane about a request beeing aborted. + // xxxHonza: the net panel shoud find out this itself. + var netProgress = spy.context.netProgress; + if (netProgress) + netProgress.post(netProgress.abortFile, [spy.request, spy.endTime, spy.postText, spy.responseText]); +} +/**/ + +// ************************************************************************************************ + +/** + * @domplate Represents a template for XHRs logged in the Console panel. The body of the + * log (displayed when expanded) is rendered using {@link Firebug.NetMonitor.NetInfoBody}. + */ + +Firebug.Spy.XHR = domplate(Firebug.Rep, +/** @lends Firebug.Spy.XHR */ + +{ + tag: + DIV({"class": "spyHead", _repObject: "$object"}, + TABLE({"class": "spyHeadTable focusRow outerFocusRow", cellpadding: 0, cellspacing: 0, + "role": "listitem", "aria-expanded": "false"}, + TBODY({"role": "presentation"}, + TR({"class": "spyRow"}, + TD({"class": "spyTitleCol spyCol", onclick: "$onToggleBody"}, + DIV({"class": "spyTitle"}, + "$object|getCaption" + ), + DIV({"class": "spyFullTitle spyTitle"}, + "$object|getFullUri" + ) + ), + TD({"class": "spyCol"}, + DIV({"class": "spyStatus"}, "$object|getStatus") + ), + TD({"class": "spyCol"}, + SPAN({"class": "spyIcon"}) + ), + TD({"class": "spyCol"}, + SPAN({"class": "spyTime"}) + ), + TD({"class": "spyCol"}, + TAG(FirebugReps.SourceLink.tag, {object: "$object.sourceLink"}) + ) + ) + ) + ) + ), + + getCaption: function(spy) + { + return spy.method.toUpperCase() + " " + cropString(spy.getURL(), 100); + }, + + getFullUri: function(spy) + { + return spy.method.toUpperCase() + " " + spy.getURL(); + }, + + getStatus: function(spy) + { + var text = ""; + if (spy.statusCode) + text += spy.statusCode + " "; + + if (spy.statusText) + return text += spy.statusText; + + return text; + }, + + onToggleBody: function(event) + { + var target = event.currentTarget || event.srcElement; + var logRow = getAncestorByClass(target, "logRow-spy"); + + if (isLeftClick(event)) + { + toggleClass(logRow, "opened"); + + var spy = getChildByClass(logRow, "spyHead").repObject; + var spyHeadTable = getAncestorByClass(target, "spyHeadTable"); + + if (hasClass(logRow, "opened")) + { + updateHttpSpyInfo(spy, logRow); + if (spyHeadTable) + spyHeadTable.setAttribute('aria-expanded', 'true'); + } + else + { + //var netInfoBox = getChildByClass(spy.logRow, "spyHead", "netInfoBody"); + //dispatch(Firebug.NetMonitor.NetInfoBody.fbListeners, "destroyTabBody", [netInfoBox, spy]); + //if (spyHeadTable) + // spyHeadTable.setAttribute('aria-expanded', 'false'); + } + } + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + copyURL: function(spy) + { + copyToClipboard(spy.getURL()); + }, + + copyParams: function(spy) + { + var text = spy.postText; + if (!text) + return; + + var url = reEncodeURL(spy, text, true); + copyToClipboard(url); + }, + + copyResponse: function(spy) + { + copyToClipboard(spy.responseText); + }, + + openInTab: function(spy) + { + openNewTab(spy.getURL(), spy.postText); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + supportsObject: function(object) + { + // TODO: xxxpedro spy xhr + return false; + + return object instanceof Firebug.Spy.XMLHttpRequestSpy; + }, + + browseObject: function(spy, context) + { + var url = spy.getURL(); + openNewTab(url); + return true; + }, + + getRealObject: function(spy, context) + { + return spy.xhrRequest; + }, + + getContextMenuItems: function(spy) + { + var items = [ + {label: "CopyLocation", command: bindFixed(this.copyURL, this, spy) } + ]; + + if (spy.postText) + { + items.push( + {label: "CopyLocationParameters", command: bindFixed(this.copyParams, this, spy) } + ); + } + + items.push( + {label: "CopyResponse", command: bindFixed(this.copyResponse, this, spy) }, + "-", + {label: "OpenInTab", command: bindFixed(this.openInTab, this, spy) } + ); + + return items; + } +}); + +// ************************************************************************************************ + +function updateTime(spy) +{ + var timeBox = spy.logRow.getElementsByClassName("spyTime").item(0); + if (spy.responseTime) + timeBox.textContent = " " + formatTime(spy.responseTime); +} + +function updateLogRow(spy) +{ + updateTime(spy); + + var statusBox = spy.logRow.getElementsByClassName("spyStatus").item(0); + statusBox.textContent = Firebug.Spy.XHR.getStatus(spy); + + removeClass(spy.logRow, "loading"); + setClass(spy.logRow, "loaded"); + + try + { + var errorRange = Math.floor(spy.xhrRequest.status/100); + if (errorRange == 4 || errorRange == 5) + setClass(spy.logRow, "error"); + } + catch (exc) + { + } +} + +var updateHttpSpyInfo = function updateHttpSpyInfo(spy, logRow) +{ + if (!spy.logRow && logRow) + spy.logRow = logRow; + + if (!spy.logRow || !hasClass(spy.logRow, "opened")) + return; + + if (!spy.params) + //spy.params = parseURLParams(spy.href+""); + spy.params = parseURLParams(spy.href+""); + + if (!spy.requestHeaders) + spy.requestHeaders = getRequestHeaders(spy); + + if (!spy.responseHeaders && spy.loaded) + spy.responseHeaders = getResponseHeaders(spy); + + var template = Firebug.NetMonitor.NetInfoBody; + var netInfoBox = getChildByClass(spy.logRow, "spyHead", "netInfoBody"); + if (!netInfoBox) + { + var head = getChildByClass(spy.logRow, "spyHead"); + netInfoBox = template.tag.append({"file": spy}, head); + dispatch(template.fbListeners, "initTabBody", [netInfoBox, spy]); + template.selectTabByName(netInfoBox, "Response"); + } + else + { + template.updateInfo(netInfoBox, spy, spy.context); + } +}; + + + +// ************************************************************************************************ + +function getRequestHeaders(spy) +{ + var headers = []; + + var channel = spy.xhrRequest.channel; + if (channel instanceof Ci.nsIHttpChannel) + { + channel.visitRequestHeaders({ + visitHeader: function(name, value) + { + headers.push({name: name, value: value}); + } + }); + } + + return headers; +} + +function getResponseHeaders(spy) +{ + var headers = []; + + try + { + var channel = spy.xhrRequest.channel; + if (channel instanceof Ci.nsIHttpChannel) + { + channel.visitResponseHeaders({ + visitHeader: function(name, value) + { + headers.push({name: name, value: value}); + } + }); + } + } + catch (exc) + { + if (FBTrace.DBG_SPY || FBTrace.DBG_ERRORS) + FBTrace.sysout("spy.getResponseHeaders; EXCEPTION " + + safeGetRequestName(spy.request), exc); + } + + return headers; +} + +// ************************************************************************************************ +// Registration + +Firebug.registerModule(Firebug.Spy); +//Firebug.registerRep(Firebug.Spy.XHR); + +// ************************************************************************************************ +}}); + + +/* See license.txt for terms of usage */ + +FBL.ns(function() { with (FBL) { + +// ************************************************************************************************ + +// List of JSON content types. +var contentTypes = +{ + // TODO: create issue: jsonViewer will not try to evaluate the contents of the requested file + // if the content-type is set to "text/plain" + //"text/plain": 1, + "text/javascript": 1, + "text/x-javascript": 1, + "text/json": 1, + "text/x-json": 1, + "application/json": 1, + "application/x-json": 1, + "application/javascript": 1, + "application/x-javascript": 1, + "application/json-rpc": 1 +}; + +// ************************************************************************************************ +// Model implementation + +Firebug.JSONViewerModel = extend(Firebug.Module, +{ + dispatchName: "jsonViewer", + initialize: function() + { + Firebug.NetMonitor.NetInfoBody.addListener(this); + + // Used by Firebug.DOMPanel.DirTable domplate. + this.toggles = {}; + }, + + shutdown: function() + { + Firebug.NetMonitor.NetInfoBody.removeListener(this); + }, + + initTabBody: function(infoBox, file) + { + if (FBTrace.DBG_JSONVIEWER) + FBTrace.sysout("jsonviewer.initTabBody", infoBox); + + // Let listeners to parse the JSON. + dispatch(this.fbListeners, "onParseJSON", [file]); + + // The JSON is still no there, try to parse most common cases. + if (!file.jsonObject) + { + ///if (this.isJSON(safeGetContentType(file.request), file.responseText)) + if (this.isJSON(file.mimeType, file.responseText)) + file.jsonObject = this.parseJSON(file); + } + + // The jsonObject is created so, the JSON tab can be displayed. + if (file.jsonObject && hasProperties(file.jsonObject)) + { + Firebug.NetMonitor.NetInfoBody.appendTab(infoBox, "JSON", + ///$STR("jsonviewer.tab.JSON")); + $STR("JSON")); + + if (FBTrace.DBG_JSONVIEWER) + FBTrace.sysout("jsonviewer.initTabBody; JSON object available " + + (typeof(file.jsonObject) != "undefined"), file.jsonObject); + } + }, + + isJSON: function(contentType, data) + { + // Workaround for JSON responses without proper content type + // Let's consider all responses starting with "{" as JSON. In the worst + // case there will be an exception when parsing. This means that no-JSON + // responses (and post data) (with "{") can be parsed unnecessarily, + // which represents a little overhead, but this happens only if the request + // is actually expanded by the user in the UI (Net & Console panels). + + ///var responseText = data ? trimLeft(data) : null; + ///if (responseText && responseText.indexOf("{") == 0) + /// return true; + var responseText = data ? trim(data) : null; + if (responseText && responseText.indexOf("{") == 0) + return true; + + if (!contentType) + return false; + + contentType = contentType.split(";")[0]; + contentType = trim(contentType); + return contentTypes[contentType]; + }, + + // Update listener for TabView + updateTabBody: function(infoBox, file, context) + { + var tab = infoBox.selectedTab; + ///var tabBody = infoBox.getElementsByClassName("netInfoJSONText").item(0); + var tabBody = $$(".netInfoJSONText", infoBox)[0]; + if (!hasClass(tab, "netInfoJSONTab") || tabBody.updated) + return; + + tabBody.updated = true; + + if (file.jsonObject) { + Firebug.DOMPanel.DirTable.tag.replace( + {object: file.jsonObject, toggles: this.toggles}, tabBody); + } + }, + + parseJSON: function(file) + { + var jsonString = new String(file.responseText); + ///return parseJSONString(jsonString, "http://" + file.request.originalURI.host); + return parseJSONString(jsonString); + } +}); + +// ************************************************************************************************ +// Registration + +Firebug.registerModule(Firebug.JSONViewerModel); + +// ************************************************************************************************ +}}); + + +/* See license.txt for terms of usage */ + +FBL.ns(function() { with (FBL) { + +// ************************************************************************************************ +// Constants + +// List of XML related content types. +var xmlContentTypes = +[ + "text/xml", + "application/xml", + "application/xhtml+xml", + "application/rss+xml", + "application/atom+xml",, + "application/vnd.mozilla.maybe.feed", + "application/rdf+xml", + "application/vnd.mozilla.xul+xml" +]; + +// ************************************************************************************************ +// Model implementation + +/** + * @module Implements viewer for XML based network responses. In order to create a new + * tab wihin network request detail, a listener is registered into + * <code>Firebug.NetMonitor.NetInfoBody</code> object. + */ +Firebug.XMLViewerModel = extend(Firebug.Module, +{ + dispatchName: "xmlViewer", + + initialize: function() + { + ///Firebug.ActivableModule.initialize.apply(this, arguments); + Firebug.Module.initialize.apply(this, arguments); + Firebug.NetMonitor.NetInfoBody.addListener(this); + }, + + shutdown: function() + { + ///Firebug.ActivableModule.shutdown.apply(this, arguments); + Firebug.Module.shutdown.apply(this, arguments); + Firebug.NetMonitor.NetInfoBody.removeListener(this); + }, + + /** + * Check response's content-type and if it's a XML, create a new tab with XML preview. + */ + initTabBody: function(infoBox, file) + { + if (FBTrace.DBG_XMLVIEWER) + FBTrace.sysout("xmlviewer.initTabBody", infoBox); + + // If the response is XML let's display a pretty preview. + ///if (this.isXML(safeGetContentType(file.request))) + if (this.isXML(file.mimeType, file.responseText)) + { + Firebug.NetMonitor.NetInfoBody.appendTab(infoBox, "XML", + ///$STR("xmlviewer.tab.XML")); + $STR("XML")); + + if (FBTrace.DBG_XMLVIEWER) + FBTrace.sysout("xmlviewer.initTabBody; XML response available"); + } + }, + + isXML: function(contentType) + { + if (!contentType) + return false; + + // Look if the response is XML based. + for (var i=0; i<xmlContentTypes.length; i++) + { + if (contentType.indexOf(xmlContentTypes[i]) == 0) + return true; + } + + return false; + }, + + /** + * Parse XML response and render pretty printed preview. + */ + updateTabBody: function(infoBox, file, context) + { + var tab = infoBox.selectedTab; + ///var tabBody = infoBox.getElementsByClassName("netInfoXMLText").item(0); + var tabBody = $$(".netInfoXMLText", infoBox)[0]; + if (!hasClass(tab, "netInfoXMLTab") || tabBody.updated) + return; + + tabBody.updated = true; + + this.insertXML(tabBody, Firebug.NetMonitor.Utils.getResponseText(file, context)); + }, + + insertXML: function(parentNode, text) + { + var xmlText = text.replace(/^\s*<?.+?>\s*/, ""); + + var div = parentNode.ownerDocument.createElement("div"); + div.innerHTML = xmlText; + + var root = div.getElementsByTagName("*")[0]; + + /*** + var parser = CCIN("@mozilla.org/xmlextras/domparser;1", "nsIDOMParser"); + var doc = parser.parseFromString(text, "text/xml"); + var root = doc.documentElement; + + // Error handling + var nsURI = "http://www.mozilla.org/newlayout/xml/parsererror.xml"; + if (root.namespaceURI == nsURI && root.nodeName == "parsererror") + { + this.ParseError.tag.replace({error: { + message: root.firstChild.nodeValue, + source: root.lastChild.textContent + }}, parentNode); + return; + } + /**/ + + if (FBTrace.DBG_XMLVIEWER) + FBTrace.sysout("xmlviewer.updateTabBody; XML response parsed", doc); + + // Override getHidden in these templates. The parsed XML documen is + // hidden, but we want to display it using 'visible' styling. + /* + var templates = [ + Firebug.HTMLPanel.CompleteElement, + Firebug.HTMLPanel.Element, + Firebug.HTMLPanel.TextElement, + Firebug.HTMLPanel.EmptyElement, + Firebug.HTMLPanel.XEmptyElement, + ]; + + var originals = []; + for (var i=0; i<templates.length; i++) + { + originals[i] = templates[i].getHidden; + templates[i].getHidden = function() { + return ""; + } + } + /**/ + + // Generate XML preview. + ///Firebug.HTMLPanel.CompleteElement.tag.replace({object: doc.documentElement}, parentNode); + + // TODO: xxxpedro html3 + ///Firebug.HTMLPanel.CompleteElement.tag.replace({object: root}, parentNode); + var html = []; + Firebug.Reps.appendNode(root, html); + parentNode.innerHTML = html.join(""); + + + /* + for (var i=0; i<originals.length; i++) + templates[i].getHidden = originals[i];/**/ + } +}); + +// ************************************************************************************************ +// Domplate + +/** + * @domplate Represents a template for displaying XML parser errors. Used by + * <code>Firebug.XMLViewerModel</code>. + */ +Firebug.XMLViewerModel.ParseError = domplate(Firebug.Rep, +{ + tag: + DIV({"class": "xmlInfoError"}, + DIV({"class": "xmlInfoErrorMsg"}, "$error.message"), + PRE({"class": "xmlInfoErrorSource"}, "$error|getSource") + ), + + getSource: function(error) + { + var parts = error.source.split("\n"); + if (parts.length != 2) + return error.source; + + var limit = 50; + var column = parts[1].length; + if (column >= limit) { + parts[0] = "..." + parts[0].substr(column - limit); + parts[1] = "..." + parts[1].substr(column - limit); + } + + if (parts[0].length > 80) + parts[0] = parts[0].substr(0, 80) + "..."; + + return parts.join("\n"); + } +}); + +// ************************************************************************************************ +// Registration + +Firebug.registerModule(Firebug.XMLViewerModel); + +}}); + + +/* See license.txt for terms of usage */ + +// next-generation Console Panel (will override consoje.js) +FBL.ns(function() { with (FBL) { +// ************************************************************************************************ + +// ************************************************************************************************ +// Constants + +/* +const Cc = Components.classes; +const Ci = Components.interfaces; +const nsIPrefBranch2 = Ci.nsIPrefBranch2; +const PrefService = Cc["@mozilla.org/preferences-service;1"]; +const prefs = PrefService.getService(nsIPrefBranch2); +/**/ +/* + +// new offline message handler +o = {x:1,y:2}; + +r = Firebug.getRep(o); + +r.tag.tag.compile(); + +outputs = []; +html = r.tag.renderHTML({object:o}, outputs); + + +// finish rendering the template (the DOM part) +target = $("build"); +target.innerHTML = html; +root = target.firstChild; + +domArgs = [root, r.tag.context, 0]; +domArgs.push.apply(domArgs, r.tag.domArgs); +domArgs.push.apply(domArgs, outputs); +r.tag.tag.renderDOM.apply(self ? self : r.tag.subject, domArgs); + + + */ +var consoleQueue = []; +var lastHighlightedObject; +var FirebugContext = Env.browser; + +// ************************************************************************************************ + +var maxQueueRequests = 500; + +// ************************************************************************************************ + +Firebug.ConsoleBase = +{ + log: function(object, context, className, rep, noThrottle, sourceLink) + { + //dispatch(this.fbListeners,"log",[context, object, className, sourceLink]); + return this.logRow(appendObject, object, context, className, rep, sourceLink, noThrottle); + }, + + logFormatted: function(objects, context, className, noThrottle, sourceLink) + { + //dispatch(this.fbListeners,"logFormatted",[context, objects, className, sourceLink]); + return this.logRow(appendFormatted, objects, context, className, null, sourceLink, noThrottle); + }, + + openGroup: function(objects, context, className, rep, noThrottle, sourceLink, noPush) + { + return this.logRow(appendOpenGroup, objects, context, className, rep, sourceLink, noThrottle); + }, + + closeGroup: function(context, noThrottle) + { + return this.logRow(appendCloseGroup, null, context, null, null, null, noThrottle, true); + }, + + logRow: function(appender, objects, context, className, rep, sourceLink, noThrottle, noRow) + { + // TODO: xxxpedro console console2 + noThrottle = true; // xxxpedro forced because there is no TabContext yet + + if (!context) + context = FirebugContext; + + if (FBTrace.DBG_ERRORS && !context) + FBTrace.sysout("Console.logRow has no context, skipping objects", objects); + + if (!context) + return; + + if (noThrottle || !context) + { + var panel = this.getPanel(context); + if (panel) + { + var row = panel.append(appender, objects, className, rep, sourceLink, noRow); + var container = panel.panelNode; + + // TODO: xxxpedro what is this? console console2 + /* + var template = Firebug.NetMonitor.NetLimit; + + while (container.childNodes.length > maxQueueRequests + 1) + { + clearDomplate(container.firstChild.nextSibling); + container.removeChild(container.firstChild.nextSibling); + panel.limit.limitInfo.totalCount++; + template.updateCounter(panel.limit); + } + dispatch([Firebug.A11yModel], "onLogRowCreated", [panel , row]); + /**/ + return row; + } + else + { + consoleQueue.push([appender, objects, context, className, rep, sourceLink, noThrottle, noRow]); + } + } + else + { + if (!context.throttle) + { + //FBTrace.sysout("console.logRow has not context.throttle! "); + return; + } + var args = [appender, objects, context, className, rep, sourceLink, true, noRow]; + context.throttle(this.logRow, this, args); + } + }, + + appendFormatted: function(args, row, context) + { + if (!context) + context = FirebugContext; + + var panel = this.getPanel(context); + panel.appendFormatted(args, row); + }, + + clear: function(context) + { + if (!context) + //context = FirebugContext; + context = Firebug.context; + + /* + if (context) + Firebug.Errors.clear(context); + /**/ + + var panel = this.getPanel(context, true); + if (panel) + { + panel.clear(); + } + }, + + // Override to direct output to your panel + getPanel: function(context, noCreate) + { + //return context.getPanel("console", noCreate); + // TODO: xxxpedro console console2 + return Firebug.chrome ? Firebug.chrome.getPanel("Console") : null; + } + +}; + +// ************************************************************************************************ + +//TODO: xxxpedro +//var ActivableConsole = extend(Firebug.ActivableModule, Firebug.ConsoleBase); +var ActivableConsole = extend(Firebug.ConsoleBase, +{ + isAlwaysEnabled: function() + { + return true; + } +}); + +Firebug.Console = Firebug.Console = extend(ActivableConsole, +//Firebug.Console = extend(ActivableConsole, +{ + dispatchName: "console", + + error: function() + { + Firebug.Console.logFormatted(arguments, Firebug.browser, "error"); + }, + + flush: function() + { + dispatch(this.fbListeners,"flush",[]); + + for (var i=0, length=consoleQueue.length; i<length; i++) + { + var args = consoleQueue[i]; + this.logRow.apply(this, args); + } + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // extends Module + + showPanel: function(browser, panel) + { + }, + + getFirebugConsoleElement: function(context, win) + { + var element = win.document.getElementById("_firebugConsole"); + if (!element) + { + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("getFirebugConsoleElement forcing element"); + var elementForcer = "(function(){var r=null; try { r = window._getFirebugConsoleElement();}catch(exc){r=exc;} return r;})();"; // we could just add the elements here + + if (context.stopped) + Firebug.Console.injector.evaluateConsoleScript(context); // todo evaluate consoleForcer on stack + else + var r = Firebug.CommandLine.evaluateInWebPage(elementForcer, context, win); + + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("getFirebugConsoleElement forcing element result "+r, r); + + var element = win.document.getElementById("_firebugConsole"); + if (!element) // elementForce fails + { + if (FBTrace.DBG_ERRORS) FBTrace.sysout("console.getFirebugConsoleElement: no _firebugConsole in win:", win); + Firebug.Console.logFormatted(["Firebug cannot find _firebugConsole element", r, win], context, "error", true); + } + } + + return element; + }, + + isReadyElsePreparing: function(context, win) // this is the only code that should call injector.attachIfNeeded + { + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("console.isReadyElsePreparing, win is " + + (win?"an argument: ":"null, context.window: ") + + (win?win.location:context.window.location), (win?win:context.window)); + + if (win) + return this.injector.attachIfNeeded(context, win); + else + { + var attached = true; + for (var i = 0; i < context.windows.length; i++) + attached = attached && this.injector.attachIfNeeded(context, context.windows[i]); + // already in the list above attached = attached && this.injector.attachIfNeeded(context, context.window); + if (context.windows.indexOf(context.window) == -1) + FBTrace.sysout("isReadyElsePreparing ***************** context.window not in context.windows"); + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("console.isReadyElsePreparing attached to "+context.windows.length+" and returns "+attached); + return attached; + } + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // extends ActivableModule + + initialize: function() + { + this.panelName = "console"; + + //TODO: xxxpedro + //Firebug.ActivableModule.initialize.apply(this, arguments); + //Firebug.Debugger.addListener(this); + }, + + enable: function() + { + if (Firebug.Console.isAlwaysEnabled()) + this.watchForErrors(); + }, + + disable: function() + { + if (Firebug.Console.isAlwaysEnabled()) + this.unwatchForErrors(); + }, + + initContext: function(context, persistedState) + { + Firebug.ActivableModule.initContext.apply(this, arguments); + context.consoleReloadWarning = true; // mark as need to warn. + }, + + loadedContext: function(context) + { + for (var url in context.sourceFileMap) + return; // if there are any sourceFiles, then do nothing + + // else we saw no JS, so the reload warning it not needed. + this.clearReloadWarning(context); + }, + + clearReloadWarning: function(context) // remove the warning about reloading. + { + if (context.consoleReloadWarning) + { + var panel = context.getPanel(this.panelName); + panel.clearReloadWarning(); + delete context.consoleReloadWarning; + } + }, + + togglePersist: function(context) + { + var panel = context.getPanel(this.panelName); + panel.persistContent = panel.persistContent ? false : true; + Firebug.chrome.setGlobalAttribute("cmd_togglePersistConsole", "checked", panel.persistContent); + }, + + showContext: function(browser, context) + { + Firebug.chrome.setGlobalAttribute("cmd_clearConsole", "disabled", !context); + + Firebug.ActivableModule.showContext.apply(this, arguments); + }, + + destroyContext: function(context, persistedState) + { + Firebug.Console.injector.detachConsole(context, context.window); // TODO iterate windows? + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + onPanelEnable: function(panelName) + { + if (panelName != this.panelName) // we don't care about other panels + return; + + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("console.onPanelEnable**************"); + + this.watchForErrors(); + Firebug.Debugger.addDependentModule(this); // we inject the console during JS compiles so we need jsd + }, + + onPanelDisable: function(panelName) + { + if (panelName != this.panelName) // we don't care about other panels + return; + + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("console.onPanelDisable**************"); + + Firebug.Debugger.removeDependentModule(this); // we inject the console during JS compiles so we need jsd + this.unwatchForErrors(); + + // Make sure possible errors coming from the page and displayed in the Firefox + // status bar are removed. + this.clear(); + }, + + onSuspendFirebug: function() + { + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("console.onSuspendFirebug\n"); + if (Firebug.Console.isAlwaysEnabled()) + this.unwatchForErrors(); + }, + + onResumeFirebug: function() + { + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("console.onResumeFirebug\n"); + if (Firebug.Console.isAlwaysEnabled()) + this.watchForErrors(); + }, + + watchForErrors: function() + { + Firebug.Errors.checkEnabled(); + $('fbStatusIcon').setAttribute("console", "on"); + }, + + unwatchForErrors: function() + { + Firebug.Errors.checkEnabled(); + $('fbStatusIcon').removeAttribute("console"); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Firebug.Debugger listener + + onMonitorScript: function(context, frame) + { + Firebug.Console.log(frame, context); + }, + + onFunctionCall: function(context, frame, depth, calling) + { + if (calling) + Firebug.Console.openGroup([frame, "depth:"+depth], context); + else + Firebug.Console.closeGroup(context); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + logRow: function(appender, objects, context, className, rep, sourceLink, noThrottle, noRow) + { + if (!context) + context = FirebugContext; + + if (FBTrace.DBG_WINDOWS && !context) FBTrace.sysout("Console.logRow: no context \n"); + + if (this.isAlwaysEnabled()) + return Firebug.ConsoleBase.logRow.apply(this, arguments); + } +}); + +Firebug.ConsoleListener = +{ + log: function(context, object, className, sourceLink) + { + }, + + logFormatted: function(context, objects, className, sourceLink) + { + } +}; + +// ************************************************************************************************ + +Firebug.ConsolePanel = function () {} // XXjjb attach Firebug so this panel can be extended. + +//TODO: xxxpedro +//Firebug.ConsolePanel.prototype = extend(Firebug.ActivablePanel, +Firebug.ConsolePanel.prototype = extend(Firebug.Panel, +{ + wasScrolledToBottom: false, + messageCount: 0, + lastLogTime: 0, + groups: null, + limit: null, + + append: function(appender, objects, className, rep, sourceLink, noRow) + { + var container = this.getTopContainer(); + + if (noRow) + { + appender.apply(this, [objects]); + } + else + { + // xxxHonza: Don't update the this.wasScrolledToBottom flag now. + // At the beginning (when the first log is created) the isScrolledToBottom + // always returns true. + //if (this.panelNode.offsetHeight) + // this.wasScrolledToBottom = isScrolledToBottom(this.panelNode); + + var row = this.createRow("logRow", className); + appender.apply(this, [objects, row, rep]); + + if (sourceLink) + FirebugReps.SourceLink.tag.append({object: sourceLink}, row); + + container.appendChild(row); + + this.filterLogRow(row, this.wasScrolledToBottom); + + if (this.wasScrolledToBottom) + scrollToBottom(this.panelNode); + + return row; + } + }, + + clear: function() + { + if (this.panelNode) + { + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("ConsolePanel.clear"); + clearNode(this.panelNode); + this.insertLogLimit(this.context); + } + }, + + insertLogLimit: function() + { + // Create limit row. This row is the first in the list of entries + // and initially hidden. It's displayed as soon as the number of + // entries reaches the limit. + var row = this.createRow("limitRow"); + + var limitInfo = { + totalCount: 0, + limitPrefsTitle: $STRF("LimitPrefsTitle", [Firebug.prefDomain+".console.logLimit"]) + }; + + //TODO: xxxpedro console net limit!? + return; + var netLimitRep = Firebug.NetMonitor.NetLimit; + var nodes = netLimitRep.createTable(row, limitInfo); + + this.limit = nodes[1]; + + var container = this.panelNode; + container.insertBefore(nodes[0], container.firstChild); + }, + + insertReloadWarning: function() + { + // put the message in, we will clear if the window console is injected. + this.warningRow = this.append(appendObject, $STR("message.Reload to activate window console"), "info"); + }, + + clearReloadWarning: function() + { + if (this.warningRow) + { + this.warningRow.parentNode.removeChild(this.warningRow); + delete this.warningRow; + } + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + appendObject: function(object, row, rep) + { + if (!rep) + rep = Firebug.getRep(object); + return rep.tag.append({object: object}, row); + }, + + appendFormatted: function(objects, row, rep) + { + if (!objects || !objects.length) + return; + + function logText(text, row) + { + var node = row.ownerDocument.createTextNode(text); + row.appendChild(node); + } + + var format = objects[0]; + var objIndex = 0; + + if (typeof(format) != "string") + { + format = ""; + objIndex = -1; + } + else // a string + { + if (objects.length === 1) // then we have only a string... + { + if (format.length < 1) { // ...and it has no characters. + logText("(an empty string)", row); + return; + } + } + } + + var parts = parseFormat(format); + var trialIndex = objIndex; + for (var i= 0; i < parts.length; i++) + { + var part = parts[i]; + if (part && typeof(part) == "object") + { + if (++trialIndex > objects.length) // then too few parameters for format, assume unformatted. + { + format = ""; + objIndex = -1; + parts.length = 0; + break; + } + } + + } + for (var i = 0; i < parts.length; ++i) + { + var part = parts[i]; + if (part && typeof(part) == "object") + { + var object = objects[++objIndex]; + if (typeof(object) != "undefined") + this.appendObject(object, row, part.rep); + else + this.appendObject(part.type, row, FirebugReps.Text); + } + else + FirebugReps.Text.tag.append({object: part}, row); + } + + for (var i = objIndex+1; i < objects.length; ++i) + { + logText(" ", row); + var object = objects[i]; + if (typeof(object) == "string") + FirebugReps.Text.tag.append({object: object}, row); + else + this.appendObject(object, row); + } + }, + + appendOpenGroup: function(objects, row, rep) + { + if (!this.groups) + this.groups = []; + + setClass(row, "logGroup"); + setClass(row, "opened"); + + var innerRow = this.createRow("logRow"); + setClass(innerRow, "logGroupLabel"); + if (rep) + rep.tag.replace({"objects": objects}, innerRow); + else + this.appendFormatted(objects, innerRow, rep); + row.appendChild(innerRow); + //dispatch([Firebug.A11yModel], 'onLogRowCreated', [this, innerRow]); + var groupBody = this.createRow("logGroupBody"); + row.appendChild(groupBody); + groupBody.setAttribute('role', 'group'); + this.groups.push(groupBody); + + addEvent(innerRow, "mousedown", function(event) + { + if (isLeftClick(event)) + { + //console.log(event.currentTarget == event.target); + + var target = event.target || event.srcElement; + + target = getAncestorByClass(target, "logGroupLabel"); + + var groupRow = target.parentNode; + + if (hasClass(groupRow, "opened")) + { + removeClass(groupRow, "opened"); + target.setAttribute('aria-expanded', 'false'); + } + else + { + setClass(groupRow, "opened"); + target.setAttribute('aria-expanded', 'true'); + } + } + }); + }, + + appendCloseGroup: function(object, row, rep) + { + if (this.groups) + this.groups.pop(); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // TODO: xxxpedro console2 + onMouseMove: function(event) + { + if (!Firebug.Inspector) return; + + var target = event.srcElement || event.target; + + var object = getAncestorByClass(target, "objectLink-element"); + object = object ? object.repObject : null; + + if(object && instanceOf(object, "Element") && object.nodeType == 1) + { + if(object != lastHighlightedObject) + { + Firebug.Inspector.drawBoxModel(object); + object = lastHighlightedObject; + } + } + else + Firebug.Inspector.hideBoxModel(); + + }, + + onMouseDown: function(event) + { + var target = event.srcElement || event.target; + + var object = getAncestorByClass(target, "objectLink"); + var repObject = object ? object.repObject : null; + + if (!repObject) + { + return; + } + + if (hasClass(object, "objectLink-object")) + { + Firebug.chrome.selectPanel("DOM"); + Firebug.chrome.getPanel("DOM").select(repObject, true); + } + else if (hasClass(object, "objectLink-element")) + { + Firebug.chrome.selectPanel("HTML"); + Firebug.chrome.getPanel("HTML").select(repObject, true); + } + + /* + if(object && instanceOf(object, "Element") && object.nodeType == 1) + { + if(object != lastHighlightedObject) + { + Firebug.Inspector.drawBoxModel(object); + object = lastHighlightedObject; + } + } + else + Firebug.Inspector.hideBoxModel(); + /**/ + + }, + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // extends Panel + + name: "Console", + title: "Console", + //searchable: true, + //breakable: true, + //editable: false, + + options: + { + hasCommandLine: true, + hasToolButtons: true, + isPreRendered: true + }, + + create: function() + { + Firebug.Panel.create.apply(this, arguments); + + this.context = Firebug.browser.window; + this.document = Firebug.chrome.document; + this.onMouseMove = bind(this.onMouseMove, this); + this.onMouseDown = bind(this.onMouseDown, this); + + this.clearButton = new Button({ + element: $("fbConsole_btClear"), + owner: Firebug.Console, + onClick: Firebug.Console.clear + }); + }, + + initialize: function() + { + Firebug.Panel.initialize.apply(this, arguments); // loads persisted content + //Firebug.ActivablePanel.initialize.apply(this, arguments); // loads persisted content + + if (!this.persistedContent && Firebug.Console.isAlwaysEnabled()) + { + this.insertLogLimit(this.context); + + // Initialize log limit and listen for changes. + this.updateMaxLimit(); + + if (this.context.consoleReloadWarning) // we have not yet injected the console + this.insertReloadWarning(); + } + + //Firebug.Console.injector.install(Firebug.browser.window); + + addEvent(this.panelNode, "mouseover", this.onMouseMove); + addEvent(this.panelNode, "mousedown", this.onMouseDown); + + this.clearButton.initialize(); + + //consolex.trace(); + //TODO: xxxpedro remove this + /* + Firebug.Console.openGroup(["asd"], null, "group", null, false); + Firebug.Console.log("asd"); + Firebug.Console.log("asd"); + Firebug.Console.log("asd"); + /**/ + + //TODO: xxxpedro preferences prefs + //prefs.addObserver(Firebug.prefDomain, this, false); + }, + + initializeNode : function() + { + //dispatch([Firebug.A11yModel], 'onInitializeNode', [this]); + if (FBTrace.DBG_CONSOLE) + { + this.onScroller = bind(this.onScroll, this); + addEvent(this.panelNode, "scroll", this.onScroller); + } + + this.onResizer = bind(this.onResize, this); + this.resizeEventTarget = Firebug.chrome.$('fbContentBox'); + addEvent(this.resizeEventTarget, "resize", this.onResizer); + }, + + destroyNode : function() + { + //dispatch([Firebug.A11yModel], 'onDestroyNode', [this]); + if (this.onScroller) + removeEvent(this.panelNode, "scroll", this.onScroller); + + //removeEvent(this.resizeEventTarget, "resize", this.onResizer); + }, + + shutdown: function() + { + //TODO: xxxpedro console console2 + this.clearButton.shutdown(); + + removeEvent(this.panelNode, "mousemove", this.onMouseMove); + removeEvent(this.panelNode, "mousedown", this.onMouseDown); + + this.destroyNode(); + + Firebug.Panel.shutdown.apply(this, arguments); + + //TODO: xxxpedro preferences prefs + //prefs.removeObserver(Firebug.prefDomain, this, false); + }, + + ishow: function(state) + { + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("Console.panel show; " + this.context.getName(), state); + + var enabled = Firebug.Console.isAlwaysEnabled(); + if (enabled) + { + Firebug.Console.disabledPanelPage.hide(this); + this.showCommandLine(true); + this.showToolbarButtons("fbConsoleButtons", true); + Firebug.chrome.setGlobalAttribute("cmd_togglePersistConsole", "checked", this.persistContent); + + if (state && state.wasScrolledToBottom) + { + this.wasScrolledToBottom = state.wasScrolledToBottom; + delete state.wasScrolledToBottom; + } + + if (this.wasScrolledToBottom) + scrollToBottom(this.panelNode); + + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("console.show ------------------ wasScrolledToBottom: " + + this.wasScrolledToBottom + ", " + this.context.getName()); + } + else + { + this.hide(state); + Firebug.Console.disabledPanelPage.show(this); + } + }, + + ihide: function(state) + { + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("Console.panel hide; " + this.context.getName(), state); + + this.showToolbarButtons("fbConsoleButtons", false); + this.showCommandLine(false); + + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("console.hide ------------------ wasScrolledToBottom: " + + this.wasScrolledToBottom + ", " + this.context.getName()); + }, + + destroy: function(state) + { + if (this.panelNode.offsetHeight) + this.wasScrolledToBottom = isScrolledToBottom(this.panelNode); + + if (state) + state.wasScrolledToBottom = this.wasScrolledToBottom; + + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("console.destroy ------------------ wasScrolledToBottom: " + + this.wasScrolledToBottom + ", " + this.context.getName()); + }, + + shouldBreakOnNext: function() + { + // xxxHonza: shouldn't the breakOnErrors be context related? + // xxxJJB, yes, but we can't support it because we can't yet tell + // which window the error is on. + return Firebug.getPref(Firebug.servicePrefDomain, "breakOnErrors"); + }, + + getBreakOnNextTooltip: function(enabled) + { + return (enabled ? $STR("console.Disable Break On All Errors") : + $STR("console.Break On All Errors")); + }, + + enablePanel: function(module) + { + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("console.ConsolePanel.enablePanel; " + this.context.getName()); + + Firebug.ActivablePanel.enablePanel.apply(this, arguments); + + this.showCommandLine(true); + + if (this.wasScrolledToBottom) + scrollToBottom(this.panelNode); + }, + + disablePanel: function(module) + { + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("console.ConsolePanel.disablePanel; " + this.context.getName()); + + Firebug.ActivablePanel.disablePanel.apply(this, arguments); + + this.showCommandLine(false); + }, + + getOptionsMenuItems: function() + { + return [ + optionMenu("ShowJavaScriptErrors", "showJSErrors"), + optionMenu("ShowJavaScriptWarnings", "showJSWarnings"), + optionMenu("ShowCSSErrors", "showCSSErrors"), + optionMenu("ShowXMLErrors", "showXMLErrors"), + optionMenu("ShowXMLHttpRequests", "showXMLHttpRequests"), + optionMenu("ShowChromeErrors", "showChromeErrors"), + optionMenu("ShowChromeMessages", "showChromeMessages"), + optionMenu("ShowExternalErrors", "showExternalErrors"), + optionMenu("ShowNetworkErrors", "showNetworkErrors"), + this.getShowStackTraceMenuItem(), + this.getStrictOptionMenuItem(), + "-", + optionMenu("LargeCommandLine", "largeCommandLine") + ]; + }, + + getShowStackTraceMenuItem: function() + { + var menuItem = serviceOptionMenu("ShowStackTrace", "showStackTrace"); + if (FirebugContext && !Firebug.Debugger.isAlwaysEnabled()) + menuItem.disabled = true; + return menuItem; + }, + + getStrictOptionMenuItem: function() + { + var strictDomain = "javascript.options"; + var strictName = "strict"; + var strictValue = prefs.getBoolPref(strictDomain+"."+strictName); + return {label: "JavascriptOptionsStrict", type: "checkbox", checked: strictValue, + command: bindFixed(Firebug.setPref, Firebug, strictDomain, strictName, !strictValue) }; + }, + + getBreakOnMenuItems: function() + { + //xxxHonza: no BON options for now. + /*return [ + optionMenu("console.option.Persist Break On Error", "persistBreakOnError") + ];*/ + return []; + }, + + search: function(text) + { + if (!text) + return; + + // Make previously visible nodes invisible again + if (this.matchSet) + { + for (var i in this.matchSet) + removeClass(this.matchSet[i], "matched"); + } + + this.matchSet = []; + + function findRow(node) { return getAncestorByClass(node, "logRow"); } + var search = new TextSearch(this.panelNode, findRow); + + var logRow = search.find(text); + if (!logRow) + { + dispatch([Firebug.A11yModel], 'onConsoleSearchMatchFound', [this, text, []]); + return false; + } + for (; logRow; logRow = search.findNext()) + { + setClass(logRow, "matched"); + this.matchSet.push(logRow); + } + dispatch([Firebug.A11yModel], 'onConsoleSearchMatchFound', [this, text, this.matchSet]); + return true; + }, + + breakOnNext: function(breaking) + { + Firebug.setPref(Firebug.servicePrefDomain, "breakOnErrors", breaking); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // private + + createRow: function(rowName, className) + { + var elt = this.document.createElement("div"); + elt.className = rowName + (className ? " " + rowName + "-" + className : ""); + return elt; + }, + + getTopContainer: function() + { + if (this.groups && this.groups.length) + return this.groups[this.groups.length-1]; + else + return this.panelNode; + }, + + filterLogRow: function(logRow, scrolledToBottom) + { + if (this.searchText) + { + setClass(logRow, "matching"); + setClass(logRow, "matched"); + + // Search after a delay because we must wait for a frame to be created for + // the new logRow so that the finder will be able to locate it + setTimeout(bindFixed(function() + { + if (this.searchFilter(this.searchText, logRow)) + this.matchSet.push(logRow); + else + removeClass(logRow, "matched"); + + removeClass(logRow, "matching"); + + if (scrolledToBottom) + scrollToBottom(this.panelNode); + }, this), 100); + } + }, + + searchFilter: function(text, logRow) + { + var count = this.panelNode.childNodes.length; + var searchRange = this.document.createRange(); + searchRange.setStart(this.panelNode, 0); + searchRange.setEnd(this.panelNode, count); + + var startPt = this.document.createRange(); + startPt.setStartBefore(logRow); + + var endPt = this.document.createRange(); + endPt.setStartAfter(logRow); + + return finder.Find(text, searchRange, startPt, endPt) != null; + }, + + // nsIPrefObserver + observe: function(subject, topic, data) + { + // We're observing preferences only. + if (topic != "nsPref:changed") + return; + + // xxxHonza check this out. + var prefDomain = "Firebug.extension."; + var prefName = data.substr(prefDomain.length); + if (prefName == "console.logLimit") + this.updateMaxLimit(); + }, + + updateMaxLimit: function() + { + var value = 1000; + //TODO: xxxpedro preferences log limit? + //var value = Firebug.getPref(Firebug.prefDomain, "console.logLimit"); + maxQueueRequests = value ? value : maxQueueRequests; + }, + + showCommandLine: function(shouldShow) + { + //TODO: xxxpedro show command line important + return; + + if (shouldShow) + { + collapse(Firebug.chrome.$("fbCommandBox"), false); + Firebug.CommandLine.setMultiLine(Firebug.largeCommandLine, Firebug.chrome); + } + else + { + // Make sure that entire content of the Console panel is hidden when + // the panel is disabled. + Firebug.CommandLine.setMultiLine(false, Firebug.chrome, Firebug.largeCommandLine); + collapse(Firebug.chrome.$("fbCommandBox"), true); + } + }, + + onScroll: function(event) + { + // Update the scroll position flag if the position changes. + this.wasScrolledToBottom = FBL.isScrolledToBottom(this.panelNode); + + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("console.onScroll ------------------ wasScrolledToBottom: " + + this.wasScrolledToBottom + ", wasScrolledToBottom: " + + this.context.getName(), event); + }, + + onResize: function(event) + { + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("console.onResize ------------------ wasScrolledToBottom: " + + this.wasScrolledToBottom + ", offsetHeight: " + this.panelNode.offsetHeight + + ", scrollTop: " + this.panelNode.scrollTop + ", scrollHeight: " + + this.panelNode.scrollHeight + ", " + this.context.getName(), event); + + if (this.wasScrolledToBottom) + scrollToBottom(this.panelNode); + } +}); + +// ************************************************************************************************ + +function parseFormat(format) +{ + var parts = []; + if (format.length <= 0) + return parts; + + var reg = /((^%|.%)(\d+)?(\.)([a-zA-Z]))|((^%|.%)([a-zA-Z]))/; + for (var m = reg.exec(format); m; m = reg.exec(format)) + { + if (m[0].substr(0, 2) == "%%") + { + parts.push(format.substr(0, m.index)); + parts.push(m[0].substr(1)); + } + else + { + var type = m[8] ? m[8] : m[5]; + var precision = m[3] ? parseInt(m[3]) : (m[4] == "." ? -1 : 0); + + var rep = null; + switch (type) + { + case "s": + rep = FirebugReps.Text; + break; + case "f": + case "i": + case "d": + rep = FirebugReps.Number; + break; + case "o": + rep = null; + break; + } + + parts.push(format.substr(0, m[0][0] == "%" ? m.index : m.index+1)); + parts.push({rep: rep, precision: precision, type: ("%" + type)}); + } + + format = format.substr(m.index+m[0].length); + } + + parts.push(format); + return parts; +} + +// ************************************************************************************************ + +var appendObject = Firebug.ConsolePanel.prototype.appendObject; +var appendFormatted = Firebug.ConsolePanel.prototype.appendFormatted; +var appendOpenGroup = Firebug.ConsolePanel.prototype.appendOpenGroup; +var appendCloseGroup = Firebug.ConsolePanel.prototype.appendCloseGroup; + +// ************************************************************************************************ + +//Firebug.registerActivableModule(Firebug.Console); +Firebug.registerModule(Firebug.Console); +Firebug.registerPanel(Firebug.ConsolePanel); + +// ************************************************************************************************ +}}); + + +/* See license.txt for terms of usage */ + +FBL.ns(function() { with (FBL) { + +// ************************************************************************************************ +// Constants + +//const Cc = Components.classes; +//const Ci = Components.interfaces; + +var frameCounters = {}; +var traceRecursion = 0; + +Firebug.Console.injector = +{ + install: function(context) + { + var win = context.window; + + var consoleHandler = new FirebugConsoleHandler(context, win); + + var properties = + [ + "log", + "debug", + "info", + "warn", + "error", + "assert", + "dir", + "dirxml", + "group", + "groupCollapsed", + "groupEnd", + "time", + "timeEnd", + "count", + "trace", + "profile", + "profileEnd", + "clear", + "open", + "close" + ]; + + var Handler = function(name) + { + var c = consoleHandler; + var f = consoleHandler[name]; + return function(){return f.apply(c,arguments);}; + }; + + var installer = function(c) + { + for (var i=0, l=properties.length; i<l; i++) + { + var name = properties[i]; + c[name] = new Handler(name); + c.firebuglite = Firebug.version; + } + }; + + var sandbox; + + if (win.console) + { + if (Env.Options.overrideConsole) + sandbox = new win.Function("arguments.callee.install(window.console={})"); + else + // if there's a console object and overrideConsole is false we should just quit + return; + } + else + { + try + { + // try overriding the console object + sandbox = new win.Function("arguments.callee.install(window.console={})"); + } + catch(E) + { + // if something goes wrong create the firebug object instead + sandbox = new win.Function("arguments.callee.install(window.firebug={})"); + } + } + + sandbox.install = installer; + sandbox(); + }, + + isAttached: function(context, win) + { + if (win.wrappedJSObject) + { + var attached = (win.wrappedJSObject._getFirebugConsoleElement ? true : false); + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("Console.isAttached:"+attached+" to win.wrappedJSObject "+safeGetWindowLocation(win.wrappedJSObject)); + + return attached; + } + else + { + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("Console.isAttached? to win "+win.location+" fnc:"+win._getFirebugConsoleElement); + return (win._getFirebugConsoleElement ? true : false); + } + }, + + attachIfNeeded: function(context, win) + { + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("Console.attachIfNeeded has win "+(win? ((win.wrappedJSObject?"YES":"NO")+" wrappedJSObject"):"null") ); + + if (this.isAttached(context, win)) + return true; + + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("Console.attachIfNeeded found isAttached false "); + + this.attachConsoleInjector(context, win); + this.addConsoleListener(context, win); + + Firebug.Console.clearReloadWarning(context); + + var attached = this.isAttached(context, win); + if (attached) + dispatch(Firebug.Console.fbListeners, "onConsoleInjected", [context, win]); + + return attached; + }, + + attachConsoleInjector: function(context, win) + { + var consoleInjection = this.getConsoleInjectionScript(); // Do it all here. + + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("attachConsoleInjector evaluating in "+win.location, consoleInjection); + + Firebug.CommandLine.evaluateInWebPage(consoleInjection, context, win); + + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("attachConsoleInjector evaluation completed for "+win.location); + }, + + getConsoleInjectionScript: function() { + if (!this.consoleInjectionScript) + { + var script = ""; + script += "window.__defineGetter__('console', function() {\n"; + script += " return (window._firebug ? window._firebug : window.loadFirebugConsole()); })\n\n"; + + script += "window.loadFirebugConsole = function() {\n"; + script += "window._firebug = new _FirebugConsole();"; + + if (FBTrace.DBG_CONSOLE) + script += " window.dump('loadFirebugConsole '+window.location+'\\n');\n"; + + script += " return window._firebug };\n"; + + var theFirebugConsoleScript = getResource("chrome://firebug/content/consoleInjected.js"); + script += theFirebugConsoleScript; + + + this.consoleInjectionScript = script; + } + return this.consoleInjectionScript; + }, + + forceConsoleCompilationInPage: function(context, win) + { + if (!win) + { + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("no win in forceConsoleCompilationInPage!"); + return; + } + + var consoleForcer = "window.loadFirebugConsole();"; + + if (context.stopped) + Firebug.Console.injector.evaluateConsoleScript(context); // todo evaluate consoleForcer on stack + else + Firebug.CommandLine.evaluateInWebPage(consoleForcer, context, win); + + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("forceConsoleCompilationInPage "+win.location, consoleForcer); + }, + + evaluateConsoleScript: function(context) + { + var scriptSource = this.getConsoleInjectionScript(); // TODO XXXjjb this should be getConsoleInjectionScript + Firebug.Debugger.evaluate(scriptSource, context); + }, + + addConsoleListener: function(context, win) + { + if (!context.activeConsoleHandlers) // then we have not been this way before + context.activeConsoleHandlers = []; + else + { // we've been this way before... + for (var i=0; i<context.activeConsoleHandlers.length; i++) + { + if (context.activeConsoleHandlers[i].window == win) + { + context.activeConsoleHandlers[i].detach(); + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("consoleInjector addConsoleListener removed handler("+context.activeConsoleHandlers[i].handler_name+") from _firebugConsole in : "+win.location+"\n"); + context.activeConsoleHandlers.splice(i,1); + } + } + } + + // We need the element to attach our event listener. + var element = Firebug.Console.getFirebugConsoleElement(context, win); + if (element) + element.setAttribute("FirebugVersion", Firebug.version); // Initialize Firebug version. + else + return false; + + var handler = new FirebugConsoleHandler(context, win); + handler.attachTo(element); + + context.activeConsoleHandlers.push(handler); + + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("consoleInjector addConsoleListener attached handler("+handler.handler_name+") to _firebugConsole in : "+win.location+"\n"); + return true; + }, + + detachConsole: function(context, win) + { + if (win && win.document) + { + var element = win.document.getElementById("_firebugConsole"); + if (element) + element.parentNode.removeChild(element); + } + } +}; + +var total_handlers = 0; +var FirebugConsoleHandler = function FirebugConsoleHandler(context, win) +{ + this.window = win; + + this.attachTo = function(element) + { + this.element = element; + // When raised on our injected element, callback to Firebug and append to console + this.boundHandler = bind(this.handleEvent, this); + this.element.addEventListener('firebugAppendConsole', this.boundHandler, true); // capturing + }; + + this.detach = function() + { + this.element.removeEventListener('firebugAppendConsole', this.boundHandler, true); + }; + + this.handler_name = ++total_handlers; + this.handleEvent = function(event) + { + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("FirebugConsoleHandler("+this.handler_name+") "+event.target.getAttribute("methodName")+", event", event); + if (!Firebug.CommandLine.CommandHandler.handle(event, this, win)) + { + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("FirebugConsoleHandler", this); + + var methodName = event.target.getAttribute("methodName"); + Firebug.Console.log($STRF("console.MethodNotSupported", [methodName])); + } + }; + + this.firebuglite = Firebug.version; + + this.init = function() + { + var consoleElement = win.document.getElementById('_firebugConsole'); + consoleElement.setAttribute("FirebugVersion", Firebug.version); + }; + + this.log = function() + { + logFormatted(arguments, "log"); + }; + + this.debug = function() + { + logFormatted(arguments, "debug", true); + }; + + this.info = function() + { + logFormatted(arguments, "info", true); + }; + + this.warn = function() + { + logFormatted(arguments, "warn", true); + }; + + this.error = function() + { + //TODO: xxxpedro console error + //if (arguments.length == 1) + //{ + // logAssert("error", arguments); // add more info based on stack trace + //} + //else + //{ + //Firebug.Errors.increaseCount(context); + logFormatted(arguments, "error", true); // user already added info + //} + }; + + this.exception = function() + { + logAssert("error", arguments); + }; + + this.assert = function(x) + { + if (!x) + { + var rest = []; + for (var i = 1; i < arguments.length; i++) + rest.push(arguments[i]); + logAssert("assert", rest); + } + }; + + this.dir = function(o) + { + Firebug.Console.log(o, context, "dir", Firebug.DOMPanel.DirTable); + }; + + this.dirxml = function(o) + { + ///if (o instanceof Window) + if (instanceOf(o, "Window")) + o = o.document.documentElement; + ///else if (o instanceof Document) + else if (instanceOf(o, "Document")) + o = o.documentElement; + + Firebug.Console.log(o, context, "dirxml", Firebug.HTMLPanel.SoloElement); + }; + + this.group = function() + { + //TODO: xxxpedro; + //var sourceLink = getStackLink(); + var sourceLink = null; + Firebug.Console.openGroup(arguments, null, "group", null, false, sourceLink); + }; + + this.groupEnd = function() + { + Firebug.Console.closeGroup(context); + }; + + this.groupCollapsed = function() + { + var sourceLink = getStackLink(); + // noThrottle true is probably ok, openGroups will likely be short strings. + var row = Firebug.Console.openGroup(arguments, null, "group", null, true, sourceLink); + removeClass(row, "opened"); + }; + + this.profile = function(title) + { + logFormatted(["console.profile() not supported."], "warn", true); + + //Firebug.Profiler.startProfiling(context, title); + }; + + this.profileEnd = function() + { + logFormatted(["console.profile() not supported."], "warn", true); + + //Firebug.Profiler.stopProfiling(context); + }; + + this.count = function(key) + { + // TODO: xxxpedro console2: is there a better way to find a unique ID for the coun() call? + var frameId = "0"; + //var frameId = FBL.getStackFrameId(); + if (frameId) + { + if (!frameCounters) + frameCounters = {}; + + if (key != undefined) + frameId += key; + + var frameCounter = frameCounters[frameId]; + if (!frameCounter) + { + var logRow = logFormatted(["0"], null, true, true); + + frameCounter = {logRow: logRow, count: 1}; + frameCounters[frameId] = frameCounter; + } + else + ++frameCounter.count; + + var label = key == undefined + ? frameCounter.count + : key + " " + frameCounter.count; + + frameCounter.logRow.firstChild.firstChild.nodeValue = label; + } + }; + + this.trace = function() + { + var getFuncName = function getFuncName (f) + { + if (f.getName instanceof Function) + { + return f.getName(); + } + if (f.name) // in FireFox, Function objects have a name property... + { + return f.name; + } + + var name = f.toString().match(/function\s*([_$\w\d]*)/)[1]; + return name || "anonymous"; + }; + + var wasVisited = function(fn) + { + for (var i=0, l=frames.length; i<l; i++) + { + if (frames[i].fn == fn) + { + return true; + } + } + + return false; + }; + + traceRecursion++; + + if (traceRecursion > 1) + { + traceRecursion--; + return; + } + + var frames = []; + + for (var fn = arguments.callee.caller.caller; fn; fn = fn.caller) + { + if (wasVisited(fn)) break; + + var args = []; + + for (var i = 0, l = fn.arguments.length; i < l; ++i) + { + args.push({value: fn.arguments[i]}); + } + + frames.push({fn: fn, name: getFuncName(fn), args: args}); + } + + + // **************************************************************************************** + + try + { + (0)(); + } + catch(e) + { + var result = e; + + var stack = + result.stack || // Firefox / Google Chrome + result.stacktrace || // Opera + ""; + + stack = stack.replace(/\n\r|\r\n/g, "\n"); // normalize line breaks + var items = stack.split(/[\n\r]/); + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Google Chrome + if (FBL.isSafari) + { + //var reChromeStackItem = /^\s+at\s+([^\(]+)\s\((.*)\)$/; + //var reChromeStackItem = /^\s+at\s+(.*)((?:http|https|ftp|file):\/\/.*)$/; + var reChromeStackItem = /^\s+at\s+(.*)((?:http|https|ftp|file):\/\/.*)$/; + + var reChromeStackItemName = /\s*\($/; + var reChromeStackItemValue = /^(.+)\:(\d+\:\d+)\)?$/; + + var framePos = 0; + for (var i=4, length=items.length; i<length; i++, framePos++) + { + var frame = frames[framePos]; + var item = items[i]; + var match = item.match(reChromeStackItem); + + //Firebug.Console.log("["+ framePos +"]--------------------------"); + //Firebug.Console.log(item); + //Firebug.Console.log("................"); + + if (match) + { + var name = match[1]; + if (name) + { + name = name.replace(reChromeStackItemName, ""); + frame.name = name; + } + + //Firebug.Console.log("name: "+name); + + var value = match[2].match(reChromeStackItemValue); + if (value) + { + frame.href = value[1]; + frame.lineNo = value[2]; + + //Firebug.Console.log("url: "+value[1]); + //Firebug.Console.log("line: "+value[2]); + } + //else + // Firebug.Console.log(match[2]); + + } + } + } + /**/ + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + else if (FBL.isFirefox) + { + // Firefox + var reFirefoxStackItem = /^(.*)@(.*)$/; + var reFirefoxStackItemValue = /^(.+)\:(\d+)$/; + + var framePos = 0; + for (var i=2, length=items.length; i<length; i++, framePos++) + { + var frame = frames[framePos] || {}; + var item = items[i]; + var match = item.match(reFirefoxStackItem); + + if (match) + { + var name = match[1]; + + //Firebug.Console.logFormatted("name: "+name); + + var value = match[2].match(reFirefoxStackItemValue); + if (value) + { + frame.href = value[1]; + frame.lineNo = value[2]; + + //Firebug.Console.log("href: "+ value[1]); + //Firebug.Console.log("line: " + value[2]); + } + //else + // Firebug.Console.logFormatted([match[2]]); + } + } + } + /**/ + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + /* + else if (FBL.isOpera) + { + // Opera + var reOperaStackItem = /^\s\s(?:\.\.\.\s\s)?Line\s(\d+)\sof\s(.+)$/; + var reOperaStackItemValue = /^linked\sscript\s(.+)$/; + + for (var i=0, length=items.length; i<length; i+=2) + { + var item = items[i]; + + var match = item.match(reOperaStackItem); + + if (match) + { + //Firebug.Console.log(match[1]); + + var value = match[2].match(reOperaStackItemValue); + + if (value) + { + //Firebug.Console.log(value[1]); + } + //else + // Firebug.Console.log(match[2]); + + //Firebug.Console.log("--------------------------"); + } + } + } + /**/ + } + + //console.log(stack); + //console.dir(frames); + Firebug.Console.log({frames: frames}, context, "stackTrace", FirebugReps.StackTrace); + + traceRecursion--; + }; + + this.trace_ok = function() + { + var getFuncName = function getFuncName (f) + { + if (f.getName instanceof Function) + return f.getName(); + if (f.name) // in FireFox, Function objects have a name property... + return f.name; + + var name = f.toString().match(/function\s*([_$\w\d]*)/)[1]; + return name || "anonymous"; + }; + + var wasVisited = function(fn) + { + for (var i=0, l=frames.length; i<l; i++) + { + if (frames[i].fn == fn) + return true; + } + + return false; + }; + + var frames = []; + + for (var fn = arguments.callee.caller; fn; fn = fn.caller) + { + if (wasVisited(fn)) break; + + var args = []; + + for (var i = 0, l = fn.arguments.length; i < l; ++i) + { + args.push({value: fn.arguments[i]}); + } + + frames.push({fn: fn, name: getFuncName(fn), args: args}); + } + + Firebug.Console.log({frames: frames}, context, "stackTrace", FirebugReps.StackTrace); + }; + + this.clear = function() + { + Firebug.Console.clear(context); + }; + + this.time = function(name, reset) + { + if (!name) + return; + + var time = new Date().getTime(); + + if (!this.timeCounters) + this.timeCounters = {}; + + var key = "KEY"+name.toString(); + + if (!reset && this.timeCounters[key]) + return; + + this.timeCounters[key] = time; + }; + + this.timeEnd = function(name) + { + var time = new Date().getTime(); + + if (!this.timeCounters) + return; + + var key = "KEY"+name.toString(); + + var timeCounter = this.timeCounters[key]; + if (timeCounter) + { + var diff = time - timeCounter; + var label = name + ": " + diff + "ms"; + + this.info(label); + + delete this.timeCounters[key]; + } + return diff; + }; + + // These functions are over-ridden by commandLine + this.evaluated = function(result, context) + { + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("consoleInjector.FirebugConsoleHandler evalutated default called", result); + + Firebug.Console.log(result, context); + }; + this.evaluateError = function(result, context) + { + Firebug.Console.log(result, context, "errorMessage"); + }; + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + function logFormatted(args, className, linkToSource, noThrottle) + { + var sourceLink = linkToSource ? getStackLink() : null; + return Firebug.Console.logFormatted(args, context, className, noThrottle, sourceLink); + } + + function logAssert(category, args) + { + Firebug.Errors.increaseCount(context); + + if (!args || !args.length || args.length == 0) + var msg = [FBL.$STR("Assertion")]; + else + var msg = args[0]; + + if (Firebug.errorStackTrace) + { + var trace = Firebug.errorStackTrace; + delete Firebug.errorStackTrace; + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("logAssert trace from errorStackTrace", trace); + } + else if (msg.stack) + { + var trace = parseToStackTrace(msg.stack); + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("logAssert trace from msg.stack", trace); + } + else + { + var trace = getJSDUserStack(); + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("logAssert trace from getJSDUserStack", trace); + } + + var errorObject = new FBL.ErrorMessage(msg, (msg.fileName?msg.fileName:win.location), (msg.lineNumber?msg.lineNumber:0), "", category, context, trace); + + + if (trace && trace.frames && trace.frames[0]) + errorObject.correctWithStackTrace(trace); + + errorObject.resetSource(); + + var objects = errorObject; + if (args.length > 1) + { + objects = [errorObject]; + for (var i = 1; i < args.length; i++) + objects.push(args[i]); + } + + var row = Firebug.Console.log(objects, context, "errorMessage", null, true); // noThrottle + row.scrollIntoView(); + } + + function getComponentsStackDump() + { + // Starting with our stack, walk back to the user-level code + var frame = Components.stack; + var userURL = win.location.href.toString(); + + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("consoleInjector.getComponentsStackDump initial stack for userURL "+userURL, frame); + + // Drop frames until we get into user code. + while (frame && FBL.isSystemURL(frame.filename) ) + frame = frame.caller; + + // Drop two more frames, the injected console function and firebugAppendConsole() + if (frame) + frame = frame.caller; + if (frame) + frame = frame.caller; + + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("consoleInjector.getComponentsStackDump final stack for userURL "+userURL, frame); + + return frame; + } + + function getStackLink() + { + // TODO: xxxpedro console2 + return; + //return FBL.getFrameSourceLink(getComponentsStackDump()); + } + + function getJSDUserStack() + { + var trace = FBL.getCurrentStackTrace(context); + + var frames = trace ? trace.frames : null; + if (frames && (frames.length > 0) ) + { + var oldest = frames.length - 1; // 6 - 1 = 5 + for (var i = 0; i < frames.length; i++) + { + if (frames[oldest - i].href.indexOf("chrome:") == 0) break; + var fn = frames[oldest - i].fn + ""; + if (fn && (fn.indexOf("_firebugEvalEvent") != -1) ) break; // command line + } + FBTrace.sysout("consoleInjector getJSDUserStack: "+frames.length+" oldest: "+oldest+" i: "+i+" i - oldest + 2: "+(i - oldest + 2), trace); + trace.frames = trace.frames.slice(2 - i); // take the oldest frames, leave 2 behind they are injection code + + return trace; + } + else + return "Firebug failed to get stack trace with any frames"; + } +}; + +// ************************************************************************************************ +// Register console namespace + +FBL.registerConsole = function() +{ + var win = Env.browser.window; + Firebug.Console.injector.install(win); +}; + +registerConsole(); + +}}); + + +/* See license.txt for terms of usage */ + +FBL.ns(function() { with (FBL) { +// ************************************************************************************************ + + +// ************************************************************************************************ +// Globals + +var commandPrefix = ">>>"; +var reOpenBracket = /[\[\(\{]/; +var reCloseBracket = /[\]\)\}]/; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +var commandHistory = []; +var commandPointer = -1; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +var isAutoCompleting = null; +var autoCompletePrefix = null; +var autoCompleteExpr = null; +var autoCompleteBuffer = null; +var autoCompletePosition = null; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +var fbCommandLine = null; +var fbLargeCommandLine = null; +var fbLargeCommandButtons = null; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +var _completion = +{ + window: + [ + "console" + ], + + document: + [ + "getElementById", + "getElementsByTagName" + ] +}; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +var _stack = function(command) +{ + Firebug.context.persistedState.commandHistory.push(command); + Firebug.context.persistedState.commandPointer = + Firebug.context.persistedState.commandHistory.length; +}; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +// ************************************************************************************************ +// CommandLine + +Firebug.CommandLine = extend(Firebug.Module, +{ + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + element: null, + isMultiLine: false, + isActive: false, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + initialize: function(doc) + { + this.clear = bind(this.clear, this); + this.enter = bind(this.enter, this); + + this.onError = bind(this.onError, this); + this.onKeyDown = bind(this.onKeyDown, this); + this.onMultiLineKeyDown = bind(this.onMultiLineKeyDown, this); + + addEvent(Firebug.browser.window, "error", this.onError); + addEvent(Firebug.chrome.window, "error", this.onError); + }, + + shutdown: function(doc) + { + this.deactivate(); + + removeEvent(Firebug.browser.window, "error", this.onError); + removeEvent(Firebug.chrome.window, "error", this.onError); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + activate: function(multiLine, hideToggleIcon, onRun) + { + defineCommandLineAPI(); + + Firebug.context.persistedState.commandHistory = + Firebug.context.persistedState.commandHistory || []; + + Firebug.context.persistedState.commandPointer = + Firebug.context.persistedState.commandPointer || -1; + + if (this.isActive) + { + if (this.isMultiLine == multiLine) return; + + this.deactivate(); + } + + fbCommandLine = $("fbCommandLine"); + fbLargeCommandLine = $("fbLargeCommandLine"); + fbLargeCommandButtons = $("fbLargeCommandButtons"); + + if (multiLine) + { + onRun = onRun || this.enter; + + this.isMultiLine = true; + + this.element = fbLargeCommandLine; + + addEvent(this.element, "keydown", this.onMultiLineKeyDown); + + addEvent($("fbSmallCommandLineIcon"), "click", Firebug.chrome.hideLargeCommandLine); + + this.runButton = new Button({ + element: $("fbCommand_btRun"), + owner: Firebug.CommandLine, + onClick: onRun + }); + + this.runButton.initialize(); + + this.clearButton = new Button({ + element: $("fbCommand_btClear"), + owner: Firebug.CommandLine, + onClick: this.clear + }); + + this.clearButton.initialize(); + } + else + { + this.isMultiLine = false; + this.element = fbCommandLine; + + if (!fbCommandLine) + return; + + addEvent(this.element, "keydown", this.onKeyDown); + } + + //Firebug.Console.log("activate", this.element); + + if (isOpera) + fixOperaTabKey(this.element); + + if(this.lastValue) + this.element.value = this.lastValue; + + this.isActive = true; + }, + + deactivate: function() + { + if (!this.isActive) return; + + //Firebug.Console.log("deactivate", this.element); + + this.isActive = false; + + this.lastValue = this.element.value; + + if (this.isMultiLine) + { + removeEvent(this.element, "keydown", this.onMultiLineKeyDown); + + removeEvent($("fbSmallCommandLineIcon"), "click", Firebug.chrome.hideLargeCommandLine); + + this.runButton.destroy(); + this.clearButton.destroy(); + } + else + { + removeEvent(this.element, "keydown", this.onKeyDown); + } + + this.element = null; + delete this.element; + + fbCommandLine = null; + fbLargeCommandLine = null; + fbLargeCommandButtons = null; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + focus: function() + { + this.element.focus(); + }, + + blur: function() + { + this.element.blur(); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + clear: function() + { + this.element.value = ""; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + evaluate: function(expr) + { + // TODO: need to register the API in console.firebug.commandLineAPI + var api = "Firebug.CommandLine.API"; + + var result = Firebug.context.evaluate(expr, "window", api, Firebug.Console.error); + + return result; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + enter: function() + { + var command = this.element.value; + + if (!command) return; + + _stack(command); + + Firebug.Console.log(commandPrefix + " " + stripNewLines(command), + Firebug.browser, "command", FirebugReps.Text); + + var result = this.evaluate(command); + + Firebug.Console.log(result); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + prevCommand: function() + { + if (Firebug.context.persistedState.commandPointer > 0 && + Firebug.context.persistedState.commandHistory.length > 0) + { + this.element.value = Firebug.context.persistedState.commandHistory + [--Firebug.context.persistedState.commandPointer]; + } + }, + + nextCommand: function() + { + var element = this.element; + + var limit = Firebug.context.persistedState.commandHistory.length -1; + var i = Firebug.context.persistedState.commandPointer; + + if (i < limit) + element.value = Firebug.context.persistedState.commandHistory + [++Firebug.context.persistedState.commandPointer]; + + else if (i == limit) + { + ++Firebug.context.persistedState.commandPointer; + element.value = ""; + } + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + autocomplete: function(reverse) + { + var element = this.element; + + var command = element.value; + var offset = getExpressionOffset(command); + + var valBegin = offset ? command.substr(0, offset) : ""; + var val = command.substr(offset); + + var buffer, obj, objName, commandBegin, result, prefix; + + // if it is the beginning of the completion + if(!isAutoCompleting) + { + + // group1 - command begin + // group2 - base object + // group3 - property prefix + var reObj = /(.*[^_$\w\d\.])?((?:[_$\w][_$\w\d]*\.)*)([_$\w][_$\w\d]*)?$/; + var r = reObj.exec(val); + + // parse command + if (r[1] || r[2] || r[3]) + { + commandBegin = r[1] || ""; + objName = r[2] || ""; + prefix = r[3] || ""; + } + else if (val == "") + { + commandBegin = objName = prefix = ""; + } else + return; + + isAutoCompleting = true; + + // find base object + if(objName == "") + obj = window; + + else + { + objName = objName.replace(/\.$/, ""); + + var n = objName.split("."); + var target = window, o; + + for (var i=0, ni; ni = n[i]; i++) + { + if (o = target[ni]) + target = o; + + else + { + target = null; + break; + } + } + obj = target; + } + + // map base object + if(obj) + { + autoCompletePrefix = prefix; + autoCompleteExpr = valBegin + commandBegin + (objName ? objName + "." : ""); + autoCompletePosition = -1; + + buffer = autoCompleteBuffer = isIE ? + _completion[objName || "window"] || [] : []; + + for(var p in obj) + buffer.push(p); + } + + // if it is the continuation of the last completion + } else + buffer = autoCompleteBuffer; + + if (buffer) + { + prefix = autoCompletePrefix; + + var diff = reverse ? -1 : 1; + + for(var i=autoCompletePosition+diff, l=buffer.length, bi; i>=0 && i<l; i+=diff) + { + bi = buffer[i]; + + if (bi.indexOf(prefix) == 0) + { + autoCompletePosition = i; + result = bi; + break; + } + } + } + + if (result) + element.value = autoCompleteExpr + result; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + setMultiLine: function(multiLine) + { + if (multiLine == this.isMultiLine) return; + + this.activate(multiLine); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + onError: function(msg, href, lineNo) + { + href = href || ""; + + var lastSlash = href.lastIndexOf("/"); + var fileName = lastSlash == -1 ? href : href.substr(lastSlash+1); + var html = [ + '<span class="errorMessage">', msg, '</span>', + '<div class="objectBox-sourceLink">', fileName, ' (line ', lineNo, ')</div>' + ]; + + // TODO: xxxpedro ajust to Console2 + //Firebug.Console.writeRow(html, "error"); + }, + + onKeyDown: function(e) + { + e = e || event; + + var code = e.keyCode; + + /*tab, shift, control, alt*/ + if (code != 9 && code != 16 && code != 17 && code != 18) + { + isAutoCompleting = false; + } + + if (code == 13 /* enter */) + { + this.enter(); + this.clear(); + } + else if (code == 27 /* ESC */) + { + setTimeout(this.clear, 0); + } + else if (code == 38 /* up */) + { + this.prevCommand(); + } + else if (code == 40 /* down */) + { + this.nextCommand(); + } + else if (code == 9 /* tab */) + { + this.autocomplete(e.shiftKey); + } + else + return; + + cancelEvent(e, true); + return false; + }, + + onMultiLineKeyDown: function(e) + { + e = e || event; + + var code = e.keyCode; + + if (code == 13 /* enter */ && e.ctrlKey) + { + this.enter(); + } + } +}); + +Firebug.registerModule(Firebug.CommandLine); + + +// ************************************************************************************************ +// + +function getExpressionOffset(command) +{ + // XXXjoe This is kind of a poor-man's JavaScript parser - trying + // to find the start of the expression that the cursor is inside. + // Not 100% fool proof, but hey... + + var bracketCount = 0; + + var start = command.length-1; + for (; start >= 0; --start) + { + var c = command[start]; + if ((c == "," || c == ";" || c == " ") && !bracketCount) + break; + if (reOpenBracket.test(c)) + { + if (bracketCount) + --bracketCount; + else + break; + } + else if (reCloseBracket.test(c)) + ++bracketCount; + } + + return start + 1; +} + +// ************************************************************************************************ +// CommandLine API + +var CommandLineAPI = +{ + $: function(id) + { + return Firebug.browser.document.getElementById(id); + }, + + $$: function(selector, context) + { + context = context || Firebug.browser.document; + return Firebug.Selector ? + Firebug.Selector(selector, context) : + Firebug.Console.error("Firebug.Selector module not loaded."); + }, + + $0: null, + + $1: null, + + dir: function(o) + { + Firebug.Console.log(o, Firebug.context, "dir", Firebug.DOMPanel.DirTable); + }, + + dirxml: function(o) + { + ///if (o instanceof Window) + if (instanceOf(o, "Window")) + o = o.document.documentElement; + ///else if (o instanceof Document) + else if (instanceOf(o, "Document")) + o = o.documentElement; + + Firebug.Console.log(o, Firebug.context, "dirxml", Firebug.HTMLPanel.SoloElement); + } +}; + +// ************************************************************************************************ + +var defineCommandLineAPI = function defineCommandLineAPI() +{ + Firebug.CommandLine.API = {}; + for (var m in CommandLineAPI) + if (!Env.browser.window[m]) + Firebug.CommandLine.API[m] = CommandLineAPI[m]; + + var stack = FirebugChrome.htmlSelectionStack; + if (stack) + { + Firebug.CommandLine.API.$0 = stack[0]; + Firebug.CommandLine.API.$1 = stack[1]; + } +}; + +// ************************************************************************************************ +}}); + +/* See license.txt for terms of usage */ + +FBL.ns(function() { with (FBL) { +// ************************************************************************************************ + +// ************************************************************************************************ +// Globals + +var ElementCache = Firebug.Lite.Cache.Element; +var cacheID = Firebug.Lite.Cache.ID; + +var ignoreHTMLProps = +{ + // ignores the attributes injected by Sizzle, otherwise it will + // be visible on IE (when enumerating element.attributes) + sizcache: 1, + sizset: 1 +}; + +if (Firebug.ignoreFirebugElements) + // ignores also the cache property injected by firebug + ignoreHTMLProps[cacheID] = 1; + + +// ************************************************************************************************ +// HTML Module + +Firebug.HTML = extend(Firebug.Module, +{ + appendTreeNode: function(nodeArray, html) + { + var reTrim = /^\s+|\s+$/g; + + if (!nodeArray.length) nodeArray = [nodeArray]; + + for (var n=0, node; node=nodeArray[n]; n++) + { + if (node.nodeType == 1) + { + if (Firebug.ignoreFirebugElements && node.firebugIgnore) continue; + + var uid = ElementCache(node); + var child = node.childNodes; + var childLength = child.length; + + var nodeName = node.nodeName.toLowerCase(); + + var nodeVisible = isVisible(node); + + var hasSingleTextChild = childLength == 1 && node.firstChild.nodeType == 3 && + nodeName != "script" && nodeName != "style"; + + var nodeControl = !hasSingleTextChild && childLength > 0 ? + ('<div class="nodeControl"></div>') : ''; + + // FIXME xxxpedro remove this + //var isIE = false; + + if(isIE && nodeControl) + html.push(nodeControl); + + if (typeof uid != 'undefined') + html.push( + '<div class="objectBox-element" ', + 'id="', uid, + '">', + !isIE && nodeControl ? nodeControl: "", + '<span ', + cacheID, + '="', uid, + '" class="nodeBox', + nodeVisible ? "" : " nodeHidden", + '"><<span class="nodeTag">', nodeName, '</span>' + ); + else + html.push( + '<div class="objectBox-element"><span class="nodeBox', + nodeVisible ? "" : " nodeHidden", + '"><<span class="nodeTag">', + nodeName, '</span>' + ); + + for (var i = 0; i < node.attributes.length; ++i) + { + var attr = node.attributes[i]; + if (!attr.specified || + // Issue 4432: Firebug Lite: HTML is mixed-up with functions + // The problem here is that expando properties added to DOM elements in + // IE < 9 will behave like DOM attributes and so they'll show up when + // looking at element.attributes list. + isIE && (browserVersion-0<9) && typeof attr.nodeValue != "string" || + Firebug.ignoreFirebugElements && ignoreHTMLProps.hasOwnProperty(attr.nodeName)) + continue; + + var name = attr.nodeName.toLowerCase(); + var value = name == "style" ? formatStyles(node.style.cssText) : attr.nodeValue; + + html.push(' <span class="nodeName">', name, + '</span>="<span class="nodeValue">', escapeHTML(value), + '</span>"'); + } + + /* + // source code nodes + if (nodeName == 'script' || nodeName == 'style') + { + + if(document.all){ + var src = node.innerHTML+'\n'; + + }else { + var src = '\n'+node.innerHTML+'\n'; + } + + var match = src.match(/\n/g); + var num = match ? match.length : 0; + var s = [], sl = 0; + + for(var c=1; c<num; c++){ + s[sl++] = '<div line="'+c+'">' + c + '</div>'; + } + + html.push('></div><div class="nodeGroup"><div class="nodeChildren"><div class="lineNo">', + s.join(''), + '</div><pre class="nodeCode">', + escapeHTML(src), + '</pre>', + '</div><div class="objectBox-element"></<span class="nodeTag">', + nodeName, + '</span>></div>', + '</div>' + ); + + + }/**/ + + // Just a single text node child + if (hasSingleTextChild) + { + var value = child[0].nodeValue.replace(reTrim, ''); + if(value) + { + html.push( + '><span class="nodeText">', + escapeHTML(value), + '</span></<span class="nodeTag">', + nodeName, + '</span>></span></div>' + ); + } + else + html.push('/></span></div>'); // blank text, print as childless node + + } + else if (childLength > 0) + { + html.push('></span></div>'); + } + else + html.push('/></span></div>'); + + } + else if (node.nodeType == 3) + { + if ( node.parentNode && ( node.parentNode.nodeName.toLowerCase() == "script" || + node.parentNode.nodeName.toLowerCase() == "style" ) ) + { + var value = node.nodeValue.replace(reTrim, ''); + + if(isIE){ + var src = value+'\n'; + + }else { + var src = '\n'+value+'\n'; + } + + var match = src.match(/\n/g); + var num = match ? match.length : 0; + var s = [], sl = 0; + + for(var c=1; c<num; c++){ + s[sl++] = '<div line="'+c+'">' + c + '</div>'; + } + + html.push('<div class="lineNo">', + s.join(''), + '</div><pre class="sourceCode">', + escapeHTML(src), + '</pre>' + ); + + } + else + { + var value = node.nodeValue.replace(reTrim, ''); + if (value) + html.push('<div class="nodeText">', escapeHTML(value),'</div>'); + } + } + } + }, + + appendTreeChildren: function(treeNode) + { + var doc = Firebug.chrome.document; + var uid = treeNode.id; + var parentNode = ElementCache.get(uid); + + if (parentNode.childNodes.length == 0) return; + + var treeNext = treeNode.nextSibling; + var treeParent = treeNode.parentNode; + + // FIXME xxxpedro remove this + //var isIE = false; + var control = isIE ? treeNode.previousSibling : treeNode.firstChild; + control.className = 'nodeControl nodeMaximized'; + + var html = []; + var children = doc.createElement("div"); + children.className = "nodeChildren"; + this.appendTreeNode(parentNode.childNodes, html); + children.innerHTML = html.join(""); + + treeParent.insertBefore(children, treeNext); + + var closeElement = doc.createElement("div"); + closeElement.className = "objectBox-element"; + closeElement.innerHTML = '</<span class="nodeTag">' + + parentNode.nodeName.toLowerCase() + '></span>'; + + treeParent.insertBefore(closeElement, treeNext); + + }, + + removeTreeChildren: function(treeNode) + { + var children = treeNode.nextSibling; + var closeTag = children.nextSibling; + + // FIXME xxxpedro remove this + //var isIE = false; + var control = isIE ? treeNode.previousSibling : treeNode.firstChild; + control.className = 'nodeControl'; + + children.parentNode.removeChild(children); + closeTag.parentNode.removeChild(closeTag); + }, + + isTreeNodeVisible: function(id) + { + return $(id); + }, + + select: function(el) + { + var id = el && ElementCache(el); + if (id) + this.selectTreeNode(id); + }, + + selectTreeNode: function(id) + { + id = ""+id; + var node, stack = []; + while(id && !this.isTreeNodeVisible(id)) + { + stack.push(id); + + var node = ElementCache.get(id).parentNode; + + if (node) + id = ElementCache(node); + else + break; + } + + stack.push(id); + + while(stack.length > 0) + { + id = stack.pop(); + node = $(id); + + if (stack.length > 0 && ElementCache.get(id).childNodes.length > 0) + this.appendTreeChildren(node); + } + + selectElement(node); + + // TODO: xxxpedro + if (fbPanel1) + fbPanel1.scrollTop = Math.round(node.offsetTop - fbPanel1.clientHeight/2); + } + +}); + +Firebug.registerModule(Firebug.HTML); + +// ************************************************************************************************ +// HTML Panel + +function HTMLPanel(){}; + +HTMLPanel.prototype = extend(Firebug.Panel, +{ + name: "HTML", + title: "HTML", + + options: { + hasSidePanel: true, + //hasToolButtons: true, + isPreRendered: !Firebug.flexChromeEnabled /* FIXME xxxpedro chromenew */, + innerHTMLSync: true + }, + + create: function(){ + Firebug.Panel.create.apply(this, arguments); + + this.panelNode.style.padding = "4px 3px 1px 15px"; + this.panelNode.style.minWidth = "500px"; + + if (Env.Options.enablePersistent || Firebug.chrome.type != "popup") + this.createUI(); + + if(this.sidePanelBar && !this.sidePanelBar.selectedPanel) + { + this.sidePanelBar.selectPanel("css"); + } + }, + + destroy: function() + { + selectedElement = null; + fbPanel1 = null; + + selectedSidePanelTS = null; + selectedSidePanelTimer = null; + + Firebug.Panel.destroy.apply(this, arguments); + }, + + createUI: function() + { + var rootNode = Firebug.browser.document.documentElement; + var html = []; + Firebug.HTML.appendTreeNode(rootNode, html); + + this.panelNode.innerHTML = html.join(""); + }, + + initialize: function() + { + Firebug.Panel.initialize.apply(this, arguments); + addEvent(this.panelNode, 'click', Firebug.HTML.onTreeClick); + + fbPanel1 = $("fbPanel1"); + + if(!selectedElement) + { + Firebug.context.persistedState.selectedHTMLElementId = + Firebug.context.persistedState.selectedHTMLElementId && + ElementCache.get(Firebug.context.persistedState.selectedHTMLElementId) ? + Firebug.context.persistedState.selectedHTMLElementId : + ElementCache(Firebug.browser.document.body); + + Firebug.HTML.selectTreeNode(Firebug.context.persistedState.selectedHTMLElementId); + } + + // TODO: xxxpedro + addEvent(fbPanel1, 'mousemove', Firebug.HTML.onListMouseMove); + addEvent($("fbContent"), 'mouseout', Firebug.HTML.onListMouseMove); + addEvent(Firebug.chrome.node, 'mouseout', Firebug.HTML.onListMouseMove); + }, + + shutdown: function() + { + // TODO: xxxpedro + removeEvent(fbPanel1, 'mousemove', Firebug.HTML.onListMouseMove); + removeEvent($("fbContent"), 'mouseout', Firebug.HTML.onListMouseMove); + removeEvent(Firebug.chrome.node, 'mouseout', Firebug.HTML.onListMouseMove); + + removeEvent(this.panelNode, 'click', Firebug.HTML.onTreeClick); + + fbPanel1 = null; + + Firebug.Panel.shutdown.apply(this, arguments); + }, + + reattach: function() + { + // TODO: panel reattach + if(Firebug.context.persistedState.selectedHTMLElementId) + Firebug.HTML.selectTreeNode(Firebug.context.persistedState.selectedHTMLElementId); + }, + + updateSelection: function(object) + { + var id = ElementCache(object); + + if (id) + { + Firebug.HTML.selectTreeNode(id); + } + } +}); + +Firebug.registerPanel(HTMLPanel); + +// ************************************************************************************************ + +var formatStyles = function(styles) +{ + return isIE ? + // IE return CSS property names in upper case, so we need to convert them + styles.replace(/([^\s]+)\s*:/g, function(m,g){return g.toLowerCase()+":";}) : + // other browsers are just fine + styles; +}; + +// ************************************************************************************************ + +var selectedElement = null; +var fbPanel1 = null; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +var selectedSidePanelTS, selectedSidePanelTimer; + +var selectElement= function selectElement(e) +{ + if (e != selectedElement) + { + if (selectedElement) + selectedElement.className = "objectBox-element"; + + e.className = e.className + " selectedElement"; + + if (FBL.isFirefox) + e.style.MozBorderRadius = "2px"; + + else if (FBL.isSafari) + e.style.WebkitBorderRadius = "2px"; + + e.style.borderRadius = "2px"; + + selectedElement = e; + + Firebug.context.persistedState.selectedHTMLElementId = e.id; + + var target = ElementCache.get(e.id); + var sidePanelBar = Firebug.chrome.getPanel("HTML").sidePanelBar; + var selectedSidePanel = sidePanelBar ? sidePanelBar.selectedPanel : null; + + var stack = FirebugChrome.htmlSelectionStack; + + stack.unshift(target); + + if (stack.length > 2) + stack.pop(); + + var lazySelect = function() + { + selectedSidePanelTS = new Date().getTime(); + + if (selectedSidePanel) + selectedSidePanel.select(target, true); + }; + + if (selectedSidePanelTimer) + { + clearTimeout(selectedSidePanelTimer); + selectedSidePanelTimer = null; + } + + if (new Date().getTime() - selectedSidePanelTS > 100) + setTimeout(lazySelect, 0); + else + selectedSidePanelTimer = setTimeout(lazySelect, 150); + } +}; + + +// ************************************************************************************************ +// *** TODO: REFACTOR ************************************************************************** +// ************************************************************************************************ +Firebug.HTML.onTreeClick = function (e) +{ + e = e || event; + var targ; + + if (e.target) targ = e.target; + else if (e.srcElement) targ = e.srcElement; + if (targ.nodeType == 3) // defeat Safari bug + targ = targ.parentNode; + + + if (targ.className.indexOf('nodeControl') != -1 || targ.className == 'nodeTag') + { + // FIXME xxxpedro remove this + //var isIE = false; + + if(targ.className == 'nodeTag') + { + var control = isIE ? (targ.parentNode.previousSibling || targ) : + (targ.parentNode.previousSibling || targ); + + selectElement(targ.parentNode.parentNode); + + if (control.className.indexOf('nodeControl') == -1) + return; + + } else + control = targ; + + FBL.cancelEvent(e); + + var treeNode = isIE ? control.nextSibling : control.parentNode; + + //FBL.Firebug.Console.log(treeNode); + + if (control.className.indexOf(' nodeMaximized') != -1) { + FBL.Firebug.HTML.removeTreeChildren(treeNode); + } else { + FBL.Firebug.HTML.appendTreeChildren(treeNode); + } + } + else if (targ.className == 'nodeValue' || targ.className == 'nodeName') + { + /* + var input = FBL.Firebug.chrome.document.getElementById('treeInput'); + + input.style.display = "block"; + input.style.left = targ.offsetLeft + 'px'; + input.style.top = FBL.topHeight + targ.offsetTop - FBL.fbPanel1.scrollTop + 'px'; + input.style.width = targ.offsetWidth + 6 + 'px'; + input.value = targ.textContent || targ.innerText; + input.focus(); + /**/ + } +}; + +function onListMouseOut(e) +{ + e = e || event || window; + var targ; + + if (e.target) targ = e.target; + else if (e.srcElement) targ = e.srcElement; + if (targ.nodeType == 3) // defeat Safari bug + targ = targ.parentNode; + + if (hasClass(targ, "fbPanel")) { + FBL.Firebug.Inspector.hideBoxModel(); + hoverElement = null; + } +}; + +var hoverElement = null; +var hoverElementTS = 0; + +Firebug.HTML.onListMouseMove = function onListMouseMove(e) +{ + try + { + e = e || event || window; + var targ; + + if (e.target) targ = e.target; + else if (e.srcElement) targ = e.srcElement; + if (targ.nodeType == 3) // defeat Safari bug + targ = targ.parentNode; + + var found = false; + while (targ && !found) { + if (!/\snodeBox\s|\sobjectBox-selector\s/.test(" " + targ.className + " ")) + targ = targ.parentNode; + else + found = true; + } + + if (!targ) + { + FBL.Firebug.Inspector.hideBoxModel(); + hoverElement = null; + return; + } + + /* + if (typeof targ.attributes[cacheID] == 'undefined') return; + + var uid = targ.attributes[cacheID]; + if (!uid) return; + /**/ + + if (typeof targ.attributes[cacheID] == 'undefined') return; + + var uid = targ.attributes[cacheID]; + if (!uid) return; + + var el = ElementCache.get(uid.value); + + var nodeName = el.nodeName.toLowerCase(); + + if (FBL.isIE && " meta title script link ".indexOf(" "+nodeName+" ") != -1) + return; + + if (!/\snodeBox\s|\sobjectBox-selector\s/.test(" " + targ.className + " ")) return; + + if (el.id == "FirebugUI" || " html head body br script link iframe ".indexOf(" "+nodeName+" ") != -1) { + FBL.Firebug.Inspector.hideBoxModel(); + hoverElement = null; + return; + } + + if ((new Date().getTime() - hoverElementTS > 40) && hoverElement != el) { + hoverElementTS = new Date().getTime(); + hoverElement = el; + FBL.Firebug.Inspector.drawBoxModel(el); + } + } + catch(E) + { + } +}; + + +// ************************************************************************************************ + +Firebug.Reps = { + + appendText: function(object, html) + { + html.push(escapeHTML(objectToString(object))); + }, + + appendNull: function(object, html) + { + html.push('<span class="objectBox-null">', escapeHTML(objectToString(object)), '</span>'); + }, + + appendString: function(object, html) + { + html.push('<span class="objectBox-string">"', escapeHTML(objectToString(object)), + '"</span>'); + }, + + appendInteger: function(object, html) + { + html.push('<span class="objectBox-number">', escapeHTML(objectToString(object)), '</span>'); + }, + + appendFloat: function(object, html) + { + html.push('<span class="objectBox-number">', escapeHTML(objectToString(object)), '</span>'); + }, + + appendFunction: function(object, html) + { + var reName = /function ?(.*?)\(/; + var m = reName.exec(objectToString(object)); + var name = m && m[1] ? m[1] : "function"; + html.push('<span class="objectBox-function">', escapeHTML(name), '()</span>'); + }, + + appendObject: function(object, html) + { + /* + var rep = Firebug.getRep(object); + var outputs = []; + + rep.tag.tag.compile(); + + var str = rep.tag.renderHTML({object: object}, outputs); + html.push(str); + /**/ + + try + { + if (object == undefined) + this.appendNull("undefined", html); + else if (object == null) + this.appendNull("null", html); + else if (typeof object == "string") + this.appendString(object, html); + else if (typeof object == "number") + this.appendInteger(object, html); + else if (typeof object == "boolean") + this.appendInteger(object, html); + else if (typeof object == "function") + this.appendFunction(object, html); + else if (object.nodeType == 1) + this.appendSelector(object, html); + else if (typeof object == "object") + { + if (typeof object.length != "undefined") + this.appendArray(object, html); + else + this.appendObjectFormatted(object, html); + } + else + this.appendText(object, html); + } + catch (exc) + { + } + /**/ + }, + + appendObjectFormatted: function(object, html) + { + var text = objectToString(object); + var reObject = /\[object (.*?)\]/; + + var m = reObject.exec(text); + html.push('<span class="objectBox-object">', m ? m[1] : text, '</span>'); + }, + + appendSelector: function(object, html) + { + var uid = ElementCache(object); + var uidString = uid ? [cacheID, '="', uid, '"'].join("") : ""; + + html.push('<span class="objectBox-selector"', uidString, '>'); + + html.push('<span class="selectorTag">', escapeHTML(object.nodeName.toLowerCase()), '</span>'); + if (object.id) + html.push('<span class="selectorId">#', escapeHTML(object.id), '</span>'); + if (object.className) + html.push('<span class="selectorClass">.', escapeHTML(object.className), '</span>'); + + html.push('</span>'); + }, + + appendNode: function(node, html) + { + if (node.nodeType == 1) + { + var uid = ElementCache(node); + var uidString = uid ? [cacheID, '="', uid, '"'].join("") : ""; + + html.push( + '<div class="objectBox-element"', uidString, '">', + '<span ', cacheID, '="', uid, '" class="nodeBox">', + '<<span class="nodeTag">', node.nodeName.toLowerCase(), '</span>'); + + for (var i = 0; i < node.attributes.length; ++i) + { + var attr = node.attributes[i]; + if (!attr.specified || attr.nodeName == cacheID) + continue; + + var name = attr.nodeName.toLowerCase(); + var value = name == "style" ? node.style.cssText : attr.nodeValue; + + html.push(' <span class="nodeName">', name, + '</span>="<span class="nodeValue">', escapeHTML(value), + '</span>"'); + } + + if (node.firstChild) + { + html.push('></div><div class="nodeChildren">'); + + for (var child = node.firstChild; child; child = child.nextSibling) + this.appendNode(child, html); + + html.push('</div><div class="objectBox-element"></<span class="nodeTag">', + node.nodeName.toLowerCase(), '></span></span></div>'); + } + else + html.push('/></span></div>'); + } + else if (node.nodeType == 3) + { + var value = trim(node.nodeValue); + if (value) + html.push('<div class="nodeText">', escapeHTML(value),'</div>'); + } + }, + + appendArray: function(object, html) + { + html.push('<span class="objectBox-array"><b>[</b> '); + + for (var i = 0, l = object.length, obj; i < l; ++i) + { + this.appendObject(object[i], html); + + if (i < l-1) + html.push(', '); + } + + html.push(' <b>]</b></span>'); + } + +}; + + + +// ************************************************************************************************ +}}); + +/* See license.txt for terms of usage */ + +/* + +Hack: +Firebug.chrome.currentPanel = Firebug.chrome.selectedPanel; +Firebug.showInfoTips = true; +Firebug.InfoTip.initializeBrowser(Firebug.chrome); + +/**/ + +FBL.ns(function() { with (FBL) { + +// ************************************************************************************************ +// Constants + +var maxWidth = 100, maxHeight = 80; +var infoTipMargin = 10; +var infoTipWindowPadding = 25; + +// ************************************************************************************************ + +Firebug.InfoTip = extend(Firebug.Module, +{ + dispatchName: "infoTip", + tags: domplate( + { + infoTipTag: DIV({"class": "infoTip"}), + + colorTag: + DIV({style: "background: $rgbValue; width: 100px; height: 40px"}, " "), + + imgTag: + DIV({"class": "infoTipImageBox infoTipLoading"}, + IMG({"class": "infoTipImage", src: "$urlValue", repeat: "$repeat", + onload: "$onLoadImage"}), + IMG({"class": "infoTipBgImage", collapsed: true, src: "blank.gif"}), + DIV({"class": "infoTipCaption"}) + ), + + onLoadImage: function(event) + { + var img = event.currentTarget || event.srcElement; + ///var bgImg = img.nextSibling; + ///if (!bgImg) + /// return; // Sometimes gets called after element is dead + + ///var caption = bgImg.nextSibling; + var innerBox = img.parentNode; + + /// TODO: xxxpedro infoTip hack + var caption = getElementByClass(innerBox, "infoTipCaption"); + var bgImg = getElementByClass(innerBox, "infoTipBgImage"); + if (!bgImg) + return; // Sometimes gets called after element is dead + + // TODO: xxxpedro infoTip IE and timing issue + // TODO: use offline document to avoid flickering + if (isIE) + removeClass(innerBox, "infoTipLoading"); + + var updateInfoTip = function(){ + + var w = img.naturalWidth || img.width || 10, + h = img.naturalHeight || img.height || 10; + + var repeat = img.getAttribute("repeat"); + + if (repeat == "repeat-x" || (w == 1 && h > 1)) + { + collapse(img, true); + collapse(bgImg, false); + bgImg.style.background = "url(" + img.src + ") repeat-x"; + bgImg.style.width = maxWidth + "px"; + if (h > maxHeight) + bgImg.style.height = maxHeight + "px"; + else + bgImg.style.height = h + "px"; + } + else if (repeat == "repeat-y" || (h == 1 && w > 1)) + { + collapse(img, true); + collapse(bgImg, false); + bgImg.style.background = "url(" + img.src + ") repeat-y"; + bgImg.style.height = maxHeight + "px"; + if (w > maxWidth) + bgImg.style.width = maxWidth + "px"; + else + bgImg.style.width = w + "px"; + } + else if (repeat == "repeat" || (w == 1 && h == 1)) + { + collapse(img, true); + collapse(bgImg, false); + bgImg.style.background = "url(" + img.src + ") repeat"; + bgImg.style.width = maxWidth + "px"; + bgImg.style.height = maxHeight + "px"; + } + else + { + if (w > maxWidth || h > maxHeight) + { + if (w > h) + { + img.style.width = maxWidth + "px"; + img.style.height = Math.round((h / w) * maxWidth) + "px"; + } + else + { + img.style.width = Math.round((w / h) * maxHeight) + "px"; + img.style.height = maxHeight + "px"; + } + } + } + + //caption.innerHTML = $STRF("Dimensions", [w, h]); + caption.innerHTML = $STRF(w + " x " + h); + + + }; + + if (isIE) + setTimeout(updateInfoTip, 0); + else + { + updateInfoTip(); + removeClass(innerBox, "infoTipLoading"); + } + + /// + } + + /* + /// onLoadImage original + onLoadImage: function(event) + { + var img = event.currentTarget; + var bgImg = img.nextSibling; + if (!bgImg) + return; // Sometimes gets called after element is dead + + var caption = bgImg.nextSibling; + var innerBox = img.parentNode; + + var w = img.naturalWidth, h = img.naturalHeight; + var repeat = img.getAttribute("repeat"); + + if (repeat == "repeat-x" || (w == 1 && h > 1)) + { + collapse(img, true); + collapse(bgImg, false); + bgImg.style.background = "url(" + img.src + ") repeat-x"; + bgImg.style.width = maxWidth + "px"; + if (h > maxHeight) + bgImg.style.height = maxHeight + "px"; + else + bgImg.style.height = h + "px"; + } + else if (repeat == "repeat-y" || (h == 1 && w > 1)) + { + collapse(img, true); + collapse(bgImg, false); + bgImg.style.background = "url(" + img.src + ") repeat-y"; + bgImg.style.height = maxHeight + "px"; + if (w > maxWidth) + bgImg.style.width = maxWidth + "px"; + else + bgImg.style.width = w + "px"; + } + else if (repeat == "repeat" || (w == 1 && h == 1)) + { + collapse(img, true); + collapse(bgImg, false); + bgImg.style.background = "url(" + img.src + ") repeat"; + bgImg.style.width = maxWidth + "px"; + bgImg.style.height = maxHeight + "px"; + } + else + { + if (w > maxWidth || h > maxHeight) + { + if (w > h) + { + img.style.width = maxWidth + "px"; + img.style.height = Math.round((h / w) * maxWidth) + "px"; + } + else + { + img.style.width = Math.round((w / h) * maxHeight) + "px"; + img.style.height = maxHeight + "px"; + } + } + } + + caption.innerHTML = $STRF("Dimensions", [w, h]); + + removeClass(innerBox, "infoTipLoading"); + } + /**/ + + }), + + initializeBrowser: function(browser) + { + browser.onInfoTipMouseOut = bind(this.onMouseOut, this, browser); + browser.onInfoTipMouseMove = bind(this.onMouseMove, this, browser); + + ///var doc = browser.contentDocument; + var doc = browser.document; + if (!doc) + return; + + ///doc.addEventListener("mouseover", browser.onInfoTipMouseMove, true); + ///doc.addEventListener("mouseout", browser.onInfoTipMouseOut, true); + ///doc.addEventListener("mousemove", browser.onInfoTipMouseMove, true); + addEvent(doc, "mouseover", browser.onInfoTipMouseMove); + addEvent(doc, "mouseout", browser.onInfoTipMouseOut); + addEvent(doc, "mousemove", browser.onInfoTipMouseMove); + + return browser.infoTip = this.tags.infoTipTag.append({}, getBody(doc)); + }, + + uninitializeBrowser: function(browser) + { + if (browser.infoTip) + { + ///var doc = browser.contentDocument; + var doc = browser.document; + ///doc.removeEventListener("mouseover", browser.onInfoTipMouseMove, true); + ///doc.removeEventListener("mouseout", browser.onInfoTipMouseOut, true); + ///doc.removeEventListener("mousemove", browser.onInfoTipMouseMove, true); + removeEvent(doc, "mouseover", browser.onInfoTipMouseMove); + removeEvent(doc, "mouseout", browser.onInfoTipMouseOut); + removeEvent(doc, "mousemove", browser.onInfoTipMouseMove); + + browser.infoTip.parentNode.removeChild(browser.infoTip); + delete browser.infoTip; + delete browser.onInfoTipMouseMove; + } + }, + + showInfoTip: function(infoTip, panel, target, x, y, rangeParent, rangeOffset) + { + if (!Firebug.showInfoTips) + return; + + var scrollParent = getOverflowParent(target); + var scrollX = x + (scrollParent ? scrollParent.scrollLeft : 0); + + if (panel.showInfoTip(infoTip, target, scrollX, y, rangeParent, rangeOffset)) + { + var htmlElt = infoTip.ownerDocument.documentElement; + var panelWidth = htmlElt.clientWidth; + var panelHeight = htmlElt.clientHeight; + + if (x+infoTip.offsetWidth+infoTipMargin > panelWidth) + { + infoTip.style.left = Math.max(0, panelWidth-(infoTip.offsetWidth+infoTipMargin)) + "px"; + infoTip.style.right = "auto"; + } + else + { + infoTip.style.left = (x+infoTipMargin) + "px"; + infoTip.style.right = "auto"; + } + + if (y+infoTip.offsetHeight+infoTipMargin > panelHeight) + { + infoTip.style.top = Math.max(0, panelHeight-(infoTip.offsetHeight+infoTipMargin)) + "px"; + infoTip.style.bottom = "auto"; + } + else + { + infoTip.style.top = (y+infoTipMargin) + "px"; + infoTip.style.bottom = "auto"; + } + + if (FBTrace.DBG_INFOTIP) + FBTrace.sysout("infotip.showInfoTip; top: " + infoTip.style.top + + ", left: " + infoTip.style.left + ", bottom: " + infoTip.style.bottom + + ", right:" + infoTip.style.right + ", offsetHeight: " + infoTip.offsetHeight + + ", offsetWidth: " + infoTip.offsetWidth + + ", x: " + x + ", panelWidth: " + panelWidth + + ", y: " + y + ", panelHeight: " + panelHeight); + + infoTip.setAttribute("active", "true"); + } + else + this.hideInfoTip(infoTip); + }, + + hideInfoTip: function(infoTip) + { + if (infoTip) + infoTip.removeAttribute("active"); + }, + + onMouseOut: function(event, browser) + { + if (!event.relatedTarget) + this.hideInfoTip(browser.infoTip); + }, + + onMouseMove: function(event, browser) + { + // Ignore if the mouse is moving over the existing info tip. + if (getAncestorByClass(event.target, "infoTip")) + return; + + if (browser.currentPanel) + { + var x = event.clientX, y = event.clientY, target = event.target || event.srcElement; + this.showInfoTip(browser.infoTip, browser.currentPanel, target, x, y, event.rangeParent, event.rangeOffset); + } + else + this.hideInfoTip(browser.infoTip); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + populateColorInfoTip: function(infoTip, color) + { + this.tags.colorTag.replace({rgbValue: color}, infoTip); + return true; + }, + + populateImageInfoTip: function(infoTip, url, repeat) + { + if (!repeat) + repeat = "no-repeat"; + + this.tags.imgTag.replace({urlValue: url, repeat: repeat}, infoTip); + + return true; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // extends Module + + disable: function() + { + // XXXjoe For each browser, call uninitializeBrowser + }, + + showPanel: function(browser, panel) + { + if (panel) + { + var infoTip = panel.panelBrowser.infoTip; + if (!infoTip) + infoTip = this.initializeBrowser(panel.panelBrowser); + this.hideInfoTip(infoTip); + } + + }, + + showSidePanel: function(browser, panel) + { + this.showPanel(browser, panel); + } +}); + +// ************************************************************************************************ + +Firebug.registerModule(Firebug.InfoTip); + +// ************************************************************************************************ + +}}); + + +/* See license.txt for terms of usage */ + +FBL.ns(function() { with (FBL) { +// ************************************************************************************************ + +var CssParser = null; + +// ************************************************************************************************ + +// Simple CSS stylesheet parser from: +// https://github.com/sergeche/webkit-css + +/** + * Simple CSS stylesheet parser that remembers rule's lines in file + * @author Sergey Chikuyonok (serge.che@gmail.com) + * @link http://chikuyonok.ru + */ +CssParser = (function(){ + /** + * Returns rule object + * @param {Number} start Character index where CSS rule definition starts + * @param {Number} body_start Character index where CSS rule's body starts + * @param {Number} end Character index where CSS rule definition ends + */ + function rule(start, body_start, end) { + return { + start: start || 0, + body_start: body_start || 0, + end: end || 0, + line: -1, + selector: null, + parent: null, + + /** @type {rule[]} */ + children: [], + + addChild: function(start, body_start, end) { + var r = rule(start, body_start, end); + r.parent = this; + this.children.push(r); + return r; + }, + /** + * Returns last child element + * @return {rule} + */ + lastChild: function() { + return this.children[this.children.length - 1]; + } + }; + } + + /** + * Replaces all occurances of substring defined by regexp + * @param {String} str + * @return {RegExp} re + * @return {String} + */ + function removeAll(str, re) { + var m; + while (m = str.match(re)) { + str = str.substring(m[0].length); + } + + return str; + } + + /** + * Trims whitespace from the beginning and the end of string + * @param {String} str + * @return {String} + */ + function trim(str) { + return str.replace(/^\s+|\s+$/g, ''); + } + + /** + * Normalizes CSS rules selector + * @param {String} selector + */ + function normalizeSelector(selector) { + // remove newlines + selector = selector.replace(/[\n\r]/g, ' '); + + selector = trim(selector); + + // remove spaces after commas + selector = selector.replace(/\s*,\s*/g, ','); + + return selector; + } + + /** + * Preprocesses parsed rules: adjusts char indexes, skipping whitespace and + * newlines, saves rule selector, removes comments, etc. + * @param {String} text CSS stylesheet + * @param {rule} rule_node CSS rule node + * @return {rule[]} + */ + function preprocessRules(text, rule_node) { + for (var i = 0, il = rule_node.children.length; i < il; i++) { + var r = rule_node.children[i], + rule_start = text.substring(r.start, r.body_start), + cur_len = rule_start.length; + + // remove newlines for better regexp matching + rule_start = rule_start.replace(/[\n\r]/g, ' '); + + // remove @import rules +// rule_start = removeAll(rule_start, /^\s*@import\s*url\((['"])?.+?\1?\)\;?/g); + + // remove comments + rule_start = removeAll(rule_start, /^\s*\/\*.*?\*\/[\s\t]*/); + + // remove whitespace + rule_start = rule_start.replace(/^[\s\t]+/, ''); + + r.start += (cur_len - rule_start.length); + r.selector = normalizeSelector(rule_start); + } + + return rule_node; + } + + /** + * Saves all lise starting indexes for faster search + * @param {String} text CSS stylesheet + * @return {Number[]} + */ + function saveLineIndexes(text) { + var result = [0], + i = 0, + il = text.length, + ch, ch2; + + while (i < il) { + ch = text.charAt(i); + + if (ch == '\n' || ch == '\r') { + if (ch == '\r' && i < il - 1 && text.charAt(i + 1) == '\n') { + // windows line ending: CRLF. Skip next character + i++; + } + + result.push(i + 1); + } + + i++; + } + + return result; + } + + /** + * Saves line number for parsed rules + * @param {String} text CSS stylesheet + * @param {rule} rule_node Rule node + * @return {rule[]} + */ + function saveLineNumbers(text, rule_node, line_indexes, startLine) { + preprocessRules(text, rule_node); + + startLine = startLine || 0; + + // remember lines start indexes, preserving line ending characters + if (!line_indexes) + var line_indexes = saveLineIndexes(text); + + // now find each rule's line + for (var i = 0, il = rule_node.children.length; i < il; i++) { + var r = rule_node.children[i]; + r.line = line_indexes.length + startLine; + for (var j = 0, jl = line_indexes.length - 1; j < jl; j++) { + var line_ix = line_indexes[j]; + if (r.start >= line_indexes[j] && r.start < line_indexes[j + 1]) { + r.line = j + 1 + startLine; + break; + } + } + + saveLineNumbers(text, r, line_indexes); + } + + return rule_node; + } + + return { + /** + * Parses text as CSS stylesheet, remembring each rule position inside + * text + * @param {String} text CSS stylesheet to parse + */ + read: function(text, startLine) { + var rule_start = [], + rule_body_start = [], + rules = [], + in_comment = 0, + root = rule(), + cur_parent = root, + last_rule = null, + stack = [], + ch, ch2; + + stack.last = function() { + return this[this.length - 1]; + }; + + function hasStr(pos, substr) { + return text.substr(pos, substr.length) == substr; + } + + for (var i = 0, il = text.length; i < il; i++) { + ch = text.charAt(i); + ch2 = i < il - 1 ? text.charAt(i + 1) : ''; + + if (!rule_start.length) + rule_start.push(i); + + switch (ch) { + case '@': + if (!in_comment) { + if (hasStr(i, '@import')) { + var m = text.substr(i).match(/^@import\s*url\((['"])?.+?\1?\)\;?/); + if (m) { + cur_parent.addChild(i, i + 7, i + m[0].length); + i += m[0].length; + rule_start.pop(); + } + break; + } + } + case '/': + // xxxpedro allowing comment inside comment + if (!in_comment && ch2 == '*') { // comment start + in_comment++; + } + break; + + case '*': + if (ch2 == '/') { // comment end + in_comment--; + } + break; + + case '{': + if (!in_comment) { + rule_body_start.push(i); + + cur_parent = cur_parent.addChild(rule_start.pop()); + stack.push(cur_parent); + } + break; + + case '}': + // found the end of the rule + if (!in_comment) { + /** @type {rule} */ + var last_rule = stack.pop(); + rule_start.pop(); + last_rule.body_start = rule_body_start.pop(); + last_rule.end = i; + cur_parent = last_rule.parent || root; + } + break; + } + + } + + return saveLineNumbers(text, root, null, startLine); + }, + + normalizeSelector: normalizeSelector, + + /** + * Find matched rule by selector. + * @param {rule} rule_node Parsed rule node + * @param {String} selector CSS selector + * @param {String} source CSS stylesheet source code + * + * @return {rule[]|null} Array of matched rules, sorted by priority (most + * recent on top) + */ + findBySelector: function(rule_node, selector, source) { + var selector = normalizeSelector(selector), + result = []; + + if (rule_node) { + for (var i = 0, il = rule_node.children.length; i < il; i++) { + /** @type {rule} */ + var r = rule_node.children[i]; + if (r.selector == selector) { + result.push(r); + } + } + } + + if (result.length) { + return result; + } else { + return null; + } + } + }; +})(); + + +// ************************************************************************************************ + +FBL.CssParser = CssParser; + +// ************************************************************************************************ +}}); + +/* See license.txt for terms of usage */ + +FBL.ns(function() { with (FBL) { + +// ************************************************************************************************ +// StyleSheet Parser + +var CssAnalyzer = {}; + +// ************************************************************************************************ +// Locals + +var CSSRuleMap = {}; +var ElementCSSRulesMap = {}; + +var internalStyleSheetIndex = -1; + +var reSelectorTag = /(^|\s)(?:\w+)/g; +var reSelectorClass = /\.[\w\d_-]+/g; +var reSelectorId = /#[\w\d_-]+/g; + +var globalCSSRuleIndex; + +var processAllStyleSheetsTimeout = null; + +var externalStyleSheetURLs = []; + +var ElementCache = Firebug.Lite.Cache.Element; +var StyleSheetCache = Firebug.Lite.Cache.StyleSheet; + +//************************************************************************************************ +// CSS Analyzer templates + +CssAnalyzer.externalStyleSheetWarning = domplate(Firebug.Rep, +{ + tag: + DIV({"class": "warning focusRow", style: "font-weight:normal;", role: 'listitem'}, + SPAN("$object|STR"), + A({"href": "$href", target:"_blank"}, "$link|STR") + ) +}); + +// ************************************************************************************************ +// CSS Analyzer methods + +CssAnalyzer.processAllStyleSheets = function(doc, styleSheetIterator) +{ + try + { + processAllStyleSheets(doc, styleSheetIterator); + } + catch(e) + { + // TODO: FBTrace condition + FBTrace.sysout("CssAnalyzer.processAllStyleSheets fails: ", e); + } +}; + +/** + * + * @param element + * @returns {String[]} Array of IDs of CSS Rules + */ +CssAnalyzer.getElementCSSRules = function(element) +{ + try + { + return getElementCSSRules(element); + } + catch(e) + { + // TODO: FBTrace condition + FBTrace.sysout("CssAnalyzer.getElementCSSRules fails: ", e); + } +}; + +CssAnalyzer.getRuleData = function(ruleId) +{ + return CSSRuleMap[ruleId]; +}; + +// TODO: do we need this? +CssAnalyzer.getRuleLine = function() +{ +}; + +CssAnalyzer.hasExternalStyleSheet = function() +{ + return externalStyleSheetURLs.length > 0; +}; + +CssAnalyzer.parseStyleSheet = function(href) +{ + var sourceData = extractSourceData(href); + var parsedObj = CssParser.read(sourceData.source, sourceData.startLine); + var parsedRules = parsedObj.children; + + // See: Issue 4776: [Firebug lite] CSS Media Types + // + // Ignore all special selectors like @media and @page + for(var i=0; i < parsedRules.length; ) + { + if (parsedRules[i].selector.indexOf("@") != -1) + { + parsedRules.splice(i, 1); + } + else + i++; + } + + return parsedRules; +}; + +//************************************************************************************************ +// Internals +//************************************************************************************************ + +// ************************************************************************************************ +// StyleSheet processing + +var processAllStyleSheets = function(doc, styleSheetIterator) +{ + styleSheetIterator = styleSheetIterator || processStyleSheet; + + globalCSSRuleIndex = -1; + + var styleSheets = doc.styleSheets; + var importedStyleSheets = []; + + if (FBTrace.DBG_CSS) + var start = new Date().getTime(); + + for(var i=0, length=styleSheets.length; i<length; i++) + { + try + { + var styleSheet = styleSheets[i]; + + if ("firebugIgnore" in styleSheet) continue; + + // we must read the length to make sure we have permission to read + // the stylesheet's content. If an error occurs here, we cannot + // read the stylesheet due to access restriction policy + var rules = isIE ? styleSheet.rules : styleSheet.cssRules; + rules.length; + } + catch(e) + { + externalStyleSheetURLs.push(styleSheet.href); + styleSheet.restricted = true; + var ssid = StyleSheetCache(styleSheet); + + /// TODO: xxxpedro external css + //loadExternalStylesheet(doc, styleSheetIterator, styleSheet); + } + + // process internal and external styleSheets + styleSheetIterator(doc, styleSheet); + + var importedStyleSheet, importedRules; + + // process imported styleSheets in IE + if (isIE) + { + var imports = styleSheet.imports; + + for(var j=0, importsLength=imports.length; j<importsLength; j++) + { + try + { + importedStyleSheet = imports[j]; + // we must read the length to make sure we have permission + // to read the imported stylesheet's content. + importedRules = importedStyleSheet.rules; + importedRules.length; + } + catch(e) + { + externalStyleSheetURLs.push(styleSheet.href); + importedStyleSheet.restricted = true; + var ssid = StyleSheetCache(importedStyleSheet); + } + + styleSheetIterator(doc, importedStyleSheet); + } + } + // process imported styleSheets in other browsers + else if (rules) + { + for(var j=0, rulesLength=rules.length; j<rulesLength; j++) + { + try + { + var rule = rules[j]; + + importedStyleSheet = rule.styleSheet; + + if (importedStyleSheet) + { + // we must read the length to make sure we have permission + // to read the imported stylesheet's content. + importedRules = importedStyleSheet.cssRules; + importedRules.length; + } + else + break; + } + catch(e) + { + externalStyleSheetURLs.push(styleSheet.href); + importedStyleSheet.restricted = true; + var ssid = StyleSheetCache(importedStyleSheet); + } + + styleSheetIterator(doc, importedStyleSheet); + } + } + }; + + if (FBTrace.DBG_CSS) + { + FBTrace.sysout("FBL.processAllStyleSheets", "all stylesheet rules processed in " + (new Date().getTime() - start) + "ms"); + } +}; + +// ************************************************************************************************ + +var processStyleSheet = function(doc, styleSheet) +{ + if (styleSheet.restricted) + return; + + var rules = isIE ? styleSheet.rules : styleSheet.cssRules; + + var ssid = StyleSheetCache(styleSheet); + + var href = styleSheet.href; + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // CSS Parser + var shouldParseCSS = typeof CssParser != "undefined" && !Firebug.disableResourceFetching; + if (shouldParseCSS) + { + try + { + var parsedRules = CssAnalyzer.parseStyleSheet(href); + } + catch(e) + { + if (FBTrace.DBG_ERRORS) FBTrace.sysout("processStyleSheet FAILS", e.message || e); + shouldParseCSS = false; + } + finally + { + var parsedRulesIndex = 0; + + var dontSupportGroupedRules = isIE && browserVersion < 9; + var group = []; + var groupItem; + } + } + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + for (var i=0, length=rules.length; i<length; i++) + { + // TODO: xxxpedro is there a better way to cache CSS Rules? The problem is that + // we cannot add expando properties in the rule object in IE + var rid = ssid + ":" + i; + var rule = rules[i]; + var selector = rule.selectorText || ""; + var lineNo = null; + + // See: Issue 4776: [Firebug lite] CSS Media Types + // + // Ignore all special selectors like @media and @page + if (!selector || selector.indexOf("@") != -1) + continue; + + if (isIE) + selector = selector.replace(reSelectorTag, function(s){return s.toLowerCase();}); + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // CSS Parser + if (shouldParseCSS) + { + var parsedRule = parsedRules[parsedRulesIndex]; + var parsedSelector = parsedRule.selector; + + if (dontSupportGroupedRules && parsedSelector.indexOf(",") != -1 && group.length == 0) + group = parsedSelector.split(","); + + if (dontSupportGroupedRules && group.length > 0) + { + groupItem = group.shift(); + + if (CssParser.normalizeSelector(selector) == groupItem) + lineNo = parsedRule.line; + + if (group.length == 0) + parsedRulesIndex++; + } + else if (CssParser.normalizeSelector(selector) == parsedRule.selector) + { + lineNo = parsedRule.line; + parsedRulesIndex++; + } + } + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + CSSRuleMap[rid] = + { + styleSheetId: ssid, + styleSheetIndex: i, + order: ++globalCSSRuleIndex, + specificity: + // See: Issue 4777: [Firebug lite] Specificity of CSS Rules + // + // if it is a normal selector then calculate the specificity + selector && selector.indexOf(",") == -1 ? + getCSSRuleSpecificity(selector) : + // See: Issue 3262: [Firebug lite] Specificity of grouped CSS Rules + // + // if it is a grouped selector, do not calculate the specificity + // because the correct value will depend of the matched element. + // The proper specificity value for grouped selectors are calculated + // via getElementCSSRules(element) + 0, + + rule: rule, + lineNo: lineNo, + selector: selector, + cssText: rule.style ? rule.style.cssText : rule.cssText ? rule.cssText : "" + }; + + // TODO: what happens with elements added after this? Need to create a test case. + // Maybe we should place this at getElementCSSRules() but it will make the function + // a lot more expensive. + // + // Maybe add a "refresh" button? + var elements = Firebug.Selector(selector, doc); + + for (var j=0, elementsLength=elements.length; j<elementsLength; j++) + { + var element = elements[j]; + var eid = ElementCache(element); + + if (!ElementCSSRulesMap[eid]) + ElementCSSRulesMap[eid] = []; + + ElementCSSRulesMap[eid].push(rid); + } + + //console.log(selector, elements); + } +}; + +// ************************************************************************************************ +// External StyleSheet Loader + +var loadExternalStylesheet = function(doc, styleSheetIterator, styleSheet) +{ + var url = styleSheet.href; + styleSheet.firebugIgnore = true; + + var source = Firebug.Lite.Proxy.load(url); + + // TODO: check for null and error responses + + // remove comments + //var reMultiComment = /(\/\*([^\*]|\*(?!\/))*\*\/)/g; + //source = source.replace(reMultiComment, ""); + + // convert relative addresses to absolute ones + source = source.replace(/url\(([^\)]+)\)/g, function(a,name){ + + var hasDomain = /\w+:\/\/./.test(name); + + if (!hasDomain) + { + name = name.replace(/^(["'])(.+)\1$/, "$2"); + var first = name.charAt(0); + + // relative path, based on root + if (first == "/") + { + // TODO: xxxpedro move to lib or Firebug.Lite.something + // getURLRoot + var m = /^([^:]+:\/{1,3}[^\/]+)/.exec(url); + + return m ? + "url(" + m[1] + name + ")" : + "url(" + name + ")"; + } + // relative path, based on current location + else + { + // TODO: xxxpedro move to lib or Firebug.Lite.something + // getURLPath + var path = url.replace(/[^\/]+\.[\w\d]+(\?.+|#.+)?$/g, ""); + + path = path + name; + + var reBack = /[^\/]+\/\.\.\//; + while(reBack.test(path)) + { + path = path.replace(reBack, ""); + } + + //console.log("url(" + path + ")"); + + return "url(" + path + ")"; + } + } + + // if it is an absolute path, there is nothing to do + return a; + }); + + var oldStyle = styleSheet.ownerNode; + + if (!oldStyle) return; + + if (!oldStyle.parentNode) return; + + var style = createGlobalElement("style"); + style.setAttribute("charset","utf-8"); + style.setAttribute("type", "text/css"); + style.innerHTML = source; + + //debugger; + oldStyle.parentNode.insertBefore(style, oldStyle.nextSibling); + oldStyle.parentNode.removeChild(oldStyle); + + doc.styleSheets[doc.styleSheets.length-1].externalURL = url; + + console.log(url, "call " + externalStyleSheetURLs.length, source); + + externalStyleSheetURLs.pop(); + + if (processAllStyleSheetsTimeout) + { + clearTimeout(processAllStyleSheetsTimeout); + } + + processAllStyleSheetsTimeout = setTimeout(function(){ + console.log("processing"); + FBL.processAllStyleSheets(doc, styleSheetIterator); + processAllStyleSheetsTimeout = null; + },200); + +}; + +//************************************************************************************************ +// getElementCSSRules + +var getElementCSSRules = function(element) +{ + var eid = ElementCache(element); + var rules = ElementCSSRulesMap[eid]; + + if (!rules) return; + + var arr = [element]; + var Selector = Firebug.Selector; + var ruleId, rule; + + // for the case of grouped selectors, we need to calculate the highest + // specificity within the selectors of the group that matches the element, + // so we can sort the rules properly without over estimating the specificity + // of grouped selectors + for (var i = 0, length = rules.length; i < length; i++) + { + ruleId = rules[i]; + rule = CSSRuleMap[ruleId]; + + // check if it is a grouped selector + if (rule.selector.indexOf(",") != -1) + { + var selectors = rule.selector.split(","); + var maxSpecificity = -1; + var sel, spec, mostSpecificSelector; + + // loop over all selectors in the group + for (var j, len = selectors.length; j < len; j++) + { + sel = selectors[j]; + + // find if the selector matches the element + if (Selector.matches(sel, arr).length == 1) + { + spec = getCSSRuleSpecificity(sel); + + // find the most specific selector that macthes the element + if (spec > maxSpecificity) + { + maxSpecificity = spec; + mostSpecificSelector = sel; + } + } + } + + rule.specificity = maxSpecificity; + } + } + + rules.sort(sortElementRules); + //rules.sort(solveRulesTied); + + return rules; +}; + +// ************************************************************************************************ +// Rule Specificity + +var sortElementRules = function(a, b) +{ + var ruleA = CSSRuleMap[a]; + var ruleB = CSSRuleMap[b]; + + var specificityA = ruleA.specificity; + var specificityB = ruleB.specificity; + + if (specificityA > specificityB) + return 1; + + else if (specificityA < specificityB) + return -1; + + else + return ruleA.order > ruleB.order ? 1 : -1; +}; + +var solveRulesTied = function(a, b) +{ + var ruleA = CSSRuleMap[a]; + var ruleB = CSSRuleMap[b]; + + if (ruleA.specificity == ruleB.specificity) + return ruleA.order > ruleB.order ? 1 : -1; + + return null; +}; + +var getCSSRuleSpecificity = function(selector) +{ + var match = selector.match(reSelectorTag); + var tagCount = match ? match.length : 0; + + match = selector.match(reSelectorClass); + var classCount = match ? match.length : 0; + + match = selector.match(reSelectorId); + var idCount = match ? match.length : 0; + + return tagCount + 10*classCount + 100*idCount; +}; + +// ************************************************************************************************ +// StyleSheet data + +var extractSourceData = function(href) +{ + var sourceData = + { + source: null, + startLine: 0 + }; + + if (href) + { + sourceData.source = Firebug.Lite.Proxy.load(href); + } + else + { + // TODO: create extractInternalSourceData(index) + // TODO: pre process the position of the inline styles so this will happen only once + // in case of having multiple inline styles + var index = 0; + var ssIndex = ++internalStyleSheetIndex; + var reStyleTag = /\<\s*style.*\>/gi; + var reEndStyleTag = /\<\/\s*style.*\>/gi; + + var source = Firebug.Lite.Proxy.load(Env.browser.location.href); + source = source.replace(/\n\r|\r\n/g, "\n"); // normalize line breaks + + var startLine = 0; + + do + { + var matchStyleTag = source.match(reStyleTag); + var i0 = source.indexOf(matchStyleTag[0]) + matchStyleTag[0].length; + + for (var i=0; i < i0; i++) + { + if (source.charAt(i) == "\n") + startLine++; + } + + source = source.substr(i0); + + index++; + } + while (index <= ssIndex); + + var matchEndStyleTag = source.match(reEndStyleTag); + var i1 = source.indexOf(matchEndStyleTag[0]); + + var extractedSource = source.substr(0, i1); + + sourceData.source = extractedSource; + sourceData.startLine = startLine; + } + + return sourceData; +}; + +// ************************************************************************************************ +// Registration + +FBL.CssAnalyzer = CssAnalyzer; + +// ************************************************************************************************ +}}); + + +/* See license.txt for terms of usage */ + +// move to FBL +(function() { + +// ************************************************************************************************ +// XPath + +/** + * Gets an XPath for an element which describes its hierarchical location. + */ +this.getElementXPath = function(element) +{ + try + { + if (element && element.id) + return '//*[@id="' + element.id + '"]'; + else + return this.getElementTreeXPath(element); + } + catch(E) + { + // xxxpedro: trying to detect the mysterious error: + // Security error" code: "1000 + //debugger; + } +}; + +this.getElementTreeXPath = function(element) +{ + var paths = []; + + for (; element && element.nodeType == 1; element = element.parentNode) + { + var index = 0; + var nodeName = element.nodeName; + + for (var sibling = element.previousSibling; sibling; sibling = sibling.previousSibling) + { + if (sibling.nodeType != 1) continue; + + if (sibling.nodeName == nodeName) + ++index; + } + + var tagName = element.nodeName.toLowerCase(); + var pathIndex = (index ? "[" + (index+1) + "]" : ""); + paths.splice(0, 0, tagName + pathIndex); + } + + return paths.length ? "/" + paths.join("/") : null; +}; + +this.getElementsByXPath = function(doc, xpath) +{ + var nodes = []; + + try { + var result = doc.evaluate(xpath, doc, null, XPathResult.ANY_TYPE, null); + for (var item = result.iterateNext(); item; item = result.iterateNext()) + nodes.push(item); + } + catch (exc) + { + // Invalid xpath expressions make their way here sometimes. If that happens, + // we still want to return an empty set without an exception. + } + + return nodes; +}; + +this.getRuleMatchingElements = function(rule, doc) +{ + var css = rule.selectorText; + var xpath = this.cssToXPath(css); + return this.getElementsByXPath(doc, xpath); +}; + + +}).call(FBL); + + + + +FBL.ns(function() { with (FBL) { + +// ************************************************************************************************ +// ************************************************************************************************ +// ************************************************************************************************ +// ************************************************************************************************ +// ************************************************************************************************ + +var toCamelCase = function toCamelCase(s) +{ + return s.replace(reSelectorCase, toCamelCaseReplaceFn); +}; + +var toSelectorCase = function toSelectorCase(s) +{ + return s.replace(reCamelCase, "-$1").toLowerCase(); + +}; + +var reCamelCase = /([A-Z])/g; +var reSelectorCase = /\-(.)/g; +var toCamelCaseReplaceFn = function toCamelCaseReplaceFn(m,g) +{ + return g.toUpperCase(); +}; + +// ************************************************************************************************ + +var ElementCache = Firebug.Lite.Cache.Element; +var StyleSheetCache = Firebug.Lite.Cache.StyleSheet; + +// ************************************************************************************************ +// ************************************************************************************************ +// ************************************************************************************************ +// ************************************************************************************************ +// ************************************************************************************************ +// ************************************************************************************************ + + +// ************************************************************************************************ +// Constants + +//const Cc = Components.classes; +//const Ci = Components.interfaces; +//const nsIDOMCSSStyleRule = Ci.nsIDOMCSSStyleRule; +//const nsIInterfaceRequestor = Ci.nsIInterfaceRequestor; +//const nsISelectionDisplay = Ci.nsISelectionDisplay; +//const nsISelectionController = Ci.nsISelectionController; + +// See: http://mxr.mozilla.org/mozilla1.9.2/source/content/events/public/nsIEventStateManager.h#153 +//const STATE_ACTIVE = 0x01; +//const STATE_FOCUS = 0x02; +//const STATE_HOVER = 0x04; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +Firebug.SourceBoxPanel = Firebug.Panel; + +var reSelectorTag = /(^|\s)(?:\w+)/g; + +var domUtils = null; + +var textContent = isIE ? "innerText" : "textContent"; +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +var CSSDomplateBase = { + isEditable: function(rule) + { + return !rule.isSystemSheet; + }, + isSelectorEditable: function(rule) + { + return rule.isSelectorEditable && this.isEditable(rule); + } +}; + +var CSSPropTag = domplate(CSSDomplateBase, { + tag: DIV({"class": "cssProp focusRow", $disabledStyle: "$prop.disabled", + $editGroup: "$rule|isEditable", + $cssOverridden: "$prop.overridden", role : "option"}, + A({"class": "cssPropDisable"}, " "), + SPAN({"class": "cssPropName", $editable: "$rule|isEditable"}, "$prop.name"), + SPAN({"class": "cssColon"}, ":"), + SPAN({"class": "cssPropValue", $editable: "$rule|isEditable"}, "$prop.value$prop.important"), + SPAN({"class": "cssSemi"}, ";") + ) +}); + +var CSSRuleTag = + TAG("$rule.tag", {rule: "$rule"}); + +var CSSImportRuleTag = domplate({ + tag: DIV({"class": "cssRule insertInto focusRow importRule", _repObject: "$rule.rule"}, + "@import "", + A({"class": "objectLink", _repObject: "$rule.rule.styleSheet"}, "$rule.rule.href"), + "";" + ) +}); + +var CSSStyleRuleTag = domplate(CSSDomplateBase, { + tag: DIV({"class": "cssRule insertInto", + $cssEditableRule: "$rule|isEditable", + $editGroup: "$rule|isSelectorEditable", + _repObject: "$rule.rule", + "ruleId": "$rule.id", role : 'presentation'}, + DIV({"class": "cssHead focusRow", role : 'listitem'}, + SPAN({"class": "cssSelector", $editable: "$rule|isSelectorEditable"}, "$rule.selector"), " {" + ), + DIV({role : 'group'}, + DIV({"class": "cssPropertyListBox", role : 'listbox'}, + FOR("prop", "$rule.props", + TAG(CSSPropTag.tag, {rule: "$rule", prop: "$prop"}) + ) + ) + ), + DIV({"class": "editable insertBefore", role:"presentation"}, "}") + ) +}); + +var reSplitCSS = /(url\("?[^"\)]+?"?\))|(rgb\(.*?\))|(#[\dA-Fa-f]+)|(-?\d+(\.\d+)?(%|[a-z]{1,2})?)|([^,\s]+)|"(.*?)"/; + +var reURL = /url\("?([^"\)]+)?"?\)/; + +var reRepeat = /no-repeat|repeat-x|repeat-y|repeat/; + +//const sothinkInstalled = !!$("swfcatcherKey_sidebar"); +var sothinkInstalled = false; +var styleGroups = +{ + text: [ + "font-family", + "font-size", + "font-weight", + "font-style", + "color", + "text-transform", + "text-decoration", + "letter-spacing", + "word-spacing", + "line-height", + "text-align", + "vertical-align", + "direction", + "column-count", + "column-gap", + "column-width" + ], + + background: [ + "background-color", + "background-image", + "background-repeat", + "background-position", + "background-attachment", + "opacity" + ], + + box: [ + "width", + "height", + "top", + "right", + "bottom", + "left", + "margin-top", + "margin-right", + "margin-bottom", + "margin-left", + "padding-top", + "padding-right", + "padding-bottom", + "padding-left", + "border-top-width", + "border-right-width", + "border-bottom-width", + "border-left-width", + "border-top-color", + "border-right-color", + "border-bottom-color", + "border-left-color", + "border-top-style", + "border-right-style", + "border-bottom-style", + "border-left-style", + "-moz-border-top-radius", + "-moz-border-right-radius", + "-moz-border-bottom-radius", + "-moz-border-left-radius", + "outline-top-width", + "outline-right-width", + "outline-bottom-width", + "outline-left-width", + "outline-top-color", + "outline-right-color", + "outline-bottom-color", + "outline-left-color", + "outline-top-style", + "outline-right-style", + "outline-bottom-style", + "outline-left-style" + ], + + layout: [ + "position", + "display", + "visibility", + "z-index", + "overflow-x", // http://www.w3.org/TR/2002/WD-css3-box-20021024/#overflow + "overflow-y", + "overflow-clip", + "white-space", + "clip", + "float", + "clear", + "-moz-box-sizing" + ], + + other: [ + "cursor", + "list-style-image", + "list-style-position", + "list-style-type", + "marker-offset", + "user-focus", + "user-select", + "user-modify", + "user-input" + ] +}; + +var styleGroupTitles = +{ + text: "Text", + background: "Background", + box: "Box Model", + layout: "Layout", + other: "Other" +}; + +Firebug.CSSModule = extend(Firebug.Module, +{ + freeEdit: function(styleSheet, value) + { + if (!styleSheet.editStyleSheet) + { + var ownerNode = getStyleSheetOwnerNode(styleSheet); + styleSheet.disabled = true; + + var url = CCSV("@mozilla.org/network/standard-url;1", Components.interfaces.nsIURL); + url.spec = styleSheet.href; + + var editStyleSheet = ownerNode.ownerDocument.createElementNS( + "http://www.w3.org/1999/xhtml", + "style"); + unwrapObject(editStyleSheet).firebugIgnore = true; + editStyleSheet.setAttribute("type", "text/css"); + editStyleSheet.setAttributeNS( + "http://www.w3.org/XML/1998/namespace", + "base", + url.directory); + if (ownerNode.hasAttribute("media")) + { + editStyleSheet.setAttribute("media", ownerNode.getAttribute("media")); + } + + // Insert the edited stylesheet directly after the old one to ensure the styles + // cascade properly. + ownerNode.parentNode.insertBefore(editStyleSheet, ownerNode.nextSibling); + + styleSheet.editStyleSheet = editStyleSheet; + } + + styleSheet.editStyleSheet.innerHTML = value; + if (FBTrace.DBG_CSS) + FBTrace.sysout("css.saveEdit styleSheet.href:"+styleSheet.href+" got innerHTML:"+value+"\n"); + + dispatch(this.fbListeners, "onCSSFreeEdit", [styleSheet, value]); + }, + + insertRule: function(styleSheet, cssText, ruleIndex) + { + if (FBTrace.DBG_CSS) FBTrace.sysout("Insert: " + ruleIndex + " " + cssText); + var insertIndex = styleSheet.insertRule(cssText, ruleIndex); + + dispatch(this.fbListeners, "onCSSInsertRule", [styleSheet, cssText, ruleIndex]); + + return insertIndex; + }, + + deleteRule: function(styleSheet, ruleIndex) + { + if (FBTrace.DBG_CSS) FBTrace.sysout("deleteRule: " + ruleIndex + " " + styleSheet.cssRules.length, styleSheet.cssRules); + dispatch(this.fbListeners, "onCSSDeleteRule", [styleSheet, ruleIndex]); + + styleSheet.deleteRule(ruleIndex); + }, + + setProperty: function(rule, propName, propValue, propPriority) + { + var style = rule.style || rule; + + // Record the original CSS text for the inline case so we can reconstruct at a later + // point for diffing purposes + var baseText = style.cssText; + + // good browsers + if (style.getPropertyValue) + { + var prevValue = style.getPropertyValue(propName); + var prevPriority = style.getPropertyPriority(propName); + + // XXXjoe Gecko bug workaround: Just changing priority doesn't have any effect + // unless we remove the property first + style.removeProperty(propName); + + style.setProperty(propName, propValue, propPriority); + } + // sad browsers + else + { + // TODO: xxxpedro parse CSS rule to find property priority in IE? + //console.log(propName, propValue); + style[toCamelCase(propName)] = propValue; + } + + if (propName) { + dispatch(this.fbListeners, "onCSSSetProperty", [style, propName, propValue, propPriority, prevValue, prevPriority, rule, baseText]); + } + }, + + removeProperty: function(rule, propName, parent) + { + var style = rule.style || rule; + + // Record the original CSS text for the inline case so we can reconstruct at a later + // point for diffing purposes + var baseText = style.cssText; + + if (style.getPropertyValue) + { + + var prevValue = style.getPropertyValue(propName); + var prevPriority = style.getPropertyPriority(propName); + + style.removeProperty(propName); + } + else + { + style[toCamelCase(propName)] = ""; + } + + if (propName) { + dispatch(this.fbListeners, "onCSSRemoveProperty", [style, propName, prevValue, prevPriority, rule, baseText]); + } + }/*, + + cleanupSheets: function(doc, context) + { + // Due to the manner in which the layout engine handles multiple + // references to the same sheet we need to kick it a little bit. + // The injecting a simple stylesheet then removing it will force + // Firefox to regenerate it's CSS hierarchy. + // + // WARN: This behavior was determined anecdotally. + // See http://code.google.com/p/fbug/issues/detail?id=2440 + var style = doc.createElementNS("http://www.w3.org/1999/xhtml", "style"); + style.setAttribute("charset","utf-8"); + unwrapObject(style).firebugIgnore = true; + style.setAttribute("type", "text/css"); + style.innerHTML = "#fbIgnoreStyleDO_NOT_USE {}"; + addStyleSheet(doc, style); + style.parentNode.removeChild(style); + + // https://bugzilla.mozilla.org/show_bug.cgi?id=500365 + // This voodoo touches each style sheet to force some Firefox internal change to allow edits. + var styleSheets = getAllStyleSheets(context); + for(var i = 0; i < styleSheets.length; i++) + { + try + { + var rules = styleSheets[i].cssRules; + if (rules.length > 0) + var touch = rules[0]; + if (FBTrace.DBG_CSS && touch) + FBTrace.sysout("css.show() touch "+typeof(touch)+" in "+(styleSheets[i].href?styleSheets[i].href:context.getName())); + } + catch(e) + { + if (FBTrace.DBG_ERRORS) + FBTrace.sysout("css.show: sheet.cssRules FAILS for "+(styleSheets[i]?styleSheets[i].href:"null sheet")+e, e); + } + } + }, + cleanupSheetHandler: function(event, context) + { + var target = event.target || event.srcElement, + tagName = (target.tagName || "").toLowerCase(); + if (tagName == "link") + { + this.cleanupSheets(target.ownerDocument, context); + } + }, + watchWindow: function(context, win) + { + var cleanupSheets = bind(this.cleanupSheets, this), + cleanupSheetHandler = bind(this.cleanupSheetHandler, this, context), + doc = win.document; + + //doc.addEventListener("DOMAttrModified", cleanupSheetHandler, false); + //doc.addEventListener("DOMNodeInserted", cleanupSheetHandler, false); + }, + loadedContext: function(context) + { + var self = this; + iterateWindows(context.browser.contentWindow, function(subwin) + { + self.cleanupSheets(subwin.document, context); + }); + } + /**/ +}); + +// ************************************************************************************************ + +Firebug.CSSStyleSheetPanel = function() {}; + +Firebug.CSSStyleSheetPanel.prototype = extend(Firebug.SourceBoxPanel, +{ + template: domplate( + { + tag: + DIV({"class": "cssSheet insertInto a11yCSSView"}, + FOR("rule", "$rules", + CSSRuleTag + ), + DIV({"class": "cssSheet editable insertBefore"}, "") + ) + }), + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + refresh: function() + { + if (this.location) + this.updateLocation(this.location); + else if (this.selection) + this.updateSelection(this.selection); + }, + + toggleEditing: function() + { + if (!this.stylesheetEditor) + this.stylesheetEditor = new StyleSheetEditor(this.document); + + if (this.editing) + Firebug.Editor.stopEditing(); + else + { + if (!this.location) + return; + + var styleSheet = this.location.editStyleSheet + ? this.location.editStyleSheet.sheet + : this.location; + + var css = getStyleSheetCSS(styleSheet, this.context); + //var topmost = getTopmostRuleLine(this.panelNode); + + this.stylesheetEditor.styleSheet = this.location; + Firebug.Editor.startEditing(this.panelNode, css, this.stylesheetEditor); + //this.stylesheetEditor.scrollToLine(topmost.line, topmost.offset); + } + }, + + getStylesheetURL: function(rule) + { + if (this.location.href) + return this.location.href; + else + return this.context.window.location.href; + }, + + getRuleByLine: function(styleSheet, line) + { + if (!domUtils) + return null; + + var cssRules = styleSheet.cssRules; + for (var i = 0; i < cssRules.length; ++i) + { + var rule = cssRules[i]; + if (rule instanceof CSSStyleRule) + { + var ruleLine = domUtils.getRuleLine(rule); + if (ruleLine >= line) + return rule; + } + } + }, + + highlightRule: function(rule) + { + var ruleElement = Firebug.getElementByRepObject(this.panelNode.firstChild, rule); + if (ruleElement) + { + scrollIntoCenterView(ruleElement, this.panelNode); + setClassTimed(ruleElement, "jumpHighlight", this.context); + } + }, + + getStyleSheetRules: function(context, styleSheet) + { + var isSystemSheet = isSystemStyleSheet(styleSheet); + + function appendRules(cssRules) + { + for (var i = 0; i < cssRules.length; ++i) + { + var rule = cssRules[i]; + + // TODO: xxxpedro opera instanceof stylesheet remove the following comments when + // the issue with opera and style sheet Classes has been solved. + + //if (rule instanceof CSSStyleRule) + if (instanceOf(rule, "CSSStyleRule")) + { + var props = this.getRuleProperties(context, rule); + //var line = domUtils.getRuleLine(rule); + var line = null; + + var selector = rule.selectorText; + + if (isIE) + { + selector = selector.replace(reSelectorTag, + function(s){return s.toLowerCase();}); + } + + var ruleId = rule.selectorText+"/"+line; + rules.push({tag: CSSStyleRuleTag.tag, rule: rule, id: ruleId, + selector: selector, props: props, + isSystemSheet: isSystemSheet, + isSelectorEditable: true}); + } + //else if (rule instanceof CSSImportRule) + else if (instanceOf(rule, "CSSImportRule")) + rules.push({tag: CSSImportRuleTag.tag, rule: rule}); + //else if (rule instanceof CSSMediaRule) + else if (instanceOf(rule, "CSSMediaRule")) + appendRules.apply(this, [rule.cssRules]); + else + { + if (FBTrace.DBG_ERRORS || FBTrace.DBG_CSS) + FBTrace.sysout("css getStyleSheetRules failed to classify a rule ", rule); + } + } + } + + var rules = []; + appendRules.apply(this, [styleSheet.cssRules || styleSheet.rules]); + return rules; + }, + + parseCSSProps: function(style, inheritMode) + { + var props = []; + + if (Firebug.expandShorthandProps) + { + var count = style.length-1, + index = style.length; + while (index--) + { + var propName = style.item(count - index); + this.addProperty(propName, style.getPropertyValue(propName), !!style.getPropertyPriority(propName), false, inheritMode, props); + } + } + else + { + var lines = style.cssText.match(/(?:[^;\(]*(?:\([^\)]*?\))?[^;\(]*)*;?/g); + var propRE = /\s*([^:\s]*)\s*:\s*(.*?)\s*(! important)?;?$/; + var line,i=0; + // TODO: xxxpedro port to firebug: variable leaked into global namespace + var m; + + while(line=lines[i++]){ + m = propRE.exec(line); + if(!m) + continue; + //var name = m[1], value = m[2], important = !!m[3]; + if (m[2]) + this.addProperty(m[1], m[2], !!m[3], false, inheritMode, props); + }; + } + + return props; + }, + + getRuleProperties: function(context, rule, inheritMode) + { + var props = this.parseCSSProps(rule.style, inheritMode); + + // TODO: xxxpedro port to firebug: variable leaked into global namespace + //var line = domUtils.getRuleLine(rule); + var line; + var ruleId = rule.selectorText+"/"+line; + this.addOldProperties(context, ruleId, inheritMode, props); + sortProperties(props); + + return props; + }, + + addOldProperties: function(context, ruleId, inheritMode, props) + { + if (context.selectorMap && context.selectorMap.hasOwnProperty(ruleId) ) + { + var moreProps = context.selectorMap[ruleId]; + for (var i = 0; i < moreProps.length; ++i) + { + var prop = moreProps[i]; + this.addProperty(prop.name, prop.value, prop.important, true, inheritMode, props); + } + } + }, + + addProperty: function(name, value, important, disabled, inheritMode, props) + { + name = name.toLowerCase(); + + if (inheritMode && !inheritedStyleNames[name]) + return; + + name = this.translateName(name, value); + if (name) + { + value = stripUnits(rgbToHex(value)); + important = important ? " !important" : ""; + + var prop = {name: name, value: value, important: important, disabled: disabled}; + props.push(prop); + } + }, + + translateName: function(name, value) + { + // Don't show these proprietary Mozilla properties + if ((value == "-moz-initial" + && (name == "-moz-background-clip" || name == "-moz-background-origin" + || name == "-moz-background-inline-policy")) + || (value == "physical" + && (name == "margin-left-ltr-source" || name == "margin-left-rtl-source" + || name == "margin-right-ltr-source" || name == "margin-right-rtl-source")) + || (value == "physical" + && (name == "padding-left-ltr-source" || name == "padding-left-rtl-source" + || name == "padding-right-ltr-source" || name == "padding-right-rtl-source"))) + return null; + + // Translate these back to the form the user probably expects + if (name == "margin-left-value") + return "margin-left"; + else if (name == "margin-right-value") + return "margin-right"; + else if (name == "margin-top-value") + return "margin-top"; + else if (name == "margin-bottom-value") + return "margin-bottom"; + else if (name == "padding-left-value") + return "padding-left"; + else if (name == "padding-right-value") + return "padding-right"; + else if (name == "padding-top-value") + return "padding-top"; + else if (name == "padding-bottom-value") + return "padding-bottom"; + // XXXjoe What about border! + else + return name; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + editElementStyle: function() + { + ///var rulesBox = this.panelNode.getElementsByClassName("cssElementRuleContainer")[0]; + var rulesBox = $$(".cssElementRuleContainer", this.panelNode)[0]; + var styleRuleBox = rulesBox && Firebug.getElementByRepObject(rulesBox, this.selection); + if (!styleRuleBox) + { + var rule = {rule: this.selection, inherited: false, selector: "element.style", props: []}; + if (!rulesBox) + { + // The element did not have any displayed styles. We need to create the whole tree and remove + // the no styles message + styleRuleBox = this.template.cascadedTag.replace({ + rules: [rule], inherited: [], inheritLabel: "Inherited from" // $STR("InheritedFrom") + }, this.panelNode); + + ///styleRuleBox = styleRuleBox.getElementsByClassName("cssElementRuleContainer")[0]; + styleRuleBox = $$(".cssElementRuleContainer", styleRuleBox)[0]; + } + else + styleRuleBox = this.template.ruleTag.insertBefore({rule: rule}, rulesBox); + + ///styleRuleBox = styleRuleBox.getElementsByClassName("insertInto")[0]; + styleRuleBox = $$(".insertInto", styleRuleBox)[0]; + } + + Firebug.Editor.insertRowForObject(styleRuleBox); + }, + + insertPropertyRow: function(row) + { + Firebug.Editor.insertRowForObject(row); + }, + + insertRule: function(row) + { + var location = getAncestorByClass(row, "cssRule"); + if (!location) + { + location = getChildByClass(this.panelNode, "cssSheet"); + Firebug.Editor.insertRowForObject(location); + } + else + { + Firebug.Editor.insertRow(location, "before"); + } + }, + + editPropertyRow: function(row) + { + var propValueBox = getChildByClass(row, "cssPropValue"); + Firebug.Editor.startEditing(propValueBox); + }, + + deletePropertyRow: function(row) + { + var rule = Firebug.getRepObject(row); + var propName = getChildByClass(row, "cssPropName")[textContent]; + Firebug.CSSModule.removeProperty(rule, propName); + + // Remove the property from the selector map, if it was disabled + var ruleId = Firebug.getRepNode(row).getAttribute("ruleId"); + if ( this.context.selectorMap && this.context.selectorMap.hasOwnProperty(ruleId) ) + { + var map = this.context.selectorMap[ruleId]; + for (var i = 0; i < map.length; ++i) + { + if (map[i].name == propName) + { + map.splice(i, 1); + break; + } + } + } + if (this.name == "stylesheet") + dispatch([Firebug.A11yModel], 'onInlineEditorClose', [this, row.firstChild, true]); + row.parentNode.removeChild(row); + + this.markChange(this.name == "stylesheet"); + }, + + disablePropertyRow: function(row) + { + toggleClass(row, "disabledStyle"); + + var rule = Firebug.getRepObject(row); + var propName = getChildByClass(row, "cssPropName")[textContent]; + + if (!this.context.selectorMap) + this.context.selectorMap = {}; + + // XXXjoe Generate unique key for elements too + var ruleId = Firebug.getRepNode(row).getAttribute("ruleId"); + if (!(this.context.selectorMap.hasOwnProperty(ruleId))) + this.context.selectorMap[ruleId] = []; + + var map = this.context.selectorMap[ruleId]; + var propValue = getChildByClass(row, "cssPropValue")[textContent]; + var parsedValue = parsePriority(propValue); + if (hasClass(row, "disabledStyle")) + { + Firebug.CSSModule.removeProperty(rule, propName); + + map.push({"name": propName, "value": parsedValue.value, + "important": parsedValue.priority}); + } + else + { + Firebug.CSSModule.setProperty(rule, propName, parsedValue.value, parsedValue.priority); + + var index = findPropByName(map, propName); + map.splice(index, 1); + } + + this.markChange(this.name == "stylesheet"); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + onMouseDown: function(event) + { + //console.log("onMouseDown", event.target || event.srcElement, event); + + // xxxpedro adjusting coordinates because the panel isn't a window yet + var offset = event.clientX - this.panelNode.parentNode.offsetLeft; + + // XXjoe Hack to only allow clicking on the checkbox + if (!isLeftClick(event) || offset > 20) + return; + + var target = event.target || event.srcElement; + if (hasClass(target, "textEditor")) + return; + + var row = getAncestorByClass(target, "cssProp"); + if (row && hasClass(row, "editGroup")) + { + this.disablePropertyRow(row); + cancelEvent(event); + } + }, + + onDoubleClick: function(event) + { + //console.log("onDoubleClick", event.target || event.srcElement, event); + + // xxxpedro adjusting coordinates because the panel isn't a window yet + var offset = event.clientX - this.panelNode.parentNode.offsetLeft; + + if (!isLeftClick(event) || offset <= 20) + return; + + var target = event.target || event.srcElement; + + //console.log("ok", target, hasClass(target, "textEditorInner"), !isLeftClick(event), offset <= 20); + + // if the inline editor was clicked, don't insert a new rule + if (hasClass(target, "textEditorInner")) + return; + + var row = getAncestorByClass(target, "cssRule"); + if (row && !getAncestorByClass(target, "cssPropName") + && !getAncestorByClass(target, "cssPropValue")) + { + this.insertPropertyRow(row); + cancelEvent(event); + } + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // extends Panel + + name: "stylesheet", + title: "CSS", + parentPanel: null, + searchable: true, + dependents: ["css", "stylesheet", "dom", "domSide", "layout"], + + options: + { + hasToolButtons: true + }, + + create: function() + { + Firebug.Panel.create.apply(this, arguments); + + this.onMouseDown = bind(this.onMouseDown, this); + this.onDoubleClick = bind(this.onDoubleClick, this); + + if (this.name == "stylesheet") + { + this.onChangeSelect = bind(this.onChangeSelect, this); + + var doc = Firebug.browser.document; + var selectNode = this.selectNode = createElement("select"); + + CssAnalyzer.processAllStyleSheets(doc, function(doc, styleSheet) + { + var key = StyleSheetCache.key(styleSheet); + var fileName = getFileName(styleSheet.href) || getFileName(doc.location.href); + var option = createElement("option", {value: key}); + + option.appendChild(Firebug.chrome.document.createTextNode(fileName)); + selectNode.appendChild(option); + }); + + this.toolButtonsNode.appendChild(selectNode); + } + /**/ + }, + + onChangeSelect: function(event) + { + event = event || window.event; + var target = event.srcElement || event.currentTarget; + var key = target.value; + var styleSheet = StyleSheetCache.get(key); + + this.updateLocation(styleSheet); + }, + + initialize: function() + { + Firebug.Panel.initialize.apply(this, arguments); + + //if (!domUtils) + //{ + // try { + // domUtils = CCSV("@mozilla.org/inspector/dom-utils;1", "inIDOMUtils"); + // } catch (exc) { + // if (FBTrace.DBG_ERRORS) + // FBTrace.sysout("@mozilla.org/inspector/dom-utils;1 FAILED to load: "+exc, exc); + // } + //} + + //TODO: xxxpedro + this.context = Firebug.chrome; // TODO: xxxpedro css2 + this.document = Firebug.chrome.document; // TODO: xxxpedro css2 + + this.initializeNode(); + + if (this.name == "stylesheet") + { + var styleSheets = Firebug.browser.document.styleSheets; + + if (styleSheets.length > 0) + { + addEvent(this.selectNode, "change", this.onChangeSelect); + + this.updateLocation(styleSheets[0]); + } + } + + //Firebug.SourceBoxPanel.initialize.apply(this, arguments); + }, + + shutdown: function() + { + // must destroy the editor when we leave the panel to avoid problems (Issue 2981) + Firebug.Editor.stopEditing(); + + if (this.name == "stylesheet") + { + removeEvent(this.selectNode, "change", this.onChangeSelect); + } + + this.destroyNode(); + + Firebug.Panel.shutdown.apply(this, arguments); + }, + + destroy: function(state) + { + //state.scrollTop = this.panelNode.scrollTop ? this.panelNode.scrollTop : this.lastScrollTop; + + //persistObjects(this, state); + + // xxxpedro we are stopping the editor in the shutdown method already + //Firebug.Editor.stopEditing(); + Firebug.Panel.destroy.apply(this, arguments); + }, + + initializeNode: function(oldPanelNode) + { + addEvent(this.panelNode, "mousedown", this.onMouseDown); + addEvent(this.panelNode, "dblclick", this.onDoubleClick); + //Firebug.SourceBoxPanel.initializeNode.apply(this, arguments); + //dispatch([Firebug.A11yModel], 'onInitializeNode', [this, 'css']); + }, + + destroyNode: function() + { + removeEvent(this.panelNode, "mousedown", this.onMouseDown); + removeEvent(this.panelNode, "dblclick", this.onDoubleClick); + //Firebug.SourceBoxPanel.destroyNode.apply(this, arguments); + //dispatch([Firebug.A11yModel], 'onDestroyNode', [this, 'css']); + }, + + ishow: function(state) + { + Firebug.Inspector.stopInspecting(true); + + this.showToolbarButtons("fbCSSButtons", true); + + if (this.context.loaded && !this.location) // wait for loadedContext to restore the panel + { + restoreObjects(this, state); + + if (!this.location) + this.location = this.getDefaultLocation(); + + if (state && state.scrollTop) + this.panelNode.scrollTop = state.scrollTop; + } + }, + + ihide: function() + { + this.showToolbarButtons("fbCSSButtons", false); + + this.lastScrollTop = this.panelNode.scrollTop; + }, + + supportsObject: function(object) + { + if (object instanceof CSSStyleSheet) + return 1; + else if (object instanceof CSSStyleRule) + return 2; + else if (object instanceof CSSStyleDeclaration) + return 2; + else if (object instanceof SourceLink && object.type == "css" && reCSS.test(object.href)) + return 2; + else + return 0; + }, + + updateLocation: function(styleSheet) + { + if (!styleSheet) + return; + if (styleSheet.editStyleSheet) + styleSheet = styleSheet.editStyleSheet.sheet; + + // if it is a restricted stylesheet, show the warning message and abort the update process + if (styleSheet.restricted) + { + FirebugReps.Warning.tag.replace({object: "AccessRestricted"}, this.panelNode); + + // TODO: xxxpedro remove when there the external resource problem is fixed + CssAnalyzer.externalStyleSheetWarning.tag.append({ + object: "The stylesheet could not be loaded due to access restrictions. ", + link: "more...", + href: "http://getfirebug.com/wiki/index.php/Firebug_Lite_FAQ#I_keep_seeing_.22Access_to_restricted_URI_denied.22" + }, this.panelNode); + + return; + } + + var rules = this.getStyleSheetRules(this.context, styleSheet); + + var result; + if (rules.length) + // FIXME xxxpedro chromenew this is making iPad's Safari to crash + result = this.template.tag.replace({rules: rules}, this.panelNode); + else + result = FirebugReps.Warning.tag.replace({object: "EmptyStyleSheet"}, this.panelNode); + + // TODO: xxxpedro need to fix showToolbarButtons function + //this.showToolbarButtons("fbCSSButtons", !isSystemStyleSheet(this.location)); + + //dispatch([Firebug.A11yModel], 'onCSSRulesAdded', [this, this.panelNode]); + }, + + updateSelection: function(object) + { + this.selection = null; + + if (object instanceof CSSStyleDeclaration) { + object = object.parentRule; + } + + if (object instanceof CSSStyleRule) + { + this.navigate(object.parentStyleSheet); + this.highlightRule(object); + } + else if (object instanceof CSSStyleSheet) + { + this.navigate(object); + } + else if (object instanceof SourceLink) + { + try + { + var sourceLink = object; + + var sourceFile = getSourceFileByHref(sourceLink.href, this.context); + if (sourceFile) + { + clearNode(this.panelNode); // replace rendered stylesheets + this.showSourceFile(sourceFile); + + var lineNo = object.line; + if (lineNo) + this.scrollToLine(lineNo, this.jumpHighlightFactory(lineNo, this.context)); + } + else // XXXjjb we should not be taking this path + { + var stylesheet = getStyleSheetByHref(sourceLink.href, this.context); + if (stylesheet) + this.navigate(stylesheet); + else + { + if (FBTrace.DBG_CSS) + FBTrace.sysout("css.updateSelection no sourceFile for "+sourceLink.href, sourceLink); + } + } + } + catch(exc) { + if (FBTrace.DBG_CSS) + FBTrace.sysout("css.upDateSelection FAILS "+exc, exc); + } + } + }, + + updateOption: function(name, value) + { + if (name == "expandShorthandProps") + this.refresh(); + }, + + getLocationList: function() + { + var styleSheets = getAllStyleSheets(this.context); + return styleSheets; + }, + + getOptionsMenuItems: function() + { + return [ + {label: "Expand Shorthand Properties", type: "checkbox", checked: Firebug.expandShorthandProps, + command: bindFixed(Firebug.togglePref, Firebug, "expandShorthandProps") }, + "-", + {label: "Refresh", command: bind(this.refresh, this) } + ]; + }, + + getContextMenuItems: function(style, target) + { + var items = []; + + if (this.infoTipType == "color") + { + items.push( + {label: "CopyColor", + command: bindFixed(copyToClipboard, FBL, this.infoTipObject) } + ); + } + else if (this.infoTipType == "image") + { + items.push( + {label: "CopyImageLocation", + command: bindFixed(copyToClipboard, FBL, this.infoTipObject) }, + {label: "OpenImageInNewTab", + command: bindFixed(openNewTab, FBL, this.infoTipObject) } + ); + } + + ///if (this.selection instanceof Element) + if (isElement(this.selection)) + { + items.push( + //"-", + {label: "EditStyle", + command: bindFixed(this.editElementStyle, this) } + ); + } + else if (!isSystemStyleSheet(this.selection)) + { + items.push( + //"-", + {label: "NewRule", + command: bindFixed(this.insertRule, this, target) } + ); + } + + var cssRule = getAncestorByClass(target, "cssRule"); + if (cssRule && hasClass(cssRule, "cssEditableRule")) + { + items.push( + "-", + {label: "NewProp", + command: bindFixed(this.insertPropertyRow, this, target) } + ); + + var propRow = getAncestorByClass(target, "cssProp"); + if (propRow) + { + var propName = getChildByClass(propRow, "cssPropName")[textContent]; + var isDisabled = hasClass(propRow, "disabledStyle"); + + items.push( + {label: $STRF("EditProp", [propName]), nol10n: true, + command: bindFixed(this.editPropertyRow, this, propRow) }, + {label: $STRF("DeleteProp", [propName]), nol10n: true, + command: bindFixed(this.deletePropertyRow, this, propRow) }, + {label: $STRF("DisableProp", [propName]), nol10n: true, + type: "checkbox", checked: isDisabled, + command: bindFixed(this.disablePropertyRow, this, propRow) } + ); + } + } + + items.push( + "-", + {label: "Refresh", command: bind(this.refresh, this) } + ); + + return items; + }, + + browseObject: function(object) + { + if (this.infoTipType == "image") + { + openNewTab(this.infoTipObject); + return true; + } + }, + + showInfoTip: function(infoTip, target, x, y) + { + var propValue = getAncestorByClass(target, "cssPropValue"); + if (propValue) + { + var offset = getClientOffset(propValue); + var offsetX = x-offset.x; + + var text = propValue[textContent]; + var charWidth = propValue.offsetWidth/text.length; + var charOffset = Math.floor(offsetX/charWidth); + + var cssValue = parseCSSValue(text, charOffset); + if (cssValue) + { + if (cssValue.value == this.infoTipValue) + return true; + + this.infoTipValue = cssValue.value; + + if (cssValue.type == "rgb" || (!cssValue.type && isColorKeyword(cssValue.value))) + { + this.infoTipType = "color"; + this.infoTipObject = cssValue.value; + + return Firebug.InfoTip.populateColorInfoTip(infoTip, cssValue.value); + } + else if (cssValue.type == "url") + { + ///var propNameNode = target.parentNode.getElementsByClassName("cssPropName").item(0); + var propNameNode = getElementByClass(target.parentNode, "cssPropName"); + if (propNameNode && isImageRule(propNameNode[textContent])) + { + var rule = Firebug.getRepObject(target); + var baseURL = this.getStylesheetURL(rule); + var relURL = parseURLValue(cssValue.value); + var absURL = isDataURL(relURL) ? relURL:absoluteURL(relURL, baseURL); + var repeat = parseRepeatValue(text); + + this.infoTipType = "image"; + this.infoTipObject = absURL; + + return Firebug.InfoTip.populateImageInfoTip(infoTip, absURL, repeat); + } + } + } + } + + delete this.infoTipType; + delete this.infoTipValue; + delete this.infoTipObject; + }, + + getEditor: function(target, value) + { + if (target == this.panelNode + || hasClass(target, "cssSelector") || hasClass(target, "cssRule") + || hasClass(target, "cssSheet")) + { + if (!this.ruleEditor) + this.ruleEditor = new CSSRuleEditor(this.document); + + return this.ruleEditor; + } + else + { + if (!this.editor) + this.editor = new CSSEditor(this.document); + + return this.editor; + } + }, + + getDefaultLocation: function() + { + try + { + var styleSheets = this.context.window.document.styleSheets; + if (styleSheets.length) + { + var sheet = styleSheets[0]; + return (Firebug.filterSystemURLs && isSystemURL(getURLForStyleSheet(sheet))) ? null : sheet; + } + } + catch (exc) + { + if (FBTrace.DBG_LOCATIONS) + FBTrace.sysout("css.getDefaultLocation FAILS "+exc, exc); + } + }, + + getObjectDescription: function(styleSheet) + { + var url = getURLForStyleSheet(styleSheet); + var instance = getInstanceForStyleSheet(styleSheet); + + var baseDescription = splitURLBase(url); + if (instance) { + baseDescription.name = baseDescription.name + " #" + (instance + 1); + } + return baseDescription; + }, + + search: function(text, reverse) + { + var curDoc = this.searchCurrentDoc(!Firebug.searchGlobal, text, reverse); + if (!curDoc && Firebug.searchGlobal) + { + return this.searchOtherDocs(text, reverse); + } + return curDoc; + }, + + searchOtherDocs: function(text, reverse) + { + var scanRE = Firebug.Search.getTestingRegex(text); + function scanDoc(styleSheet) { + // we don't care about reverse here as we are just looking for existence, + // if we do have a result we will handle the reverse logic on display + for (var i = 0; i < styleSheet.cssRules.length; i++) + { + if (scanRE.test(styleSheet.cssRules[i].cssText)) + { + return true; + } + } + } + + if (this.navigateToNextDocument(scanDoc, reverse)) + { + return this.searchCurrentDoc(true, text, reverse); + } + }, + + searchCurrentDoc: function(wrapSearch, text, reverse) + { + if (!text) + { + delete this.currentSearch; + return false; + } + + var row; + if (this.currentSearch && text == this.currentSearch.text) + { + row = this.currentSearch.findNext(wrapSearch, false, reverse, Firebug.Search.isCaseSensitive(text)); + } + else + { + if (this.editing) + { + this.currentSearch = new TextSearch(this.stylesheetEditor.box); + row = this.currentSearch.find(text, reverse, Firebug.Search.isCaseSensitive(text)); + + if (row) + { + var sel = this.document.defaultView.getSelection(); + sel.removeAllRanges(); + sel.addRange(this.currentSearch.range); + scrollSelectionIntoView(this); + return true; + } + else + return false; + } + else + { + function findRow(node) { return node.nodeType == 1 ? node : node.parentNode; } + this.currentSearch = new TextSearch(this.panelNode, findRow); + row = this.currentSearch.find(text, reverse, Firebug.Search.isCaseSensitive(text)); + } + } + + if (row) + { + this.document.defaultView.getSelection().selectAllChildren(row); + scrollIntoCenterView(row, this.panelNode); + dispatch([Firebug.A11yModel], 'onCSSSearchMatchFound', [this, text, row]); + return true; + } + else + { + dispatch([Firebug.A11yModel], 'onCSSSearchMatchFound', [this, text, null]); + return false; + } + }, + + getSearchOptionsMenuItems: function() + { + return [ + Firebug.Search.searchOptionMenu("search.Case_Sensitive", "searchCaseSensitive"), + Firebug.Search.searchOptionMenu("search.Multiple_Files", "searchGlobal") + ]; + } +}); +/**/ +// ************************************************************************************************ + +function CSSElementPanel() {} + +CSSElementPanel.prototype = extend(Firebug.CSSStyleSheetPanel.prototype, +{ + template: domplate( + { + cascadedTag: + DIV({"class": "a11yCSSView", role : 'presentation'}, + DIV({role : 'list', 'aria-label' : $STR('aria.labels.style rules') }, + FOR("rule", "$rules", + TAG("$ruleTag", {rule: "$rule"}) + ) + ), + DIV({role : "list", 'aria-label' :$STR('aria.labels.inherited style rules')}, + FOR("section", "$inherited", + H1({"class": "cssInheritHeader groupHeader focusRow", role : 'listitem' }, + SPAN({"class": "cssInheritLabel"}, "$inheritLabel"), + TAG(FirebugReps.Element.shortTag, {object: "$section.element"}) + ), + DIV({role : 'group'}, + FOR("rule", "$section.rules", + TAG("$ruleTag", {rule: "$rule"}) + ) + ) + ) + ) + ), + + ruleTag: + isIE ? + // IE needs the sourceLink first, otherwise it will be rendered outside the panel + DIV({"class": "cssElementRuleContainer"}, + TAG(FirebugReps.SourceLink.tag, {object: "$rule.sourceLink"}), + TAG(CSSStyleRuleTag.tag, {rule: "$rule"}) + ) + : + // other browsers need the sourceLink last, otherwise it will cause an extra space + // before the rule representation + DIV({"class": "cssElementRuleContainer"}, + TAG(CSSStyleRuleTag.tag, {rule: "$rule"}), + TAG(FirebugReps.SourceLink.tag, {object: "$rule.sourceLink"}) + ) + }), + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + updateCascadeView: function(element) + { + //dispatch([Firebug.A11yModel], 'onBeforeCSSRulesAdded', [this]); + var rules = [], sections = [], usedProps = {}; + this.getInheritedRules(element, sections, usedProps); + this.getElementRules(element, rules, usedProps); + + if (rules.length || sections.length) + { + var inheritLabel = "Inherited from"; // $STR("InheritedFrom"); + var result = this.template.cascadedTag.replace({rules: rules, inherited: sections, + inheritLabel: inheritLabel}, this.panelNode); + //dispatch([Firebug.A11yModel], 'onCSSRulesAdded', [this, result]); + } + else + { + var result = FirebugReps.Warning.tag.replace({object: "EmptyElementCSS"}, this.panelNode); + //dispatch([Firebug.A11yModel], 'onCSSRulesAdded', [this, result]); + } + + // TODO: xxxpedro remove when there the external resource problem is fixed + if (CssAnalyzer.hasExternalStyleSheet()) + CssAnalyzer.externalStyleSheetWarning.tag.append({ + object: "The results here may be inaccurate because some " + + "stylesheets could not be loaded due to access restrictions. ", + link: "more...", + href: "http://getfirebug.com/wiki/index.php/Firebug_Lite_FAQ#I_keep_seeing_.22This_element_has_no_style_rules.22" + }, this.panelNode); + }, + + getStylesheetURL: function(rule) + { + // if the parentStyleSheet.href is null, CSS std says its inline style. + // TODO: xxxpedro IE doesn't have rule.parentStyleSheet so we must fall back to the doc.location + if (rule && rule.parentStyleSheet && rule.parentStyleSheet.href) + return rule.parentStyleSheet.href; + else + return this.selection.ownerDocument.location.href; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + getInheritedRules: function(element, sections, usedProps) + { + var parent = element.parentNode; + if (parent && parent.nodeType == 1) + { + this.getInheritedRules(parent, sections, usedProps); + + var rules = []; + this.getElementRules(parent, rules, usedProps, true); + + if (rules.length) + sections.splice(0, 0, {element: parent, rules: rules}); + } + }, + + getElementRules: function(element, rules, usedProps, inheritMode) + { + var inspectedRules, displayedRules = {}; + + inspectedRules = CssAnalyzer.getElementCSSRules(element); + + if (inspectedRules) + { + for (var i = 0, length=inspectedRules.length; i < length; ++i) + { + var ruleId = inspectedRules[i]; + var ruleData = CssAnalyzer.getRuleData(ruleId); + var rule = ruleData.rule; + + var ssid = ruleData.styleSheetId; + var parentStyleSheet = StyleSheetCache.get(ssid); + + var href = parentStyleSheet.externalURL ? parentStyleSheet.externalURL : parentStyleSheet.href; // Null means inline + + var instance = null; + //var instance = getInstanceForStyleSheet(rule.parentStyleSheet, element.ownerDocument); + + var isSystemSheet = false; + //var isSystemSheet = isSystemStyleSheet(rule.parentStyleSheet); + + if (!Firebug.showUserAgentCSS && isSystemSheet) // This removes user agent rules + continue; + + if (!href) + href = element.ownerDocument.location.href; // http://code.google.com/p/fbug/issues/detail?id=452 + + var props = this.getRuleProperties(this.context, rule, inheritMode); + if (inheritMode && !props.length) + continue; + + // + //var line = domUtils.getRuleLine(rule); + // TODO: xxxpedro CSS line number + var line = ruleData.lineNo; + + var ruleId = rule.selectorText+"/"+line; + var sourceLink = new SourceLink(href, line, "css", rule, instance); + + this.markOverridenProps(props, usedProps, inheritMode); + + rules.splice(0, 0, {rule: rule, id: ruleId, + selector: ruleData.selector, sourceLink: sourceLink, + props: props, inherited: inheritMode, + isSystemSheet: isSystemSheet}); + } + } + + if (element.style) + this.getStyleProperties(element, rules, usedProps, inheritMode); + + if (FBTrace.DBG_CSS) + FBTrace.sysout("getElementRules "+rules.length+" rules for "+getElementXPath(element), rules); + }, + /* + getElementRules: function(element, rules, usedProps, inheritMode) + { + var inspectedRules, displayedRules = {}; + try + { + inspectedRules = domUtils ? domUtils.getCSSStyleRules(element) : null; + } catch (exc) {} + + if (inspectedRules) + { + for (var i = 0; i < inspectedRules.Count(); ++i) + { + var rule = QI(inspectedRules.GetElementAt(i), nsIDOMCSSStyleRule); + + var href = rule.parentStyleSheet.href; // Null means inline + + var instance = getInstanceForStyleSheet(rule.parentStyleSheet, element.ownerDocument); + + var isSystemSheet = isSystemStyleSheet(rule.parentStyleSheet); + if (!Firebug.showUserAgentCSS && isSystemSheet) // This removes user agent rules + continue; + if (!href) + href = element.ownerDocument.location.href; // http://code.google.com/p/fbug/issues/detail?id=452 + + var props = this.getRuleProperties(this.context, rule, inheritMode); + if (inheritMode && !props.length) + continue; + + var line = domUtils.getRuleLine(rule); + var ruleId = rule.selectorText+"/"+line; + var sourceLink = new SourceLink(href, line, "css", rule, instance); + + this.markOverridenProps(props, usedProps, inheritMode); + + rules.splice(0, 0, {rule: rule, id: ruleId, + selector: rule.selectorText, sourceLink: sourceLink, + props: props, inherited: inheritMode, + isSystemSheet: isSystemSheet}); + } + } + + if (element.style) + this.getStyleProperties(element, rules, usedProps, inheritMode); + + if (FBTrace.DBG_CSS) + FBTrace.sysout("getElementRules "+rules.length+" rules for "+getElementXPath(element), rules); + }, + /**/ + markOverridenProps: function(props, usedProps, inheritMode) + { + for (var i = 0; i < props.length; ++i) + { + var prop = props[i]; + if ( usedProps.hasOwnProperty(prop.name) ) + { + var deadProps = usedProps[prop.name]; // all previous occurrences of this property + for (var j = 0; j < deadProps.length; ++j) + { + var deadProp = deadProps[j]; + if (!deadProp.disabled && !deadProp.wasInherited && deadProp.important && !prop.important) + prop.overridden = true; // new occurrence overridden + else if (!prop.disabled) + deadProp.overridden = true; // previous occurrences overridden + } + } + else + usedProps[prop.name] = []; + + prop.wasInherited = inheritMode ? true : false; + usedProps[prop.name].push(prop); // all occurrences of a property seen so far, by name + } + }, + + getStyleProperties: function(element, rules, usedProps, inheritMode) + { + var props = this.parseCSSProps(element.style, inheritMode); + this.addOldProperties(this.context, getElementXPath(element), inheritMode, props); + + sortProperties(props); + this.markOverridenProps(props, usedProps, inheritMode); + + if (props.length) + rules.splice(0, 0, + {rule: element, id: getElementXPath(element), + selector: "element.style", props: props, inherited: inheritMode}); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // extends Panel + + name: "css", + title: "Style", + parentPanel: "HTML", + order: 0, + + initialize: function() + { + this.context = Firebug.chrome; // TODO: xxxpedro css2 + this.document = Firebug.chrome.document; // TODO: xxxpedro css2 + + Firebug.CSSStyleSheetPanel.prototype.initialize.apply(this, arguments); + + // TODO: xxxpedro css2 + var selection = ElementCache.get(Firebug.context.persistedState.selectedHTMLElementId); + if (selection) + this.select(selection, true); + + //this.updateCascadeView(document.getElementsByTagName("h1")[0]); + //this.updateCascadeView(document.getElementById("build")); + + /* + this.onStateChange = bindFixed(this.contentStateCheck, this); + this.onHoverChange = bindFixed(this.contentStateCheck, this, STATE_HOVER); + this.onActiveChange = bindFixed(this.contentStateCheck, this, STATE_ACTIVE); + /**/ + }, + + ishow: function(state) + { + }, + + watchWindow: function(win) + { + if (domUtils) + { + // Normally these would not be required, but in order to update after the state is set + // using the options menu we need to monitor these global events as well + var doc = win.document; + ///addEvent(doc, "mouseover", this.onHoverChange); + ///addEvent(doc, "mousedown", this.onActiveChange); + } + }, + unwatchWindow: function(win) + { + var doc = win.document; + ///removeEvent(doc, "mouseover", this.onHoverChange); + ///removeEvent(doc, "mousedown", this.onActiveChange); + + if (isAncestor(this.stateChangeEl, doc)) + { + this.removeStateChangeHandlers(); + } + }, + + supportsObject: function(object) + { + return object instanceof Element ? 1 : 0; + }, + + updateView: function(element) + { + this.updateCascadeView(element); + if (domUtils) + { + this.contentState = safeGetContentState(element); + this.addStateChangeHandlers(element); + } + }, + + updateSelection: function(element) + { + if ( !instanceOf(element , "Element") ) // html supports SourceLink + return; + + if (sothinkInstalled) + { + FirebugReps.Warning.tag.replace({object: "SothinkWarning"}, this.panelNode); + return; + } + + /* + if (!domUtils) + { + FirebugReps.Warning.tag.replace({object: "DOMInspectorWarning"}, this.panelNode); + return; + } + /**/ + + if (!element) + return; + + this.updateView(element); + }, + + updateOption: function(name, value) + { + if (name == "showUserAgentCSS" || name == "expandShorthandProps") + this.refresh(); + }, + + getOptionsMenuItems: function() + { + var ret = [ + {label: "Show User Agent CSS", type: "checkbox", checked: Firebug.showUserAgentCSS, + command: bindFixed(Firebug.togglePref, Firebug, "showUserAgentCSS") }, + {label: "Expand Shorthand Properties", type: "checkbox", checked: Firebug.expandShorthandProps, + command: bindFixed(Firebug.togglePref, Firebug, "expandShorthandProps") } + ]; + if (domUtils && this.selection) + { + var state = safeGetContentState(this.selection); + + ret.push("-"); + ret.push({label: ":active", type: "checkbox", checked: state & STATE_ACTIVE, + command: bindFixed(this.updateContentState, this, STATE_ACTIVE, state & STATE_ACTIVE)}); + ret.push({label: ":hover", type: "checkbox", checked: state & STATE_HOVER, + command: bindFixed(this.updateContentState, this, STATE_HOVER, state & STATE_HOVER)}); + } + return ret; + }, + + updateContentState: function(state, remove) + { + domUtils.setContentState(remove ? this.selection.ownerDocument.documentElement : this.selection, state); + this.refresh(); + }, + + addStateChangeHandlers: function(el) + { + this.removeStateChangeHandlers(); + + /* + addEvent(el, "focus", this.onStateChange); + addEvent(el, "blur", this.onStateChange); + addEvent(el, "mouseup", this.onStateChange); + addEvent(el, "mousedown", this.onStateChange); + addEvent(el, "mouseover", this.onStateChange); + addEvent(el, "mouseout", this.onStateChange); + /**/ + + this.stateChangeEl = el; + }, + + removeStateChangeHandlers: function() + { + var sel = this.stateChangeEl; + if (sel) + { + /* + removeEvent(sel, "focus", this.onStateChange); + removeEvent(sel, "blur", this.onStateChange); + removeEvent(sel, "mouseup", this.onStateChange); + removeEvent(sel, "mousedown", this.onStateChange); + removeEvent(sel, "mouseover", this.onStateChange); + removeEvent(sel, "mouseout", this.onStateChange); + /**/ + } + }, + + contentStateCheck: function(state) + { + if (!state || this.contentState & state) + { + var timeoutRunner = bindFixed(function() + { + var newState = safeGetContentState(this.selection); + if (newState != this.contentState) + { + this.context.invalidatePanels(this.name); + } + }, this); + + // Delay exec until after the event has processed and the state has been updated + setTimeout(timeoutRunner, 0); + } + } +}); + +function safeGetContentState(selection) +{ + try + { + return domUtils.getContentState(selection); + } + catch (e) + { + if (FBTrace.DBG_ERRORS) + FBTrace.sysout("css.safeGetContentState; EXCEPTION", e); + } +} + +// ************************************************************************************************ + +function CSSComputedElementPanel() {} + +CSSComputedElementPanel.prototype = extend(CSSElementPanel.prototype, +{ + template: domplate( + { + computedTag: + DIV({"class": "a11yCSSView", role : "list", "aria-label" : $STR('aria.labels.computed styles')}, + FOR("group", "$groups", + H1({"class": "cssInheritHeader groupHeader focusRow", role : "listitem"}, + SPAN({"class": "cssInheritLabel"}, "$group.title") + ), + TABLE({width: "100%", role : 'group'}, + TBODY({role : 'presentation'}, + FOR("prop", "$group.props", + TR({"class": 'focusRow computedStyleRow', role : 'listitem'}, + TD({"class": "stylePropName", role : 'presentation'}, "$prop.name"), + TD({"class": "stylePropValue", role : 'presentation'}, "$prop.value") + ) + ) + ) + ) + ) + ) + }), + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + updateComputedView: function(element) + { + var win = isIE ? + element.ownerDocument.parentWindow : + element.ownerDocument.defaultView; + + var style = isIE ? + element.currentStyle : + win.getComputedStyle(element, ""); + + var groups = []; + + for (var groupName in styleGroups) + { + // TODO: xxxpedro i18n $STR + //var title = $STR("StyleGroup-" + groupName); + var title = styleGroupTitles[groupName]; + var group = {title: title, props: []}; + groups.push(group); + + var props = styleGroups[groupName]; + for (var i = 0; i < props.length; ++i) + { + var propName = props[i]; + var propValue = style.getPropertyValue ? + style.getPropertyValue(propName) : + ""+style[toCamelCase(propName)]; + + if (propValue === undefined || propValue === null) + continue; + + propValue = stripUnits(rgbToHex(propValue)); + if (propValue) + group.props.push({name: propName, value: propValue}); + } + } + + var result = this.template.computedTag.replace({groups: groups}, this.panelNode); + //dispatch([Firebug.A11yModel], 'onCSSRulesAdded', [this, result]); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // extends Panel + + name: "computed", + title: "Computed", + parentPanel: "HTML", + order: 1, + + updateView: function(element) + { + this.updateComputedView(element); + }, + + getOptionsMenuItems: function() + { + return [ + {label: "Refresh", command: bind(this.refresh, this) } + ]; + } +}); + +// ************************************************************************************************ +// CSSEditor + +function CSSEditor(doc) +{ + this.initializeInline(doc); +} + +CSSEditor.prototype = domplate(Firebug.InlineEditor.prototype, +{ + insertNewRow: function(target, insertWhere) + { + var rule = Firebug.getRepObject(target); + var emptyProp = + { + // TODO: xxxpedro - uses charCode(255) to force the element being rendered, + // allowing webkit to get the correct position of the property name "span", + // when inserting a new CSS rule? + name: "", + value: "", + important: "" + }; + + if (insertWhere == "before") + return CSSPropTag.tag.insertBefore({prop: emptyProp, rule: rule}, target); + else + return CSSPropTag.tag.insertAfter({prop: emptyProp, rule: rule}, target); + }, + + saveEdit: function(target, value, previousValue) + { + // We need to check the value first in order to avoid a problem in IE8 + // See Issue 3038: Empty (null) styles when adding CSS styles in Firebug Lite + if (!value) return; + + target.innerHTML = escapeForCss(value); + + var row = getAncestorByClass(target, "cssProp"); + if (hasClass(row, "disabledStyle")) + toggleClass(row, "disabledStyle"); + + var rule = Firebug.getRepObject(target); + + if (hasClass(target, "cssPropName")) + { + if (value && previousValue != value) // name of property has changed. + { + var propValue = getChildByClass(row, "cssPropValue")[textContent]; + var parsedValue = parsePriority(propValue); + + if (propValue && propValue != "undefined") { + if (FBTrace.DBG_CSS) + FBTrace.sysout("CSSEditor.saveEdit : "+previousValue+"->"+value+" = "+propValue+"\n"); + if (previousValue) + Firebug.CSSModule.removeProperty(rule, previousValue); + Firebug.CSSModule.setProperty(rule, value, parsedValue.value, parsedValue.priority); + } + } + else if (!value) // name of the property has been deleted, so remove the property. + Firebug.CSSModule.removeProperty(rule, previousValue); + } + else if (getAncestorByClass(target, "cssPropValue")) + { + var propName = getChildByClass(row, "cssPropName")[textContent]; + var propValue = getChildByClass(row, "cssPropValue")[textContent]; + + if (FBTrace.DBG_CSS) + { + FBTrace.sysout("CSSEditor.saveEdit propName=propValue: "+propName +" = "+propValue+"\n"); + // FBTrace.sysout("CSSEditor.saveEdit BEFORE style:",style); + } + + if (value && value != "null") + { + var parsedValue = parsePriority(value); + Firebug.CSSModule.setProperty(rule, propName, parsedValue.value, parsedValue.priority); + } + else if (previousValue && previousValue != "null") + Firebug.CSSModule.removeProperty(rule, propName); + } + + this.panel.markChange(this.panel.name == "stylesheet"); + }, + + advanceToNext: function(target, charCode) + { + if (charCode == 58 /*":"*/ && hasClass(target, "cssPropName")) + return true; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + getAutoCompleteRange: function(value, offset) + { + if (hasClass(this.target, "cssPropName")) + return {start: 0, end: value.length-1}; + else + return parseCSSValue(value, offset); + }, + + getAutoCompleteList: function(preExpr, expr, postExpr) + { + if (hasClass(this.target, "cssPropName")) + { + return getCSSPropertyNames(); + } + else + { + var row = getAncestorByClass(this.target, "cssProp"); + var propName = getChildByClass(row, "cssPropName")[textContent]; + return getCSSKeywordsByProperty(propName); + } + } +}); + +//************************************************************************************************ +//CSSRuleEditor + +function CSSRuleEditor(doc) +{ + this.initializeInline(doc); + this.completeAsYouType = false; +} +CSSRuleEditor.uniquifier = 0; +CSSRuleEditor.prototype = domplate(Firebug.InlineEditor.prototype, +{ + insertNewRow: function(target, insertWhere) + { + var emptyRule = { + selector: "", + id: "", + props: [], + isSelectorEditable: true + }; + + if (insertWhere == "before") + return CSSStyleRuleTag.tag.insertBefore({rule: emptyRule}, target); + else + return CSSStyleRuleTag.tag.insertAfter({rule: emptyRule}, target); + }, + + saveEdit: function(target, value, previousValue) + { + if (FBTrace.DBG_CSS) + FBTrace.sysout("CSSRuleEditor.saveEdit: '" + value + "' '" + previousValue + "'", target); + + target.innerHTML = escapeForCss(value); + + if (value === previousValue) return; + + var row = getAncestorByClass(target, "cssRule"); + var styleSheet = this.panel.location; + styleSheet = styleSheet.editStyleSheet ? styleSheet.editStyleSheet.sheet : styleSheet; + + var cssRules = styleSheet.cssRules; + var rule = Firebug.getRepObject(target), oldRule = rule; + var ruleIndex = cssRules.length; + if (rule || Firebug.getRepObject(row.nextSibling)) + { + var searchRule = rule || Firebug.getRepObject(row.nextSibling); + for (ruleIndex=0; ruleIndex<cssRules.length && searchRule!=cssRules[ruleIndex]; ruleIndex++) {} + } + + // Delete in all cases except for new add + // We want to do this before the insert to ease change tracking + if (oldRule) + { + Firebug.CSSModule.deleteRule(styleSheet, ruleIndex); + } + + // Firefox does not follow the spec for the update selector text case. + // When attempting to update the value, firefox will silently fail. + // See https://bugzilla.mozilla.org/show_bug.cgi?id=37468 for the quite + // old discussion of this bug. + // As a result we need to recreate the style every time the selector + // changes. + if (value) + { + var cssText = [ value, "{" ]; + var props = row.getElementsByClassName("cssProp"); + for (var i = 0; i < props.length; i++) { + var propEl = props[i]; + if (!hasClass(propEl, "disabledStyle")) { + cssText.push(getChildByClass(propEl, "cssPropName")[textContent]); + cssText.push(":"); + cssText.push(getChildByClass(propEl, "cssPropValue")[textContent]); + cssText.push(";"); + } + } + cssText.push("}"); + cssText = cssText.join(""); + + try + { + var insertLoc = Firebug.CSSModule.insertRule(styleSheet, cssText, ruleIndex); + rule = cssRules[insertLoc]; + ruleIndex++; + } + catch (err) + { + if (FBTrace.DBG_CSS || FBTrace.DBG_ERRORS) + FBTrace.sysout("CSS Insert Error: "+err, err); + + target.innerHTML = escapeForCss(previousValue); + row.repObject = undefined; + return; + } + } else { + rule = undefined; + } + + // Update the rep object + row.repObject = rule; + if (!oldRule) + { + // Who knows what the domutils will return for rule line + // for a recently created rule. To be safe we just generate + // a unique value as this is only used as an internal key. + var ruleId = "new/"+value+"/"+(++CSSRuleEditor.uniquifier); + row.setAttribute("ruleId", ruleId); + } + + this.panel.markChange(this.panel.name == "stylesheet"); + } +}); + +// ************************************************************************************************ +// StyleSheetEditor + +function StyleSheetEditor(doc) +{ + this.box = this.tag.replace({}, doc, this); + this.input = this.box.firstChild; +} + +StyleSheetEditor.prototype = domplate(Firebug.BaseEditor, +{ + multiLine: true, + + tag: DIV( + TEXTAREA({"class": "styleSheetEditor fullPanelEditor", oninput: "$onInput"}) + ), + + getValue: function() + { + return this.input.value; + }, + + setValue: function(value) + { + return this.input.value = value; + }, + + show: function(target, panel, value, textSize, targetSize) + { + this.target = target; + this.panel = panel; + + this.panel.panelNode.appendChild(this.box); + + this.input.value = value; + this.input.focus(); + + var command = Firebug.chrome.$("cmd_toggleCSSEditing"); + command.setAttribute("checked", true); + }, + + hide: function() + { + var command = Firebug.chrome.$("cmd_toggleCSSEditing"); + command.setAttribute("checked", false); + + if (this.box.parentNode == this.panel.panelNode) + this.panel.panelNode.removeChild(this.box); + + delete this.target; + delete this.panel; + delete this.styleSheet; + }, + + saveEdit: function(target, value, previousValue) + { + Firebug.CSSModule.freeEdit(this.styleSheet, value); + }, + + endEditing: function() + { + this.panel.refresh(); + return true; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + onInput: function() + { + Firebug.Editor.update(); + }, + + scrollToLine: function(line, offset) + { + this.startMeasuring(this.input); + var lineHeight = this.measureText().height; + this.stopMeasuring(); + + this.input.scrollTop = (line * lineHeight) + offset; + } +}); + +// ************************************************************************************************ +// Local Helpers + +var rgbToHex = function rgbToHex(value) +{ + return value.replace(/\brgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)/gi, rgbToHexReplacer); +}; + +var rgbToHexReplacer = function(_, r, g, b) { + return '#' + ((1 << 24) + (r << 16) + (g << 8) + (b << 0)).toString(16).substr(-6).toUpperCase(); +}; + +var stripUnits = function stripUnits(value) +{ + // remove units from '0px', '0em' etc. leave non-zero units in-tact. + return value.replace(/(url\(.*?\)|[^0]\S*\s*)|0(%|em|ex|px|in|cm|mm|pt|pc)(\s|$)/gi, stripUnitsReplacer); +}; + +var stripUnitsReplacer = function(_, skip, remove, whitespace) { + return skip || ('0' + whitespace); +}; + +function parsePriority(value) +{ + var rePriority = /(.*?)\s*(!important)?$/; + var m = rePriority.exec(value); + var propValue = m ? m[1] : ""; + var priority = m && m[2] ? "important" : ""; + return {value: propValue, priority: priority}; +} + +function parseURLValue(value) +{ + var m = reURL.exec(value); + return m ? m[1] : ""; +} + +function parseRepeatValue(value) +{ + var m = reRepeat.exec(value); + return m ? m[0] : ""; +} + +function parseCSSValue(value, offset) +{ + var start = 0; + var m; + while (1) + { + m = reSplitCSS.exec(value); + if (m && m.index+m[0].length < offset) + { + value = value.substr(m.index+m[0].length); + start += m.index+m[0].length; + offset -= m.index+m[0].length; + } + else + break; + } + + if (m) + { + var type; + if (m[1]) + type = "url"; + else if (m[2] || m[3]) + type = "rgb"; + else if (m[4]) + type = "int"; + + return {value: m[0], start: start+m.index, end: start+m.index+(m[0].length-1), type: type}; + } +} + +function findPropByName(props, name) +{ + for (var i = 0; i < props.length; ++i) + { + if (props[i].name == name) + return i; + } +} + +function sortProperties(props) +{ + props.sort(function(a, b) + { + return a.name > b.name ? 1 : -1; + }); +} + +function getTopmostRuleLine(panelNode) +{ + for (var child = panelNode.firstChild; child; child = child.nextSibling) + { + if (child.offsetTop+child.offsetHeight > panelNode.scrollTop) + { + var rule = child.repObject; + if (rule) + return { + line: domUtils.getRuleLine(rule), + offset: panelNode.scrollTop-child.offsetTop + }; + } + } + return 0; +} + +function getStyleSheetCSS(sheet, context) +{ + if (sheet.ownerNode instanceof HTMLStyleElement) + return sheet.ownerNode.innerHTML; + else + return context.sourceCache.load(sheet.href).join(""); +} + +function getStyleSheetOwnerNode(sheet) { + for (; sheet && !sheet.ownerNode; sheet = sheet.parentStyleSheet); + + return sheet.ownerNode; +} + +function scrollSelectionIntoView(panel) +{ + var selCon = getSelectionController(panel); + selCon.scrollSelectionIntoView( + nsISelectionController.SELECTION_NORMAL, + nsISelectionController.SELECTION_FOCUS_REGION, true); +} + +function getSelectionController(panel) +{ + var browser = Firebug.chrome.getPanelBrowser(panel); + return browser.docShell.QueryInterface(nsIInterfaceRequestor) + .getInterface(nsISelectionDisplay) + .QueryInterface(nsISelectionController); +} + +// ************************************************************************************************ + +Firebug.registerModule(Firebug.CSSModule); +Firebug.registerPanel(Firebug.CSSStyleSheetPanel); +Firebug.registerPanel(CSSElementPanel); +Firebug.registerPanel(CSSComputedElementPanel); + +// ************************************************************************************************ + +}}); + + +/* See license.txt for terms of usage */ + +FBL.ns(function() { with (FBL) { +// ************************************************************************************************ + +// ************************************************************************************************ +// Script Module + +Firebug.Script = extend(Firebug.Module, +{ + getPanel: function() + { + return Firebug.chrome ? Firebug.chrome.getPanel("Script") : null; + }, + + selectSourceCode: function(index) + { + this.getPanel().selectSourceCode(index); + } +}); + +Firebug.registerModule(Firebug.Script); + + +// ************************************************************************************************ +// Script Panel + +function ScriptPanel(){}; + +ScriptPanel.prototype = extend(Firebug.Panel, +{ + name: "Script", + title: "Script", + + selectIndex: 0, // index of the current selectNode's option + sourceIndex: -1, // index of the script node, based in doc.getElementsByTagName("script") + + options: { + hasToolButtons: true + }, + + create: function() + { + Firebug.Panel.create.apply(this, arguments); + + this.onChangeSelect = bind(this.onChangeSelect, this); + + var doc = Firebug.browser.document; + var scripts = doc.getElementsByTagName("script"); + var selectNode = this.selectNode = createElement("select"); + + for(var i=0, script; script=scripts[i]; i++) + { + // Don't show Firebug Lite source code in the list of options + if (Firebug.ignoreFirebugElements && script.getAttribute("firebugIgnore")) + continue; + + var fileName = getFileName(script.src) || getFileName(doc.location.href); + var option = createElement("option", {value:i}); + + option.appendChild(Firebug.chrome.document.createTextNode(fileName)); + selectNode.appendChild(option); + }; + + this.toolButtonsNode.appendChild(selectNode); + }, + + initialize: function() + { + // we must render the code first, so the persistent state can be restore + this.selectSourceCode(this.selectIndex); + + Firebug.Panel.initialize.apply(this, arguments); + + addEvent(this.selectNode, "change", this.onChangeSelect); + }, + + shutdown: function() + { + removeEvent(this.selectNode, "change", this.onChangeSelect); + + Firebug.Panel.shutdown.apply(this, arguments); + }, + + detach: function(oldChrome, newChrome) + { + Firebug.Panel.detach.apply(this, arguments); + + var oldPanel = oldChrome.getPanel("Script"); + var index = oldPanel.selectIndex; + + this.selectNode.selectedIndex = index; + this.selectIndex = index; + this.sourceIndex = -1; + }, + + onChangeSelect: function(event) + { + var select = this.selectNode; + + this.selectIndex = select.selectedIndex; + + var option = select.options[select.selectedIndex]; + if (!option) + return; + + var selectedSourceIndex = parseInt(option.value); + + this.renderSourceCode(selectedSourceIndex); + }, + + selectSourceCode: function(index) + { + var select = this.selectNode; + select.selectedIndex = index; + + var option = select.options[index]; + if (!option) + return; + + var selectedSourceIndex = parseInt(option.value); + + this.renderSourceCode(selectedSourceIndex); + }, + + renderSourceCode: function(index) + { + if (this.sourceIndex != index) + { + var renderProcess = function renderProcess(src) + { + var html = [], + hl = 0; + + src = isIE && !isExternal ? + src+'\n' : // IE put an extra line when reading source of local resources + '\n'+src; + + // find the number of lines of code + src = src.replace(/\n\r|\r\n/g, "\n"); + var match = src.match(/[\n]/g); + var lines=match ? match.length : 0; + + // render the full source code + line numbers html + html[hl++] = '<div><div class="sourceBox" style="left:'; + html[hl++] = 35 + 7*(lines+'').length; + html[hl++] = 'px;"><pre class="sourceCode">'; + html[hl++] = escapeHTML(src); + html[hl++] = '</pre></div><div class="lineNo">'; + + // render the line number divs + for(var l=1, lines; l<=lines; l++) + { + html[hl++] = '<div line="'; + html[hl++] = l; + html[hl++] = '">'; + html[hl++] = l; + html[hl++] = '</div>'; + } + + html[hl++] = '</div></div>'; + + updatePanel(html); + }; + + var updatePanel = function(html) + { + self.panelNode.innerHTML = html.join(""); + + // IE needs this timeout, otherwise the panel won't scroll + setTimeout(function(){ + self.synchronizeUI(); + },0); + }; + + var onFailure = function() + { + FirebugReps.Warning.tag.replace({object: "AccessRestricted"}, self.panelNode); + }; + + var self = this; + + var doc = Firebug.browser.document; + var script = doc.getElementsByTagName("script")[index]; + var url = getScriptURL(script); + var isExternal = url && url != doc.location.href; + + try + { + if (Firebug.disableResourceFetching) + { + renderProcess(Firebug.Lite.Proxy.fetchResourceDisabledMessage); + } + else if (isExternal) + { + Ajax.request({url: url, onSuccess: renderProcess, onFailure: onFailure}); + } + else + { + var src = script.innerHTML; + renderProcess(src); + } + } + catch(e) + { + onFailure(); + } + + this.sourceIndex = index; + } + } +}); + +Firebug.registerPanel(ScriptPanel); + + +// ************************************************************************************************ + + +var getScriptURL = function getScriptURL(script) +{ + var reFile = /([^\/\?#]+)(#.+)?$/; + var rePath = /^(.*\/)/; + var reProtocol = /^\w+:\/\//; + var path = null; + var doc = Firebug.browser.document; + + var file = reFile.exec(script.src); + + if (file) + { + var fileName = file[1]; + var fileOptions = file[2]; + + // absolute path + if (reProtocol.test(script.src)) { + path = rePath.exec(script.src)[1]; + + } + // relative path + else + { + var r = rePath.exec(script.src); + var src = r ? r[1] : script.src; + var backDir = /^((?:\.\.\/)+)(.*)/.exec(src); + var reLastDir = /^(.*\/)[^\/]+\/$/; + path = rePath.exec(doc.location.href)[1]; + + // "../some/path" + if (backDir) + { + var j = backDir[1].length/3; + var p; + while (j-- > 0) + path = reLastDir.exec(path)[1]; + + path += backDir[2]; + } + + else if(src.indexOf("/") != -1) + { + // "./some/path" + if(/^\.\/./.test(src)) + { + path += src.substring(2); + } + // "/some/path" + else if(/^\/./.test(src)) + { + var domain = /^(\w+:\/\/[^\/]+)/.exec(path); + path = domain[1] + src; + } + // "some/path" + else + { + path += src; + } + } + } + } + + var m = path && path.match(/([^\/]+)\/$/) || null; + + if (path && m) + { + return path + fileName; + } +}; + +var getFileName = function getFileName(path) +{ + if (!path) return ""; + + var match = path && path.match(/[^\/]+(\?.*)?(#.*)?$/); + + return match && match[0] || path; +}; + + +// ************************************************************************************************ +}}); + +/* See license.txt for terms of usage */ + +FBL.ns(function() { with (FBL) { +// ************************************************************************************************ + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +var ElementCache = Firebug.Lite.Cache.Element; + +var insertSliceSize = 18; +var insertInterval = 40; + +var ignoreVars = +{ + "__firebug__": 1, + "eval": 1, + + // We are forced to ignore Java-related variables, because + // trying to access them causes browser freeze + "java": 1, + "sun": 1, + "Packages": 1, + "JavaArray": 1, + "JavaMember": 1, + "JavaObject": 1, + "JavaClass": 1, + "JavaPackage": 1, + "_firebug": 1, + "_FirebugConsole": 1, + "_FirebugCommandLine": 1 +}; + +if (Firebug.ignoreFirebugElements) + ignoreVars[Firebug.Lite.Cache.ID] = 1; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +var memberPanelRep = + isIE6 ? + {"class": "memberLabel $member.type\\Label", href: "javacript:void(0)"} + : + {"class": "memberLabel $member.type\\Label"}; + +var RowTag = + TR({"class": "memberRow $member.open $member.type\\Row", $hasChildren: "$member.hasChildren", role : 'presentation', + level: "$member.level"}, + TD({"class": "memberLabelCell", style: "padding-left: $member.indent\\px", role : 'presentation'}, + A(memberPanelRep, + SPAN({}, "$member.name") + ) + ), + TD({"class": "memberValueCell", role : 'presentation'}, + TAG("$member.tag", {object: "$member.value"}) + ) + ); + +var WatchRowTag = + TR({"class": "watchNewRow", level: 0}, + TD({"class": "watchEditCell", colspan: 2}, + DIV({"class": "watchEditBox a11yFocusNoTab", role: "button", 'tabindex' : '0', + 'aria-label' : $STR('press enter to add new watch expression')}, + $STR("NewWatch") + ) + ) + ); + +var SizerRow = + TR({role : 'presentation'}, + TD({width: "30%"}), + TD({width: "70%"}) + ); + +var domTableClass = isIElt8 ? "domTable domTableIE" : "domTable"; +var DirTablePlate = domplate(Firebug.Rep, +{ + tag: + TABLE({"class": domTableClass, cellpadding: 0, cellspacing: 0, onclick: "$onClick", role :"tree"}, + TBODY({role: 'presentation'}, + SizerRow, + FOR("member", "$object|memberIterator", RowTag) + ) + ), + + watchTag: + TABLE({"class": domTableClass, cellpadding: 0, cellspacing: 0, + _toggles: "$toggles", _domPanel: "$domPanel", onclick: "$onClick", role : 'tree'}, + TBODY({role : 'presentation'}, + SizerRow, + WatchRowTag + ) + ), + + tableTag: + TABLE({"class": domTableClass, cellpadding: 0, cellspacing: 0, + _toggles: "$toggles", _domPanel: "$domPanel", onclick: "$onClick", role : 'tree'}, + TBODY({role : 'presentation'}, + SizerRow + ) + ), + + rowTag: + FOR("member", "$members", RowTag), + + memberIterator: function(object, level) + { + return getMembers(object, level); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + onClick: function(event) + { + if (!isLeftClick(event)) + return; + + var target = event.target || event.srcElement; + + var row = getAncestorByClass(target, "memberRow"); + var label = getAncestorByClass(target, "memberLabel"); + if (label && hasClass(row, "hasChildren")) + { + var row = label.parentNode.parentNode; + this.toggleRow(row); + } + else + { + var object = Firebug.getRepObject(target); + if (typeof(object) == "function") + { + Firebug.chrome.select(object, "script"); + cancelEvent(event); + } + else if (event.detail == 2 && !object) + { + var panel = row.parentNode.parentNode.domPanel; + if (panel) + { + var rowValue = panel.getRowPropertyValue(row); + if (typeof(rowValue) == "boolean") + panel.setPropertyValue(row, !rowValue); + else + panel.editProperty(row); + + cancelEvent(event); + } + } + } + + return false; + }, + + toggleRow: function(row) + { + var level = parseInt(row.getAttribute("level")); + var toggles = row.parentNode.parentNode.toggles; + + if (hasClass(row, "opened")) + { + removeClass(row, "opened"); + + if (toggles) + { + var path = getPath(row); + + // Remove the path from the toggle tree + for (var i = 0; i < path.length; ++i) + { + if (i == path.length-1) + delete toggles[path[i]]; + else + toggles = toggles[path[i]]; + } + } + + var rowTag = this.rowTag; + var tbody = row.parentNode; + + setTimeout(function() + { + for (var firstRow = row.nextSibling; firstRow; firstRow = row.nextSibling) + { + if (parseInt(firstRow.getAttribute("level")) <= level) + break; + + tbody.removeChild(firstRow); + } + }, row.insertTimeout ? row.insertTimeout : 0); + } + else + { + setClass(row, "opened"); + + if (toggles) + { + var path = getPath(row); + + // Mark the path in the toggle tree + for (var i = 0; i < path.length; ++i) + { + var name = path[i]; + if (toggles.hasOwnProperty(name)) + toggles = toggles[name]; + else + toggles = toggles[name] = {}; + } + } + + var value = row.lastChild.firstChild.repObject; + var members = getMembers(value, level+1); + + var rowTag = this.rowTag; + var lastRow = row; + + var delay = 0; + //var setSize = members.length; + //var rowCount = 1; + while (members.length) + { + with({slice: members.splice(0, insertSliceSize), isLast: !members.length}) + { + setTimeout(function() + { + if (lastRow.parentNode) + { + var result = rowTag.insertRows({members: slice}, lastRow); + lastRow = result[1]; + //dispatch([Firebug.A11yModel], 'onMemberRowSliceAdded', [null, result, rowCount, setSize]); + //rowCount += insertSliceSize; + } + if (isLast) + row.removeAttribute("insertTimeout"); + }, delay); + } + + delay += insertInterval; + } + + row.insertTimeout = delay; + } + } +}); + + + +// ************************************************************************************************ + +Firebug.DOMBasePanel = function() {}; + +Firebug.DOMBasePanel.prototype = extend(Firebug.Panel, +{ + tag: DirTablePlate.tableTag, + + getRealObject: function(object) + { + // TODO: Move this to some global location + // TODO: Unwrapping should be centralized rather than sprinkling it around ad hoc. + // TODO: We might be able to make this check more authoritative with QueryInterface. + if (!object) return object; + if (object.wrappedJSObject) return object.wrappedJSObject; + return object; + }, + + rebuild: function(update, scrollTop) + { + //dispatch([Firebug.A11yModel], 'onBeforeDomUpdateSelection', [this]); + var members = getMembers(this.selection); + expandMembers(members, this.toggles, 0, 0); + + this.showMembers(members, update, scrollTop); + + //TODO: xxxpedro statusbar + if (!this.parentPanel) + updateStatusBar(this); + }, + + showMembers: function(members, update, scrollTop) + { + // If we are still in the midst of inserting rows, cancel all pending + // insertions here - this is a big speedup when stepping in the debugger + if (this.timeouts) + { + for (var i = 0; i < this.timeouts.length; ++i) + this.context.clearTimeout(this.timeouts[i]); + delete this.timeouts; + } + + if (!members.length) + return this.showEmptyMembers(); + + var panelNode = this.panelNode; + var priorScrollTop = scrollTop == undefined ? panelNode.scrollTop : scrollTop; + + // If we are asked to "update" the current view, then build the new table + // offscreen and swap it in when it's done + var offscreen = update && panelNode.firstChild; + var dest = offscreen ? panelNode.ownerDocument : panelNode; + + var table = this.tag.replace({domPanel: this, toggles: this.toggles}, dest); + var tbody = table.lastChild; + var rowTag = DirTablePlate.rowTag; + + // Insert the first slice immediately + //var slice = members.splice(0, insertSliceSize); + //var result = rowTag.insertRows({members: slice}, tbody.lastChild); + + //var setSize = members.length; + //var rowCount = 1; + + var panel = this; + var result; + + //dispatch([Firebug.A11yModel], 'onMemberRowSliceAdded', [panel, result, rowCount, setSize]); + var timeouts = []; + + var delay = 0; + + // enable to measure rendering performance + var renderStart = new Date().getTime(); + while (members.length) + { + with({slice: members.splice(0, insertSliceSize), isLast: !members.length}) + { + timeouts.push(this.context.setTimeout(function() + { + // TODO: xxxpedro can this be a timing error related to the + // "iteration number" approach insted of "duration time"? + // avoid error in IE8 + if (!tbody.lastChild) return; + + result = rowTag.insertRows({members: slice}, tbody.lastChild); + + //rowCount += insertSliceSize; + //dispatch([Firebug.A11yModel], 'onMemberRowSliceAdded', [panel, result, rowCount, setSize]); + + if ((panelNode.scrollHeight+panelNode.offsetHeight) >= priorScrollTop) + panelNode.scrollTop = priorScrollTop; + + + // enable to measure rendering performance + //if (isLast) alert(new Date().getTime() - renderStart + "ms"); + + + }, delay)); + + delay += insertInterval; + } + } + + if (offscreen) + { + timeouts.push(this.context.setTimeout(function() + { + if (panelNode.firstChild) + panelNode.replaceChild(table, panelNode.firstChild); + else + panelNode.appendChild(table); + + // Scroll back to where we were before + panelNode.scrollTop = priorScrollTop; + }, delay)); + } + else + { + timeouts.push(this.context.setTimeout(function() + { + panelNode.scrollTop = scrollTop == undefined ? 0 : scrollTop; + }, delay)); + } + this.timeouts = timeouts; + }, + + /* + // new + showMembers: function(members, update, scrollTop) + { + // If we are still in the midst of inserting rows, cancel all pending + // insertions here - this is a big speedup when stepping in the debugger + if (this.timeouts) + { + for (var i = 0; i < this.timeouts.length; ++i) + this.context.clearTimeout(this.timeouts[i]); + delete this.timeouts; + } + + if (!members.length) + return this.showEmptyMembers(); + + var panelNode = this.panelNode; + var priorScrollTop = scrollTop == undefined ? panelNode.scrollTop : scrollTop; + + // If we are asked to "update" the current view, then build the new table + // offscreen and swap it in when it's done + var offscreen = update && panelNode.firstChild; + var dest = offscreen ? panelNode.ownerDocument : panelNode; + + var table = this.tag.replace({domPanel: this, toggles: this.toggles}, dest); + var tbody = table.lastChild; + var rowTag = DirTablePlate.rowTag; + + // Insert the first slice immediately + //var slice = members.splice(0, insertSliceSize); + //var result = rowTag.insertRows({members: slice}, tbody.lastChild); + + //var setSize = members.length; + //var rowCount = 1; + + var panel = this; + var result; + + //dispatch([Firebug.A11yModel], 'onMemberRowSliceAdded', [panel, result, rowCount, setSize]); + var timeouts = []; + + var delay = 0; + var _insertSliceSize = insertSliceSize; + var _insertInterval = insertInterval; + + // enable to measure rendering performance + var renderStart = new Date().getTime(); + var lastSkip = renderStart, now; + + while (members.length) + { + with({slice: members.splice(0, _insertSliceSize), isLast: !members.length}) + { + var _tbody = tbody; + var _rowTag = rowTag; + var _panelNode = panelNode; + var _priorScrollTop = priorScrollTop; + + timeouts.push(this.context.setTimeout(function() + { + // TODO: xxxpedro can this be a timing error related to the + // "iteration number" approach insted of "duration time"? + // avoid error in IE8 + if (!_tbody.lastChild) return; + + result = _rowTag.insertRows({members: slice}, _tbody.lastChild); + + //rowCount += _insertSliceSize; + //dispatch([Firebug.A11yModel], 'onMemberRowSliceAdded', [panel, result, rowCount, setSize]); + + if ((_panelNode.scrollHeight + _panelNode.offsetHeight) >= _priorScrollTop) + _panelNode.scrollTop = _priorScrollTop; + + + // enable to measure rendering performance + //alert("gap: " + (new Date().getTime() - lastSkip)); + //lastSkip = new Date().getTime(); + + //if (isLast) alert("new: " + (new Date().getTime() - renderStart) + "ms"); + + }, delay)); + + delay += _insertInterval; + } + } + + if (offscreen) + { + timeouts.push(this.context.setTimeout(function() + { + if (panelNode.firstChild) + panelNode.replaceChild(table, panelNode.firstChild); + else + panelNode.appendChild(table); + + // Scroll back to where we were before + panelNode.scrollTop = priorScrollTop; + }, delay)); + } + else + { + timeouts.push(this.context.setTimeout(function() + { + panelNode.scrollTop = scrollTop == undefined ? 0 : scrollTop; + }, delay)); + } + this.timeouts = timeouts; + }, + /**/ + + showEmptyMembers: function() + { + FirebugReps.Warning.tag.replace({object: "NoMembersWarning"}, this.panelNode); + }, + + findPathObject: function(object) + { + var pathIndex = -1; + for (var i = 0; i < this.objectPath.length; ++i) + { + // IE needs === instead of == or otherwise some objects will + // be considered equal to different objects, returning the + // wrong index of the objectPath array + if (this.getPathObject(i) === object) + return i; + } + + return -1; + }, + + getPathObject: function(index) + { + var object = this.objectPath[index]; + + if (object instanceof Property) + return object.getObject(); + else + return object; + }, + + getRowObject: function(row) + { + var object = getRowOwnerObject(row); + return object ? object : this.selection; + }, + + getRowPropertyValue: function(row) + { + var object = this.getRowObject(row); + object = this.getRealObject(object); + if (object) + { + var propName = getRowName(row); + + if (object instanceof jsdIStackFrame) + return Firebug.Debugger.evaluate(propName, this.context); + else + return object[propName]; + } + }, + /* + copyProperty: function(row) + { + var value = this.getRowPropertyValue(row); + copyToClipboard(value); + }, + + editProperty: function(row, editValue) + { + if (hasClass(row, "watchNewRow")) + { + if (this.context.stopped) + Firebug.Editor.startEditing(row, ""); + else if (Firebug.Console.isAlwaysEnabled()) // not stopped in debugger, need command line + { + if (Firebug.CommandLine.onCommandLineFocus()) + Firebug.Editor.startEditing(row, ""); + else + row.innerHTML = $STR("warning.Command line blocked?"); + } + else + row.innerHTML = $STR("warning.Console must be enabled"); + } + else if (hasClass(row, "watchRow")) + Firebug.Editor.startEditing(row, getRowName(row)); + else + { + var object = this.getRowObject(row); + this.context.thisValue = object; + + if (!editValue) + { + var propValue = this.getRowPropertyValue(row); + + var type = typeof(propValue); + if (type == "undefined" || type == "number" || type == "boolean") + editValue = propValue; + else if (type == "string") + editValue = "\"" + escapeJS(propValue) + "\""; + else if (propValue == null) + editValue = "null"; + else if (object instanceof Window || object instanceof jsdIStackFrame) + editValue = getRowName(row); + else + editValue = "this." + getRowName(row); + } + + + Firebug.Editor.startEditing(row, editValue); + } + }, + + deleteProperty: function(row) + { + if (hasClass(row, "watchRow")) + this.deleteWatch(row); + else + { + var object = getRowOwnerObject(row); + if (!object) + object = this.selection; + object = this.getRealObject(object); + + if (object) + { + var name = getRowName(row); + try + { + delete object[name]; + } + catch (exc) + { + return; + } + + this.rebuild(true); + this.markChange(); + } + } + }, + + setPropertyValue: function(row, value) // value must be string + { + if(FBTrace.DBG_DOM) + { + FBTrace.sysout("row: "+row); + FBTrace.sysout("value: "+value+" type "+typeof(value), value); + } + + var name = getRowName(row); + if (name == "this") + return; + + var object = this.getRowObject(row); + object = this.getRealObject(object); + if (object && !(object instanceof jsdIStackFrame)) + { + // unwrappedJSObject.property = unwrappedJSObject + Firebug.CommandLine.evaluate(value, this.context, object, this.context.getGlobalScope(), + function success(result, context) + { + if (FBTrace.DBG_DOM) + FBTrace.sysout("setPropertyValue evaluate success object["+name+"]="+result+" type "+typeof(result), result); + object[name] = result; + }, + function failed(exc, context) + { + try + { + if (FBTrace.DBG_DOM) + FBTrace.sysout("setPropertyValue evaluate failed with exc:"+exc+" object["+name+"]="+value+" type "+typeof(value), exc); + // If the value doesn't parse, then just store it as a string. Some users will + // not realize they're supposed to enter a JavaScript expression and just type + // literal text + object[name] = String(value); // unwrappedJSobject.property = string + } + catch (exc) + { + return; + } + } + ); + } + else if (this.context.stopped) + { + try + { + Firebug.CommandLine.evaluate(name+"="+value, this.context); + } + catch (exc) + { + try + { + // See catch block above... + object[name] = String(value); // unwrappedJSobject.property = string + } + catch (exc) + { + return; + } + } + } + + this.rebuild(true); + this.markChange(); + }, + + highlightRow: function(row) + { + if (this.highlightedRow) + cancelClassTimed(this.highlightedRow, "jumpHighlight", this.context); + + this.highlightedRow = row; + + if (row) + setClassTimed(row, "jumpHighlight", this.context); + },/**/ + + onMouseMove: function(event) + { + var target = event.srcElement || event.target; + + var object = getAncestorByClass(target, "objectLink-element"); + object = object ? object.repObject : null; + + if(object && instanceOf(object, "Element") && object.nodeType == 1) + { + if(object != lastHighlightedObject) + { + Firebug.Inspector.drawBoxModel(object); + object = lastHighlightedObject; + } + } + else + Firebug.Inspector.hideBoxModel(); + + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // extends Panel + + create: function() + { + // TODO: xxxpedro + this.context = Firebug.browser; + + this.objectPath = []; + this.propertyPath = []; + this.viewPath = []; + this.pathIndex = -1; + this.toggles = {}; + + Firebug.Panel.create.apply(this, arguments); + + this.panelNode.style.padding = "0 1px"; + }, + + initialize: function(){ + Firebug.Panel.initialize.apply(this, arguments); + + addEvent(this.panelNode, "mousemove", this.onMouseMove); + }, + + shutdown: function() + { + removeEvent(this.panelNode, "mousemove", this.onMouseMove); + + Firebug.Panel.shutdown.apply(this, arguments); + }, + + /* + destroy: function(state) + { + var view = this.viewPath[this.pathIndex]; + if (view && this.panelNode.scrollTop) + view.scrollTop = this.panelNode.scrollTop; + + if (this.pathIndex) + state.pathIndex = this.pathIndex; + if (this.viewPath) + state.viewPath = this.viewPath; + if (this.propertyPath) + state.propertyPath = this.propertyPath; + + if (this.propertyPath.length > 0 && !this.propertyPath[1]) + state.firstSelection = persistObject(this.getPathObject(1), this.context); + + Firebug.Panel.destroy.apply(this, arguments); + }, + /**/ + + ishow: function(state) + { + if (this.context.loaded && !this.selection) + { + if (!state) + { + this.select(null); + return; + } + if (state.viewPath) + this.viewPath = state.viewPath; + if (state.propertyPath) + this.propertyPath = state.propertyPath; + + var defaultObject = this.getDefaultSelection(this.context); + var selectObject = defaultObject; + + if (state.firstSelection) + { + var restored = state.firstSelection(this.context); + if (restored) + { + selectObject = restored; + this.objectPath = [defaultObject, restored]; + } + else + this.objectPath = [defaultObject]; + } + else + this.objectPath = [defaultObject]; + + if (this.propertyPath.length > 1) + { + for (var i = 1; i < this.propertyPath.length; ++i) + { + var name = this.propertyPath[i]; + if (!name) + continue; + + var object = selectObject; + try + { + selectObject = object[name]; + } + catch (exc) + { + selectObject = null; + } + + if (selectObject) + { + this.objectPath.push(new Property(object, name)); + } + else + { + // If we can't access a property, just stop + this.viewPath.splice(i); + this.propertyPath.splice(i); + this.objectPath.splice(i); + selectObject = this.getPathObject(this.objectPath.length-1); + break; + } + } + } + + var selection = state.pathIndex <= this.objectPath.length-1 + ? this.getPathObject(state.pathIndex) + : this.getPathObject(this.objectPath.length-1); + + this.select(selection); + } + }, + /* + hide: function() + { + var view = this.viewPath[this.pathIndex]; + if (view && this.panelNode.scrollTop) + view.scrollTop = this.panelNode.scrollTop; + }, + /**/ + + supportsObject: function(object) + { + if (object == null) + return 1000; + + if (typeof(object) == "undefined") + return 1000; + else if (object instanceof SourceLink) + return 0; + else + return 1; // just agree to support everything but not agressively. + }, + + refresh: function() + { + this.rebuild(true); + }, + + updateSelection: function(object) + { + var previousIndex = this.pathIndex; + var previousView = previousIndex == -1 ? null : this.viewPath[previousIndex]; + + var newPath = this.pathToAppend; + delete this.pathToAppend; + + var pathIndex = this.findPathObject(object); + if (newPath || pathIndex == -1) + { + this.toggles = {}; + + if (newPath) + { + // Remove everything after the point where we are inserting, so we + // essentially replace it with the new path + if (previousView) + { + if (this.panelNode.scrollTop) + previousView.scrollTop = this.panelNode.scrollTop; + + var start = previousIndex + 1, + // Opera needs the length argument in splice(), otherwise + // it will consider that only one element should be removed + length = this.objectPath.length - start; + + this.objectPath.splice(start, length); + this.propertyPath.splice(start, length); + this.viewPath.splice(start, length); + } + + var value = this.getPathObject(previousIndex); + if (!value) + { + if (FBTrace.DBG_ERRORS) + FBTrace.sysout("dom.updateSelection no pathObject for "+previousIndex+"\n"); + return; + } + + for (var i = 0, length = newPath.length; i < length; ++i) + { + var name = newPath[i]; + var object = value; + try + { + value = value[name]; + } + catch(exc) + { + if (FBTrace.DBG_ERRORS) + FBTrace.sysout("dom.updateSelection FAILS at path_i="+i+" for name:"+name+"\n"); + return; + } + + ++this.pathIndex; + this.objectPath.push(new Property(object, name)); + this.propertyPath.push(name); + this.viewPath.push({toggles: this.toggles, scrollTop: 0}); + } + } + else + { + this.toggles = {}; + + var win = Firebug.browser.window; + //var win = this.context.getGlobalScope(); + if (object === win) + { + this.pathIndex = 0; + this.objectPath = [win]; + this.propertyPath = [null]; + this.viewPath = [{toggles: this.toggles, scrollTop: 0}]; + } + else + { + this.pathIndex = 1; + this.objectPath = [win, object]; + this.propertyPath = [null, null]; + this.viewPath = [ + {toggles: {}, scrollTop: 0}, + {toggles: this.toggles, scrollTop: 0} + ]; + } + } + + this.panelNode.scrollTop = 0; + this.rebuild(); + } + else + { + this.pathIndex = pathIndex; + + var view = this.viewPath[pathIndex]; + this.toggles = view.toggles; + + // Persist the current scroll location + if (previousView && this.panelNode.scrollTop) + previousView.scrollTop = this.panelNode.scrollTop; + + this.rebuild(false, view.scrollTop); + } + }, + + getObjectPath: function(object) + { + return this.objectPath; + }, + + getDefaultSelection: function() + { + return Firebug.browser.window; + //return this.context.getGlobalScope(); + }/*, + + updateOption: function(name, value) + { + const optionMap = {showUserProps: 1, showUserFuncs: 1, showDOMProps: 1, + showDOMFuncs: 1, showDOMConstants: 1}; + if ( optionMap.hasOwnProperty(name) ) + this.rebuild(true); + }, + + getOptionsMenuItems: function() + { + return [ + optionMenu("ShowUserProps", "showUserProps"), + optionMenu("ShowUserFuncs", "showUserFuncs"), + optionMenu("ShowDOMProps", "showDOMProps"), + optionMenu("ShowDOMFuncs", "showDOMFuncs"), + optionMenu("ShowDOMConstants", "showDOMConstants"), + "-", + {label: "Refresh", command: bindFixed(this.rebuild, this, true) } + ]; + }, + + getContextMenuItems: function(object, target) + { + var row = getAncestorByClass(target, "memberRow"); + + var items = []; + + if (row) + { + var rowName = getRowName(row); + var rowObject = this.getRowObject(row); + var rowValue = this.getRowPropertyValue(row); + + var isWatch = hasClass(row, "watchRow"); + var isStackFrame = rowObject instanceof jsdIStackFrame; + + if (typeof(rowValue) == "string" || typeof(rowValue) == "number") + { + // Functions already have a copy item in their context menu + items.push( + "-", + {label: "CopyValue", + command: bindFixed(this.copyProperty, this, row) } + ); + } + + items.push( + "-", + {label: isWatch ? "EditWatch" : (isStackFrame ? "EditVariable" : "EditProperty"), + command: bindFixed(this.editProperty, this, row) } + ); + + if (isWatch || (!isStackFrame && !isDOMMember(rowObject, rowName))) + { + items.push( + {label: isWatch ? "DeleteWatch" : "DeleteProperty", + command: bindFixed(this.deleteProperty, this, row) } + ); + } + } + + items.push( + "-", + {label: "Refresh", command: bindFixed(this.rebuild, this, true) } + ); + + return items; + }, + + getEditor: function(target, value) + { + if (!this.editor) + this.editor = new DOMEditor(this.document); + + return this.editor; + }/**/ +}); + +// ************************************************************************************************ + +// TODO: xxxpedro statusbar +var updateStatusBar = function(panel) +{ + var path = panel.propertyPath; + var index = panel.pathIndex; + + var r = []; + + for (var i=0, l=path.length; i<l; i++) + { + r.push(i==index ? '<a class="fbHover fbButton fbBtnSelected" ' : '<a class="fbHover fbButton" '); + r.push('pathIndex='); + r.push(i); + + if(isIE6) + r.push(' href="javascript:void(0)"'); + + r.push('>'); + r.push(i==0 ? "window" : path[i] || "Object"); + r.push('</a>'); + + if(i < l-1) + r.push('<span class="fbStatusSeparator">></span>'); + } + panel.statusBarNode.innerHTML = r.join(""); +}; + + +var DOMMainPanel = Firebug.DOMPanel = function () {}; + +Firebug.DOMPanel.DirTable = DirTablePlate; + +DOMMainPanel.prototype = extend(Firebug.DOMBasePanel.prototype, +{ + onClickStatusBar: function(event) + { + var target = event.srcElement || event.target; + var element = getAncestorByClass(target, "fbHover"); + + if(element) + { + var pathIndex = element.getAttribute("pathIndex"); + + if(pathIndex) + { + this.select(this.getPathObject(pathIndex)); + } + } + }, + + selectRow: function(row, target) + { + if (!target) + target = row.lastChild.firstChild; + + if (!target || !target.repObject) + return; + + this.pathToAppend = getPath(row); + + // If the object is inside an array, look up its index + var valueBox = row.lastChild.firstChild; + if (hasClass(valueBox, "objectBox-array")) + { + var arrayIndex = FirebugReps.Arr.getItemIndex(target); + this.pathToAppend.push(arrayIndex); + } + + // Make sure we get a fresh status path for the object, since otherwise + // it might find the object in the existing path and not refresh it + //Firebug.chrome.clearStatusPath(); + + this.select(target.repObject, true); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + onClick: function(event) + { + var target = event.srcElement || event.target; + var repNode = Firebug.getRepNode(target); + if (repNode) + { + var row = getAncestorByClass(target, "memberRow"); + if (row) + { + this.selectRow(row, repNode); + cancelEvent(event); + } + } + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // extends Panel + + name: "DOM", + title: "DOM", + searchable: true, + statusSeparator: ">", + + options: { + hasToolButtons: true, + hasStatusBar: true + }, + + create: function() + { + Firebug.DOMBasePanel.prototype.create.apply(this, arguments); + + this.onClick = bind(this.onClick, this); + + //TODO: xxxpedro + this.onClickStatusBar = bind(this.onClickStatusBar, this); + + this.panelNode.style.padding = "0 1px"; + }, + + initialize: function(oldPanelNode) + { + //this.panelNode.addEventListener("click", this.onClick, false); + //dispatch([Firebug.A11yModel], 'onInitializeNode', [this, 'console']); + + Firebug.DOMBasePanel.prototype.initialize.apply(this, arguments); + + addEvent(this.panelNode, "click", this.onClick); + + // TODO: xxxpedro dom + this.ishow(); + + //TODO: xxxpedro + addEvent(this.statusBarNode, "click", this.onClickStatusBar); + }, + + shutdown: function() + { + //this.panelNode.removeEventListener("click", this.onClick, false); + //dispatch([Firebug.A11yModel], 'onDestroyNode', [this, 'console']); + + removeEvent(this.panelNode, "click", this.onClick); + + Firebug.DOMBasePanel.prototype.shutdown.apply(this, arguments); + }/*, + + search: function(text, reverse) + { + if (!text) + { + delete this.currentSearch; + this.highlightRow(null); + return false; + } + + var row; + if (this.currentSearch && text == this.currentSearch.text) + row = this.currentSearch.findNext(true, undefined, reverse, Firebug.searchCaseSensitive); + else + { + function findRow(node) { return getAncestorByClass(node, "memberRow"); } + this.currentSearch = new TextSearch(this.panelNode, findRow); + row = this.currentSearch.find(text, reverse, Firebug.searchCaseSensitive); + } + + if (row) + { + var sel = this.document.defaultView.getSelection(); + sel.removeAllRanges(); + sel.addRange(this.currentSearch.range); + + scrollIntoCenterView(row, this.panelNode); + + this.highlightRow(row); + dispatch([Firebug.A11yModel], 'onDomSearchMatchFound', [this, text, row]); + return true; + } + else + { + dispatch([Firebug.A11yModel], 'onDomSearchMatchFound', [this, text, null]); + return false; + } + }/**/ +}); + +Firebug.registerPanel(DOMMainPanel); + + +// ************************************************************************************************ + + + +// ************************************************************************************************ +// Local Helpers + +var getMembers = function getMembers(object, level) // we expect object to be user-level object wrapped in security blanket +{ + if (!level) + level = 0; + + var ordinals = [], userProps = [], userClasses = [], userFuncs = [], + domProps = [], domFuncs = [], domConstants = []; + + try + { + var domMembers = getDOMMembers(object); + //var domMembers = {}; // TODO: xxxpedro + //var domConstantMap = {}; // TODO: xxxpedro + + if (object.wrappedJSObject) + var insecureObject = object.wrappedJSObject; + else + var insecureObject = object; + + // IE function prototype is not listed in (for..in) + if (isIE && isFunction(object)) + addMember("user", userProps, "prototype", object.prototype, level); + + for (var name in insecureObject) // enumeration is safe + { + if (ignoreVars[name] == 1) // javascript.options.strict says ignoreVars is undefined. + continue; + + var val; + try + { + val = insecureObject[name]; // getter is safe + } + catch (exc) + { + // Sometimes we get exceptions trying to access certain members + if (FBTrace.DBG_ERRORS && FBTrace.DBG_DOM) + FBTrace.sysout("dom.getMembers cannot access "+name, exc); + } + + var ordinal = parseInt(name); + if (ordinal || ordinal == 0) + { + addMember("ordinal", ordinals, name, val, level); + } + else if (isFunction(val)) + { + if (isClassFunction(val) && !(name in domMembers)) + addMember("userClass", userClasses, name, val, level); + else if (name in domMembers) + addMember("domFunction", domFuncs, name, val, level, domMembers[name]); + else + addMember("userFunction", userFuncs, name, val, level); + } + else + { + //TODO: xxxpedro + /* + var getterFunction = insecureObject.__lookupGetter__(name), + setterFunction = insecureObject.__lookupSetter__(name), + prefix = ""; + + if(getterFunction && !setterFunction) + prefix = "get "; + /**/ + + var prefix = ""; + + if (name in domMembers && !(name in domConstantMap)) + addMember("dom", domProps, (prefix+name), val, level, domMembers[name]); + else if (name in domConstantMap) + addMember("dom", domConstants, (prefix+name), val, level); + else + addMember("user", userProps, (prefix+name), val, level); + } + } + } + catch (exc) + { + // Sometimes we get exceptions just from trying to iterate the members + // of certain objects, like StorageList, but don't let that gum up the works + throw exc; + if (FBTrace.DBG_ERRORS && FBTrace.DBG_DOM) + FBTrace.sysout("dom.getMembers FAILS: ", exc); + //throw exc; + } + + function sortName(a, b) { return a.name > b.name ? 1 : -1; } + function sortOrder(a, b) { return a.order > b.order ? 1 : -1; } + + var members = []; + + members.push.apply(members, ordinals); + + Firebug.showUserProps = true; // TODO: xxxpedro + Firebug.showUserFuncs = true; // TODO: xxxpedro + Firebug.showDOMProps = true; + Firebug.showDOMFuncs = true; + Firebug.showDOMConstants = true; + + if (Firebug.showUserProps) + { + userProps.sort(sortName); + members.push.apply(members, userProps); + } + + if (Firebug.showUserFuncs) + { + userClasses.sort(sortName); + members.push.apply(members, userClasses); + + userFuncs.sort(sortName); + members.push.apply(members, userFuncs); + } + + if (Firebug.showDOMProps) + { + domProps.sort(sortName); + members.push.apply(members, domProps); + } + + if (Firebug.showDOMFuncs) + { + domFuncs.sort(sortName); + members.push.apply(members, domFuncs); + } + + if (Firebug.showDOMConstants) + members.push.apply(members, domConstants); + + return members; +}; + +function expandMembers(members, toggles, offset, level) // recursion starts with offset=0, level=0 +{ + var expanded = 0; + for (var i = offset; i < members.length; ++i) + { + var member = members[i]; + if (member.level > level) + break; + + if ( toggles.hasOwnProperty(member.name) ) + { + member.open = "opened"; // member.level <= level && member.name in toggles. + + var newMembers = getMembers(member.value, level+1); // sets newMembers.level to level+1 + + var args = [i+1, 0]; + args.push.apply(args, newMembers); + members.splice.apply(members, args); + + /* + if (FBTrace.DBG_DOM) + { + FBTrace.sysout("expandMembers member.name", member.name); + FBTrace.sysout("expandMembers toggles", toggles); + FBTrace.sysout("expandMembers toggles[member.name]", toggles[member.name]); + FBTrace.sysout("dom.expandedMembers level: "+level+" member", member); + } + /**/ + + expanded += newMembers.length; + i += newMembers.length + expandMembers(members, toggles[member.name], i+1, level+1); + } + } + + return expanded; +} + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + +function isClassFunction(fn) +{ + try + { + for (var name in fn.prototype) + return true; + } catch (exc) {} + return false; +} + +// FIXME: xxxpedro This function is already defined in Lib. If we keep this definition here, it +// will crash IE9 when not running the IE Developer Tool with JavaScript Debugging enabled!!! +// Check if this function is in fact defined in Firebug for Firefox. If so, we should remove +// this from here. The only difference of this function is the IE hack to show up the prototype +// of functions, but Firebug no longer shows the prototype for simple functions. +//var hasProperties = function hasProperties(ob) +//{ +// try +// { +// for (var name in ob) +// return true; +// } catch (exc) {} +// +// // IE function prototype is not listed in (for..in) +// if (isFunction(ob)) return true; +// +// return false; +//}; + +FBL.ErrorCopy = function(message) +{ + this.message = message; +}; + +var addMember = function addMember(type, props, name, value, level, order) +{ + var rep = Firebug.getRep(value); // do this first in case a call to instanceof reveals contents + var tag = rep.shortTag ? rep.shortTag : rep.tag; + + var ErrorCopy = function(){}; //TODO: xxxpedro + + var valueType = typeof(value); + var hasChildren = hasProperties(value) && !(value instanceof ErrorCopy) && + (isFunction(value) || (valueType == "object" && value != null) + || (valueType == "string" && value.length > Firebug.stringCropLength)); + + props.push({ + name: name, + value: value, + type: type, + rowClass: "memberRow-"+type, + open: "", + order: order, + level: level, + indent: level*16, + hasChildren: hasChildren, + tag: tag + }); +}; + +var getWatchRowIndex = function getWatchRowIndex(row) +{ + var index = -1; + for (; row && hasClass(row, "watchRow"); row = row.previousSibling) + ++index; + return index; +}; + +var getRowName = function getRowName(row) +{ + var node = row.firstChild; + return node.textContent ? node.textContent : node.innerText; +}; + +var getRowValue = function getRowValue(row) +{ + return row.lastChild.firstChild.repObject; +}; + +var getRowOwnerObject = function getRowOwnerObject(row) +{ + var parentRow = getParentRow(row); + if (parentRow) + return getRowValue(parentRow); +}; + +var getParentRow = function getParentRow(row) +{ + var level = parseInt(row.getAttribute("level"))-1; + for (row = row.previousSibling; row; row = row.previousSibling) + { + if (parseInt(row.getAttribute("level")) == level) + return row; + } +}; + +var getPath = function getPath(row) +{ + var name = getRowName(row); + var path = [name]; + + var level = parseInt(row.getAttribute("level"))-1; + for (row = row.previousSibling; row; row = row.previousSibling) + { + if (parseInt(row.getAttribute("level")) == level) + { + var name = getRowName(row); + path.splice(0, 0, name); + + --level; + } + } + + return path; +}; + +// ************************************************************************************************ + + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + +// ************************************************************************************************ +// DOM Module + +Firebug.DOM = extend(Firebug.Module, +{ + getPanel: function() + { + return Firebug.chrome ? Firebug.chrome.getPanel("DOM") : null; + } +}); + +Firebug.registerModule(Firebug.DOM); + + +// ************************************************************************************************ +// DOM Panel + +var lastHighlightedObject; + +function DOMSidePanel(){}; + +DOMSidePanel.prototype = extend(Firebug.DOMBasePanel.prototype, +{ + selectRow: function(row, target) + { + if (!target) + target = row.lastChild.firstChild; + + if (!target || !target.repObject) + return; + + this.pathToAppend = getPath(row); + + // If the object is inside an array, look up its index + var valueBox = row.lastChild.firstChild; + if (hasClass(valueBox, "objectBox-array")) + { + var arrayIndex = FirebugReps.Arr.getItemIndex(target); + this.pathToAppend.push(arrayIndex); + } + + // Make sure we get a fresh status path for the object, since otherwise + // it might find the object in the existing path and not refresh it + //Firebug.chrome.clearStatusPath(); + + var object = target.repObject; + + if (instanceOf(object, "Element")) + { + Firebug.HTML.selectTreeNode(ElementCache(object)); + } + else + { + Firebug.chrome.selectPanel("DOM"); + Firebug.chrome.getPanel("DOM").select(object, true); + } + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + onClick: function(event) + { + /* + var target = event.srcElement || event.target; + + var object = getAncestorByClass(target, "objectLink"); + object = object ? object.repObject : null; + + if(!object) return; + + if (instanceOf(object, "Element")) + { + Firebug.HTML.selectTreeNode(ElementCache(object)); + } + else + { + Firebug.chrome.selectPanel("DOM"); + Firebug.chrome.getPanel("DOM").select(object, true); + } + /**/ + + + var target = event.srcElement || event.target; + var repNode = Firebug.getRepNode(target); + if (repNode) + { + var row = getAncestorByClass(target, "memberRow"); + if (row) + { + this.selectRow(row, repNode); + cancelEvent(event); + } + } + /**/ + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // extends Panel + + name: "DOMSidePanel", + parentPanel: "HTML", + title: "DOM", + + options: { + hasToolButtons: true + }, + + isInitialized: false, + + create: function() + { + Firebug.DOMBasePanel.prototype.create.apply(this, arguments); + + this.onClick = bind(this.onClick, this); + }, + + initialize: function(){ + Firebug.DOMBasePanel.prototype.initialize.apply(this, arguments); + + addEvent(this.panelNode, "click", this.onClick); + + // TODO: xxxpedro css2 + var selection = ElementCache.get(Firebug.context.persistedState.selectedHTMLElementId); + if (selection) + this.select(selection, true); + }, + + shutdown: function() + { + removeEvent(this.panelNode, "click", this.onClick); + + Firebug.DOMBasePanel.prototype.shutdown.apply(this, arguments); + }, + + reattach: function(oldChrome) + { + //this.isInitialized = oldChrome.getPanel("DOM").isInitialized; + this.toggles = oldChrome.getPanel("DOMSidePanel").toggles; + } + +}); + +Firebug.registerPanel(DOMSidePanel); + + +// ************************************************************************************************ +}}); + +/* See license.txt for terms of usage */ + +FBL.FBTrace = {}; + +(function() { +// ************************************************************************************************ + +var traceOptions = { + DBG_TIMESTAMP: 1, + DBG_INITIALIZE: 1, + DBG_CHROME: 1, + DBG_ERRORS: 1, + DBG_DISPATCH: 1, + DBG_CSS: 1 +}; + +this.module = null; + +this.initialize = function() +{ + if (!this.messageQueue) + this.messageQueue = []; + + for (var name in traceOptions) + this[name] = traceOptions[name]; +}; + +// ************************************************************************************************ +// FBTrace API + +this.sysout = function() +{ + return this.logFormatted(arguments, ""); +}; + +this.dumpProperties = function(title, object) +{ + return this.logFormatted("dumpProperties() not supported.", "warning"); +}; + +this.dumpStack = function() +{ + return this.logFormatted("dumpStack() not supported.", "warning"); +}; + +this.flush = function(module) +{ + this.module = module; + + var queue = this.messageQueue; + this.messageQueue = []; + + for (var i = 0; i < queue.length; ++i) + this.writeMessage(queue[i][0], queue[i][1], queue[i][2]); +}; + +this.getPanel = function() +{ + return this.module ? this.module.getPanel() : null; +}; + +//************************************************************************************************* + +this.logFormatted = function(objects, className) +{ + var html = this.DBG_TIMESTAMP ? [getTimestamp(), " | "] : []; + var length = objects.length; + + for (var i = 0; i < length; ++i) + { + appendText(" ", html); + + var object = objects[i]; + + if (i == 0) + { + html.push("<b>"); + appendText(object, html); + html.push("</b>"); + } + else + appendText(object, html); + } + + return this.logRow(html, className); +}; + +this.logRow = function(message, className) +{ + var panel = this.getPanel(); + + if (panel && panel.panelNode) + this.writeMessage(message, className); + else + { + this.messageQueue.push([message, className]); + } + + return this.LOG_COMMAND; +}; + +this.writeMessage = function(message, className) +{ + var container = this.getPanel().containerNode; + var isScrolledToBottom = + container.scrollTop + container.offsetHeight >= container.scrollHeight; + + this.writeRow.call(this, message, className); + + if (isScrolledToBottom) + container.scrollTop = container.scrollHeight - container.offsetHeight; +}; + +this.appendRow = function(row) +{ + var container = this.getPanel().panelNode; + container.appendChild(row); +}; + +this.writeRow = function(message, className) +{ + var row = this.getPanel().panelNode.ownerDocument.createElement("div"); + row.className = "logRow" + (className ? " logRow-"+className : ""); + row.innerHTML = message.join(""); + this.appendRow(row); +}; + +//************************************************************************************************* + +function appendText(object, html) +{ + html.push(escapeHTML(objectToString(object))); +}; + +function getTimestamp() +{ + var now = new Date(); + var ms = "" + (now.getMilliseconds() / 1000).toFixed(3); + ms = ms.substr(2); + + return now.toLocaleTimeString() + "." + ms; +}; + +//************************************************************************************************* + +var HTMLtoEntity = +{ + "<": "<", + ">": ">", + "&": "&", + "'": "'", + '"': """ +}; + +function replaceChars(ch) +{ + return HTMLtoEntity[ch]; +}; + +function escapeHTML(value) +{ + return (value+"").replace(/[<>&"']/g, replaceChars); +}; + +//************************************************************************************************* + +function objectToString(object) +{ + try + { + return object+""; + } + catch (exc) + { + return null; + } +}; + +// ************************************************************************************************ +}).apply(FBL.FBTrace); + +/* See license.txt for terms of usage */ + +FBL.ns(function() { with (FBL) { +// ************************************************************************************************ + +// If application isn't in trace mode, the FBTrace panel won't be loaded +if (!Env.Options.enableTrace) return; + +// ************************************************************************************************ +// FBTrace Module + +Firebug.Trace = extend(Firebug.Module, +{ + getPanel: function() + { + return Firebug.chrome ? Firebug.chrome.getPanel("Trace") : null; + }, + + clear: function() + { + this.getPanel().panelNode.innerHTML = ""; + } +}); + +Firebug.registerModule(Firebug.Trace); + + +// ************************************************************************************************ +// FBTrace Panel + +function TracePanel(){}; + +TracePanel.prototype = extend(Firebug.Panel, +{ + name: "Trace", + title: "Trace", + + options: { + hasToolButtons: true, + innerHTMLSync: true + }, + + create: function(){ + Firebug.Panel.create.apply(this, arguments); + + this.clearButton = new Button({ + caption: "Clear", + title: "Clear FBTrace logs", + owner: Firebug.Trace, + onClick: Firebug.Trace.clear + }); + }, + + initialize: function(){ + Firebug.Panel.initialize.apply(this, arguments); + + this.clearButton.initialize(); + }, + + shutdown: function() + { + this.clearButton.shutdown(); + + Firebug.Panel.shutdown.apply(this, arguments); + } + +}); + +Firebug.registerPanel(TracePanel); + +// ************************************************************************************************ +}}); + +/* See license.txt for terms of usage */ + +FBL.ns(function() { with (FBL) { +// ************************************************************************************************ + +// ************************************************************************************************ +// Globals + +var modules = []; +var panelTypes = []; +var panelTypeMap = {}; + +var parentPanelMap = {}; + + +var registerModule = Firebug.registerModule; +var registerPanel = Firebug.registerPanel; + +// ************************************************************************************************ +append(Firebug, +{ + extend: function(fn) + { + if (Firebug.chrome && Firebug.chrome.addPanel) + { + var namespace = ns(fn); + fn.call(namespace, FBL); + } + else + { + setTimeout(function(){Firebug.extend(fn);},100); + } + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Registration + + registerModule: function() + { + registerModule.apply(Firebug, arguments); + + modules.push.apply(modules, arguments); + + dispatch(modules, "initialize", []); + + if (FBTrace.DBG_INITIALIZE) FBTrace.sysout("Firebug.registerModule"); + }, + + registerPanel: function() + { + registerPanel.apply(Firebug, arguments); + + panelTypes.push.apply(panelTypes, arguments); + + for (var i = 0, panelType; panelType = arguments[i]; ++i) + { + // TODO: xxxpedro investigate why Dev Panel throws an error + if (panelType.prototype.name == "Dev") continue; + + panelTypeMap[panelType.prototype.name] = arguments[i]; + + var parentPanelName = panelType.prototype.parentPanel; + if (parentPanelName) + { + parentPanelMap[parentPanelName] = 1; + } + else + { + var panelName = panelType.prototype.name; + var chrome = Firebug.chrome; + chrome.addPanel(panelName); + + // tab click handler + var onTabClick = function onTabClick() + { + chrome.selectPanel(panelName); + return false; + }; + + chrome.addController([chrome.panelMap[panelName].tabNode, "mousedown", onTabClick]); + } + } + + if (FBTrace.DBG_INITIALIZE) + for (var i = 0; i < arguments.length; ++i) + FBTrace.sysout("Firebug.registerPanel", arguments[i].prototype.name); + } + +}); + + + + +// ************************************************************************************************ +}}); + +FBL.ns(function() { with (FBL) { +// ************************************************************************************************ + +FirebugChrome.Skin = +{ + CSS: '.obscured{left:-999999px !important;}.collapsed{display:none;}[collapsed="true"]{display:none;}#fbCSS{padding:0 !important;}.cssPropDisable{float:left;display:block;width:2em;cursor:default;}.infoTip{z-index:2147483647;position:fixed;padding:2px 3px;border:1px solid #CBE087;background:LightYellow;font-family:Monaco,monospace;color:#000000;display:none;white-space:nowrap;pointer-events:none;}.infoTip[active="true"]{display:block;}.infoTipLoading{width:16px;height:16px;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/loading_16.gif) no-repeat;}.infoTipImageBox{font-size:11px;min-width:100px;text-align:center;}.infoTipCaption{font-size:11px;font:Monaco,monospace;}.infoTipLoading > .infoTipImage,.infoTipLoading > .infoTipCaption{display:none;}h1.groupHeader{padding:2px 4px;margin:0 0 4px 0;border-top:1px solid #CCCCCC;border-bottom:1px solid #CCCCCC;background:#eee url(https://getfirebug.com/releases/lite/latest/skin/xp/group.gif) repeat-x;font-size:11px;font-weight:bold;_position:relative;}.inlineEditor,.fixedWidthEditor{z-index:2147483647;position:absolute;display:none;}.inlineEditor{margin-left:-6px;margin-top:-3px;}.textEditorInner,.fixedWidthEditor{margin:0 0 0 0 !important;padding:0;border:none !important;font:inherit;text-decoration:inherit;background-color:#FFFFFF;}.fixedWidthEditor{border-top:1px solid #888888 !important;border-bottom:1px solid #888888 !important;}.textEditorInner{position:relative;top:-7px;left:-5px;outline:none;resize:none;}.textEditorInner1{padding-left:11px;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/textEditorBorders.png) repeat-y;_background:url(https://getfirebug.com/releases/lite/latest/skin/xp/textEditorBorders.gif) repeat-y;_overflow:hidden;}.textEditorInner2{position:relative;padding-right:2px;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/textEditorBorders.png) repeat-y 100% 0;_background:url(https://getfirebug.com/releases/lite/latest/skin/xp/textEditorBorders.gif) repeat-y 100% 0;_position:fixed;}.textEditorTop1{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/textEditorCorners.png) no-repeat 100% 0;margin-left:11px;height:10px;_background:url(https://getfirebug.com/releases/lite/latest/skin/xp/textEditorCorners.gif) no-repeat 100% 0;_overflow:hidden;}.textEditorTop2{position:relative;left:-11px;width:11px;height:10px;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/textEditorCorners.png) no-repeat;_background:url(https://getfirebug.com/releases/lite/latest/skin/xp/textEditorCorners.gif) no-repeat;}.textEditorBottom1{position:relative;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/textEditorCorners.png) no-repeat 100% 100%;margin-left:11px;height:12px;_background:url(https://getfirebug.com/releases/lite/latest/skin/xp/textEditorCorners.gif) no-repeat 100% 100%;}.textEditorBottom2{position:relative;left:-11px;width:11px;height:12px;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/textEditorCorners.png) no-repeat 0 100%;_background:url(https://getfirebug.com/releases/lite/latest/skin/xp/textEditorCorners.gif) no-repeat 0 100%;}.panelNode-css{overflow-x:hidden;}.cssSheet > .insertBefore{height:1.5em;}.cssRule{position:relative;margin:0;padding:1em 0 0 6px;font-family:Monaco,monospace;color:#000000;}.cssRule:first-child{padding-top:6px;}.cssElementRuleContainer{position:relative;}.cssHead{padding-right:150px;}.cssProp{}.cssPropName{color:DarkGreen;}.cssPropValue{margin-left:8px;color:DarkBlue;}.cssOverridden span{text-decoration:line-through;}.cssInheritedRule{}.cssInheritLabel{margin-right:0.5em;font-weight:bold;}.cssRule .objectLink-sourceLink{top:0;}.cssProp.editGroup:hover{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/disable.png) no-repeat 2px 1px;_background:url(https://getfirebug.com/releases/lite/latest/skin/xp/disable.gif) no-repeat 2px 1px;}.cssProp.editGroup.editing{background:none;}.cssProp.disabledStyle{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/disableHover.png) no-repeat 2px 1px;_background:url(https://getfirebug.com/releases/lite/latest/skin/xp/disableHover.gif) no-repeat 2px 1px;opacity:1;color:#CCCCCC;}.disabledStyle .cssPropName,.disabledStyle .cssPropValue{color:#CCCCCC;}.cssPropValue.editing + .cssSemi,.inlineExpander + .cssSemi{display:none;}.cssPropValue.editing{white-space:nowrap;}.stylePropName{font-weight:bold;padding:0 4px 4px 4px;width:50%;}.stylePropValue{width:50%;}.panelNode-net{overflow-x:hidden;}.netTable{width:100%;}.hideCategory-undefined .category-undefined,.hideCategory-html .category-html,.hideCategory-css .category-css,.hideCategory-js .category-js,.hideCategory-image .category-image,.hideCategory-xhr .category-xhr,.hideCategory-flash .category-flash,.hideCategory-txt .category-txt,.hideCategory-bin .category-bin{display:none;}.netHeadRow{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/group.gif) repeat-x #FFFFFF;}.netHeadCol{border-bottom:1px solid #CCCCCC;padding:2px 4px 2px 18px;font-weight:bold;}.netHeadLabel{white-space:nowrap;overflow:hidden;}.netHeaderRow{height:16px;}.netHeaderCell{cursor:pointer;-moz-user-select:none;border-bottom:1px solid #9C9C9C;padding:0 !important;font-weight:bold;background:#BBBBBB url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/tableHeader.gif) repeat-x;white-space:nowrap;}.netHeaderRow > .netHeaderCell:first-child > .netHeaderCellBox{padding:2px 14px 2px 18px;}.netHeaderCellBox{padding:2px 14px 2px 10px;border-left:1px solid #D9D9D9;border-right:1px solid #9C9C9C;}.netHeaderCell:hover:active{background:#959595 url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/tableHeaderActive.gif) repeat-x;}.netHeaderSorted{background:#7D93B2 url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/tableHeaderSorted.gif) repeat-x;}.netHeaderSorted > .netHeaderCellBox{border-right-color:#6B7C93;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/arrowDown.png) no-repeat right;}.netHeaderSorted.sortedAscending > .netHeaderCellBox{background-image:url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/arrowUp.png);}.netHeaderSorted:hover:active{background:#536B90 url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/tableHeaderSortedActive.gif) repeat-x;}.panelNode-net .netRowHeader{display:block;}.netRowHeader{cursor:pointer;display:none;height:15px;margin-right:0 !important;}.netRow .netRowHeader{background-position:5px 1px;}.netRow[breakpoint="true"] .netRowHeader{background-image:url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/breakpoint.png);}.netRow[breakpoint="true"][disabledBreakpoint="true"] .netRowHeader{background-image:url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/breakpointDisabled.png);}.netRow.category-xhr:hover .netRowHeader{background-color:#F6F6F6;}#netBreakpointBar{max-width:38px;}#netHrefCol > .netHeaderCellBox{border-left:0px;}.netRow .netRowHeader{width:3px;}.netInfoRow .netRowHeader{display:table-cell;}.netTable[hiddenCols~=netHrefCol] TD[id="netHrefCol"],.netTable[hiddenCols~=netHrefCol] TD.netHrefCol,.netTable[hiddenCols~=netStatusCol] TD[id="netStatusCol"],.netTable[hiddenCols~=netStatusCol] TD.netStatusCol,.netTable[hiddenCols~=netDomainCol] TD[id="netDomainCol"],.netTable[hiddenCols~=netDomainCol] TD.netDomainCol,.netTable[hiddenCols~=netSizeCol] TD[id="netSizeCol"],.netTable[hiddenCols~=netSizeCol] TD.netSizeCol,.netTable[hiddenCols~=netTimeCol] TD[id="netTimeCol"],.netTable[hiddenCols~=netTimeCol] TD.netTimeCol{display:none;}.netRow{background:LightYellow;}.netRow.loaded{background:#FFFFFF;}.netRow.loaded:hover{background:#EFEFEF;}.netCol{padding:0;vertical-align:top;border-bottom:1px solid #EFEFEF;white-space:nowrap;height:17px;}.netLabel{width:100%;}.netStatusCol{padding-left:10px;color:rgb(128,128,128);}.responseError > .netStatusCol{color:red;}.netDomainCol{padding-left:5px;}.netSizeCol{text-align:right;padding-right:10px;}.netHrefLabel{-moz-box-sizing:padding-box;overflow:hidden;z-index:10;position:absolute;padding-left:18px;padding-top:1px;max-width:15%;font-weight:bold;}.netFullHrefLabel{display:none;-moz-user-select:none;padding-right:10px;padding-bottom:3px;max-width:100%;background:#FFFFFF;z-index:200;}.netHrefCol:hover > .netFullHrefLabel{display:block;}.netRow.loaded:hover .netCol > .netFullHrefLabel{background-color:#EFEFEF;}.useA11y .a11yShowFullLabel{display:block;background-image:none !important;border:1px solid #CBE087;background-color:LightYellow;font-family:Monaco,monospace;color:#000000;font-size:10px;z-index:2147483647;}.netSizeLabel{padding-left:6px;}.netStatusLabel,.netDomainLabel,.netSizeLabel,.netBar{padding:1px 0 2px 0 !important;}.responseError{color:red;}.hasHeaders .netHrefLabel:hover{cursor:pointer;color:blue;text-decoration:underline;}.netLoadingIcon{position:absolute;border:0;margin-left:14px;width:16px;height:16px;background:transparent no-repeat 0 0;background-image:url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/loading_16.gif);display:inline-block;}.loaded .netLoadingIcon{display:none;}.netBar,.netSummaryBar{position:relative;border-right:50px solid transparent;}.netResolvingBar{position:absolute;left:0;top:0;bottom:0;background:#FFFFFF url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/netBarResolving.gif) repeat-x;z-index:60;}.netConnectingBar{position:absolute;left:0;top:0;bottom:0;background:#FFFFFF url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/netBarConnecting.gif) repeat-x;z-index:50;}.netBlockingBar{position:absolute;left:0;top:0;bottom:0;background:#FFFFFF url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/netBarWaiting.gif) repeat-x;z-index:40;}.netSendingBar{position:absolute;left:0;top:0;bottom:0;background:#FFFFFF url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/netBarSending.gif) repeat-x;z-index:30;}.netWaitingBar{position:absolute;left:0;top:0;bottom:0;background:#FFFFFF url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/netBarResponded.gif) repeat-x;z-index:20;min-width:1px;}.netReceivingBar{position:absolute;left:0;top:0;bottom:0;background:#38D63B url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/netBarLoading.gif) repeat-x;z-index:10;}.netWindowLoadBar,.netContentLoadBar{position:absolute;left:0;top:0;bottom:0;width:1px;background-color:red;z-index:70;opacity:0.5;display:none;margin-bottom:-1px;}.netContentLoadBar{background-color:Blue;}.netTimeLabel{-moz-box-sizing:padding-box;position:absolute;top:1px;left:100%;padding-left:6px;color:#444444;min-width:16px;}.loaded .netReceivingBar,.loaded.netReceivingBar{background:#B6B6B6 url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/netBarLoaded.gif) repeat-x;border-color:#B6B6B6;}.fromCache .netReceivingBar,.fromCache.netReceivingBar{background:#D6D6D6 url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/netBarCached.gif) repeat-x;border-color:#D6D6D6;}.netSummaryRow .netTimeLabel,.loaded .netTimeLabel{background:transparent;}.timeInfoTip{width:150px; height:40px}.timeInfoTipBar,.timeInfoTipEventBar{position:relative;display:block;margin:0;opacity:1;height:15px;width:4px;}.timeInfoTipEventBar{width:1px !important;}.timeInfoTipCell.startTime{padding-right:8px;}.timeInfoTipCell.elapsedTime{text-align:right;padding-right:8px;}.sizeInfoLabelCol{font-weight:bold;padding-right:10px;font-family:Lucida Grande,Tahoma,sans-serif;font-size:11px;}.sizeInfoSizeCol{font-weight:bold;}.sizeInfoDetailCol{color:gray;text-align:right;}.sizeInfoDescCol{font-style:italic;}.netSummaryRow .netReceivingBar{background:#BBBBBB;border:none;}.netSummaryLabel{color:#222222;}.netSummaryRow{background:#BBBBBB !important;font-weight:bold;}.netSummaryRow .netBar{border-right-color:#BBBBBB;}.netSummaryRow > .netCol{border-top:1px solid #999999;border-bottom:2px solid;-moz-border-bottom-colors:#EFEFEF #999999;padding-top:1px;padding-bottom:2px;}.netSummaryRow > .netHrefCol:hover{background:transparent !important;}.netCountLabel{padding-left:18px;}.netTotalSizeCol{text-align:right;padding-right:10px;}.netTotalTimeCol{text-align:right;}.netCacheSizeLabel{position:absolute;z-index:1000;left:0;top:0;}.netLimitRow{background:rgb(255,255,225) !important;font-weight:normal;color:black;font-weight:normal;}.netLimitLabel{padding-left:18px;}.netLimitRow > .netCol{border-bottom:2px solid;-moz-border-bottom-colors:#EFEFEF #999999;vertical-align:middle !important;padding-top:2px;padding-bottom:2px;}.netLimitButton{font-size:11px;padding-top:1px;padding-bottom:1px;}.netInfoCol{border-top:1px solid #EEEEEE;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/group.gif) repeat-x #FFFFFF;}.netInfoBody{margin:10px 0 4px 10px;}.netInfoTabs{position:relative;padding-left:17px;}.netInfoTab{position:relative;top:-3px;margin-top:10px;padding:4px 6px;border:1px solid transparent;border-bottom:none;_border:none;font-weight:bold;color:#565656;cursor:pointer;}.netInfoTabSelected{cursor:default !important;border:1px solid #D7D7D7 !important;border-bottom:none !important;-moz-border-radius:4px 4px 0 0;-webkit-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;background-color:#FFFFFF;}.logRow-netInfo.error .netInfoTitle{color:red;}.logRow-netInfo.loading .netInfoResponseText{font-style:italic;color:#888888;}.loading .netInfoResponseHeadersTitle{display:none;}.netInfoResponseSizeLimit{font-family:Lucida Grande,Tahoma,sans-serif;padding-top:10px;font-size:11px;}.netInfoText{display:none;margin:0;border:1px solid #D7D7D7;border-right:none;padding:8px;background-color:#FFFFFF;font-family:Monaco,monospace;white-space:pre-wrap;}.netInfoTextSelected{display:block;}.netInfoParamName{padding-right:10px;font-family:Lucida Grande,Tahoma,sans-serif;font-weight:bold;vertical-align:top;text-align:right;white-space:nowrap;}.netInfoPostText .netInfoParamName{width:1px;}.netInfoParamValue{width:100%;}.netInfoHeadersText,.netInfoPostText,.netInfoPutText{padding-top:0;}.netInfoHeadersGroup,.netInfoPostParams,.netInfoPostSource{margin-bottom:4px;border-bottom:1px solid #D7D7D7;padding-top:8px;padding-bottom:2px;font-family:Lucida Grande,Tahoma,sans-serif;font-weight:bold;color:#565656;}.netInfoPostParamsTable,.netInfoPostPartsTable,.netInfoPostJSONTable,.netInfoPostXMLTable,.netInfoPostSourceTable{margin-bottom:10px;width:100%;}.netInfoPostContentType{color:#bdbdbd;padding-left:50px;font-weight:normal;}.netInfoHtmlPreview{border:0;width:100%;height:100%;}.netHeadersViewSource{color:#bdbdbd;margin-left:200px;font-weight:normal;}.netHeadersViewSource:hover{color:blue;cursor:pointer;}.netActivationRow,.netPageSeparatorRow{background:rgb(229,229,229) !important;font-weight:normal;color:black;}.netActivationLabel{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/infoIcon.png) no-repeat 3px 2px;padding-left:22px;}.netPageSeparatorRow{height:5px !important;}.netPageSeparatorLabel{padding-left:22px;height:5px !important;}.netPageRow{background-color:rgb(255,255,255);}.netPageRow:hover{background:#EFEFEF;}.netPageLabel{padding:1px 0 2px 18px !important;font-weight:bold;}.netActivationRow > .netCol{border-bottom:2px solid;-moz-border-bottom-colors:#EFEFEF #999999;padding-top:2px;padding-bottom:3px;}.twisty,.logRow-errorMessage > .hasTwisty > .errorTitle,.logRow-log > .objectBox-array.hasTwisty,.logRow-spy .spyHead .spyTitle,.logGroup > .logRow,.memberRow.hasChildren > .memberLabelCell > .memberLabel,.hasHeaders .netHrefLabel,.netPageRow > .netCol > .netPageTitle{background-image:url(https://getfirebug.com/releases/lite/latest/skin/xp/tree_open.gif);background-repeat:no-repeat;background-position:2px 2px;min-height:12px;}.logRow-errorMessage > .hasTwisty.opened > .errorTitle,.logRow-log > .objectBox-array.hasTwisty.opened,.logRow-spy.opened .spyHead .spyTitle,.logGroup.opened > .logRow,.memberRow.hasChildren.opened > .memberLabelCell > .memberLabel,.nodeBox.highlightOpen > .nodeLabel > .twisty,.nodeBox.open > .nodeLabel > .twisty,.netRow.opened > .netCol > .netHrefLabel,.netPageRow.opened > .netCol > .netPageTitle{background-image:url(https://getfirebug.com/releases/lite/latest/skin/xp/tree_close.gif);}.twisty{background-position:4px 4px;}* html .logRow-spy .spyHead .spyTitle,* html .logGroup .logGroupLabel,* html .hasChildren .memberLabelCell .memberLabel,* html .hasHeaders .netHrefLabel{background-image:url(https://getfirebug.com/releases/lite/latest/skin/xp/tree_open.gif);background-repeat:no-repeat;background-position:2px 2px;}* html .opened .spyHead .spyTitle,* html .opened .logGroupLabel,* html .opened .memberLabelCell .memberLabel{background-image:url(https://getfirebug.com/releases/lite/latest/skin/xp/tree_close.gif);background-repeat:no-repeat;background-position:2px 2px;}.panelNode-console{overflow-x:hidden;}.objectLink{text-decoration:none;}.objectLink:hover{cursor:pointer;text-decoration:underline;}.logRow{position:relative;margin:0;border-bottom:1px solid #D7D7D7;padding:2px 4px 1px 6px;background-color:#FFFFFF;overflow:hidden !important;}.useA11y .logRow:focus{border-bottom:1px solid #000000 !important;outline:none !important;background-color:#FFFFAD !important;}.useA11y .logRow:focus a.objectLink-sourceLink{background-color:#FFFFAD;}.useA11y .a11yFocus:focus,.useA11y .objectBox:focus{outline:2px solid #FF9933;background-color:#FFFFAD;}.useA11y .objectBox-null:focus,.useA11y .objectBox-undefined:focus{background-color:#888888 !important;}.useA11y .logGroup.opened > .logRow{border-bottom:1px solid #ffffff;}.logGroup{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/group.gif) repeat-x #FFFFFF;padding:0 !important;border:none !important;}.logGroupBody{display:none;margin-left:16px;border-left:1px solid #D7D7D7;border-top:1px solid #D7D7D7;background:#FFFFFF;}.logGroup > .logRow{background-color:transparent !important;font-weight:bold;}.logGroup.opened > .logRow{border-bottom:none;}.logGroup.opened > .logGroupBody{display:block;}.logRow-command > .objectBox-text{font-family:Monaco,monospace;color:#0000FF;white-space:pre-wrap;}.logRow-info,.logRow-warn,.logRow-error,.logRow-assert,.logRow-warningMessage,.logRow-errorMessage{padding-left:22px;background-repeat:no-repeat;background-position:4px 2px;}.logRow-assert,.logRow-warningMessage,.logRow-errorMessage{padding-top:0;padding-bottom:0;}.logRow-info,.logRow-info .objectLink-sourceLink{background-color:#FFFFFF;}.logRow-warn,.logRow-warningMessage,.logRow-warn .objectLink-sourceLink,.logRow-warningMessage .objectLink-sourceLink{background-color:cyan;}.logRow-error,.logRow-assert,.logRow-errorMessage,.logRow-error .objectLink-sourceLink,.logRow-errorMessage .objectLink-sourceLink{background-color:LightYellow;}.logRow-error,.logRow-assert,.logRow-errorMessage{color:#FF0000;}.logRow-info{}.logRow-warn,.logRow-warningMessage{}.logRow-error,.logRow-assert,.logRow-errorMessage{}.objectBox-string,.objectBox-text,.objectBox-number,.objectLink-element,.objectLink-textNode,.objectLink-function,.objectBox-stackTrace,.objectLink-profile{font-family:Monaco,monospace;}.objectBox-string,.objectBox-text,.objectLink-textNode{white-space:pre-wrap;}.objectBox-number,.objectLink-styleRule,.objectLink-element,.objectLink-textNode{color:#000088;}.objectBox-string{color:#FF0000;}.objectLink-function,.objectBox-stackTrace,.objectLink-profile{color:DarkGreen;}.objectBox-null,.objectBox-undefined{padding:0 2px;border:1px solid #666666;background-color:#888888;color:#FFFFFF;}.objectBox-exception{padding:0 2px 0 18px;color:red;}.objectLink-sourceLink{position:absolute;right:4px;top:2px;padding-left:8px;font-family:Lucida Grande,sans-serif;font-weight:bold;color:#0000FF;}.errorTitle{margin-top:0px;margin-bottom:1px;padding-top:2px;padding-bottom:2px;}.errorTrace{margin-left:17px;}.errorSourceBox{margin:2px 0;}.errorSource-none{display:none;}.errorSource-syntax > .errorBreak{visibility:hidden;}.errorSource{cursor:pointer;font-family:Monaco,monospace;color:DarkGreen;}.errorSource:hover{text-decoration:underline;}.errorBreak{cursor:pointer;display:none;margin:0 6px 0 0;width:13px;height:14px;vertical-align:bottom;opacity:0.1;}.hasBreakSwitch .errorBreak{display:inline;}.breakForError .errorBreak{opacity:1;}.assertDescription{margin:0;}.logRow-profile > .logRow > .objectBox-text{font-family:Lucida Grande,Tahoma,sans-serif;color:#000000;}.logRow-profile > .logRow > .objectBox-text:last-child{color:#555555;font-style:italic;}.logRow-profile.opened > .logRow{padding-bottom:4px;}.profilerRunning > .logRow{padding-left:22px !important;}.profileSizer{width:100%;overflow-x:auto;overflow-y:scroll;}.profileTable{border-bottom:1px solid #D7D7D7;padding:0 0 4px 0;}.profileTable tr[odd="1"]{background-color:#F5F5F5;vertical-align:middle;}.profileTable a{vertical-align:middle;}.profileTable td{padding:1px 4px 0 4px;}.headerCell{cursor:pointer;-moz-user-select:none;border-bottom:1px solid #9C9C9C;padding:0 !important;font-weight:bold;}.headerCellBox{padding:2px 4px;border-left:1px solid #D9D9D9;border-right:1px solid #9C9C9C;}.headerCell:hover:active{}.headerSorted{}.headerSorted > .headerCellBox{border-right-color:#6B7C93;}.headerSorted.sortedAscending > .headerCellBox{}.headerSorted:hover:active{}.linkCell{text-align:right;}.linkCell > .objectLink-sourceLink{position:static;}.logRow-stackTrace{padding-top:0;background:#f8f8f8;}.logRow-stackTrace > .objectBox-stackFrame{position:relative;padding-top:2px;}.objectLink-object{font-family:Lucida Grande,sans-serif;font-weight:bold;color:DarkGreen;white-space:pre-wrap;}.objectProp-object{color:DarkGreen;}.objectProps{color:#000;font-weight:normal;}.objectPropName{color:#777;}.objectProps .objectProp-string{color:#f55;}.objectProps .objectProp-number{color:#55a;}.objectProps .objectProp-object{color:#585;}.selectorTag,.selectorId,.selectorClass{font-family:Monaco,monospace;font-weight:normal;}.selectorTag{color:#0000FF;}.selectorId{color:DarkBlue;}.selectorClass{color:red;}.selectorHidden > .selectorTag{color:#5F82D9;}.selectorHidden > .selectorId{color:#888888;}.selectorHidden > .selectorClass{color:#D86060;}.selectorValue{font-family:Lucida Grande,sans-serif;font-style:italic;color:#555555;}.panelNode.searching .logRow{display:none;}.logRow.matched{display:block !important;}.logRow.matching{position:absolute;left:-1000px;top:-1000px;max-width:0;max-height:0;overflow:hidden;}.objectLeftBrace,.objectRightBrace,.objectEqual,.objectComma,.arrayLeftBracket,.arrayRightBracket,.arrayComma{font-family:Monaco,monospace;}.objectLeftBrace,.objectRightBrace,.arrayLeftBracket,.arrayRightBracket{font-weight:bold;}.objectLeftBrace,.arrayLeftBracket{margin-right:4px;}.objectRightBrace,.arrayRightBracket{margin-left:4px;}.logRow-dir{padding:0;}.logRow-errorMessage .hasTwisty .errorTitle,.logRow-spy .spyHead .spyTitle,.logGroup .logRow{cursor:pointer;padding-left:18px;background-repeat:no-repeat;background-position:3px 3px;}.logRow-errorMessage > .hasTwisty > .errorTitle{background-position:2px 3px;}.logRow-errorMessage > .hasTwisty > .errorTitle:hover,.logRow-spy .spyHead .spyTitle:hover,.logGroup > .logRow:hover{text-decoration:underline;}.logRow-spy{padding:0 !important;}.logRow-spy,.logRow-spy .objectLink-sourceLink{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/group.gif) repeat-x #FFFFFF;padding-right:4px;right:0;}.logRow-spy.opened{padding-bottom:4px;border-bottom:none;}.spyTitle{color:#000000;font-weight:bold;-moz-box-sizing:padding-box;overflow:hidden;z-index:100;padding-left:18px;}.spyCol{padding:0;white-space:nowrap;height:16px;}.spyTitleCol:hover > .objectLink-sourceLink,.spyTitleCol:hover > .spyTime,.spyTitleCol:hover > .spyStatus,.spyTitleCol:hover > .spyTitle{display:none;}.spyFullTitle{display:none;-moz-user-select:none;max-width:100%;background-color:Transparent;}.spyTitleCol:hover > .spyFullTitle{display:block;}.spyStatus{padding-left:10px;color:rgb(128,128,128);}.spyTime{margin-left:4px;margin-right:4px;color:rgb(128,128,128);}.spyIcon{margin-right:4px;margin-left:4px;width:16px;height:16px;vertical-align:middle;background:transparent no-repeat 0 0;display:none;}.loading .spyHead .spyRow .spyIcon{background-image:url(https://getfirebug.com/releases/lite/latest/skin/xp/loading_16.gif);display:block;}.logRow-spy.loaded:not(.error) .spyHead .spyRow .spyIcon{width:0;margin:0;}.logRow-spy.error .spyHead .spyRow .spyIcon{background-image:url(https://getfirebug.com/releases/lite/latest/skin/xp/errorIcon-sm.png);display:block;background-position:2px 2px;}.logRow-spy .spyHead .netInfoBody{display:none;}.logRow-spy.opened .spyHead .netInfoBody{margin-top:10px;display:block;}.logRow-spy.error .spyTitle,.logRow-spy.error .spyStatus,.logRow-spy.error .spyTime{color:red;}.logRow-spy.loading .spyResponseText{font-style:italic;color:#888888;}.caption{font-family:Lucida Grande,Tahoma,sans-serif;font-weight:bold;color:#444444;}.warning{padding:10px;font-family:Lucida Grande,Tahoma,sans-serif;font-weight:bold;color:#888888;}.panelNode-dom{overflow-x:hidden !important;}.domTable{font-size:1em;width:100%;table-layout:fixed;background:#fff;}.domTableIE{width:auto;}.memberLabelCell{padding:2px 0 2px 0;vertical-align:top;}.memberValueCell{padding:1px 0 1px 5px;display:block;overflow:hidden;}.memberLabel{display:block;cursor:default;-moz-user-select:none;overflow:hidden;padding-left:18px;background-color:#FFFFFF;text-decoration:none;}.memberRow.hasChildren .memberLabelCell .memberLabel:hover{cursor:pointer;color:blue;text-decoration:underline;}.userLabel{color:#000000;font-weight:bold;}.userClassLabel{color:#E90000;font-weight:bold;}.userFunctionLabel{color:#025E2A;font-weight:bold;}.domLabel{color:#000000;}.domFunctionLabel{color:#025E2A;}.ordinalLabel{color:SlateBlue;font-weight:bold;}.scopesRow{padding:2px 18px;background-color:LightYellow;border-bottom:5px solid #BEBEBE;color:#666666;}.scopesLabel{background-color:LightYellow;}.watchEditCell{padding:2px 18px;background-color:LightYellow;border-bottom:1px solid #BEBEBE;color:#666666;}.editor-watchNewRow,.editor-memberRow{font-family:Monaco,monospace !important;}.editor-memberRow{padding:1px 0 !important;}.editor-watchRow{padding-bottom:0 !important;}.watchRow > .memberLabelCell{font-family:Monaco,monospace;padding-top:1px;padding-bottom:1px;}.watchRow > .memberLabelCell > .memberLabel{background-color:transparent;}.watchRow > .memberValueCell{padding-top:2px;padding-bottom:2px;}.watchRow > .memberLabelCell,.watchRow > .memberValueCell{background-color:#F5F5F5;border-bottom:1px solid #BEBEBE;}.watchToolbox{z-index:2147483647;position:absolute;right:0;padding:1px 2px;}#fbConsole{overflow-x:hidden !important;}#fbCSS{font:1em Monaco,monospace;padding:0 7px;}#fbstylesheetButtons select,#fbScriptButtons select{font:11px Lucida Grande,Tahoma,sans-serif;margin-top:1px;padding-left:3px;background:#fafafa;border:1px inset #fff;width:220px;outline:none;}.Selector{margin-top:10px}.CSSItem{margin-left:4%}.CSSText{padding-left:20px;}.CSSProperty{color:#005500;}.CSSValue{padding-left:5px; color:#000088;}#fbHTMLStatusBar{display:inline;}.fbToolbarButtons{display:none;}.fbStatusSeparator{display:block;float:left;padding-top:4px;}#fbStatusBarBox{display:none;}#fbToolbarContent{display:block;position:absolute;_position:absolute;top:0;padding-top:4px;height:23px;clip:rect(0,2048px,27px,0);}.fbTabMenuTarget{display:none !important;float:left;width:10px;height:10px;margin-top:6px;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/tabMenuTarget.png);}.fbTabMenuTarget:hover{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/tabMenuTargetHover.png);}.fbShadow{float:left;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/shadowAlpha.png) no-repeat bottom right !important;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/shadow2.gif) no-repeat bottom right;margin:10px 0 0 10px !important;margin:10px 0 0 5px;}.fbShadowContent{display:block;position:relative;background-color:#fff;border:1px solid #a9a9a9;top:-6px;left:-6px;}.fbMenu{display:none;position:absolute;font-size:11px;line-height:13px;z-index:2147483647;}.fbMenuContent{padding:2px;}.fbMenuSeparator{display:block;position:relative;padding:1px 18px 0;text-decoration:none;color:#000;cursor:default;background:#ACA899;margin:4px 0;}.fbMenuOption{display:block;position:relative;padding:2px 18px;text-decoration:none;color:#000;cursor:default;}.fbMenuOption:hover{color:#fff;background:#316AC5;}.fbMenuGroup{background:transparent url(https://getfirebug.com/releases/lite/latest/skin/xp/tabMenuPin.png) no-repeat right 0;}.fbMenuGroup:hover{background:#316AC5 url(https://getfirebug.com/releases/lite/latest/skin/xp/tabMenuPin.png) no-repeat right -17px;}.fbMenuGroupSelected{color:#fff;background:#316AC5 url(https://getfirebug.com/releases/lite/latest/skin/xp/tabMenuPin.png) no-repeat right -17px;}.fbMenuChecked{background:transparent url(https://getfirebug.com/releases/lite/latest/skin/xp/tabMenuCheckbox.png) no-repeat 4px 0;}.fbMenuChecked:hover{background:#316AC5 url(https://getfirebug.com/releases/lite/latest/skin/xp/tabMenuCheckbox.png) no-repeat 4px -17px;}.fbMenuRadioSelected{background:transparent url(https://getfirebug.com/releases/lite/latest/skin/xp/tabMenuRadio.png) no-repeat 4px 0;}.fbMenuRadioSelected:hover{background:#316AC5 url(https://getfirebug.com/releases/lite/latest/skin/xp/tabMenuRadio.png) no-repeat 4px -17px;}.fbMenuShortcut{padding-right:85px;}.fbMenuShortcutKey{position:absolute;right:0;top:2px;width:77px;}#fbFirebugMenu{top:22px;left:0;}.fbMenuDisabled{color:#ACA899 !important;}#fbFirebugSettingsMenu{left:245px;top:99px;}#fbConsoleMenu{top:42px;left:48px;}.fbIconButton{display:block;}.fbIconButton{display:block;}.fbIconButton{display:block;float:left;height:20px;width:20px;color:#000;margin-right:2px;text-decoration:none;cursor:default;}.fbIconButton:hover{position:relative;top:-1px;left:-1px;margin-right:0;_margin-right:1px;color:#333;border:1px solid #fff;border-bottom:1px solid #bbb;border-right:1px solid #bbb;}.fbIconPressed{position:relative;margin-right:0;_margin-right:1px;top:0 !important;left:0 !important;height:19px;color:#333 !important;border:1px solid #bbb !important;border-bottom:1px solid #cfcfcf !important;border-right:1px solid #ddd !important;}#fbErrorPopup{position:absolute;right:0;bottom:0;height:19px;width:75px;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/sprite.png) #f1f2ee 0 0;z-index:999;}#fbErrorPopupContent{position:absolute;right:0;top:1px;height:18px;width:75px;_width:74px;border-left:1px solid #aca899;}#fbErrorIndicator{position:absolute;top:2px;right:5px;}.fbBtnInspectActive{background:#aaa;color:#fff !important;}.fbBody{margin:0;padding:0;overflow:hidden;font-family:Lucida Grande,Tahoma,sans-serif;font-size:11px;background:#fff;}.clear{clear:both;}#fbMiniChrome{display:none;right:0;height:27px;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/sprite.png) #f1f2ee 0 0;margin-left:1px;}#fbMiniContent{display:block;position:relative;left:-1px;right:0;top:1px;height:25px;border-left:1px solid #aca899;}#fbToolbarSearch{float:right;border:1px solid #ccc;margin:0 5px 0 0;background:#fff url(https://getfirebug.com/releases/lite/latest/skin/xp/search.png) no-repeat 4px 2px !important;background:#fff url(https://getfirebug.com/releases/lite/latest/skin/xp/search.gif) no-repeat 4px 2px;padding-left:20px;font-size:11px;}#fbToolbarErrors{float:right;margin:1px 4px 0 0;font-size:11px;}#fbLeftToolbarErrors{float:left;margin:7px 0px 0 5px;font-size:11px;}.fbErrors{padding-left:20px;height:14px;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/errorIcon.png) no-repeat !important;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/errorIcon.gif) no-repeat;color:#f00;font-weight:bold;}#fbMiniErrors{display:inline;display:none;float:right;margin:5px 2px 0 5px;}#fbMiniIcon{float:right;margin:3px 4px 0;height:20px;width:20px;float:right;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/sprite.png) 0 -135px;cursor:pointer;}#fbChrome{font-family:Lucida Grande,Tahoma,sans-serif;font-size:11px;position:absolute;_position:static;top:0;left:0;height:100%;width:100%;border-collapse:collapse;border-spacing:0;background:#fff;overflow:hidden;}#fbChrome > tbody > tr > td{padding:0;}#fbTop{height:49px;}#fbToolbar{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/sprite.png) #f1f2ee 0 0;height:27px;font-size:11px;line-height:13px;}#fbPanelBarBox{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/sprite.png) #dbd9c9 0 -27px;height:22px;}#fbContent{height:100%;vertical-align:top;}#fbBottom{height:18px;background:#fff;}#fbToolbarIcon{float:left;padding:0 5px 0;}#fbToolbarIcon a{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/sprite.png) 0 -135px;}#fbToolbarButtons{padding:0 2px 0 5px;}#fbToolbarButtons{padding:0 2px 0 5px;}.fbButton{text-decoration:none;display:block;float:left;color:#000;padding:4px 6px 4px 7px;cursor:default;}.fbButton:hover{color:#333;background:#f5f5ef url(https://getfirebug.com/releases/lite/latest/skin/xp/buttonBg.png);padding:3px 5px 3px 6px;border:1px solid #fff;border-bottom:1px solid #bbb;border-right:1px solid #bbb;}.fbBtnPressed{background:#e3e3db url(https://getfirebug.com/releases/lite/latest/skin/xp/buttonBgHover.png) !important;padding:3px 4px 2px 6px !important;margin:1px 0 0 1px !important;border:1px solid #ACA899 !important;border-color:#ACA899 #ECEBE3 #ECEBE3 #ACA899 !important;}#fbStatusBarBox{top:4px;cursor:default;}.fbToolbarSeparator{overflow:hidden;border:1px solid;border-color:transparent #fff transparent #777;_border-color:#eee #fff #eee #777;height:7px;margin:6px 3px;float:left;}.fbBtnSelected{font-weight:bold;}.fbStatusBar{color:#aca899;}.fbStatusBar a{text-decoration:none;color:black;}.fbStatusBar a:hover{color:blue;cursor:pointer;}#fbWindowButtons{position:absolute;white-space:nowrap;right:0;top:0;height:17px;width:48px;padding:5px;z-index:6;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/sprite.png) #f1f2ee 0 0;}#fbPanelBar1{width:1024px; z-index:8;left:0;white-space:nowrap;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/sprite.png) #dbd9c9 0 -27px;position:absolute;left:4px;}#fbPanelBar2Box{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/sprite.png) #dbd9c9 0 -27px;position:absolute;height:22px;width:300px; z-index:9;right:0;}#fbPanelBar2{position:absolute;width:290px; height:22px;padding-left:4px;}.fbPanel{display:none;}#fbPanelBox1,#fbPanelBox2{max-height:inherit;height:100%;font-size:1em;}#fbPanelBox2{background:#fff;}#fbPanelBox2{width:300px;background:#fff;}#fbPanel2{margin-left:6px;background:#fff;}#fbLargeCommandLine{display:none;position:absolute;z-index:9;top:27px;right:0;width:294px;height:201px;border-width:0;margin:0;padding:2px 0 0 2px;resize:none;outline:none;font-size:11px;overflow:auto;border-top:1px solid #B9B7AF;_right:-1px;_border-left:1px solid #fff;}#fbLargeCommandButtons{display:none;background:#ECE9D8;bottom:0;right:0;width:294px;height:21px;padding-top:1px;position:fixed;border-top:1px solid #ACA899;z-index:9;}#fbSmallCommandLineIcon{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/down.png) no-repeat;position:absolute;right:2px;bottom:3px;z-index:99;}#fbSmallCommandLineIcon:hover{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/downHover.png) no-repeat;}.hide{overflow:hidden !important;position:fixed !important;display:none !important;visibility:hidden !important;}#fbCommand{height:18px;}#fbCommandBox{position:fixed;_position:absolute;width:100%;height:18px;bottom:0;overflow:hidden;z-index:9;background:#fff;border:0;border-top:1px solid #ccc;}#fbCommandIcon{position:absolute;color:#00f;top:2px;left:6px;display:inline;font:11px Monaco,monospace;z-index:10;}#fbCommandLine{position:absolute;width:100%;top:0;left:0;border:0;margin:0;padding:2px 0 2px 32px;font:11px Monaco,monospace;z-index:9;outline:none;}#fbLargeCommandLineIcon{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/up.png) no-repeat;position:absolute;right:1px;bottom:1px;z-index:10;}#fbLargeCommandLineIcon:hover{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/upHover.png) no-repeat;}div.fbFitHeight{overflow:auto;position:relative;}.fbSmallButton{overflow:hidden;width:16px;height:16px;display:block;text-decoration:none;cursor:default;}#fbWindowButtons .fbSmallButton{float:right;}#fbWindow_btClose{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/min.png);}#fbWindow_btClose:hover{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/minHover.png);}#fbWindow_btDetach{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/detach.png);}#fbWindow_btDetach:hover{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/detachHover.png);}#fbWindow_btDeactivate{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/off.png);}#fbWindow_btDeactivate:hover{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/offHover.png);}.fbTab{text-decoration:none;display:none;float:left;width:auto;float:left;cursor:default;font-family:Lucida Grande,Tahoma,sans-serif;font-size:11px;line-height:13px;font-weight:bold;height:22px;color:#565656;}.fbPanelBar span{float:left;}.fbPanelBar .fbTabL,.fbPanelBar .fbTabR{height:22px;width:8px;}.fbPanelBar .fbTabText{padding:4px 1px 0;}a.fbTab:hover{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/sprite.png) 0 -73px;}a.fbTab:hover .fbTabL{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/sprite.png) -16px -96px;}a.fbTab:hover .fbTabR{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/sprite.png) -24px -96px;}.fbSelectedTab{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/sprite.png) #f1f2ee 0 -50px !important;color:#000;}.fbSelectedTab .fbTabL{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/sprite.png) 0 -96px !important;}.fbSelectedTab .fbTabR{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/sprite.png) -8px -96px !important;}#fbHSplitter{position:fixed;_position:absolute;left:0;top:0;width:100%;height:5px;overflow:hidden;cursor:n-resize !important;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/pixel_transparent.gif);z-index:9;}#fbHSplitter.fbOnMovingHSplitter{height:100%;z-index:100;}.fbVSplitter{background:#ece9d8;color:#000;border:1px solid #716f64;border-width:0 1px;border-left-color:#aca899;width:4px;cursor:e-resize;overflow:hidden;right:294px;text-decoration:none;z-index:10;position:absolute;height:100%;top:27px;}div.lineNo{font:1em/1.4545em Monaco,monospace;position:relative;float:left;top:0;left:0;margin:0 5px 0 0;padding:0 5px 0 10px;background:#eee;color:#888;border-right:1px solid #ccc;text-align:right;}.sourceBox{position:absolute;}.sourceCode{font:1em Monaco,monospace;overflow:hidden;white-space:pre;display:inline;}.nodeControl{margin-top:3px;margin-left:-14px;float:left;width:9px;height:9px;overflow:hidden;cursor:default;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/tree_open.gif);_float:none;_display:inline;_position:absolute;}div.nodeMaximized{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/tree_close.gif);}div.objectBox-element{padding:1px 3px;}.objectBox-selector{cursor:default;}.selectedElement{background:highlight;color:#fff !important;}.selectedElement span{color:#fff !important;}* html .selectedElement{position:relative;}@media screen and (-webkit-min-device-pixel-ratio:0){.selectedElement{background:#316AC5;color:#fff !important;}}.logRow *{font-size:1em;}.logRow{position:relative;border-bottom:1px solid #D7D7D7;padding:2px 4px 1px 6px;zbackground-color:#FFFFFF;}.logRow-command{font-family:Monaco,monospace;color:blue;}.objectBox-string,.objectBox-text,.objectBox-number,.objectBox-function,.objectLink-element,.objectLink-textNode,.objectLink-function,.objectBox-stackTrace,.objectLink-profile{font-family:Monaco,monospace;}.objectBox-null{padding:0 2px;border:1px solid #666666;background-color:#888888;color:#FFFFFF;}.objectBox-string{color:red;}.objectBox-number{color:#000088;}.objectBox-function{color:DarkGreen;}.objectBox-object{color:DarkGreen;font-weight:bold;font-family:Lucida Grande,sans-serif;}.objectBox-array{color:#000;}.logRow-info,.logRow-error,.logRow-warn{background:#fff no-repeat 2px 2px;padding-left:20px;padding-bottom:3px;}.logRow-info{background-image:url(https://getfirebug.com/releases/lite/latest/skin/xp/infoIcon.png) !important;background-image:url(https://getfirebug.com/releases/lite/latest/skin/xp/infoIcon.gif);}.logRow-warn{background-color:cyan;background-image:url(https://getfirebug.com/releases/lite/latest/skin/xp/warningIcon.png) !important;background-image:url(https://getfirebug.com/releases/lite/latest/skin/xp/warningIcon.gif);}.logRow-error{background-color:LightYellow;background-image:url(https://getfirebug.com/releases/lite/latest/skin/xp/errorIcon.png) !important;background-image:url(https://getfirebug.com/releases/lite/latest/skin/xp/errorIcon.gif);color:#f00;}.errorMessage{vertical-align:top;color:#f00;}.objectBox-sourceLink{position:absolute;right:4px;top:2px;padding-left:8px;font-family:Lucida Grande,sans-serif;font-weight:bold;color:#0000FF;}.selectorTag,.selectorId,.selectorClass{font-family:Monaco,monospace;font-weight:normal;}.selectorTag{color:#0000FF;}.selectorId{color:DarkBlue;}.selectorClass{color:red;}.objectBox-element{font-family:Monaco,monospace;color:#000088;}.nodeChildren{padding-left:26px;}.nodeTag{color:blue;cursor:pointer;}.nodeValue{color:#FF0000;font-weight:normal;}.nodeText,.nodeComment{margin:0 2px;vertical-align:top;}.nodeText{color:#333333;font-family:Monaco,monospace;}.nodeComment{color:DarkGreen;}.nodeHidden,.nodeHidden *{color:#888888;}.nodeHidden .nodeTag{color:#5F82D9;}.nodeHidden .nodeValue{color:#D86060;}.selectedElement .nodeHidden,.selectedElement .nodeHidden *{color:SkyBlue !important;}.log-object{}.property{position:relative;clear:both;height:15px;}.propertyNameCell{vertical-align:top;float:left;width:28%;position:absolute;left:0;z-index:0;}.propertyValueCell{float:right;width:68%;background:#fff;position:absolute;padding-left:5px;display:table-cell;right:0;z-index:1;}.propertyName{font-weight:bold;}.FirebugPopup{height:100% !important;}.FirebugPopup #fbWindowButtons{display:none !important;}.FirebugPopup #fbHSplitter{display:none !important;}', + HTML: '<table id="fbChrome" cellpadding="0" cellspacing="0" border="0"><tbody><tr><td id="fbTop" colspan="2"><div id="fbWindowButtons"><a id="fbWindow_btDeactivate" class="fbSmallButton fbHover" title="Deactivate Firebug for this web page"> </a><a id="fbWindow_btDetach" class="fbSmallButton fbHover" title="Open Firebug in popup window"> </a><a id="fbWindow_btClose" class="fbSmallButton fbHover" title="Minimize Firebug"> </a></div><div id="fbToolbar"><div id="fbToolbarContent"><span id="fbToolbarIcon"><a id="fbFirebugButton" class="fbIconButton" class="fbHover" target="_blank"> </a></span><span id="fbToolbarButtons"><span id="fbFixedButtons"><a id="fbChrome_btInspect" class="fbButton fbHover" title="Click an element in the page to inspect">Inspect</a></span><span id="fbConsoleButtons" class="fbToolbarButtons"><a id="fbConsole_btClear" class="fbButton fbHover" title="Clear the console">Clear</a></span></span><span id="fbStatusBarBox"><span class="fbToolbarSeparator"></span></span></div></div><div id="fbPanelBarBox"><div id="fbPanelBar1" class="fbPanelBar"><a id="fbConsoleTab" class="fbTab fbHover"><span class="fbTabL"></span><span class="fbTabText">Console</span><span class="fbTabMenuTarget"></span><span class="fbTabR"></span></a><a id="fbHTMLTab" class="fbTab fbHover"><span class="fbTabL"></span><span class="fbTabText">HTML</span><span class="fbTabR"></span></a><a class="fbTab fbHover"><span class="fbTabL"></span><span class="fbTabText">CSS</span><span class="fbTabR"></span></a><a class="fbTab fbHover"><span class="fbTabL"></span><span class="fbTabText">Script</span><span class="fbTabR"></span></a><a class="fbTab fbHover"><span class="fbTabL"></span><span class="fbTabText">DOM</span><span class="fbTabR"></span></a></div><div id="fbPanelBar2Box" class="hide"><div id="fbPanelBar2" class="fbPanelBar"></div></div></div><div id="fbHSplitter"> </div></td></tr><tr id="fbContent"><td id="fbPanelBox1"><div id="fbPanel1" class="fbFitHeight"><div id="fbConsole" class="fbPanel"></div><div id="fbHTML" class="fbPanel"></div></div></td><td id="fbPanelBox2" class="hide"><div id="fbVSplitter" class="fbVSplitter"> </div><div id="fbPanel2" class="fbFitHeight"><div id="fbHTML_Style" class="fbPanel"></div><div id="fbHTML_Layout" class="fbPanel"></div><div id="fbHTML_DOM" class="fbPanel"></div></div><textarea id="fbLargeCommandLine" class="fbFitHeight"></textarea><div id="fbLargeCommandButtons"><a id="fbCommand_btRun" class="fbButton fbHover">Run</a><a id="fbCommand_btClear" class="fbButton fbHover">Clear</a><a id="fbSmallCommandLineIcon" class="fbSmallButton fbHover"></a></div></td></tr><tr id="fbBottom" class="hide"><td id="fbCommand" colspan="2"><div id="fbCommandBox"><div id="fbCommandIcon">>>></div><input id="fbCommandLine" name="fbCommandLine" type="text"/><a id="fbLargeCommandLineIcon" class="fbSmallButton fbHover"></a></div></td></tr></tbody></table><span id="fbMiniChrome"><span id="fbMiniContent"><span id="fbMiniIcon" title="Open Firebug Lite"></span><span id="fbMiniErrors" class="fbErrors"></span></span></span>' +}; + +// ************************************************************************************************ +}}); + +// ************************************************************************************************ +FBL.initialize(); +// ************************************************************************************************ + +})();
\ No newline at end of file |