|
1 // -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*- |
|
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 let ConsolePanelView = { |
|
7 _list: null, |
|
8 _inited: false, |
|
9 _evalTextbox: null, |
|
10 _evalFrame: null, |
|
11 _evalCode: "", |
|
12 _bundle: null, |
|
13 _showChromeErrors: -1, |
|
14 _enabledPref: "devtools.errorconsole.enabled", |
|
15 |
|
16 get enabled() { |
|
17 return Services.prefs.getBoolPref(this._enabledPref); |
|
18 }, |
|
19 |
|
20 get follow() { |
|
21 return document.getElementById("console-follow-checkbox").checked; |
|
22 }, |
|
23 |
|
24 init: function cv_init() { |
|
25 if (this._list) |
|
26 return; |
|
27 |
|
28 this._list = document.getElementById("console-box"); |
|
29 this._evalTextbox = document.getElementById("console-eval-textbox"); |
|
30 this._bundle = Strings.browser; |
|
31 |
|
32 this._count = 0; |
|
33 this.limit = 250; |
|
34 this.fieldMaxLength = 140; |
|
35 |
|
36 try { |
|
37 // update users using the legacy pref |
|
38 if (Services.prefs.getBoolPref("browser.console.showInPanel")) { |
|
39 Services.prefs.setBoolPref(this._enabledPref, true); |
|
40 Services.prefs.clearUserPref("browser.console.showInPanel"); |
|
41 } |
|
42 } catch(ex) { |
|
43 // likely don't have an old pref |
|
44 } |
|
45 Services.prefs.addObserver(this._enabledPref, this, false); |
|
46 }, |
|
47 |
|
48 show: function show() { |
|
49 if (this._inited) |
|
50 return; |
|
51 this._inited = true; |
|
52 |
|
53 this.init(); // In case the panel is selected before init has been called. |
|
54 |
|
55 Services.console.registerListener(this); |
|
56 |
|
57 this.appendInitialItems(); |
|
58 |
|
59 // Delay creation of the iframe for startup performance |
|
60 this._evalFrame = document.createElement("iframe"); |
|
61 this._evalFrame.id = "console-evaluator"; |
|
62 this._evalFrame.collapsed = true; |
|
63 document.getElementById("console-container").appendChild(this._evalFrame); |
|
64 |
|
65 this._evalFrame.addEventListener("load", this.loadOrDisplayResult.bind(this), true); |
|
66 }, |
|
67 |
|
68 uninit: function cv_uninit() { |
|
69 if (this._inited) |
|
70 Services.console.unregisterListener(this); |
|
71 |
|
72 Services.prefs.removeObserver(this._enabledPref, this, false); |
|
73 }, |
|
74 |
|
75 observe: function(aSubject, aTopic, aData) { |
|
76 if (aTopic == "nsPref:changed") { |
|
77 // We may choose to create a new menu in v2 |
|
78 } |
|
79 else |
|
80 this.appendItem(aSubject); |
|
81 }, |
|
82 |
|
83 showChromeErrors: function() { |
|
84 if (this._showChromeErrors != -1) |
|
85 return this._showChromeErrors; |
|
86 |
|
87 try { |
|
88 let pref = Services.prefs; |
|
89 return this._showChromeErrors = pref.getBoolPref("javascript.options.showInConsole"); |
|
90 } |
|
91 catch(ex) { |
|
92 return this._showChromeErrors = false; |
|
93 } |
|
94 }, |
|
95 |
|
96 appendItem: function cv_appendItem(aObject) { |
|
97 let index = -1; |
|
98 try { |
|
99 // Try to QI it to a script error to get more info |
|
100 let scriptError = aObject.QueryInterface(Ci.nsIScriptError); |
|
101 |
|
102 // filter chrome urls |
|
103 if (!this.showChromeErrors && scriptError.sourceName.substr(0, 9) == "chrome://") |
|
104 return; |
|
105 index = this.appendError(scriptError); |
|
106 } |
|
107 catch (ex) { |
|
108 try { |
|
109 // Try to QI it to a console message |
|
110 let msg = aObject.QueryInterface(Ci.nsIConsoleMessage); |
|
111 |
|
112 if (msg.message) |
|
113 index = this.appendMessage(msg.message); |
|
114 else // observed a null/"clear" message |
|
115 this.clearConsole(); |
|
116 } |
|
117 catch (ex2) { |
|
118 // Give up and append the object itself as a string |
|
119 index = this.appendMessage(aObject); |
|
120 } |
|
121 } |
|
122 if (this.follow) { |
|
123 this._list.ensureIndexIsVisible(index); |
|
124 } |
|
125 }, |
|
126 |
|
127 truncateIfNecessary: function (aString) { |
|
128 if (!aString || aString.length <= this.fieldMaxLength) { |
|
129 return aString; |
|
130 } |
|
131 let truncatedString = aString.substring(0, this.fieldMaxLength); |
|
132 let Ci = Components.interfaces; |
|
133 let ellipsis = Services.prefs.getComplexValue("intl.ellipsis", |
|
134 Ci.nsIPrefLocalizedString).data; |
|
135 truncatedString = truncatedString + ellipsis; |
|
136 return truncatedString; |
|
137 }, |
|
138 |
|
139 appendError: function cv_appendError(aObject) { |
|
140 let row = this.createConsoleRow(); |
|
141 let nsIScriptError = Ci.nsIScriptError; |
|
142 |
|
143 // Is this error actually just a non-fatal warning? |
|
144 let warning = aObject.flags & nsIScriptError.warningFlag != 0; |
|
145 |
|
146 let typetext = warning ? "typeWarning" : "typeError"; |
|
147 row.setAttribute("typetext", this._bundle.GetStringFromName(typetext)); |
|
148 row.setAttribute("type", warning ? "warning" : "error"); |
|
149 row.setAttribute("msg", aObject.errorMessage); |
|
150 row.setAttribute("category", aObject.category); |
|
151 if (aObject.lineNumber || aObject.sourceName) { |
|
152 row.setAttribute("href", aObject.sourceName); |
|
153 row.setAttribute("line", aObject.lineNumber); |
|
154 } |
|
155 else { |
|
156 row.setAttribute("hideSource", "true"); |
|
157 } |
|
158 // hide code by default, otherwise initial item display will |
|
159 // hang the browser. |
|
160 row.setAttribute("hideCode", "true"); |
|
161 row.setAttribute("hideCaret", "true"); |
|
162 |
|
163 if (aObject.sourceLine) { |
|
164 row.setAttribute("code", this.truncateIfNecessary(aObject.sourceLine.replace(/\s/g, " "))); |
|
165 if (aObject.columnNumber) { |
|
166 row.setAttribute("col", aObject.columnNumber); |
|
167 } |
|
168 } |
|
169 |
|
170 let mode = document.getElementById("console-filter").value; |
|
171 if (mode != "all" && mode != row.getAttribute("type")) { |
|
172 row.collapsed = true; |
|
173 } |
|
174 |
|
175 row.setAttribute("onclick", "ConsolePanelView.onRowClick(this)"); |
|
176 this.appendConsoleRow(row); |
|
177 return this._list.getIndexOfItem(row); |
|
178 }, |
|
179 |
|
180 appendMessage: function cv_appendMessage (aMessage) { |
|
181 let row = this.createConsoleRow(); |
|
182 row.setAttribute("type", "message"); |
|
183 row.setAttribute("msg", aMessage); |
|
184 |
|
185 let mode = document.getElementById("console-filter").value; |
|
186 if (mode != "all" && mode != "message") |
|
187 row.collapsed = true; |
|
188 |
|
189 this.appendConsoleRow(row); |
|
190 return this._list.getIndexOfItem(row); |
|
191 }, |
|
192 |
|
193 createConsoleRow: function cv_createConsoleRow() { |
|
194 let row = document.createElement("richlistitem"); |
|
195 row.setAttribute("class", "console-row"); |
|
196 return row; |
|
197 }, |
|
198 |
|
199 appendConsoleRow: function cv_appendConsoleRow(aRow) { |
|
200 this._list.appendChild(aRow); |
|
201 if (++this._count > this.limit) { |
|
202 this.deleteFirst(); |
|
203 } |
|
204 }, |
|
205 |
|
206 deleteFirst: function cv_deleteFirst() { |
|
207 let node = this._list.firstChild; |
|
208 this._list.removeChild(node); |
|
209 --this._count; |
|
210 }, |
|
211 |
|
212 appendInitialItems: function cv_appendInitialItems() { |
|
213 this._list.collapsed = true; |
|
214 let messages = Services.console.getMessageArray(); |
|
215 |
|
216 // In case getMessageArray returns 0-length array as null |
|
217 if (!messages) |
|
218 messages = []; |
|
219 |
|
220 let limit = messages.length - this.limit; |
|
221 if (limit < 0) |
|
222 limit = 0; |
|
223 |
|
224 // Checks if console ever been cleared |
|
225 for (var i = messages.length - 1; i >= limit; --i) { |
|
226 if (!messages[i].message) { |
|
227 break; |
|
228 } |
|
229 } |
|
230 |
|
231 // Populate with messages after latest "clear" |
|
232 while (++i < messages.length) { |
|
233 this.appendItem(messages[i]); |
|
234 } |
|
235 this._list.collapsed = false; |
|
236 }, |
|
237 |
|
238 clearConsole: function cv_clearConsole() { |
|
239 if (this._count == 0) // already clear |
|
240 return; |
|
241 this._count = 0; |
|
242 |
|
243 let newRows = this._list.cloneNode(false); |
|
244 this._list.parentNode.replaceChild(newRows, this._list); |
|
245 this._list = newRows; |
|
246 this.selectedItem = null; |
|
247 }, |
|
248 |
|
249 copyAll: function () { |
|
250 let mode = document.getElementById("console-filter").value; |
|
251 let rows = this._list.childNodes; |
|
252 let copyText = ""; |
|
253 for (let i=0; i < rows.length; i++) { |
|
254 let row = rows[i]; |
|
255 if (mode == "all" || row.getAttribute ("type") == mode) { |
|
256 let text = "* " + row.getAttribute("msg"); |
|
257 if (row.hasAttribute("href")) { |
|
258 text += "\r\n " + row.getAttribute("href") + " line:" + row.getAttribute("line"); |
|
259 } |
|
260 if (row.hasAttribute("code")) { |
|
261 text += "\r\n " + row.getAttribute("code") + " col:" + row.getAttribute("col"); |
|
262 } |
|
263 copyText += text + "\r\n"; |
|
264 } |
|
265 } |
|
266 let clip = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper); |
|
267 clip.copyString(copyText, document); |
|
268 }, |
|
269 |
|
270 changeMode: function cv_changeMode() { |
|
271 let mode = document.getElementById("console-filter").value; |
|
272 if (this._list.getAttribute("mode") != mode) { |
|
273 let rows = this._list.childNodes; |
|
274 for (let i=0; i < rows.length; i++) { |
|
275 let row = rows[i]; |
|
276 if (mode == "all" || row.getAttribute ("type") == mode) |
|
277 row.collapsed = false; |
|
278 else |
|
279 row.collapsed = true; |
|
280 } |
|
281 this._list.mode = mode; |
|
282 this._list.scrollToIndex(0); |
|
283 } |
|
284 }, |
|
285 |
|
286 onContextMenu: function cv_onContextMenu(aEvent) { |
|
287 let row = aEvent.target; |
|
288 let text = ["msg", "href", "line", "code", "col"].map(function(attr) row.getAttribute(attr)) |
|
289 .filter(function(x) x).join("\r\n"); |
|
290 |
|
291 ContextMenuUI.showContextMenu({ |
|
292 target: row, |
|
293 json: { |
|
294 types: ["copy"], |
|
295 string: text, |
|
296 xPos: aEvent.clientX, |
|
297 yPos: aEvent.clientY |
|
298 } |
|
299 }); |
|
300 }, |
|
301 |
|
302 onRowClick: function (aRow) { |
|
303 if (aRow.hasAttribute("code")) { |
|
304 aRow.setAttribute("hideCode", "false"); |
|
305 } |
|
306 if (aRow.hasAttribute("col")) { |
|
307 aRow.setAttribute("hideCaret", "false"); |
|
308 } |
|
309 }, |
|
310 |
|
311 onEvalKeyPress: function cv_onEvalKeyPress(aEvent) { |
|
312 if (aEvent.keyCode == 13) |
|
313 this.evaluateTypein(); |
|
314 }, |
|
315 |
|
316 onConsoleBoxKeyPress: function cv_onConsoleBoxKeyPress(aEvent) { |
|
317 if ((aEvent.charCode == 99 || aEvent.charCode == 67) && aEvent.ctrlKey && this._list && this._list.selectedItem) { |
|
318 let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper); |
|
319 clipboard.copyString(this._list.selectedItem.getAttribute("msg"), document); |
|
320 } |
|
321 }, |
|
322 |
|
323 evaluateTypein: function cv_evaluateTypein() { |
|
324 this._evalCode = this._evalTextbox.value; |
|
325 this.loadOrDisplayResult(); |
|
326 }, |
|
327 |
|
328 loadOrDisplayResult: function cv_loadOrDisplayResult() { |
|
329 if (this._evalCode) { |
|
330 this._evalFrame.contentWindow.location = "javascript: " + this._evalCode.replace(/%/g, "%25"); |
|
331 this._evalCode = ""; |
|
332 return; |
|
333 } |
|
334 |
|
335 let resultRange = this._evalFrame.contentDocument.createRange(); |
|
336 resultRange.selectNode(this._evalFrame.contentDocument.documentElement); |
|
337 let result = resultRange.toString(); |
|
338 if (result) |
|
339 Services.console.logStringMessage(result); |
|
340 // or could use appendMessage which doesn't persist |
|
341 }, |
|
342 |
|
343 repeatChar: function cv_repeatChar(aChar, aCol) { |
|
344 if (--aCol <= 0) |
|
345 return ""; |
|
346 |
|
347 for (let i = 2; i < aCol; i += i) |
|
348 aChar += aChar; |
|
349 |
|
350 return aChar + aChar.slice(0, aCol - aChar.length); |
|
351 } |
|
352 }; |