Bug 1207090 - Expose TCPSocket to chrome contexts. r=bz a=lizzard
authorJosh Matthews <josh@joshmatthews.net>
Tue, 22 Sep 2015 08:45:00 -0400
changeset 283537 fc9b7d64d4fb47fb561e63813088397052a992f0
parent 283536 1389a10c45421c088b236620904f77f30f15dbcb
child 283538 333965c37e8dac23c3c1ae2de771e4a7d80cefee
push id5245
push userraliiev@mozilla.com
push dateThu, 29 Oct 2015 11:30:51 +0000
treeherdermozilla-esr52@dac831dc1bd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz, lizzard
bugs1207090
milestone43.0a2
Bug 1207090 - Expose TCPSocket to chrome contexts. r=bz a=lizzard
dom/network/TCPServerSocket.cpp
dom/network/TCPSocket.cpp
dom/network/TCPSocket.h
dom/network/moz.build
dom/network/tests/chrome.ini
dom/network/tests/tcpsocket_test.jsm
dom/network/tests/test_tcpsocket_client_and_server_basics.html
dom/network/tests/test_tcpsocket_client_and_server_basics.js
dom/network/tests/test_tcpsocket_jsm.html
dom/webidl/TCPServerSocket.webidl
dom/webidl/TCPServerSocketEvent.webidl
dom/webidl/TCPSocket.webidl
dom/webidl/TCPSocketErrorEvent.webidl
dom/webidl/TCPSocketEvent.webidl
--- a/dom/network/TCPServerSocket.cpp
+++ b/dom/network/TCPServerSocket.cpp
@@ -118,17 +118,17 @@ TCPServerSocket::Close()
     mServerSocket->Close();
   }
 }
 
 void
 TCPServerSocket::FireEvent(const nsAString& aType, TCPSocket* aSocket)
 {
   AutoJSAPI api;
-  api.Init(GetOwner());
+  api.Init(GetOwnerGlobal());
 
   TCPServerSocketEventInit init;
   init.mBubbles = false;
   init.mCancelable = false;
   init.mSocket = aSocket;
 
   nsRefPtr<TCPServerSocketEvent> event =
       TCPServerSocketEvent::Constructor(this, aType, init);
@@ -139,17 +139,17 @@ TCPServerSocket::FireEvent(const nsAStri
   if (mServerBridgeParent) {
     mServerBridgeParent->OnConnect(event);
   }
 }
 
 NS_IMETHODIMP
 TCPServerSocket::OnSocketAccepted(nsIServerSocket* aServer, nsISocketTransport* aTransport)
 {
-  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
+  nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal();
   nsRefPtr<TCPSocket> socket = TCPSocket::CreateAcceptedSocket(global, aTransport, mUseArrayBuffers);
   if (mServerBridgeParent) {
     socket->SetAppIdAndBrowser(mServerBridgeParent->GetAppId(),
                                mServerBridgeParent->GetInBrowser());
   }
   FireEvent(NS_LITERAL_STRING("connect"), socket);
   return NS_OK;
 }
@@ -170,17 +170,17 @@ TCPServerSocket::OnStopListening(nsIServ
   }
   mServerSocket = nullptr;
   return NS_OK;
 }
 
 nsresult
 TCPServerSocket::AcceptChildSocket(TCPSocketChild* aSocketChild)
 {
-  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
+  nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal();
   NS_ENSURE_TRUE(global, NS_ERROR_FAILURE);
   nsRefPtr<TCPSocket> socket = TCPSocket::CreateAcceptedSocket(global, aSocketChild, mUseArrayBuffers);
   NS_ENSURE_TRUE(socket, NS_ERROR_FAILURE);
   FireEvent(NS_LITERAL_STRING("connect"), socket);
   return NS_OK;
 }
 
 void
--- a/dom/network/TCPSocket.cpp
+++ b/dom/network/TCPSocket.cpp
@@ -488,29 +488,29 @@ NS_IMETHODIMP
 TCPSocket::FireEvent(const nsAString& aType)
 {
   if (mSocketBridgeParent) {
     mSocketBridgeParent->FireEvent(aType, mReadyState);
     return NS_OK;
   }
 
   AutoJSAPI api;
-  if (NS_WARN_IF(!api.Init(GetOwner()))) {
+  if (NS_WARN_IF(!api.Init(GetOwnerGlobal()))) {
     return NS_ERROR_FAILURE;
   }
   JS::Rooted<JS::Value> val(api.cx());
   return FireDataEvent(api.cx(), aType, val);
 }
 
 NS_IMETHODIMP
 TCPSocket::FireDataArrayEvent(const nsAString& aType,
                               const InfallibleTArray<uint8_t>& buffer)
 {
   AutoJSAPI api;
-  if (NS_WARN_IF(!api.Init(GetOwner()))) {
+  if (NS_WARN_IF(!api.Init(GetOwnerGlobal()))) {
     return NS_ERROR_FAILURE;
   }
   JSContext* cx = api.cx();
   JS::Rooted<JS::Value> val(cx);
 
   bool ok = IPC::DeserializeArrayBuffer(cx, buffer, &val);
   if (ok) {
     return FireDataEvent(cx, aType, val);
@@ -518,17 +518,17 @@ TCPSocket::FireDataArrayEvent(const nsAS
   return NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
 TCPSocket::FireDataStringEvent(const nsAString& aType,
                                const nsACString& aString)
 {
   AutoJSAPI api;
-  if (NS_WARN_IF(!api.Init(GetOwner()))) {
+  if (NS_WARN_IF(!api.Init(GetOwnerGlobal()))) {
     return NS_ERROR_FAILURE;
   }
   JSContext* cx = api.cx();
   JS::Rooted<JS::Value> val(cx);
 
   bool ok = ToJSValue(cx, NS_ConvertASCIItoUTF16(aString), &val);
   if (ok) {
     return FireDataEvent(cx, aType, val);
@@ -1029,17 +1029,17 @@ TCPSocket::OnDataAvailable(nsIRequest* a
     buffer.SetLength(actual);
 
     if (mSocketBridgeParent) {
       mSocketBridgeParent->FireArrayBufferDataEvent(buffer, mReadyState);
       return NS_OK;
     }
 
     AutoJSAPI api;
-    if (!api.Init(GetOwner())) {
+    if (!api.Init(GetOwnerGlobal())) {
       return NS_ERROR_FAILURE;
     }
     JSContext* cx = api.cx();
 
     JS::Rooted<JS::Value> value(cx);
     if (!ToJSValue(cx, TypedArrayCreator<Uint8Array>(buffer), &value)) {
       return NS_ERROR_FAILURE;
     }
@@ -1052,17 +1052,17 @@ TCPSocket::OnDataAvailable(nsIRequest* a
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (mSocketBridgeParent) {
     mSocketBridgeParent->FireStringDataEvent(data, mReadyState);
     return NS_OK;
   }
 
   AutoJSAPI api;
-  if (!api.Init(GetOwner())) {
+  if (!api.Init(GetOwnerGlobal())) {
     return NS_ERROR_FAILURE;
   }
   JSContext* cx = api.cx();
 
   JS::Rooted<JS::Value> value(cx);
   if (!ToJSValue(cx, NS_ConvertASCIItoUTF16(data), &value)) {
     return NS_ERROR_FAILURE;
   }
@@ -1184,8 +1184,22 @@ TCPSocket::Observe(nsISupports* aSubject
       }
 
       Close();
     }
   }
 
   return NS_OK;
 }
+
+/* static */
+bool
+TCPSocket::ShouldTCPSocketExist(JSContext* aCx, JSObject* aGlobal)
+{
+  JS::Rooted<JSObject*> global(aCx, aGlobal);
+  if (nsContentUtils::IsSystemPrincipal(nsContentUtils::ObjectPrincipal(global))) {
+    return true;
+  }
+
+  const char* const perms[] = { "tcp-socket", nullptr };
+  return Preferences::GetBool("dom.mozTCPSocket.enabled") &&
+      CheckAnyPermissions(aCx, global, perms);
+}
--- a/dom/network/TCPSocket.h
+++ b/dom/network/TCPSocket.h
@@ -83,22 +83,19 @@ public:
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(TCPSocket, DOMEventTargetHelper)
   NS_DECL_NSIREQUESTOBSERVER
   NS_DECL_NSISTREAMLISTENER
   NS_DECL_NSITRANSPORTEVENTSINK
   NS_DECL_NSIINPUTSTREAMCALLBACK
   NS_DECL_NSIOBSERVER
   NS_DECL_NSITCPSOCKETCALLBACK
 
-  nsPIDOMWindow* GetParentObject() const
-  {
-    return GetOwner();
-  }
+  virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
-  virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+  static bool ShouldTCPSocketExist(JSContext* aCx, JSObject* aGlobal);
 
   void GetHost(nsAString& aHost);
   uint32_t Port();
   bool Ssl();
   uint64_t BufferedAmount();
   void Suspend();
   void Resume(ErrorResult& aRv);
   void Close();
--- a/dom/network/moz.build
+++ b/dom/network/moz.build
@@ -4,16 +4,17 @@
 # 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/.
 
 DIRS += ['interfaces']
 
 if CONFIG['MOZ_B2G_RIL']:
     XPCSHELL_TESTS_MANIFESTS += ['tests/unit_stats/xpcshell.ini']
 
+MOCHITEST_CHROME_MANIFESTS += ['tests/chrome.ini']
 MOCHITEST_MANIFESTS += ['tests/mochitest.ini']
 
 EXPORTS.mozilla.dom += [
     'TCPServerSocket.h',
     'TCPSocket.h',
     'UDPSocket.h',
 ]
 
new file mode 100644
--- /dev/null
+++ b/dom/network/tests/chrome.ini
@@ -0,0 +1,7 @@
+[DEFAULT]
+support-files =
+  tcpsocket_test.jsm
+  test_tcpsocket_client_and_server_basics.js
+  add_task.js
+
+[test_tcpsocket_jsm.html]
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/network/tests/tcpsocket_test.jsm
@@ -0,0 +1,13 @@
+this.EXPORTED_SYMBOLS = ['createSocket', 'createServer', 'enablePrefsAndPermissions'];
+
+this.createSocket = function(host, port, options) {
+  return new TCPSocket(host, port, options);
+}
+
+this.createServer = function(port, options, backlog) {
+  return new TCPServerSocket(port, options, backlog);
+}
+
+this.enablePrefsAndPermissions = function() {
+  return false;
+}
--- a/dom/network/tests/test_tcpsocket_client_and_server_basics.html
+++ b/dom/network/tests/test_tcpsocket_client_and_server_basics.html
@@ -8,16 +8,29 @@ of bug 1084245 in order to get coverage 
 https://bugzilla.mozilla.org/show_bug.cgi?id=1084245
 -->
 <head>
   <meta charset="utf-8">
   <title>Test for Bug 1084245</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript" src="add_task.js"></script>
+  <script type="application/javascript">
+    function createServer(port, options, backlog) {
+      return new TCPServerSocket(port, options, backlog);
+    }
+
+    function createSocket(host, port, options) {
+      return new TCPSocket(host, port, options);
+    }
+
+    function enablePrefsAndPermissions() {
+      return true;
+    }
+  </script>
   <script type="application/javascript;version=1.7" src="test_tcpsocket_client_and_server_basics.js"></script>
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1084245">Mozilla Bug 1084245</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 
 </div>
--- a/dom/network/tests/test_tcpsocket_client_and_server_basics.js
+++ b/dom/network/tests/test_tcpsocket_client_and_server_basics.js
@@ -148,44 +148,46 @@ function defer() {
     deferred.resolve = resolve;
     deferred.reject = reject;
   });
   return deferred;
 }
 
 
 function* test_basics() {
-  // Enable our use of TCPSocket
-  let prefDeferred = defer();
-  SpecialPowers.pushPrefEnv(
-    { set: [ ['dom.mozTCPSocket.enabled', true] ] },
-    prefDeferred.resolve);
-  yield prefDeferred.promise;
+  if (enablePrefsAndPermissions()) {
+    // Enable our use of TCPSocket
+    let prefDeferred = defer();
+    SpecialPowers.pushPrefEnv(
+      { set: [ ['dom.mozTCPSocket.enabled', true] ] },
+      prefDeferred.resolve);
+    yield prefDeferred.promise;
 
-  let permDeferred = defer();
-  SpecialPowers.pushPermissions(
-    [ { type: 'tcp-socket', allow: true, context: document } ],
-    permDeferred.resolve);
-  yield permDeferred.promise;
+    let permDeferred = defer();
+    SpecialPowers.pushPermissions(
+      [ { type: 'tcp-socket', allow: true, context: document } ],
+      permDeferred.resolve);
+    yield permDeferred.promise;
+  }
 
   // See bug 903830; in e10s mode we never get to find out the localPort if we
   // let it pick a free port by choosing 0.  This is the same port the xpcshell
   // test was using.
   let serverPort = 8085;
 
   // - Start up a listening socket.
-  let listeningServer = new TCPServerSocket(serverPort,
-                                            { binaryType: 'arraybuffer' },
-                                            SERVER_BACKLOG);
+  let listeningServer = createServer(serverPort,
+                                     { binaryType: 'arraybuffer' },
+                                     SERVER_BACKLOG);
 
   let connectedPromise = waitForConnection(listeningServer);
 
   // -- Open a connection to the server
-  let clientSocket = new TCPSocket('127.0.0.1', serverPort,
-                                   { binaryType: 'arraybuffer' });
+  let clientSocket = createSocket('127.0.0.1', serverPort,
+                                  { binaryType: 'arraybuffer' });
   let clientQueue = listenForEventsOnSocket(clientSocket, 'client');
 
   // (the client connects)
   is((yield clientQueue.waitForEvent()).type, 'open', 'got open event');
   is(clientSocket.readyState, 'open', 'client readyState is open');
 
   // (the server connected)
   let { socket: serverSocket, queue: serverQueue } = yield connectedPromise;
@@ -281,18 +283,18 @@ function* test_basics() {
      'client readyState should be closed after close event');
   is((yield serverQueue.waitForEvent()).type, 'close',
      'The server should get a close event when it closes itself.');
   is(serverSocket.readyState, 'closed',
      'server readyState should be closed after close event');
 
   // -- Re-establish connection
   connectedPromise = waitForConnection(listeningServer);
-  clientSocket = new TCPSocket('127.0.0.1', serverPort,
-                               { binaryType: 'arraybuffer' });
+  clientSocket = createSocket('127.0.0.1', serverPort,
+                              { binaryType: 'arraybuffer' });
   clientQueue = listenForEventsOnSocket(clientSocket, 'client');
   is((yield clientQueue.waitForEvent()).type, 'open', 'got open event');
 
   let connectedResult = yield connectedPromise;
   // destructuring assignment is not yet ES6 compliant, must manually unpack
   serverSocket = connectedResult.socket;
   serverQueue = connectedResult.queue;
 
@@ -308,18 +310,18 @@ function* test_basics() {
   is((yield serverQueue.waitForEvent()).type, 'close',
      'The server should get a close event when the client closes.');
   is(serverSocket.readyState, 'closed',
      'server readyState should be closed after the close event is received');
 
 
   // -- Re-establish connection
   connectedPromise = waitForConnection(listeningServer);
-  clientSocket = new TCPSocket('127.0.0.1', serverPort,
-                               { binaryType: 'arraybuffer' });
+  clientSocket = createSocket('127.0.0.1', serverPort,
+                              { binaryType: 'arraybuffer' });
   clientQueue = listenForEventsOnSocket(clientSocket, 'client');
   is((yield clientQueue.waitForEvent()).type, 'open', 'got open event');
 
   connectedResult = yield connectedPromise;
   // destructuring assignment is not yet ES6 compliant, must manually unpack
   serverSocket = connectedResult.socket;
   serverQueue = connectedResult.queue;
 
@@ -342,18 +344,18 @@ function* test_basics() {
                          'server received/client sent');
   // And a close.
   is((yield serverQueue.waitForEvent()).type, 'close',
      'The drain event should fire after a large send that returned true.');
 
 
   // -- Re-establish connection
   connectedPromise = waitForConnection(listeningServer);
-  clientSocket = new TCPSocket('127.0.0.1', serverPort,
-                               { binaryType: 'string' });
+  clientSocket = createSocket('127.0.0.1', serverPort,
+                              { binaryType: 'string' });
   clientQueue = listenForEventsOnSocket(clientSocket, 'client');
   is((yield clientQueue.waitForEvent()).type, 'open', 'got open event');
 
   connectedResult = yield connectedPromise;
   // destructuring assignment is not yet ES6 compliant, must manually unpack
   serverSocket = connectedResult.socket;
   serverQueue = connectedResult.queue;
 
@@ -370,17 +372,17 @@ function* test_basics() {
 
 
   // -- Close the listening server (and try to connect)
   // We want to verify that the server actually closes / stops listening when
   // we tell it to.
   listeningServer.close();
 
   // - try and connect, get an error
-  clientSocket = new TCPSocket('127.0.0.1', serverPort,
-                               { binaryType: 'arraybuffer' });
+  clientSocket = createSocket('127.0.0.1', serverPort,
+                              { binaryType: 'arraybuffer' });
   clientQueue = listenForEventsOnSocket(clientSocket, 'client');
   is((yield clientQueue.waitForEvent()).type, 'error', 'fail to connect');
   is(clientSocket.readyState, 'closed',
      'client readyState should be closed after the failure to connect');
 }
 
 add_task(test_basics);
new file mode 100644
--- /dev/null
+++ b/dom/network/tests/test_tcpsocket_jsm.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for 1207090</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"/>
+  <script type="application/javascript">
+    Components.utils.import("chrome://mochitests/content/chrome/dom/network/tests/tcpsocket_test.jsm");
+  </script>
+  <script type="application/javascript" src="add_task.js"></script>
+  <script type="application/javascript;version=1.7" src="test_tcpsocket_client_and_server_basics.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1207090">Mozilla Bug 1207090</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+<div id="container"></div>
+</body>
+</html>
--- a/dom/webidl/TCPServerSocket.webidl
+++ b/dom/webidl/TCPServerSocket.webidl
@@ -9,19 +9,18 @@
  * An interface to a server socket that can accept incoming connections for gaia apps.
  */
 
 dictionary ServerSocketOptions {
   TCPSocketBinaryType binaryType = "string";
 };
 
 [Constructor(unsigned short port, optional ServerSocketOptions options, optional unsigned short backlog = 0),
- Pref="dom.mozTCPSocket.enabled",
- CheckAnyPermissions="tcp-socket",
- Exposed=Window]
+ Func="mozilla::dom::TCPSocket::ShouldTCPSocketExist",
+ Exposed=(Window,System)]
 interface TCPServerSocket : EventTarget {
   /**
    * The port of this server socket object.
    */
   readonly attribute unsigned short localPort;
 
   /**
    * The "connect" event is dispatched when a client connection is accepted.
--- a/dom/webidl/TCPServerSocketEvent.webidl
+++ b/dom/webidl/TCPServerSocketEvent.webidl
@@ -1,16 +1,15 @@
 /* -*- 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/. */
 
 [Constructor(DOMString type, optional TCPServerSocketEventInit eventInitDict),
- Pref="dom.mozTCPSocket.enabled",
- CheckAnyPermissions="tcp-socket",
- Exposed=Window]
+ Func="mozilla::dom::TCPSocket::ShouldTCPSocketExist",
+ Exposed=(Window,System)]
 interface TCPServerSocketEvent : Event {
   readonly attribute TCPSocket socket;
 };
 
 dictionary TCPServerSocketEventInit : EventInit {
   TCPSocket? socket = null;
 };
--- a/dom/webidl/TCPSocket.webidl
+++ b/dom/webidl/TCPSocket.webidl
@@ -35,19 +35,18 @@ interface LegacyMozTCPSocket {
   [Throws]
   TCPSocket open(DOMString host, unsigned short port, optional SocketOptions options);
 
   [Throws]
   TCPServerSocket listen(unsigned short port, optional ServerSocketOptions options, optional unsigned short backlog = 0);
 };
 
 [Constructor(DOMString host, unsigned short port, optional SocketOptions options),
- Pref="dom.mozTCPSocket.enabled",
- CheckAnyPermissions="tcp-socket",
- Exposed=Window]
+ Func="mozilla::dom::TCPSocket::ShouldTCPSocketExist",
+ Exposed=(Window,System)]
 interface TCPSocket : EventTarget {
   /**
    * Upgrade an insecure connection to use TLS. Throws if the ready state is not OPEN.
    */
   [Throws] void upgradeToSecure();
 
   /**
    * The UTF16 host of this socket object.
--- a/dom/webidl/TCPSocketErrorEvent.webidl
+++ b/dom/webidl/TCPSocketErrorEvent.webidl
@@ -4,19 +4,19 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* Dispatched as part of the "error" event in the following situations:
 * - if there's an error detected when the TCPSocket closes
 * - if there's an internal error while sending data
 * - if there's an error connecting to the host
 */
 
-[Pref="dom.mozTCPSocket.enabled",
- CheckAnyPermissions="tcp-socket",
- Constructor(DOMString type, optional TCPSocketErrorEventInit eventInitDict)]
+[Func="mozilla::dom::TCPSocket::ShouldTCPSocketExist",
+ Constructor(DOMString type, optional TCPSocketErrorEventInit eventInitDict),
+ Exposed=(Window,System)]
 interface TCPSocketErrorEvent : Event {
   readonly attribute DOMString name;
   readonly attribute DOMString message;
 };
 
 dictionary TCPSocketErrorEventInit : EventInit
 {
   DOMString name = "";
--- a/dom/webidl/TCPSocketEvent.webidl
+++ b/dom/webidl/TCPSocketEvent.webidl
@@ -5,19 +5,18 @@
 
 /**
  * TCPSocketEvent is the event dispatched for all of the events described by TCPSocket,
  * except the "error" event. It contains the socket that was associated with the event,
  * the type of event, and the data associated with the event if the event is a "data" event.
  */
 
 [Constructor(DOMString type, optional TCPSocketEventInit eventInitDict),
- Pref="dom.mozTCPSocket.enabled",
- CheckAnyPermissions="tcp-socket",
- Exposed=Window]
+ Func="mozilla::dom::TCPSocket::ShouldTCPSocketExist",
+ Exposed=(Window,System)]
 interface TCPSocketEvent : Event {
   /**
    * If the event is a "data" event, data will be the bytes read from the network;
    * if the binaryType of the socket was "arraybuffer", this value will be of type
    * ArrayBuffer, otherwise, it will be a ByteString.
    *
    * For other events, data will be an empty string.
    */