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