mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-24 10:45:42 +00:00
Merge b2ginbound to central, a=merge
This commit is contained in:
commit
3fc4028393
@ -118,3 +118,6 @@ contract @mozilla.org/app-migrator;1 {7211ece0-b458-4635-9afc-f8d7f376ee95}
|
||||
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}
|
||||
|
62
b2g/components/PresentationRequestUIGlue.js
Normal file
62
b2g/components/PresentationRequestUIGlue.js
Normal file
@ -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]);
|
@ -23,6 +23,7 @@ EXTRA_COMPONENTS += [
|
||||
'OMAContentHandler.js',
|
||||
'PaymentGlue.js',
|
||||
'PaymentProviderStrategy.js',
|
||||
'PresentationRequestUIGlue.js',
|
||||
'ProcessGlobal.js',
|
||||
'SmsProtocolHandler.js',
|
||||
'SystemMessageGlue.js',
|
||||
|
@ -7,6 +7,7 @@ support-files =
|
||||
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]
|
||||
@ -17,3 +18,4 @@ skip-if = true # Bug 1019572 - frequent timeouts
|
||||
[test_systemapp.html]
|
||||
[test_presentation_device_prompt.html]
|
||||
[test_permission_visibilitychange.html]
|
||||
[test_presentation_request_ui_glue.html]
|
||||
|
@ -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);
|
||||
});
|
@ -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>
|
@ -15,7 +15,7 @@
|
||||
<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"/>
|
||||
|
@ -15,7 +15,7 @@
|
||||
<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"/>
|
||||
@ -138,7 +138,7 @@
|
||||
<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>
|
||||
|
@ -19,7 +19,7 @@
|
||||
<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"/>
|
||||
|
@ -17,7 +17,7 @@
|
||||
</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"/>
|
||||
|
@ -15,7 +15,7 @@
|
||||
<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"/>
|
||||
|
@ -15,7 +15,7 @@
|
||||
<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"/>
|
||||
|
@ -19,7 +19,7 @@
|
||||
<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"/>
|
||||
|
@ -15,7 +15,7 @@
|
||||
<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"/>
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -17,7 +17,7 @@
|
||||
</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"/>
|
||||
|
@ -15,7 +15,7 @@
|
||||
<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"/>
|
||||
|
@ -960,6 +960,7 @@ bin/libfreebl_32int64_3.so
|
||||
@RESPATH@/components/SystemMessageGlue.js
|
||||
@RESPATH@/components/B2GAppMigrator.js
|
||||
@RESPATH@/components/B2GPresentationDevicePrompt.js
|
||||
@RESPATH@/components/PresentationRequestUIGlue.js
|
||||
|
||||
#ifndef MOZ_WIDGET_GONK
|
||||
@RESPATH@/components/SimulatorScreen.js
|
||||
|
@ -566,7 +566,13 @@ this.PermissionsTable = { geolocation: {
|
||||
trusted: DENY_ACTION,
|
||||
privileged: DENY_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
}
|
||||
},
|
||||
"presentation": {
|
||||
app: DENY_ACTION,
|
||||
trusted: DENY_ACTION,
|
||||
privileged: ALLOW_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -41,6 +41,7 @@
|
||||
#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"
|
||||
@ -215,6 +216,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Navigator)
|
||||
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
|
||||
|
||||
@ -334,6 +336,10 @@ Navigator::Invalidate()
|
||||
mTimeManager = nullptr;
|
||||
}
|
||||
|
||||
if (mPresentation) {
|
||||
mPresentation = nullptr;
|
||||
}
|
||||
|
||||
mServiceWorkerContainer = nullptr;
|
||||
|
||||
#ifdef MOZ_EME
|
||||
@ -2763,5 +2769,19 @@ Navigator::RequestMediaKeySystemAccess(const nsAString& aKeySystem,
|
||||
|
||||
#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
|
||||
|
@ -97,6 +97,7 @@ class Voicemail;
|
||||
class TVManager;
|
||||
class InputPortManager;
|
||||
class DeviceStorageAreaListener;
|
||||
class Presentation;
|
||||
|
||||
namespace time {
|
||||
class TimeManager;
|
||||
@ -268,6 +269,8 @@ public:
|
||||
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);
|
||||
@ -389,6 +392,7 @@ private:
|
||||
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
|
||||
|
@ -683,6 +683,7 @@ 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")
|
||||
|
@ -171,6 +171,8 @@
|
||||
#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
|
||||
@ -1382,6 +1384,37 @@ ContentChild::SendPBlobConstructor(PBlobChild* aActor,
|
||||
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)
|
||||
|
@ -272,6 +272,11 @@ public:
|
||||
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,
|
||||
|
@ -57,6 +57,8 @@
|
||||
#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"
|
||||
@ -3890,6 +3892,27 @@ ContentParent::DeallocPFMRadioParent(PFMRadioParent* aActor)
|
||||
#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,
|
||||
|
@ -662,6 +662,10 @@ private:
|
||||
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,
|
||||
|
@ -51,6 +51,7 @@ include protocol PVoicemail;
|
||||
include protocol PJavaScript;
|
||||
include protocol PRemoteSpellcheckEngine;
|
||||
include protocol PWebrtcGlobal;
|
||||
include protocol PPresentation;
|
||||
include DOMTypes;
|
||||
include JavaScriptTypes;
|
||||
include InputStreamParams;
|
||||
@ -452,6 +453,7 @@ prio(normal upto urgent) sync protocol PContent
|
||||
manages PJavaScript;
|
||||
manages PRemoteSpellcheckEngine;
|
||||
manages PWebrtcGlobal;
|
||||
manages PPresentation;
|
||||
|
||||
both:
|
||||
// Depending on exactly how the new browser is being created, it might be
|
||||
@ -654,6 +656,13 @@ child:
|
||||
* 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
|
||||
@ -777,6 +786,8 @@ parent:
|
||||
PAsmJSCacheEntry(OpenMode openMode, WriteParams write, Principal principal);
|
||||
|
||||
PWebrtcGlobal();
|
||||
|
||||
PPresentation();
|
||||
|
||||
// Services remoting
|
||||
|
||||
|
220
dom/presentation/Presentation.cpp
Normal file
220
dom/presentation/Presentation.cpp
Normal file
@ -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();
|
||||
}
|
54
dom/presentation/Presentation.h
Normal file
54
dom/presentation/Presentation.h
Normal file
@ -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
|
185
dom/presentation/PresentationCallbacks.cpp
Normal file
185
dom/presentation/PresentationCallbacks.cpp
Normal file
@ -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;
|
||||
}
|
68
dom/presentation/PresentationCallbacks.h
Normal file
68
dom/presentation/PresentationCallbacks.h
Normal file
@ -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
|
555
dom/presentation/PresentationService.cpp
Normal file
555
dom/presentation/PresentationService.cpp
Normal file
@ -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();
|
||||
}
|
68
dom/presentation/PresentationService.h
Normal file
68
dom/presentation/PresentationService.h
Normal file
@ -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
|
284
dom/presentation/PresentationSession.cpp
Normal file
284
dom/presentation/PresentationSession.cpp
Normal file
@ -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();
|
||||
}
|
60
dom/presentation/PresentationSession.h
Normal file
60
dom/presentation/PresentationSession.h
Normal file
@ -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
|
810
dom/presentation/PresentationSessionInfo.cpp
Normal file
810
dom/presentation/PresentationSessionInfo.cpp
Normal file
@ -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);
|
||||
}
|
206
dom/presentation/PresentationSessionInfo.h
Normal file
206
dom/presentation/PresentationSessionInfo.h
Normal file
@ -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
|
484
dom/presentation/PresentationSessionTransport.cpp
Normal file
484
dom/presentation/PresentationSessionTransport.cpp
Normal file
@ -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);
|
||||
}
|
99
dom/presentation/PresentationSessionTransport.h
Normal file
99
dom/presentation/PresentationSessionTransport.h
Normal file
@ -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
|
@ -10,7 +10,11 @@ XPIDL_SOURCES += [
|
||||
'nsIPresentationDeviceManager.idl',
|
||||
'nsIPresentationDevicePrompt.idl',
|
||||
'nsIPresentationDeviceProvider.idl',
|
||||
'nsIPresentationListener.idl',
|
||||
'nsIPresentationRequestUIGlue.idl',
|
||||
'nsIPresentationService.idl',
|
||||
'nsIPresentationSessionRequest.idl',
|
||||
'nsIPresentationSessionTransport.idl',
|
||||
'nsITCPPresentationServer.idl',
|
||||
]
|
||||
|
||||
|
@ -63,7 +63,7 @@ interface nsIPresentationControlChannelListener: nsISupports
|
||||
* 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.
|
||||
@ -87,7 +87,14 @@ interface nsIPresentationControlChannel: nsISupports
|
||||
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);
|
||||
};
|
||||
|
34
dom/presentation/interfaces/nsIPresentationListener.idl
Normal file
34
dom/presentation/interfaces/nsIPresentationListener.idl
Normal file
@ -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);
|
||||
};
|
25
dom/presentation/interfaces/nsIPresentationRequestUIGlue.idl
Normal file
25
dom/presentation/interfaces/nsIPresentationRequestUIGlue.idl
Normal file
@ -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);
|
||||
};
|
112
dom/presentation/interfaces/nsIPresentationService.idl
Normal file
112
dom/presentation/interfaces/nsIPresentationService.idl
Normal file
@ -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);
|
||||
};
|
@ -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);
|
||||
};
|
68
dom/presentation/ipc/PPresentation.ipdl
Normal file
68
dom/presentation/ipc/PPresentation.ipdl
Normal file
@ -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
|
21
dom/presentation/ipc/PPresentationRequest.ipdl
Normal file
21
dom/presentation/ipc/PPresentationRequest.ipdl
Normal file
@ -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
|
130
dom/presentation/ipc/PresentationChild.cpp
Normal file
130
dom/presentation/ipc/PresentationChild.cpp
Normal file
@ -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;
|
||||
}
|
75
dom/presentation/ipc/PresentationChild.h
Normal file
75
dom/presentation/ipc/PresentationChild.h
Normal file
@ -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
|
209
dom/presentation/ipc/PresentationIPCService.cpp
Normal file
209
dom/presentation/ipc/PresentationIPCService.cpp
Normal file
@ -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);
|
||||
}
|
56
dom/presentation/ipc/PresentationIPCService.h
Normal file
56
dom/presentation/ipc/PresentationIPCService.h
Normal file
@ -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
|
265
dom/presentation/ipc/PresentationParent.cpp
Normal file
265
dom/presentation/ipc/PresentationParent.cpp
Normal file
@ -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;
|
||||
}
|
96
dom/presentation/ipc/PresentationParent.h
Normal file
96
dom/presentation/ipc/PresentationParent.h
Normal file
@ -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__
|
@ -9,13 +9,31 @@ 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 += [
|
||||
@ -27,6 +45,11 @@ EXTRA_JS_MODULES += [
|
||||
'PresentationDeviceInfoManager.jsm',
|
||||
]
|
||||
|
||||
IPDL_SOURCES += [
|
||||
'ipc/PPresentation.ipdl',
|
||||
'ipc/PPresentationRequest.ipdl'
|
||||
]
|
||||
|
||||
FAIL_ON_WARNINGS = True
|
||||
|
||||
include('/ipc/chromium/chromium-config.mozbuild')
|
||||
|
@ -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);
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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
|
||||
|
122
dom/presentation/tests/mochitest/test_presentation_receiver.html
Normal file
122
dom/presentation/tests/mochitest/test_presentation_receiver.html
Normal file
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
167
dom/presentation/tests/mochitest/test_presentation_sender.html
Normal file
167
dom/presentation/tests/mochitest/test_presentation_sender.html
Normal file
@ -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>
|
@ -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>
|
@ -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>
|
@ -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();
|
||||
}
|
@ -4,4 +4,5 @@ tail =
|
||||
|
||||
[test_multicast_dns_device_provider.js]
|
||||
[test_presentation_device_manager.js]
|
||||
[test_presentation_session_transport.js]
|
||||
[test_tcp_control_channel.js]
|
||||
|
@ -430,6 +430,11 @@ partial interface Navigator {
|
||||
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]
|
||||
|
64
dom/webidl/Presentation.webidl
Normal file
64
dom/webidl/Presentation.webidl
Normal file
@ -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;
|
||||
};
|
20
dom/webidl/PresentationAvailableEvent.webidl
Normal file
20
dom/webidl/PresentationAvailableEvent.webidl
Normal file
@ -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;
|
||||
};
|
70
dom/webidl/PresentationSession.webidl
Normal file
70
dom/webidl/PresentationSession.webidl
Normal file
@ -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();
|
||||
};
|
@ -365,7 +365,9 @@ WEBIDL_FILES = [
|
||||
'PopupBoxObject.webidl',
|
||||
'Position.webidl',
|
||||
'PositionError.webidl',
|
||||
'Presentation.webidl',
|
||||
'PresentationDeviceInfoManager.webidl',
|
||||
'PresentationSession.webidl',
|
||||
'ProcessingInstruction.webidl',
|
||||
'ProfileTimelineMarker.webidl',
|
||||
'Promise.webidl',
|
||||
@ -783,6 +785,7 @@ GENERATED_EVENTS_WEBIDL_FILES = [
|
||||
'PluginCrashedEvent.webidl',
|
||||
'PopStateEvent.webidl',
|
||||
'PopupBlockedEvent.webidl',
|
||||
'PresentationAvailableEvent.webidl',
|
||||
'ProgressEvent.webidl',
|
||||
'RecordErrorEvent.webidl',
|
||||
'ScrollViewChangeEvent.webidl',
|
||||
|
@ -240,6 +240,7 @@ static void Shutdown();
|
||||
#include "mozilla/dom/time/TimeService.h"
|
||||
#include "StreamingProtocolService.h"
|
||||
|
||||
#include "nsIPresentationService.h"
|
||||
#include "nsITelephonyService.h"
|
||||
#include "nsIVoicemailService.h"
|
||||
|
||||
@ -260,7 +261,8 @@ static void Shutdown();
|
||||
|
||||
#include "GMPService.h"
|
||||
|
||||
#include "mozilla/dom/presentation/PresentationDeviceManager.h"
|
||||
#include "mozilla/dom/PresentationDeviceManager.h"
|
||||
#include "mozilla/dom/PresentationSessionTransport.h"
|
||||
|
||||
#include "mozilla/TextInputProcessor.h"
|
||||
|
||||
@ -293,6 +295,11 @@ using mozilla::gmp::GeckoMediaPluginService;
|
||||
#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)
|
||||
@ -401,6 +408,9 @@ 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;
|
||||
@ -857,7 +867,9 @@ 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);
|
||||
|
||||
@ -1155,7 +1167,9 @@ static const mozilla::Module::CIDEntry kLayoutCIDs[] = {
|
||||
{ &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 },
|
||||
@ -1324,7 +1338,9 @@ static const mozilla::Module::ContractIDEntry kLayoutContracts[] = {
|
||||
{ "@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 },
|
||||
@ -1353,6 +1369,7 @@ static const mozilla::Module::CategoryEntry kLayoutCategories[] = {
|
||||
{ "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 }
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user