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