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