1 /* See license.txt for terms of usage */ 2 3 define([ 4 "firebug/lib/object", 5 "firebug/firebug", 6 "firebug/chrome/firefox", 7 "firebug/lib/domplate", 8 "firebug/chrome/reps", 9 "firebug/lib/xpcom", 10 "firebug/lib/locale", 11 "firebug/lib/events", 12 "firebug/lib/url", 13 "firebug/lib/array", 14 "firebug/js/sourceLink", 15 "firebug/lib/dom", 16 "firebug/lib/css", 17 "firebug/lib/xpath", 18 "firebug/lib/fonts", 19 "firebug/lib/options", 20 "firebug/css/cssModule", 21 "firebug/css/cssPanel", 22 "firebug/chrome/menu" 23 ], 24 function(Obj, Firebug, Firefox, Domplate, FirebugReps, Xpcom, Locale, Events, Url, Arr, 25 SourceLink, Dom, Css, Xpath, Fonts, Options, CSSModule, CSSStyleSheetPanel, Menu) { 26 27 with (Domplate) { 28 29 // ********************************************************************************************* // 30 // Constants 31 32 const Cc = Components.classes; 33 const Ci = Components.interfaces; 34 const nsIDOMCSSStyleRule = Ci.nsIDOMCSSStyleRule; 35 36 // before firefox 6 getCSSStyleRules accepted only one argument 37 const DOMUTILS_SUPPORTS_PSEUDOELEMENTS = Dom.domUtils.getCSSStyleRules.length > 1; 38 39 // See: http://mxr.mozilla.org/mozilla1.9.2/source/content/events/public/nsIEventStateManager.h#153 40 const STATE_ACTIVE = 0x01; 41 const STATE_FOCUS = 0x02; 42 const STATE_HOVER = 0x04; 43 44 // ********************************************************************************************* // 45 // CSS Elemenet Panel (HTML side panel) 46 47 function CSSStylePanel() {} 48 49 CSSStylePanel.prototype = Obj.extend(CSSStyleSheetPanel.prototype, 50 { 51 template: domplate( 52 { 53 cascadedTag: 54 DIV({"class": "a11yCSSView", role: "presentation"}, 55 DIV({"class": "cssNonInherited", role: "list", 56 "aria-label": Locale.$STR("aria.labels.style rules") }, 57 FOR("rule", "$rules", 58 TAG("$ruleTag", {rule: "$rule"}) 59 ) 60 ), 61 DIV({role: "list", "aria-label": Locale.$STR("aria.labels.inherited style rules")}, 62 FOR("section", "$inherited", 63 H1({"class": "cssInheritHeader groupHeader focusRow", role: "listitem" }, 64 SPAN({"class": "cssInheritLabel"}, "$inheritLabel"), 65 TAG(FirebugReps.Element.shortTag, {object: "$section.element"}) 66 ), 67 DIV({role: "group"}, 68 FOR("rule", "$section.rules", 69 TAG("$ruleTag", {rule: "$rule"}) 70 ) 71 ) 72 ) 73 ) 74 ), 75 76 ruleTag: 77 DIV({"class": "cssElementRuleContainer"}, 78 TAG(Firebug.CSSStyleRuleTag.tag, {rule: "$rule"}), 79 TAG(FirebugReps.SourceLink.tag, {object: "$rule.sourceLink"}) 80 ), 81 82 newRuleTag: 83 DIV({"class": "cssElementRuleContainer"}, 84 DIV({"class": "cssRule insertBefore", style: "display: none"}, "") 85 ), 86 87 CSSFontPropValueTag: 88 SPAN({"class": "cssFontPropValue"}, 89 FOR("part", "$propValueParts", 90 SPAN({"class": "$part.type|getClass", _repObject: "$part.font"}, "$part.value"), 91 SPAN({"class": "cssFontPropSeparator"}, "$part|getSeparator") 92 ) 93 ), 94 95 getSeparator: function(part) 96 { 97 if (part.lastFont || part.type == "important") 98 return ""; 99 100 if (part.type == "otherProps") 101 return " "; 102 103 return ","; 104 }, 105 106 getClass: function(type) 107 { 108 switch (type) 109 { 110 case "used": 111 return "cssPropValueUsed"; 112 113 case "unused": 114 return "cssPropValueUnused"; 115 116 default: 117 return ""; 118 } 119 } 120 }), 121 122 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 123 // All calls to this method must call cleanupSheets first 124 125 updateCascadeView: function(element) 126 { 127 Events.dispatch(this.fbListeners, "onBeforeCSSRulesAdded", [this]); 128 129 var result, warning, inheritLabel; 130 var rules = [], sections = [], usedProps = {}; 131 132 this.getInheritedRules(element, sections, usedProps); 133 this.getElementRules(element, rules, usedProps); 134 135 if (rules.length || sections.length) 136 { 137 // This removes overridden properties. 138 if (Firebug.onlyShowAppliedStyles) 139 this.removeOverriddenProps(rules, sections); 140 141 // This removes user agent rules 142 if (!Firebug.showUserAgentCSS) 143 this.removeSystemRules(rules, sections); 144 } 145 146 // Reset the selection, so that clicking that starts before the view 147 // update still result in proper mouseup events (issue 5500). 148 this.document.defaultView.getSelection().removeAllRanges(); 149 150 if (rules.length || sections.length) 151 { 152 inheritLabel = Locale.$STR("InheritedFrom"); 153 result = this.template.cascadedTag.replace({rules: rules, inherited: sections, 154 inheritLabel: inheritLabel}, this.panelNode); 155 156 var props = result.getElementsByClassName("cssProp"); 157 158 for (var i = 0; i < props.length; i++) 159 { 160 var prop = props[i]; 161 var propName = prop.getElementsByClassName("cssPropName").item(0).textContent; 162 if (propName == "font-family" || propName == "font") 163 { 164 var propValueElem = prop.getElementsByClassName("cssPropValue").item(0); 165 var propValue = propValueElem.textContent; 166 var fontPropValueParts = getFontPropValueParts(element, propValue, propName); 167 168 // xxxsz: Web fonts not being loaded at display time 169 // won't be marked as used. See issue 5420. 170 this.template.CSSFontPropValueTag.replace({propValueParts: fontPropValueParts}, 171 propValueElem); 172 } 173 } 174 175 Events.dispatch(this.fbListeners, "onCSSRulesAdded", [this, result]); 176 } 177 else 178 { 179 warning = FirebugReps.Warning.tag.replace({object: ""}, this.panelNode); 180 result = FirebugReps.Description.render(Locale.$STR("css.EmptyElementCSS"), 181 warning, Obj.bind(this.editElementStyle, this)); 182 183 Events.dispatch([Firebug.A11yModel], "onCSSRulesAdded", [this, result]); 184 } 185 186 // Avoid a flickering "disable" icon by forcing a reflow (issue 5500). 187 this.panelNode.offsetHeight; 188 }, 189 190 getStylesheetURL: function(rule, getBaseUri) 191 { 192 // If parentStyleSheet.href is null, then per the CSS standard this is an inline style. 193 if (rule && rule.parentStyleSheet && rule.parentStyleSheet.href) 194 return rule.parentStyleSheet.href; 195 else if (getBaseUri) 196 return this.selection.ownerDocument.baseURI; 197 else 198 return this.selection.ownerDocument.location.href; 199 }, 200 201 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 202 203 // All calls to this method must call cleanupSheets first 204 getInheritedRules: function(element, sections, usedProps) 205 { 206 var parent = element.parentNode; 207 if (parent && parent.nodeType == Node.ELEMENT_NODE) 208 { 209 this.getInheritedRules(parent, sections, usedProps); 210 211 var rules = []; 212 this.getElementRules(parent, rules, usedProps, true); 213 214 if (rules.length) 215 sections.unshift({element: parent, rules: rules}); 216 } 217 }, 218 219 // All calls to this method must call cleanupSheets first 220 getElementRules: function(element, rules, usedProps, inheritMode) 221 { 222 var pseudoElements = [""]; 223 var inspectedRules, displayedRules = {}; 224 225 // Firefox 6+ allows inspecting of pseudo-elements (see issue 537) 226 if (DOMUTILS_SUPPORTS_PSEUDOELEMENTS && !inheritMode) 227 pseudoElements = Arr.extendArray(pseudoElements, 228 [":first-letter", ":first-line", ":before", ":after"]); 229 230 for (var p in pseudoElements) 231 { 232 try 233 { 234 inspectedRules = Dom.domUtils.getCSSStyleRules(element, pseudoElements[p]); 235 } 236 catch (exc) 237 { 238 continue; 239 } 240 241 if (!inspectedRules) 242 continue; 243 244 for (var i = 0; i < inspectedRules.Count(); ++i) 245 { 246 var rule = Xpcom.QI(inspectedRules.GetElementAt(i), nsIDOMCSSStyleRule); 247 var isSystemSheet = Url.isSystemStyleSheet(rule.parentStyleSheet); 248 249 var props = this.getRuleProperties(this.context, rule, inheritMode); 250 if (inheritMode && !props.length) 251 continue; 252 253 var isPseudoElementSheet = (pseudoElements[p] != ""); 254 var sourceLink = this.getSourceLink(null, rule); 255 256 if (!isPseudoElementSheet) 257 this.markOverriddenProps(element, props, usedProps, inheritMode); 258 259 rules.unshift({ 260 rule: rule, 261 selector: rule.selectorText.replace(/ :/g, " *:"), // (issue 3683) 262 sourceLink: sourceLink, 263 props: props, inherited: inheritMode, 264 isSystemSheet: isSystemSheet, 265 isPseudoElementSheet: isPseudoElementSheet, 266 isSelectorEditable: true 267 }); 268 } 269 } 270 271 if (element.style) 272 this.getStyleProperties(element, rules, usedProps, inheritMode); 273 274 if (FBTrace.DBG_CSS) 275 FBTrace.sysout("getElementRules " + rules.length + " rules for " + 276 Xpath.getElementXPath(element), rules); 277 }, 278 279 markOverriddenProps: function(element, props, usedProps, inheritMode) 280 { 281 // Element can contain an invalid name (see issue 5303) 282 try 283 { 284 var dummyElement = element.ownerDocument.createElementNS( 285 element.namespaceURI, element.tagName); 286 } 287 catch (err) 288 { 289 if (FBTrace.DBG_ERRORS) 290 FBTrace.sysout("css.markOverriddenProps:", err); 291 return; 292 } 293 294 for (var i=0; i<props.length; i++) 295 { 296 var prop = props[i]; 297 298 // Helper array for all shorthand properties for the current property. 299 prop.computed = {}; 300 301 // Get all shorthand propertis. 302 var dummyStyle = dummyElement.style; 303 304 // xxxHonza: Not sure why this happens. 305 if (!dummyStyle) 306 { 307 if (FBTrace.DBG_ERRORS) 308 FBTrace.sysout("css.markOverridenProps; ERROR dummyStyle is NULL"); 309 return; 310 } 311 312 dummyStyle.cssText = ""; 313 dummyStyle.setProperty(prop.name, prop.value, prop.important); 314 315 var length = dummyStyle.length; 316 for (var k=0; k<length; k++) 317 { 318 var name = dummyStyle.item(k); 319 320 prop.computed[name] = { 321 overridden: false 322 }; 323 324 if (usedProps.hasOwnProperty(name)) 325 { 326 var deadProps = usedProps[name]; 327 328 // all previous occurrences of this property 329 for (var j=0; j<deadProps.length; j++) 330 { 331 var deadProp = deadProps[j]; 332 333 // xxxHonza: fix for issue 3009, cross out even inherited properties 334 //if (deadProp.wasInherited) 335 // continue; 336 337 if (!deadProp.disabled && deadProp.important && !prop.important) 338 { 339 // new occurrence overridden 340 prop.overridden = true; 341 342 // Remember what exact shorthand property has been overridden. 343 // This should help when we want to cross out only specific 344 // part of the property value. 345 if (prop.computed.hasOwnProperty(name)) 346 prop.computed[name].overridden = true; 347 } 348 else if (!prop.disabled) 349 { 350 // previous occurrences overridden 351 deadProp.overridden = true; 352 353 if (deadProp.computed.hasOwnProperty(name)) 354 deadProp.computed[name].overridden = true; 355 } 356 } 357 } 358 else 359 { 360 usedProps[name] = []; 361 } 362 363 // all occurrences of a property seen so far, by name 364 usedProps[name].push(prop); 365 } 366 367 prop.wasInherited = inheritMode ? true : false; 368 } 369 }, 370 371 removeOverriddenProps: function(rules, sections) 372 { 373 function removeProps(rules) 374 { 375 var i=0; 376 while (i<rules.length) 377 { 378 var props = rules[i].props; 379 380 var j=0; 381 while (j<props.length) 382 { 383 if (props[j].overridden) 384 props.splice(j, 1); 385 else 386 ++j; 387 } 388 389 if (props.length == 0) 390 rules.splice(i, 1); 391 else 392 ++i; 393 } 394 } 395 396 removeProps(rules); 397 398 var i=0; 399 while (i < sections.length) 400 { 401 var section = sections[i]; 402 removeProps(section.rules); 403 404 if (section.rules.length == 0) 405 sections.splice(i, 1); 406 else 407 ++i; 408 } 409 }, 410 411 removeSystemRules: function(rules, sections) 412 { 413 function removeSystem(rules) 414 { 415 var i=0; 416 while (i<rules.length) 417 { 418 if (rules[i].isSystemSheet) 419 rules.splice(i, 1); 420 else 421 ++i; 422 } 423 } 424 425 removeSystem(rules); 426 427 var i=0; 428 while (i<sections.length) 429 { 430 var section = sections[i]; 431 removeSystem(section.rules); 432 433 if (section.rules.length == 0) 434 sections.splice(i, 1); 435 else 436 ++i; 437 } 438 }, 439 440 getStyleProperties: function(element, rules, usedProps, inheritMode) 441 { 442 var props = this.parseCSSProps(element.style, inheritMode); 443 this.addDisabledProperties(this.context, element, inheritMode, props); 444 445 this.sortProperties(props); 446 447 this.markOverriddenProps(element, props, usedProps, inheritMode); 448 449 if (props.length) 450 { 451 rules.unshift({rule: element, selector: "element.style", 452 props: props, inherited: inheritMode}); 453 } 454 }, 455 456 inspectDeclaration: function(rule) 457 { 458 Firebug.chrome.select(rule, "stylesheet"); 459 }, 460 461 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 462 // extends Panel 463 464 name: "css", 465 parentPanel: "html", 466 order: 0, 467 468 initialize: function() 469 { 470 this.onStateChange = Obj.bindFixed(this.contentStateCheck, this); 471 this.onHoverChange = Obj.bindFixed(this.contentStateCheck, this, STATE_HOVER); 472 this.onActiveChange = Obj.bindFixed(this.contentStateCheck, this, STATE_ACTIVE); 473 474 CSSStyleSheetPanel.prototype.initialize.apply(this, arguments); 475 }, 476 477 show: function(state) 478 { 479 if (this.selection) 480 this.refresh(); 481 }, 482 483 watchWindow: function(context, win) 484 { 485 if (Dom.domUtils) 486 { 487 // Normally these would not be required, but in order to update after the state is set 488 // using the options menu we need to monitor these global events as well 489 context.addEventListener(win, "mouseover", this.onHoverChange, false); 490 context.addEventListener(win, "mousedown", this.onActiveChange, false); 491 } 492 }, 493 494 unwatchWindow: function(context, win) 495 { 496 context.removeEventListener(win, "mouseover", this.onHoverChange, false); 497 context.removeEventListener(win, "mousedown", this.onActiveChange, false); 498 499 var doc = win.document; 500 if (Dom.isAncestor(this.stateChangeEl, doc)) 501 this.removeStateChangeHandlers(); 502 }, 503 504 supportsObject: function(object, type) 505 { 506 return object instanceof window.Element ? 1 : 0; 507 }, 508 509 updateView: function(element) 510 { 511 var result = CSSModule.cleanupSheets(element.ownerDocument, Firebug.currentContext); 512 513 // If cleanupSheets returns false there was an exception thrown when accessing 514 // a styleshet (probably since it isn't fully loaded yet). So, delay the panel 515 // update and try it again a bit later (issue 5654). 516 if (!result) 517 { 518 this.context.setTimeout(Obj.bindFixed(this.updateView, this, element), 200); 519 return; 520 } 521 522 // All stylesheets should be ready now, update the view. 523 this.updateCascadeView(element); 524 525 if (Dom.domUtils) 526 { 527 this.contentState = safeGetContentState(element); 528 this.addStateChangeHandlers(element); 529 } 530 }, 531 532 updateSelection: function(element) 533 { 534 if (!(element instanceof window.Element)) // html supports SourceLink 535 return; 536 537 var sothinkInstalled = !!Firefox.getElementById("swfcatcherKey_sidebar"); 538 if (sothinkInstalled) 539 { 540 var div = FirebugReps.Warning.tag.replace({object: "SothinkWarning"}, this.panelNode); 541 div.textContent = Locale.$STR("SothinkWarning"); 542 return; 543 } 544 545 this.updateView(element); 546 }, 547 548 updateOption: function(name, value) 549 { 550 var options = new Set(); 551 options.add("onlyShowAppliedStyles"); 552 options.add("showUserAgentCSS"); 553 options.add("expandShorthandProps"); 554 options.add("colorDisplay"); 555 options.add("showMozillaSpecificStyles"); 556 557 if (options.has(name)) 558 this.refresh(); 559 }, 560 561 getOptionsMenuItems: function() 562 { 563 var items = [ 564 Menu.optionMenu("Only_Show_Applied_Styles", "onlyShowAppliedStyles", 565 "style.option.tip.Only_Show_Applied_Styles"), 566 Menu.optionMenu("Show_User_Agent_CSS", "showUserAgentCSS", 567 "style.option.tip.Show_User_Agent_CSS"), 568 Menu.optionMenu("Expand_Shorthand_Properties", "expandShorthandProps", 569 "css.option.tip.Expand_Shorthand_Properties") 570 ]; 571 572 items = Arr.extendArray(items, CSSModule.getColorDisplayOptionMenuItems()); 573 574 if (Dom.domUtils && this.selection) 575 { 576 var self = this; 577 578 items.push( 579 "-", 580 { 581 label: "style.option.label.hover", 582 type: "checkbox", 583 checked: self.hasPseudoClassLock(":hover"), 584 tooltiptext: "style.option.tip.hover", 585 command: function() 586 { 587 self.togglePseudoClassLock(":hover"); 588 } 589 }, 590 { 591 label: "style.option.label.active", 592 type: "checkbox", 593 checked: self.hasPseudoClassLock(":active"), 594 tooltiptext: "style.option.tip.active", 595 command: function() 596 { 597 self.togglePseudoClassLock(":active"); 598 } 599 } 600 ); 601 602 if (Dom.domUtils.hasPseudoClassLock) 603 { 604 items.push( 605 { 606 label: "style.option.label.focus", 607 type: "checkbox", 608 checked: self.hasPseudoClassLock(":focus"), 609 tooltiptext: "style.option.tip.focus", 610 command: function() 611 { 612 self.togglePseudoClassLock(":focus"); 613 } 614 } 615 ); 616 } 617 } 618 619 return items; 620 }, 621 622 getContextMenuItems: function(style, target) 623 { 624 var items = CSSStyleSheetPanel.prototype.getContextMenuItems.apply(this, [style, target]); 625 var insertIndex = 0; 626 627 for (var i = 0; i < items.length; ++i) 628 { 629 if (items[i].id == "fbNewCSSRule") 630 { 631 items.splice(i, 1); 632 insertIndex = i; 633 break; 634 } 635 } 636 637 items.splice(insertIndex, 0, { 638 label: "EditStyle", 639 tooltiptext: "style.tip.Edit_Style", 640 command: Obj.bindFixed(this.editElementStyle, this) 641 }, 642 { 643 label: "AddRule", 644 tooltiptext: "css.tip.AddRule", 645 command: Obj.bindFixed(this.addRelatedRule, this) 646 }); 647 648 if (style instanceof Ci.nsIDOMFontFace && style.rule) 649 { 650 items.push( 651 "-", 652 { 653 label: "css.label.Inspect_Declaration", 654 tooltiptext: "css.tip.Inspect_Declaration", 655 id: "fbInspectDeclaration", 656 command: Obj.bindFixed(this.inspectDeclaration, this, style.rule) 657 } 658 ); 659 } 660 661 return items; 662 }, 663 664 showInfoTip: function(infoTip, target, x, y, rangeParent, rangeOffset) 665 { 666 var prop = Dom.getAncestorByClass(target, "cssProp"); 667 if (prop) 668 var propNameNode = prop.getElementsByClassName("cssPropName").item(0); 669 670 if (propNameNode && (propNameNode.textContent.toLowerCase() == "font" || 671 propNameNode.textContent.toLowerCase() == "font-family")) 672 { 673 var prevSibling = target.previousElementSibling; 674 while (prevSibling) 675 { 676 rangeOffset += prevSibling.textContent.length; 677 prevSibling = prevSibling.previousElementSibling; 678 } 679 } 680 681 return CSSStyleSheetPanel.prototype.showInfoTip.call( 682 this, infoTip, target, x, y, rangeParent, rangeOffset); 683 }, 684 685 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 686 // Extends stylesheet (CSS Panel) 687 688 deleteRuleDeclaration: function(cssSelector) 689 { 690 var repObject = Firebug.getRepObject(cssSelector); 691 692 if (repObject instanceof window.Element) 693 CSSModule.deleteRule(repObject); 694 else 695 CSSStyleSheetPanel.prototype.deleteRuleDeclaration(cssSelector); 696 697 this.refresh(); 698 }, 699 700 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 701 702 hasPseudoClassLock: function(pseudoClass) 703 { 704 if (Dom.domUtils.hasPseudoClassLock) 705 { 706 return Dom.domUtils.hasPseudoClassLock(this.selection, pseudoClass); 707 } 708 else 709 { 710 // Fallback in case the new pseudo-class lock API isn't available 711 var state = safeGetContentState(this.selection); 712 switch(pseudoClass) 713 { 714 case ":active": 715 return state & STATE_ACTIVE; 716 717 case ":hover": 718 return state & STATE_HOVER; 719 } 720 } 721 }, 722 723 togglePseudoClassLock: function(pseudoClass) 724 { 725 if (FBTrace.DBG_CSS) 726 FBTrace.sysout("css.togglePseudoClassLock; pseudo-class: " + pseudoClass); 727 728 if (Dom.domUtils.hasPseudoClassLock) 729 { 730 if (Dom.domUtils.hasPseudoClassLock(this.selection, pseudoClass)) 731 Dom.domUtils.removePseudoClassLock(this.selection, pseudoClass); 732 else 733 Dom.domUtils.addPseudoClassLock(this.selection, pseudoClass); 734 } 735 else 736 { 737 // Fallback in case the new pseudo-class lock API isn't available 738 var currentState = safeGetContentState(this.selection); 739 var remove = false; 740 switch(pseudoClass) 741 { 742 case ":active": 743 state = STATE_ACTIVE; 744 break; 745 746 case ":hover": 747 state = STATE_HOVER; 748 break; 749 } 750 remove = currentState & state; 751 752 Dom.domUtils.setContentState(remove ? this.selection.ownerDocument.documentElement : 753 this.selection, state); 754 } 755 756 this.refresh(); 757 }, 758 759 clearPseudoClassLocks: function() 760 { 761 if (Dom.domUtils.clearPseudoClassLocks) 762 Dom.domUtils.clearPseudoClassLocks(this.selection); 763 }, 764 765 addStateChangeHandlers: function(el) 766 { 767 this.removeStateChangeHandlers(); 768 769 Events.addEventListener(el, "focus", this.onStateChange, true); 770 Events.addEventListener(el, "blur", this.onStateChange, true); 771 Events.addEventListener(el, "mouseup", this.onStateChange, false); 772 Events.addEventListener(el, "mousedown", this.onStateChange, false); 773 Events.addEventListener(el, "mouseover", this.onStateChange, false); 774 Events.addEventListener(el, "mouseout", this.onStateChange, false); 775 776 this.stateChangeEl = el; 777 }, 778 779 removeStateChangeHandlers: function() 780 { 781 var sel = this.stateChangeEl; 782 if (sel) 783 { 784 Events.removeEventListener(sel, "focus", this.onStateChange, true); 785 Events.removeEventListener(sel, "blur", this.onStateChange, true); 786 Events.removeEventListener(sel, "mouseup", this.onStateChange, false); 787 Events.removeEventListener(sel, "mousedown", this.onStateChange, false); 788 Events.removeEventListener(sel, "mouseover", this.onStateChange, false); 789 Events.removeEventListener(sel, "mouseout", this.onStateChange, false); 790 } 791 792 this.stateChangeEl = null; 793 }, 794 795 contentStateCheck: function(state) 796 { 797 if (!state || this.contentState & state) 798 { 799 var timeoutRunner = Obj.bindFixed(function() 800 { 801 var newState = safeGetContentState(this.selection); 802 if (newState != this.contentState) 803 { 804 this.context.invalidatePanels(this.name); 805 } 806 }, this); 807 808 // Delay exec until after the event has processed and the state has been updated 809 setTimeout(timeoutRunner, 0); 810 } 811 } 812 }); 813 814 // ********************************************************************************************* // 815 // Helpers 816 817 function safeGetContentState(selection) 818 { 819 try 820 { 821 if (selection && selection.ownerDocument) 822 return Dom.domUtils.getContentState(selection); 823 } 824 catch (e) 825 { 826 if (FBTrace.DBG_ERRORS && FBTrace.DBG_CSS) 827 FBTrace.sysout("css.safeGetContentState; EXCEPTION "+e, e); 828 } 829 } 830 831 function getFontPropValueParts(element, value, propName) 832 { 833 const genericFontFamilies = 834 { 835 "serif": 1, 836 "sans-serif": 1, 837 "cursive": 1, 838 "fantasy": 1, 839 "monospace": 1, 840 }; 841 842 var parts = [], origValue = value; 843 844 // (Mirroring CSSModule.parseCSSFontFamilyValue) 845 if (propName === "font") 846 { 847 var rePreFont = new RegExp( 848 "^.*" + // anything, then 849 "(" + 850 "\\d+(\\.\\d+)?([a-z]*|%)|" + // a number (with possible unit) 851 "(x{1,2}-)?(small|large)|medium|larger|smaller" + // or an named size description 852 ") " 853 ); 854 var matches = rePreFont.exec(value); 855 if (!matches) 856 { 857 // Non-simple font value, like "inherit", "status-bar" or 858 // "calc(12px) Arial" - just return the whole text. 859 return [{type: "otherProps", value: value, lastFont: true}]; 860 } 861 var preProps = matches[0].slice(0, -1); 862 parts.push({type: "otherProps", value: preProps}); 863 value = value.substr(matches[0].length); 864 } 865 866 var matches = /^(.*?)( !important)?$/.exec(value); 867 var fonts = matches[1].split(","); 868 869 // What we want to know is what the specified "font-family" property means 870 // for the selected element's text, not what the element actually uses (that 871 // depends on font styles of its descendants). Thus, we just check the direct 872 // child text nodes of the element. 873 // Do not create a temporary element for testing to avoid problems like in 874 // issue 5905 and 6048 875 var usedFonts = []; 876 var child = element.firstChild; 877 do 878 { 879 if (!child) 880 break; 881 882 if (child.nodeType == Node.TEXT_NODE) 883 usedFonts = Arr.extendArray(usedFonts, Fonts.getFonts(child)); 884 } 885 while (child = child.nextSibling); 886 887 var genericFontUsed = false; 888 for (var i = 0; i < fonts.length; ++i) 889 { 890 var font = fonts[i].replace(/^["'](.*)["']$/, "$1").toLowerCase(); 891 var isGeneric = genericFontFamilies.hasOwnProperty(font); 892 var isUsedFont = false; 893 894 for (var j = 0; j < usedFonts.length; ++j) 895 { 896 var usedFont = usedFonts[j].CSSFamilyName.toLowerCase(); 897 if (font == usedFont || (isGeneric && !genericFontUsed)) 898 { 899 parts.push({type: "used", value: fonts[i], font: usedFonts[j]}); 900 usedFonts.splice(j, 1); 901 902 isUsedFont = true; 903 if (isGeneric) 904 genericFontUsed = true; 905 break; 906 } 907 } 908 909 if (!isUsedFont) 910 parts.push({type: "unused", value: fonts[i]}); 911 } 912 913 // xxxsz: Domplate doesn't allow to check for the last element in an array yet, 914 // so use this as hack 915 parts[parts.length-1].lastFont = true; 916 917 if (matches[2]) 918 parts.push({type: "important", value: " !important"}); 919 920 return parts; 921 } 922 923 // ********************************************************************************************* // 924 // Registration 925 926 Firebug.registerPanel(CSSStylePanel); 927 928 return CSSStylePanel; 929 930 // ********************************************************************************************* // 931 }}); 932