| OLD | NEW |
| (Empty) |
| 1 /* | |
| 2 * Copyright (C) 2011, 2012 Google 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 are | |
| 6 * met: | |
| 7 * | |
| 8 * * Redistributions of source code must retain the above copyright | |
| 9 * notice, this list of conditions and the following disclaimer. | |
| 10 * * Redistributions in binary form must reproduce the above | |
| 11 * copyright notice, this list of conditions and the following disclaimer | |
| 12 * in the documentation and/or other materials provided with the | |
| 13 * distribution. | |
| 14 * * Neither the name of Google Inc. nor the names of its | |
| 15 * contributors may be used to endorse or promote products derived from | |
| 16 * this software without specific prior written permission. | |
| 17 * | |
| 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 29 */ | |
| 30 | |
| 31 #include "config.h" | |
| 32 #include "modules/websockets/MainThreadWebSocketChannel.h" | |
| 33 | |
| 34 #include "bindings/core/v8/ExceptionStatePlaceholder.h" | |
| 35 #include "core/dom/Document.h" | |
| 36 #include "core/dom/ExecutionContext.h" | |
| 37 #include "core/fileapi/Blob.h" | |
| 38 #include "core/fileapi/FileReaderLoader.h" | |
| 39 #include "core/frame/LocalFrame.h" | |
| 40 #include "core/inspector/ConsoleMessage.h" | |
| 41 #include "core/inspector/InspectorInstrumentation.h" | |
| 42 #include "core/inspector/InspectorTraceEvents.h" | |
| 43 #include "core/loader/FrameLoader.h" | |
| 44 #include "core/loader/FrameLoaderClient.h" | |
| 45 #include "core/loader/MixedContentChecker.h" | |
| 46 #include "core/loader/UniqueIdentifier.h" | |
| 47 #include "core/page/Page.h" | |
| 48 #include "modules/websockets/WebSocketChannelClient.h" | |
| 49 #include "platform/Logging.h" | |
| 50 #include "platform/network/SocketStreamError.h" | |
| 51 #include "platform/network/SocketStreamHandle.h" | |
| 52 #include "wtf/ArrayBuffer.h" | |
| 53 #include "wtf/FastMalloc.h" | |
| 54 #include "wtf/HashMap.h" | |
| 55 #include "wtf/OwnPtr.h" | |
| 56 #include "wtf/text/StringHash.h" | |
| 57 #include "wtf/text/WTFString.h" | |
| 58 | |
| 59 namespace blink { | |
| 60 | |
| 61 const double TCPMaximumSegmentLifetime = 2 * 60.0; | |
| 62 | |
| 63 MainThreadWebSocketChannel::MainThreadWebSocketChannel(Document* document, WebSo
cketChannelClient* client, const String& sourceURL, unsigned lineNumber) | |
| 64 : m_document(document) | |
| 65 , m_client(client) | |
| 66 , m_resumeTimer(this, &MainThreadWebSocketChannel::resumeTimerFired) | |
| 67 , m_suspended(false) | |
| 68 , m_didSendErrorToClient(false) | |
| 69 , m_hasCalledDisconnectOnHandle(false) | |
| 70 , m_receivedClosingHandshake(false) | |
| 71 , m_closingTimer(this, &MainThreadWebSocketChannel::closingTimerFired) | |
| 72 , m_state(ChannelIdle) | |
| 73 , m_shouldDiscardReceivedData(false) | |
| 74 , m_identifier(0) | |
| 75 , m_hasContinuousFrame(false) | |
| 76 , m_closeEventCode(CloseEventCodeAbnormalClosure) | |
| 77 , m_outgoingFrameQueueStatus(OutgoingFrameQueueOpen) | |
| 78 , m_numConsumedBytesInCurrentFrame(0) | |
| 79 , m_blobLoaderStatus(BlobLoaderNotStarted) | |
| 80 , m_sourceURLAtConstruction(sourceURL) | |
| 81 , m_lineNumberAtConstruction(lineNumber) | |
| 82 { | |
| 83 if (m_document->page()) | |
| 84 m_identifier = createUniqueIdentifier(); | |
| 85 } | |
| 86 | |
| 87 MainThreadWebSocketChannel::~MainThreadWebSocketChannel() | |
| 88 { | |
| 89 } | |
| 90 | |
| 91 bool MainThreadWebSocketChannel::connect(const KURL& url, const String& protocol
) | |
| 92 { | |
| 93 WTF_LOG(Network, "MainThreadWebSocketChannel %p connect()", this); | |
| 94 ASSERT(!m_handle); | |
| 95 ASSERT(!m_suspended); | |
| 96 | |
| 97 if (m_document->frame() && !m_document->frame()->loader().mixedContentChecke
r()->canConnectInsecureWebSocket(m_document->securityOrigin(), url)) | |
| 98 return false; | |
| 99 if (MixedContentChecker::isMixedContent(m_document->securityOrigin(), url))
{ | |
| 100 String message = "Connecting to a non-secure WebSocket server from a sec
ure origin is deprecated."; | |
| 101 m_document->addConsoleMessage(ConsoleMessage::create(JSMessageSource, Wa
rningMessageLevel, message)); | |
| 102 } | |
| 103 | |
| 104 m_handshake = new WebSocketHandshake(url, protocol, m_document); | |
| 105 m_handshake->reset(); | |
| 106 m_handshake->addExtensionProcessor(m_perMessageDeflate.createExtensionProces
sor()); | |
| 107 m_handshake->addExtensionProcessor(m_deflateFramer.createExtensionProcessor(
)); | |
| 108 if (m_identifier) { | |
| 109 TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "We
bSocketCreate", "data", InspectorWebSocketCreateEvent::data(m_document, m_identi
fier, url, protocol)); | |
| 110 TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline.stack"
), "CallStack", "stack", InspectorCallStackEvent::currentCallStack()); | |
| 111 // FIXME(361045): remove InspectorInstrumentation calls once DevTools Ti
meline migrates to tracing. | |
| 112 InspectorInstrumentation::didCreateWebSocket(m_document, m_identifier, u
rl, protocol); | |
| 113 } | |
| 114 ref(); | |
| 115 | |
| 116 m_handle = SocketStreamHandle::create(this); | |
| 117 ASSERT(m_handle); | |
| 118 if (m_document->frame()) { | |
| 119 m_document->frame()->loader().client()->dispatchWillOpenSocketStream(m_h
andle.get()); | |
| 120 } | |
| 121 m_handle->connect(m_handshake->url()); | |
| 122 | |
| 123 return true; | |
| 124 } | |
| 125 | |
| 126 void MainThreadWebSocketChannel::send(const String& message) | |
| 127 { | |
| 128 WTF_LOG(Network, "MainThreadWebSocketChannel %p send() Sending String '%s'",
this, message.utf8().data()); | |
| 129 CString utf8 = message.utf8(StrictUTF8ConversionReplacingUnpairedSurrogatesW
ithFFFD); | |
| 130 enqueueTextFrame(utf8); | |
| 131 processOutgoingFrameQueue(); | |
| 132 } | |
| 133 | |
| 134 void MainThreadWebSocketChannel::send(const ArrayBuffer& binaryData, unsigned by
teOffset, unsigned byteLength) | |
| 135 { | |
| 136 WTF_LOG(Network, "MainThreadWebSocketChannel %p send() Sending ArrayBuffer %
p byteOffset=%u byteLength=%u", this, &binaryData, byteOffset, byteLength); | |
| 137 enqueueRawFrame(WebSocketFrame::OpCodeBinary, static_cast<const char*>(binar
yData.data()) + byteOffset, byteLength); | |
| 138 processOutgoingFrameQueue(); | |
| 139 } | |
| 140 | |
| 141 void MainThreadWebSocketChannel::send(PassRefPtr<BlobDataHandle> binaryData) | |
| 142 { | |
| 143 WTF_LOG(Network, "MainThreadWebSocketChannel %p send() Sending Blob '%s'", t
his, binaryData->uuid().utf8().data()); | |
| 144 enqueueBlobFrame(WebSocketFrame::OpCodeBinary, binaryData); | |
| 145 processOutgoingFrameQueue(); | |
| 146 } | |
| 147 | |
| 148 void MainThreadWebSocketChannel::send(PassOwnPtr<Vector<char> > data) | |
| 149 { | |
| 150 WTF_LOG(Network, "MainThreadWebSocketChannel %p send() Sending Vector %p", t
his, data.get()); | |
| 151 enqueueVector(WebSocketFrame::OpCodeBinary, data); | |
| 152 processOutgoingFrameQueue(); | |
| 153 } | |
| 154 | |
| 155 void MainThreadWebSocketChannel::close(int code, const String& reason) | |
| 156 { | |
| 157 WTF_LOG(Network, "MainThreadWebSocketChannel %p close() code=%d reason='%s'"
, this, code, reason.utf8().data()); | |
| 158 ASSERT(!m_suspended); | |
| 159 if (!m_handle) | |
| 160 return; | |
| 161 startClosingHandshake(code, reason); | |
| 162 if (!m_closingTimer.isActive()) | |
| 163 m_closingTimer.startOneShot(2 * TCPMaximumSegmentLifetime, FROM_HERE); | |
| 164 } | |
| 165 | |
| 166 void MainThreadWebSocketChannel::clearDocument() | |
| 167 { | |
| 168 if (m_handshake) | |
| 169 m_handshake->clearDocument(); | |
| 170 m_document = nullptr; | |
| 171 } | |
| 172 | |
| 173 void MainThreadWebSocketChannel::disconnectHandle() | |
| 174 { | |
| 175 if (!m_handle) | |
| 176 return; | |
| 177 m_hasCalledDisconnectOnHandle = true; | |
| 178 m_handle->disconnect(); | |
| 179 } | |
| 180 | |
| 181 void MainThreadWebSocketChannel::callDidError() | |
| 182 { | |
| 183 if (!m_client || m_didSendErrorToClient) | |
| 184 return; | |
| 185 m_didSendErrorToClient = true; | |
| 186 m_client->didError(); | |
| 187 } | |
| 188 | |
| 189 void MainThreadWebSocketChannel::fail(const String& reason, MessageLevel level,
const String& sourceURL, unsigned lineNumber) | |
| 190 { | |
| 191 WTF_LOG(Network, "MainThreadWebSocketChannel %p fail() reason='%s'", this, r
eason.utf8().data()); | |
| 192 if (m_document) { | |
| 193 InspectorInstrumentation::didReceiveWebSocketFrameError(m_document, m_id
entifier, reason); | |
| 194 const String message = "WebSocket connection to '" + m_handshake->url().
elidedString() + "' failed: " + reason; | |
| 195 m_document->addConsoleMessage(ConsoleMessage::create(JSMessageSource, le
vel, message, sourceURL, lineNumber)); | |
| 196 } | |
| 197 // Hybi-10 specification explicitly states we must not continue to handle in
coming data | |
| 198 // once the WebSocket connection is failed (section 7.1.7). | |
| 199 m_shouldDiscardReceivedData = true; | |
| 200 if (!m_buffer.isEmpty()) | |
| 201 skipBuffer(m_buffer.size()); // Save memory. | |
| 202 m_deflateFramer.didFail(); | |
| 203 m_perMessageDeflate.didFail(); | |
| 204 m_hasContinuousFrame = false; | |
| 205 m_continuousFrameData.clear(); | |
| 206 | |
| 207 callDidError(); | |
| 208 | |
| 209 if (m_state != ChannelClosed) | |
| 210 disconnectHandle(); // Will call didCloseSocketStream(). | |
| 211 } | |
| 212 | |
| 213 void MainThreadWebSocketChannel::disconnect() | |
| 214 { | |
| 215 WTF_LOG(Network, "MainThreadWebSocketChannel %p disconnect()", this); | |
| 216 if (m_identifier && m_document) { | |
| 217 TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "We
bSocketDestroy", "data", InspectorWebSocketEvent::data(m_document, m_identifier)
); | |
| 218 TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline.stack"
), "CallStack", "stack", InspectorCallStackEvent::currentCallStack()); | |
| 219 // FIXME(361045): remove InspectorInstrumentation calls once DevTools Ti
meline migrates to tracing. | |
| 220 InspectorInstrumentation::didCloseWebSocket(m_document, m_identifier); | |
| 221 } | |
| 222 | |
| 223 clearDocument(); | |
| 224 | |
| 225 m_client = nullptr; | |
| 226 disconnectHandle(); | |
| 227 } | |
| 228 | |
| 229 void MainThreadWebSocketChannel::suspend() | |
| 230 { | |
| 231 m_suspended = true; | |
| 232 } | |
| 233 | |
| 234 void MainThreadWebSocketChannel::resume() | |
| 235 { | |
| 236 m_suspended = false; | |
| 237 if ((!m_buffer.isEmpty() || (m_state == ChannelClosed)) && m_client && !m_re
sumeTimer.isActive()) | |
| 238 m_resumeTimer.startOneShot(0, FROM_HERE); | |
| 239 } | |
| 240 | |
| 241 void MainThreadWebSocketChannel::didOpenSocketStream(SocketStreamHandle* handle) | |
| 242 { | |
| 243 WTF_LOG(Network, "MainThreadWebSocketChannel %p didOpenSocketStream()", this
); | |
| 244 ASSERT(handle == m_handle); | |
| 245 if (!m_document) | |
| 246 return; | |
| 247 if (m_identifier) { | |
| 248 TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "We
bSocketSendHandshakeRequest", "data", InspectorWebSocketEvent::data(m_document,
m_identifier)); | |
| 249 TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline.stack"
), "CallStack", "stack", InspectorCallStackEvent::currentCallStack()); | |
| 250 // FIXME(361045): remove InspectorInstrumentation calls once DevTools Ti
meline migrates to tracing. | |
| 251 InspectorInstrumentation::willSendWebSocketHandshakeRequest(m_document,
m_identifier, m_handshake->clientHandshakeRequest().get()); | |
| 252 } | |
| 253 CString handshakeMessage = m_handshake->clientHandshakeMessage(); | |
| 254 if (!handle->send(handshakeMessage.data(), handshakeMessage.length())) | |
| 255 failAsError("Failed to send WebSocket handshake."); | |
| 256 } | |
| 257 | |
| 258 void MainThreadWebSocketChannel::didCloseSocketStream(SocketStreamHandle* handle
) | |
| 259 { | |
| 260 WTF_LOG(Network, "MainThreadWebSocketChannel %p didCloseSocketStream()", thi
s); | |
| 261 if (m_identifier && m_document) { | |
| 262 TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "We
bSocketDestroy", "data", InspectorWebSocketEvent::data(m_document, m_identifier)
); | |
| 263 TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline.stack"
), "CallStack", "stack", InspectorCallStackEvent::currentCallStack()); | |
| 264 // FIXME(361045): remove InspectorInstrumentation calls once DevTools Ti
meline migrates to tracing. | |
| 265 InspectorInstrumentation::didCloseWebSocket(m_document, m_identifier); | |
| 266 } | |
| 267 ASSERT_UNUSED(handle, handle == m_handle || !m_handle); | |
| 268 | |
| 269 // Show error message on JS console if this is unexpected connection close | |
| 270 // during opening handshake. | |
| 271 if (!m_hasCalledDisconnectOnHandle && m_handshake->mode() == WebSocketHandsh
ake::Incomplete && m_document) { | |
| 272 const String message = "WebSocket connection to '" + m_handshake->url().
elidedString() + "' failed: Connection closed before receiving a handshake respo
nse"; | |
| 273 m_document->addConsoleMessage(ConsoleMessage::create(JSMessageSource, Er
rorMessageLevel, message, m_sourceURLAtConstruction, m_lineNumberAtConstruction)
); | |
| 274 } | |
| 275 | |
| 276 m_state = ChannelClosed; | |
| 277 if (m_closingTimer.isActive()) | |
| 278 m_closingTimer.stop(); | |
| 279 if (m_outgoingFrameQueueStatus != OutgoingFrameQueueClosed) | |
| 280 abortOutgoingFrameQueue(); | |
| 281 if (m_handle) { | |
| 282 WebSocketChannelClient* client = m_client; | |
| 283 m_client = nullptr; | |
| 284 clearDocument(); | |
| 285 m_handle = nullptr; | |
| 286 if (client) | |
| 287 client->didClose(m_receivedClosingHandshake ? WebSocketChannelClient
::ClosingHandshakeComplete : WebSocketChannelClient::ClosingHandshakeIncomplete,
m_closeEventCode, m_closeEventReason); | |
| 288 } | |
| 289 deref(); | |
| 290 } | |
| 291 | |
| 292 void MainThreadWebSocketChannel::didReceiveSocketStreamData(SocketStreamHandle*
handle, const char* data, int len) | |
| 293 { | |
| 294 WTF_LOG(Network, "MainThreadWebSocketChannel %p didReceiveSocketStreamData()
Received %d bytes", this, len); | |
| 295 ASSERT(handle == m_handle); | |
| 296 if (!m_document) | |
| 297 return; | |
| 298 if (len <= 0) { | |
| 299 disconnectHandle(); | |
| 300 return; | |
| 301 } | |
| 302 if (!m_client) { | |
| 303 m_shouldDiscardReceivedData = true; | |
| 304 disconnectHandle(); | |
| 305 return; | |
| 306 } | |
| 307 if (m_shouldDiscardReceivedData) | |
| 308 return; | |
| 309 if (!appendToBuffer(data, len)) { | |
| 310 m_shouldDiscardReceivedData = true; | |
| 311 failAsError("Ran out of memory while receiving WebSocket data."); | |
| 312 return; | |
| 313 } | |
| 314 processBuffer(); | |
| 315 } | |
| 316 | |
| 317 void MainThreadWebSocketChannel::didConsumeBufferedAmount(SocketStreamHandle*, s
ize_t consumed) | |
| 318 { | |
| 319 if (m_framingOverheadQueue.isEmpty()) { | |
| 320 // Ignore the handshake consumption. | |
| 321 return; | |
| 322 } | |
| 323 if (!m_client || m_state == ChannelClosed) | |
| 324 return; | |
| 325 size_t remain = consumed; | |
| 326 while (remain > 0) { | |
| 327 ASSERT(!m_framingOverheadQueue.isEmpty()); | |
| 328 const FramingOverhead& frame = m_framingOverheadQueue.first(); | |
| 329 | |
| 330 ASSERT(m_numConsumedBytesInCurrentFrame <= frame.frameDataSize()); | |
| 331 size_t consumedInThisFrame = std::min(remain, frame.frameDataSize() - m_
numConsumedBytesInCurrentFrame); | |
| 332 remain -= consumedInThisFrame; | |
| 333 m_numConsumedBytesInCurrentFrame += consumedInThisFrame; | |
| 334 | |
| 335 if (m_numConsumedBytesInCurrentFrame == frame.frameDataSize()) { | |
| 336 if (m_client && WebSocketFrame::isNonControlOpCode(frame.opcode()))
{ | |
| 337 // FIXME: As |consumed| is the number of possibly compressed | |
| 338 // bytes, we can't determine the number of consumed original | |
| 339 // bytes in the middle of a frame. | |
| 340 m_client->didConsumeBufferedAmount(frame.originalPayloadLength()
); | |
| 341 } | |
| 342 m_framingOverheadQueue.takeFirst(); | |
| 343 m_numConsumedBytesInCurrentFrame = 0; | |
| 344 } | |
| 345 } | |
| 346 } | |
| 347 | |
| 348 void MainThreadWebSocketChannel::didFailSocketStream(SocketStreamHandle* handle,
const SocketStreamError& error) | |
| 349 { | |
| 350 WTF_LOG(Network, "MainThreadWebSocketChannel %p didFailSocketStream()", this
); | |
| 351 ASSERT_UNUSED(handle, handle == m_handle || !m_handle); | |
| 352 m_shouldDiscardReceivedData = true; | |
| 353 String message; | |
| 354 if (error.isNull()) | |
| 355 message = "WebSocket network error"; | |
| 356 else if (error.localizedDescription().isNull()) | |
| 357 message = "WebSocket network error: error code " + String::number(error.
errorCode()); | |
| 358 else | |
| 359 message = "WebSocket network error: error code " + String::number(error.
errorCode()) + ", " + error.localizedDescription(); | |
| 360 String failingURL = error.failingURL(); | |
| 361 ASSERT(failingURL.isNull() || m_handshake->url().string() == failingURL); | |
| 362 if (failingURL.isNull()) | |
| 363 failingURL = m_handshake->url().string(); | |
| 364 WTF_LOG(Network, "Error Message: '%s', FailURL: '%s'", message.utf8().data()
, failingURL.utf8().data()); | |
| 365 | |
| 366 if (m_state != ChannelClosing && m_state != ChannelClosed) | |
| 367 callDidError(); | |
| 368 | |
| 369 if (m_state != ChannelClosed) | |
| 370 disconnectHandle(); | |
| 371 } | |
| 372 | |
| 373 void MainThreadWebSocketChannel::didStartLoading() | |
| 374 { | |
| 375 WTF_LOG(Network, "MainThreadWebSocketChannel %p didStartLoading()", this); | |
| 376 ASSERT(m_blobLoader); | |
| 377 ASSERT(m_blobLoaderStatus == BlobLoaderStarted); | |
| 378 } | |
| 379 | |
| 380 void MainThreadWebSocketChannel::didReceiveData() | |
| 381 { | |
| 382 WTF_LOG(Network, "MainThreadWebSocketChannel %p didReceiveData()", this); | |
| 383 ASSERT(m_blobLoader); | |
| 384 ASSERT(m_blobLoaderStatus == BlobLoaderStarted); | |
| 385 } | |
| 386 | |
| 387 void MainThreadWebSocketChannel::didFinishLoading() | |
| 388 { | |
| 389 WTF_LOG(Network, "MainThreadWebSocketChannel %p didFinishLoading()", this); | |
| 390 ASSERT(m_blobLoader); | |
| 391 ASSERT(m_blobLoaderStatus == BlobLoaderStarted); | |
| 392 m_blobLoaderStatus = BlobLoaderFinished; | |
| 393 processOutgoingFrameQueue(); | |
| 394 deref(); | |
| 395 } | |
| 396 | |
| 397 void MainThreadWebSocketChannel::didFail(FileError::ErrorCode errorCode) | |
| 398 { | |
| 399 WTF_LOG(Network, "MainThreadWebSocketChannel %p didFail() errorCode=%d", thi
s, errorCode); | |
| 400 ASSERT(m_blobLoader); | |
| 401 ASSERT(m_blobLoaderStatus == BlobLoaderStarted); | |
| 402 m_blobLoader.clear(); | |
| 403 m_blobLoaderStatus = BlobLoaderFailed; | |
| 404 failAsError("Failed to load Blob: error code = " + String::number(errorCode)
); // FIXME: Generate human-friendly reason message. | |
| 405 deref(); | |
| 406 } | |
| 407 | |
| 408 bool MainThreadWebSocketChannel::appendToBuffer(const char* data, size_t len) | |
| 409 { | |
| 410 size_t newBufferSize = m_buffer.size() + len; | |
| 411 if (newBufferSize < m_buffer.size()) { | |
| 412 WTF_LOG(Network, "MainThreadWebSocketChannel %p appendToBuffer() Buffer
overflow (%lu bytes already in receive buffer and appending %lu bytes)", this, s
tatic_cast<unsigned long>(m_buffer.size()), static_cast<unsigned long>(len)); | |
| 413 return false; | |
| 414 } | |
| 415 m_buffer.append(data, len); | |
| 416 return true; | |
| 417 } | |
| 418 | |
| 419 void MainThreadWebSocketChannel::skipBuffer(size_t len) | |
| 420 { | |
| 421 ASSERT_WITH_SECURITY_IMPLICATION(len <= m_buffer.size()); | |
| 422 memmove(m_buffer.data(), m_buffer.data() + len, m_buffer.size() - len); | |
| 423 m_buffer.resize(m_buffer.size() - len); | |
| 424 } | |
| 425 | |
| 426 void MainThreadWebSocketChannel::processBuffer() | |
| 427 { | |
| 428 while (!m_suspended && m_client && !m_buffer.isEmpty()) { | |
| 429 if (!processOneItemFromBuffer()) | |
| 430 break; | |
| 431 } | |
| 432 } | |
| 433 | |
| 434 bool MainThreadWebSocketChannel::processOneItemFromBuffer() | |
| 435 { | |
| 436 ASSERT(!m_suspended); | |
| 437 ASSERT(m_client); | |
| 438 ASSERT(!m_buffer.isEmpty()); | |
| 439 WTF_LOG(Network, "MainThreadWebSocketChannel %p processBuffer() Receive buff
er has %lu bytes", this, static_cast<unsigned long>(m_buffer.size())); | |
| 440 | |
| 441 if (m_shouldDiscardReceivedData) | |
| 442 return false; | |
| 443 | |
| 444 if (m_receivedClosingHandshake) { | |
| 445 skipBuffer(m_buffer.size()); | |
| 446 return false; | |
| 447 } | |
| 448 | |
| 449 if (m_handshake->mode() == WebSocketHandshake::Incomplete) { | |
| 450 int headerLength = m_handshake->readServerHandshake(m_buffer.data(), m_b
uffer.size()); | |
| 451 if (headerLength <= 0) | |
| 452 return false; | |
| 453 if (m_handshake->mode() == WebSocketHandshake::Connected) { | |
| 454 if (m_identifier) { | |
| 455 TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timelin
e"), "WebSocketReceiveHandshakeResponse", "data", InspectorWebSocketEvent::data(
m_document, m_identifier)); | |
| 456 // FIXME(361045): remove InspectorInstrumentation calls once Dev
Tools Timeline migrates to tracing. | |
| 457 InspectorInstrumentation::didReceiveWebSocketHandshakeResponse(m
_document, m_identifier, 0, &m_handshake->serverHandshakeResponse()); | |
| 458 } | |
| 459 | |
| 460 if (m_deflateFramer.enabled() && m_document) { | |
| 461 const String message = "WebSocket extension \"x-webkit-deflate-f
rame\" is deprecated"; | |
| 462 m_document->addConsoleMessage(ConsoleMessage::create(JSMessageSo
urce, WarningMessageLevel, message, m_sourceURLAtConstruction, m_lineNumberAtCon
struction)); | |
| 463 } | |
| 464 | |
| 465 WTF_LOG(Network, "MainThreadWebSocketChannel %p Connected", this); | |
| 466 skipBuffer(headerLength); | |
| 467 String subprotocol = m_handshake->serverWebSocketProtocol(); | |
| 468 String extensions = m_handshake->acceptedExtensions(); | |
| 469 m_client->didConnect(subprotocol.isNull() ? "" : subprotocol, extens
ions.isNull() ? "" : extensions); | |
| 470 WTF_LOG(Network, "MainThreadWebSocketChannel %p %lu bytes remaining
in m_buffer", this, static_cast<unsigned long>(m_buffer.size())); | |
| 471 return !m_buffer.isEmpty(); | |
| 472 } | |
| 473 ASSERT(m_handshake->mode() == WebSocketHandshake::Failed); | |
| 474 WTF_LOG(Network, "MainThreadWebSocketChannel %p Connection failed", this
); | |
| 475 skipBuffer(headerLength); | |
| 476 m_shouldDiscardReceivedData = true; | |
| 477 failAsError(m_handshake->failureReason()); | |
| 478 return false; | |
| 479 } | |
| 480 if (m_handshake->mode() != WebSocketHandshake::Connected) | |
| 481 return false; | |
| 482 | |
| 483 return processFrame(); | |
| 484 } | |
| 485 | |
| 486 void MainThreadWebSocketChannel::resumeTimerFired(Timer<MainThreadWebSocketChann
el>* timer) | |
| 487 { | |
| 488 ASSERT_UNUSED(timer, timer == &m_resumeTimer); | |
| 489 | |
| 490 processBuffer(); | |
| 491 if (!m_suspended && m_client && (m_state == ChannelClosed) && m_handle) | |
| 492 didCloseSocketStream(m_handle.get()); | |
| 493 } | |
| 494 | |
| 495 void MainThreadWebSocketChannel::startClosingHandshake(int code, const String& r
eason) | |
| 496 { | |
| 497 WTF_LOG(Network, "MainThreadWebSocketChannel %p startClosingHandshake() code
=%d m_state=%d m_receivedClosingHandshake=%d", this, code, m_state, m_receivedCl
osingHandshake); | |
| 498 if (m_state == ChannelClosing || m_state == ChannelClosed) | |
| 499 return; | |
| 500 ASSERT(m_handle); | |
| 501 | |
| 502 Vector<char> buf; | |
| 503 if (!m_receivedClosingHandshake && code != CloseEventCodeNotSpecified) { | |
| 504 unsigned char highByte = code >> 8; | |
| 505 unsigned char lowByte = code; | |
| 506 buf.append(static_cast<char>(highByte)); | |
| 507 buf.append(static_cast<char>(lowByte)); | |
| 508 buf.append(reason.utf8().data(), reason.utf8().length()); | |
| 509 } | |
| 510 enqueueRawFrame(WebSocketFrame::OpCodeClose, buf.data(), buf.size()); | |
| 511 processOutgoingFrameQueue(); | |
| 512 | |
| 513 m_state = ChannelClosing; | |
| 514 if (m_client) | |
| 515 m_client->didStartClosingHandshake(); | |
| 516 } | |
| 517 | |
| 518 void MainThreadWebSocketChannel::closingTimerFired(Timer<MainThreadWebSocketChan
nel>* timer) | |
| 519 { | |
| 520 WTF_LOG(Network, "MainThreadWebSocketChannel %p closingTimerFired()", this); | |
| 521 ASSERT_UNUSED(timer, &m_closingTimer == timer); | |
| 522 disconnectHandle(); | |
| 523 } | |
| 524 | |
| 525 | |
| 526 bool MainThreadWebSocketChannel::processFrame() | |
| 527 { | |
| 528 ASSERT(!m_buffer.isEmpty()); | |
| 529 | |
| 530 WebSocketFrame frame; | |
| 531 const char* frameEnd; | |
| 532 String errorString; | |
| 533 WebSocketFrame::ParseFrameResult result = WebSocketFrame::parseFrame(m_buffe
r.data(), m_buffer.size(), frame, frameEnd, errorString); | |
| 534 if (result == WebSocketFrame::FrameIncomplete) | |
| 535 return false; | |
| 536 if (result == WebSocketFrame::FrameError) { | |
| 537 failAsError(errorString); | |
| 538 return false; | |
| 539 } | |
| 540 | |
| 541 ASSERT(m_buffer.data() < frameEnd); | |
| 542 ASSERT(frameEnd <= m_buffer.data() + m_buffer.size()); | |
| 543 | |
| 544 OwnPtr<InflateResultHolder> inflateResult = m_deflateFramer.inflate(frame); | |
| 545 if (!inflateResult->succeeded()) { | |
| 546 failAsError(inflateResult->failureReason()); | |
| 547 return false; | |
| 548 } | |
| 549 if (!m_perMessageDeflate.inflate(frame)) { | |
| 550 failAsError(m_perMessageDeflate.failureReason()); | |
| 551 return false; | |
| 552 } | |
| 553 | |
| 554 // Validate the frame data. | |
| 555 if (WebSocketFrame::isReservedOpCode(frame.opCode)) { | |
| 556 failAsError("Unrecognized frame opcode: " + String::number(frame.opCode)
); | |
| 557 return false; | |
| 558 } | |
| 559 | |
| 560 if (frame.compress || frame.reserved2 || frame.reserved3) { | |
| 561 failAsError("One or more reserved bits are on: reserved1 = " + String::n
umber(frame.compress) + ", reserved2 = " + String::number(frame.reserved2) + ",
reserved3 = " + String::number(frame.reserved3)); | |
| 562 return false; | |
| 563 } | |
| 564 | |
| 565 if (frame.masked) { | |
| 566 failAsError("A server must not mask any frames that it sends to the clie
nt."); | |
| 567 return false; | |
| 568 } | |
| 569 | |
| 570 // All control frames must not be fragmented. | |
| 571 if (WebSocketFrame::isControlOpCode(frame.opCode) && !frame.final) { | |
| 572 failAsError("Received fragmented control frame: opcode = " + String::num
ber(frame.opCode)); | |
| 573 return false; | |
| 574 } | |
| 575 | |
| 576 // All control frames must have a payload of 125 bytes or less, which means
the frame must not contain | |
| 577 // the "extended payload length" field. | |
| 578 if (WebSocketFrame::isControlOpCode(frame.opCode) && WebSocketFrame::needsEx
tendedLengthField(frame.payloadLength)) { | |
| 579 failAsError("Received control frame having too long payload: " + String:
:number(frame.payloadLength) + " bytes"); | |
| 580 return false; | |
| 581 } | |
| 582 | |
| 583 // A new data frame is received before the previous continuous frame finishe
s. | |
| 584 // Note that control frames are allowed to come in the middle of continuous
frames. | |
| 585 if (m_hasContinuousFrame && frame.opCode != WebSocketFrame::OpCodeContinuati
on && !WebSocketFrame::isControlOpCode(frame.opCode)) { | |
| 586 failAsError("Received start of new message but previous message is unfin
ished."); | |
| 587 return false; | |
| 588 } | |
| 589 | |
| 590 InspectorInstrumentation::didReceiveWebSocketFrame(m_document, m_identifier,
frame.opCode, frame.masked, frame.payload, frame.payloadLength); | |
| 591 | |
| 592 switch (frame.opCode) { | |
| 593 case WebSocketFrame::OpCodeContinuation: | |
| 594 // An unexpected continuation frame is received without any leading fram
e. | |
| 595 if (!m_hasContinuousFrame) { | |
| 596 failAsError("Received unexpected continuation frame."); | |
| 597 return false; | |
| 598 } | |
| 599 m_continuousFrameData.append(frame.payload, frame.payloadLength); | |
| 600 skipBuffer(frameEnd - m_buffer.data()); | |
| 601 if (frame.final) { | |
| 602 // onmessage handler may eventually call the other methods of this c
hannel, | |
| 603 // so we should pretend that we have finished to read this frame and | |
| 604 // make sure that the member variables are in a consistent state bef
ore | |
| 605 // the handler is invoked. | |
| 606 // Vector<char>::swap() is used here to clear m_continuousFrameData. | |
| 607 OwnPtr<Vector<char> > continuousFrameData = adoptPtr(new Vector<char
>); | |
| 608 m_continuousFrameData.swap(*continuousFrameData); | |
| 609 m_hasContinuousFrame = false; | |
| 610 if (m_continuousFrameOpCode == WebSocketFrame::OpCodeText) { | |
| 611 String message; | |
| 612 if (continuousFrameData->size()) | |
| 613 message = String::fromUTF8(continuousFrameData->data(), cont
inuousFrameData->size()); | |
| 614 else | |
| 615 message = ""; | |
| 616 if (message.isNull()) | |
| 617 failAsError("Could not decode a text frame as UTF-8."); | |
| 618 else | |
| 619 m_client->didReceiveTextMessage(message); | |
| 620 } else if (m_continuousFrameOpCode == WebSocketFrame::OpCodeBinary)
{ | |
| 621 m_client->didReceiveBinaryMessage(continuousFrameData.release())
; | |
| 622 } | |
| 623 } | |
| 624 break; | |
| 625 | |
| 626 case WebSocketFrame::OpCodeText: | |
| 627 if (frame.final) { | |
| 628 String message; | |
| 629 if (frame.payloadLength) | |
| 630 message = String::fromUTF8(frame.payload, frame.payloadLength); | |
| 631 else | |
| 632 message = ""; | |
| 633 skipBuffer(frameEnd - m_buffer.data()); | |
| 634 if (message.isNull()) | |
| 635 failAsError("Could not decode a text frame as UTF-8."); | |
| 636 else | |
| 637 m_client->didReceiveTextMessage(message); | |
| 638 } else { | |
| 639 m_hasContinuousFrame = true; | |
| 640 m_continuousFrameOpCode = WebSocketFrame::OpCodeText; | |
| 641 ASSERT(m_continuousFrameData.isEmpty()); | |
| 642 m_continuousFrameData.append(frame.payload, frame.payloadLength); | |
| 643 skipBuffer(frameEnd - m_buffer.data()); | |
| 644 } | |
| 645 break; | |
| 646 | |
| 647 case WebSocketFrame::OpCodeBinary: | |
| 648 if (frame.final) { | |
| 649 OwnPtr<Vector<char> > binaryData = adoptPtr(new Vector<char>(frame.p
ayloadLength)); | |
| 650 memcpy(binaryData->data(), frame.payload, frame.payloadLength); | |
| 651 skipBuffer(frameEnd - m_buffer.data()); | |
| 652 m_client->didReceiveBinaryMessage(binaryData.release()); | |
| 653 } else { | |
| 654 m_hasContinuousFrame = true; | |
| 655 m_continuousFrameOpCode = WebSocketFrame::OpCodeBinary; | |
| 656 ASSERT(m_continuousFrameData.isEmpty()); | |
| 657 m_continuousFrameData.append(frame.payload, frame.payloadLength); | |
| 658 skipBuffer(frameEnd - m_buffer.data()); | |
| 659 } | |
| 660 break; | |
| 661 | |
| 662 case WebSocketFrame::OpCodeClose: | |
| 663 if (!frame.payloadLength) { | |
| 664 m_closeEventCode = CloseEventCodeNoStatusRcvd; | |
| 665 } else if (frame.payloadLength == 1) { | |
| 666 m_closeEventCode = CloseEventCodeAbnormalClosure; | |
| 667 failAsError("Received a broken close frame containing an invalid siz
e body."); | |
| 668 return false; | |
| 669 } else { | |
| 670 unsigned char highByte = static_cast<unsigned char>(frame.payload[0]
); | |
| 671 unsigned char lowByte = static_cast<unsigned char>(frame.payload[1])
; | |
| 672 m_closeEventCode = highByte << 8 | lowByte; | |
| 673 if (m_closeEventCode == CloseEventCodeNoStatusRcvd || m_closeEventCo
de == CloseEventCodeAbnormalClosure || m_closeEventCode == CloseEventCodeTLSHand
shake) { | |
| 674 m_closeEventCode = CloseEventCodeAbnormalClosure; | |
| 675 failAsError("Received a broken close frame containing a reserved
status code."); | |
| 676 return false; | |
| 677 } | |
| 678 } | |
| 679 if (frame.payloadLength >= 3) | |
| 680 m_closeEventReason = String::fromUTF8(&frame.payload[2], frame.paylo
adLength - 2); | |
| 681 else | |
| 682 m_closeEventReason = ""; | |
| 683 skipBuffer(frameEnd - m_buffer.data()); | |
| 684 m_receivedClosingHandshake = true; | |
| 685 startClosingHandshake(m_closeEventCode, m_closeEventReason); | |
| 686 m_outgoingFrameQueueStatus = OutgoingFrameQueueClosing; | |
| 687 processOutgoingFrameQueue(); | |
| 688 break; | |
| 689 | |
| 690 case WebSocketFrame::OpCodePing: | |
| 691 enqueueRawFrame(WebSocketFrame::OpCodePong, frame.payload, frame.payload
Length); | |
| 692 skipBuffer(frameEnd - m_buffer.data()); | |
| 693 processOutgoingFrameQueue(); | |
| 694 break; | |
| 695 | |
| 696 case WebSocketFrame::OpCodePong: | |
| 697 // A server may send a pong in response to our ping, or an unsolicited p
ong which is not associated with | |
| 698 // any specific ping. Either way, there's nothing to do on receipt of po
ng. | |
| 699 skipBuffer(frameEnd - m_buffer.data()); | |
| 700 break; | |
| 701 | |
| 702 default: | |
| 703 ASSERT_NOT_REACHED(); | |
| 704 skipBuffer(frameEnd - m_buffer.data()); | |
| 705 break; | |
| 706 } | |
| 707 | |
| 708 m_perMessageDeflate.resetInflateBuffer(); | |
| 709 return !m_buffer.isEmpty(); | |
| 710 } | |
| 711 | |
| 712 void MainThreadWebSocketChannel::enqueueTextFrame(const CString& string) | |
| 713 { | |
| 714 ASSERT(m_outgoingFrameQueueStatus == OutgoingFrameQueueOpen); | |
| 715 | |
| 716 OwnPtr<QueuedFrame> frame = adoptPtr(new QueuedFrame); | |
| 717 frame->opCode = WebSocketFrame::OpCodeText; | |
| 718 frame->frameType = QueuedFrameTypeString; | |
| 719 frame->stringData = string; | |
| 720 m_outgoingFrameQueue.append(frame.release()); | |
| 721 } | |
| 722 | |
| 723 void MainThreadWebSocketChannel::enqueueRawFrame(WebSocketFrame::OpCode opCode,
const char* data, size_t dataLength) | |
| 724 { | |
| 725 ASSERT(m_outgoingFrameQueueStatus == OutgoingFrameQueueOpen); | |
| 726 | |
| 727 OwnPtr<QueuedFrame> frame = adoptPtr(new QueuedFrame); | |
| 728 frame->opCode = opCode; | |
| 729 frame->frameType = QueuedFrameTypeVector; | |
| 730 frame->vectorData.resize(dataLength); | |
| 731 if (dataLength) | |
| 732 memcpy(frame->vectorData.data(), data, dataLength); | |
| 733 m_outgoingFrameQueue.append(frame.release()); | |
| 734 } | |
| 735 | |
| 736 void MainThreadWebSocketChannel::enqueueVector(WebSocketFrame::OpCode opCode, Pa
ssOwnPtr<Vector<char> > data) | |
| 737 { | |
| 738 ASSERT(m_outgoingFrameQueueStatus == OutgoingFrameQueueOpen); | |
| 739 | |
| 740 OwnPtr<QueuedFrame> frame = adoptPtr(new QueuedFrame); | |
| 741 frame->opCode = opCode; | |
| 742 frame->frameType = QueuedFrameTypeVector; | |
| 743 frame->vectorData.swap(*data); | |
| 744 m_outgoingFrameQueue.append(frame.release()); | |
| 745 } | |
| 746 | |
| 747 void MainThreadWebSocketChannel::enqueueBlobFrame(WebSocketFrame::OpCode opCode,
PassRefPtr<BlobDataHandle> blobData) | |
| 748 { | |
| 749 ASSERT(m_outgoingFrameQueueStatus == OutgoingFrameQueueOpen); | |
| 750 | |
| 751 OwnPtr<QueuedFrame> frame = adoptPtr(new QueuedFrame); | |
| 752 frame->opCode = opCode; | |
| 753 frame->frameType = QueuedFrameTypeBlob; | |
| 754 frame->blobData = blobData; | |
| 755 m_outgoingFrameQueue.append(frame.release()); | |
| 756 } | |
| 757 | |
| 758 void MainThreadWebSocketChannel::processOutgoingFrameQueue() | |
| 759 { | |
| 760 if (m_outgoingFrameQueueStatus == OutgoingFrameQueueClosed) | |
| 761 return; | |
| 762 | |
| 763 while (!m_outgoingFrameQueue.isEmpty()) { | |
| 764 OwnPtr<QueuedFrame> frame = m_outgoingFrameQueue.takeFirst(); | |
| 765 switch (frame->frameType) { | |
| 766 case QueuedFrameTypeString: { | |
| 767 if (!sendFrame(frame->opCode, frame->stringData.data(), frame->strin
gData.length())) | |
| 768 failAsError("Failed to send WebSocket frame."); | |
| 769 break; | |
| 770 } | |
| 771 | |
| 772 case QueuedFrameTypeVector: | |
| 773 if (!sendFrame(frame->opCode, frame->vectorData.data(), frame->vecto
rData.size())) | |
| 774 failAsError("Failed to send WebSocket frame."); | |
| 775 break; | |
| 776 | |
| 777 case QueuedFrameTypeBlob: { | |
| 778 switch (m_blobLoaderStatus) { | |
| 779 case BlobLoaderNotStarted: | |
| 780 ref(); // Will be derefed after didFinishLoading() or didFail(). | |
| 781 ASSERT(!m_blobLoader); | |
| 782 m_blobLoader = adoptPtr(new FileReaderLoader(FileReaderLoader::R
eadAsArrayBuffer, this)); | |
| 783 m_blobLoaderStatus = BlobLoaderStarted; | |
| 784 m_blobLoader->start(m_document, frame->blobData); | |
| 785 m_outgoingFrameQueue.prepend(frame.release()); | |
| 786 return; | |
| 787 | |
| 788 case BlobLoaderStarted: | |
| 789 case BlobLoaderFailed: | |
| 790 m_outgoingFrameQueue.prepend(frame.release()); | |
| 791 return; | |
| 792 | |
| 793 case BlobLoaderFinished: { | |
| 794 RefPtr<ArrayBuffer> result = m_blobLoader->arrayBufferResult(); | |
| 795 m_blobLoader.clear(); | |
| 796 m_blobLoaderStatus = BlobLoaderNotStarted; | |
| 797 if (!sendFrame(frame->opCode, static_cast<const char*>(result->d
ata()), result->byteLength())) | |
| 798 failAsError("Failed to send WebSocket frame."); | |
| 799 break; | |
| 800 } | |
| 801 } | |
| 802 break; | |
| 803 } | |
| 804 | |
| 805 default: | |
| 806 ASSERT_NOT_REACHED(); | |
| 807 break; | |
| 808 } | |
| 809 } | |
| 810 | |
| 811 ASSERT(m_outgoingFrameQueue.isEmpty()); | |
| 812 if (m_outgoingFrameQueueStatus == OutgoingFrameQueueClosing) { | |
| 813 m_outgoingFrameQueueStatus = OutgoingFrameQueueClosed; | |
| 814 m_handle->close(); | |
| 815 } | |
| 816 } | |
| 817 | |
| 818 void MainThreadWebSocketChannel::abortOutgoingFrameQueue() | |
| 819 { | |
| 820 m_outgoingFrameQueue.clear(); | |
| 821 m_outgoingFrameQueueStatus = OutgoingFrameQueueClosed; | |
| 822 if (m_blobLoaderStatus == BlobLoaderStarted) { | |
| 823 m_blobLoader->cancel(); | |
| 824 didFail(FileError::ABORT_ERR); | |
| 825 } | |
| 826 } | |
| 827 | |
| 828 bool MainThreadWebSocketChannel::sendFrame(WebSocketFrame::OpCode opCode, const
char* data, size_t dataLength) | |
| 829 { | |
| 830 ASSERT(m_handle); | |
| 831 ASSERT(!m_suspended); | |
| 832 | |
| 833 WebSocketFrame frame(opCode, data, dataLength, WebSocketFrame::Final | WebSo
cketFrame::Masked); | |
| 834 InspectorInstrumentation::didSendWebSocketFrame(m_document, m_identifier, fr
ame.opCode, frame.masked, frame.payload, frame.payloadLength); | |
| 835 | |
| 836 OwnPtr<DeflateResultHolder> deflateResult = m_deflateFramer.deflate(frame); | |
| 837 if (!deflateResult->succeeded()) { | |
| 838 failAsError(deflateResult->failureReason()); | |
| 839 return false; | |
| 840 } | |
| 841 | |
| 842 if (!m_perMessageDeflate.deflate(frame)) { | |
| 843 failAsError(m_perMessageDeflate.failureReason()); | |
| 844 return false; | |
| 845 } | |
| 846 | |
| 847 Vector<char> frameData; | |
| 848 frame.makeFrameData(frameData); | |
| 849 m_framingOverheadQueue.append(FramingOverhead(opCode, frameData.size(), data
Length)); | |
| 850 | |
| 851 m_perMessageDeflate.resetDeflateBuffer(); | |
| 852 return m_handle->send(frameData.data(), frameData.size()); | |
| 853 } | |
| 854 | |
| 855 void MainThreadWebSocketChannel::trace(Visitor* visitor) | |
| 856 { | |
| 857 visitor->trace(m_document); | |
| 858 visitor->trace(m_client); | |
| 859 visitor->trace(m_handshake); | |
| 860 visitor->trace(m_handle); | |
| 861 WebSocketChannel::trace(visitor); | |
| 862 SocketStreamHandleClient::trace(visitor); | |
| 863 } | |
| 864 | |
| 865 } // namespace blink | |
| OLD | NEW |