Bug 1146724 - test for WebChannel SendingContext. r=MattN/markh
authorShane Tomlinson <stomlinson@mozilla.com>
Mon, 22 Feb 2016 17:21:34 +1100
changeset 285023 675ef3c3fb457fff86b31ee0cc8b74d7fb5066d7
parent 285022 8a4eed67be3b2a60bace43c79d49253247769962
child 285024 789a12291942763bc1e3a89f97e0b82dc1c9d00b
child 285096 d5153540fc53c84d11017b5c179ff8c2fe4e03b2
push id30021
push usercbook@mozilla.com
push dateMon, 22 Feb 2016 13:37:39 +0000
treeherdermozilla-central@789a12291942 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersMattN, markh
bugs1146724
milestone47.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1146724 - test for WebChannel SendingContext. r=MattN/markh
browser/base/content/test/general/browser.ini
browser/base/content/test/general/browser_web_channel.html
browser/base/content/test/general/browser_web_channel.js
browser/base/content/test/general/browser_web_channel_iframe.html
toolkit/modules/tests/xpcshell/test_web_channel_broker.js
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -14,16 +14,17 @@ support-files =
   browser_bug970746.xhtml
   browser_fxa_oauth.html
   browser_fxa_oauth_with_keys.html
   browser_fxa_web_channel.html
   browser_registerProtocolHandler_notification.html
   browser_star_hsts.sjs
   browser_tab_dragdrop2_frame1.xul
   browser_web_channel.html
+  browser_web_channel_iframe.html
   bug592338.html
   bug792517-2.html
   bug792517.html
   bug792517.sjs
   bug839103.css
   contextmenu_common.js
   ctxmenu-image.png
   discovery.html
--- a/browser/base/content/test/general/browser_web_channel.html
+++ b/browser/base/content/test/general/browser_web_channel.html
@@ -1,29 +1,46 @@
 <!DOCTYPE html>
 <html>
 <head>
   <meta charset="utf-8">
   <title>web_channel_test</title>
 </head>
 <body>
 <script>
+   var IFRAME_SRC_ROOT = "http://mochi.test:8888/browser/browser/base/content/test/general/browser_web_channel_iframe.html";
+
   window.onload = function() {
     var testName = window.location.search.replace(/^\?/, "");
 
     switch(testName) {
       case "generic":
         test_generic();
         break;
       case "twoway":
         test_twoWay();
         break;
       case "multichannel":
         test_multichannel();
         break;
+      case "iframe":
+        test_iframe();
+        break;
+      case "iframe_pre_redirect":
+        test_iframe_pre_redirect();
+        break;
+      case "unsolicited":
+        test_unsolicited();
+        break;
+      case "bubbles":
+        test_bubbles();
+        break;
+      default:
+        throw new Error(`INVALID TEST NAME ${testName}`);
+        break;
     }
   };
 
   function test_generic() {
     var event = new window.CustomEvent("WebChannelMessageToChrome", {
       detail: {
         id: "generic",
         message: {
@@ -79,11 +96,72 @@
         id: "multichannel",
         message: {},
       }
     });
 
     window.dispatchEvent(event1);
     window.dispatchEvent(event2);
   }
+
+  function test_iframe() {
+    // Note that this message is the response to the message sent
+    // by the iframe!  This is bad, as this page is *not* trusted.
+    window.addEventListener("WebChannelMessageToContent", function(e) {
+      // the test parent will fail if the echo message is received.
+      echoEventToChannel(e, "echo");
+    });
+
+    // only attach the iframe for the iframe test to avoid
+    // interfering with other tests.
+    var iframe = document.createElement("iframe");
+    iframe.setAttribute("src", IFRAME_SRC_ROOT + "?iframe");
+    document.body.appendChild(iframe);
+  }
+
+  function test_iframe_pre_redirect() {
+    var iframe = document.createElement("iframe");
+    iframe.setAttribute("src", IFRAME_SRC_ROOT + "?iframe_pre_redirect");
+    document.body.appendChild(iframe);
+  }
+
+  function test_unsolicited() {
+    // echo any unsolicted events back to chrome.
+    window.addEventListener("WebChannelMessageToContent", function(e) {
+      echoEventToChannel(e, "echo");
+    }, true);
+  }
+
+  function test_bubbles() {
+    var event = new window.CustomEvent("WebChannelMessageToChrome", {
+      detail: {
+        id: "not_a_window",
+        message: {
+          command: "start"
+        }
+      }
+    });
+
+    var nonWindowTarget = document.getElementById("not_a_window");
+
+    nonWindowTarget.addEventListener("WebChannelMessageToContent", function(e) {
+      echoEventToChannel(e, "not_a_window");
+    }, true);
+
+
+    nonWindowTarget.dispatchEvent(event);
+  }
+
+  function echoEventToChannel(e, channelId) {
+    var echoedEvent = new window.CustomEvent("WebChannelMessageToChrome", {
+      detail: {
+        id: channelId,
+        message: e.detail.message,
+      }
+    });
+
+    e.target.dispatchEvent(echoedEvent);
+  }
 </script>
+
+<div id="not_a_window"></div>
 </body>
 </html>
--- a/browser/base/content/test/general/browser_web_channel.js
+++ b/browser/base/content/test/general/browser_web_channel.js
@@ -5,16 +5,19 @@
 Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "WebChannel",
   "resource://gre/modules/WebChannel.jsm");
 
 const HTTP_PATH = "http://example.com";
 const HTTP_ENDPOINT = "/browser/browser/base/content/test/general/browser_web_channel.html";
+const HTTP_MISMATCH_PATH = "http://example.org";
+const HTTP_IFRAME_PATH = "http://mochi.test:8888";
+const HTTP_REDIRECTED_IFRAME_PATH = "http://example.org";
 
 // Keep this synced with /mobile/android/tests/browser/robocop/testWebChannel.js
 // as much as possible.  (We only have that since we can't run browser chrome
 // tests on Android.  Yet?)
 var gTests = [
   {
     desc: "WebChannel generic message",
     run: function* () {
@@ -56,32 +59,280 @@ var gTests = [
           }
         });
 
         tab = gBrowser.addTab(HTTP_PATH + HTTP_ENDPOINT + "?twoway");
       });
     }
   },
   {
+    desc: "WebChannel two way communication in an iframe",
+    run: function* () {
+      let parentChannel = new WebChannel("echo", Services.io.newURI(HTTP_PATH, null, null));
+      let iframeChannel = new WebChannel("twoway", Services.io.newURI(HTTP_IFRAME_PATH, null, null));
+      let promiseTestDone = new Promise(function (resolve, reject) {
+        parentChannel.listen(function (id, message, sender) {
+          reject(new Error("WebChannel message incorrectly sent to parent"));
+        });
+
+        iframeChannel.listen(function (id, message, sender) {
+          is(id, "twoway");
+          ok(message.command);
+
+          if (message.command === "one") {
+            iframeChannel.send({ data: { nested: true } }, sender);
+          }
+
+          if (message.command === "two") {
+            is(message.detail.data.nested, true);
+            resolve();
+          }
+        });
+      });
+      yield BrowserTestUtils.withNewTab({
+        gBrowser: gBrowser,
+        url: HTTP_PATH + HTTP_ENDPOINT + "?iframe"
+      }, function* () {
+        yield promiseTestDone;
+        parentChannel.stopListening();
+        iframeChannel.stopListening();
+      });
+    }
+  },
+  {
+    desc: "WebChannel response to a redirected iframe",
+    run: function* () {
+      /**
+       * This test checks that WebChannel responses are only sent
+       * to an iframe if the iframe has not redirected to another origin.
+       * Test flow:
+       * 1. create a page, embed an iframe on origin A.
+       * 2. the iframe sends a message `redirecting`, then redirects to
+       *    origin B.
+       * 3. the iframe at origin B is set up to echo any messages back to the
+       *    test parent.
+       * 4. the test parent receives the `redirecting` message from origin A.
+       *    the test parent creates a new channel with origin B.
+       * 5. when origin B is ready, it sends a `loaded` message to the test
+       *    parent, letting the test parent know origin B is ready to echo
+       *    messages.
+       * 5. the test parent tries to send a response to origin A. If the
+       *    WebChannel does not perform a valid origin check, the response
+       *    will be received by origin B. If the WebChannel does perform
+       *    a valid origin check, the response will not be sent.
+       * 6. the test parent sends a `done` message to origin B, which origin
+       *    B echoes back. If the response to origin A is not echoed but
+       *    the message to origin B is, then hooray, the test passes.
+       */
+
+      let preRedirectChannel = new WebChannel("pre_redirect", Services.io.newURI(HTTP_IFRAME_PATH, null, null));
+      let postRedirectChannel = new WebChannel("post_redirect", Services.io.newURI(HTTP_REDIRECTED_IFRAME_PATH, null, null));
+
+      let promiseTestDone = new Promise(function (resolve, reject) {
+        preRedirectChannel.listen(function (id, message, preRedirectSender) {
+          if (message.command === "redirecting") {
+
+            postRedirectChannel.listen(function (id, message, postRedirectSender) {
+              is(id, "post_redirect");
+              isnot(message.command, "no_response_expected");
+
+              if (message.command === "loaded") {
+                // The message should not be received on the preRedirectChannel
+                // because the target window has redirected.
+                preRedirectChannel.send({ command: "no_response_expected" }, preRedirectSender);
+                postRedirectChannel.send({ command: "done" }, postRedirectSender);
+              } else if (message.command === "done") {
+                resolve();
+              } else {
+                reject(new Error(`Unexpected command ${message.command}`));
+              }
+            });
+          } else {
+            reject(new Error(`Unexpected command ${message.command}`));
+          }
+        });
+      });
+
+      yield BrowserTestUtils.withNewTab({
+        gBrowser: gBrowser,
+        url: HTTP_PATH + HTTP_ENDPOINT + "?iframe_pre_redirect"
+      }, function* () {
+        yield promiseTestDone;
+        preRedirectChannel.stopListening();
+        postRedirectChannel.stopListening();
+      });
+    }
+  },
+  {
     desc: "WebChannel multichannel",
     run: function* () {
       return new Promise(function(resolve, reject) {
         let tab;
         let channel = new WebChannel("multichannel", Services.io.newURI(HTTP_PATH, null, null));
 
         channel.listen(function (id, message, sender) {
           is(id, "multichannel");
           gBrowser.removeTab(tab);
           resolve();
         });
 
         tab = gBrowser.addTab(HTTP_PATH + HTTP_ENDPOINT + "?multichannel");
       });
     }
-  }
+  },
+  {
+    desc: "WebChannel unsolicited send, using system principal",
+    run: function* () {
+      let channel = new WebChannel("echo", Services.io.newURI(HTTP_PATH, null, null));
+
+      // an unsolicted message is sent from Chrome->Content which is then
+      // echoed back. If the echo is received here, then the content
+      // received the message.
+      let messagePromise = new Promise(function (resolve, reject) {
+        channel.listen(function (id, message, sender) {
+          is(id, "echo");
+          is(message.command, "unsolicited");
+
+          resolve()
+        });
+      });
+
+      yield BrowserTestUtils.withNewTab({
+        gBrowser,
+        url: HTTP_PATH + HTTP_ENDPOINT + "?unsolicited"
+      }, function* (targetBrowser) {
+        channel.send({ command: "unsolicited" }, {
+          browser: targetBrowser,
+          principal: Services.scriptSecurityManager.getSystemPrincipal()
+        });
+        yield messagePromise;
+        channel.stopListening();
+      });
+    }
+  },
+  {
+    desc: "WebChannel unsolicited send, using target origin's principal",
+    run: function* () {
+      let targetURI = Services.io.newURI(HTTP_PATH, null, null);
+      let channel = new WebChannel("echo", targetURI);
+
+      // an unsolicted message is sent from Chrome->Content which is then
+      // echoed back. If the echo is received here, then the content
+      // received the message.
+      let messagePromise = new Promise(function (resolve, reject) {
+        channel.listen(function (id, message, sender) {
+          is(id, "echo");
+          is(message.command, "unsolicited");
+
+          resolve();
+        });
+      });
+
+      yield BrowserTestUtils.withNewTab({
+        gBrowser,
+        url: HTTP_PATH + HTTP_ENDPOINT + "?unsolicited"
+      }, function* (targetBrowser) {
+
+        channel.send({ command: "unsolicited" }, {
+          browser: targetBrowser,
+          principal: Services.scriptSecurityManager.getNoAppCodebasePrincipal(targetURI)
+        });
+
+        yield messagePromise;
+        channel.stopListening();
+      });
+    }
+  },
+  {
+    desc: "WebChannel unsolicited send with principal mismatch",
+    run: function* () {
+      let targetURI = Services.io.newURI(HTTP_PATH, null, null);
+      let channel = new WebChannel("echo", targetURI);
+
+      // two unsolicited messages are sent from Chrome->Content. The first,
+      // `unsolicited_no_response_expected` is sent to the wrong principal
+      // and should not be echoed back. The second, `done`, is sent to the
+      // correct principal and should be echoed back.
+      let messagePromise = new Promise(function (resolve, reject) {
+        channel.listen(function (id, message, sender) {
+          is(id, "echo");
+
+          if (message.command === "done") {
+            resolve();
+          } else {
+            reject(new Error(`Unexpected command ${message.command}`));
+          }
+        });
+      });
+
+      yield BrowserTestUtils.withNewTab({
+        gBrowser: gBrowser,
+        url: HTTP_PATH + HTTP_ENDPOINT + "?unsolicited"
+      }, function* (targetBrowser) {
+
+        let mismatchURI = Services.io.newURI(HTTP_MISMATCH_PATH, null, null);
+        let mismatchPrincipal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(mismatchURI);
+
+        // send a message to the wrong principal. It should not be delivered
+        // to content, and should not be echoed back.
+        channel.send({ command: "unsolicited_no_response_expected" }, {
+          browser: targetBrowser,
+          principal: mismatchPrincipal
+        });
+
+        let targetPrincipal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(targetURI);
+
+        // send the `done` message to the correct principal. It
+        // should be echoed back.
+        channel.send({ command: "done" }, {
+          browser: targetBrowser,
+          principal: targetPrincipal
+        });
+
+        yield messagePromise;
+        channel.stopListening();
+      });
+    }
+  },
+  {
+    desc: "WebChannel non-window target",
+    run: function* () {
+      /**
+       * This test ensures messages can be received from and responses
+       * sent to non-window elements.
+       *
+       * First wait for the non-window element to send a "start" message.
+       * Then send the non-window element a "done" message.
+       * The non-window element will echo the "done" message back, if it
+       * receives the message.
+       * Listen for the response. If received, good to go!
+       */
+      let channel = new WebChannel("not_a_window", Services.io.newURI(HTTP_PATH, null, null));
+
+      let testDonePromise = new Promise(function (resolve, reject) {
+        channel.listen(function (id, message, sender) {
+          if (message.command === "start") {
+            channel.send({ command: "done" }, sender);
+          } else if (message.command === "done") {
+            resolve();
+          } else {
+            reject(new Error(`Unexpected command ${message.command}`));
+          }
+        });
+      });
+
+      yield BrowserTestUtils.withNewTab({
+        gBrowser,
+        url: HTTP_PATH + HTTP_ENDPOINT + "?bubbles"
+      }, function* () {
+        yield testDonePromise;
+        channel.stopListening();
+      });
+    }
+  },
 ]; // gTests
 
 function test() {
   waitForExplicitFinish();
 
   Task.spawn(function () {
     for (let test of gTests) {
       info("Running: " + test.desc);
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_web_channel_iframe.html
@@ -0,0 +1,97 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>web_channel_test (iframe)</title>
+</head>
+<body>
+<script>
+  var REDIRECTED_IFRAME_SRC_ROOT = "http://example.org/browser/browser/base/content/test/general/browser_web_channel_iframe.html";
+
+  window.onload = function() {
+    var testName = window.location.search.replace(/^\?/, "");
+    switch(testName) {
+      case "iframe":
+        test_iframe();
+        break;
+      case "iframe_pre_redirect":
+        test_iframe_pre_redirect();
+        break;
+      case "iframe_post_redirect":
+        test_iframe_post_redirect();
+        break;
+      default:
+        throw new Error(`INVALID TEST NAME ${testName}`);
+        break;
+    }
+  };
+
+  function test_iframe() {
+    var firstMessage = new window.CustomEvent("WebChannelMessageToChrome", {
+      detail: {
+        id: "twoway",
+        message: {
+          command: "one",
+        },
+      }
+    });
+
+    window.addEventListener("WebChannelMessageToContent", function(e) {
+      var secondMessage = new window.CustomEvent("WebChannelMessageToChrome", {
+        detail: {
+          id: "twoway",
+          message: {
+            command: "two",
+            detail: e.detail.message,
+          },
+        },
+      });
+
+      if (!e.detail.message.error) {
+        window.dispatchEvent(secondMessage);
+      }
+    }, true);
+
+    window.dispatchEvent(firstMessage);
+  }
+
+
+  function test_iframe_pre_redirect() {
+    var firstMessage = new window.CustomEvent("WebChannelMessageToChrome", {
+      detail: {
+        id: "pre_redirect",
+        message: {
+          command: "redirecting",
+        },
+      },
+    });
+    window.dispatchEvent(firstMessage);
+    document.location = REDIRECTED_IFRAME_SRC_ROOT + "?iframe_post_redirect";
+  }
+
+  function test_iframe_post_redirect() {
+    window.addEventListener("WebChannelMessageToContent", function(e) {
+      var echoMessage = new window.CustomEvent("WebChannelMessageToChrome", {
+        detail: {
+          id: "post_redirect",
+          message: e.detail.message,
+        },
+      });
+
+      window.dispatchEvent(echoMessage);
+    }, true);
+
+    // Let the test parent know the page has loaded and is ready to echo events
+    var loadedMessage = new window.CustomEvent("WebChannelMessageToChrome", {
+      detail: {
+        id: "post_redirect",
+        message: {
+          command: "loaded",
+        },
+      },
+    });
+    window.dispatchEvent(loadedMessage);
+  }
+</script>
+</body>
+</html>
--- a/toolkit/modules/tests/xpcshell/test_web_channel_broker.js
+++ b/toolkit/modules/tests/xpcshell/test_web_channel_broker.js
@@ -56,16 +56,17 @@ add_task(function test_web_channel_broke
     var channel = new Object({
       id: VALID_WEB_CHANNEL_ID,
       _originCheckCallback: requestPrincipal => {
         return VALID_WEB_CHANNEL_ORIGIN.prePath === requestPrincipal.origin;
       },
       deliver: function(data, sender) {
         do_check_eq(data.id, VALID_WEB_CHANNEL_ID);
         do_check_eq(data.message.command, "hello");
+        do_check_neq(sender, undefined);
         WebChannelBroker.unregisterChannel(channel);
         resolve();
       }
     });
 
     WebChannelBroker.registerChannel(channel);
 
     var mockEvent = {