|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
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 "MessagePort.h" |
|
7 #include "MessageEvent.h" |
|
8 #include "mozilla/dom/Event.h" |
|
9 #include "mozilla/dom/MessageChannel.h" |
|
10 #include "mozilla/dom/MessagePortBinding.h" |
|
11 #include "mozilla/dom/MessagePortList.h" |
|
12 #include "mozilla/dom/StructuredCloneTags.h" |
|
13 #include "nsContentUtils.h" |
|
14 #include "nsGlobalWindow.h" |
|
15 #include "nsPresContext.h" |
|
16 |
|
17 #include "nsIDocument.h" |
|
18 #include "nsIDOMFile.h" |
|
19 #include "nsIDOMFileList.h" |
|
20 #include "nsIPresShell.h" |
|
21 |
|
22 namespace mozilla { |
|
23 namespace dom { |
|
24 |
|
25 class DispatchEventRunnable : public nsRunnable |
|
26 { |
|
27 friend class MessagePort; |
|
28 |
|
29 public: |
|
30 DispatchEventRunnable(MessagePort* aPort) |
|
31 : mPort(aPort) |
|
32 { |
|
33 } |
|
34 |
|
35 NS_IMETHOD |
|
36 Run() |
|
37 { |
|
38 nsRefPtr<DispatchEventRunnable> mKungFuDeathGrip(this); |
|
39 |
|
40 mPort->mDispatchRunnable = nullptr; |
|
41 mPort->Dispatch(); |
|
42 |
|
43 return NS_OK; |
|
44 } |
|
45 |
|
46 private: |
|
47 nsRefPtr<MessagePort> mPort; |
|
48 }; |
|
49 |
|
50 class PostMessageRunnable : public nsRunnable |
|
51 { |
|
52 friend class MessagePort; |
|
53 |
|
54 public: |
|
55 NS_DECL_NSIRUNNABLE |
|
56 |
|
57 PostMessageRunnable() |
|
58 { |
|
59 } |
|
60 |
|
61 ~PostMessageRunnable() |
|
62 { |
|
63 } |
|
64 |
|
65 JSAutoStructuredCloneBuffer& Buffer() |
|
66 { |
|
67 return mBuffer; |
|
68 } |
|
69 |
|
70 bool StoreISupports(nsISupports* aSupports) |
|
71 { |
|
72 mSupportsArray.AppendElement(aSupports); |
|
73 return true; |
|
74 } |
|
75 |
|
76 void Dispatch(MessagePort* aPort) |
|
77 { |
|
78 mPort = aPort; |
|
79 NS_DispatchToCurrentThread(this); |
|
80 } |
|
81 |
|
82 private: |
|
83 nsRefPtr<MessagePort> mPort; |
|
84 JSAutoStructuredCloneBuffer mBuffer; |
|
85 |
|
86 nsTArray<nsCOMPtr<nsISupports> > mSupportsArray; |
|
87 }; |
|
88 |
|
89 namespace { |
|
90 |
|
91 struct StructuredCloneInfo |
|
92 { |
|
93 PostMessageRunnable* mEvent; |
|
94 MessagePort* mPort; |
|
95 nsRefPtrHashtable<nsRefPtrHashKey<MessagePortBase>, MessagePortBase> mPorts; |
|
96 }; |
|
97 |
|
98 static JSObject* |
|
99 PostMessageReadStructuredClone(JSContext* cx, |
|
100 JSStructuredCloneReader* reader, |
|
101 uint32_t tag, |
|
102 uint32_t data, |
|
103 void* closure) |
|
104 { |
|
105 if (tag == SCTAG_DOM_BLOB || tag == SCTAG_DOM_FILELIST) { |
|
106 NS_ASSERTION(!data, "Data should be empty"); |
|
107 |
|
108 nsISupports* supports; |
|
109 if (JS_ReadBytes(reader, &supports, sizeof(supports))) { |
|
110 JS::Rooted<JS::Value> val(cx); |
|
111 if (NS_SUCCEEDED(nsContentUtils::WrapNative(cx, supports, &val))) { |
|
112 return JSVAL_TO_OBJECT(val); |
|
113 } |
|
114 } |
|
115 } |
|
116 |
|
117 const JSStructuredCloneCallbacks* runtimeCallbacks = |
|
118 js::GetContextStructuredCloneCallbacks(cx); |
|
119 |
|
120 if (runtimeCallbacks) { |
|
121 return runtimeCallbacks->read(cx, reader, tag, data, nullptr); |
|
122 } |
|
123 |
|
124 return nullptr; |
|
125 } |
|
126 |
|
127 static bool |
|
128 PostMessageWriteStructuredClone(JSContext* cx, |
|
129 JSStructuredCloneWriter* writer, |
|
130 JS::Handle<JSObject*> obj, |
|
131 void *closure) |
|
132 { |
|
133 StructuredCloneInfo* scInfo = static_cast<StructuredCloneInfo*>(closure); |
|
134 NS_ASSERTION(scInfo, "Must have scInfo!"); |
|
135 |
|
136 nsCOMPtr<nsIXPConnectWrappedNative> wrappedNative; |
|
137 nsContentUtils::XPConnect()-> |
|
138 GetWrappedNativeOfJSObject(cx, obj, getter_AddRefs(wrappedNative)); |
|
139 if (wrappedNative) { |
|
140 uint32_t scTag = 0; |
|
141 nsISupports* supports = wrappedNative->Native(); |
|
142 |
|
143 nsCOMPtr<nsIDOMBlob> blob = do_QueryInterface(supports); |
|
144 if (blob) { |
|
145 scTag = SCTAG_DOM_BLOB; |
|
146 } |
|
147 |
|
148 nsCOMPtr<nsIDOMFileList> list = do_QueryInterface(supports); |
|
149 if (list) { |
|
150 scTag = SCTAG_DOM_FILELIST; |
|
151 } |
|
152 |
|
153 if (scTag) { |
|
154 return JS_WriteUint32Pair(writer, scTag, 0) && |
|
155 JS_WriteBytes(writer, &supports, sizeof(supports)) && |
|
156 scInfo->mEvent->StoreISupports(supports); |
|
157 } |
|
158 } |
|
159 |
|
160 const JSStructuredCloneCallbacks* runtimeCallbacks = |
|
161 js::GetContextStructuredCloneCallbacks(cx); |
|
162 |
|
163 if (runtimeCallbacks) { |
|
164 return runtimeCallbacks->write(cx, writer, obj, nullptr); |
|
165 } |
|
166 |
|
167 return false; |
|
168 } |
|
169 |
|
170 static bool |
|
171 PostMessageReadTransferStructuredClone(JSContext* aCx, |
|
172 JSStructuredCloneReader* reader, |
|
173 uint32_t tag, void* data, |
|
174 uint64_t unused, |
|
175 void* aClosure, |
|
176 JS::MutableHandle<JSObject*> returnObject) |
|
177 { |
|
178 StructuredCloneInfo* scInfo = static_cast<StructuredCloneInfo*>(aClosure); |
|
179 NS_ASSERTION(scInfo, "Must have scInfo!"); |
|
180 |
|
181 if (tag == SCTAG_DOM_MAP_MESSAGEPORT) { |
|
182 MessagePort* port = static_cast<MessagePort*>(data); |
|
183 port->BindToOwner(scInfo->mPort->GetOwner()); |
|
184 scInfo->mPorts.Put(port, nullptr); |
|
185 |
|
186 JS::Rooted<JSObject*> obj(aCx, port->WrapObject(aCx)); |
|
187 if (JS_WrapObject(aCx, &obj)) { |
|
188 MOZ_ASSERT(port->GetOwner() == scInfo->mPort->GetOwner()); |
|
189 returnObject.set(obj); |
|
190 } |
|
191 return true; |
|
192 } |
|
193 |
|
194 return false; |
|
195 } |
|
196 |
|
197 static bool |
|
198 PostMessageTransferStructuredClone(JSContext* aCx, |
|
199 JS::Handle<JSObject*> aObj, |
|
200 void* aClosure, |
|
201 uint32_t* aTag, |
|
202 JS::TransferableOwnership* aOwnership, |
|
203 void** aContent, |
|
204 uint64_t *aExtraData) |
|
205 { |
|
206 StructuredCloneInfo* scInfo = static_cast<StructuredCloneInfo*>(aClosure); |
|
207 NS_ASSERTION(scInfo, "Must have scInfo!"); |
|
208 |
|
209 MessagePortBase *port = nullptr; |
|
210 nsresult rv = UNWRAP_OBJECT(MessagePort, aObj, port); |
|
211 if (NS_SUCCEEDED(rv)) { |
|
212 nsRefPtr<MessagePortBase> newPort; |
|
213 if (scInfo->mPorts.Get(port, getter_AddRefs(newPort))) { |
|
214 // No duplicate. |
|
215 return false; |
|
216 } |
|
217 |
|
218 newPort = port->Clone(); |
|
219 scInfo->mPorts.Put(port, newPort); |
|
220 |
|
221 *aTag = SCTAG_DOM_MAP_MESSAGEPORT; |
|
222 *aOwnership = JS::SCTAG_TMO_CUSTOM; |
|
223 *aContent = newPort; |
|
224 *aExtraData = 0; |
|
225 |
|
226 return true; |
|
227 } |
|
228 |
|
229 return false; |
|
230 } |
|
231 |
|
232 static void |
|
233 PostMessageFreeTransferStructuredClone(uint32_t aTag, JS::TransferableOwnership aOwnership, |
|
234 void* aData, |
|
235 uint64_t aExtraData, |
|
236 void* aClosure) |
|
237 { |
|
238 StructuredCloneInfo* scInfo = static_cast<StructuredCloneInfo*>(aClosure); |
|
239 NS_ASSERTION(scInfo, "Must have scInfo!"); |
|
240 |
|
241 if (aTag == SCTAG_DOM_MAP_MESSAGEPORT) { |
|
242 MOZ_ASSERT(aOwnership == JS::SCTAG_TMO_CUSTOM); |
|
243 nsRefPtr<MessagePort> port(static_cast<MessagePort*>(aData)); |
|
244 scInfo->mPorts.Remove(port); |
|
245 } |
|
246 } |
|
247 |
|
248 JSStructuredCloneCallbacks kPostMessageCallbacks = { |
|
249 PostMessageReadStructuredClone, |
|
250 PostMessageWriteStructuredClone, |
|
251 nullptr, |
|
252 PostMessageReadTransferStructuredClone, |
|
253 PostMessageTransferStructuredClone, |
|
254 PostMessageFreeTransferStructuredClone |
|
255 }; |
|
256 |
|
257 } // anonymous namespace |
|
258 |
|
259 static PLDHashOperator |
|
260 PopulateMessagePortList(MessagePortBase* aKey, MessagePortBase* aValue, void* aClosure) |
|
261 { |
|
262 nsTArray<nsRefPtr<MessagePortBase> > *array = |
|
263 static_cast<nsTArray<nsRefPtr<MessagePortBase> > *>(aClosure); |
|
264 |
|
265 array->AppendElement(aKey); |
|
266 return PL_DHASH_NEXT; |
|
267 } |
|
268 |
|
269 NS_IMETHODIMP |
|
270 PostMessageRunnable::Run() |
|
271 { |
|
272 MOZ_ASSERT(mPort); |
|
273 |
|
274 // Get the JSContext for the target window |
|
275 nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(mPort->GetOwner()); |
|
276 NS_ENSURE_STATE(sgo); |
|
277 nsCOMPtr<nsIScriptContext> scriptContext = sgo->GetContext(); |
|
278 AutoPushJSContext cx(scriptContext ? scriptContext->GetNativeContext() |
|
279 : nsContentUtils::GetSafeJSContext()); |
|
280 |
|
281 MOZ_ASSERT(cx); |
|
282 |
|
283 // Deserialize the structured clone data |
|
284 JS::Rooted<JS::Value> messageData(cx); |
|
285 StructuredCloneInfo scInfo; |
|
286 scInfo.mEvent = this; |
|
287 scInfo.mPort = mPort; |
|
288 |
|
289 if (!mBuffer.read(cx, &messageData, &kPostMessageCallbacks, &scInfo)) { |
|
290 return NS_ERROR_DOM_DATA_CLONE_ERR; |
|
291 } |
|
292 |
|
293 // Create the event |
|
294 nsCOMPtr<mozilla::dom::EventTarget> eventTarget = |
|
295 do_QueryInterface(mPort->GetOwner()); |
|
296 nsRefPtr<MessageEvent> event = |
|
297 new MessageEvent(eventTarget, nullptr, nullptr); |
|
298 |
|
299 event->InitMessageEvent(NS_LITERAL_STRING("message"), false /* non-bubbling */, |
|
300 false /* cancelable */, messageData, EmptyString(), |
|
301 EmptyString(), nullptr); |
|
302 event->SetTrusted(true); |
|
303 event->SetSource(mPort); |
|
304 |
|
305 nsTArray<nsRefPtr<MessagePortBase> > ports; |
|
306 scInfo.mPorts.EnumerateRead(PopulateMessagePortList, &ports); |
|
307 event->SetPorts(new MessagePortList(static_cast<dom::Event*>(event.get()), ports)); |
|
308 |
|
309 bool status; |
|
310 mPort->DispatchEvent(static_cast<dom::Event*>(event.get()), &status); |
|
311 return status ? NS_OK : NS_ERROR_FAILURE; |
|
312 } |
|
313 |
|
314 MessagePortBase::MessagePortBase(nsPIDOMWindow* aWindow) |
|
315 : DOMEventTargetHelper(aWindow) |
|
316 { |
|
317 // SetIsDOMBinding() is called by DOMEventTargetHelper's ctor. |
|
318 } |
|
319 |
|
320 MessagePortBase::MessagePortBase() |
|
321 { |
|
322 SetIsDOMBinding(); |
|
323 } |
|
324 |
|
325 NS_IMPL_CYCLE_COLLECTION_CLASS(MessagePort) |
|
326 |
|
327 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MessagePort, |
|
328 DOMEventTargetHelper) |
|
329 NS_IMPL_CYCLE_COLLECTION_UNLINK(mEntangledPort) |
|
330 |
|
331 // Custom unlink loop because this array contains nsRunnable objects |
|
332 // which are not cycle colleactable. |
|
333 while (!tmp->mMessageQueue.IsEmpty()) { |
|
334 NS_IMPL_CYCLE_COLLECTION_UNLINK(mMessageQueue[0]->mPort); |
|
335 NS_IMPL_CYCLE_COLLECTION_UNLINK(mMessageQueue[0]->mSupportsArray); |
|
336 tmp->mMessageQueue.RemoveElementAt(0); |
|
337 } |
|
338 |
|
339 if (tmp->mDispatchRunnable) { |
|
340 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDispatchRunnable->mPort); |
|
341 } |
|
342 |
|
343 NS_IMPL_CYCLE_COLLECTION_UNLINK_END |
|
344 |
|
345 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MessagePort, |
|
346 DOMEventTargetHelper) |
|
347 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEntangledPort) |
|
348 |
|
349 // Custom unlink loop because this array contains nsRunnable objects |
|
350 // which are not cycle colleactable. |
|
351 for (uint32_t i = 0, len = tmp->mMessageQueue.Length(); i < len; ++i) { |
|
352 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMessageQueue[i]->mPort); |
|
353 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMessageQueue[i]->mSupportsArray); |
|
354 } |
|
355 |
|
356 if (tmp->mDispatchRunnable) { |
|
357 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDispatchRunnable->mPort); |
|
358 } |
|
359 |
|
360 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END |
|
361 |
|
362 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(MessagePort) |
|
363 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) |
|
364 |
|
365 NS_IMPL_ADDREF_INHERITED(MessagePort, DOMEventTargetHelper) |
|
366 NS_IMPL_RELEASE_INHERITED(MessagePort, DOMEventTargetHelper) |
|
367 |
|
368 MessagePort::MessagePort(nsPIDOMWindow* aWindow) |
|
369 : MessagePortBase(aWindow) |
|
370 , mMessageQueueEnabled(false) |
|
371 { |
|
372 } |
|
373 |
|
374 MessagePort::~MessagePort() |
|
375 { |
|
376 Close(); |
|
377 } |
|
378 |
|
379 JSObject* |
|
380 MessagePort::WrapObject(JSContext* aCx) |
|
381 { |
|
382 return MessagePortBinding::Wrap(aCx, this); |
|
383 } |
|
384 |
|
385 void |
|
386 MessagePort::PostMessageMoz(JSContext* aCx, JS::Handle<JS::Value> aMessage, |
|
387 const Optional<Sequence<JS::Value>>& aTransferable, |
|
388 ErrorResult& aRv) |
|
389 { |
|
390 nsRefPtr<PostMessageRunnable> event = new PostMessageRunnable(); |
|
391 |
|
392 // We *must* clone the data here, or the JS::Value could be modified |
|
393 // by script |
|
394 StructuredCloneInfo scInfo; |
|
395 scInfo.mEvent = event; |
|
396 scInfo.mPort = this; |
|
397 |
|
398 JS::Rooted<JS::Value> transferable(aCx, JS::UndefinedValue()); |
|
399 if (aTransferable.WasPassed()) { |
|
400 const Sequence<JS::Value>& realTransferable = aTransferable.Value(); |
|
401 |
|
402 // The input sequence only comes from the generated bindings code, which |
|
403 // ensures it is rooted. |
|
404 JS::HandleValueArray elements = |
|
405 JS::HandleValueArray::fromMarkedLocation(realTransferable.Length(), |
|
406 realTransferable.Elements()); |
|
407 |
|
408 JSObject* array = |
|
409 JS_NewArrayObject(aCx, elements); |
|
410 if (!array) { |
|
411 aRv.Throw(NS_ERROR_OUT_OF_MEMORY); |
|
412 return; |
|
413 } |
|
414 transferable.setObject(*array); |
|
415 } |
|
416 |
|
417 if (!event->Buffer().write(aCx, aMessage, transferable, |
|
418 &kPostMessageCallbacks, &scInfo)) { |
|
419 aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR); |
|
420 return; |
|
421 } |
|
422 |
|
423 if (!mEntangledPort) { |
|
424 return; |
|
425 } |
|
426 |
|
427 mEntangledPort->mMessageQueue.AppendElement(event); |
|
428 mEntangledPort->Dispatch(); |
|
429 } |
|
430 |
|
431 void |
|
432 MessagePort::Start() |
|
433 { |
|
434 if (mMessageQueueEnabled) { |
|
435 return; |
|
436 } |
|
437 |
|
438 mMessageQueueEnabled = true; |
|
439 Dispatch(); |
|
440 } |
|
441 |
|
442 void |
|
443 MessagePort::Dispatch() |
|
444 { |
|
445 if (!mMessageQueueEnabled || mMessageQueue.IsEmpty() || mDispatchRunnable) { |
|
446 return; |
|
447 } |
|
448 |
|
449 nsRefPtr<PostMessageRunnable> event = mMessageQueue.ElementAt(0); |
|
450 mMessageQueue.RemoveElementAt(0); |
|
451 |
|
452 event->Dispatch(this); |
|
453 |
|
454 mDispatchRunnable = new DispatchEventRunnable(this); |
|
455 NS_DispatchToCurrentThread(mDispatchRunnable); |
|
456 } |
|
457 |
|
458 void |
|
459 MessagePort::Close() |
|
460 { |
|
461 if (!mEntangledPort) { |
|
462 return; |
|
463 } |
|
464 |
|
465 // This avoids loops. |
|
466 nsRefPtr<MessagePort> port = mEntangledPort; |
|
467 mEntangledPort = nullptr; |
|
468 |
|
469 // Let's disentangle the 2 ports symmetrically. |
|
470 port->Close(); |
|
471 } |
|
472 |
|
473 EventHandlerNonNull* |
|
474 MessagePort::GetOnmessage() |
|
475 { |
|
476 if (NS_IsMainThread()) { |
|
477 return GetEventHandler(nsGkAtoms::onmessage, EmptyString()); |
|
478 } |
|
479 return GetEventHandler(nullptr, NS_LITERAL_STRING("message")); |
|
480 } |
|
481 |
|
482 void |
|
483 MessagePort::SetOnmessage(EventHandlerNonNull* aCallback) |
|
484 { |
|
485 if (NS_IsMainThread()) { |
|
486 SetEventHandler(nsGkAtoms::onmessage, EmptyString(), aCallback); |
|
487 } else { |
|
488 SetEventHandler(nullptr, NS_LITERAL_STRING("message"), aCallback); |
|
489 } |
|
490 |
|
491 // When using onmessage, the call to start() is implied. |
|
492 Start(); |
|
493 } |
|
494 |
|
495 void |
|
496 MessagePort::Entangle(MessagePort* aMessagePort) |
|
497 { |
|
498 MOZ_ASSERT(aMessagePort); |
|
499 MOZ_ASSERT(aMessagePort != this); |
|
500 |
|
501 Close(); |
|
502 |
|
503 mEntangledPort = aMessagePort; |
|
504 } |
|
505 |
|
506 already_AddRefed<MessagePortBase> |
|
507 MessagePort::Clone() |
|
508 { |
|
509 nsRefPtr<MessagePort> newPort = new MessagePort(nullptr); |
|
510 |
|
511 // Move all the events in the port message queue of original port. |
|
512 newPort->mMessageQueue.SwapElements(mMessageQueue); |
|
513 |
|
514 if (mEntangledPort) { |
|
515 nsRefPtr<MessagePort> port = mEntangledPort; |
|
516 mEntangledPort = nullptr; |
|
517 |
|
518 newPort->Entangle(port); |
|
519 port->Entangle(newPort); |
|
520 } |
|
521 |
|
522 return newPort.forget(); |
|
523 } |
|
524 |
|
525 } // namespace dom |
|
526 } // namespace mozilla |