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
41namespace WebCore {
42
43WTF_MAKE_ISO_ALLOCATED_IMPL(MessagePort);
44
45static Lock allMessagePortsLock;
46static HashMap<MessagePortIdentifier, MessagePort*>& allMessagePorts()
47{
48 static NeverDestroyed<HashMap<MessagePortIdentifier, MessagePort*>> map;
49 return map;
50}
51
52void MessagePort::ref() const
53{
54 ++m_refCount;
55}
56
57void 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
76bool MessagePort::isExistingMessagePortLocallyReachable(const MessagePortIdentifier& identifier)
77{
78 Locker<Lock> locker(allMessagePortsLock);
79 auto* port = allMessagePorts().get(identifier);
80 return port && port->isLocallyReachable();
81}
82
83void MessagePort::notifyMessageAvailable(const MessagePortIdentifier& identifier)
84{
85 Locker<Lock> locker(allMessagePortsLock);
86 if (auto* port = allMessagePorts().get(identifier))
87 port->messageAvailable();
88
89}
90
91Ref<MessagePort> MessagePort::create(ScriptExecutionContext& scriptExecutionContext, const MessagePortIdentifier& local, const MessagePortIdentifier& remote)
92{
93 return adoptRef(*new MessagePort(scriptExecutionContext, local, remote));
94}
95
96MessagePort::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
112MessagePort::~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
125void MessagePort::entangle()
126{
127 MessagePortChannelProvider::singleton().entangleLocalPortInThisProcessToRemote(m_identifier, m_remoteIdentifier);
128}
129
130ExceptionOr<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
167void 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
185void 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).
194void 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
204void 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
220void 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
232void MessagePort::contextDestroyed()
233{
234 ASSERT(m_scriptExecutionContext);
235
236 close();
237 m_scriptExecutionContext = nullptr;
238}
239
240void 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
295void 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
309bool 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
352bool MessagePort::isLocallyReachable() const
353{
354 return !m_mightBeEligibleForGC;
355}
356
357MessagePort* 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
364ExceptionOr<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
387Vector<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
404bool MessagePort::addEventListener(const AtomString& 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
416bool MessagePort::removeEventListener(const AtomString& 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
426const char* MessagePort::activeDOMObjectName() const
427{
428 return "MessagePort";
429}
430
431bool MessagePort::canSuspendForDocumentSuspension() const
432{
433 return !hasPendingActivity() || (!m_started || m_closed);
434}
435
436} // namespace WebCore
437