michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set sw=2 ts=8 et tw=80 : */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "WebSocket.h" michael@0: #include "mozilla/dom/WebSocketBinding.h" michael@0: michael@0: #include "jsapi.h" michael@0: #include "jsfriendapi.h" michael@0: #include "js/OldDebugAPI.h" michael@0: #include "mozilla/DOMEventTargetHelper.h" michael@0: #include "nsIScriptGlobalObject.h" michael@0: #include "nsIDOMWindow.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsXPCOM.h" michael@0: #include "nsIXPConnect.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsCxPusher.h" michael@0: #include "nsError.h" michael@0: #include "nsIScriptObjectPrincipal.h" michael@0: #include "nsIURL.h" michael@0: #include "nsIUnicodeEncoder.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsIDOMMessageEvent.h" michael@0: #include "nsIPromptFactory.h" michael@0: #include "nsIWindowWatcher.h" michael@0: #include "nsIPrompt.h" michael@0: #include "nsIStringBundle.h" michael@0: #include "nsIConsoleService.h" michael@0: #include "nsIDOMCloseEvent.h" michael@0: #include "nsICryptoHash.h" michael@0: #include "nsJSUtils.h" michael@0: #include "nsIScriptError.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsILoadGroup.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "xpcpublic.h" michael@0: #include "nsContentPolicyUtils.h" michael@0: #include "nsDOMFile.h" michael@0: #include "nsWrapperCacheInlines.h" michael@0: #include "nsIObserverService.h" michael@0: #include "nsIWebSocketChannel.h" michael@0: #include "GeneratedEvents.h" michael@0: michael@0: namespace mozilla { michael@0: namespace dom { michael@0: michael@0: #define UTF_8_REPLACEMENT_CHAR static_cast(0xFFFD) michael@0: michael@0: class CallDispatchConnectionCloseEvents: public nsRunnable michael@0: { michael@0: public: michael@0: CallDispatchConnectionCloseEvents(WebSocket* aWebSocket) michael@0: : mWebSocket(aWebSocket) michael@0: {} michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: mWebSocket->DispatchConnectionCloseEvents(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: nsRefPtr mWebSocket; michael@0: }; michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // WebSocket michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: nsresult michael@0: WebSocket::PrintErrorOnConsole(const char *aBundleURI, michael@0: const char16_t *aError, michael@0: const char16_t **aFormatStrings, michael@0: uint32_t aFormatStringsLen) michael@0: { michael@0: NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); michael@0: michael@0: nsresult rv; michael@0: nsCOMPtr bundleService = michael@0: do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr strBundle; michael@0: rv = bundleService->CreateBundle(aBundleURI, getter_AddRefs(strBundle)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr console( michael@0: do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr errorObject( michael@0: do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Localize the error message michael@0: nsXPIDLString message; michael@0: if (aFormatStrings) { michael@0: rv = strBundle->FormatStringFromName(aError, aFormatStrings, michael@0: aFormatStringsLen, michael@0: getter_Copies(message)); michael@0: } else { michael@0: rv = strBundle->GetStringFromName(aError, getter_Copies(message)); michael@0: } michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = errorObject->InitWithWindowID(message, michael@0: NS_ConvertUTF8toUTF16(mScriptFile), michael@0: EmptyString(), mScriptLine, 0, michael@0: nsIScriptError::errorFlag, "Web Socket", michael@0: mInnerWindowID); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // print the error message directly to the JS console michael@0: rv = console->LogMessage(errorObject); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: WebSocket::CloseConnection(uint16_t aReasonCode, michael@0: const nsACString& aReasonString) michael@0: { michael@0: NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); michael@0: if (mReadyState == WebSocket::CLOSING || michael@0: mReadyState == WebSocket::CLOSED) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // The common case... michael@0: if (mChannel) { michael@0: mReadyState = WebSocket::CLOSING; michael@0: return mChannel->Close(aReasonCode, aReasonString); michael@0: } michael@0: michael@0: // No channel, but not disconnected: canceled or failed early michael@0: // michael@0: MOZ_ASSERT(mReadyState == WebSocket::CONNECTING, michael@0: "Should only get here for early websocket cancel/error"); michael@0: michael@0: // Server won't be sending us a close code, so use what's passed in here. michael@0: mCloseEventCode = aReasonCode; michael@0: CopyUTF8toUTF16(aReasonString, mCloseEventReason); michael@0: michael@0: mReadyState = WebSocket::CLOSING; michael@0: michael@0: // Can be called from Cancel() or Init() codepaths, so need to dispatch michael@0: // onerror/onclose asynchronously michael@0: ScheduleConnectionCloseEvents( michael@0: nullptr, michael@0: (aReasonCode == nsIWebSocketChannel::CLOSE_NORMAL || michael@0: aReasonCode == nsIWebSocketChannel::CLOSE_GOING_AWAY) ? michael@0: NS_OK : NS_ERROR_FAILURE, michael@0: false); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: WebSocket::ConsoleError() michael@0: { michael@0: NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); michael@0: michael@0: nsAutoCString targetSpec; michael@0: nsresult rv = mURI->GetSpec(targetSpec); michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("Failed to get targetSpec"); michael@0: } else { michael@0: NS_ConvertUTF8toUTF16 specUTF16(targetSpec); michael@0: const char16_t* formatStrings[] = { specUTF16.get() }; michael@0: michael@0: if (mReadyState < WebSocket::OPEN) { michael@0: PrintErrorOnConsole("chrome://global/locale/appstrings.properties", michael@0: MOZ_UTF16("connectionFailure"), michael@0: formatStrings, ArrayLength(formatStrings)); michael@0: } else { michael@0: PrintErrorOnConsole("chrome://global/locale/appstrings.properties", michael@0: MOZ_UTF16("netInterrupt"), michael@0: formatStrings, ArrayLength(formatStrings)); michael@0: } michael@0: } michael@0: /// todo some specific errors - like for message too large michael@0: return rv; michael@0: } michael@0: michael@0: michael@0: void michael@0: WebSocket::FailConnection(uint16_t aReasonCode, michael@0: const nsACString& aReasonString) michael@0: { michael@0: NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); michael@0: michael@0: ConsoleError(); michael@0: mFailed = true; michael@0: CloseConnection(aReasonCode, aReasonString); michael@0: } michael@0: michael@0: nsresult michael@0: WebSocket::Disconnect() michael@0: { michael@0: NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); michael@0: michael@0: if (mDisconnected) michael@0: return NS_OK; michael@0: michael@0: nsCOMPtr loadGroup; michael@0: GetLoadGroup(getter_AddRefs(loadGroup)); michael@0: if (loadGroup) michael@0: loadGroup->RemoveRequest(this, nullptr, NS_OK); michael@0: michael@0: nsCOMPtr os = mozilla::services::GetObserverService(); michael@0: if (os) { michael@0: os->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC); michael@0: os->RemoveObserver(this, DOM_WINDOW_FROZEN_TOPIC); michael@0: } michael@0: michael@0: // DontKeepAliveAnyMore() can release the object. So hold a reference to this michael@0: // until the end of the method. michael@0: nsRefPtr kungfuDeathGrip = this; michael@0: michael@0: DontKeepAliveAnyMore(); michael@0: mChannel = nullptr; michael@0: mDisconnected = true; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // WebSocket::nsIWebSocketListener methods: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: nsresult michael@0: WebSocket::DoOnMessageAvailable(const nsACString& aMsg, bool isBinary) michael@0: { michael@0: NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); michael@0: michael@0: if (mReadyState == WebSocket::CLOSED) { michael@0: NS_ERROR("Received message after CLOSED"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: if (mReadyState == WebSocket::OPEN) { michael@0: // Dispatch New Message michael@0: nsresult rv = CreateAndDispatchMessageEvent(aMsg, isBinary); michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("Failed to dispatch the message event"); michael@0: } michael@0: } else { michael@0: // CLOSING should be the only other state where it's possible to get msgs michael@0: // from channel: Spec says to drop them. michael@0: MOZ_ASSERT(mReadyState == WebSocket::CLOSING, michael@0: "Received message while CONNECTING or CLOSED"); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: WebSocket::OnMessageAvailable(nsISupports* aContext, const nsACString& aMsg) michael@0: { michael@0: return DoOnMessageAvailable(aMsg, false); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: WebSocket::OnBinaryMessageAvailable(nsISupports* aContext, michael@0: const nsACString& aMsg) michael@0: { michael@0: return DoOnMessageAvailable(aMsg, true); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: WebSocket::OnStart(nsISupports* aContext) michael@0: { michael@0: NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); michael@0: michael@0: // This is the only function that sets OPEN, and should be called only once michael@0: MOZ_ASSERT(mReadyState != WebSocket::OPEN, michael@0: "readyState already OPEN! OnStart called twice?"); michael@0: michael@0: // Nothing to do if we've already closed/closing michael@0: if (mReadyState != WebSocket::CONNECTING) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Attempt to kill "ghost" websocket: but usually too early for check to fail michael@0: nsresult rv = CheckInnerWindowCorrectness(); michael@0: if (NS_FAILED(rv)) { michael@0: CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY); michael@0: return rv; michael@0: } michael@0: michael@0: if (!mRequestedProtocolList.IsEmpty()) { michael@0: mChannel->GetProtocol(mEstablishedProtocol); michael@0: } michael@0: michael@0: mChannel->GetExtensions(mEstablishedExtensions); michael@0: UpdateURI(); michael@0: michael@0: mReadyState = WebSocket::OPEN; michael@0: michael@0: // Call 'onopen' michael@0: rv = CreateAndDispatchSimpleEvent(NS_LITERAL_STRING("open")); michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("Failed to dispatch the open event"); michael@0: } michael@0: michael@0: UpdateMustKeepAlive(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: WebSocket::OnStop(nsISupports* aContext, nsresult aStatusCode) michael@0: { michael@0: // We can be CONNECTING here if connection failed. michael@0: // We can be OPEN if we have encountered a fatal protocol error michael@0: // We can be CLOSING if close() was called and/or server initiated close. michael@0: MOZ_ASSERT(mReadyState != WebSocket::CLOSED, michael@0: "Shouldn't already be CLOSED when OnStop called"); michael@0: michael@0: // called by network stack, not JS, so can dispatch JS events synchronously michael@0: return ScheduleConnectionCloseEvents(aContext, aStatusCode, true); michael@0: } michael@0: michael@0: nsresult michael@0: WebSocket::ScheduleConnectionCloseEvents(nsISupports* aContext, michael@0: nsresult aStatusCode, michael@0: bool sync) michael@0: { michael@0: NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); michael@0: michael@0: // no-op if some other code has already initiated close event michael@0: if (!mOnCloseScheduled) { michael@0: mCloseEventWasClean = NS_SUCCEEDED(aStatusCode); michael@0: michael@0: if (aStatusCode == NS_BASE_STREAM_CLOSED) { michael@0: // don't generate an error event just because of an unclean close michael@0: aStatusCode = NS_OK; michael@0: } michael@0: michael@0: if (NS_FAILED(aStatusCode)) { michael@0: ConsoleError(); michael@0: mFailed = true; michael@0: } michael@0: michael@0: mOnCloseScheduled = true; michael@0: michael@0: if (sync) { michael@0: DispatchConnectionCloseEvents(); michael@0: } else { michael@0: NS_DispatchToMainThread(new CallDispatchConnectionCloseEvents(this), michael@0: NS_DISPATCH_NORMAL); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: WebSocket::OnAcknowledge(nsISupports *aContext, uint32_t aSize) michael@0: { michael@0: NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); michael@0: michael@0: if (aSize > mOutgoingBufferedAmount) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: mOutgoingBufferedAmount -= aSize; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: WebSocket::OnServerClose(nsISupports *aContext, uint16_t aCode, michael@0: const nsACString &aReason) michael@0: { michael@0: NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); michael@0: michael@0: MOZ_ASSERT(mReadyState != WebSocket::CONNECTING, michael@0: "Received server close before connected?"); michael@0: MOZ_ASSERT(mReadyState != WebSocket::CLOSED, michael@0: "Received server close after already closed!"); michael@0: michael@0: // store code/string for onclose DOM event michael@0: mCloseEventCode = aCode; michael@0: CopyUTF8toUTF16(aReason, mCloseEventReason); michael@0: michael@0: if (mReadyState == WebSocket::OPEN) { michael@0: // Server initiating close. michael@0: // RFC 6455, 5.5.1: "When sending a Close frame in response, the endpoint michael@0: // typically echos the status code it received". michael@0: // But never send certain codes, per section 7.4.1 michael@0: if (aCode == 1005 || aCode == 1006 || aCode == 1015) { michael@0: CloseConnection(0, EmptyCString()); michael@0: } else { michael@0: CloseConnection(aCode, aReason); michael@0: } michael@0: } else { michael@0: // We initiated close, and server has replied: OnStop does rest of the work. michael@0: MOZ_ASSERT(mReadyState == WebSocket::CLOSING, "unknown state"); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // WebSocket::nsIInterfaceRequestor michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: WebSocket::GetInterface(const nsIID& aIID, void** aResult) michael@0: { michael@0: NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); michael@0: michael@0: if (mReadyState == WebSocket::CLOSED) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: if (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) || michael@0: aIID.Equals(NS_GET_IID(nsIAuthPrompt2))) { michael@0: nsresult rv; michael@0: nsIScriptContext* sc = GetContextForEventHandlers(&rv); michael@0: nsCOMPtr doc = michael@0: nsContentUtils::GetDocumentFromScriptContext(sc); michael@0: if (!doc) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: nsCOMPtr wwatch = michael@0: do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr outerWindow = doc->GetWindow(); michael@0: return wwatch->GetPrompt(outerWindow, aIID, aResult); michael@0: } michael@0: michael@0: return QueryInterface(aIID, aResult); michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // WebSocket michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: WebSocket::WebSocket(nsPIDOMWindow* aOwnerWindow) michael@0: : DOMEventTargetHelper(aOwnerWindow), michael@0: mKeepingAlive(false), michael@0: mCheckMustKeepAlive(true), michael@0: mOnCloseScheduled(false), michael@0: mFailed(false), michael@0: mDisconnected(false), michael@0: mCloseEventWasClean(false), michael@0: mCloseEventCode(nsIWebSocketChannel::CLOSE_ABNORMAL), michael@0: mReadyState(WebSocket::CONNECTING), michael@0: mOutgoingBufferedAmount(0), michael@0: mBinaryType(dom::BinaryType::Blob), michael@0: mScriptLine(0), michael@0: mInnerWindowID(0) michael@0: { michael@0: NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); michael@0: MOZ_ASSERT(aOwnerWindow); michael@0: MOZ_ASSERT(aOwnerWindow->IsInnerWindow()); michael@0: } michael@0: michael@0: WebSocket::~WebSocket() michael@0: { michael@0: NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); michael@0: michael@0: // If we threw during Init we never called disconnect michael@0: if (!mDisconnected) { michael@0: Disconnect(); michael@0: } michael@0: } michael@0: michael@0: JSObject* michael@0: WebSocket::WrapObject(JSContext* cx) michael@0: { michael@0: return WebSocketBinding::Wrap(cx, this); michael@0: } michael@0: michael@0: //--------------------------------------------------------------------------- michael@0: // WebIDL michael@0: //--------------------------------------------------------------------------- michael@0: michael@0: // Constructor: michael@0: already_AddRefed michael@0: WebSocket::Constructor(const GlobalObject& aGlobal, michael@0: const nsAString& aUrl, michael@0: ErrorResult& aRv) michael@0: { michael@0: Sequence protocols; michael@0: return WebSocket::Constructor(aGlobal, aUrl, protocols, aRv); michael@0: } michael@0: michael@0: already_AddRefed michael@0: WebSocket::Constructor(const GlobalObject& aGlobal, michael@0: const nsAString& aUrl, michael@0: const nsAString& aProtocol, michael@0: ErrorResult& aRv) michael@0: { michael@0: Sequence protocols; michael@0: protocols.AppendElement(aProtocol); michael@0: return WebSocket::Constructor(aGlobal, aUrl, protocols, aRv); michael@0: } michael@0: michael@0: already_AddRefed michael@0: WebSocket::Constructor(const GlobalObject& aGlobal, michael@0: const nsAString& aUrl, michael@0: const Sequence& aProtocols, michael@0: ErrorResult& aRv) michael@0: { michael@0: if (!PrefEnabled()) { michael@0: aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); michael@0: return nullptr; michael@0: } michael@0: michael@0: nsCOMPtr scriptPrincipal = michael@0: do_QueryInterface(aGlobal.GetAsSupports()); michael@0: if (!scriptPrincipal) { michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: return nullptr; michael@0: } michael@0: michael@0: nsCOMPtr principal = scriptPrincipal->GetPrincipal(); michael@0: if (!principal) { michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: return nullptr; michael@0: } michael@0: michael@0: nsCOMPtr sgo = do_QueryInterface(aGlobal.GetAsSupports()); michael@0: if (!sgo) { michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: return nullptr; michael@0: } michael@0: michael@0: nsCOMPtr ownerWindow = do_QueryInterface(aGlobal.GetAsSupports()); michael@0: if (!ownerWindow) { michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: return nullptr; michael@0: } michael@0: michael@0: nsTArray protocolArray; michael@0: michael@0: for (uint32_t index = 0, len = aProtocols.Length(); index < len; ++index) { michael@0: michael@0: const nsString& protocolElement = aProtocols[index]; michael@0: michael@0: if (protocolElement.IsEmpty()) { michael@0: aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); michael@0: return nullptr; michael@0: } michael@0: if (protocolArray.Contains(protocolElement)) { michael@0: aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); michael@0: return nullptr; michael@0: } michael@0: if (protocolElement.FindChar(',') != -1) /* interferes w/list */ { michael@0: aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); michael@0: return nullptr; michael@0: } michael@0: michael@0: protocolArray.AppendElement(protocolElement); michael@0: } michael@0: michael@0: nsRefPtr webSocket = new WebSocket(ownerWindow); michael@0: nsresult rv = webSocket->Init(aGlobal.GetContext(), principal, michael@0: aUrl, protocolArray); michael@0: if (NS_FAILED(rv)) { michael@0: aRv.Throw(rv); michael@0: return nullptr; michael@0: } michael@0: michael@0: return webSocket.forget(); michael@0: } michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CLASS(WebSocket) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(WebSocket) michael@0: bool isBlack = tmp->IsBlack(); michael@0: if (isBlack|| tmp->mKeepingAlive) { michael@0: if (tmp->mListenerManager) { michael@0: tmp->mListenerManager->MarkForCC(); michael@0: } michael@0: if (!isBlack && tmp->PreservingWrapper()) { michael@0: // This marks the wrapper black. michael@0: tmp->GetWrapper(); michael@0: } michael@0: return true; michael@0: } michael@0: NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(WebSocket) michael@0: return tmp->IsBlack(); michael@0: NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(WebSocket) michael@0: return tmp->IsBlack(); michael@0: NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(WebSocket, michael@0: DOMEventTargetHelper) michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(WebSocket, michael@0: DOMEventTargetHelper) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrincipal) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mURI) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChannel) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(WebSocket, michael@0: DOMEventTargetHelper) michael@0: tmp->Disconnect(); michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrincipal) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mURI) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mChannel) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_END michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(WebSocket) michael@0: NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) michael@0: NS_INTERFACE_MAP_ENTRY(nsIWebSocketListener) michael@0: NS_INTERFACE_MAP_ENTRY(nsIObserver) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) michael@0: NS_INTERFACE_MAP_ENTRY(nsIRequest) michael@0: NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) michael@0: michael@0: NS_IMPL_ADDREF_INHERITED(WebSocket, DOMEventTargetHelper) michael@0: NS_IMPL_RELEASE_INHERITED(WebSocket, DOMEventTargetHelper) michael@0: michael@0: void michael@0: WebSocket::DisconnectFromOwner() michael@0: { michael@0: DOMEventTargetHelper::DisconnectFromOwner(); michael@0: CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY); michael@0: DontKeepAliveAnyMore(); michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // WebSocket:: initialization michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: nsresult michael@0: WebSocket::Init(JSContext* aCx, michael@0: nsIPrincipal* aPrincipal, michael@0: const nsAString& aURL, michael@0: nsTArray& aProtocolArray) michael@0: { michael@0: NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); michael@0: MOZ_ASSERT(aPrincipal); michael@0: michael@0: if (!PrefEnabled()) { michael@0: return NS_ERROR_DOM_SECURITY_ERR; michael@0: } michael@0: michael@0: mPrincipal = aPrincipal; michael@0: michael@0: // Attempt to kill "ghost" websocket: but usually too early for check to fail michael@0: nsresult rv = CheckInnerWindowCorrectness(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Shut down websocket if window is frozen or destroyed (only needed for michael@0: // "ghost" websockets--see bug 696085) michael@0: nsCOMPtr os = mozilla::services::GetObserverService(); michael@0: NS_ENSURE_STATE(os); michael@0: rv = os->AddObserver(this, DOM_WINDOW_DESTROYED_TOPIC, true); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = os->AddObserver(this, DOM_WINDOW_FROZEN_TOPIC, true); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: unsigned lineno; michael@0: JS::AutoFilename file; michael@0: if (JS::DescribeScriptedCaller(aCx, &file, &lineno)) { michael@0: mScriptFile = file.get(); michael@0: mScriptLine = lineno; michael@0: } michael@0: michael@0: // Get WindowID michael@0: mInnerWindowID = nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(aCx); michael@0: michael@0: // parses the url michael@0: rv = ParseURL(PromiseFlatString(aURL)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsIScriptContext* sc = GetContextForEventHandlers(&rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr originDoc = nsContentUtils::GetDocumentFromScriptContext(sc); michael@0: michael@0: // Don't allow https:// to open ws:// michael@0: if (!mSecure && michael@0: !Preferences::GetBool("network.websocket.allowInsecureFromHTTPS", michael@0: false)) { michael@0: // Confirmed we are opening plain ws:// and want to prevent this from a michael@0: // secure context (e.g. https). Check the security context of the document michael@0: // associated with this script, which is the same as associated with mOwner. michael@0: if (originDoc && originDoc->GetSecurityInfo()) { michael@0: return NS_ERROR_DOM_SECURITY_ERR; michael@0: } michael@0: } michael@0: michael@0: // Assign the sub protocol list and scan it for illegal values michael@0: for (uint32_t index = 0; index < aProtocolArray.Length(); ++index) { michael@0: for (uint32_t i = 0; i < aProtocolArray[index].Length(); ++i) { michael@0: if (aProtocolArray[index][i] < static_cast(0x0021) || michael@0: aProtocolArray[index][i] > static_cast(0x007E)) michael@0: return NS_ERROR_DOM_SYNTAX_ERR; michael@0: } michael@0: michael@0: if (!mRequestedProtocolList.IsEmpty()) { michael@0: mRequestedProtocolList.Append(NS_LITERAL_CSTRING(", ")); michael@0: } michael@0: michael@0: AppendUTF16toUTF8(aProtocolArray[index], mRequestedProtocolList); michael@0: } michael@0: michael@0: // Check content policy. michael@0: int16_t shouldLoad = nsIContentPolicy::ACCEPT; michael@0: rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_WEBSOCKET, michael@0: mURI, michael@0: mPrincipal, michael@0: originDoc, michael@0: EmptyCString(), michael@0: nullptr, michael@0: &shouldLoad, michael@0: nsContentUtils::GetContentPolicy(), michael@0: nsContentUtils::GetSecurityManager()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (NS_CP_REJECTED(shouldLoad)) { michael@0: // Disallowed by content policy. michael@0: return NS_ERROR_CONTENT_BLOCKED; michael@0: } michael@0: michael@0: // the constructor should throw a SYNTAX_ERROR only if it fails to parse the michael@0: // url parameter, so don't throw if EstablishConnection fails, and call michael@0: // onerror/onclose asynchronously michael@0: if (NS_FAILED(EstablishConnection())) { michael@0: FailConnection(nsIWebSocketChannel::CLOSE_ABNORMAL); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // WebSocket methods: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: class nsAutoCloseWS michael@0: { michael@0: public: michael@0: nsAutoCloseWS(WebSocket* aWebSocket) michael@0: : mWebSocket(aWebSocket) michael@0: {} michael@0: michael@0: ~nsAutoCloseWS() michael@0: { michael@0: if (!mWebSocket->mChannel) { michael@0: mWebSocket->CloseConnection(nsIWebSocketChannel::CLOSE_INTERNAL_ERROR); michael@0: } michael@0: } michael@0: private: michael@0: nsRefPtr mWebSocket; michael@0: }; michael@0: michael@0: nsresult michael@0: WebSocket::EstablishConnection() michael@0: { michael@0: NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); michael@0: NS_ABORT_IF_FALSE(!mChannel, "mChannel should be null"); michael@0: michael@0: nsCOMPtr wsChannel; michael@0: nsAutoCloseWS autoClose(this); michael@0: nsresult rv; michael@0: michael@0: if (mSecure) { michael@0: wsChannel = michael@0: do_CreateInstance("@mozilla.org/network/protocol;1?name=wss", &rv); michael@0: } else { michael@0: wsChannel = michael@0: do_CreateInstance("@mozilla.org/network/protocol;1?name=ws", &rv); michael@0: } michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = wsChannel->SetNotificationCallbacks(this); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // add ourselves to the document's load group and michael@0: // provide the http stack the loadgroup info too michael@0: nsCOMPtr loadGroup; michael@0: rv = GetLoadGroup(getter_AddRefs(loadGroup)); michael@0: if (loadGroup) { michael@0: rv = wsChannel->SetLoadGroup(loadGroup); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = loadGroup->AddRequest(this, nullptr); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: if (!mRequestedProtocolList.IsEmpty()) { michael@0: rv = wsChannel->SetProtocol(mRequestedProtocolList); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: nsCString asciiOrigin; michael@0: rv = nsContentUtils::GetASCIIOrigin(mPrincipal, asciiOrigin); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: ToLowerCase(asciiOrigin); michael@0: michael@0: rv = wsChannel->AsyncOpen(mURI, asciiOrigin, this, nullptr); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mChannel = wsChannel; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: WebSocket::DispatchConnectionCloseEvents() michael@0: { michael@0: mReadyState = WebSocket::CLOSED; michael@0: michael@0: // Call 'onerror' if needed michael@0: if (mFailed) { michael@0: nsresult rv = CreateAndDispatchSimpleEvent(NS_LITERAL_STRING("error")); michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("Failed to dispatch the error event"); michael@0: } michael@0: } michael@0: michael@0: nsresult rv = CreateAndDispatchCloseEvent(mCloseEventWasClean, mCloseEventCode, michael@0: mCloseEventReason); michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("Failed to dispatch the close event"); michael@0: } michael@0: michael@0: UpdateMustKeepAlive(); michael@0: Disconnect(); michael@0: } michael@0: michael@0: nsresult michael@0: WebSocket::CreateAndDispatchSimpleEvent(const nsString& aName) michael@0: { michael@0: NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); michael@0: michael@0: nsresult rv = CheckInnerWindowCorrectness(); michael@0: if (NS_FAILED(rv)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr event; michael@0: rv = NS_NewDOMEvent(getter_AddRefs(event), this, nullptr, nullptr); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // it doesn't bubble, and it isn't cancelable michael@0: rv = event->InitEvent(aName, false, false); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: event->SetTrusted(true); michael@0: michael@0: return DispatchDOMEvent(nullptr, event, nullptr, nullptr); michael@0: } michael@0: michael@0: nsresult michael@0: WebSocket::CreateAndDispatchMessageEvent(const nsACString& aData, michael@0: bool isBinary) michael@0: { michael@0: NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); michael@0: michael@0: nsresult rv = CheckInnerWindowCorrectness(); michael@0: if (NS_FAILED(rv)) michael@0: return NS_OK; michael@0: michael@0: // Get the JSContext michael@0: nsCOMPtr sgo = do_QueryInterface(GetOwner()); michael@0: NS_ENSURE_TRUE(sgo, NS_ERROR_FAILURE); michael@0: michael@0: nsIScriptContext* scriptContext = sgo->GetContext(); michael@0: NS_ENSURE_TRUE(scriptContext, NS_ERROR_FAILURE); michael@0: michael@0: AutoPushJSContext cx(scriptContext->GetNativeContext()); michael@0: NS_ENSURE_TRUE(cx, NS_ERROR_FAILURE); michael@0: michael@0: // Create appropriate JS object for message michael@0: JS::Rooted jsData(cx); michael@0: if (isBinary) { michael@0: if (mBinaryType == dom::BinaryType::Blob) { michael@0: rv = nsContentUtils::CreateBlobBuffer(cx, aData, &jsData); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } else if (mBinaryType == dom::BinaryType::Arraybuffer) { michael@0: JS::Rooted arrayBuf(cx); michael@0: rv = nsContentUtils::CreateArrayBuffer(cx, aData, arrayBuf.address()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: jsData = OBJECT_TO_JSVAL(arrayBuf); michael@0: } else { michael@0: NS_RUNTIMEABORT("Unknown binary type!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: } else { michael@0: // JS string michael@0: NS_ConvertUTF8toUTF16 utf16Data(aData); michael@0: JSString* jsString; michael@0: jsString = JS_NewUCStringCopyN(cx, utf16Data.get(), utf16Data.Length()); michael@0: NS_ENSURE_TRUE(jsString, NS_ERROR_FAILURE); michael@0: michael@0: jsData = STRING_TO_JSVAL(jsString); michael@0: } michael@0: michael@0: // create an event that uses the MessageEvent interface, michael@0: // which does not bubble, is not cancelable, and has no default action michael@0: michael@0: nsCOMPtr event; michael@0: rv = NS_NewDOMMessageEvent(getter_AddRefs(event), this, nullptr, nullptr); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr messageEvent = do_QueryInterface(event); michael@0: rv = messageEvent->InitMessageEvent(NS_LITERAL_STRING("message"), michael@0: false, false, michael@0: jsData, michael@0: mUTF16Origin, michael@0: EmptyString(), nullptr); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: event->SetTrusted(true); michael@0: michael@0: return DispatchDOMEvent(nullptr, event, nullptr, nullptr); michael@0: } michael@0: michael@0: nsresult michael@0: WebSocket::CreateAndDispatchCloseEvent(bool aWasClean, michael@0: uint16_t aCode, michael@0: const nsString &aReason) michael@0: { michael@0: NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); michael@0: michael@0: nsresult rv = CheckInnerWindowCorrectness(); michael@0: if (NS_FAILED(rv)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // create an event that uses the CloseEvent interface, michael@0: // which does not bubble, is not cancelable, and has no default action michael@0: michael@0: nsCOMPtr event; michael@0: rv = NS_NewDOMCloseEvent(getter_AddRefs(event), this, nullptr, nullptr); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr closeEvent = do_QueryInterface(event); michael@0: rv = closeEvent->InitCloseEvent(NS_LITERAL_STRING("close"), michael@0: false, false, michael@0: aWasClean, aCode, aReason); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: event->SetTrusted(true); michael@0: michael@0: return DispatchDOMEvent(nullptr, event, nullptr, nullptr); michael@0: } michael@0: michael@0: bool michael@0: WebSocket::PrefEnabled(JSContext* aCx, JSObject* aGlobal) michael@0: { michael@0: return Preferences::GetBool("network.websocket.enabled", true); michael@0: } michael@0: michael@0: nsresult michael@0: WebSocket::ParseURL(const nsString& aURL) michael@0: { michael@0: NS_ENSURE_TRUE(!aURL.IsEmpty(), NS_ERROR_DOM_SYNTAX_ERR); michael@0: michael@0: nsCOMPtr uri; michael@0: nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL); michael@0: NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR); michael@0: michael@0: nsCOMPtr parsedURL = do_QueryInterface(uri, &rv); michael@0: NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR); michael@0: michael@0: nsAutoCString fragment; michael@0: rv = parsedURL->GetRef(fragment); michael@0: NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && fragment.IsEmpty(), michael@0: NS_ERROR_DOM_SYNTAX_ERR); michael@0: michael@0: nsAutoCString scheme; michael@0: rv = parsedURL->GetScheme(scheme); michael@0: NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && !scheme.IsEmpty(), michael@0: NS_ERROR_DOM_SYNTAX_ERR); michael@0: michael@0: nsAutoCString host; michael@0: rv = parsedURL->GetAsciiHost(host); michael@0: NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && !host.IsEmpty(), NS_ERROR_DOM_SYNTAX_ERR); michael@0: michael@0: int32_t port; michael@0: rv = parsedURL->GetPort(&port); michael@0: NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR); michael@0: michael@0: rv = NS_CheckPortSafety(port, scheme.get()); michael@0: NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR); michael@0: michael@0: nsAutoCString filePath; michael@0: rv = parsedURL->GetFilePath(filePath); michael@0: if (filePath.IsEmpty()) { michael@0: filePath.AssignLiteral("/"); michael@0: } michael@0: NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR); michael@0: michael@0: nsAutoCString query; michael@0: rv = parsedURL->GetQuery(query); michael@0: NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR); michael@0: michael@0: if (scheme.LowerCaseEqualsLiteral("ws")) { michael@0: mSecure = false; michael@0: mPort = (port == -1) ? DEFAULT_WS_SCHEME_PORT : port; michael@0: } else if (scheme.LowerCaseEqualsLiteral("wss")) { michael@0: mSecure = true; michael@0: mPort = (port == -1) ? DEFAULT_WSS_SCHEME_PORT : port; michael@0: } else { michael@0: return NS_ERROR_DOM_SYNTAX_ERR; michael@0: } michael@0: michael@0: rv = nsContentUtils::GetUTFOrigin(parsedURL, mUTF16Origin); michael@0: NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR); michael@0: michael@0: mAsciiHost = host; michael@0: ToLowerCase(mAsciiHost); michael@0: michael@0: mResource = filePath; michael@0: if (!query.IsEmpty()) { michael@0: mResource.AppendLiteral("?"); michael@0: mResource.Append(query); michael@0: } michael@0: uint32_t length = mResource.Length(); michael@0: uint32_t i; michael@0: for (i = 0; i < length; ++i) { michael@0: if (mResource[i] < static_cast(0x0021) || michael@0: mResource[i] > static_cast(0x007E)) { michael@0: return NS_ERROR_DOM_SYNTAX_ERR; michael@0: } michael@0: } michael@0: michael@0: mOriginalURL = aURL; michael@0: mURI = parsedURL; michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // Methods that keep alive the WebSocket object when: michael@0: // 1. the object has registered event listeners that can be triggered michael@0: // ("strong event listeners"); michael@0: // 2. there are outgoing not sent messages. michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: void michael@0: WebSocket::UpdateMustKeepAlive() michael@0: { michael@0: NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); michael@0: if (!mCheckMustKeepAlive) { michael@0: return; michael@0: } michael@0: michael@0: bool shouldKeepAlive = false; michael@0: michael@0: if (mListenerManager) { michael@0: switch (mReadyState) michael@0: { michael@0: case WebSocket::CONNECTING: michael@0: { michael@0: if (mListenerManager->HasListenersFor(nsGkAtoms::onopen) || michael@0: mListenerManager->HasListenersFor(nsGkAtoms::onmessage) || michael@0: mListenerManager->HasListenersFor(nsGkAtoms::onerror) || michael@0: mListenerManager->HasListenersFor(nsGkAtoms::onclose)) { michael@0: shouldKeepAlive = true; michael@0: } michael@0: } michael@0: break; michael@0: michael@0: case WebSocket::OPEN: michael@0: case WebSocket::CLOSING: michael@0: { michael@0: if (mListenerManager->HasListenersFor(nsGkAtoms::onmessage) || michael@0: mListenerManager->HasListenersFor(nsGkAtoms::onerror) || michael@0: mListenerManager->HasListenersFor(nsGkAtoms::onclose) || michael@0: mOutgoingBufferedAmount != 0) { michael@0: shouldKeepAlive = true; michael@0: } michael@0: } michael@0: break; michael@0: michael@0: case WebSocket::CLOSED: michael@0: { michael@0: shouldKeepAlive = false; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (mKeepingAlive && !shouldKeepAlive) { michael@0: mKeepingAlive = false; michael@0: static_cast(this)->Release(); michael@0: } else if (!mKeepingAlive && shouldKeepAlive) { michael@0: mKeepingAlive = true; michael@0: static_cast(this)->AddRef(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: WebSocket::DontKeepAliveAnyMore() michael@0: { michael@0: NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); michael@0: if (mKeepingAlive) { michael@0: mKeepingAlive = false; michael@0: static_cast(this)->Release(); michael@0: } michael@0: mCheckMustKeepAlive = false; michael@0: } michael@0: michael@0: nsresult michael@0: WebSocket::UpdateURI() michael@0: { michael@0: // Check for Redirections michael@0: nsCOMPtr uri; michael@0: nsresult rv = mChannel->GetURI(getter_AddRefs(uri)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsAutoCString spec; michael@0: rv = uri->GetSpec(spec); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: CopyUTF8toUTF16(spec, mEffectiveURL); michael@0: michael@0: bool isWSS = false; michael@0: rv = uri->SchemeIs("wss", &isWSS); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: mSecure = isWSS ? true : false; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: WebSocket::EventListenerAdded(nsIAtom* aType) michael@0: { michael@0: UpdateMustKeepAlive(); michael@0: } michael@0: michael@0: void michael@0: WebSocket::EventListenerRemoved(nsIAtom* aType) michael@0: { michael@0: UpdateMustKeepAlive(); michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // WebSocket - methods michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: // webIDL: readonly attribute DOMString url michael@0: void michael@0: WebSocket::GetUrl(nsAString& aURL) michael@0: { michael@0: if (mEffectiveURL.IsEmpty()) { michael@0: aURL = mOriginalURL; michael@0: } else { michael@0: aURL = mEffectiveURL; michael@0: } michael@0: } michael@0: michael@0: // webIDL: readonly attribute DOMString extensions; michael@0: void michael@0: WebSocket::GetExtensions(nsAString& aExtensions) michael@0: { michael@0: CopyUTF8toUTF16(mEstablishedExtensions, aExtensions); michael@0: } michael@0: michael@0: // webIDL: readonly attribute DOMString protocol; michael@0: void michael@0: WebSocket::GetProtocol(nsAString& aProtocol) michael@0: { michael@0: CopyUTF8toUTF16(mEstablishedProtocol, aProtocol); michael@0: } michael@0: michael@0: // webIDL: void send(DOMString data); michael@0: void michael@0: WebSocket::Send(const nsAString& aData, michael@0: ErrorResult& aRv) michael@0: { michael@0: NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); michael@0: michael@0: NS_ConvertUTF16toUTF8 msgString(aData); michael@0: Send(nullptr, msgString, msgString.Length(), false, aRv); michael@0: } michael@0: michael@0: void michael@0: WebSocket::Send(nsIDOMBlob* aData, michael@0: ErrorResult& aRv) michael@0: { michael@0: NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); michael@0: michael@0: nsCOMPtr msgStream; michael@0: nsresult rv = aData->GetInternalStream(getter_AddRefs(msgStream)); michael@0: if (NS_FAILED(rv)) { michael@0: aRv.Throw(rv); michael@0: return; michael@0: } michael@0: michael@0: uint64_t msgLength; michael@0: rv = aData->GetSize(&msgLength); michael@0: if (NS_FAILED(rv)) { michael@0: aRv.Throw(rv); michael@0: return; michael@0: } michael@0: michael@0: if (msgLength > UINT32_MAX) { michael@0: aRv.Throw(NS_ERROR_FILE_TOO_BIG); michael@0: return; michael@0: } michael@0: michael@0: Send(msgStream, EmptyCString(), msgLength, true, aRv); michael@0: } michael@0: michael@0: void michael@0: WebSocket::Send(const ArrayBuffer& aData, michael@0: ErrorResult& aRv) michael@0: { michael@0: NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); michael@0: michael@0: aData.ComputeLengthAndData(); michael@0: michael@0: static_assert(sizeof(*aData.Data()) == 1, "byte-sized data required"); michael@0: michael@0: uint32_t len = aData.Length(); michael@0: char* data = reinterpret_cast(aData.Data()); michael@0: michael@0: nsDependentCSubstring msgString(data, len); michael@0: Send(nullptr, msgString, len, true, aRv); michael@0: } michael@0: michael@0: void michael@0: WebSocket::Send(const ArrayBufferView& aData, michael@0: ErrorResult& aRv) michael@0: { michael@0: NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); michael@0: michael@0: aData.ComputeLengthAndData(); michael@0: michael@0: static_assert(sizeof(*aData.Data()) == 1, "byte-sized data required"); michael@0: michael@0: uint32_t len = aData.Length(); michael@0: char* data = reinterpret_cast(aData.Data()); michael@0: michael@0: nsDependentCSubstring msgString(data, len); michael@0: Send(nullptr, msgString, len, true, aRv); michael@0: } michael@0: michael@0: void michael@0: WebSocket::Send(nsIInputStream* aMsgStream, michael@0: const nsACString& aMsgString, michael@0: uint32_t aMsgLength, michael@0: bool aIsBinary, michael@0: ErrorResult& aRv) michael@0: { michael@0: if (mReadyState == WebSocket::CONNECTING) { michael@0: aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); michael@0: return; michael@0: } michael@0: michael@0: // Always increment outgoing buffer len, even if closed michael@0: mOutgoingBufferedAmount += aMsgLength; michael@0: michael@0: if (mReadyState == WebSocket::CLOSING || michael@0: mReadyState == WebSocket::CLOSED) { michael@0: return; michael@0: } michael@0: michael@0: MOZ_ASSERT(mReadyState == WebSocket::OPEN, michael@0: "Unknown state in WebSocket::Send"); michael@0: michael@0: nsresult rv; michael@0: if (aMsgStream) { michael@0: rv = mChannel->SendBinaryStream(aMsgStream, aMsgLength); michael@0: } else { michael@0: if (aIsBinary) { michael@0: rv = mChannel->SendBinaryMsg(aMsgString); michael@0: } else { michael@0: rv = mChannel->SendMsg(aMsgString); michael@0: } michael@0: } michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: aRv.Throw(rv); michael@0: return; michael@0: } michael@0: michael@0: UpdateMustKeepAlive(); michael@0: } michael@0: michael@0: // webIDL: void close(optional unsigned short code, optional DOMString reason): michael@0: void michael@0: WebSocket::Close(const Optional& aCode, michael@0: const Optional& aReason, michael@0: ErrorResult& aRv) michael@0: { michael@0: NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); michael@0: michael@0: // the reason code is optional, but if provided it must be in a specific range michael@0: uint16_t closeCode = 0; michael@0: if (aCode.WasPassed()) { michael@0: if (aCode.Value() != 1000 && (aCode.Value() < 3000 || aCode.Value() > 4999)) { michael@0: aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR); michael@0: return; michael@0: } michael@0: closeCode = aCode.Value(); michael@0: } michael@0: michael@0: nsCString closeReason; michael@0: if (aReason.WasPassed()) { michael@0: CopyUTF16toUTF8(aReason.Value(), closeReason); michael@0: michael@0: // The API requires the UTF-8 string to be 123 or less bytes michael@0: if (closeReason.Length() > 123) { michael@0: aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: if (mReadyState == WebSocket::CLOSING || michael@0: mReadyState == WebSocket::CLOSED) { michael@0: return; michael@0: } michael@0: michael@0: if (mReadyState == WebSocket::CONNECTING) { michael@0: FailConnection(closeCode, closeReason); michael@0: return; michael@0: } michael@0: michael@0: MOZ_ASSERT(mReadyState == WebSocket::OPEN); michael@0: CloseConnection(closeCode, closeReason); michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // WebSocket::nsIObserver michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: WebSocket::Observe(nsISupports* aSubject, michael@0: const char* aTopic, michael@0: const char16_t* aData) michael@0: { michael@0: if ((mReadyState == WebSocket::CLOSING) || michael@0: (mReadyState == WebSocket::CLOSED)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr window = do_QueryInterface(aSubject); michael@0: if (!GetOwner() || window != GetOwner()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: if ((strcmp(aTopic, DOM_WINDOW_FROZEN_TOPIC) == 0) || michael@0: (strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC) == 0)) michael@0: { michael@0: CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // WebSocket::nsIRequest michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: WebSocket::GetName(nsACString& aName) michael@0: { michael@0: CopyUTF16toUTF8(mOriginalURL, aName); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: WebSocket::IsPending(bool* aValue) michael@0: { michael@0: *aValue = (mReadyState != WebSocket::CLOSED); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: WebSocket::GetStatus(nsresult* aStatus) michael@0: { michael@0: *aStatus = NS_OK; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Window closed, stop/reload button pressed, user navigated away from page, etc. michael@0: NS_IMETHODIMP michael@0: WebSocket::Cancel(nsresult aStatus) michael@0: { michael@0: NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); michael@0: michael@0: if (mReadyState == CLOSING || mReadyState == CLOSED) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: ConsoleError(); michael@0: michael@0: return CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: WebSocket::Suspend() michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: WebSocket::Resume() michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: WebSocket::GetLoadGroup(nsILoadGroup** aLoadGroup) michael@0: { michael@0: *aLoadGroup = nullptr; michael@0: michael@0: nsresult rv; michael@0: nsIScriptContext* sc = GetContextForEventHandlers(&rv); michael@0: nsCOMPtr doc = michael@0: nsContentUtils::GetDocumentFromScriptContext(sc); michael@0: michael@0: if (doc) { michael@0: *aLoadGroup = doc->GetDocumentLoadGroup().take(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: WebSocket::SetLoadGroup(nsILoadGroup* aLoadGroup) michael@0: { michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: WebSocket::GetLoadFlags(nsLoadFlags* aLoadFlags) michael@0: { michael@0: *aLoadFlags = nsIRequest::LOAD_BACKGROUND; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: WebSocket::SetLoadFlags(nsLoadFlags aLoadFlags) michael@0: { michael@0: // we won't change the load flags at all. michael@0: return NS_OK; michael@0: } michael@0: michael@0: } // dom namespace michael@0: } // mozilla namespace