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 "StreamFilter.h"
8 
9 #include "jsapi.h"
10 #include "jsfriendapi.h"
11 #include "xpcpublic.h"
12 
13 #include "mozilla/AbstractThread.h"
14 #include "mozilla/extensions/StreamFilterChild.h"
15 #include "mozilla/extensions/StreamFilterEvents.h"
16 #include "mozilla/extensions/StreamFilterParent.h"
17 #include "mozilla/dom/AutoEntryScript.h"
18 #include "mozilla/dom/ContentChild.h"
19 #include "mozilla/ipc/Endpoint.h"
20 #include "nsContentUtils.h"
21 #include "nsCycleCollectionParticipant.h"
22 #include "nsLiteralString.h"
23 #include "nsThreadUtils.h"
24 #include "nsTArray.h"
25 
26 using namespace JS;
27 using namespace mozilla::dom;
28 
29 namespace mozilla {
30 namespace extensions {
31 
32 /*****************************************************************************
33  * Initialization
34  *****************************************************************************/
35 
StreamFilter(nsIGlobalObject * aParent,uint64_t aRequestId,const nsAString & aAddonId)36 StreamFilter::StreamFilter(nsIGlobalObject* aParent, uint64_t aRequestId,
37                            const nsAString& aAddonId)
38     : mParent(aParent), mChannelId(aRequestId), mAddonId(NS_Atomize(aAddonId)) {
39   MOZ_ASSERT(aParent);
40 
41   Connect();
42 };
43 
~StreamFilter()44 StreamFilter::~StreamFilter() { ForgetActor(); }
45 
ForgetActor()46 void StreamFilter::ForgetActor() {
47   if (mActor) {
48     mActor->Cleanup();
49     mActor->SetStreamFilter(nullptr);
50   }
51 }
52 
53 /* static */
Create(GlobalObject & aGlobal,uint64_t aRequestId,const nsAString & aAddonId)54 already_AddRefed<StreamFilter> StreamFilter::Create(GlobalObject& aGlobal,
55                                                     uint64_t aRequestId,
56                                                     const nsAString& aAddonId) {
57   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
58   MOZ_ASSERT(global);
59 
60   RefPtr<StreamFilter> filter = new StreamFilter(global, aRequestId, aAddonId);
61   return filter.forget();
62 }
63 
64 /*****************************************************************************
65  * Actor allocation
66  *****************************************************************************/
67 
Connect()68 void StreamFilter::Connect() {
69   MOZ_ASSERT(!mActor);
70 
71   mActor = new StreamFilterChild();
72   mActor->SetStreamFilter(this);
73 
74   nsAutoString addonId;
75   mAddonId->ToString(addonId);
76 
77   ContentChild* cc = ContentChild::GetSingleton();
78   RefPtr<StreamFilter> self(this);
79   if (cc) {
80     cc->SendInitStreamFilter(mChannelId, addonId)
81         ->Then(
82             GetCurrentSerialEventTarget(), __func__,
83             [self](mozilla::ipc::Endpoint<PStreamFilterChild>&& aEndpoint) {
84               self->FinishConnect(std::move(aEndpoint));
85             },
86             [self](mozilla::ipc::ResponseRejectReason&& aReason) {
87               self->mActor->RecvInitialized(false);
88             });
89   } else {
90     StreamFilterParent::Create(nullptr, mChannelId, addonId)
91         ->Then(
92             GetCurrentSerialEventTarget(), __func__,
93             [self](mozilla::ipc::Endpoint<PStreamFilterChild>&& aEndpoint) {
94               self->FinishConnect(std::move(aEndpoint));
95             },
96             [self](bool aDummy) { self->mActor->RecvInitialized(false); });
97   }
98 }
99 
FinishConnect(mozilla::ipc::Endpoint<PStreamFilterChild> && aEndpoint)100 void StreamFilter::FinishConnect(
101     mozilla::ipc::Endpoint<PStreamFilterChild>&& aEndpoint) {
102   if (aEndpoint.IsValid()) {
103     MOZ_RELEASE_ASSERT(aEndpoint.Bind(mActor));
104     mActor->RecvInitialized(true);
105 
106     // IPC now owns this reference.
107     Unused << do_AddRef(mActor);
108   } else {
109     mActor->RecvInitialized(false);
110   }
111 }
112 
CheckAlive()113 bool StreamFilter::CheckAlive() {
114   // Check whether the global that owns this StreamFitler is still scriptable
115   // and, if not, disconnect the actor so that it can be cleaned up.
116   JSObject* wrapper = GetWrapperPreserveColor();
117   if (!wrapper || !xpc::Scriptability::Get(wrapper).Allowed()) {
118     ForgetActor();
119     return false;
120   }
121   return true;
122 }
123 
124 /*****************************************************************************
125  * Binding methods
126  *****************************************************************************/
127 
128 template <typename T>
ReadTypedArrayData(nsTArray<uint8_t> & aData,const T & aArray,ErrorResult & aRv)129 static inline bool ReadTypedArrayData(nsTArray<uint8_t>& aData, const T& aArray,
130                                       ErrorResult& aRv) {
131   aArray.ComputeState();
132   if (!aData.SetLength(aArray.Length(), fallible)) {
133     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
134     return false;
135   }
136   memcpy(aData.Elements(), aArray.Data(), aArray.Length());
137   return true;
138 }
139 
Write(const ArrayBufferOrUint8Array & aData,ErrorResult & aRv)140 void StreamFilter::Write(const ArrayBufferOrUint8Array& aData,
141                          ErrorResult& aRv) {
142   if (!mActor) {
143     aRv.Throw(NS_ERROR_NOT_INITIALIZED);
144     return;
145   }
146 
147   nsTArray<uint8_t> data;
148 
149   bool ok;
150   if (aData.IsArrayBuffer()) {
151     ok = ReadTypedArrayData(data, aData.GetAsArrayBuffer(), aRv);
152   } else if (aData.IsUint8Array()) {
153     ok = ReadTypedArrayData(data, aData.GetAsUint8Array(), aRv);
154   } else {
155     MOZ_ASSERT_UNREACHABLE("Argument should be ArrayBuffer or Uint8Array");
156     return;
157   }
158 
159   if (ok) {
160     mActor->Write(std::move(data), aRv);
161   }
162 }
163 
Status() const164 StreamFilterStatus StreamFilter::Status() const {
165   if (!mActor) {
166     return StreamFilterStatus::Uninitialized;
167   }
168   return mActor->Status();
169 }
170 
Suspend(ErrorResult & aRv)171 void StreamFilter::Suspend(ErrorResult& aRv) {
172   if (mActor) {
173     mActor->Suspend(aRv);
174   } else {
175     aRv.Throw(NS_ERROR_NOT_INITIALIZED);
176   }
177 }
178 
Resume(ErrorResult & aRv)179 void StreamFilter::Resume(ErrorResult& aRv) {
180   if (mActor) {
181     mActor->Resume(aRv);
182   } else {
183     aRv.Throw(NS_ERROR_NOT_INITIALIZED);
184   }
185 }
186 
Disconnect(ErrorResult & aRv)187 void StreamFilter::Disconnect(ErrorResult& aRv) {
188   if (mActor) {
189     mActor->Disconnect(aRv);
190   } else {
191     aRv.Throw(NS_ERROR_NOT_INITIALIZED);
192   }
193 }
194 
Close(ErrorResult & aRv)195 void StreamFilter::Close(ErrorResult& aRv) {
196   if (mActor) {
197     mActor->Close(aRv);
198   } else {
199     aRv.Throw(NS_ERROR_NOT_INITIALIZED);
200   }
201 }
202 
203 /*****************************************************************************
204  * Event emitters
205  *****************************************************************************/
206 
FireEvent(const nsAString & aType)207 void StreamFilter::FireEvent(const nsAString& aType) {
208   EventInit init;
209   init.mBubbles = false;
210   init.mCancelable = false;
211 
212   RefPtr<Event> event = Event::Constructor(this, aType, init);
213   event->SetTrusted(true);
214 
215   DispatchEvent(*event);
216 }
217 
FireDataEvent(const nsTArray<uint8_t> & aData)218 void StreamFilter::FireDataEvent(const nsTArray<uint8_t>& aData) {
219   AutoEntryScript aes(mParent, "StreamFilter data event");
220   JSContext* cx = aes.cx();
221 
222   RootedDictionary<StreamFilterDataEventInit> init(cx);
223   init.mBubbles = false;
224   init.mCancelable = false;
225 
226   auto buffer = ArrayBuffer::Create(cx, aData.Length(), aData.Elements());
227   if (!buffer) {
228     // TODO: There is no way to recover from this. This chunk of data is lost.
229     FireErrorEvent(u"Out of memory"_ns);
230     return;
231   }
232 
233   init.mData.Init(buffer);
234 
235   RefPtr<StreamFilterDataEvent> event =
236       StreamFilterDataEvent::Constructor(this, u"data"_ns, init);
237   event->SetTrusted(true);
238 
239   DispatchEvent(*event);
240 }
241 
FireErrorEvent(const nsAString & aError)242 void StreamFilter::FireErrorEvent(const nsAString& aError) {
243   MOZ_ASSERT(mError.IsEmpty());
244 
245   mError = aError;
246   FireEvent(u"error"_ns);
247 }
248 
249 /*****************************************************************************
250  * Glue
251  *****************************************************************************/
252 
253 /* static */
IsAllowedInContext(JSContext * aCx,JSObject *)254 bool StreamFilter::IsAllowedInContext(JSContext* aCx, JSObject* /* unused */) {
255   return nsContentUtils::CallerHasPermission(aCx,
256                                              nsGkAtoms::webRequestBlocking);
257 }
258 
WrapObject(JSContext * aCx,HandleObject aGivenProto)259 JSObject* StreamFilter::WrapObject(JSContext* aCx, HandleObject aGivenProto) {
260   return StreamFilter_Binding::Wrap(aCx, this, aGivenProto);
261 }
262 
263 NS_IMPL_CYCLE_COLLECTION_CLASS(StreamFilter)
264 
265 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(StreamFilter)
266 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
267 
268 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(StreamFilter,
269                                                 DOMEventTargetHelper)
270   NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent)
271 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
272 
273 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(StreamFilter,
274                                                   DOMEventTargetHelper)
275   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent)
276 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
277 
278 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(StreamFilter,
279                                                DOMEventTargetHelper)
280 NS_IMPL_CYCLE_COLLECTION_TRACE_END
281 
282 NS_IMPL_ADDREF_INHERITED(StreamFilter, DOMEventTargetHelper)
283 NS_IMPL_RELEASE_INHERITED(StreamFilter, DOMEventTargetHelper)
284 
285 }  // namespace extensions
286 }  // namespace mozilla
287