test_chromeutils_callFunctionAndLogException.js (8726B)
1 let lastMessage; 2 const consoleListener = { 3 observe(message) { 4 dump(" >> new message: " + message.errorMessage + "\n"); 5 lastMessage = message; 6 }, 7 }; 8 Services.console.registerListener(consoleListener); 9 10 // The Console Service notifies its listener after one event loop cycle. 11 // So wait for one tick after each action dispatching a message/error to the service. 12 function waitForATick() { 13 return new Promise(resolve => Services.tm.dispatchToMainThread(resolve)); 14 } 15 16 add_task(async function customScriptError() { 17 const scriptError = Cc["@mozilla.org/scripterror;1"].createInstance( 18 Ci.nsIScriptError 19 ); 20 scriptError.init( 21 "foo", 22 "file.js", 23 1, 24 2, 25 Ci.nsIScriptError.warningFlag, 26 "some javascript" 27 ); 28 Services.console.logMessage(scriptError); 29 30 await waitForATick(); 31 32 Assert.equal( 33 lastMessage, 34 scriptError, 35 "We receive the exact same nsIScriptError object" 36 ); 37 38 Assert.equal(lastMessage.errorMessage, "foo"); 39 Assert.equal(lastMessage.sourceName, "file.js"); 40 Assert.equal(lastMessage.lineNumber, 1); 41 Assert.equal(lastMessage.columnNumber, 2); 42 Assert.equal(lastMessage.flags, Ci.nsIScriptError.warningFlag); 43 Assert.equal(lastMessage.category, "some javascript"); 44 45 Assert.equal( 46 lastMessage.stack, 47 undefined, 48 "Custom nsIScriptError object created from JS can't convey any stack" 49 ); 50 }); 51 52 add_task(async function callFunctionAndLogExceptionWithChromeGlobal() { 53 try { 54 ChromeUtils.callFunctionAndLogException(globalThis, function () { 55 throw new Error("custom exception"); 56 }); 57 Assert.ok(false, "callFunctionAndLogException should throw"); 58 } catch (e) { 59 Assert.equal( 60 e.name, 61 "Error", 62 "callFunctionAndLogException thrown with the expected exception name" 63 ); 64 Assert.equal( 65 e.message, 66 "custom exception", 67 "callFunctionAndLogException thrown with the expected message" 68 ); 69 } 70 71 await waitForATick(); 72 73 Assert.ok(!!lastMessage, "Got the message"); 74 Assert.ok( 75 lastMessage instanceof Ci.nsIScriptError, 76 "This is a nsIScriptError" 77 ); 78 79 Assert.equal(lastMessage.errorMessage, "Error: custom exception"); 80 Assert.equal(lastMessage.sourceName, _TEST_FILE); 81 Assert.equal(lastMessage.lineNumber, 55); 82 Assert.equal(lastMessage.columnNumber, 13); 83 Assert.equal(lastMessage.flags, Ci.nsIScriptError.errorFlag); 84 Assert.equal(lastMessage.category, "chrome javascript"); 85 Assert.ok(lastMessage.stack, "It has a stack"); 86 Assert.equal(lastMessage.stack.source, _TEST_FILE); 87 Assert.equal(lastMessage.stack.line, 55); 88 Assert.equal(lastMessage.stack.column, 13); 89 Assert.ok(!!lastMessage.stack.parent, "stack has a parent frame"); 90 Assert.equal( 91 lastMessage.innerWindowID, 92 0, 93 "The message isn't bound to any WindowGlobal" 94 ); 95 }); 96 97 add_task(async function callFunctionAndLogExceptionWithContentGlobal() { 98 const window = createContentWindow(); 99 try { 100 ChromeUtils.callFunctionAndLogException(window, function () { 101 throw new Error("another custom exception"); 102 }); 103 Assert.ok(false, "callFunctionAndLogException should throw"); 104 } catch (e) { 105 Assert.equal( 106 e.name, 107 "Error", 108 "callFunctionAndLogException thrown with the expected exception name" 109 ); 110 Assert.equal( 111 e.message, 112 "another custom exception", 113 "callFunctionAndLogException thrown with the expected message" 114 ); 115 } 116 117 await waitForATick(); 118 119 Assert.ok(!!lastMessage, "Got the message"); 120 Assert.ok( 121 lastMessage instanceof Ci.nsIScriptError, 122 "This is a nsIScriptError" 123 ); 124 125 Assert.equal(lastMessage.errorMessage, "Error: another custom exception"); 126 Assert.equal(lastMessage.sourceName, _TEST_FILE); 127 Assert.equal(lastMessage.lineNumber, 101); 128 Assert.equal(lastMessage.columnNumber, 13); 129 Assert.equal(lastMessage.flags, Ci.nsIScriptError.errorFlag); 130 Assert.equal(lastMessage.category, "content javascript"); 131 Assert.ok(lastMessage.stack, "It has a stack"); 132 Assert.equal(lastMessage.stack.source, _TEST_FILE); 133 Assert.equal(lastMessage.stack.line, 101); 134 Assert.equal(lastMessage.stack.column, 13); 135 Assert.ok(!!lastMessage.stack.parent, "stack has a parent frame"); 136 Assert.ok( 137 !!window.windowGlobalChild.innerWindowId, 138 "The window has a innerWindowId" 139 ); 140 Assert.equal( 141 lastMessage.innerWindowID, 142 window.windowGlobalChild.innerWindowId, 143 "The message is bound to the content window" 144 ); 145 }); 146 147 add_task(async function callFunctionAndLogExceptionForContentScriptSandboxes() { 148 const { sandbox, window } = createContentScriptSandbox(); 149 Cu.evalInSandbox( 150 `function foo() { throw new Error("sandbox exception"); }`, 151 sandbox, 152 null, 153 "sandbox-file.js", 154 1, 155 0 156 ); 157 try { 158 ChromeUtils.callFunctionAndLogException(window, sandbox.foo); 159 Assert.fail("callFunctionAndLogException should throw"); 160 } catch (e) { 161 Assert.equal( 162 e.name, 163 "Error", 164 "callFunctionAndLogException thrown with the expected exception name" 165 ); 166 Assert.equal( 167 e.message, 168 "sandbox exception", 169 "callFunctionAndLogException thrown with the expected message" 170 ); 171 } 172 173 await waitForATick(); 174 175 Assert.ok(!!lastMessage, "Got the message"); 176 // Note that it is important to "instanceof" in order to expose the nsIScriptError attributes. 177 Assert.ok( 178 lastMessage instanceof Ci.nsIScriptError, 179 "This is a nsIScriptError" 180 ); 181 182 Assert.equal(lastMessage.errorMessage, "Error: sandbox exception"); 183 Assert.equal(lastMessage.sourceName, "sandbox-file.js"); 184 Assert.equal(lastMessage.lineNumber, 1); 185 Assert.equal(lastMessage.columnNumber, 24); 186 Assert.equal(lastMessage.flags, Ci.nsIScriptError.errorFlag); 187 Assert.equal(lastMessage.category, "content javascript"); 188 Assert.ok(lastMessage.stack, "It has a stack"); 189 Assert.equal(lastMessage.stack.source, "sandbox-file.js"); 190 Assert.equal(lastMessage.stack.line, 1); 191 Assert.equal(lastMessage.stack.column, 24); 192 Assert.ok(!!lastMessage.stack.parent, "stack has a parent frame"); 193 Assert.ok( 194 !!window.windowGlobalChild.innerWindowId, 195 "The sandbox's prototype is a window and has a innerWindowId" 196 ); 197 Assert.equal( 198 lastMessage.innerWindowID, 199 window.windowGlobalChild.innerWindowId, 200 "The message is bound to the sandbox's prototype WindowGlobal" 201 ); 202 }); 203 204 add_task( 205 async function callFunctionAndLogExceptionForContentScriptSandboxesWrappedInChrome() { 206 const { sandbox, window } = createContentScriptSandbox(); 207 Cu.evalInSandbox( 208 `function foo() { throw new Error("sandbox exception"); }`, 209 sandbox, 210 null, 211 "sandbox-file.js", 212 1, 213 0 214 ); 215 try { 216 ChromeUtils.callFunctionAndLogException(window, function () { 217 sandbox.foo(); 218 }); 219 Assert.fail("callFunctionAndLogException should throw"); 220 } catch (e) { 221 Assert.equal( 222 e.name, 223 "Error", 224 "callFunctionAndLogException thrown with the expected exception name" 225 ); 226 Assert.equal( 227 e.message, 228 "sandbox exception", 229 "callFunctionAndLogException thrown with the expected message" 230 ); 231 } 232 233 await waitForATick(); 234 235 Assert.ok(!!lastMessage, "Got the message"); 236 // Note that it is important to "instanceof" in order to expose the nsIScriptError attributes. 237 Assert.ok( 238 lastMessage instanceof Ci.nsIScriptError, 239 "This is a nsIScriptError" 240 ); 241 242 Assert.ok( 243 !!window.windowGlobalChild.innerWindowId, 244 "The sandbox's prototype is a window and has a innerWindowId" 245 ); 246 Assert.equal( 247 lastMessage.innerWindowID, 248 window.windowGlobalChild.innerWindowId, 249 "The message is bound to the sandbox's prototype WindowGlobal" 250 ); 251 } 252 ); 253 254 add_task(function teardown() { 255 Services.console.unregisterListener(consoleListener); 256 }); 257 258 // We are in xpcshell, so we can't have a real DOM Window as in Firefox 259 // but let's try to have a fake one. 260 function createContentWindow() { 261 const principal = 262 Services.scriptSecurityManager.createContentPrincipalFromOrigin( 263 "http://example.com/" 264 ); 265 266 const webnav = Services.appShell.createWindowlessBrowser(false); 267 268 webnav.docShell.createAboutBlankDocumentViewer(principal, principal); 269 270 return webnav.document.defaultView; 271 } 272 273 // Create a Sandbox as in WebExtension content scripts 274 function createContentScriptSandbox() { 275 const window = createContentWindow(); 276 // The sandboxPrototype is the key here in order to 277 // make xpc::SandboxWindowOrNull ignore the sandbox 278 // and instead retrieve its prototype and link the error message 279 // to the window instead of the sandbox. 280 return { 281 sandbox: Cu.Sandbox(window, { sandboxPrototype: window }), 282 window, 283 }; 284 }