|
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* vim: set sw=2 ts=8 et tw=80 : */ |
|
3 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 #include "WebSocket.h" |
|
8 #include "mozilla/dom/WebSocketBinding.h" |
|
9 |
|
10 #include "jsapi.h" |
|
11 #include "jsfriendapi.h" |
|
12 #include "js/OldDebugAPI.h" |
|
13 #include "mozilla/DOMEventTargetHelper.h" |
|
14 #include "nsIScriptGlobalObject.h" |
|
15 #include "nsIDOMWindow.h" |
|
16 #include "nsIDocument.h" |
|
17 #include "nsXPCOM.h" |
|
18 #include "nsIXPConnect.h" |
|
19 #include "nsContentUtils.h" |
|
20 #include "nsCxPusher.h" |
|
21 #include "nsError.h" |
|
22 #include "nsIScriptObjectPrincipal.h" |
|
23 #include "nsIURL.h" |
|
24 #include "nsIUnicodeEncoder.h" |
|
25 #include "nsThreadUtils.h" |
|
26 #include "nsIDOMMessageEvent.h" |
|
27 #include "nsIPromptFactory.h" |
|
28 #include "nsIWindowWatcher.h" |
|
29 #include "nsIPrompt.h" |
|
30 #include "nsIStringBundle.h" |
|
31 #include "nsIConsoleService.h" |
|
32 #include "nsIDOMCloseEvent.h" |
|
33 #include "nsICryptoHash.h" |
|
34 #include "nsJSUtils.h" |
|
35 #include "nsIScriptError.h" |
|
36 #include "nsNetUtil.h" |
|
37 #include "nsILoadGroup.h" |
|
38 #include "mozilla/Preferences.h" |
|
39 #include "xpcpublic.h" |
|
40 #include "nsContentPolicyUtils.h" |
|
41 #include "nsDOMFile.h" |
|
42 #include "nsWrapperCacheInlines.h" |
|
43 #include "nsIObserverService.h" |
|
44 #include "nsIWebSocketChannel.h" |
|
45 #include "GeneratedEvents.h" |
|
46 |
|
47 namespace mozilla { |
|
48 namespace dom { |
|
49 |
|
50 #define UTF_8_REPLACEMENT_CHAR static_cast<char16_t>(0xFFFD) |
|
51 |
|
52 class CallDispatchConnectionCloseEvents: public nsRunnable |
|
53 { |
|
54 public: |
|
55 CallDispatchConnectionCloseEvents(WebSocket* aWebSocket) |
|
56 : mWebSocket(aWebSocket) |
|
57 {} |
|
58 |
|
59 NS_IMETHOD Run() |
|
60 { |
|
61 mWebSocket->DispatchConnectionCloseEvents(); |
|
62 return NS_OK; |
|
63 } |
|
64 |
|
65 private: |
|
66 nsRefPtr<WebSocket> mWebSocket; |
|
67 }; |
|
68 |
|
69 //----------------------------------------------------------------------------- |
|
70 // WebSocket |
|
71 //----------------------------------------------------------------------------- |
|
72 |
|
73 nsresult |
|
74 WebSocket::PrintErrorOnConsole(const char *aBundleURI, |
|
75 const char16_t *aError, |
|
76 const char16_t **aFormatStrings, |
|
77 uint32_t aFormatStringsLen) |
|
78 { |
|
79 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); |
|
80 |
|
81 nsresult rv; |
|
82 nsCOMPtr<nsIStringBundleService> bundleService = |
|
83 do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv); |
|
84 NS_ENSURE_SUCCESS(rv, rv); |
|
85 |
|
86 nsCOMPtr<nsIStringBundle> strBundle; |
|
87 rv = bundleService->CreateBundle(aBundleURI, getter_AddRefs(strBundle)); |
|
88 NS_ENSURE_SUCCESS(rv, rv); |
|
89 |
|
90 nsCOMPtr<nsIConsoleService> console( |
|
91 do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv)); |
|
92 NS_ENSURE_SUCCESS(rv, rv); |
|
93 |
|
94 nsCOMPtr<nsIScriptError> errorObject( |
|
95 do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv)); |
|
96 NS_ENSURE_SUCCESS(rv, rv); |
|
97 |
|
98 // Localize the error message |
|
99 nsXPIDLString message; |
|
100 if (aFormatStrings) { |
|
101 rv = strBundle->FormatStringFromName(aError, aFormatStrings, |
|
102 aFormatStringsLen, |
|
103 getter_Copies(message)); |
|
104 } else { |
|
105 rv = strBundle->GetStringFromName(aError, getter_Copies(message)); |
|
106 } |
|
107 NS_ENSURE_SUCCESS(rv, rv); |
|
108 |
|
109 rv = errorObject->InitWithWindowID(message, |
|
110 NS_ConvertUTF8toUTF16(mScriptFile), |
|
111 EmptyString(), mScriptLine, 0, |
|
112 nsIScriptError::errorFlag, "Web Socket", |
|
113 mInnerWindowID); |
|
114 NS_ENSURE_SUCCESS(rv, rv); |
|
115 |
|
116 // print the error message directly to the JS console |
|
117 rv = console->LogMessage(errorObject); |
|
118 NS_ENSURE_SUCCESS(rv, rv); |
|
119 |
|
120 return NS_OK; |
|
121 } |
|
122 |
|
123 nsresult |
|
124 WebSocket::CloseConnection(uint16_t aReasonCode, |
|
125 const nsACString& aReasonString) |
|
126 { |
|
127 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); |
|
128 if (mReadyState == WebSocket::CLOSING || |
|
129 mReadyState == WebSocket::CLOSED) { |
|
130 return NS_OK; |
|
131 } |
|
132 |
|
133 // The common case... |
|
134 if (mChannel) { |
|
135 mReadyState = WebSocket::CLOSING; |
|
136 return mChannel->Close(aReasonCode, aReasonString); |
|
137 } |
|
138 |
|
139 // No channel, but not disconnected: canceled or failed early |
|
140 // |
|
141 MOZ_ASSERT(mReadyState == WebSocket::CONNECTING, |
|
142 "Should only get here for early websocket cancel/error"); |
|
143 |
|
144 // Server won't be sending us a close code, so use what's passed in here. |
|
145 mCloseEventCode = aReasonCode; |
|
146 CopyUTF8toUTF16(aReasonString, mCloseEventReason); |
|
147 |
|
148 mReadyState = WebSocket::CLOSING; |
|
149 |
|
150 // Can be called from Cancel() or Init() codepaths, so need to dispatch |
|
151 // onerror/onclose asynchronously |
|
152 ScheduleConnectionCloseEvents( |
|
153 nullptr, |
|
154 (aReasonCode == nsIWebSocketChannel::CLOSE_NORMAL || |
|
155 aReasonCode == nsIWebSocketChannel::CLOSE_GOING_AWAY) ? |
|
156 NS_OK : NS_ERROR_FAILURE, |
|
157 false); |
|
158 |
|
159 return NS_OK; |
|
160 } |
|
161 |
|
162 nsresult |
|
163 WebSocket::ConsoleError() |
|
164 { |
|
165 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); |
|
166 |
|
167 nsAutoCString targetSpec; |
|
168 nsresult rv = mURI->GetSpec(targetSpec); |
|
169 if (NS_FAILED(rv)) { |
|
170 NS_WARNING("Failed to get targetSpec"); |
|
171 } else { |
|
172 NS_ConvertUTF8toUTF16 specUTF16(targetSpec); |
|
173 const char16_t* formatStrings[] = { specUTF16.get() }; |
|
174 |
|
175 if (mReadyState < WebSocket::OPEN) { |
|
176 PrintErrorOnConsole("chrome://global/locale/appstrings.properties", |
|
177 MOZ_UTF16("connectionFailure"), |
|
178 formatStrings, ArrayLength(formatStrings)); |
|
179 } else { |
|
180 PrintErrorOnConsole("chrome://global/locale/appstrings.properties", |
|
181 MOZ_UTF16("netInterrupt"), |
|
182 formatStrings, ArrayLength(formatStrings)); |
|
183 } |
|
184 } |
|
185 /// todo some specific errors - like for message too large |
|
186 return rv; |
|
187 } |
|
188 |
|
189 |
|
190 void |
|
191 WebSocket::FailConnection(uint16_t aReasonCode, |
|
192 const nsACString& aReasonString) |
|
193 { |
|
194 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); |
|
195 |
|
196 ConsoleError(); |
|
197 mFailed = true; |
|
198 CloseConnection(aReasonCode, aReasonString); |
|
199 } |
|
200 |
|
201 nsresult |
|
202 WebSocket::Disconnect() |
|
203 { |
|
204 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); |
|
205 |
|
206 if (mDisconnected) |
|
207 return NS_OK; |
|
208 |
|
209 nsCOMPtr<nsILoadGroup> loadGroup; |
|
210 GetLoadGroup(getter_AddRefs(loadGroup)); |
|
211 if (loadGroup) |
|
212 loadGroup->RemoveRequest(this, nullptr, NS_OK); |
|
213 |
|
214 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); |
|
215 if (os) { |
|
216 os->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC); |
|
217 os->RemoveObserver(this, DOM_WINDOW_FROZEN_TOPIC); |
|
218 } |
|
219 |
|
220 // DontKeepAliveAnyMore() can release the object. So hold a reference to this |
|
221 // until the end of the method. |
|
222 nsRefPtr<WebSocket> kungfuDeathGrip = this; |
|
223 |
|
224 DontKeepAliveAnyMore(); |
|
225 mChannel = nullptr; |
|
226 mDisconnected = true; |
|
227 |
|
228 return NS_OK; |
|
229 } |
|
230 |
|
231 //----------------------------------------------------------------------------- |
|
232 // WebSocket::nsIWebSocketListener methods: |
|
233 //----------------------------------------------------------------------------- |
|
234 |
|
235 nsresult |
|
236 WebSocket::DoOnMessageAvailable(const nsACString& aMsg, bool isBinary) |
|
237 { |
|
238 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); |
|
239 |
|
240 if (mReadyState == WebSocket::CLOSED) { |
|
241 NS_ERROR("Received message after CLOSED"); |
|
242 return NS_ERROR_UNEXPECTED; |
|
243 } |
|
244 |
|
245 if (mReadyState == WebSocket::OPEN) { |
|
246 // Dispatch New Message |
|
247 nsresult rv = CreateAndDispatchMessageEvent(aMsg, isBinary); |
|
248 if (NS_FAILED(rv)) { |
|
249 NS_WARNING("Failed to dispatch the message event"); |
|
250 } |
|
251 } else { |
|
252 // CLOSING should be the only other state where it's possible to get msgs |
|
253 // from channel: Spec says to drop them. |
|
254 MOZ_ASSERT(mReadyState == WebSocket::CLOSING, |
|
255 "Received message while CONNECTING or CLOSED"); |
|
256 } |
|
257 |
|
258 return NS_OK; |
|
259 } |
|
260 |
|
261 NS_IMETHODIMP |
|
262 WebSocket::OnMessageAvailable(nsISupports* aContext, const nsACString& aMsg) |
|
263 { |
|
264 return DoOnMessageAvailable(aMsg, false); |
|
265 } |
|
266 |
|
267 NS_IMETHODIMP |
|
268 WebSocket::OnBinaryMessageAvailable(nsISupports* aContext, |
|
269 const nsACString& aMsg) |
|
270 { |
|
271 return DoOnMessageAvailable(aMsg, true); |
|
272 } |
|
273 |
|
274 NS_IMETHODIMP |
|
275 WebSocket::OnStart(nsISupports* aContext) |
|
276 { |
|
277 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); |
|
278 |
|
279 // This is the only function that sets OPEN, and should be called only once |
|
280 MOZ_ASSERT(mReadyState != WebSocket::OPEN, |
|
281 "readyState already OPEN! OnStart called twice?"); |
|
282 |
|
283 // Nothing to do if we've already closed/closing |
|
284 if (mReadyState != WebSocket::CONNECTING) { |
|
285 return NS_OK; |
|
286 } |
|
287 |
|
288 // Attempt to kill "ghost" websocket: but usually too early for check to fail |
|
289 nsresult rv = CheckInnerWindowCorrectness(); |
|
290 if (NS_FAILED(rv)) { |
|
291 CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY); |
|
292 return rv; |
|
293 } |
|
294 |
|
295 if (!mRequestedProtocolList.IsEmpty()) { |
|
296 mChannel->GetProtocol(mEstablishedProtocol); |
|
297 } |
|
298 |
|
299 mChannel->GetExtensions(mEstablishedExtensions); |
|
300 UpdateURI(); |
|
301 |
|
302 mReadyState = WebSocket::OPEN; |
|
303 |
|
304 // Call 'onopen' |
|
305 rv = CreateAndDispatchSimpleEvent(NS_LITERAL_STRING("open")); |
|
306 if (NS_FAILED(rv)) { |
|
307 NS_WARNING("Failed to dispatch the open event"); |
|
308 } |
|
309 |
|
310 UpdateMustKeepAlive(); |
|
311 |
|
312 return NS_OK; |
|
313 } |
|
314 |
|
315 NS_IMETHODIMP |
|
316 WebSocket::OnStop(nsISupports* aContext, nsresult aStatusCode) |
|
317 { |
|
318 // We can be CONNECTING here if connection failed. |
|
319 // We can be OPEN if we have encountered a fatal protocol error |
|
320 // We can be CLOSING if close() was called and/or server initiated close. |
|
321 MOZ_ASSERT(mReadyState != WebSocket::CLOSED, |
|
322 "Shouldn't already be CLOSED when OnStop called"); |
|
323 |
|
324 // called by network stack, not JS, so can dispatch JS events synchronously |
|
325 return ScheduleConnectionCloseEvents(aContext, aStatusCode, true); |
|
326 } |
|
327 |
|
328 nsresult |
|
329 WebSocket::ScheduleConnectionCloseEvents(nsISupports* aContext, |
|
330 nsresult aStatusCode, |
|
331 bool sync) |
|
332 { |
|
333 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); |
|
334 |
|
335 // no-op if some other code has already initiated close event |
|
336 if (!mOnCloseScheduled) { |
|
337 mCloseEventWasClean = NS_SUCCEEDED(aStatusCode); |
|
338 |
|
339 if (aStatusCode == NS_BASE_STREAM_CLOSED) { |
|
340 // don't generate an error event just because of an unclean close |
|
341 aStatusCode = NS_OK; |
|
342 } |
|
343 |
|
344 if (NS_FAILED(aStatusCode)) { |
|
345 ConsoleError(); |
|
346 mFailed = true; |
|
347 } |
|
348 |
|
349 mOnCloseScheduled = true; |
|
350 |
|
351 if (sync) { |
|
352 DispatchConnectionCloseEvents(); |
|
353 } else { |
|
354 NS_DispatchToMainThread(new CallDispatchConnectionCloseEvents(this), |
|
355 NS_DISPATCH_NORMAL); |
|
356 } |
|
357 } |
|
358 |
|
359 return NS_OK; |
|
360 } |
|
361 |
|
362 NS_IMETHODIMP |
|
363 WebSocket::OnAcknowledge(nsISupports *aContext, uint32_t aSize) |
|
364 { |
|
365 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); |
|
366 |
|
367 if (aSize > mOutgoingBufferedAmount) |
|
368 return NS_ERROR_UNEXPECTED; |
|
369 |
|
370 mOutgoingBufferedAmount -= aSize; |
|
371 return NS_OK; |
|
372 } |
|
373 |
|
374 NS_IMETHODIMP |
|
375 WebSocket::OnServerClose(nsISupports *aContext, uint16_t aCode, |
|
376 const nsACString &aReason) |
|
377 { |
|
378 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); |
|
379 |
|
380 MOZ_ASSERT(mReadyState != WebSocket::CONNECTING, |
|
381 "Received server close before connected?"); |
|
382 MOZ_ASSERT(mReadyState != WebSocket::CLOSED, |
|
383 "Received server close after already closed!"); |
|
384 |
|
385 // store code/string for onclose DOM event |
|
386 mCloseEventCode = aCode; |
|
387 CopyUTF8toUTF16(aReason, mCloseEventReason); |
|
388 |
|
389 if (mReadyState == WebSocket::OPEN) { |
|
390 // Server initiating close. |
|
391 // RFC 6455, 5.5.1: "When sending a Close frame in response, the endpoint |
|
392 // typically echos the status code it received". |
|
393 // But never send certain codes, per section 7.4.1 |
|
394 if (aCode == 1005 || aCode == 1006 || aCode == 1015) { |
|
395 CloseConnection(0, EmptyCString()); |
|
396 } else { |
|
397 CloseConnection(aCode, aReason); |
|
398 } |
|
399 } else { |
|
400 // We initiated close, and server has replied: OnStop does rest of the work. |
|
401 MOZ_ASSERT(mReadyState == WebSocket::CLOSING, "unknown state"); |
|
402 } |
|
403 |
|
404 return NS_OK; |
|
405 } |
|
406 |
|
407 //----------------------------------------------------------------------------- |
|
408 // WebSocket::nsIInterfaceRequestor |
|
409 //----------------------------------------------------------------------------- |
|
410 |
|
411 NS_IMETHODIMP |
|
412 WebSocket::GetInterface(const nsIID& aIID, void** aResult) |
|
413 { |
|
414 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); |
|
415 |
|
416 if (mReadyState == WebSocket::CLOSED) |
|
417 return NS_ERROR_FAILURE; |
|
418 |
|
419 if (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) || |
|
420 aIID.Equals(NS_GET_IID(nsIAuthPrompt2))) { |
|
421 nsresult rv; |
|
422 nsIScriptContext* sc = GetContextForEventHandlers(&rv); |
|
423 nsCOMPtr<nsIDocument> doc = |
|
424 nsContentUtils::GetDocumentFromScriptContext(sc); |
|
425 if (!doc) |
|
426 return NS_ERROR_NOT_AVAILABLE; |
|
427 |
|
428 nsCOMPtr<nsIPromptFactory> wwatch = |
|
429 do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv); |
|
430 NS_ENSURE_SUCCESS(rv, rv); |
|
431 |
|
432 nsCOMPtr<nsPIDOMWindow> outerWindow = doc->GetWindow(); |
|
433 return wwatch->GetPrompt(outerWindow, aIID, aResult); |
|
434 } |
|
435 |
|
436 return QueryInterface(aIID, aResult); |
|
437 } |
|
438 |
|
439 //////////////////////////////////////////////////////////////////////////////// |
|
440 // WebSocket |
|
441 //////////////////////////////////////////////////////////////////////////////// |
|
442 |
|
443 WebSocket::WebSocket(nsPIDOMWindow* aOwnerWindow) |
|
444 : DOMEventTargetHelper(aOwnerWindow), |
|
445 mKeepingAlive(false), |
|
446 mCheckMustKeepAlive(true), |
|
447 mOnCloseScheduled(false), |
|
448 mFailed(false), |
|
449 mDisconnected(false), |
|
450 mCloseEventWasClean(false), |
|
451 mCloseEventCode(nsIWebSocketChannel::CLOSE_ABNORMAL), |
|
452 mReadyState(WebSocket::CONNECTING), |
|
453 mOutgoingBufferedAmount(0), |
|
454 mBinaryType(dom::BinaryType::Blob), |
|
455 mScriptLine(0), |
|
456 mInnerWindowID(0) |
|
457 { |
|
458 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); |
|
459 MOZ_ASSERT(aOwnerWindow); |
|
460 MOZ_ASSERT(aOwnerWindow->IsInnerWindow()); |
|
461 } |
|
462 |
|
463 WebSocket::~WebSocket() |
|
464 { |
|
465 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); |
|
466 |
|
467 // If we threw during Init we never called disconnect |
|
468 if (!mDisconnected) { |
|
469 Disconnect(); |
|
470 } |
|
471 } |
|
472 |
|
473 JSObject* |
|
474 WebSocket::WrapObject(JSContext* cx) |
|
475 { |
|
476 return WebSocketBinding::Wrap(cx, this); |
|
477 } |
|
478 |
|
479 //--------------------------------------------------------------------------- |
|
480 // WebIDL |
|
481 //--------------------------------------------------------------------------- |
|
482 |
|
483 // Constructor: |
|
484 already_AddRefed<WebSocket> |
|
485 WebSocket::Constructor(const GlobalObject& aGlobal, |
|
486 const nsAString& aUrl, |
|
487 ErrorResult& aRv) |
|
488 { |
|
489 Sequence<nsString> protocols; |
|
490 return WebSocket::Constructor(aGlobal, aUrl, protocols, aRv); |
|
491 } |
|
492 |
|
493 already_AddRefed<WebSocket> |
|
494 WebSocket::Constructor(const GlobalObject& aGlobal, |
|
495 const nsAString& aUrl, |
|
496 const nsAString& aProtocol, |
|
497 ErrorResult& aRv) |
|
498 { |
|
499 Sequence<nsString> protocols; |
|
500 protocols.AppendElement(aProtocol); |
|
501 return WebSocket::Constructor(aGlobal, aUrl, protocols, aRv); |
|
502 } |
|
503 |
|
504 already_AddRefed<WebSocket> |
|
505 WebSocket::Constructor(const GlobalObject& aGlobal, |
|
506 const nsAString& aUrl, |
|
507 const Sequence<nsString>& aProtocols, |
|
508 ErrorResult& aRv) |
|
509 { |
|
510 if (!PrefEnabled()) { |
|
511 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); |
|
512 return nullptr; |
|
513 } |
|
514 |
|
515 nsCOMPtr<nsIScriptObjectPrincipal> scriptPrincipal = |
|
516 do_QueryInterface(aGlobal.GetAsSupports()); |
|
517 if (!scriptPrincipal) { |
|
518 aRv.Throw(NS_ERROR_FAILURE); |
|
519 return nullptr; |
|
520 } |
|
521 |
|
522 nsCOMPtr<nsIPrincipal> principal = scriptPrincipal->GetPrincipal(); |
|
523 if (!principal) { |
|
524 aRv.Throw(NS_ERROR_FAILURE); |
|
525 return nullptr; |
|
526 } |
|
527 |
|
528 nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(aGlobal.GetAsSupports()); |
|
529 if (!sgo) { |
|
530 aRv.Throw(NS_ERROR_FAILURE); |
|
531 return nullptr; |
|
532 } |
|
533 |
|
534 nsCOMPtr<nsPIDOMWindow> ownerWindow = do_QueryInterface(aGlobal.GetAsSupports()); |
|
535 if (!ownerWindow) { |
|
536 aRv.Throw(NS_ERROR_FAILURE); |
|
537 return nullptr; |
|
538 } |
|
539 |
|
540 nsTArray<nsString> protocolArray; |
|
541 |
|
542 for (uint32_t index = 0, len = aProtocols.Length(); index < len; ++index) { |
|
543 |
|
544 const nsString& protocolElement = aProtocols[index]; |
|
545 |
|
546 if (protocolElement.IsEmpty()) { |
|
547 aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); |
|
548 return nullptr; |
|
549 } |
|
550 if (protocolArray.Contains(protocolElement)) { |
|
551 aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); |
|
552 return nullptr; |
|
553 } |
|
554 if (protocolElement.FindChar(',') != -1) /* interferes w/list */ { |
|
555 aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); |
|
556 return nullptr; |
|
557 } |
|
558 |
|
559 protocolArray.AppendElement(protocolElement); |
|
560 } |
|
561 |
|
562 nsRefPtr<WebSocket> webSocket = new WebSocket(ownerWindow); |
|
563 nsresult rv = webSocket->Init(aGlobal.GetContext(), principal, |
|
564 aUrl, protocolArray); |
|
565 if (NS_FAILED(rv)) { |
|
566 aRv.Throw(rv); |
|
567 return nullptr; |
|
568 } |
|
569 |
|
570 return webSocket.forget(); |
|
571 } |
|
572 |
|
573 NS_IMPL_CYCLE_COLLECTION_CLASS(WebSocket) |
|
574 |
|
575 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(WebSocket) |
|
576 bool isBlack = tmp->IsBlack(); |
|
577 if (isBlack|| tmp->mKeepingAlive) { |
|
578 if (tmp->mListenerManager) { |
|
579 tmp->mListenerManager->MarkForCC(); |
|
580 } |
|
581 if (!isBlack && tmp->PreservingWrapper()) { |
|
582 // This marks the wrapper black. |
|
583 tmp->GetWrapper(); |
|
584 } |
|
585 return true; |
|
586 } |
|
587 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END |
|
588 |
|
589 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(WebSocket) |
|
590 return tmp->IsBlack(); |
|
591 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END |
|
592 |
|
593 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(WebSocket) |
|
594 return tmp->IsBlack(); |
|
595 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END |
|
596 |
|
597 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(WebSocket, |
|
598 DOMEventTargetHelper) |
|
599 NS_IMPL_CYCLE_COLLECTION_TRACE_END |
|
600 |
|
601 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(WebSocket, |
|
602 DOMEventTargetHelper) |
|
603 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrincipal) |
|
604 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mURI) |
|
605 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChannel) |
|
606 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END |
|
607 |
|
608 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(WebSocket, |
|
609 DOMEventTargetHelper) |
|
610 tmp->Disconnect(); |
|
611 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrincipal) |
|
612 NS_IMPL_CYCLE_COLLECTION_UNLINK(mURI) |
|
613 NS_IMPL_CYCLE_COLLECTION_UNLINK(mChannel) |
|
614 NS_IMPL_CYCLE_COLLECTION_UNLINK_END |
|
615 |
|
616 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(WebSocket) |
|
617 NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) |
|
618 NS_INTERFACE_MAP_ENTRY(nsIWebSocketListener) |
|
619 NS_INTERFACE_MAP_ENTRY(nsIObserver) |
|
620 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) |
|
621 NS_INTERFACE_MAP_ENTRY(nsIRequest) |
|
622 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) |
|
623 |
|
624 NS_IMPL_ADDREF_INHERITED(WebSocket, DOMEventTargetHelper) |
|
625 NS_IMPL_RELEASE_INHERITED(WebSocket, DOMEventTargetHelper) |
|
626 |
|
627 void |
|
628 WebSocket::DisconnectFromOwner() |
|
629 { |
|
630 DOMEventTargetHelper::DisconnectFromOwner(); |
|
631 CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY); |
|
632 DontKeepAliveAnyMore(); |
|
633 } |
|
634 |
|
635 //----------------------------------------------------------------------------- |
|
636 // WebSocket:: initialization |
|
637 //----------------------------------------------------------------------------- |
|
638 |
|
639 nsresult |
|
640 WebSocket::Init(JSContext* aCx, |
|
641 nsIPrincipal* aPrincipal, |
|
642 const nsAString& aURL, |
|
643 nsTArray<nsString>& aProtocolArray) |
|
644 { |
|
645 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); |
|
646 MOZ_ASSERT(aPrincipal); |
|
647 |
|
648 if (!PrefEnabled()) { |
|
649 return NS_ERROR_DOM_SECURITY_ERR; |
|
650 } |
|
651 |
|
652 mPrincipal = aPrincipal; |
|
653 |
|
654 // Attempt to kill "ghost" websocket: but usually too early for check to fail |
|
655 nsresult rv = CheckInnerWindowCorrectness(); |
|
656 NS_ENSURE_SUCCESS(rv, rv); |
|
657 |
|
658 // Shut down websocket if window is frozen or destroyed (only needed for |
|
659 // "ghost" websockets--see bug 696085) |
|
660 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); |
|
661 NS_ENSURE_STATE(os); |
|
662 rv = os->AddObserver(this, DOM_WINDOW_DESTROYED_TOPIC, true); |
|
663 NS_ENSURE_SUCCESS(rv, rv); |
|
664 rv = os->AddObserver(this, DOM_WINDOW_FROZEN_TOPIC, true); |
|
665 NS_ENSURE_SUCCESS(rv, rv); |
|
666 |
|
667 unsigned lineno; |
|
668 JS::AutoFilename file; |
|
669 if (JS::DescribeScriptedCaller(aCx, &file, &lineno)) { |
|
670 mScriptFile = file.get(); |
|
671 mScriptLine = lineno; |
|
672 } |
|
673 |
|
674 // Get WindowID |
|
675 mInnerWindowID = nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(aCx); |
|
676 |
|
677 // parses the url |
|
678 rv = ParseURL(PromiseFlatString(aURL)); |
|
679 NS_ENSURE_SUCCESS(rv, rv); |
|
680 |
|
681 nsIScriptContext* sc = GetContextForEventHandlers(&rv); |
|
682 NS_ENSURE_SUCCESS(rv, rv); |
|
683 |
|
684 nsCOMPtr<nsIDocument> originDoc = nsContentUtils::GetDocumentFromScriptContext(sc); |
|
685 |
|
686 // Don't allow https:// to open ws:// |
|
687 if (!mSecure && |
|
688 !Preferences::GetBool("network.websocket.allowInsecureFromHTTPS", |
|
689 false)) { |
|
690 // Confirmed we are opening plain ws:// and want to prevent this from a |
|
691 // secure context (e.g. https). Check the security context of the document |
|
692 // associated with this script, which is the same as associated with mOwner. |
|
693 if (originDoc && originDoc->GetSecurityInfo()) { |
|
694 return NS_ERROR_DOM_SECURITY_ERR; |
|
695 } |
|
696 } |
|
697 |
|
698 // Assign the sub protocol list and scan it for illegal values |
|
699 for (uint32_t index = 0; index < aProtocolArray.Length(); ++index) { |
|
700 for (uint32_t i = 0; i < aProtocolArray[index].Length(); ++i) { |
|
701 if (aProtocolArray[index][i] < static_cast<char16_t>(0x0021) || |
|
702 aProtocolArray[index][i] > static_cast<char16_t>(0x007E)) |
|
703 return NS_ERROR_DOM_SYNTAX_ERR; |
|
704 } |
|
705 |
|
706 if (!mRequestedProtocolList.IsEmpty()) { |
|
707 mRequestedProtocolList.Append(NS_LITERAL_CSTRING(", ")); |
|
708 } |
|
709 |
|
710 AppendUTF16toUTF8(aProtocolArray[index], mRequestedProtocolList); |
|
711 } |
|
712 |
|
713 // Check content policy. |
|
714 int16_t shouldLoad = nsIContentPolicy::ACCEPT; |
|
715 rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_WEBSOCKET, |
|
716 mURI, |
|
717 mPrincipal, |
|
718 originDoc, |
|
719 EmptyCString(), |
|
720 nullptr, |
|
721 &shouldLoad, |
|
722 nsContentUtils::GetContentPolicy(), |
|
723 nsContentUtils::GetSecurityManager()); |
|
724 NS_ENSURE_SUCCESS(rv, rv); |
|
725 if (NS_CP_REJECTED(shouldLoad)) { |
|
726 // Disallowed by content policy. |
|
727 return NS_ERROR_CONTENT_BLOCKED; |
|
728 } |
|
729 |
|
730 // the constructor should throw a SYNTAX_ERROR only if it fails to parse the |
|
731 // url parameter, so don't throw if EstablishConnection fails, and call |
|
732 // onerror/onclose asynchronously |
|
733 if (NS_FAILED(EstablishConnection())) { |
|
734 FailConnection(nsIWebSocketChannel::CLOSE_ABNORMAL); |
|
735 } |
|
736 |
|
737 return NS_OK; |
|
738 } |
|
739 |
|
740 //----------------------------------------------------------------------------- |
|
741 // WebSocket methods: |
|
742 //----------------------------------------------------------------------------- |
|
743 |
|
744 class nsAutoCloseWS |
|
745 { |
|
746 public: |
|
747 nsAutoCloseWS(WebSocket* aWebSocket) |
|
748 : mWebSocket(aWebSocket) |
|
749 {} |
|
750 |
|
751 ~nsAutoCloseWS() |
|
752 { |
|
753 if (!mWebSocket->mChannel) { |
|
754 mWebSocket->CloseConnection(nsIWebSocketChannel::CLOSE_INTERNAL_ERROR); |
|
755 } |
|
756 } |
|
757 private: |
|
758 nsRefPtr<WebSocket> mWebSocket; |
|
759 }; |
|
760 |
|
761 nsresult |
|
762 WebSocket::EstablishConnection() |
|
763 { |
|
764 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); |
|
765 NS_ABORT_IF_FALSE(!mChannel, "mChannel should be null"); |
|
766 |
|
767 nsCOMPtr<nsIWebSocketChannel> wsChannel; |
|
768 nsAutoCloseWS autoClose(this); |
|
769 nsresult rv; |
|
770 |
|
771 if (mSecure) { |
|
772 wsChannel = |
|
773 do_CreateInstance("@mozilla.org/network/protocol;1?name=wss", &rv); |
|
774 } else { |
|
775 wsChannel = |
|
776 do_CreateInstance("@mozilla.org/network/protocol;1?name=ws", &rv); |
|
777 } |
|
778 NS_ENSURE_SUCCESS(rv, rv); |
|
779 |
|
780 rv = wsChannel->SetNotificationCallbacks(this); |
|
781 NS_ENSURE_SUCCESS(rv, rv); |
|
782 |
|
783 // add ourselves to the document's load group and |
|
784 // provide the http stack the loadgroup info too |
|
785 nsCOMPtr<nsILoadGroup> loadGroup; |
|
786 rv = GetLoadGroup(getter_AddRefs(loadGroup)); |
|
787 if (loadGroup) { |
|
788 rv = wsChannel->SetLoadGroup(loadGroup); |
|
789 NS_ENSURE_SUCCESS(rv, rv); |
|
790 rv = loadGroup->AddRequest(this, nullptr); |
|
791 NS_ENSURE_SUCCESS(rv, rv); |
|
792 } |
|
793 |
|
794 if (!mRequestedProtocolList.IsEmpty()) { |
|
795 rv = wsChannel->SetProtocol(mRequestedProtocolList); |
|
796 NS_ENSURE_SUCCESS(rv, rv); |
|
797 } |
|
798 |
|
799 nsCString asciiOrigin; |
|
800 rv = nsContentUtils::GetASCIIOrigin(mPrincipal, asciiOrigin); |
|
801 NS_ENSURE_SUCCESS(rv, rv); |
|
802 |
|
803 ToLowerCase(asciiOrigin); |
|
804 |
|
805 rv = wsChannel->AsyncOpen(mURI, asciiOrigin, this, nullptr); |
|
806 NS_ENSURE_SUCCESS(rv, rv); |
|
807 |
|
808 mChannel = wsChannel; |
|
809 |
|
810 return NS_OK; |
|
811 } |
|
812 |
|
813 void |
|
814 WebSocket::DispatchConnectionCloseEvents() |
|
815 { |
|
816 mReadyState = WebSocket::CLOSED; |
|
817 |
|
818 // Call 'onerror' if needed |
|
819 if (mFailed) { |
|
820 nsresult rv = CreateAndDispatchSimpleEvent(NS_LITERAL_STRING("error")); |
|
821 if (NS_FAILED(rv)) { |
|
822 NS_WARNING("Failed to dispatch the error event"); |
|
823 } |
|
824 } |
|
825 |
|
826 nsresult rv = CreateAndDispatchCloseEvent(mCloseEventWasClean, mCloseEventCode, |
|
827 mCloseEventReason); |
|
828 if (NS_FAILED(rv)) { |
|
829 NS_WARNING("Failed to dispatch the close event"); |
|
830 } |
|
831 |
|
832 UpdateMustKeepAlive(); |
|
833 Disconnect(); |
|
834 } |
|
835 |
|
836 nsresult |
|
837 WebSocket::CreateAndDispatchSimpleEvent(const nsString& aName) |
|
838 { |
|
839 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); |
|
840 |
|
841 nsresult rv = CheckInnerWindowCorrectness(); |
|
842 if (NS_FAILED(rv)) { |
|
843 return NS_OK; |
|
844 } |
|
845 |
|
846 nsCOMPtr<nsIDOMEvent> event; |
|
847 rv = NS_NewDOMEvent(getter_AddRefs(event), this, nullptr, nullptr); |
|
848 NS_ENSURE_SUCCESS(rv, rv); |
|
849 |
|
850 // it doesn't bubble, and it isn't cancelable |
|
851 rv = event->InitEvent(aName, false, false); |
|
852 NS_ENSURE_SUCCESS(rv, rv); |
|
853 |
|
854 event->SetTrusted(true); |
|
855 |
|
856 return DispatchDOMEvent(nullptr, event, nullptr, nullptr); |
|
857 } |
|
858 |
|
859 nsresult |
|
860 WebSocket::CreateAndDispatchMessageEvent(const nsACString& aData, |
|
861 bool isBinary) |
|
862 { |
|
863 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); |
|
864 |
|
865 nsresult rv = CheckInnerWindowCorrectness(); |
|
866 if (NS_FAILED(rv)) |
|
867 return NS_OK; |
|
868 |
|
869 // Get the JSContext |
|
870 nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(GetOwner()); |
|
871 NS_ENSURE_TRUE(sgo, NS_ERROR_FAILURE); |
|
872 |
|
873 nsIScriptContext* scriptContext = sgo->GetContext(); |
|
874 NS_ENSURE_TRUE(scriptContext, NS_ERROR_FAILURE); |
|
875 |
|
876 AutoPushJSContext cx(scriptContext->GetNativeContext()); |
|
877 NS_ENSURE_TRUE(cx, NS_ERROR_FAILURE); |
|
878 |
|
879 // Create appropriate JS object for message |
|
880 JS::Rooted<JS::Value> jsData(cx); |
|
881 if (isBinary) { |
|
882 if (mBinaryType == dom::BinaryType::Blob) { |
|
883 rv = nsContentUtils::CreateBlobBuffer(cx, aData, &jsData); |
|
884 NS_ENSURE_SUCCESS(rv, rv); |
|
885 } else if (mBinaryType == dom::BinaryType::Arraybuffer) { |
|
886 JS::Rooted<JSObject*> arrayBuf(cx); |
|
887 rv = nsContentUtils::CreateArrayBuffer(cx, aData, arrayBuf.address()); |
|
888 NS_ENSURE_SUCCESS(rv, rv); |
|
889 jsData = OBJECT_TO_JSVAL(arrayBuf); |
|
890 } else { |
|
891 NS_RUNTIMEABORT("Unknown binary type!"); |
|
892 return NS_ERROR_UNEXPECTED; |
|
893 } |
|
894 } else { |
|
895 // JS string |
|
896 NS_ConvertUTF8toUTF16 utf16Data(aData); |
|
897 JSString* jsString; |
|
898 jsString = JS_NewUCStringCopyN(cx, utf16Data.get(), utf16Data.Length()); |
|
899 NS_ENSURE_TRUE(jsString, NS_ERROR_FAILURE); |
|
900 |
|
901 jsData = STRING_TO_JSVAL(jsString); |
|
902 } |
|
903 |
|
904 // create an event that uses the MessageEvent interface, |
|
905 // which does not bubble, is not cancelable, and has no default action |
|
906 |
|
907 nsCOMPtr<nsIDOMEvent> event; |
|
908 rv = NS_NewDOMMessageEvent(getter_AddRefs(event), this, nullptr, nullptr); |
|
909 NS_ENSURE_SUCCESS(rv, rv); |
|
910 |
|
911 nsCOMPtr<nsIDOMMessageEvent> messageEvent = do_QueryInterface(event); |
|
912 rv = messageEvent->InitMessageEvent(NS_LITERAL_STRING("message"), |
|
913 false, false, |
|
914 jsData, |
|
915 mUTF16Origin, |
|
916 EmptyString(), nullptr); |
|
917 NS_ENSURE_SUCCESS(rv, rv); |
|
918 |
|
919 event->SetTrusted(true); |
|
920 |
|
921 return DispatchDOMEvent(nullptr, event, nullptr, nullptr); |
|
922 } |
|
923 |
|
924 nsresult |
|
925 WebSocket::CreateAndDispatchCloseEvent(bool aWasClean, |
|
926 uint16_t aCode, |
|
927 const nsString &aReason) |
|
928 { |
|
929 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); |
|
930 |
|
931 nsresult rv = CheckInnerWindowCorrectness(); |
|
932 if (NS_FAILED(rv)) { |
|
933 return NS_OK; |
|
934 } |
|
935 |
|
936 // create an event that uses the CloseEvent interface, |
|
937 // which does not bubble, is not cancelable, and has no default action |
|
938 |
|
939 nsCOMPtr<nsIDOMEvent> event; |
|
940 rv = NS_NewDOMCloseEvent(getter_AddRefs(event), this, nullptr, nullptr); |
|
941 NS_ENSURE_SUCCESS(rv, rv); |
|
942 |
|
943 nsCOMPtr<nsIDOMCloseEvent> closeEvent = do_QueryInterface(event); |
|
944 rv = closeEvent->InitCloseEvent(NS_LITERAL_STRING("close"), |
|
945 false, false, |
|
946 aWasClean, aCode, aReason); |
|
947 NS_ENSURE_SUCCESS(rv, rv); |
|
948 |
|
949 event->SetTrusted(true); |
|
950 |
|
951 return DispatchDOMEvent(nullptr, event, nullptr, nullptr); |
|
952 } |
|
953 |
|
954 bool |
|
955 WebSocket::PrefEnabled(JSContext* aCx, JSObject* aGlobal) |
|
956 { |
|
957 return Preferences::GetBool("network.websocket.enabled", true); |
|
958 } |
|
959 |
|
960 nsresult |
|
961 WebSocket::ParseURL(const nsString& aURL) |
|
962 { |
|
963 NS_ENSURE_TRUE(!aURL.IsEmpty(), NS_ERROR_DOM_SYNTAX_ERR); |
|
964 |
|
965 nsCOMPtr<nsIURI> uri; |
|
966 nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL); |
|
967 NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR); |
|
968 |
|
969 nsCOMPtr<nsIURL> parsedURL = do_QueryInterface(uri, &rv); |
|
970 NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR); |
|
971 |
|
972 nsAutoCString fragment; |
|
973 rv = parsedURL->GetRef(fragment); |
|
974 NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && fragment.IsEmpty(), |
|
975 NS_ERROR_DOM_SYNTAX_ERR); |
|
976 |
|
977 nsAutoCString scheme; |
|
978 rv = parsedURL->GetScheme(scheme); |
|
979 NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && !scheme.IsEmpty(), |
|
980 NS_ERROR_DOM_SYNTAX_ERR); |
|
981 |
|
982 nsAutoCString host; |
|
983 rv = parsedURL->GetAsciiHost(host); |
|
984 NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && !host.IsEmpty(), NS_ERROR_DOM_SYNTAX_ERR); |
|
985 |
|
986 int32_t port; |
|
987 rv = parsedURL->GetPort(&port); |
|
988 NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR); |
|
989 |
|
990 rv = NS_CheckPortSafety(port, scheme.get()); |
|
991 NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR); |
|
992 |
|
993 nsAutoCString filePath; |
|
994 rv = parsedURL->GetFilePath(filePath); |
|
995 if (filePath.IsEmpty()) { |
|
996 filePath.AssignLiteral("/"); |
|
997 } |
|
998 NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR); |
|
999 |
|
1000 nsAutoCString query; |
|
1001 rv = parsedURL->GetQuery(query); |
|
1002 NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR); |
|
1003 |
|
1004 if (scheme.LowerCaseEqualsLiteral("ws")) { |
|
1005 mSecure = false; |
|
1006 mPort = (port == -1) ? DEFAULT_WS_SCHEME_PORT : port; |
|
1007 } else if (scheme.LowerCaseEqualsLiteral("wss")) { |
|
1008 mSecure = true; |
|
1009 mPort = (port == -1) ? DEFAULT_WSS_SCHEME_PORT : port; |
|
1010 } else { |
|
1011 return NS_ERROR_DOM_SYNTAX_ERR; |
|
1012 } |
|
1013 |
|
1014 rv = nsContentUtils::GetUTFOrigin(parsedURL, mUTF16Origin); |
|
1015 NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR); |
|
1016 |
|
1017 mAsciiHost = host; |
|
1018 ToLowerCase(mAsciiHost); |
|
1019 |
|
1020 mResource = filePath; |
|
1021 if (!query.IsEmpty()) { |
|
1022 mResource.AppendLiteral("?"); |
|
1023 mResource.Append(query); |
|
1024 } |
|
1025 uint32_t length = mResource.Length(); |
|
1026 uint32_t i; |
|
1027 for (i = 0; i < length; ++i) { |
|
1028 if (mResource[i] < static_cast<char16_t>(0x0021) || |
|
1029 mResource[i] > static_cast<char16_t>(0x007E)) { |
|
1030 return NS_ERROR_DOM_SYNTAX_ERR; |
|
1031 } |
|
1032 } |
|
1033 |
|
1034 mOriginalURL = aURL; |
|
1035 mURI = parsedURL; |
|
1036 return NS_OK; |
|
1037 } |
|
1038 |
|
1039 //----------------------------------------------------------------------------- |
|
1040 // Methods that keep alive the WebSocket object when: |
|
1041 // 1. the object has registered event listeners that can be triggered |
|
1042 // ("strong event listeners"); |
|
1043 // 2. there are outgoing not sent messages. |
|
1044 //----------------------------------------------------------------------------- |
|
1045 |
|
1046 void |
|
1047 WebSocket::UpdateMustKeepAlive() |
|
1048 { |
|
1049 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); |
|
1050 if (!mCheckMustKeepAlive) { |
|
1051 return; |
|
1052 } |
|
1053 |
|
1054 bool shouldKeepAlive = false; |
|
1055 |
|
1056 if (mListenerManager) { |
|
1057 switch (mReadyState) |
|
1058 { |
|
1059 case WebSocket::CONNECTING: |
|
1060 { |
|
1061 if (mListenerManager->HasListenersFor(nsGkAtoms::onopen) || |
|
1062 mListenerManager->HasListenersFor(nsGkAtoms::onmessage) || |
|
1063 mListenerManager->HasListenersFor(nsGkAtoms::onerror) || |
|
1064 mListenerManager->HasListenersFor(nsGkAtoms::onclose)) { |
|
1065 shouldKeepAlive = true; |
|
1066 } |
|
1067 } |
|
1068 break; |
|
1069 |
|
1070 case WebSocket::OPEN: |
|
1071 case WebSocket::CLOSING: |
|
1072 { |
|
1073 if (mListenerManager->HasListenersFor(nsGkAtoms::onmessage) || |
|
1074 mListenerManager->HasListenersFor(nsGkAtoms::onerror) || |
|
1075 mListenerManager->HasListenersFor(nsGkAtoms::onclose) || |
|
1076 mOutgoingBufferedAmount != 0) { |
|
1077 shouldKeepAlive = true; |
|
1078 } |
|
1079 } |
|
1080 break; |
|
1081 |
|
1082 case WebSocket::CLOSED: |
|
1083 { |
|
1084 shouldKeepAlive = false; |
|
1085 } |
|
1086 } |
|
1087 } |
|
1088 |
|
1089 if (mKeepingAlive && !shouldKeepAlive) { |
|
1090 mKeepingAlive = false; |
|
1091 static_cast<EventTarget*>(this)->Release(); |
|
1092 } else if (!mKeepingAlive && shouldKeepAlive) { |
|
1093 mKeepingAlive = true; |
|
1094 static_cast<EventTarget*>(this)->AddRef(); |
|
1095 } |
|
1096 } |
|
1097 |
|
1098 void |
|
1099 WebSocket::DontKeepAliveAnyMore() |
|
1100 { |
|
1101 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); |
|
1102 if (mKeepingAlive) { |
|
1103 mKeepingAlive = false; |
|
1104 static_cast<EventTarget*>(this)->Release(); |
|
1105 } |
|
1106 mCheckMustKeepAlive = false; |
|
1107 } |
|
1108 |
|
1109 nsresult |
|
1110 WebSocket::UpdateURI() |
|
1111 { |
|
1112 // Check for Redirections |
|
1113 nsCOMPtr<nsIURI> uri; |
|
1114 nsresult rv = mChannel->GetURI(getter_AddRefs(uri)); |
|
1115 NS_ENSURE_SUCCESS(rv, rv); |
|
1116 |
|
1117 nsAutoCString spec; |
|
1118 rv = uri->GetSpec(spec); |
|
1119 NS_ENSURE_SUCCESS(rv, rv); |
|
1120 CopyUTF8toUTF16(spec, mEffectiveURL); |
|
1121 |
|
1122 bool isWSS = false; |
|
1123 rv = uri->SchemeIs("wss", &isWSS); |
|
1124 NS_ENSURE_SUCCESS(rv, rv); |
|
1125 mSecure = isWSS ? true : false; |
|
1126 |
|
1127 return NS_OK; |
|
1128 } |
|
1129 |
|
1130 void |
|
1131 WebSocket::EventListenerAdded(nsIAtom* aType) |
|
1132 { |
|
1133 UpdateMustKeepAlive(); |
|
1134 } |
|
1135 |
|
1136 void |
|
1137 WebSocket::EventListenerRemoved(nsIAtom* aType) |
|
1138 { |
|
1139 UpdateMustKeepAlive(); |
|
1140 } |
|
1141 |
|
1142 //----------------------------------------------------------------------------- |
|
1143 // WebSocket - methods |
|
1144 //----------------------------------------------------------------------------- |
|
1145 |
|
1146 // webIDL: readonly attribute DOMString url |
|
1147 void |
|
1148 WebSocket::GetUrl(nsAString& aURL) |
|
1149 { |
|
1150 if (mEffectiveURL.IsEmpty()) { |
|
1151 aURL = mOriginalURL; |
|
1152 } else { |
|
1153 aURL = mEffectiveURL; |
|
1154 } |
|
1155 } |
|
1156 |
|
1157 // webIDL: readonly attribute DOMString extensions; |
|
1158 void |
|
1159 WebSocket::GetExtensions(nsAString& aExtensions) |
|
1160 { |
|
1161 CopyUTF8toUTF16(mEstablishedExtensions, aExtensions); |
|
1162 } |
|
1163 |
|
1164 // webIDL: readonly attribute DOMString protocol; |
|
1165 void |
|
1166 WebSocket::GetProtocol(nsAString& aProtocol) |
|
1167 { |
|
1168 CopyUTF8toUTF16(mEstablishedProtocol, aProtocol); |
|
1169 } |
|
1170 |
|
1171 // webIDL: void send(DOMString data); |
|
1172 void |
|
1173 WebSocket::Send(const nsAString& aData, |
|
1174 ErrorResult& aRv) |
|
1175 { |
|
1176 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); |
|
1177 |
|
1178 NS_ConvertUTF16toUTF8 msgString(aData); |
|
1179 Send(nullptr, msgString, msgString.Length(), false, aRv); |
|
1180 } |
|
1181 |
|
1182 void |
|
1183 WebSocket::Send(nsIDOMBlob* aData, |
|
1184 ErrorResult& aRv) |
|
1185 { |
|
1186 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); |
|
1187 |
|
1188 nsCOMPtr<nsIInputStream> msgStream; |
|
1189 nsresult rv = aData->GetInternalStream(getter_AddRefs(msgStream)); |
|
1190 if (NS_FAILED(rv)) { |
|
1191 aRv.Throw(rv); |
|
1192 return; |
|
1193 } |
|
1194 |
|
1195 uint64_t msgLength; |
|
1196 rv = aData->GetSize(&msgLength); |
|
1197 if (NS_FAILED(rv)) { |
|
1198 aRv.Throw(rv); |
|
1199 return; |
|
1200 } |
|
1201 |
|
1202 if (msgLength > UINT32_MAX) { |
|
1203 aRv.Throw(NS_ERROR_FILE_TOO_BIG); |
|
1204 return; |
|
1205 } |
|
1206 |
|
1207 Send(msgStream, EmptyCString(), msgLength, true, aRv); |
|
1208 } |
|
1209 |
|
1210 void |
|
1211 WebSocket::Send(const ArrayBuffer& aData, |
|
1212 ErrorResult& aRv) |
|
1213 { |
|
1214 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); |
|
1215 |
|
1216 aData.ComputeLengthAndData(); |
|
1217 |
|
1218 static_assert(sizeof(*aData.Data()) == 1, "byte-sized data required"); |
|
1219 |
|
1220 uint32_t len = aData.Length(); |
|
1221 char* data = reinterpret_cast<char*>(aData.Data()); |
|
1222 |
|
1223 nsDependentCSubstring msgString(data, len); |
|
1224 Send(nullptr, msgString, len, true, aRv); |
|
1225 } |
|
1226 |
|
1227 void |
|
1228 WebSocket::Send(const ArrayBufferView& aData, |
|
1229 ErrorResult& aRv) |
|
1230 { |
|
1231 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); |
|
1232 |
|
1233 aData.ComputeLengthAndData(); |
|
1234 |
|
1235 static_assert(sizeof(*aData.Data()) == 1, "byte-sized data required"); |
|
1236 |
|
1237 uint32_t len = aData.Length(); |
|
1238 char* data = reinterpret_cast<char*>(aData.Data()); |
|
1239 |
|
1240 nsDependentCSubstring msgString(data, len); |
|
1241 Send(nullptr, msgString, len, true, aRv); |
|
1242 } |
|
1243 |
|
1244 void |
|
1245 WebSocket::Send(nsIInputStream* aMsgStream, |
|
1246 const nsACString& aMsgString, |
|
1247 uint32_t aMsgLength, |
|
1248 bool aIsBinary, |
|
1249 ErrorResult& aRv) |
|
1250 { |
|
1251 if (mReadyState == WebSocket::CONNECTING) { |
|
1252 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
|
1253 return; |
|
1254 } |
|
1255 |
|
1256 // Always increment outgoing buffer len, even if closed |
|
1257 mOutgoingBufferedAmount += aMsgLength; |
|
1258 |
|
1259 if (mReadyState == WebSocket::CLOSING || |
|
1260 mReadyState == WebSocket::CLOSED) { |
|
1261 return; |
|
1262 } |
|
1263 |
|
1264 MOZ_ASSERT(mReadyState == WebSocket::OPEN, |
|
1265 "Unknown state in WebSocket::Send"); |
|
1266 |
|
1267 nsresult rv; |
|
1268 if (aMsgStream) { |
|
1269 rv = mChannel->SendBinaryStream(aMsgStream, aMsgLength); |
|
1270 } else { |
|
1271 if (aIsBinary) { |
|
1272 rv = mChannel->SendBinaryMsg(aMsgString); |
|
1273 } else { |
|
1274 rv = mChannel->SendMsg(aMsgString); |
|
1275 } |
|
1276 } |
|
1277 |
|
1278 if (NS_FAILED(rv)) { |
|
1279 aRv.Throw(rv); |
|
1280 return; |
|
1281 } |
|
1282 |
|
1283 UpdateMustKeepAlive(); |
|
1284 } |
|
1285 |
|
1286 // webIDL: void close(optional unsigned short code, optional DOMString reason): |
|
1287 void |
|
1288 WebSocket::Close(const Optional<uint16_t>& aCode, |
|
1289 const Optional<nsAString>& aReason, |
|
1290 ErrorResult& aRv) |
|
1291 { |
|
1292 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); |
|
1293 |
|
1294 // the reason code is optional, but if provided it must be in a specific range |
|
1295 uint16_t closeCode = 0; |
|
1296 if (aCode.WasPassed()) { |
|
1297 if (aCode.Value() != 1000 && (aCode.Value() < 3000 || aCode.Value() > 4999)) { |
|
1298 aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR); |
|
1299 return; |
|
1300 } |
|
1301 closeCode = aCode.Value(); |
|
1302 } |
|
1303 |
|
1304 nsCString closeReason; |
|
1305 if (aReason.WasPassed()) { |
|
1306 CopyUTF16toUTF8(aReason.Value(), closeReason); |
|
1307 |
|
1308 // The API requires the UTF-8 string to be 123 or less bytes |
|
1309 if (closeReason.Length() > 123) { |
|
1310 aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); |
|
1311 return; |
|
1312 } |
|
1313 } |
|
1314 |
|
1315 if (mReadyState == WebSocket::CLOSING || |
|
1316 mReadyState == WebSocket::CLOSED) { |
|
1317 return; |
|
1318 } |
|
1319 |
|
1320 if (mReadyState == WebSocket::CONNECTING) { |
|
1321 FailConnection(closeCode, closeReason); |
|
1322 return; |
|
1323 } |
|
1324 |
|
1325 MOZ_ASSERT(mReadyState == WebSocket::OPEN); |
|
1326 CloseConnection(closeCode, closeReason); |
|
1327 } |
|
1328 |
|
1329 //----------------------------------------------------------------------------- |
|
1330 // WebSocket::nsIObserver |
|
1331 //----------------------------------------------------------------------------- |
|
1332 |
|
1333 NS_IMETHODIMP |
|
1334 WebSocket::Observe(nsISupports* aSubject, |
|
1335 const char* aTopic, |
|
1336 const char16_t* aData) |
|
1337 { |
|
1338 if ((mReadyState == WebSocket::CLOSING) || |
|
1339 (mReadyState == WebSocket::CLOSED)) { |
|
1340 return NS_OK; |
|
1341 } |
|
1342 |
|
1343 nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aSubject); |
|
1344 if (!GetOwner() || window != GetOwner()) { |
|
1345 return NS_OK; |
|
1346 } |
|
1347 |
|
1348 if ((strcmp(aTopic, DOM_WINDOW_FROZEN_TOPIC) == 0) || |
|
1349 (strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC) == 0)) |
|
1350 { |
|
1351 CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY); |
|
1352 } |
|
1353 |
|
1354 return NS_OK; |
|
1355 } |
|
1356 |
|
1357 //----------------------------------------------------------------------------- |
|
1358 // WebSocket::nsIRequest |
|
1359 //----------------------------------------------------------------------------- |
|
1360 |
|
1361 NS_IMETHODIMP |
|
1362 WebSocket::GetName(nsACString& aName) |
|
1363 { |
|
1364 CopyUTF16toUTF8(mOriginalURL, aName); |
|
1365 return NS_OK; |
|
1366 } |
|
1367 |
|
1368 NS_IMETHODIMP |
|
1369 WebSocket::IsPending(bool* aValue) |
|
1370 { |
|
1371 *aValue = (mReadyState != WebSocket::CLOSED); |
|
1372 return NS_OK; |
|
1373 } |
|
1374 |
|
1375 NS_IMETHODIMP |
|
1376 WebSocket::GetStatus(nsresult* aStatus) |
|
1377 { |
|
1378 *aStatus = NS_OK; |
|
1379 return NS_OK; |
|
1380 } |
|
1381 |
|
1382 // Window closed, stop/reload button pressed, user navigated away from page, etc. |
|
1383 NS_IMETHODIMP |
|
1384 WebSocket::Cancel(nsresult aStatus) |
|
1385 { |
|
1386 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); |
|
1387 |
|
1388 if (mReadyState == CLOSING || mReadyState == CLOSED) { |
|
1389 return NS_OK; |
|
1390 } |
|
1391 |
|
1392 ConsoleError(); |
|
1393 |
|
1394 return CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY); |
|
1395 } |
|
1396 |
|
1397 NS_IMETHODIMP |
|
1398 WebSocket::Suspend() |
|
1399 { |
|
1400 return NS_ERROR_NOT_IMPLEMENTED; |
|
1401 } |
|
1402 |
|
1403 NS_IMETHODIMP |
|
1404 WebSocket::Resume() |
|
1405 { |
|
1406 return NS_ERROR_NOT_IMPLEMENTED; |
|
1407 } |
|
1408 |
|
1409 NS_IMETHODIMP |
|
1410 WebSocket::GetLoadGroup(nsILoadGroup** aLoadGroup) |
|
1411 { |
|
1412 *aLoadGroup = nullptr; |
|
1413 |
|
1414 nsresult rv; |
|
1415 nsIScriptContext* sc = GetContextForEventHandlers(&rv); |
|
1416 nsCOMPtr<nsIDocument> doc = |
|
1417 nsContentUtils::GetDocumentFromScriptContext(sc); |
|
1418 |
|
1419 if (doc) { |
|
1420 *aLoadGroup = doc->GetDocumentLoadGroup().take(); |
|
1421 } |
|
1422 |
|
1423 return NS_OK; |
|
1424 } |
|
1425 |
|
1426 NS_IMETHODIMP |
|
1427 WebSocket::SetLoadGroup(nsILoadGroup* aLoadGroup) |
|
1428 { |
|
1429 return NS_ERROR_UNEXPECTED; |
|
1430 } |
|
1431 |
|
1432 NS_IMETHODIMP |
|
1433 WebSocket::GetLoadFlags(nsLoadFlags* aLoadFlags) |
|
1434 { |
|
1435 *aLoadFlags = nsIRequest::LOAD_BACKGROUND; |
|
1436 return NS_OK; |
|
1437 } |
|
1438 |
|
1439 NS_IMETHODIMP |
|
1440 WebSocket::SetLoadFlags(nsLoadFlags aLoadFlags) |
|
1441 { |
|
1442 // we won't change the load flags at all. |
|
1443 return NS_OK; |
|
1444 } |
|
1445 |
|
1446 } // dom namespace |
|
1447 } // mozilla namespace |