Bug 1203802 - Websocket Frame Listener API for devtool Network Inspector - part 2 - WebSocketFrameService, r=michal
authorAndrea Marchesini <amarchesini@mozilla.com>
Mon, 26 Oct 2015 15:30:11 +0000
changeset 304688 1465c8af67d72c1e554a15d572cabdba4864cffe
parent 304687 bf77e2eef0a0d12f2b0c5dc8d771d436291b5de8
child 304689 709a898fdc8bbf27f5f250e10e638e207005ae58
push id1001
push userraliiev@mozilla.com
push dateMon, 18 Jan 2016 19:06:03 +0000
treeherdermozilla-release@8b89261f3ac4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmichal
bugs1203802
milestone44.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1203802 - Websocket Frame Listener API for devtool Network Inspector - part 2 - WebSocketFrameService, r=michal
dom/base/WebSocket.cpp
dom/base/test/chrome.ini
dom/base/test/test_websocket_frame.html
layout/build/nsLayoutModule.cpp
netwerk/protocol/websocket/WebSocketChannel.cpp
netwerk/protocol/websocket/WebSocketChannel.h
netwerk/protocol/websocket/WebSocketFrame.cpp
netwerk/protocol/websocket/WebSocketFrame.h
netwerk/protocol/websocket/WebSocketFrameService.cpp
netwerk/protocol/websocket/WebSocketFrameService.h
netwerk/protocol/websocket/moz.build
netwerk/protocol/websocket/nsIWebSocketFrameService.idl
--- a/dom/base/WebSocket.cpp
+++ b/dom/base/WebSocket.cpp
@@ -1139,16 +1139,20 @@ protected:
       return true;
     }
 
     uint64_t windowID = 0;
     nsCOMPtr<nsIDOMWindow> topWindow;
     aWindow->GetScriptableTop(getter_AddRefs(topWindow));
     nsCOMPtr<nsPIDOMWindow> pTopWindow = do_QueryInterface(topWindow);
     if (pTopWindow) {
+      pTopWindow = pTopWindow->GetCurrentInnerWindow();
+    }
+
+    if (pTopWindow) {
       windowID = pTopWindow->WindowID();
     }
 
     mImpl->AsyncOpen(principal, windowID, mRv);
     return true;
   }
 
   virtual bool InitWindowless(WorkerPrivate* aTopLevelWorkerPrivate) override
@@ -1319,16 +1323,20 @@ WebSocket::Constructor(const GlobalObjec
   if (NS_IsMainThread()) {
     MOZ_ASSERT(principal);
 
     uint64_t windowID = 0;
     nsCOMPtr<nsIDOMWindow> topWindow;
     ownerWindow->GetScriptableTop(getter_AddRefs(topWindow));
     nsCOMPtr<nsPIDOMWindow> pTopWindow = do_QueryInterface(topWindow);
     if (pTopWindow) {
+      pTopWindow = pTopWindow->GetCurrentInnerWindow();
+    }
+
+    if (pTopWindow) {
       windowID = pTopWindow->WindowID();
     }
 
     webSocket->mImpl->AsyncOpen(principal, windowID, aRv);
   } else {
     RefPtr<AsyncOpenRunnable> runnable =
       new AsyncOpenRunnable(webSocket->mImpl, aRv);
     runnable->Dispatch(aGlobal.Context());
--- a/dom/base/test/chrome.ini
+++ b/dom/base/test/chrome.ini
@@ -21,8 +21,9 @@ support-files =
 [test_messagemanager_principal.html]
 [test_messagemanager_send_principal.html]
 skip-if = buildapp == 'mulet'
 [test_bug945152.html]
 run-if = os == 'linux'
 [test_bug1008126.html]
 run-if = os == 'linux'
 [test_sandboxed_blob_uri.html]
+[test_websocket_frame.html]
new file mode 100644
--- /dev/null
+++ b/dom/base/test/test_websocket_frame.html
@@ -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>
--- a/layout/build/nsLayoutModule.cpp
+++ b/layout/build/nsLayoutModule.cpp
@@ -226,16 +226,17 @@ static void Shutdown();
 #include "nsIMobileMessageService.h"
 #include "nsIMobileMessageDatabaseService.h"
 #include "nsIPowerManagerService.h"
 #include "nsIAlarmHalService.h"
 #include "nsIMediaManager.h"
 #include "mozilla/dom/nsMixedContentBlocker.h"
 
 #include "AudioChannelService.h"
+#include "mozilla/net/WebSocketFrameService.h"
 
 #include "mozilla/dom/DataStoreService.h"
 
 #include "mozilla/dom/power/PowerManagerService.h"
 #include "mozilla/dom/alarm/AlarmHalService.h"
 #include "mozilla/dom/time/TimeService.h"
 #include "StreamingProtocolService.h"
 
@@ -627,23 +628,28 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsDOMScri
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(Geolocation, Init)
 
 #define NS_GEOLOCATION_SERVICE_CID \
   { 0x404d02a, 0x1CA, 0xAAAB, { 0x47, 0x62, 0x94, 0x4b, 0x1b, 0xf2, 0xf7, 0xb5 } }
 
 #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 }}
 
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsGeolocationService, nsGeolocationService::GetGeolocationService)
 
 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
 NS_GENERIC_FACTORY_CONSTRUCTOR(FakeSpeechRecognitionService)
 #endif
 #ifdef MOZ_WEBSPEECH_POCKETSPHINX
 NS_GENERIC_FACTORY_CONSTRUCTOR(PocketSphinxSpeechRecognitionService)
 #endif
@@ -784,16 +790,17 @@ NS_DEFINE_NAMED_CID(NS_HTMLEDITOR_CID);
 NS_DEFINE_NAMED_CID(NS_EDITORCONTROLLER_CID);
 NS_DEFINE_NAMED_CID(NS_EDITINGCONTROLLER_CID);
 NS_DEFINE_NAMED_CID(NS_EDITORCOMMANDTABLE_CID);
 NS_DEFINE_NAMED_CID(NS_EDITINGCOMMANDTABLE_CID);
 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);
 NS_DEFINE_NAMED_CID(CSPSERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_CSPCONTEXT_CID);
 NS_DEFINE_NAMED_CID(NS_MIXEDCONTENTBLOCKER_CID);
 NS_DEFINE_NAMED_CID(NS_EVENTLISTENERSERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_GLOBALMESSAGEMANAGER_CID);
@@ -1091,16 +1098,17 @@ static const mozilla::Module::CIDEntry k
   { &kNS_EDITORCONTROLLER_CID, false, nullptr, nsEditorControllerConstructor },
   { &kNS_EDITINGCONTROLLER_CID, false, nullptr, nsEditingControllerConstructor },
   { &kNS_EDITORCOMMANDTABLE_CID, false, nullptr, nsEditorCommandTableConstructor },
   { &kNS_EDITINGCOMMANDTABLE_CID, false, nullptr, nsEditingCommandTableConstructor },
   { &kNS_TEXTSERVICESDOCUMENT_CID, false, nullptr, nsTextServicesDocumentConstructor },
   { &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
   { &kNS_FAKE_SPEECH_RECOGNITION_SERVICE_CID, false, nullptr, FakeSpeechRecognitionServiceConstructor },
 #endif
 #ifdef MOZ_WEBSPEECH_POCKETSPHINX
   { &kNS_POCKETSPHINX_SPEECH_RECOGNITION_SERVICE_CID, false, nullptr, PocketSphinxSpeechRecognitionServiceConstructor },
 #endif
@@ -1258,16 +1266,17 @@ static const mozilla::Module::ContractID
   { NS_AUDIOCHANNELAGENT_CONTRACTID, &kNS_AUDIOCHANNELAGENT_CID },
   { "@mozilla.org/editor/htmleditor;1", &kNS_HTMLEDITOR_CID },
   { "@mozilla.org/editor/editorcontroller;1", &kNS_EDITORCONTROLLER_CID },
   { "@mozilla.org/editor/editingcontroller;1", &kNS_EDITINGCONTROLLER_CID },
   { "@mozilla.org/textservices/textservicesdocument;1", &kNS_TEXTSERVICESDOCUMENT_CID },
   { "@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
   { NS_SPEECH_RECOGNITION_SERVICE_CONTRACTID_PREFIX "fake", &kNS_FAKE_SPEECH_RECOGNITION_SERVICE_CID },
 #endif
 #ifdef MOZ_WEBSPEECH_POCKETSPHINX
   { NS_SPEECH_RECOGNITION_SERVICE_CONTRACTID_PREFIX "pocketsphinx-en-US", &kNS_POCKETSPHINX_SPEECH_RECOGNITION_SERVICE_CID },
 #endif
--- a/netwerk/protocol/websocket/WebSocketChannel.cpp
+++ b/netwerk/protocol/websocket/WebSocketChannel.cpp
@@ -1,21 +1,23 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set sw=2 ts=8 et 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 "WebSocketLog.h"
 #include "WebSocketChannel.h"
 
 #include "mozilla/Atomics.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/Endian.h"
 #include "mozilla/MathAlgorithms.h"
+#include "mozilla/net/WebSocketFrameService.h"
 
 #include "nsIURI.h"
 #include "nsIChannel.h"
 #include "nsICryptoHash.h"
 #include "nsIRunnable.h"
 #include "nsIPrefBranch.h"
 #include "nsIPrefService.h"
 #include "nsICancelable.h"
@@ -958,17 +960,18 @@ static const char* msgNames[] = {
 
 class OutboundMessage
 {
 public:
   OutboundMessage(WsMsgType type, nsCString *str)
     : 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;
   }
 
   OutboundMessage(nsIInputStream *stream, uint32_t length)
     : mMsgType(kMsgTypeStream), mLength(length), mDeflated(false)
     , mOrigLength(0)
   {
     MOZ_COUNT_CTOR(OutboundMessage);
@@ -978,17 +981,19 @@ public:
 
  ~OutboundMessage() {
     MOZ_COUNT_DTOR(OutboundMessage);
     switch (mMsgType) {
       case kMsgTypeString:
       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
         if (mMsg.pStream) {
           mMsg.pStream->Close();
           mMsg.pStream->Release();
         }
         break;
@@ -999,23 +1004,31 @@ public:
 
   WsMsgType GetMsgType() const { return mMsgType; }
   int32_t Length() const { return mLength; }
   int32_t OrigLength() const { return mDeflated ? mOrigLength : mLength; }
 
   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()
   {
     MOZ_ASSERT(mMsgType == kMsgTypeStream, "Not a stream!");
 
 #ifdef DEBUG
     // Make sure we got correct length from Blob
@@ -1026,17 +1039,18 @@ public:
 
     nsAutoPtr<nsCString> temp(new nsCString());
     nsresult rv = NS_ReadInputStreamToString(mMsg.pStream, *temp, mLength);
 
     NS_ENSURE_SUCCESS(rv, rv);
 
     mMsg.pStream->Close();
     mMsg.pStream->Release();
-    mMsg.pString = temp.forget();
+    mMsg.pString.mValue = temp.forget();
+    mMsg.pString.mOrigValue = nullptr;
     mMsgType = kMsgTypeBinaryString;
 
     return NS_OK;
   }
 
   bool DeflatePayload(PMCECompression *aCompressor)
   {
     MOZ_ASSERT(mMsgType != kMsgTypeStream,
@@ -1069,24 +1083,27 @@ public:
            "deflated payload is larger than the original one [deflated=%d, "
            "original=%d]", temp->Length(), mLength));
       return false;
     }
 
     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;
   uint32_t                    mLength;
   bool                        mDeflated;
   uint32_t                    mOrigLength;
 };
 
@@ -1144,17 +1161,17 @@ WebSocketChannel::WebSocketChannel() :
   mTCPClosed(0),
   mOpenedHttpChannel(0),
   mIncrementedSessionCount(0),
   mDecrementedSessionCount(0),
   mMaxMessageSize(INT32_MAX),
   mStopOnClose(NS_OK),
   mServerCloseCode(CLOSE_ABNORMAL),
   mScriptCloseCode(0),
-  mFragmentOpcode(kContinuation),
+  mFragmentOpcode(nsIWebSocketFrame::OPCODE_CONTINUATION),
   mFragmentAccumulator(0),
   mBuffered(0),
   mBufferSize(kIncomingBufferInitialSize),
   mCurrentOut(nullptr),
   mCurrentOutSent(0),
   mDynamicOutputSize(0),
   mDynamicOutput(nullptr),
   mPrivateBrowsing(false),
@@ -1173,16 +1190,18 @@ WebSocketChannel::WebSocketChannel() :
   mFramePtr = mBuffer = static_cast<uint8_t *>(moz_xmalloc(mBufferSize));
 
   nsresult rv;
   mConnectionLogService = do_GetService("@mozilla.org/network/dashboard;1",&rv);
   if (NS_FAILED(rv))
     LOG(("Failed to initiate dashboard service."));
 
   mSerial = sSerialSeed++;
+
+  mFrameService = WebSocketFrameService::GetOrCreate();
 }
 
 WebSocketChannel::~WebSocketChannel()
 {
   LOG(("WebSocketChannel::~WebSocketChannel() %p\n", this));
 
   if (mWasOpened) {
     MOZ_ASSERT(mCalledOnStop, "WebSocket was opened but OnStop was not called");
@@ -1204,16 +1223,17 @@ WebSocketChannel::~WebSocketChannel()
 
   NS_ReleaseOnMainThread(mURI);
   NS_ReleaseOnMainThread(mOriginalURI);
 
   mListenerMT = nullptr;
 
   NS_ReleaseOnMainThread(mLoadGroup);
   NS_ReleaseOnMainThread(mLoadInfo);
+  NS_ReleaseOnMainThread(static_cast<nsIWebSocketFrameService*>(mFrameService.forget().take()));
 }
 
 NS_IMETHODIMP
 WebSocketChannel::Observe(nsISupports *subject,
                           const char *topic,
                           const char16_t *data)
 {
   LOG(("WebSocketChannel::Observe [topic=\"%s\"]\n", topic));
@@ -1496,21 +1516,25 @@ WebSocketChannel::ProcessInput(uint8_t *
       return NS_ERROR_FILE_TOO_BIG;
     }
   }
 
   uint8_t *payload;
   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)
       framingLength += 4;
 
     if (payloadLength64 < 126) {
       if (avail < framingLength)
         break;
@@ -1555,17 +1579,17 @@ WebSocketChannel::ProcessInput(uint8_t *
     LOG(("WebSocketChannel::ProcessInput: Frame accumulated - opcode %d\n",
          opcode));
 
     if (maskBit) {
       // This is unexpected - the server does not generally send masked
       // 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);
     }
 
     // Control codes are required to have the fin bit set
     if (!finBit && (opcode & kControlFrameMask)) {
       LOG(("WebSocketChannel:: fragmented control frame code %d\n", opcode));
       return NS_ERROR_ILLEGAL_VALUE;
     }
@@ -1579,32 +1603,33 @@ WebSocketChannel::ProcessInput(uint8_t *
         LOG(("WebSocketChannel::ProcessInput: received deflated frame\n"));
       } else {
         LOG(("WebSocketChannel::ProcessInput: unexpected reserved bits %x\n",
              rsvBits));
         return NS_ERROR_ILLEGAL_VALUE;
       }
     }
 
-    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;
         }
 
         // For frag > 1 move the data body back on top of the headers
         // so we have contiguous stream of data
         MOZ_ASSERT(mFramePtr + framingLength == payload,
                    "payload offset from frameptr wrong");
@@ -1619,19 +1644,19 @@ WebSocketChannel::ProcessInput(uint8_t *
       if (finBit) {
         LOG(("WebSocketChannel:: Finalizing Fragment\n"));
         payload -= mFragmentAccumulator;
         payloadLength += mFragmentAccumulator;
         avail += mFragmentAccumulator;
         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)) {
       // This frame is not part of a fragment sequence but we
       // have an open fragment.. it must be a control code or else
       // we have a problem
       LOG(("WebSocketChannel:: illegal fragment sequence\n"));
       return NS_ERROR_ILLEGAL_VALUE;
@@ -1639,17 +1664,17 @@ WebSocketChannel::ProcessInput(uint8_t *
 
     if (mServerClosed) {
       LOG(("WebSocketChannel:: ignoring read frame code %d after close\n",
                  opcode));
       // nop
     } 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 " : ""));
 
       if (mListenerMT) {
         nsCString utf8Data;
 
         if (isDeflated) {
@@ -1668,32 +1693,45 @@ WebSocketChannel::ProcessInput(uint8_t *
         }
 
         // Section 8.1 says to fail connection if invalid utf-8 in text message
         if (!IsUTF8(utf8Data, false)) {
           LOG(("WebSocketChannel:: text frame invalid utf-8\n"));
           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) {
           mConnectionLogService->NewMsgReceived(mHost, mSerial, count);
           LOG(("Added new msg received for %s", mHost.get()));
         }
       }
     } else if (opcode & kControlFrameMask) {
       // control frames
       if (payloadLength > 125) {
         LOG(("WebSocketChannel:: bad control frame code %d length %d\n",
              opcode, payloadLength));
         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;
 
         mServerCloseCode = CLOSE_NO_STATUS;
         if (payloadLength >= 2) {
           mServerCloseCode = NetworkEndian::readUint16(payload);
           LOG(("WebSocketChannel:: close recvd code %u\n", mServerCloseCode));
           uint16_t msglen = static_cast<uint16_t>(payloadLength - 2);
@@ -1715,30 +1753,38 @@ WebSocketChannel::ProcessInput(uint8_t *
                  mServerCloseReason.get()));
           }
         }
 
         if (mCloseTimer) {
           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),
                                   NS_DISPATCH_NORMAL);
         }
 
         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 */
         LOG(("WebSocketChannel:: unknown control op code %d\n", opcode));
         return NS_ERROR_ILLEGAL_VALUE;
       }
 
       if (mFragmentAccumulator) {
@@ -1749,17 +1795,21 @@ WebSocketChannel::ProcessInput(uint8_t *
                    "payload offset from frameptr wrong");
         ::memmove(mFramePtr, payload + payloadLength, avail - payloadLength);
         payload = mFramePtr;
         avail -= payloadLength;
         if (mBuffered)
           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 " : ""));
 
       if (mListenerMT) {
         nsCString binaryData;
 
         if (isDeflated) {
@@ -1772,26 +1822,33 @@ WebSocketChannel::ProcessInput(uint8_t *
                binaryData.Length()));
         } else {
           if (!binaryData.Assign((const char *)payload, payloadLength,
                                  mozilla::fallible)) {
             return NS_ERROR_OUT_OF_MEMORY;
           }
         }
 
+        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);
         // To add the header to 'Networking Dashboard' log
         if (mConnectionLogService && !mPrivateBrowsing) {
           mConnectionLogService->NewMsgReceived(mHost, mSerial, 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;
     }
 
     mFramePtr = payload + payloadLength;
     avail -= payloadLength;
     totalAvail = avail;
@@ -1832,17 +1889,17 @@ WebSocketChannel::ProcessInput(uint8_t *
       mBufferSize = kIncomingBufferStableSize;
       free(mBuffer);
       mBuffer = (uint8_t *)moz_xmalloc(mBufferSize);
     }
   }
   return NS_OK;
 }
 
-void
+/* static */ void
 WebSocketChannel::ApplyMask(uint32_t mask, uint8_t *data, uint64_t len)
 {
   if (!data || len == 0)
     return;
 
   // Optimally we want to apply the mask 32 bits at a time,
   // but the buffer might not be alligned. So we first deal with
   // 0 to 3 bytes of preamble individually
@@ -1975,17 +2032,17 @@ WebSocketChannel::PrimeNewOutgoingMessag
     // This is a demand to create a close message
     if (mClientClosed) {
       DeleteCurrentOutGoingMessage();
       PrimeNewOutgoingMessage();
       return;
     }
 
     mClientClosed = 1;
-    mOutHeader[0] = kFinalFragBit | kClose;
+    mOutHeader[0] = kFinalFragBit | nsIWebSocketFrame::OPCODE_CLOSE;
     mOutHeader[1] = kMaskBit;
 
     // payload is offset 6 including 4 for the mask
     payload = mOutHeader + 6;
 
     // The close reason code sits in the first 2 bytes of payload
     // If the channel user provided a code and reason during Close()
     // and there isn't an internal error, use that.
@@ -2029,40 +2086,40 @@ WebSocketChannel::PrimeNewOutgoingMessag
                                       nsITimer::TYPE_ONE_SHOT);
       } else {
         StopSession(rv);
       }
     }
   } 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.
       // Will block socket transport thread if file is blocking.
       // TODO: bug 704447:  don't block socket thread!
       rv = mCurrentOut->ConvertStreamToString();
       if (NS_FAILED(rv)) {
         AbortSession(NS_ERROR_FILE_TOO_BIG);
         return;
       }
       // Now we're a binary string
       msgType = kMsgTypeBinaryString;
 
       // 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
       break;
     }
 
     // deflate the payload if PMCE is negotiated
     if (mPMCECompressor &&
@@ -2114,16 +2171,33 @@ WebSocketChannel::PrimeNewOutgoingMessag
   LOG(("WebSocketChannel::PrimeNewOutgoingMessage() using mask %08x\n", mask));
 
   // We don't mask the framing, but occasionally we stick a little payload
   // data in the buffer used for the framing. Close frames are the current
   // example. This data needs to be masked, but it is never more than a
   // 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);
     payload++;
   }
 
   // Mask the real message payloads
 
@@ -2136,17 +2210,17 @@ WebSocketChannel::PrimeNewOutgoingMessag
     memcpy(mOutHeader + mHdrOutToSend, mCurrentOut->BeginWriting(), len);
     mHdrOutToSend += len;
     mCurrentOutSent = len;
   }
 
   // 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
 WebSocketChannel::DeleteCurrentOutGoingMessage()
 {
   delete mCurrentOut;
   mCurrentOut = nullptr;
   mCurrentOutSent = 0;
--- a/netwerk/protocol/websocket/WebSocketChannel.h
+++ b/netwerk/protocol/websocket/WebSocketChannel.h
@@ -34,26 +34,28 @@
 class nsIAsyncVerifyRedirectCallback;
 class nsIDashboardEventNotifier;
 class nsIEventTarget;
 class nsIHttpChannel;
 class nsIRandomGenerator;
 class nsISocketTransport;
 class nsIURI;
 
-namespace mozilla { namespace net {
+namespace mozilla {
+namespace net {
 
 class OutboundMessage;
 class OutboundEnqueuer;
 class nsWSAdmissionManager;
 class PMCECompression;
 class CallOnMessageAvailable;
 class CallOnStop;
 class CallOnServerClose;
 class CallAcknowledge;
+class WebSocketFrameService;
 
 // Used to enforce "1 connecting websocket per host" rule, and reconnect delays
 enum wsConnectingState {
   NOT_CONNECTING = 0,     // Not yet (or no longer) trying to open connection
   CONNECTING_QUEUED,      // Waiting for other ws to same host to finish opening
   CONNECTING_DELAYED,     // Delayed by "reconnect after failure" algorithm
   CONNECTING_IN_PROGRESS  // Started connection: waiting for result
 };
@@ -65,16 +67,18 @@ class WebSocketChannel : public BaseWebS
                          public nsIOutputStreamCallback,
                          public nsITimerCallback,
                          public nsIDNSListener,
                          public nsIObserver,
                          public nsIProtocolProxyCallback,
                          public nsIInterfaceRequestor,
                          public nsIChannelEventSink
 {
+  friend class WebSocketFrame;
+
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIHTTPUPGRADELISTENER
   NS_DECL_NSIREQUESTOBSERVER
   NS_DECL_NSISTREAMLISTENER
   NS_DECL_NSIINPUTSTREAMCALLBACK
   NS_DECL_NSIOUTPUTSTREAMCALLBACK
   NS_DECL_NSITIMERCALLBACK
@@ -100,33 +104,29 @@ public:
   WebSocketChannel();
   static void Shutdown();
   bool IsOnTargetThread();
 
   // Off main thread URI access.
   void GetEffectiveURL(nsAString& aEffectiveURL) const override;
   bool IsEncrypted() const override;
 
-  enum {
-    // Non Control Frames
-    kContinuation = 0x0,
-    kText =         0x1,
-    kBinary =       0x2,
+  const static uint32_t kControlFrameMask   = 0x8;
 
-    // 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();
 
 private:
   friend class OutboundEnqueuer;
   friend class nsWSAdmissionManager;
   friend class FailDelayManager;
@@ -162,17 +162,18 @@ private:
   void StopSession(nsresult reason);
   void AbortSession(nsresult reason);
   void ReleaseSession();
   void CleanupConnection();
   void IncrementSessionCount();
   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);
   bool UpdateReadBuffer(uint8_t *buffer, uint32_t count,
                         uint32_t accumulatedFragments,
                         uint32_t *available);
 
   inline void ResetPingTimer()
@@ -221,16 +222,18 @@ private:
   nsCOMPtr<nsITimer>              mReconnectDelayTimer;
 
   nsCOMPtr<nsITimer>              mPingTimer;
 
   nsCOMPtr<nsITimer>              mLingeringCloseTimer;
   const static int32_t            kLingeringCloseTimeout =   1000;
   const static int32_t            kLingeringCloseThreshold = 50;
 
+  RefPtr<WebSocketFrameService>   mFrameService;
+
   int32_t                         mMaxConcurrentConnections;
 
   uint64_t                        mInnerWindowID;
 
   // following members are accessed only on the main thread
   uint32_t                        mGotUpgradeOK              : 1;
   uint32_t                        mRecvdHttpUpgradeTransport : 1;
   uint32_t                        mAutoFollowRedirects       : 1;
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketFrame.cpp
@@ -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
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketFrame.h
@@ -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
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketFrameService.cpp
@@ -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
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketFrameService.h
@@ -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
--- a/netwerk/protocol/websocket/moz.build
+++ b/netwerk/protocol/websocket/moz.build
@@ -1,33 +1,37 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 XPIDL_SOURCES += [
     'nsIWebSocketChannel.idl',
+    'nsIWebSocketFrameService.idl',
     'nsIWebSocketListener.idl',
 ]
 
 XPIDL_MODULE = 'necko_websocket'
 
 EXPORTS.mozilla.net += [
     'BaseWebSocketChannel.h',
     'WebSocketChannel.h',
     'WebSocketChannelChild.h',
     'WebSocketChannelParent.h',
+    'WebSocketFrameService.h',
 ]
 
 UNIFIED_SOURCES += [
     'BaseWebSocketChannel.cpp',
     'WebSocketChannel.cpp',
     'WebSocketChannelChild.cpp',
     'WebSocketChannelParent.cpp',
+    'WebSocketFrame.cpp',
+    'WebSocketFrameService.cpp',
 ]
 
 IPDL_SOURCES += [
     'PWebSocket.ipdl',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/websocket/nsIWebSocketFrameService.idl
@@ -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);
+};