|
1 <?xml version="1.0"?> |
|
2 <!-- This Source Code Form is subject to the terms of the Mozilla Public |
|
3 - License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> |
|
5 |
|
6 |
|
7 <!DOCTYPE bindings SYSTEM "chrome://global/locale/console.dtd"> |
|
8 |
|
9 <bindings id="consoleBindings" |
|
10 xmlns="http://www.mozilla.org/xbl" |
|
11 xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" |
|
12 xmlns:xbl="http://www.mozilla.org/xbl"> |
|
13 |
|
14 <binding id="console-box" extends="xul:box"> |
|
15 <content> |
|
16 <xul:stringbundle src="chrome://global/locale/console.properties" role="string-bundle"/> |
|
17 <xul:vbox class="console-box-internal"> |
|
18 <xul:vbox class="console-rows" role="console-rows" xbl:inherits="dir=sortOrder"/> |
|
19 </xul:vbox> |
|
20 </content> |
|
21 |
|
22 <implementation> |
|
23 <field name="limit" readonly="true"> |
|
24 250 |
|
25 </field> |
|
26 |
|
27 <field name="fieldMaxLength" readonly="true"> |
|
28 <!-- Limit displayed string lengths to avoid performance issues. (Bug 796179 and 831020) --> |
|
29 200 |
|
30 </field> |
|
31 |
|
32 <field name="showChromeErrors" readonly="true"> |
|
33 Services.prefs.getBoolPref("javascript.options.showInConsole"); |
|
34 </field> |
|
35 |
|
36 <property name="count" readonly="true"> |
|
37 <getter>return this.mCount</getter> |
|
38 </property> |
|
39 |
|
40 <property name="mode"> |
|
41 <getter>return this.mMode;</getter> |
|
42 <setter><![CDATA[ |
|
43 if (this.mode != val) { |
|
44 this.mMode = val || "All"; |
|
45 this.setAttribute("mode", this.mMode); |
|
46 this.selectedItem = null; |
|
47 } |
|
48 return val; |
|
49 ]]></setter> |
|
50 </property> |
|
51 |
|
52 <property name="filter"> |
|
53 <getter>return this.mFilter;</getter> |
|
54 <setter><![CDATA[ |
|
55 val = val.toLowerCase(); |
|
56 if (this.mFilter != val) { |
|
57 this.mFilter = val; |
|
58 for (let aRow of this.mConsoleRowBox.children) { |
|
59 this.filterElement(aRow); |
|
60 } |
|
61 } |
|
62 return val; |
|
63 ]]></setter> |
|
64 </property> |
|
65 |
|
66 <property name="sortOrder"> |
|
67 <getter>return this.getAttribute("sortOrder");</getter> |
|
68 <setter>this.setAttribute("sortOrder", val); return val;</setter> |
|
69 </property> |
|
70 <field name="mSelectedItem">null</field> |
|
71 <property name="selectedItem"> |
|
72 <getter>return this.mSelectedItem</getter> |
|
73 <setter><![CDATA[ |
|
74 if (this.mSelectedItem) |
|
75 this.mSelectedItem.removeAttribute("selected"); |
|
76 |
|
77 this.mSelectedItem = val; |
|
78 if (val) |
|
79 val.setAttribute("selected", "true"); |
|
80 |
|
81 // Update edit commands |
|
82 window.updateCommands("focus"); |
|
83 return val; |
|
84 ]]></setter> |
|
85 </property> |
|
86 |
|
87 <method name="init"> |
|
88 <body><![CDATA[ |
|
89 this.mCount = 0; |
|
90 |
|
91 this.mConsoleListener = { |
|
92 console: this, |
|
93 observe : function(aObject) { |
|
94 // The message can arrive a little bit after the xbl binding has been |
|
95 // unbind. So node.appendItem will not be available anymore. |
|
96 if ('appendItem' in this.console) |
|
97 this.console.appendItem(aObject); |
|
98 } |
|
99 }; |
|
100 |
|
101 this.mConsoleRowBox = document.getAnonymousElementByAttribute(this, "role", "console-rows"); |
|
102 this.mStrBundle = document.getAnonymousElementByAttribute(this, "role", "string-bundle"); |
|
103 |
|
104 try { |
|
105 Services.console.registerListener(this.mConsoleListener); |
|
106 } catch (ex) { |
|
107 appendItem( |
|
108 "Unable to display errors - couldn't get Console Service component. " + |
|
109 "(Missing @mozilla.org/consoleservice;1)"); |
|
110 return; |
|
111 } |
|
112 |
|
113 this.mMode = this.getAttribute("mode") || "All"; |
|
114 this.mFilter = ""; |
|
115 |
|
116 this.appendInitialItems(); |
|
117 window.controllers.insertControllerAt(0, this._controller); |
|
118 ]]></body> |
|
119 </method> |
|
120 |
|
121 <method name="destroy"> |
|
122 <body><![CDATA[ |
|
123 Services.console.unregisterListener(this.mConsoleListener); |
|
124 window.controllers.removeController(this._controller); |
|
125 ]]></body> |
|
126 </method> |
|
127 |
|
128 <method name="appendInitialItems"> |
|
129 <body><![CDATA[ |
|
130 var messages = Services.console.getMessageArray(); |
|
131 |
|
132 // In case getMessageArray returns 0-length array as null |
|
133 if (!messages) |
|
134 messages = []; |
|
135 |
|
136 var limit = messages.length - this.limit; |
|
137 if (limit < 0) limit = 0; |
|
138 |
|
139 // Checks if console ever been cleared |
|
140 for (var i = messages.length - 1; i >= limit; --i) |
|
141 if (!messages[i].message) |
|
142 break; |
|
143 |
|
144 // Populate with messages after latest "clear" |
|
145 while (++i < messages.length) |
|
146 this.appendItem(messages[i]); |
|
147 ]]></body> |
|
148 </method> |
|
149 |
|
150 <method name="appendItem"> |
|
151 <parameter name="aObject"/> |
|
152 <body><![CDATA[ |
|
153 try { |
|
154 // Try to QI it to a script error to get more info |
|
155 var scriptError = aObject.QueryInterface(Components.interfaces.nsIScriptError); |
|
156 |
|
157 // filter chrome urls |
|
158 if (!this.showChromeErrors && scriptError.sourceName.substr(0, 9) == "chrome://") |
|
159 return; |
|
160 |
|
161 // filter private windows |
|
162 if (scriptError.isFromPrivateWindow) |
|
163 return; |
|
164 |
|
165 this.appendError(scriptError); |
|
166 } catch (ex) { |
|
167 try { |
|
168 // Try to QI it to a console message |
|
169 var msg = aObject.QueryInterface(Components.interfaces.nsIConsoleMessage); |
|
170 if (msg.message) |
|
171 this.appendMessage(msg.message); |
|
172 else // observed a null/"clear" message |
|
173 this.clearConsole(); |
|
174 } catch (ex2) { |
|
175 // Give up and append the object itself as a string |
|
176 this.appendMessage(aObject); |
|
177 } |
|
178 } |
|
179 ]]></body> |
|
180 </method> |
|
181 |
|
182 <method name="_truncateIfNecessary"> |
|
183 <parameter name="aString"/> |
|
184 <parameter name="aMiddleCharacter"/> |
|
185 <body><![CDATA[ |
|
186 if (!aString || aString.length <= this.fieldMaxLength) |
|
187 return {string: aString, column: aMiddleCharacter}; |
|
188 let halfLimit = this.fieldMaxLength / 2; |
|
189 if (!aMiddleCharacter || aMiddleCharacter < 0 || aMiddleCharacter > aString.length) |
|
190 aMiddleCharacter = halfLimit; |
|
191 |
|
192 let startPosition = 0; |
|
193 let endPosition = aString.length; |
|
194 if (aMiddleCharacter - halfLimit >= 0) |
|
195 startPosition = aMiddleCharacter - halfLimit; |
|
196 if (aMiddleCharacter + halfLimit <= aString.length) |
|
197 endPosition = aMiddleCharacter + halfLimit; |
|
198 if (endPosition - startPosition < this.fieldMaxLength) |
|
199 endPosition += this.fieldMaxLength - (endPosition - startPosition); |
|
200 let truncatedString = aString.substring(startPosition, endPosition); |
|
201 let Ci = Components.interfaces; |
|
202 let ellipsis = Services.prefs.getComplexValue("intl.ellipsis", |
|
203 Ci.nsIPrefLocalizedString).data; |
|
204 if (startPosition > 0) { |
|
205 truncatedString = ellipsis + truncatedString; |
|
206 aMiddleCharacter += ellipsis.length; |
|
207 } |
|
208 if (endPosition < aString.length) |
|
209 truncatedString = truncatedString + ellipsis; |
|
210 |
|
211 return { |
|
212 string: truncatedString, |
|
213 column: aMiddleCharacter - startPosition |
|
214 }; |
|
215 ]]></body> |
|
216 </method> |
|
217 |
|
218 <method name="appendError"> |
|
219 <parameter name="aObject"/> |
|
220 <body><![CDATA[ |
|
221 var row = this.createConsoleRow(); |
|
222 var nsIScriptError = Components.interfaces.nsIScriptError; |
|
223 |
|
224 // Is this error actually just a non-fatal warning? |
|
225 var warning = aObject.flags & nsIScriptError.warningFlag != 0; |
|
226 |
|
227 var typetext = warning ? "typeWarning" : "typeError"; |
|
228 row.setAttribute("typetext", this.mStrBundle.getString(typetext)); |
|
229 row.setAttribute("type", warning ? "warning" : "error"); |
|
230 row.setAttribute("msg", aObject.errorMessage); |
|
231 row.setAttribute("category", aObject.category); |
|
232 row.setAttribute("time", this.properFormatTime(aObject.timeStamp)); |
|
233 if (aObject.lineNumber || aObject.sourceName) { |
|
234 row.setAttribute("href", this._truncateIfNecessary(aObject.sourceName).string); |
|
235 row.mSourceName = aObject.sourceName; |
|
236 row.setAttribute("line", aObject.lineNumber); |
|
237 } else { |
|
238 row.setAttribute("hideSource", "true"); |
|
239 } |
|
240 if (aObject.sourceLine) { |
|
241 let sourceLine = aObject.sourceLine.replace(/\s/g, " "); |
|
242 let truncatedLineObj = this._truncateIfNecessary(sourceLine, aObject.columnNumber); |
|
243 row.setAttribute("code", truncatedLineObj.string); |
|
244 row.mSourceLine = sourceLine; |
|
245 if (aObject.columnNumber) { |
|
246 row.setAttribute("col", aObject.columnNumber); |
|
247 row.setAttribute("errorDots", this.repeatChar(" ", truncatedLineObj.column)); |
|
248 row.setAttribute("errorCaret", " "); |
|
249 } else { |
|
250 row.setAttribute("hideCaret", "true"); |
|
251 } |
|
252 } else { |
|
253 row.setAttribute("hideCode", "true"); |
|
254 } |
|
255 |
|
256 this.appendConsoleRow(row); |
|
257 ]]></body> |
|
258 </method> |
|
259 |
|
260 <method name="appendMessage"> |
|
261 <parameter name="aMessage"/> |
|
262 <parameter name="aType"/> |
|
263 <body><![CDATA[ |
|
264 var row = this.createConsoleRow(); |
|
265 row.setAttribute("type", aType || "message"); |
|
266 row.setAttribute("msg", aMessage); |
|
267 this.appendConsoleRow(row); |
|
268 ]]></body> |
|
269 </method> |
|
270 |
|
271 <method name="clear"> |
|
272 <body><![CDATA[ |
|
273 // add a "clear" message (mainly for other listeners) |
|
274 Services.console.logStringMessage(null); |
|
275 Services.console.reset(); |
|
276 ]]></body> |
|
277 </method> |
|
278 |
|
279 <method name="properFormatTime"> |
|
280 <parameter name="aTime"/> |
|
281 <body><![CDATA[ |
|
282 const dateServ = Components.classes["@mozilla.org/intl/scriptabledateformat;1"] |
|
283 .getService(Components.interfaces.nsIScriptableDateFormat); |
|
284 let errorTime = new Date(aTime); |
|
285 return dateServ.FormatDateTime("", dateServ.dateFormatShort, dateServ.timeFormatSeconds, |
|
286 errorTime.getFullYear(), errorTime.getMonth() + 1, errorTime.getDate(), |
|
287 errorTime.getHours(), errorTime.getMinutes(), errorTime.getSeconds()); |
|
288 ]]></body> |
|
289 </method> |
|
290 |
|
291 <method name="copySelectedItem"> |
|
292 <body><![CDATA[ |
|
293 if (this.mSelectedItem) try { |
|
294 const clipURI = "@mozilla.org/widget/clipboardhelper;1"; |
|
295 const clipI = Components.interfaces.nsIClipboardHelper; |
|
296 var clipboard = Components.classes[clipURI].getService(clipI); |
|
297 |
|
298 clipboard.copyString(this.mSelectedItem.toString(), document); |
|
299 } catch (ex) { |
|
300 // Unable to copy anything, die quietly |
|
301 } |
|
302 ]]></body> |
|
303 </method> |
|
304 |
|
305 <method name="createConsoleRow"> |
|
306 <body><![CDATA[ |
|
307 var row = document.createElement("box"); |
|
308 row.setAttribute("class", "console-row"); |
|
309 row._IsConsoleRow = true; |
|
310 row._ConsoleBox = this; |
|
311 return row; |
|
312 ]]></body> |
|
313 </method> |
|
314 |
|
315 <method name="appendConsoleRow"> |
|
316 <parameter name="aRow"/> |
|
317 <body><![CDATA[ |
|
318 this.filterElement(aRow); |
|
319 this.mConsoleRowBox.appendChild(aRow); |
|
320 if (++this.mCount > this.limit) this.deleteFirst(); |
|
321 ]]></body> |
|
322 </method> |
|
323 |
|
324 <method name="deleteFirst"> |
|
325 <body><![CDATA[ |
|
326 var node = this.mConsoleRowBox.firstChild; |
|
327 this.mConsoleRowBox.removeChild(node); |
|
328 --this.mCount; |
|
329 ]]></body> |
|
330 </method> |
|
331 |
|
332 <method name="clearConsole"> |
|
333 <body><![CDATA[ |
|
334 if (this.mCount == 0) // already clear |
|
335 return; |
|
336 this.mCount = 0; |
|
337 |
|
338 var newRows = this.mConsoleRowBox.cloneNode(false); |
|
339 this.mConsoleRowBox.parentNode.replaceChild(newRows, this.mConsoleRowBox); |
|
340 this.mConsoleRowBox = newRows; |
|
341 this.selectedItem = null; |
|
342 ]]></body> |
|
343 </method> |
|
344 |
|
345 <method name="filterElement"> |
|
346 <parameter name="aRow" /> |
|
347 <body><![CDATA[ |
|
348 let anyMatch = ["msg", "line", "code"].some(function (key) { |
|
349 return (aRow.hasAttribute(key) && |
|
350 this.stringMatchesFilters(aRow.getAttribute(key), this.mFilter)); |
|
351 }, this) || (aRow.mSourceName && |
|
352 this.stringMatchesFilters(aRow.mSourceName, this.mFilter)); |
|
353 |
|
354 if (anyMatch) { |
|
355 aRow.classList.remove("filtered-by-string") |
|
356 } else { |
|
357 aRow.classList.add("filtered-by-string") |
|
358 } |
|
359 ]]></body> |
|
360 </method> |
|
361 |
|
362 <!-- UTILITY FUNCTIONS --> |
|
363 |
|
364 <method name="repeatChar"> |
|
365 <parameter name="aChar"/> |
|
366 <parameter name="aCol"/> |
|
367 <body><![CDATA[ |
|
368 if (--aCol <= 0) |
|
369 return ""; |
|
370 |
|
371 for (var i = 2; i < aCol; i += i) |
|
372 aChar += aChar; |
|
373 |
|
374 return aChar + aChar.slice(0, aCol - aChar.length); |
|
375 ]]></body> |
|
376 </method> |
|
377 |
|
378 <method name="stringMatchesFilters"> |
|
379 <parameter name="aString"/> |
|
380 <parameter name="aFilter"/> |
|
381 <body><![CDATA[ |
|
382 if (!aString || !aFilter) { |
|
383 return true; |
|
384 } |
|
385 |
|
386 let searchStr = aString.toLowerCase(); |
|
387 let filterStrings = aFilter.split(/\s+/); |
|
388 return !filterStrings.some(function (f) { |
|
389 return searchStr.indexOf(f) == -1; |
|
390 }); |
|
391 ]]></body> |
|
392 </method> |
|
393 |
|
394 <constructor> this.init(); </constructor> |
|
395 <destructor> this.destroy(); </destructor> |
|
396 |
|
397 <!-- Command controller for the copy command --> |
|
398 <field name="_controller"><![CDATA[({ |
|
399 _outer: this, |
|
400 |
|
401 QueryInterface: function(aIID) { |
|
402 if (aIID.equals(Components.interfaces.nsIController) || |
|
403 aIID.equals(Components.interfaces.nsISupports)) |
|
404 return this; |
|
405 throw Components.results.NS_NOINTERFACE; |
|
406 }, |
|
407 |
|
408 supportsCommand: function(aCommand) { |
|
409 return aCommand == "cmd_copy"; |
|
410 }, |
|
411 |
|
412 isCommandEnabled: function(aCommand) { |
|
413 return aCommand == "cmd_copy" && this._outer.selectedItem; |
|
414 }, |
|
415 |
|
416 doCommand: function(aCommand) { |
|
417 if (aCommand == "cmd_copy") |
|
418 this._outer.copySelectedItem(); |
|
419 }, |
|
420 |
|
421 onEvent: function() { } |
|
422 });]]></field> |
|
423 </implementation> |
|
424 |
|
425 <handlers> |
|
426 <handler event="mousedown"><![CDATA[ |
|
427 if (event.button == 0 || event.button == 2) { |
|
428 var target = event.originalTarget; |
|
429 |
|
430 while (target && !("_IsConsoleRow" in target)) |
|
431 target = target.parentNode; |
|
432 |
|
433 if (target) |
|
434 this.selectedItem = target; |
|
435 } |
|
436 ]]></handler> |
|
437 </handlers> |
|
438 </binding> |
|
439 |
|
440 <binding id="error" extends="xul:box"> |
|
441 <content> |
|
442 <xul:box class="console-row-internal-box" flex="1"> |
|
443 <xul:box class="console-row-icon" align="center" xbl:inherits="selected"> |
|
444 <xul:image class="console-icon" xbl:inherits="src,type"/> |
|
445 </xul:box> |
|
446 <xul:vbox class="console-row-content" xbl:inherits="selected" flex="1"> |
|
447 <xul:box class="console-row-msg" align="start"> |
|
448 <xul:label class="label" xbl:inherits="value=typetext"/> |
|
449 <xul:description class="console-error-msg" xbl:inherits="xbl:text=msg" flex="1"/> |
|
450 <xul:label class="label console-time" xbl:inherits="value=time"/> |
|
451 </xul:box> |
|
452 <xul:box class="console-row-file" xbl:inherits="hidden=hideSource"> |
|
453 <xul:label class="label" value="&errFile.label;"/> |
|
454 <xul:box class="console-error-source" xbl:inherits="href,line"/> |
|
455 <xul:spacer flex="1"/> |
|
456 <xul:hbox class="lineNumberRow" xbl:inherits="line"> |
|
457 <xul:label class="label" value="&errLine.label;"/> |
|
458 <xul:label class="label" xbl:inherits="value=line"/> |
|
459 </xul:hbox> |
|
460 </xul:box> |
|
461 <xul:vbox class="console-row-code" xbl:inherits="selected,hidden=hideCode"> |
|
462 <xul:label class="monospace console-code" xbl:inherits="value=code" crop="end"/> |
|
463 <xul:box xbl:inherits="hidden=hideCaret"> |
|
464 <xul:label class="monospace console-dots" xbl:inherits="value=errorDots"/> |
|
465 <xul:label class="monospace console-caret" xbl:inherits="value=errorCaret"/> |
|
466 <xul:spacer flex="1"/> |
|
467 </xul:box> |
|
468 </xul:vbox> |
|
469 </xul:vbox> |
|
470 </xul:box> |
|
471 </content> |
|
472 |
|
473 <implementation> |
|
474 <field name="mSourceName">null</field> |
|
475 <field name="mSourceLine">null</field> |
|
476 |
|
477 <method name="toString"> |
|
478 <body><![CDATA[ |
|
479 let msg = ""; |
|
480 let strBundle = this._ConsoleBox.mStrBundle; |
|
481 |
|
482 if (this.hasAttribute("time")) |
|
483 msg += strBundle.getFormattedString("errTime", [this.getAttribute("time")]) + "\n"; |
|
484 |
|
485 msg += this.getAttribute("typetext") + " " + this.getAttribute("msg"); |
|
486 |
|
487 if (this.hasAttribute("line") && this.mSourceName) { |
|
488 msg += "\n" + strBundle.getFormattedString("errFile", |
|
489 [this.mSourceName]) + "\n"; |
|
490 if (this.hasAttribute("col")) { |
|
491 msg += strBundle.getFormattedString("errLineCol", |
|
492 [this.getAttribute("line"), this.getAttribute("col")]); |
|
493 } else |
|
494 msg += strBundle.getFormattedString("errLine", [this.getAttribute("line")]); |
|
495 } |
|
496 |
|
497 if (this.hasAttribute("code")) |
|
498 msg += "\n" + strBundle.getString("errCode") + "\n" + this.mSourceLine; |
|
499 |
|
500 return msg; |
|
501 ]]></body> |
|
502 </method> |
|
503 </implementation> |
|
504 |
|
505 </binding> |
|
506 |
|
507 <binding id="message" extends="xul:box"> |
|
508 <content> |
|
509 <xul:box class="console-internal-box" flex="1"> |
|
510 <xul:box class="console-row-icon" align="center"> |
|
511 <xul:image class="console-icon" xbl:inherits="src,type"/> |
|
512 </xul:box> |
|
513 <xul:vbox class="console-row-content" xbl:inherits="selected" flex="1"> |
|
514 <xul:vbox class="console-row-msg" flex="1"> |
|
515 <xul:description class="console-msg-text" xbl:inherits="xbl:text=msg"/> |
|
516 </xul:vbox> |
|
517 </xul:vbox> |
|
518 </xul:box> |
|
519 </content> |
|
520 |
|
521 <implementation> |
|
522 <method name="toString"> |
|
523 <body><![CDATA[ |
|
524 return this.getAttribute("msg"); |
|
525 ]]></body> |
|
526 </method> |
|
527 </implementation> |
|
528 </binding> |
|
529 |
|
530 <binding id="console-error-source" extends="xul:box"> |
|
531 <content> |
|
532 <xul:label class="text-link" xbl:inherits="value=href" crop="right"/> |
|
533 </content> |
|
534 |
|
535 <handlers> |
|
536 <handler event="click" phase="capturing" button="0" preventdefault="true"> |
|
537 <![CDATA[ |
|
538 var url = document.getBindingParent(this).mSourceName; |
|
539 url = url.substring(url.lastIndexOf(" ") + 1); |
|
540 var line = getAttribute("line"); |
|
541 gViewSourceUtils.viewSource(url, null, null, line); |
|
542 ]]> |
|
543 </handler> |
|
544 </handlers> |
|
545 </binding> |
|
546 |
|
547 </bindings> |