Merge b2ginbound to central, a=merge

This commit is contained in:
Wes Kocher 2015-08-06 18:09:39 -07:00
commit 3fc4028393
74 changed files with 6543 additions and 20 deletions

View File

@ -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}

View 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]);

View File

@ -23,6 +23,7 @@ EXTRA_COMPONENTS += [
'OMAContentHandler.js',
'PaymentGlue.js',
'PaymentProviderStrategy.js',
'PresentationRequestUIGlue.js',
'ProcessGlobal.js',
'SmsProtocolHandler.js',
'SystemMessageGlue.js',

View File

@ -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]

View File

@ -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);
});

View File

@ -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>

View File

@ -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"/>

View File

@ -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>

View File

@ -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"/>

View File

@ -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"/>

View File

@ -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"/>

View File

@ -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"/>

View File

@ -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"/>

View File

@ -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"/>

View File

@ -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"
}

View File

@ -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"/>

View File

@ -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"/>

View File

@ -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

View File

@ -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
},
};
/**

View File

@ -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

View File

@ -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

View File

@ -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")

View File

@ -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)

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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

View 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();
}

View 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

View 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;
}

View 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

View 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();
}

View 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

View 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();
}

View 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

View 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);
}

View 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

View 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);
}

View 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

View File

@ -10,7 +10,11 @@ XPIDL_SOURCES += [
'nsIPresentationDeviceManager.idl',
'nsIPresentationDevicePrompt.idl',
'nsIPresentationDeviceProvider.idl',
'nsIPresentationListener.idl',
'nsIPresentationRequestUIGlue.idl',
'nsIPresentationService.idl',
'nsIPresentationSessionRequest.idl',
'nsIPresentationSessionTransport.idl',
'nsITCPPresentationServer.idl',
]

View File

@ -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);
};

View 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);
};

View 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);
};

View 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);
};

View File

@ -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);
};

View 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

View 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

View 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;
}

View 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

View 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);
}

View 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

View 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;
}

View 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__

View File

@ -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')

View File

@ -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);

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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

View 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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View 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>

View File

@ -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>

View File

@ -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>

View File

@ -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();
}

View File

@ -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]

View File

@ -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]

View 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;
};

View 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;
};

View 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();
};

View File

@ -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',

View File

@ -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 }
};