Bug 1264513 - Part 3: new test cases for oop data channel scenario, r=smaug
authorJunior Hsu <juhsu@mozilla.com>
Mon, 25 Apr 2016 17:57:30 +0800
changeset 339422 c9b085a46a531e115314ecfa86928f7dd4931a9e
parent 339421 9b7a469be51121e5bc8ada8038d0b3c46cdd0a46
child 339423 ba2c44ebb458ba9f62158643d54b34c64a5459fc
push id6249
push userjlund@mozilla.com
push dateMon, 01 Aug 2016 13:59:36 +0000
treeherdermozilla-beta@bad9d4f5bf7e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1264513
milestone49.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 1264513 - Part 3: new test cases for oop data channel scenario, r=smaug
dom/presentation/tests/mochitest/PresentationSessionChromeScript.js
dom/presentation/tests/mochitest/PresentationSessionFrameScript.js
dom/presentation/tests/mochitest/mochitest.ini
dom/presentation/tests/mochitest/test_presentation_dc_receiver_oop.html
dom/presentation/tests/mochitest/test_presentation_dc_sender.html
--- a/dom/presentation/tests/mochitest/PresentationSessionChromeScript.js
+++ b/dom/presentation/tests/mochitest/PresentationSessionChromeScript.js
@@ -34,16 +34,17 @@ function registerMockedFactory(contractI
            mockedClassId: mockedClassId,
            mockedFactory: mockedFactory,
            originalClassId: originalClassId,
            originalFactory: originalFactory };
 }
 
 function registerOriginalFactory(contractId, mockedClassId, mockedFactory, originalClassId, originalFactory) {
   if (originalFactory) {
+    var registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
     registrar.unregisterFactory(mockedClassId, mockedFactory);
     registrar.registerFactory(originalClassId, "", contractId, originalFactory);
   }
 }
 
 const sessionId = 'test-session-id-' + uuidGenerator.generateUUID().toString();
 
 const address = Cc["@mozilla.org/supports-cstring;1"]
@@ -105,52 +106,45 @@ const mockedControlChannel = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannel]),
   set listener(listener) {
     this._listener = listener;
   },
   get listener() {
     return this._listener;
   },
   sendOffer: function(offer) {
-    var isValid = false;
-    try {
-      var addresses = offer.tcpAddress;
-      if (addresses.length > 0) {
-        for (var i = 0; i < addresses.length; i++) {
-          // Ensure CString addresses are used. Otherwise, an error will be thrown.
-          addresses.queryElementAt(i, Ci.nsISupportsCString);
-        }
-
-        isValid = true;
-      }
-    } catch (e) {
-      isValid = false;
-    }
-
-    sendAsyncMessage('offer-sent', isValid);
+    sendAsyncMessage('offer-sent', this._isValidSDP(offer));
   },
   sendAnswer: function(answer) {
-    var isValid = false;
-    try {
-      var addresses = answer.tcpAddress;
-      if (addresses.length > 0) {
-        for (var i = 0; i < addresses.length; i++) {
-          // Ensure CString addresses are used. Otherwise, an error will be thrown.
-          addresses.queryElementAt(i, Ci.nsISupportsCString);
-        }
+    sendAsyncMessage('answer-sent', this._isValidSDP(answer));
 
-        isValid = true;
-      }
-    } catch (e) {
-      isValid = false;
+    if (answer.type == Ci.nsIPresentationChannelDescription.TYPE_TCP) {
+      this._listener.QueryInterface(Ci.nsIPresentationSessionTransportCallback).notifyTransportReady();
     }
+  },
+  _isValidSDP: function(aSDP) {
+    var isValid = false;
+    if (aSDP.type == Ci.nsIPresentationChannelDescription.TYPE_TCP) {
+      try {
+        var addresses = aSDP.tcpAddress;
+        if (addresses.length > 0) {
+          for (var i = 0; i < addresses.length; i++) {
+            // Ensure CString addresses are used. Otherwise, an error will be thrown.
+            addresses.queryElementAt(i, Ci.nsISupportsCString);
+          }
 
-    sendAsyncMessage('answer-sent', isValid);
-
-    this._listener.QueryInterface(Ci.nsIPresentationSessionTransportCallback).notifyTransportReady();
+          isValid = true;
+        }
+      } catch (e) {
+        isValid = false;
+      }
+    } else if (aSDP.type == Ci.nsIPresentationChannelDescription.TYPE_DATACHANNEL) {
+      isValid = (aSDP.dataChannelSDP == "test-sdp");
+    }
+    return isValid;
   },
   close: function(reason) {
     sendAsyncMessage('control-channel-closed', reason);
     this._listener.QueryInterface(Ci.nsIPresentationControlChannelListener).notifyClosed(reason);
   },
   simulateReceiverReady: function() {
     this._listener.QueryInterface(Ci.nsIPresentationControlChannelListener).notifyReceiverReady();
   },
@@ -252,16 +246,17 @@ const mockedSessionTransport = {
 
     setTimeout(()=>{
       this._listener.onSessionTransport(this);
       this._listener = null;
     }, 0);
   },
   // in-process case
   buildDataChannelTransport: function(role, window, listener) {
+    dump("PresentationSessionChromeScript: build data channel transport\n");
     this._listener = listener;
     this._role = role;
 
     var hasNavigator = window ? (typeof window.navigator != "undefined") : false;
     sendAsyncMessage('check-navigator', hasNavigator);
 
     setTimeout(()=>{
       this._listener.onSessionTransport(this);
new file mode 100644
--- /dev/null
+++ b/dom/presentation/tests/mochitest/PresentationSessionFrameScript.js
@@ -0,0 +1,258 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function loadPrivilegedScriptTest() {
+  /**
+   * The script is loaded as
+   * (a) a privileged script in content process for dc_sender.html
+   * (b) a frame script in the remote iframe process for dc_receiver_oop.html
+   * |type port == "undefined"| indicates the script is load by
+   * |loadPrivilegedScript| which is the first case.
+   */
+  function sendMessage(type, data) {
+    if (typeof port == "undefined") {
+      sendAsyncMessage(type, {'data': data});
+    } else {
+      port.postMessage({'type': type,
+                        'data': data
+                       });
+    }
+  }
+
+  if (typeof port != "undefined") {
+    /**
+     * When the script is loaded by |loadPrivilegedScript|, these APIs
+     * are exposed to this script.
+     */
+    port.onmessage = (e) => {
+      var type = e.data['type'];
+      if (!handlers.hasOwnProperty(type)) {
+        return;
+      }
+      var args = [e];
+      handlers[type].forEach(handler => handler.apply(null, args));
+    };
+    var handlers = {};
+    addMessageListener = function(message, handler) {
+      if (handlers.hasOwnProperty(message)) {
+        handlers[message].push(handler);
+      } else {
+        handlers[message] = [handler];
+      }
+    };
+    removeMessageListener = function(message, handler) {
+      if (!handler || !handlers.hasOwnProperty(message)) {
+        return;
+      }
+      var index = handlers[message].indexOf(handler);
+      if (index != -1) {
+        handlers[message].splice(index, 1);
+      }
+    };
+  }
+
+  const { classes: Cc, interfaces: Ci, manager: Cm, utils: Cu, results: Cr } = Components;
+
+  const mockedChannelDescription = {
+    QueryInterface : function (iid) {
+      const interfaces = [Ci.nsIPresentationChannelDescription];
+
+      if (!interfaces.some(v => iid.equals(v))) {
+          throw Cr.NS_ERROR_NO_INTERFACE;
+      }
+      return this;
+    },
+    get type() {
+      if (Services.prefs.getBoolPref("dom.presentation.session_transport.data_channel.enable")) {
+        return Ci.nsIPresentationChannelDescription.TYPE_DATACHANNEL;
+      }
+      return Ci.nsIPresentationChannelDescription.TYPE_TCP;
+    },
+    get dataChannelSDP() {
+      return "test-sdp";
+    }
+  };
+
+  function setTimeout(callback, delay) {
+    let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+    timer.initWithCallback({ notify: callback },
+                           delay,
+                           Ci.nsITimer.TYPE_ONE_SHOT);
+    return timer;
+  }
+
+  const mockedSessionTransport = {
+    QueryInterface : function (iid) {
+        const interfaces = [Ci.nsIPresentationSessionTransport,
+                            Ci.nsIPresentationDataChannelSessionTransportBuilder,
+                            Ci.nsIFactory];
+
+        if (!interfaces.some(v => iid.equals(v))) {
+            throw Cr.NS_ERROR_NO_INTERFACE;
+        }
+        return this;
+    },
+    createInstance: function(aOuter, aIID) {
+      if (aOuter) {
+        throw Components.results.NS_ERROR_NO_AGGREGATION;
+      }
+      return this.QueryInterface(aIID);
+    },
+    set callback(callback) {
+      this._callback = callback;
+    },
+    get callback() {
+      return this._callback;
+    },
+    /* OOP case */
+    buildDataChannelTransport: function(role, window, listener) {
+      dump("PresentationSessionFrameScript: build data channel transport\n");
+      this._listener = listener;
+      this._role = role;
+
+      var hasNavigator = window ? (typeof window.navigator != "undefined") : false;
+      sendMessage('check-navigator', hasNavigator);
+
+      if (this._role == Ci.nsIPresentationService.ROLE_CONTROLLER) {
+        this._listener.sendOffer(mockedChannelDescription);
+      }
+    },
+
+    enableDataNotification: function() {
+      sendMessage('data-transport-notification-enabled');
+    },
+    send: function(data) {
+      sendMessage('message-sent', data);
+    },
+    close: function(reason) {
+      sendMessage('data-transport-closed', reason);
+      this._callback.QueryInterface(Ci.nsIPresentationSessionTransportCallback).notifyTransportClosed(reason);
+      this._callback = null;
+    },
+    simulateTransportReady: function() {
+      this._callback.QueryInterface(Ci.nsIPresentationSessionTransportCallback).notifyTransportReady();
+    },
+    simulateIncomingMessage: function(message) {
+      this._callback.QueryInterface(Ci.nsIPresentationSessionTransportCallback).notifyData(message);
+    },
+    onOffer: function(aOffer) {
+      this._listener.sendAnswer(mockedChannelDescription);
+      this._onSessionTransport();
+    },
+    onAnswer: function(aAnswer) {
+      this._onSessionTransport();
+    },
+    _onSessionTransport: function() {
+      setTimeout(()=>{
+        this._listener.onSessionTransport(this);
+        this.simulateTransportReady();
+        this._listener = null;
+      }, 0);
+    }
+  };
+
+
+  function tearDown() {
+    mockedSessionTransport.callback = null;
+
+    /* Register original factories. */
+    for (var data in originalFactoryData) {
+      registerOriginalFactory(data.contractId, data.mockedClassId,
+                              data.mockedFactory, data.originalClassId,
+                              data.originalFactory);
+    }
+    sendMessage("teardown-complete");
+  }
+
+
+  function registerMockedFactory(contractId, mockedClassId, mockedFactory) {
+    var originalClassId, originalFactory;
+    var registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
+
+    if (!registrar.isCIDRegistered(mockedClassId)) {
+      try {
+        originalClassId = registrar.contractIDToCID(contractId);
+        originalFactory = Cm.getClassObject(Cc[contractId], Ci.nsIFactory);
+      } catch (ex) {
+        originalClassId = "";
+        originalFactory = null;
+      }
+      if (originalFactory) {
+        registrar.unregisterFactory(originalClassId, originalFactory);
+      }
+      registrar.registerFactory(mockedClassId, "", contractId, mockedFactory);
+    }
+
+    return { contractId: contractId,
+             mockedClassId: mockedClassId,
+             mockedFactory: mockedFactory,
+             originalClassId: originalClassId,
+             originalFactory: originalFactory };
+  }
+
+  function registerOriginalFactory(contractId, mockedClassId, mockedFactory, originalClassId, originalFactory) {
+    if (originalFactory) {
+      var registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
+      registrar.unregisterFactory(mockedClassId, mockedFactory);
+      registrar.registerFactory(originalClassId, "", contractId, originalFactory);
+    }
+  }
+
+  /* Register mocked factories. */
+  const originalFactoryData = [];
+  const uuidGenerator = Cc["@mozilla.org/uuid-generator;1"]
+                        .getService(Ci.nsIUUIDGenerator);
+  originalFactoryData.push(registerMockedFactory("@mozilla.org/presentation/datachanneltransportbuilder;1",
+                                                 uuidGenerator.generateUUID(),
+                                                 mockedSessionTransport));
+
+  addMessageListener('trigger-incoming-message', function(event) {
+    mockedSessionTransport.simulateIncomingMessage(event.data.data);
+  });
+  addMessageListener('teardown', ()=>tearDown());
+}
+
+// Exposed to the caller of |loadPrivilegedScript|
+var contentScript = {
+  handlers: {},
+  addMessageListener: function(message, handler) {
+    if (this.handlers.hasOwnProperty(message)) {
+      this.handlers[message].push(handler);
+    } else {
+      this.handlers[message] = [handler];
+    }
+  },
+  removeMessageListener: function(message, handler) {
+    if (!handler || !this.handlers.hasOwnProperty(message)) {
+      return;
+    }
+    var index = this.handlers[message].indexOf(handler);
+    if (index != -1) {
+      this.handlers[message].splice(index, 1);
+    }
+  },
+  sendAsyncMessage: function(message, data) {
+    port.postMessage({'type': message,
+                      'data': data
+                     });
+  }
+}
+
+if (!SpecialPowers.isMainProcess()) {
+  var port;
+  try {
+    port = SpecialPowers.loadPrivilegedScript(loadPrivilegedScriptTest.toSource());
+  } catch (e) {
+    ok(false, "loadPrivilegedScript shoulde not throw" + e);
+  }
+
+  port.onmessage = (e) => {
+    var type = e.data['type'];
+    if (!contentScript.handlers.hasOwnProperty(type)) {
+    return;
+    }
+    var args = [e.data['data']];
+    contentScript.handlers[type].forEach(handler => handler.apply(null, args));
+  };
+}
--- a/dom/presentation/tests/mochitest/mochitest.ini
+++ b/dom/presentation/tests/mochitest/mochitest.ini
@@ -1,25 +1,27 @@
 [DEFAULT]
 support-files =
   PresentationDeviceInfoChromeScript.js
   PresentationSessionChromeScript.js
+  PresentationSessionFrameScript.js
   PresentationSessionChromeScript1UA.js
   file_presentation_1ua_receiver.html
   file_presentation_non_receiver_inner_iframe.html
   file_presentation_non_receiver.html
   file_presentation_receiver.html
   file_presentation_receiver_establish_connection_error.html
   file_presentation_receiver_inner_iframe.html
   file_presentation_1ua_wentaway.html
   test_presentation_1ua_connection_wentaway.js
 
 [test_presentation_dc_sender.html]
+[test_presentation_dc_receiver.html]
 skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android') # Bug 1129785
-[test_presentation_dc_receiver.html]
+[test_presentation_dc_receiver_oop.html]
 skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android') # Bug 1129785
 [test_presentation_1ua_sender_and_receiver.html]
 skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android') # Bug 1129785
 [test_presentation_1ua_sender_and_receiver_oop.html]
 skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android') # Bug 1129785
 [test_presentation_1ua_connection_wentaway_inproc.html]
 skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android') # Bug 1129785
 [test_presentation_1ua_connection_wentaway_oop.html]
new file mode 100644
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_dc_receiver_oop.html
@@ -0,0 +1,211 @@
+<!DOCTYPE HTML>
+<html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<head>
+  <meta charset="utf-8">
+  <title>Test for B2G PresentationConnection API at receiver side (OOP)</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="PresentationSessionFrameScript.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1148307">Test B2G PresentationConnection API at receiver side (OOP)</a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script type="application/javascript">
+
+'use strict';
+
+var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('PresentationSessionChromeScript.js'));
+var receiverUrl = SimpleTest.getTestFileURL('file_presentation_receiver.html');
+var nonReceiverUrl = SimpleTest.getTestFileURL('file_presentation_non_receiver.html');
+
+var isReceiverFinished = false;
+var isNonReceiverFinished = false;
+
+var obs = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
+          .getService(SpecialPowers.Ci.nsIObserverService);
+var receiverIframe;
+
+function setup() {
+  return new Promise(function(aResolve, aReject) {
+    gScript.sendAsyncMessage('trigger-device-add');
+
+    // Create a receiver OOP iframe.
+    receiverIframe = document.createElement('iframe');
+    receiverIframe.setAttribute('remote', 'true');
+    receiverIframe.setAttribute('mozbrowser', 'true');
+    receiverIframe.setAttribute('mozpresentation', receiverUrl);
+    receiverIframe.setAttribute('src', receiverUrl);
+
+    // This event is triggered when the iframe calls "alert".
+    receiverIframe.addEventListener('mozbrowsershowmodalprompt', function receiverListener(aEvent) {
+      var message = aEvent.detail.message;
+      if (/^OK /.exec(message)) {
+        ok(true, "Message from iframe: " + message);
+      } else if (/^KO /.exec(message)) {
+        ok(false, "Message from iframe: " + message);
+      } else if (/^INFO /.exec(message)) {
+        info("Message from iframe: " + message);
+      } else if (/^COMMAND /.exec(message)) {
+        var command = JSON.parse(message.replace(/^COMMAND /, ''));
+        if (command.name == "trigger-incoming-message") {
+          var mm = SpecialPowers.getBrowserFrameMessageManager(receiverIframe);
+          mm.sendAsyncMessage('trigger-incoming-message', {"data": command.data});
+        } else {
+          gScript.sendAsyncMessage(command.name, command.data);
+        }
+      } else if (/^DONE$/.exec(message)) {
+        ok(true, "Messaging from iframe complete.");
+        receiverIframe.removeEventListener('mozbrowsershowmodalprompt', receiverListener);
+
+        isReceiverFinished = true;
+
+        if (isNonReceiverFinished) {
+          teardown();
+        }
+      }
+    }, false);
+
+    var promise = new Promise(function(aResolve, aReject) {
+      document.body.appendChild(receiverIframe);
+      receiverIframe.addEventListener("mozbrowserloadstart", function onLoadEnd() {
+        receiverIframe.removeEventListener("mozbrowserloadstart", onLoadEnd);
+        var mm = SpecialPowers.getBrowserFrameMessageManager(receiverIframe);
+        mm.loadFrameScript("data:,(" + loadPrivilegedScriptTest.toString() + ")();", false);
+      });
+
+      aResolve(receiverIframe);
+    });
+    obs.notifyObservers(promise, 'setup-request-promise', null);
+
+    // Create a non-receiver OOP iframe.
+    var nonReceiverIframe = document.createElement('iframe');
+    nonReceiverIframe.setAttribute('remote', 'true');
+    nonReceiverIframe.setAttribute('mozbrowser', 'true');
+    nonReceiverIframe.setAttribute('src', nonReceiverUrl);
+
+    // This event is triggered when the iframe calls "alert".
+    nonReceiverIframe.addEventListener('mozbrowsershowmodalprompt', function nonReceiverListener(aEvent) {
+      var message = aEvent.detail.message;
+      if (/^OK /.exec(message)) {
+        ok(true, "Message from iframe: " + message);
+      } else if (/^KO /.exec(message)) {
+        ok(false, "Message from iframe: " + message);
+      } else if (/^INFO /.exec(message)) {
+        info("Message from iframe: " + message);
+      } else if (/^COMMAND /.exec(message)) {
+        var command = JSON.parse(message.replace(/^COMMAND /, ''));
+        gScript.sendAsyncMessage(command.name, command.data);
+      } else if (/^DONE$/.exec(message)) {
+        ok(true, "Messaging from iframe complete.");
+        nonReceiverIframe.removeEventListener('mozbrowsershowmodalprompt', nonReceiverListener);
+
+        isNonReceiverFinished = true;
+
+        if (isReceiverFinished) {
+          teardown();
+        }
+      }
+    }, false);
+
+    document.body.appendChild(nonReceiverIframe);
+
+    gScript.addMessageListener('offer-received', function offerReceivedHandler() {
+      gScript.removeMessageListener('offer-received', offerReceivedHandler);
+      info("An offer is received.");
+    });
+
+    gScript.addMessageListener('answer-sent', function answerSentHandler(aIsValid) {
+      gScript.removeMessageListener('answer-sent', answerSentHandler);
+      ok(aIsValid, "A valid answer is sent.");
+    });
+
+    gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
+      gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
+      is(aReason, SpecialPowers.Cr.NS_OK, "The control channel is closed normally.");
+    });
+
+    var mm = SpecialPowers.getBrowserFrameMessageManager(receiverIframe);
+    mm.addMessageListener('check-navigator', function checknavigatorHandler(aSuccess) {
+      mm.removeMessageListener('check-navigator', checknavigatorHandler);
+      ok(aSuccess.data.data, "buildDataChannel get correct window object");
+    });
+
+    mm.addMessageListener('data-transport-notification-enabled', function dataTransportNotificationEnabledHandler() {
+      mm.removeMessageListener('data-transport-notification-enabled', dataTransportNotificationEnabledHandler);
+      info("Data notification is enabled for data transport channel.");
+    });
+
+    mm.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
+      mm.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
+      is(aReason.data.data, SpecialPowers.Cr.NS_OK, "The data transport should be closed normally.");
+    });
+
+    aResolve();
+  });
+}
+
+function testIncomingSessionRequest() {
+  return new Promise(function(aResolve, aReject) {
+    gScript.addMessageListener('receiver-launching', function launchReceiverHandler(aSessionId) {
+      gScript.removeMessageListener('receiver-launching', launchReceiverHandler);
+      info("Trying to launch receiver page.");
+
+      aResolve();
+    });
+
+    gScript.sendAsyncMessage('trigger-incoming-session-request', receiverUrl);
+  });
+}
+
+var mmTeardownComplete = false;
+var gScriptTeardownComplete = false;
+function teardown() {
+  var mm = SpecialPowers.getBrowserFrameMessageManager(receiverIframe);
+  mm.addMessageListener('teardown-complete', function teardownCompleteHandler() {
+    mm.removeMessageListener('teardown-complete', teardownCompleteHandler);
+    mmTeardownComplete = true;
+    if (gScriptTeardownComplete) {
+      SimpleTest.finish();
+    }
+  });
+
+  mm.sendAsyncMessage('teardown');
+
+  gScript.addMessageListener('teardown-complete', function teardownCompleteHandler() {
+    gScript.removeMessageListener('teardown-complete', teardownCompleteHandler);
+    gScript.destroy();
+    gScriptTeardownComplete = true;
+    if (mmTeardownComplete) {
+      SimpleTest.finish();
+    }
+  });
+
+  gScript.sendAsyncMessage('teardown');
+}
+
+function runTests() {
+  setup().
+  then(testIncomingSessionRequest);
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPermissions([
+  {type: 'presentation-device-manage', allow: false, context: document},
+  {type: 'presentation', allow: true, context: document},
+  {type: 'browser', allow: true, context: document},
+], function() {
+  SpecialPowers.pushPrefEnv({ 'set': [["dom.presentation.enabled", true],
+                                      ["dom.presentation.session_transport.data_channel.enable", true],
+                                      ["dom.mozBrowserFramesEnabled", true],
+                                      ["dom.ipc.browser_frames.oop_by_default", true],
+                                      ["presentation.receiver.loading.timeout", 5000000]]},
+                            runTests);
+});
+
+</script>
+</body>
+</html>
--- a/dom/presentation/tests/mochitest/test_presentation_dc_sender.html
+++ b/dom/presentation/tests/mochitest/test_presentation_dc_sender.html
@@ -2,24 +2,26 @@
 <html>
 <!-- Any copyright is dedicated to the Public Domain.
    - http://creativecommons.org/publicdomain/zero/1.0/ -->
 <head>
   <meta charset="utf-8">
   <title>Test for B2G Presentation API at sender side</title>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="PresentationSessionFrameScript.js"></script>
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1148307">Test for B2G Presentation API at sender side</a>
 <script type="application/javascript;version=1.8">
 
 'use strict';
 
 var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('PresentationSessionChromeScript.js'));
+var frameScript = SpecialPowers.isMainProcess() ? gScript : contentScript;
 var request;
 var connection;
 
 function testSetup() {
   return new Promise(function(aResolve, aReject) {
     request = new PresentationRequest("http://example.com");
 
     request.getAvailability().then(
@@ -60,40 +62,39 @@ function testStartConnection() {
       info("The control channel is opened.");
     });
 
     gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
       gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
       info("The control channel is closed. " + aReason);
     });
 
-    gScript.addMessageListener('check-navigator', function checknavigatorHandler(aSuccess) {
-      gScript.removeMessageListener('check-navigator', checknavigatorHandler);
+    frameScript.addMessageListener('check-navigator', function checknavigatorHandler(aSuccess) {
+      frameScript.removeMessageListener('check-navigator', checknavigatorHandler);
       ok(aSuccess, "buildDataChannel get correct window object");
     });
 
     gScript.addMessageListener('offer-sent', function offerSentHandler(aIsValid) {
       gScript.removeMessageListener('offer-sent', offerSentHandler);
       ok(aIsValid, "A valid offer is sent out.");
-      gScript.sendAsyncMessage('trigger-incoming-transport');
+      gScript.sendAsyncMessage('trigger-incoming-answer');
     });
 
     gScript.addMessageListener('answer-received', function answerReceivedHandler() {
       gScript.removeMessageListener('answer-received', answerReceivedHandler);
       info("An answer is received.");
     });
 
-    gScript.addMessageListener('data-transport-initialized', function dataTransportInitializedHandler() {
-      gScript.removeMessageListener('data-transport-initialized', dataTransportInitializedHandler);
+    frameScript.addMessageListener('data-transport-initialized', function dataTransportInitializedHandler() {
+      frameScript.removeMessageListener('data-transport-initialized', dataTransportInitializedHandler);
       info("Data transport channel is initialized.");
-      gScript.sendAsyncMessage('trigger-incoming-answer');
     });
 
-    gScript.addMessageListener('data-transport-notification-enabled', function dataTransportNotificationEnabledHandler() {
-      gScript.removeMessageListener('data-transport-notification-enabled', dataTransportNotificationEnabledHandler);
+    frameScript.addMessageListener('data-transport-notification-enabled', function dataTransportNotificationEnabledHandler() {
+      frameScript.removeMessageListener('data-transport-notification-enabled', dataTransportNotificationEnabledHandler);
       info("Data notification is enabled for data transport channel.");
     });
 
     var connectionFromEvent;
     request.onconnectionavailable = function(aEvent) {
       request.onconnectionavailable = null;
       connectionFromEvent = aEvent.connection;
       ok(connectionFromEvent, "|connectionavailable| event is fired with a connection.");
@@ -127,18 +128,18 @@ function testStartConnection() {
     );
   });
 }
 
 function testSend() {
   return new Promise(function(aResolve, aReject) {
     const outgoingMessage = "test outgoing message";
 
-    gScript.addMessageListener('message-sent', function messageSentHandler(aMessage) {
-      gScript.removeMessageListener('message-sent', messageSentHandler);
+    frameScript.addMessageListener('message-sent', function messageSentHandler(aMessage) {
+      frameScript.removeMessageListener('message-sent', messageSentHandler);
       is(aMessage, outgoingMessage, "The message is sent out.");
       aResolve();
     });
 
     connection.send(outgoingMessage);
   });
 }
 
@@ -147,24 +148,24 @@ function testIncomingMessage() {
     const incomingMessage = "test incoming message";
 
     connection.addEventListener('message', function messageHandler(aEvent) {
       connection.removeEventListener('message', messageHandler);
       is(aEvent.data, incomingMessage, "An incoming message should be received.");
       aResolve();
     });
 
-    gScript.sendAsyncMessage('trigger-incoming-message', incomingMessage);
+    frameScript.sendAsyncMessage('trigger-incoming-message', incomingMessage);
   });
 }
 
 function testTerminateConnection() {
   return new Promise(function(aResolve, aReject) {
-    gScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
-      gScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
+    frameScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
+      frameScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
       info("The data transport is closed. " + aReason);
     });
 
     connection.onterminate = function() {
       connection.onterminate = null;
       is(connection.state, "terminated", "Connection should be terminated.");
       aResolve();
     };
@@ -190,17 +191,16 @@ function runTests() {
   testSetup().
   then(testStartConnection).
   then(testSend).
   then(testIncomingMessage).
   then(testTerminateConnection).
   then(teardown);
 }
 
-SimpleTest.expectAssertions(0, 5);
 SimpleTest.waitForExplicitFinish();
 SpecialPowers.pushPermissions([
   {type: 'presentation-device-manage', allow: false, context: document},
   {type: 'presentation', allow: true, context: document},
 ], function() {
   SpecialPowers.pushPrefEnv({ 'set': [["dom.presentation.enabled", true],
                                       ["dom.presentation.session_transport.data_channel.enable", true]]},
                             runTests);