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