From 1efa4382a3b267bef9c8744bd8e38fe5adc5f394 Mon Sep 17 00:00:00 2001 From: Sean Lin Date: Wed, 22 Apr 2015 16:01:38 +0800 Subject: [PATCH] Bug 1069230 - Presentation API implementation. Part 8 - Data transport channel. r=jdm --- dom/presentation/PresentationSessionInfo.cpp | 211 +++++++- dom/presentation/PresentationSessionInfo.h | 2 + .../PresentationSessionTransport.cpp | 484 ++++++++++++++++++ .../PresentationSessionTransport.h | 99 ++++ .../nsIPresentationSessionTransport.idl | 3 - dom/presentation/moz.build | 2 + .../test_presentation_session_transport.js | 171 +++++++ dom/presentation/tests/xpcshell/xpcshell.ini | 1 + layout/build/nsLayoutModule.cpp | 8 + 9 files changed, 975 insertions(+), 6 deletions(-) create mode 100644 dom/presentation/PresentationSessionTransport.cpp create mode 100644 dom/presentation/PresentationSessionTransport.h create mode 100644 dom/presentation/tests/xpcshell/test_presentation_session_transport.js diff --git a/dom/presentation/PresentationSessionInfo.cpp b/dom/presentation/PresentationSessionInfo.cpp index 7c0f3b6a9bdf..65c94fc5d841 100644 --- a/dom/presentation/PresentationSessionInfo.cpp +++ b/dom/presentation/PresentationSessionInfo.cpp @@ -11,15 +11,119 @@ #include "mozilla/Services.h" #include "nsIDocShell.h" #include "nsIFrameLoader.h" +#include "nsIMutableArray.h" +#include "nsINetAddr.h" +#include "nsISocketTransport.h" +#include "nsISupportsPrimitives.h" +#include "nsNetCID.h" #include "nsServiceManagerUtils.h" #include "nsThreadUtils.h" #include "PresentationService.h" #include "PresentationSessionInfo.h" +#ifdef MOZ_WIDGET_GONK +#include "nsINetworkInterface.h" +#include "nsINetworkManager.h" +#endif + using namespace mozilla; using namespace mozilla::dom; using namespace mozilla::services; +/* + * Implementation of PresentationChannelDescription + */ + +namespace mozilla { +namespace dom { + +class PresentationChannelDescription final : public nsIPresentationChannelDescription +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIPRESENTATIONCHANNELDESCRIPTION + + PresentationChannelDescription(nsACString& aAddress, + uint16_t aPort) + : mAddress(aAddress) + , mPort(aPort) + { + } + +private: + ~PresentationChannelDescription() {} + + nsCString mAddress; + uint16_t mPort; +}; + +} // namespace dom +} // namespace mozilla + +NS_IMPL_ISUPPORTS(PresentationChannelDescription, nsIPresentationChannelDescription) + +NS_IMETHODIMP +PresentationChannelDescription::GetType(uint8_t* aRetVal) +{ + if (NS_WARN_IF(!aRetVal)) { + return NS_ERROR_INVALID_POINTER; + } + + // TODO bug 1148307 Implement PresentationSessionTransport with DataChannel. + // Only support TCP socket for now. + *aRetVal = nsIPresentationChannelDescription::TYPE_TCP; + return NS_OK; +} + +NS_IMETHODIMP +PresentationChannelDescription::GetTcpAddress(nsIArray** aRetVal) +{ + if (NS_WARN_IF(!aRetVal)) { + return NS_ERROR_INVALID_POINTER; + } + + nsCOMPtr array = do_CreateInstance(NS_ARRAY_CONTRACTID); + if (NS_WARN_IF(!array)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + // TODO bug 1148307 Implement PresentationSessionTransport with DataChannel. + // Ultimately we may use all the available addresses. DataChannel appears + // more robust upon handling ICE. And at the first stage Presentation API is + // only exposed on Firefox OS where the first IP appears enough for most + // scenarios. + nsCOMPtr address = do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID); + if (NS_WARN_IF(!address)) { + return NS_ERROR_OUT_OF_MEMORY; + } + address->SetData(mAddress); + + array->AppendElement(address, false); + array.forget(aRetVal); + + return NS_OK; +} + +NS_IMETHODIMP +PresentationChannelDescription::GetTcpPort(uint16_t* aRetVal) +{ + if (NS_WARN_IF(!aRetVal)) { + return NS_ERROR_INVALID_POINTER; + } + + *aRetVal = mPort; + return NS_OK; +} + +NS_IMETHODIMP +PresentationChannelDescription::GetDataChannelSDP(nsAString& aDataChannelSDP) +{ + // TODO bug 1148307 Implement PresentationSessionTransport with DataChannel. + // Only support TCP socket for now. + aDataChannelSDP.Truncate(); + return NS_OK; +} + /* * Implementation of PresentationSessionInfo */ @@ -231,8 +335,42 @@ PresentationRequesterInfo::Init(nsIPresentationControlChannel* aControlChannel) { PresentationSessionInfo::Init(aControlChannel); - // TODO Initialize |mServerSocket|, use |this| as the listener, and prepare to - // send offer. + // Initialize |mServerSocket| for bootstrapping the data transport channel and + // use |this| as the listener. + mServerSocket = do_CreateInstance(NS_SERVERSOCKET_CONTRACTID); + if (NS_WARN_IF(!mServerSocket)) { + return ReplyError(NS_ERROR_NOT_AVAILABLE); + } + + nsresult rv = mServerSocket->Init(-1, false, -1); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = mServerSocket->AsyncListen(this); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Prepare and send the offer. + int32_t port; + rv = mServerSocket->GetPort(&port); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCString address; + rv = GetAddress(address); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsRefPtr description = + new PresentationChannelDescription(address, static_cast(port)); + rv = mControlChannel->SendOffer(description); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } return NS_OK; } @@ -249,6 +387,51 @@ PresentationRequesterInfo::Shutdown(nsresult aReason) } } +nsresult +PresentationRequesterInfo::GetAddress(nsACString& aAddress) +{ +#ifdef MOZ_WIDGET_GONK + nsCOMPtr networkManager = + do_GetService("@mozilla.org/network/manager;1"); + if (NS_WARN_IF(!networkManager)) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsCOMPtr activeNetworkInfo; + networkManager->GetActiveNetworkInfo(getter_AddRefs(activeNetworkInfo)); + if (NS_WARN_IF(!activeNetworkInfo)) { + return NS_ERROR_FAILURE; + } + + char16_t** ips = nullptr; + uint32_t* prefixes = nullptr; + uint32_t count = 0; + activeNetworkInfo->GetAddresses(&ips, &prefixes, &count); + if (NS_WARN_IF(!count)) { + NS_Free(prefixes); + NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, ips); + return NS_ERROR_FAILURE; + } + + // TODO bug 1148307 Implement PresentationSessionTransport with DataChannel. + // Ultimately we may use all the available addresses. DataChannel appears + // more robust upon handling ICE. And at the first stage Presentation API is + // only exposed on Firefox OS where the first IP appears enough for most + // scenarios. + nsAutoString ip; + ip.Assign(ips[0]); + aAddress = NS_ConvertUTF16toUTF8(ip); + + NS_Free(prefixes); + NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, ips); +#else + // TODO Get host IP via other platforms. + aAddress.Truncate(); +#endif + + return NS_OK; +} + // nsIPresentationControlChannelListener NS_IMETHODIMP PresentationRequesterInfo::OnOffer(nsIPresentationChannelDescription* aDescription) @@ -425,7 +608,29 @@ PresentationResponderInfo::InitTransportAndSendAnswer() return rv; } - // TODO Prepare and send the answer. + // Prepare and send the answer. + // TODO bug 1148307 Implement PresentationSessionTransport with DataChannel. + // In the current implementation of |PresentationSessionTransport|, + // |GetSelfAddress| cannot return the real info when it's initialized via + // |InitWithChannelDescription|. Yet this deficiency only affects the channel + // description for the answer, which is not actually checked at requester side. + nsCOMPtr selfAddr; + rv = mTransport->GetSelfAddress(getter_AddRefs(selfAddr)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCString address; + selfAddr->GetAddress(address); + uint16_t port; + selfAddr->GetPort(&port); + nsCOMPtr description = + new PresentationChannelDescription(address, port); + + rv = mControlChannel->SendAnswer(description); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } return NS_OK; } diff --git a/dom/presentation/PresentationSessionInfo.h b/dom/presentation/PresentationSessionInfo.h index e441eb221820..5ec21cf4f5c3 100644 --- a/dom/presentation/PresentationSessionInfo.h +++ b/dom/presentation/PresentationSessionInfo.h @@ -145,6 +145,8 @@ private: void Shutdown(nsresult aReason) override; + nsresult GetAddress(nsACString& aAddress); + nsCOMPtr mServerSocket; }; diff --git a/dom/presentation/PresentationSessionTransport.cpp b/dom/presentation/PresentationSessionTransport.cpp new file mode 100644 index 000000000000..efd42621c1f2 --- /dev/null +++ b/dom/presentation/PresentationSessionTransport.cpp @@ -0,0 +1,484 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsArrayUtils.h" +#include "nsIAsyncStreamCopier.h" +#include "nsIInputStreamPump.h" +#include "nsIMultiplexInputStream.h" +#include "nsIMutableArray.h" +#include "nsIOutputStream.h" +#include "nsIPresentationControlChannel.h" +#include "nsIScriptableInputStream.h" +#include "nsISocketTransport.h" +#include "nsISocketTransportService.h" +#include "nsISupportsPrimitives.h" +#include "nsNetUtil.h" +#include "nsServiceManagerUtils.h" +#include "nsStreamUtils.h" +#include "nsThreadUtils.h" +#include "PresentationSessionTransport.h" + +#define BUFFER_SIZE 65536 + +using namespace mozilla; +using namespace mozilla::dom; + +class CopierCallbacks final : public nsIRequestObserver +{ +public: + explicit CopierCallbacks(PresentationSessionTransport* aTransport) + : mOwner(aTransport) + {} + + NS_DECL_ISUPPORTS + NS_DECL_NSIREQUESTOBSERVER +private: + ~CopierCallbacks() {} + + nsRefPtr mOwner; +}; + +NS_IMPL_ISUPPORTS(CopierCallbacks, nsIRequestObserver) + +NS_IMETHODIMP +CopierCallbacks::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext) +{ + return NS_OK; +} + +NS_IMETHODIMP +CopierCallbacks::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext, nsresult aStatus) +{ + mOwner->NotifyCopyComplete(aStatus); + return NS_OK; +} + +NS_IMPL_ISUPPORTS(PresentationSessionTransport, + nsIPresentationSessionTransport, + nsITransportEventSink, + nsIInputStreamCallback, + nsIStreamListener, + nsIRequestObserver) + +PresentationSessionTransport::PresentationSessionTransport() + : mReadyState(CLOSED) + , mAsyncCopierActive(false) + , mCloseStatus(NS_OK) +{ +} + +PresentationSessionTransport::~PresentationSessionTransport() +{ +} + +NS_IMETHODIMP +PresentationSessionTransport::InitWithSocketTransport(nsISocketTransport* aTransport, + nsIPresentationSessionTransportCallback* aCallback) +{ + if (NS_WARN_IF(!aCallback)) { + return NS_ERROR_INVALID_ARG; + } + mCallback = aCallback; + + if (NS_WARN_IF(!aTransport)) { + return NS_ERROR_INVALID_ARG; + } + mTransport = aTransport; + + nsresult rv = CreateStream(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + SetReadyState(OPEN); + + rv = CreateInputStreamPump(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +NS_IMETHODIMP +PresentationSessionTransport::InitWithChannelDescription(nsIPresentationChannelDescription* aDescription, + nsIPresentationSessionTransportCallback* aCallback) +{ + if (NS_WARN_IF(!aCallback)) { + return NS_ERROR_INVALID_ARG; + } + mCallback = aCallback; + + if (NS_WARN_IF(!aDescription)) { + return NS_ERROR_INVALID_ARG; + } + + uint16_t serverPort; + nsresult rv = aDescription->GetTcpPort(&serverPort); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr serverHosts; + rv = aDescription->GetTcpAddress(getter_AddRefs(serverHosts)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // TODO bug 1148307 Implement PresentationSessionTransport with DataChannel. + // Ultimately we may use all the available addresses. DataChannel appears + // more robust upon handling ICE. And at the first stage Presentation API is + // only exposed on Firefox OS where the first IP appears enough for most + // scenarios. + nsCOMPtr supportStr = do_QueryElementAt(serverHosts, 0); + if (NS_WARN_IF(!supportStr)) { + return NS_ERROR_INVALID_ARG; + } + + nsAutoCString serverHost; + supportStr->GetData(serverHost); + if (serverHost.IsEmpty()) { + return NS_ERROR_INVALID_ARG; + } + + SetReadyState(CONNECTING); + + nsCOMPtr sts = + do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID); + if (NS_WARN_IF(!sts)) { + return NS_ERROR_NOT_AVAILABLE; + } + rv = sts->CreateTransport(nullptr, 0, serverHost, serverPort, nullptr, + getter_AddRefs(mTransport)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr mainThread; + NS_GetMainThread(getter_AddRefs(mainThread)); + + mTransport->SetEventSink(this, mainThread); + + rv = CreateStream(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +nsresult +PresentationSessionTransport::CreateStream() +{ + nsresult rv = mTransport->OpenInputStream(0, 0, 0, getter_AddRefs(mSocketInputStream)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + rv = mTransport->OpenOutputStream(nsITransport::OPEN_UNBUFFERED, 0, 0, getter_AddRefs(mSocketOutputStream)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // If the other side is not listening, we will get an |onInputStreamReady| + // callback where |available| raises to indicate the connection was refused. + nsCOMPtr asyncStream = do_QueryInterface(mSocketInputStream); + if (NS_WARN_IF(!asyncStream)) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsCOMPtr mainThread; + NS_GetMainThread(getter_AddRefs(mainThread)); + + rv = asyncStream->AsyncWait(this, nsIAsyncInputStream::WAIT_CLOSURE_ONLY, 0, mainThread); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mInputStreamScriptable = do_CreateInstance("@mozilla.org/scriptableinputstream;1", &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + rv = mInputStreamScriptable->Init(mSocketInputStream); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mMultiplexStream = do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1", &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mMultiplexStreamCopier = do_CreateInstance("@mozilla.org/network/async-stream-copier;1", &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr sts = + do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID); + if (NS_WARN_IF(!sts)) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsCOMPtr target = do_QueryInterface(sts); + rv = mMultiplexStreamCopier->Init(mMultiplexStream, + mSocketOutputStream, + target, + true, /* source buffered */ + false, /* sink buffered */ + BUFFER_SIZE, + false, /* close source */ + false); /* close sink */ + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +nsresult +PresentationSessionTransport::CreateInputStreamPump() +{ + nsresult rv; + mInputStreamPump = do_CreateInstance(NS_INPUTSTREAMPUMP_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = mInputStreamPump->Init(mSocketInputStream, -1, -1, 0, 0, false); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = mInputStreamPump->AsyncRead(this, nullptr); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +NS_IMETHODIMP +PresentationSessionTransport::GetCallback(nsIPresentationSessionTransportCallback** aCallback) +{ + nsCOMPtr callback = mCallback; + callback.forget(aCallback); + return NS_OK; +} + +NS_IMETHODIMP +PresentationSessionTransport::SetCallback(nsIPresentationSessionTransportCallback* aCallback) +{ + mCallback = aCallback; + return NS_OK; +} + +NS_IMETHODIMP +PresentationSessionTransport::GetSelfAddress(nsINetAddr** aSelfAddress) +{ + if (NS_WARN_IF(mReadyState != OPEN)) { + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + return mTransport->GetScriptableSelfAddr(aSelfAddress); +} + +void +PresentationSessionTransport::EnsureCopying() +{ + if (mAsyncCopierActive) { + return; + } + + mAsyncCopierActive = true; + nsRefPtr callbacks = new CopierCallbacks(this); + NS_WARN_IF(NS_FAILED(mMultiplexStreamCopier->AsyncCopy(callbacks, nullptr))); +} + +void +PresentationSessionTransport::NotifyCopyComplete(nsresult aStatus) +{ + mAsyncCopierActive = false; + mMultiplexStream->RemoveStream(0); + if (NS_WARN_IF(NS_FAILED(aStatus))) { + if (mReadyState != CLOSED) { + mCloseStatus = aStatus; + SetReadyState(CLOSED); + } + return; + } + + uint32_t count; + nsresult rv = mMultiplexStream->GetCount(&count); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + if (count) { + EnsureCopying(); + return; + } + + if (mReadyState == CLOSING) { + mSocketOutputStream->Close(); + mCloseStatus = NS_OK; + SetReadyState(CLOSED); + } +} + +NS_IMETHODIMP +PresentationSessionTransport::Send(nsIInputStream* aData) +{ + if (NS_WARN_IF(mReadyState != OPEN)) { + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + mMultiplexStream->AppendStream(aData); + + EnsureCopying(); + + return NS_OK; +} + +NS_IMETHODIMP +PresentationSessionTransport::Close(nsresult aReason) +{ + if (mReadyState == CLOSED || mReadyState == CLOSING) { + return NS_OK; + } + + mCloseStatus = aReason; + SetReadyState(CLOSING); + + uint32_t count = 0; + mMultiplexStream->GetCount(&count); + if (!count) { + mSocketOutputStream->Close(); + } + + mSocketInputStream->Close(); + + return NS_OK; +} + +void +PresentationSessionTransport::SetReadyState(ReadyState aReadyState) +{ + mReadyState = aReadyState; + + if (mReadyState == OPEN && mCallback) { + // Notify the transport channel is ready. + NS_WARN_IF(NS_FAILED(mCallback->NotifyTransportReady())); + } else if (mReadyState == CLOSED && mCallback) { + // Notify the transport channel has been shut down. + NS_WARN_IF(NS_FAILED(mCallback->NotifyTransportClosed(mCloseStatus))); + } +} + +// nsITransportEventSink +NS_IMETHODIMP +PresentationSessionTransport::OnTransportStatus(nsITransport* aTransport, + nsresult aStatus, + int64_t aProgress, + int64_t aProgressMax) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (aStatus != NS_NET_STATUS_CONNECTED_TO) { + return NS_OK; + } + + SetReadyState(OPEN); + + nsresult rv = CreateInputStreamPump(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +// nsIInputStreamCallback +NS_IMETHODIMP +PresentationSessionTransport::OnInputStreamReady(nsIAsyncInputStream* aStream) +{ + MOZ_ASSERT(NS_IsMainThread()); + + // Only used for detecting if the connection was refused. + uint64_t dummy; + nsresult rv = aStream->Available(&dummy); + if (NS_WARN_IF(NS_FAILED(rv))) { + if (mReadyState != CLOSED) { + mCloseStatus = NS_ERROR_CONNECTION_REFUSED; + SetReadyState(CLOSED); + } + } + + return NS_OK; +} + +// nsIRequestObserver +NS_IMETHODIMP +PresentationSessionTransport::OnStartRequest(nsIRequest* aRequest, + nsISupports* aContext) +{ + // Do nothing. + return NS_OK; +} + +NS_IMETHODIMP +PresentationSessionTransport::OnStopRequest(nsIRequest* aRequest, + nsISupports* aContext, + nsresult aStatusCode) +{ + MOZ_ASSERT(NS_IsMainThread()); + + uint32_t count; + nsresult rv = mMultiplexStream->GetCount(&count); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mInputStreamPump = nullptr; + + if (count != 0 && NS_SUCCEEDED(aStatusCode)) { + // If we have some buffered output still, and status is not an error, the + // other side has done a half-close, but we don't want to be in the close + // state until we are done sending everything that was buffered. We also + // don't want to call |NotifyTransportClosed| yet. + return NS_OK; + } + + // We call this even if there is no error. + if (mReadyState != CLOSED) { + mCloseStatus = aStatusCode; + SetReadyState(CLOSED); + } + return NS_OK; +} + +// nsIStreamListener +NS_IMETHODIMP +PresentationSessionTransport::OnDataAvailable(nsIRequest* aRequest, + nsISupports* aContext, + nsIInputStream* aStream, + uint64_t aOffset, + uint32_t aCount) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (NS_WARN_IF(!mCallback)) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsCString data; + nsresult rv = mInputStreamScriptable->ReadBytes(aCount, data); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Pass the incoming data to the listener. + return mCallback->NotifyData(data); +} diff --git a/dom/presentation/PresentationSessionTransport.h b/dom/presentation/PresentationSessionTransport.h new file mode 100644 index 000000000000..10624e87dec2 --- /dev/null +++ b/dom/presentation/PresentationSessionTransport.h @@ -0,0 +1,99 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_PresentationSessionTransport_h +#define mozilla_dom_PresentationSessionTransport_h + +#include "mozilla/nsRefPtr.h" +#include "nsCOMPtr.h" +#include "nsIAsyncInputStream.h" +#include "nsIPresentationSessionTransport.h" +#include "nsIStreamListener.h" +#include "nsISupportsImpl.h" +#include "nsITransport.h" + +class nsISocketTransport; +class nsIInputStreamPump; +class nsIScriptableInputStream; +class nsIMultiplexInputStream; +class nsIAsyncStreamCopier; +class nsIInputStream; + +namespace mozilla { +namespace dom { + +/* + * App-to-App transport channel for the presentation session. It's usually + * initialized with an |InitWithSocketTransport| call if at the presenting sender + * side; whereas it's initialized with an |InitWithChannelDescription| if at the + * presenting receiver side. The lifetime is managed in either + * |PresentationRequesterInfo| (sender side) or |PresentationResponderInfo| + * (receiver side) in PresentationSessionInfo.cpp. + * + * TODO bug 1148307 Implement PresentationSessionTransport with DataChannel. + * The implementation over the TCP channel is primarily used for the early stage + * of Presentation API (without SSL) and should be migrated to DataChannel with + * full support soon. + */ +class PresentationSessionTransport final : public nsIPresentationSessionTransport + , public nsITransportEventSink + , public nsIInputStreamCallback + , public nsIStreamListener +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIPRESENTATIONSESSIONTRANSPORT + NS_DECL_NSITRANSPORTEVENTSINK + NS_DECL_NSIINPUTSTREAMCALLBACK + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + + PresentationSessionTransport(); + + void NotifyCopyComplete(nsresult aStatus); + +private: + ~PresentationSessionTransport(); + + nsresult CreateStream(); + + nsresult CreateInputStreamPump(); + + void EnsureCopying(); + + enum ReadyState { + CONNECTING, + OPEN, + CLOSING, + CLOSED + }; + + void SetReadyState(ReadyState aReadyState); + + ReadyState mReadyState; + bool mAsyncCopierActive; + nsresult mCloseStatus; + + // Raw socket streams + nsCOMPtr mTransport; + nsCOMPtr mSocketInputStream; + nsCOMPtr mSocketOutputStream; + + // Input stream machinery + nsCOMPtr mInputStreamPump; + nsCOMPtr mInputStreamScriptable; + + // Output stream machinery + nsCOMPtr mMultiplexStream; + nsCOMPtr mMultiplexStreamCopier; + + nsCOMPtr mCallback; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_PresentationSessionTransport_h diff --git a/dom/presentation/interfaces/nsIPresentationSessionTransport.idl b/dom/presentation/interfaces/nsIPresentationSessionTransport.idl index 8f5668e54758..73f3970449a4 100644 --- a/dom/presentation/interfaces/nsIPresentationSessionTransport.idl +++ b/dom/presentation/interfaces/nsIPresentationSessionTransport.idl @@ -10,9 +10,6 @@ interface nsIPresentationChannelDescription; interface nsISocketTransport; %{C++ -#define PRESENTATION_SESSION_TRANSPORT_CID \ - { 0xc9d023f4, 0x6228, 0x4c07, \ - { 0x8b, 0x1d, 0x9c, 0x19, 0x57, 0x3f, 0xaa, 0x27 } } #define PRESENTATION_SESSION_TRANSPORT_CONTRACTID \ "@mozilla.org/presentation/presentationsessiontransport;1" %} diff --git a/dom/presentation/moz.build b/dom/presentation/moz.build index 18f37fb5bf89..13f9582e426e 100644 --- a/dom/presentation/moz.build +++ b/dom/presentation/moz.build @@ -19,6 +19,7 @@ EXPORTS.mozilla.dom += [ 'PresentationService.h', 'PresentationSession.h', 'PresentationSessionInfo.h', + 'PresentationSessionTransport.h', ] UNIFIED_SOURCES += [ @@ -32,6 +33,7 @@ UNIFIED_SOURCES += [ 'PresentationSession.cpp', 'PresentationSessionInfo.cpp', 'PresentationSessionRequest.cpp', + 'PresentationSessionTransport.cpp', ] EXTRA_COMPONENTS += [ diff --git a/dom/presentation/tests/xpcshell/test_presentation_session_transport.js b/dom/presentation/tests/xpcshell/test_presentation_session_transport.js new file mode 100644 index 000000000000..f254e61fa052 --- /dev/null +++ b/dom/presentation/tests/xpcshell/test_presentation_session_transport.js @@ -0,0 +1,171 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +'use strict'; + +const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr, Constructor: CC } = Components; +const ServerSocket = CC("@mozilla.org/network/server-socket;1", + "nsIServerSocket", + "init"); + +Cu.import('resource://gre/modules/XPCOMUtils.jsm'); +Cu.import('resource://gre/modules/Services.jsm'); + +var testServer = null; +var clientTransport = null; +var serverTransport = null; + +const clientMessage = "Client Message"; +const serverMessage = "Server Message"; + +const address = Cc["@mozilla.org/supports-cstring;1"] + .createInstance(Ci.nsISupportsCString); +address.data = "127.0.0.1"; +const addresses = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray); +addresses.appendElement(address, false); + +const serverChannelDescription = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationChannelDescription]), + type: 1, + tcpAddress: addresses, +}; + +var isClientReady = false; +var isServerReady = false; +var isClientClosed = false; +var isServerClosed = false; + +const clientCallback = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationSessionTransportCallback]), + notifyTransportReady: function () { + Assert.ok(true, "Client transport ready."); + + isClientReady = true; + if (isClientReady && isServerReady) { + run_next_test(); + } + }, + notifyTransportClosed: function (aReason) { + Assert.ok(true, "Client transport is closed."); + + isClientClosed = true; + if (isClientClosed && isServerClosed) { + run_next_test(); + } + }, + notifyData: function(aData) { + Assert.equal(aData, serverMessage, "Client transport receives data."); + run_next_test(); + }, +}; + +const serverCallback = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationSessionTransportCallback]), + notifyTransportReady: function () { + Assert.ok(true, "Server transport ready."); + + isServerReady = true; + if (isClientReady && isServerReady) { + run_next_test(); + } + }, + notifyTransportClosed: function (aReason) { + Assert.ok(true, "Server transport is closed."); + + isServerClosed = true; + if (isClientClosed && isServerClosed) { + run_next_test(); + } + }, + notifyData: function(aData) { + Assert.equal(aData, clientMessage, "Server transport receives data."); + run_next_test(); + }, +}; + +function TestServer() { + this.serverSocket = ServerSocket(-1, true, -1); + this.serverSocket.asyncListen(this) +} + +TestServer.prototype = { + onSocketAccepted: function(aSocket, aTransport) { + print("Test server gets a client connection."); + serverTransport = Cc["@mozilla.org/presentation/presentationsessiontransport;1"] + .createInstance(Ci.nsIPresentationSessionTransport); + serverTransport.initWithSocketTransport(aTransport, serverCallback); + }, + onStopListening: function(aSocket) { + print("Test server stops listening."); + }, + close: function() { + if (this.serverSocket) { + this.serverSocket.close(); + this.serverSocket = null; + } + } +}; + +// Set up the transport connection and ensure |notifyTransportReady| triggered +// at both sides. +function setup() { + clientTransport = Cc["@mozilla.org/presentation/presentationsessiontransport;1"] + .createInstance(Ci.nsIPresentationSessionTransport); + clientTransport.initWithChannelDescription(serverChannelDescription, clientCallback); +} + +// Test |selfAddress| attribute of |nsIPresentationSessionTransport|. +function selfAddress() { + var serverSelfAddress = serverTransport.selfAddress; + Assert.equal(serverSelfAddress.address, address.data, "The self address of server transport should be set."); + Assert.equal(serverSelfAddress.port, testServer.serverSocket.port, "The port of server transport should be set."); + + var clientSelfAddress = clientTransport.selfAddress; + Assert.ok(clientSelfAddress.address, "The self address of client transport should be set."); + Assert.ok(clientSelfAddress.port, "The port of client transport should be set."); + + run_next_test(); +} + +// Test the client sends a message and then a corresponding notification gets +// triggered at the server side. +function clientSendMessage() { + var stream = Cc["@mozilla.org/io/string-input-stream;1"] + .createInstance(Ci.nsIStringInputStream); + stream.setData(clientMessage, clientMessage.length); + clientTransport.send(stream); +} + +// Test the server sends a message an then a corresponding notification gets +// triggered at the client side. +function serverSendMessage() { + var stream = Cc["@mozilla.org/io/string-input-stream;1"] + .createInstance(Ci.nsIStringInputStream); + stream.setData(serverMessage, serverMessage.length); + serverTransport.send(stream); +} + +function transportClose() { + clientTransport.close(Cr.NS_OK); +} + +function shutdown() { + testServer.close(); + run_next_test(); +} + +add_test(setup); +add_test(selfAddress); +add_test(clientSendMessage); +add_test(serverSendMessage); +add_test(transportClose); +add_test(shutdown); + +function run_test() { + testServer = new TestServer(); + // Get the port of the test server. + serverChannelDescription.tcpPort = testServer.serverSocket.port; + + run_next_test(); +} diff --git a/dom/presentation/tests/xpcshell/xpcshell.ini b/dom/presentation/tests/xpcshell/xpcshell.ini index e954805c3895..76063d1479ed 100644 --- a/dom/presentation/tests/xpcshell/xpcshell.ini +++ b/dom/presentation/tests/xpcshell/xpcshell.ini @@ -4,4 +4,5 @@ tail = [test_multicast_dns_device_provider.js] [test_presentation_device_manager.js] +[test_presentation_session_transport.js] [test_tcp_control_channel.js] diff --git a/layout/build/nsLayoutModule.cpp b/layout/build/nsLayoutModule.cpp index 2af3ab0f2c4e..193c21a8fb2d 100644 --- a/layout/build/nsLayoutModule.cpp +++ b/layout/build/nsLayoutModule.cpp @@ -262,6 +262,7 @@ static void Shutdown(); #include "GMPService.h" #include "mozilla/dom/PresentationDeviceManager.h" +#include "mozilla/dom/PresentationSessionTransport.h" #include "mozilla/TextInputProcessor.h" @@ -294,6 +295,9 @@ using mozilla::gmp::GeckoMediaPluginService; #define PRESENTATION_DEVICE_MANAGER_CID \ { 0xe1e79dec, 0x4085, 0x4994, { 0xac, 0x5b, 0x74, 0x4b, 0x01, 0x66, 0x97, 0xe6 } } +#define PRESENTATION_SESSION_TRANSPORT_CID \ +{ 0xc9d023f4, 0x6228, 0x4c07, { 0x8b, 0x1d, 0x9c, 0x19, 0x57, 0x3f, 0xaa, 0x27 } } + already_AddRefed NS_CreatePresentationService(); // Factory Constructor @@ -406,6 +410,7 @@ NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(FakeInputPortService, NS_GENERIC_FACTORY_CONSTRUCTOR(InputPortData) NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIPresentationService, NS_CreatePresentationService) +NS_GENERIC_FACTORY_CONSTRUCTOR(PresentationSessionTransport) //----------------------------------------------------------------------------- static bool gInitialized = false; @@ -864,6 +869,7 @@ NS_DEFINE_NAMED_CID(GECKO_MEDIA_PLUGIN_SERVICE_CID); NS_DEFINE_NAMED_CID(PRESENTATION_SERVICE_CID); NS_DEFINE_NAMED_CID(PRESENTATION_DEVICE_MANAGER_CID); +NS_DEFINE_NAMED_CID(PRESENTATION_SESSION_TRANSPORT_CID); NS_DEFINE_NAMED_CID(TEXT_INPUT_PROCESSOR_CID); @@ -1163,6 +1169,7 @@ static const mozilla::Module::CIDEntry kLayoutCIDs[] = { { &kTV_PROGRAM_DATA_CID, false, nullptr, TVProgramDataConstructor }, { &kPRESENTATION_SERVICE_CID, false, nullptr, nsIPresentationServiceConstructor }, { &kPRESENTATION_DEVICE_MANAGER_CID, false, nullptr, PresentationDeviceManagerConstructor }, + { &kPRESENTATION_SESSION_TRANSPORT_CID, false, nullptr, PresentationSessionTransportConstructor }, { &kTEXT_INPUT_PROCESSOR_CID, false, nullptr, TextInputProcessorConstructor }, { &kFAKE_INPUTPORT_SERVICE_CID, false, nullptr, FakeInputPortServiceConstructor }, { &kINPUTPORT_DATA_CID, false, nullptr, InputPortDataConstructor }, @@ -1333,6 +1340,7 @@ static const mozilla::Module::ContractIDEntry kLayoutContracts[] = { { NS_VOICEMAIL_SERVICE_CONTRACTID, &kNS_VOICEMAIL_SERVICE_CID }, { PRESENTATION_SERVICE_CONTRACTID, &kPRESENTATION_SERVICE_CID }, { PRESENTATION_DEVICE_MANAGER_CONTRACTID, &kPRESENTATION_DEVICE_MANAGER_CID }, + { PRESENTATION_SESSION_TRANSPORT_CONTRACTID, &kPRESENTATION_SESSION_TRANSPORT_CID }, { "@mozilla.org/text-input-processor;1", &kTEXT_INPUT_PROCESSOR_CID }, { FAKE_INPUTPORT_SERVICE_CONTRACTID, &kFAKE_INPUTPORT_SERVICE_CID }, { INPUTPORT_DATA_CONTRACTID, &kINPUTPORT_DATA_CID },