1 | /* |
2 | * Copyright (C) 2008 Apple Inc. All Rights Reserved. |
3 | * |
4 | * Redistribution and use in source and binary forms, with or without |
5 | * modification, are permitted provided that the following conditions |
6 | * are met: |
7 | * 1. Redistributions of source code must retain the above copyright |
8 | * notice, this list of conditions and the following disclaimer. |
9 | * 2. Redistributions in binary form must reproduce the above copyright |
10 | * notice, this list of conditions and the following disclaimer in the |
11 | * documentation and/or other materials provided with the distribution. |
12 | * |
13 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
14 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
15 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
16 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
17 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
20 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
21 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
23 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
24 | * |
25 | */ |
26 | |
27 | #include "config.h" |
28 | #include "MessagePort.h" |
29 | |
30 | #include "Document.h" |
31 | #include "EventNames.h" |
32 | #include "Logging.h" |
33 | #include "MessageEvent.h" |
34 | #include "MessagePortChannelProvider.h" |
35 | #include "MessageWithMessagePorts.h" |
36 | #include "WorkerGlobalScope.h" |
37 | #include "WorkerThread.h" |
38 | #include <wtf/CompletionHandler.h> |
39 | #include <wtf/IsoMallocInlines.h> |
40 | |
41 | namespace WebCore { |
42 | |
43 | WTF_MAKE_ISO_ALLOCATED_IMPL(MessagePort); |
44 | |
45 | static Lock allMessagePortsLock; |
46 | static HashMap<MessagePortIdentifier, MessagePort*>& allMessagePorts() |
47 | { |
48 | static NeverDestroyed<HashMap<MessagePortIdentifier, MessagePort*>> map; |
49 | return map; |
50 | } |
51 | |
52 | void MessagePort::ref() const |
53 | { |
54 | ++m_refCount; |
55 | } |
56 | |
57 | void MessagePort::deref() const |
58 | { |
59 | // This custom deref() function ensures that as long as the lock to allMessagePortsLock is taken, no MessagePort will be destroyed. |
60 | // This allows isExistingMessagePortLocallyReachable and notifyMessageAvailable to easily query the map and manipulate MessagePort instances. |
61 | |
62 | if (!--m_refCount) { |
63 | Locker<Lock> locker(allMessagePortsLock); |
64 | |
65 | if (m_refCount) |
66 | return; |
67 | |
68 | auto iterator = allMessagePorts().find(m_identifier); |
69 | if (iterator != allMessagePorts().end() && iterator->value == this) |
70 | allMessagePorts().remove(iterator); |
71 | |
72 | delete this; |
73 | } |
74 | } |
75 | |
76 | bool MessagePort::isExistingMessagePortLocallyReachable(const MessagePortIdentifier& identifier) |
77 | { |
78 | Locker<Lock> locker(allMessagePortsLock); |
79 | auto* port = allMessagePorts().get(identifier); |
80 | return port && port->isLocallyReachable(); |
81 | } |
82 | |
83 | void MessagePort::notifyMessageAvailable(const MessagePortIdentifier& identifier) |
84 | { |
85 | Locker<Lock> locker(allMessagePortsLock); |
86 | if (auto* port = allMessagePorts().get(identifier)) |
87 | port->messageAvailable(); |
88 | |
89 | } |
90 | |
91 | Ref<MessagePort> MessagePort::create(ScriptExecutionContext& scriptExecutionContext, const MessagePortIdentifier& local, const MessagePortIdentifier& remote) |
92 | { |
93 | return adoptRef(*new MessagePort(scriptExecutionContext, local, remote)); |
94 | } |
95 | |
96 | MessagePort::MessagePort(ScriptExecutionContext& scriptExecutionContext, const MessagePortIdentifier& local, const MessagePortIdentifier& remote) |
97 | : ActiveDOMObject(&scriptExecutionContext) |
98 | , m_identifier(local) |
99 | , m_remoteIdentifier(remote) |
100 | { |
101 | LOG(MessagePorts, "Created MessagePort %s (%p) in process %" PRIu64, m_identifier.logString().utf8().data(), this, Process::identifier().toUInt64()); |
102 | |
103 | Locker<Lock> locker(allMessagePortsLock); |
104 | allMessagePorts().set(m_identifier, this); |
105 | |
106 | m_scriptExecutionContext->createdMessagePort(*this); |
107 | suspendIfNeeded(); |
108 | |
109 | // Don't need to call processMessageWithMessagePortsSoon() here, because the port will not be opened until start() is invoked. |
110 | } |
111 | |
112 | MessagePort::~MessagePort() |
113 | { |
114 | LOG(MessagePorts, "Destroyed MessagePort %s (%p) in process %" PRIu64, m_identifier.logString().utf8().data(), this, Process::identifier().toUInt64()); |
115 | |
116 | ASSERT(allMessagePortsLock.isLocked()); |
117 | |
118 | if (m_entangled) |
119 | close(); |
120 | |
121 | if (m_scriptExecutionContext) |
122 | m_scriptExecutionContext->destroyedMessagePort(*this); |
123 | } |
124 | |
125 | void MessagePort::entangle() |
126 | { |
127 | MessagePortChannelProvider::singleton().entangleLocalPortInThisProcessToRemote(m_identifier, m_remoteIdentifier); |
128 | } |
129 | |
130 | ExceptionOr<void> MessagePort::postMessage(JSC::ExecState& state, JSC::JSValue messageValue, Vector<JSC::Strong<JSC::JSObject>>&& transfer) |
131 | { |
132 | LOG(MessagePorts, "Attempting to post message to port %s (to be received by port %s)" , m_identifier.logString().utf8().data(), m_remoteIdentifier.logString().utf8().data()); |
133 | |
134 | registerLocalActivity(); |
135 | |
136 | Vector<RefPtr<MessagePort>> ports; |
137 | auto messageData = SerializedScriptValue::create(state, messageValue, WTFMove(transfer), ports); |
138 | if (messageData.hasException()) |
139 | return messageData.releaseException(); |
140 | |
141 | if (!isEntangled()) |
142 | return { }; |
143 | ASSERT(m_scriptExecutionContext); |
144 | |
145 | TransferredMessagePortArray transferredPorts; |
146 | // Make sure we aren't connected to any of the passed-in ports. |
147 | if (!ports.isEmpty()) { |
148 | for (auto& port : ports) { |
149 | if (port->identifier() == m_identifier || port->identifier() == m_remoteIdentifier) |
150 | return Exception { DataCloneError }; |
151 | } |
152 | |
153 | auto disentangleResult = MessagePort::disentanglePorts(WTFMove(ports)); |
154 | if (disentangleResult.hasException()) |
155 | return disentangleResult.releaseException(); |
156 | transferredPorts = disentangleResult.releaseReturnValue(); |
157 | } |
158 | |
159 | MessageWithMessagePorts message { messageData.releaseReturnValue(), WTFMove(transferredPorts) }; |
160 | |
161 | LOG(MessagePorts, "Actually posting message to port %s (to be received by port %s)" , m_identifier.logString().utf8().data(), m_remoteIdentifier.logString().utf8().data()); |
162 | |
163 | MessagePortChannelProvider::singleton().postMessageToRemote(WTFMove(message), m_remoteIdentifier); |
164 | return { }; |
165 | } |
166 | |
167 | void MessagePort::disentangle() |
168 | { |
169 | ASSERT(m_entangled); |
170 | m_entangled = false; |
171 | |
172 | registerLocalActivity(); |
173 | |
174 | MessagePortChannelProvider::singleton().messagePortDisentangled(m_identifier); |
175 | |
176 | // We can't receive any messages or generate any events after this, so remove ourselves from the list of active ports. |
177 | ASSERT(m_scriptExecutionContext); |
178 | m_scriptExecutionContext->destroyedMessagePort(*this); |
179 | m_scriptExecutionContext->willDestroyActiveDOMObject(*this); |
180 | m_scriptExecutionContext->willDestroyDestructionObserver(*this); |
181 | |
182 | m_scriptExecutionContext = nullptr; |
183 | } |
184 | |
185 | void MessagePort::registerLocalActivity() |
186 | { |
187 | // Any time certain local operations happen, we dirty our own state to delay GC. |
188 | m_hasHadLocalActivitySinceLastCheck = true; |
189 | m_mightBeEligibleForGC = false; |
190 | } |
191 | |
192 | // Invoked to notify us that there are messages available for this port. |
193 | // This code may be called from another thread, and so should not call any non-threadsafe APIs (i.e. should not call into the entangled channel or access mutable variables). |
194 | void MessagePort::messageAvailable() |
195 | { |
196 | // This MessagePort object might be disentangled because the port is being transferred, |
197 | // in which case we'll notify it that messages are available once a new end point is created. |
198 | if (!m_scriptExecutionContext) |
199 | return; |
200 | |
201 | m_scriptExecutionContext->processMessageWithMessagePortsSoon(); |
202 | } |
203 | |
204 | void MessagePort::start() |
205 | { |
206 | // Do nothing if we've been cloned or closed. |
207 | if (!isEntangled()) |
208 | return; |
209 | |
210 | registerLocalActivity(); |
211 | |
212 | ASSERT(m_scriptExecutionContext); |
213 | if (m_started) |
214 | return; |
215 | |
216 | m_started = true; |
217 | m_scriptExecutionContext->processMessageWithMessagePortsSoon(); |
218 | } |
219 | |
220 | void MessagePort::close() |
221 | { |
222 | m_mightBeEligibleForGC = true; |
223 | |
224 | if (m_closed) |
225 | return; |
226 | m_closed = true; |
227 | |
228 | MessagePortChannelProvider::singleton().messagePortClosed(m_identifier); |
229 | removeAllEventListeners(); |
230 | } |
231 | |
232 | void MessagePort::contextDestroyed() |
233 | { |
234 | ASSERT(m_scriptExecutionContext); |
235 | |
236 | close(); |
237 | m_scriptExecutionContext = nullptr; |
238 | } |
239 | |
240 | void MessagePort::dispatchMessages() |
241 | { |
242 | // Messages for contexts that are not fully active get dispatched too, but JSAbstractEventListener::handleEvent() doesn't call handlers for these. |
243 | // The HTML5 spec specifies that any messages sent to a document that is not fully active should be dropped, so this behavior is OK. |
244 | ASSERT(started()); |
245 | |
246 | if (!isEntangled()) |
247 | return; |
248 | |
249 | RefPtr<WorkerThread> workerThread; |
250 | if (is<WorkerGlobalScope>(*m_scriptExecutionContext)) |
251 | workerThread = &downcast<WorkerGlobalScope>(*m_scriptExecutionContext).thread(); |
252 | |
253 | auto messagesTakenHandler = [this, weakThis = makeWeakPtr(this), workerThread = WTFMove(workerThread)](Vector<MessageWithMessagePorts>&& messages, Function<void()>&& completionCallback) mutable { |
254 | ASSERT(isMainThread()); |
255 | auto innerHandler = [this, weakThis = WTFMove(weakThis)](auto&& messages) { |
256 | if (!weakThis) |
257 | return; |
258 | |
259 | LOG(MessagePorts, "MessagePort %s (%p) dispatching %zu messages" , m_identifier.logString().utf8().data(), this, messages.size()); |
260 | |
261 | if (!m_scriptExecutionContext) |
262 | return; |
263 | |
264 | if (!messages.isEmpty()) |
265 | registerLocalActivity(); |
266 | |
267 | ASSERT(m_scriptExecutionContext->isContextThread()); |
268 | |
269 | bool contextIsWorker = is<WorkerGlobalScope>(*m_scriptExecutionContext); |
270 | for (auto& message : messages) { |
271 | // close() in Worker onmessage handler should prevent next message from dispatching. |
272 | if (contextIsWorker && downcast<WorkerGlobalScope>(*m_scriptExecutionContext).isClosing()) |
273 | return; |
274 | auto ports = MessagePort::entanglePorts(*m_scriptExecutionContext, WTFMove(message.transferredPorts)); |
275 | dispatchEvent(MessageEvent::create(WTFMove(ports), message.message.releaseNonNull())); |
276 | } |
277 | }; |
278 | |
279 | if (!workerThread) { |
280 | innerHandler(WTFMove(messages)); |
281 | completionCallback(); |
282 | return; |
283 | } |
284 | workerThread->runLoop().postTaskForMode([innerHandler = WTFMove(innerHandler), messages = WTFMove(messages), completionCallback = WTFMove(completionCallback)](auto&) mutable { |
285 | innerHandler(WTFMove(messages)); |
286 | callOnMainThread([completionCallback = WTFMove(completionCallback)] { |
287 | completionCallback(); |
288 | }); |
289 | }, WorkerRunLoop::defaultMode()); |
290 | }; |
291 | |
292 | MessagePortChannelProvider::singleton().takeAllMessagesForPort(m_identifier, WTFMove(messagesTakenHandler)); |
293 | } |
294 | |
295 | void MessagePort::updateActivity(MessagePortChannelProvider::HasActivity hasActivity) |
296 | { |
297 | bool hasHadLocalActivity = m_hasHadLocalActivitySinceLastCheck; |
298 | m_hasHadLocalActivitySinceLastCheck = false; |
299 | |
300 | if (hasActivity == MessagePortChannelProvider::HasActivity::No && !hasHadLocalActivity) |
301 | m_isRemoteEligibleForGC = true; |
302 | |
303 | if (hasActivity == MessagePortChannelProvider::HasActivity::Yes) |
304 | m_isRemoteEligibleForGC = false; |
305 | |
306 | m_isAskingRemoteAboutGC = false; |
307 | } |
308 | |
309 | bool MessagePort::hasPendingActivity() const |
310 | { |
311 | m_mightBeEligibleForGC = true; |
312 | |
313 | // If the ScriptExecutionContext has been shut down on this object close()'ed, we can GC. |
314 | if (!m_scriptExecutionContext || m_closed) |
315 | return false; |
316 | |
317 | // If this object has been idle since the remote port declared itself elgibile for GC, we can GC. |
318 | if (!m_hasHadLocalActivitySinceLastCheck && m_isRemoteEligibleForGC) |
319 | return false; |
320 | |
321 | // If this MessagePort has no message event handler then the existence of remote activity cannot keep it alive. |
322 | if (!m_hasMessageEventListener) |
323 | return false; |
324 | |
325 | // If we're not in the middle of asking the remote port about collectability, do so now. |
326 | if (!m_isAskingRemoteAboutGC) { |
327 | RefPtr<WorkerThread> workerThread; |
328 | if (is<WorkerGlobalScope>(*m_scriptExecutionContext)) |
329 | workerThread = &downcast<WorkerGlobalScope>(*m_scriptExecutionContext).thread(); |
330 | |
331 | MessagePortChannelProvider::singleton().checkRemotePortForActivity(m_remoteIdentifier, [weakThis = makeWeakPtr(const_cast<MessagePort*>(this)), workerThread = WTFMove(workerThread)](MessagePortChannelProvider::HasActivity hasActivity) mutable { |
332 | |
333 | ASSERT(isMainThread()); |
334 | if (!workerThread) { |
335 | if (weakThis) |
336 | weakThis->updateActivity(hasActivity); |
337 | return; |
338 | } |
339 | |
340 | workerThread->runLoop().postTaskForMode([weakThis = WTFMove(weakThis), hasActivity](auto&) mutable { |
341 | if (weakThis) |
342 | weakThis->updateActivity(hasActivity); |
343 | }, WorkerRunLoop::defaultMode()); |
344 | }); |
345 | m_isAskingRemoteAboutGC = true; |
346 | } |
347 | |
348 | // Since we need an answer from the remote object, we have to pretend we have pending activity for now. |
349 | return true; |
350 | } |
351 | |
352 | bool MessagePort::isLocallyReachable() const |
353 | { |
354 | return !m_mightBeEligibleForGC; |
355 | } |
356 | |
357 | MessagePort* MessagePort::locallyEntangledPort() const |
358 | { |
359 | // FIXME: As the header describes, this is an optional optimization. |
360 | // Even in the new async model we should be able to get it right. |
361 | return nullptr; |
362 | } |
363 | |
364 | ExceptionOr<TransferredMessagePortArray> MessagePort::disentanglePorts(Vector<RefPtr<MessagePort>>&& ports) |
365 | { |
366 | if (ports.isEmpty()) |
367 | return TransferredMessagePortArray { }; |
368 | |
369 | // Walk the incoming array - if there are any duplicate ports, or null ports or cloned ports, throw an error (per section 8.3.3 of the HTML5 spec). |
370 | HashSet<MessagePort*> portSet; |
371 | for (auto& port : ports) { |
372 | if (!port || !port->m_entangled || !portSet.add(port.get()).isNewEntry) |
373 | return Exception { DataCloneError }; |
374 | } |
375 | |
376 | // Passed-in ports passed validity checks, so we can disentangle them. |
377 | TransferredMessagePortArray portArray; |
378 | portArray.reserveInitialCapacity(ports.size()); |
379 | for (auto& port : ports) { |
380 | portArray.uncheckedAppend({ port->identifier(), port->remoteIdentifier() }); |
381 | port->disentangle(); |
382 | } |
383 | |
384 | return portArray; |
385 | } |
386 | |
387 | Vector<RefPtr<MessagePort>> MessagePort::entanglePorts(ScriptExecutionContext& context, TransferredMessagePortArray&& transferredPorts) |
388 | { |
389 | LOG(MessagePorts, "Entangling %zu transferred ports to ScriptExecutionContext %s (%p)" , transferredPorts.size(), context.url().string().utf8().data(), &context); |
390 | |
391 | if (transferredPorts.isEmpty()) |
392 | return { }; |
393 | |
394 | Vector<RefPtr<MessagePort>> ports; |
395 | ports.reserveInitialCapacity(transferredPorts.size()); |
396 | for (auto& transferredPort : transferredPorts) { |
397 | auto port = MessagePort::create(context, transferredPort.first, transferredPort.second); |
398 | port->entangle(); |
399 | ports.uncheckedAppend(WTFMove(port)); |
400 | } |
401 | return ports; |
402 | } |
403 | |
404 | bool MessagePort::addEventListener(const AtomicString& eventType, Ref<EventListener>&& listener, const AddEventListenerOptions& options) |
405 | { |
406 | if (eventType == eventNames().messageEvent) { |
407 | if (listener->isAttribute()) |
408 | start(); |
409 | m_hasMessageEventListener = true; |
410 | registerLocalActivity(); |
411 | } |
412 | |
413 | return EventTargetWithInlineData::addEventListener(eventType, WTFMove(listener), options); |
414 | } |
415 | |
416 | bool MessagePort::removeEventListener(const AtomicString& eventType, EventListener& listener, const ListenerOptions& options) |
417 | { |
418 | auto result = EventTargetWithInlineData::removeEventListener(eventType, listener, options); |
419 | |
420 | if (!hasEventListeners(eventNames().messageEvent)) |
421 | m_hasMessageEventListener = false; |
422 | |
423 | return result; |
424 | } |
425 | |
426 | const char* MessagePort::activeDOMObjectName() const |
427 | { |
428 | return "MessagePort" ; |
429 | } |
430 | |
431 | bool MessagePort::canSuspendForDocumentSuspension() const |
432 | { |
433 | return !hasPendingActivity() || (!m_started || m_closed); |
434 | } |
435 | |
436 | } // namespace WebCore |
437 | |