Bug 1288297 - Construct PresentationRequest with multiple URLs, r=smaug

This commit is contained in:
Kershaw Chang 2016-09-05 01:17:00 +02:00
parent 5a098d36c7
commit 4317633349
22 changed files with 267 additions and 153 deletions

View File

@ -72,7 +72,7 @@ AvailabilityCollection::Remove(PresentationAvailability* aAvailability)
}
already_AddRefed<PresentationAvailability>
AvailabilityCollection::Find(const uint64_t aWindowId, const nsAString& aUrl)
AvailabilityCollection::Find(const uint64_t aWindowId, const nsTArray<nsString>& aUrls)
{
MOZ_ASSERT(NS_IsMainThread());
@ -85,7 +85,7 @@ AvailabilityCollection::Find(const uint64_t aWindowId, const nsAString& aUrl)
continue;
}
if (availability->Equals(aWindowId, aUrl)) {
if (availability->Equals(aWindowId, aUrls)) {
RefPtr<PresentationAvailability> matchedAvailability = availability.get();
return matchedAvailability.forget();
}

View File

@ -27,7 +27,7 @@ public:
void Remove(PresentationAvailability* aAvailability);
already_AddRefed<PresentationAvailability>
Find(const uint64_t aWindowId, const nsAString& aUrl);
Find(const uint64_t aWindowId, const nsTArray<nsString>& aUrls);
private:
friend class StaticAutoPtr<AvailabilityCollection>;

View File

@ -37,20 +37,20 @@ NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
/* static */ already_AddRefed<PresentationAvailability>
PresentationAvailability::Create(nsPIDOMWindowInner* aWindow,
const nsAString& aUrl,
const nsTArray<nsString>& aUrls,
RefPtr<Promise>& aPromise)
{
RefPtr<PresentationAvailability> availability =
new PresentationAvailability(aWindow, aUrl);
new PresentationAvailability(aWindow, aUrls);
return NS_WARN_IF(!availability->Init(aPromise)) ? nullptr
: availability.forget();
}
PresentationAvailability::PresentationAvailability(nsPIDOMWindowInner* aWindow,
const nsAString& aUrl)
const nsTArray<nsString>& aUrls)
: DOMEventTargetHelper(aWindow)
, mIsAvailable(false)
, mUrl(aUrl)
, mUrls(aUrls)
{
}
@ -120,10 +120,15 @@ PresentationAvailability::WrapObject(JSContext* aCx,
bool
PresentationAvailability::Equals(const uint64_t aWindowID,
const nsAString& aUrl) const
const nsTArray<nsString>& aUrls) const
{
if (GetOwner() && GetOwner()->WindowID() == aWindowID &&
mUrl.Equals(aUrl)) {
mUrls.Length() == aUrls.Length()) {
for (const auto& url : aUrls) {
if (!mUrls.Contains(url)) {
return false;
}
}
return true;
}
@ -162,8 +167,7 @@ PresentationAvailability::NotifyAvailableChange(bool aIsAvailable)
void
PresentationAvailability::UpdateAvailabilityAndDispatchEvent(bool aIsAvailable)
{
PRES_DEBUG("%s:id[%s]\n", __func__,
NS_ConvertUTF16toUTF8(mUrl).get());
PRES_DEBUG("%s\n", __func__);
bool isChanged = (aIsAvailable != mIsAvailable);
mIsAvailable = aIsAvailable;

View File

@ -29,7 +29,7 @@ public:
static already_AddRefed<PresentationAvailability>
Create(nsPIDOMWindowInner* aWindow,
const nsAString& aUrl,
const nsTArray<nsString>& aUrls,
RefPtr<Promise>& aPromise);
virtual void DisconnectFromOwner() override;
@ -37,7 +37,7 @@ public:
virtual JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
bool Equals(const uint64_t aWindowID, const nsAString& aUrl) const;
bool Equals(const uint64_t aWindowID, const nsTArray<nsString>& aUrls) const;
bool IsCachedValueReady();
@ -50,7 +50,7 @@ public:
private:
explicit PresentationAvailability(nsPIDOMWindowInner* aWindow,
const nsAString& aUrl);
const nsTArray<nsString>& aUrls);
virtual ~PresentationAvailability();
@ -64,7 +64,7 @@ private:
nsTArray<RefPtr<Promise>> mPromises;
nsString mUrl;
nsTArray<nsString> mUrls;
};
} // namespace dom

View File

@ -25,11 +25,9 @@ using namespace mozilla::dom;
NS_IMPL_ISUPPORTS(PresentationRequesterCallback, nsIPresentationServiceCallback)
PresentationRequesterCallback::PresentationRequesterCallback(PresentationRequest* aRequest,
const nsAString& aUrl,
const nsAString& aSessionId,
Promise* aPromise)
: mRequest(aRequest)
, mUrl(aUrl)
, mSessionId(aSessionId)
, mPromise(aPromise)
{
@ -44,12 +42,16 @@ PresentationRequesterCallback::~PresentationRequesterCallback()
// nsIPresentationServiceCallback
NS_IMETHODIMP
PresentationRequesterCallback::NotifySuccess()
PresentationRequesterCallback::NotifySuccess(const nsAString& aUrl)
{
MOZ_ASSERT(NS_IsMainThread());
if (aUrl.IsEmpty()) {
return NotifyError(NS_ERROR_DOM_OPERATION_ERR);
}
RefPtr<PresentationConnection> connection =
PresentationConnection::Create(mRequest->GetOwner(), mSessionId, mUrl,
PresentationConnection::Create(mRequest->GetOwner(), mSessionId, aUrl,
nsIPresentationService::ROLE_CONTROLLER);
if (NS_WARN_IF(!connection)) {
mPromise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
@ -79,11 +81,10 @@ NS_IMPL_ISUPPORTS_INHERITED0(PresentationReconnectCallback,
PresentationReconnectCallback::PresentationReconnectCallback(
PresentationRequest* aRequest,
const nsAString& aUrl,
const nsAString& aSessionId,
Promise* aPromise,
PresentationConnection* aConnection)
: PresentationRequesterCallback(aRequest, aUrl, aSessionId, aPromise)
: PresentationRequesterCallback(aRequest, aSessionId, aPromise)
, mConnection(aConnection)
{
}
@ -93,7 +94,7 @@ PresentationReconnectCallback::~PresentationReconnectCallback()
}
NS_IMETHODIMP
PresentationReconnectCallback::NotifySuccess()
PresentationReconnectCallback::NotifySuccess(const nsAString& aUrl)
{
MOZ_ASSERT(NS_IsMainThread());
@ -116,7 +117,7 @@ PresentationReconnectCallback::NotifySuccess()
} else {
// Use |PresentationRequesterCallback::NotifySuccess| to create a new
// connection since we don't find one that can be reused.
rv = PresentationRequesterCallback::NotifySuccess();
rv = PresentationRequesterCallback::NotifySuccess(aUrl);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}

View File

@ -31,7 +31,6 @@ public:
NS_DECL_NSIPRESENTATIONSERVICECALLBACK
PresentationRequesterCallback(PresentationRequest* aRequest,
const nsAString& aUrl,
const nsAString& aSessionId,
Promise* aPromise);
@ -39,7 +38,6 @@ protected:
virtual ~PresentationRequesterCallback();
RefPtr<PresentationRequest> mRequest;
nsString mUrl;
nsString mSessionId;
RefPtr<Promise> mPromise;
};
@ -51,7 +49,6 @@ public:
NS_DECL_NSIPRESENTATIONSERVICECALLBACK
PresentationReconnectCallback(PresentationRequest* aRequest,
const nsAString& aUrl,
const nsAString& aSessionId,
Promise* aPromise,
PresentationConnection* aConnection);

View File

@ -12,6 +12,7 @@
#include "mozilla/dom/PresentationRequestBinding.h"
#include "mozilla/dom/PresentationConnectionAvailableEvent.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/Move.h"
#include "mozIThirdPartyUtil.h"
#include "nsContentSecurityManager.h"
#include "nsCycleCollectionParticipant.h"
@ -64,6 +65,16 @@ GetAbsoluteURL(const nsAString& aUrl,
PresentationRequest::Constructor(const GlobalObject& aGlobal,
const nsAString& aUrl,
ErrorResult& aRv)
{
Sequence<nsString> urls;
urls.AppendElement(aUrl, fallible);
return Constructor(aGlobal, urls, aRv);
}
/* static */ already_AddRefed<PresentationRequest>
PresentationRequest::Constructor(const GlobalObject& aGlobal,
const Sequence<nsString>& aUrls,
ErrorResult& aRv)
{
nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal.GetAsSupports());
if (!window) {
@ -71,31 +82,35 @@ PresentationRequest::Constructor(const GlobalObject& aGlobal,
return nullptr;
}
// Ensure the URL is not empty.
if (NS_WARN_IF(aUrl.IsEmpty())) {
aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
if (aUrls.IsEmpty()) {
aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
return nullptr;
}
// Resolve relative URL to absolute URL
nsCOMPtr<nsIURI> baseUri = window->GetDocBaseURI();
nsTArray<nsString> urls;
for (const auto& url : aUrls) {
nsAutoString absoluteUrl;
nsresult rv =
GetAbsoluteURL(url, baseUri, window->GetExtantDoc(), absoluteUrl);
if (NS_WARN_IF(NS_FAILED(rv))) {
aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
return nullptr;
}
nsAutoString absoluteUrl;
nsresult rv = GetAbsoluteURL(aUrl, baseUri, window->GetExtantDoc(), absoluteUrl);
if (NS_WARN_IF(NS_FAILED(rv))) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
urls.AppendElement(absoluteUrl);
}
RefPtr<PresentationRequest> request =
new PresentationRequest(window, absoluteUrl);
new PresentationRequest(window, Move(urls));
return NS_WARN_IF(!request->Init()) ? nullptr : request.forget();
}
PresentationRequest::PresentationRequest(nsPIDOMWindowInner* aWindow,
const nsAString& aUrl)
nsTArray<nsString>&& aUrls)
: DOMEventTargetHelper(aWindow)
, mUrl(aUrl)
, mUrls(Move(aUrls))
{
}
@ -152,7 +167,7 @@ PresentationRequest::StartWithDevice(const nsAString& aDeviceId,
}
if (IsProhibitMixedSecurityContexts(doc) &&
!IsPrioriAuthenticatedURL(mUrl)) {
!IsAllURLAuthenticated()) {
promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
return promise.forget();
}
@ -185,8 +200,8 @@ PresentationRequest::StartWithDevice(const nsAString& aDeviceId,
}
nsCOMPtr<nsIPresentationServiceCallback> callback =
new PresentationRequesterCallback(this, mUrl, id, promise);
rv = service->StartSession(mUrl, id, origin, aDeviceId, GetOwner()->WindowID(), callback);
new PresentationRequesterCallback(this, id, promise);
rv = service->StartSession(mUrls, id, origin, aDeviceId, GetOwner()->WindowID(), callback);
if (NS_WARN_IF(NS_FAILED(rv))) {
promise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
}
@ -216,7 +231,7 @@ PresentationRequest::Reconnect(const nsAString& aPresentationId,
}
if (IsProhibitMixedSecurityContexts(doc) &&
!IsPrioriAuthenticatedURL(mUrl)) {
!IsAllURLAuthenticated()) {
promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
return promise.forget();
}
@ -262,7 +277,7 @@ PresentationRequest::FindOrCreatePresentationConnection(
if (connection) {
nsAutoString url;
connection->GetUrl(url);
if (url.Equals(mUrl)) {
if (mUrls.Contains(url)) {
switch (connection->State()) {
case PresentationConnectionState::Closed:
// We found the matched connection.
@ -293,13 +308,12 @@ PresentationRequest::FindOrCreatePresentationConnection(
nsCOMPtr<nsIPresentationServiceCallback> callback =
new PresentationReconnectCallback(this,
mUrl,
aPresentationId,
aPromise,
connection);
nsresult rv =
service->ReconnectSession(mUrl,
service->ReconnectSession(mUrls,
aPresentationId,
nsIPresentationService::ROLE_CONTROLLER,
callback);
@ -311,8 +325,7 @@ PresentationRequest::FindOrCreatePresentationConnection(
already_AddRefed<Promise>
PresentationRequest::GetAvailability(ErrorResult& aRv)
{
PRES_DEBUG("%s:id[%s]\n", __func__,
NS_ConvertUTF16toUTF8(mUrl).get());
PRES_DEBUG("%s\n", __func__);
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
if (NS_WARN_IF(!global)) {
aRv.Throw(NS_ERROR_UNEXPECTED);
@ -331,7 +344,7 @@ PresentationRequest::GetAvailability(ErrorResult& aRv)
}
if (IsProhibitMixedSecurityContexts(doc) &&
!IsPrioriAuthenticatedURL(mUrl)) {
!IsAllURLAuthenticated()) {
promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
return promise.forget();
}
@ -363,13 +376,12 @@ PresentationRequest::FindOrCreatePresentationAvailability(RefPtr<Promise>& aProm
}
RefPtr<PresentationAvailability> availability =
collection->Find(GetOwner()->WindowID(), mUrl);
collection->Find(GetOwner()->WindowID(), mUrls);
if (!availability) {
availability = PresentationAvailability::Create(GetOwner(), mUrl, aPromise);
availability = PresentationAvailability::Create(GetOwner(), mUrls, aPromise);
} else {
PRES_DEBUG(">resolve with same object:id[%s]\n",
NS_ConvertUTF16toUTF8(mUrl).get());
PRES_DEBUG(">resolve with same object\n");
// Fetching cached available devices is asynchronous in our implementation,
// we need to ensure the promise is resolved in order.
@ -474,3 +486,15 @@ PresentationRequest::IsPrioriAuthenticatedURL(const nsAString& aUrl)
csm->IsOriginPotentiallyTrustworthy(principal, &isTrustworthyOrigin);
return isTrustworthyOrigin;
}
bool
PresentationRequest::IsAllURLAuthenticated()
{
for (const auto& url : mUrls) {
if (!IsPrioriAuthenticatedURL(url)) {
return false;
}
}
return true;
}

View File

@ -7,6 +7,7 @@
#ifndef mozilla_dom_PresentationRequest_h
#define mozilla_dom_PresentationRequest_h
#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/DOMEventTargetHelper.h"
class nsIDocument;
@ -23,9 +24,15 @@ class PresentationRequest final : public DOMEventTargetHelper
public:
NS_DECL_ISUPPORTS_INHERITED
static already_AddRefed<PresentationRequest> Constructor(const GlobalObject& aGlobal,
const nsAString& aUrl,
ErrorResult& aRv);
static already_AddRefed<PresentationRequest> Constructor(
const GlobalObject& aGlobal,
const nsAString& aUrl,
ErrorResult& aRv);
static already_AddRefed<PresentationRequest> Constructor(
const GlobalObject& aGlobal,
const Sequence<nsString>& aUrls,
ErrorResult& aRv);
virtual JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
@ -47,7 +54,7 @@ public:
private:
PresentationRequest(nsPIDOMWindowInner* aWindow,
const nsAString& aUrl);
nsTArray<nsString>&& aUrls);
~PresentationRequest();
@ -64,7 +71,9 @@ private:
// Implement https://w3c.github.io/webappsec-mixed-content/#a-priori-authenticated-url
bool IsPrioriAuthenticatedURL(const nsAString& aUrl);
nsString mUrl;
bool IsAllURLAuthenticated();
nsTArray<nsString> mUrls;
};
} // namespace dom

View File

@ -59,6 +59,44 @@ IsSameDevice(nsIPresentationDevice* aDevice, nsIPresentationDevice* aDeviceAnoth
return true;
}
static nsresult
ConvertURLArrayHelper(const nsTArray<nsString>& aUrls, nsIArray** aResult)
{
if (!aResult) {
return NS_ERROR_INVALID_POINTER;
}
*aResult = nullptr;
nsresult rv;
nsCOMPtr<nsIMutableArray> urls =
do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
for (const auto& url : aUrls) {
nsCOMPtr<nsISupportsString> isupportsString =
do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = isupportsString->SetData(url);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = urls->AppendElement(isupportsString, false);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
}
urls.forget(aResult);
return NS_OK;
}
/*
* Implementation of PresentationDeviceRequest
*/
@ -69,7 +107,7 @@ public:
NS_DECL_ISUPPORTS
NS_DECL_NSIPRESENTATIONDEVICEREQUEST
PresentationDeviceRequest(const nsAString& aRequestUrl,
PresentationDeviceRequest(const nsTArray<nsString>& aUrls,
const nsAString& aId,
const nsAString& aOrigin,
uint64_t aWindowId,
@ -77,9 +115,10 @@ public:
private:
virtual ~PresentationDeviceRequest() = default;
nsresult CreateSessionInfo(nsIPresentationDevice* aDevice);
nsresult CreateSessionInfo(nsIPresentationDevice* aDevice,
const nsAString& aSelectedRequestUrl);
nsString mRequestUrl;
nsTArray<nsString> mRequestUrls;
nsString mId;
nsString mOrigin;
uint64_t mWindowId;
@ -94,18 +133,18 @@ LazyLogModule gPresentationLog("Presentation");
NS_IMPL_ISUPPORTS(PresentationDeviceRequest, nsIPresentationDeviceRequest)
PresentationDeviceRequest::PresentationDeviceRequest(
const nsAString& aRequestUrl,
const nsTArray<nsString>& aUrls,
const nsAString& aId,
const nsAString& aOrigin,
uint64_t aWindowId,
nsIPresentationServiceCallback* aCallback)
: mRequestUrl(aRequestUrl)
: mRequestUrls(aUrls)
, mId(aId)
, mOrigin(aOrigin)
, mWindowId(aWindowId)
, mCallback(aCallback)
{
MOZ_ASSERT(!mRequestUrl.IsEmpty());
MOZ_ASSERT(!mRequestUrls.IsEmpty());
MOZ_ASSERT(!mId.IsEmpty());
MOZ_ASSERT(!mOrigin.IsEmpty());
MOZ_ASSERT(mCallback);
@ -119,29 +158,47 @@ PresentationDeviceRequest::GetOrigin(nsAString& aOrigin)
}
NS_IMETHODIMP
PresentationDeviceRequest::GetRequestURL(nsAString& aRequestUrl)
PresentationDeviceRequest::GetRequestURLs(nsIArray** aUrls)
{
aRequestUrl = mRequestUrl;
return NS_OK;
return ConvertURLArrayHelper(mRequestUrls, aUrls);
}
NS_IMETHODIMP
PresentationDeviceRequest::Select(nsIPresentationDevice* aDevice)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aDevice);
nsresult rv = CreateSessionInfo(aDevice);
if (NS_WARN_IF(NS_FAILED(rv))) {
mCallback->NotifyError(rv);
return rv;
if (NS_WARN_IF(!aDevice)) {
MOZ_ASSERT(false, "|aDevice| should noe be null.");
mCallback->NotifyError(NS_ERROR_DOM_OPERATION_ERR);
return NS_ERROR_INVALID_ARG;
}
return mCallback->NotifySuccess();
// Select the most suitable URL for starting the presentation.
nsAutoString selectedRequestUrl;
for (const auto& url : mRequestUrls) {
bool isSupported;
if (NS_SUCCEEDED(aDevice->IsRequestedUrlSupported(url, &isSupported)) &&
isSupported) {
selectedRequestUrl.Assign(url);
break;
}
}
if (selectedRequestUrl.IsEmpty()) {
return mCallback->NotifyError(NS_ERROR_DOM_NOT_FOUND_ERR);
}
if (NS_WARN_IF(NS_FAILED(CreateSessionInfo(aDevice, selectedRequestUrl)))) {
return mCallback->NotifyError(NS_ERROR_DOM_OPERATION_ERR);
}
return mCallback->NotifySuccess(selectedRequestUrl);
}
nsresult
PresentationDeviceRequest::CreateSessionInfo(nsIPresentationDevice* aDevice)
PresentationDeviceRequest::CreateSessionInfo(
nsIPresentationDevice* aDevice,
const nsAString& aSelectedRequestUrl)
{
nsCOMPtr<nsIPresentationService> service =
do_GetService(PRESENTATION_SERVICE_CONTRACTID);
@ -152,7 +209,7 @@ PresentationDeviceRequest::CreateSessionInfo(nsIPresentationDevice* aDevice)
// Create the controlling session info
RefPtr<PresentationSessionInfo> info =
static_cast<PresentationService*>(service.get())->
CreateControllingSessionInfo(mRequestUrl, mId, mWindowId);
CreateControllingSessionInfo(aSelectedRequestUrl, mId, mWindowId);
if (NS_WARN_IF(!info)) {
return NS_ERROR_NOT_AVAILABLE;
}
@ -572,23 +629,22 @@ PresentationService::IsAppInstalled(nsIURI* aUri)
}
NS_IMETHODIMP
PresentationService::StartSession(const nsAString& aUrl,
PresentationService::StartSession(const nsTArray<nsString>& aUrls,
const nsAString& aSessionId,
const nsAString& aOrigin,
const nsAString& aDeviceId,
uint64_t aWindowId,
nsIPresentationServiceCallback* aCallback)
{
PRES_DEBUG("%s:url[%s], id[%s]\n", __func__,
NS_ConvertUTF16toUTF8(aUrl).get(),
NS_ConvertUTF16toUTF8(aSessionId).get());
PRES_DEBUG("%s:id[%s]\n", __func__, NS_ConvertUTF16toUTF8(aSessionId).get());
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aCallback);
MOZ_ASSERT(!aSessionId.IsEmpty());
MOZ_ASSERT(!aUrls.IsEmpty());
nsCOMPtr<nsIPresentationDeviceRequest> request =
new PresentationDeviceRequest(aUrl,
new PresentationDeviceRequest(aUrls,
aSessionId,
aOrigin,
aWindowId,
@ -617,15 +673,11 @@ PresentationService::StartSession(const nsAString& aUrl,
return aCallback->NotifyError(NS_ERROR_DOM_OPERATION_ERR);
}
nsCOMPtr<nsIMutableArray> presentationUrls =
do_CreateInstance(NS_ARRAY_CONTRACTID);
if (!presentationUrls) {
nsCOMPtr<nsIArray> presentationUrls;
if (NS_WARN_IF(NS_FAILED(
ConvertURLArrayHelper(aUrls, getter_AddRefs(presentationUrls))))) {
return aCallback->NotifyError(NS_ERROR_DOM_OPERATION_ERR);
}
nsCOMPtr<nsISupportsString> supportsStr =
do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID);
supportsStr->SetData(aUrl);
presentationUrls->AppendElement(supportsStr, false);
nsCOMPtr<nsIArray> devices;
nsresult rv = deviceManager->GetAvailableDevices(presentationUrls, getter_AddRefs(devices));
@ -747,18 +799,17 @@ PresentationService::TerminateSession(const nsAString& aSessionId,
}
NS_IMETHODIMP
PresentationService::ReconnectSession(const nsAString& aUrl,
PresentationService::ReconnectSession(const nsTArray<nsString>& aUrls,
const nsAString& aSessionId,
uint8_t aRole,
nsIPresentationServiceCallback* aCallback)
{
PRES_DEBUG("%s:url[%s], id[%s]\n", __func__,
NS_ConvertUTF16toUTF8(aUrl).get(),
NS_ConvertUTF16toUTF8(aSessionId).get());
PRES_DEBUG("%s:id[%s]\n", __func__, NS_ConvertUTF16toUTF8(aSessionId).get());
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!aSessionId.IsEmpty());
MOZ_ASSERT(aCallback);
MOZ_ASSERT(!aUrls.IsEmpty());
if (aRole != nsIPresentationService::ROLE_CONTROLLER) {
MOZ_ASSERT(false, "Only controller can call ReconnectSession.");
@ -774,7 +825,7 @@ PresentationService::ReconnectSession(const nsAString& aUrl,
return aCallback->NotifyError(NS_ERROR_DOM_NOT_FOUND_ERR);
}
if (NS_WARN_IF(!info->GetUrl().Equals(aUrl))) {
if (NS_WARN_IF(!aUrls.Contains(info->GetUrl()))) {
return aCallback->NotifyError(NS_ERROR_DOM_NOT_FOUND_ERR);
}

View File

@ -806,7 +806,7 @@ PresentationControllingInfo::NotifyReconnected()
return NS_ERROR_FAILURE;
}
return mReconnectCallback->NotifySuccess();
return mReconnectCallback->NotifySuccess(GetUrl());
}
nsresult

View File

@ -4,6 +4,7 @@
#include "nsISupports.idl"
interface nsIArray;
interface nsIPresentationDevice;
%{C++
@ -19,8 +20,8 @@ interface nsIPresentationDeviceRequest : nsISupports
// The origin which initiate the request.
readonly attribute DOMString origin;
// The URL to be opened after selection.
readonly attribute DOMString requestURL;
// The array of candidate URLs.
readonly attribute nsIArray requestURLs;
/*
* Callback after selecting a device

View File

@ -15,15 +15,23 @@ interface nsIPresentationSessionListener;
{ 0x9e, 0x4f, 0x40, 0x58, 0xb8, 0x51, 0x98, 0x32 } }
#define PRESENTATION_SERVICE_CONTRACTID \
"@mozilla.org/presentation/presentationservice;1"
#include "nsTArray.h"
class nsString;
%}
[ref] native URLArrayRef(const nsTArray<nsString>);
[scriptable, uuid(12073206-0065-4b10-9488-a6eb9b23e65b)]
interface nsIPresentationServiceCallback : nsISupports
{
/*
* Called when the operation succeeds.
*
* @param url: the selected request url used to start or reconnect a session.
*/
void notifySuccess();
void notifySuccess(in DOMString url);
/*
* Called when the operation fails.
@ -47,7 +55,7 @@ interface nsIPresentationService : nsISupports
* Start a new presentation session and display a prompt box which asks users
* to select a device.
*
* @param url: The url of presenting page.
* @param urls: The candidate Urls of presenting page. Only one url would be used.
* @param sessionId: An ID to identify presentation session.
* @param origin: The url of requesting page.
* @param deviceId: The specified device of handling this request, null string
@ -61,12 +69,12 @@ interface nsIPresentationService : nsISupports
* established successfully with the selected device.
* Otherwise, NotifyError() is called with a error message.
*/
void startSession(in DOMString url,
in DOMString sessionId,
in DOMString origin,
in DOMString deviceId,
in unsigned long long windowId,
in nsIPresentationServiceCallback callback);
[noscript] void startSession(in URLArrayRef urls,
in DOMString sessionId,
in DOMString origin,
in DOMString deviceId,
in unsigned long long windowId,
in nsIPresentationServiceCallback callback);
/*
* Send the message to the session.
@ -101,17 +109,17 @@ interface nsIPresentationService : nsISupports
/*
* Reconnect the session.
*
* @param url: The url of presenting page.
* @param url: The request Urls.
* @param sessionId: An ID to identify presentation session.
* @param role: Identify the function called by controller or receiver.
* @param callback: NotifySuccess() is called when a control channel
* is opened successfully.
* Otherwise, NotifyError() is called with a error message.
*/
void reconnectSession(in DOMString url,
in DOMString sessionId,
in uint8_t role,
in nsIPresentationServiceCallback callback);
[noscript] void reconnectSession(in URLArrayRef urls,
in DOMString sessionId,
in uint8_t role,
in nsIPresentationServiceCallback callback);
/*
* Register an availability listener. Must be called from the main thread.

View File

@ -15,7 +15,7 @@ namespace dom {
struct StartSessionRequest
{
nsString url;
nsString[] urls;
nsString sessionId;
nsString origin;
nsString deviceId;
@ -44,7 +44,7 @@ struct TerminateSessionRequest
struct ReconnectSessionRequest
{
nsString url;
nsString[] urls;
nsString sessionId;
uint8_t role;
};

View File

@ -15,6 +15,7 @@ sync protocol PPresentationRequest
child:
async __delete__(nsresult result);
async NotifyRequestUrlSelected(nsString aUrl);
};
} // namespace dom

View File

@ -163,12 +163,17 @@ PresentationRequestChild::Recv__delete__(const nsresult& aResult)
}
if (mCallback) {
if (NS_SUCCEEDED(aResult)) {
NS_WARN_IF(NS_FAILED(mCallback->NotifySuccess()));
} else {
if (NS_FAILED(aResult)) {
NS_WARN_IF(NS_FAILED(mCallback->NotifyError(aResult)));
}
}
return true;
}
bool
PresentationRequestChild::RecvNotifyRequestUrlSelected(const nsString& aUrl)
{
NS_WARN_IF(NS_FAILED(mCallback->NotifySuccess(aUrl)));
return true;
}

View File

@ -78,6 +78,9 @@ public:
virtual bool
Recv__delete__(const nsresult& aResult) override;
virtual bool
RecvNotifyRequestUrlSelected(const nsString& aUrl) override;
private:
virtual ~PresentationRequestChild();

View File

@ -52,7 +52,7 @@ PresentationIPCService::~PresentationIPCService()
}
NS_IMETHODIMP
PresentationIPCService::StartSession(const nsAString& aUrl,
PresentationIPCService::StartSession(const nsTArray<nsString>& aUrls,
const nsAString& aSessionId,
const nsAString& aOrigin,
const nsAString& aDeviceId,
@ -65,7 +65,7 @@ PresentationIPCService::StartSession(const nsAString& aUrl,
nsIPresentationService::ROLE_CONTROLLER);
}
return SendRequest(aCallback, StartSessionRequest(nsString(aUrl),
return SendRequest(aCallback, StartSessionRequest(aUrls,
nsString(aSessionId),
nsString(aOrigin),
nsString(aDeviceId),
@ -135,7 +135,7 @@ PresentationIPCService::TerminateSession(const nsAString& aSessionId,
}
NS_IMETHODIMP
PresentationIPCService::ReconnectSession(const nsAString& aUrl,
PresentationIPCService::ReconnectSession(const nsTArray<nsString>& aUrls,
const nsAString& aSessionId,
uint8_t aRole,
nsIPresentationServiceCallback* aCallback)
@ -147,7 +147,7 @@ PresentationIPCService::ReconnectSession(const nsAString& aUrl,
return NS_ERROR_INVALID_ARG;
}
return SendRequest(aCallback, ReconnectSessionRequest(nsString(aUrl),
return SendRequest(aCallback, ReconnectSessionRequest(aUrls,
nsString(aSessionId),
aRole));
}

View File

@ -6,6 +6,7 @@
#include "DCPresentationChannelDescription.h"
#include "mozilla/ipc/InputStreamUtils.h"
#include "mozilla/Unused.h"
#include "nsIPresentationDeviceManager.h"
#include "nsServiceManagerUtils.h"
#include "PresentationBuilderParent.h"
@ -333,7 +334,7 @@ PresentationRequestParent::DoRequest(const StartSessionRequest& aRequest)
MOZ_ASSERT(mService);
mNeedRegisterBuilder = true;
mSessionId = aRequest.sessionId();
return mService->StartSession(aRequest.url(), aRequest.sessionId(),
return mService->StartSession(aRequest.urls(), aRequest.sessionId(),
aRequest.origin(), aRequest.deviceId(),
aRequest.windowId(), this);
}
@ -347,16 +348,16 @@ PresentationRequestParent::DoRequest(const SendSessionMessageRequest& aRequest)
// compromised child process can't fake the ID.
if (NS_WARN_IF(!static_cast<PresentationService*>(mService.get())->
IsSessionAccessible(aRequest.sessionId(), aRequest.role(), OtherPid()))) {
return NotifyError(NS_ERROR_DOM_SECURITY_ERR);
return SendResponse(NS_ERROR_DOM_SECURITY_ERR);
}
nsresult rv = mService->SendSessionMessage(aRequest.sessionId(),
aRequest.role(),
aRequest.data());
if (NS_WARN_IF(NS_FAILED(rv))) {
return NotifyError(rv);
return SendResponse(rv);
}
return NotifySuccess();
return SendResponse(NS_OK);
}
nsresult
@ -368,16 +369,16 @@ PresentationRequestParent::DoRequest(const CloseSessionRequest& aRequest)
// compromised child process can't fake the ID.
if (NS_WARN_IF(!static_cast<PresentationService*>(mService.get())->
IsSessionAccessible(aRequest.sessionId(), aRequest.role(), OtherPid()))) {
return NotifyError(NS_ERROR_DOM_SECURITY_ERR);
return SendResponse(NS_ERROR_DOM_SECURITY_ERR);
}
nsresult rv = mService->CloseSession(aRequest.sessionId(),
aRequest.role(),
aRequest.closedReason());
if (NS_WARN_IF(NS_FAILED(rv))) {
return NotifyError(rv);
return SendResponse(rv);
}
return NotifySuccess();
return SendResponse(NS_OK);
}
nsresult
@ -389,14 +390,14 @@ PresentationRequestParent::DoRequest(const TerminateSessionRequest& aRequest)
// compromised child process can't fake the ID.
if (NS_WARN_IF(!static_cast<PresentationService*>(mService.get())->
IsSessionAccessible(aRequest.sessionId(), aRequest.role(), OtherPid()))) {
return NotifyError(NS_ERROR_DOM_SECURITY_ERR);
return SendResponse(NS_ERROR_DOM_SECURITY_ERR);
}
nsresult rv = mService->TerminateSession(aRequest.sessionId(), aRequest.role());
if (NS_WARN_IF(NS_FAILED(rv))) {
return NotifyError(rv);
return SendResponse(rv);
}
return NotifySuccess();
return SendResponse(NS_OK);
}
nsresult
@ -411,12 +412,12 @@ PresentationRequestParent::DoRequest(const ReconnectSessionRequest& aRequest)
// NOTE: Return NS_ERROR_DOM_NOT_FOUND_ERR here to match the spec.
// https://w3c.github.io/presentation-api/#reconnecting-to-a-presentation
return NotifyError(NS_ERROR_DOM_NOT_FOUND_ERR);
return SendResponse(NS_ERROR_DOM_NOT_FOUND_ERR);
}
mNeedRegisterBuilder = true;
mSessionId = aRequest.sessionId();
return mService->ReconnectSession(aRequest.url(),
return mService->ReconnectSession(aRequest.urls(),
aRequest.sessionId(),
aRequest.role(),
this);
@ -431,18 +432,18 @@ PresentationRequestParent::DoRequest(const BuildTransportRequest& aRequest)
// compromised child process can't fake the ID.
if (NS_WARN_IF(!static_cast<PresentationService*>(mService.get())->
IsSessionAccessible(aRequest.sessionId(), aRequest.role(), OtherPid()))) {
return NotifyError(NS_ERROR_DOM_SECURITY_ERR);
return SendResponse(NS_ERROR_DOM_SECURITY_ERR);
}
nsresult rv = mService->BuildTransport(aRequest.sessionId(), aRequest.role());
if (NS_WARN_IF(NS_FAILED(rv))) {
return NotifyError(rv);
return SendResponse(rv);
}
return NotifySuccess();
return SendResponse(NS_OK);
}
NS_IMETHODIMP
PresentationRequestParent::NotifySuccess()
PresentationRequestParent::NotifySuccess(const nsAString& aUrl)
{
if (mNeedRegisterBuilder) {
RefPtr<PresentationParent> parent = static_cast<PresentationParent*>(Manager());
@ -451,6 +452,7 @@ PresentationRequestParent::NotifySuccess()
nsIPresentationService::ROLE_CONTROLLER));
}
Unused << SendNotifyRequestUrlSelected(nsString(aUrl));
return SendResponse(NS_OK);
}

View File

@ -238,6 +238,29 @@ function teardown() {
gScript.sendAsyncMessage('teardown');
}
function testConstructRequestError() {
return Promise.all([
new Promise(function(aResolve, aReject) {
try {
request = new PresentationRequest("\\\\\\");
}
catch(e) {
is(e.name, "SyntaxError", "Expect to get SyntaxError.");
aResolve();
}
}),
new Promise(function(aResolve, aReject) {
try {
request = new PresentationRequest([]);
}
catch(e) {
is(e.name, "NotSupportedError", "Expect to get NotSupportedError.");
aResolve();
}
}),
]);
}
function runTests() {
ok(window.PresentationRequest, "PresentationRequest should be available.");
@ -248,6 +271,7 @@ function runTests() {
then(testCloseConnection).
then(testReconnect).
then(testCloseConnection).
then(testConstructRequestError).
then(teardown);
}

View File

@ -41,17 +41,6 @@ function setup() {
});
}
function testCreateRequestWithEmptyURL() {
return new Promise(function(aResolve, aReject) {
try {
request = new PresentationRequest("");
} catch (aError) {
is(aError.name, "SyntaxError", "SyntaxError is expected when using an empty URL.");
aResolve();
}
});
}
function testStartConnectionCancelPrompt() {
return new Promise(function(aResolve, aReject) {
gScript.addMessageListener('device-prompt', function devicePromptHandler() {
@ -380,8 +369,7 @@ function teardown() {
function runTests() {
ok(window.PresentationRequest, "PresentationRequest should be available.");
testCreateRequestWithEmptyURL().
then(setup).
setup().
then(testStartConnectionCancelPrompt).
then(testStartConnectionNoDevice).
then(testStartConnectionUnexpectedControlChannelCloseBeforeDataTransportInit).

View File

@ -5,6 +5,7 @@
*/
[Constructor(DOMString url),
Constructor(sequence<DOMString> urls),
Pref="dom.presentation.controller.enabled"]
interface PresentationRequest : EventTarget {
/*

View File

@ -46,17 +46,12 @@ PresentationDevicePrompt.prototype = {
return this.bundle.GetStringFromName(aName);
},
_loadDevices: function(requestURL) {
_loadDevices: function(requestURLs) {
debug("_loadDevices");
let presentationUrls = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
let url = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
url.data = requestURL;
presentationUrls.appendElement(url, false);
let deviceManager = Cc["@mozilla.org/presentation-device/manager;1"]
.getService(Ci.nsIPresentationDeviceManager);
let devices = deviceManager.getAvailableDevices(presentationUrls).QueryInterface(Ci.nsIArray);
let devices = deviceManager.getAvailableDevices(requestURLs).QueryInterface(Ci.nsIArray);
// Re-load the available devices
this._devices = [];
@ -118,7 +113,7 @@ PresentationDevicePrompt.prototype = {
debug("promptDeviceSelection");
// Load available presentation devices into this._devices
this._loadDevices(aRequest.requestURL);
this._loadDevices(aRequest.requestURLs);
if (!this._devices.length) { // Cancel request if no available device
aRequest.cancel(Cr.NS_ERROR_DOM_NOT_FOUND_ERR);