1 /* See license.txt for terms of usage */
  2 
  3 define([
  4     "firebug/lib/object",
  5     "firebug/firebug",
  6     "firebug/lib/css",
  7     "firebug/lib/search",
  8     "firebug/lib/system",
  9     "firebug/lib/string",
 10     "firebug/lib/locale"
 11 ],
 12 function(Obj, Firebug, Css, Search, System, Str, Locale) {
 13 
 14 // ********************************************************************************************* //
 15 // Constants
 16 
 17 const Cc = Components.classes;
 18 const Ci = Components.interfaces;
 19 
 20 const searchDelay = 150;
 21 
 22 // ********************************************************************************************* //
 23 
 24 /**
 25  * @module Implements basic search box functionality. The box is displayed on the right side
 26  * of the Firebug's toolbar. Specific search capabilities depends on the current panel
 27  * and implemented in <code>panel.search</code> method. The search-box is automatically
 28  * available for panels that have <code>searchable<code> property set to true (set to
 29  * false by default).
 30  */
 31 Firebug.Search = Obj.extend(Firebug.Module,
 32 {
 33     dispatchName: "search",
 34 
 35     onSearchCommand: function(document)
 36     {
 37         var el = document.activeElement;
 38         var id = el.id;
 39 
 40         if (id == "fbPanelBar1-browser" || id == "fbPanelBar2-browser")
 41         {
 42             var sel = el.contentWindow.getSelection().toString();
 43             if (!sel)
 44             {
 45                 var input = el.contentDocument.activeElement;
 46                 if (input instanceof Ci.nsIDOMNSEditableElement)
 47                 {
 48                     sel = input.QueryInterface(Ci.nsIDOMNSEditableElement).
 49                         editor.selection.toString();
 50                 }
 51             }
 52 
 53             this.search(sel, Firebug.currentContext);
 54             this.focus();
 55         }
 56     },
 57 
 58     search: function(text, context)
 59     {
 60         var searchBox = Firebug.chrome.$("fbSearchBox");
 61         searchBox.value = text;
 62         this.update(context);
 63     },
 64 
 65     searchNext: function(context)
 66     {
 67         return this.update(context, true, false);
 68     },
 69 
 70     searchPrev: function(context)
 71     {
 72         return this.update(context, true, true);
 73     },
 74 
 75     displayOnly: function(text, context)
 76     {
 77         var searchBox = Firebug.chrome.$("fbSearchBox");
 78 
 79         if (text && text.length > 0)
 80             Css.setClass(searchBox, "fbSearchBox-attention");
 81         else
 82             Css.removeClass(searchBox, "fbSearchBox-attention");
 83 
 84         searchBox.value = text;
 85     },
 86 
 87     focus: function(context)
 88     {
 89         if (Firebug.isDetached())
 90             Firebug.chrome.focus();
 91         else
 92             Firebug.toggleBar(true);
 93 
 94         var searchBox = Firebug.chrome.$("fbSearchBox");
 95         searchBox.focus();
 96         searchBox.select();
 97     },
 98 
 99     update: function(context, immediate, reverse)
100     {
101         var panel = Firebug.chrome.getSelectedPanel();
102         if (!panel || !panel.searchable)
103             return;
104 
105         var searchBox = Firebug.chrome.$("fbSearchBox");
106         var panelNode = panel.panelNode;
107 
108         var value = searchBox.value;
109 
110         this.addToHistory(value);
111 
112         // This sucks, but the find service won't match nodes that are invisible, so we
113         // have to make sure to make them all visible unless the user is appending to the
114         // last string, in which case it's ok to just search the set of visible nodes
115         if (!panel.searchText || value == panel.searchText ||
116             !Str.hasPrefix(value, panel.searchText))
117         {
118             Css.removeClass(panelNode, "searching");
119         }
120 
121         if (Firebug.Search.isCaseSensitive(value))
122             Css.setClass(searchBox, "fbSearchBox-autoSensitive");
123         else
124             Css.removeClass(searchBox, "fbSearchBox-autoSensitive");
125 
126         if (FBTrace.DBG_SEARCH)
127         {
128             FBTrace.sysout("search Firebug.Search.isAutoSensitive(value):" +
129                 Firebug.Search.isAutoSensitive(value) + " for " + value, searchBox)
130         }
131 
132         // Cancel the previous search to keep typing smooth
133         clearTimeout(panelNode.searchTimeout);
134 
135         if (immediate)
136         {
137             var found = panel.search(value, reverse);
138             if (!found && value)
139                this.onNotFound();
140 
141             if (value)
142             {
143                 // Hides all nodes that didn't pass the filter
144                 Css.setClass(panelNode, "searching");
145             }
146             else
147             {
148                 // Makes all nodes visible again
149                 Css.removeClass(panelNode, "searching");
150             }
151 
152             panel.searchText = value;
153 
154             return found;
155         }
156         else
157         {
158             // After a delay, perform the search
159             panelNode.searchTimeout = setTimeout(function()
160             {
161                 var found = panel.search(value, reverse);
162                 if (!found && value)
163                     Firebug.Search.onNotFound(value);
164 
165                 if (value)
166                 {
167                     // Hides all nodes that didn't pass the filter
168                     Css.setClass(panelNode, "searching");
169                 }
170                 else
171                 {
172                     // Makes all nodes visible again
173                     Css.removeClass(panelNode, "searching");
174                 }
175 
176                 panel.searchText = value;
177                 searchBox.status = (found ? "found" : "notfound");
178 
179                 if (FBTrace.DBG_SEARCH)
180                     FBTrace.sysout("search "+searchBox.status+" "+value);
181             }, searchDelay);
182         }
183     },
184 
185     onNotFound: function()
186     {
187         if (this.status != 'notfound')
188             System.beep();
189     },
190 
191     isCaseSensitive: function(text)
192     {
193         return !!Firebug.searchCaseSensitive || this.isAutoSensitive(text);
194     },
195 
196     isAutoSensitive: function(text)
197     {
198         return (text.toLowerCase() !== text);
199     },
200 
201     getTestingRegex: function(text)
202     {
203         var caseSensitive = Firebug.Search.isCaseSensitive(text);
204 
205         try
206         {
207             if (Firebug.searchUseRegularExpression)
208                 return new RegExp(text, caseSensitive ? "g" : "gi");
209             else
210                 return new Search.LiteralRegExp(text, false, caseSensitive);
211         }
212         catch (err)
213         {
214             // The user entered an invalid regex. Duck type the regex object
215             // to support literal searches when an invalid regex is entered
216             return new Search.LiteralRegExp(text, false, caseSensitive);
217         }
218     },
219 
220     searchOptionMenu: function(label, option, tooltiptext)
221     {
222         return {
223             label: label,
224             tooltiptext: tooltiptext,
225             checked: Firebug[option],
226             option: option,
227             command: Obj.bindFixed(this.onToggleSearchOption, this, option)
228         };
229     },
230 
231     onToggleSearchOption: function(option)
232     {
233         Firebug.Options.set(option, !Firebug[option]);
234 
235         // Make sure the "Case Sensitive || Case Insensitive" label is updated.
236         this.update();
237     },
238 
239     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
240     // History
241 
242     history: [""],
243 
244     addToHistory: function(val)
245     {
246         var history = this.history;
247 
248         if (!history[0] || Str.hasPrefix(val, history[0]))
249             history[0] = val;
250         else if (Str.hasPrefix(history[0], val))
251             return;
252         else
253             history.unshift(val);
254     },
255 
256     cycleHistory: function(dir)
257     {
258         var history = this.history;
259         if (dir > 0)
260             history.unshift(history.pop());
261         else
262             history.push(history.shift());
263 
264         return history[0]
265     },
266 
267     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
268     // extends Module
269 
270     internationalizeUI: function()
271     {
272         var sensitive = Firebug.chrome.$("fbSearchBoxIsSensitive");
273         sensitive.value = Locale.$STR("search.Case_Sensitive");
274         sensitive.setAttribute("tooltiptext", Locale.$STR("search.tip.Case_Sensitive"));
275 
276         var notSensitive = Firebug.chrome.$("fbSearchBoxIsNotSensitive");
277         notSensitive.value = Locale.$STR("search.Case_Insensitive");
278         notSensitive.setAttribute("tooltiptext", Locale.$STR("search.tip.Case_Insensitive"));
279     },
280 
281     shutdown: function()
282     {
283     },
284 
285     showPanel: function(browser, panel)
286     {
287         // Manage visibility of the search-box according to the searchable flag.
288         var searchBox = Firebug.chrome.$("fbSearchBox");
289         searchBox.status = "noSearch";
290         Css.removeClass(searchBox, "fbSearchBox-attention");
291         Css.removeClass(searchBox, "fbSearchBox-autoSensitive");
292 
293         if (panel)
294         {
295             searchBox.collapsed = !panel.searchable;
296             searchBox.updateOptions(panel.getSearchOptionsMenuItems());
297         }
298         else
299         {
300             searchBox.collapsed = false;
301         }
302     }
303 });
304 
305 // ********************************************************************************************* //
306 // Registration
307 
308 Firebug.registerModule(Firebug.Search);
309 
310 return Firebug.Search;
311 
312 // ********************************************************************************************* //
313 });
314