Identify MessagePorts by a globally unique MessagePortIdentifier.
[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 "MessageEvent.h"
33 #include "WorkerGlobalScope.h"
34
35 namespace WebCore {
36
37 static HashMap<MessagePortIdentifier, MessagePort*>& allMessagePorts()
38 {
39     static NeverDestroyed<HashMap<MessagePortIdentifier, MessagePort*>> map;
40     return map;
41 }
42
43 static Lock& allMessagePortsLock()
44 {
45     static NeverDestroyed<Lock> lock;
46     return lock;
47 }
48
49 void MessagePort::ref() const
50 {
51     ++m_refCount;
52 }
53
54 void MessagePort::deref() const
55 {
56     // MessagePort::existingMessagePortForIdentifier() is unique in that it holds a raw pointer to a MessagePort
57     // but might create a RefPtr from it.
58     // If that happens on one thread at the same time that a MessagePort is being deref'ed and destroyed on a
59     // different thread then Bad Things could happen.
60     // This custom deref() function is designed to handle that contention by guaranteeing that nobody can be
61     // creating a RefPtr inside existingMessagePortForIdentifier while the object is mid-deletion.
62
63     if (!--m_refCount) {
64         Locker<Lock> locker(allMessagePortsLock());
65
66         if (m_refCount)
67             return;
68
69         allMessagePorts().remove(m_identifier);
70         delete this;
71     }
72 }
73
74 RefPtr<MessagePort> MessagePort::existingMessagePortForIdentifier(const MessagePortIdentifier& identifier)
75 {
76     Locker<Lock> locker(allMessagePortsLock());
77
78     return allMessagePorts().get(identifier);
79 }
80
81 MessagePort::MessagePort(ScriptExecutionContext& scriptExecutionContext, const MessagePortIdentifier& identifier)
82     : ActiveDOMObject(&scriptExecutionContext)
83     , m_identifier(identifier)
84 {
85     Locker<Lock> locker(allMessagePortsLock());
86     allMessagePorts().set(m_identifier, this);
87
88     m_scriptExecutionContext->createdMessagePort(*this);
89     suspendIfNeeded();
90
91     // Don't need to call processMessagePortMessagesSoon() here, because the port will not be opened until start() is invoked.
92 }
93
94 MessagePort::~MessagePort()
95 {
96     ASSERT(allMessagePortsLock().isLocked());
97
98     close();
99     if (m_scriptExecutionContext)
100         m_scriptExecutionContext->destroyedMessagePort(*this);
101 }
102
103 ExceptionOr<void> MessagePort::postMessage(JSC::ExecState& state, JSC::JSValue messageValue, Vector<JSC::Strong<JSC::JSObject>>&& transfer)
104 {
105     Vector<RefPtr<MessagePort>> ports;
106     auto message = SerializedScriptValue::create(state, messageValue, WTFMove(transfer), ports);
107     if (message.hasException())
108         return message.releaseException();
109
110     if (!isEntangled())
111         return { };
112     ASSERT(m_scriptExecutionContext);
113
114     std::unique_ptr<MessagePortChannelArray> channels;
115     // Make sure we aren't connected to any of the passed-in ports.
116     if (!ports.isEmpty()) {
117         for (auto& dataPort : ports) {
118             if (dataPort == this || m_entangledChannel->isConnectedTo(dataPort->identifier()))
119                 return Exception { DataCloneError };
120         }
121
122         auto disentangleResult = MessagePort::disentanglePorts(WTFMove(ports));
123         if (disentangleResult.hasException())
124             return disentangleResult.releaseException();
125         channels = disentangleResult.releaseReturnValue();
126     }
127     m_entangledChannel->postMessageToRemote(message.releaseReturnValue(), WTFMove(channels));
128     return { };
129 }
130
131 RefPtr<MessagePortChannel> MessagePort::disentangle()
132 {
133     ASSERT(m_entangledChannel);
134
135     m_entangledChannel->disentangle();
136
137     // We can't receive any messages or generate any events after this, so remove ourselves from the list of active ports.
138     ASSERT(m_scriptExecutionContext);
139     m_scriptExecutionContext->destroyedMessagePort(*this);
140     m_scriptExecutionContext->willDestroyActiveDOMObject(*this);
141     m_scriptExecutionContext->willDestroyDestructionObserver(*this);
142
143     m_scriptExecutionContext = nullptr;
144
145     return WTFMove(m_entangledChannel);
146 }
147
148 // Invoked to notify us that there are messages available for this port.
149 // 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).
150 void MessagePort::messageAvailable()
151 {
152     ASSERT(m_scriptExecutionContext);
153     m_scriptExecutionContext->processMessagePortMessagesSoon();
154 }
155
156 void MessagePort::start()
157 {
158     // Do nothing if we've been cloned or closed.
159     if (!isEntangled())
160         return;
161
162     ASSERT(m_scriptExecutionContext);
163     if (m_started)
164         return;
165
166     m_started = true;
167     m_scriptExecutionContext->processMessagePortMessagesSoon();
168 }
169
170 void MessagePort::close()
171 {
172     if (isEntangled())
173         m_entangledChannel->close();
174     m_closed = true;
175 }
176
177 void MessagePort::entangleWithRemote(RefPtr<MessagePortChannel>&& remote)
178 {
179     // Only invoked to set our initial entanglement.
180     ASSERT(!m_entangledChannel);
181     ASSERT(m_scriptExecutionContext);
182
183     // Don't entangle the ports if the channel is closed.
184     if (remote->entangleWithRemoteIfOpen(m_identifier))
185         m_entangledChannel = WTFMove(remote);
186 }
187
188 void MessagePort::contextDestroyed()
189 {
190     ASSERT(m_scriptExecutionContext);
191     // Must be closed before blowing away the cached context, to ensure that we get no more calls to messageAvailable().
192     // ScriptExecutionContext::closeMessagePorts() takes care of that.
193     ASSERT(m_closed);
194     m_scriptExecutionContext = nullptr;
195 }
196
197 void MessagePort::dispatchMessages()
198 {
199     // Messages for contexts that are not fully active get dispatched too, but JSAbstractEventListener::handleEvent() doesn't call handlers for these.
200     // The HTML5 spec specifies that any messages sent to a document that is not fully active should be dropped, so this behavior is OK.
201     ASSERT(started());
202
203     if (!m_entangledChannel)
204         return;
205
206     bool contextIsWorker = is<WorkerGlobalScope>(*m_scriptExecutionContext);
207
208     auto pendingMessages = m_entangledChannel->takeAllMessagesFromRemote();
209     for (auto& message : pendingMessages) {
210         // close() in Worker onmessage handler should prevent next message from dispatching.
211         if (contextIsWorker && downcast<WorkerGlobalScope>(*m_scriptExecutionContext).isClosing())
212             return;
213
214         auto ports = MessagePort::entanglePorts(*m_scriptExecutionContext, WTFMove(message->channels));
215         dispatchEvent(MessageEvent::create(WTFMove(ports), WTFMove(message->message)));
216     }
217 }
218
219 bool MessagePort::hasPendingActivity() const
220 {
221     // The spec says that entangled message ports should always be treated as if they have a strong reference.
222     // We'll also stipulate that the queue needs to be open (if the app drops its reference to the port before start()-ing it, then it's not really entangled as it's unreachable).
223     if (m_started && m_entangledChannel && m_entangledChannel->hasPendingActivity())
224         return true;
225
226     if (isEntangled() && !locallyEntangledPort())
227         return true;
228
229     return false;
230 }
231
232 MessagePort* MessagePort::locallyEntangledPort() const
233 {
234     return m_entangledChannel ? m_entangledChannel->locallyEntangledPort(m_scriptExecutionContext) : nullptr;
235 }
236
237 ExceptionOr<std::unique_ptr<MessagePortChannelArray>> MessagePort::disentanglePorts(Vector<RefPtr<MessagePort>>&& ports)
238 {
239     if (ports.isEmpty())
240         return nullptr;
241
242     // 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).
243     HashSet<MessagePort*> portSet;
244     for (auto& port : ports) {
245         if (!port || port->isNeutered() || !portSet.add(port.get()).isNewEntry)
246             return Exception { DataCloneError };
247     }
248
249     // Passed-in ports passed validity checks, so we can disentangle them.
250     auto portArray = std::make_unique<MessagePortChannelArray>(ports.size());
251     for (unsigned i = 0 ; i < ports.size(); ++i)
252         (*portArray)[i] = ports[i]->disentangle();
253     return WTFMove(portArray);
254 }
255
256 Vector<RefPtr<MessagePort>> MessagePort::entanglePorts(ScriptExecutionContext& context, std::unique_ptr<MessagePortChannelArray>&& channels)
257 {
258     if (!channels || !channels->size())
259         return { };
260
261     Vector<RefPtr<MessagePort>> portArray;
262     portArray.reserveInitialCapacity(channels->size());
263     for (unsigned int i = 0; i < channels->size(); ++i) {
264         auto port = MessagePort::create(context, { Process::identifier(), generateObjectIdentifier<MessagePortIdentifier::PortIdentifierType>() });
265         port->entangleWithRemote(WTFMove((*channels)[i]));
266         portArray.uncheckedAppend(WTFMove(port));
267     }
268     return portArray;
269 }
270
271 bool MessagePort::addEventListener(const AtomicString& eventType, Ref<EventListener>&& listener, const AddEventListenerOptions& options)
272 {
273     if (listener->isAttribute() && eventType == eventNames().messageEvent)
274         start();
275     return EventTargetWithInlineData::addEventListener(eventType, WTFMove(listener), options);
276 }
277
278 const char* MessagePort::activeDOMObjectName() const
279 {
280     return "MessagePort";
281 }
282
283 bool MessagePort::canSuspendForDocumentSuspension() const
284 {
285     return !hasPendingActivity() || (!m_started || m_closed);
286 }
287
288 } // namespace WebCore