summaryrefslogtreecommitdiffstats
path: root/dom/presentation/PresentationSessionInfo.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/presentation/PresentationSessionInfo.cpp')
-rw-r--r--dom/presentation/PresentationSessionInfo.cpp1536
1 files changed, 1536 insertions, 0 deletions
diff --git a/dom/presentation/PresentationSessionInfo.cpp b/dom/presentation/PresentationSessionInfo.cpp
new file mode 100644
index 0000000000..2db21b31e1
--- /dev/null
+++ b/dom/presentation/PresentationSessionInfo.cpp
@@ -0,0 +1,1536 @@
+/* -*- 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 "PresentationSessionInfo.h"
+
+#include <utility>
+
+#include "PresentationLog.h"
+#include "PresentationService.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/dom/BrowserParent.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/ElementBinding.h"
+#include "mozilla/dom/HTMLIFrameElementBinding.h"
+#include "nsContentUtils.h"
+#include "nsFrameLoader.h"
+#include "nsFrameLoaderOwner.h"
+#include "nsGlobalWindow.h"
+#include "nsIDocShell.h"
+#include "nsIMutableArray.h"
+#include "nsINetAddr.h"
+#include "nsISocketTransport.h"
+#include "nsISupportsPrimitives.h"
+#include "nsNetCID.h"
+#include "nsQueryObject.h"
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+
+#ifdef MOZ_WIDGET_ANDROID
+# include "nsIPresentationNetworkHelper.h"
+#endif // MOZ_WIDGET_ANDROID
+
+/*
+ * 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() = default;
+
+ nsCString mAddress;
+ uint16_t mPort;
+};
+
+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(Blob* 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;
+}
+
+void PresentationSessionInfo::OnTerminate(
+ nsIPresentationControlChannel* aControlChannel) {
+ mIsOnTerminating = true; // Mark for terminating transport channel
+ SetStateWithReason(nsIPresentationSessionListener::STATE_TERMINATED, NS_OK);
+ SetControlChannel(aControlChannel);
+}
+
+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;
+ }
+
+ return nsGlobalWindowInner::GetInnerWindowWithId(windowId);
+}
+
+/* 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 nsTArray<nsCString>& aAddressArray) {
+ if (aAddressArray.IsEmpty()) {
+ return OnListNetworkAddressesFailed();
+ }
+
+ // TODO bug 1228504 Take all IP addresses in PresentationChannelDescription
+ // into account.
+
+ // 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, aAddressArray[0]));
+
+ 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 =
+ std::move(mReconnectCallback);
+ 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;
+ }
+
+ RefPtr<nsFrameLoaderOwner> owner = do_QueryObject(frame);
+ if (NS_WARN_IF(!owner)) {
+ ReplyError(NS_ERROR_DOM_OPERATION_ERR);
+ return;
+ }
+
+ RefPtr<nsFrameLoader> frameLoader = owner->GetFrameLoader();
+ if (NS_WARN_IF(!frameLoader)) {
+ ReplyError(NS_ERROR_DOM_OPERATION_ERR);
+ return;
+ }
+
+ RefPtr<BrowserParent> browserParent = BrowserParent::GetFrom(frameLoader);
+ if (browserParent) {
+ // OOP frame
+ // Notify the content process that a receiver page has launched, so it can
+ // start monitoring the loading progress.
+ mContentParent = browserParent->Manager();
+ Unused << NS_WARN_IF(!static_cast<ContentParent*>(mContentParent.get())
+ ->SendNotifyPresentationReceiverLaunched(
+ browserParent, mSessionId));
+ } else {
+ // In-process frame
+ IgnoredErrorResult error;
+ nsCOMPtr<nsIDocShell> docShell = frameLoader->GetDocShell(error);
+ if (NS_WARN_IF(error.Failed())) {
+ 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);
+}
+
+} // namespace dom
+} // namespace mozilla