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