Delete WebMetal implementation in favor of WebGPU
[WebKit-https.git] / Source / WebCore / dom / MessagePort.cpp
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
40 namespace WebCore {
41
42 static Lock allMessagePortsLock;
43 static HashMap<MessagePortIdentifier, MessagePort*>& allMessagePorts()
44 {
45     static NeverDestroyed<HashMap<MessagePortIdentifier, MessagePort*>> map;
46     return map;
47 }
48
49 void MessagePort::ref() const
50 {
51     ++m_refCount;
52 }
53
54 void MessagePort::deref() const
55 {
56     // This custom deref() function ensures that as long as the lock to allMessagePortsLock is taken, no MessagePort will be destroyed.
57     // This allows isExistingMessagePortLocallyReachable and notifyMessageAvailable to easily query the map and manipulate MessagePort instances.
58
59     if (!--m_refCount) {
60         Locker<Lock> locker(allMessagePortsLock);
61
62         if (m_refCount)
63             return;
64
65         auto iterator = allMessagePorts().find(m_identifier);
66         if (iterator != allMessagePorts().end() && iterator->value == this)
67             allMessagePorts().remove(iterator);
68
69         delete this;
70     }
71 }
72
73 bool MessagePort::isExistingMessagePortLocallyReachable(const MessagePortIdentifier& identifier)
74 {
75     Locker<Lock> locker(allMessagePortsLock);
76     auto* port = allMessagePorts().get(identifier);
77     return port && port->isLocallyReachable();
78 }
79
80 void MessagePort::notifyMessageAvailable(const MessagePortIdentifier& identifier)
81 {
82     Locker<Lock> locker(allMessagePortsLock);
83     if (auto* port = allMessagePorts().get(identifier))
84         port->messageAvailable();
85
86 }
87
88 Ref<MessagePort> MessagePort::create(ScriptExecutionContext& scriptExecutionContext, const MessagePortIdentifier& local, const MessagePortIdentifier& remote)
89 {
90     return adoptRef(*new MessagePort(scriptExecutionContext, local, remote));
91 }
92
93 MessagePort::MessagePort(ScriptExecutionContext& scriptExecutionContext, const MessagePortIdentifier& local, const MessagePortIdentifier& remote)
94     : ActiveDOMObject(&scriptExecutionContext)
95     , m_identifier(local)
96     , m_remoteIdentifier(remote)
97 {
98     LOG(MessagePorts, "Created MessagePort %s (%p) in process %" PRIu64, m_identifier.logString().utf8().data(), this, Process::identifier().toUInt64());
99
100     Locker<Lock> locker(allMessagePortsLock);
101     allMessagePorts().set(m_identifier, this);
102
103     m_scriptExecutionContext->createdMessagePort(*this);
104     suspendIfNeeded();
105
106     // Don't need to call processMessageWithMessagePortsSoon() here, because the port will not be opened until start() is invoked.
107 }
108
109 MessagePort::~MessagePort()
110 {
111     LOG(MessagePorts, "Destroyed MessagePort %s (%p) in process %" PRIu64, m_identifier.logString().utf8().data(), this, Process::identifier().toUInt64());
112
113     ASSERT(allMessagePortsLock.isLocked());
114
115     if (m_entangled)
116         close();
117
118     if (m_scriptExecutionContext)
119         m_scriptExecutionContext->destroyedMessagePort(*this);
120 }
121
122 void MessagePort::entangle()
123 {
124     MessagePortChannelProvider::singleton().entangleLocalPortInThisProcessToRemote(m_identifier, m_remoteIdentifier);
125 }
126
127 ExceptionOr<void> MessagePort::postMessage(JSC::ExecState& state, JSC::JSValue messageValue, Vector<JSC::Strong<JSC::JSObject>>&& transfer)
128 {
129     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());
130
131     registerLocalActivity();
132
133     Vector<RefPtr<MessagePort>> ports;
134     auto messageData = SerializedScriptValue::create(state, messageValue, WTFMove(transfer), ports);
135     if (messageData.hasException())
136         return messageData.releaseException();
137
138     if (!isEntangled())
139         return { };
140     ASSERT(m_scriptExecutionContext);
141
142     TransferredMessagePortArray transferredPorts;
143     // Make sure we aren't connected to any of the passed-in ports.
144     if (!ports.isEmpty()) {
145         for (auto& port : ports) {
146             if (port->identifier() == m_identifier || port->identifier() == m_remoteIdentifier)
147                 return Exception { DataCloneError };
148         }
149
150         auto disentangleResult = MessagePort::disentanglePorts(WTFMove(ports));
151         if (disentangleResult.hasException())
152             return disentangleResult.releaseException();
153         transferredPorts = disentangleResult.releaseReturnValue();
154     }
155
156     MessageWithMessagePorts message { messageData.releaseReturnValue(), WTFMove(transferredPorts) };
157
158     LOG(MessagePorts, "Actually posting message to port %s (to be received by port %s)", m_identifier.logString().utf8().data(), m_remoteIdentifier.logString().utf8().data());
159
160     MessagePortChannelProvider::singleton().postMessageToRemote(WTFMove(message), m_remoteIdentifier);
161     return { };
162 }
163
164 void MessagePort::disentangle()
165 {
166     ASSERT(m_entangled);
167     m_entangled = false;
168
169     registerLocalActivity();
170
171     MessagePortChannelProvider::singleton().messagePortDisentangled(m_identifier);
172
173     // We can't receive any messages or generate any events after this, so remove ourselves from the list of active ports.
174     ASSERT(m_scriptExecutionContext);
175     m_scriptExecutionContext->destroyedMessagePort(*this);
176     m_scriptExecutionContext->willDestroyActiveDOMObject(*this);
177     m_scriptExecutionContext->willDestroyDestructionObserver(*this);
178
179     m_scriptExecutionContext = nullptr;
180 }
181
182 void MessagePort::registerLocalActivity()
183 {
184     // Any time certain local operations happen, we dirty our own state to delay GC.
185     m_hasHadLocalActivitySinceLastCheck = true;
186     m_mightBeEligibleForGC = false;
187 }
188
189 // Invoked to notify us that there are messages available for this port.
190 // 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).
191 void MessagePort::messageAvailable()
192 {
193     // This MessagePort object might be disentangled because the port is being transferred,
194     // in which case we'll notify it that messages are available once a new end point is created.
195     if (!m_scriptExecutionContext)
196         return;
197
198     m_scriptExecutionContext->processMessageWithMessagePortsSoon();
199 }
200
201 void MessagePort::start()
202 {
203     // Do nothing if we've been cloned or closed.
204     if (!isEntangled())
205         return;
206
207     registerLocalActivity();
208
209     ASSERT(m_scriptExecutionContext);
210     if (m_started)
211         return;
212
213     m_started = true;
214     m_scriptExecutionContext->processMessageWithMessagePortsSoon();
215 }
216
217 void MessagePort::close()
218 {
219     m_mightBeEligibleForGC = true;
220
221     if (m_closed)
222         return;
223     m_closed = true;
224
225     MessagePortChannelProvider::singleton().messagePortClosed(m_identifier);
226     removeAllEventListeners();
227 }
228
229 void MessagePort::contextDestroyed()
230 {
231     ASSERT(m_scriptExecutionContext);
232
233     close();
234     m_scriptExecutionContext = nullptr;
235 }
236
237 void MessagePort::dispatchMessages()
238 {
239     // Messages for contexts that are not fully active get dispatched too, but JSAbstractEventListener::handleEvent() doesn't call handlers for these.
240     // The HTML5 spec specifies that any messages sent to a document that is not fully active should be dropped, so this behavior is OK.
241     ASSERT(started());
242
243     if (!isEntangled())
244         return;
245
246     RefPtr<WorkerThread> workerThread;
247     if (is<WorkerGlobalScope>(*m_scriptExecutionContext))
248         workerThread = &downcast<WorkerGlobalScope>(*m_scriptExecutionContext).thread();
249
250     auto messagesTakenHandler = [this, weakThis = makeWeakPtr(this), workerThread = WTFMove(workerThread)](Vector<MessageWithMessagePorts>&& messages, Function<void()>&& completionCallback) mutable {
251         ASSERT(isMainThread());
252         auto innerHandler = [this, weakThis = WTFMove(weakThis)](auto&& messages) {
253             if (!weakThis)
254                 return;
255
256             LOG(MessagePorts, "MessagePort %s (%p) dispatching %zu messages", m_identifier.logString().utf8().data(), this, messages.size());
257
258             if (!m_scriptExecutionContext)
259                 return;
260
261             if (!messages.isEmpty())
262                 registerLocalActivity();
263
264             ASSERT(m_scriptExecutionContext->isContextThread());
265
266             bool contextIsWorker = is<WorkerGlobalScope>(*m_scriptExecutionContext);
267             for (auto& message : messages) {
268                 // close() in Worker onmessage handler should prevent next message from dispatching.
269                 if (contextIsWorker && downcast<WorkerGlobalScope>(*m_scriptExecutionContext).isClosing())
270                     return;
271                 auto ports = MessagePort::entanglePorts(*m_scriptExecutionContext, WTFMove(message.transferredPorts));
272                 dispatchEvent(MessageEvent::create(WTFMove(ports), message.message.releaseNonNull()));
273             }
274         };
275
276         if (!workerThread) {
277             innerHandler(WTFMove(messages));
278             completionCallback();
279             return;
280         }
281         workerThread->runLoop().postTaskForMode([innerHandler = WTFMove(innerHandler), messages = WTFMove(messages), completionCallback = WTFMove(completionCallback)](auto&) mutable {
282             innerHandler(WTFMove(messages));
283             callOnMainThread([completionCallback = WTFMove(completionCallback)] {
284                 completionCallback();
285             });
286         }, WorkerRunLoop::defaultMode());
287     };
288
289     MessagePortChannelProvider::singleton().takeAllMessagesForPort(m_identifier, WTFMove(messagesTakenHandler));
290 }
291
292 void MessagePort::updateActivity(MessagePortChannelProvider::HasActivity hasActivity)
293 {
294     bool hasHadLocalActivity = m_hasHadLocalActivitySinceLastCheck;
295     m_hasHadLocalActivitySinceLastCheck = false;
296
297     if (hasActivity == MessagePortChannelProvider::HasActivity::No && !hasHadLocalActivity)
298         m_isRemoteEligibleForGC = true;
299
300     if (hasActivity == MessagePortChannelProvider::HasActivity::Yes)
301         m_isRemoteEligibleForGC = false;
302
303     m_isAskingRemoteAboutGC = false;
304 }
305
306 bool MessagePort::hasPendingActivity() const
307 {
308     m_mightBeEligibleForGC = true;
309
310     // If the ScriptExecutionContext has been shut down on this object close()'ed, we can GC.
311     if (!m_scriptExecutionContext || m_closed)
312         return false;
313
314     // If this object has been idle since the remote port declared itself elgibile for GC, we can GC.
315     if (!m_hasHadLocalActivitySinceLastCheck && m_isRemoteEligibleForGC)
316         return false;
317
318     // If this MessagePort has no message event handler then the existence of remote activity cannot keep it alive.
319     if (!m_hasMessageEventListener)
320         return false;
321
322     // If we're not in the middle of asking the remote port about collectability, do so now.
323     if (!m_isAskingRemoteAboutGC) {
324         RefPtr<WorkerThread> workerThread;
325         if (is<WorkerGlobalScope>(*m_scriptExecutionContext))
326             workerThread = &downcast<WorkerGlobalScope>(*m_scriptExecutionContext).thread();
327
328         MessagePortChannelProvider::singleton().checkRemotePortForActivity(m_remoteIdentifier, [weakThis = makeWeakPtr(const_cast<MessagePort*>(this)), workerThread = WTFMove(workerThread)](MessagePortChannelProvider::HasActivity hasActivity) mutable {
329
330             ASSERT(isMainThread());
331             if (!workerThread) {
332                 if (weakThis)
333                     weakThis->updateActivity(hasActivity);
334                 return;
335             }
336
337             workerThread->runLoop().postTaskForMode([weakThis = WTFMove(weakThis), hasActivity](auto&) mutable {
338                 if (weakThis)
339                     weakThis->updateActivity(hasActivity);
340             }, WorkerRunLoop::defaultMode());
341         });
342         m_isAskingRemoteAboutGC = true;
343     }
344
345     // Since we need an answer from the remote object, we have to pretend we have pending activity for now.
346     return true;
347 }
348
349 bool MessagePort::isLocallyReachable() const
350 {
351     return !m_mightBeEligibleForGC;
352 }
353
354 MessagePort* MessagePort::locallyEntangledPort() const
355 {
356     // FIXME: As the header describes, this is an optional optimization.
357     // Even in the new async model we should be able to get it right.
358     return nullptr;
359 }
360
361 ExceptionOr<TransferredMessagePortArray> MessagePort::disentanglePorts(Vector<RefPtr<MessagePort>>&& ports)
362 {
363     if (ports.isEmpty())
364         return TransferredMessagePortArray { };
365
366     // 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).
367     HashSet<MessagePort*> portSet;
368     for (auto& port : ports) {
369         if (!port || !port->m_entangled || !portSet.add(port.get()).isNewEntry)
370             return Exception { DataCloneError };
371     }
372
373     // Passed-in ports passed validity checks, so we can disentangle them.
374     TransferredMessagePortArray portArray;
375     portArray.reserveInitialCapacity(ports.size());
376     for (auto& port : ports) {
377         portArray.uncheckedAppend({ port->identifier(), port->remoteIdentifier() });
378         port->disentangle();
379     }
380
381     return portArray;
382 }
383
384 Vector<RefPtr<MessagePort>> MessagePort::entanglePorts(ScriptExecutionContext& context, TransferredMessagePortArray&& transferredPorts)
385 {
386     LOG(MessagePorts, "Entangling %zu transferred ports to ScriptExecutionContext %s (%p)", transferredPorts.size(), context.url().string().utf8().data(), &context);
387
388     if (transferredPorts.isEmpty())
389         return { };
390
391     Vector<RefPtr<MessagePort>> ports;
392     ports.reserveInitialCapacity(transferredPorts.size());
393     for (auto& transferredPort : transferredPorts) {
394         auto port = MessagePort::create(context, transferredPort.first, transferredPort.second);
395         port->entangle();
396         ports.uncheckedAppend(WTFMove(port));
397     }
398     return ports;
399 }
400
401 bool MessagePort::addEventListener(const AtomicString& eventType, Ref<EventListener>&& listener, const AddEventListenerOptions& options)
402 {
403     if (eventType == eventNames().messageEvent) {
404         if (listener->isAttribute())
405             start();
406         m_hasMessageEventListener = true;
407         registerLocalActivity();
408     }
409
410     return EventTargetWithInlineData::addEventListener(eventType, WTFMove(listener), options);
411 }
412
413 bool MessagePort::removeEventListener(const AtomicString& eventType, EventListener& listener, const ListenerOptions& options)
414 {
415     auto result = EventTargetWithInlineData::removeEventListener(eventType, listener, options);
416
417     if (!hasEventListeners(eventNames().messageEvent))
418         m_hasMessageEventListener = false;
419
420     return result;
421 }
422
423 const char* MessagePort::activeDOMObjectName() const
424 {
425     return "MessagePort";
426 }
427
428 bool MessagePort::canSuspendForDocumentSuspension() const
429 {
430     return !hasPendingActivity() || (!m_started || m_closed);
431 }
432
433 } // namespace WebCore