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