1 /* See license.txt for terms of usage */ 2 3 define([ 4 "firebug/firebug", 5 "firebug/lib/object", 6 "firebug/lib/locale", 7 "firebug/lib/events", 8 "firebug/lib/dom", 9 "firebug/lib/domplate", 10 "firebug/chrome/menu", 11 "firebug/css/selectorEditor", 12 "firebug/css/selectorModule", 13 ], 14 function(Firebug, Obj, Locale, Events, Dom, Domplate, Menu, SelectorEditor) { 15 with (Domplate) { 16 17 // ********************************************************************************************* // 18 // Constants 19 20 const Cc = Components.classes; 21 const Ci = Components.interfaces; 22 23 const prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch); 24 25 // ********************************************************************************************* // 26 // CSS Selector Panel 27 28 /** 29 * @panel Selector side panel displaying HTML elements for the current selector, 30 * either from the CSS main panel or user entry 31 */ 32 function SelectorPanel() {} 33 SelectorPanel.prototype = Obj.extend(Firebug.Panel, 34 /** @lends SelectorPanel */ 35 { 36 name: "selector", 37 parentPanel: "stylesheet", 38 title: Locale.$STR("css.selector.Selection"), 39 editable: true, 40 41 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 42 // Initialization 43 44 initialize: function(context, doc) 45 { 46 Firebug.Panel.initialize.apply(this, arguments); 47 }, 48 49 shutdown: function(context, doc) 50 { 51 Firebug.Panel.shutdown.apply(this, arguments); 52 }, 53 54 initializeNode: function(oldPanelNode) 55 { 56 Firebug.Panel.initializeNode.apply(this, arguments); 57 58 this.setSelection = Obj.bind(this.setSelection, this); 59 this.clearSelection = Obj.bind(this.clearSelection, this); 60 this.lockSelection = Obj.bind(this.lockSelection, this); 61 62 var panelNode = this.mainPanel.panelNode; 63 // See: http://code.google.com/p/fbug/issues/detail?id=5931 64 //Events.addEventListener(panelNode, "mouseover", this.setSelection, false); 65 //Events.addEventListener(panelNode, "mouseout", this.clearSelection, false); 66 //Events.addEventListener(panelNode, "mousedown", this.lockSelection, false); 67 }, 68 69 destroyNode: function() 70 { 71 var panelNode = this.mainPanel.panelNode; 72 //Events.removeEventListener(panelNode, "mouseover", this.setSelection, false); 73 //Events.removeEventListener(panelNode, "mouseout", this.clearSelection, false); 74 //Events.removeEventListener(panelNode, "mousedown", this.lockSelection, false); 75 76 Firebug.Panel.destroyNode.apply(this, arguments); 77 }, 78 79 show: function(state) 80 { 81 Firebug.Panel.show.apply(this, arguments); 82 83 this.refresh(); 84 }, 85 86 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 87 88 getCSSStyleRule: function(event) 89 { 90 var object = Firebug.getRepObject(event.target); 91 92 if (object && (object instanceof window.CSSStyleRule)) 93 return object; 94 }, 95 96 getCSSRuleElement: function(element) 97 { 98 while (element && !element.classList.contains("cssRule")) 99 element = element.parentNode; 100 101 return element; 102 }, 103 104 getMatchingElements: function(rule) 105 { 106 this.trialSelector = rule.selectorText; 107 this.selection = rule; 108 this.rebuild(); 109 }, 110 111 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 112 // Selection 113 114 setSelection: function(event) 115 { 116 var rule = this.getCSSStyleRule(event); 117 118 if (rule) 119 { 120 // then we have entered a rule element 121 var ruleElement = this.getCSSRuleElement(event.target); 122 if (ruleElement && ruleElement !== this.lockedElement) 123 ruleElement.classList.add("selectedSelectorRule"); 124 125 this.selection = rule; 126 this.rebuild(); 127 } 128 }, 129 130 clearSelection: function(event) 131 { 132 if (this.selection !== this.lockedSelection) 133 { 134 this.selection = this.lockedSelection; 135 this.rebuild(); 136 } 137 138 var rule = this.getCSSStyleRule(event); 139 if (rule) 140 { 141 // then we are leaving a rule element that we may have highlighted. 142 var ruleElement = this.getCSSRuleElement(event.target); 143 if (ruleElement) 144 ruleElement.classList.remove("selectedSelectorRule"); 145 } 146 }, 147 148 lockSelection: function(event) 149 { 150 var rule = this.getCSSStyleRule(event); 151 if (rule) 152 { 153 if (this.lockedElement) 154 this.lockedElement.classList.remove("lockedSelectorRule"); 155 156 this.lockedElement = this.getCSSRuleElement(event.target); 157 158 if (this.lockedElement) 159 { 160 this.lockedElement.classList.add("lockedSelectorRule"); 161 this.lockedElement.classList.remove("selectedSelectorRule"); 162 } 163 164 this.lockedSelection = rule; 165 } 166 }, 167 168 hide: function() 169 { 170 Firebug.Panel.hide.apply(this, arguments); 171 }, 172 173 refresh: function() 174 { 175 var root = this.context.window.document.documentElement; 176 this.selection = this.mainPanel.selection; 177 178 // Use trial selector if there is no selection in the CSS panel. 179 if (!this.selection) 180 this.selection = this.trialSelector; 181 182 this.rebuild(true); 183 }, 184 185 /** 186 * returns an array of Elements matched from selector 187 */ 188 getSelectedElements: function(selectorText) 189 { 190 var elements = []; 191 192 // Execute the query also in all iframes (see issue 5962) 193 var windows = this.context.windows; 194 for (var i=0; i<windows.length; i++) 195 { 196 var win = windows[i]; 197 var selections = win.document.querySelectorAll(selectorText); 198 199 // For some reason the return value of querySelectorAll() 200 // is not recognized as a NodeList anymore since Firefox 10.0. 201 // See issue 5442. 202 // But since there can be more iframes we need to collect all matching 203 // elements in an extra array anyway. 204 if (selections) 205 { 206 for (var j=0; j<selections.length; j++) 207 elements.push(selections[j]); 208 } 209 else 210 { 211 throw new Error("Selection Failed: " + selections); 212 } 213 } 214 215 return elements; 216 }, 217 218 /** 219 * Build content of the panel. The basic layout of the panel is generated by 220 * {@link SelectorTemplate} template. 221 */ 222 rebuild: function() 223 { 224 if (this.selection) 225 { 226 try 227 { 228 var selectorText; 229 230 if (this.selection instanceof window.CSSStyleRule) 231 selectorText = this.selection.selectorText; 232 else 233 selectorText = this.selection; 234 235 var elements = this.getSelectedElements(selectorText); 236 if (elements && elements.length != 0) 237 { 238 SelectorTemplate.tag.replace({object: elements}, this.panelNode); 239 this.showTrialSelector(this.trialSelector); 240 return; 241 } 242 } 243 catch (e) 244 { 245 var table = SelectorTemplate.tag.replace({object: []}, this.panelNode); 246 var tbody = table.lastChild; 247 248 WarningTemplate.selectErrorTag.insertRows({object: e}, tbody.lastChild); 249 WarningTemplate.selectErrorTextTag.insertRows({object: e}, tbody.lastChild); 250 251 this.showTrialSelector(this.trialSelector); 252 return; 253 } 254 } 255 256 var table = SelectorTemplate.tag.replace({object: []}, this.panelNode); 257 var tbody = table.lastChild; 258 259 if (this.trialSelector) 260 { 261 WarningTemplate.noSelectionResultsTag.insertRows( 262 {object: this.selection}, tbody.lastChild) 263 } 264 else 265 { 266 WarningTemplate.noSelectionTag.insertRows( 267 {object: this.selection}, tbody.lastChild); 268 } 269 270 this.showTrialSelector(this.trialSelector); 271 }, 272 273 getObjectPath: function(object) 274 { 275 if (FBTrace.DBG_ELEMENTS) 276 FBTrace.sysout("css.selector.getObjectPath NOOP", object); 277 }, 278 279 supportsObject: function(object) 280 { 281 return 0; 282 }, 283 284 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 285 286 tryASelector: function(element) 287 { 288 if (!this.trialSelector) 289 this.trialSelector = this.selection ? this.selection.selectorText : ""; 290 291 this.editProperty(element, this.trialSelector); 292 }, 293 294 editProperty: function(row, editValue) 295 { 296 Firebug.Editor.startEditing(row, editValue); 297 }, 298 299 getEditor: function(target, value) 300 { 301 if (!this.editor) 302 this.editor = new SelectorPanelEditor(this.document); 303 304 return this.editor; 305 }, 306 307 setTrialSelector: function(target, value) 308 { 309 if (this.lockedElement) 310 this.lockedElement.classList.remove("lockedSelectorRule"); 311 312 this.trialSelector = value; 313 this.selection = this.trialSelector; 314 this.lockedElement = target; 315 this.lockedSelection = this.selection; 316 this.rebuild(); 317 }, 318 319 showTrialSelector: function(trialSelector) 320 { 321 var show = trialSelector ? true : false; 322 Dom.collapse(this.document.getElementById("trialHint"), show); 323 324 var trialSelectorDiv = this.document.getElementById("trialSelector"); 325 trialSelectorDiv.textContent = trialSelector; 326 Dom.collapse(trialSelectorDiv, !show); 327 }, 328 }); 329 330 function SelectorPanelEditor(doc) 331 { 332 this.box = this.tag.replace({}, doc, this); 333 this.input = this.box; 334 335 Firebug.InlineEditor.prototype.initialize.call(this); 336 this.tabNavigation = false; 337 this.fixedWidth = true; 338 } 339 340 SelectorPanelEditor.prototype = domplate(SelectorEditor.prototype, 341 { 342 tag: 343 INPUT({"class": "fixedWidthEditor a11yFocusNoTab", 344 type: "text", 345 title: Locale.$STR("Selector"), 346 oninput: "$onInput", 347 onkeypress: "$onKeyPress"} 348 ), 349 350 endEditing: function(target, value, cancel) 351 { 352 if (cancel) 353 return; 354 355 this.panel.setTrialSelector(target, value); 356 } 357 }); 358 359 // ********************************************************************************************* // 360 361 var BaseRep = domplate(Firebug.Rep, 362 { 363 // xxxHonza: shouldn't this be in Firebug.Rep? 364 getNaturalTag: function(value) 365 { 366 var rep = Firebug.getRep(value); 367 var tag = rep.shortTag ? rep.shortTag : rep.tag; 368 return tag; 369 } 370 }); 371 372 // ********************************************************************************************* // 373 374 var TrialRow = 375 TR({"class": "watchNewRow", level: 0, onclick: "$onClickEditor"}, 376 TD({"class": "watchEditCell", colspan: 3}, 377 DIV({"class": "watchEditBox a11yFocusNoTab", "id": "trialHint", 378 role: "button", "tabindex" : "0", 379 "aria-label": Locale.$STR("a11y.labels.press enter to add new selector")}, 380 Locale.$STR("css.selector.TryASelector") 381 ), 382 DIV({"class": "trialSelector", "id": "trialSelector"}, "") 383 ) 384 ); 385 386 // ********************************************************************************************* // 387 388 /** 389 * @domplate: Template for basic layout of the {@link SelectorPanel} panel. 390 */ 391 var SelectorTemplate = domplate(BaseRep, 392 { 393 // object will be array of elements CSSStyleRule 394 tag: 395 TABLE({"class": "cssSelectionTable", cellpadding: 0, cellspacing: 0}, 396 TBODY({"class": "cssSelectionTBody"}, 397 TrialRow, 398 FOR("element", "$object", 399 TR({"class": "selectionElementRow", _repObject: "$element"}, 400 TD({"class": "selectionElement"}, 401 TAG( "$element|getNaturalTag", {object: "$element"}) 402 ) 403 ) 404 ) 405 ) 406 ), 407 408 onClickEditor: function(event) 409 { 410 var tr = event.currentTarget; 411 var panel = Firebug.getElementPanel(tr); 412 panel.tryASelector(tr); 413 }, 414 }); 415 416 // ********************************************************************************************* // 417 418 var WarningTemplate = domplate(Firebug.Rep, 419 { 420 noSelectionTag: 421 TR({"class": "selectorWarning"}, 422 TD({"class": "selectionElement"}, Locale.$STR("css.selector.noSelection")) 423 ), 424 425 noSelectionResultsTag: 426 TR({"class": "selectorWarning"}, 427 TD({"class": "selectionElement"}, Locale.$STR("css.selector.noSelectionResults")) 428 ), 429 430 selectErrorTag: 431 TR({"class": "selectorWarning"}, 432 TD({"class": "selectionElement"}, Locale.$STR("css.selector.selectorError")) 433 ), 434 435 selectErrorTextTag: 436 TR({"class": "selectorWarning"}, 437 TD({"class": "selectionErrorText selectionElement"}, 438 SPAN("$object|getErrorMessage") 439 ) 440 ), 441 442 getErrorMessage: function(object) 443 { 444 if (object.message) 445 return object.message; 446 447 return Locale.$STR("css.selector.unknownErrorMessage"); 448 } 449 }); 450 451 // ********************************************************************************************* // 452 // Registration 453 454 Firebug.registerStylesheet("chrome://firebug/skin/selector.css"); 455 Firebug.registerPanel(SelectorPanel); 456 457 return SelectorPanel; 458 459 // ********************************************************************************************* // 460 }}); 461