1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "PresentationAvailability.h"
8
9 #include "mozilla/dom/PresentationAvailabilityBinding.h"
10 #include "mozilla/dom/Promise.h"
11 #include "mozilla/Unused.h"
12 #include "nsContentUtils.h"
13 #include "nsCycleCollectionParticipant.h"
14 #include "nsIPresentationDeviceManager.h"
15 #include "nsIPresentationService.h"
16 #include "nsServiceManagerUtils.h"
17 #include "PresentationLog.h"
18
19 using namespace mozilla;
20 using namespace mozilla::dom;
21
22 NS_IMPL_CYCLE_COLLECTION_CLASS(PresentationAvailability)
23
24 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(PresentationAvailability,
25 DOMEventTargetHelper)
26 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromises)
27 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
28
29 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(PresentationAvailability,
30 DOMEventTargetHelper)
31 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromises);
32 tmp->Shutdown();
33 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
34
NS_IMPL_ADDREF_INHERITED(PresentationAvailability,DOMEventTargetHelper)35 NS_IMPL_ADDREF_INHERITED(PresentationAvailability, DOMEventTargetHelper)
36 NS_IMPL_RELEASE_INHERITED(PresentationAvailability, DOMEventTargetHelper)
37
38 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PresentationAvailability)
39 NS_INTERFACE_MAP_ENTRY(nsIPresentationAvailabilityListener)
40 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
41
42 /* static */ already_AddRefed<PresentationAvailability>
43 PresentationAvailability::Create(nsPIDOMWindowInner* aWindow,
44 const nsTArray<nsString>& aUrls,
45 RefPtr<Promise>& aPromise) {
46 RefPtr<PresentationAvailability> availability =
47 new PresentationAvailability(aWindow, aUrls);
48 return NS_WARN_IF(!availability->Init(aPromise)) ? nullptr
49 : availability.forget();
50 }
51
PresentationAvailability(nsPIDOMWindowInner * aWindow,const nsTArray<nsString> & aUrls)52 PresentationAvailability::PresentationAvailability(
53 nsPIDOMWindowInner* aWindow, const nsTArray<nsString>& aUrls)
54 : DOMEventTargetHelper(aWindow), mIsAvailable(false), mUrls(aUrls) {
55 for (uint32_t i = 0; i < mUrls.Length(); ++i) {
56 mAvailabilityOfUrl.AppendElement(false);
57 }
58 }
59
~PresentationAvailability()60 PresentationAvailability::~PresentationAvailability() { Shutdown(); }
61
Init(RefPtr<Promise> & aPromise)62 bool PresentationAvailability::Init(RefPtr<Promise>& aPromise) {
63 nsCOMPtr<nsIPresentationService> service =
64 do_GetService(PRESENTATION_SERVICE_CONTRACTID);
65 if (NS_WARN_IF(!service)) {
66 return false;
67 }
68
69 nsresult rv = service->RegisterAvailabilityListener(mUrls, this);
70 if (NS_WARN_IF(NS_FAILED(rv))) {
71 // If the user agent is unable to monitor available device,
72 // Resolve promise with |value| set to false.
73 mIsAvailable = false;
74 aPromise->MaybeResolve(this);
75 return true;
76 }
77
78 EnqueuePromise(aPromise);
79
80 AvailabilityCollection* collection = AvailabilityCollection::GetSingleton();
81 if (collection) {
82 collection->Add(this);
83 }
84
85 return true;
86 }
87
Shutdown()88 void PresentationAvailability::Shutdown() {
89 AvailabilityCollection* collection = AvailabilityCollection::GetSingleton();
90 if (collection) {
91 collection->Remove(this);
92 }
93
94 nsCOMPtr<nsIPresentationService> service =
95 do_GetService(PRESENTATION_SERVICE_CONTRACTID);
96 if (NS_WARN_IF(!service)) {
97 return;
98 }
99
100 Unused << NS_WARN_IF(
101 NS_FAILED(service->UnregisterAvailabilityListener(mUrls, this)));
102 }
103
DisconnectFromOwner()104 /* virtual */ void PresentationAvailability::DisconnectFromOwner() {
105 Shutdown();
106 DOMEventTargetHelper::DisconnectFromOwner();
107 }
108
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)109 /* virtual */ JSObject* PresentationAvailability::WrapObject(
110 JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
111 return PresentationAvailabilityBinding::Wrap(aCx, this, aGivenProto);
112 }
113
Equals(const uint64_t aWindowID,const nsTArray<nsString> & aUrls) const114 bool PresentationAvailability::Equals(const uint64_t aWindowID,
115 const nsTArray<nsString>& aUrls) const {
116 if (GetOwner() && GetOwner()->WindowID() == aWindowID &&
117 mUrls.Length() == aUrls.Length()) {
118 for (const auto& url : aUrls) {
119 if (!mUrls.Contains(url)) {
120 return false;
121 }
122 }
123 return true;
124 }
125
126 return false;
127 }
128
IsCachedValueReady()129 bool PresentationAvailability::IsCachedValueReady() {
130 // All pending promises will be solved when cached value is ready and
131 // no promise should be enqueued afterward.
132 return mPromises.IsEmpty();
133 }
134
EnqueuePromise(RefPtr<Promise> & aPromise)135 void PresentationAvailability::EnqueuePromise(RefPtr<Promise>& aPromise) {
136 mPromises.AppendElement(aPromise);
137 }
138
Value() const139 bool PresentationAvailability::Value() const {
140 if (nsContentUtils::ShouldResistFingerprinting()) {
141 return false;
142 }
143
144 return mIsAvailable;
145 }
146
147 NS_IMETHODIMP
NotifyAvailableChange(const nsTArray<nsString> & aAvailabilityUrls,bool aIsAvailable)148 PresentationAvailability::NotifyAvailableChange(
149 const nsTArray<nsString>& aAvailabilityUrls, bool aIsAvailable) {
150 bool available = false;
151 for (uint32_t i = 0; i < mUrls.Length(); ++i) {
152 if (aAvailabilityUrls.Contains(mUrls[i])) {
153 mAvailabilityOfUrl[i] = aIsAvailable;
154 }
155 available |= mAvailabilityOfUrl[i];
156 }
157
158 return NS_DispatchToCurrentThread(NewRunnableMethod<bool>(
159 "dom::PresentationAvailability::UpdateAvailabilityAndDispatchEvent", this,
160 &PresentationAvailability::UpdateAvailabilityAndDispatchEvent,
161 available));
162 }
163
UpdateAvailabilityAndDispatchEvent(bool aIsAvailable)164 void PresentationAvailability::UpdateAvailabilityAndDispatchEvent(
165 bool aIsAvailable) {
166 PRES_DEBUG("%s\n", __func__);
167 bool isChanged = (aIsAvailable != mIsAvailable);
168
169 mIsAvailable = aIsAvailable;
170
171 if (!mPromises.IsEmpty()) {
172 // Use the first availability change notification to resolve promise.
173 do {
174 nsTArray<RefPtr<Promise>> promises = Move(mPromises);
175
176 if (nsContentUtils::ShouldResistFingerprinting()) {
177 continue;
178 }
179
180 for (auto& promise : promises) {
181 promise->MaybeResolve(this);
182 }
183 // more promises may have been added to mPromises, at least in theory
184 } while (!mPromises.IsEmpty());
185
186 return;
187 }
188
189 if (nsContentUtils::ShouldResistFingerprinting()) {
190 return;
191 }
192
193 if (isChanged) {
194 Unused << NS_WARN_IF(
195 NS_FAILED(DispatchTrustedEvent(NS_LITERAL_STRING("change"))));
196 }
197 }
198