b18f27de13a7d86aa07d9152fead09fdc6b0c4cb
[WebKit-https.git] / Source / WebCore / Modules / websockets / WebSocket.cpp
1 /*
2  * Copyright (C) 2011 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
33 #if ENABLE(WEB_SOCKETS)
34
35 #include "WebSocket.h"
36
37 #include "Blob.h"
38 #include "CloseEvent.h"
39 #include "ContentSecurityPolicy.h"
40 #include "DOMWindow.h"
41 #include "Document.h"
42 #include "Event.h"
43 #include "EventListener.h"
44 #include "EventNames.h"
45 #include "ExceptionCode.h"
46 #include "Frame.h"
47 #include "Logging.h"
48 #include "MessageEvent.h"
49 #include "ScriptController.h"
50 #include "ScriptExecutionContext.h"
51 #include "SecurityOrigin.h"
52 #include "ThreadableWebSocketChannel.h"
53 #include "WebSocketChannel.h"
54 #include <inspector/ScriptCallStack.h>
55 #include <runtime/ArrayBuffer.h>
56 #include <runtime/ArrayBufferView.h>
57 #include <wtf/HashSet.h>
58 #include <wtf/RunLoop.h>
59 #include <wtf/StdLibExtras.h>
60 #include <wtf/text/CString.h>
61 #include <wtf/text/StringBuilder.h>
62 #include <wtf/text/WTFString.h>
63
64 namespace WebCore {
65
66 const size_t maxReasonSizeInBytes = 123;
67
68 static inline bool isValidProtocolCharacter(UChar character)
69 {
70     // Hybi-10 says "(Subprotocol string must consist of) characters in the range U+0021 to U+007E not including
71     // separator characters as defined in [RFC2616]."
72     const UChar minimumProtocolCharacter = '!'; // U+0021.
73     const UChar maximumProtocolCharacter = '~'; // U+007E.
74     return character >= minimumProtocolCharacter && character <= maximumProtocolCharacter
75         && character != '"' && character != '(' && character != ')' && character != ',' && character != '/'
76         && !(character >= ':' && character <= '@') // U+003A - U+0040 (':', ';', '<', '=', '>', '?', '@').
77         && !(character >= '[' && character <= ']') // U+005B - U+005D ('[', '\\', ']').
78         && character != '{' && character != '}';
79 }
80
81 static bool isValidProtocolString(const String& protocol)
82 {
83     if (protocol.isEmpty())
84         return false;
85     for (size_t i = 0; i < protocol.length(); ++i) {
86         if (!isValidProtocolCharacter(protocol[i]))
87             return false;
88     }
89     return true;
90 }
91
92 static String encodeProtocolString(const String& protocol)
93 {
94     StringBuilder builder;
95     for (size_t i = 0; i < protocol.length(); i++) {
96         if (protocol[i] < 0x20 || protocol[i] > 0x7E)
97             builder.append(String::format("\\u%04X", protocol[i]));
98         else if (protocol[i] == 0x5c)
99             builder.appendLiteral("\\\\");
100         else
101             builder.append(protocol[i]);
102     }
103     return builder.toString();
104 }
105
106 static String joinStrings(const Vector<String>& strings, const char* separator)
107 {
108     StringBuilder builder;
109     for (size_t i = 0; i < strings.size(); ++i) {
110         if (i)
111             builder.append(separator);
112         builder.append(strings[i]);
113     }
114     return builder.toString();
115 }
116
117 static unsigned long saturateAdd(unsigned long a, unsigned long b)
118 {
119     if (std::numeric_limits<unsigned long>::max() - a < b)
120         return std::numeric_limits<unsigned long>::max();
121     return a + b;
122 }
123
124 static bool webSocketsAvailable = true;
125
126 void WebSocket::setIsAvailable(bool available)
127 {
128     webSocketsAvailable = available;
129 }
130
131 bool WebSocket::isAvailable()
132 {
133     return webSocketsAvailable;
134 }
135
136 const char* WebSocket::subProtocolSeperator()
137 {
138     return ", ";
139 }
140
141 WebSocket::WebSocket(ScriptExecutionContext& context)
142     : ActiveDOMObject(&context)
143     , m_state(CONNECTING)
144     , m_bufferedAmount(0)
145     , m_bufferedAmountAfterClose(0)
146     , m_binaryType(BinaryTypeBlob)
147     , m_subprotocol("")
148     , m_extensions("")
149     , m_resumeTimer(*this, &WebSocket::resumeTimerFired)
150 {
151 }
152
153 WebSocket::~WebSocket()
154 {
155     if (m_channel)
156         m_channel->disconnect();
157 }
158
159 Ref<WebSocket> WebSocket::create(ScriptExecutionContext& context)
160 {
161     Ref<WebSocket> webSocket(adoptRef(*new WebSocket(context)));
162     webSocket->suspendIfNeeded();
163     return webSocket;
164 }
165
166 RefPtr<WebSocket> WebSocket::create(ScriptExecutionContext& context, const String& url, ExceptionCode& ec)
167 {
168     Vector<String> protocols;
169     return WebSocket::create(context, url, protocols, ec);
170 }
171
172 RefPtr<WebSocket> WebSocket::create(ScriptExecutionContext& context, const String& url, const Vector<String>& protocols, ExceptionCode& ec)
173 {
174     if (url.isNull()) {
175         ec = SYNTAX_ERR;
176         return nullptr;
177     }
178
179     RefPtr<WebSocket> webSocket(adoptRef(*new WebSocket(context)));
180     webSocket->suspendIfNeeded();
181
182     webSocket->connect(context.completeURL(url), protocols, ec);
183     if (ec)
184         return nullptr;
185
186     return webSocket;
187 }
188
189 RefPtr<WebSocket> WebSocket::create(ScriptExecutionContext& context, const String& url, const String& protocol, ExceptionCode& ec)
190 {
191     Vector<String> protocols;
192     protocols.append(protocol);
193     return WebSocket::create(context, url, protocols, ec);
194 }
195
196 void WebSocket::connect(const String& url, ExceptionCode& ec)
197 {
198     Vector<String> protocols;
199     connect(url, protocols, ec);
200 }
201
202 void WebSocket::connect(const String& url, const String& protocol, ExceptionCode& ec)
203 {
204     Vector<String> protocols;
205     protocols.append(protocol);
206     connect(url, protocols, ec);
207 }
208
209 void WebSocket::connect(const String& url, const Vector<String>& protocols, ExceptionCode& ec)
210 {
211     LOG(Network, "WebSocket %p connect() url='%s'", this, url.utf8().data());
212     m_url = URL(URL(), url);
213
214     if (!m_url.isValid()) {
215         scriptExecutionContext()->addConsoleMessage(MessageSource::JS, MessageLevel::Error, "Invalid url for WebSocket " + m_url.stringCenterEllipsizedToLength());
216         m_state = CLOSED;
217         ec = SYNTAX_ERR;
218         return;
219     }
220
221     if (!m_url.protocolIs("ws") && !m_url.protocolIs("wss")) {
222         scriptExecutionContext()->addConsoleMessage(MessageSource::JS, MessageLevel::Error, "Wrong url scheme for WebSocket " + m_url.stringCenterEllipsizedToLength());
223         m_state = CLOSED;
224         ec = SYNTAX_ERR;
225         return;
226     }
227     if (m_url.hasFragmentIdentifier()) {
228         scriptExecutionContext()->addConsoleMessage(MessageSource::JS, MessageLevel::Error, "URL has fragment component " + m_url.stringCenterEllipsizedToLength());
229         m_state = CLOSED;
230         ec = SYNTAX_ERR;
231         return;
232     }
233     if (!portAllowed(m_url)) {
234         scriptExecutionContext()->addConsoleMessage(MessageSource::JS, MessageLevel::Error, "WebSocket port " + String::number(m_url.port()) + " blocked");
235         m_state = CLOSED;
236         ec = SECURITY_ERR;
237         return;
238     }
239
240     // FIXME: Convert this to check the isolated world's Content Security Policy once webkit.org/b/104520 is solved.
241     if (!scriptExecutionContext()->contentSecurityPolicy()->allowConnectToSource(m_url, scriptExecutionContext()->shouldBypassMainWorldContentSecurityPolicy())) {
242         m_state = CLOSED;
243
244         // FIXME: Should this be throwing an exception?
245         ec = SECURITY_ERR;
246         return;
247     }
248
249     m_channel = ThreadableWebSocketChannel::create(scriptExecutionContext(), this);
250
251     // FIXME: There is a disagreement about restriction of subprotocols between WebSocket API and hybi-10 protocol
252     // draft. The former simply says "only characters in the range U+0021 to U+007E are allowed," while the latter
253     // imposes a stricter rule: "the elements MUST be non-empty strings with characters as defined in [RFC2616],
254     // and MUST all be unique strings."
255     //
256     // Here, we throw SYNTAX_ERR if the given protocols do not meet the latter criteria. This behavior does not
257     // comply with WebSocket API specification, but it seems to be the only reasonable way to handle this conflict.
258     for (auto& protocol : protocols) {
259         if (!isValidProtocolString(protocol)) {
260             scriptExecutionContext()->addConsoleMessage(MessageSource::JS, MessageLevel::Error, "Wrong protocol for WebSocket '" + encodeProtocolString(protocol) + "'");
261             m_state = CLOSED;
262             ec = SYNTAX_ERR;
263             return;
264         }
265     }
266     HashSet<String> visited;
267     for (auto& protocol : protocols) {
268         if (!visited.add(protocol).isNewEntry) {
269             scriptExecutionContext()->addConsoleMessage(MessageSource::JS, MessageLevel::Error, "WebSocket protocols contain duplicates: '" + encodeProtocolString(protocol) + "'");
270             m_state = CLOSED;
271             ec = SYNTAX_ERR;
272             return;
273         }
274     }
275
276     if (is<Document>(*scriptExecutionContext())) {
277         Document& document = downcast<Document>(*scriptExecutionContext());
278         if (!document.frame()->loader().mixedContentChecker().canRunInsecureContent(document.securityOrigin(), m_url)) {
279             // Balanced by the call to ActiveDOMObject::unsetPendingActivity() in WebSocket::stop().
280             ActiveDOMObject::setPendingActivity(this);
281
282             // We must block this connection. Instead of throwing an exception, we indicate this
283             // using the error event. But since this code executes as part of the WebSocket's
284             // constructor, we have to wait until the constructor has completed before firing the
285             // event; otherwise, users can't connect to the event.
286             RunLoop::main().dispatch([this]() {
287                 dispatchOrQueueErrorEvent();
288                 stop();
289             });
290             return;
291         }
292     }
293
294     String protocolString;
295     if (!protocols.isEmpty())
296         protocolString = joinStrings(protocols, subProtocolSeperator());
297
298     m_channel->connect(m_url, protocolString);
299     ActiveDOMObject::setPendingActivity(this);
300 }
301
302 void WebSocket::send(const String& message, ExceptionCode& ec)
303 {
304     LOG(Network, "WebSocket %p send() Sending String '%s'", this, message.utf8().data());
305     if (m_state == CONNECTING) {
306         ec = INVALID_STATE_ERR;
307         return;
308     }
309     // No exception is raised if the connection was once established but has subsequently been closed.
310     if (m_state == CLOSING || m_state == CLOSED) {
311         size_t payloadSize = message.utf8().length();
312         m_bufferedAmountAfterClose = saturateAdd(m_bufferedAmountAfterClose, payloadSize);
313         m_bufferedAmountAfterClose = saturateAdd(m_bufferedAmountAfterClose, getFramingOverhead(payloadSize));
314         return;
315     }
316     ASSERT(m_channel);
317     ThreadableWebSocketChannel::SendResult result = m_channel->send(message);
318     if (result == ThreadableWebSocketChannel::InvalidMessage) {
319         scriptExecutionContext()->addConsoleMessage(MessageSource::JS, MessageLevel::Error, ASCIILiteral("Websocket message contains invalid character(s)."));
320         ec = SYNTAX_ERR;
321         return;
322     }
323 }
324
325 void WebSocket::send(ArrayBuffer& binaryData, ExceptionCode& ec)
326 {
327     LOG(Network, "WebSocket %p send() Sending ArrayBuffer %p", this, &binaryData);
328     if (m_state == CONNECTING) {
329         ec = INVALID_STATE_ERR;
330         return;
331     }
332     if (m_state == CLOSING || m_state == CLOSED) {
333         unsigned payloadSize = binaryData.byteLength();
334         m_bufferedAmountAfterClose = saturateAdd(m_bufferedAmountAfterClose, payloadSize);
335         m_bufferedAmountAfterClose = saturateAdd(m_bufferedAmountAfterClose, getFramingOverhead(payloadSize));
336         return;
337     }
338     ASSERT(m_channel);
339     m_channel->send(binaryData, 0, binaryData.byteLength());
340 }
341
342 void WebSocket::send(ArrayBufferView* arrayBufferView, ExceptionCode& ec)
343 {
344     LOG(Network, "WebSocket %p send() Sending ArrayBufferView %p", this, arrayBufferView);
345     ASSERT(arrayBufferView);
346     if (m_state == CONNECTING) {
347         ec = INVALID_STATE_ERR;
348         return;
349     }
350     if (m_state == CLOSING || m_state == CLOSED) {
351         unsigned payloadSize = arrayBufferView->byteLength();
352         m_bufferedAmountAfterClose = saturateAdd(m_bufferedAmountAfterClose, payloadSize);
353         m_bufferedAmountAfterClose = saturateAdd(m_bufferedAmountAfterClose, getFramingOverhead(payloadSize));
354         return;
355     }
356     ASSERT(m_channel);
357     RefPtr<ArrayBuffer> arrayBuffer(arrayBufferView->buffer());
358     m_channel->send(*arrayBuffer, arrayBufferView->byteOffset(), arrayBufferView->byteLength());
359 }
360
361 void WebSocket::send(Blob& binaryData, ExceptionCode& ec)
362 {
363     LOG(Network, "WebSocket %p send() Sending Blob '%s'", this, binaryData.url().stringCenterEllipsizedToLength().utf8().data());
364     if (m_state == CONNECTING) {
365         ec = INVALID_STATE_ERR;
366         return;
367     }
368     if (m_state == CLOSING || m_state == CLOSED) {
369         unsigned long payloadSize = static_cast<unsigned long>(binaryData.size());
370         m_bufferedAmountAfterClose = saturateAdd(m_bufferedAmountAfterClose, payloadSize);
371         m_bufferedAmountAfterClose = saturateAdd(m_bufferedAmountAfterClose, getFramingOverhead(payloadSize));
372         return;
373     }
374     ASSERT(m_channel);
375     m_channel->send(binaryData);
376 }
377
378 void WebSocket::close(int code, const String& reason, ExceptionCode& ec)
379 {
380     if (code == WebSocketChannel::CloseEventCodeNotSpecified)
381         LOG(Network, "WebSocket %p close() without code and reason", this);
382     else {
383         LOG(Network, "WebSocket %p close() code=%d reason='%s'", this, code, reason.utf8().data());
384         if (!(code == WebSocketChannel::CloseEventCodeNormalClosure || (WebSocketChannel::CloseEventCodeMinimumUserDefined <= code && code <= WebSocketChannel::CloseEventCodeMaximumUserDefined))) {
385             ec = INVALID_ACCESS_ERR;
386             return;
387         }
388         CString utf8 = reason.utf8(StrictConversionReplacingUnpairedSurrogatesWithFFFD);
389         if (utf8.length() > maxReasonSizeInBytes) {
390             scriptExecutionContext()->addConsoleMessage(MessageSource::JS, MessageLevel::Error, ASCIILiteral("WebSocket close message is too long."));
391             ec = SYNTAX_ERR;
392             return;
393         }
394     }
395
396     if (m_state == CLOSING || m_state == CLOSED)
397         return;
398     if (m_state == CONNECTING) {
399         m_state = CLOSING;
400         m_channel->fail("WebSocket is closed before the connection is established.");
401         return;
402     }
403     m_state = CLOSING;
404     if (m_channel)
405         m_channel->close(code, reason);
406 }
407
408 const URL& WebSocket::url() const
409 {
410     return m_url;
411 }
412
413 WebSocket::State WebSocket::readyState() const
414 {
415     return m_state;
416 }
417
418 unsigned long WebSocket::bufferedAmount() const
419 {
420     return saturateAdd(m_bufferedAmount, m_bufferedAmountAfterClose);
421 }
422
423 String WebSocket::protocol() const
424 {
425     return m_subprotocol;
426 }
427
428 String WebSocket::extensions() const
429 {
430     return m_extensions;
431 }
432
433 String WebSocket::binaryType() const
434 {
435     switch (m_binaryType) {
436     case BinaryTypeBlob:
437         return "blob";
438     case BinaryTypeArrayBuffer:
439         return "arraybuffer";
440     }
441     ASSERT_NOT_REACHED();
442     return String();
443 }
444
445 void WebSocket::setBinaryType(const String& binaryType, ExceptionCode& ec)
446 {
447     if (binaryType == "blob") {
448         m_binaryType = BinaryTypeBlob;
449         return;
450     }
451     if (binaryType == "arraybuffer") {
452         m_binaryType = BinaryTypeArrayBuffer;
453         return;
454     }
455     ec = SYNTAX_ERR;
456     scriptExecutionContext()->addConsoleMessage(MessageSource::JS, MessageLevel::Error, "'" + binaryType + "' is not a valid value for binaryType; binaryType remains unchanged.");
457 }
458
459 EventTargetInterface WebSocket::eventTargetInterface() const
460 {
461     return WebSocketEventTargetInterfaceType;
462 }
463
464 ScriptExecutionContext* WebSocket::scriptExecutionContext() const
465 {
466     return ActiveDOMObject::scriptExecutionContext();
467 }
468
469 void WebSocket::contextDestroyed()
470 {
471     LOG(Network, "WebSocket %p contextDestroyed()", this);
472     ASSERT(!m_channel);
473     ASSERT(m_state == CLOSED);
474     ActiveDOMObject::contextDestroyed();
475 }
476
477 bool WebSocket::canSuspendForDocumentSuspension() const
478 {
479     return true;
480 }
481
482 void WebSocket::suspend(ReasonForSuspension reason)
483 {
484     if (m_resumeTimer.isActive())
485         m_resumeTimer.stop();
486
487     m_shouldDelayEventFiring = true;
488
489     if (m_channel) {
490         if (reason == ActiveDOMObject::PageCache) {
491             // This will cause didClose() to be called.
492             m_channel->fail("WebSocket is closed due to suspension.");
493         } else
494             m_channel->suspend();
495     }
496 }
497
498 void WebSocket::resume()
499 {
500     if (m_channel)
501         m_channel->resume();
502     else if (!m_pendingEvents.isEmpty() && !m_resumeTimer.isActive()) {
503         // Fire the pending events in a timer as we are not allowed to execute arbitrary JS from resume().
504         m_resumeTimer.startOneShot(0);
505     }
506
507     m_shouldDelayEventFiring = false;
508 }
509
510 void WebSocket::resumeTimerFired()
511 {
512     Ref<WebSocket> protect(*this);
513
514     ASSERT(!m_pendingEvents.isEmpty());
515
516     // Check m_shouldDelayEventFiring when iterating in case firing an event causes
517     // suspend() to be called.
518     while (!m_pendingEvents.isEmpty() && !m_shouldDelayEventFiring)
519         dispatchEvent(m_pendingEvents.takeFirst());
520 }
521
522 void WebSocket::stop()
523 {
524     bool pending = hasPendingActivity();
525     if (m_channel)
526         m_channel->disconnect();
527     m_channel = nullptr;
528     m_state = CLOSED;
529     m_pendingEvents.clear();
530     ActiveDOMObject::stop();
531     if (pending)
532         ActiveDOMObject::unsetPendingActivity(this);
533 }
534
535 const char* WebSocket::activeDOMObjectName() const
536 {
537     return "WebSocket";
538 }
539
540 void WebSocket::didConnect()
541 {
542     LOG(Network, "WebSocket %p didConnect()", this);
543     if (m_state != CONNECTING) {
544         didClose(0, ClosingHandshakeIncomplete, WebSocketChannel::CloseEventCodeAbnormalClosure, "");
545         return;
546     }
547     ASSERT(scriptExecutionContext());
548     m_state = OPEN;
549     m_subprotocol = m_channel->subprotocol();
550     m_extensions = m_channel->extensions();
551     dispatchEvent(Event::create(eventNames().openEvent, false, false));
552 }
553
554 void WebSocket::didReceiveMessage(const String& msg)
555 {
556     LOG(Network, "WebSocket %p didReceiveMessage() Text message '%s'", this, msg.utf8().data());
557     if (m_state != OPEN)
558         return;
559     ASSERT(scriptExecutionContext());
560     dispatchEvent(MessageEvent::create(msg, SecurityOrigin::create(m_url)->toString()));
561 }
562
563 void WebSocket::didReceiveBinaryData(Vector<uint8_t>&& binaryData)
564 {
565     LOG(Network, "WebSocket %p didReceiveBinaryData() %lu byte binary message", this, static_cast<unsigned long>(binaryData.size()));
566     switch (m_binaryType) {
567     case BinaryTypeBlob:
568         // FIXME: We just received the data from NetworkProcess, and are sending it back. This is inefficient.
569         dispatchEvent(MessageEvent::create(Blob::create(WTFMove(binaryData), emptyString()), SecurityOrigin::create(m_url)->toString()));
570         break;
571     case BinaryTypeArrayBuffer:
572         dispatchEvent(MessageEvent::create(ArrayBuffer::create(binaryData.data(), binaryData.size()), SecurityOrigin::create(m_url)->toString()));
573         break;
574     }
575 }
576
577 void WebSocket::didReceiveMessageError()
578 {
579     LOG(Network, "WebSocket %p didReceiveErrorMessage()", this);
580     m_state = CLOSED;
581     ASSERT(scriptExecutionContext());
582     dispatchOrQueueErrorEvent();
583 }
584
585 void WebSocket::didUpdateBufferedAmount(unsigned long bufferedAmount)
586 {
587     LOG(Network, "WebSocket %p didUpdateBufferedAmount() New bufferedAmount is %lu", this, bufferedAmount);
588     if (m_state == CLOSED)
589         return;
590     m_bufferedAmount = bufferedAmount;
591 }
592
593 void WebSocket::didStartClosingHandshake()
594 {
595     LOG(Network, "WebSocket %p didStartClosingHandshake()", this);
596     m_state = CLOSING;
597 }
598
599 void WebSocket::didClose(unsigned long unhandledBufferedAmount, ClosingHandshakeCompletionStatus closingHandshakeCompletion, unsigned short code, const String& reason)
600 {
601     LOG(Network, "WebSocket %p didClose()", this);
602     if (!m_channel)
603         return;
604     bool wasClean = m_state == CLOSING && !unhandledBufferedAmount && closingHandshakeCompletion == ClosingHandshakeComplete && code != WebSocketChannel::CloseEventCodeAbnormalClosure;
605     m_state = CLOSED;
606     m_bufferedAmount = unhandledBufferedAmount;
607     ASSERT(scriptExecutionContext());
608
609     dispatchOrQueueEvent(CloseEvent::create(wasClean, code, reason));
610
611     if (m_channel) {
612         m_channel->disconnect();
613         m_channel = nullptr;
614     }
615     if (hasPendingActivity())
616         ActiveDOMObject::unsetPendingActivity(this);
617 }
618
619 size_t WebSocket::getFramingOverhead(size_t payloadSize)
620 {
621     static const size_t hybiBaseFramingOverhead = 2; // Every frame has at least two-byte header.
622     static const size_t hybiMaskingKeyLength = 4; // Every frame from client must have masking key.
623     static const size_t minimumPayloadSizeWithTwoByteExtendedPayloadLength = 126;
624     static const size_t minimumPayloadSizeWithEightByteExtendedPayloadLength = 0x10000;
625     size_t overhead = hybiBaseFramingOverhead + hybiMaskingKeyLength;
626     if (payloadSize >= minimumPayloadSizeWithEightByteExtendedPayloadLength)
627         overhead += 8;
628     else if (payloadSize >= minimumPayloadSizeWithTwoByteExtendedPayloadLength)
629         overhead += 2;
630     return overhead;
631 }
632
633 void WebSocket::dispatchOrQueueErrorEvent()
634 {
635     if (m_dispatchedErrorEvent)
636         return;
637
638     m_dispatchedErrorEvent = true;
639     dispatchOrQueueEvent(Event::create(eventNames().errorEvent, false, false));
640 }
641
642 void WebSocket::dispatchOrQueueEvent(Ref<Event>&& event)
643 {
644     if (m_shouldDelayEventFiring)
645         m_pendingEvents.append(WTFMove(event));
646     else
647         dispatchEvent(event);
648 }
649
650 }  // namespace WebCore
651
652 #endif