1 /* See license.txt for terms of usage */
  2 
  3 // ************************************************************************************************
  4 // Constants
  5 
  6 const Cc = Components.classes;
  7 const Ci = Components.interfaces;
  8 const Cu = Components.utils;
  9 
 10 const prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
 11 const names = ["label", "executable", "cmdline", "image"];
 12 
 13 // ************************************************************************************************
 14 // Globals
 15 
 16 var gEditorManager =
 17 {
 18     _tree : null,
 19     _data : [],
 20     _removeButton : null,
 21     _changeButton : null,
 22     _moveUpButton : null,
 23 
 24     init: function()
 25     {
 26         var args = window.arguments[0];
 27         this._FBL = args.FBL;
 28         this._prefName = args.prefName;
 29 
 30         (this._removeButton = document.getElementById("removeEditor")).disabled = true;
 31         (this._changeButton = document.getElementById("changeEditor")).disabled = true;
 32         (this._moveUpButton = document.getElementById("moveUpEditor")).disabled = true;
 33 
 34         this._tree = document.getElementById("editorsList");
 35 
 36         this._treeView =
 37         {
 38             data: this._data,
 39             selection: null,
 40 
 41             get rowCount() { return this.data.length; },
 42             getCellText: function(row, column)
 43             {
 44                 switch(column.id)
 45                 {
 46                 case "editorName":
 47                     return " "+this.data[row].label;
 48                 case "editorExecutable":
 49                     return this.data[row].executable;
 50                 case "editorParams":
 51                     return this.data[row].cmdline;
 52                 }
 53                 return "";
 54             },
 55             setTree: function(treebox){ this.treebox = treebox; },
 56             isContainer: function(row) { return false; },
 57             isContainerOpen: function(row) { return false; },
 58             isContainerEmpty: function(row) { return false; },
 59             isSeparator: function(row) { return false; },
 60             isSorted: function() { return false; },
 61             getLevel: function(row) { return 0; },
 62             getImageSrc: function(row,column) { return column.id=="editorName" ? this.data[row].image : null; },
 63             getRowProperties: function(row,props) {},
 64             getCellProperties: function(row,column,props) {},
 65             getColumnProperties: function(colid,column,props) {}
 66         };
 67 
 68         this._load();
 69         this._tree.view = this._treeView;
 70 
 71         this.internationalizeUI(document);
 72     },
 73 
 74     uninit: function()
 75     {
 76     },
 77 
 78     internationalizeUI: function(doc)
 79     {
 80         var elements = doc.getElementsByClassName("fbInternational");
 81         var attributes = ["title", "label", "value"];
 82         for (var i=0; i<elements.length; i++)
 83         {
 84             for(var j=0; j<attributes.length; j++)
 85             {
 86                 if (elements[i].hasAttribute(attributes[j]))
 87                     this._FBL.internationalize(elements[i], attributes[j]);
 88             }
 89         }
 90     },
 91 
 92     onSelectionChanged: function()
 93     {
 94         var selection = this._tree.view.selection, disabled = (selection.count != 1);
 95         this._removeButton.disabled = disabled;
 96         this._changeButton.disabled = disabled;
 97         this._moveUpButton.disabled = disabled || (selection.currentIndex == 0);
 98     },
 99 
100     addEditorHandler: function()
101     {
102         var item = { label: "", executable: null, cmdline: "" };
103         var result = {};
104         var args = {
105             item: item,
106             FBL: this._FBL
107         };
108 
109         openDialog("chrome://firebug/content/firefox/external-editors/changeeditor.xul",
110             "_blank", "modal,centerscreen,resizable", args, result);
111 
112         if (result.saveChanges)
113         {
114             item.id = item.label.replace(/\W/g, "_");
115             this._saveItem(item);
116 
117             this._loadItem(item);
118             this._data.push(item);
119             this._tree.view = this._treeView;
120 
121             var editors = [];
122             try {
123                 editors = prefs.getCharPref(this._prefName).split(",");
124                 for( var i = 0; i < editors.length; ++i )
125                 {
126                     if ( editors[i].replace(/^\s+|\s+$/,"") == "" )
127                         editors.splice(i, 1);
128                 }
129             }
130             catch(exc)
131             {
132                 this._FBL.ERROR(exc);
133             }
134             editors.push(item.id);
135             prefs.setCharPref(this._prefName, editors.join(","));
136         }
137     },
138 
139     removeEditorHandler: function()
140     {
141         var selection = this._tree.view.selection;
142         if (selection.count < 1)
143             return;
144         var item = this._data[selection.currentIndex];
145         this._data.splice(selection.currentIndex, 1);
146         this._tree.view = this._treeView;
147 
148         try {
149             var editors = prefs.getCharPref(this._prefName).split(",");
150             this._FBL.remove(editors, item.id);
151             prefs.setCharPref(this._prefName, editors.join(","));
152             prefs.deleteBranch(this._prefName+"."+item.id);
153         }
154         catch(exc)
155         {
156             this._FBL.ERROR(exc);
157         }
158         // update disabled state of buttons
159         if (this._data.length == 0)
160             selection.clearSelection();
161     },
162 
163     changeEditorHandler: function()
164     {
165         var selection = this._tree.view.selection;
166         if (selection.count != 1)
167             return;
168         var item = this._data[selection.currentIndex];
169         var args = {
170             item: item,
171             FBL: this._FBL
172         };
173         var result = {};
174 
175         openDialog("chrome://firebug/content/firefox/external-editors/changeeditor.xul",
176             "_blank", "modal,centerscreen", args, result);
177 
178         if (result.saveChanges)
179         {
180             this._saveItem(item);
181         }
182         this._loadItem(item);
183         this._tree.view = this._treeView;
184     },
185 
186     moveUpEditorHandler: function()
187     {
188         var selection = this._tree.view.selection;
189         if (selection.count < 1)
190             return;
191         var item = this._data[selection.currentIndex];
192         this._data.splice(selection.currentIndex, 1);
193         this._data.unshift(item);
194         this._tree.view = this._treeView;
195         try {
196             var editors = this._data.map(function(x) x.id);
197             prefs.setCharPref(this._prefName, editors.join(","));
198         }
199         catch(exc)
200         {
201             this._FBL.ERROR(exc);
202         }
203     },
204 
205 
206     _loadItem: function(item)
207     {
208         const prefName = this._prefName;
209         for( var i = 0; i < names.length; ++i )
210         {
211             try {
212                 item[names[i]] = prefs.getCharPref(prefName+"."+item.id+"."+names[i]);
213             }
214             catch(exc)
215             {}
216         }
217         if (!item.image)
218             item.image = this._FBL.getIconURLForFile(item.executable);
219     },
220 
221     _saveItem: function(item)
222     {
223         if ( item.image && item.image == this._FBL.getIconURLForFile(item.executable) )
224             item.image = null;
225 
226         const prefName = this._prefName;
227         for( var i = 0; i < names.length; ++i )
228         {
229             try {
230                 var value = item[names[i]];
231                 if ( value )
232                     prefs.setCharPref(prefName+"."+item.id+"."+names[i], value);
233                 else
234                     prefs.clearUserPref(prefName+"."+item.id+"."+names[i]);
235             }
236             catch(exc)
237             {}
238         }
239     },
240 
241     _load: function()
242     {
243         try {
244             var list = prefs.getCharPref(this._prefName).split(",");
245             for (var i = 0; i < list.length; ++i)
246             {
247                 var editorId = list[i].replace(/\s/g, "_");
248                 if ( !editorId )
249                     continue;
250                 var item = { id: editorId };
251                 this._data.push(item);
252                 this._loadItem(item);
253             }
254         }
255         catch(exc)
256         {
257             this._FBL.ERROR(exc);
258         }
259     }
260 
261 };
262 
263 // ************************************************************************************************
264 // URLMappings
265 Cu.import("resource://firebug/loader.js")
266 
267 var headerName = "X-Local-File-Path";
268 var headerExplaination = "\
269 // the following regexp is used by firebug to determine\n\
270 // if it should send request to the server to get\n\
271 // file path with " + headerName + " header\n\
272 // defalt value is ^https?:\\/\\/(localhost)(\\/|:|$)";
273 
274 var listExplaination = "\
275 // list of mappings in the form\n\
276 // ^https?:\\/\\/my.domain.com/ => c:\\php/www\\ \n\
277 // ";
278 var noMapping = "no mappings for tested url";
279 var willQueryServer = "for this url Firebug will send query to server";
280 
281 var splitter = " => ";
282 var gUrlMappingManager = {
283     init: function()
284     {
285         var Firebug = opener.Firebug;
286         var extModule = Firebug.ExternalEditors;
287         this.checkHeaderRe = extModule.checkHeaderRe;
288         this.pathTransformations = extModule.pathTransformations;
289 
290         var val = [
291             headerExplaination,    "\n",
292             headerName, splitter, extModule.checkHeaderRe.source,
293             "\n\n",
294             listExplaination,
295             "\n"
296         ];
297 
298         for (var i = 0; i < this.pathTransformations.length; i++)
299         {
300             var transform = this.pathTransformations[i];
301             val.push(transform.regexp.source, splitter, transform.filePath, '\n');
302         }
303 
304         val.push(splitter, "\n")
305 
306         document.getElementById("urlMappings").value = val.join("");
307         document.getElementById("test").value = Firebug.Firefox.getCurrentBrowser().currentURI.spec;
308 
309         this.onMainInput();
310     },
311     uninit: function()
312     {
313         this.save();
314         opener.Firebug.ExternalEditors.saveUrlMappings();
315     },
316     save: function()
317     {
318         var checkHeaderRe = this.checkHeaderRe;
319         var pathTransformations = this.pathTransformations;
320 
321         FirebugLoader.forEachWindow(function(win)
322         {
323             var extModule = win.Firebug.ExternalEditors;
324             delete extModule.pathTransformations;
325             delete extModule.checkHeaderRe;
326             extModule.checkHeaderRe = checkHeaderRe;
327             extModule.pathTransformations = pathTransformations;
328         });
329     },
330     parse: function(val)
331     {
332         var lines = val.split(/(?:\n\r|\n|\r)/);
333         var errors = this.errors = [];
334         function addRegexp(source, line)
335         {
336             if (!source)
337                 return;
338 
339             try
340             {
341                 source = source.replace(/\\?\//g, '\\/');
342                 return RegExp(source, 'i');
343             }
344             catch(e)
345             {
346                 errors.push(line + ': ' + e);
347                 return null;
348             }
349         }
350 
351         this.pathTransformations = [];
352         this.checkHeaderRe = null;
353         for (var i in lines)
354         {
355             var line = lines[i].split('=>');
356 
357             if (!line[1] || !line[0])
358                 continue;
359 
360             var start = line[0].trim()
361             var end = line[1].trim();
362 
363             if (start[0] == '/' && start[1] == '/')
364                 continue;
365 
366             if (start == headerName)
367             {
368                 if (this.checkHeaderRe)
369                     erors.push(i)
370                 else
371                     this.checkHeaderRe = addRegexp(end, i);
372                 continue;
373             }
374             var t = {
375                 regexp: addRegexp(start, i),
376                 filePath: end
377             }
378             if (t.regexp && t.filePath)
379                 this.pathTransformations.push(t)
380         }
381 
382         if (!this.checkHeaderRe)
383             this.checkHeaderRe = /^$/
384     },
385 
386     onTestInput: function()
387     {
388         var testBox = document.getElementById("test");
389         var resultBox = document.getElementById("result");
390         var href = testBox.value;
391 
392         if (this.checkHeaderRe.test(href))
393         {
394             resultBox.value = "firebug will send query to server";
395         }
396         else
397         {
398             for (var i = 0; i < this.pathTransformations.length; i++)
399             {
400                 var transform = this.pathTransformations[i];
401                 if (transform.regexp.test(href))
402                 {
403                     var path = href.replace(transform.regexp, transform.filePath);
404                     break;
405                 }
406             }
407 
408             if (path)
409             {
410                 resultBox.style.cssText = "box-shadow: 0px 0px 1.5px 1px lime;";
411                 href = path;
412             }
413 
414             resultBox.value = href.replace(/([^:\\\/])[\\\/]+/g, '$1/');
415         }
416     },
417 
418     onMainInput: function()
419     {
420         this.parse(document.getElementById("urlMappings").value);
421         var resultBox = document.getElementById("result");
422         if (this.errors.length)
423         {
424             resultBox.value = this.errors;
425             resultBox.style.cssText = "box-shadow: 0px 0px 1.5px 1px red;";
426         } else
427         {
428             resultBox.style.cssText = "";
429             this.onTestInput()
430         }
431     },
432     schedule: function(funcName)
433     {
434         if (this._scheduled != "onMainInput")
435             this._scheduled = funcName
436 
437         if (this.timeOut != null)
438             return;
439         this.timeOut = setTimeout(function(_this)
440         {
441             _this[_this._scheduled]();
442             _this._scheduled = _this.timeOut = null;
443             _this.save()
444         }, 80, this);
445     }
446 }
447 
448 // ************************************************************************************************
449 
450