michael@0: // -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*- michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: let ConsolePanelView = { michael@0: _list: null, michael@0: _inited: false, michael@0: _evalTextbox: null, michael@0: _evalFrame: null, michael@0: _evalCode: "", michael@0: _bundle: null, michael@0: _showChromeErrors: -1, michael@0: _enabledPref: "devtools.errorconsole.enabled", michael@0: michael@0: get enabled() { michael@0: return Services.prefs.getBoolPref(this._enabledPref); michael@0: }, michael@0: michael@0: get follow() { michael@0: return document.getElementById("console-follow-checkbox").checked; michael@0: }, michael@0: michael@0: init: function cv_init() { michael@0: if (this._list) michael@0: return; michael@0: michael@0: this._list = document.getElementById("console-box"); michael@0: this._evalTextbox = document.getElementById("console-eval-textbox"); michael@0: this._bundle = Strings.browser; michael@0: michael@0: this._count = 0; michael@0: this.limit = 250; michael@0: this.fieldMaxLength = 140; michael@0: michael@0: try { michael@0: // update users using the legacy pref michael@0: if (Services.prefs.getBoolPref("browser.console.showInPanel")) { michael@0: Services.prefs.setBoolPref(this._enabledPref, true); michael@0: Services.prefs.clearUserPref("browser.console.showInPanel"); michael@0: } michael@0: } catch(ex) { michael@0: // likely don't have an old pref michael@0: } michael@0: Services.prefs.addObserver(this._enabledPref, this, false); michael@0: }, michael@0: michael@0: show: function show() { michael@0: if (this._inited) michael@0: return; michael@0: this._inited = true; michael@0: michael@0: this.init(); // In case the panel is selected before init has been called. michael@0: michael@0: Services.console.registerListener(this); michael@0: michael@0: this.appendInitialItems(); michael@0: michael@0: // Delay creation of the iframe for startup performance michael@0: this._evalFrame = document.createElement("iframe"); michael@0: this._evalFrame.id = "console-evaluator"; michael@0: this._evalFrame.collapsed = true; michael@0: document.getElementById("console-container").appendChild(this._evalFrame); michael@0: michael@0: this._evalFrame.addEventListener("load", this.loadOrDisplayResult.bind(this), true); michael@0: }, michael@0: michael@0: uninit: function cv_uninit() { michael@0: if (this._inited) michael@0: Services.console.unregisterListener(this); michael@0: michael@0: Services.prefs.removeObserver(this._enabledPref, this, false); michael@0: }, michael@0: michael@0: observe: function(aSubject, aTopic, aData) { michael@0: if (aTopic == "nsPref:changed") { michael@0: // We may choose to create a new menu in v2 michael@0: } michael@0: else michael@0: this.appendItem(aSubject); michael@0: }, michael@0: michael@0: showChromeErrors: function() { michael@0: if (this._showChromeErrors != -1) michael@0: return this._showChromeErrors; michael@0: michael@0: try { michael@0: let pref = Services.prefs; michael@0: return this._showChromeErrors = pref.getBoolPref("javascript.options.showInConsole"); michael@0: } michael@0: catch(ex) { michael@0: return this._showChromeErrors = false; michael@0: } michael@0: }, michael@0: michael@0: appendItem: function cv_appendItem(aObject) { michael@0: let index = -1; michael@0: try { michael@0: // Try to QI it to a script error to get more info michael@0: let scriptError = aObject.QueryInterface(Ci.nsIScriptError); michael@0: michael@0: // filter chrome urls michael@0: if (!this.showChromeErrors && scriptError.sourceName.substr(0, 9) == "chrome://") michael@0: return; michael@0: index = this.appendError(scriptError); michael@0: } michael@0: catch (ex) { michael@0: try { michael@0: // Try to QI it to a console message michael@0: let msg = aObject.QueryInterface(Ci.nsIConsoleMessage); michael@0: michael@0: if (msg.message) michael@0: index = this.appendMessage(msg.message); michael@0: else // observed a null/"clear" message michael@0: this.clearConsole(); michael@0: } michael@0: catch (ex2) { michael@0: // Give up and append the object itself as a string michael@0: index = this.appendMessage(aObject); michael@0: } michael@0: } michael@0: if (this.follow) { michael@0: this._list.ensureIndexIsVisible(index); michael@0: } michael@0: }, michael@0: michael@0: truncateIfNecessary: function (aString) { michael@0: if (!aString || aString.length <= this.fieldMaxLength) { michael@0: return aString; michael@0: } michael@0: let truncatedString = aString.substring(0, this.fieldMaxLength); michael@0: let Ci = Components.interfaces; michael@0: let ellipsis = Services.prefs.getComplexValue("intl.ellipsis", michael@0: Ci.nsIPrefLocalizedString).data; michael@0: truncatedString = truncatedString + ellipsis; michael@0: return truncatedString; michael@0: }, michael@0: michael@0: appendError: function cv_appendError(aObject) { michael@0: let row = this.createConsoleRow(); michael@0: let nsIScriptError = Ci.nsIScriptError; michael@0: michael@0: // Is this error actually just a non-fatal warning? michael@0: let warning = aObject.flags & nsIScriptError.warningFlag != 0; michael@0: michael@0: let typetext = warning ? "typeWarning" : "typeError"; michael@0: row.setAttribute("typetext", this._bundle.GetStringFromName(typetext)); michael@0: row.setAttribute("type", warning ? "warning" : "error"); michael@0: row.setAttribute("msg", aObject.errorMessage); michael@0: row.setAttribute("category", aObject.category); michael@0: if (aObject.lineNumber || aObject.sourceName) { michael@0: row.setAttribute("href", aObject.sourceName); michael@0: row.setAttribute("line", aObject.lineNumber); michael@0: } michael@0: else { michael@0: row.setAttribute("hideSource", "true"); michael@0: } michael@0: // hide code by default, otherwise initial item display will michael@0: // hang the browser. michael@0: row.setAttribute("hideCode", "true"); michael@0: row.setAttribute("hideCaret", "true"); michael@0: michael@0: if (aObject.sourceLine) { michael@0: row.setAttribute("code", this.truncateIfNecessary(aObject.sourceLine.replace(/\s/g, " "))); michael@0: if (aObject.columnNumber) { michael@0: row.setAttribute("col", aObject.columnNumber); michael@0: } michael@0: } michael@0: michael@0: let mode = document.getElementById("console-filter").value; michael@0: if (mode != "all" && mode != row.getAttribute("type")) { michael@0: row.collapsed = true; michael@0: } michael@0: michael@0: row.setAttribute("onclick", "ConsolePanelView.onRowClick(this)"); michael@0: this.appendConsoleRow(row); michael@0: return this._list.getIndexOfItem(row); michael@0: }, michael@0: michael@0: appendMessage: function cv_appendMessage (aMessage) { michael@0: let row = this.createConsoleRow(); michael@0: row.setAttribute("type", "message"); michael@0: row.setAttribute("msg", aMessage); michael@0: michael@0: let mode = document.getElementById("console-filter").value; michael@0: if (mode != "all" && mode != "message") michael@0: row.collapsed = true; michael@0: michael@0: this.appendConsoleRow(row); michael@0: return this._list.getIndexOfItem(row); michael@0: }, michael@0: michael@0: createConsoleRow: function cv_createConsoleRow() { michael@0: let row = document.createElement("richlistitem"); michael@0: row.setAttribute("class", "console-row"); michael@0: return row; michael@0: }, michael@0: michael@0: appendConsoleRow: function cv_appendConsoleRow(aRow) { michael@0: this._list.appendChild(aRow); michael@0: if (++this._count > this.limit) { michael@0: this.deleteFirst(); michael@0: } michael@0: }, michael@0: michael@0: deleteFirst: function cv_deleteFirst() { michael@0: let node = this._list.firstChild; michael@0: this._list.removeChild(node); michael@0: --this._count; michael@0: }, michael@0: michael@0: appendInitialItems: function cv_appendInitialItems() { michael@0: this._list.collapsed = true; michael@0: let messages = Services.console.getMessageArray(); michael@0: michael@0: // In case getMessageArray returns 0-length array as null michael@0: if (!messages) michael@0: messages = []; michael@0: michael@0: let limit = messages.length - this.limit; michael@0: if (limit < 0) michael@0: limit = 0; michael@0: michael@0: // Checks if console ever been cleared michael@0: for (var i = messages.length - 1; i >= limit; --i) { michael@0: if (!messages[i].message) { michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // Populate with messages after latest "clear" michael@0: while (++i < messages.length) { michael@0: this.appendItem(messages[i]); michael@0: } michael@0: this._list.collapsed = false; michael@0: }, michael@0: michael@0: clearConsole: function cv_clearConsole() { michael@0: if (this._count == 0) // already clear michael@0: return; michael@0: this._count = 0; michael@0: michael@0: let newRows = this._list.cloneNode(false); michael@0: this._list.parentNode.replaceChild(newRows, this._list); michael@0: this._list = newRows; michael@0: this.selectedItem = null; michael@0: }, michael@0: michael@0: copyAll: function () { michael@0: let mode = document.getElementById("console-filter").value; michael@0: let rows = this._list.childNodes; michael@0: let copyText = ""; michael@0: for (let i=0; i < rows.length; i++) { michael@0: let row = rows[i]; michael@0: if (mode == "all" || row.getAttribute ("type") == mode) { michael@0: let text = "* " + row.getAttribute("msg"); michael@0: if (row.hasAttribute("href")) { michael@0: text += "\r\n " + row.getAttribute("href") + " line:" + row.getAttribute("line"); michael@0: } michael@0: if (row.hasAttribute("code")) { michael@0: text += "\r\n " + row.getAttribute("code") + " col:" + row.getAttribute("col"); michael@0: } michael@0: copyText += text + "\r\n"; michael@0: } michael@0: } michael@0: let clip = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper); michael@0: clip.copyString(copyText, document); michael@0: }, michael@0: michael@0: changeMode: function cv_changeMode() { michael@0: let mode = document.getElementById("console-filter").value; michael@0: if (this._list.getAttribute("mode") != mode) { michael@0: let rows = this._list.childNodes; michael@0: for (let i=0; i < rows.length; i++) { michael@0: let row = rows[i]; michael@0: if (mode == "all" || row.getAttribute ("type") == mode) michael@0: row.collapsed = false; michael@0: else michael@0: row.collapsed = true; michael@0: } michael@0: this._list.mode = mode; michael@0: this._list.scrollToIndex(0); michael@0: } michael@0: }, michael@0: michael@0: onContextMenu: function cv_onContextMenu(aEvent) { michael@0: let row = aEvent.target; michael@0: let text = ["msg", "href", "line", "code", "col"].map(function(attr) row.getAttribute(attr)) michael@0: .filter(function(x) x).join("\r\n"); michael@0: michael@0: ContextMenuUI.showContextMenu({ michael@0: target: row, michael@0: json: { michael@0: types: ["copy"], michael@0: string: text, michael@0: xPos: aEvent.clientX, michael@0: yPos: aEvent.clientY michael@0: } michael@0: }); michael@0: }, michael@0: michael@0: onRowClick: function (aRow) { michael@0: if (aRow.hasAttribute("code")) { michael@0: aRow.setAttribute("hideCode", "false"); michael@0: } michael@0: if (aRow.hasAttribute("col")) { michael@0: aRow.setAttribute("hideCaret", "false"); michael@0: } michael@0: }, michael@0: michael@0: onEvalKeyPress: function cv_onEvalKeyPress(aEvent) { michael@0: if (aEvent.keyCode == 13) michael@0: this.evaluateTypein(); michael@0: }, michael@0: michael@0: onConsoleBoxKeyPress: function cv_onConsoleBoxKeyPress(aEvent) { michael@0: if ((aEvent.charCode == 99 || aEvent.charCode == 67) && aEvent.ctrlKey && this._list && this._list.selectedItem) { michael@0: let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper); michael@0: clipboard.copyString(this._list.selectedItem.getAttribute("msg"), document); michael@0: } michael@0: }, michael@0: michael@0: evaluateTypein: function cv_evaluateTypein() { michael@0: this._evalCode = this._evalTextbox.value; michael@0: this.loadOrDisplayResult(); michael@0: }, michael@0: michael@0: loadOrDisplayResult: function cv_loadOrDisplayResult() { michael@0: if (this._evalCode) { michael@0: this._evalFrame.contentWindow.location = "javascript: " + this._evalCode.replace(/%/g, "%25"); michael@0: this._evalCode = ""; michael@0: return; michael@0: } michael@0: michael@0: let resultRange = this._evalFrame.contentDocument.createRange(); michael@0: resultRange.selectNode(this._evalFrame.contentDocument.documentElement); michael@0: let result = resultRange.toString(); michael@0: if (result) michael@0: Services.console.logStringMessage(result); michael@0: // or could use appendMessage which doesn't persist michael@0: }, michael@0: michael@0: repeatChar: function cv_repeatChar(aChar, aCol) { michael@0: if (--aCol <= 0) michael@0: return ""; michael@0: michael@0: for (let i = 2; i < aCol; i += i) michael@0: aChar += aChar; michael@0: michael@0: return aChar + aChar.slice(0, aCol - aChar.length); michael@0: } michael@0: };