Bug 1339559 - Identify script that resulted in non-structured-clonable data draft
authorTomislav Jovanovic <tomica@gmail.com>
Mon, 13 Mar 2017 09:04:45 +0100
changeset 498162 8b3550893d25787f39822a0d5db38f33b4125972
parent 497274 f923de5a11109e677c993d7d95d1a9aba1a89e9a
child 500709 e1cb70eeed815da7be628a4e69b5c398a634229a
child 500710 e6b688acfa26ee6e6d39e91d454dfdaf376d7c00
child 500738 7ff101c12050cac017084aa5c1689bdb58124283
child 500792 e234783afb218d63057cf9c1757b168c7e7c28cb
push id49135
push userbmo:tomica@gmail.com
push dateTue, 14 Mar 2017 13:51:46 +0000
bugs1339559
milestone55.0a1
Bug 1339559 - Identify script that resulted in non-structured-clonable data MozReview-Commit-ID: AURB4Qpwimh
browser/components/extensions/test/browser/browser_ext_tabs_executeScript.js
mobile/android/components/extensions/test/mochitest/test_ext_tabs_executeScript.html
toolkit/components/extensions/ExtensionCommon.jsm
toolkit/components/extensions/ExtensionContent.jsm
toolkit/components/extensions/ExtensionParent.jsm
--- a/browser/components/extensions/test/browser/browser_ext_tabs_executeScript.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_executeScript.js
@@ -111,30 +111,42 @@ add_task(function* testExecuteScript() {
           browser.test.assertTrue(/\/file_iframe_document\.html$/.test(result[0]), "Result is correct");
         }),
 
         browser.tabs.executeScript({
           code: "window",
         }).then(result => {
           browser.test.fail("Expected error when returning non-structured-clonable object");
         }, error => {
-          browser.test.assertEq("Script returned non-structured-clonable data",
+          browser.test.assertEq("<anonymous code>", error.fileName, "Got expected fileName");
+          browser.test.assertEq("Script '<anonymous code>' result is non-structured-clonable data",
                                 error.message, "Got expected error");
         }),
 
         browser.tabs.executeScript({
           code: "Promise.resolve(window)",
         }).then(result => {
           browser.test.fail("Expected error when returning non-structured-clonable object");
         }, error => {
-          browser.test.assertEq("Script returned non-structured-clonable data",
+          browser.test.assertEq("<anonymous code>", error.fileName, "Got expected fileName");
+          browser.test.assertEq("Script '<anonymous code>' result is non-structured-clonable data",
                                 error.message, "Got expected error");
         }),
 
         browser.tabs.executeScript({
+          file: "script3.js",
+        }).then(result => {
+          browser.test.fail("Expected error when returning non-structured-clonable object");
+        }, error => {
+          const expected = /Script '.*script3.js' result is non-structured-clonable data/;
+          browser.test.assertTrue(expected.test(error.message), "Got expected error");
+          browser.test.assertTrue(error.fileName.endsWith("script3.js"), "Got expected fileName");
+        }),
+
+        browser.tabs.executeScript({
           frameId: Number.MAX_SAFE_INTEGER,
           code: "42",
         }).then(result => {
           browser.test.fail("Expected error when specifying invalid frame ID");
         }, error => {
           let details = {
             frame_id: Number.MAX_SAFE_INTEGER,
             matchesHost: ["http://mochi.test/", "http://example.com/"],
@@ -231,16 +243,18 @@ add_task(function* testExecuteScript() {
     background,
 
     files: {
       "script.js": function() {
         browser.runtime.sendMessage("script ran");
       },
 
       "script2.js": "27",
+
+      "script3.js": "window",
     },
   });
 
   yield extension.startup();
 
   yield extension.awaitFinish("executeScript");
 
   yield extension.unload();
--- a/mobile/android/components/extensions/test/mochitest/test_ext_tabs_executeScript.html
+++ b/mobile/android/components/extensions/test/mochitest/test_ext_tabs_executeScript.html
@@ -94,30 +94,42 @@ add_task(function* testExecuteScript() {
           browser.test.assertTrue(/\/file_iframe_document\.html$/.test(result[0]), "Result is correct");
         }),
 
         browser.tabs.executeScript({
           code: "window",
         }).then(result => {
           browser.test.fail("Expected error when returning non-structured-clonable object");
         }, error => {
-          browser.test.assertEq("Script returned non-structured-clonable data",
+          browser.test.assertEq("<anonymous code>", error.fileName, "Got expected fileName");
+          browser.test.assertEq("Script '<anonymous code>' result is non-structured-clonable data",
                                 error.message, "Got expected error");
         }),
 
         browser.tabs.executeScript({
           code: "Promise.resolve(window)",
         }).then(result => {
           browser.test.fail("Expected error when returning non-structured-clonable object");
         }, error => {
-          browser.test.assertEq("Script returned non-structured-clonable data",
+          browser.test.assertEq("<anonymous code>", error.fileName, "Got expected fileName");
+          browser.test.assertEq("Script '<anonymous code>' result is non-structured-clonable data",
                                 error.message, "Got expected error");
         }),
 
         browser.tabs.executeScript({
+          file: "script3.js",
+        }).then(result => {
+          browser.test.fail("Expected error when returning non-structured-clonable object");
+        }, error => {
+          const expected = /Script '.*script3.js' result is non-structured-clonable data/;
+          browser.test.assertTrue(expected.test(error.message), "Got expected error");
+          browser.test.assertTrue(error.fileName.endsWith("script3.js"), "Got expected fileName");
+        }),
+
+        browser.tabs.executeScript({
           frameId: Number.MAX_SAFE_INTEGER,
           code: "42",
         }).then(result => {
           browser.test.fail("Expected error when specifying invalid frame ID");
         }, error => {
           let details = {
             frame_id: Number.MAX_SAFE_INTEGER,
             matchesHost: ["http://mochi.test/", "http://example.com/"],
@@ -217,16 +229,18 @@ add_task(function* testExecuteScript() {
     background,
 
     files: {
       "script.js": function() {
         browser.runtime.sendMessage("script ran");
       },
 
       "script2.js": "27",
+
+      "script3.js": "window",
     },
   });
 
   yield extension.startup();
 
   yield extension.awaitFinish("executeScript");
 
   yield extension.unload();
--- a/toolkit/components/extensions/ExtensionCommon.jsm
+++ b/toolkit/components/extensions/ExtensionCommon.jsm
@@ -227,27 +227,26 @@ class BaseContext {
    *
    * @param {Error|object} error
    * @returns {Error}
    */
   normalizeError(error) {
     if (error instanceof this.cloneScope.Error) {
       return error;
     }
-    let message;
-    if (instanceOf(error, "Object") || error instanceof ExtensionError) {
+    let message, fileName;
+    if (instanceOf(error, "Object") || error instanceof ExtensionError ||
+        typeof error == "object" && this.principal.subsumes(Cu.getObjectPrincipal(error))) {
       message = error.message;
-    } else if (typeof error == "object" &&
-        this.principal.subsumes(Cu.getObjectPrincipal(error))) {
-      message = error.message;
+      fileName = error.fileName;
     } else {
       Cu.reportError(error);
     }
     message = message || "An unexpected error occurred";
-    return new this.cloneScope.Error(message);
+    return new this.cloneScope.Error(message, fileName);
   }
 
   /**
    * Sets the value of `.lastError` to `error`, calls the given
    * callback, and reports an error if the value has not been checked
    * when the callback returns.
    *
    * @param {object} error An object with a `message` property. May
--- a/toolkit/components/extensions/ExtensionContent.jsm
+++ b/toolkit/components/extensions/ExtensionContent.jsm
@@ -1012,17 +1012,20 @@ class ExtensionGlobal {
   // Used to executeScript, insertCSS and removeCSS.
   handleExtensionExecute(target, extensionId, options) {
     return DocumentManager.executeScript(target, extensionId, options).then(result => {
       try {
         // Make sure we can structured-clone the result value before
         // we try to send it back over the message manager.
         Cu.cloneInto(result, target);
       } catch (e) {
-        return Promise.reject({message: "Script returned non-structured-clonable data"});
+        const {js} = options;
+        const fileName = js.length ? js[js.length - 1] : "<anonymous code>";
+        const message = `Script '${fileName}' result is non-structured-clonable data`;
+        return Promise.reject({message, fileName});
       }
       return result;
     });
   }
 
   handleWebNavigationGetFrame({frameId}) {
     return WebNavigationFrames.getFrame(this.global.docShell, frameId);
   }
--- a/toolkit/components/extensions/ExtensionParent.jsm
+++ b/toolkit/components/extensions/ExtensionParent.jsm
@@ -596,17 +596,17 @@ ParentAPIManager = {
         result = result || Promise.resolve();
 
         result.then(result => {
           result = result instanceof SpreadArgs ? [...result] : [result];
 
           reply({result});
         }, error => {
           error = context.normalizeError(error);
-          reply({error: {message: error.message}});
+          reply({error: {message: error.message, fileName: error.fileName}});
         });
       }
     } catch (e) {
       if (data.callId) {
         let error = context.normalizeError(e);
         reply({error: {message: error.message}});
       } else {
         Cu.reportError(e);