mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-10 20:05:49 +00:00
ff8b5bd178
These were originally exposed directly as static methods on nsGlobalWindow, but as they are clearly associated with either the inner or outer window, it makes more sense for them to be called as such. MozReview-Commit-ID: LFq8EfnhDlo
1634 lines
45 KiB
C++
1634 lines
45 KiB
C++
/* -*- 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/. */
|
|
|
|
#include "mozilla/dom/ContentParent.h"
|
|
#include "mozilla/dom/HTMLIFrameElementBinding.h"
|
|
#include "mozilla/dom/TabParent.h"
|
|
#include "mozilla/Logging.h"
|
|
#include "mozilla/Move.h"
|
|
#include "mozilla/Preferences.h"
|
|
#include "mozilla/Services.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsGlobalWindow.h"
|
|
#include "nsIDocShell.h"
|
|
#include "nsFrameLoader.h"
|
|
#include "nsIMutableArray.h"
|
|
#include "nsINetAddr.h"
|
|
#include "nsISocketTransport.h"
|
|
#include "nsISupportsPrimitives.h"
|
|
#include "nsNetCID.h"
|
|
#include "nsServiceManagerUtils.h"
|
|
#include "nsThreadUtils.h"
|
|
#include "PresentationLog.h"
|
|
#include "PresentationService.h"
|
|
#include "PresentationSessionInfo.h"
|
|
|
|
#ifdef MOZ_WIDGET_ANDROID
|
|
#include "nsIPresentationNetworkHelper.h"
|
|
#endif // MOZ_WIDGET_ANDROID
|
|
|
|
using namespace mozilla;
|
|
using namespace mozilla::dom;
|
|
using namespace mozilla::services;
|
|
|
|
/*
|
|
* Implementation of PresentationChannelDescription
|
|
*/
|
|
|
|
namespace mozilla {
|
|
namespace dom {
|
|
|
|
#ifdef MOZ_WIDGET_ANDROID
|
|
|
|
namespace {
|
|
|
|
class PresentationNetworkHelper final : public nsIPresentationNetworkHelperListener
|
|
{
|
|
public:
|
|
NS_DECL_ISUPPORTS
|
|
NS_DECL_NSIPRESENTATIONNETWORKHELPERLISTENER
|
|
|
|
using Function = nsresult(PresentationControllingInfo::*)(const nsACString&);
|
|
|
|
explicit PresentationNetworkHelper(PresentationControllingInfo* aInfo,
|
|
const Function& aFunc);
|
|
|
|
nsresult GetWifiIPAddress();
|
|
|
|
private:
|
|
~PresentationNetworkHelper() = default;
|
|
|
|
RefPtr<PresentationControllingInfo> mInfo;
|
|
Function mFunc;
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS(PresentationNetworkHelper,
|
|
nsIPresentationNetworkHelperListener)
|
|
|
|
PresentationNetworkHelper::PresentationNetworkHelper(PresentationControllingInfo* aInfo,
|
|
const Function& aFunc)
|
|
: mInfo(aInfo)
|
|
, mFunc(aFunc)
|
|
{
|
|
MOZ_ASSERT(aInfo);
|
|
MOZ_ASSERT(aFunc);
|
|
}
|
|
|
|
nsresult
|
|
PresentationNetworkHelper::GetWifiIPAddress()
|
|
{
|
|
nsresult rv;
|
|
|
|
nsCOMPtr<nsIPresentationNetworkHelper> networkHelper =
|
|
do_GetService(PRESENTATION_NETWORK_HELPER_CONTRACTID, &rv);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return networkHelper->GetWifiIPAddress(this);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PresentationNetworkHelper::OnError(const nsACString & aReason)
|
|
{
|
|
PRES_ERROR("PresentationNetworkHelper::OnError: %s",
|
|
nsPromiseFlatCString(aReason).get());
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PresentationNetworkHelper::OnGetWifiIPAddress(const nsACString& aIPAddress)
|
|
{
|
|
MOZ_ASSERT(mInfo);
|
|
MOZ_ASSERT(mFunc);
|
|
|
|
NS_DispatchToMainThread(
|
|
NewRunnableMethod<nsCString>("dom::PresentationNetworkHelper::OnGetWifiIPAddress",
|
|
mInfo,
|
|
mFunc,
|
|
aIPAddress));
|
|
return NS_OK;
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
#endif // MOZ_WIDGET_ANDROID
|
|
|
|
class TCPPresentationChannelDescription final : public nsIPresentationChannelDescription
|
|
{
|
|
public:
|
|
NS_DECL_ISUPPORTS
|
|
NS_DECL_NSIPRESENTATIONCHANNELDESCRIPTION
|
|
|
|
TCPPresentationChannelDescription(const nsACString& aAddress,
|
|
uint16_t aPort)
|
|
: mAddress(aAddress)
|
|
, mPort(aPort)
|
|
{
|
|
}
|
|
|
|
private:
|
|
~TCPPresentationChannelDescription() {}
|
|
|
|
nsCString mAddress;
|
|
uint16_t mPort;
|
|
};
|
|
|
|
} // namespace dom
|
|
} // namespace mozilla
|
|
|
|
NS_IMPL_ISUPPORTS(TCPPresentationChannelDescription, nsIPresentationChannelDescription)
|
|
|
|
NS_IMETHODIMP
|
|
TCPPresentationChannelDescription::GetType(uint8_t* aRetVal)
|
|
{
|
|
if (NS_WARN_IF(!aRetVal)) {
|
|
return NS_ERROR_INVALID_POINTER;
|
|
}
|
|
|
|
*aRetVal = nsIPresentationChannelDescription::TYPE_TCP;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
TCPPresentationChannelDescription::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 1228504 Take all IP addresses in PresentationChannelDescription
|
|
// into account. 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);
|
|
array.forget(aRetVal);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
TCPPresentationChannelDescription::GetTcpPort(uint16_t* aRetVal)
|
|
{
|
|
if (NS_WARN_IF(!aRetVal)) {
|
|
return NS_ERROR_INVALID_POINTER;
|
|
}
|
|
|
|
*aRetVal = mPort;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
TCPPresentationChannelDescription::GetDataChannelSDP(nsAString& aDataChannelSDP)
|
|
{
|
|
aDataChannelSDP.Truncate();
|
|
return NS_OK;
|
|
}
|
|
|
|
/*
|
|
* Implementation of PresentationSessionInfo
|
|
*/
|
|
|
|
NS_IMPL_ISUPPORTS(PresentationSessionInfo,
|
|
nsIPresentationSessionTransportCallback,
|
|
nsIPresentationControlChannelListener,
|
|
nsIPresentationSessionTransportBuilderListener);
|
|
|
|
/* virtual */ nsresult
|
|
PresentationSessionInfo::Init(nsIPresentationControlChannel* aControlChannel)
|
|
{
|
|
SetControlChannel(aControlChannel);
|
|
return NS_OK;
|
|
}
|
|
|
|
/* virtual */ void
|
|
PresentationSessionInfo::Shutdown(nsresult aReason)
|
|
{
|
|
PRES_DEBUG("%s:id[%s], reason[%" PRIx32 "], role[%d]\n", __func__,
|
|
NS_ConvertUTF16toUTF8(mSessionId).get(), static_cast<uint32_t>(aReason),
|
|
mRole);
|
|
|
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(aReason), "bad reason");
|
|
|
|
// Close the control channel if any.
|
|
if (mControlChannel) {
|
|
Unused << NS_WARN_IF(NS_FAILED(mControlChannel->Disconnect(aReason)));
|
|
}
|
|
|
|
// Close the data transport channel if any.
|
|
if (mTransport) {
|
|
// |mIsTransportReady| will be unset once |NotifyTransportClosed| is called.
|
|
Unused << NS_WARN_IF(NS_FAILED(mTransport->Close(aReason)));
|
|
}
|
|
|
|
mIsResponderReady = false;
|
|
mIsOnTerminating = false;
|
|
|
|
ResetBuilder();
|
|
}
|
|
|
|
nsresult
|
|
PresentationSessionInfo::SetListener(nsIPresentationSessionListener* aListener)
|
|
{
|
|
mListener = aListener;
|
|
|
|
if (mListener) {
|
|
// Enable data notification for the transport channel if it's available.
|
|
if (mTransport) {
|
|
nsresult rv = mTransport->EnableDataNotification();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
// The transport might become ready, or might become un-ready again, before
|
|
// the listener has registered. So notify the listener of the state change.
|
|
return mListener->NotifyStateChange(mSessionId, mState, mReason);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
PresentationSessionInfo::Send(const nsAString& 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::SendBinaryMsg(const nsACString& 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->SendBinaryMsg(aData);
|
|
}
|
|
|
|
nsresult
|
|
PresentationSessionInfo::SendBlob(nsIDOMBlob* aBlob)
|
|
{
|
|
if (NS_WARN_IF(!IsSessionReady())) {
|
|
return NS_ERROR_DOM_INVALID_STATE_ERR;
|
|
}
|
|
|
|
if (NS_WARN_IF(!mTransport)) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
return mTransport->SendBlob(aBlob);
|
|
}
|
|
|
|
nsresult
|
|
PresentationSessionInfo::Close(nsresult aReason,
|
|
uint32_t aState)
|
|
{
|
|
// Do nothing if session is already terminated.
|
|
if (nsIPresentationSessionListener::STATE_TERMINATED == mState) {
|
|
return NS_OK;
|
|
}
|
|
|
|
SetStateWithReason(aState, aReason);
|
|
|
|
switch (aState) {
|
|
case nsIPresentationSessionListener::STATE_CLOSED: {
|
|
Shutdown(aReason);
|
|
break;
|
|
}
|
|
case nsIPresentationSessionListener::STATE_TERMINATED: {
|
|
if (!mControlChannel) {
|
|
nsCOMPtr<nsIPresentationControlChannel> ctrlChannel;
|
|
nsresult rv = mDevice->EstablishControlChannel(getter_AddRefs(ctrlChannel));
|
|
if (NS_FAILED(rv)) {
|
|
Shutdown(rv);
|
|
return rv;
|
|
}
|
|
|
|
SetControlChannel(ctrlChannel);
|
|
return rv;
|
|
}
|
|
|
|
ContinueTermination();
|
|
return NS_OK;
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
PresentationSessionInfo::OnTerminate(nsIPresentationControlChannel* aControlChannel)
|
|
{
|
|
mIsOnTerminating = true; // Mark for terminating transport channel
|
|
SetStateWithReason(nsIPresentationSessionListener::STATE_TERMINATED, NS_OK);
|
|
SetControlChannel(aControlChannel);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
PresentationSessionInfo::ReplySuccess()
|
|
{
|
|
SetStateWithReason(nsIPresentationSessionListener::STATE_CONNECTED, NS_OK);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
PresentationSessionInfo::ReplyError(nsresult aError)
|
|
{
|
|
Shutdown(aError);
|
|
|
|
// Remove itself since it never succeeds.
|
|
return UntrackFromService();
|
|
}
|
|
|
|
/* virtual */ nsresult
|
|
PresentationSessionInfo::UntrackFromService()
|
|
{
|
|
nsCOMPtr<nsIPresentationService> service =
|
|
do_GetService(PRESENTATION_SERVICE_CONTRACTID);
|
|
if (NS_WARN_IF(!service)) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
static_cast<PresentationService*>(service.get())->UntrackSessionInfo(mSessionId, mRole);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsPIDOMWindowInner*
|
|
PresentationSessionInfo::GetWindow()
|
|
{
|
|
nsCOMPtr<nsIPresentationService> service =
|
|
do_GetService(PRESENTATION_SERVICE_CONTRACTID);
|
|
if (NS_WARN_IF(!service)) {
|
|
return nullptr;
|
|
}
|
|
uint64_t windowId = 0;
|
|
if (NS_WARN_IF(NS_FAILED(service->GetWindowIdBySessionId(mSessionId,
|
|
mRole,
|
|
&windowId)))) {
|
|
return nullptr;
|
|
}
|
|
|
|
auto window = nsGlobalWindowInner::GetInnerWindowWithId(windowId);
|
|
if (!window) {
|
|
return nullptr;
|
|
}
|
|
|
|
return window->AsInner();
|
|
}
|
|
|
|
/* virtual */ bool
|
|
PresentationSessionInfo::IsAccessible(base::ProcessId aProcessId)
|
|
{
|
|
// No restriction by default.
|
|
return true;
|
|
}
|
|
|
|
void
|
|
PresentationSessionInfo::ContinueTermination()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(mControlChannel);
|
|
|
|
if (NS_WARN_IF(NS_FAILED(mControlChannel->Terminate(mSessionId)))
|
|
|| mIsOnTerminating) {
|
|
Shutdown(NS_OK);
|
|
}
|
|
}
|
|
|
|
// nsIPresentationSessionTransportCallback
|
|
NS_IMETHODIMP
|
|
PresentationSessionInfo::NotifyTransportReady()
|
|
{
|
|
PRES_DEBUG("%s:id[%s], role[%d], state[%d]\n", __func__,
|
|
NS_ConvertUTF16toUTF8(mSessionId).get(), mRole, mState);
|
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (mState != nsIPresentationSessionListener::STATE_CONNECTING &&
|
|
mState != nsIPresentationSessionListener::STATE_CONNECTED) {
|
|
return NS_OK;
|
|
}
|
|
|
|
mIsTransportReady = true;
|
|
|
|
// Established RTCDataChannel implies responder is ready.
|
|
if (mTransportType == nsIPresentationChannelDescription::TYPE_DATACHANNEL) {
|
|
mIsResponderReady = 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)
|
|
{
|
|
PRES_DEBUG("%s:id[%s], reason[%" PRIx32 "], role[%d]\n", __func__,
|
|
NS_ConvertUTF16toUTF8(mSessionId).get(), static_cast<uint32_t>(aReason),
|
|
mRole);
|
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
// Nullify |mTransport| here so it won't try to re-close |mTransport| in
|
|
// potential subsequent |Shutdown| calls.
|
|
mTransport = nullptr;
|
|
|
|
if (NS_WARN_IF(!IsSessionReady() &&
|
|
mState == nsIPresentationSessionListener::STATE_CONNECTING)) {
|
|
// It happens before the session is ready. Reply the callback.
|
|
return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
|
|
}
|
|
|
|
// Unset |mIsTransportReady| here so it won't affect |IsSessionReady()| above.
|
|
mIsTransportReady = false;
|
|
|
|
if (mState == nsIPresentationSessionListener::STATE_CONNECTED) {
|
|
// The transport channel is closed unexpectedly (not caused by a |Close| call).
|
|
SetStateWithReason(nsIPresentationSessionListener::STATE_CLOSED, aReason);
|
|
}
|
|
|
|
Shutdown(aReason);
|
|
|
|
if (mState == nsIPresentationSessionListener::STATE_TERMINATED) {
|
|
// Directly untrack the session info from the service.
|
|
return UntrackFromService();
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PresentationSessionInfo::NotifyData(const nsACString& aData, bool aIsBinary)
|
|
{
|
|
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, aIsBinary);
|
|
}
|
|
|
|
// nsIPresentationSessionTransportBuilderListener
|
|
NS_IMETHODIMP
|
|
PresentationSessionInfo::OnSessionTransport(nsIPresentationSessionTransport* aTransport)
|
|
{
|
|
PRES_DEBUG("%s:id[%s], role[%d], state[%d]\n", __func__,
|
|
NS_ConvertUTF16toUTF8(mSessionId).get(), mRole, mState);
|
|
|
|
ResetBuilder();
|
|
|
|
if (mState != nsIPresentationSessionListener::STATE_CONNECTING) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
if (NS_WARN_IF(!aTransport)) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
mTransport = aTransport;
|
|
|
|
nsresult rv = mTransport->SetCallback(this);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (mListener) {
|
|
mTransport->EnableDataNotification();
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PresentationSessionInfo::OnError(nsresult aReason)
|
|
{
|
|
PRES_DEBUG("%s:id[%s], reason[%" PRIx32 "], role[%d]\n", __func__,
|
|
NS_ConvertUTF16toUTF8(mSessionId).get(), static_cast<uint32_t>(aReason),
|
|
mRole);
|
|
|
|
ResetBuilder();
|
|
return ReplyError(aReason);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PresentationSessionInfo::SendOffer(nsIPresentationChannelDescription* aOffer)
|
|
{
|
|
return mControlChannel->SendOffer(aOffer);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PresentationSessionInfo::SendAnswer(nsIPresentationChannelDescription* aAnswer)
|
|
{
|
|
return mControlChannel->SendAnswer(aAnswer);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PresentationSessionInfo::SendIceCandidate(const nsAString& candidate)
|
|
{
|
|
return mControlChannel->SendIceCandidate(candidate);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PresentationSessionInfo::Close(nsresult reason)
|
|
{
|
|
return mControlChannel->Disconnect(reason);
|
|
}
|
|
|
|
/**
|
|
* Implementation of PresentationControllingInfo
|
|
*
|
|
* During presentation session establishment, the sender expects the following
|
|
* after trying to establish the control channel: (The order between step 3 and
|
|
* 4 is not guaranteed.)
|
|
* 1. |Init| is called to open a socket |mServerSocket| for data transport
|
|
* channel.
|
|
* 2. |NotifyConnected| of |nsIPresentationControlChannelListener| is called to
|
|
* indicate the control channel is ready to use. Then send the offer to the
|
|
* receiver via the control channel.
|
|
* 3.1 |OnSocketAccepted| of |nsIServerSocketListener| is called to indicate the
|
|
* data transport channel is connected. Then initialize |mTransport|.
|
|
* 3.2 |NotifyTransportReady| of |nsIPresentationSessionTransportCallback| is
|
|
* called.
|
|
* 4. |OnAnswer| of |nsIPresentationControlChannelListener| is called to
|
|
* indicate the receiver is ready. Close the control channel since it's no
|
|
* longer needed.
|
|
* 5. Once both step 3 and 4 are done, the presentation session is ready to use.
|
|
* So notify the listener of CONNECTED state.
|
|
*/
|
|
|
|
NS_IMPL_ISUPPORTS_INHERITED(PresentationControllingInfo,
|
|
PresentationSessionInfo,
|
|
nsIServerSocketListener)
|
|
|
|
nsresult
|
|
PresentationControllingInfo::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_DOM_OPERATION_ERR);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
int32_t port;
|
|
rv = mServerSocket->GetPort(&port);
|
|
if (!NS_WARN_IF(NS_FAILED(rv))) {
|
|
PRES_DEBUG("%s:ServerSocket created.port[%d]\n",__func__, port);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
PresentationControllingInfo::Shutdown(nsresult aReason)
|
|
{
|
|
PresentationSessionInfo::Shutdown(aReason);
|
|
|
|
// Close the server socket if any.
|
|
if (mServerSocket) {
|
|
Unused << NS_WARN_IF(NS_FAILED(mServerSocket->Close()));
|
|
mServerSocket = nullptr;
|
|
}
|
|
}
|
|
|
|
nsresult
|
|
PresentationControllingInfo::GetAddress()
|
|
{
|
|
if (nsContentUtils::ShouldResistFingerprinting()) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
#if defined(MOZ_WIDGET_ANDROID)
|
|
RefPtr<PresentationNetworkHelper> networkHelper =
|
|
new PresentationNetworkHelper(this,
|
|
&PresentationControllingInfo::OnGetAddress);
|
|
nsresult rv = networkHelper->GetWifiIPAddress();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
#else
|
|
nsCOMPtr<nsINetworkInfoService> networkInfo = do_GetService(NETWORKINFOSERVICE_CONTRACT_ID);
|
|
MOZ_ASSERT(networkInfo);
|
|
|
|
nsresult rv = networkInfo->ListNetworkAddresses(this);
|
|
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
#endif
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
PresentationControllingInfo::OnGetAddress(const nsACString& aAddress)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (NS_WARN_IF(!mServerSocket)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
if (NS_WARN_IF(!mControlChannel)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
// Prepare and send the offer.
|
|
int32_t port;
|
|
nsresult rv = mServerSocket->GetPort(&port);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
RefPtr<TCPPresentationChannelDescription> description =
|
|
new TCPPresentationChannelDescription(aAddress, static_cast<uint16_t>(port));
|
|
return mControlChannel->SendOffer(description);
|
|
}
|
|
|
|
// nsIPresentationControlChannelListener
|
|
NS_IMETHODIMP
|
|
PresentationControllingInfo::OnIceCandidate(const nsAString& aCandidate)
|
|
{
|
|
if (mTransportType != nsIPresentationChannelDescription::TYPE_DATACHANNEL) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsCOMPtr<nsIPresentationDataChannelSessionTransportBuilder>
|
|
builder = do_QueryInterface(mBuilder);
|
|
|
|
if (NS_WARN_IF(!builder)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
return builder->OnIceCandidate(aCandidate);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PresentationControllingInfo::OnOffer(nsIPresentationChannelDescription* aDescription)
|
|
{
|
|
MOZ_ASSERT(false, "Sender side should not receive offer.");
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PresentationControllingInfo::OnAnswer(nsIPresentationChannelDescription* aDescription)
|
|
{
|
|
if (mTransportType == nsIPresentationChannelDescription::TYPE_DATACHANNEL) {
|
|
nsCOMPtr<nsIPresentationDataChannelSessionTransportBuilder>
|
|
builder = do_QueryInterface(mBuilder);
|
|
|
|
if (NS_WARN_IF(!builder)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
return builder->OnAnswer(aDescription);
|
|
}
|
|
|
|
mIsResponderReady = true;
|
|
|
|
// Close the control channel since it's no longer needed.
|
|
nsresult rv = mControlChannel->Disconnect(NS_OK);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
|
|
}
|
|
|
|
// 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
|
|
PresentationControllingInfo::NotifyConnected()
|
|
{
|
|
PRES_DEBUG("%s:id[%s], role[%d]\n", __func__,
|
|
NS_ConvertUTF16toUTF8(mSessionId).get(), mRole);
|
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
switch (mState) {
|
|
case nsIPresentationSessionListener::STATE_CONNECTING: {
|
|
if (mIsReconnecting) {
|
|
return ContinueReconnect();
|
|
}
|
|
|
|
nsresult rv = mControlChannel->Launch(GetSessionId(), GetUrl());
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
Unused << NS_WARN_IF(NS_FAILED(BuildTransport()));
|
|
break;
|
|
}
|
|
case nsIPresentationSessionListener::STATE_TERMINATED: {
|
|
ContinueTermination();
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PresentationControllingInfo::NotifyReconnected()
|
|
{
|
|
PRES_DEBUG("%s:id[%s], role[%d]\n", __func__,
|
|
NS_ConvertUTF16toUTF8(mSessionId).get(), mRole);
|
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (NS_WARN_IF(mState != nsIPresentationSessionListener::STATE_CONNECTING)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
return NotifyReconnectResult(NS_OK);
|
|
}
|
|
|
|
nsresult
|
|
PresentationControllingInfo::BuildTransport()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (mState != nsIPresentationSessionListener::STATE_CONNECTING) {
|
|
return NS_OK;
|
|
}
|
|
|
|
if (NS_WARN_IF(!mBuilderConstructor)) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
if (!Preferences::GetBool("dom.presentation.session_transport.data_channel.enable")) {
|
|
// Build TCP session transport
|
|
return GetAddress();
|
|
}
|
|
/**
|
|
* Generally transport is maintained by the chrome process. However, data
|
|
* channel should be live with the DOM , which implies RTCDataChannel in an OOP
|
|
* page should be establish in the content process.
|
|
*
|
|
* |mBuilderConstructor| is responsible for creating a builder, which is for
|
|
* building a data channel transport.
|
|
*
|
|
* In the OOP case, |mBuilderConstructor| would create a builder which is
|
|
* an object of |PresentationBuilderParent|. So, |BuildDataChannelTransport|
|
|
* triggers an IPC call to make content process establish a RTCDataChannel
|
|
* transport.
|
|
*/
|
|
|
|
mTransportType = nsIPresentationChannelDescription::TYPE_DATACHANNEL;
|
|
if (NS_WARN_IF(NS_FAILED(
|
|
mBuilderConstructor->CreateTransportBuilder(mTransportType,
|
|
getter_AddRefs(mBuilder))))) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
nsCOMPtr<nsIPresentationDataChannelSessionTransportBuilder>
|
|
dataChannelBuilder(do_QueryInterface(mBuilder));
|
|
if (NS_WARN_IF(!dataChannelBuilder)) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
// OOP window would be set from content process
|
|
nsPIDOMWindowInner* window = GetWindow();
|
|
|
|
nsresult rv = dataChannelBuilder->
|
|
BuildDataChannelTransport(nsIPresentationService::ROLE_CONTROLLER,
|
|
window,
|
|
this);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PresentationControllingInfo::NotifyDisconnected(nsresult aReason)
|
|
{
|
|
PRES_DEBUG("%s:id[%s], reason[%" PRIx32 "], role[%d]\n", __func__,
|
|
NS_ConvertUTF16toUTF8(mSessionId).get(), static_cast<uint32_t>(aReason),
|
|
mRole);
|
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (mTransportType == nsIPresentationChannelDescription::TYPE_DATACHANNEL) {
|
|
nsCOMPtr<nsIPresentationDataChannelSessionTransportBuilder>
|
|
builder = do_QueryInterface(mBuilder);
|
|
if (builder) {
|
|
Unused << NS_WARN_IF(NS_FAILED(builder->NotifyDisconnected(aReason)));
|
|
}
|
|
}
|
|
|
|
// 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) || !mIsResponderReady)) {
|
|
// The presentation session instance may already exist.
|
|
// Change the state to CLOSED if it is not terminated.
|
|
if (nsIPresentationSessionListener::STATE_TERMINATED != mState) {
|
|
SetStateWithReason(nsIPresentationSessionListener::STATE_CLOSED, aReason);
|
|
}
|
|
|
|
// If |aReason| is NS_OK, it implies that the user closes the connection
|
|
// before becomming connected. No need to call |ReplyError| in this case.
|
|
if (NS_FAILED(aReason)) {
|
|
if (mIsReconnecting) {
|
|
NotifyReconnectResult(NS_ERROR_DOM_OPERATION_ERR);
|
|
}
|
|
// Reply error for an abnormal close.
|
|
return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
|
|
}
|
|
Shutdown(aReason);
|
|
}
|
|
|
|
// This is the case for reconnecting a connection which is in
|
|
// connecting state and |mTransport| is not ready.
|
|
if (mDoReconnectAfterClose && !mTransport) {
|
|
mDoReconnectAfterClose = false;
|
|
return Reconnect(mReconnectCallback);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// nsIServerSocketListener
|
|
NS_IMETHODIMP
|
|
PresentationControllingInfo::OnSocketAccepted(nsIServerSocket* aServerSocket,
|
|
nsISocketTransport* aTransport)
|
|
{
|
|
int32_t port;
|
|
nsresult rv = aTransport->GetPort(&port);
|
|
if (!NS_WARN_IF(NS_FAILED(rv))) {
|
|
PRES_DEBUG("%s:receive from port[%d]\n",__func__, port);
|
|
}
|
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (NS_WARN_IF(!mBuilderConstructor)) {
|
|
return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
|
|
}
|
|
|
|
// Initialize session transport builder and use |this| as the callback.
|
|
nsCOMPtr<nsIPresentationTCPSessionTransportBuilder> builder;
|
|
if (NS_SUCCEEDED(mBuilderConstructor->CreateTransportBuilder(
|
|
nsIPresentationChannelDescription::TYPE_TCP,
|
|
getter_AddRefs(mBuilder)))) {
|
|
builder = do_QueryInterface(mBuilder);
|
|
}
|
|
|
|
if (NS_WARN_IF(!builder)) {
|
|
return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
|
|
}
|
|
|
|
mTransportType = nsIPresentationChannelDescription::TYPE_TCP;
|
|
return builder->BuildTCPSenderTransport(aTransport, this);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PresentationControllingInfo::OnStopListening(nsIServerSocket* aServerSocket,
|
|
nsresult aStatus)
|
|
{
|
|
PRES_DEBUG("controller %s:status[%" PRIx32 "]\n",__func__,
|
|
static_cast<uint32_t>(aStatus));
|
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (aStatus == NS_BINDING_ABORTED) { // The server socket was manually closed.
|
|
return NS_OK;
|
|
}
|
|
|
|
Shutdown(aStatus);
|
|
|
|
if (NS_WARN_IF(!IsSessionReady())) {
|
|
// It happens before the session is ready. Reply the callback.
|
|
return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
|
|
}
|
|
|
|
// It happens after the session is ready. Change the state to CLOSED.
|
|
SetStateWithReason(nsIPresentationSessionListener::STATE_CLOSED, aStatus);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/**
|
|
* The steps to reconnect a session are summarized below:
|
|
* 1. Change |mState| to CONNECTING.
|
|
* 2. Check whether |mControlChannel| is existed or not. Usually we have to
|
|
* create a new control cahnnel.
|
|
* 3.1 |mControlChannel| is null, which means we have to create a new one.
|
|
* |EstablishControlChannel| is called to create a new control channel.
|
|
* At this point, |mControlChannel| is not able to use yet. Set
|
|
* |mIsReconnecting| to true and wait until |NotifyConnected|.
|
|
* 3.2 |mControlChannel| is not null and is avaliable.
|
|
* We can just call |ContinueReconnect| to send reconnect command.
|
|
* 4. |NotifyReconnected| of |nsIPresentationControlChannelListener| is called
|
|
* to indicate the receiver is ready for reconnecting.
|
|
* 5. Once both step 3 and 4 are done, the rest is to build a new data
|
|
* transport channel by following the same steps as starting a
|
|
* new session.
|
|
*/
|
|
|
|
nsresult
|
|
PresentationControllingInfo::Reconnect(nsIPresentationServiceCallback* aCallback)
|
|
{
|
|
PRES_DEBUG("%s:id[%s], role[%d], state[%d]\n", __func__,
|
|
NS_ConvertUTF16toUTF8(mSessionId).get(), mRole, mState);
|
|
|
|
if (!aCallback) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
mReconnectCallback = aCallback;
|
|
|
|
if (NS_WARN_IF(mState == nsIPresentationSessionListener::STATE_TERMINATED)) {
|
|
return NotifyReconnectResult(NS_ERROR_DOM_INVALID_STATE_ERR);
|
|
}
|
|
|
|
// If |mState| is not CLOSED, we have to close the connection before
|
|
// reconnecting. The process to reconnect will be continued after
|
|
// |NotifyDisconnected| or |NotifyTransportClosed| is invoked.
|
|
if (mState == nsIPresentationSessionListener::STATE_CONNECTING ||
|
|
mState == nsIPresentationSessionListener::STATE_CONNECTED) {
|
|
mDoReconnectAfterClose = true;
|
|
return Close(NS_OK, nsIPresentationSessionListener::STATE_CLOSED);
|
|
}
|
|
|
|
// Make sure |mState| is closed at this point.
|
|
MOZ_ASSERT(mState == nsIPresentationSessionListener::STATE_CLOSED);
|
|
|
|
mState = nsIPresentationSessionListener::STATE_CONNECTING;
|
|
mIsReconnecting = true;
|
|
|
|
nsresult rv = NS_OK;
|
|
if (!mControlChannel) {
|
|
nsCOMPtr<nsIPresentationControlChannel> ctrlChannel;
|
|
rv = mDevice->EstablishControlChannel(getter_AddRefs(ctrlChannel));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return NotifyReconnectResult(NS_ERROR_DOM_OPERATION_ERR);
|
|
}
|
|
|
|
rv = Init(ctrlChannel);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return NotifyReconnectResult(NS_ERROR_DOM_OPERATION_ERR);
|
|
}
|
|
} else {
|
|
return ContinueReconnect();
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
PresentationControllingInfo::ContinueReconnect()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(mControlChannel);
|
|
|
|
mIsReconnecting = false;
|
|
if (NS_WARN_IF(NS_FAILED(mControlChannel->Reconnect(mSessionId, GetUrl())))) {
|
|
return NotifyReconnectResult(NS_ERROR_DOM_OPERATION_ERR);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// nsIListNetworkAddressesListener
|
|
NS_IMETHODIMP
|
|
PresentationControllingInfo::OnListedNetworkAddresses(const char** aAddressArray,
|
|
uint32_t aAddressArraySize)
|
|
{
|
|
if (!aAddressArraySize) {
|
|
return OnListNetworkAddressesFailed();
|
|
}
|
|
|
|
// TODO bug 1228504 Take all IP addresses in PresentationChannelDescription
|
|
// into account. And at the first stage Presentation API is only exposed on
|
|
// Firefox OS where the first IP appears enough for most scenarios.
|
|
|
|
nsAutoCString ip;
|
|
ip.Assign(aAddressArray[0]);
|
|
|
|
// On Firefox desktop, the IP address is retrieved from a callback function.
|
|
// To make consistent code sequence, following function call is dispatched
|
|
// into main thread instead of calling it directly.
|
|
NS_DispatchToMainThread(NewRunnableMethod<nsCString>(
|
|
"dom::PresentationControllingInfo::OnGetAddress",
|
|
this,
|
|
&PresentationControllingInfo::OnGetAddress,
|
|
ip));
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PresentationControllingInfo::OnListNetworkAddressesFailed()
|
|
{
|
|
PRES_ERROR("PresentationControllingInfo:OnListNetworkAddressesFailed");
|
|
|
|
// In 1-UA case, transport channel can still be established
|
|
// on loopback interface even if no network address available.
|
|
NS_DispatchToMainThread(NewRunnableMethod<nsCString>(
|
|
"dom::PresentationControllingInfo::OnGetAddress",
|
|
this,
|
|
&PresentationControllingInfo::OnGetAddress,
|
|
"127.0.0.1"));
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
PresentationControllingInfo::NotifyReconnectResult(nsresult aStatus)
|
|
{
|
|
if (!mReconnectCallback) {
|
|
MOZ_ASSERT(false, "mReconnectCallback can not be null here.");
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
mIsReconnecting = false;
|
|
nsCOMPtr<nsIPresentationServiceCallback> callback =
|
|
mReconnectCallback.forget();
|
|
if (NS_FAILED(aStatus)) {
|
|
return callback->NotifyError(aStatus);
|
|
}
|
|
|
|
return callback->NotifySuccess(GetUrl());
|
|
}
|
|
|
|
// nsIPresentationSessionTransportCallback
|
|
NS_IMETHODIMP
|
|
PresentationControllingInfo::NotifyTransportReady()
|
|
{
|
|
return PresentationSessionInfo::NotifyTransportReady();
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PresentationControllingInfo::NotifyTransportClosed(nsresult aReason)
|
|
{
|
|
if (!mDoReconnectAfterClose) {
|
|
return PresentationSessionInfo::NotifyTransportClosed(aReason);;
|
|
}
|
|
|
|
MOZ_ASSERT(mState == nsIPresentationSessionListener::STATE_CLOSED);
|
|
|
|
mTransport = nullptr;
|
|
mIsTransportReady = false;
|
|
mDoReconnectAfterClose = false;
|
|
return Reconnect(mReconnectCallback);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PresentationControllingInfo::NotifyData(const nsACString& aData, bool aIsBinary)
|
|
{
|
|
return PresentationSessionInfo::NotifyData(aData, aIsBinary);
|
|
}
|
|
|
|
/**
|
|
* Implementation of PresentationPresentingInfo
|
|
*
|
|
* 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(PresentationPresentingInfo,
|
|
PresentationSessionInfo,
|
|
nsITimerCallback,
|
|
nsINamed)
|
|
|
|
nsresult
|
|
PresentationPresentingInfo::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);
|
|
rv = NS_NewTimerWithCallback(getter_AddRefs(mTimer),
|
|
this, timeout, nsITimer::TYPE_ONE_SHOT);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
PresentationPresentingInfo::Shutdown(nsresult aReason)
|
|
{
|
|
PresentationSessionInfo::Shutdown(aReason);
|
|
|
|
if (mTimer) {
|
|
mTimer->Cancel();
|
|
}
|
|
|
|
mLoadingCallback = nullptr;
|
|
mRequesterDescription = nullptr;
|
|
mPendingCandidates.Clear();
|
|
mPromise = nullptr;
|
|
mHasFlushPendingEvents = false;
|
|
}
|
|
|
|
// nsIPresentationSessionTransportBuilderListener
|
|
NS_IMETHODIMP
|
|
PresentationPresentingInfo::OnSessionTransport(nsIPresentationSessionTransport* aTransport)
|
|
{
|
|
nsresult rv = PresentationSessionInfo::OnSessionTransport(aTransport);
|
|
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// The session transport is managed by content process
|
|
if (NS_WARN_IF(!aTransport)) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
// send answer for TCP session transport
|
|
if (mTransportType == nsIPresentationChannelDescription::TYPE_TCP) {
|
|
// Prepare and send the answer.
|
|
// In the current implementation of |PresentationSessionTransport|,
|
|
// |GetSelfAddress| cannot return the real info when it's initialized via
|
|
// |buildTCPReceiverTransport|. 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));
|
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "GetSelfAddress failed");
|
|
|
|
nsCString address;
|
|
uint16_t port = 0;
|
|
if (NS_SUCCEEDED(rv)) {
|
|
selfAddr->GetAddress(address);
|
|
selfAddr->GetPort(&port);
|
|
}
|
|
nsCOMPtr<nsIPresentationChannelDescription> description =
|
|
new TCPPresentationChannelDescription(address, port);
|
|
|
|
return mControlChannel->SendAnswer(description);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// Delegate the pending offer and ICE candidates to builder.
|
|
NS_IMETHODIMP
|
|
PresentationPresentingInfo::FlushPendingEvents(nsIPresentationDataChannelSessionTransportBuilder* builder)
|
|
{
|
|
if (NS_WARN_IF(!builder)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
mHasFlushPendingEvents = true;
|
|
|
|
if (mRequesterDescription) {
|
|
builder->OnOffer(mRequesterDescription);
|
|
}
|
|
mRequesterDescription = nullptr;
|
|
|
|
for (size_t i = 0; i < mPendingCandidates.Length(); ++i) {
|
|
builder->OnIceCandidate(mPendingCandidates[i]);
|
|
}
|
|
mPendingCandidates.Clear();
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
PresentationPresentingInfo::InitTransportAndSendAnswer()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(mState == nsIPresentationSessionListener::STATE_CONNECTING);
|
|
|
|
uint8_t type = 0;
|
|
nsresult rv = mRequesterDescription->GetType(&type);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (NS_WARN_IF(!mBuilderConstructor)) {
|
|
return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
|
|
}
|
|
|
|
if (NS_WARN_IF(NS_FAILED(
|
|
mBuilderConstructor->CreateTransportBuilder(type,
|
|
getter_AddRefs(mBuilder))))) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
if (type == nsIPresentationChannelDescription::TYPE_TCP) {
|
|
// Establish a data transport channel |mTransport| to the sender and use
|
|
// |this| as the callback.
|
|
nsCOMPtr<nsIPresentationTCPSessionTransportBuilder> builder =
|
|
do_QueryInterface(mBuilder);
|
|
if (NS_WARN_IF(!builder)) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
mTransportType = nsIPresentationChannelDescription::TYPE_TCP;
|
|
return builder->BuildTCPReceiverTransport(mRequesterDescription, this);
|
|
}
|
|
|
|
if (type == nsIPresentationChannelDescription::TYPE_DATACHANNEL) {
|
|
if (!Preferences::GetBool("dom.presentation.session_transport.data_channel.enable")) {
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
/**
|
|
* Generally transport is maintained by the chrome process. However, data
|
|
* channel should be live with the DOM , which implies RTCDataChannel in an OOP
|
|
* page should be establish in the content process.
|
|
*
|
|
* |mBuilderConstructor| is responsible for creating a builder, which is for
|
|
* building a data channel transport.
|
|
*
|
|
* In the OOP case, |mBuilderConstructor| would create a builder which is
|
|
* an object of |PresentationBuilderParent|. So, |BuildDataChannelTransport|
|
|
* triggers an IPC call to make content process establish a RTCDataChannel
|
|
* transport.
|
|
*/
|
|
|
|
mTransportType = nsIPresentationChannelDescription::TYPE_DATACHANNEL;
|
|
|
|
nsCOMPtr<nsIPresentationDataChannelSessionTransportBuilder> dataChannelBuilder =
|
|
do_QueryInterface(mBuilder);
|
|
if (NS_WARN_IF(!dataChannelBuilder)) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
nsPIDOMWindowInner* window = GetWindow();
|
|
|
|
rv = dataChannelBuilder->
|
|
BuildDataChannelTransport(nsIPresentationService::ROLE_RECEIVER,
|
|
window,
|
|
this);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = FlushPendingEvents(dataChannelBuilder);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
MOZ_ASSERT(false, "Unknown nsIPresentationChannelDescription type!");
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
nsresult
|
|
PresentationPresentingInfo::UntrackFromService()
|
|
{
|
|
// Remove the OOP responding info (if it has never been used).
|
|
if (mContentParent) {
|
|
Unused << NS_WARN_IF(!static_cast<ContentParent*>(mContentParent.get())->SendNotifyPresentationReceiverCleanUp(mSessionId));
|
|
}
|
|
|
|
// Receiver device might need clean up after session termination.
|
|
if (mDevice) {
|
|
mDevice->Disconnect();
|
|
}
|
|
mDevice = nullptr;
|
|
|
|
// Remove the session info (and the in-process responding info if there's any).
|
|
nsCOMPtr<nsIPresentationService> service =
|
|
do_GetService(PRESENTATION_SERVICE_CONTRACTID);
|
|
if (NS_WARN_IF(!service)) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
static_cast<PresentationService*>(service.get())->UntrackSessionInfo(mSessionId, mRole);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
bool
|
|
PresentationPresentingInfo::IsAccessible(base::ProcessId aProcessId)
|
|
{
|
|
// Only the specific content process should access the responder info.
|
|
return (mContentParent) ?
|
|
aProcessId == static_cast<ContentParent*>(mContentParent.get())->OtherPid() :
|
|
false;
|
|
}
|
|
|
|
nsresult
|
|
PresentationPresentingInfo::NotifyResponderReady()
|
|
{
|
|
PRES_DEBUG("%s:id[%s], role[%d], state[%d]\n", __func__,
|
|
NS_ConvertUTF16toUTF8(mSessionId).get(), mRole, mState);
|
|
|
|
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(NS_ERROR_DOM_OPERATION_ERR);
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
PresentationPresentingInfo::NotifyResponderFailure()
|
|
{
|
|
PRES_DEBUG("%s:id[%s], role[%d]\n", __func__,
|
|
NS_ConvertUTF16toUTF8(mSessionId).get(), mRole);
|
|
|
|
if (mTimer) {
|
|
mTimer->Cancel();
|
|
mTimer = nullptr;
|
|
}
|
|
|
|
return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
|
|
}
|
|
|
|
nsresult
|
|
PresentationPresentingInfo::DoReconnect()
|
|
{
|
|
PRES_DEBUG("%s:id[%s], role[%d]\n", __func__,
|
|
NS_ConvertUTF16toUTF8(mSessionId).get(), mRole);
|
|
|
|
MOZ_ASSERT(mState == nsIPresentationSessionListener::STATE_CLOSED);
|
|
|
|
SetStateWithReason(nsIPresentationSessionListener::STATE_CONNECTING, NS_OK);
|
|
|
|
return NotifyResponderReady();
|
|
}
|
|
|
|
// nsIPresentationControlChannelListener
|
|
NS_IMETHODIMP
|
|
PresentationPresentingInfo::OnOffer(nsIPresentationChannelDescription* aDescription)
|
|
{
|
|
if (NS_WARN_IF(mHasFlushPendingEvents)) {
|
|
return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
|
|
}
|
|
|
|
if (NS_WARN_IF(!aDescription)) {
|
|
return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
|
|
}
|
|
|
|
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(NS_ERROR_DOM_OPERATION_ERR);
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PresentationPresentingInfo::OnAnswer(nsIPresentationChannelDescription* aDescription)
|
|
{
|
|
MOZ_ASSERT(false, "Receiver side should not receive answer.");
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PresentationPresentingInfo::OnIceCandidate(const nsAString& aCandidate)
|
|
{
|
|
if (!mBuilder && !mHasFlushPendingEvents) {
|
|
mPendingCandidates.AppendElement(nsString(aCandidate));
|
|
return NS_OK;
|
|
}
|
|
|
|
if (NS_WARN_IF(!mBuilder && mHasFlushPendingEvents)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsCOMPtr<nsIPresentationDataChannelSessionTransportBuilder>
|
|
builder = do_QueryInterface(mBuilder);
|
|
|
|
return builder->OnIceCandidate(aCandidate);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PresentationPresentingInfo::NotifyConnected()
|
|
{
|
|
PRES_DEBUG("%s:id[%s], role[%d]\n", __func__,
|
|
NS_ConvertUTF16toUTF8(mSessionId).get(), mRole);
|
|
|
|
if (nsIPresentationSessionListener::STATE_TERMINATED == mState) {
|
|
ContinueTermination();
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PresentationPresentingInfo::NotifyReconnected()
|
|
{
|
|
MOZ_ASSERT(false, "NotifyReconnected should not be called at receiver side.");
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PresentationPresentingInfo::NotifyDisconnected(nsresult aReason)
|
|
{
|
|
PRES_DEBUG("%s:id[%s], reason[%" PRIx32 "], role[%d]\n", __func__,
|
|
NS_ConvertUTF16toUTF8(mSessionId).get(), static_cast<uint32_t>(aReason),
|
|
mRole);
|
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (mTransportType == nsIPresentationChannelDescription::TYPE_DATACHANNEL) {
|
|
nsCOMPtr<nsIPresentationDataChannelSessionTransportBuilder>
|
|
builder = do_QueryInterface(mBuilder);
|
|
if (builder) {
|
|
Unused << NS_WARN_IF(NS_FAILED(builder->NotifyDisconnected(aReason)));
|
|
}
|
|
}
|
|
|
|
// 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))) {
|
|
// The presentation session instance may already exist.
|
|
// Change the state to TERMINATED since it never succeeds.
|
|
SetStateWithReason(nsIPresentationSessionListener::STATE_TERMINATED, aReason);
|
|
|
|
// Reply error for an abnormal close.
|
|
return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// nsITimerCallback
|
|
NS_IMETHODIMP
|
|
PresentationPresentingInfo::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);
|
|
}
|
|
|
|
// nsITimerCallback
|
|
NS_IMETHODIMP
|
|
PresentationPresentingInfo::GetName(nsACString& aName)
|
|
{
|
|
aName.AssignLiteral("PresentationPresentingInfo");
|
|
return NS_OK;
|
|
}
|
|
|
|
// PromiseNativeHandler
|
|
void
|
|
PresentationPresentingInfo::ResolvedCallback(JSContext* aCx,
|
|
JS::Handle<JS::Value> aValue)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (NS_WARN_IF(!aValue.isObject())) {
|
|
ReplyError(NS_ERROR_DOM_OPERATION_ERR);
|
|
return;
|
|
}
|
|
|
|
JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
|
|
if (NS_WARN_IF(!obj)) {
|
|
ReplyError(NS_ERROR_DOM_OPERATION_ERR);
|
|
return;
|
|
}
|
|
|
|
// Start to listen to document state change event |STATE_TRANSFERRING|.
|
|
// Use Element to support both HTMLIFrameElement and nsXULElement.
|
|
Element* frame = nullptr;
|
|
nsresult rv = UNWRAP_OBJECT(Element, &obj, frame);
|
|
if (NS_WARN_IF(!frame)) {
|
|
ReplyError(NS_ERROR_DOM_OPERATION_ERR);
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIFrameLoaderOwner> owner = do_QueryInterface((nsIFrameLoaderOwner*) frame);
|
|
if (NS_WARN_IF(!owner)) {
|
|
ReplyError(NS_ERROR_DOM_OPERATION_ERR);
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIFrameLoader> frameLoader = owner->GetFrameLoader();
|
|
if (NS_WARN_IF(!frameLoader)) {
|
|
ReplyError(NS_ERROR_DOM_OPERATION_ERR);
|
|
return;
|
|
}
|
|
|
|
RefPtr<TabParent> tabParent = TabParent::GetFrom(frameLoader);
|
|
if (tabParent) {
|
|
// OOP frame
|
|
// Notify the content process that a receiver page has launched, so it can
|
|
// start monitoring the loading progress.
|
|
mContentParent = tabParent->Manager();
|
|
Unused << NS_WARN_IF(!static_cast<ContentParent*>(mContentParent.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(NS_ERROR_DOM_OPERATION_ERR);
|
|
return;
|
|
}
|
|
|
|
// Keep an eye on the loading progress of the receiver page.
|
|
mLoadingCallback = new PresentationResponderLoadingCallback(mSessionId);
|
|
rv = mLoadingCallback->Init(docShell);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
ReplyError(NS_ERROR_DOM_OPERATION_ERR);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
PresentationPresentingInfo::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_OPERATION_ERR);
|
|
}
|