1 /* See license.txt for terms of usage */
  2 
  3 define([
  4     "firebug/chrome/reps",
  5     "firebug/lib/domplate",
  6     "firebug/lib/locale",
  7     "firebug/lib/dom",
  8     "firebug/chrome/window",
  9     "firebug/lib/css",
 10     "firebug/lib/string",
 11     "firebug/lib/options",
 12     "firebug/chrome/menu",
 13     "firebug/lib/system",
 14     "firebug/lib/xpcom",
 15     "firebug/lib/object",
 16     "firebug/editor/editor",
 17 ],
 18 function(FirebugReps, Domplate, Locale, Dom, Win, Css, Str, Options, Menu, System, Xpcom, Obj) {
 19 with (Domplate) {
 20 
 21 // ********************************************************************************************* //
 22 // Constants
 23 
 24 const Ci = Components.interfaces;
 25 const Cu = Components.utils;
 26 const removeConfirmation = "commandline.include.removeConfirmation";
 27 const prompts = Xpcom.CCSV("@mozilla.org/embedcomp/prompt-service;1", "nsIPromptService");
 28 
 29 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 30 
 31 var ScratchpadManager;
 32 
 33 try
 34 {
 35     var scope = {};
 36     Cu.import("resource:///modules/devtools/scratchpad-manager.jsm", scope);
 37     ScratchpadManager = scope.ScratchpadManager;
 38 }
 39 catch(ex)
 40 {
 41     // Scratchpad does not exists (when using Seamonkey ...)
 42 }
 43 
 44 var storageScope = {};
 45 Cu.import("resource://firebug/storageService.js", storageScope);
 46 
 47 // ********************************************************************************************* //
 48 // Implementation
 49 
 50 var CommandLineIncludeRep = domplate(FirebugReps.Table,
 51 {
 52     tableClassName: "tableCommandLineInclude dataTable",
 53 
 54     tag:
 55         FirebugReps.OBJECTBOX({_repObject: "$object"},
 56             FirebugReps.Table.tag
 57         ),
 58 
 59     inspectable: false,
 60 
 61     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 62     // Domplate Handlers
 63 
 64     getValueTag: function(object)
 65     {
 66         if (object.cons === DomplateTag)
 67             return object;
 68         else
 69             return FirebugReps.Table.getValueTag(object);
 70     },
 71 
 72     getUrlTag: function(href, aliasName)
 73     {
 74         var urlTag =
 75             SPAN({style:"height:100%"},
 76                 A({"href": href, "target": "_blank", "class": "url"},
 77                     Str.cropString(href, 100)
 78                 ),
 79                 SPAN({"class": "commands"}
 80                 // xxxFlorent: temporarily disabled, see: 
 81                 //    http://code.google.com/p/fbug/issues/detail?id=5878#c27
 82                 /*,
 83                 IMG({
 84                     "src":"blank.gif",
 85                     "class":"closeButton ",
 86                     onclick: this.deleteAlias.bind(this, aliasName),
 87                 })*/
 88                 )
 89             );
 90 
 91         return urlTag;
 92     },
 93 
 94     displayAliases: function(context)
 95     {
 96         var store = CommandLineInclude.getStore();
 97         var keys = store.getKeys();
 98         var arrayToDisplay = [];
 99         var returnValue = Firebug.Console.getDefaultReturnValue(context.window);
100 
101         if (keys.length === 0)
102         {
103             var msg = Locale.$STR("commandline.include.noDefinedAlias");
104             Firebug.Console.log(msg, context, null, FirebugReps.Hint);
105             return returnValue;
106         }
107 
108         for (var i=0; i<keys.length; i++)
109         {
110             var aliasName = keys[i];
111             arrayToDisplay.push({
112                 "alias": SPAN({"class":"aliasName", "data-aliasname": aliasName}, aliasName),
113                 "URL": this.getUrlTag(store.getItem(aliasName), aliasName, context)
114             });
115         }
116 
117         var input = new CommandLineIncludeObject();
118         this.log(arrayToDisplay, ["alias", "URL"], context, input);
119         return returnValue;
120     },
121 
122     deleteAlias: function(aliasName, ev)
123     {
124         // NOTE: that piece of code has not been tested since deleting aliases through the table 
125         // has been disabled.
126         // Once it is enabled again, make sure FBTests is available for this feature
127         var store = CommandLine.getStore();
128         if (! Options.get(removeConfirmation))
129         {
130             var check = {value: false};
131             var flags = prompts.BUTTON_POS_0 * prompts.BUTTON_TITLE_YES +
132             prompts.BUTTON_POS_1 * prompts.BUTTON_TITLE_NO;
133 
134             if  (prompts.confirmEx(context.chrome.window, Locale.$STR("Firebug"),
135                 Locale.$STR("commandline.include.confirmDelete"), flags, "", "", "",
136                 Locale.$STR("Do_not_show_this_message_again"), check) > 0)
137             {
138                 return;
139             }
140 
141             // Update 'Remove Cookies' confirmation option according to the value
142             // of the dialog's "do not show again" checkbox.
143             Options.set(removeConfirmation, !check.value);
144         }
145         store.removeItem(aliasName);
146     },
147 
148     startEditing: function(target)
149     {
150         var editor = this.getEditor(target.ownerDocument);
151         Firebug.Editor.startEditing(target, target.dataset.aliasname, editor);
152     },
153 
154     editAliasName: function(tr)
155     {
156         var target = tr.querySelector(".aliasName");
157         this.startEditing(target);
158     },
159 
160     editAliasURL: function(tr)
161     {
162         var target = tr.querySelector(".url");
163         this.startEditing(target);
164     },
165 
166     openInScratchpad: function(url)
167     {
168         var spWin = ScratchpadManager.openScratchpad();
169         var scriptContent = null;
170         var editor = null;
171 
172         spWin.onload = function()
173         {
174             var spInstance = spWin.Scratchpad;
175             //intro = spInstance.strings.GetStringFromName("scratchpadIntro");
176             spInstance.addObserver(
177             {
178                 onReady: function()
179                 {
180                     editor = spInstance.editor;
181 
182                     // if the content of the script is loaded, we write the content in the editor
183                     // otherwise, we write a text that asks the user to wait
184                     if (scriptContent)
185                         editor.setText(scriptContent);
186                     else
187                         editor.setText("// "+Locale.$STR("scratchpad.loading"));
188                 }
189             });
190         }
191 
192         var xhr = new XMLHttpRequest({mozAnon: true});
193         xhr.open("GET", url, true);
194 
195         xhr.onload = function()
196         {
197             if (spWin.closed)
198                 return;
199 
200             scriptContent = xhr.responseText;
201 
202             // if the editor is ready, we put the content on it now
203             // otherwise, we wait for the editor
204             if (editor)
205                 editor.setText(scriptContent);
206         }
207 
208         xhr.onerror = function()
209         {
210             if (spWin.closed)
211                 return;
212 
213             spInstance.setText("// "+Locale.$STR("scratchpad.failLoading"));
214         }
215 
216         xhr.send(null);
217     },
218 
219     supportsObject: function(object, type)
220     {
221         return object instanceof CommandLineIncludeObject;
222     },
223 
224     getContextMenuItems: function(object, target, context)
225     {
226         var tr = Dom.getAncestorByTagName(target, "tr");
227         if (!tr)
228             return [];
229 
230         var url = tr.querySelector("a.url").href;
231         var aliasName = tr.querySelector(".aliasName").dataset.aliasname;
232         var context = Firebug.currentContext;
233         var items = [
234             {
235                 label: "CopyLocation",
236                 id: "fbCopyLocation",
237                 tooltiptext: "clipboard.tip.Copy_Location",
238                 command: Obj.bindFixed(System.copyToClipboard, System, url)
239             },
240             // xxxFlorent: temporarily disabled, see: 
241             //    http://code.google.com/p/fbug/issues/detail?id=5878#c27
242             /*"-",
243             {
244                 label: "commandline.label.EditAliasName",
245                 id: "fbEditAliasName",
246                 tooltiptext: "commandline.tip.Edit_Alias_Name",
247                 command: this.editAliasName.bind(this, tr)
248             },
249             {
250                 label: "commandline.label.EditAliasURL",
251                 id: "fbEditAliasUrl",
252                 tooltiptext: "commandline.tip.Edit_Alias_URL",
253                 command: this.editAliasURL.bind(this, tr)
254             },
255             {
256                 label: "commandline.label.DeleteAlias",
257                 id: "fbDeleteAlias",
258                 tooltiptext: "commandline.tip.Delete_Alias",
259                 command: this.deleteAlias.bind(this, aliasName, ev)
260             },*/
261             "-",
262             {
263                 label: Locale.$STRF("commandline.label.IncludeScript", [aliasName]),
264                 id: "fbInclude",
265                 tooltiptext: "commandline.tip.Include_Script",
266                 command: Obj.bindFixed(CommandLineInclude.include, CommandLineInclude,
267                     context, aliasName)
268             },
269             "-",
270             {
271                 label: "OpenInTab",
272                 id: "fbOpenInTab",
273                 tooltiptext: "firebug.tip.Open_In_Tab",
274                 command: Obj.bindFixed(Win.openNewTab, Win, url)
275             }
276         ];
277 
278         if (ScratchpadManager)
279         {
280             items.push({
281                 label: "commandline.label.OpenInScratchpad",
282                 id: "fbOpenInScratchpad",
283                 tooltiptext: "commandline.tip.Open_In_Scratchpad",
284                 command: this.openInScratchpad.bind(this, url)
285             });
286         }
287 
288         return items;
289     },
290 
291     getEditor: function(doc)
292     {
293         if (!this.editor)
294             this.editor = new IncludeEditor(doc);
295         return this.editor;
296     }
297 });
298 
299 // ********************************************************************************************* //
300 
301 function CommandLineIncludeObject()
302 {
303 }
304 
305 // ********************************************************************************************* //
306 
307 var CommandLineInclude =
308 {
309     onSuccess: function(newAlias, context, loadingMsgRow, xhr, hasWarnings)
310     {
311         var urlComponent = xhr.channel.URI.QueryInterface(Ci.nsIURL);
312         var filename = urlComponent.fileName, url = urlComponent.spec;
313         // clear the message saying "loading..."
314         this.clearLoadingMessage(loadingMsgRow);
315 
316         if (newAlias)
317         {
318             var store = this.getStore();
319             store.setItem(newAlias, url);
320             this.log("aliasCreated", [newAlias], [context, "info"]);
321         }
322 
323         if (!hasWarnings)
324             this.log("includeSuccess", [filename], [context, "info", true]);
325     },
326 
327     onError: function(context, url, loadingMsgRow)
328     {
329         this.clearLoadingMessage(loadingMsgRow);
330         this.log("loadFail", [url], [context, "error"]);
331     },
332 
333     clearLoadingMessage: function(loadingMsgRow)
334     {
335         if (loadingMsgRow && loadingMsgRow.parentNode)
336             loadingMsgRow.parentNode.removeChild(loadingMsgRow);
337     },
338 
339     getStore: function()
340     {
341         if (!this.store)
342             this.store = storageScope.StorageService.getStorage("includeAliases.json");
343         return this.store;
344     },
345 
346     log: function(localeStr, localeArgs, logArgs, noAutoPrefix)
347     {
348         var prefixedLocaleStr = (noAutoPrefix ? localeStr : "commandline.include."+localeStr);
349 
350         var msg = Locale.$STRF(prefixedLocaleStr, localeArgs);
351         logArgs.unshift([msg]);
352         return Firebug.Console.logFormatted.apply(Firebug.Console, logArgs);
353     },
354 
355     // include(context, url[, newAlias])
356     // includes a remote script
357     include: function(context, url, newAlias)
358     {
359         var reNotAlias = /[\.\/]/;
360         var urlIsAlias = url !== null && !reNotAlias.test(url);
361         var returnValue = Firebug.Console.getDefaultReturnValue(context.window);
362 
363         // checking arguments:
364         if ((newAlias !== undefined && typeof newAlias !== "string") || newAlias === "")
365         {
366             this.log("invalidAliasArgumentType", [], [context, "error"]);
367             return returnValue;
368         }
369 
370         if (url !== null && typeof url !== "string" || !url && !newAlias)
371         {
372             this.log("invalidUrlArgumentType", [], [context, "error"]);
373             return returnValue;
374         }
375 
376         if (newAlias !== undefined)
377             newAlias = newAlias.toLowerCase();
378 
379         if ((urlIsAlias && url.length > 30) || (newAlias && newAlias.length > 30))
380         {
381             this.log("tooLongAliasName", [newAlias || url], [context, "error"]);
382             return returnValue;
383         }
384 
385         if (newAlias !== undefined && reNotAlias.test(newAlias))
386         {
387             this.log("invalidAliasName", [newAlias], [context, "error"]);
388             return returnValue;
389         }
390 
391         if (urlIsAlias)
392         {
393             var store = this.getStore();
394             var aliasName = url.toLowerCase();
395             url = store.getItem(aliasName);
396             if (url === undefined)
397             {
398                 this.log("aliasNotFound", [aliasName], [context, "error"]);
399                 return returnValue;
400             }
401         }
402 
403         // if the URL is null, we delete the alias
404         if (newAlias !== undefined && url === null)
405         {
406             var store = this.getStore();
407             if (store.getItem(newAlias) === undefined)
408             {
409                 this.log("aliasNotFound", [newAlias], [context, "error"]);
410                 return returnValue;
411             }
412 
413             store.removeItem(newAlias);
414             this.log("aliasRemoved", [newAlias], [context, "info"]);
415             return returnValue;
416         }
417         var loadingMsgRow = this.log("Loading", [], [context, "loading", true], true);
418         var onSuccess = this.onSuccess.bind(this, newAlias, context, loadingMsgRow);
419         var onError = Obj.bindFixed(this.onError, this, context, url, loadingMsgRow);
420         this.evaluateRemoteScript(url, context, onSuccess, onError, loadingMsgRow);
421 
422         return returnValue;
423     },
424 
425     evaluateRemoteScript: function(url, context, successFunction, errorFunction, loadingMsgRow)
426     {
427         var xhr = new XMLHttpRequest({ mozAnon: true, timeout:30});
428         var acceptedSchemes = ["http", "https"];
429         var absoluteURL = context.browser.currentURI.resolve(url);
430 
431         xhr.onload = function()
432         {
433             if (xhr.status !== 200)
434                 return errorFunction.apply(this, arguments);
435             var codeToEval = xhr.responseText;
436             var hasWarnings = false;
437 
438             // test if the content is an HTML file, which is the most current after a mistake
439             if (!isValidJS(codeToEval))
440             {
441                 CommandLineInclude.log("invalidSyntax", [], [context, "warn"]);
442                 CommandLineInclude.clearLoadingMessage(loadingMsgRow);
443                 hasWarnings = true;
444             }
445 
446             Firebug.CommandLine.evaluateInWebPage(codeToEval, context);
447             if (successFunction)
448                 successFunction(xhr, hasWarnings);
449         }
450 
451         if (errorFunction)
452         {
453             xhr.ontimeout = xhr.onerror = errorFunction;
454         }
455 
456         try
457         {
458             xhr.open("GET", absoluteURL, true);
459         }
460         catch(ex)
461         {
462             this.clearLoadingMessage(loadingMsgRow);
463             if (ex.name === "NS_ERROR_UNKNOWN_PROTOCOL")
464             {
465                 this.log("invalidRequestProtocol", [], [context, "error"]);
466                 return;
467             }
468             throw ex;
469         }
470 
471         if (!~acceptedSchemes.indexOf(xhr.channel.URI.scheme))
472         {
473             this.log("invalidRequestProtocol", [], [context, "error"]);
474             this.clearLoadingMessage(loadingMsgRow);
475             return;
476         }
477 
478         xhr.send(null);
479 
480         // xxxFlorent: TODO show XHR progress
481     }
482 };
483 
484 // ********************************************************************************************* //
485 // Command Handler
486 
487 function onCommand(context, args)
488 {
489     if (args.length === 0)
490         return CommandLineIncludeRep.displayAliases(context);
491 
492     var self = CommandLineInclude;
493     Array.unshift(args, context);
494     return CommandLineInclude.include.apply(self, args);
495 }
496 
497 // ********************************************************************************************* //
498 // Local Helpers
499 
500 function IncludeEditor(doc)
501 {
502     Firebug.InlineEditor.call(this, doc);
503 }
504 
505 IncludeEditor.prototype = domplate(Firebug.InlineEditor.prototype,
506 {
507     endEditing: function(target, value, cancel)
508     {
509         if (cancel)
510             return;
511 
512         var context = Firebug.currentContext;
513         if (Css.hasClass(target, "aliasName"))
514             this.updateAliasName(target, value, context);
515         else if (Css.hasClass(target, "url"))
516             this.updateURL(target, value, context);
517     },
518 
519     updateURL: function(target, value, context)
520     {
521         var tr = Dom.getAncestorByTagName(target, "tr");
522         var aliasName = tr.querySelector(".aliasName").textContent;
523         CommandLineInclude.include(context, value, aliasName, {"onlyUpdate":true});
524         target.textContent = value;
525     },
526 
527     updateAliasName: function(target, value, context)
528     {
529         var oldAliasName = target.textContent;
530         var store = CommandLineInclude.getStore();
531         var url = store.getItem(oldAliasName);
532         store.removeItem(oldAliasName);
533         store.setItem(value, url);
534         target.dataset.aliasname = value;
535         target.textContent = value;
536     }
537 });
538 
539 function isValidJS(codeToCheck)
540 {
541     try
542     {
543         new Function(codeToCheck);
544         return true;
545     }
546     catch(ex)
547     {
548         if (ex instanceof SyntaxError)
549             return false;
550         else
551             throw ex;
552     }
553 };
554 
555 // ********************************************************************************************* //
556 // Registration
557 
558 Firebug.registerCommand("include", {
559     handler: onCommand,
560     description: Locale.$STR("console.cmd.help.include"),
561     helpUrl: "http://getfirebug.com/wiki/index.php/include"
562 });
563 
564 Firebug.registerRep(CommandLineIncludeRep);
565 
566 return CommandLineInclude;
567 
568 // ********************************************************************************************* //
569 }});
570