GeckoViewConsole.sys.mjs (5011B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 import { GeckoViewUtils } from "resource://gre/modules/GeckoViewUtils.sys.mjs"; 6 7 const { debug, warn } = GeckoViewUtils.initLogging("Console"); 8 9 const lazy = {}; 10 11 ChromeUtils.defineLazyGetter( 12 lazy, 13 "l10n", 14 () => new Localization(["mobile/android/geckoViewConsole.ftl"], true) 15 ); 16 17 export var GeckoViewConsole = { 18 _isEnabled: false, 19 20 get enabled() { 21 return this._isEnabled; 22 }, 23 24 set enabled(aVal) { 25 debug`enabled = ${aVal}`; 26 if (!!aVal === this._isEnabled) { 27 return; 28 } 29 30 this._isEnabled = !!aVal; 31 const ConsoleAPIStorage = Cc[ 32 "@mozilla.org/consoleAPI-storage;1" 33 ].getService(Ci.nsIConsoleAPIStorage); 34 if (this._isEnabled) { 35 this._consoleMessageListener = this._handleConsoleMessage.bind(this); 36 ConsoleAPIStorage.addLogEventListener( 37 this._consoleMessageListener, 38 Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal) 39 ); 40 } else if (this._consoleMessageListener) { 41 ConsoleAPIStorage.removeLogEventListener(this._consoleMessageListener); 42 delete this._consoleMessageListener; 43 } 44 }, 45 46 observe(aSubject, aTopic, aData) { 47 if (aTopic == "nsPref:changed") { 48 this.enabled = Services.prefs.getBoolPref(aData, false); 49 } 50 }, 51 52 _handleConsoleMessage(aMessage) { 53 aMessage = aMessage.wrappedJSObject; 54 55 const mappedArguments = Array.from( 56 aMessage.arguments, 57 this.formatResult, 58 this 59 ); 60 const joinedArguments = mappedArguments.join(" "); 61 62 if (aMessage.level == "error" || aMessage.level == "warn") { 63 const flag = 64 aMessage.level == "error" 65 ? Ci.nsIScriptError.errorFlag 66 : Ci.nsIScriptError.warningFlag; 67 const consoleMsg = Cc["@mozilla.org/scripterror;1"].createInstance( 68 Ci.nsIScriptError 69 ); 70 consoleMsg.init(joinedArguments, null, 0, 0, flag, "content javascript"); 71 Services.console.logMessage(consoleMsg); 72 } else if (aMessage.level == "trace") { 73 const args = aMessage.arguments; 74 const msgDetails = args[0] ?? aMessage; 75 const filename = this.abbreviateSourceURL(msgDetails.filename); 76 const functionName = 77 msgDetails.functionName || 78 lazy.l10n.formatValueSync("console-stacktrace-anonymous-function"); 79 80 let body = lazy.l10n.formatValueSync("console-stacktrace", { 81 filename, 82 functionName, 83 lineNumber: msgDetails.lineNumber ?? "", 84 }); 85 body += "\n"; 86 for (const aFrame of args) { 87 const functionName = 88 aFrame.functionName || 89 lazy.l10n.formatValueSync("console-stacktrace-anonymous-function"); 90 body += ` ${aFrame.filename} :: ${functionName} :: ${aFrame.lineNumber}\n`; 91 } 92 93 Services.console.logStringMessage(body); 94 } else if (aMessage.level == "time" && aMessage.arguments) { 95 const body = lazy.l10n.formatValueSync("console-timer-start", { 96 name: aMessage.arguments.name ?? "", 97 }); 98 Services.console.logStringMessage(body); 99 } else if (aMessage.level == "timeEnd" && aMessage.arguments) { 100 const body = lazy.l10n.formatValueSync("console-timer-end", { 101 name: aMessage.arguments.name ?? "", 102 duration: aMessage.arguments.duration ?? "", 103 }); 104 Services.console.logStringMessage(body); 105 } else if ( 106 ["group", "groupCollapsed", "groupEnd"].includes(aMessage.level) 107 ) { 108 // Do nothing yet 109 } else { 110 Services.console.logStringMessage(joinedArguments); 111 } 112 }, 113 114 getResultType(aResult) { 115 let type = aResult === null ? "null" : typeof aResult; 116 if (type == "object" && aResult.constructor && aResult.constructor.name) { 117 type = aResult.constructor.name; 118 } 119 return type.toLowerCase(); 120 }, 121 122 formatResult(aResult) { 123 let output = ""; 124 const type = this.getResultType(aResult); 125 switch (type) { 126 case "string": 127 case "boolean": 128 case "date": 129 case "error": 130 case "number": 131 case "regexp": 132 output = aResult.toString(); 133 break; 134 case "null": 135 case "undefined": 136 output = type; 137 break; 138 default: 139 output = aResult.toString(); 140 break; 141 } 142 143 return output; 144 }, 145 146 abbreviateSourceURL(aSourceURL) { 147 // Remove any query parameters. 148 const hookIndex = aSourceURL.indexOf("?"); 149 if (hookIndex > -1) { 150 aSourceURL = aSourceURL.substring(0, hookIndex); 151 } 152 153 // Remove a trailing "/". 154 if (aSourceURL[aSourceURL.length - 1] == "/") { 155 aSourceURL = aSourceURL.substring(0, aSourceURL.length - 1); 156 } 157 158 // Remove all but the last path component. 159 const slashIndex = aSourceURL.lastIndexOf("/"); 160 if (slashIndex > -1) { 161 aSourceURL = aSourceURL.substring(slashIndex + 1); 162 } 163 164 return aSourceURL; 165 }, 166 };