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