Line data Source code
1 : /* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */
2 : /* This Source Code Form is subject to the terms of the Mozilla Public
3 : * License, v. 2.0. If a copy of the MPL was not distributed with this
4 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 :
6 : #include "mozilla/plugins/BrowserStreamChild.h"
7 :
8 : #include "mozilla/Attributes.h"
9 : #include "mozilla/plugins/PluginInstanceChild.h"
10 : #include "mozilla/plugins/StreamNotifyChild.h"
11 :
12 : namespace mozilla {
13 : namespace plugins {
14 :
15 0 : BrowserStreamChild::BrowserStreamChild(PluginInstanceChild* instance,
16 : const nsCString& url,
17 : const uint32_t& length,
18 : const uint32_t& lastmodified,
19 : StreamNotifyChild* notifyData,
20 0 : const nsCString& headers)
21 : : mInstance(instance)
22 : , mStreamStatus(kStreamOpen)
23 : , mDestroyPending(NOT_DESTROYED)
24 : , mNotifyPending(false)
25 : , mStreamAsFilePending(false)
26 : , mInstanceDying(false)
27 : , mState(CONSTRUCTING)
28 : , mURL(url)
29 : , mHeaders(headers)
30 : , mStreamNotify(notifyData)
31 0 : , mDeliveryTracker(this)
32 : {
33 0 : PLUGIN_LOG_DEBUG(("%s (%s, %i, %i, %p, %s)", FULLFUNCTION,
34 : url.get(), length, lastmodified, (void*) notifyData,
35 : headers.get()));
36 :
37 0 : AssertPluginThread();
38 :
39 0 : memset(&mStream, 0, sizeof(mStream));
40 0 : mStream.ndata = static_cast<AStream*>(this);
41 0 : mStream.url = NullableStringGet(mURL);
42 0 : mStream.end = length;
43 0 : mStream.lastmodified = lastmodified;
44 0 : mStream.headers = NullableStringGet(mHeaders);
45 0 : if (notifyData) {
46 0 : mStream.notifyData = notifyData->mClosure;
47 0 : notifyData->SetAssociatedStream(this);
48 : }
49 0 : }
50 :
51 : NPError
52 0 : BrowserStreamChild::StreamConstructed(
53 : const nsCString& mimeType,
54 : const bool& seekable,
55 : uint16_t* stype)
56 : {
57 0 : NPError rv = NPERR_NO_ERROR;
58 :
59 0 : *stype = NP_NORMAL;
60 0 : rv = mInstance->mPluginIface->newstream(
61 0 : &mInstance->mData, const_cast<char*>(NullableStringGet(mimeType)),
62 0 : &mStream, seekable, stype);
63 0 : if (rv != NPERR_NO_ERROR) {
64 0 : mState = DELETING;
65 0 : if (mStreamNotify) {
66 0 : mStreamNotify->SetAssociatedStream(nullptr);
67 0 : mStreamNotify = nullptr;
68 : }
69 : }
70 : else {
71 0 : mState = ALIVE;
72 : }
73 :
74 0 : return rv;
75 : }
76 :
77 0 : BrowserStreamChild::~BrowserStreamChild()
78 : {
79 0 : NS_ASSERTION(!mStreamNotify, "Should have nulled it by now!");
80 0 : }
81 :
82 : mozilla::ipc::IPCResult
83 0 : BrowserStreamChild::RecvWrite(const int32_t& offset,
84 : const uint32_t& newlength,
85 : const Buffer& data)
86 : {
87 0 : PLUGIN_LOG_DEBUG_FUNCTION;
88 :
89 0 : AssertPluginThread();
90 :
91 0 : if (ALIVE != mState)
92 0 : MOZ_CRASH("Unexpected state: received data after NPP_DestroyStream?");
93 :
94 0 : if (kStreamOpen != mStreamStatus)
95 0 : return IPC_OK();
96 :
97 0 : mStream.end = newlength;
98 :
99 0 : NS_ASSERTION(data.Length() > 0, "Empty data");
100 :
101 0 : PendingData* newdata = mPendingData.AppendElement();
102 0 : newdata->offset = offset;
103 0 : newdata->data = data;
104 0 : newdata->curpos = 0;
105 :
106 0 : EnsureDeliveryPending();
107 :
108 0 : return IPC_OK();
109 : }
110 :
111 : mozilla::ipc::IPCResult
112 0 : BrowserStreamChild::RecvNPP_StreamAsFile(const nsCString& fname)
113 : {
114 0 : PLUGIN_LOG_DEBUG(("%s (fname=%s)", FULLFUNCTION, fname.get()));
115 :
116 0 : AssertPluginThread();
117 :
118 0 : if (ALIVE != mState)
119 0 : MOZ_CRASH("Unexpected state: received file after NPP_DestroyStream?");
120 :
121 0 : if (kStreamOpen != mStreamStatus)
122 0 : return IPC_OK();
123 :
124 0 : mStreamAsFilePending = true;
125 0 : mStreamAsFileName = fname;
126 0 : EnsureDeliveryPending();
127 :
128 0 : return IPC_OK();
129 : }
130 :
131 : mozilla::ipc::IPCResult
132 0 : BrowserStreamChild::RecvNPP_DestroyStream(const NPReason& reason)
133 : {
134 0 : PLUGIN_LOG_DEBUG_METHOD;
135 :
136 0 : if (ALIVE != mState)
137 0 : MOZ_CRASH("Unexpected state: recevied NPP_DestroyStream twice?");
138 :
139 0 : mState = DYING;
140 0 : mDestroyPending = DESTROY_PENDING;
141 0 : if (NPRES_DONE != reason)
142 0 : mStreamStatus = reason;
143 :
144 0 : EnsureDeliveryPending();
145 0 : return IPC_OK();
146 : }
147 :
148 : mozilla::ipc::IPCResult
149 0 : BrowserStreamChild::Recv__delete__()
150 : {
151 0 : AssertPluginThread();
152 :
153 0 : if (DELETING != mState)
154 0 : MOZ_CRASH("Bad state, not DELETING");
155 :
156 0 : return IPC_OK();
157 : }
158 :
159 : NPError
160 0 : BrowserStreamChild::NPN_RequestRead(NPByteRange* aRangeList)
161 : {
162 0 : PLUGIN_LOG_DEBUG_FUNCTION;
163 :
164 0 : AssertPluginThread();
165 :
166 0 : if (ALIVE != mState || kStreamOpen != mStreamStatus)
167 0 : return NPERR_GENERIC_ERROR;
168 :
169 0 : IPCByteRanges ranges;
170 0 : for (; aRangeList; aRangeList = aRangeList->next) {
171 0 : IPCByteRange br = {aRangeList->offset, aRangeList->length};
172 0 : ranges.AppendElement(br);
173 : }
174 :
175 : NPError result;
176 0 : CallNPN_RequestRead(ranges, &result);
177 0 : return result;
178 : }
179 :
180 : void
181 0 : BrowserStreamChild::NPN_DestroyStream(NPReason reason)
182 : {
183 0 : mStreamStatus = reason;
184 0 : if (ALIVE == mState)
185 0 : SendNPN_DestroyStream(reason);
186 :
187 0 : EnsureDeliveryPending();
188 0 : }
189 :
190 : void
191 0 : BrowserStreamChild::EnsureDeliveryPending()
192 : {
193 0 : MessageLoop::current()->PostTask(
194 0 : mDeliveryTracker.NewRunnableMethod(&BrowserStreamChild::Deliver));
195 0 : }
196 :
197 : void
198 0 : BrowserStreamChild::Deliver()
199 : {
200 0 : while (kStreamOpen == mStreamStatus && mPendingData.Length()) {
201 0 : if (DeliverPendingData() && kStreamOpen == mStreamStatus) {
202 0 : SetSuspendedTimer();
203 0 : return;
204 : }
205 : }
206 0 : ClearSuspendedTimer();
207 :
208 0 : NS_ASSERTION(kStreamOpen != mStreamStatus || 0 == mPendingData.Length(),
209 : "Exit out of the data-delivery loop with pending data");
210 0 : mPendingData.Clear();
211 :
212 : // NPP_StreamAsFile() is documented (at MDN) to be called "when the stream
213 : // is complete" -- i.e. after all calls to NPP_WriteReady() and NPP_Write()
214 : // have finished. We make these calls asynchronously (from
215 : // DeliverPendingData()). So we need to make sure all the "pending data"
216 : // has been "delivered" before calling NPP_StreamAsFile() (also
217 : // asynchronously). Doing this resolves bug 687610, bug 670036 and possibly
218 : // also other bugs.
219 0 : if (mStreamAsFilePending) {
220 0 : if (mStreamStatus == kStreamOpen)
221 0 : mInstance->mPluginIface->asfile(&mInstance->mData, &mStream,
222 0 : mStreamAsFileName.get());
223 0 : mStreamAsFilePending = false;
224 : }
225 :
226 0 : if (DESTROY_PENDING == mDestroyPending) {
227 0 : mDestroyPending = DESTROYED;
228 0 : if (mState != DYING)
229 0 : MOZ_CRASH("mDestroyPending but state not DYING");
230 :
231 0 : NS_ASSERTION(NPRES_DONE != mStreamStatus, "Success status set too early!");
232 0 : if (kStreamOpen == mStreamStatus)
233 0 : mStreamStatus = NPRES_DONE;
234 :
235 0 : (void) mInstance->mPluginIface
236 0 : ->destroystream(&mInstance->mData, &mStream, mStreamStatus);
237 : }
238 0 : if (DESTROYED == mDestroyPending && mNotifyPending) {
239 0 : NS_ASSERTION(mStreamNotify, "mDestroyPending but no mStreamNotify?");
240 :
241 0 : mNotifyPending = false;
242 0 : mStreamNotify->NPP_URLNotify(mStreamStatus);
243 0 : delete mStreamNotify;
244 0 : mStreamNotify = nullptr;
245 : }
246 0 : if (DYING == mState && DESTROYED == mDestroyPending
247 0 : && !mStreamNotify && !mInstanceDying) {
248 0 : SendStreamDestroyed();
249 0 : mState = DELETING;
250 : }
251 : }
252 :
253 : bool
254 0 : BrowserStreamChild::DeliverPendingData()
255 : {
256 0 : if (mState != ALIVE && mState != DYING)
257 0 : MOZ_CRASH("Unexpected state");
258 :
259 0 : NS_ASSERTION(mPendingData.Length(), "Called from Deliver with empty pending");
260 :
261 0 : while (mPendingData[0].curpos < static_cast<int32_t>(mPendingData[0].data.Length())) {
262 0 : int32_t r = mInstance->mPluginIface->writeready(&mInstance->mData, &mStream);
263 0 : if (kStreamOpen != mStreamStatus)
264 0 : return false;
265 0 : if (0 == r) // plugin wants to suspend delivery
266 0 : return true;
267 :
268 0 : r = mInstance->mPluginIface->write(
269 0 : &mInstance->mData, &mStream,
270 0 : mPendingData[0].offset + mPendingData[0].curpos, // offset
271 0 : mPendingData[0].data.Length() - mPendingData[0].curpos, // length
272 0 : const_cast<char*>(mPendingData[0].data.BeginReading() + mPendingData[0].curpos));
273 0 : if (kStreamOpen != mStreamStatus)
274 0 : return false;
275 0 : if (0 == r)
276 0 : return true;
277 0 : if (r < 0) { // error condition
278 0 : NPN_DestroyStream(NPRES_NETWORK_ERR);
279 0 : return false;
280 : }
281 0 : mPendingData[0].curpos += r;
282 : }
283 0 : mPendingData.RemoveElementAt(0);
284 0 : return false;
285 : }
286 :
287 : void
288 0 : BrowserStreamChild::SetSuspendedTimer()
289 : {
290 0 : if (mSuspendedTimer.IsRunning())
291 0 : return;
292 0 : mSuspendedTimer.Start(
293 : base::TimeDelta::FromMilliseconds(100), // 100ms copied from Mozilla plugin host
294 0 : this, &BrowserStreamChild::Deliver);
295 : }
296 :
297 : void
298 0 : BrowserStreamChild::ClearSuspendedTimer()
299 : {
300 0 : mSuspendedTimer.Stop();
301 0 : }
302 :
303 : } /* namespace plugins */
304 : } /* namespace mozilla */
|