Merge b2ginbound to central, a=merge
authorWes Kocher <wkocher@mozilla.com>
Thu, 06 Aug 2015 18:09:39 -0700
changeset 288294 84bfbe34da654487199a5010d6aa443f7e0523b5
parent 288274 c797b5ac24424c7cc5cdeb9a0274e9cf5f6e59b6 (current diff)
parent 288293 ec2076aa63191df4a6d2652f14413265538c86ae (diff)
child 288295 d6ea652c579992daa9041cc9718bb7c6abefbc91
child 288301 d3b12a797a8ed643ee1cf105c3e33c79d78c6913
child 288318 4354a7c2aeb576b153e4aa7636f464c39b426ba6
child 288389 efc97cd68267c35d255daa6c2aa2baeddc4daf94
push id5067
push userraliiev@mozilla.com
push dateMon, 21 Sep 2015 14:04:52 +0000
treeherdermozilla-beta@14221ffe5b2f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone42.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
Merge b2ginbound to central, a=merge
--- a/b2g/components/B2GComponents.manifest
+++ b/b2g/components/B2GComponents.manifest
@@ -113,8 +113,11 @@ contract @mozilla.org/services/mobileid-
 # B2GAppMigrator.js
 component {7211ece0-b458-4635-9afc-f8d7f376ee95} B2GAppMigrator.js
 contract @mozilla.org/app-migrator;1 {7211ece0-b458-4635-9afc-f8d7f376ee95}
 
 # B2GPresentationDevicePrompt.js
 component {4a300c26-e99b-4018-ab9b-c48cf9bc4de1} B2GPresentationDevicePrompt.js
 contract @mozilla.org/presentation-device/prompt;1 {4a300c26-e99b-4018-ab9b-c48cf9bc4de1}
 
+# PresentationRequestUIGlue.js
+component {ccc8a839-0b64-422b-8a60-fb2af0e376d0} PresentationRequestUIGlue.js
+contract @mozilla.org/presentation/requestuiglue;1 {ccc8a839-0b64-422b-8a60-fb2af0e376d0}
new file mode 100644
--- /dev/null
+++ b/b2g/components/PresentationRequestUIGlue.js
@@ -0,0 +1,62 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict"
+
+const { interfaces: Ci, utils: Cu, classes: Cc } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
+                                  "resource://gre/modules/SystemAppProxy.jsm");
+
+function PresentationRequestUIGlue() {
+  // This is to store the session ID / resolver binding.
+  // An example of the object literal is shown below:
+  //
+  // {
+  //   "sessionId1" : resolver1,
+  //   ...
+  // }
+  this._resolvers = {};
+
+  // Listen to the result for the opened iframe from front-end.
+  SystemAppProxy.addEventListener("mozPresentationContentEvent", aEvent => {
+    let detail = aEvent.detail;
+
+    if (detail.type != "presentation-receiver-launched") {
+      return;
+    }
+
+    let sessionId = detail.sessionId;
+    let resolver = this._resolvers[sessionId];
+    if (!resolver) {
+      return;
+    }
+
+    delete this._resolvers[sessionId];
+    resolver(detail.frame);
+  });
+}
+
+PresentationRequestUIGlue.prototype = {
+
+  sendRequest: function(aUrl, aSessionId) {
+    SystemAppProxy._sendCustomEvent("mozPresentationChromeEvent",
+                                    { type: "presentation-launch-receiver",
+                                      url: aUrl,
+                                      id: aSessionId });
+
+    return new Promise(function(aResolve, aReject) {
+      this._resolvers[aSessionId] = aResolve;
+    }.bind(this));
+  },
+
+  classID: Components.ID("{ccc8a839-0b64-422b-8a60-fb2af0e376d0}"),
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationRequestUIGlue])
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([PresentationRequestUIGlue]);
--- a/b2g/components/moz.build
+++ b/b2g/components/moz.build
@@ -18,16 +18,17 @@ EXTRA_COMPONENTS += [
     'FxAccountsUIGlue.js',
     'HelperAppDialog.js',
     'InterAppCommUIGlue.js',
     'MailtoProtocolHandler.js',
     'MobileIdentityUIGlue.js',
     'OMAContentHandler.js',
     'PaymentGlue.js',
     'PaymentProviderStrategy.js',
+    'PresentationRequestUIGlue.js',
     'ProcessGlobal.js',
     'SmsProtocolHandler.js',
     'SystemMessageGlue.js',
     'TelProtocolHandler.js',
     'WebappsUpdateTimer.js',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'gonk':
--- a/b2g/components/test/mochitest/mochitest.ini
+++ b/b2g/components/test/mochitest/mochitest.ini
@@ -2,18 +2,20 @@
 skip-if = toolkit != "gonk"
 support-files =
   permission_handler_chrome.js
   SandboxPromptTest.html
   filepicker_path_handler_chrome.js
   screenshot_helper.js
   systemapp_helper.js
   presentation_prompt_handler_chrome.js
+  presentation_ui_glue_handler_chrome.js
 
 [test_filepicker_path.html]
 [test_permission_deny.html]
 [test_permission_gum_remember.html]
 skip-if = true # Bug 1019572 - frequent timeouts
 [test_sandbox_permission.html]
 [test_screenshot.html]
 [test_systemapp.html]
 [test_presentation_device_prompt.html]
 [test_permission_visibilitychange.html]
+[test_presentation_request_ui_glue.html]
new file mode 100644
--- /dev/null
+++ b/b2g/components/test/mochitest/presentation_ui_glue_handler_chrome.js
@@ -0,0 +1,35 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+'use strict';
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+const { XPCOMUtils } = Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+const { SystemAppProxy } = Cu.import('resource://gre/modules/SystemAppProxy.jsm');
+
+const glue = Cc["@mozilla.org/presentation/requestuiglue;1"]
+             .createInstance(Ci.nsIPresentationRequestUIGlue);
+
+SystemAppProxy.addEventListener('mozPresentationChromeEvent', function(aEvent) {
+  if (!aEvent.detail || aEvent.detail.type !== 'presentation-launch-receiver') {
+    return;
+  }
+  sendAsyncMessage('presentation-launch-receiver', aEvent.detail);
+});
+
+addMessageListener('trigger-ui-glue', function(aData) {
+  var promise = glue.sendRequest(aData.url, aData.sessionId);
+  promise.then(function(aFrame){
+    sendAsyncMessage('iframe-resolved', aFrame);
+  });
+});
+
+addMessageListener('trigger-presentation-content-event', function(aData) {
+  var detail = {
+    type: 'presentation-receiver-launched',
+    sessionId: aData.sessionId,
+    frame: aData.frame
+  };
+  SystemAppProxy._sendCustomEvent('mozPresentationContentEvent', detail);
+});
new file mode 100644
--- /dev/null
+++ b/b2g/components/test/mochitest/test_presentation_request_ui_glue.html
@@ -0,0 +1,78 @@
+<!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 Presentation Device Selection</title>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Test for Presentation UI Glue</a>
+<script type="application/javascript;version=1.8">
+
+'use strict';
+
+SimpleTest.waitForExplicitFinish();
+
+var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('presentation_ui_glue_handler_chrome.js'));
+
+var obs = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
+          .getService(SpecialPowers.Ci.nsIObserverService);
+
+var url = 'http://example.com';
+var sessionId = 'sessionId';
+
+function testLaunchReceiver() {
+  return new Promise(function(aResolve, aReject) {
+    gScript.addMessageListener('presentation-launch-receiver', function launchReceiverHandler(aDetail) {
+      gScript.removeMessageListener('presentation-launch-receiver', launchReceiverHandler);
+      ok(true, "A presentation-launch-receiver mozPresentationChromeEvent should be received.");
+      is(aDetail.url, url, "Url should be the same.");
+      is(aDetail.id, sessionId, "Session ID should be the same.");
+
+      aResolve();
+    });
+
+    gScript.sendAsyncMessage('trigger-ui-glue',
+                             { url: url,
+                               sessionId : sessionId });
+  });
+}
+
+function testReceiverLaunched() {
+  return new Promise(function(aResolve, aReject) {
+    gScript.addMessageListener('iframe-resolved', function iframeResolvedHandler(aFrame) {
+      gScript.removeMessageListener('iframe-resolved', iframeResolvedHandler);
+      ok(true, "The promise should be resolved.");
+
+      aResolve();
+    });
+
+    var iframe = document.createElement('iframe');
+    iframe.setAttribute('remote', 'true');
+    iframe.setAttribute('mozbrowser', 'true');
+    iframe.setAttribute('src', 'http://example.com');
+    document.body.appendChild(iframe);
+
+    gScript.sendAsyncMessage('trigger-presentation-content-event',
+                             { sessionId : sessionId,
+                               frame: iframe });
+  });
+}
+
+function runTests() {
+  testLaunchReceiver()
+  .then(testReceiverLaunched)
+  .then(function() {
+    info('test finished, teardown');
+    gScript.destroy();
+    SimpleTest.finish();
+  });
+}
+
+window.addEventListener('load', runTests);
+</script>
+</body>
+</html>
--- a/b2g/config/aries/sources.xml
+++ b/b2g/config/aries/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="e862ab9177af664f00b4522e2350f4cb13866d73">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="7f387f859d48f9ad0761637c78447dc524747738"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="91068221506ff05692aa187ac314e15443db68fd"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4d9fbc08e87731447c19e96e13d8c7444baafcca"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="657894b4a1dc0a926117f4812e0940229f9f676f"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="735805df70bc64af1e5b709133afb76499a92ee1"/>
--- a/b2g/config/dolphin/sources.xml
+++ b/b2g/config/dolphin/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="e862ab9177af664f00b4522e2350f4cb13866d73">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="7f387f859d48f9ad0761637c78447dc524747738"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="91068221506ff05692aa187ac314e15443db68fd"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4d9fbc08e87731447c19e96e13d8c7444baafcca"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="657894b4a1dc0a926117f4812e0940229f9f676f"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="735805df70bc64af1e5b709133afb76499a92ee1"/>
@@ -133,12 +133,12 @@
   <project name="platform/frameworks/av" path="frameworks/av" revision="8bb69db127112fc66da75f8ca7a1158614b919f6"/>
   <project name="platform/hardware/akm" path="hardware/akm" revision="6d3be412647b0eab0adff8a2768736cf4eb68039"/>
   <project groups="invensense" name="platform/hardware/invensense" path="hardware/invensense" revision="e6d9ab28b4f4e7684f6c07874ee819c9ea0002a2"/>
   <project name="platform/hardware/ril" path="hardware/ril" revision="865ce3b4a2ba0b3a31421ca671f4d6c5595f8690"/>
   <project name="kernel/common" path="kernel" revision="0f36762ab0c1d8ce10c6a5eda948b05d5d6cc379"/>
   <project name="platform/system/core" path="system/core" revision="4b989b1bec28b0838420c4d5bb454c78afa62bea"/>
   <project name="u-boot" path="u-boot" revision="f1502910977ac88f43da7bf9277c3523ad4b0b2f"/>
   <project name="vendor/sprd/gps" path="vendor/sprd/gps" revision="7d6e1269be7186b2073fa568958b357826692c4b"/>
-  <project name="vendor/sprd/open-source" path="vendor/sprd/open-source" revision="17ea4b64fb0144e0cffeb52344d10215976945fe"/>
+  <project name="vendor/sprd/open-source" path="vendor/sprd/open-source" revision="295ff253b74353751a99aafd687196a28c84a58e"/>
   <project name="vendor/sprd/partner" path="vendor/sprd/partner" revision="8649c7145972251af11b0639997edfecabfc7c2e"/>
   <project name="vendor/sprd/proprietories" path="vendor/sprd/proprietories" revision="d2466593022f7078aaaf69026adf3367c2adb7bb"/>
 </manifest>
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1b0db93fb6b870b03467aff50d6419771ba0d88c">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="7f387f859d48f9ad0761637c78447dc524747738"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="91068221506ff05692aa187ac314e15443db68fd"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4d9fbc08e87731447c19e96e13d8c7444baafcca"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="2d58f4b9206b50b8fda0d5036da6f0c62608db7c"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="d70e4bfdcb65e7514de0f9315b74aea1c811678d"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="657894b4a1dc0a926117f4812e0940229f9f676f"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="660169a3d7e034a892359e39135e8c2785a6ad6f">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="7f387f859d48f9ad0761637c78447dc524747738"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="91068221506ff05692aa187ac314e15443db68fd"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4d9fbc08e87731447c19e96e13d8c7444baafcca"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="657894b4a1dc0a926117f4812e0940229f9f676f"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="735805df70bc64af1e5b709133afb76499a92ee1"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="e862ab9177af664f00b4522e2350f4cb13866d73">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="7f387f859d48f9ad0761637c78447dc524747738"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="91068221506ff05692aa187ac314e15443db68fd"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4d9fbc08e87731447c19e96e13d8c7444baafcca"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="657894b4a1dc0a926117f4812e0940229f9f676f"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="735805df70bc64af1e5b709133afb76499a92ee1"/>
--- a/b2g/config/emulator-l/sources.xml
+++ b/b2g/config/emulator-l/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="05a36844c1046a1eb07d5b1325f85ed741f961ea">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="7f387f859d48f9ad0761637c78447dc524747738"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="91068221506ff05692aa187ac314e15443db68fd"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4d9fbc08e87731447c19e96e13d8c7444baafcca"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="657894b4a1dc0a926117f4812e0940229f9f676f"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="735805df70bc64af1e5b709133afb76499a92ee1"/>
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1b0db93fb6b870b03467aff50d6419771ba0d88c">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="7f387f859d48f9ad0761637c78447dc524747738"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="91068221506ff05692aa187ac314e15443db68fd"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4d9fbc08e87731447c19e96e13d8c7444baafcca"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="2d58f4b9206b50b8fda0d5036da6f0c62608db7c"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="d70e4bfdcb65e7514de0f9315b74aea1c811678d"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="657894b4a1dc0a926117f4812e0940229f9f676f"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/flame-kk/sources.xml
+++ b/b2g/config/flame-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="e862ab9177af664f00b4522e2350f4cb13866d73">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="7f387f859d48f9ad0761637c78447dc524747738"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="91068221506ff05692aa187ac314e15443db68fd"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4d9fbc08e87731447c19e96e13d8c7444baafcca"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="657894b4a1dc0a926117f4812e0940229f9f676f"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="735805df70bc64af1e5b709133afb76499a92ee1"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
-        "git_revision": "7f387f859d48f9ad0761637c78447dc524747738", 
+        "git_revision": "91068221506ff05692aa187ac314e15443db68fd", 
         "remote": "https://git.mozilla.org/releases/gaia.git", 
         "branch": ""
     }, 
-    "revision": "16423131f4a9b03659d92e8ffad7a6f80a8eae37", 
+    "revision": "ab36b7edaadc88ece94cc213db139d1615dadf64", 
     "repo_path": "integration/gaia-central"
 }
--- a/b2g/config/nexus-4/sources.xml
+++ b/b2g/config/nexus-4/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="660169a3d7e034a892359e39135e8c2785a6ad6f">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="7f387f859d48f9ad0761637c78447dc524747738"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="91068221506ff05692aa187ac314e15443db68fd"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4d9fbc08e87731447c19e96e13d8c7444baafcca"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="657894b4a1dc0a926117f4812e0940229f9f676f"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="735805df70bc64af1e5b709133afb76499a92ee1"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
--- a/b2g/config/nexus-5-l/sources.xml
+++ b/b2g/config/nexus-5-l/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="05a36844c1046a1eb07d5b1325f85ed741f961ea">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="7f387f859d48f9ad0761637c78447dc524747738"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="91068221506ff05692aa187ac314e15443db68fd"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4d9fbc08e87731447c19e96e13d8c7444baafcca"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="657894b4a1dc0a926117f4812e0940229f9f676f"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="735805df70bc64af1e5b709133afb76499a92ee1"/>
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -955,16 +955,17 @@ bin/libfreebl_32int64_3.so
 @RESPATH@/components/B2GAboutRedirector.js
 @RESPATH@/components/FilePicker.js
 @RESPATH@/components/HelperAppDialog.js
 @RESPATH@/components/DownloadsUI.js
 @RESPATH@/components/InterAppCommUIGlue.js
 @RESPATH@/components/SystemMessageGlue.js
 @RESPATH@/components/B2GAppMigrator.js
 @RESPATH@/components/B2GPresentationDevicePrompt.js
+@RESPATH@/components/PresentationRequestUIGlue.js
 
 #ifndef MOZ_WIDGET_GONK
 @RESPATH@/components/SimulatorScreen.js
 #endif
 
 @RESPATH@/components/FxAccountsUIGlue.js
 @RESPATH@/components/services_fxaccounts.xpt
 
--- a/dom/apps/PermissionsTable.jsm
+++ b/dom/apps/PermissionsTable.jsm
@@ -561,17 +561,23 @@ this.PermissionsTable =  { geolocation: 
                              privileged: ALLOW_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "system-update": {
                              app: DENY_ACTION,
                              trusted: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
-                           }
+                           },
+                           "presentation": {
+                             app: DENY_ACTION,
+                             trusted: DENY_ACTION,
+                             privileged: ALLOW_ACTION,
+                             certified: ALLOW_ACTION
+                           },
                          };
 
 /**
  * Append access modes to the permission name as suffixes.
  *   e.g. permission name 'contacts' with ['read', 'write'] =
  *   ['contacts-read', contacts-write']
  * @param string aPermName
  * @param array aAccess
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -36,16 +36,17 @@
 #include "mozilla/dom/PowerManager.h"
 #include "mozilla/dom/WakeLock.h"
 #include "mozilla/dom/power/PowerManagerService.h"
 #include "mozilla/dom/CellBroadcast.h"
 #include "mozilla/dom/IccManager.h"
 #include "mozilla/dom/InputPortManager.h"
 #include "mozilla/dom/MobileMessageManager.h"
 #include "mozilla/dom/Permissions.h"
+#include "mozilla/dom/Presentation.h"
 #include "mozilla/dom/ServiceWorkerContainer.h"
 #include "mozilla/dom/Telephony.h"
 #include "mozilla/dom/Voicemail.h"
 #include "mozilla/dom/TVManager.h"
 #include "mozilla/dom/VRDevice.h"
 #include "mozilla/Hal.h"
 #include "nsISiteSpecificUserAgent.h"
 #include "mozilla/ClearOnShutdown.h"
@@ -210,16 +211,17 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mServiceWorkerContainer)
 
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCachedResolveResults)
 #ifdef MOZ_EME
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaKeySystemAccessManager)
 #endif
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDeviceStorageAreaListener)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPresentation)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(Navigator)
 
 void
 Navigator::Invalidate()
 {
@@ -329,16 +331,20 @@ Navigator::Invalidate()
     mDeviceStorageStores[i]->Shutdown();
   }
   mDeviceStorageStores.Clear();
 
   if (mTimeManager) {
     mTimeManager = nullptr;
   }
 
+  if (mPresentation) {
+    mPresentation = nullptr;
+  }
+
   mServiceWorkerContainer = nullptr;
 
 #ifdef MOZ_EME
   if (mMediaKeySystemAccessManager) {
     mMediaKeySystemAccessManager->Shutdown();
     mMediaKeySystemAccessManager = nullptr;
   }
 #endif
@@ -2758,10 +2764,24 @@ Navigator::RequestMediaKeySystemAccess(c
   }
 
   mMediaKeySystemAccessManager->Request(promise, aKeySystem, aOptions);
   return promise.forget();
 }
 
 #endif
 
+Presentation*
+Navigator::GetPresentation(ErrorResult& aRv)
+{
+  if (!mPresentation) {
+    if (!mWindow) {
+      aRv.Throw(NS_ERROR_UNEXPECTED);
+      return nullptr;
+    }
+    mPresentation = Presentation::Create(mWindow);
+  }
+
+  return mPresentation;
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/base/Navigator.h
+++ b/dom/base/Navigator.h
@@ -92,16 +92,17 @@ class MobileConnectionArray;
 class PowerManager;
 class CellBroadcast;
 class IccManager;
 class Telephony;
 class Voicemail;
 class TVManager;
 class InputPortManager;
 class DeviceStorageAreaListener;
+class Presentation;
 
 namespace time {
 class TimeManager;
 } // namespace time
 
 namespace system {
 #ifdef MOZ_AUDIO_CHANNEL_MANAGER
 class AudioChannelManager;
@@ -263,16 +264,18 @@ public:
 #endif // MOZ_B2G_BT
 #ifdef MOZ_TIME_MANAGER
   time::TimeManager* GetMozTime(ErrorResult& aRv);
 #endif // MOZ_TIME_MANAGER
 #ifdef MOZ_AUDIO_CHANNEL_MANAGER
   system::AudioChannelManager* GetMozAudioChannelManager(ErrorResult& aRv);
 #endif // MOZ_AUDIO_CHANNEL_MANAGER
 
+  Presentation* GetPresentation(ErrorResult& aRv);
+
   bool SendBeacon(const nsAString& aUrl,
                   const Nullable<ArrayBufferViewOrBlobOrStringOrFormData>& aData,
                   ErrorResult& aRv);
 
 #ifdef MOZ_MEDIA_NAVIGATOR
   void MozGetUserMedia(const MediaStreamConstraints& aConstraints,
                        NavigatorUserMediaSuccessCallback& aOnSuccess,
                        NavigatorUserMediaErrorCallback& aOnError,
@@ -384,16 +387,17 @@ private:
   nsRefPtr<nsDOMCameraManager> mCameraManager;
   nsRefPtr<MediaDevices> mMediaDevices;
   nsCOMPtr<nsIDOMNavigatorSystemMessages> mMessagesManager;
   nsTArray<nsRefPtr<nsDOMDeviceStorage> > mDeviceStorageStores;
   nsRefPtr<time::TimeManager> mTimeManager;
   nsRefPtr<ServiceWorkerContainer> mServiceWorkerContainer;
   nsCOMPtr<nsPIDOMWindow> mWindow;
   nsRefPtr<DeviceStorageAreaListener> mDeviceStorageAreaListener;
+  nsRefPtr<Presentation> mPresentation;
 
   // Hashtable for saving cached objects DoResolve created, so we don't create
   // the object twice if asked for it twice, whether due to use of "delete" or
   // due to Xrays.  We could probably use a nsJSThingHashtable here, but then
   // we'd need to figure out exactly how to trace that, and that seems to be
   // rocket science.  :(
   nsInterfaceHashtable<nsStringHashKey, nsISupports> mCachedResolveResults;
 };
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -678,16 +678,17 @@ GK_ATOM(onafterscriptexecute, "onaftersc
 GK_ATOM(onalerting, "onalerting")
 GK_ATOM(onanimationend, "onanimationend")
 GK_ATOM(onanimationiteration, "onanimationiteration")
 GK_ATOM(onanimationstart, "onanimationstart")
 GK_ATOM(onantennaavailablechange, "onantennaavailablechange")
 GK_ATOM(onAppCommand, "onAppCommand")
 GK_ATOM(onattributechanged, "onattributechanged")
 GK_ATOM(onaudioprocess, "onaudioprocess")
+GK_ATOM(onavailablechange, "onavailablechange")
 GK_ATOM(onbeforecopy, "onbeforecopy")
 GK_ATOM(onbeforecut, "onbeforecut")
 GK_ATOM(onbeforepaste, "onbeforepaste")
 GK_ATOM(onbeforeevicted, "onbeforeevicted")
 GK_ATOM(onbeforeprint, "onbeforeprint")
 GK_ATOM(onbeforescriptexecute, "onbeforescriptexecute")
 GK_ATOM(onbeforeunload, "onbeforeunload")
 GK_ATOM(onblocked, "onblocked")
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -166,16 +166,18 @@
 #include "mozilla/dom/icc/IccChild.h"
 #include "mozilla/dom/mobileconnection/MobileConnectionChild.h"
 #include "mozilla/dom/mobilemessage/SmsChild.h"
 #include "mozilla/dom/devicestorage/DeviceStorageRequestChild.h"
 #include "mozilla/dom/PFileSystemRequestChild.h"
 #include "mozilla/dom/FileSystemTaskBase.h"
 #include "mozilla/dom/bluetooth/PBluetoothChild.h"
 #include "mozilla/dom/PFMRadioChild.h"
+#include "mozilla/dom/PPresentationChild.h"
+#include "mozilla/dom/PresentationIPCService.h"
 #include "mozilla/ipc/InputStreamUtils.h"
 
 #ifdef MOZ_WEBSPEECH
 #include "mozilla/dom/PSpeechSynthesisChild.h"
 #endif
 
 #include "ProcessUtils.h"
 #include "StructuredCloneUtils.h"
@@ -1377,16 +1379,47 @@ ContentChild::DeallocPBlobChild(PBlobChi
 
 PBlobChild*
 ContentChild::SendPBlobConstructor(PBlobChild* aActor,
                                    const BlobConstructorParams& aParams)
 {
     return PContentChild::SendPBlobConstructor(aActor, aParams);
 }
 
+PPresentationChild*
+ContentChild::AllocPPresentationChild()
+{
+    NS_NOTREACHED("We should never be manually allocating PPresentationChild actors");
+    return nullptr;
+}
+
+bool
+ContentChild::DeallocPPresentationChild(PPresentationChild* aActor)
+{
+    delete aActor;
+    return true;
+}
+
+bool
+ContentChild::RecvNotifyPresentationReceiverLaunched(PBrowserChild* aIframe,
+                                                     const nsString& aSessionId)
+{
+    nsCOMPtr<nsIDocShell> docShell =
+        do_GetInterface(static_cast<TabChild*>(aIframe)->WebNavigation());
+    NS_WARN_IF(!docShell);
+
+    nsCOMPtr<nsIPresentationService> service =
+        do_GetService(PRESENTATION_SERVICE_CONTRACTID);
+    NS_WARN_IF(!service);
+
+    NS_WARN_IF(NS_FAILED(static_cast<PresentationIPCService*>(service.get())->MonitorResponderLoading(aSessionId, docShell)));
+
+    return true;
+}
+
 PCrashReporterChild*
 ContentChild::AllocPCrashReporterChild(const mozilla::dom::NativeThreadId& id,
                                        const uint32_t& processType)
 {
 #ifdef MOZ_CRASHREPORTER
     return new CrashReporterChild();
 #else
     return nullptr;
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -267,16 +267,21 @@ public:
     virtual bool DeallocPStorageChild(PStorageChild* aActor) override;
 
     virtual PBluetoothChild* AllocPBluetoothChild() override;
     virtual bool DeallocPBluetoothChild(PBluetoothChild* aActor) override;
 
     virtual PFMRadioChild* AllocPFMRadioChild() override;
     virtual bool DeallocPFMRadioChild(PFMRadioChild* aActor) override;
 
+    virtual PPresentationChild* AllocPPresentationChild() override;
+    virtual bool DeallocPPresentationChild(PPresentationChild* aActor) override;
+    virtual bool RecvNotifyPresentationReceiverLaunched(PBrowserChild* aIframe,
+                                                        const nsString& aSessionId) override;
+
     virtual PAsmJSCacheEntryChild* AllocPAsmJSCacheEntryChild(
                                  const asmjscache::OpenMode& aOpenMode,
                                  const asmjscache::WriteParams& aWriteParams,
                                  const IPC::Principal& aPrincipal) override;
     virtual bool DeallocPAsmJSCacheEntryChild(
                                     PAsmJSCacheEntryChild* aActor) override;
 
     virtual PSpeechSynthesisChild* AllocPSpeechSynthesisChild() override;
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -52,16 +52,18 @@
 #include "mozilla/dom/asmjscache/AsmJSCache.h"
 #include "mozilla/dom/bluetooth/PBluetoothParent.h"
 #include "mozilla/dom/cellbroadcast/CellBroadcastParent.h"
 #include "mozilla/dom/devicestorage/DeviceStorageRequestParent.h"
 #include "mozilla/dom/icc/IccParent.h"
 #include "mozilla/dom/mobileconnection/MobileConnectionParent.h"
 #include "mozilla/dom/mobilemessage/SmsParent.h"
 #include "mozilla/dom/power/PowerManagerService.h"
+#include "mozilla/dom/PresentationParent.h"
+#include "mozilla/dom/PPresentationParent.h"
 #include "mozilla/dom/quota/QuotaManager.h"
 #include "mozilla/dom/telephony/TelephonyParent.h"
 #include "mozilla/dom/time/DateCacheCleaner.h"
 #include "mozilla/dom/voicemail/VoicemailParent.h"
 #include "mozilla/embedding/printingui/PrintingParent.h"
 #include "mozilla/hal_sandbox/PHalParent.h"
 #include "mozilla/ipc/BackgroundChild.h"
 #include "mozilla/ipc/BackgroundParent.h"
@@ -3885,16 +3887,37 @@ ContentParent::DeallocPFMRadioParent(PFM
     delete aActor;
     return true;
 #else
     NS_WARNING("No support for FMRadio on this platform!");
     return false;
 #endif
 }
 
+PPresentationParent*
+ContentParent::AllocPPresentationParent()
+{
+  nsRefPtr<PresentationParent> actor = new PresentationParent();
+  return actor.forget().take();
+}
+
+bool
+ContentParent::DeallocPPresentationParent(PPresentationParent* aActor)
+{
+  nsRefPtr<PresentationParent> actor =
+    dont_AddRef(static_cast<PresentationParent*>(aActor));
+  return true;
+}
+
+bool
+ContentParent::RecvPPresentationConstructor(PPresentationParent* aActor)
+{
+  return static_cast<PresentationParent*>(aActor)->Init();
+}
+
 asmjscache::PAsmJSCacheEntryParent*
 ContentParent::AllocPAsmJSCacheEntryParent(
                                     const asmjscache::OpenMode& aOpenMode,
                                     const asmjscache::WriteParams& aWriteParams,
                                     const IPC::Principal& aPrincipal)
 {
     return asmjscache::AllocEntryParent(aOpenMode, aWriteParams, aPrincipal);
 }
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -657,16 +657,20 @@ private:
 
     virtual PBluetoothParent* AllocPBluetoothParent() override;
     virtual bool DeallocPBluetoothParent(PBluetoothParent* aActor) override;
     virtual bool RecvPBluetoothConstructor(PBluetoothParent* aActor) override;
 
     virtual PFMRadioParent* AllocPFMRadioParent() override;
     virtual bool DeallocPFMRadioParent(PFMRadioParent* aActor) override;
 
+    virtual PPresentationParent* AllocPPresentationParent() override;
+    virtual bool DeallocPPresentationParent(PPresentationParent* aActor) override;
+    virtual bool RecvPPresentationConstructor(PPresentationParent* aActor) override;
+
     virtual PAsmJSCacheEntryParent* AllocPAsmJSCacheEntryParent(
                                  const asmjscache::OpenMode& aOpenMode,
                                  const asmjscache::WriteParams& aWriteParams,
                                  const IPC::Principal& aPrincipal) override;
     virtual bool DeallocPAsmJSCacheEntryParent(
                                    PAsmJSCacheEntryParent* aActor) override;
 
     virtual PSpeechSynthesisParent* AllocPSpeechSynthesisParent() override;
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -46,16 +46,17 @@ include protocol PSms;
 include protocol PSpeechSynthesis;
 include protocol PStorage;
 include protocol PTelephony;
 include protocol PTestShell;
 include protocol PVoicemail;
 include protocol PJavaScript;
 include protocol PRemoteSpellcheckEngine;
 include protocol PWebrtcGlobal;
+include protocol PPresentation;
 include DOMTypes;
 include JavaScriptTypes;
 include InputStreamParams;
 include PTabContext;
 include URIParams;
 include PluginTypes;
 include ProtocolTypes;
 include PContentPermission;
@@ -447,16 +448,17 @@ prio(normal upto urgent) sync protocol P
     manages PSpeechSynthesis;
     manages PStorage;
     manages PTelephony;
     manages PTestShell;
     manages PVoicemail;
     manages PJavaScript;
     manages PRemoteSpellcheckEngine;
     manages PWebrtcGlobal;
+    manages PPresentation;
 
 both:
     // Depending on exactly how the new browser is being created, it might be
     // created from either the child or parent process!
     //
     // The child creates the PBrowser as part of
     // TabChild::BrowserFrameProvideWindow (which happens when the child's
     // content calls window.open()), and the parent creates the PBrowser as part
@@ -649,16 +651,23 @@ child:
      */
     GamepadUpdate(GamepadChangeEvent aGamepadEvent);
 
     /**
      * Tell the child that for testing purposes, a graphics device reset has
      * occurred.
      */
     async TestGraphicsDeviceReset(uint32_t aReason);
+
+    /**
+     * Notify the child that presentation receiver has been launched with the
+     * correspondent iframe.
+     */
+    async NotifyPresentationReceiverLaunched(PBrowser aIframe, nsString aSessionId);
+
 parent:
     /**
      * Tell the content process some attributes of itself.  This is
      * among the first information queried by content processes after
      * startup.  (The message is sync to allow the content process to
      * control when it receives the information.)
      *
      * |id| is a unique ID among all subprocesses.  When |isForApp &&
@@ -772,16 +781,18 @@ parent:
 
     PBluetooth();
 
     PFMRadio();
 
     PAsmJSCacheEntry(OpenMode openMode, WriteParams write, Principal principal);
 
     PWebrtcGlobal();
+    
+    PPresentation();
 
     // Services remoting
 
     async StartVisitedQuery(URIParams uri);
     async VisitURI(URIParams uri, OptionalURIParams referrer, uint32_t flags);
     async SetURITitle(URIParams uri, nsString title);
 
     async LoadURIExternal(URIParams uri);
new file mode 100644
--- /dev/null
+++ b/dom/presentation/Presentation.cpp
@@ -0,0 +1,220 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/dom/PresentationAvailableEvent.h"
+#include "mozilla/dom/PresentationBinding.h"
+#include "mozilla/dom/Promise.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIPresentationDeviceManager.h"
+#include "nsIPresentationService.h"
+#include "nsIUUIDGenerator.h"
+#include "nsServiceManagerUtils.h"
+#include "Presentation.h"
+#include "PresentationCallbacks.h"
+#include "PresentationSession.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(Presentation)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(Presentation, DOMEventTargetHelper)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSession)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(Presentation, DOMEventTargetHelper)
+  tmp->Shutdown();
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSession)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_ADDREF_INHERITED(Presentation, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(Presentation, DOMEventTargetHelper)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(Presentation)
+  NS_INTERFACE_MAP_ENTRY(nsIPresentationListener)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+/* static */ already_AddRefed<Presentation>
+Presentation::Create(nsPIDOMWindow* aWindow)
+{
+  nsRefPtr<Presentation> presentation = new Presentation(aWindow);
+  return NS_WARN_IF(!presentation->Init()) ? nullptr : presentation.forget();
+}
+
+Presentation::Presentation(nsPIDOMWindow* aWindow)
+  : DOMEventTargetHelper(aWindow)
+  , mAvailable(false)
+{
+}
+
+Presentation::~Presentation()
+{
+  Shutdown();
+}
+
+bool
+Presentation::Init()
+{
+  nsCOMPtr<nsIPresentationService> service =
+    do_GetService(PRESENTATION_SERVICE_CONTRACTID);
+  if (NS_WARN_IF(!service)) {
+    return false;
+  }
+
+  nsresult rv = service->RegisterListener(this);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return false;
+  }
+
+  nsCOMPtr<nsIPresentationDeviceManager> deviceManager =
+    do_GetService(PRESENTATION_DEVICE_MANAGER_CONTRACTID);
+  if (NS_WARN_IF(!deviceManager)) {
+    return false;
+  }
+  deviceManager->GetDeviceAvailable(&mAvailable);
+
+  // Check if a session instance is required now. The receiver requires a
+  // session instance is ready at beginning because the web content may access
+  // it right away; whereas the sender doesn't until |startSession| succeeds.
+  nsAutoString sessionId;
+  rv = service->GetExistentSessionIdAtLaunch(sessionId);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return false;
+  }
+  if (!sessionId.IsEmpty()) {
+    mSession = PresentationSession::Create(GetOwner(), sessionId,
+                                           PresentationSessionState::Disconnected);
+    if (NS_WARN_IF(!mSession)) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+void Presentation::Shutdown()
+{
+  mSession = nullptr;
+
+  nsCOMPtr<nsIPresentationService> service =
+    do_GetService(PRESENTATION_SERVICE_CONTRACTID);
+  if (NS_WARN_IF(!service)) {
+    return;
+  }
+
+  nsresult rv = service->UnregisterListener(this);
+  NS_WARN_IF(NS_FAILED(rv));
+}
+
+/* virtual */ JSObject*
+Presentation::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+  return PresentationBinding::Wrap(aCx, this, aGivenProto);
+}
+
+already_AddRefed<Promise>
+Presentation::StartSession(const nsAString& aUrl,
+                           const Optional<nsAString>& aId,
+                           ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
+  if (NS_WARN_IF(!global)) {
+    aRv.Throw(NS_ERROR_UNEXPECTED);
+    return nullptr;
+  }
+
+  // Get the origin.
+  nsAutoString origin;
+  nsresult rv = nsContentUtils::GetUTFOrigin(global->PrincipalOrNull(), origin);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aRv.Throw(rv);
+    return nullptr;
+  }
+
+  nsRefPtr<Promise> promise = Promise::Create(global, aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  // Ensure there's something to select.
+  if (NS_WARN_IF(!mAvailable)) {
+    promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return promise.forget();
+  }
+
+  // Ensure the URL is not empty.
+  if (NS_WARN_IF(aUrl.IsEmpty())) {
+    promise->MaybeReject(NS_ERROR_DOM_SYNTAX_ERR);
+    return promise.forget();
+  }
+
+  // Generate an ID if it's not assigned.
+  nsAutoString id;
+  if (aId.WasPassed()) {
+    id = aId.Value();
+  } else {
+    nsCOMPtr<nsIUUIDGenerator> uuidgen =
+      do_GetService("@mozilla.org/uuid-generator;1");
+    if(NS_WARN_IF(!uuidgen)) {
+      promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
+      return promise.forget();
+    }
+
+    nsID uuid;
+    uuidgen->GenerateUUIDInPlace(&uuid);
+    char buffer[NSID_LENGTH];
+    uuid.ToProvidedString(buffer);
+    CopyASCIItoUTF16(buffer, id);
+  }
+
+  nsCOMPtr<nsIPresentationService> service =
+    do_GetService(PRESENTATION_SERVICE_CONTRACTID);
+  if(NS_WARN_IF(!service)) {
+    promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
+    return promise.forget();
+  }
+
+  nsCOMPtr<nsIPresentationServiceCallback> callback =
+    new PresentationRequesterCallback(GetOwner(), aUrl, id, promise);
+  rv = service->StartSession(aUrl, id, origin, callback);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
+  }
+
+  return promise.forget();
+}
+
+already_AddRefed<PresentationSession>
+Presentation::GetSession() const
+{
+  nsRefPtr<PresentationSession> session = mSession;
+  return session.forget();
+}
+
+bool
+Presentation::CachedAvailable() const
+{
+  return mAvailable;
+}
+
+NS_IMETHODIMP
+Presentation::NotifyAvailableChange(bool aAvailable)
+{
+  mAvailable = aAvailable;
+
+  PresentationAvailableEventInit init;
+  init.mAvailable = mAvailable;
+  nsRefPtr<PresentationAvailableEvent> event =
+    PresentationAvailableEvent::Constructor(this,
+                                            NS_LITERAL_STRING("availablechange"),
+                                            init);
+  event->SetTrusted(true);
+
+  nsRefPtr<AsyncEventDispatcher> asyncDispatcher =
+    new AsyncEventDispatcher(this, event);
+  return asyncDispatcher->PostDOMEvent();
+}
new file mode 100644
--- /dev/null
+++ b/dom/presentation/Presentation.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_Presentation_h
+#define mozilla_dom_Presentation_h
+
+#include "mozilla/DOMEventTargetHelper.h"
+#include "nsIPresentationListener.h"
+
+namespace mozilla {
+namespace dom {
+
+class Promise;
+class PresentationSession;
+
+class Presentation final : public DOMEventTargetHelper
+                         , public nsIPresentationListener
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(Presentation,
+                                           DOMEventTargetHelper)
+  NS_DECL_NSIPRESENTATIONLISTENER
+
+  static already_AddRefed<Presentation> Create(nsPIDOMWindow* aWindow);
+  virtual JSObject*
+    WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+  // WebIDL (public APIs)
+  already_AddRefed<Promise> StartSession(const nsAString& aUrl,
+                                         const Optional<nsAString>& aId,
+                                         ErrorResult& aRv);
+  already_AddRefed<PresentationSession> GetSession() const;
+  bool CachedAvailable() const;
+  IMPL_EVENT_HANDLER(availablechange);
+
+private:
+  explicit Presentation(nsPIDOMWindow* aWindow);
+  ~Presentation();
+
+  bool Init();
+  void Shutdown();
+
+  bool mAvailable;
+  nsRefPtr<PresentationSession> mSession;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_Presentation_h
new file mode 100644
--- /dev/null
+++ b/dom/presentation/PresentationCallbacks.cpp
@@ -0,0 +1,185 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/Promise.h"
+#include "nsIDocShell.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIPresentationService.h"
+#include "nsIWebProgress.h"
+#include "nsServiceManagerUtils.h"
+#include "PresentationCallbacks.h"
+#include "PresentationSession.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+/*
+ * Implementation of PresentationRequesterCallback
+ */
+
+NS_IMPL_ISUPPORTS(PresentationRequesterCallback, nsIPresentationServiceCallback)
+
+PresentationRequesterCallback::PresentationRequesterCallback(nsPIDOMWindow* aWindow,
+                                                             const nsAString& aUrl,
+                                                             const nsAString& aSessionId,
+                                                             Promise* aPromise)
+  : mWindow(aWindow)
+  , mSessionId(aSessionId)
+  , mPromise(aPromise)
+{
+  MOZ_ASSERT(mWindow);
+  MOZ_ASSERT(mPromise);
+  MOZ_ASSERT(!mSessionId.IsEmpty());
+}
+
+PresentationRequesterCallback::~PresentationRequesterCallback()
+{
+}
+
+// nsIPresentationServiceCallback
+NS_IMETHODIMP
+PresentationRequesterCallback::NotifySuccess()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // At the sender side, this function must get called after the transport
+  // channel is ready. So we simply set the session state as connected.
+  nsRefPtr<PresentationSession> session =
+    PresentationSession::Create(mWindow, mSessionId, PresentationSessionState::Connected);
+  if (!session) {
+    mPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
+    return NS_OK;
+  }
+
+  mPromise->MaybeResolve(session);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationRequesterCallback::NotifyError(nsresult aError)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  mPromise->MaybeReject(aError);
+  return NS_OK;
+}
+
+/*
+ * Implementation of PresentationRequesterCallback
+ */
+
+NS_IMPL_ISUPPORTS(PresentationResponderLoadingCallback,
+                  nsIWebProgressListener,
+                  nsISupportsWeakReference)
+
+PresentationResponderLoadingCallback::PresentationResponderLoadingCallback(const nsAString& aSessionId)
+  : mSessionId(aSessionId)
+{
+}
+
+PresentationResponderLoadingCallback::~PresentationResponderLoadingCallback()
+{
+  if (mProgress) {
+    mProgress->RemoveProgressListener(this);
+    mProgress = nullptr;
+  }
+}
+
+nsresult
+PresentationResponderLoadingCallback::Init(nsIDocShell* aDocShell)
+{
+  mProgress = do_GetInterface(aDocShell);
+  if (NS_WARN_IF(!mProgress)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  uint32_t busyFlags = nsIDocShell::BUSY_FLAGS_NONE;
+  nsresult rv = aDocShell->GetBusyFlags(&busyFlags);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if ((busyFlags & nsIDocShell::BUSY_FLAGS_NONE) ||
+      (busyFlags & nsIDocShell::BUSY_FLAGS_PAGE_LOADING)) {
+    // The docshell has finished loading or is receiving data (|STATE_TRANSFERRING|
+    // has already been fired), so the page is ready for presentation use.
+    return NotifyReceiverReady();
+  }
+
+  // Start to listen to document state change event |STATE_TRANSFERRING|.
+  return mProgress->AddProgressListener(this, nsIWebProgress::NOTIFY_STATE_DOCUMENT);
+}
+
+nsresult
+PresentationResponderLoadingCallback::NotifyReceiverReady()
+{
+  nsCOMPtr<nsIPresentationService> service =
+    do_GetService(PRESENTATION_SERVICE_CONTRACTID);
+  if (NS_WARN_IF(!service)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  return service->NotifyReceiverReady(mSessionId);
+}
+
+// nsIWebProgressListener
+NS_IMETHODIMP
+PresentationResponderLoadingCallback::OnStateChange(nsIWebProgress* aWebProgress,
+                                                    nsIRequest* aRequest,
+                                                    uint32_t aStateFlags,
+                                                    nsresult aStatus)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (aStateFlags & nsIWebProgressListener::STATE_TRANSFERRING) {
+    mProgress->RemoveProgressListener(this);
+
+    return NotifyReceiverReady();
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationResponderLoadingCallback::OnProgressChange(nsIWebProgress* aWebProgress,
+                                                       nsIRequest* aRequest,
+                                                       int32_t aCurSelfProgress,
+                                                       int32_t aMaxSelfProgress,
+                                                       int32_t aCurTotalProgress,
+                                                       int32_t aMaxTotalProgress)
+{
+  // Do nothing.
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationResponderLoadingCallback::OnLocationChange(nsIWebProgress* aWebProgress,
+                                                       nsIRequest* aRequest,
+                                                       nsIURI* aURI,
+                                                       uint32_t aFlags)
+{
+  // Do nothing.
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationResponderLoadingCallback::OnStatusChange(nsIWebProgress* aWebProgress,
+                                                     nsIRequest* aRequest,
+                                                     nsresult aStatus,
+                                                     const char16_t* aMessage)
+{
+  // Do nothing.
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationResponderLoadingCallback::OnSecurityChange(nsIWebProgress* aWebProgress,
+                                                       nsIRequest* aRequest,
+                                                       uint32_t state)
+{
+  // Do nothing.
+  return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/dom/presentation/PresentationCallbacks.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PresentationCallbacks_h
+#define mozilla_dom_PresentationCallbacks_h
+
+#include "mozilla/nsRefPtr.h"
+#include "nsCOMPtr.h"
+#include "nsIPresentationService.h"
+#include "nsIWebProgressListener.h"
+#include "nsString.h"
+#include "nsWeakReference.h"
+
+class nsIDocShell;
+class nsIWebProgress;
+class nsPIDOMWindow;
+
+namespace mozilla {
+namespace dom {
+
+class Promise;
+
+class PresentationRequesterCallback final : public nsIPresentationServiceCallback
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIPRESENTATIONSERVICECALLBACK
+
+  PresentationRequesterCallback(nsPIDOMWindow* aWindow,
+                                const nsAString& aUrl,
+                                const nsAString& aSessionId,
+                                Promise* aPromise);
+
+private:
+  ~PresentationRequesterCallback();
+
+  nsCOMPtr<nsPIDOMWindow> mWindow;
+  nsString mSessionId;
+  nsRefPtr<Promise> mPromise;
+};
+
+class PresentationResponderLoadingCallback final : public nsIWebProgressListener
+                                                 , public nsSupportsWeakReference
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIWEBPROGRESSLISTENER
+
+  explicit PresentationResponderLoadingCallback(const nsAString& aSessionId);
+
+  nsresult Init(nsIDocShell* aDocShell);
+
+private:
+  ~PresentationResponderLoadingCallback();
+
+  nsresult NotifyReceiverReady();
+
+  nsString mSessionId;
+  nsCOMPtr<nsIWebProgress> mProgress;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PresentationCallbacks_h
new file mode 100644
--- /dev/null
+++ b/dom/presentation/PresentationService.cpp
@@ -0,0 +1,555 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ipc/PresentationIPCService.h"
+#include "mozilla/Services.h"
+#include "mozIApplication.h"
+#include "nsIAppsService.h"
+#include "nsIObserverService.h"
+#include "nsIPresentationControlChannel.h"
+#include "nsIPresentationDeviceManager.h"
+#include "nsIPresentationDevicePrompt.h"
+#include "nsIPresentationListener.h"
+#include "nsIPresentationRequestUIGlue.h"
+#include "nsIPresentationSessionRequest.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
+#include "PresentationService.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+namespace mozilla {
+namespace dom {
+
+/*
+ * Implementation of PresentationDeviceRequest
+ */
+
+class PresentationDeviceRequest final : public nsIPresentationDeviceRequest
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIPRESENTATIONDEVICEREQUEST
+
+  PresentationDeviceRequest(const nsAString& aRequestUrl,
+                            const nsAString& aId,
+                            const nsAString& aOrigin);
+
+private:
+  virtual ~PresentationDeviceRequest();
+
+  nsString mRequestUrl;
+  nsString mId;
+  nsString mOrigin;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+NS_IMPL_ISUPPORTS(PresentationDeviceRequest, nsIPresentationDeviceRequest)
+
+PresentationDeviceRequest::PresentationDeviceRequest(const nsAString& aRequestUrl,
+                                                     const nsAString& aId,
+                                                     const nsAString& aOrigin)
+  : mRequestUrl(aRequestUrl)
+  , mId(aId)
+  , mOrigin(aOrigin)
+{
+  MOZ_ASSERT(!mRequestUrl.IsEmpty());
+  MOZ_ASSERT(!mId.IsEmpty());
+  MOZ_ASSERT(!mOrigin.IsEmpty());
+}
+
+PresentationDeviceRequest::~PresentationDeviceRequest()
+{
+}
+
+NS_IMETHODIMP
+PresentationDeviceRequest::GetOrigin(nsAString& aOrigin)
+{
+  aOrigin = mOrigin;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationDeviceRequest::GetRequestURL(nsAString& aRequestUrl)
+{
+  aRequestUrl = mRequestUrl;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationDeviceRequest::Select(nsIPresentationDevice* aDevice)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aDevice);
+
+  nsCOMPtr<nsIPresentationService> service =
+    do_GetService(PRESENTATION_SERVICE_CONTRACTID);
+  if (NS_WARN_IF(!service)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  // Update device in the session info.
+  nsRefPtr<PresentationSessionInfo> info =
+    static_cast<PresentationService*>(service.get())->GetSessionInfo(mId);
+  if (NS_WARN_IF(!info)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+  info->SetDevice(aDevice);
+
+  // Establish a control channel. If we failed to do so, the callback is called
+  // with an error message.
+  nsCOMPtr<nsIPresentationControlChannel> ctrlChannel;
+  nsresult rv = aDevice->EstablishControlChannel(mRequestUrl, mId, getter_AddRefs(ctrlChannel));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return info->ReplyError(NS_ERROR_DOM_NETWORK_ERR);
+  }
+
+  // Initialize the session info with the control channel.
+  rv = info->Init(ctrlChannel);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return info->ReplyError(NS_ERROR_DOM_NETWORK_ERR);
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationDeviceRequest::Cancel()
+{
+  nsCOMPtr<nsIPresentationService> service =
+    do_GetService(PRESENTATION_SERVICE_CONTRACTID);
+  if (NS_WARN_IF(!service)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  nsRefPtr<PresentationSessionInfo> info =
+    static_cast<PresentationService*>(service.get())->GetSessionInfo(mId);
+  if (NS_WARN_IF(!info)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  return info->ReplyError(NS_ERROR_DOM_PROP_ACCESS_DENIED);
+}
+
+/*
+ * Implementation of PresentationService
+ */
+
+NS_IMPL_ISUPPORTS(PresentationService, nsIPresentationService, nsIObserver)
+
+PresentationService::PresentationService()
+  : mIsAvailable(false)
+{
+}
+
+PresentationService::~PresentationService()
+{
+  HandleShutdown();
+}
+
+bool
+PresentationService::Init()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+  if (NS_WARN_IF(!obs)) {
+    return false;
+  }
+
+  nsresult rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return false;
+  }
+  rv = obs->AddObserver(this, PRESENTATION_DEVICE_CHANGE_TOPIC, false);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return false;
+  }
+  rv = obs->AddObserver(this, PRESENTATION_SESSION_REQUEST_TOPIC, false);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return false;
+  }
+
+  nsCOMPtr<nsIPresentationDeviceManager> deviceManager =
+    do_GetService(PRESENTATION_DEVICE_MANAGER_CONTRACTID);
+  if (NS_WARN_IF(!deviceManager)) {
+    return false;
+  }
+
+  rv = deviceManager->GetDeviceAvailable(&mIsAvailable);
+  return !NS_WARN_IF(NS_FAILED(rv));
+}
+
+NS_IMETHODIMP
+PresentationService::Observe(nsISupports* aSubject,
+                             const char* aTopic,
+                             const char16_t* aData)
+{
+  if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+    HandleShutdown();
+    return NS_OK;
+  } else if (!strcmp(aTopic, PRESENTATION_DEVICE_CHANGE_TOPIC)) {
+    return HandleDeviceChange();
+  } else if (!strcmp(aTopic, PRESENTATION_SESSION_REQUEST_TOPIC)) {
+    nsCOMPtr<nsIPresentationSessionRequest> request(do_QueryInterface(aSubject));
+    if (NS_WARN_IF(!request)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    return HandleSessionRequest(request);
+  } else if (!strcmp(aTopic, "profile-after-change")) {
+    // It's expected since we add and entry to |kLayoutCategories| in
+    // |nsLayoutModule.cpp| to launch this service earlier.
+    return NS_OK;
+  }
+
+  MOZ_ASSERT(false, "Unexpected topic for PresentationService");
+  return NS_ERROR_UNEXPECTED;
+}
+
+void
+PresentationService::HandleShutdown()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  mListeners.Clear();
+  mSessionInfo.Clear();
+
+  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+  if (obs) {
+    obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+    obs->RemoveObserver(this, PRESENTATION_DEVICE_CHANGE_TOPIC);
+    obs->RemoveObserver(this, PRESENTATION_SESSION_REQUEST_TOPIC);
+  }
+}
+
+nsresult
+PresentationService::HandleDeviceChange()
+{
+  nsCOMPtr<nsIPresentationDeviceManager> deviceManager =
+    do_GetService(PRESENTATION_DEVICE_MANAGER_CONTRACTID);
+  if (NS_WARN_IF(!deviceManager)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  bool isAvailable;
+  nsresult rv = deviceManager->GetDeviceAvailable(&isAvailable);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (isAvailable != mIsAvailable) {
+    mIsAvailable = isAvailable;
+    NotifyAvailableChange(mIsAvailable);
+  }
+
+  return NS_OK;
+}
+
+nsresult
+PresentationService::HandleSessionRequest(nsIPresentationSessionRequest* aRequest)
+{
+  nsCOMPtr<nsIPresentationControlChannel> ctrlChannel;
+  nsresult rv = aRequest->GetControlChannel(getter_AddRefs(ctrlChannel));
+  if (NS_WARN_IF(NS_FAILED(rv) || !ctrlChannel)) {
+    return rv;
+  }
+
+  nsAutoString url;
+  rv = aRequest->GetUrl(url);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    ctrlChannel->Close(NS_ERROR_DOM_ABORT_ERR);
+    return rv;
+  }
+
+  nsAutoString sessionId;
+  rv = aRequest->GetPresentationId(sessionId);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    ctrlChannel->Close(NS_ERROR_DOM_ABORT_ERR);
+    return rv;
+  }
+
+  nsCOMPtr<nsIPresentationDevice> device;
+  rv = aRequest->GetDevice(getter_AddRefs(device));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    ctrlChannel->Close(NS_ERROR_DOM_ABORT_ERR);
+    return rv;
+  }
+
+#ifdef MOZ_WIDGET_GONK
+  // Verify the existence of the app if necessary.
+  nsCOMPtr<nsIURI> uri;
+  rv = NS_NewURI(getter_AddRefs(uri), url);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    ctrlChannel->Close(NS_ERROR_DOM_BAD_URI);
+    return rv;
+  }
+
+  bool isApp;
+  rv = uri->SchemeIs("app", &isApp);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    ctrlChannel->Close(NS_ERROR_DOM_ABORT_ERR);
+    return rv;
+  }
+
+  if (NS_WARN_IF(isApp && !IsAppInstalled(uri))) {
+    ctrlChannel->Close(NS_ERROR_DOM_NOT_FOUND_ERR);
+    return NS_OK;
+  }
+#endif
+
+  // Make sure the service is not handling another session request.
+  if (NS_WARN_IF(!mRespondingSessionId.IsEmpty())) {
+    ctrlChannel->Close(NS_ERROR_DOM_INUSE_ATTRIBUTE_ERR);
+    return rv;
+  }
+
+  // Set |mRespondingSessionId| to indicate the service is handling a session
+  // request. Then a session instance will be prepared while instantiating
+  // |navigator.presentation| at receiver side. This variable will be reset when
+  // registering the session listener.
+  mRespondingSessionId = sessionId;
+
+  // Create or reuse session info.
+  nsRefPtr<PresentationSessionInfo> info = GetSessionInfo(sessionId);
+  if (NS_WARN_IF(info)) {
+    // TODO Update here after session resumption becomes supported.
+    ctrlChannel->Close(NS_ERROR_DOM_ABORT_ERR);
+    mRespondingSessionId.Truncate();
+    return NS_ERROR_DOM_ABORT_ERR;
+  }
+
+  info = new PresentationResponderInfo(url, sessionId, device);
+  rv = info->Init(ctrlChannel);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    ctrlChannel->Close(NS_ERROR_DOM_ABORT_ERR);
+    mRespondingSessionId.Truncate();
+    return rv;
+  }
+
+  mSessionInfo.Put(sessionId, info);
+
+  // Notify the receiver to launch.
+  nsCOMPtr<nsIPresentationRequestUIGlue> glue =
+    do_CreateInstance(PRESENTATION_REQUEST_UI_GLUE_CONTRACTID);
+  if (NS_WARN_IF(!glue)) {
+    ctrlChannel->Close(NS_ERROR_DOM_ABORT_ERR);
+    return info->ReplyError(NS_ERROR_NOT_AVAILABLE);
+  }
+  nsCOMPtr<nsISupports> promise;
+  rv = glue->SendRequest(url, sessionId, getter_AddRefs(promise));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    ctrlChannel->Close(NS_ERROR_DOM_ABORT_ERR);
+    return info->ReplyError(rv);
+  }
+  nsCOMPtr<Promise> realPromise = do_QueryInterface(promise);
+  static_cast<PresentationResponderInfo*>(info.get())->SetPromise(realPromise);
+
+  return NS_OK;
+}
+
+void
+PresentationService::NotifyAvailableChange(bool aIsAvailable)
+{
+  nsTObserverArray<nsCOMPtr<nsIPresentationListener> >::ForwardIterator iter(mListeners);
+  while (iter.HasMore()) {
+    nsCOMPtr<nsIPresentationListener> listener = iter.GetNext();
+    NS_WARN_IF(NS_FAILED(listener->NotifyAvailableChange(aIsAvailable)));
+  }
+}
+
+bool
+PresentationService::IsAppInstalled(nsIURI* aUri)
+{
+  nsAutoCString prePath;
+  nsresult rv = aUri->GetPrePath(prePath);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return false;
+  }
+
+  nsAutoString manifestUrl;
+  AppendUTF8toUTF16(prePath, manifestUrl);
+  manifestUrl.AppendLiteral("/manifest.webapp");
+
+  nsCOMPtr<nsIAppsService> appsService = do_GetService(APPS_SERVICE_CONTRACTID);
+  if (NS_WARN_IF(!appsService)) {
+    return false;
+  }
+
+  nsCOMPtr<mozIApplication> app;
+  appsService->GetAppByManifestURL(manifestUrl, getter_AddRefs(app));
+  if (NS_WARN_IF(!app)) {
+    return false;
+  }
+
+  return true;
+}
+
+NS_IMETHODIMP
+PresentationService::StartSession(const nsAString& aUrl,
+                                  const nsAString& aSessionId,
+                                  const nsAString& aOrigin,
+                                  nsIPresentationServiceCallback* aCallback)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aCallback);
+  MOZ_ASSERT(!aSessionId.IsEmpty());
+
+  // Create session info  and set the callback. The callback is called when the
+  // request is finished.
+  nsRefPtr<PresentationRequesterInfo> info =
+    new PresentationRequesterInfo(aUrl, aSessionId, aCallback);
+  mSessionInfo.Put(aSessionId, info);
+
+  // Pop up a prompt and ask user to select a device.
+  nsCOMPtr<nsIPresentationDevicePrompt> prompt =
+    do_GetService(PRESENTATION_DEVICE_PROMPT_CONTRACTID);
+  if (NS_WARN_IF(!prompt)) {
+    return info->ReplyError(NS_ERROR_DOM_ABORT_ERR);
+  }
+  nsCOMPtr<nsIPresentationDeviceRequest> request =
+    new PresentationDeviceRequest(aUrl, aSessionId, aOrigin);
+  nsresult rv = prompt->PromptDeviceSelection(request);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return info->ReplyError(NS_ERROR_DOM_ABORT_ERR);
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationService::SendSessionMessage(const nsAString& aSessionId,
+                                        nsIInputStream* aStream)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aStream);
+  MOZ_ASSERT(!aSessionId.IsEmpty());
+
+  nsRefPtr<PresentationSessionInfo> info = GetSessionInfo(aSessionId);
+  if (NS_WARN_IF(!info)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  return info->Send(aStream);
+}
+
+NS_IMETHODIMP
+PresentationService::Terminate(const nsAString& aSessionId)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!aSessionId.IsEmpty());
+
+  nsRefPtr<PresentationSessionInfo> info = GetSessionInfo(aSessionId);
+  if (NS_WARN_IF(!info)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  return info->Close(NS_OK);
+}
+
+NS_IMETHODIMP
+PresentationService::RegisterListener(nsIPresentationListener* aListener)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (NS_WARN_IF(mListeners.Contains(aListener))) {
+    return NS_OK;
+  }
+
+  mListeners.AppendElement(aListener);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationService::UnregisterListener(nsIPresentationListener* aListener)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  mListeners.RemoveElement(aListener);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationService::RegisterSessionListener(const nsAString& aSessionId,
+                                             nsIPresentationSessionListener* aListener)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aListener);
+
+  if (mRespondingSessionId.Equals(aSessionId)) {
+    mRespondingSessionId.Truncate();
+  }
+
+  nsRefPtr<PresentationSessionInfo> info = GetSessionInfo(aSessionId);
+  if (NS_WARN_IF(!info)) {
+    // Notify the listener of TERMINATED since no correspondent session info is
+    // available possibly due to establishment failure. This would be useful at
+    // the receiver side, since a presentation session is created at beginning
+    // and here is the place to realize the underlying establishment fails.
+    nsresult rv = aListener->NotifyStateChange(aSessionId,
+                                               nsIPresentationSessionListener::STATE_TERMINATED);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  return info->SetListener(aListener);
+}
+
+NS_IMETHODIMP
+PresentationService::UnregisterSessionListener(const nsAString& aSessionId)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsRefPtr<PresentationSessionInfo> info = GetSessionInfo(aSessionId);
+  if (info) {
+    NS_WARN_IF(NS_FAILED(info->Close(NS_OK)));
+    RemoveSessionInfo(aSessionId);
+    return info->SetListener(nullptr);
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationService::GetExistentSessionIdAtLaunch(nsAString& aSessionId)
+{
+  aSessionId = mRespondingSessionId;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationService::NotifyReceiverReady(const nsAString& aSessionId)
+{
+  nsRefPtr<PresentationSessionInfo> info = GetSessionInfo(aSessionId);
+  if (NS_WARN_IF(!info)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  return static_cast<PresentationResponderInfo*>(info.get())->NotifyResponderReady();
+}
+
+already_AddRefed<nsIPresentationService>
+NS_CreatePresentationService()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsCOMPtr<nsIPresentationService> service;
+  if (XRE_GetProcessType() == GeckoProcessType_Content) {
+    service = new mozilla::dom::PresentationIPCService();
+  } else {
+    service = new PresentationService();
+    if (NS_WARN_IF(!static_cast<PresentationService*>(service.get())->Init())) {
+      return nullptr;
+    }
+  }
+
+  return service.forget();
+}
new file mode 100644
--- /dev/null
+++ b/dom/presentation/PresentationService.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PresentationService_h
+#define mozilla_dom_PresentationService_h
+
+#include "nsCOMPtr.h"
+#include "nsIObserver.h"
+#include "nsRefPtrHashtable.h"
+#include "nsTObserverArray.h"
+#include "PresentationSessionInfo.h"
+
+class nsIPresentationSessionRequest;
+class nsIURI;
+
+namespace mozilla {
+namespace dom {
+
+class PresentationService final : public nsIPresentationService
+                                , public nsIObserver
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIOBSERVER
+  NS_DECL_NSIPRESENTATIONSERVICE
+
+  PresentationService();
+  bool Init();
+
+  already_AddRefed<PresentationSessionInfo>
+  GetSessionInfo(const nsAString& aSessionId)
+  {
+    nsRefPtr<PresentationSessionInfo> info;
+    return mSessionInfo.Get(aSessionId, getter_AddRefs(info)) ?
+           info.forget() : nullptr;
+  }
+
+  void
+  RemoveSessionInfo(const nsAString& aSessionId)
+  {
+    if (mRespondingSessionId.Equals(aSessionId)) {
+      mRespondingSessionId.Truncate();
+    }
+
+    mSessionInfo.Remove(aSessionId);
+  }
+
+private:
+  ~PresentationService();
+  void HandleShutdown();
+  nsresult HandleDeviceChange();
+  nsresult HandleSessionRequest(nsIPresentationSessionRequest* aRequest);
+  void NotifyAvailableChange(bool aIsAvailable);
+  bool IsAppInstalled(nsIURI* aUri);
+
+  bool mIsAvailable;
+  nsString mRespondingSessionId;
+  nsRefPtrHashtable<nsStringHashKey, PresentationSessionInfo> mSessionInfo;
+  nsTObserverArray<nsCOMPtr<nsIPresentationListener>> mListeners;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PresentationService_h
new file mode 100644
--- /dev/null
+++ b/dom/presentation/PresentationSession.cpp
@@ -0,0 +1,284 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/AsyncEventDispatcher.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIDOMMessageEvent.h"
+#include "nsIPresentationService.h"
+#include "nsServiceManagerUtils.h"
+#include "nsStringStream.h"
+#include "PresentationSession.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(PresentationSession)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(PresentationSession, DOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(PresentationSession, DOMEventTargetHelper)
+  tmp->Shutdown();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_ADDREF_INHERITED(PresentationSession, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(PresentationSession, DOMEventTargetHelper)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(PresentationSession)
+  NS_INTERFACE_MAP_ENTRY(nsIPresentationSessionListener)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+PresentationSession::PresentationSession(nsPIDOMWindow* aWindow,
+                                         const nsAString& aId,
+                                         PresentationSessionState aState)
+  : DOMEventTargetHelper(aWindow)
+  , mId(aId)
+  , mState(aState)
+{
+}
+
+/* virtual */ PresentationSession::~PresentationSession()
+{
+}
+
+/* static */ already_AddRefed<PresentationSession>
+PresentationSession::Create(nsPIDOMWindow* aWindow,
+                            const nsAString& aId,
+                            PresentationSessionState aState)
+{
+  nsRefPtr<PresentationSession> session =
+    new PresentationSession(aWindow, aId, aState);
+  return NS_WARN_IF(!session->Init()) ? nullptr : session.forget();
+}
+
+bool
+PresentationSession::Init()
+{
+  if (NS_WARN_IF(mId.IsEmpty())) {
+    return false;
+  }
+
+  nsCOMPtr<nsIPresentationService> service =
+    do_GetService(PRESENTATION_SERVICE_CONTRACTID);
+  if(NS_WARN_IF(!service)) {
+    return false;
+  }
+
+  nsresult rv = service->RegisterSessionListener(mId, this);
+  if(NS_WARN_IF(NS_FAILED(rv))) {
+    return false;
+  }
+
+  return true;
+}
+
+void
+PresentationSession::Shutdown()
+{
+  nsCOMPtr<nsIPresentationService> service =
+    do_GetService(PRESENTATION_SERVICE_CONTRACTID);
+  if (NS_WARN_IF(!service)) {
+    return;
+  }
+
+  nsresult rv = service->UnregisterSessionListener(mId);
+  NS_WARN_IF(NS_FAILED(rv));
+}
+
+/* virtual */ JSObject*
+PresentationSession::WrapObject(JSContext* aCx,
+                                JS::Handle<JSObject*> aGivenProto)
+{
+  return PresentationSessionBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+PresentationSession::GetId(nsAString& aId) const
+{
+  aId = mId;
+}
+
+PresentationSessionState
+PresentationSession::State() const
+{
+  return mState;
+}
+
+void
+PresentationSession::Send(const nsAString& aData,
+                          ErrorResult& aRv)
+{
+  // Sending is not allowed if the session is not connected.
+  if (NS_WARN_IF(mState != PresentationSessionState::Connected)) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return;
+  }
+
+  nsresult rv;
+  nsCOMPtr<nsIStringInputStream> stream =
+    do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID, &rv);
+  if(NS_WARN_IF(NS_FAILED(rv))) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return;
+  }
+
+  NS_ConvertUTF16toUTF8 msgString(aData);
+  rv = stream->SetData(msgString.BeginReading(), msgString.Length());
+  if(NS_WARN_IF(NS_FAILED(rv))) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return;
+  }
+
+  nsCOMPtr<nsIPresentationService> service =
+    do_GetService(PRESENTATION_SERVICE_CONTRACTID);
+  if(NS_WARN_IF(!service)) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return;
+  }
+
+  rv = service->SendSessionMessage(mId, stream);
+  if(NS_WARN_IF(NS_FAILED(rv))) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+  }
+}
+
+void
+PresentationSession::Close()
+{
+  // Closing does nothing if the session is already terminated.
+  if (NS_WARN_IF(mState == PresentationSessionState::Terminated)) {
+    return;
+  }
+
+  nsCOMPtr<nsIPresentationService> service =
+    do_GetService(PRESENTATION_SERVICE_CONTRACTID);
+  if(NS_WARN_IF(!service)) {
+    return;
+  }
+
+  NS_WARN_IF(NS_FAILED(service->Terminate(mId)));
+}
+
+NS_IMETHODIMP
+PresentationSession::NotifyStateChange(const nsAString& aSessionId,
+                                       uint16_t aState)
+{
+  if (!aSessionId.Equals(mId)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  PresentationSessionState state;
+  switch (aState) {
+    case nsIPresentationSessionListener::STATE_CONNECTED:
+      state = PresentationSessionState::Connected;
+      break;
+    case nsIPresentationSessionListener::STATE_DISCONNECTED:
+      state = PresentationSessionState::Disconnected;
+      break;
+    case nsIPresentationSessionListener::STATE_TERMINATED:
+      state = PresentationSessionState::Terminated;
+      break;
+    default:
+      NS_WARNING("Unknown presentation session state.");
+      return NS_ERROR_INVALID_ARG;
+  }
+
+  if (mState == state) {
+    return NS_OK;
+  }
+
+  mState = state;
+
+  // Unregister session listener if the session is no longer connected.
+  if (mState == PresentationSessionState::Terminated) {
+    nsCOMPtr<nsIPresentationService> service =
+      do_GetService(PRESENTATION_SERVICE_CONTRACTID);
+    if (NS_WARN_IF(!service)) {
+      return NS_ERROR_NOT_AVAILABLE;
+    }
+
+    nsresult rv = service->UnregisterSessionListener(mId);
+    if(NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  }
+
+  return DispatchStateChangeEvent();
+}
+
+NS_IMETHODIMP
+PresentationSession::NotifyMessage(const nsAString& aSessionId,
+                                   const nsACString& aData)
+{
+  if (!aSessionId.Equals(mId)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  // No message should be expected when the session is not connected.
+  if (NS_WARN_IF(mState != PresentationSessionState::Connected)) {
+    return NS_ERROR_DOM_INVALID_STATE_ERR;
+  }
+
+  // Transform the data.
+  AutoJSAPI jsapi;
+  if (!jsapi.Init(GetOwner())) {
+    return NS_ERROR_FAILURE;
+  }
+  JSContext* cx = jsapi.cx();
+  JS::Rooted<JS::Value> jsData(cx);
+  NS_ConvertUTF8toUTF16 utf16Data(aData);
+  if(NS_WARN_IF(!ToJSValue(cx, utf16Data, &jsData))) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return DispatchMessageEvent(jsData);
+}
+
+nsresult
+PresentationSession::DispatchStateChangeEvent()
+{
+  nsRefPtr<AsyncEventDispatcher> asyncDispatcher =
+    new AsyncEventDispatcher(this, NS_LITERAL_STRING("statechange"), false);
+  return asyncDispatcher->PostDOMEvent();
+}
+
+nsresult
+PresentationSession::DispatchMessageEvent(JS::Handle<JS::Value> aData)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
+  if (NS_WARN_IF(!global)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  // Get the origin.
+  nsAutoString origin;
+  nsresult rv = nsContentUtils::GetUTFOrigin(global->PrincipalOrNull(), origin);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsCOMPtr<nsIDOMEvent> event;
+  rv = NS_NewDOMMessageEvent(getter_AddRefs(event), this, nullptr, nullptr);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsCOMPtr<nsIDOMMessageEvent> messageEvent = do_QueryInterface(event);
+  rv = messageEvent->InitMessageEvent(NS_LITERAL_STRING("message"),
+                                      false, false,
+                                      aData,
+                                      origin,
+                                      EmptyString(), nullptr);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  event->SetTrusted(true);
+
+  nsRefPtr<AsyncEventDispatcher> asyncDispatcher =
+    new AsyncEventDispatcher(this, event);
+  return asyncDispatcher->PostDOMEvent();
+}
new file mode 100644
--- /dev/null
+++ b/dom/presentation/PresentationSession.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PresentationSession_h
+#define mozilla_dom_PresentationSession_h
+
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/dom/PresentationSessionBinding.h"
+#include "nsIPresentationListener.h"
+
+namespace mozilla {
+namespace dom {
+
+class PresentationSession final : public DOMEventTargetHelper
+                                , public nsIPresentationSessionListener
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(PresentationSession,
+                                           DOMEventTargetHelper)
+  NS_DECL_NSIPRESENTATIONSESSIONLISTENER
+
+  static already_AddRefed<PresentationSession>
+    Create(nsPIDOMWindow* aWindow,
+           const nsAString& aId,
+           PresentationSessionState aState);
+  virtual JSObject*
+    WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+  // WebIDL (public APIs)
+  void GetId(nsAString& aId) const;
+  PresentationSessionState State() const;
+  void Send(const nsAString& aData, ErrorResult& aRv);
+  void Close();
+
+  IMPL_EVENT_HANDLER(statechange);
+  IMPL_EVENT_HANDLER(message);
+
+private:
+  explicit PresentationSession(nsPIDOMWindow* aWindow,
+                               const nsAString& aId,
+                               PresentationSessionState aState);
+  ~PresentationSession();
+
+  bool Init();
+  void Shutdown();
+  nsresult DispatchStateChangeEvent();
+  nsresult DispatchMessageEvent(JS::Handle<JS::Value> aData);
+
+  nsString mId;
+  PresentationSessionState mState;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PresentationSession_h
new file mode 100644
--- /dev/null
+++ b/dom/presentation/PresentationSessionInfo.cpp
@@ -0,0 +1,810 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/HTMLIFrameElementBinding.h"
+#include "mozilla/dom/TabParent.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "nsIDocShell.h"
+#include "nsIFrameLoader.h"
+#include "nsIMutableArray.h"
+#include "nsINetAddr.h"
+#include "nsISocketTransport.h"
+#include "nsISupportsPrimitives.h"
+#include "nsNetCID.h"
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+#include "PresentationService.h"
+#include "PresentationSessionInfo.h"
+
+#ifdef MOZ_WIDGET_GONK
+#include "nsINetworkInterface.h"
+#include "nsINetworkManager.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::services;
+
+/*
+ * Implementation of PresentationChannelDescription
+ */
+
+namespace mozilla {
+namespace dom {
+
+class PresentationChannelDescription final : public nsIPresentationChannelDescription
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIPRESENTATIONCHANNELDESCRIPTION
+
+  PresentationChannelDescription(nsACString& aAddress,
+                                 uint16_t aPort)
+    : mAddress(aAddress)
+    , mPort(aPort)
+  {
+  }
+
+private:
+  ~PresentationChannelDescription() {}
+
+  nsCString mAddress;
+  uint16_t mPort;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+NS_IMPL_ISUPPORTS(PresentationChannelDescription, nsIPresentationChannelDescription)
+
+NS_IMETHODIMP
+PresentationChannelDescription::GetType(uint8_t* aRetVal)
+{
+  if (NS_WARN_IF(!aRetVal)) {
+    return NS_ERROR_INVALID_POINTER;
+  }
+
+  // TODO bug 1148307 Implement PresentationSessionTransport with DataChannel.
+  // Only support TCP socket for now.
+  *aRetVal = nsIPresentationChannelDescription::TYPE_TCP;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationChannelDescription::GetTcpAddress(nsIArray** aRetVal)
+{
+  if (NS_WARN_IF(!aRetVal)) {
+    return NS_ERROR_INVALID_POINTER;
+  }
+
+  nsCOMPtr<nsIMutableArray> array = do_CreateInstance(NS_ARRAY_CONTRACTID);
+  if (NS_WARN_IF(!array)) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  // TODO bug 1148307 Implement PresentationSessionTransport with DataChannel.
+  // Ultimately we may use all the available addresses. DataChannel appears
+  // more robust upon handling ICE. And at the first stage Presentation API is
+  // only exposed on Firefox OS where the first IP appears enough for most
+  // scenarios.
+  nsCOMPtr<nsISupportsCString> address = do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID);
+  if (NS_WARN_IF(!address)) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+  address->SetData(mAddress);
+
+  array->AppendElement(address, false);
+  array.forget(aRetVal);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationChannelDescription::GetTcpPort(uint16_t* aRetVal)
+{
+  if (NS_WARN_IF(!aRetVal)) {
+    return NS_ERROR_INVALID_POINTER;
+  }
+
+  *aRetVal = mPort;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationChannelDescription::GetDataChannelSDP(nsAString& aDataChannelSDP)
+{
+  // TODO bug 1148307 Implement PresentationSessionTransport with DataChannel.
+  // Only support TCP socket for now.
+  aDataChannelSDP.Truncate();
+  return NS_OK;
+}
+
+/*
+ * Implementation of PresentationSessionInfo
+ */
+
+NS_IMPL_ISUPPORTS(PresentationSessionInfo,
+                  nsIPresentationSessionTransportCallback,
+                  nsIPresentationControlChannelListener);
+
+/* virtual */ nsresult
+PresentationSessionInfo::Init(nsIPresentationControlChannel* aControlChannel)
+{
+  SetControlChannel(aControlChannel);
+  return NS_OK;
+}
+
+/* virtual */ void
+PresentationSessionInfo::Shutdown(nsresult aReason)
+{
+  // Close the control channel if any.
+  if (mControlChannel) {
+    NS_WARN_IF(NS_FAILED(mControlChannel->Close(aReason)));
+  }
+
+  // Close the data transport channel if any.
+  if (mTransport) {
+    // |mIsTransportReady| will be unset once |NotifyTransportClosed| is called.
+    NS_WARN_IF(NS_FAILED(mTransport->Close(aReason)));
+  }
+
+  mIsResponderReady = false;
+}
+
+nsresult
+PresentationSessionInfo::SetListener(nsIPresentationSessionListener* aListener)
+{
+  mListener = aListener;
+
+  if (mListener) {
+    // The transport might become ready, or might become un-ready again, before
+    // the listener has registered. So notify the listener of the state change.
+    uint16_t state = IsSessionReady() ?
+                     nsIPresentationSessionListener::STATE_CONNECTED :
+                     nsIPresentationSessionListener::STATE_DISCONNECTED;
+    return mListener->NotifyStateChange(mSessionId, state);
+  }
+
+  return NS_OK;
+}
+
+nsresult
+PresentationSessionInfo::Send(nsIInputStream* aData)
+{
+  if (NS_WARN_IF(!IsSessionReady())) {
+    return NS_ERROR_DOM_INVALID_STATE_ERR;
+  }
+
+  if (NS_WARN_IF(!mTransport)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  return mTransport->Send(aData);
+}
+
+nsresult
+PresentationSessionInfo::Close(nsresult aReason)
+{
+  // The session is disconnected and it's a normal close. Simply change the
+  // state to TERMINATED.
+  if (!IsSessionReady() && NS_SUCCEEDED(aReason) && mListener) {
+    nsresult rv = mListener->NotifyStateChange(mSessionId,
+                                               nsIPresentationSessionListener::STATE_TERMINATED);
+    NS_WARN_IF(NS_FAILED(rv));
+  }
+
+  Shutdown(aReason);
+  return NS_OK;
+}
+
+nsresult
+PresentationSessionInfo::ReplySuccess()
+{
+  if (mListener) {
+    // Notify session state change.
+    nsresult rv = mListener->NotifyStateChange(mSessionId,
+                                               nsIPresentationSessionListener::STATE_CONNECTED);
+    NS_WARN_IF(NS_FAILED(rv));
+  }
+
+  if (mCallback) {
+    NS_WARN_IF(NS_FAILED(mCallback->NotifySuccess()));
+    SetCallback(nullptr);
+  }
+
+  return NS_OK;
+}
+
+nsresult
+PresentationSessionInfo::ReplyError(nsresult aError)
+{
+  Shutdown(aError);
+
+  if (mCallback) {
+    NS_WARN_IF(NS_FAILED(mCallback->NotifyError(aError)));
+    SetCallback(nullptr);
+  }
+
+  // Remove itself since it never succeeds.
+  nsCOMPtr<nsIPresentationService> service =
+    do_GetService(PRESENTATION_SERVICE_CONTRACTID);
+  if (NS_WARN_IF(!service)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+  static_cast<PresentationService*>(service.get())->RemoveSessionInfo(mSessionId);
+
+  return NS_OK;
+}
+
+// nsIPresentationSessionTransportCallback
+NS_IMETHODIMP
+PresentationSessionInfo::NotifyTransportReady()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  mIsTransportReady = true;
+
+  // At sender side, session might not be ready at this point (waiting for
+  // receiver's answer). Yet at receiver side, session must be ready at this
+  // point since the data transport channel is created after the receiver page
+  // is ready for presentation use.
+  if (IsSessionReady()) {
+    return ReplySuccess();
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationSessionInfo::NotifyTransportClosed(nsresult aReason)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // Nullify |mTransport| here so it won't try to re-close |mTransport| in
+  // potential subsequent |Shutdown| calls.
+  mTransport->SetCallback(nullptr);
+  mTransport = nullptr;
+
+  if (!IsSessionReady()) {
+    // It happens before the session is ready. Reply the callback.
+    return ReplyError(aReason);
+  }
+
+  // Unset |mIsTransportReady| here so it won't affect |IsSessionReady()| above.
+  mIsTransportReady = false;
+
+  Shutdown(aReason);
+
+  if (mListener) {
+    // It happens after the session is ready. Notify session state change.
+    uint16_t state = (NS_WARN_IF(NS_FAILED(aReason))) ?
+                     nsIPresentationSessionListener::STATE_DISCONNECTED :
+                     nsIPresentationSessionListener::STATE_TERMINATED;
+    return mListener->NotifyStateChange(mSessionId, state);
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationSessionInfo::NotifyData(const nsACString& aData)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (NS_WARN_IF(!IsSessionReady())) {
+    return NS_ERROR_DOM_INVALID_STATE_ERR;
+  }
+
+  if (NS_WARN_IF(!mListener)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  return mListener->NotifyMessage(mSessionId, aData);
+}
+
+/*
+ * Implementation of PresentationRequesterInfo
+ *
+ * During presentation session establishment, the sender expects the following
+ * after trying to establish the control channel: (The order between step 2 and
+ * 3 is not guaranteed.)
+ * 1. |Init| is called to open a socket |mServerSocket| for data transport
+ *    channel and send the offer to the receiver via the control channel.
+ * 2.1 |OnSocketAccepted| of |nsIServerSocketListener| is called to indicate the
+ *     data transport channel is connected. Then initialize |mTransport|.
+ * 2.2 |NotifyTransportReady| of |nsIPresentationSessionTransportCallback| is
+ *     called.
+ * 3. |OnAnswer| of |nsIPresentationControlChannelListener| is called to
+ *    indicate the receiver is ready. Close the control channel since it's no
+ *    longer needed.
+ * 4. Once both step 2 and 3 are done, the presentation session is ready to use.
+ *    So notify the listener of CONNECTED state.
+ */
+
+NS_IMPL_ISUPPORTS_INHERITED(PresentationRequesterInfo,
+                            PresentationSessionInfo,
+                            nsIServerSocketListener)
+
+nsresult
+PresentationRequesterInfo::Init(nsIPresentationControlChannel* aControlChannel)
+{
+  PresentationSessionInfo::Init(aControlChannel);
+
+  // Initialize |mServerSocket| for bootstrapping the data transport channel and
+  // use |this| as the listener.
+  mServerSocket = do_CreateInstance(NS_SERVERSOCKET_CONTRACTID);
+  if (NS_WARN_IF(!mServerSocket)) {
+    return ReplyError(NS_ERROR_NOT_AVAILABLE);
+  }
+
+  nsresult rv = mServerSocket->Init(-1, false, -1);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = mServerSocket->AsyncListen(this);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // Prepare and send the offer.
+  int32_t port;
+  rv = mServerSocket->GetPort(&port);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsCString address;
+  rv = GetAddress(address);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsRefPtr<PresentationChannelDescription> description =
+    new PresentationChannelDescription(address, static_cast<uint16_t>(port));
+  rv = mControlChannel->SendOffer(description);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+void
+PresentationRequesterInfo::Shutdown(nsresult aReason)
+{
+  PresentationSessionInfo::Shutdown(aReason);
+
+  // Close the server socket if any.
+  if (mServerSocket) {
+    NS_WARN_IF(NS_FAILED(mServerSocket->Close()));
+    mServerSocket = nullptr;
+  }
+}
+
+nsresult
+PresentationRequesterInfo::GetAddress(nsACString& aAddress)
+{
+#ifdef MOZ_WIDGET_GONK
+  nsCOMPtr<nsINetworkManager> networkManager =
+    do_GetService("@mozilla.org/network/manager;1");
+  if (NS_WARN_IF(!networkManager)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  nsCOMPtr<nsINetworkInfo> activeNetworkInfo;
+  networkManager->GetActiveNetworkInfo(getter_AddRefs(activeNetworkInfo));
+  if (NS_WARN_IF(!activeNetworkInfo)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  char16_t** ips = nullptr;
+  uint32_t* prefixes = nullptr;
+  uint32_t count = 0;
+  activeNetworkInfo->GetAddresses(&ips, &prefixes, &count);
+  if (NS_WARN_IF(!count)) {
+    NS_Free(prefixes);
+    NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, ips);
+    return NS_ERROR_FAILURE;
+  }
+
+  // TODO bug 1148307 Implement PresentationSessionTransport with DataChannel.
+  // Ultimately we may use all the available addresses. DataChannel appears
+  // more robust upon handling ICE. And at the first stage Presentation API is
+  // only exposed on Firefox OS where the first IP appears enough for most
+  // scenarios.
+  nsAutoString ip;
+  ip.Assign(ips[0]);
+  aAddress = NS_ConvertUTF16toUTF8(ip);
+
+  NS_Free(prefixes);
+  NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, ips);
+#else
+  // TODO Get host IP via other platforms.
+  aAddress.Truncate();
+#endif
+
+  return NS_OK;
+}
+
+// nsIPresentationControlChannelListener
+NS_IMETHODIMP
+PresentationRequesterInfo::OnOffer(nsIPresentationChannelDescription* aDescription)
+{
+  MOZ_ASSERT(false, "Sender side should not receive offer.");
+  return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+PresentationRequesterInfo::OnAnswer(nsIPresentationChannelDescription* aDescription)
+{
+  mIsResponderReady = true;
+
+  // Close the control channel since it's no longer needed.
+  nsresult rv = mControlChannel->Close(NS_OK);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return ReplyError(rv);
+  }
+
+  // Session might not be ready at this moment (waiting for the establishment of
+  // the data transport channel).
+  if (IsSessionReady()){
+    return ReplySuccess();
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationRequesterInfo::NotifyOpened()
+{
+  // Do nothing and wait for receiver to be ready.
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationRequesterInfo::NotifyClosed(nsresult aReason)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // Unset control channel here so it won't try to re-close it in potential
+  // subsequent |Shutdown| calls.
+  SetControlChannel(nullptr);
+
+  if (NS_WARN_IF(NS_FAILED(aReason))) {
+    if (mListener) {
+      // The presentation session instance at receiver side may already exist.
+      // Change the state to TERMINATED since it never succeeds.
+      return mListener->NotifyStateChange(mSessionId,
+                                          nsIPresentationSessionListener::STATE_TERMINATED);
+    }
+
+    // Reply error for an abnormal close.
+    return ReplyError(aReason);
+  }
+
+  return NS_OK;
+}
+
+// nsIServerSocketListener
+NS_IMETHODIMP
+PresentationRequesterInfo::OnSocketAccepted(nsIServerSocket* aServerSocket,
+                                            nsISocketTransport* aTransport)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // Initialize |mTransport| and use |this| as the callback.
+  mTransport = do_CreateInstance(PRESENTATION_SESSION_TRANSPORT_CONTRACTID);
+  if (NS_WARN_IF(!mTransport)) {
+    return ReplyError(NS_ERROR_NOT_AVAILABLE);
+  }
+
+  nsresult rv = mTransport->InitWithSocketTransport(aTransport, this);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationRequesterInfo::OnStopListening(nsIServerSocket* aServerSocket,
+                                           nsresult aStatus)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (aStatus == NS_BINDING_ABORTED) { // The server socket was manually closed.
+    return NS_OK;
+  }
+
+  Shutdown(aStatus);
+
+  if (!IsSessionReady()) {
+    // It happens before the session is ready. Reply the callback.
+    return ReplyError(aStatus);
+  }
+
+  // It happens after the session is ready. Notify session state change.
+  if (mListener) {
+    return mListener->NotifyStateChange(mSessionId,
+                                        nsIPresentationSessionListener::STATE_DISCONNECTED);
+  }
+
+  return NS_OK;
+}
+
+/*
+ * Implementation of PresentationResponderInfo
+ *
+ * During presentation session establishment, the receiver expects the following
+ * after trying to launch the app by notifying "presentation-launch-receiver":
+ * (The order between step 2 and 3 is not guaranteed.)
+ * 1. |Observe| of |nsIObserver| is called with "presentation-receiver-launched".
+ *    Then start listen to document |STATE_TRANSFERRING| event.
+ * 2. |NotifyResponderReady| is called to indicate the receiver page is ready
+ *    for presentation use.
+ * 3. |OnOffer| of |nsIPresentationControlChannelListener| is called.
+ * 4. Once both step 2 and 3 are done, establish the data transport channel and
+ *    send the answer. (The control channel will be closed by the sender once it
+ *    receives the answer.)
+ * 5. |NotifyTransportReady| of |nsIPresentationSessionTransportCallback| is
+ *    called. The presentation session is ready to use, so notify the listener
+ *    of CONNECTED state.
+ */
+
+NS_IMPL_ISUPPORTS_INHERITED(PresentationResponderInfo,
+                            PresentationSessionInfo,
+                            nsITimerCallback)
+
+nsresult
+PresentationResponderInfo::Init(nsIPresentationControlChannel* aControlChannel)
+{
+  PresentationSessionInfo::Init(aControlChannel);
+
+  // Add a timer to prevent waiting indefinitely in case the receiver page fails
+  // to become ready.
+  nsresult rv;
+  int32_t timeout =
+    Preferences::GetInt("presentation.receiver.loading.timeout", 10000);
+  mTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  rv = mTimer->InitWithCallback(this, timeout, nsITimer::TYPE_ONE_SHOT);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+void
+PresentationResponderInfo::Shutdown(nsresult aReason)
+{
+  PresentationSessionInfo::Shutdown(aReason);
+
+  if (mTimer) {
+    mTimer->Cancel();
+  }
+
+  mLoadingCallback = nullptr;
+  mRequesterDescription = nullptr;
+  mPromise = nullptr;
+}
+
+nsresult
+PresentationResponderInfo::InitTransportAndSendAnswer()
+{
+  // Establish a data transport channel |mTransport| to the sender and use
+  // |this| as the callback.
+  mTransport = do_CreateInstance(PRESENTATION_SESSION_TRANSPORT_CONTRACTID);
+  if (NS_WARN_IF(!mTransport)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  nsresult rv = mTransport->InitWithChannelDescription(mRequesterDescription, this);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // Prepare and send the answer.
+  // TODO bug 1148307 Implement PresentationSessionTransport with DataChannel.
+  // In the current implementation of |PresentationSessionTransport|,
+  // |GetSelfAddress| cannot return the real info when it's initialized via
+  // |InitWithChannelDescription|. Yet this deficiency only affects the channel
+  // description for the answer, which is not actually checked at requester side.
+  nsCOMPtr<nsINetAddr> selfAddr;
+  rv = mTransport->GetSelfAddress(getter_AddRefs(selfAddr));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsCString address;
+  selfAddr->GetAddress(address);
+  uint16_t port;
+  selfAddr->GetPort(&port);
+  nsCOMPtr<nsIPresentationChannelDescription> description =
+    new PresentationChannelDescription(address, port);
+
+  rv = mControlChannel->SendAnswer(description);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+ }
+
+nsresult
+PresentationResponderInfo::NotifyResponderReady()
+{
+  if (mTimer) {
+    mTimer->Cancel();
+    mTimer = nullptr;
+  }
+
+  mIsResponderReady = true;
+
+  // Initialize |mTransport| and send the answer to the sender if sender's
+  // description is already offered.
+  if (mRequesterDescription) {
+    nsresult rv = InitTransportAndSendAnswer();
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return ReplyError(rv);
+    }
+  }
+
+  return NS_OK;
+}
+
+// nsIPresentationControlChannelListener
+NS_IMETHODIMP
+PresentationResponderInfo::OnOffer(nsIPresentationChannelDescription* aDescription)
+{
+  if (NS_WARN_IF(!aDescription)) {
+    return ReplyError(NS_ERROR_INVALID_ARG);
+  }
+
+  mRequesterDescription = aDescription;
+
+  // Initialize |mTransport| and send the answer to the sender if the receiver
+  // page is ready for presentation use.
+  if (mIsResponderReady) {
+    nsresult rv = InitTransportAndSendAnswer();
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return ReplyError(rv);
+    }
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationResponderInfo::OnAnswer(nsIPresentationChannelDescription* aDescription)
+{
+  MOZ_ASSERT(false, "Receiver side should not receive answer.");
+  return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+PresentationResponderInfo::NotifyOpened()
+{
+  // Do nothing.
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationResponderInfo::NotifyClosed(nsresult aReason)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // Unset control channel here so it won't try to re-close it in potential
+  // subsequent |Shutdown| calls.
+  SetControlChannel(nullptr);
+
+  if (NS_WARN_IF(NS_FAILED(aReason))) {
+    if (mListener) {
+      // The presentation session instance at receiver side may already exist.
+      // Change the state to TERMINATED since it never succeeds.
+      return mListener->NotifyStateChange(mSessionId,
+                                          nsIPresentationSessionListener::STATE_TERMINATED);
+    }
+
+    // Reply error for an abnormal close.
+    return ReplyError(aReason);
+  }
+
+  return NS_OK;
+}
+
+// nsITimerCallback
+NS_IMETHODIMP
+PresentationResponderInfo::Notify(nsITimer* aTimer)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  NS_WARNING("The receiver page fails to become ready before timeout.");
+
+  mTimer = nullptr;
+  return ReplyError(NS_ERROR_DOM_TIMEOUT_ERR);
+}
+
+// PromiseNativeHandler
+void
+PresentationResponderInfo::ResolvedCallback(JSContext* aCx,
+                                            JS::Handle<JS::Value> aValue)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (NS_WARN_IF(!aValue.isObject())) {
+    ReplyError(NS_ERROR_NOT_AVAILABLE);
+    return;
+  }
+
+  JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
+  if (NS_WARN_IF(!obj)) {
+    ReplyError(NS_ERROR_NOT_AVAILABLE);
+    return;
+  }
+
+  // Start to listen to document state change event |STATE_TRANSFERRING|.
+  HTMLIFrameElement* frame = nullptr;
+  nsresult rv = UNWRAP_OBJECT(HTMLIFrameElement, obj, frame);
+  if (NS_WARN_IF(!frame)) {
+    ReplyError(NS_ERROR_NOT_AVAILABLE);
+    return;
+  }
+
+  nsCOMPtr<nsIFrameLoaderOwner> owner = do_QueryInterface((nsIFrameLoaderOwner*) frame);
+  if (NS_WARN_IF(!owner)) {
+    ReplyError(NS_ERROR_NOT_AVAILABLE);
+    return;
+  }
+
+  nsCOMPtr<nsIFrameLoader> frameLoader;
+  rv = owner->GetFrameLoader(getter_AddRefs(frameLoader));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    ReplyError(rv);
+    return;
+  }
+
+  nsRefPtr<TabParent> tabParent = TabParent::GetFrom(frameLoader);
+  if (tabParent) {
+    // OOP frame
+    nsCOMPtr<nsIContentParent> cp = tabParent->Manager();
+    NS_WARN_IF(!static_cast<ContentParent*>(cp.get())->SendNotifyPresentationReceiverLaunched(tabParent, mSessionId));
+  } else {
+    // In-process frame
+    nsCOMPtr<nsIDocShell> docShell;
+    rv = frameLoader->GetDocShell(getter_AddRefs(docShell));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      ReplyError(rv);
+      return;
+    }
+
+    mLoadingCallback = new PresentationResponderLoadingCallback(mSessionId);
+    rv = mLoadingCallback->Init(docShell);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      ReplyError(rv);
+      return;
+    }
+  }
+}
+
+void
+PresentationResponderInfo::RejectedCallback(JSContext* aCx,
+                                            JS::Handle<JS::Value> aValue)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  NS_WARNING("Launching the receiver page has been rejected.");
+
+  if (mTimer) {
+    mTimer->Cancel();
+    mTimer = nullptr;
+  }
+
+  ReplyError(NS_ERROR_DOM_ABORT_ERR);
+}
new file mode 100644
--- /dev/null
+++ b/dom/presentation/PresentationSessionInfo.h
@@ -0,0 +1,206 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PresentationSessionInfo_h
+#define mozilla_dom_PresentationSessionInfo_h
+
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PromiseNativeHandler.h"
+#include "mozilla/nsRefPtr.h"
+#include "nsCOMPtr.h"
+#include "nsIPresentationControlChannel.h"
+#include "nsIPresentationDevice.h"
+#include "nsIPresentationListener.h"
+#include "nsIPresentationService.h"
+#include "nsIPresentationSessionTransport.h"
+#include "nsIServerSocket.h"
+#include "nsITimer.h"
+#include "nsString.h"
+#include "PresentationCallbacks.h"
+
+namespace mozilla {
+namespace dom {
+
+class PresentationSessionInfo : public nsIPresentationSessionTransportCallback
+                              , public nsIPresentationControlChannelListener
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIPRESENTATIONSESSIONTRANSPORTCALLBACK
+
+  PresentationSessionInfo(const nsAString& aUrl,
+                          const nsAString& aSessionId,
+                          nsIPresentationServiceCallback* aCallback)
+    : mUrl(aUrl)
+    , mSessionId(aSessionId)
+    , mIsResponderReady(false)
+    , mIsTransportReady(false)
+    , mCallback(aCallback)
+  {
+    MOZ_ASSERT(!mUrl.IsEmpty());
+    MOZ_ASSERT(!mSessionId.IsEmpty());
+  }
+
+  virtual nsresult Init(nsIPresentationControlChannel* aControlChannel);
+
+  const nsAString& GetUrl() const
+  {
+    return mUrl;
+  }
+
+  const nsAString& GetSessionId() const
+  {
+    return mSessionId;
+  }
+
+  void SetCallback(nsIPresentationServiceCallback* aCallback)
+  {
+    mCallback = aCallback;
+  }
+
+  nsresult SetListener(nsIPresentationSessionListener* aListener);
+
+  void SetDevice(nsIPresentationDevice* aDevice)
+  {
+    mDevice = aDevice;
+  }
+
+  already_AddRefed<nsIPresentationDevice> GetDevice() const
+  {
+    nsCOMPtr<nsIPresentationDevice> device = mDevice;
+    return device.forget();
+  }
+
+  void SetControlChannel(nsIPresentationControlChannel* aControlChannel)
+  {
+    if (mControlChannel) {
+      mControlChannel->SetListener(nullptr);
+    }
+
+    mControlChannel = aControlChannel;
+    if (mControlChannel) {
+      mControlChannel->SetListener(this);
+    }
+  }
+
+  nsresult Send(nsIInputStream* aData);
+
+  nsresult Close(nsresult aReason);
+
+  nsresult ReplyError(nsresult aReason);
+
+protected:
+  virtual ~PresentationSessionInfo()
+  {
+    Shutdown(NS_OK);
+  }
+
+  virtual void Shutdown(nsresult aReason);
+
+  nsresult ReplySuccess();
+
+  bool IsSessionReady()
+  {
+    return mIsResponderReady && mIsTransportReady;
+  }
+
+  nsString mUrl;
+  nsString mSessionId;
+  bool mIsResponderReady;
+  bool mIsTransportReady;
+  nsCOMPtr<nsIPresentationServiceCallback> mCallback;
+  nsCOMPtr<nsIPresentationSessionListener> mListener;
+  nsCOMPtr<nsIPresentationDevice> mDevice;
+  nsCOMPtr<nsIPresentationSessionTransport> mTransport;
+  nsCOMPtr<nsIPresentationControlChannel> mControlChannel;
+};
+
+// Session info with sender side behaviors.
+class PresentationRequesterInfo final : public PresentationSessionInfo
+                                      , public nsIServerSocketListener
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_NSIPRESENTATIONCONTROLCHANNELLISTENER
+  NS_DECL_NSISERVERSOCKETLISTENER
+
+  PresentationRequesterInfo(const nsAString& aUrl,
+                            const nsAString& aSessionId,
+                            nsIPresentationServiceCallback* aCallback)
+    : PresentationSessionInfo(aUrl, aSessionId, aCallback)
+  {
+    MOZ_ASSERT(mCallback);
+  }
+
+  nsresult Init(nsIPresentationControlChannel* aControlChannel) override;
+
+private:
+  ~PresentationRequesterInfo()
+  {
+    Shutdown(NS_OK);
+  }
+
+  void Shutdown(nsresult aReason) override;
+
+  nsresult GetAddress(nsACString& aAddress);
+
+  nsCOMPtr<nsIServerSocket> mServerSocket;
+};
+
+// Session info with receiver side behaviors.
+class PresentationResponderInfo final : public PresentationSessionInfo
+                                      , public PromiseNativeHandler
+                                      , public nsITimerCallback
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_NSIPRESENTATIONCONTROLCHANNELLISTENER
+  NS_DECL_NSITIMERCALLBACK
+
+  PresentationResponderInfo(const nsAString& aUrl,
+                            const nsAString& aSessionId,
+                            nsIPresentationDevice* aDevice)
+    : PresentationSessionInfo(aUrl, aSessionId, nullptr)
+  {
+    MOZ_ASSERT(aDevice);
+
+    SetDevice(aDevice);
+  }
+
+  nsresult Init(nsIPresentationControlChannel* aControlChannel) override;
+
+  nsresult NotifyResponderReady();
+
+  void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
+
+  void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
+
+  void SetPromise(Promise* aPromise)
+  {
+    mPromise = aPromise;
+    mPromise->AppendNativeHandler(this);
+  }
+
+private:
+  ~PresentationResponderInfo()
+  {
+    Shutdown(NS_OK);
+  }
+
+  void Shutdown(nsresult aReason) override;
+
+  nsresult InitTransportAndSendAnswer();
+
+  nsRefPtr<PresentationResponderLoadingCallback> mLoadingCallback;
+  nsCOMPtr<nsITimer> mTimer;
+  nsCOMPtr<nsIPresentationChannelDescription> mRequesterDescription;
+  nsRefPtr<Promise> mPromise;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PresentationSessionInfo_h
new file mode 100644
--- /dev/null
+++ b/dom/presentation/PresentationSessionTransport.cpp
@@ -0,0 +1,484 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsArrayUtils.h"
+#include "nsIAsyncStreamCopier.h"
+#include "nsIInputStreamPump.h"
+#include "nsIMultiplexInputStream.h"
+#include "nsIMutableArray.h"
+#include "nsIOutputStream.h"
+#include "nsIPresentationControlChannel.h"
+#include "nsIScriptableInputStream.h"
+#include "nsISocketTransport.h"
+#include "nsISocketTransportService.h"
+#include "nsISupportsPrimitives.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+#include "nsStreamUtils.h"
+#include "nsThreadUtils.h"
+#include "PresentationSessionTransport.h"
+
+#define BUFFER_SIZE 65536
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+class CopierCallbacks final : public nsIRequestObserver
+{
+public:
+  explicit CopierCallbacks(PresentationSessionTransport* aTransport)
+    : mOwner(aTransport)
+  {}
+
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIREQUESTOBSERVER
+private:
+  ~CopierCallbacks() {}
+
+  nsRefPtr<PresentationSessionTransport> mOwner;
+};
+
+NS_IMPL_ISUPPORTS(CopierCallbacks, nsIRequestObserver)
+
+NS_IMETHODIMP
+CopierCallbacks::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+CopierCallbacks::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext, nsresult aStatus)
+{
+  mOwner->NotifyCopyComplete(aStatus);
+  return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(PresentationSessionTransport,
+                  nsIPresentationSessionTransport,
+                  nsITransportEventSink,
+                  nsIInputStreamCallback,
+                  nsIStreamListener,
+                  nsIRequestObserver)
+
+PresentationSessionTransport::PresentationSessionTransport()
+  : mReadyState(CLOSED)
+  , mAsyncCopierActive(false)
+  , mCloseStatus(NS_OK)
+{
+}
+
+PresentationSessionTransport::~PresentationSessionTransport()
+{
+}
+
+NS_IMETHODIMP
+PresentationSessionTransport::InitWithSocketTransport(nsISocketTransport* aTransport,
+                                                      nsIPresentationSessionTransportCallback* aCallback)
+{
+  if (NS_WARN_IF(!aCallback)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+  mCallback = aCallback;
+
+  if (NS_WARN_IF(!aTransport)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+  mTransport = aTransport;
+
+  nsresult rv = CreateStream();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  SetReadyState(OPEN);
+
+  rv = CreateInputStreamPump();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationSessionTransport::InitWithChannelDescription(nsIPresentationChannelDescription* aDescription,
+                                                         nsIPresentationSessionTransportCallback* aCallback)
+{
+  if (NS_WARN_IF(!aCallback)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+  mCallback = aCallback;
+
+  if (NS_WARN_IF(!aDescription)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  uint16_t serverPort;
+  nsresult rv = aDescription->GetTcpPort(&serverPort);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsCOMPtr<nsIArray> serverHosts;
+  rv = aDescription->GetTcpAddress(getter_AddRefs(serverHosts));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // TODO bug 1148307 Implement PresentationSessionTransport with DataChannel.
+  // Ultimately we may use all the available addresses. DataChannel appears
+  // more robust upon handling ICE. And at the first stage Presentation API is
+  // only exposed on Firefox OS where the first IP appears enough for most
+  // scenarios.
+  nsCOMPtr<nsISupportsCString> supportStr = do_QueryElementAt(serverHosts, 0);
+  if (NS_WARN_IF(!supportStr)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  nsAutoCString serverHost;
+  supportStr->GetData(serverHost);
+  if (serverHost.IsEmpty()) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  SetReadyState(CONNECTING);
+
+  nsCOMPtr<nsISocketTransportService> sts =
+    do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID);
+  if (NS_WARN_IF(!sts)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+  rv = sts->CreateTransport(nullptr, 0, serverHost, serverPort, nullptr,
+                            getter_AddRefs(mTransport));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsCOMPtr<nsIThread> mainThread;
+  NS_GetMainThread(getter_AddRefs(mainThread));
+
+  mTransport->SetEventSink(this, mainThread);
+
+  rv = CreateStream();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+PresentationSessionTransport::CreateStream()
+{
+  nsresult rv = mTransport->OpenInputStream(0, 0, 0, getter_AddRefs(mSocketInputStream));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  rv = mTransport->OpenOutputStream(nsITransport::OPEN_UNBUFFERED, 0, 0, getter_AddRefs(mSocketOutputStream));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // If the other side is not listening, we will get an |onInputStreamReady|
+  // callback where |available| raises to indicate the connection was refused.
+  nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(mSocketInputStream);
+  if (NS_WARN_IF(!asyncStream)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  nsCOMPtr<nsIThread> mainThread;
+  NS_GetMainThread(getter_AddRefs(mainThread));
+
+  rv = asyncStream->AsyncWait(this, nsIAsyncInputStream::WAIT_CLOSURE_ONLY, 0, mainThread);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  mInputStreamScriptable = do_CreateInstance("@mozilla.org/scriptableinputstream;1", &rv);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  rv = mInputStreamScriptable->Init(mSocketInputStream);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  mMultiplexStream = do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1", &rv);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  mMultiplexStreamCopier = do_CreateInstance("@mozilla.org/network/async-stream-copier;1", &rv);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsCOMPtr<nsISocketTransportService> sts =
+    do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID);
+  if (NS_WARN_IF(!sts)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  nsCOMPtr<nsIEventTarget> target = do_QueryInterface(sts);
+  rv = mMultiplexStreamCopier->Init(mMultiplexStream,
+                                    mSocketOutputStream,
+                                    target,
+                                    true, /* source buffered */
+                                    false, /* sink buffered */
+                                    BUFFER_SIZE,
+                                    false, /* close source */
+                                    false); /* close sink */
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+PresentationSessionTransport::CreateInputStreamPump()
+{
+  nsresult rv;
+  mInputStreamPump = do_CreateInstance(NS_INPUTSTREAMPUMP_CONTRACTID, &rv);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = mInputStreamPump->Init(mSocketInputStream, -1, -1, 0, 0, false);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = mInputStreamPump->AsyncRead(this, nullptr);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationSessionTransport::GetCallback(nsIPresentationSessionTransportCallback** aCallback)
+{
+  nsCOMPtr<nsIPresentationSessionTransportCallback> callback = mCallback;
+  callback.forget(aCallback);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationSessionTransport::SetCallback(nsIPresentationSessionTransportCallback* aCallback)
+{
+  mCallback = aCallback;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationSessionTransport::GetSelfAddress(nsINetAddr** aSelfAddress)
+{
+  if (NS_WARN_IF(mReadyState != OPEN)) {
+    return NS_ERROR_DOM_INVALID_STATE_ERR;
+  }
+
+  return mTransport->GetScriptableSelfAddr(aSelfAddress);
+}
+
+void
+PresentationSessionTransport::EnsureCopying()
+{
+  if (mAsyncCopierActive) {
+    return;
+  }
+
+  mAsyncCopierActive = true;
+  nsRefPtr<CopierCallbacks> callbacks = new CopierCallbacks(this);
+  NS_WARN_IF(NS_FAILED(mMultiplexStreamCopier->AsyncCopy(callbacks, nullptr)));
+}
+
+void
+PresentationSessionTransport::NotifyCopyComplete(nsresult aStatus)
+{
+  mAsyncCopierActive = false;
+  mMultiplexStream->RemoveStream(0);
+  if (NS_WARN_IF(NS_FAILED(aStatus))) {
+    if (mReadyState != CLOSED) {
+      mCloseStatus = aStatus;
+      SetReadyState(CLOSED);
+    }
+    return;
+  }
+
+  uint32_t count;
+  nsresult rv = mMultiplexStream->GetCount(&count);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
+
+  if (count) {
+    EnsureCopying();
+    return;
+  }
+
+  if (mReadyState == CLOSING) {
+    mSocketOutputStream->Close();
+    mCloseStatus = NS_OK;
+    SetReadyState(CLOSED);
+  }
+}
+
+NS_IMETHODIMP
+PresentationSessionTransport::Send(nsIInputStream* aData)
+{
+  if (NS_WARN_IF(mReadyState != OPEN)) {
+    return NS_ERROR_DOM_INVALID_STATE_ERR;
+  }
+
+  mMultiplexStream->AppendStream(aData);
+
+  EnsureCopying();
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationSessionTransport::Close(nsresult aReason)
+{
+  if (mReadyState == CLOSED || mReadyState == CLOSING) {
+    return NS_OK;
+  }
+
+  mCloseStatus = aReason;
+  SetReadyState(CLOSING);
+
+  uint32_t count = 0;
+  mMultiplexStream->GetCount(&count);
+  if (!count) {
+    mSocketOutputStream->Close();
+  }
+
+  mSocketInputStream->Close();
+
+  return NS_OK;
+}
+
+void
+PresentationSessionTransport::SetReadyState(ReadyState aReadyState)
+{
+  mReadyState = aReadyState;
+
+  if (mReadyState == OPEN && mCallback) {
+    // Notify the transport channel is ready.
+    NS_WARN_IF(NS_FAILED(mCallback->NotifyTransportReady()));
+  } else if (mReadyState == CLOSED && mCallback) {
+    // Notify the transport channel has been shut down.
+    NS_WARN_IF(NS_FAILED(mCallback->NotifyTransportClosed(mCloseStatus)));
+  }
+}
+
+// nsITransportEventSink
+NS_IMETHODIMP
+PresentationSessionTransport::OnTransportStatus(nsITransport* aTransport,
+                                                nsresult aStatus,
+                                                int64_t aProgress,
+                                                int64_t aProgressMax)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (aStatus != NS_NET_STATUS_CONNECTED_TO) {
+    return NS_OK;
+  }
+
+  SetReadyState(OPEN);
+
+  nsresult rv = CreateInputStreamPump();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+// nsIInputStreamCallback
+NS_IMETHODIMP
+PresentationSessionTransport::OnInputStreamReady(nsIAsyncInputStream* aStream)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // Only used for detecting if the connection was refused.
+  uint64_t dummy;
+  nsresult rv = aStream->Available(&dummy);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    if (mReadyState != CLOSED) {
+      mCloseStatus = NS_ERROR_CONNECTION_REFUSED;
+      SetReadyState(CLOSED);
+    }
+  }
+
+  return NS_OK;
+}
+
+// nsIRequestObserver
+NS_IMETHODIMP
+PresentationSessionTransport::OnStartRequest(nsIRequest* aRequest,
+                                             nsISupports* aContext)
+{
+  // Do nothing.
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationSessionTransport::OnStopRequest(nsIRequest* aRequest,
+                                            nsISupports* aContext,
+                                            nsresult aStatusCode)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  uint32_t count;
+  nsresult rv = mMultiplexStream->GetCount(&count);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  mInputStreamPump = nullptr;
+
+  if (count != 0 && NS_SUCCEEDED(aStatusCode)) {
+    // If we have some buffered output still, and status is not an error, the
+    // other side has done a half-close, but we don't want to be in the close
+    // state until we are done sending everything that was buffered. We also
+    // don't want to call |NotifyTransportClosed| yet.
+    return NS_OK;
+  }
+
+  // We call this even if there is no error.
+  if (mReadyState != CLOSED) {
+    mCloseStatus = aStatusCode;
+    SetReadyState(CLOSED);
+  }
+  return NS_OK;
+}
+
+// nsIStreamListener
+NS_IMETHODIMP
+PresentationSessionTransport::OnDataAvailable(nsIRequest* aRequest,
+                                              nsISupports* aContext,
+                                              nsIInputStream* aStream,
+                                              uint64_t aOffset,
+                                              uint32_t aCount)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (NS_WARN_IF(!mCallback)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  nsCString data;
+  nsresult rv = mInputStreamScriptable->ReadBytes(aCount, data);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // Pass the incoming data to the listener.
+  return mCallback->NotifyData(data);
+}
new file mode 100644
--- /dev/null
+++ b/dom/presentation/PresentationSessionTransport.h
@@ -0,0 +1,99 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PresentationSessionTransport_h
+#define mozilla_dom_PresentationSessionTransport_h
+
+#include "mozilla/nsRefPtr.h"
+#include "nsCOMPtr.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIPresentationSessionTransport.h"
+#include "nsIStreamListener.h"
+#include "nsISupportsImpl.h"
+#include "nsITransport.h"
+
+class nsISocketTransport;
+class nsIInputStreamPump;
+class nsIScriptableInputStream;
+class nsIMultiplexInputStream;
+class nsIAsyncStreamCopier;
+class nsIInputStream;
+
+namespace mozilla {
+namespace dom {
+
+/*
+ * App-to-App transport channel for the presentation session. It's usually
+ * initialized with an |InitWithSocketTransport| call if at the presenting sender
+ * side; whereas it's initialized with an |InitWithChannelDescription| if at the
+ * presenting receiver side. The lifetime is managed in either
+ * |PresentationRequesterInfo| (sender side) or |PresentationResponderInfo|
+ * (receiver side) in PresentationSessionInfo.cpp.
+ *
+ * TODO bug 1148307 Implement PresentationSessionTransport with DataChannel.
+ * The implementation over the TCP channel is primarily used for the early stage
+ * of Presentation API (without SSL) and should be migrated to DataChannel with
+ * full support soon.
+ */
+class PresentationSessionTransport final : public nsIPresentationSessionTransport
+                                         , public nsITransportEventSink
+                                         , public nsIInputStreamCallback
+                                         , public nsIStreamListener
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIPRESENTATIONSESSIONTRANSPORT
+  NS_DECL_NSITRANSPORTEVENTSINK
+  NS_DECL_NSIINPUTSTREAMCALLBACK
+  NS_DECL_NSIREQUESTOBSERVER
+  NS_DECL_NSISTREAMLISTENER
+
+  PresentationSessionTransport();
+
+  void NotifyCopyComplete(nsresult aStatus);
+
+private:
+  ~PresentationSessionTransport();
+
+  nsresult CreateStream();
+
+  nsresult CreateInputStreamPump();
+
+  void EnsureCopying();
+
+  enum ReadyState {
+    CONNECTING,
+    OPEN,
+    CLOSING,
+    CLOSED
+  };
+
+  void SetReadyState(ReadyState aReadyState);
+
+  ReadyState mReadyState;
+  bool mAsyncCopierActive;
+  nsresult mCloseStatus;
+
+  // Raw socket streams
+  nsCOMPtr<nsISocketTransport> mTransport;
+  nsCOMPtr<nsIInputStream> mSocketInputStream;
+  nsCOMPtr<nsIOutputStream> mSocketOutputStream;
+
+  // Input stream machinery
+  nsCOMPtr<nsIInputStreamPump> mInputStreamPump;
+  nsCOMPtr<nsIScriptableInputStream> mInputStreamScriptable;
+
+  // Output stream machinery
+  nsCOMPtr<nsIMultiplexInputStream> mMultiplexStream;
+  nsCOMPtr<nsIAsyncStreamCopier> mMultiplexStreamCopier;
+
+  nsCOMPtr<nsIPresentationSessionTransportCallback> mCallback;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PresentationSessionTransport_h
--- a/dom/presentation/interfaces/moz.build
+++ b/dom/presentation/interfaces/moz.build
@@ -5,14 +5,18 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 XPIDL_SOURCES += [
     'nsIPresentationControlChannel.idl',
     'nsIPresentationDevice.idl',
     'nsIPresentationDeviceManager.idl',
     'nsIPresentationDevicePrompt.idl',
     'nsIPresentationDeviceProvider.idl',
+    'nsIPresentationListener.idl',
+    'nsIPresentationRequestUIGlue.idl',
+    'nsIPresentationService.idl',
     'nsIPresentationSessionRequest.idl',
+    'nsIPresentationSessionTransport.idl',
     'nsITCPPresentationServer.idl',
 ]
 
 XPIDL_MODULE = 'dom_presentation'
 
--- a/dom/presentation/interfaces/nsIPresentationControlChannel.idl
+++ b/dom/presentation/interfaces/nsIPresentationControlChannel.idl
@@ -58,17 +58,17 @@ interface nsIPresentationControlChannelL
    */
   void notifyClosed(in nsresult reason);
 };
 
 /*
  * The control channel for establishing RTCPeerConnection for a presentation
  * session. SDP Offer/Answer will be exchanged through this interface.
  */
-[scriptable, uuid(6bff04b9-8e79-466f-9446-f969de646fd3)]
+[scriptable, uuid(2c8ec493-4e5b-4df7-bedc-7ab25af323f0)]
 interface nsIPresentationControlChannel: nsISupports
 {
   // The listener for handling events of this control channel.
   // All the events should be pending until listener is assigned.
   attribute nsIPresentationControlChannelListener listener;
 
   /*
    * Send offer to remote endpiont. |onOffer| should be invoked
@@ -82,12 +82,19 @@ interface nsIPresentationControlChannel:
    * Send answer to remote endpiont. |onAnswer| should
    * be invoked on remote endpoint.
    * @param answer The answer to send.
    * @throws  NS_ERROR_FAILURE on failure
    */
   void sendAnswer(in nsIPresentationChannelDescription answer);
 
   /*
-   * Close the transport channel.
+   * Notify the app-to-app connection is fully established. (Only used at the
+   * receiver side.)
    */
-  void close();
+  void sendReceiverReady();
+
+  /*
+   * Close the transport channel.
+   * @param reason The reason of channel close; NS_OK represents normal.
+   */
+  void close(in nsresult reason);
 };
new file mode 100644
--- /dev/null
+++ b/dom/presentation/interfaces/nsIPresentationListener.idl
@@ -0,0 +1,34 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(0105f837-4279-4715-9d5b-2dc3f8b65353)]
+interface nsIPresentationListener : nsISupports
+{
+  /*
+   * Called when device availability changes.
+   */
+  void notifyAvailableChange(in bool available);
+};
+
+[scriptable, uuid(3b9ae71f-2905-4969-9117-101627c1c2ea)]
+interface nsIPresentationSessionListener : nsISupports
+{
+  const unsigned short STATE_CONNECTED = 0;
+  const unsigned short STATE_DISCONNECTED = 1;
+  const unsigned short STATE_TERMINATED = 2;
+
+  /*
+   * Called when session state changes.
+   */
+  void notifyStateChange(in DOMString sessionId,
+                         in unsigned short state);
+
+  /*
+   * Called when receive messages.
+   */
+  void notifyMessage(in DOMString sessionId,
+                     in ACString data);
+};
new file mode 100644
--- /dev/null
+++ b/dom/presentation/interfaces/nsIPresentationRequestUIGlue.idl
@@ -0,0 +1,25 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+%{C++
+#define PRESENTATION_REQUEST_UI_GLUE_CONTRACTID \
+  "@mozilla.org/presentation/requestuiglue;1"
+%}
+
+[scriptable, uuid(faa45119-6fb5-496c-aa4c-f740177a38b5)]
+interface nsIPresentationRequestUIGlue : nsISupports
+{
+  /*
+   * This method is called to open the responding app/page when a presentation
+   * request comes in at receiver side.
+   *
+   * @param url       The url of the request.
+   * @param sessionId The session ID of the request.
+   *
+   * @return A promise that resolves to the opening frame.
+   */
+  nsISupports sendRequest(in DOMString url, in DOMString sessionId);
+};
new file mode 100644
--- /dev/null
+++ b/dom/presentation/interfaces/nsIPresentationService.idl
@@ -0,0 +1,112 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIInputStream;
+interface nsIPresentationListener;
+interface nsIPresentationSessionListener;
+
+%{C++
+#define PRESENTATION_SERVICE_CID \
+  { 0x1d9bb10c, 0xc0ab, 0x4fe8, \
+    { 0x9e, 0x4f, 0x40, 0x58, 0xb8, 0x51, 0x98, 0x32 } }
+#define PRESENTATION_SERVICE_CONTRACTID \
+  "@mozilla.org/presentation/presentationservice;1"
+%}
+
+[scriptable, uuid(12073206-0065-4b10-9488-a6eb9b23e65b)]
+interface nsIPresentationServiceCallback : nsISupports
+{
+  /*
+   * Called when the operation succeeds.
+   */
+  void notifySuccess();
+
+  /*
+   * Called when the operation fails.
+   *
+   * @param error: error message.
+   */
+  void notifyError(in nsresult error);
+};
+
+[scriptable, uuid(5801efd9-9dba-4af7-8be9-8fc97c2d54a6)]
+interface nsIPresentationService : nsISupports
+{
+  /*
+   * Start a new presentation session and display a prompt box which asks users
+   * to select a device.
+   *
+   * @param url: The url of presenting page.
+   * @param sessionId: An ID to identify presentation session.
+   * @param origin: The url of requesting page.
+   * @param callback: Invoke the callback when the operation is completed.
+   *                  NotifySuccess() is called with |id| if a session is
+   *                  established successfully with the selected device.
+   *                  Otherwise, NotifyError() is called with a error message.
+   */
+  void startSession(in DOMString url,
+                    in DOMString sessionId,
+                    in DOMString origin,
+                    in nsIPresentationServiceCallback callback);
+
+  /*
+   * Send the message wrapped with an input stream to the session.
+   *
+   * @param sessionId: An ID to identify presentation session.
+   * @param stream: The message is converted to an input stream.
+   */
+  void sendSessionMessage(in DOMString sessionId,
+                          in nsIInputStream stream);
+
+  /*
+   * Terminate the session.
+   *
+   * @param sessionId: An ID to identify presentation session.
+   */
+  void terminate(in DOMString sessionId);
+
+  /*
+   * Register a listener. Must be called from the main thread.
+   *
+   * @param listener: The listener to register.
+   */
+  void registerListener(in nsIPresentationListener listener);
+
+  /*
+   * Unregister a listener. Must be called from the main thread.
+   * @param listener: The listener to unregister.
+   */
+  void unregisterListener(in nsIPresentationListener listener);
+
+  /*
+   * Register a session listener. Must be called from the main thread.
+   *
+   * @param sessionId: An ID to identify presentation session.
+   * @param listener: The listener to register.
+   */
+  void registerSessionListener(in DOMString sessionId,
+                               in nsIPresentationSessionListener listener);
+
+  /*
+   * Unregister a session listener. Must be called from the main thread.
+   *
+   * @param sessionId: An ID to identify presentation session.
+   */
+  void unregisterSessionListener(in DOMString sessionId);
+
+  /*
+   * Check if the presentation instance has an existent session ID at launch.
+   * An empty string is returned at sender side; non-empty at receiver side.
+   */
+  DOMString getExistentSessionIdAtLaunch();
+
+  /*
+   * Notify the receiver page is ready for presentation use.
+   *
+   * @param sessionId: An ID to identify presentation session.
+   */
+  void notifyReceiverReady(in DOMString sessionId);
+};
new file mode 100644
--- /dev/null
+++ b/dom/presentation/interfaces/nsIPresentationSessionTransport.idl
@@ -0,0 +1,66 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIInputStream;
+interface nsINetAddr;
+interface nsIPresentationChannelDescription;
+interface nsISocketTransport;
+
+%{C++
+#define PRESENTATION_SESSION_TRANSPORT_CONTRACTID \
+  "@mozilla.org/presentation/presentationsessiontransport;1"
+%}
+
+/*
+ * The callback for session transport events.
+ */
+[scriptable, uuid(9f158786-41a6-4a10-b29b-9497f25d4b67)]
+interface nsIPresentationSessionTransportCallback : nsISupports
+{
+  void notifyTransportReady();
+  void notifyTransportClosed(in nsresult reason);
+  void notifyData(in ACString data);
+};
+
+/*
+ * App-to-App transport channel for the presentation session.
+ */
+[scriptable, uuid(5a9fb9e9-b846-4c49-ad57-20ed88457295)]
+interface nsIPresentationSessionTransport : nsISupports
+{
+  attribute nsIPresentationSessionTransportCallback callback;
+  readonly attribute nsINetAddr selfAddress;
+
+  /*
+   * Initialize the transport channel with an existent socket transport. (This
+   * is primarily used at the sender side.)
+   * @param transport The socket transport.
+   * @param callback The callback for followup notifications.
+   */
+  void initWithSocketTransport(in nsISocketTransport transport,
+                               in nsIPresentationSessionTransportCallback callback);
+
+  /*
+   * Initialize the transport channel with the channel description. (This is
+   * primarily used at the receiver side.)
+   * @param description The channel description.
+   * @param callback The callback for followup notifications.
+   */
+  void initWithChannelDescription(in nsIPresentationChannelDescription description,
+                                  in nsIPresentationSessionTransportCallback callback);
+
+  /*
+   * Send message to the remote endpoint.
+   * @param data The message to send.
+   */
+  void send(in nsIInputStream data);
+
+  /*
+   * Close this session transport.
+   * @param reason The reason for closing this session transport.
+   */
+  void close(in nsresult reason);
+};
new file mode 100644
--- /dev/null
+++ b/dom/presentation/ipc/PPresentation.ipdl
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PContent;
+include protocol PPresentationRequest;
+
+include InputStreamParams;
+
+namespace mozilla {
+namespace dom {
+
+struct StartSessionRequest
+{
+  nsString url;
+  nsString sessionId;
+  nsString origin;
+};
+
+struct SendSessionMessageRequest
+{
+  nsString sessionId;
+  InputStreamParams data;
+};
+
+struct TerminateRequest
+{
+  nsString sessionId;
+};
+
+union PresentationRequest
+{
+  StartSessionRequest;
+  SendSessionMessageRequest;
+  TerminateRequest;
+};
+
+sync protocol PPresentation
+{
+  manager PContent;
+  manages PPresentationRequest;
+
+child:
+  NotifyAvailableChange(bool aAvailable);
+  NotifySessionStateChange(nsString aSessionId, uint16_t aState);
+  NotifyMessage(nsString aSessionId, nsCString aData);
+
+parent:
+  __delete__();
+
+  RegisterHandler();
+  UnregisterHandler();
+
+  RegisterSessionHandler(nsString aSessionId);
+  UnregisterSessionHandler(nsString aSessionId);
+
+  PPresentationRequest(PresentationRequest aRequest);
+
+  sync GetExistentSessionIdAtLaunch()
+    returns (nsString aSessionId);
+
+  NotifyReceiverReady(nsString aSessionId);
+};
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/presentation/ipc/PPresentationRequest.ipdl
@@ -0,0 +1,21 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PPresentation;
+
+namespace mozilla {
+namespace dom {
+
+sync protocol PPresentationRequest
+{
+  manager PPresentation;
+
+child:
+  __delete__(nsresult result);
+};
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/presentation/ipc/PresentationChild.cpp
@@ -0,0 +1,130 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/StaticPtr.h"
+#include "PresentationChild.h"
+#include "PresentationIPCService.h"
+#include "nsThreadUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+/*
+ * Implementation of PresentationChild
+ */
+
+PresentationChild::PresentationChild(PresentationIPCService* aService)
+  : mActorDestroyed(false)
+  , mService(aService)
+{
+  MOZ_ASSERT(mService);
+
+  MOZ_COUNT_CTOR(PresentationChild);
+}
+
+PresentationChild::~PresentationChild()
+{
+  MOZ_COUNT_DTOR(PresentationChild);
+
+  if (!mActorDestroyed) {
+    Send__delete__(this);
+  }
+  mService = nullptr;
+}
+
+void
+PresentationChild::ActorDestroy(ActorDestroyReason aWhy)
+{
+  mActorDestroyed = true;
+  mService->NotifyPresentationChildDestroyed();
+  mService = nullptr;
+}
+
+PPresentationRequestChild*
+PresentationChild::AllocPPresentationRequestChild(const PresentationRequest& aRequest)
+{
+  NS_NOTREACHED("We should never be manually allocating PPresentationRequestChild actors");
+  return nullptr;
+}
+
+bool
+PresentationChild::DeallocPPresentationRequestChild(PPresentationRequestChild* aActor)
+{
+  delete aActor;
+  return true;
+}
+
+bool
+PresentationChild::RecvNotifyAvailableChange(const bool& aAvailable)
+{
+  if (mService) {
+    NS_WARN_IF(NS_FAILED(mService->NotifyAvailableChange(aAvailable)));
+  }
+  return true;
+}
+
+bool
+PresentationChild::RecvNotifySessionStateChange(const nsString& aSessionId,
+                                                const uint16_t& aState)
+{
+  if (mService) {
+    NS_WARN_IF(NS_FAILED(mService->NotifySessionStateChange(aSessionId, aState)));
+  }
+  return true;
+}
+
+bool
+PresentationChild::RecvNotifyMessage(const nsString& aSessionId,
+                                     const nsCString& aData)
+{
+  if (mService) {
+    NS_WARN_IF(NS_FAILED(mService->NotifyMessage(aSessionId, aData)));
+  }
+  return true;
+}
+
+/*
+ * Implementation of PresentationRequestChild
+ */
+
+PresentationRequestChild::PresentationRequestChild(nsIPresentationServiceCallback* aCallback)
+  : mActorDestroyed(false)
+  , mCallback(aCallback)
+{
+  MOZ_COUNT_CTOR(PresentationRequestChild);
+}
+
+PresentationRequestChild::~PresentationRequestChild()
+{
+  MOZ_COUNT_DTOR(PresentationRequestChild);
+
+  mCallback = nullptr;
+}
+
+void
+PresentationRequestChild::ActorDestroy(ActorDestroyReason aWhy)
+{
+  mActorDestroyed = true;
+  mCallback = nullptr;
+}
+
+bool
+PresentationRequestChild::Recv__delete__(const nsresult& aResult)
+{
+  if (mActorDestroyed) {
+    return true;
+  }
+
+  if (mCallback) {
+    if (NS_SUCCEEDED(aResult)) {
+      NS_WARN_IF(NS_FAILED(mCallback->NotifySuccess()));
+    } else {
+      NS_WARN_IF(NS_FAILED(mCallback->NotifyError(aResult)));
+    }
+  }
+
+  return true;
+}
new file mode 100644
--- /dev/null
+++ b/dom/presentation/ipc/PresentationChild.h
@@ -0,0 +1,75 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PresentationChild_h
+#define mozilla_dom_PresentationChild_h
+
+#include "mozilla/dom/PPresentationChild.h"
+#include "mozilla/dom/PPresentationRequestChild.h"
+
+class nsIPresentationServiceCallback;
+
+namespace mozilla {
+namespace dom {
+
+class PresentationIPCService;
+
+class PresentationChild final : public PPresentationChild
+{
+public:
+  explicit PresentationChild(PresentationIPCService* aService);
+
+  virtual void
+  ActorDestroy(ActorDestroyReason aWhy) override;
+
+  virtual PPresentationRequestChild*
+  AllocPPresentationRequestChild(const PresentationRequest& aRequest) override;
+
+  virtual bool
+  DeallocPPresentationRequestChild(PPresentationRequestChild* aActor) override;
+
+  virtual bool
+  RecvNotifyAvailableChange(const bool& aAvailable) override;
+
+  virtual bool
+  RecvNotifySessionStateChange(const nsString& aSessionId,
+                               const uint16_t& aState) override;
+
+  virtual bool
+  RecvNotifyMessage(const nsString& aSessionId,
+                    const nsCString& aData) override;
+
+private:
+  virtual ~PresentationChild();
+
+  bool mActorDestroyed;
+  nsRefPtr<PresentationIPCService> mService;
+};
+
+class PresentationRequestChild final : public PPresentationRequestChild
+{
+  friend class PresentationChild;
+
+public:
+  explicit PresentationRequestChild(nsIPresentationServiceCallback* aCallback);
+
+  virtual void
+  ActorDestroy(ActorDestroyReason aWhy) override;
+
+  virtual bool
+  Recv__delete__(const nsresult& aResult) override;
+
+private:
+  virtual ~PresentationRequestChild();
+
+  bool mActorDestroyed;
+  nsCOMPtr<nsIPresentationServiceCallback> mCallback;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PresentationChild_h
new file mode 100644
--- /dev/null
+++ b/dom/presentation/ipc/PresentationIPCService.cpp
@@ -0,0 +1,209 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/PPresentation.h"
+#include "mozilla/ipc/InputStreamUtils.h"
+#include "nsIPresentationListener.h"
+#include "PresentationCallbacks.h"
+#include "PresentationChild.h"
+#include "PresentationIPCService.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+namespace {
+
+PresentationChild* sPresentationChild;
+
+} // anonymous
+
+NS_IMPL_ISUPPORTS(PresentationIPCService, nsIPresentationService)
+
+PresentationIPCService::PresentationIPCService()
+{
+  ContentChild* contentChild = ContentChild::GetSingleton();
+  if (NS_WARN_IF(!contentChild)) {
+    return;
+  }
+  sPresentationChild = new PresentationChild(this);
+  NS_WARN_IF(!contentChild->SendPPresentationConstructor(sPresentationChild));
+}
+
+/* virtual */
+PresentationIPCService::~PresentationIPCService()
+{
+  mListeners.Clear();
+  mSessionListeners.Clear();
+  sPresentationChild = nullptr;
+}
+
+NS_IMETHODIMP
+PresentationIPCService::StartSession(const nsAString& aUrl,
+                                     const nsAString& aSessionId,
+                                     const nsAString& aOrigin,
+                                     nsIPresentationServiceCallback* aCallback)
+{
+  return SendRequest(aCallback,
+                     StartSessionRequest(nsAutoString(aUrl), nsAutoString(aSessionId), nsAutoString(aOrigin)));
+}
+
+NS_IMETHODIMP
+PresentationIPCService::SendSessionMessage(const nsAString& aSessionId,
+                                           nsIInputStream* aStream)
+{
+  MOZ_ASSERT(!aSessionId.IsEmpty());
+  MOZ_ASSERT(aStream);
+
+  mozilla::ipc::OptionalInputStreamParams stream;
+  nsTArray<mozilla::ipc::FileDescriptor> fds;
+  SerializeInputStream(aStream, stream, fds);
+  MOZ_ASSERT(fds.IsEmpty());
+
+  return SendRequest(nullptr, SendSessionMessageRequest(nsAutoString(aSessionId), stream));
+}
+
+NS_IMETHODIMP
+PresentationIPCService::Terminate(const nsAString& aSessionId)
+{
+  MOZ_ASSERT(!aSessionId.IsEmpty());
+
+  return SendRequest(nullptr, TerminateRequest(nsAutoString(aSessionId)));
+}
+
+nsresult
+PresentationIPCService::SendRequest(nsIPresentationServiceCallback* aCallback,
+                                    const PresentationRequest& aRequest)
+{
+  if (sPresentationChild) {
+    PresentationRequestChild* actor = new PresentationRequestChild(aCallback);
+    NS_WARN_IF(!sPresentationChild->SendPPresentationRequestConstructor(actor, aRequest));
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationIPCService::RegisterListener(nsIPresentationListener* aListener)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aListener);
+
+  mListeners.AppendElement(aListener);
+  if (sPresentationChild) {
+    NS_WARN_IF(!sPresentationChild->SendRegisterHandler());
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationIPCService::UnregisterListener(nsIPresentationListener* aListener)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aListener);
+
+  mListeners.RemoveElement(aListener);
+  if (sPresentationChild) {
+    NS_WARN_IF(!sPresentationChild->SendUnregisterHandler());
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationIPCService::RegisterSessionListener(const nsAString& aSessionId,
+                                                nsIPresentationSessionListener* aListener)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aListener);
+
+  mSessionListeners.Put(aSessionId, aListener);
+  if (sPresentationChild) {
+    NS_WARN_IF(!sPresentationChild->SendRegisterSessionHandler(nsAutoString(aSessionId)));
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationIPCService::UnregisterSessionListener(const nsAString& aSessionId)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  mSessionListeners.Remove(aSessionId);
+  if (sPresentationChild) {
+    NS_WARN_IF(!sPresentationChild->SendUnregisterSessionHandler(nsAutoString(aSessionId)));
+  }
+  return NS_OK;
+}
+
+nsresult
+PresentationIPCService::NotifySessionStateChange(const nsAString& aSessionId,
+                                                 uint16_t aState)
+{
+  nsCOMPtr<nsIPresentationSessionListener> listener;
+  if (NS_WARN_IF(!mSessionListeners.Get(aSessionId, getter_AddRefs(listener)))) {
+    return NS_OK;
+  }
+
+  return listener->NotifyStateChange(aSessionId, aState);
+}
+
+nsresult
+PresentationIPCService::NotifyMessage(const nsAString& aSessionId,
+                                      const nsACString& aData)
+{
+  nsCOMPtr<nsIPresentationSessionListener> listener;
+  if (NS_WARN_IF(!mSessionListeners.Get(aSessionId, getter_AddRefs(listener)))) {
+    return NS_OK;
+  }
+
+  return listener->NotifyMessage(aSessionId, aData);
+}
+
+nsresult
+PresentationIPCService::NotifyAvailableChange(bool aAvailable)
+{
+  nsTObserverArray<nsCOMPtr<nsIPresentationListener> >::ForwardIterator iter(mListeners);
+  while (iter.HasMore()) {
+    nsIPresentationListener* listener = iter.GetNext();
+    NS_WARN_IF(NS_FAILED(listener->NotifyAvailableChange(aAvailable)));
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationIPCService::GetExistentSessionIdAtLaunch(nsAString& aSessionId)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsAutoString sessionId(aSessionId);
+  NS_WARN_IF(!sPresentationChild->SendGetExistentSessionIdAtLaunch(&sessionId));
+  aSessionId = sessionId;
+
+  return NS_OK;
+}
+
+nsresult
+PresentationIPCService::NotifyReceiverReady(const nsAString& aSessionId)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  NS_WARN_IF(!sPresentationChild->SendNotifyReceiverReady(nsAutoString(aSessionId)));
+  return NS_OK;
+}
+
+void
+PresentationIPCService::NotifyPresentationChildDestroyed()
+{
+  sPresentationChild = nullptr;
+}
+
+nsresult
+PresentationIPCService::MonitorResponderLoading(const nsAString& aSessionId,
+                                                nsIDocShell* aDocShell)
+{
+  mCallback = new PresentationResponderLoadingCallback(aSessionId);
+  return mCallback->Init(aDocShell);
+}
new file mode 100644
--- /dev/null
+++ b/dom/presentation/ipc/PresentationIPCService.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PresentationIPCService_h
+#define mozilla_dom_PresentationIPCService_h
+
+#include "nsIPresentationService.h"
+#include "nsRefPtrHashtable.h"
+#include "nsTObserverArray.h"
+
+class nsIDocShell;
+
+namespace mozilla {
+namespace dom {
+
+class PresentationRequest;
+class PresentationResponderLoadingCallback;
+
+class PresentationIPCService final : public nsIPresentationService
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIPRESENTATIONSERVICE
+
+  PresentationIPCService();
+
+  nsresult NotifyAvailableChange(bool aAvailable);
+
+  nsresult NotifySessionStateChange(const nsAString& aSessionId,
+                                    uint16_t aState);
+
+  nsresult NotifyMessage(const nsAString& aSessionId,
+                         const nsACString& aData);
+
+  void NotifyPresentationChildDestroyed();
+
+  nsresult MonitorResponderLoading(const nsAString& aSessionId,
+                                   nsIDocShell* aDocShell);
+
+private:
+  virtual ~PresentationIPCService();
+  nsresult SendRequest(nsIPresentationServiceCallback* aCallback,
+                       const PresentationRequest& aRequest);
+
+  nsTObserverArray<nsCOMPtr<nsIPresentationListener> > mListeners;
+  nsRefPtrHashtable<nsStringHashKey, nsIPresentationSessionListener> mSessionListeners;
+  nsRefPtr<PresentationResponderLoadingCallback> mCallback;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PresentationIPCService_h
new file mode 100644
--- /dev/null
+++ b/dom/presentation/ipc/PresentationParent.cpp
@@ -0,0 +1,265 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ipc/InputStreamUtils.h"
+#include "nsIPresentationDeviceManager.h"
+#include "nsServiceManagerUtils.h"
+#include "PresentationParent.h"
+#include "PresentationService.h"
+
+using namespace mozilla::dom;
+
+/*
+ * Implementation of PresentationParent
+ */
+
+NS_IMPL_ISUPPORTS(PresentationParent, nsIPresentationListener, nsIPresentationSessionListener)
+
+PresentationParent::PresentationParent()
+  : mActorDestroyed(false)
+{
+  MOZ_COUNT_CTOR(PresentationParent);
+}
+
+/* virtual */ PresentationParent::~PresentationParent()
+{
+  MOZ_COUNT_DTOR(PresentationParent);
+}
+
+bool
+PresentationParent::Init()
+{
+  MOZ_ASSERT(!mService);
+  mService = do_GetService(PRESENTATION_SERVICE_CONTRACTID);
+  return NS_WARN_IF(!mService) ? false : true;
+}
+
+void
+PresentationParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+  mActorDestroyed = true;
+
+  for (uint32_t i = 0; i < mSessionIds.Length(); i++) {
+    NS_WARN_IF(NS_FAILED(mService->UnregisterSessionListener(mSessionIds[i])));
+  }
+  mSessionIds.Clear();
+
+  mService->UnregisterListener(this);
+  mService = nullptr;
+}
+
+bool
+PresentationParent::RecvPPresentationRequestConstructor(
+  PPresentationRequestParent* aActor,
+  const PresentationRequest& aRequest)
+{
+  PresentationRequestParent* actor = static_cast<PresentationRequestParent*>(aActor);
+
+  nsresult rv = NS_ERROR_FAILURE;
+  switch (aRequest.type()) {
+    case PresentationRequest::TStartSessionRequest:
+      rv = actor->DoRequest(aRequest.get_StartSessionRequest());
+      break;
+    case PresentationRequest::TSendSessionMessageRequest:
+      rv = actor->DoRequest(aRequest.get_SendSessionMessageRequest());
+      break;
+    case PresentationRequest::TTerminateRequest:
+      rv = actor->DoRequest(aRequest.get_TerminateRequest());
+      break;
+    default:
+      MOZ_CRASH("Unknown PresentationRequest type");
+  }
+
+  return NS_WARN_IF(NS_FAILED(rv)) ? false : true;
+}
+
+PPresentationRequestParent*
+PresentationParent::AllocPPresentationRequestParent(
+  const PresentationRequest& aRequest)
+{
+  MOZ_ASSERT(mService);
+  nsRefPtr<PresentationRequestParent> actor = new PresentationRequestParent(mService);
+  return actor.forget().take();
+}
+
+bool
+PresentationParent::DeallocPPresentationRequestParent(
+  PPresentationRequestParent* aActor)
+{
+  nsRefPtr<PresentationRequestParent> actor =
+    dont_AddRef(static_cast<PresentationRequestParent*>(aActor));
+  return true;
+}
+
+bool
+PresentationParent::Recv__delete__()
+{
+  return true;
+}
+
+bool
+PresentationParent::RecvRegisterHandler()
+{
+  MOZ_ASSERT(mService);
+  NS_WARN_IF(NS_FAILED(mService->RegisterListener(this)));
+  return true;
+}
+
+bool
+PresentationParent::RecvUnregisterHandler()
+{
+  MOZ_ASSERT(mService);
+  NS_WARN_IF(NS_FAILED(mService->UnregisterListener(this)));
+  return true;
+}
+
+/* virtual */ bool
+PresentationParent::RecvRegisterSessionHandler(const nsString& aSessionId)
+{
+  MOZ_ASSERT(mService);
+  mSessionIds.AppendElement(aSessionId);
+  NS_WARN_IF(NS_FAILED(mService->RegisterSessionListener(aSessionId, this)));
+  return true;
+}
+
+/* virtual */ bool
+PresentationParent::RecvUnregisterSessionHandler(const nsString& aSessionId)
+{
+  MOZ_ASSERT(mService);
+  mSessionIds.RemoveElement(aSessionId);
+  NS_WARN_IF(NS_FAILED(mService->UnregisterSessionListener(aSessionId)));
+  return true;
+}
+
+NS_IMETHODIMP
+PresentationParent::NotifyAvailableChange(bool aAvailable)
+{
+  if (NS_WARN_IF(mActorDestroyed || !SendNotifyAvailableChange(aAvailable))) {
+    return NS_ERROR_FAILURE;
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationParent::NotifyStateChange(const nsAString& aSessionId,
+                                      uint16_t aState)
+{
+  if (NS_WARN_IF(mActorDestroyed ||
+                 !SendNotifySessionStateChange(nsString(aSessionId), aState))) {
+    return NS_ERROR_FAILURE;
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationParent::NotifyMessage(const nsAString& aSessionId,
+                                  const nsACString& aData)
+{
+  if (NS_WARN_IF(mActorDestroyed ||
+                 !SendNotifyMessage(nsString(aSessionId), nsCString(aData)))) {
+    return NS_ERROR_FAILURE;
+  }
+  return NS_OK;
+}
+
+bool
+PresentationParent::RecvGetExistentSessionIdAtLaunch(nsString* aSessionId)
+{
+  MOZ_ASSERT(mService);
+  NS_WARN_IF(NS_FAILED(mService->GetExistentSessionIdAtLaunch(*aSessionId)));
+  return true;
+}
+
+bool
+PresentationParent::RecvNotifyReceiverReady(const nsString& aSessionId)
+{
+  MOZ_ASSERT(mService);
+  NS_WARN_IF(NS_FAILED(mService->NotifyReceiverReady(aSessionId)));
+  return true;
+}
+
+/*
+ * Implementation of PresentationRequestParent
+ */
+
+NS_IMPL_ISUPPORTS(PresentationRequestParent, nsIPresentationServiceCallback)
+
+PresentationRequestParent::PresentationRequestParent(nsIPresentationService* aService)
+  : mActorDestroyed(false)
+  , mService(aService)
+{
+  MOZ_COUNT_CTOR(PresentationRequestParent);
+}
+
+PresentationRequestParent::~PresentationRequestParent()
+{
+  MOZ_COUNT_DTOR(PresentationRequestParent);
+}
+
+void
+PresentationRequestParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+  mActorDestroyed = true;
+  mService = nullptr;
+}
+
+nsresult
+PresentationRequestParent::DoRequest(const StartSessionRequest& aRequest)
+{
+  MOZ_ASSERT(mService);
+  return mService->StartSession(aRequest.url(), aRequest.sessionId(),
+                                aRequest.origin(), this);
+}
+
+nsresult
+PresentationRequestParent::DoRequest(const SendSessionMessageRequest& aRequest)
+{
+  MOZ_ASSERT(mService);
+  nsTArray<mozilla::ipc::FileDescriptor> fds;
+  nsCOMPtr<nsIInputStream> stream = DeserializeInputStream(aRequest.data(), fds);
+  if(NS_WARN_IF(!stream)) {
+    return NotifyError(NS_ERROR_NOT_AVAILABLE);
+  }
+
+  nsresult rv = mService->SendSessionMessage(aRequest.sessionId(), stream);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return NotifyError(rv);
+  }
+  return NotifySuccess();
+}
+
+nsresult
+PresentationRequestParent::DoRequest(const TerminateRequest& aRequest)
+{
+  MOZ_ASSERT(mService);
+  nsresult rv = mService->Terminate(aRequest.sessionId());
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return NotifyError(rv);
+  }
+  return NotifySuccess();
+}
+
+NS_IMETHODIMP
+PresentationRequestParent::NotifySuccess()
+{
+  return SendResponse(NS_OK);
+}
+
+NS_IMETHODIMP
+PresentationRequestParent::NotifyError(nsresult aError)
+{
+  return SendResponse(aError);
+}
+
+nsresult
+PresentationRequestParent::SendResponse(nsresult aResult)
+{
+  if (NS_WARN_IF(mActorDestroyed || !Send__delete__(this, aResult))) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/dom/presentation/ipc/PresentationParent.h
@@ -0,0 +1,96 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PresentationParent_h__
+#define mozilla_dom_PresentationParent_h__
+
+#include "mozilla/dom/PPresentationParent.h"
+#include "mozilla/dom/PPresentationRequestParent.h"
+#include "nsIPresentationListener.h"
+#include "nsIPresentationService.h"
+
+namespace mozilla {
+namespace dom {
+
+class PresentationParent final : public PPresentationParent
+                               , public nsIPresentationListener
+                               , public nsIPresentationSessionListener
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIPRESENTATIONLISTENER
+  NS_DECL_NSIPRESENTATIONSESSIONLISTENER
+
+  PresentationParent();
+
+  bool Init();
+
+  virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+  virtual bool
+  RecvPPresentationRequestConstructor(PPresentationRequestParent* aActor,
+                                      const PresentationRequest& aRequest) override;
+
+  virtual PPresentationRequestParent*
+  AllocPPresentationRequestParent(const PresentationRequest& aRequest) override;
+
+  virtual bool
+  DeallocPPresentationRequestParent(PPresentationRequestParent* aActor) override;
+
+  virtual bool Recv__delete__() override;
+
+  virtual bool RecvRegisterHandler() override;
+
+  virtual bool RecvUnregisterHandler() override;
+
+  virtual bool RecvRegisterSessionHandler(const nsString& aSessionId) override;
+
+  virtual bool RecvUnregisterSessionHandler(const nsString& aSessionId) override;
+
+  virtual bool RecvGetExistentSessionIdAtLaunch(nsString* aSessionId) override;
+
+  virtual bool RecvNotifyReceiverReady(const nsString& aSessionId) override;
+
+private:
+  virtual ~PresentationParent();
+
+  bool mActorDestroyed;
+  nsCOMPtr<nsIPresentationService> mService;
+  nsTArray<nsString> mSessionIds;
+};
+
+class PresentationRequestParent final : public PPresentationRequestParent
+                                      , public nsIPresentationServiceCallback
+{
+  friend class PresentationParent;
+
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIPRESENTATIONSERVICECALLBACK
+
+  explicit PresentationRequestParent(nsIPresentationService* aService);
+
+  virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+private:
+  virtual ~PresentationRequestParent();
+
+  nsresult SendResponse(nsresult aResult);
+
+  nsresult DoRequest(const StartSessionRequest& aRequest);
+
+  nsresult DoRequest(const SendSessionMessageRequest& aRequest);
+
+  nsresult DoRequest(const TerminateRequest& aRequest);
+
+  bool mActorDestroyed;
+  nsCOMPtr<nsIPresentationService> mService;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PresentationParent_h__
--- a/dom/presentation/moz.build
+++ b/dom/presentation/moz.build
@@ -4,31 +4,54 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DIRS += ['interfaces', 'provider']
 
 XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell/xpcshell.ini']
 MOCHITEST_MANIFESTS += ['tests/mochitest/mochitest.ini']
 
-EXPORTS.mozilla.dom.presentation += [
+EXPORTS.mozilla.dom += [
+    'ipc/PresentationChild.h',
+    'ipc/PresentationIPCService.h',
+    'ipc/PresentationParent.h',
+    'Presentation.h',
+    'PresentationCallbacks.h',
     'PresentationDeviceManager.h',
+    'PresentationService.h',
+    'PresentationSession.h',
+    'PresentationSessionInfo.h',
+    'PresentationSessionTransport.h',
 ]
 
-SOURCES += [
+UNIFIED_SOURCES += [
+    'ipc/PresentationChild.cpp',
+    'ipc/PresentationIPCService.cpp',
+    'ipc/PresentationParent.cpp',
+    'Presentation.cpp',
+    'PresentationCallbacks.cpp',
     'PresentationDeviceManager.cpp',
+    'PresentationService.cpp',
+    'PresentationSession.cpp',
+    'PresentationSessionInfo.cpp',
     'PresentationSessionRequest.cpp',
+    'PresentationSessionTransport.cpp',
 ]
 
 EXTRA_COMPONENTS += [
     'PresentationDeviceInfoManager.js',
     'PresentationDeviceInfoManager.manifest',
 ]
 
 EXTRA_JS_MODULES += [
     'PresentationDeviceInfoManager.jsm',
 ]
 
+IPDL_SOURCES += [
+    'ipc/PPresentation.ipdl',
+    'ipc/PPresentationRequest.ipdl'
+]
+
 FAIL_ON_WARNINGS = True
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
new file mode 100644
--- /dev/null
+++ b/dom/presentation/tests/mochitest/PresentationSessionChromeScript.js
@@ -0,0 +1,359 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+'use strict';
+
+const { classes: Cc, interfaces: Ci, manager: Cm, utils: Cu, results: Cr } = Components;
+
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+
+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) {
+    registrar.unregisterFactory(mockedClassId, mockedFactory);
+    registrar.registerFactory(originalClassId, "", contractId, originalFactory);
+  }
+}
+
+const sessionId = 'test-session-id';
+
+const address = Cc["@mozilla.org/supports-cstring;1"]
+                  .createInstance(Ci.nsISupportsCString);
+address.data = "127.0.0.1";
+const addresses = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
+addresses.appendElement(address, false);
+
+const mockedChannelDescription = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationChannelDescription]),
+  type: 1,
+  tcpAddress: addresses,
+  tcpPort: 1234,
+};
+
+const mockedServerSocket = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIServerSocket,
+                                         Ci.nsIFactory]),
+  createInstance: function(aOuter, aIID) {
+    if (aOuter) {
+      throw Components.results.NS_ERROR_NO_AGGREGATION;
+    }
+    return this.QueryInterface(aIID);
+  },
+  get port() {
+    return this._port;
+  },
+  set listener(listener) {
+    this._listener = listener;
+  },
+  init: function(port, loopbackOnly, backLog) {
+    if (port != -1) {
+      this._port = port;
+    } else {
+      this._port = 5678;
+    }
+  },
+  asyncListen: function(listener) {
+    this._listener = listener;
+  },
+  close: function() {
+    this._listener.onStopListening(this, Cr.NS_BINDING_ABORTED);
+  },
+  simulateOnSocketAccepted: function(serverSocket, socketTransport) {
+    this._listener.onSocketAccepted(serverSocket, socketTransport);
+  }
+};
+
+const mockedSocketTransport = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsISocketTransport]),
+};
+
+const mockedControlChannel = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannel]),
+  set listener(listener) {
+    this._listener = listener;
+  },
+  get listener() {
+    return this._listener;
+  },
+  sendOffer: function(offer) {
+    sendAsyncMessage('offer-sent');
+  },
+  sendAnswer: function(answer) {
+    this._listener.QueryInterface(Ci.nsIPresentationSessionTransportCallback).notifyTransportReady();
+  },
+  close: function(reason) {
+    sendAsyncMessage('control-channel-closed', reason);
+    this._listener.QueryInterface(Ci.nsIPresentationControlChannelListener).notifyClosed(reason);
+  },
+  simulateReceiverReady: function() {
+    this._listener.QueryInterface(Ci.nsIPresentationControlChannelListener).notifyReceiverReady();
+  },
+  simulateOnOffer: function() {
+    sendAsyncMessage('offer-received');
+    this._listener.QueryInterface(Ci.nsIPresentationControlChannelListener).onOffer(mockedChannelDescription);
+  },
+  simulateOnAnswer: function() {
+    sendAsyncMessage('answer-received');
+    this._listener.QueryInterface(Ci.nsIPresentationControlChannelListener).onAnswer(mockedChannelDescription);
+  },
+};
+
+const mockedDevice = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDevice]),
+  id: 'id',
+  name: 'name',
+  type: 'type',
+  establishControlChannel: function(url, presentationId) {
+    sendAsyncMessage('control-channel-established');
+    return mockedControlChannel;
+  },
+  set listener(listener) {
+    this._listener = listener;
+  },
+  get listener() {
+    return this._listener;
+  },
+  simulateSessionRequest: function(url, presentationId, controlChannel) {
+    this._listener.onSessionRequest(this, url, presentationId, controlChannel);
+  },
+};
+
+const mockedDevicePrompt = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDevicePrompt,
+                                         Ci.nsIFactory]),
+  createInstance: function(aOuter, aIID) {
+    if (aOuter) {
+      throw Components.results.NS_ERROR_NO_AGGREGATION;
+    }
+    return this.QueryInterface(aIID);
+  },
+  set request(request) {
+    this._request = request;
+  },
+  get request() {
+    return this._request;
+  },
+  promptDeviceSelection: function(request) {
+    this._request = request;
+    sendAsyncMessage('device-prompt');
+  },
+  simulateSelect: function() {
+    this._request.select(mockedDevice);
+  },
+  simulateCancel: function() {
+    this._request.cancel();
+  }
+};
+
+const mockedSessionTransport = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationSessionTransport,
+                                         Ci.nsIFactory]),
+  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;
+  },
+  get selfAddress() {
+    return this._selfAddress;
+  },
+  initWithSocketTransport: function(transport, callback) {
+    sendAsyncMessage('data-transport-initialized');
+    this._callback = callback;
+    this.simulateTransportReady();
+  },
+  initWithChannelDescription: function(description, callback) {
+    this._callback = callback;
+
+    var addresses = description.QueryInterface(Ci.nsIPresentationChannelDescription).tcpAddress;
+    this._selfAddress = {
+      QueryInterface: XPCOMUtils.generateQI([Ci.nsINetAddr]),
+      address: (addresses.length > 0) ?
+                addresses.queryElementAt(0, Ci.nsISupportsCString).data : "",
+      port: description.QueryInterface(Ci.nsIPresentationChannelDescription).tcpPort,
+    };
+  },
+  send: function(data) {
+    var binaryStream = Cc["@mozilla.org/binaryinputstream;1"].
+                       createInstance(Ci.nsIBinaryInputStream);
+    binaryStream.setInputStream(data);
+    var message = binaryStream.readBytes(binaryStream.available());
+    sendAsyncMessage('message-sent', message);
+  },
+  close: function(reason) {
+    sendAsyncMessage('data-transport-closed', reason);
+    this._callback.QueryInterface(Ci.nsIPresentationSessionTransportCallback).notifyTransportClosed(reason);
+  },
+  simulateTransportReady: function() {
+    this._callback.QueryInterface(Ci.nsIPresentationSessionTransportCallback).notifyTransportReady();
+  },
+  simulateIncomingMessage: function(message) {
+    this._callback.QueryInterface(Ci.nsIPresentationSessionTransportCallback).notifyData(message);
+  },
+};
+
+const mockedNetworkInfo = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsINetworkInfo]),
+  getAddresses: function(ips, prefixLengths) {
+    ips.value = ["127.0.0.1"];
+    prefixLengths.value = [0];
+    return 1;
+  },
+};
+
+const mockedNetworkManager = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsINetworkManager,
+                                         Ci.nsIFactory]),
+  createInstance: function(aOuter, aIID) {
+    if (aOuter) {
+      throw Components.results.NS_ERROR_NO_AGGREGATION;
+    }
+    return this.QueryInterface(aIID);
+  },
+  get activeNetworkInfo() {
+    return mockedNetworkInfo;
+  },
+};
+
+var requestPromise = null;
+
+const mockedRequestUIGlue = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationRequestUIGlue,
+                                         Ci.nsIFactory]),
+  createInstance: function(aOuter, aIID) {
+    if (aOuter) {
+      throw Components.results.NS_ERROR_NO_AGGREGATION;
+    }
+    return this.QueryInterface(aIID);
+  },
+  sendRequest: function(aUrl, aSessionId) {
+    sendAsyncMessage('receiver-launching', aSessionId);
+    return requestPromise;
+  },
+};
+
+// Register mocked factories.
+const uuidGenerator = Cc["@mozilla.org/uuid-generator;1"]
+                      .getService(Ci.nsIUUIDGenerator);
+const originalFactoryData = [];
+originalFactoryData.push(registerMockedFactory("@mozilla.org/presentation-device/prompt;1",
+                                               uuidGenerator.generateUUID(),
+                                               mockedDevicePrompt));
+originalFactoryData.push(registerMockedFactory("@mozilla.org/network/server-socket;1",
+                                               uuidGenerator.generateUUID(),
+                                               mockedServerSocket));
+originalFactoryData.push(registerMockedFactory("@mozilla.org/presentation/presentationsessiontransport;1",
+                                               uuidGenerator.generateUUID(),
+                                               mockedSessionTransport));
+originalFactoryData.push(registerMockedFactory("@mozilla.org/network/manager;1",
+                                               uuidGenerator.generateUUID(),
+                                               mockedNetworkManager));
+originalFactoryData.push(registerMockedFactory("@mozilla.org/presentation/requestuiglue;1",
+                                               uuidGenerator.generateUUID(),
+                                               mockedRequestUIGlue));
+
+function tearDown() {
+  requestPromise = null;
+  mockedServerSocket.listener = null;
+  mockedControlChannel.listener = null;
+  mockedDevice.listener = null;
+  mockedDevicePrompt.request = null;
+  mockedSessionTransport.callback = null;
+
+  var deviceManager = Cc['@mozilla.org/presentation-device/manager;1']
+                      .getService(Ci.nsIPresentationDeviceManager);
+  deviceManager.QueryInterface(Ci.nsIPresentationDeviceListener).removeDevice(mockedDevice);
+
+  // Register original factories.
+  for (var data in originalFactoryData) {
+    registerOriginalFactory(data.contractId, data.mockedClassId,
+                            data.mockedFactory, data.originalClassId,
+                            data.originalFactory);
+  }
+
+  sendAsyncMessage('teardown-complete');
+}
+
+addMessageListener('trigger-device-add', function() {
+  var deviceManager = Cc['@mozilla.org/presentation-device/manager;1']
+                      .getService(Ci.nsIPresentationDeviceManager);
+  deviceManager.QueryInterface(Ci.nsIPresentationDeviceListener).addDevice(mockedDevice);
+});
+
+addMessageListener('trigger-device-prompt-select', function() {
+  mockedDevicePrompt.simulateSelect();
+});
+
+addMessageListener('trigger-device-prompt-cancel', function() {
+  mockedDevicePrompt.simulateCancel();
+});
+
+addMessageListener('trigger-incoming-session-request', function(url) {
+  mockedDevice.simulateSessionRequest(url, sessionId, mockedControlChannel);
+});
+
+addMessageListener('trigger-incoming-offer', function() {
+  mockedControlChannel.simulateOnOffer();
+});
+
+addMessageListener('trigger-incoming-answer', function() {
+  mockedControlChannel.simulateOnAnswer();
+});
+
+addMessageListener('trigger-incoming-transport', function() {
+  mockedServerSocket.simulateOnSocketAccepted(mockedServerSocket, mockedSocketTransport);
+});
+
+addMessageListener('trigger-control-channel-close', function(reason) {
+  mockedControlChannel.close(reason);
+});
+
+addMessageListener('trigger-data-transport-close', function(reason) {
+  mockedSessionTransport.close(reason);
+});
+
+addMessageListener('trigger-incoming-message', function(message) {
+  mockedSessionTransport.simulateIncomingMessage(message);
+});
+
+addMessageListener('teardown', function() {
+  tearDown();
+});
+
+var obs = Cc["@mozilla.org/observer-service;1"]
+          .getService(Ci.nsIObserverService);
+obs.addObserver(function observer(aSubject, aTopic, aData) {
+  obs.removeObserver(observer, aTopic);
+
+  requestPromise = aSubject;
+}, 'setup-request-promise', false);
new file mode 100644
--- /dev/null
+++ b/dom/presentation/tests/mochitest/file_presentation_receiver.html
@@ -0,0 +1,77 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test for B2G Presentation Session API at receiver side</title>
+</head>
+<body>
+<div id="content"></div>
+<script type="application/javascript;version=1.7">
+
+"use strict";
+
+function is(a, b, msg) {
+  window.parent.postMessage((a === b ? 'OK ' : 'KO ') + msg, '*');
+}
+
+function ok(a, msg) {
+  window.parent.postMessage((a ? 'OK ' : 'KO ') + msg, '*');
+}
+
+function info(msg) {
+  window.parent.postMessage('INFO ' + msg, '*');
+}
+
+function command(msg) {
+  window.parent.postMessage('COMMAND ' + JSON.stringify(msg), '*');
+}
+
+function finish() {
+  window.parent.postMessage('DONE', '*');
+}
+
+var session;
+
+function testSessionAvailable() {
+  return new Promise(function(aResolve, aReject) {
+    ok(navigator.presentation, "navigator.presentation should be available.");
+
+    session = navigator.presentation.session;
+    ok(session.id, "Session ID should be set: " + session.id);
+    is(session.state, "disconnected", "Session state at receiver side should be disconnected by default.");
+    aResolve();
+  });
+}
+
+function testSessionReady() {
+  return new Promise(function(aResolve, aReject) {
+    session.onstatechange = function() {
+      session.onstatechange = null;
+      is(session.state, "connected", "Session state should become connected.");
+      aResolve();
+    };
+
+    command({ name: 'trigger-incoming-offer' });
+  });
+}
+
+function testCloseSession() {
+  return new Promise(function(aResolve, aReject) {
+    session.onstatechange = function() {
+      session.onstatechange = null;
+      is(session.state, "terminated", "Session should be terminated.");
+      aResolve();
+    };
+
+    session.close();
+  });
+}
+
+testSessionAvailable().
+then(testSessionReady).
+then(testCloseSession).
+then(finish);
+
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/presentation/tests/mochitest/file_presentation_receiver_oop.html
@@ -0,0 +1,77 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test for B2G Presentation Session API at receiver side (OOP)</title>
+</head>
+<body>
+<div id="content"></div>
+<script type="application/javascript;version=1.7">
+
+"use strict";
+
+function is(a, b, msg) {
+  alert((a === b ? 'OK ' : 'KO ') + msg);
+}
+
+function ok(a, msg) {
+  alert((a ? 'OK ' : 'KO ') + msg);
+}
+
+function info(msg) {
+  alert('INFO ' + msg);
+}
+
+function command(msg) {
+  alert('COMMAND ' + JSON.stringify(msg));
+}
+
+function finish() {
+  alert('DONE');
+}
+
+var session;
+
+function testSessionAvailable() {
+  return new Promise(function(aResolve, aReject) {
+    ok(navigator.presentation, "navigator.presentation should be available.");
+
+    session = navigator.presentation.session;
+    ok(session.id, "Session ID should be set: " + session.id);
+    is(session.state, "disconnected", "Session state at receiver side should be disconnected by default.");
+    aResolve();
+  });
+}
+
+function testSessionReady() {
+  return new Promise(function(aResolve, aReject) {
+    session.onstatechange = function() {
+      session.onstatechange = null;
+      is(session.state, "connected", "Session state should become connected.");
+      aResolve();
+    };
+
+    command({ name: 'trigger-incoming-offer' });
+  });
+}
+
+function testCloseSession() {
+  return new Promise(function(aResolve, aReject) {
+    session.onstatechange = function() {
+      session.onstatechange = null;
+      is(session.state, "terminated", "Session should be terminated.");
+      aResolve();
+    };
+
+    session.close();
+  });
+}
+
+testSessionAvailable().
+then(testSessionReady).
+then(testCloseSession).
+then(finish);
+
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/presentation/tests/mochitest/file_presentation_receiver_start_session_error.html
@@ -0,0 +1,65 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test for startSession errors of B2G Presentation API at receiver side</title>
+</head>
+<body>
+<div id="content"></div>
+<script type="application/javascript;version=1.7">
+
+"use strict";
+
+function is(a, b, msg) {
+  window.parent.postMessage((a === b ? 'OK ' : 'KO ') + msg, '*');
+}
+
+function ok(a, msg) {
+  window.parent.postMessage((a ? 'OK ' : 'KO ') + msg, '*');
+}
+
+function info(msg) {
+  window.parent.postMessage('INFO ' + msg, '*');
+}
+
+function command(msg) {
+  window.parent.postMessage('COMMAND ' + JSON.stringify(msg), '*');
+}
+
+function finish() {
+  window.parent.postMessage('DONE', '*');
+}
+
+var session;
+
+function testSessionAvailable() {
+  return new Promise(function(aResolve, aReject) {
+    ok(navigator.presentation, "navigator.presentation should be available.");
+
+    session = navigator.presentation.session;
+    ok(session.id, "Session ID should be set: " + session.id);
+    is(session.state, "disconnected", "Session state at receiver side should be disconnected by default.");
+    aResolve();
+  });
+}
+
+function testUnexpectedControlChannelClose() {
+  return new Promise(function(aResolve, aReject) {
+    session.onstatechange = function() {
+      session.onstatechange = null;
+      is(session.state, "terminated", "Session state should become terminated.");
+      aResolve();
+    };
+
+    // Trigger the control channel to be closed with error code.
+    command({ name: 'trigger-control-channel-close', data: 0x80004004 /* NS_ERROR_ABORT */ });
+  });
+}
+
+testSessionAvailable().
+then(testUnexpectedControlChannelClose).
+then(finish);
+
+</script>
+</body>
+</html>
--- a/dom/presentation/tests/mochitest/mochitest.ini
+++ b/dom/presentation/tests/mochitest/mochitest.ini
@@ -1,6 +1,24 @@
 [DEFAULT]
 support-files =
   PresentationDeviceInfoChromeScript.js
+  PresentationSessionChromeScript.js
+  file_presentation_receiver.html
+  file_presentation_receiver_oop.html
+  file_presentation_receiver_start_session_error.html
 
 [test_presentation_device_info.html]
 [test_presentation_device_info_permission.html]
+[test_presentation_sender_disconnect.html]
+skip-if = toolkit == 'android' # Bug 1129785
+[test_presentation_sender_start_session_error.html]
+skip-if = toolkit == 'android' # Bug 1129785
+[test_presentation_sender.html]
+skip-if = toolkit == 'android' # Bug 1129785
+[test_presentation_receiver_start_session_error.html]
+skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android') # Bug 1129785
+[test_presentation_receiver_start_session_timeout.html]
+skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android') # Bug 1129785
+[test_presentation_receiver.html]
+skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android') # Bug 1129785
+[test_presentation_receiver_oop.html]
+skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android') # Bug 1129785
new file mode 100644
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_receiver.html
@@ -0,0 +1,122 @@
+<!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 Presentation Session API at receiver side</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.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=1069230">Test for B2G Presentation Session API at receiver side</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 obs = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
+          .getService(SpecialPowers.Ci.nsIObserverService);
+
+function setup() {
+  return new Promise(function(aResolve, aReject) {
+    gScript.sendAsyncMessage('trigger-device-add');
+
+    var iframe = document.createElement('iframe');
+    iframe.setAttribute('src', receiverUrl);
+
+    // This event is triggered when the iframe calls "postMessage".
+    window.addEventListener('message', function listener(aEvent) {
+      var message = aEvent.data;
+      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.");
+        window.removeEventListener('message', listener);
+
+        teardown();
+      }
+    }, false);
+
+    var promise = new Promise(function(aResolve, aReject) {
+      document.body.appendChild(iframe);
+
+      aResolve(iframe);
+    });
+    obs.notifyObservers(promise, 'setup-request-promise', null);
+
+    gScript.addMessageListener('offer-received', function offerReceivedHandler() {
+      gScript.removeMessageListener('offer-received', offerReceivedHandler);
+      info("An offer is received.");
+    });
+
+    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.");
+    });
+
+    gScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
+      gScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
+      is(aReason, 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);
+  });
+}
+
+function teardown() {
+  gScript.addMessageListener('teardown-complete', function teardownCompleteHandler() {
+    gScript.removeMessageListener('teardown-complete', teardownCompleteHandler);
+    gScript.destroy();
+    SimpleTest.finish();
+  });
+
+  gScript.sendAsyncMessage('teardown');
+}
+
+function runTests() {
+  setup().
+  then(testIncomingSessionRequest);
+}
+
+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.ignore_webidl_scope_checks", true],
+                                      ["dom.presentation.test.enabled", true],
+                                      ["dom.presentation.test.stage", 0]]},
+                            runTests);
+});
+
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_receiver_oop.html
@@ -0,0 +1,131 @@
+<!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 Presentation Session API at receiver side (OOP)</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.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=1069230">Test B2G Presentation Session 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_oop.html');
+
+var obs = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
+          .getService(SpecialPowers.Ci.nsIObserverService);
+
+function setup() {
+  return new Promise(function(aResolve, aReject) {
+    gScript.sendAsyncMessage('trigger-device-add');
+
+    SpecialPowers.addPermission('presentation', true, { url: receiverUrl,
+                                                        appId: SpecialPowers.Ci.nsIScriptSecurityManager.NO_APP_ID,
+                                                        isInBrowserElement: true });
+
+    var iframe = document.createElement('iframe');
+    iframe.setAttribute('remote', 'true');
+    iframe.setAttribute('mozbrowser', 'true');
+    iframe.setAttribute('src', receiverUrl);
+
+    // This event is triggered when the iframe calls "alert".
+    iframe.addEventListener('mozbrowsershowmodalprompt', function listener(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.");
+        iframe.removeEventListener('mozbrowsershowmodalprompt', listener);
+
+        teardown();
+      }
+    }, false);
+
+    var promise = new Promise(function(aResolve, aReject) {
+      document.body.appendChild(iframe);
+
+      aResolve(iframe);
+    });
+    obs.notifyObservers(promise, 'setup-request-promise', null);
+
+    gScript.addMessageListener('offer-received', function offerReceivedHandler() {
+      gScript.removeMessageListener('offer-received', offerReceivedHandler);
+      info("An offer is received.");
+    });
+
+    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.");
+    });
+
+    gScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
+      gScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
+      is(aReason, 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);
+  });
+}
+
+function teardown() {
+  gScript.addMessageListener('teardown-complete', function teardownCompleteHandler() {
+    gScript.removeMessageListener('teardown-complete', teardownCompleteHandler);
+    gScript.destroy();
+    SimpleTest.finish();
+  });
+
+  gScript.sendAsyncMessage('teardown');
+}
+
+function runTests() {
+  setup().
+  then(testIncomingSessionRequest);
+}
+
+SimpleTest.expectAssertions(0, 5);
+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.ignore_webidl_scope_checks", true],
+                                      ["dom.presentation.test.enabled", true],
+                                      ["dom.presentation.test.stage", 0],
+                                      ["dom.mozBrowserFramesEnabled", true],
+                                      ["dom.ipc.browser_frames.oop_by_default", true]]},
+                            runTests);
+});
+
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_receiver_start_session_error.html
@@ -0,0 +1,110 @@
+<!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 startSession errors of B2G Presentation API at receiver side</title>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1069230">Test for startSession errors of B2G Presentation API at receiver side</a>
+<script type="application/javascript;version=1.8">
+
+'use strict';
+
+var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('PresentationSessionChromeScript.js'));
+var receiverUrl = SimpleTest.getTestFileURL('file_presentation_receiver_start_session_error.html');
+
+var obs = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
+          .getService(SpecialPowers.Ci.nsIObserverService);
+var session;
+
+function setup() {
+  return new Promise(function(aResolve, aReject) {
+    gScript.sendAsyncMessage('trigger-device-add');
+
+    var iframe = document.createElement('iframe');
+    iframe.setAttribute('src', receiverUrl);
+
+    // This event is triggered when the iframe calls "postMessage".
+    window.addEventListener('message', function listener(aEvent) {
+      var message = aEvent.data;
+      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.");
+        window.removeEventListener('message', listener);
+
+        teardown();
+      }
+    }, false);
+
+    var promise = new Promise(function(aResolve, aReject) {
+      document.body.appendChild(iframe);
+
+      aResolve(iframe);
+    });
+    obs.notifyObservers(promise, 'setup-request-promise', null);
+
+    gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
+      gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
+      is(aReason, 0x80004004 /* NS_ERROR_ABORT */, "The control channel is closed abnormally.");
+    });
+
+    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);
+  });
+}
+
+function teardown() {
+  gScript.addMessageListener('teardown-complete', function teardownCompleteHandler() {
+    gScript.removeMessageListener('teardown-complete', teardownCompleteHandler);
+    gScript.destroy();
+    SimpleTest.finish();
+  });
+
+  gScript.sendAsyncMessage('teardown');
+}
+
+function runTests() {
+  setup().
+  then(testIncomingSessionRequest);
+}
+
+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.ignore_webidl_scope_checks", true],
+                                      ["dom.presentation.test.enabled", true],
+                                      ["dom.presentation.test.stage", 0]]},
+                            runTests);
+});
+
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_receiver_start_session_timeout.html
@@ -0,0 +1,84 @@
+<!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 startSession timeout of B2G Presentation API at receiver side</title>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1069230">Test for startSession timeout of B2G Presentation API at receiver side</a>
+<script type="application/javascript;version=1.8">
+
+'use strict';
+
+var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('PresentationSessionChromeScript.js'));
+
+var obs = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
+          .getService(SpecialPowers.Ci.nsIObserverService);
+
+function setup() {
+  return new Promise(function(aResolve, aReject) {
+    gScript.sendAsyncMessage('trigger-device-add');
+
+    var promise = new Promise(function(aResolve, aReject) {
+      // In order to trigger timeout, do not resolve the promise.
+    });
+    obs.notifyObservers(promise, 'setup-request-promise', null);
+
+    aResolve();
+  });
+}
+
+function testIncomingSessionRequestReceiverLaunchTimeout() {
+  return new Promise(function(aResolve, aReject) {
+    gScript.addMessageListener('receiver-launching', function launchReceiverHandler(aSessionId) {
+      gScript.removeMessageListener('receiver-launching', launchReceiverHandler);
+      info("Trying to launch receiver page.");
+    });
+
+    gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
+      gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
+      is(aReason, 0x80530017 /* NS_ERROR_DOM_TIMEOUT_ERR */, "The control channel is closed due to timeout.");
+      aResolve();
+    });
+
+    gScript.sendAsyncMessage('trigger-incoming-session-request', 'http://example.com');
+  });
+}
+
+function teardown() {
+  gScript.addMessageListener('teardown-complete', function teardownCompleteHandler() {
+    gScript.removeMessageListener('teardown-complete', teardownCompleteHandler);
+    gScript.destroy();
+    SimpleTest.finish();
+  });
+
+  gScript.sendAsyncMessage('teardown');
+}
+
+function runTests() {
+  setup().
+  then(testIncomingSessionRequestReceiverLaunchTimeout).
+  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.ignore_webidl_scope_checks", true],
+                                      ["dom.presentation.test.enabled", true],
+                                      ["dom.presentation.test.stage", 0],
+                                      ["presentation.receiver.loading.timeout", 10]]},
+                            runTests);
+});
+
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_sender.html
@@ -0,0 +1,167 @@
+<!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 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>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1069230">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 presentation;
+var session;
+
+function testSetup() {
+  return new Promise(function(aResolve, aReject) {
+    presentation.onavailablechange = function(aIsAvailable) {
+      presentation.onavailablechange = null;
+      ok(aIsAvailable, "Device should be available.");
+      aResolve();
+    };
+
+    gScript.sendAsyncMessage('trigger-device-add');
+  });
+}
+
+function testStartSession() {
+  return new Promise(function(aResolve, aReject) {
+    gScript.addMessageListener('device-prompt', function devicePromptHandler() {
+      gScript.removeMessageListener('device-prompt', devicePromptHandler);
+      info("Device prompt is triggered.");
+      gScript.sendAsyncMessage('trigger-device-prompt-select');
+    });
+
+    gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
+      gScript.removeMessageListener('control-channel-established', controlChannelEstablishedHandler);
+      info("A control channel is established.");
+    });
+
+    gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
+      gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
+      info("The control channel is closed. " + aReason);
+    });
+
+    gScript.addMessageListener('offer-sent', function offerSentHandler() {
+      gScript.removeMessageListener('offer-sent', offerSentHandler);
+      info("An offer is sent out.");
+      gScript.sendAsyncMessage('trigger-incoming-transport');
+    });
+
+    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);
+      info("Data transport channel is initialized.");
+      gScript.sendAsyncMessage('trigger-incoming-answer');
+    });
+
+    presentation.startSession("http://example.com").then(
+      function(aSession) {
+        session = aSession;
+        ok(session, "Session should be availlable.");
+        ok(session.id, "Session ID should be set.");
+        is(session.state, "connected", "Session state at sender side should be connected by default.");
+        aResolve();
+      },
+      function(aError) {
+        ok(false, "Error occurred when starting session: " + aError);
+        teardown();
+        aReject();
+      }
+    );
+  });
+}
+
+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);
+      is(aMessage, outgoingMessage, "The message is sent out.");
+      aResolve();
+    });
+
+    session.send(outgoingMessage);
+  });
+}
+
+function testIncomingMessage() {
+  return new Promise(function(aResolve, aReject) {
+    const incomingMessage = "test incoming message";
+
+    session.addEventListener('message', function messageHandler(aEvent) {
+      session.removeEventListener('message', messageHandler);
+      is(aEvent.data, incomingMessage, "An incoming message should be received.");
+      aResolve();
+    });
+
+    gScript.sendAsyncMessage('trigger-incoming-message', incomingMessage);
+  });
+}
+
+function testCloseSession() {
+  return new Promise(function(aResolve, aReject) {
+    gScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
+      gScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
+      info("The data transport is closed. " + aReason);
+    });
+
+    session.onstatechange = function() {
+      session.onstatechange = null;
+      is(session.state, "terminated", "Session should be terminated.");
+      aResolve();
+    };
+
+    session.close();
+  });
+}
+
+function teardown() {
+  gScript.addMessageListener('teardown-complete', function teardownCompleteHandler() {
+    gScript.removeMessageListener('teardown-complete', teardownCompleteHandler);
+    gScript.destroy();
+    SimpleTest.finish();
+  });
+
+  gScript.sendAsyncMessage('teardown');
+}
+
+function runTests() {
+  ok(navigator.presentation, "navigator.presentation should be available.");
+  presentation = navigator.presentation;
+
+  testSetup().
+  then(testStartSession).
+  then(testSend).
+  then(testIncomingMessage).
+  then(testCloseSession).
+  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.ignore_webidl_scope_checks", true],
+                                      ["dom.presentation.test.enabled", true],
+                                      ["dom.presentation.test.stage", 0]]},
+                            runTests);
+});
+
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_sender_disconnect.html
@@ -0,0 +1,150 @@
+<!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 session disconnection of 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>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1069230">Test for session disconnection of 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 presentation;
+var session;
+
+function testSetup() {
+  return new Promise(function(aResolve, aReject) {
+    presentation.onavailablechange = function(aIsAvailable) {
+      presentation.onavailablechange = null;
+      ok(aIsAvailable, "Device should be available.");
+      aResolve();
+    };
+
+    gScript.sendAsyncMessage('trigger-device-add');
+  });
+}
+
+function testStartSession() {
+  return new Promise(function(aResolve, aReject) {
+    gScript.addMessageListener('device-prompt', function devicePromptHandler() {
+      gScript.removeMessageListener('device-prompt', devicePromptHandler);
+      info("Device prompt is triggered.");
+      gScript.sendAsyncMessage('trigger-device-prompt-select');
+    });
+
+    gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
+      gScript.removeMessageListener('control-channel-established', controlChannelEstablishedHandler);
+      info("A control channel is established.");
+    });
+
+    gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
+      gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
+      info("The control channel is closed. " + aReason);
+    });
+
+    gScript.addMessageListener('offer-sent', function offerSentHandler() {
+      gScript.removeMessageListener('offer-sent', offerSentHandler);
+      info("An offer is sent out.");
+      gScript.sendAsyncMessage('trigger-incoming-answer');
+    });
+
+    gScript.addMessageListener('answer-received', function answerReceivedHandler() {
+      gScript.removeMessageListener('answer-received', answerReceivedHandler);
+      info("An answer is received.");
+      gScript.sendAsyncMessage('trigger-incoming-transport');
+    });
+
+    gScript.addMessageListener('data-transport-initialized', function dataTransportInitializedHandler() {
+      gScript.removeMessageListener('data-transport-initialized', dataTransportInitializedHandler);
+      info("Data transport channel is initialized.");
+    });
+
+    presentation.startSession("http://example.com").then(
+      function(aSession) {
+        session = aSession;
+        ok(session, "Session should be availlable.");
+        ok(session.id, "Session ID should be set.");
+        is(session.state, "connected", "Session state at sender side should be connected by default.");
+        aResolve();
+      },
+      function(aError) {
+        ok(false, "Error occurred when starting session: " + aError);
+        teardown();
+        aReject();
+      }
+    );
+  });
+}
+
+function testSessionDisconnection() {
+  return new Promise(function(aResolve, aReject) {
+    gScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
+      gScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
+      info("The data transport is closed. " + aReason);
+    });
+
+    session.onstatechange = function() {
+      session.onstatechange = null;
+      is(session.state, "disconnected", "Session should be disconnected.");
+      aResolve();
+    };
+
+    gScript.sendAsyncMessage('trigger-data-transport-close', SpecialPowers.Cr.NS_ERROR_FAILURE);
+  });
+}
+
+function testCloseSession() {
+  return new Promise(function(aResolve, aReject) {
+    session.onstatechange = function() {
+      session.onstatechange = null;
+      is(session.state, "terminated", "Session should be terminated.");
+      aResolve();
+    };
+
+    session.close();
+  });
+}
+
+function teardown() {
+  gScript.addMessageListener('teardown-complete', function teardownCompleteHandler() {
+    gScript.removeMessageListener('teardown-complete', teardownCompleteHandler);
+    gScript.destroy();
+    SimpleTest.finish();
+  });
+
+  gScript.sendAsyncMessage('teardown');
+}
+
+function runTests() {
+  ok(navigator.presentation, "navigator.presentation should be available.");
+  presentation = navigator.presentation;
+
+  testSetup().
+  then(testStartSession).
+  then(testSessionDisconnection).
+  then(testCloseSession).
+  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.ignore_webidl_scope_checks", true],
+                                      ["dom.presentation.test.enabled", true],
+                                      ["dom.presentation.test.stage", 0]]},
+                            runTests);
+});
+
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_sender_start_session_error.html
@@ -0,0 +1,239 @@
+<!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 startSession errors of 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>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1069230">Test for startSession errors of 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 presentation;
+
+function setup() {
+  return new Promise(function(aResolve, aReject) {
+    presentation.onavailablechange = function(aIsAvailable) {
+      presentation.onavailablechange = null;
+      ok(aIsAvailable, "Device should be available.");
+      aResolve();
+    };
+
+    gScript.sendAsyncMessage('trigger-device-add');
+  });
+}
+
+function testStartSessionNoAvailableDevice() {
+  return new Promise(function(aResolve, aReject) {
+    presentation.startSession("http://example.com").then(
+      function(aSession) {
+        ok(false, "startSession shouldn't succeed in this case.");
+        aReject();
+      },
+      function(aError) {
+        is(aError.name, "InvalidStateError", "InvalidStateError is expected when starting session.");
+        aResolve();
+      }
+    );
+  });
+}
+
+function testStartSessionCancelPrompt() {
+  return new Promise(function(aResolve, aReject) {
+    gScript.addMessageListener('device-prompt', function devicePromptHandler() {
+      gScript.removeMessageListener('device-prompt', devicePromptHandler);
+      info("Device prompt is triggered.");
+      gScript.sendAsyncMessage('trigger-device-prompt-cancel');
+    });
+
+    presentation.startSession("http://example.com").then(
+      function(aSession) {
+        ok(false, "startSession shouldn't succeed in this case.");
+        aReject();
+      },
+      function(aError) {
+        is(aError.name, "NS_ERROR_DOM_PROP_ACCESS_DENIED", "NS_ERROR_DOM_PROP_ACCESS_DENIED is expected when starting session.");
+        aResolve();
+      }
+    );
+  });
+}
+
+function testStartSessionUnexpectedControlChannelCloseBeforeDataTransportInit() {
+  return new Promise(function(aResolve, aReject) {
+    gScript.addMessageListener('device-prompt', function devicePromptHandler() {
+      gScript.removeMessageListener('device-prompt', devicePromptHandler);
+      info("Device prompt is triggered.");
+      gScript.sendAsyncMessage('trigger-device-prompt-select');
+    });
+
+    gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
+      gScript.removeMessageListener('control-channel-established', controlChannelEstablishedHandler);
+      info("A control channel is established.");
+    });
+
+    gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
+      gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
+      info("The control channel is closed. " + aReason);
+    });
+
+    gScript.addMessageListener('offer-sent', function offerSentHandler() {
+      gScript.removeMessageListener('offer-sent', offerSentHandler);
+      info("An offer is sent out.");
+      gScript.sendAsyncMessage('trigger-control-channel-close', SpecialPowers.Cr.NS_ERROR_FAILURE);
+    });
+
+    presentation.startSession("http://example.com").then(
+      function(aSession) {
+        ok(false, "startSession shouldn't succeed in this case.");
+        aReject();
+      },
+      function(aError) {
+        is(aError.name, "NS_ERROR_FAILURE", "NS_ERROR_FAILURE is expected when starting session.");
+        aResolve();
+      }
+    );
+  });
+}
+
+function testStartSessionUnexpectedControlChannelCloseBeforeDataTransportReady() {
+  return new Promise(function(aResolve, aReject) {
+    gScript.addMessageListener('device-prompt', function devicePromptHandler() {
+      gScript.removeMessageListener('device-prompt', devicePromptHandler);
+      info("Device prompt is triggered.");
+      gScript.sendAsyncMessage('trigger-device-prompt-select');
+    });
+
+    gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
+      gScript.removeMessageListener('control-channel-established', controlChannelEstablishedHandler);
+      info("A control channel is established.");
+    });
+
+    gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
+      gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
+      info("The control channel is closed. " + aReason);
+    });
+
+    gScript.addMessageListener('offer-sent', function offerSentHandler() {
+      gScript.removeMessageListener('offer-sent', offerSentHandler);
+      info("An offer is sent out.");
+      gScript.sendAsyncMessage('trigger-incoming-transport');
+    });
+
+    gScript.addMessageListener('data-transport-initialized', function dataTransportInitializedHandler() {
+      gScript.removeMessageListener('data-transport-initialized', dataTransportInitializedHandler);
+      info("Data transport channel is initialized.");
+      gScript.sendAsyncMessage('trigger-control-channel-close', SpecialPowers.Cr.NS_ERROR_ABORT);
+    });
+
+    gScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
+      gScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
+      info("The data transport is closed. " + aReason);
+    });
+
+    presentation.startSession("http://example.com").then(
+      function(aSession) {
+        ok(false, "startSession shouldn't succeed in this case.");
+        aReject();
+      },
+      function(aError) {
+        is(aError.name, "NS_ERROR_ABORT", "NS_ERROR_ABORT is expected when starting session.");
+        aResolve();
+      }
+    );
+  });
+}
+
+function testStartSessionUnexpectedDataTransportClose() {
+  return new Promise(function(aResolve, aReject) {
+    gScript.addMessageListener('device-prompt', function devicePromptHandler() {
+      gScript.removeMessageListener('device-prompt', devicePromptHandler);
+      info("Device prompt is triggered.");
+      gScript.sendAsyncMessage('trigger-device-prompt-select');
+    });
+
+    gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
+      gScript.removeMessageListener('control-channel-established', controlChannelEstablishedHandler);
+      info("A control channel is established.");
+    });
+
+    gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
+      gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
+      info("The control channel is closed. " + aReason);
+    });
+
+    gScript.addMessageListener('offer-sent', function offerSentHandler() {
+      gScript.removeMessageListener('offer-sent', offerSentHandler);
+      info("An offer is sent out.");
+      gScript.sendAsyncMessage('trigger-incoming-transport');
+    });
+
+    gScript.addMessageListener('data-transport-initialized', function dataTransportInitializedHandler() {
+      gScript.removeMessageListener('data-transport-initialized', dataTransportInitializedHandler);
+      info("Data transport channel is initialized.");
+      gScript.sendAsyncMessage('trigger-data-transport-close', SpecialPowers.Cr.NS_ERROR_UNEXPECTED);
+    });
+
+    gScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
+      gScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
+      info("The data transport is closed. " + aReason);
+    });
+
+    presentation.startSession("http://example.com").then(
+      function(aSession) {
+        ok(false, "startSession shouldn't succeed in this case.");
+        aReject();
+      },
+      function(aError) {
+        is(aError.name, "NS_ERROR_UNEXPECTED", "NS_ERROR_UNEXPECTED is expected when starting session.");
+        aResolve();
+      }
+    );
+  });
+}
+
+function teardown() {
+  gScript.addMessageListener('teardown-complete', function teardownCompleteHandler() {
+    gScript.removeMessageListener('teardown-complete', teardownCompleteHandler);
+    gScript.destroy();
+    SimpleTest.finish();
+  });
+
+  gScript.sendAsyncMessage('teardown');
+}
+
+function runTests() {
+  ok(navigator.presentation, "navigator.presentation should be available.");
+  presentation = navigator.presentation;
+
+  testStartSessionNoAvailableDevice().
+  then(setup).
+  then(testStartSessionCancelPrompt).
+  then(testStartSessionUnexpectedControlChannelCloseBeforeDataTransportInit).
+  then(testStartSessionUnexpectedControlChannelCloseBeforeDataTransportReady).
+  then(testStartSessionUnexpectedDataTransportClose).
+  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.ignore_webidl_scope_checks", true],
+                                      ["dom.presentation.test.enabled", true],
+                                      ["dom.presentation.test.stage", 0]]},
+                            runTests);
+});
+
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/presentation/tests/xpcshell/test_presentation_session_transport.js
@@ -0,0 +1,171 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+'use strict';
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr, Constructor: CC } = Components;
+const ServerSocket = CC("@mozilla.org/network/server-socket;1",
+                        "nsIServerSocket",
+                        "init");
+
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+Cu.import('resource://gre/modules/Services.jsm');
+
+var testServer = null;
+var clientTransport = null;
+var serverTransport = null;
+
+const clientMessage = "Client Message";
+const serverMessage = "Server Message";
+
+const address = Cc["@mozilla.org/supports-cstring;1"]
+                  .createInstance(Ci.nsISupportsCString);
+address.data = "127.0.0.1";
+const addresses = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
+addresses.appendElement(address, false);
+
+const serverChannelDescription = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationChannelDescription]),
+  type: 1,
+  tcpAddress: addresses,
+};
+
+var isClientReady = false;
+var isServerReady = false;
+var isClientClosed = false;
+var isServerClosed = false;
+
+const clientCallback = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationSessionTransportCallback]),
+  notifyTransportReady: function () {
+    Assert.ok(true, "Client transport ready.");
+
+    isClientReady = true;
+    if (isClientReady && isServerReady) {
+      run_next_test();
+    }
+  },
+  notifyTransportClosed: function (aReason) {
+    Assert.ok(true, "Client transport is closed.");
+
+    isClientClosed = true;
+    if (isClientClosed && isServerClosed) {
+      run_next_test();
+    }
+  },
+  notifyData: function(aData) {
+    Assert.equal(aData, serverMessage, "Client transport receives data.");
+    run_next_test();
+  },
+};
+
+const serverCallback = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationSessionTransportCallback]),
+  notifyTransportReady: function () {
+    Assert.ok(true, "Server transport ready.");
+
+    isServerReady = true;
+    if (isClientReady && isServerReady) {
+      run_next_test();
+    }
+  },
+  notifyTransportClosed: function (aReason) {
+    Assert.ok(true, "Server transport is closed.");
+
+    isServerClosed = true;
+    if (isClientClosed && isServerClosed) {
+      run_next_test();
+    }
+  },
+  notifyData: function(aData) {
+    Assert.equal(aData, clientMessage, "Server transport receives data.");
+    run_next_test();
+  },
+};
+
+function TestServer() {
+  this.serverSocket = ServerSocket(-1, true, -1);
+  this.serverSocket.asyncListen(this)
+}
+
+TestServer.prototype = {
+  onSocketAccepted: function(aSocket, aTransport) {
+    print("Test server gets a client connection.");
+    serverTransport = Cc["@mozilla.org/presentation/presentationsessiontransport;1"]
+                        .createInstance(Ci.nsIPresentationSessionTransport);
+    serverTransport.initWithSocketTransport(aTransport, serverCallback);
+  },
+  onStopListening: function(aSocket) {
+    print("Test server stops listening.");
+  },
+  close: function() {
+    if (this.serverSocket) {
+      this.serverSocket.close();
+      this.serverSocket = null;
+    }
+  }
+};
+
+// Set up the transport connection and ensure |notifyTransportReady| triggered
+// at both sides.
+function setup() {
+  clientTransport = Cc["@mozilla.org/presentation/presentationsessiontransport;1"]
+                      .createInstance(Ci.nsIPresentationSessionTransport);
+  clientTransport.initWithChannelDescription(serverChannelDescription, clientCallback);
+}
+
+// Test |selfAddress| attribute of |nsIPresentationSessionTransport|.
+function selfAddress() {
+  var serverSelfAddress = serverTransport.selfAddress;
+  Assert.equal(serverSelfAddress.address, address.data, "The self address of server transport should be set.");
+  Assert.equal(serverSelfAddress.port, testServer.serverSocket.port, "The port of server transport should be set.");
+
+  var clientSelfAddress = clientTransport.selfAddress;
+  Assert.ok(clientSelfAddress.address, "The self address of client transport should be set.");
+  Assert.ok(clientSelfAddress.port, "The port of client transport should be set.");
+
+  run_next_test();
+}
+
+// Test the client sends a message and then a corresponding notification gets
+// triggered at the server side.
+function clientSendMessage() {
+  var stream = Cc["@mozilla.org/io/string-input-stream;1"]
+                 .createInstance(Ci.nsIStringInputStream);
+  stream.setData(clientMessage, clientMessage.length);
+  clientTransport.send(stream);
+}
+
+// Test the server sends a message an then a corresponding notification gets
+// triggered at the client side.
+function serverSendMessage() {
+  var stream = Cc["@mozilla.org/io/string-input-stream;1"]
+                 .createInstance(Ci.nsIStringInputStream);
+  stream.setData(serverMessage, serverMessage.length);
+  serverTransport.send(stream);
+}
+
+function transportClose() {
+  clientTransport.close(Cr.NS_OK);
+}
+
+function shutdown() {
+  testServer.close();
+  run_next_test();
+}
+
+add_test(setup);
+add_test(selfAddress);
+add_test(clientSendMessage);
+add_test(serverSendMessage);
+add_test(transportClose);
+add_test(shutdown);
+
+function run_test() {
+  testServer = new TestServer();
+  // Get the port of the test server.
+  serverChannelDescription.tcpPort = testServer.serverSocket.port;
+
+  run_next_test();
+}
--- a/dom/presentation/tests/xpcshell/xpcshell.ini
+++ b/dom/presentation/tests/xpcshell/xpcshell.ini
@@ -1,7 +1,8 @@
 [DEFAULT]
 head =
 tail =
 
 [test_multicast_dns_device_provider.js]
 [test_presentation_device_manager.js]
+[test_presentation_session_transport.js]
 [test_tcp_control_channel.js]
--- a/dom/webidl/Navigator.webidl
+++ b/dom/webidl/Navigator.webidl
@@ -425,16 +425,21 @@ partial interface Navigator {
   readonly attribute TVManager? tv;
 };
 
 partial interface Navigator {
   [Throws, Pref="dom.inputport.enabled", CheckAnyPermissions="inputport", AvailableIn=CertifiedApps]
   readonly attribute InputPortManager inputPortManager;
 };
 
+partial interface Navigator {
+  [Throws, Pref="dom.presentation.enabled", CheckAnyPermissions="presentation", AvailableIn="PrivilegedApps"]
+  readonly attribute Presentation? presentation;
+};
+
 #ifdef MOZ_EME
 partial interface Navigator {
   [Pref="media.eme.apiVisible", NewObject]
   Promise<MediaKeySystemAccess>
   requestMediaKeySystemAccess(DOMString keySystem,
                               optional sequence<MediaKeySystemOptions> supportedConfigurations);
 };
 #endif
new file mode 100644
--- /dev/null
+++ b/dom/webidl/Presentation.webidl
@@ -0,0 +1,64 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+[Pref="dom.presentation.enabled",
+ CheckAnyPermissions="presentation",
+ AvailableIn="PrivilegedApps"]
+interface Presentation : EventTarget {
+  /*
+   * A requesting page use startSession() to start a new session, and the
+   * session will be returned with the promise. UA may show a prompt box with a
+   * list of available devices and ask the user to grant permission, choose a
+   * device, or cancel the operation.
+   *
+   * @url: The URL of presenting page.
+   * @sessionId: Optional. If it's not specified, a random alphanumeric value of
+   *             at least 16 characters drawn from the character [A-Za-z0-9] is
+   *             automatically generated as the id of the session.
+   *
+   * The promise is resolved when the presenting page is successfully loaded and
+   * the communication channel is established, i.e., the session state is
+   * "connected".
+   *
+   * The promise may be rejected duo to one of the following reasons:
+   * - "InternalError":        Unexpected internal error occurs.
+   * - "NoDeviceAvailable":    No available device.
+   * - "PermissionDenied":     User dismiss the device prompt box.
+   * - "ControlChannelFailed": Failed to establish control channel.
+   * - "NoApplicationFound":  app:// scheme is supported on Firefox OS, but no
+   *                           corresponding application is found on remote side.
+   * - "PageLoadTimeout":      Presenting page takes too long to load.
+   * - "DataChannelFailed":    Failed to establish data channel.
+   */
+  [Throws]
+  Promise<PresentationSession> startSession(DOMString url,
+                                            optional DOMString sessionId);
+
+  /*
+   * This attribute is only available on the presenting page. It should be
+   * created when loading the presenting page, and it's ready to be used after
+   * 'onload' event is dispatched.
+   */
+  [Pure]
+  readonly attribute PresentationSession? session;
+
+ /*
+  * Device availability. If there is more than one device discovered by UA,
+  * the value is |true|. Otherwise, its value should be |false|.
+  *
+  * UA triggers device discovery mechanism periodically and cache the latest
+  * result in this attribute. Thus, it may be out-of-date when we're not in
+  * discovery mode, however, it is still useful to give the developers an idea
+  * that whether there are devices nearby some time ago.
+  */
+  readonly attribute boolean cachedAvailable;
+
+  /*
+   * It is called when device availability changes. New value is dispatched with
+   * the event.
+   */
+  attribute EventHandler onavailablechange;
+};
new file mode 100644
--- /dev/null
+++ b/dom/webidl/PresentationAvailableEvent.webidl
@@ -0,0 +1,20 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+[Constructor(DOMString typeArg,
+ optional PresentationAvailableEventInit eventInitDict),
+ Pref="dom.presentation.enabled",
+ CheckAnyPermissions="presentation",
+ AvailableIn="PrivilegedApps"]
+interface PresentationAvailableEvent : Event
+{
+  readonly attribute boolean available;
+};
+
+dictionary PresentationAvailableEventInit : EventInit
+{
+  boolean available = false;
+};
new file mode 100644
--- /dev/null
+++ b/dom/webidl/PresentationSession.webidl
@@ -0,0 +1,70 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+enum PresentationSessionState
+{
+  // Existing presentation, and the communication channel is active.
+  "connected",
+
+  // Existing presentation, but the communication channel is inactive.
+  "disconnected",
+
+  // The presentation is nonexistent anymore. It could be terminated manually,
+  // or either requesting page or presenting page is no longer available.
+  "terminated"
+};
+
+[Pref="dom.presentation.enabled",
+ CheckAnyPermissions="presentation",
+ AvailableIn="PrivilegedApps"]
+interface PresentationSession : EventTarget {
+  /*
+   * Unique id for all existing sessions.
+   */
+  [Constant]
+  readonly attribute DOMString id;
+
+  /*
+   * Please refer to PresentationSessionStateEvent.webidl for the declaration of
+   * PresentationSessionState.
+   *
+   * @value "connected", "disconnected", or "terminated".
+   */
+  readonly attribute PresentationSessionState state;
+
+  /*
+   * It is called when session state changes. New state is dispatched with the
+   * event.
+   */
+  attribute EventHandler onstatechange;
+
+  /*
+   * After a communication channel has been established between the requesting
+   * page and the presenting page, send() is called to send message out, and the
+   * event handler "onmessage" will be invoked on the remote side.
+   *
+   * This function only works when state equals "connected".
+   *
+   * @data: String literal-only for current implementation.
+   */
+  [Throws]
+  void send(DOMString data);
+
+  /*
+   * It is triggered when receiving messages.
+   */
+  attribute EventHandler onmessage;
+
+  /*
+   * Both the requesting page and the presenting page can close the session by
+   * calling terminate(). Then, the session is destroyed and its state is
+   * truned into "terminated". After getting into the state of "terminated",
+   * resumeSession() is incapable of re-establishing the connection.
+   *
+   * This function does nothing if the state has already been "terminated".
+   */
+  void close();
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -360,17 +360,19 @@ WEBIDL_FILES = [
     'PermissionStatus.webidl',
     'PhoneNumberService.webidl',
     'Plugin.webidl',
     'PluginArray.webidl',
     'PointerEvent.webidl',
     'PopupBoxObject.webidl',
     'Position.webidl',
     'PositionError.webidl',
+    'Presentation.webidl',
     'PresentationDeviceInfoManager.webidl',
+    'PresentationSession.webidl',
     'ProcessingInstruction.webidl',
     'ProfileTimelineMarker.webidl',
     'Promise.webidl',
     'PromiseDebugging.webidl',
     'RadioNodeList.webidl',
     'Range.webidl',
     'Rect.webidl',
     'Request.webidl',
@@ -778,16 +780,17 @@ GENERATED_EVENTS_WEBIDL_FILES = [
     'MozSmsEvent.webidl',
     'MozStkCommandEvent.webidl',
     'MozVoicemailEvent.webidl',
     'PageTransitionEvent.webidl',
     'PerformanceEntryEvent.webidl',
     'PluginCrashedEvent.webidl',
     'PopStateEvent.webidl',
     'PopupBlockedEvent.webidl',
+    'PresentationAvailableEvent.webidl',
     'ProgressEvent.webidl',
     'RecordErrorEvent.webidl',
     'ScrollViewChangeEvent.webidl',
     'SelectionStateChangedEvent.webidl',
     'StyleRuleChangeEvent.webidl',
     'StyleSheetApplicableStateChangeEvent.webidl',
     'StyleSheetChangeEvent.webidl',
     'TrackEvent.webidl',
--- a/layout/build/nsLayoutModule.cpp
+++ b/layout/build/nsLayoutModule.cpp
@@ -235,16 +235,17 @@ static void Shutdown();
 
 #include "mozilla/dom/DataStoreService.h"
 
 #include "mozilla/dom/power/PowerManagerService.h"
 #include "mozilla/dom/alarm/AlarmHalService.h"
 #include "mozilla/dom/time/TimeService.h"
 #include "StreamingProtocolService.h"
 
+#include "nsIPresentationService.h"
 #include "nsITelephonyService.h"
 #include "nsIVoicemailService.h"
 
 #include "mozilla/dom/FakeTVService.h"
 #include "mozilla/dom/TVServiceFactory.h"
 #include "mozilla/dom/TVTypes.h"
 #include "nsITVService.h"
 
@@ -255,17 +256,18 @@ static void Shutdown();
 
 #ifdef MOZ_WIDGET_GONK
 #include "GonkGPSGeolocationProvider.h"
 #endif
 #include "MediaManager.h"
 
 #include "GMPService.h"
 
-#include "mozilla/dom/presentation/PresentationDeviceManager.h"
+#include "mozilla/dom/PresentationDeviceManager.h"
+#include "mozilla/dom/PresentationSessionTransport.h"
 
 #include "mozilla/TextInputProcessor.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using mozilla::dom::alarm::AlarmHalService;
 using mozilla::dom::power::PowerManagerService;
 using mozilla::dom::quota::QuotaManager;
@@ -288,16 +290,21 @@ using mozilla::gmp::GeckoMediaPluginServ
 #define TRANSFORMIIX_NODESET_CONTRACTID \
 "@mozilla.org/transformiix-nodeset;1"
 
 // PresentationDeviceManager
 /* e1e79dec-4085-4994-ac5b-744b016697e6 */
 #define PRESENTATION_DEVICE_MANAGER_CID \
 { 0xe1e79dec, 0x4085, 0x4994, { 0xac, 0x5b, 0x74, 0x4b, 0x01, 0x66, 0x97, 0xe6 } }
 
+#define PRESENTATION_SESSION_TRANSPORT_CID \
+{ 0xc9d023f4, 0x6228, 0x4c07, { 0x8b, 0x1d, 0x9c, 0x19, 0x57, 0x3f, 0xaa, 0x27 } }
+
+already_AddRefed<nsIPresentationService> NS_CreatePresentationService();
+
 // Factory Constructor
 NS_GENERIC_FACTORY_CONSTRUCTOR(txMozillaXSLTProcessor)
 NS_GENERIC_FACTORY_CONSTRUCTOR(XPathEvaluator)
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(txNodeSetAdaptor, Init)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsDOMSerializer)
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsXMLHttpRequest, Init)
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsDOMFileReader, Init)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsFormData)
@@ -396,16 +403,19 @@ NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR
 NS_GENERIC_FACTORY_CONSTRUCTOR(TVTunerData)
 NS_GENERIC_FACTORY_CONSTRUCTOR(TVChannelData)
 NS_GENERIC_FACTORY_CONSTRUCTOR(TVProgramData)
 NS_GENERIC_FACTORY_CONSTRUCTOR(PresentationDeviceManager)
 NS_GENERIC_FACTORY_CONSTRUCTOR(TextInputProcessor)
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(FakeInputPortService,
                                          InputPortServiceFactory::CreateFakeInputPortService)
 NS_GENERIC_FACTORY_CONSTRUCTOR(InputPortData)
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIPresentationService,
+                                         NS_CreatePresentationService)
+NS_GENERIC_FACTORY_CONSTRUCTOR(PresentationSessionTransport)
 //-----------------------------------------------------------------------------
 
 static bool gInitialized = false;
 
 // Perform our one-time intialization for this module
 
 // static
 nsresult
@@ -852,17 +862,19 @@ NS_DEFINE_NAMED_CID(TV_TUNER_DATA_CID);
 NS_DEFINE_NAMED_CID(TV_CHANNEL_DATA_CID);
 NS_DEFINE_NAMED_CID(TV_PROGRAM_DATA_CID);
 
 NS_DEFINE_NAMED_CID(FAKE_INPUTPORT_SERVICE_CID);
 NS_DEFINE_NAMED_CID(INPUTPORT_DATA_CID);
 
 NS_DEFINE_NAMED_CID(GECKO_MEDIA_PLUGIN_SERVICE_CID);
 
+NS_DEFINE_NAMED_CID(PRESENTATION_SERVICE_CID);
 NS_DEFINE_NAMED_CID(PRESENTATION_DEVICE_MANAGER_CID);
+NS_DEFINE_NAMED_CID(PRESENTATION_SESSION_TRANSPORT_CID);
 
 NS_DEFINE_NAMED_CID(TEXT_INPUT_PROCESSOR_CID);
 
 static nsresult
 CreateWindowCommandTableConstructor(nsISupports *aOuter,
                                     REFNSIID aIID, void **aResult)
 {
   nsresult rv;
@@ -1150,17 +1162,19 @@ static const mozilla::Module::CIDEntry k
 #endif
   { &kTELEPHONY_SERVICE_CID, false, nullptr, nsITelephonyServiceConstructor },
   { &kNS_MOBILE_CONNECTION_SERVICE_CID, false, NULL, nsIMobileConnectionServiceConstructor },
   { &kNS_VOICEMAIL_SERVICE_CID, false, nullptr, nsIVoicemailServiceConstructor },
   { &kFAKE_TV_SERVICE_CID, false, nullptr, FakeTVServiceConstructor },
   { &kTV_TUNER_DATA_CID, false, nullptr, TVTunerDataConstructor },
   { &kTV_CHANNEL_DATA_CID, false, nullptr, TVChannelDataConstructor },
   { &kTV_PROGRAM_DATA_CID, false, nullptr, TVProgramDataConstructor },
+  { &kPRESENTATION_SERVICE_CID, false, nullptr, nsIPresentationServiceConstructor },
   { &kPRESENTATION_DEVICE_MANAGER_CID, false, nullptr, PresentationDeviceManagerConstructor },
+  { &kPRESENTATION_SESSION_TRANSPORT_CID, false, nullptr, PresentationSessionTransportConstructor },
   { &kTEXT_INPUT_PROCESSOR_CID, false, nullptr, TextInputProcessorConstructor },
   { &kFAKE_INPUTPORT_SERVICE_CID, false, nullptr, FakeInputPortServiceConstructor },
   { &kINPUTPORT_DATA_CID, false, nullptr, InputPortDataConstructor },
   { nullptr }
 };
 
 static const mozilla::Module::ContractIDEntry kLayoutContracts[] = {
   XPCONNECT_CONTRACTS
@@ -1319,17 +1333,19 @@ static const mozilla::Module::ContractID
   { TELEPHONY_SERVICE_CONTRACTID, &kTELEPHONY_SERVICE_CID },
   { FAKE_TV_SERVICE_CONTRACTID, &kFAKE_TV_SERVICE_CID },
   { TV_TUNER_DATA_CONTRACTID, &kTV_TUNER_DATA_CID },
   { TV_CHANNEL_DATA_CONTRACTID, &kTV_CHANNEL_DATA_CID },
   { TV_PROGRAM_DATA_CONTRACTID, &kTV_PROGRAM_DATA_CID },
   { "@mozilla.org/gecko-media-plugin-service;1",  &kGECKO_MEDIA_PLUGIN_SERVICE_CID },
   { NS_MOBILE_CONNECTION_SERVICE_CONTRACTID, &kNS_MOBILE_CONNECTION_SERVICE_CID },
   { NS_VOICEMAIL_SERVICE_CONTRACTID, &kNS_VOICEMAIL_SERVICE_CID },
+  { PRESENTATION_SERVICE_CONTRACTID, &kPRESENTATION_SERVICE_CID },
   { PRESENTATION_DEVICE_MANAGER_CONTRACTID, &kPRESENTATION_DEVICE_MANAGER_CID },
+  { PRESENTATION_SESSION_TRANSPORT_CONTRACTID, &kPRESENTATION_SESSION_TRANSPORT_CID },
   { "@mozilla.org/text-input-processor;1", &kTEXT_INPUT_PROCESSOR_CID },
   { FAKE_INPUTPORT_SERVICE_CONTRACTID, &kFAKE_INPUTPORT_SERVICE_CID },
   { INPUTPORT_DATA_CONTRACTID, &kINPUTPORT_DATA_CID },
   { nullptr }
 };
 
 static const mozilla::Module::CategoryEntry kLayoutCategories[] = {
   XPCONNECT_CATEGORIES
@@ -1348,16 +1364,17 @@ static const mozilla::Module::CategoryEn
   CONTENTDLF_CATEGORIES
 #ifdef MOZ_WIDGET_GONK
   { "profile-after-change", "Gonk System Worker Manager", SYSTEMWORKERMANAGER_CONTRACTID },
 #endif
 #ifdef MOZ_B2G_BT
   { "profile-after-change", "Bluetooth Service", BLUETOOTHSERVICE_CONTRACTID },
 #endif
   { "profile-after-change", "PresentationDeviceManager", PRESENTATION_DEVICE_MANAGER_CONTRACTID },
+  { "profile-after-change", "PresentationService", PRESENTATION_SERVICE_CONTRACTID },
   { "idle-daily", "ServiceWorker Periodic Updater", SERVICEWORKERPERIODICUPDATER_CONTRACTID },
   { nullptr }
 };
 
 static void
 LayoutModuleDtor()
 {
   Shutdown();