Bug 1228508 - Part 2, maintain the set of availability objects. r=smaug
MozReview-Commit-ID: 8JNWvnfsMU7
new file mode 100644
--- /dev/null
+++ b/dom/presentation/AvailabilityCollection.cpp
@@ -0,0 +1,99 @@
+/* -*- 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 "AvailabilityCollection.h"
+
+#include "mozilla/ClearOnShutdown.h"
+#include "PresentationAvailability.h"
+
+namespace mozilla {
+namespace dom {
+
+/* static */
+StaticAutoPtr<AvailabilityCollection>
+AvailabilityCollection::sSingleton;
+static bool gOnceAliveNowDead = false;
+
+/* static */ AvailabilityCollection*
+AvailabilityCollection::GetSingleton()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!sSingleton && !gOnceAliveNowDead) {
+ sSingleton = new AvailabilityCollection();
+ ClearOnShutdown(&sSingleton);
+ }
+
+ return sSingleton;
+}
+
+AvailabilityCollection::AvailabilityCollection()
+{
+ MOZ_COUNT_CTOR(AvailabilityCollection);
+}
+
+AvailabilityCollection::~AvailabilityCollection()
+{
+ MOZ_COUNT_DTOR(AvailabilityCollection);
+ gOnceAliveNowDead = true;
+}
+
+void
+AvailabilityCollection::Add(PresentationAvailability* aAvailability)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!aAvailability) {
+ return;
+ }
+
+ WeakPtr<PresentationAvailability> availability = aAvailability;
+ if (mAvailabilities.Contains(aAvailability)) {
+ return;
+ }
+
+ mAvailabilities.AppendElement(aAvailability);
+}
+
+void
+AvailabilityCollection::Remove(PresentationAvailability* aAvailability)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!aAvailability) {
+ return;
+ }
+
+ WeakPtr<PresentationAvailability> availability = aAvailability;
+ mAvailabilities.RemoveElement(availability);
+}
+
+already_AddRefed<PresentationAvailability>
+AvailabilityCollection::Find(const uint64_t aWindowId, const nsAString& aUrl)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Loop backwards to allow removing elements in the loop.
+ for (int i = mAvailabilities.Length() - 1; i >= 0; --i) {
+ WeakPtr<PresentationAvailability> availability = mAvailabilities[i];
+ if (!availability) {
+ // The availability object was destroyed. Remove it from the list.
+ mAvailabilities.RemoveElementAt(i);
+ continue;
+ }
+
+ if (availability->Equals(aWindowId, aUrl)) {
+ RefPtr<PresentationAvailability> matchedAvailability = availability.get();
+ return matchedAvailability.forget();
+ }
+ }
+
+
+ return nullptr;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/presentation/AvailabilityCollection.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_AvailabilityCollection_h
+#define mozilla_dom_AvailabilityCollection_h
+
+#include "mozilla/StaticPtr.h"
+#include "mozilla/WeakPtr.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace dom {
+
+class PresentationAvailability;
+
+class AvailabilityCollection final
+{
+public:
+ static AvailabilityCollection* GetSingleton();
+
+ void Add(PresentationAvailability* aAvailability);
+
+ void Remove(PresentationAvailability* aAvailability);
+
+ already_AddRefed<PresentationAvailability>
+ Find(const uint64_t aWindowId, const nsAString& aUrl);
+
+private:
+ friend class StaticAutoPtr<AvailabilityCollection>;
+
+ AvailabilityCollection();
+ virtual ~AvailabilityCollection();
+
+ static StaticAutoPtr<AvailabilityCollection> sSingleton;
+ nsTArray<WeakPtr<PresentationAvailability>> mAvailabilities;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_AvailabilityCollection_h
--- a/dom/presentation/PresentationAvailability.cpp
+++ b/dom/presentation/PresentationAvailability.cpp
@@ -7,51 +7,55 @@
#include "PresentationAvailability.h"
#include "mozilla/dom/PresentationAvailabilityBinding.h"
#include "mozilla/dom/Promise.h"
#include "nsCycleCollectionParticipant.h"
#include "nsIPresentationDeviceManager.h"
#include "nsIPresentationService.h"
#include "nsServiceManagerUtils.h"
+#include "PresentationLog.h"
using namespace mozilla;
using namespace mozilla::dom;
NS_IMPL_CYCLE_COLLECTION_CLASS(PresentationAvailability)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(PresentationAvailability, DOMEventTargetHelper)
- NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromises)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(PresentationAvailability, DOMEventTargetHelper)
- NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise);
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromises);
tmp->Shutdown();
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_ADDREF_INHERITED(PresentationAvailability, DOMEventTargetHelper)
NS_IMPL_RELEASE_INHERITED(PresentationAvailability, DOMEventTargetHelper)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(PresentationAvailability)
NS_INTERFACE_MAP_ENTRY(nsIPresentationAvailabilityListener)
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
/* static */ already_AddRefed<PresentationAvailability>
PresentationAvailability::Create(nsPIDOMWindowInner* aWindow,
+ const nsAString& aUrl,
RefPtr<Promise>& aPromise)
{
RefPtr<PresentationAvailability> availability =
- new PresentationAvailability(aWindow);
+ new PresentationAvailability(aWindow, aUrl);
return NS_WARN_IF(!availability->Init(aPromise)) ? nullptr
: availability.forget();
}
-PresentationAvailability::PresentationAvailability(nsPIDOMWindowInner* aWindow)
+PresentationAvailability::PresentationAvailability(nsPIDOMWindowInner* aWindow,
+ const nsAString& aUrl)
: DOMEventTargetHelper(aWindow)
, mIsAvailable(false)
+ , mUrl(aUrl)
{
}
PresentationAvailability::~PresentationAvailability()
{
Shutdown();
}
@@ -68,23 +72,33 @@ PresentationAvailability::Init(RefPtr<Pr
if (NS_WARN_IF(NS_FAILED(rv))) {
// If the user agent is unable to monitor available device,
// Resolve promise with |value| set to false.
mIsAvailable = false;
aPromise->MaybeResolve(this);
return true;
}
- mPromise = aPromise;
+ EnqueuePromise(aPromise);
+
+ AvailabilityCollection* collection = AvailabilityCollection::GetSingleton();
+ if (collection) {
+ collection->Add(this);
+ }
return true;
}
void PresentationAvailability::Shutdown()
{
+ AvailabilityCollection* collection = AvailabilityCollection::GetSingleton();
+ if (collection ) {
+ collection->Remove(this);
+ }
+
nsCOMPtr<nsIPresentationService> service =
do_GetService(PRESENTATION_SERVICE_CONTRACTID);
if (NS_WARN_IF(!service)) {
return;
}
nsresult rv = service->UnregisterAvailabilityListener(this);
NS_WARN_IF(NS_FAILED(rv));
@@ -100,16 +114,42 @@ PresentationAvailability::DisconnectFrom
/* virtual */ JSObject*
PresentationAvailability::WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto)
{
return PresentationAvailabilityBinding::Wrap(aCx, this, aGivenProto);
}
bool
+PresentationAvailability::Equals(const uint64_t aWindowID,
+ const nsAString& aUrl) const
+{
+ if (GetOwner() && GetOwner()->WindowID() == aWindowID &&
+ mUrl.Equals(aUrl)) {
+ return true;
+ }
+
+ return false;
+}
+
+bool
+PresentationAvailability::IsCachedValueReady()
+{
+ // All pending promises will be solved when cached value is ready and
+ // no promise should be enqueued afterward.
+ return mPromises.IsEmpty();
+}
+
+void
+PresentationAvailability::EnqueuePromise(RefPtr<Promise>& aPromise)
+{
+ mPromises.AppendElement(aPromise);
+}
+
+bool
PresentationAvailability::Value() const
{
return mIsAvailable;
}
NS_IMETHODIMP
PresentationAvailability::NotifyAvailableChange(bool aIsAvailable)
{
@@ -117,22 +157,31 @@ PresentationAvailability::NotifyAvailabl
<bool>(this,
&PresentationAvailability::UpdateAvailabilityAndDispatchEvent,
aIsAvailable));
}
void
PresentationAvailability::UpdateAvailabilityAndDispatchEvent(bool aIsAvailable)
{
+ PRES_DEBUG("%s:id[%s]\n", __func__,
+ NS_ConvertUTF16toUTF8(mUrl).get());
bool isChanged = (aIsAvailable != mIsAvailable);
mIsAvailable = aIsAvailable;
- if (mPromise) {
- mPromise->MaybeResolve(this);
- mPromise = nullptr;
+ if (!mPromises.IsEmpty()) {
+ // Use the first availability change notification to resolve promise.
+ do {
+ nsTArray<RefPtr<Promise>> promises = Move(mPromises);
+ for (auto& promise : promises) {
+ promise->MaybeResolve(this);
+ }
+ // more promises may have been added to mPromises, at least in theory
+ } while (!mPromises.IsEmpty());
+
return;
}
if (isChanged) {
NS_WARN_IF(NS_FAILED(DispatchTrustedEvent(NS_LITERAL_STRING("change"))));
}
}
--- a/dom/presentation/PresentationAvailability.h
+++ b/dom/presentation/PresentationAvailability.h
@@ -13,49 +13,61 @@
namespace mozilla {
namespace dom {
class Promise;
class PresentationAvailability final : public DOMEventTargetHelper
, public nsIPresentationAvailabilityListener
+ , public SupportsWeakPtr<PresentationAvailability>
{
public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(PresentationAvailability,
DOMEventTargetHelper)
NS_DECL_NSIPRESENTATIONAVAILABILITYLISTENER
+ MOZ_DECLARE_WEAKREFERENCE_TYPENAME(PresentationAvailability)
static already_AddRefed<PresentationAvailability>
Create(nsPIDOMWindowInner* aWindow,
+ const nsAString& aUrl,
RefPtr<Promise>& aPromise);
virtual void DisconnectFromOwner() override;
virtual JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
+ bool Equals(const uint64_t aWindowID, const nsAString& aUrl) const;
+
+ bool IsCachedValueReady();
+
+ void EnqueuePromise(RefPtr<Promise>& aPromise);
+
// WebIDL (public APIs)
bool Value() const;
IMPL_EVENT_HANDLER(change);
private:
- explicit PresentationAvailability(nsPIDOMWindowInner* aWindow);
+ explicit PresentationAvailability(nsPIDOMWindowInner* aWindow,
+ const nsAString& aUrl);
virtual ~PresentationAvailability();
bool Init(RefPtr<Promise>& aPromise);
void Shutdown();
void UpdateAvailabilityAndDispatchEvent(bool aIsAvailable);
bool mIsAvailable;
- RefPtr<Promise> mPromise;
+ nsTArray<RefPtr<Promise>> mPromises;
+
+ nsString mUrl;
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_PresentationAvailability_h
--- a/dom/presentation/PresentationRequest.cpp
+++ b/dom/presentation/PresentationRequest.cpp
@@ -1,16 +1,17 @@
/* -*- 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 "PresentationRequest.h"
+#include "AvailabilityCollection.h"
#include "ControllerConnectionCollection.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/dom/PresentationRequestBinding.h"
#include "mozilla/dom/PresentationConnectionAvailableEvent.h"
#include "mozilla/dom/Promise.h"
#include "mozIThirdPartyUtil.h"
#include "nsContentSecurityManager.h"
#include "nsCycleCollectionParticipant.h"
@@ -335,29 +336,60 @@ PresentationRequest::GetAvailability(Err
return promise.forget();
}
if (doc->GetSandboxFlags() & SANDBOXED_PRESENTATION) {
promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
return promise.forget();
}
- // TODO
- // Search the set of availability object and resolve
- // promise with the one had same presentation URLs.
+ FindOrCreatePresentationAvailability(promise);
+
+ return promise.forget();
+}
+
+void
+PresentationRequest::FindOrCreatePresentationAvailability(RefPtr<Promise>& aPromise)
+{
+ MOZ_ASSERT(aPromise);
+
+ if (NS_WARN_IF(!GetOwner())) {
+ aPromise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
+ return;
+ }
+
+ AvailabilityCollection* collection = AvailabilityCollection::GetSingleton();
+ if (NS_WARN_IF(!collection)) {
+ aPromise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
+ return;
+ }
RefPtr<PresentationAvailability> availability =
- PresentationAvailability::Create(GetOwner(), promise);
+ collection->Find(GetOwner()->WindowID(), mUrl);
if (!availability) {
- promise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
- return promise.forget();
+ availability = PresentationAvailability::Create(GetOwner(), mUrl, aPromise);
+ } else {
+ PRES_DEBUG(">resolve with same object:id[%s]\n",
+ NS_ConvertUTF16toUTF8(mUrl).get());
+
+ // Fetching cached available devices is asynchronous in our implementation,
+ // we need to ensure the promise is resolved in order.
+ if (availability->IsCachedValueReady()) {
+ aPromise->MaybeResolve(availability);
+ return;
+ }
+
+ availability->EnqueuePromise(aPromise);
}
- return promise.forget();
+ if (!availability) {
+ aPromise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return;
+ }
}
nsresult
PresentationRequest::DispatchConnectionAvailableEvent(PresentationConnection* aConnection)
{
PresentationConnectionAvailableEventInit init;
init.mConnection = aConnection;
--- a/dom/presentation/PresentationRequest.h
+++ b/dom/presentation/PresentationRequest.h
@@ -51,16 +51,18 @@ private:
~PresentationRequest();
bool Init();
void FindOrCreatePresentationConnection(const nsAString& aPresentationId,
Promise* aPromise);
+ void FindOrCreatePresentationAvailability(RefPtr<Promise>& aPromise);
+
// Implement https://w3c.github.io/webappsec-mixed-content/#categorize-settings-object
bool IsProhibitMixedSecurityContexts(nsIDocument* aDocument);
// Implement https://w3c.github.io/webappsec-mixed-content/#a-priori-authenticated-url
bool IsPrioriAuthenticatedURL(const nsAString& aUrl);
nsString mUrl;
};
--- a/dom/presentation/moz.build
+++ b/dom/presentation/moz.build
@@ -27,16 +27,17 @@ EXPORTS.mozilla.dom += [
'PresentationRequest.h',
'PresentationService.h',
'PresentationServiceBase.h',
'PresentationSessionInfo.h',
'PresentationTCPSessionTransport.h',
]
UNIFIED_SOURCES += [
+ 'AvailabilityCollection.cpp',
'ControllerConnectionCollection.cpp',
'DCPresentationChannelDescription.cpp',
'ipc/PresentationBuilderChild.cpp',
'ipc/PresentationBuilderParent.cpp',
'ipc/PresentationChild.cpp',
'ipc/PresentationContentSessionInfo.cpp',
'ipc/PresentationIPCService.cpp',
'ipc/PresentationParent.cpp',
--- a/dom/presentation/tests/mochitest/test_presentation_availability.html
+++ b/dom/presentation/tests/mochitest/test_presentation_availability.html
@@ -56,16 +56,26 @@ function testInitialAvailable() {
is(aAvailability.value, true, "Should have available device initially");
isnot(aAvailability, availability, "Should get different availability object for different request URL");
}).catch(function(aError) {
ok(false, "Error occurred when getting availability: " + aError);
teardown();
});
}
+function testSameObject() {
+ let sameUrlRequest = new PresentationRequest("https://example.com");
+ return sameUrlRequest.getAvailability().then(function(aAvailability) {
+ is(aAvailability, availability, "Should get same availability object for same request URL");
+ }).catch(function(aError) {
+ ok(false, "Error occurred when getting availability: " + aError);
+ teardown();
+ });
+}
+
function testOnChangeEvent() {
return new Promise(function(aResolve, aReject) {
availability.onchange = function() {
availability.onchange = null;
is(availability.value, false, "Should have no available device after device removed");
aResolve();
}
gScript.sendAsyncMessage('trigger-device-remove');
@@ -95,16 +105,17 @@ function teardown() {
gScript.destroy();
SimpleTest.finish();
}
function runTests() {
ok(navigator.presentation, "navigator.presentation should be available.");
testSetup().then(testInitialUnavailable)
.then(testInitialAvailable)
+ .then(testSameObject)
.then(testOnChangeEvent)
.then(testConsecutiveGetAvailability)
.then(teardown);
}
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPermissions([
{type: "presentation-device-manage", allow: false, context: document},