1 /* See license.txt for terms of usage */
  2 
  3 define([
  4     "firebug/lib/events",
  5     "firebug/lib/trace",
  6     "firebug/lib/deprecated",
  7 ],
  8 function factoryOptions(Events, FBTrace, Deprecated) {
  9 "use strict";
 10 
 11 // xxxFlorent: maybe parts of these methods should be in a seperate module
 12 // ********************************************************************************************* //
 13 // Constants
 14 
 15 const Cc = Components.classes;
 16 const Ci = Components.interfaces;
 17 
 18 const nsIPrefBranch = Ci.nsIPrefBranch;
 19 const PrefService = Cc["@mozilla.org/preferences-service;1"];
 20 
 21 const nsIPrefService = Ci.nsIPrefService;
 22 const prefService = PrefService.getService(nsIPrefService);
 23 const prefs = PrefService.getService(nsIPrefBranch);
 24 
 25 const prefNames =  // XXXjjb TODO distribute to modules
 26 [
 27     // Global
 28     "defaultPanelName", "throttleMessages", "textSize", "showInfoTips",
 29     "commandEditor", "textWrapWidth", "framePosition", "showErrorCount",
 30     "activateSameOrigin", "allPagesActivation", "hiddenPanels",
 31     "panelTabMinWidth", "sourceLinkLabelWidth", "currentVersion",
 32     "useDefaultLocale", "toolbarCustomizationDone", "addonBarOpened",
 33     "showBreakNotification", "stringCropLength", "showFirstRunPage",
 34 
 35     // Search
 36     "searchCaseSensitive", "searchGlobal", "searchUseRegularExpression",
 37     "netSearchHeaders", "netSearchParameters", "netSearchResponseBody",
 38 
 39     // Console
 40     "showJSErrors", "showJSWarnings", "showCSSErrors", "showXMLErrors",
 41     "showChromeErrors", "showChromeMessages",
 42     "showXMLHttpRequests", "showNetworkErrors", "tabularLogMaxHeight",
 43     "consoleFilterTypes", "alwaysShowCommandLine",
 44 
 45     // HTML
 46     "showFullTextNodes", "showCommentNodes",
 47     "showTextNodesWithWhitespace", "entityDisplay",
 48     "highlightMutations", "expandMutations", "scrollToMutations", "shadeBoxModel",
 49     "showQuickInfoBox", "displayedAttributeValueLimit", "multiHighlightLimit",
 50 
 51     // CSS
 52     "onlyShowAppliedStyles",
 53     "showUserAgentCSS",
 54     "expandShorthandProps",
 55     "cssEditMode",
 56     "colorDisplay",
 57 
 58     // Computed
 59     "computedStylesDisplay",
 60     "showMozillaSpecificStyles",
 61 
 62     // Script
 63     "decompileEvals", "replaceTabs", "maxScriptLineLength",
 64 
 65     // DOM
 66     "showUserProps", "showUserFuncs", "showDOMProps", "showDOMFuncs", "showDOMConstants",
 67     "ObjectShortIteratorMax", "showEnumerableProperties", "showOwnProperties",
 68     "showInlineEventHandlers",
 69 
 70     // Layout
 71     "showRulers",
 72 
 73     // Net
 74     "netFilterCategory", "netDisplayedResponseLimit",
 75     "netDisplayedPostBodyLimit", "netPhaseInterval", "sizePrecision",
 76     "netParamNameLimit", "netShowPaintEvents", "netShowBFCacheResponses",
 77     "netHtmlPreviewHeight",
 78 
 79     // JSON Preview
 80     "sortJsonPreview",
 81 
 82     // Stack
 83     "omitObjectPathStack",
 84 
 85     "showStackTrace", // Console
 86     "filterSystemURLs", // Stack
 87     "breakOnErrors",  "trackThrowCatch" // Script
 88 ];
 89 
 90 var optionUpdateMap = {};
 91 
 92 var listeners = [];
 93 
 94 var prefDomain = "extensions.firebug";
 95 
 96 // ********************************************************************************************* //
 97 
 98 /**
 99  * @name Options
100  * @lib Util to manage firefox preferences <br/>
101  *
102  * Panels send commands to request option change.
103  * Backend responds with events when the change is accepted.
104  */
105 var Options =
106 /** @lends Options */
107 {
108     // xxxFlorent: do we allow this syntax? (https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Operators/get)
109     get prefDomain()
110     {
111         return prefDomain;
112     },
113 
114     // this function is less used than direct access to Options.prefDomain. 
115     // So, this one is deprecated
116     getPrefDomain: Deprecated.deprecated("Use Options.prefDomain instead", function()
117     {
118         return prefDomain;
119     }),
120 
121     initialize: function(_prefDomain)
122     {
123         prefDomain = _prefDomain;
124 
125         if (FBTrace.DBG_INITIALIZE)
126             FBTrace.sysout("options.initialize with prefDomain " + this.prefDomain);
127 
128         this.initializePrefs();
129     },
130 
131     shutdown: function()
132     {
133         prefs.removeObserver(this.prefDomain, this, false);
134     },
135 
136     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
137     // Custom Listeners
138 
139     addListener: function(listener)
140     {
141         listeners.push(listener);
142     },
143 
144     removeListener: function(listener)
145     {
146         for (var i=0; i<listeners.length; ++i)
147             if (listeners[i] == listener)
148                 return listeners.splice(i, 1);
149     },
150 
151     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
152     // nsIPrefObserver
153 
154     observe: function(subject, topic, data)
155     {
156         if (data.indexOf(Options.prefDomain) === -1)
157             return;
158 
159         var name = data.substr(Options.prefDomain.length+1);  // +1 for .
160         var value = this.get(name);
161 
162         if (FBTrace.DBG_OPTIONS)
163             FBTrace.sysout("options.observe name = value: "+name+"= "+value+"\n");
164 
165         this.updatePref(name, value);
166     },
167 
168     updatePref: function(name, value)
169     {
170         // Prevent infinite recursion due to pref observer
171         if (optionUpdateMap.hasOwnProperty(name))
172             return;
173 
174         try
175         {
176             optionUpdateMap[name] = 1;
177             Firebug[name] = value;
178 
179             Events.dispatch(listeners, "updateOption", [name, value]);
180         }
181         catch (err)
182         {
183             if (FBTrace.DBG_OPTIONS || FBTrace.DBG_ERRORS)
184                 FBTrace.sysout("options.updatePref EXCEPTION:" + err, err);
185         }
186         finally
187         {
188             delete optionUpdateMap[name];
189         }
190 
191         if (FBTrace.DBG_OPTIONS)
192             FBTrace.sysout("options.updatePref EXIT: "+name+"="+value+"\n");
193     },
194 
195     register: function(name, value)
196     {
197         var currentValue = this.getPref(this.prefDomain, name);
198 
199         if (FBTrace.DBG_INITIALIZE)
200             FBTrace.sysout("registerPreference "+name+" -> "+value+" type "+typeof(value)+
201                 " with currentValue "+currentValue);
202 
203         if (currentValue === undefined)
204         {
205             // https://developer.mozilla.org/en/Code_snippets/Preferences
206             // This is the reason why you should usually pass strings ending with a dot to
207             // getBranch(), like prefs.getBranch("accessibility.").
208             var defaultBranch = prefService.getDefaultBranch(this.prefDomain+"."); //
209 
210             var type = this.getPreferenceTypeByExample(typeof(value));
211             if (this.setPreference(name, value, type, defaultBranch))
212                 return true;
213         }
214 
215         return false;
216     },
217 
218     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
219     // Options
220     // TODO support per context options eg break on error
221 
222     initializePrefs: function()
223     {
224         for (var i = 0; i < prefNames.length; ++i)
225             Firebug[prefNames[i]] = this.getPref(this.prefDomain, prefNames[i]);
226 
227         prefs.addObserver(this.prefDomain, this, false);
228 
229         var basePrefNames = prefNames.length;
230 
231         for (var i = basePrefNames; i < prefNames.length; ++i)
232             Firebug[prefNames[i]] = this.getPref(this.prefDomain, prefNames[i]);
233 
234         if (FBTrace.DBG_OPTIONS)
235         {
236              for (var i = 0; i < prefNames.length; ++i)
237              {
238                 FBTrace.sysout("options.initialize option "+this.prefDomain+"."+prefNames[i]+"="+
239                     Firebug[prefNames[i]]+"\n");
240              }
241         }
242     },
243 
244     togglePref: function(name)
245     {
246         this.set(name, !this.get(name));
247     },
248 
249     get: function(name)
250     {
251         return Options.getPref(this.prefDomain, name);
252     },
253 
254     getPref: function(prefDomain, name)
255     {
256         var prefName = prefDomain + "." + name;
257 
258         var type = prefs.getPrefType(prefName);
259 
260         var value;
261         if (type == nsIPrefBranch.PREF_STRING)
262             value = prefs.getCharPref(prefName);
263         else if (type == nsIPrefBranch.PREF_INT)
264             value = prefs.getIntPref(prefName);
265         else if (type == nsIPrefBranch.PREF_BOOL)
266             value = prefs.getBoolPref(prefName);
267 
268         if (FBTrace.DBG_OPTIONS)
269             FBTrace.sysout("options.getPref "+prefName+" has type "+
270                 this.getPreferenceTypeName(type)+" and value "+value);
271 
272         return value;
273     },
274 
275     set: function(name, value)
276     {
277         Options.setPref(Options.prefDomain, name, value);
278     },
279 
280     /**
281      * Set a preference value.
282      *
283      * @param prefDomain, e.g. "extensions.firebug"
284      * @param name Name of the preference (the part after prfDomain without dot)
285      * @param value New value for the preference.
286      * @param prefType optional pref type useful when adding a new preference.
287      */
288     setPref: function(prefDomain, name, value, prefType)
289     {
290         var prefName = prefDomain + "." + name;
291 
292         var type = this.getPreferenceTypeByExample((prefType ? prefType : typeof(value)));
293         if (!this.setPreference(prefName, value, type, prefs))
294             return;
295 
296         if (FBTrace.DBG_OPTIONS)
297             FBTrace.sysout("options.setPref type="+type+" name="+prefName+" value="+value);
298     },
299 
300     setPreference: function(prefName, value, type, prefBranch)
301     {
302         if (FBTrace.DBG_OPTIONS)
303             FBTrace.sysout("setPreference "+prefName, {prefName: prefName, value: value});
304 
305         if (type == nsIPrefBranch.PREF_STRING)
306             prefBranch.setCharPref(prefName, value);
307         else if (type == nsIPrefBranch.PREF_INT)
308             prefBranch.setIntPref(prefName, value);
309         else if (type == nsIPrefBranch.PREF_BOOL)
310             prefBranch.setBoolPref(prefName, value);
311         else if (type == nsIPrefBranch.PREF_INVALID)
312         {
313             FBTrace.sysout("options.setPref FAILS: Invalid preference "+prefName+" with type "+
314                 type+", check that it is listed in defaults/prefs.js");
315 
316             return false;
317         }
318 
319         return true;
320     },
321 
322     getPreferenceTypeByExample: function(prefType)
323     {
324         if (prefType)
325         {
326             if (prefType === typeof("s"))
327                 var type = nsIPrefBranch.PREF_STRING;
328             else if (prefType === typeof(1))
329                 var type = nsIPrefBranch.PREF_INT;
330             else if (prefType === typeof (true))
331                 var type = nsIPrefBranch.PREF_BOOL;
332             else
333                 var type = nsIPrefBranch.PREF_INVALID;
334         }
335         else
336         {
337             var type = prefs.getPrefType(prefName);
338         }
339 
340         return type;
341     },
342 
343     getPreferenceTypeName: function(prefType)
344     {
345         if (prefType == Ci.nsIPrefBranch.PREF_STRING)
346             return "string";
347         else if (prefType == Ci.nsIPrefBranch.PREF_INT)
348             return "int";
349         else if (prefType == Ci.nsIPrefBranch.PREF_BOOL)
350             return "boolean";
351     },
352 
353     clear: function(name)
354     {
355         Options.clearPref(Options.prefDomain, name);
356     },
357 
358     clearPref: function(prefDomain, name)
359     {
360         var prefName = prefDomain + "." + name;
361         if (prefs.prefHasUserValue(prefName))
362             prefs.clearUserPref(prefName);
363     },
364 
365     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
366     // Firebug UI text zoom
367 
368     changeTextSize: function(amt)
369     {
370         var textSize = Options.get("textSize");
371         var newTextSize = textSize + amt;
372         if ((newTextSize < 0 && Math.abs(newTextSize) < this.negativeZoomFactors.length) ||
373             (newTextSize >= 0 && textSize+amt < this.positiveZoomFactors.length))
374         {
375             this.setTextSize(textSize+amt);
376         }
377     },
378 
379     setTextSize: function(value)
380     {
381         var setValue = value;
382         if (value >= this.positiveZoomFactors.length)
383             setValue = this.positiveZoomFactors[this.positiveZoomFactors.length-1];
384         else if (value < 0 && Math.abs(value) >= this.negativeZoomFactors.length)
385             setValue = this.negativeZoomFactors[this.negativeZoomFactors.length-1];
386         this.set("textSize", setValue);
387     },
388 
389     positiveZoomFactors: [1, 1.1, 1.2, 1.3, 1.5, 2, 3],
390     negativeZoomFactors: [1, 0.95, 0.8, 0.7, 0.5],
391 
392     getZoomByTextSize: function(value)
393     {
394         var zoom = value >= 0 ? this.positiveZoomFactors[value] :
395             this.negativeZoomFactors[Math.abs(value)];
396 
397         return zoom;
398     },
399 
400     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
401 
402     /**
403      * Resets all Firebug options to default state. Note that every option
404      * starting with "extensions.firebug" is considered as a Firebug option.
405      *
406      * Options starting with DBG_ are intended for Firebug Tracing Console (FBTrace)
407      * and ignored (their state is not changed).
408      */
409     resetAllOptions: function()
410     {
411         var preferences = prefs.getChildList("extensions.firebug", {});
412         for (var i = 0; i < preferences.length; i++)
413         {
414             if (preferences[i].indexOf("DBG_") == -1)
415             {
416                 if (FBTrace.DBG_OPTIONS)
417                     FBTrace.sysout("Clearing option: " + i + ") " + preferences[i]);
418 
419                 if (prefs.prefHasUserValue(preferences[i]))  // avoid exception
420                     prefs.clearUserPref(preferences[i]);
421             }
422             else
423             {
424                 if (FBTrace.DBG_OPTIONS)
425                     FBTrace.sysout("Skipped clearing option: " + i + ") " + preferences[i]);
426             }
427         }
428     },
429 
430     forceSave: function()
431     {
432         prefs.savePrefFile(null);
433     }
434 };
435 
436 // ********************************************************************************************* //
437 // Registration
438 
439 return Options;
440 
441 // ********************************************************************************************* //
442 });
443