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