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