1 /* See license.txt for terms of usage */ 2 3 var FBL = fbXPCOMUtils; 4 5 try { /*@explore*/ 6 7 (function() { 8 9 // ************************************************************************************************ 10 // Modules 11 12 Components.utils.import("resource://gre/modules/PluralForm.jsm"); 13 14 // ************************************************************************************************ 15 // Constants 16 17 const Cc = Components.classes; 18 const Ci = Components.interfaces; 19 20 this.fbs = Cc["@joehewitt.com/firebug;1"].getService().wrappedJSObject; 21 this.httpObserver = this.CCSV("@joehewitt.com/firebug-http-observer;1", "nsIObserverService"); 22 this.jsd = this.CCSV("@mozilla.org/js/jsd/debugger-service;1", "jsdIDebuggerService"); 23 24 const finder = this.finder = this.CCIN("@mozilla.org/embedcomp/rangefind;1", "nsIFind"); 25 const wm = this.CCSV("@mozilla.org/appshell/window-mediator;1", "nsIWindowMediator"); 26 const ioService = this.CCSV("@mozilla.org/network/io-service;1", "nsIIOService"); 27 const consoleService = Components.classes["@mozilla.org/consoleservice;1"]. 28 getService(Components.interfaces["nsIConsoleService"]); 29 30 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 31 32 const reNotWhitespace = /[^\s]/; 33 const reSplitFile = /:\/{1,3}(.*?)\/([^\/]*?)\/?($|\?.*)/; 34 const reURL = /(([^:]+:)\/{1,2}[^\/]*)(.*?)$/; // This RE and the previous one should changed to be consistent 35 const reChromeCase = /chrome:\/\/([^/]*)\/(.*?)$/; 36 // Globals 37 this.reDataURL = /data:text\/javascript;fileName=([^;]*);baseLineNumber=(\d*?),((?:.*?%0A)|(?:.*))/g; 38 this.reJavascript = /\s*javascript:\s*(.*)/; 39 this.reChrome = /chrome:\/\/([^\/]*)\//; 40 this.reCSS = /\.css$/; 41 this.reFile = /file:\/\/([^\/]*)\//; 42 this.reUpperCase = /[A-Z]/; 43 44 const reSplitLines = /\r\n|\r|\n/; 45 const reFunctionArgNames = /function ([^(]*)\(([^)]*)\)/; 46 const reGuessFunction = /['"]?([$0-9A-Za-z_]+)['"]?\s*[:=]\s*(function|eval|new Function)/; 47 const reWord = /([A-Za-z_$][A-Za-z_$0-9]*)(\.([A-Za-z_$][A-Za-z_$0-9]*))*/; 48 49 const overrideDefaultsWithPersistedValuesTimeout = 500; 50 51 const NS_SEEK_SET = Ci.nsISeekableStream.NS_SEEK_SET; 52 53 // ************************************************************************************************ 54 // Namespaces 55 56 var namespaces = []; 57 58 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 59 60 this.ns = function(fn) 61 { 62 var ns = {}; 63 namespaces.push(fn, ns); 64 return ns; 65 }; 66 67 this.initialize = function() 68 { 69 if (FBTrace.DBG_INITIALIZE) 70 FBTrace.sysout("FBL.initialize BEGIN "+namespaces.length+" namespaces\n"); 71 72 for (var i = 0; i < namespaces.length; i += 2) 73 { 74 var fn = namespaces[i]; 75 var ns = namespaces[i+1]; 76 fn.apply(ns); 77 } 78 79 if (FBTrace.DBG_INITIALIZE) 80 FBTrace.sysout("FBL.initialize END "+namespaces.length+" namespaces\n"); 81 }; 82 83 // ************************************************************************************************ 84 // Basics 85 86 this.bind = function() // fn, thisObject, args => thisObject.fn(args, arguments); 87 { 88 var args = cloneArray(arguments), fn = args.shift(), object = args.shift(); 89 return function() { return fn.apply(object, arrayInsert(cloneArray(args), 0, arguments)); } 90 }; 91 92 this.bindFixed = function() // fn, thisObject, args => thisObject.fn(args); 93 { 94 var args = cloneArray(arguments), fn = args.shift(), object = args.shift(); 95 return function() { return fn.apply(object, args); } 96 }; 97 98 this.extend = function(l, r) 99 { 100 var newOb = {}; 101 for (var n in l) 102 newOb[n] = l[n]; 103 for (var n in r) 104 newOb[n] = r[n]; 105 return newOb; 106 }; 107 108 this.descend = function(prototypeParent, childProperties) 109 { 110 function protoSetter() {}; 111 protoSetter.prototype = prototypeParent; 112 var newOb = new protoSetter(); 113 for (var n in childProperties) 114 newOb[n] = childProperties[n]; 115 return newOb; 116 }; 117 118 // ************************************************************************************************ 119 // Arrays 120 121 this.keys = function(map) // At least sometimes the keys will be on user-level window objects 122 { 123 var keys = []; 124 try 125 { 126 for (var name in map) // enumeration is safe 127 keys.push(name); // name is string, safe 128 } 129 catch (exc) 130 { 131 // Sometimes we get exceptions trying to iterate properties 132 } 133 134 return keys; // return is safe 135 }; 136 137 this.values = function(map) 138 { 139 var values = []; 140 try 141 { 142 for (var name in map) 143 { 144 try 145 { 146 values.push(map[name]); 147 } 148 catch (exc) 149 { 150 // Sometimes we get exceptions trying to access properties 151 if (FBTrace.DBG_ERRORS) 152 FBTrace.dumpPropreties("lib.values FAILED ", exc); 153 } 154 155 } 156 } 157 catch (exc) 158 { 159 // Sometimes we get exceptions trying to iterate properties 160 if (FBTrace.DBG_ERRORS) 161 FBTrace.dumpPropreties("lib.values FAILED ", exc); 162 } 163 164 return values; 165 }; 166 167 this.remove = function(list, item) 168 { 169 for (var i = 0; i < list.length; ++i) 170 { 171 if (list[i] == item) 172 { 173 list.splice(i, 1); 174 break; 175 } 176 } 177 }; 178 179 this.sliceArray = function(array, index) 180 { 181 var slice = []; 182 for (var i = index; i < array.length; ++i) 183 slice.push(array[i]); 184 185 return slice; 186 }; 187 188 function cloneArray(array, fn) 189 { 190 var newArray = []; 191 192 if (fn) 193 for (var i = 0; i < array.length; ++i) 194 newArray.push(fn(array[i])); 195 else 196 for (var i = 0; i < array.length; ++i) 197 newArray.push(array[i]); 198 199 return newArray; 200 } 201 202 function extendArray(array, array2) 203 { 204 var newArray = []; 205 newArray.push.apply(newArray, array); 206 newArray.push.apply(newArray, array2); 207 return newArray; 208 } 209 210 this.extendArray = extendArray; 211 this.cloneArray = cloneArray; 212 213 function arrayInsert(array, index, other) 214 { 215 for (var i = 0; i < other.length; ++i) 216 array.splice(i+index, 0, other[i]); 217 218 return array; 219 } 220 221 this.arrayInsert = arrayInsert; 222 223 // ************************************************************************************************ 224 225 this.safeToString = function(ob) 226 { 227 try 228 { 229 if (!ob) 230 { 231 if (ob == undefined) 232 return 'undefined'; 233 if (ob == null) 234 return 'null'; 235 if (ob == false) 236 return 'false'; 237 return ""; 238 } 239 if (ob && (typeof (ob['toString']) == "function") ) 240 return ob.toString(); 241 if (ob && typeof (ob['toSource']) == 'function') 242 return ob.toSource(); 243 /* https://bugzilla.mozilla.org/show_bug.cgi?id=522590 */ 244 var str = "["; 245 for (var p in ob) 246 str += p+','; 247 return str + ']'; 248 249 } 250 catch (exc) 251 { 252 if (FBTrace.DBG_ERRORS) 253 FBTrace.sysout("safeToString FAILS "+exc, exc); 254 } 255 return "[unsupported: no toString() function in type "+typeof(ob)+"]"; 256 }; 257 258 // ************************************************************************************************ 259 260 this.hasProperties = function(ob) 261 { 262 try 263 { 264 for (var name in ob) 265 return true; 266 } catch (exc) {} 267 return false; 268 }; 269 270 // ************************************************************************************************ 271 272 this.convertToUnicode = function(text, charset) 273 { 274 if (!text) 275 return ""; 276 277 try 278 { 279 var conv = this.CCSV("@mozilla.org/intl/scriptableunicodeconverter", "nsIScriptableUnicodeConverter"); 280 conv.charset = charset ? charset : "UTF-8"; 281 return conv.ConvertToUnicode(text); 282 } 283 catch (exc) 284 { 285 if (FBTrace.DBG_ERRORS) 286 FBTrace.sysout("lib.convertToUnicode: fails: for charset "+charset+" conv.charset:"+conv.charset+" exc: "+exc, exc); 287 // the exception is worthless, make up a new one 288 throw new Error("Firebug failed to convert to unicode using charset: "+conv.charset+" in @mozilla.org/intl/scriptableunicodeconverter"); 289 } 290 }; 291 292 this.convertFromUnicode = function(text, charset) 293 { 294 if (!text) 295 return ""; 296 297 try 298 { 299 var conv = this.CCSV("@mozilla.org/intl/scriptableunicodeconverter", "nsIScriptableUnicodeConverter"); 300 conv.charset = charset ? charset : "UTF-8"; 301 return conv.ConvertFromUnicode(text); 302 } 303 catch (exc) 304 { 305 if (FBTrace.DBG_ERRORS) 306 FBTrace.sysout("lib.convertFromUnicode: fails: for charset "+charset+" conv.charset:"+conv.charset+" exc: "+exc, exc); 307 } 308 }; 309 310 this.getPlatformName = function() 311 { 312 return this.CCSV("@mozilla.org/xre/app-info;1", "nsIXULRuntime").OS; 313 }; 314 315 this.beep = function() 316 { 317 var sounder = this.CCSV("@mozilla.org/sound;1", "nsISound"); 318 sounder.beep(); 319 }; 320 321 this.getUniqueId = function() { 322 return this.getRandomInt(0,65536); 323 } 324 325 this.getRandomInt = function(min, max) { 326 return Math.floor(Math.random() * (max - min + 1) + min); 327 } 328 329 // ************************************************************************************************ 330 331 this.createStyleSheet = function(doc, url) 332 { 333 var style = doc.createElementNS("http://www.w3.org/1999/xhtml", "style"); 334 style.setAttribute("charset","utf-8"); 335 style.setAttribute("type", "text/css"); 336 style.innerHTML = this.getResource(url); 337 FBL.unwrapObject(style).firebugIgnore = true; 338 return style; 339 } 340 341 this.addStyleSheet = function(doc, style) 342 { 343 var heads = doc.getElementsByTagName("head"); 344 if (heads.length) 345 heads[0].appendChild(style); 346 else 347 doc.documentElement.appendChild(style); 348 }; 349 350 this.addScript = function(doc, id, src) 351 { 352 var element = doc.createElementNS("http://www.w3.org/1999/xhtml", "html:script"); 353 element.setAttribute("type", "text/javascript"); 354 element.setAttribute("id", id); 355 if (!FBTrace.DBG_CONSOLE) 356 FBL.unwrapObject(element).firebugIgnore = true; 357 358 element.innerHTML = src; 359 if (doc.documentElement) 360 doc.documentElement.appendChild(element); 361 else 362 { 363 // See issue 1079, the svg test case gives this error 364 if (FBTrace.DBG_ERRORS) 365 FBTrace.sysout("lib.addScript doc has no documentElement:", doc); 366 } 367 return element; 368 } 369 370 // ************************************************************************************************ 371 372 this.isAncestorIgnored = function(node) 373 { 374 for (var parent = node; parent; parent = parent.parentNode) 375 { 376 if (FBL.unwrapObject(parent).firebugIgnore) 377 return true; 378 } 379 380 return false; 381 } 382 383 // ************************************************************************************************ 384 // Localization 385 386 /* 387 * $STR - intended for localization of a static string. 388 * $STRF - intended for localization of a string with dynamically inserted values. 389 * $STRP - intended for localization of a string with dynamically plural forms. 390 * 391 * Notes: 392 * 1) Name with _ in place of spaces is the key in the firebug.properties file. 393 * 2) If the specified key isn't localized for particular language, both methods use 394 * the part after the last dot (in the specified name) as the return value. 395 * 396 * Examples: 397 * $STR("Label"); - search for key "Label" within the firebug.properties file 398 * and returns its value. If the key doesn't exist returns "Label". 399 * 400 * $STR("Button Label"); - search for key "Button_Label" withing the firebug.properties 401 * file. If the key doesn't exist returns "Button Label". 402 * 403 * $STR("net.Response Header"); - search for key "net.Response_Header". If the key doesn't 404 * exist returns "Response Header". 405 * 406 * firebug.properties: 407 * net.timing.Request_Time=Request Time: %S [%S] 408 * 409 * var param1 = 10; 410 * var param2 = "ms"; 411 * $STRF("net.timing.Request Time", param1, param2); -> "Request Time: 10 [ms]" 412 * 413 * - search for key "net.timing.Request_Time" within the firebug.properties file. Parameters 414 * are inserted at specified places (%S) in the same order as they are passed. If the 415 * key doesn't exist the method returns "Request Time". 416 */ 417 function $STR(name, bundle) 418 { 419 try 420 { 421 if (typeof bundle == "string") 422 bundle = document.getElementById(bundle); 423 424 if (bundle) 425 return bundle.getString(name.replace(' ', '_', "g")); 426 427 if (Firebug) 428 return Firebug.getStringBundle().GetStringFromName(name.replace(' ', '_', "g")); 429 } 430 catch (err) 431 { 432 if (FBTrace.DBG_LOCALE) 433 { 434 FBTrace.sysout("lib.getString: " + name + "\n"); 435 FBTrace.sysout("lib.getString FAILS ", err); 436 } 437 } 438 439 // XXXjjb apparently we get to this code if we get an exception above...is that best we can do? 440 441 // Use only the label after last dot. 442 var index = name.lastIndexOf("."); 443 if (index > 0 && name.charAt(index-1) != "\\") 444 name = name.substr(index + 1); 445 name = name.replace("_", " "); 446 447 return name; 448 } 449 450 function $STRF(name, args, bundle) 451 { 452 try 453 { 454 // xxxHonza: Workaround for #485511 455 if (!bundle) 456 bundle = "strings_firebug"; 457 458 if (typeof bundle == "string") 459 bundle = document.getElementById(bundle); 460 461 if (bundle) 462 return bundle.getFormattedString(name.replace(' ', '_', "g"), args); 463 else 464 return Firebug.getStringBundle().formatStringFromName(name.replace(' ', '_', "g"), args, args.length); 465 } 466 catch (err) 467 { 468 if (FBTrace.DBG_LOCALE) 469 { 470 FBTrace.sysout("lib.getString: " + name + "\n"); 471 FBTrace.sysout("lib.getString FAILS ", err); 472 } 473 } 474 475 // Use only the label after last dot. 476 var index = name.lastIndexOf("."); 477 if (index > 0) 478 name = name.substr(index + 1); 479 480 return name; 481 } 482 483 function $STRP(name, args, index, bundle) 484 { 485 // xxxHonza: 486 // pluralRule from chrome://global/locale/intl.properties for Chinese is 1, 487 // which is wrong, it should be 0. 488 489 var getPluralForm = PluralForm.get; 490 var getNumForms = PluralForm.numForms; 491 492 // Get custom plural rule; otherwise the rule from chrome://global/locale/intl.properties 493 // (depends on the current locale) is used. 494 var pluralRule = Firebug.getPluralRule(); 495 if (!isNaN(parseInt(pluralRule, 10))) 496 [getPluralForm, getNumForms] = PluralForm.makeGetter(pluralRule); 497 498 // Index of the argument with plural form (there must be only one arg that needs plural form). 499 if (!index) 500 index = 0; 501 502 var margs = []; 503 var numForms = getNumForms(); 504 505 // Repeat the args for numForms time(s) 506 for (var i = 0; i < numForms; i++) 507 margs = margs.concat(args); 508 509 // Get proper plural form from the string (depends on the current Firefox locale). 510 var translatedString = $STRF(name, margs, bundle); 511 if (translatedString.search(";") > 0) 512 return getPluralForm(args[index], translatedString); 513 514 // translatedString contains no ";", either rule 0 or getString fails 515 return translatedString; 516 } 517 518 this.$STR = $STR; 519 this.$STRF = $STRF; 520 this.$STRP = $STRP; 521 522 /* 523 * Use the current value of the attribute as a key to look up the localized value. 524 */ 525 this.internationalize = function(element, attr, args) 526 { 527 if (typeof element == "string") 528 element = document.getElementById(element); 529 530 if (element) 531 { 532 var xulString = element.getAttribute(attr); 533 if (xulString) 534 { 535 var localized = args ? $STRF(xulString, args) : $STR(xulString); 536 537 // Set localized value of the attribute. 538 element.setAttribute(attr, localized); 539 } 540 } 541 else 542 { 543 if (FBTrace.DBG_LOCALE) 544 FBTrace.sysout("Failed to internationalize element with attr "+attr+' args:'+args); 545 } 546 } 547 548 // ************************************************************************************************ 549 // Visibility 550 551 this.isVisible = function(elt) 552 { 553 if (isElementXUL(elt)) 554 { 555 //FBTrace.sysout("isVisible elt.offsetWidth: "+elt.offsetWidth+" offsetHeight:"+ elt.offsetHeight+" localName:"+ elt.localName+" nameSpace:"+elt.nameSpaceURI+"\n"); 556 return (!elt.hidden && !elt.collapsed); 557 } 558 return elt.offsetWidth > 0 || elt.offsetHeight > 0 || elt.localName in invisibleTags || isElementSVG(elt) || isElementMathML(elt); 559 }; 560 561 this.collapse = function(elt, collapsed) 562 { 563 elt.setAttribute("collapsed", collapsed ? "true" : "false"); 564 }; 565 566 this.obscure = function(elt, obscured) 567 { 568 if (obscured) 569 this.setClass(elt, "obscured"); 570 else 571 this.removeClass(elt, "obscured"); 572 }; 573 574 this.hide = function(elt, hidden) 575 { 576 elt.style.visibility = hidden ? "hidden" : "visible"; 577 }; 578 579 this.clearNode = function(node) 580 { 581 this.clearDomplate(node); 582 node.innerHTML = ""; 583 }; 584 585 this.eraseNode = function(node) 586 { 587 this.clearDomplate(node); 588 while (node.lastChild) 589 node.removeChild(node.lastChild); 590 }; 591 592 this.clearDomplate = function(node) 593 { 594 if (!Firebug.clearDomplate) 595 return; 596 597 var walker = node.ownerDocument.createTreeWalker(node, 598 Ci.nsIDOMNodeFilter.SHOW_ALL, null, true); 599 600 while (node) 601 { 602 if (node.repObject) 603 node.repObject = null; 604 605 if (node.stackTrace) 606 node.stackTrace = null; 607 608 if (node.checked) 609 node.checked = null; 610 611 if (node.domObject) 612 node.domObject = null; 613 614 if (node.toggles) 615 node.toggles = null; 616 617 if (node.domPanel) 618 node.domPanel = null; 619 620 node = walker.nextNode(); 621 } 622 } 623 624 // ************************************************************************************************ 625 // Window iteration 626 627 this.iterateWindows = function(win, handler) 628 { 629 if (!win || !win.document) 630 return; 631 632 handler(win); 633 634 if (win == top || !win.frames) return; // XXXjjb hack for chromeBug 635 636 for (var i = 0; i < win.frames.length; ++i) 637 { 638 var subWin = win.frames[i]; 639 if (subWin != win) 640 this.iterateWindows(subWin, handler); 641 } 642 }; 643 644 this.getRootWindow = function(win) 645 { 646 for (; win; win = win.parent) 647 { 648 if (!win.parent || win == win.parent || !(win.parent instanceof Window) ) 649 return win; 650 } 651 return null; 652 }; 653 654 // ************************************************************************************************ 655 // CSS classes 656 657 var classNameReCache={}; 658 this.hasClass = function(node, name) 659 { 660 if (!node || node.nodeType != 1 || !node.className || name == '') 661 return false; 662 663 if (name.indexOf(" ") != -1) 664 { 665 var classes = name.split(" "), len = classes.length, found=false; 666 for (var i = 0; i < len; i++) 667 { 668 var cls = classes[i].trim(); 669 if (cls != "") 670 { 671 if (this.hasClass(node, cls) == false) 672 return false; 673 found = true; 674 } 675 } 676 return found; 677 } 678 679 var re; 680 if (name.indexOf("-") == -1) 681 re = classNameReCache[name] = classNameReCache[name] || new RegExp('(^|\\s)' + name + '(\\s|$)', "g"); 682 else // XXXsroussey don't cache these, they are often setting values. Should be using setUserData/getUserData??? 683 re = new RegExp('(^|\\s)' + name + '(\\s|$)', "g") 684 return node.className.search(re) != -1; 685 }; 686 687 this.setClass = function(node, name) 688 { 689 if (!node || node.nodeType != 1 || name == '') 690 return; 691 692 if (name.indexOf(" ") != -1) 693 { 694 var classes = name.split(" "), len = classes.length; 695 for (var i = 0; i < len; i++) 696 { 697 var cls = classes[i].trim(); 698 if (cls != "") 699 { 700 this.setClass(node, cls); 701 } 702 } 703 return; 704 } 705 if (!this.hasClass(node, name)) 706 node.className = node.className.trim() + " " + name; 707 }; 708 709 this.getClassValue = function(node, name) 710 { 711 var re = new RegExp(name+"-([^ ]+)"); 712 var m = re.exec(node.className); 713 return m ? m[1] : ""; 714 }; 715 716 this.removeClass = function(node, name) 717 { 718 if (!node || node.nodeType != 1 || node.className == '' || name == '') 719 return; 720 721 if (name.indexOf(" ") != -1) 722 { 723 var classes = name.split(" "), len = classes.length; 724 for (var i = 0; i < len; i++) 725 { 726 var cls = classes[i].trim(); 727 if (cls != "") 728 { 729 if (this.hasClass(node, cls) == false) 730 this.removeClass(node, cls); 731 } 732 } 733 return; 734 } 735 736 var re; 737 if (name.indexOf("-") == -1) 738 re = classNameReCache[name] = classNameReCache[name] || new RegExp('(^|\\s)' + name + '(\\s|$)', "g"); 739 else // XXXsroussey don't cache these, they are often setting values. Should be using setUserData/getUserData??? 740 re = new RegExp('(^|\\s)' + name + '(\\s|$)', "g") 741 742 node.className = node.className.replace(re, " "); 743 744 }; 745 746 this.toggleClass = function(elt, name) 747 { 748 if (this.hasClass(elt, name)) 749 this.removeClass(elt, name); 750 else 751 this.setClass(elt, name); 752 }; 753 754 this.setClassTimed = function(elt, name, context, timeout) 755 { 756 if (!timeout) 757 timeout = 1300; 758 759 if (elt.__setClassTimeout) 760 context.clearTimeout(elt.__setClassTimeout); 761 else 762 this.setClass(elt, name); 763 764 if (!this.isVisible(elt)) 765 { 766 if (elt.__invisibleAtSetPoint) 767 elt.__invisibleAtSetPoint--; 768 else 769 elt.__invisibleAtSetPoint = 5; 770 } 771 else 772 { 773 delete elt.__invisibleAtSetPoint; 774 } 775 776 elt.__setClassTimeout = context.setTimeout(function() 777 { 778 delete elt.__setClassTimeout; 779 780 if (elt.__invisibleAtSetPoint) 781 FBL.setClassTimed(elt, name, context, timeout); 782 else 783 { 784 delete elt.__invisibleAtSetPoint; 785 FBL.removeClass(elt, name); 786 } 787 }, timeout); 788 }; 789 790 this.cancelClassTimed = function(elt, name, context) 791 { 792 if (elt.__setClassTimeout) 793 { 794 FBL.removeClass(elt, name); 795 context.clearTimeout(elt.__setClassTimeout); 796 delete elt.__setClassTimeout; 797 } 798 }; 799 800 // ************************************************************************************************ 801 // DOM queries 802 803 this.$ = function(id, doc) 804 { 805 if (doc) 806 return doc.getElementById(id); 807 else 808 return document.getElementById(id); 809 }; 810 811 this.getChildByClass = function(node) // ,classname, classname, classname... 812 { 813 for (var i = 1; i < arguments.length; ++i) 814 { 815 var className = arguments[i]; 816 var child = node.firstChild; 817 node = null; 818 for (; child; child = child.nextSibling) 819 { 820 if (this.hasClass(child, className)) 821 { 822 node = child; 823 break; 824 } 825 } 826 } 827 828 return node; 829 }; 830 831 this.getAncestorByClass = function(node, className) 832 { 833 for (var parent = node; parent; parent = parent.parentNode) 834 { 835 if (this.hasClass(parent, className)) 836 return parent; 837 } 838 839 return null; 840 }; 841 842 /* @Deprecated Use native Firefox: node.getElementsByClassName(names).item(0) */ 843 this.getElementByClass = function(node, className) // className, className, ... 844 { 845 return FBL.getElementsByClass.apply(this,arguments).item(0); 846 }; 847 848 /* @Deprecated Use native Firefox: node.getElementsByClassName(names) */ 849 this.getElementsByClass = function(node, className) // className, className, ... 850 { 851 var args = cloneArray(arguments); args.splice(0, 1); 852 var className = args.join(" "); 853 return node.getElementsByClassName(className); 854 }; 855 856 this.getElementsByAttribute = function(node, attrName, attrValue) 857 { 858 function iteratorHelper(node, attrName, attrValue, result) 859 { 860 for (var child = node.firstChild; child; child = child.nextSibling) 861 { 862 if (child.getAttribute(attrName) == attrValue) 863 result.push(child); 864 865 iteratorHelper(child, attrName, attrValue, result); 866 } 867 } 868 869 var result = []; 870 iteratorHelper(node, attrName, attrValue, result); 871 return result; 872 } 873 874 this.isAncestor = function(node, potentialAncestor) 875 { 876 for (var parent = node; parent; parent = parent.parentNode) 877 { 878 if (parent == potentialAncestor) 879 return true; 880 } 881 882 return false; 883 }; 884 885 this.getNextElement = function(node) 886 { 887 while (node && node.nodeType != 1) 888 node = node.nextSibling; 889 890 return node; 891 }; 892 893 this.getPreviousElement = function(node) 894 { 895 while (node && node.nodeType != 1) 896 node = node.previousSibling; 897 898 return node; 899 }; 900 901 this.getBody = function(doc) 902 { 903 if (doc.body) 904 return doc.body; 905 906 var body = doc.getElementsByTagName("body")[0]; 907 if (body) 908 return body; 909 910 return doc.documentElement; // For non-HTML docs 911 }; 912 913 this.findNextDown = function(node, criteria) 914 { 915 if (!node) 916 return null; 917 918 for (var child = node.firstChild; child; child = child.nextSibling) 919 { 920 if (criteria(child)) 921 return child; 922 923 var next = this.findNextDown(child, criteria); 924 if (next) 925 return next; 926 } 927 }; 928 929 this.findPreviousUp = function(node, criteria) 930 { 931 if (!node) 932 return null; 933 934 for (var child = node.lastChild; child; child = child.previousSibling) 935 { 936 var next = this.findPreviousUp(child, criteria); 937 if (next) 938 return next; 939 940 if (criteria(child)) 941 return child; 942 } 943 }; 944 945 this.findNext = function(node, criteria, upOnly, maxRoot) 946 { 947 if (!node) 948 return null; 949 950 if (!upOnly) 951 { 952 var next = this.findNextDown(node, criteria); 953 if (next) 954 return next; 955 } 956 957 for (var sib = node.nextSibling; sib; sib = sib.nextSibling) 958 { 959 if (criteria(sib)) 960 return sib; 961 962 var next = this.findNextDown(sib, criteria); 963 if (next) 964 return next; 965 } 966 967 if (node.parentNode && node.parentNode != maxRoot) 968 return this.findNext(node.parentNode, criteria, true); 969 }; 970 971 this.findPrevious = function(node, criteria, downOnly, maxRoot) 972 { 973 if (!node) 974 return null; 975 976 for (var sib = node.previousSibling; sib; sib = sib.previousSibling) 977 { 978 var prev = this.findPreviousUp(sib, criteria); 979 if (prev) 980 return prev; 981 982 if (criteria(sib)) 983 return sib; 984 } 985 986 if (!downOnly) 987 { 988 var next = this.findPreviousUp(node, criteria); 989 if (next) 990 return next; 991 } 992 993 if (node.parentNode && node.parentNode != maxRoot) 994 { 995 if (criteria(node.parentNode)) 996 return node.parentNode; 997 998 return this.findPrevious(node.parentNode, criteria, true); 999 } 1000 }; 1001 1002 this.getNextByClass = function(root, state) 1003 { 1004 function iter(node) { return node.nodeType == 1 && FBL.hasClass(node, state); } 1005 return this.findNext(root, iter); 1006 }; 1007 1008 this.getPreviousByClass = function(root, state) 1009 { 1010 function iter(node) { return node.nodeType == 1 && FBL.hasClass(node, state); } 1011 return this.findPrevious(root, iter); 1012 }; 1013 1014 this.hasChildElements = function(node) 1015 { 1016 if (node.contentDocument) // iframes 1017 return true; 1018 1019 for (var child = node.firstChild; child; child = child.nextSibling) 1020 { 1021 if (child.nodeType == 1) 1022 return true; 1023 } 1024 1025 return false; 1026 }; 1027 1028 this.isElement = function(o) 1029 { 1030 try { 1031 return o && o instanceof Element; 1032 } 1033 catch (ex) { 1034 return false; 1035 } 1036 }; 1037 1038 this.isNode = function(o) 1039 { 1040 try { 1041 return o && o instanceof Node; 1042 } 1043 catch (ex) { 1044 return false; 1045 } 1046 }; 1047 1048 this.XW_instanceof = function(obj, type) // Cross Window instanceof; type is local to this window 1049 { 1050 if (obj instanceof type) 1051 return true; // within-window test 1052 1053 if (!type) 1054 return false; 1055 if (!obj) 1056 return (type == "undefined"); 1057 1058 // compare strings: obj constructor.name to type.name. 1059 // This is not perfect, we should compare type.prototype to object.__proto__, but mostly code does not change the constructor object. 1060 do 1061 { 1062 if (obj.constructor && obj.constructor.name == type.name) // then the function that constructed us is the argument 1063 return true; 1064 } 1065 while(obj = obj.__proto__); // walk the prototype chain. 1066 return false; 1067 // https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Property_Inheritance_Revisited/Determining_Instance_Relationships 1068 } 1069 1070 // ************************************************************************************************ 1071 // DOM Modification 1072 1073 this.setOuterHTML = function(element, html) 1074 { 1075 var doc = element.ownerDocument; 1076 var range = doc.createRange(); 1077 range.selectNode(element || doc.documentElement); 1078 try 1079 { 1080 var fragment = range.createContextualFragment(html); 1081 var first = fragment.firstChild; 1082 var last = fragment.lastChild; 1083 element.parentNode.replaceChild(fragment, element); 1084 return [first, last]; 1085 } catch (e) 1086 { 1087 return [element,element] 1088 } 1089 }; 1090 1091 this.appendInnerHTML = function(element, html, referenceElement) 1092 { 1093 var doc = element.ownerDocument; 1094 var range = doc.createRange(); // a helper object 1095 range.selectNodeContents(element); // the environment to interpret the html 1096 1097 var fragment = range.createContextualFragment(html); // parse 1098 var firstChild = fragment.firstChild; 1099 element.insertBefore(fragment, referenceElement); 1100 return firstChild; 1101 }; 1102 1103 this.insertTextIntoElement = function(element, text) 1104 { 1105 var command = "cmd_insertText"; 1106 1107 var controller = element.controllers.getControllerForCommand(command); 1108 if (!controller || !controller.isCommandEnabled(command)) 1109 return; 1110 1111 var params = this.CCIN("@mozilla.org/embedcomp/command-params;1", "nsICommandParams"); 1112 params.setStringValue("state_data", text); 1113 1114 controller = this.QI(controller, Ci.nsICommandController); 1115 controller.doCommandWithParams(command, params); 1116 }; 1117 1118 1119 // ************************************************************************************************ 1120 // XPath 1121 1122 /** 1123 * Gets an XPath for an element which describes its hierarchical location. 1124 */ 1125 this.getElementXPath = function(element) 1126 { 1127 if (element && element.id) 1128 return '//*[@id="' + element.id + '"]'; 1129 else 1130 return this.getElementTreeXPath(element); 1131 }; 1132 1133 this.getElementTreeXPath = function(element) 1134 { 1135 var paths = []; 1136 1137 for (; element && element.nodeType == 1; element = element.parentNode) 1138 { 1139 var index = 0; 1140 for (var sibling = element.previousSibling; sibling; sibling = sibling.previousSibling) 1141 { 1142 if (sibling.localName == element.localName) 1143 ++index; 1144 } 1145 1146 var tagName = element.localName.toLowerCase(); 1147 var pathIndex = (index ? "[" + (index+1) + "]" : ""); 1148 paths.splice(0, 0, tagName + pathIndex); 1149 } 1150 1151 return paths.length ? "/" + paths.join("/") : null; 1152 }; 1153 1154 this.cssToXPath = function(rule) 1155 { 1156 var regElement = /^([#.]?)([a-z0-9\\*_-]*)((\|)([a-z0-9\\*_-]*))?/i; 1157 var regAttr1 = /^\[([^\]]*)\]/i; 1158 var regAttr2 = /^\[\s*([^~=\s]+)\s*(~?=)\s*"([^"]+)"\s*\]/i; 1159 var regPseudo = /^:([a-z_-])+/i; 1160 var regCombinator = /^(\s*[>+\s])?/i; 1161 var regComma = /^\s*,/i; 1162 1163 var index = 1; 1164 var parts = ["//", "*"]; 1165 var lastRule = null; 1166 1167 while (rule.length && rule != lastRule) 1168 { 1169 lastRule = rule; 1170 1171 // Trim leading whitespace 1172 rule = this.trim(rule); 1173 if (!rule.length) 1174 break; 1175 1176 // Match the element identifier 1177 var m = regElement.exec(rule); 1178 if (m) 1179 { 1180 if (!m[1]) 1181 { 1182 // XXXjoe Namespace ignored for now 1183 if (m[5]) 1184 parts[index] = m[5]; 1185 else 1186 parts[index] = m[2]; 1187 } 1188 else if (m[1] == '#') 1189 parts.push("[@id='" + m[2] + "']"); 1190 else if (m[1] == '.') 1191 parts.push("[contains(@class, '" + m[2] + "')]"); 1192 1193 rule = rule.substr(m[0].length); 1194 } 1195 1196 // Match attribute selectors 1197 m = regAttr2.exec(rule); 1198 if (m) 1199 { 1200 if (m[2] == "~=") 1201 parts.push("[contains(@" + m[1] + ", '" + m[3] + "')]"); 1202 else 1203 parts.push("[@" + m[1] + "='" + m[3] + "']"); 1204 1205 rule = rule.substr(m[0].length); 1206 } 1207 else 1208 { 1209 m = regAttr1.exec(rule); 1210 if (m) 1211 { 1212 parts.push("[@" + m[1] + "]"); 1213 rule = rule.substr(m[0].length); 1214 } 1215 } 1216 1217 // Skip over pseudo-classes and pseudo-elements, which are of no use to us 1218 m = regPseudo.exec(rule); 1219 while (m) 1220 { 1221 rule = rule.substr(m[0].length); 1222 m = regPseudo.exec(rule); 1223 } 1224 1225 // Match combinators 1226 m = regCombinator.exec(rule); 1227 if (m && m[0].length) 1228 { 1229 if (m[0].indexOf(">") != -1) 1230 parts.push("/"); 1231 else if (m[0].indexOf("+") != -1) 1232 parts.push("/following-sibling::"); 1233 else 1234 parts.push("//"); 1235 1236 index = parts.length; 1237 parts.push("*"); 1238 rule = rule.substr(m[0].length); 1239 } 1240 1241 m = regComma.exec(rule); 1242 if (m) 1243 { 1244 parts.push(" | ", "//", "*"); 1245 index = parts.length-1; 1246 rule = rule.substr(m[0].length); 1247 } 1248 } 1249 1250 var xpath = parts.join(""); 1251 return xpath; 1252 }; 1253 1254 this.getElementsBySelector = function(doc, css) 1255 { 1256 var xpath = this.cssToXPath(css); 1257 return this.getElementsByXPath(doc, xpath); 1258 }; 1259 1260 this.getElementsByXPath = function(doc, xpath) 1261 { 1262 var nodes = []; 1263 1264 try { 1265 var result = doc.evaluate(xpath, doc, null, XPathResult.ANY_TYPE, null); 1266 for (var item = result.iterateNext(); item; item = result.iterateNext()) 1267 nodes.push(item); 1268 } 1269 catch (exc) 1270 { 1271 // Invalid xpath expressions make their way here sometimes. If that happens, 1272 // we still want to return an empty set without an exception. 1273 } 1274 1275 return nodes; 1276 }; 1277 1278 this.getRuleMatchingElements = function(rule, doc) 1279 { 1280 var css = rule.selectorText; 1281 var xpath = this.cssToXPath(css); 1282 return this.getElementsByXPath(doc, xpath); 1283 }; 1284 1285 // ************************************************************************************************ 1286 // Clipboard 1287 1288 this.copyToClipboard = function(string) 1289 { 1290 var clipboard = this.CCSV("@mozilla.org/widget/clipboardhelper;1", "nsIClipboardHelper"); 1291 clipboard.copyString(string); 1292 }; 1293 1294 // ************************************************************************************************ 1295 // Graphics 1296 1297 this.getClientOffset = function(elt) 1298 { 1299 function addOffset(elt, coords, view) 1300 { 1301 var p = elt.offsetParent; 1302 1303 var style = view.getComputedStyle(elt, ""); 1304 1305 if (elt.offsetLeft) 1306 coords.x += elt.offsetLeft + parseInt(style.borderLeftWidth); 1307 if (elt.offsetTop) 1308 coords.y += elt.offsetTop + parseInt(style.borderTopWidth); 1309 1310 if (p) 1311 { 1312 if (p.nodeType == 1) 1313 addOffset(p, coords, view); 1314 } 1315 else if (elt.ownerDocument.defaultView.frameElement) 1316 addOffset(elt.ownerDocument.defaultView.frameElement, coords, elt.ownerDocument.defaultView); 1317 } 1318 1319 var coords = {x: 0, y: 0}; 1320 if (elt) 1321 { 1322 var view = elt.ownerDocument.defaultView; 1323 addOffset(elt, coords, view); 1324 } 1325 1326 return coords; 1327 }; 1328 1329 this.getLTRBWH = function(elt) 1330 { 1331 var bcrect, 1332 dims = {"left": 0, "top": 0, "right": 0, "bottom": 0, "width": 0, "height": 0}; 1333 1334 if (elt) 1335 { 1336 bcrect = elt.getBoundingClientRect(); 1337 dims.left = bcrect.left; 1338 dims.top = bcrect.top; 1339 dims.right = bcrect.right; 1340 dims.bottom = bcrect.bottom; 1341 1342 if(bcrect.width) 1343 { 1344 dims.width = bcrect.width; 1345 dims.height = bcrect.height; 1346 } 1347 else 1348 { 1349 dims.width = dims.right - dims.left; 1350 dims.height = dims.bottom - dims.top; 1351 } 1352 } 1353 return dims; 1354 }; 1355 1356 this.applyBodyOffsets = function(elt, clientRect) 1357 { 1358 var od = elt.ownerDocument; 1359 if (!od.body) 1360 return clientRect; 1361 1362 var style = od.defaultView.getComputedStyle(od.body, null); 1363 1364 var pos = style.getPropertyValue('position'); 1365 if(pos === 'absolute' || pos === 'relative') 1366 { 1367 var borderLeft = parseInt(style.getPropertyValue('border-left-width').replace('px', ''),10) || 0; 1368 var borderTop = parseInt(style.getPropertyValue('border-top-width').replace('px', ''),10) || 0; 1369 var paddingLeft = parseInt(style.getPropertyValue('padding-left').replace('px', ''),10) || 0; 1370 var paddingTop = parseInt(style.getPropertyValue('padding-top').replace('px', ''),10) || 0; 1371 var marginLeft = parseInt(style.getPropertyValue('margin-left').replace('px', ''),10) || 0; 1372 var marginTop = parseInt(style.getPropertyValue('margin-top').replace('px', ''),10) || 0; 1373 1374 var offsetX = borderLeft + paddingLeft + marginLeft; 1375 var offsetY = borderTop + paddingTop + marginTop; 1376 1377 clientRect.left -= offsetX; 1378 clientRect.top -= offsetY; 1379 clientRect.right -= offsetX; 1380 clientRect.bottom -= offsetY; 1381 } 1382 1383 return clientRect; 1384 }; 1385 1386 this.getOffsetSize = function(elt) 1387 { 1388 return {width: elt.offsetWidth, height: elt.offsetHeight}; 1389 }; 1390 1391 this.getOverflowParent = function(element) 1392 { 1393 for (var scrollParent = element.parentNode; scrollParent; scrollParent = scrollParent.offsetParent) 1394 { 1395 if (scrollParent.scrollHeight > scrollParent.offsetHeight) 1396 return scrollParent; 1397 } 1398 }; 1399 1400 this.isScrolledToBottom = function(element) 1401 { 1402 var onBottom = (element.scrollTop + element.offsetHeight) == element.scrollHeight; 1403 if (FBTrace.DBG_CONSOLE) 1404 FBTrace.sysout("isScrolledToBottom offsetHeight: " + element.offsetHeight + 1405 ", scrollTop: " + element.scrollTop + ", scrollHeight: " + element.scrollHeight + 1406 ", onBottom: " + onBottom); 1407 return onBottom; 1408 }; 1409 1410 this.scrollToBottom = function(element) 1411 { 1412 element.scrollTop = element.scrollHeight; 1413 1414 if (FBTrace.DBG_CONSOLE) 1415 { 1416 FBTrace.sysout("scrollToBottom reset scrollTop "+element.scrollTop+" = "+element.scrollHeight); 1417 if (element.scrollHeight == element.offsetHeight) 1418 FBTrace.sysout("scrollToBottom attempt to scroll non-scrollable element "+element, element); 1419 } 1420 1421 return (element.scrollTop == element.scrollHeight); 1422 }; 1423 1424 this.move = function(element, x, y) 1425 { 1426 element.style.left = x + "px"; 1427 element.style.top = y + "px"; 1428 }; 1429 1430 this.resize = function(element, w, h) 1431 { 1432 element.style.width = w + "px"; 1433 element.style.height = h + "px"; 1434 }; 1435 1436 this.linesIntoCenterView = function(element, scrollBox) // {before: int, after: int} 1437 { 1438 if (!scrollBox) 1439 scrollBox = this.getOverflowParent(element); 1440 1441 if (!scrollBox) 1442 return; 1443 1444 var offset = this.getClientOffset(element); 1445 1446 var topSpace = offset.y - scrollBox.scrollTop; 1447 var bottomSpace = (scrollBox.scrollTop + scrollBox.clientHeight) 1448 - (offset.y + element.offsetHeight); 1449 1450 if (topSpace < 0 || bottomSpace < 0) 1451 { 1452 var split = (scrollBox.clientHeight/2); 1453 var centerY = offset.y - split; 1454 scrollBox.scrollTop = centerY; 1455 topSpace = split; 1456 bottomSpace = split - element.offsetHeight; 1457 } 1458 1459 return {before: Math.round((topSpace/element.offsetHeight) + 0.5), 1460 after: Math.round((bottomSpace/element.offsetHeight) + 0.5) } 1461 }; 1462 1463 this.scrollIntoCenterView = function(element, scrollBox, notX, notY) 1464 { 1465 if (!element) 1466 return; 1467 1468 if (!scrollBox) 1469 scrollBox = this.getOverflowParent(element); 1470 1471 if (!scrollBox) 1472 return; 1473 1474 var offset = this.getClientOffset(element); 1475 1476 if (!notY) 1477 { 1478 var topSpace = offset.y - scrollBox.scrollTop; 1479 var bottomSpace = (scrollBox.scrollTop + scrollBox.clientHeight) 1480 - (offset.y + element.offsetHeight); 1481 1482 if (topSpace < 0 || bottomSpace < 0) 1483 { 1484 var centerY = offset.y - (scrollBox.clientHeight/2); 1485 scrollBox.scrollTop = centerY; 1486 } 1487 } 1488 1489 if (!notX) 1490 { 1491 var leftSpace = offset.x - scrollBox.scrollLeft; 1492 var rightSpace = (scrollBox.scrollLeft + scrollBox.clientWidth) 1493 - (offset.x + element.clientWidth); 1494 1495 if (leftSpace < 0 || rightSpace < 0) 1496 { 1497 var centerX = offset.x - (scrollBox.clientWidth/2); 1498 scrollBox.scrollLeft = centerX; 1499 } 1500 } 1501 if (FBTrace.DBG_SOURCEFILES) 1502 FBTrace.sysout("lib.scrollIntoCenterView ","Element:"+element.innerHTML); 1503 }; 1504 1505 // ************************************************************************************************ 1506 // CSS 1507 1508 var cssKeywordMap = null; 1509 var cssPropNames = null; 1510 var cssColorNames = null; 1511 var imageRules = null; 1512 1513 this.getCSSKeywordsByProperty = function(propName) 1514 { 1515 if (!cssKeywordMap) 1516 { 1517 cssKeywordMap = {}; 1518 1519 for (var name in this.cssInfo) 1520 { 1521 var list = []; 1522 1523 var types = this.cssInfo[name]; 1524 for (var i = 0; i < types.length; ++i) 1525 { 1526 var keywords = this.cssKeywords[types[i]]; 1527 if (keywords) 1528 list.push.apply(list, keywords); 1529 } 1530 1531 cssKeywordMap[name] = list; 1532 } 1533 } 1534 1535 return propName in cssKeywordMap ? cssKeywordMap[propName] : []; 1536 }; 1537 1538 this.getCSSPropertyNames = function() 1539 { 1540 if (!cssPropNames) 1541 { 1542 cssPropNames = []; 1543 1544 for (var name in this.cssInfo) 1545 cssPropNames.push(name); 1546 } 1547 1548 return cssPropNames; 1549 }; 1550 1551 this.isColorKeyword = function(keyword) 1552 { 1553 if (keyword == "transparent") 1554 return false; 1555 1556 if (!cssColorNames) 1557 { 1558 cssColorNames = []; 1559 1560 var colors = this.cssKeywords["color"]; 1561 for (var i = 0; i < colors.length; ++i) 1562 cssColorNames.push(colors[i].toLowerCase()); 1563 1564 var systemColors = this.cssKeywords["systemColor"]; 1565 for (var i = 0; i < systemColors.length; ++i) 1566 cssColorNames.push(systemColors[i].toLowerCase()); 1567 } 1568 1569 return cssColorNames.indexOf(keyword.toLowerCase()) != -1; 1570 }; 1571 1572 this.isImageRule = function(rule) 1573 { 1574 if (!imageRules) 1575 { 1576 imageRules = []; 1577 1578 for (var i in this.cssInfo) 1579 { 1580 var r = i.toLowerCase(); 1581 var suffix = "image"; 1582 if (r.match(suffix + "$") == suffix || r == "background") 1583 imageRules.push(r); 1584 } 1585 } 1586 1587 return imageRules.indexOf(rule.toLowerCase()) != -1; 1588 }; 1589 1590 this.copyTextStyles = function(fromNode, toNode, style) 1591 { 1592 var view = fromNode.ownerDocument.defaultView; 1593 if (view) 1594 { 1595 if (!style) 1596 style = view.getComputedStyle(fromNode, ""); 1597 1598 toNode.style.fontFamily = style.getPropertyCSSValue("font-family").cssText; 1599 toNode.style.fontSize = style.getPropertyCSSValue("font-size").cssText; 1600 toNode.style.fontWeight = style.getPropertyCSSValue("font-weight").cssText; 1601 toNode.style.fontStyle = style.getPropertyCSSValue("font-style").cssText; 1602 1603 return style; 1604 } 1605 }; 1606 1607 this.copyBoxStyles = function(fromNode, toNode, style) 1608 { 1609 var view = fromNode.ownerDocument.defaultView; 1610 if (view) 1611 { 1612 if (!style) 1613 style = view.getComputedStyle(fromNode, ""); 1614 1615 toNode.style.marginTop = style.getPropertyCSSValue("margin-top").cssText; 1616 toNode.style.marginRight = style.getPropertyCSSValue("margin-right").cssText; 1617 toNode.style.marginBottom = style.getPropertyCSSValue("margin-bottom").cssText; 1618 toNode.style.marginLeft = style.getPropertyCSSValue("margin-left").cssText; 1619 toNode.style.borderTopWidth = style.getPropertyCSSValue("border-top-width").cssText; 1620 toNode.style.borderRightWidth = style.getPropertyCSSValue("border-right-width").cssText; 1621 toNode.style.borderBottomWidth = style.getPropertyCSSValue("border-bottom-width").cssText; 1622 toNode.style.borderLeftWidth = style.getPropertyCSSValue("border-left-width").cssText; 1623 1624 return style; 1625 } 1626 }; 1627 1628 this.readBoxStyles = function(style) 1629 { 1630 const styleNames = { 1631 "margin-top": "marginTop", "margin-right": "marginRight", 1632 "margin-left": "marginLeft", "margin-bottom": "marginBottom", 1633 "border-top-width": "borderTop", "border-right-width": "borderRight", 1634 "border-left-width": "borderLeft", "border-bottom-width": "borderBottom", 1635 "padding-top": "paddingTop", "padding-right": "paddingRight", 1636 "padding-left": "paddingLeft", "padding-bottom": "paddingBottom", 1637 "z-index": "zIndex", 1638 }; 1639 1640 var styles = {}; 1641 for (var styleName in styleNames) 1642 styles[styleNames[styleName]] = parseInt(style.getPropertyCSSValue(styleName).cssText) || 0; 1643 if (FBTrace.DBG_INSPECT) 1644 FBTrace.sysout("readBoxStyles ", styles); 1645 return styles; 1646 }; 1647 1648 this.getBoxFromStyles = function(style, element) 1649 { 1650 var args = this.readBoxStyles(style); 1651 args.width = element.offsetWidth 1652 - (args.paddingLeft+args.paddingRight+args.borderLeft+args.borderRight); 1653 args.height = element.offsetHeight 1654 - (args.paddingTop+args.paddingBottom+args.borderTop+args.borderBottom); 1655 return args; 1656 }; 1657 1658 this.getElementCSSSelector = function(element) 1659 { 1660 var label = element.localName.toLowerCase(); 1661 if (element.id) 1662 label += "#" + element.id; 1663 if (element.hasAttribute("class")) 1664 label += "." + element.getAttribute("class").split(" ")[0]; 1665 1666 return label; 1667 }; 1668 1669 this.getURLForStyleSheet= function(styleSheet) 1670 { 1671 //http://www.w3.org/TR/DOM-Level-2-Style/stylesheets.html#StyleSheets-StyleSheet. For inline style sheets, the value of this attribute is null. 1672 return (styleSheet.href ? styleSheet.href : styleSheet.ownerNode.ownerDocument.URL); 1673 }; 1674 1675 this.getDocumentForStyleSheet = function(styleSheet) 1676 { 1677 while (styleSheet.parentStyleSheet && !styleSheet.ownerNode) 1678 { 1679 styleSheet = styleSheet.parentStyleSheet; 1680 } 1681 if (styleSheet.ownerNode) 1682 return styleSheet.ownerNode.ownerDocument; 1683 }; 1684 1685 /** 1686 * Retrieves the instance number for a given style sheet. The instance number 1687 * is sheet's index within the set of all other sheets whose URL is the same. 1688 */ 1689 this.getInstanceForStyleSheet = function(styleSheet, ownerDocument) 1690 { 1691 // System URLs are always unique (or at least we are making this assumption) 1692 if (FBL.isSystemStyleSheet(styleSheet)) 1693 return 0; 1694 1695 // ownerDocument is an optional hint for performance 1696 if (FBTrace.DBG_CSS) FBTrace.sysout("getInstanceForStyleSheet: " + styleSheet.href + " " + styleSheet.media.mediaText + " " + (styleSheet.ownerNode && FBL.getElementXPath(styleSheet.ownerNode)), ownerDocument); 1697 ownerDocument = ownerDocument || FBL.getDocumentForStyleSheet(styleSheet); 1698 1699 var ret = 0, 1700 styleSheets = ownerDocument.styleSheets, 1701 href = styleSheet.href; 1702 for (var i = 0; i < styleSheets.length; i++) 1703 { 1704 var curSheet = styleSheets[i]; 1705 if (FBTrace.DBG_CSS) FBTrace.sysout("getInstanceForStyleSheet: compare href " + i + " " + curSheet.href + " " + curSheet.media.mediaText + " " + (curSheet.ownerNode && FBL.getElementXPath(curSheet.ownerNode))); 1706 if (curSheet == styleSheet) 1707 break; 1708 if (curSheet.href == href) 1709 ret++; 1710 } 1711 return ret; 1712 }; 1713 1714 // ************************************************************************************************ 1715 // HTML and XML Serialization 1716 1717 1718 var getElementType = this.getElementType = function(node) 1719 { 1720 if (isElementXUL(node)) 1721 return 'xul'; 1722 else if (isElementSVG(node)) 1723 return 'svg'; 1724 else if (isElementMathML(node)) 1725 return 'mathml'; 1726 else if (isElementXHTML(node)) 1727 return 'xhtml'; 1728 else if (isElementHTML(node)) 1729 return 'html'; 1730 } 1731 1732 var isElementHTML = this.isElementHTML = function(node) 1733 { 1734 return node.nodeName == node.nodeName.toUpperCase(); 1735 } 1736 1737 var isElementXHTML = this.isElementXHTML = function(node) 1738 { 1739 return node.nodeName == node.nodeName.toLowerCase(); 1740 } 1741 1742 var isElementMathML = this.isElementMathML = function(node) 1743 { 1744 return node.namespaceURI == 'http://www.w3.org/1998/Math/MathML'; 1745 } 1746 1747 var isElementSVG = this.isElementSVG = function(node) 1748 { 1749 return node.namespaceURI == 'http://www.w3.org/2000/svg'; 1750 } 1751 1752 var isElementXUL = this.isElementXUL = function(node) 1753 { 1754 return node instanceof XULElement; 1755 } 1756 1757 this.isSelfClosing = function(element) 1758 { 1759 if (isElementSVG(element) || isElementMathML(element)) 1760 return true; 1761 var tag = element.localName.toLowerCase(); 1762 return (this.selfClosingTags.hasOwnProperty(tag)); 1763 }; 1764 1765 this.getElementHTML = function(element) 1766 { 1767 var self=this; 1768 function toHTML(elt) 1769 { 1770 if (elt.nodeType == Node.ELEMENT_NODE) 1771 { 1772 if (unwrapObject(elt).firebugIgnore) 1773 return; 1774 1775 html.push('<', elt.nodeName.toLowerCase()); 1776 1777 for (var i = 0; i < elt.attributes.length; ++i) 1778 { 1779 var attr = elt.attributes[i]; 1780 1781 // Hide attributes set by Firebug 1782 if (attr.localName.indexOf("firebug-") == 0) 1783 continue; 1784 1785 // MathML 1786 if (attr.localName.indexOf("-moz-math") == 0) 1787 { 1788 // just hide for now 1789 continue; 1790 } 1791 1792 html.push(' ', attr.nodeName, '="', escapeForElementAttribute(attr.nodeValue),'"'); 1793 } 1794 1795 if (elt.firstChild) 1796 { 1797 html.push('>'); 1798 1799 var pureText=true; 1800 for (var child = element.firstChild; child; child = child.nextSibling) 1801 pureText=pureText && (child.nodeType == Node.TEXT_NODE); 1802 1803 if (pureText) 1804 html.push(escapeForHtmlEditor(elt.textContent)); 1805 else { 1806 for (var child = elt.firstChild; child; child = child.nextSibling) 1807 toHTML(child); 1808 } 1809 1810 html.push('</', elt.nodeName.toLowerCase(), '>'); 1811 } 1812 else if (isElementSVG(elt) || isElementMathML(elt)) 1813 { 1814 html.push('/>'); 1815 } 1816 else if (self.isSelfClosing(elt)) 1817 { 1818 html.push((isElementXHTML(elt))?'/>':'>'); 1819 } 1820 else 1821 { 1822 html.push('></', elt.nodeName.toLowerCase(), '>'); 1823 } 1824 } 1825 else if (elt.nodeType == Node.TEXT_NODE) 1826 html.push(escapeForTextNode(elt.textContent)); 1827 else if (elt.nodeType == Node.CDATA_SECTION_NODE) 1828 html.push('<![CDATA[', elt.nodeValue, ']]>'); 1829 else if (elt.nodeType == Node.COMMENT_NODE) 1830 html.push('<!--', elt.nodeValue, '-->'); 1831 } 1832 1833 var html = []; 1834 toHTML(element); 1835 return html.join(""); 1836 }; 1837 1838 this.getElementXML = function(element) 1839 { 1840 function toXML(elt) 1841 { 1842 if (elt.nodeType == Node.ELEMENT_NODE) 1843 { 1844 if (unwrapObject(elt).firebugIgnore) 1845 return; 1846 1847 xml.push('<', elt.nodeName.toLowerCase()); 1848 1849 for (var i = 0; i < elt.attributes.length; ++i) 1850 { 1851 var attr = elt.attributes[i]; 1852 1853 // Hide attributes set by Firebug 1854 if (attr.localName.indexOf("firebug-") == 0) 1855 continue; 1856 1857 // MathML 1858 if (attr.localName.indexOf("-moz-math") == 0) 1859 { 1860 // just hide for now 1861 continue; 1862 } 1863 1864 xml.push(' ', attr.nodeName, '="', escapeForElementAttribute(attr.nodeValue),'"'); 1865 } 1866 1867 if (elt.firstChild) 1868 { 1869 xml.push('>'); 1870 1871 for (var child = elt.firstChild; child; child = child.nextSibling) 1872 toXML(child); 1873 1874 xml.push('</', elt.nodeName.toLowerCase(), '>'); 1875 } 1876 else 1877 xml.push('/>'); 1878 } 1879 else if (elt.nodeType == Node.TEXT_NODE) 1880 xml.push(elt.nodeValue); 1881 else if (elt.nodeType == Node.CDATA_SECTION_NODE) 1882 xml.push('<![CDATA[', elt.nodeValue, ']]>'); 1883 else if (elt.nodeType == Node.COMMENT_NODE) 1884 xml.push('<!--', elt.nodeValue, '-->'); 1885 } 1886 1887 var xml = []; 1888 toXML(element); 1889 return xml.join(""); 1890 }; 1891 1892 // ************************************************************************************************ 1893 // Whitespace and Entity conversions 1894 1895 var entityConversionLists = this.entityConversionLists = { 1896 normal : { 1897 whitespace : { 1898 '\t' : '\u200c\u2192', 1899 '\n' : '\u200c\u00b6', 1900 '\r' : '\u200c\u00ac', 1901 ' ' : '\u200c\u00b7' 1902 } 1903 }, 1904 reverse : { 1905 whitespace : { 1906 '	' : '\t', 1907 '
' : '\n', 1908 '\u200c\u2192' : '\t', 1909 '\u200c\u00b6' : '\n', 1910 '\u200c\u00ac' : '\r', 1911 '\u200c\u00b7' : ' ' 1912 } 1913 } 1914 }; 1915 1916 var normal = entityConversionLists.normal, 1917 reverse = entityConversionLists.reverse; 1918 1919 function addEntityMapToList(ccode, entity) 1920 { 1921 var lists = Array.slice(arguments, 2), 1922 len = lists.length, 1923 ch = String.fromCharCode(ccode); 1924 for (var i = 0; i < len; i++) 1925 { 1926 var list = lists[i]; 1927 normal[list]=normal[list] || {}; 1928 normal[list][ch] = '&' + entity + ';'; 1929 reverse[list]=reverse[list] || {}; 1930 reverse[list]['&' + entity + ';'] = ch; 1931 } 1932 } 1933 1934 var e = addEntityMapToList, 1935 white = 'whitespace', 1936 text = 'text', 1937 attr = 'attributes', 1938 css = 'css', 1939 editor = 'editor'; 1940 1941 e(0x0022, 'quot', attr, css); 1942 e(0x0026, 'amp', attr, text, css); 1943 e(0x0027, 'apos', css); 1944 e(0x003c, 'lt', attr, text, css); 1945 e(0x003e, 'gt', attr, text, css); 1946 e(0xa9, 'copy', text, editor); 1947 e(0xae, 'reg', text, editor); 1948 e(0x2122, 'trade', text, editor); 1949 1950 // See http://en.wikipedia.org/wiki/Dash 1951 e(0x2012, '#8210', attr, text, editor); // figure dash 1952 e(0x2013, 'ndash', attr, text, editor); // en dash 1953 e(0x2014, 'mdash', attr, text, editor); // em dash 1954 e(0x2015, '#8213', attr, text, editor); // horizontal bar 1955 1956 e(0x00a0, 'nbsp', attr, text, white, editor); 1957 e(0x2002, 'ensp', attr, text, white, editor); 1958 e(0x2003, 'emsp', attr, text, white, editor); 1959 e(0x2009, 'thinsp', attr, text, white, editor); 1960 e(0x200c, 'zwnj', attr, text, white, editor); 1961 e(0x200d, 'zwj', attr, text, white, editor); 1962 e(0x200e, 'lrm', attr, text, white, editor); 1963 e(0x200f, 'rlm', attr, text, white, editor); 1964 e(0x200b, '#8203', attr, text, white, editor); // zero-width space (ZWSP) 1965 1966 //************************************************************************************************ 1967 // Entity escaping 1968 1969 var entityConversionRegexes = { 1970 normal : {}, 1971 reverse : {} 1972 }; 1973 1974 var escapeEntitiesRegEx = { 1975 normal : function(list) 1976 { 1977 var chars = []; 1978 for ( var ch in list) 1979 { 1980 chars.push(ch); 1981 } 1982 return new RegExp('([' + chars.join('') + '])', 'gm'); 1983 }, 1984 reverse : function(list) 1985 { 1986 var chars = []; 1987 for ( var ch in list) 1988 { 1989 chars.push(ch); 1990 } 1991 return new RegExp('(' + chars.join('|') + ')', 'gm'); 1992 } 1993 }; 1994 1995 function getEscapeRegexp(direction, lists) 1996 { 1997 var name = '', re; 1998 var groups = [].concat(lists); 1999 for (i = 0; i < groups.length; i++) 2000 { 2001 name += groups[i].group; 2002 } 2003 re = entityConversionRegexes[direction][name]; 2004 if (!re) 2005 { 2006 var list = {}; 2007 if (groups.length > 1) 2008 { 2009 for ( var i = 0; i < groups.length; i++) 2010 { 2011 var aList = entityConversionLists[direction][groups[i].group]; 2012 for ( var item in aList) 2013 list[item] = aList[item]; 2014 } 2015 } else if (groups.length==1) 2016 { 2017 list = entityConversionLists[direction][groups[0].group]; // faster for special case 2018 } else { 2019 list = {}; // perhaps should print out an error here? 2020 } 2021 re = entityConversionRegexes[direction][name] = escapeEntitiesRegEx[direction](list); 2022 } 2023 return re; 2024 } 2025 2026 function createSimpleEscape(name, direction) 2027 { 2028 return function(value) 2029 { 2030 var list = entityConversionLists[direction][name]; 2031 return String(value).replace( 2032 getEscapeRegexp(direction, { 2033 group : name, 2034 list : list 2035 }), 2036 function(ch) 2037 { 2038 return list[ch]; 2039 } 2040 ); 2041 } 2042 } 2043 2044 function escapeGroupsForEntities(str, lists) 2045 { 2046 lists = [].concat(lists); 2047 var re = getEscapeRegexp('normal', lists), 2048 split = String(str).split(re), 2049 len = split.length, 2050 results = [], 2051 cur, r, i, ri = 0, l, list, last = ''; 2052 if (!len) 2053 return [ { 2054 str : String(str), 2055 group : '', 2056 name : '' 2057 } ]; 2058 for (i = 0; i < len; i++) 2059 { 2060 cur = split[i]; 2061 if (cur == '') 2062 continue; 2063 for (l = 0; l < lists.length; l++) 2064 { 2065 list = lists[l]; 2066 r = entityConversionLists.normal[list.group][cur]; 2067 // if (cur == ' ' && list.group == 'whitespace' && last == ' ') // only show for runs of more than one space 2068 // r = ' '; 2069 if (r) 2070 { 2071 results[ri] = { 2072 'str' : r, 2073 'class' : list['class'], 2074 'extra' : list.extra[cur] ? list['class'] 2075 + list.extra[cur] : '' 2076 }; 2077 break; 2078 } 2079 } 2080 // last=cur; 2081 if (!r) 2082 results[ri] = { 2083 'str' : cur, 2084 'class' : '', 2085 'extra' : '' 2086 }; 2087 ri++; 2088 } 2089 return results; 2090 } 2091 2092 this.escapeGroupsForEntities = escapeGroupsForEntities; 2093 2094 2095 function unescapeEntities(str, lists) 2096 { 2097 var re = getEscapeRegexp('reverse', lists), 2098 split = String(str).split(re), 2099 len = split.length, 2100 results = [], 2101 cur, r, i, ri = 0, l, list; 2102 if (!len) 2103 return str; 2104 lists = [].concat(lists); 2105 for (i = 0; i < len; i++) 2106 { 2107 cur = split[i]; 2108 if (cur == '') 2109 continue; 2110 for (l = 0; l < lists.length; l++) 2111 { 2112 list = lists[l]; 2113 r = entityConversionLists.reverse[list.group][cur]; 2114 if (r) 2115 { 2116 results[ri] = r; 2117 break; 2118 } 2119 } 2120 if (!r) 2121 results[ri] = cur; 2122 ri++; 2123 } 2124 return results.join('') || ''; 2125 } 2126 2127 2128 // ************************************************************************************************ 2129 // String escaping 2130 2131 var escapeForTextNode = this.escapeForTextNode = createSimpleEscape('text', 'normal'); 2132 var escapeForHtmlEditor = this.escapeForHtmlEditor = createSimpleEscape('editor', 'normal'); 2133 var escapeForElementAttribute = this.escapeForElementAttribute = createSimpleEscape('attributes', 'normal'); 2134 var escapeForCss = this.escapeForCss = createSimpleEscape('css', 'normal'); 2135 2136 // deprecated compatibility functions 2137 this.deprecateEscapeHTML = createSimpleEscape('text', 'normal'); 2138 this.deprecatedUnescapeHTML = createSimpleEscape('text', 'reverse'); 2139 this.escapeHTML = deprecated("use appropriate escapeFor... function", this.deprecateEscapeHTML); 2140 this.unescapeHTML = deprecated("use appropriate unescapeFor... function", this.deprecatedUnescapeHTML); 2141 2142 var escapeForSourceLine = this.escapeForSourceLine = createSimpleEscape('text', 'normal'