mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1203802 - Websocket Frame Listener API for devtool Network Inspector - part 2 - WebSocketFrameService, r=michal
This commit is contained in:
parent
c997842c3d
commit
a2f1eba556
@ -1143,6 +1143,10 @@ protected:
|
||||
nsCOMPtr<nsIDOMWindow> topWindow;
|
||||
aWindow->GetScriptableTop(getter_AddRefs(topWindow));
|
||||
nsCOMPtr<nsPIDOMWindow> pTopWindow = do_QueryInterface(topWindow);
|
||||
if (pTopWindow) {
|
||||
pTopWindow = pTopWindow->GetCurrentInnerWindow();
|
||||
}
|
||||
|
||||
if (pTopWindow) {
|
||||
windowID = pTopWindow->WindowID();
|
||||
}
|
||||
@ -1323,6 +1327,10 @@ WebSocket::Constructor(const GlobalObject& aGlobal,
|
||||
nsCOMPtr<nsIDOMWindow> topWindow;
|
||||
ownerWindow->GetScriptableTop(getter_AddRefs(topWindow));
|
||||
nsCOMPtr<nsPIDOMWindow> pTopWindow = do_QueryInterface(topWindow);
|
||||
if (pTopWindow) {
|
||||
pTopWindow = pTopWindow->GetCurrentInnerWindow();
|
||||
}
|
||||
|
||||
if (pTopWindow) {
|
||||
windowID = pTopWindow->WindowID();
|
||||
}
|
||||
|
@ -26,3 +26,4 @@ run-if = os == 'linux'
|
||||
[test_bug1008126.html]
|
||||
run-if = os == 'linux'
|
||||
[test_sandboxed_blob_uri.html]
|
||||
[test_websocket_frame.html]
|
||||
|
128
dom/base/test/test_websocket_frame.html
Normal file
128
dom/base/test/test_websocket_frame.html
Normal file
@ -0,0 +1,128 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
-->
|
||||
<head>
|
||||
<title>Basic websocket frame interception test</title>
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
|
||||
</head>
|
||||
<body>
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
|
||||
var frameReceivedCounter = 0;
|
||||
var frameSentCounter = 0;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
var tests = [
|
||||
{ payload: "Hello world!" },
|
||||
{ payload: (function() { var buffer = ""; for (var i = 0; i < 120; ++i) buffer += i; return buffer; }()) },
|
||||
{ payload: "end" },
|
||||
]
|
||||
|
||||
var innerId =
|
||||
window.top.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
|
||||
ok(innerId, "We have a valid innerWindowID: " + innerId);
|
||||
|
||||
var service = Cc["@mozilla.org/websocketframe/service;1"]
|
||||
.getService(Ci.nsIWebSocketFrameService);
|
||||
ok(!!service, "We have the nsIWebSocketFrameService");
|
||||
|
||||
var listener = {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebSocketFrameListener]),
|
||||
|
||||
frameReceived: function(aWebSocketSerialID, aFrame) {
|
||||
ok(!!aFrame, "We have received a frame");
|
||||
|
||||
if (tests.length) {
|
||||
is(aFrame.finBit, true, "Checking finBit");
|
||||
is(aFrame.rsvBit1, true, "Checking rsvBit1");
|
||||
is(aFrame.rsvBit2, false, "Checking rsvBit2");
|
||||
is(aFrame.rsvBit3, false, "Checking rsvBit3");
|
||||
is(aFrame.opCode, aFrame.OPCODE_TEXT, "Checking opCode");
|
||||
is(aFrame.maskBit, false, "Checking maskBit");
|
||||
is(aFrame.mask, 0, "Checking mask");
|
||||
is(aFrame.payload, tests[0].payload, "Checking payload: " + aFrame.payload);
|
||||
} else {
|
||||
is(aFrame.finBit, true, "Checking finBit");
|
||||
is(aFrame.rsvBit1, false, "Checking rsvBit1");
|
||||
is(aFrame.rsvBit2, false, "Checking rsvBit2");
|
||||
is(aFrame.rsvBit3, false, "Checking rsvBit3");
|
||||
is(aFrame.opCode, aFrame.OPCODE_CLOSE, "Checking opCode");
|
||||
is(aFrame.maskBit, false, "Checking maskBit");
|
||||
is(aFrame.mask, 0, "Checking mask");
|
||||
}
|
||||
|
||||
frameReceivedCounter++;
|
||||
},
|
||||
|
||||
frameSent: function(aWebSocketSerialID, aFrame) {
|
||||
ok(!!aFrame, "We have sent a frame");
|
||||
|
||||
if (tests.length) {
|
||||
is(aFrame.finBit, true, "Checking finBit");
|
||||
is(aFrame.rsvBit1, true, "Checking rsvBit1");
|
||||
is(aFrame.rsvBit2, false, "Checking rsvBit2");
|
||||
is(aFrame.rsvBit3, false, "Checking rsvBit3");
|
||||
is(aFrame.opCode, aFrame.OPCODE_TEXT, "Checking opCode");
|
||||
is(aFrame.maskBit, true, "Checking maskBit");
|
||||
ok(!!aFrame.mask, "Checking mask: " + aFrame.mask);
|
||||
is(aFrame.payload, tests[0].payload, "Checking payload: " + aFrame.payload);
|
||||
} else {
|
||||
is(aFrame.finBit, true, "Checking finBit");
|
||||
is(aFrame.rsvBit1, false, "Checking rsvBit1");
|
||||
is(aFrame.rsvBit2, false, "Checking rsvBit2");
|
||||
is(aFrame.rsvBit3, false, "Checking rsvBit3");
|
||||
is(aFrame.opCode, aFrame.OPCODE_CLOSE, "Checking opCode");
|
||||
is(aFrame.maskBit, true, "Checking maskBit");
|
||||
ok(!!aFrame.mask, "Checking mask: " + aFrame.mask);
|
||||
}
|
||||
|
||||
frameSentCounter++;
|
||||
}
|
||||
};
|
||||
|
||||
service.addListener(innerId, listener);
|
||||
ok(true, "Listener added");
|
||||
|
||||
function checkListener() {
|
||||
service.removeListener(innerId, listener);
|
||||
|
||||
ok(frameReceivedCounter, "We received some frames!");
|
||||
ok(frameSentCounter, "We sent some frames!");
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
var ws = new WebSocket("ws://mochi.test:8888/tests/dom/base/test/file_websocket_basic", "frame");
|
||||
ws.onopen = function(e) {
|
||||
info("onopen");
|
||||
|
||||
ws.send(tests[0].payload);
|
||||
}
|
||||
|
||||
ws.onclose = function(e) {
|
||||
info("onclose");
|
||||
|
||||
ws.close();
|
||||
checkListener();
|
||||
}
|
||||
|
||||
ws.onmessage = function(e) {
|
||||
info("onmessage");
|
||||
|
||||
is(e.data, tests[0].payload, "Wrong data");
|
||||
tests.shift();
|
||||
if (tests.length) {
|
||||
ws.send(tests[0].payload);
|
||||
}
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -231,6 +231,7 @@ static void Shutdown();
|
||||
#include "mozilla/dom/nsMixedContentBlocker.h"
|
||||
|
||||
#include "AudioChannelService.h"
|
||||
#include "mozilla/net/WebSocketFrameService.h"
|
||||
|
||||
#include "mozilla/dom/DataStoreService.h"
|
||||
|
||||
@ -632,6 +633,9 @@ NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(Geolocation, Init)
|
||||
#define NS_AUDIOCHANNEL_SERVICE_CID \
|
||||
{ 0xf712e983, 0x048a, 0x443f, { 0x88, 0x02, 0xfc, 0xc3, 0xd9, 0x27, 0xce, 0xac }}
|
||||
|
||||
#define NS_WEBSOCKETFRAME_SERVICE_CID \
|
||||
{ 0x5973dd8f, 0xed2c, 0x41ff, { 0x9e, 0x64, 0x25, 0x1f, 0xf5, 0x5a, 0x67, 0xb9 }}
|
||||
|
||||
#define NS_DATASTORE_SERVICE_CID \
|
||||
{ 0x0d4285fe, 0xf1b3, 0x49fa, { 0xbc, 0x51, 0xa4, 0xa8, 0x3f, 0x0a, 0xaf, 0x85 }}
|
||||
|
||||
@ -639,6 +643,8 @@ NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsGeolocationService, nsGeolocationServ
|
||||
|
||||
NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(AudioChannelService, AudioChannelService::GetOrCreate)
|
||||
|
||||
NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(WebSocketFrameService, WebSocketFrameService::GetOrCreate)
|
||||
|
||||
NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(DataStoreService, DataStoreService::GetOrCreate)
|
||||
|
||||
#ifdef MOZ_WEBSPEECH_TEST_BACKEND
|
||||
@ -789,6 +795,7 @@ NS_DEFINE_NAMED_CID(NS_TEXTSERVICESDOCUMENT_CID);
|
||||
NS_DEFINE_NAMED_CID(NS_GEOLOCATION_SERVICE_CID);
|
||||
NS_DEFINE_NAMED_CID(NS_GEOLOCATION_CID);
|
||||
NS_DEFINE_NAMED_CID(NS_AUDIOCHANNEL_SERVICE_CID);
|
||||
NS_DEFINE_NAMED_CID(NS_WEBSOCKETFRAME_SERVICE_CID);
|
||||
NS_DEFINE_NAMED_CID(NS_DATASTORE_SERVICE_CID);
|
||||
NS_DEFINE_NAMED_CID(NS_FOCUSMANAGER_CID);
|
||||
NS_DEFINE_NAMED_CID(NS_CONTENTSECURITYMANAGER_CID);
|
||||
@ -1096,6 +1103,7 @@ static const mozilla::Module::CIDEntry kLayoutCIDs[] = {
|
||||
{ &kNS_GEOLOCATION_SERVICE_CID, false, nullptr, nsGeolocationServiceConstructor },
|
||||
{ &kNS_GEOLOCATION_CID, false, nullptr, GeolocationConstructor },
|
||||
{ &kNS_AUDIOCHANNEL_SERVICE_CID, false, nullptr, AudioChannelServiceConstructor },
|
||||
{ &kNS_WEBSOCKETFRAME_SERVICE_CID, false, nullptr, WebSocketFrameServiceConstructor },
|
||||
{ &kNS_DATASTORE_SERVICE_CID, false, nullptr, DataStoreServiceConstructor },
|
||||
{ &kNS_FOCUSMANAGER_CID, false, nullptr, CreateFocusManager },
|
||||
#ifdef MOZ_WEBSPEECH_TEST_BACKEND
|
||||
@ -1263,6 +1271,7 @@ static const mozilla::Module::ContractIDEntry kLayoutContracts[] = {
|
||||
{ "@mozilla.org/geolocation/service;1", &kNS_GEOLOCATION_SERVICE_CID },
|
||||
{ "@mozilla.org/geolocation;1", &kNS_GEOLOCATION_CID },
|
||||
{ "@mozilla.org/audiochannel/service;1", &kNS_AUDIOCHANNEL_SERVICE_CID },
|
||||
{ "@mozilla.org/websocketframe/service;1", &kNS_WEBSOCKETFRAME_SERVICE_CID },
|
||||
{ "@mozilla.org/datastore-service;1", &kNS_DATASTORE_SERVICE_CID },
|
||||
{ "@mozilla.org/focus-manager;1", &kNS_FOCUSMANAGER_CID },
|
||||
#ifdef MOZ_WEBSPEECH_TEST_BACKEND
|
||||
|
@ -4,6 +4,7 @@
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "WebSocketFrame.h"
|
||||
#include "WebSocketLog.h"
|
||||
#include "WebSocketChannel.h"
|
||||
|
||||
@ -11,6 +12,7 @@
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/Endian.h"
|
||||
#include "mozilla/MathAlgorithms.h"
|
||||
#include "mozilla/net/WebSocketFrameService.h"
|
||||
|
||||
#include "nsIURI.h"
|
||||
#include "nsIChannel.h"
|
||||
@ -963,7 +965,8 @@ public:
|
||||
: mMsgType(type), mDeflated(false), mOrigLength(0)
|
||||
{
|
||||
MOZ_COUNT_CTOR(OutboundMessage);
|
||||
mMsg.pString = str;
|
||||
mMsg.pString.mValue = str;
|
||||
mMsg.pString.mOrigValue = nullptr;
|
||||
mLength = str ? str->Length() : 0;
|
||||
}
|
||||
|
||||
@ -983,7 +986,9 @@ public:
|
||||
case kMsgTypeBinaryString:
|
||||
case kMsgTypePing:
|
||||
case kMsgTypePong:
|
||||
delete mMsg.pString;
|
||||
delete mMsg.pString.mValue;
|
||||
if (mMsg.pString.mOrigValue)
|
||||
delete mMsg.pString.mOrigValue;
|
||||
break;
|
||||
case kMsgTypeStream:
|
||||
// for now this only gets hit if msg deleted w/o being sent
|
||||
@ -1004,13 +1009,21 @@ public:
|
||||
uint8_t* BeginWriting() {
|
||||
MOZ_ASSERT(mMsgType != kMsgTypeStream,
|
||||
"Stream should have been converted to string by now");
|
||||
return (uint8_t *)(mMsg.pString ? mMsg.pString->BeginWriting() : nullptr);
|
||||
return (uint8_t *)(mMsg.pString.mValue ? mMsg.pString.mValue->BeginWriting() : nullptr);
|
||||
}
|
||||
|
||||
uint8_t* BeginReading() {
|
||||
MOZ_ASSERT(mMsgType != kMsgTypeStream,
|
||||
"Stream should have been converted to string by now");
|
||||
return (uint8_t *)(mMsg.pString ? mMsg.pString->BeginReading() : nullptr);
|
||||
return (uint8_t *)(mMsg.pString.mValue ? mMsg.pString.mValue->BeginReading() : nullptr);
|
||||
}
|
||||
|
||||
uint8_t* BeginOrigReading() {
|
||||
MOZ_ASSERT(mMsgType != kMsgTypeStream,
|
||||
"Stream should have been converted to string by now");
|
||||
if (!mDeflated)
|
||||
return BeginReading();
|
||||
return (uint8_t *)(mMsg.pString.mOrigValue ? mMsg.pString.mOrigValue->BeginReading() : nullptr);
|
||||
}
|
||||
|
||||
nsresult ConvertStreamToString()
|
||||
@ -1031,7 +1044,8 @@ public:
|
||||
|
||||
mMsg.pStream->Close();
|
||||
mMsg.pStream->Release();
|
||||
mMsg.pString = temp.forget();
|
||||
mMsg.pString.mValue = temp.forget();
|
||||
mMsg.pString.mOrigValue = nullptr;
|
||||
mMsgType = kMsgTypeBinaryString;
|
||||
|
||||
return NS_OK;
|
||||
@ -1074,14 +1088,17 @@ public:
|
||||
mOrigLength = mLength;
|
||||
mDeflated = true;
|
||||
mLength = temp->Length();
|
||||
delete mMsg.pString;
|
||||
mMsg.pString = temp.forget();
|
||||
mMsg.pString.mOrigValue = mMsg.pString.mValue;
|
||||
mMsg.pString.mValue = temp.forget();
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
union {
|
||||
nsCString *pString;
|
||||
struct {
|
||||
nsCString *mValue;
|
||||
nsCString *mOrigValue;
|
||||
} pString;
|
||||
nsIInputStream *pStream;
|
||||
} mMsg;
|
||||
WsMsgType mMsgType;
|
||||
@ -1149,7 +1166,7 @@ WebSocketChannel::WebSocketChannel() :
|
||||
mStopOnClose(NS_OK),
|
||||
mServerCloseCode(CLOSE_ABNORMAL),
|
||||
mScriptCloseCode(0),
|
||||
mFragmentOpcode(kContinuation),
|
||||
mFragmentOpcode(nsIWebSocketFrame::OPCODE_CONTINUATION),
|
||||
mFragmentAccumulator(0),
|
||||
mBuffered(0),
|
||||
mBufferSize(kIncomingBufferInitialSize),
|
||||
@ -1178,6 +1195,8 @@ WebSocketChannel::WebSocketChannel() :
|
||||
LOG(("Failed to initiate dashboard service."));
|
||||
|
||||
mSerial = sSerialSeed++;
|
||||
|
||||
mFrameService = WebSocketFrameService::GetOrCreate();
|
||||
}
|
||||
|
||||
WebSocketChannel::~WebSocketChannel()
|
||||
@ -1209,6 +1228,7 @@ WebSocketChannel::~WebSocketChannel()
|
||||
|
||||
NS_ReleaseOnMainThread(mLoadGroup);
|
||||
NS_ReleaseOnMainThread(mLoadInfo);
|
||||
NS_ReleaseOnMainThread(static_cast<nsIWebSocketFrameService*>(mFrameService.forget().take()));
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
@ -1501,11 +1521,15 @@ WebSocketChannel::ProcessInput(uint8_t *buffer, uint32_t count)
|
||||
uint32_t totalAvail = avail;
|
||||
|
||||
while (avail >= 2) {
|
||||
int64_t payloadLength64 = mFramePtr[1] & 0x7F;
|
||||
int64_t payloadLength64 = mFramePtr[1] & kPayloadLengthBitsMask;
|
||||
uint8_t finBit = mFramePtr[0] & kFinalFragBit;
|
||||
uint8_t rsvBits = mFramePtr[0] & kRsvBitsMask;
|
||||
uint8_t rsvBit1 = mFramePtr[0] & kRsv1Bit;
|
||||
uint8_t rsvBit2 = mFramePtr[0] & kRsv2Bit;
|
||||
uint8_t rsvBit3 = mFramePtr[0] & kRsv3Bit;
|
||||
uint8_t opcode = mFramePtr[0] & kOpcodeBitsMask;
|
||||
uint8_t maskBit = mFramePtr[1] & kMaskBit;
|
||||
uint8_t opcode = mFramePtr[0] & 0x0F;
|
||||
uint32_t mask = 0;
|
||||
|
||||
uint32_t framingLength = 2;
|
||||
if (maskBit)
|
||||
@ -1560,7 +1584,7 @@ WebSocketChannel::ProcessInput(uint8_t *buffer, uint32_t count)
|
||||
// frames to the client, but it is allowed
|
||||
LOG(("WebSocketChannel:: Client RECEIVING masked frame."));
|
||||
|
||||
uint32_t mask = NetworkEndian::readUint32(payload - 4);
|
||||
mask = NetworkEndian::readUint32(payload - 4);
|
||||
ApplyMask(mask, payload, payloadLength);
|
||||
}
|
||||
|
||||
@ -1584,22 +1608,23 @@ WebSocketChannel::ProcessInput(uint8_t *buffer, uint32_t count)
|
||||
}
|
||||
}
|
||||
|
||||
if (!finBit || opcode == kContinuation) {
|
||||
if (!finBit || opcode == nsIWebSocketFrame::OPCODE_CONTINUATION) {
|
||||
// This is part of a fragment response
|
||||
|
||||
// Only the first frame has a non zero op code: Make sure we don't see a
|
||||
// first frame while some old fragments are open
|
||||
if ((mFragmentAccumulator != 0) && (opcode != kContinuation)) {
|
||||
if ((mFragmentAccumulator != 0) &&
|
||||
(opcode != nsIWebSocketFrame::OPCODE_CONTINUATION)) {
|
||||
LOG(("WebSocketChannel:: nested fragments\n"));
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
|
||||
LOG(("WebSocketChannel:: Accumulating Fragment %ld\n", payloadLength));
|
||||
|
||||
if (opcode == kContinuation) {
|
||||
if (opcode == nsIWebSocketFrame::OPCODE_CONTINUATION) {
|
||||
|
||||
// Make sure this continuation fragment isn't the first fragment
|
||||
if (mFragmentOpcode == kContinuation) {
|
||||
if (mFragmentOpcode == nsIWebSocketFrame::OPCODE_CONTINUATION) {
|
||||
LOG(("WebSocketHeandler:: continuation code in first fragment\n"));
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
@ -1624,9 +1649,9 @@ WebSocketChannel::ProcessInput(uint8_t *buffer, uint32_t count)
|
||||
mFragmentAccumulator = 0;
|
||||
opcode = mFragmentOpcode;
|
||||
// reset to detect if next message illegally starts with continuation
|
||||
mFragmentOpcode = kContinuation;
|
||||
mFragmentOpcode = nsIWebSocketFrame::OPCODE_CONTINUATION;
|
||||
} else {
|
||||
opcode = kContinuation;
|
||||
opcode = nsIWebSocketFrame::OPCODE_CONTINUATION;
|
||||
mFragmentAccumulator += payloadLength;
|
||||
}
|
||||
} else if (mFragmentAccumulator != 0 && !(opcode & kControlFrameMask)) {
|
||||
@ -1644,7 +1669,7 @@ WebSocketChannel::ProcessInput(uint8_t *buffer, uint32_t count)
|
||||
} else if (mStopped) {
|
||||
LOG(("WebSocketChannel:: ignoring read frame code %d after completion\n",
|
||||
opcode));
|
||||
} else if (opcode == kText) {
|
||||
} else if (opcode == nsIWebSocketFrame::OPCODE_TEXT) {
|
||||
bool isDeflated = mPMCECompressor && mPMCECompressor->IsMessageDeflated();
|
||||
LOG(("WebSocketChannel:: %stext frame received\n",
|
||||
isDeflated ? "deflated " : ""));
|
||||
@ -1673,6 +1698,14 @@ WebSocketChannel::ProcessInput(uint8_t *buffer, uint32_t count)
|
||||
return NS_ERROR_CANNOT_CONVERT_DATA;
|
||||
}
|
||||
|
||||
RefPtr<WebSocketFrame> frame =
|
||||
mFrameService->CreateFrameIfNeeded(finBit, rsvBit1, rsvBit2, rsvBit3,
|
||||
opcode, maskBit, mask, utf8Data);
|
||||
|
||||
if (frame) {
|
||||
mFrameService->FrameReceived(mSerial, mInnerWindowID, frame);
|
||||
}
|
||||
|
||||
mTargetThread->Dispatch(new CallOnMessageAvailable(this, utf8Data, -1),
|
||||
NS_DISPATCH_NORMAL);
|
||||
if (mConnectionLogService && !mPrivateBrowsing) {
|
||||
@ -1688,7 +1721,12 @@ WebSocketChannel::ProcessInput(uint8_t *buffer, uint32_t count)
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
|
||||
if (opcode == kClose) {
|
||||
RefPtr<WebSocketFrame> frame =
|
||||
mFrameService->CreateFrameIfNeeded(finBit, rsvBit1, rsvBit2, rsvBit3,
|
||||
opcode, maskBit, mask, payload,
|
||||
payloadLength);
|
||||
|
||||
if (opcode == nsIWebSocketFrame::OPCODE_CLOSE) {
|
||||
LOG(("WebSocketChannel:: close received\n"));
|
||||
mServerClosed = 1;
|
||||
|
||||
@ -1720,6 +1758,14 @@ WebSocketChannel::ProcessInput(uint8_t *buffer, uint32_t count)
|
||||
mCloseTimer->Cancel();
|
||||
mCloseTimer = nullptr;
|
||||
}
|
||||
|
||||
if (frame) {
|
||||
// We send the frame immediately becuase we want to have it dispatched
|
||||
// before the CallOnServerClose.
|
||||
mFrameService->FrameReceived(mSerial, mInnerWindowID, frame);
|
||||
frame = nullptr;
|
||||
}
|
||||
|
||||
if (mListenerMT) {
|
||||
mTargetThread->Dispatch(new CallOnServerClose(this, mServerCloseCode,
|
||||
mServerCloseReason),
|
||||
@ -1728,12 +1774,12 @@ WebSocketChannel::ProcessInput(uint8_t *buffer, uint32_t count)
|
||||
|
||||
if (mClientClosed)
|
||||
ReleaseSession();
|
||||
} else if (opcode == kPing) {
|
||||
} else if (opcode == nsIWebSocketFrame::OPCODE_PING) {
|
||||
LOG(("WebSocketChannel:: ping received\n"));
|
||||
GeneratePong(payload, payloadLength);
|
||||
} else if (opcode == kPong) {
|
||||
// opcode kPong: the mere act of receiving the packet is all we need
|
||||
// to do for the pong to trigger the activity timers
|
||||
} else if (opcode == nsIWebSocketFrame::OPCODE_PONG) {
|
||||
// opcode OPCODE_PONG: the mere act of receiving the packet is all we
|
||||
// need to do for the pong to trigger the activity timers
|
||||
LOG(("WebSocketChannel:: pong received\n"));
|
||||
} else {
|
||||
/* unknown control frame opcode */
|
||||
@ -1754,7 +1800,11 @@ WebSocketChannel::ProcessInput(uint8_t *buffer, uint32_t count)
|
||||
mBuffered -= framingLength + payloadLength;
|
||||
payloadLength = 0;
|
||||
}
|
||||
} else if (opcode == kBinary) {
|
||||
|
||||
if (frame) {
|
||||
mFrameService->FrameReceived(mSerial, mInnerWindowID, frame);
|
||||
}
|
||||
} else if (opcode == nsIWebSocketFrame::OPCODE_BINARY) {
|
||||
bool isDeflated = mPMCECompressor && mPMCECompressor->IsMessageDeflated();
|
||||
LOG(("WebSocketChannel:: %sbinary frame received\n",
|
||||
isDeflated ? "deflated " : ""));
|
||||
@ -1777,6 +1827,13 @@ WebSocketChannel::ProcessInput(uint8_t *buffer, uint32_t count)
|
||||
}
|
||||
}
|
||||
|
||||
RefPtr<WebSocketFrame> frame =
|
||||
mFrameService->CreateFrameIfNeeded(finBit, rsvBit1, rsvBit2, rsvBit3,
|
||||
opcode, maskBit, mask, binaryData);
|
||||
if (frame) {
|
||||
mFrameService->FrameReceived(mSerial, mInnerWindowID, frame);
|
||||
}
|
||||
|
||||
mTargetThread->Dispatch(
|
||||
new CallOnMessageAvailable(this, binaryData, binaryData.Length()),
|
||||
NS_DISPATCH_NORMAL);
|
||||
@ -1786,7 +1843,7 @@ WebSocketChannel::ProcessInput(uint8_t *buffer, uint32_t count)
|
||||
LOG(("Added new received msg for %s", mHost.get()));
|
||||
}
|
||||
}
|
||||
} else if (opcode != kContinuation) {
|
||||
} else if (opcode != nsIWebSocketFrame::OPCODE_CONTINUATION) {
|
||||
/* unknown opcode */
|
||||
LOG(("WebSocketChannel:: unknown op code %d\n", opcode));
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
@ -1837,7 +1894,7 @@ WebSocketChannel::ProcessInput(uint8_t *buffer, uint32_t count)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
/* static */ void
|
||||
WebSocketChannel::ApplyMask(uint32_t mask, uint8_t *data, uint64_t len)
|
||||
{
|
||||
if (!data || len == 0)
|
||||
@ -1980,7 +2037,7 @@ WebSocketChannel::PrimeNewOutgoingMessage()
|
||||
}
|
||||
|
||||
mClientClosed = 1;
|
||||
mOutHeader[0] = kFinalFragBit | kClose;
|
||||
mOutHeader[0] = kFinalFragBit | nsIWebSocketFrame::OPCODE_CLOSE;
|
||||
mOutHeader[1] = kMaskBit;
|
||||
|
||||
// payload is offset 6 including 4 for the mask
|
||||
@ -2034,13 +2091,13 @@ WebSocketChannel::PrimeNewOutgoingMessage()
|
||||
} else {
|
||||
switch (msgType) {
|
||||
case kMsgTypePong:
|
||||
mOutHeader[0] = kFinalFragBit | kPong;
|
||||
mOutHeader[0] = kFinalFragBit | nsIWebSocketFrame::OPCODE_PONG;
|
||||
break;
|
||||
case kMsgTypePing:
|
||||
mOutHeader[0] = kFinalFragBit | kPing;
|
||||
mOutHeader[0] = kFinalFragBit | nsIWebSocketFrame::OPCODE_PING;
|
||||
break;
|
||||
case kMsgTypeString:
|
||||
mOutHeader[0] = kFinalFragBit | kText;
|
||||
mOutHeader[0] = kFinalFragBit | nsIWebSocketFrame::OPCODE_TEXT;
|
||||
break;
|
||||
case kMsgTypeStream:
|
||||
// HACK ALERT: read in entire stream into string.
|
||||
@ -2057,7 +2114,7 @@ WebSocketChannel::PrimeNewOutgoingMessage()
|
||||
// no break: fall down into binary string case
|
||||
|
||||
case kMsgTypeBinaryString:
|
||||
mOutHeader[0] = kFinalFragBit | kBinary;
|
||||
mOutHeader[0] = kFinalFragBit | nsIWebSocketFrame::OPCODE_BINARY;
|
||||
break;
|
||||
case kMsgTypeFin:
|
||||
MOZ_ASSERT(false, "unreachable"); // avoid compiler warning
|
||||
@ -2119,6 +2176,23 @@ WebSocketChannel::PrimeNewOutgoingMessage()
|
||||
// handful of bytes and might rotate the mask, so we can just do it locally.
|
||||
// For real data frames we ship the bulk of the payload off to ApplyMask()
|
||||
|
||||
RefPtr<WebSocketFrame> frame =
|
||||
mFrameService->CreateFrameIfNeeded(
|
||||
mOutHeader[0] & WebSocketChannel::kFinalFragBit,
|
||||
mOutHeader[0] & WebSocketChannel::kRsv1Bit,
|
||||
mOutHeader[0] & WebSocketChannel::kRsv2Bit,
|
||||
mOutHeader[0] & WebSocketChannel::kRsv3Bit,
|
||||
mOutHeader[0] & WebSocketChannel::kOpcodeBitsMask,
|
||||
mOutHeader[1] & WebSocketChannel::kMaskBit,
|
||||
mask,
|
||||
payload, mHdrOutToSend - (payload - mOutHeader),
|
||||
mCurrentOut->BeginOrigReading(),
|
||||
mCurrentOut->OrigLength());
|
||||
|
||||
if (frame) {
|
||||
mFrameService->FrameSent(mSerial, mInnerWindowID, frame);
|
||||
}
|
||||
|
||||
while (payload < (mOutHeader + mHdrOutToSend)) {
|
||||
*payload ^= mask >> 24;
|
||||
mask = RotateLeft(mask, 8);
|
||||
@ -2141,7 +2215,7 @@ WebSocketChannel::PrimeNewOutgoingMessage()
|
||||
// Transmitting begins - mHdrOutToSend bytes from mOutHeader and
|
||||
// mCurrentOut->Length() bytes from mCurrentOut. The latter may be
|
||||
// coaleseced into the former for small messages or as the result of the
|
||||
// compression process,
|
||||
// compression process.
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -39,7 +39,8 @@ class nsIRandomGenerator;
|
||||
class nsISocketTransport;
|
||||
class nsIURI;
|
||||
|
||||
namespace mozilla { namespace net {
|
||||
namespace mozilla {
|
||||
namespace net {
|
||||
|
||||
class OutboundMessage;
|
||||
class OutboundEnqueuer;
|
||||
@ -49,6 +50,7 @@ class CallOnMessageAvailable;
|
||||
class CallOnStop;
|
||||
class CallOnServerClose;
|
||||
class CallAcknowledge;
|
||||
class WebSocketFrameService;
|
||||
|
||||
// Used to enforce "1 connecting websocket per host" rule, and reconnect delays
|
||||
enum wsConnectingState {
|
||||
@ -70,6 +72,8 @@ class WebSocketChannel : public BaseWebSocketChannel,
|
||||
public nsIInterfaceRequestor,
|
||||
public nsIChannelEventSink
|
||||
{
|
||||
friend class WebSocketFrame;
|
||||
|
||||
public:
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
NS_DECL_NSIHTTPUPGRADELISTENER
|
||||
@ -105,23 +109,19 @@ public:
|
||||
void GetEffectiveURL(nsAString& aEffectiveURL) const override;
|
||||
bool IsEncrypted() const override;
|
||||
|
||||
enum {
|
||||
// Non Control Frames
|
||||
kContinuation = 0x0,
|
||||
kText = 0x1,
|
||||
kBinary = 0x2,
|
||||
|
||||
// Control Frames
|
||||
kClose = 0x8,
|
||||
kPing = 0x9,
|
||||
kPong = 0xA
|
||||
};
|
||||
|
||||
const static uint32_t kControlFrameMask = 0x8;
|
||||
const static uint8_t kMaskBit = 0x80;
|
||||
|
||||
// First byte of the header
|
||||
const static uint8_t kFinalFragBit = 0x80;
|
||||
const static uint8_t kRsvBitsMask = 0x70;
|
||||
const static uint8_t kRsv1Bit = 0x40;
|
||||
const static uint8_t kRsv2Bit = 0x20;
|
||||
const static uint8_t kRsv3Bit = 0x10;
|
||||
const static uint8_t kOpcodeBitsMask = 0x0F;
|
||||
|
||||
// Second byte of the header
|
||||
const static uint8_t kMaskBit = 0x80;
|
||||
const static uint8_t kPayloadLengthBitsMask = 0x7F;
|
||||
|
||||
protected:
|
||||
virtual ~WebSocketChannel();
|
||||
@ -167,7 +167,8 @@ private:
|
||||
void DecrementSessionCount();
|
||||
|
||||
void EnsureHdrOut(uint32_t size);
|
||||
void ApplyMask(uint32_t mask, uint8_t *data, uint64_t len);
|
||||
|
||||
static void ApplyMask(uint32_t mask, uint8_t *data, uint64_t len);
|
||||
|
||||
bool IsPersistentFramePtr();
|
||||
nsresult ProcessInput(uint8_t *buffer, uint32_t count);
|
||||
@ -226,6 +227,8 @@ private:
|
||||
const static int32_t kLingeringCloseTimeout = 1000;
|
||||
const static int32_t kLingeringCloseThreshold = 50;
|
||||
|
||||
RefPtr<WebSocketFrameService> mFrameService;
|
||||
|
||||
int32_t mMaxConcurrentConnections;
|
||||
|
||||
uint64_t mInnerWindowID;
|
||||
|
73
netwerk/protocol/websocket/WebSocketFrame.cpp
Normal file
73
netwerk/protocol/websocket/WebSocketFrame.cpp
Normal file
@ -0,0 +1,73 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "WebSocketFrame.h"
|
||||
|
||||
#include "WebSocketChannel.h"
|
||||
|
||||
extern PRThread *gSocketThread;
|
||||
|
||||
namespace mozilla {
|
||||
namespace net {
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN(WebSocketFrame)
|
||||
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWebSocketFrame)
|
||||
NS_INTERFACE_MAP_ENTRY(nsIWebSocketFrame)
|
||||
NS_INTERFACE_MAP_END
|
||||
|
||||
NS_IMPL_ADDREF(WebSocketFrame)
|
||||
NS_IMPL_RELEASE(WebSocketFrame)
|
||||
|
||||
WebSocketFrame::WebSocketFrame(bool aFinBit, bool aRsvBit1, bool aRsvBit2,
|
||||
bool aRsvBit3, uint8_t aOpCode, bool aMaskBit,
|
||||
uint32_t aMask, const nsCString& aPayload)
|
||||
: mFinBit(aFinBit)
|
||||
, mRsvBit1(aRsvBit1)
|
||||
, mRsvBit2(aRsvBit2)
|
||||
, mRsvBit3(aRsvBit3)
|
||||
, mMaskBit(aMaskBit)
|
||||
, mOpCode(aOpCode)
|
||||
, mMask(aMask)
|
||||
, mPayload(aPayload)
|
||||
{
|
||||
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
|
||||
}
|
||||
|
||||
WebSocketFrame::~WebSocketFrame()
|
||||
{}
|
||||
|
||||
#define WSF_GETTER( method, value , type ) \
|
||||
NS_IMETHODIMP \
|
||||
WebSocketFrame::method(type* aValue) \
|
||||
{ \
|
||||
MOZ_ASSERT(NS_IsMainThread()); \
|
||||
if (!aValue) { \
|
||||
return NS_ERROR_FAILURE; \
|
||||
} \
|
||||
*aValue = value; \
|
||||
return NS_OK; \
|
||||
}
|
||||
|
||||
WSF_GETTER(GetFinBit, mFinBit, bool);
|
||||
WSF_GETTER(GetRsvBit1, mRsvBit1, bool);
|
||||
WSF_GETTER(GetRsvBit2, mRsvBit2, bool);
|
||||
WSF_GETTER(GetRsvBit3, mRsvBit3, bool);
|
||||
WSF_GETTER(GetOpCode, mOpCode, uint16_t);
|
||||
WSF_GETTER(GetMaskBit, mMaskBit, bool);
|
||||
WSF_GETTER(GetMask, mMask, uint32_t);
|
||||
|
||||
#undef WSF_GETTER
|
||||
|
||||
NS_IMETHODIMP
|
||||
WebSocketFrame::GetPayload(nsACString& aValue)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
aValue = mPayload;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
} // net namespace
|
||||
} // mozilla namespace
|
44
netwerk/protocol/websocket/WebSocketFrame.h
Normal file
44
netwerk/protocol/websocket/WebSocketFrame.h
Normal file
@ -0,0 +1,44 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef mozilla_net_WebSocketFrame_h
|
||||
#define mozilla_net_WebSocketFrame_h
|
||||
|
||||
#include "nsAutoPtr.h"
|
||||
#include "nsIWebSocketFrameService.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace net {
|
||||
|
||||
class WebSocketFrame final : public nsIWebSocketFrame
|
||||
{
|
||||
public:
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
NS_DECL_NSIWEBSOCKETFRAME
|
||||
|
||||
WebSocketFrame(bool aFinBit, bool aRsvBit1, bool aRsvBit2, bool aRsvBit3,
|
||||
uint8_t aOpCode, bool aMaskBit, uint32_t aMask,
|
||||
const nsCString& aPayload);
|
||||
|
||||
private:
|
||||
~WebSocketFrame();
|
||||
|
||||
bool mFinBit : 1;
|
||||
bool mRsvBit1 : 1;
|
||||
bool mRsvBit2 : 1;
|
||||
bool mRsvBit3 : 1;
|
||||
bool mMaskBit : 1;
|
||||
uint8_t mOpCode;
|
||||
|
||||
uint32_t mMask;
|
||||
|
||||
nsCString mPayload;
|
||||
};
|
||||
|
||||
} // net namespace
|
||||
} // mozilla namespace
|
||||
|
||||
#endif // mozilla_net_WebSocketFrame_h
|
356
netwerk/protocol/websocket/WebSocketFrameService.cpp
Normal file
356
netwerk/protocol/websocket/WebSocketFrameService.cpp
Normal file
@ -0,0 +1,356 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "WebSocketFrame.h"
|
||||
#include "WebSocketFrameService.h"
|
||||
|
||||
#include "mozilla/StaticPtr.h"
|
||||
#include "nsISupportsPrimitives.h"
|
||||
#include "nsXULAppAPI.h"
|
||||
|
||||
extern PRThread *gSocketThread;
|
||||
|
||||
namespace mozilla {
|
||||
namespace net {
|
||||
|
||||
namespace {
|
||||
|
||||
StaticRefPtr<WebSocketFrameService> gWebSocketFrameService;
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
class WebSocketFrameRunnable final : public nsRunnable
|
||||
{
|
||||
public:
|
||||
WebSocketFrameRunnable(uint32_t aWebSocketSerialID,
|
||||
uint64_t aInnerWindowID,
|
||||
WebSocketFrame* aFrame,
|
||||
bool aFrameSent)
|
||||
: mWebSocketSerialID(aWebSocketSerialID)
|
||||
, mInnerWindowID(aInnerWindowID)
|
||||
, mFrame(aFrame)
|
||||
, mFrameSent(aFrameSent)
|
||||
{}
|
||||
|
||||
NS_IMETHOD Run() override
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
RefPtr<WebSocketFrameService> service =
|
||||
WebSocketFrameService::GetOrCreate();
|
||||
MOZ_ASSERT(service);
|
||||
|
||||
WebSocketFrameService::WindowListeners* listeners =
|
||||
service->GetListeners(mInnerWindowID);
|
||||
if (!listeners) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult rv;
|
||||
WebSocketFrameService::WindowListeners::ForwardIterator iter(*listeners);
|
||||
while (iter.HasMore()) {
|
||||
nsCOMPtr<nsIWebSocketFrameListener> listener = iter.GetNext();
|
||||
|
||||
if (mFrameSent) {
|
||||
rv = listener->FrameSent(mWebSocketSerialID, mFrame);
|
||||
} else {
|
||||
rv = listener->FrameReceived(mWebSocketSerialID, mFrame);
|
||||
}
|
||||
|
||||
NS_WARN_IF(NS_FAILED(rv));
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
protected:
|
||||
~WebSocketFrameRunnable()
|
||||
{}
|
||||
|
||||
uint32_t mWebSocketSerialID;
|
||||
uint64_t mInnerWindowID;
|
||||
|
||||
RefPtr<WebSocketFrame> mFrame;
|
||||
|
||||
bool mFrameSent;
|
||||
};
|
||||
|
||||
/* static */ already_AddRefed<WebSocketFrameService>
|
||||
WebSocketFrameService::GetOrCreate()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(XRE_IsParentProcess());
|
||||
|
||||
if (!gWebSocketFrameService) {
|
||||
gWebSocketFrameService = new WebSocketFrameService();
|
||||
}
|
||||
|
||||
RefPtr<WebSocketFrameService> service = gWebSocketFrameService.get();
|
||||
return service.forget();
|
||||
}
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN(WebSocketFrameService)
|
||||
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWebSocketFrameService)
|
||||
NS_INTERFACE_MAP_ENTRY(nsIObserver)
|
||||
NS_INTERFACE_MAP_ENTRY(nsIWebSocketFrameService)
|
||||
NS_INTERFACE_MAP_END
|
||||
|
||||
NS_IMPL_ADDREF(WebSocketFrameService)
|
||||
NS_IMPL_RELEASE(WebSocketFrameService)
|
||||
|
||||
WebSocketFrameService::WebSocketFrameService()
|
||||
: mCountListeners(0)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(XRE_IsParentProcess());
|
||||
|
||||
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
|
||||
if (obs) {
|
||||
obs->AddObserver(this, "xpcom-shutdown", false);
|
||||
obs->AddObserver(this, "inner-window-destroyed", false);
|
||||
}
|
||||
}
|
||||
|
||||
WebSocketFrameService::~WebSocketFrameService()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
}
|
||||
|
||||
void
|
||||
WebSocketFrameService::FrameReceived(uint32_t aWebSocketSerialID,
|
||||
uint64_t aInnerWindowID,
|
||||
WebSocketFrame* aFrame)
|
||||
{
|
||||
MOZ_ASSERT(aFrame);
|
||||
|
||||
// This method can be called only from a the network thread.
|
||||
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
|
||||
|
||||
// Let's continue only if we have some listeners.
|
||||
if (!HasListeners()) {
|
||||
return;
|
||||
}
|
||||
|
||||
RefPtr<WebSocketFrameRunnable> runnable =
|
||||
new WebSocketFrameRunnable(aWebSocketSerialID, aInnerWindowID,
|
||||
aFrame, false /* frameSent */);
|
||||
nsresult rv = NS_DispatchToMainThread(runnable);
|
||||
NS_WARN_IF(NS_FAILED(rv));
|
||||
}
|
||||
|
||||
void
|
||||
WebSocketFrameService::FrameSent(uint32_t aWebSocketSerialID,
|
||||
uint64_t aInnerWindowID,
|
||||
WebSocketFrame* aFrame)
|
||||
{
|
||||
MOZ_ASSERT(aFrame);
|
||||
|
||||
// This method can be called only from a the network thread.
|
||||
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
|
||||
|
||||
// Let's continue only if we have some listeners.
|
||||
if (!HasListeners()) {
|
||||
return;
|
||||
}
|
||||
|
||||
RefPtr<WebSocketFrameRunnable> runnable =
|
||||
new WebSocketFrameRunnable(aWebSocketSerialID, aInnerWindowID,
|
||||
aFrame, true /* frameSent */);
|
||||
|
||||
nsresult rv = NS_DispatchToMainThread(runnable);
|
||||
NS_WARN_IF(NS_FAILED(rv));
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
WebSocketFrameService::AddListener(uint64_t aInnerWindowID,
|
||||
nsIWebSocketFrameListener* aListener)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (!aListener) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
++mCountListeners;
|
||||
|
||||
WindowListeners* listeners = mWindows.Get(aInnerWindowID);
|
||||
if (!listeners) {
|
||||
listeners = new WindowListeners();
|
||||
mWindows.Put(aInnerWindowID, listeners);
|
||||
}
|
||||
|
||||
listeners->AppendElement(aListener);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
WebSocketFrameService::RemoveListener(uint64_t aInnerWindowID,
|
||||
nsIWebSocketFrameListener* aListener)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (!aListener) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
WindowListeners* listeners = mWindows.Get(aInnerWindowID);
|
||||
if (!listeners) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
if (!listeners->RemoveElement(aListener)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// The last listener for this window.
|
||||
if (listeners->IsEmpty()) {
|
||||
mWindows.Remove(aInnerWindowID);
|
||||
}
|
||||
|
||||
MOZ_ASSERT(mCountListeners);
|
||||
--mCountListeners;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
WebSocketFrameService::Observe(nsISupports* aSubject, const char* aTopic,
|
||||
const char16_t* aData)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (!strcmp(aTopic, "xpcom-shutdown")) {
|
||||
Shutdown();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (!strcmp(aTopic, "inner-window-destroyed") && HasListeners()) {
|
||||
nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
|
||||
NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
|
||||
|
||||
uint64_t innerID;
|
||||
nsresult rv = wrapper->GetData(&innerID);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
WindowListeners* listeners = mWindows.Get(innerID);
|
||||
if (!listeners) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(mCountListeners >= listeners->Length());
|
||||
mCountListeners -= listeners->Length();
|
||||
|
||||
mWindows.Remove(innerID);
|
||||
}
|
||||
|
||||
// This should not happen.
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
void
|
||||
WebSocketFrameService::Shutdown()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (gWebSocketFrameService) {
|
||||
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
|
||||
if (obs) {
|
||||
obs->RemoveObserver(gWebSocketFrameService, "xpcom-shutdown");
|
||||
obs->RemoveObserver(gWebSocketFrameService, "inner-window-destroyed");
|
||||
}
|
||||
|
||||
mWindows.Clear();
|
||||
gWebSocketFrameService = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
WebSocketFrameService::HasListeners() const
|
||||
{
|
||||
return !!mCountListeners;
|
||||
}
|
||||
|
||||
WebSocketFrameService::WindowListeners*
|
||||
WebSocketFrameService::GetListeners(uint64_t aInnerWindowID) const
|
||||
{
|
||||
return mWindows.Get(aInnerWindowID);
|
||||
}
|
||||
|
||||
WebSocketFrame*
|
||||
WebSocketFrameService::CreateFrameIfNeeded(bool aFinBit, bool aRsvBit1,
|
||||
bool aRsvBit2, bool aRsvBit3,
|
||||
uint8_t aOpCode, bool aMaskBit,
|
||||
uint32_t aMask,
|
||||
const nsCString& aPayload)
|
||||
{
|
||||
if (!HasListeners()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return new WebSocketFrame(aFinBit, aRsvBit1, aRsvBit2, aRsvBit3, aOpCode,
|
||||
aMaskBit, aMask, aPayload);
|
||||
}
|
||||
|
||||
WebSocketFrame*
|
||||
WebSocketFrameService::CreateFrameIfNeeded(bool aFinBit, bool aRsvBit1,
|
||||
bool aRsvBit2, bool aRsvBit3,
|
||||
uint8_t aOpCode, bool aMaskBit,
|
||||
uint32_t aMask, uint8_t* aPayload,
|
||||
uint32_t aPayloadLength)
|
||||
{
|
||||
if (!HasListeners()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsAutoCString payloadStr;
|
||||
if (NS_WARN_IF(!(payloadStr.Assign((const char*) aPayload, aPayloadLength,
|
||||
mozilla::fallible)))) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return new WebSocketFrame(aFinBit, aRsvBit1, aRsvBit2, aRsvBit3, aOpCode,
|
||||
aMaskBit, aMask, payloadStr);
|
||||
}
|
||||
|
||||
WebSocketFrame*
|
||||
WebSocketFrameService::CreateFrameIfNeeded(bool aFinBit, bool aRsvBit1,
|
||||
bool aRsvBit2, bool aRsvBit3,
|
||||
uint8_t aOpCode, bool aMaskBit,
|
||||
uint32_t aMask,
|
||||
uint8_t* aPayloadInHdr,
|
||||
uint32_t aPayloadInHdrLength,
|
||||
uint8_t* aPayload,
|
||||
uint32_t aPayloadLength)
|
||||
{
|
||||
if (!HasListeners()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
uint32_t payloadLength = aPayloadLength + aPayloadInHdrLength;
|
||||
|
||||
nsAutoArrayPtr<uint8_t> payload(new uint8_t[payloadLength]);
|
||||
if (NS_WARN_IF(!payload)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (aPayloadInHdrLength) {
|
||||
memcpy(payload, aPayloadInHdr, aPayloadInHdrLength);
|
||||
}
|
||||
|
||||
memcpy(payload + aPayloadInHdrLength, aPayload, aPayloadLength);
|
||||
|
||||
nsAutoCString payloadStr;
|
||||
if (NS_WARN_IF(!(payloadStr.Assign((const char*) payload.get(), payloadLength,
|
||||
mozilla::fallible)))) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return new WebSocketFrame(aFinBit, aRsvBit1, aRsvBit2, aRsvBit3, aOpCode,
|
||||
aMaskBit, aMask, payloadStr);
|
||||
}
|
||||
|
||||
} // net namespace
|
||||
} // mozilla namespace
|
81
netwerk/protocol/websocket/WebSocketFrameService.h
Normal file
81
netwerk/protocol/websocket/WebSocketFrameService.h
Normal file
@ -0,0 +1,81 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef mozilla_net_WebSocketFrameService_h
|
||||
#define mozilla_net_WebSocketFrameService_h
|
||||
|
||||
#include "mozilla/Atomics.h"
|
||||
#include "nsIWebSocketFrameService.h"
|
||||
#include "nsAutoPtr.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsClassHashtable.h"
|
||||
#include "nsHashKeys.h"
|
||||
#include "nsIObserver.h"
|
||||
#include "nsISupportsImpl.h"
|
||||
#include "nsTObserverArray.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace net {
|
||||
|
||||
class WebSocketFrame;
|
||||
|
||||
class WebSocketFrameService final : public nsIWebSocketFrameService
|
||||
, public nsIObserver
|
||||
{
|
||||
friend class WebSocketFrameRunnable;
|
||||
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIOBSERVER
|
||||
NS_DECL_NSIWEBSOCKETFRAMESERVICE
|
||||
|
||||
static already_AddRefed<WebSocketFrameService> GetOrCreate();
|
||||
|
||||
void FrameReceived(uint32_t aWebSocketSerialID,
|
||||
uint64_t aInnerWindowID,
|
||||
WebSocketFrame* aFrame);
|
||||
|
||||
void FrameSent(uint32_t aWebSocketSerialID,
|
||||
uint64_t aInnerWindowID,
|
||||
WebSocketFrame* aFrame);
|
||||
|
||||
WebSocketFrame*
|
||||
CreateFrameIfNeeded(bool aFinBit, bool aRsvBit1, bool aRsvBit2, bool aRsvBit3,
|
||||
uint8_t aOpCode, bool aMaskBit, uint32_t aMask,
|
||||
const nsCString& aPayload);
|
||||
|
||||
WebSocketFrame*
|
||||
CreateFrameIfNeeded(bool aFinBit, bool aRsvBit1, bool aRsvBit2, bool aRsvBit3,
|
||||
uint8_t aOpCode, bool aMaskBit, uint32_t aMask,
|
||||
uint8_t* aPayload, uint32_t aPayloadLength);
|
||||
|
||||
WebSocketFrame*
|
||||
CreateFrameIfNeeded(bool aFinBit, bool aRsvBit1, bool aRsvBit2, bool aRsvBit3,
|
||||
uint8_t aOpCode, bool aMaskBit, uint32_t aMask,
|
||||
uint8_t* aPayloadInHdr, uint32_t aPayloadInHdrLength,
|
||||
uint8_t* aPayload, uint32_t aPayloadLength);
|
||||
|
||||
private:
|
||||
WebSocketFrameService();
|
||||
~WebSocketFrameService();
|
||||
|
||||
bool HasListeners() const;
|
||||
void Shutdown();
|
||||
|
||||
typedef nsTObserverArray<nsCOMPtr<nsIWebSocketFrameListener>> WindowListeners;
|
||||
|
||||
WindowListeners* GetListeners(uint64_t aInnerWindowID) const;
|
||||
|
||||
// Used only on the main-thread.
|
||||
nsClassHashtable<nsUint64HashKey, WindowListeners> mWindows;
|
||||
|
||||
Atomic<uint64_t> mCountListeners;
|
||||
};
|
||||
|
||||
} // net namespace
|
||||
} // mozilla namespace
|
||||
|
||||
#endif // mozilla_net_WebSocketFrameService_h
|
@ -6,6 +6,7 @@
|
||||
|
||||
XPIDL_SOURCES += [
|
||||
'nsIWebSocketChannel.idl',
|
||||
'nsIWebSocketFrameService.idl',
|
||||
'nsIWebSocketListener.idl',
|
||||
]
|
||||
|
||||
@ -16,6 +17,7 @@ EXPORTS.mozilla.net += [
|
||||
'WebSocketChannel.h',
|
||||
'WebSocketChannelChild.h',
|
||||
'WebSocketChannelParent.h',
|
||||
'WebSocketFrameService.h',
|
||||
]
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
@ -23,6 +25,8 @@ UNIFIED_SOURCES += [
|
||||
'WebSocketChannel.cpp',
|
||||
'WebSocketChannelChild.cpp',
|
||||
'WebSocketChannelParent.cpp',
|
||||
'WebSocketFrame.cpp',
|
||||
'WebSocketFrameService.cpp',
|
||||
]
|
||||
|
||||
IPDL_SOURCES += [
|
||||
|
54
netwerk/protocol/websocket/nsIWebSocketFrameService.idl
Normal file
54
netwerk/protocol/websocket/nsIWebSocketFrameService.idl
Normal file
@ -0,0 +1,54 @@
|
||||
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "nsISupports.idl"
|
||||
|
||||
[scriptable, builtinclass, uuid(e668b6bf-d29d-4861-b1bc-bab70da4da5c)]
|
||||
interface nsIWebSocketFrame : nsISupports
|
||||
{
|
||||
readonly attribute boolean finBit;
|
||||
|
||||
readonly attribute boolean rsvBit1;
|
||||
readonly attribute boolean rsvBit2;
|
||||
readonly attribute boolean rsvBit3;
|
||||
|
||||
readonly attribute unsigned short opCode;
|
||||
|
||||
readonly attribute boolean maskBit;
|
||||
|
||||
readonly attribute unsigned long mask;
|
||||
|
||||
readonly attribute ACString payload;
|
||||
|
||||
// Non-Control opCode values:
|
||||
const long OPCODE_CONTINUATION = 0x0;
|
||||
const long OPCODE_TEXT = 0x1;
|
||||
const long OPCODE_BINARY = 0x2;
|
||||
|
||||
// Control opCode values:
|
||||
const long OPCODE_CLOSE = 0x8;
|
||||
const long OPCODE_PING = 0x9;
|
||||
const long OPCODE_PONG = 0xA;
|
||||
};
|
||||
|
||||
[scriptable, uuid(f6a7ec44-23b2-4c77-bb94-f11a8df5a874)]
|
||||
interface nsIWebSocketFrameListener : nsISupports
|
||||
{
|
||||
void frameReceived(in unsigned long aWebSocketSerialID,
|
||||
in nsIWebSocketFrame aFrame);
|
||||
|
||||
void frameSent(in unsigned long aWebSocketSerialID,
|
||||
in nsIWebSocketFrame aFrame);
|
||||
};
|
||||
|
||||
[scriptable, builtinclass, uuid(b89d1b90-2cf3-4d8f-ac21-5aedfb25c760)]
|
||||
interface nsIWebSocketFrameService : nsISupports
|
||||
{
|
||||
void addListener(in unsigned long long aInnerWindowID,
|
||||
in nsIWebSocketFrameListener aListener);
|
||||
|
||||
void removeListener(in unsigned long long aInnerWindowID,
|
||||
in nsIWebSocketFrameListener aListener);
|
||||
};
|
Loading…
Reference in New Issue
Block a user