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 COMPUTER, 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 COMPUTER, 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 "core/dom/MessagePort.h" 29 30 #include "bindings/core/v8/ExceptionState.h" 31 #include "bindings/core/v8/ExceptionStatePlaceholder.h" 32 #include "bindings/core/v8/SerializedScriptValue.h" 33 #include "core/dom/CrossThreadTask.h" 34 #include "core/dom/ExceptionCode.h" 35 #include "core/dom/ExecutionContext.h" 36 #include "core/events/MessageEvent.h" 37 #include "core/frame/LocalDOMWindow.h" 38 #include "core/workers/WorkerGlobalScope.h" 39 #include "public/platform/WebString.h" 40 #include "wtf/Functional.h" 41 #include "wtf/text/AtomicString.h" 42 43 namespace blink { 44 45 PassRefPtrWillBeRawPtr<MessagePort> MessagePort::create(ExecutionContext& executionContext) 46 { 47 RefPtrWillBeRawPtr<MessagePort> port = adoptRefWillBeNoop(new MessagePort(executionContext)); 48 port->suspendIfNeeded(); 49 return port.release(); 50 } 51 52 MessagePort::MessagePort(ExecutionContext& executionContext) 53 : ActiveDOMObject(&executionContext) 54 , m_started(false) 55 , m_closed(false) 56 , m_weakFactory(this) 57 { 58 } 59 60 MessagePort::~MessagePort() 61 { 62 close(); 63 } 64 65 void MessagePort::postMessage(ExecutionContext*, PassRefPtr<SerializedScriptValue> message, const MessagePortArray* ports, ExceptionState& exceptionState) 66 { 67 if (!isEntangled()) 68 return; 69 ASSERT(executionContext()); 70 ASSERT(m_entangledChannel); 71 72 OwnPtr<MessagePortChannelArray> channels; 73 // Make sure we aren't connected to any of the passed-in ports. 74 if (ports) { 75 for (unsigned i = 0; i < ports->size(); ++i) { 76 MessagePort* dataPort = (*ports)[i].get(); 77 if (dataPort == this) { 78 exceptionState.throwDOMException(DataCloneError, "Port at index " + String::number(i) + " contains the source port."); 79 return; 80 } 81 } 82 channels = MessagePort::disentanglePorts(ports, exceptionState); 83 if (exceptionState.hadException()) 84 return; 85 } 86 87 WebString messageString = message->toWireString(); 88 OwnPtr<WebMessagePortChannelArray> webChannels = toWebMessagePortChannelArray(channels.release()); 89 m_entangledChannel->postMessage(messageString, webChannels.leakPtr()); 90 } 91 92 // static 93 PassOwnPtr<WebMessagePortChannelArray> MessagePort::toWebMessagePortChannelArray(PassOwnPtr<MessagePortChannelArray> channels) 94 { 95 OwnPtr<WebMessagePortChannelArray> webChannels; 96 if (channels && channels->size()) { 97 webChannels = adoptPtr(new WebMessagePortChannelArray(channels->size())); 98 for (size_t i = 0; i < channels->size(); ++i) 99 (*webChannels)[i] = (*channels)[i].leakPtr(); 100 } 101 return webChannels.release(); 102 } 103 104 // static 105 PassOwnPtrWillBeRawPtr<MessagePortArray> MessagePort::toMessagePortArray(ExecutionContext* context, const WebMessagePortChannelArray& webChannels) 106 { 107 OwnPtrWillBeRawPtr<MessagePortArray> ports = nullptr; 108 if (!webChannels.isEmpty()) { 109 OwnPtr<MessagePortChannelArray> channels = adoptPtr(new MessagePortChannelArray(webChannels.size())); 110 for (size_t i = 0; i < webChannels.size(); ++i) 111 (*channels)[i] = adoptPtr(webChannels[i]); 112 ports = MessagePort::entanglePorts(*context, channels.release()); 113 } 114 return ports.release(); 115 } 116 117 PassOwnPtr<WebMessagePortChannel> MessagePort::disentangle() 118 { 119 ASSERT(m_entangledChannel); 120 m_entangledChannel->setClient(0); 121 return m_entangledChannel.release(); 122 } 123 124 // Invoked to notify us that there are messages available for this port. 125 // 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). 126 void MessagePort::messageAvailable() 127 { 128 ASSERT(executionContext()); 129 executionContext()->postTask(createCrossThreadTask(&MessagePort::dispatchMessages, m_weakFactory.createWeakPtr())); 130 } 131 132 void MessagePort::start() 133 { 134 // Do nothing if we've been cloned or closed. 135 if (!isEntangled()) 136 return; 137 138 ASSERT(executionContext()); 139 if (m_started) 140 return; 141 142 m_started = true; 143 messageAvailable(); 144 } 145 146 void MessagePort::close() 147 { 148 if (isEntangled()) 149 m_entangledChannel->setClient(0); 150 m_closed = true; 151 } 152 153 void MessagePort::entangle(PassOwnPtr<WebMessagePortChannel> remote) 154 { 155 // Only invoked to set our initial entanglement. 156 ASSERT(!m_entangledChannel); 157 ASSERT(executionContext()); 158 159 m_entangledChannel = remote; 160 m_entangledChannel->setClient(this); 161 } 162 163 const AtomicString& MessagePort::interfaceName() const 164 { 165 return EventTargetNames::MessagePort; 166 } 167 168 static bool tryGetMessageFrom(WebMessagePortChannel& webChannel, RefPtr<SerializedScriptValue>& message, OwnPtr<MessagePortChannelArray>& channels) 169 { 170 WebString messageString; 171 WebMessagePortChannelArray webChannels; 172 if (!webChannel.tryGetMessage(&messageString, webChannels)) 173 return false; 174 175 if (webChannels.size()) { 176 channels = adoptPtr(new MessagePortChannelArray(webChannels.size())); 177 for (size_t i = 0; i < webChannels.size(); ++i) 178 (*channels)[i] = adoptPtr(webChannels[i]); 179 } 180 message = SerializedScriptValue::createFromWire(messageString); 181 return true; 182 } 183 184 void MessagePort::dispatchMessages() 185 { 186 // Messages for contexts that are not fully active get dispatched too, but JSAbstractEventListener::handleEvent() doesn't call handlers for these. 187 // The HTML5 spec specifies that any messages sent to a document that is not fully active should be dropped, so this behavior is OK. 188 if (!started()) 189 return; 190 191 RefPtr<SerializedScriptValue> message; 192 OwnPtr<MessagePortChannelArray> channels; 193 while (m_entangledChannel && tryGetMessageFrom(*m_entangledChannel, message, channels)) { 194 // close() in Worker onmessage handler should prevent next message from dispatching. 195 if (executionContext()->isWorkerGlobalScope() && toWorkerGlobalScope(executionContext())->isClosing()) 196 return; 197 198 OwnPtrWillBeRawPtr<MessagePortArray> ports = MessagePort::entanglePorts(*executionContext(), channels.release()); 199 RefPtrWillBeRawPtr<Event> evt = MessageEvent::create(ports.release(), message.release()); 200 201 dispatchEvent(evt.release(), ASSERT_NO_EXCEPTION); 202 } 203 } 204 205 bool MessagePort::hasPendingActivity() const 206 { 207 // The spec says that entangled message ports should always be treated as if they have a strong reference. 208 // 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). 209 return m_started && isEntangled(); 210 } 211 212 PassOwnPtr<MessagePortChannelArray> MessagePort::disentanglePorts(const MessagePortArray* ports, ExceptionState& exceptionState) 213 { 214 if (!ports || !ports->size()) 215 return nullptr; 216 217 // HashSet used to efficiently check for duplicates in the passed-in array. 218 HashSet<MessagePort*> portSet; 219 220 // 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). 221 for (unsigned i = 0; i < ports->size(); ++i) { 222 MessagePort* port = (*ports)[i].get(); 223 if (!port || port->isNeutered() || portSet.contains(port)) { 224 String type; 225 if (!port) 226 type = "null"; 227 else if (port->isNeutered()) 228 type = "already neutered"; 229 else 230 type = "a duplicate"; 231 exceptionState.throwDOMException(DataCloneError, "Port at index " + String::number(i) + " is " + type + "."); 232 return nullptr; 233 } 234 portSet.add(port); 235 } 236 237 // Passed-in ports passed validity checks, so we can disentangle them. 238 OwnPtr<MessagePortChannelArray> portArray = adoptPtr(new MessagePortChannelArray(ports->size())); 239 for (unsigned i = 0; i < ports->size(); ++i) 240 (*portArray)[i] = (*ports)[i]->disentangle(); 241 return portArray.release(); 242 } 243 244 PassOwnPtrWillBeRawPtr<MessagePortArray> MessagePort::entanglePorts(ExecutionContext& context, PassOwnPtr<MessagePortChannelArray> channels) 245 { 246 if (!channels || !channels->size()) 247 return nullptr; 248 249 OwnPtrWillBeRawPtr<MessagePortArray> portArray = adoptPtrWillBeNoop(new MessagePortArray(channels->size())); 250 for (unsigned i = 0; i < channels->size(); ++i) { 251 RefPtrWillBeRawPtr<MessagePort> port = MessagePort::create(context); 252 port->entangle((*channels)[i].release()); 253 (*portArray)[i] = port.release(); 254 } 255 return portArray.release(); 256 } 257 258 } // namespace blink 259