Bug 1272099, Bug 1272101 - FlyWeb core implementation, DOM and Network changes. r=baku r=hurley
authorKannan Vijayan <kvijayan@mozilla.com>, Jonas Sicking <jonas@sicking.cc>
Thu, 02 Jun 2016 02:47:00 -0400
changeset 300046 576019c741038e5d6d8100b93c8218c8cc8ea5f8
parent 300045 bc8e2b503a967e9cfdfa16e5c689bc71c595d79e
child 300047 d66df97e2190f0b1cf345ad2008a8b9967ceed41
push id77803
push userkvijayan@mozilla.com
push dateThu, 02 Jun 2016 06:47:13 +0000
treeherdermozilla-inbound@576019c74103 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku, hurley
bugs1272099, 1272101
milestone49.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 1272099, Bug 1272101 - FlyWeb core implementation, DOM and Network changes. r=baku r=hurley
dom/base/Navigator.cpp
dom/base/Navigator.h
dom/base/nsDocument.cpp
dom/bindings/Bindings.conf
dom/events/test/test_all_synthetic_events.html
dom/flyweb/FlyWebDiscoveryManager.cpp
dom/flyweb/FlyWebDiscoveryManager.h
dom/flyweb/FlyWebPublishedServer.cpp
dom/flyweb/FlyWebPublishedServer.h
dom/flyweb/FlyWebServerEvents.cpp
dom/flyweb/FlyWebServerEvents.h
dom/flyweb/FlyWebService.cpp
dom/flyweb/FlyWebService.h
dom/flyweb/HttpServer.cpp
dom/flyweb/HttpServer.h
dom/flyweb/moz.build
dom/moz.build
dom/webidl/FlyWebDiscoveryManager.webidl
dom/webidl/FlyWebFetchEvent.webidl
dom/webidl/FlyWebPublish.webidl
dom/webidl/FlyWebWebSocketEvent.webidl
dom/webidl/Navigator.webidl
dom/webidl/moz.build
layout/build/nsLayoutModule.cpp
modules/libpref/init/all.js
netwerk/base/nsSocketTransport2.cpp
netwerk/base/nsSocketTransport2.h
netwerk/base/nsSocketTransportService2.cpp
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -32,16 +32,17 @@
 #include "mozilla/Preferences.h"
 #include "mozilla/Telemetry.h"
 #include "BatteryManager.h"
 #include "mozilla/dom/DeviceStorageAreaListener.h"
 #include "mozilla/dom/PowerManager.h"
 #include "mozilla/dom/WakeLock.h"
 #include "mozilla/dom/power/PowerManagerService.h"
 #include "mozilla/dom/CellBroadcast.h"
+#include "mozilla/dom/FlyWebService.h"
 #include "mozilla/dom/IccManager.h"
 #include "mozilla/dom/InputPortManager.h"
 #include "mozilla/dom/MobileMessageManager.h"
 #include "mozilla/dom/Permissions.h"
 #include "mozilla/dom/Presentation.h"
 #include "mozilla/dom/ServiceWorkerContainer.h"
 #include "mozilla/dom/TCPSocket.h"
 #include "mozilla/dom/Telephony.h"
@@ -1614,16 +1615,30 @@ Navigator::GetDeprecatedBattery(ErrorRes
   if (doc) {
     doc->WarnOnceAbout(nsIDocument::eNavigatorBattery);
   }
 
   return mBatteryManager;
 }
 
 already_AddRefed<Promise>
+Navigator::PublishServer(const nsAString& aName,
+                         const FlyWebPublishOptions& aOptions,
+                         ErrorResult& aRv)
+{
+  RefPtr<FlyWebService> service = FlyWebService::GetOrCreate();
+  if (!service) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  return service->PublishServer(aName, aOptions, mWindow, aRv);
+}
+
+already_AddRefed<Promise>
 Navigator::GetFeature(const nsAString& aName, ErrorResult& aRv)
 {
   nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(mWindow);
   RefPtr<Promise> p = Promise::Create(go, aRv);
   if (aRv.Failed()) {
     return nullptr;
   }
 
--- a/dom/base/Navigator.h
+++ b/dom/base/Navigator.h
@@ -38,16 +38,18 @@ class Geolocation;
 class systemMessageCallback;
 class MediaDevices;
 struct MediaStreamConstraints;
 class WakeLock;
 class ArrayBufferViewOrBlobOrStringOrFormData;
 struct MobileIdOptions;
 class ServiceWorkerContainer;
 class DOMRequest;
+struct FlyWebPublishOptions;
+struct FlyWebFilter;
 } // namespace dom
 } // namespace mozilla
 
 //*****************************************************************************
 // Navigator: Script "navigator" object
 //*****************************************************************************
 
 namespace mozilla {
@@ -162,16 +164,19 @@ public:
   nsMimeTypeArray* GetMimeTypes(ErrorResult& aRv);
   nsPluginArray* GetPlugins(ErrorResult& aRv);
   Permissions* GetPermissions(ErrorResult& aRv);
   // The XPCOM GetDoNotTrack is ok
   Geolocation* GetGeolocation(ErrorResult& aRv);
   Promise* GetBattery(ErrorResult& aRv);
   battery::BatteryManager* GetDeprecatedBattery(ErrorResult& aRv);
 
+  already_AddRefed<Promise> PublishServer(const nsAString& aName,
+                                          const FlyWebPublishOptions& aOptions,
+                                          ErrorResult& aRv);
   static void AppName(nsAString& aAppName, bool aUsePrefOverriddenValue);
 
   static nsresult GetPlatform(nsAString& aPlatform,
                               bool aUsePrefOverriddenValue);
 
   static nsresult GetAppVersion(nsAString& aAppVersion,
                                 bool aUsePrefOverriddenValue);
 
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -159,16 +159,17 @@
 #include "nsEscape.h"
 #include "nsObjectLoadingContent.h"
 #include "nsHtml5TreeOpExecutor.h"
 #include "mozilla/dom/HTMLLinkElement.h"
 #include "mozilla/dom/HTMLMediaElement.h"
 #include "mozilla/dom/HTMLIFrameElement.h"
 #include "mozilla/dom/HTMLImageElement.h"
 #include "mozilla/dom/MediaSource.h"
+#include "mozilla/dom/FlyWebService.h"
 
 #include "mozAutoDocUpdate.h"
 #include "nsGlobalWindow.h"
 #include "mozilla/dom/EncodingUtils.h"
 #include "nsDOMNavigationTiming.h"
 
 #include "nsSMILAnimationController.h"
 #include "imgIContainer.h"
@@ -8983,16 +8984,23 @@ nsDocument::CanSavePresentation(nsIReque
 #endif
 
   // Don't save presentations for documents containing MSE content, to
   // reduce memory usage.
   if (ContainsMSEContent()) {
     return false;
   }
 
+  // Don't save presentation if there are active FlyWeb connections or FlyWeb
+  // servers.
+  FlyWebService* flyWebService = FlyWebService::GetExisting();
+  if (flyWebService && flyWebService->HasConnectionOrServer(win->WindowID())) {
+    return false;
+  }
+
   if (mSubDocuments) {
     for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
       auto entry = static_cast<SubDocMapEntry*>(iter.Get());
       nsIDocument* subdoc = entry->mSubDocument;
 
       // The aIgnoreRequest we were passed is only for us, so don't pass it on.
       bool canCache = subdoc ? subdoc->CanSavePresentation(nullptr) : false;
       if (!canCache) {
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -493,16 +493,26 @@ DOMInterfaces = {
 'FileReader': {
     'implicitJSContext': [ 'readAsArrayBuffer' ],
 },
 
 'FileReaderSync': {
     'wrapperCache': False,
 },
 
+'FlyWebFetchEvent': {
+    'headerFile': 'FlyWebServerEvents.h',
+    'nativeType': 'mozilla::dom::FlyWebFetchEvent',
+},
+
+'FlyWebWebSocketEvent': {
+    'headerFile': 'FlyWebServerEvents.h',
+    'nativeType': 'mozilla::dom::FlyWebWebSocketEvent',
+},
+
 'FontFaceSet': {
     'implicitJSContext': [ 'load' ],
 },
 
 'FontFaceSetIterator': {
     'wrapperCache': False,
 },
 
--- a/dom/events/test/test_all_synthetic_events.html
+++ b/dom/events/test/test_all_synthetic_events.html
@@ -234,16 +234,20 @@ const kEventConstructors = {
                                                                          aProps.button, aProps.relatedTarget, aProps.dataTransfer);
                                                          return e;
                                                        },
                                              },
   ErrorEvent:                                { create: function (aName, aProps) {
                                                          return new ErrorEvent(aName, aProps);
                                                        },
   },
+  FlyWebFetchEvent:                          { create: null, // Cannot create untrusted event from JS.
+                                             },
+  FlyWebWebSocketEvent:                      { create: null, // Cannot create untrusted event from JS.
+                                             },
   FocusEvent:                                { create: function (aName, aProps) {
                                                          return new FocusEvent(aName, aProps);
                                                        },
                                              },
   FontFaceSetLoadEvent:                      { create: function (aName, aProps) {
                                                          return new FontFaceSetLoadEvent(aName, aProps);
                                                        },
                                              },
new file mode 100644
--- /dev/null
+++ b/dom/flyweb/FlyWebDiscoveryManager.cpp
@@ -0,0 +1,125 @@
+/* -*- 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 "nsString.h"
+#include "nsTHashtable.h"
+#include "nsClassHashtable.h"
+#include "nsIUUIDGenerator.h"
+#include "jsapi.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/Logging.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+
+#include "mozilla/dom/FlyWebDiscoveryManager.h"
+#include "mozilla/dom/FlyWebDiscoveryManagerBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+static LazyLogModule gFlyWebDiscoveryManagerLog("FlyWebDiscoveryManager");
+#undef LOG_I
+#define LOG_I(...) MOZ_LOG(mozilla::dom::gFlyWebDiscoveryManagerLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
+#undef LOG_E
+#define LOG_E(...) MOZ_LOG(mozilla::dom::gFlyWebDiscoveryManagerLog, mozilla::LogLevel::Error, (__VA_ARGS__))
+
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(FlyWebDiscoveryManager)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(FlyWebDiscoveryManager)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(FlyWebDiscoveryManager)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FlyWebDiscoveryManager)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+FlyWebDiscoveryManager::FlyWebDiscoveryManager(nsISupports* aParent,
+                                               FlyWebService* aService)
+  : mParent(aParent)
+  , mService(aService)
+  , mNextId(0)
+{
+}
+
+FlyWebDiscoveryManager::~FlyWebDiscoveryManager()
+{
+  mService->UnregisterDiscoveryManager(this);
+}
+
+JSObject*
+FlyWebDiscoveryManager::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+  return FlyWebDiscoveryManagerBinding::Wrap(aCx, this, aGivenProto);
+}
+
+nsISupports*
+FlyWebDiscoveryManager::GetParentObject() const
+{
+  return mParent;
+}
+
+/* static */ already_AddRefed<FlyWebDiscoveryManager>
+FlyWebDiscoveryManager::Constructor(const GlobalObject& aGlobal, ErrorResult& rv)
+{
+  RefPtr<FlyWebService> service = FlyWebService::GetOrCreate();
+  if (!service) {
+    return nullptr;
+  }
+
+  RefPtr<FlyWebDiscoveryManager> result = new FlyWebDiscoveryManager(
+                    aGlobal.GetAsSupports(), service);
+  return result.forget();
+}
+
+void
+FlyWebDiscoveryManager::ListServices(nsTArray<FlyWebDiscoveredService>& aServices)
+{
+  return mService->ListDiscoveredServices(aServices);
+}
+
+uint32_t
+FlyWebDiscoveryManager::StartDiscovery(FlyWebDiscoveryCallback& aCallback)
+{
+  uint32_t id = GenerateId();
+  mCallbackMap.Put(id, &aCallback);
+  mService->RegisterDiscoveryManager(this);
+  return id;
+}
+
+void
+FlyWebDiscoveryManager::StopDiscovery(uint32_t aId)
+{
+  mCallbackMap.Remove(aId);
+  if (mCallbackMap.Count() == 0) {
+    mService->UnregisterDiscoveryManager(this);
+  }
+}
+
+void
+FlyWebDiscoveryManager::PairWithService(const nsAString& aServiceId,
+                                        FlyWebPairingCallback& aCallback)
+{
+  mService->PairWithService(aServiceId, aCallback);
+}
+
+void
+FlyWebDiscoveryManager::NotifyDiscoveredServicesChanged()
+{
+  nsTArray<FlyWebDiscoveredService> services;
+  ListServices(services);
+  Sequence<FlyWebDiscoveredService> servicesSeq;
+  servicesSeq.SwapElements(services);
+  for (auto iter = mCallbackMap.Iter(); !iter.Done(); iter.Next()) {
+    FlyWebDiscoveryCallback *callback = iter.UserData();
+    ErrorResult err;
+    callback->OnDiscoveredServicesChanged(servicesSeq, err);
+    ENSURE_SUCCESS_VOID(err);
+  }
+}
+
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/flyweb/FlyWebDiscoveryManager.h
@@ -0,0 +1,61 @@
+/* -*- 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_dom_FlyWebDiscoveryManager_h
+#define mozilla_dom_FlyWebDiscoveryManager_h
+
+#include "nsISupportsImpl.h"
+#include "mozilla/ErrorResult.h"
+#include "nsRefPtrHashtable.h"
+#include "nsWrapperCache.h"
+#include "FlyWebDiscoveryManagerBinding.h"
+#include "FlyWebService.h"
+
+namespace mozilla {
+namespace dom {
+
+class FlyWebDiscoveryManager final : public nsISupports
+                                   , public nsWrapperCache
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(FlyWebDiscoveryManager)
+
+  virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+  nsISupports* GetParentObject() const;
+
+  static already_AddRefed<FlyWebDiscoveryManager> Constructor(const GlobalObject& aGlobal,
+                                                              ErrorResult& rv);
+
+  void ListServices(nsTArray<FlyWebDiscoveredService>& aServices);
+  uint32_t StartDiscovery(FlyWebDiscoveryCallback& aCallback);
+  void StopDiscovery(uint32_t aId);
+
+  void PairWithService(const nsAString& aServiceId,
+                       FlyWebPairingCallback& callback);
+
+  void NotifyDiscoveredServicesChanged();
+
+private:
+  FlyWebDiscoveryManager(nsISupports* mParent, FlyWebService* aService);
+  ~FlyWebDiscoveryManager();
+
+  uint32_t GenerateId() {
+    return ++mNextId;
+  }
+
+  nsCOMPtr<nsISupports> mParent;
+  RefPtr<FlyWebService> mService;
+
+  uint32_t mNextId;
+
+  nsRefPtrHashtable<nsUint32HashKey, FlyWebDiscoveryCallback> mCallbackMap;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_FlyWebDiscoveryManager_h
new file mode 100644
--- /dev/null
+++ b/dom/flyweb/FlyWebPublishedServer.cpp
@@ -0,0 +1,212 @@
+/* -*- 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 "mozilla/dom/FlyWebPublishedServer.h"
+#include "mozilla/dom/FlyWebPublishBinding.h"
+#include "mozilla/dom/FlyWebService.h"
+#include "mozilla/dom/Request.h"
+#include "mozilla/dom/FlyWebServerEvents.h"
+#include "mozilla/Preferences.h"
+#include "nsGlobalWindow.h"
+
+namespace mozilla {
+namespace dom {
+
+static LazyLogModule gFlyWebPublishedServerLog("FlyWebPublishedServer");
+#undef LOG_I
+#define LOG_I(...) MOZ_LOG(mozilla::dom::gFlyWebPublishedServerLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
+#undef LOG_E
+#define LOG_E(...) MOZ_LOG(mozilla::dom::gFlyWebPublishedServerLog, mozilla::LogLevel::Error, (__VA_ARGS__))
+
+NS_IMPL_ISUPPORTS_INHERITED0(FlyWebPublishedServer, mozilla::DOMEventTargetHelper)
+
+FlyWebPublishedServer::FlyWebPublishedServer(nsPIDOMWindowInner* aOwner,
+                                             const nsAString& aName,
+                                             const FlyWebPublishOptions& aOptions,
+                                             Promise* aPublishPromise)
+  : mozilla::DOMEventTargetHelper(aOwner)
+  , mOwnerWindowID(aOwner ? aOwner->WindowID() : 0)
+  , mPublishPromise(aPublishPromise)
+  , mName(aName)
+  , mCategory(aOptions.mCategory)
+  , mHttp(aOptions.mHttp)
+  , mMessage(aOptions.mMessage)
+  , mUiUrl(aOptions.mUiUrl)
+  , mIsRegistered(true) // Registered by the FlyWebService
+{
+  if (mCategory.IsEmpty()) {
+    mCategory.SetIsVoid(true);
+  }
+
+  mHttpServer = new HttpServer();
+  mHttpServer->Init(-1, Preferences::GetBool("flyweb.use-tls", false), this);
+}
+
+FlyWebPublishedServer::~FlyWebPublishedServer()
+{
+  // Make sure to unregister to avoid dangling pointers
+  Close();
+}
+
+JSObject*
+FlyWebPublishedServer::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+  return FlyWebPublishedServerBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+FlyWebPublishedServer::Close()
+{
+  // Unregister from server.
+  if (mIsRegistered) {
+    FlyWebService::GetOrCreate()->UnregisterServer(this);
+    mIsRegistered = false;
+  }
+
+  if (mMDNSCancelRegister) {
+    mMDNSCancelRegister->Cancel(NS_BINDING_ABORTED);
+    mMDNSCancelRegister = nullptr;
+  }
+
+  if (mHttpServer) {
+    RefPtr<HttpServer> server = mHttpServer.forget();
+    server->Close();
+  }
+}
+
+void
+FlyWebPublishedServer::OnServerStarted(nsresult aStatus)
+{
+  if (NS_SUCCEEDED(aStatus)) {
+    FlyWebService::GetOrCreate()->StartDiscoveryOf(this);
+  } else {
+    DiscoveryStarted(aStatus);
+  }
+}
+
+void
+FlyWebPublishedServer::OnServerClose()
+{
+  mHttpServer = nullptr;
+  Close();
+
+  DispatchTrustedEvent(NS_LITERAL_STRING("close"));
+}
+
+void
+FlyWebPublishedServer::OnRequest(InternalRequest* aRequest)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
+  RefPtr<FlyWebFetchEvent> e = new FlyWebFetchEvent(this,
+                                                    new Request(global, aRequest),
+                                                    aRequest);
+  e->Init(this);
+  e->InitEvent(NS_LITERAL_STRING("fetch"), false, false);
+
+  DispatchTrustedEvent(e);
+}
+
+void
+FlyWebPublishedServer::OnWebSocket(InternalRequest* aConnectRequest)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
+  RefPtr<FlyWebFetchEvent> e = new FlyWebWebSocketEvent(this,
+                                                        new Request(global, aConnectRequest),
+                                                        aConnectRequest);
+  e->Init(this);
+  e->InitEvent(NS_LITERAL_STRING("websocket"), false, false);
+
+  DispatchTrustedEvent(e);
+}
+
+void
+FlyWebPublishedServer::OnFetchResponse(InternalRequest* aRequest,
+                                       InternalResponse* aResponse)
+{
+  MOZ_ASSERT(aRequest);
+  MOZ_ASSERT(aResponse);
+
+  LOG_I("FlyWebPublishedMDNSServer::OnFetchResponse(%p)", this);
+
+  if (mHttpServer) {
+    mHttpServer->SendResponse(aRequest, aResponse);
+  }
+}
+
+already_AddRefed<WebSocket>
+FlyWebPublishedServer::OnWebSocketAccept(InternalRequest* aConnectRequest,
+                                         const Optional<nsAString>& aProtocol,
+                                         ErrorResult& aRv)
+{
+  MOZ_ASSERT(aConnectRequest);
+
+  LOG_I("FlyWebPublishedMDNSServer::OnWebSocketAccept(%p)", this);
+
+  if (!mHttpServer) {
+    aRv.Throw(NS_ERROR_UNEXPECTED);
+    return nullptr;
+  }
+
+  nsAutoCString negotiatedExtensions;
+  nsCOMPtr<nsITransportProvider> provider =
+    mHttpServer->AcceptWebSocket(aConnectRequest,
+                                 aProtocol,
+                                 negotiatedExtensions,
+                                 aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+  MOZ_ASSERT(provider);
+
+  nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetOwner());
+  AutoJSContext cx;
+  GlobalObject global(cx, nsGlobalWindow::Cast(window)->FastGetGlobalJSObject());
+
+  nsCString url;
+  aConnectRequest->GetURL(url);
+  Sequence<nsString> protocols;
+  if (aProtocol.WasPassed() &&
+      !protocols.AppendElement(aProtocol.Value(), fallible)) {
+    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+    return nullptr;
+  }
+
+  return WebSocket::ConstructorCommon(global,
+                                      NS_ConvertUTF8toUTF16(url),
+                                      protocols,
+                                      provider,
+                                      negotiatedExtensions,
+                                      aRv);
+}
+
+void
+FlyWebPublishedServer::OnWebSocketResponse(InternalRequest* aConnectRequest,
+                                           InternalResponse* aResponse)
+{
+  MOZ_ASSERT(aConnectRequest);
+  MOZ_ASSERT(aResponse);
+
+  LOG_I("FlyWebPublishedMDNSServer::OnWebSocketResponse(%p)", this);
+
+  if (mHttpServer) {
+    mHttpServer->SendWebSocketResponse(aConnectRequest, aResponse);
+  }
+}
+
+void FlyWebPublishedServer::DiscoveryStarted(nsresult aStatus)
+{
+  if (NS_SUCCEEDED(aStatus)) {
+    mPublishPromise->MaybeResolve(this);
+  } else {
+    Close();
+    mPublishPromise->MaybeReject(aStatus);
+  }
+}
+
+} // namespace dom
+} // namespace mozilla
+
+
new file mode 100644
--- /dev/null
+++ b/dom/flyweb/FlyWebPublishedServer.h
@@ -0,0 +1,134 @@
+/* -*- 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_dom_FlyWebPublishedServer_h
+#define mozilla_dom_FlyWebPublishedServer_h
+
+#include "nsISupportsImpl.h"
+#include "nsICancelable.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/ErrorResult.h"
+#include "HttpServer.h"
+#include "mozilla/dom/WebSocket.h"
+
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+namespace dom {
+
+class Promise;
+class InternalResponse;
+class InternalRequest;
+struct FlyWebPublishOptions;
+class FlyWebPublishedServer;
+
+class FlyWebPublishedServer final : public mozilla::DOMEventTargetHelper
+                                  , public HttpServerListener
+{
+public:
+  FlyWebPublishedServer(nsPIDOMWindowInner* aOwner,
+                        const nsAString& aName,
+                        const FlyWebPublishOptions& aOptions,
+                        Promise* aPublishPromise);
+
+  virtual JSObject* WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto) override;
+
+  NS_DECL_ISUPPORTS_INHERITED
+
+  uint64_t OwnerWindowID() const {
+    return mOwnerWindowID;
+  }
+
+  int32_t Port()
+  {
+    return mHttpServer ? mHttpServer->GetPort() : 0;
+  }
+  void GetCertKey(nsACString& aKey) {
+    if (mHttpServer) {
+      mHttpServer->GetCertKey(aKey);
+    } else {
+      aKey.Truncate();
+    }
+  }
+
+  void GetName(nsAString& aName)
+  {
+    aName = mName;
+  }
+  nsAString& Name()
+  {
+    return mName;
+  }
+
+  void GetCategory(nsAString& aCategory)
+  {
+    aCategory = mCategory;
+  }
+
+  bool Http()
+  {
+    return mHttp;
+  }
+
+  bool Message()
+  {
+    return mMessage;
+  }
+
+  void GetUiUrl(nsAString& aUiUrl)
+  {
+    aUiUrl = mUiUrl;
+  }
+
+  void OnFetchResponse(InternalRequest* aRequest,
+                       InternalResponse* aResponse);
+  already_AddRefed<WebSocket>
+    OnWebSocketAccept(InternalRequest* aConnectRequest,
+                      const Optional<nsAString>& aProtocol,
+                      ErrorResult& aRv);
+  void OnWebSocketResponse(InternalRequest* aConnectRequest,
+                           InternalResponse* aResponse);
+
+  void SetCancelRegister(nsICancelable* aCancelRegister)
+  {
+    mMDNSCancelRegister = aCancelRegister;
+  }
+
+  void Close();
+
+  // HttpServerListener
+  virtual void OnServerStarted(nsresult aStatus) override;
+  virtual void OnRequest(InternalRequest* aRequest) override;
+  virtual void OnWebSocket(InternalRequest* aConnectRequest) override;
+  virtual void OnServerClose() override;
+
+  IMPL_EVENT_HANDLER(fetch)
+  IMPL_EVENT_HANDLER(websocket)
+  IMPL_EVENT_HANDLER(close)
+
+  void DiscoveryStarted(nsresult aStatus);
+
+private:
+  ~FlyWebPublishedServer();
+
+  uint64_t mOwnerWindowID;
+  RefPtr<HttpServer> mHttpServer;
+  RefPtr<Promise> mPublishPromise;
+  nsCOMPtr<nsICancelable> mMDNSCancelRegister;
+
+  nsString mName;
+  nsString mCategory;
+  bool mHttp;
+  bool mMessage;
+  nsString mUiUrl;
+
+  bool mIsRegistered;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_FlyWebPublishedServer_h
new file mode 100644
--- /dev/null
+++ b/dom/flyweb/FlyWebServerEvents.cpp
@@ -0,0 +1,141 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 "mozilla/dom/EventBinding.h"
+#include "mozilla/dom/FlyWebFetchEventBinding.h"
+#include "mozilla/dom/FlyWebPublishedServer.h"
+#include "mozilla/dom/FlyWebServerEvents.h"
+#include "mozilla/dom/FlyWebWebSocketEventBinding.h"
+#include "mozilla/dom/Nullable.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/Response.h"
+
+#include "js/GCAPI.h"
+
+namespace mozilla {
+namespace dom {
+
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(FlyWebFetchEvent)
+
+NS_IMPL_ADDREF_INHERITED(FlyWebFetchEvent, Event)
+NS_IMPL_RELEASE_INHERITED(FlyWebFetchEvent, Event)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(FlyWebFetchEvent, Event)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRequest)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(FlyWebFetchEvent, Event)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(FlyWebFetchEvent, Event)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mRequest)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(FlyWebFetchEvent)
+NS_INTERFACE_MAP_END_INHERITING(Event)
+
+FlyWebFetchEvent::FlyWebFetchEvent(FlyWebPublishedServer* aServer,
+                                   class Request* aRequest,
+                                   InternalRequest* aInternalRequest)
+  : Event(aServer, nullptr, nullptr)
+  , mRequest(aRequest)
+  , mInternalRequest(aInternalRequest)
+  , mServer(aServer)
+  , mResponded(false)
+{
+  MOZ_ASSERT(aServer);
+  MOZ_ASSERT(aRequest);
+  MOZ_ASSERT(aInternalRequest);
+}
+
+FlyWebFetchEvent::~FlyWebFetchEvent()
+{
+}
+
+JSObject*
+FlyWebFetchEvent::WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+  return FlyWebFetchEventBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+FlyWebFetchEvent::RespondWith(Promise& aArg, ErrorResult& aRv)
+{
+  if (mResponded) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return;
+  }
+
+  mResponded = true;
+
+  aArg.AppendNativeHandler(this);
+}
+
+void
+FlyWebFetchEvent::ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue)
+{
+  RefPtr<Response> response;
+  if (aValue.isObject()) {
+    UNWRAP_OBJECT(Response, &aValue.toObject(), response);
+  }
+
+  RefPtr<InternalResponse> intResponse;
+  if (response && response->Type() != ResponseType::Opaque) {
+    intResponse = response->GetInternalResponse();
+  }
+
+  if (!intResponse) {
+    intResponse = InternalResponse::NetworkError();
+  }
+
+  NotifyServer(intResponse);
+}
+
+void
+FlyWebFetchEvent::NotifyServer(InternalResponse* aResponse)
+{
+  mServer->OnFetchResponse(mInternalRequest, aResponse);
+}
+
+void
+FlyWebFetchEvent::RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue)
+{
+  RefPtr<InternalResponse> err = InternalResponse::NetworkError();
+
+  NotifyServer(err);
+}
+
+JSObject*
+FlyWebWebSocketEvent::WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+  return FlyWebWebSocketEventBinding::Wrap(aCx, this, aGivenProto);
+}
+
+already_AddRefed<WebSocket>
+FlyWebWebSocketEvent::Accept(const Optional<nsAString>& aProtocol,
+                             ErrorResult& aRv)
+{
+  if (mResponded) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return nullptr;
+  }
+
+  mResponded = true;
+
+  return mServer->OnWebSocketAccept(mInternalRequest, aProtocol, aRv);
+}
+
+
+void
+FlyWebWebSocketEvent::NotifyServer(InternalResponse* aResponse)
+{
+  mServer->OnWebSocketResponse(mInternalRequest, aResponse);
+}
+
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/flyweb/FlyWebServerEvents.h
@@ -0,0 +1,88 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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_dom_FlyWebFetchEvent_h
+#define mozilla_dom_FlyWebFetchEvent_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/FlyWebFetchEventBinding.h"
+#include "mozilla/dom/PromiseNativeHandler.h"
+#include "mozilla/dom/WebSocket.h"
+
+struct JSContext;
+namespace mozilla {
+namespace dom {
+
+class Request;
+class Response;
+class FlyWebPublishedServer;
+class InternalRequest;
+class InternalResponse;
+
+class FlyWebFetchEvent : public Event
+                       , public PromiseNativeHandler
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(FlyWebFetchEvent, Event)
+
+  virtual JSObject* WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+  virtual void
+  ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
+  virtual void
+  RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
+
+  class Request* Request() const
+  {
+    return mRequest;
+  }
+
+  void RespondWith(Promise& aArg, ErrorResult& aRv);
+
+  FlyWebFetchEvent(FlyWebPublishedServer* aServer,
+                   class Request* aRequest,
+                   InternalRequest* aInternalRequest);
+
+protected:
+  virtual ~FlyWebFetchEvent();
+
+  virtual void NotifyServer(InternalResponse* aResponse);
+
+  RefPtr<class Request> mRequest;
+  RefPtr<InternalRequest> mInternalRequest;
+  RefPtr<FlyWebPublishedServer> mServer;
+
+  bool mResponded;
+};
+
+class FlyWebWebSocketEvent final : public FlyWebFetchEvent
+{
+public:
+  FlyWebWebSocketEvent(FlyWebPublishedServer* aServer,
+                       class Request* aRequest,
+                       InternalRequest* aInternalRequest)
+    : FlyWebFetchEvent(aServer, aRequest, aInternalRequest)
+  {}
+
+  virtual JSObject* WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+  already_AddRefed<WebSocket> Accept(const Optional<nsAString>& aProtocol,
+                                     ErrorResult& aRv);
+
+private:
+  ~FlyWebWebSocketEvent() {};
+
+  virtual void NotifyServer(InternalResponse* aResponse) override;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_FlyWebFetchEvent_h
new file mode 100644
--- /dev/null
+++ b/dom/flyweb/FlyWebService.cpp
@@ -0,0 +1,1127 @@
+/* -*- 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 "mozilla/dom/FlyWebService.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/FlyWebPublishedServer.h"
+#include "nsISocketTransportService.h"
+#include "mdns/libmdns/nsDNSServiceInfo.h"
+#include "nsIUUIDGenerator.h"
+#include "nsStandardURL.h"
+#include "mozilla/Services.h"
+#include "nsISupportsPrimitives.h"
+#include "mozilla/dom/FlyWebDiscoveryManagerBinding.h"
+#include "prnetdb.h"
+#include "DNS.h"
+#include "nsSocketTransportService2.h"
+#include "nsSocketTransport2.h"
+#include "nsHashPropertyBag.h"
+#include "nsNetUtil.h"
+#include "nsISimpleEnumerator.h"
+#include "nsIProperty.h"
+#include "nsICertOverrideService.h"
+
+namespace mozilla {
+namespace dom {
+
+struct FlyWebPublishOptions;
+
+static LazyLogModule gFlyWebServiceLog("FlyWebService");
+#undef LOG_I
+#define LOG_I(...) MOZ_LOG(mozilla::dom::gFlyWebServiceLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
+#undef LOG_E
+#define LOG_E(...) MOZ_LOG(mozilla::dom::gFlyWebServiceLog, mozilla::LogLevel::Error, (__VA_ARGS__))
+#undef LOG_TEST_I
+#define LOG_TEST_I(...) MOZ_LOG_TEST(mozilla::dom::gFlyWebServiceLog, mozilla::LogLevel::Debug)
+
+class FlyWebMDNSService final
+  : public nsIDNSServiceDiscoveryListener
+  , public nsIDNSServiceResolveListener
+  , public nsIDNSRegistrationListener
+  , public nsITimerCallback
+{
+  friend class FlyWebService;
+
+private:
+  enum DiscoveryState {
+    DISCOVERY_IDLE,
+    DISCOVERY_STARTING,
+    DISCOVERY_RUNNING,
+    DISCOVERY_STOPPING
+  };
+
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIDNSSERVICEDISCOVERYLISTENER
+  NS_DECL_NSIDNSSERVICERESOLVELISTENER
+  NS_DECL_NSIDNSREGISTRATIONLISTENER
+  NS_DECL_NSITIMERCALLBACK
+
+  explicit FlyWebMDNSService(FlyWebService* aService,
+                             const nsACString& aServiceType);
+
+private:
+  virtual ~FlyWebMDNSService() = default;
+
+  nsresult Init();
+  nsresult StartDiscovery();
+  nsresult StopDiscovery();
+
+  void ListDiscoveredServices(nsTArray<FlyWebDiscoveredService>& aServices);
+  bool HasService(const nsAString& aServiceId);
+  nsresult PairWithService(const nsAString& aServiceId,
+                           UniquePtr<FlyWebService::PairedInfo>& aInfo);
+
+  nsresult StartDiscoveryOf(FlyWebPublishedServer* aServer);
+
+  void EnsureDiscoveryStarted();
+  void EnsureDiscoveryStopped();
+
+  // Cycle-breaking link to manager.
+  FlyWebService* mService;
+  nsCString mServiceType;
+
+  // Indicates the desired state of the system.  If mDiscoveryActive is true,
+  // it indicates that backend discovery "should be happening", and discovery
+  // events should be forwarded to listeners.
+  // If false, the backend discovery "should be idle", and any discovery events
+  // that show up should not be forwarded to listeners.
+  bool mDiscoveryActive;
+
+  uint32_t mNumConsecutiveStartDiscoveryFailures;
+
+  // Represents the internal discovery state as it relates to nsDNSServiceDiscovery.
+  // When mDiscoveryActive is true, this state will periodically loop from
+  // (IDLE => STARTING => RUNNING => STOPPING => IDLE).
+  DiscoveryState mDiscoveryState;
+
+  nsCOMPtr<nsITimer> mDiscoveryStartTimer;
+  nsCOMPtr<nsITimer> mDiscoveryStopTimer;
+  nsCOMPtr<nsIDNSServiceDiscovery> mDNSServiceDiscovery;
+  nsCOMPtr<nsICancelable> mCancelDiscovery;
+  nsTHashtable<nsStringHashKey> mNewServiceSet;
+
+  struct DiscoveredInfo
+  {
+    explicit DiscoveredInfo(nsIDNSServiceInfo* aDNSServiceInfo);
+    FlyWebDiscoveredService mService;
+    nsCOMPtr<nsIDNSServiceInfo> mDNSServiceInfo;
+  };
+  nsClassHashtable<nsStringHashKey, DiscoveredInfo> mServiceMap;
+};
+
+void
+LogDNSInfo(nsIDNSServiceInfo* aServiceInfo, const char* aFunc)
+{
+  if (!LOG_TEST_I()) {
+    return;
+  }
+
+  nsCString tmp;
+  aServiceInfo->GetServiceName(tmp);
+  LOG_I("%s: serviceName=%s", aFunc, tmp.get());
+
+  aServiceInfo->GetHost(tmp);
+  LOG_I("%s: host=%s", aFunc, tmp.get());
+
+  aServiceInfo->GetAddress(tmp);
+  LOG_I("%s: address=%s", aFunc, tmp.get());
+
+  uint16_t port = -2;
+  aServiceInfo->GetPort(&port);
+  LOG_I("%s: port=%d", aFunc, (int)port);
+
+  nsCOMPtr<nsIPropertyBag2> attributes;
+  aServiceInfo->GetAttributes(getter_AddRefs(attributes));
+  if (!attributes) {
+    LOG_I("%s: no attributes", aFunc);
+  } else {
+    nsCOMPtr<nsISimpleEnumerator> enumerator;
+    attributes->GetEnumerator(getter_AddRefs(enumerator));
+    MOZ_ASSERT(enumerator);
+
+    LOG_I("%s: attributes start", aFunc);
+
+    bool hasMoreElements;
+    while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMoreElements)) &&
+           hasMoreElements) {
+      nsCOMPtr<nsISupports> element;
+      MOZ_ALWAYS_SUCCEEDS(enumerator->GetNext(getter_AddRefs(element)));
+      nsCOMPtr<nsIProperty> property = do_QueryInterface(element);
+      MOZ_ASSERT(property);
+
+      nsAutoString name;
+      nsCOMPtr<nsIVariant> value;
+      MOZ_ALWAYS_SUCCEEDS(property->GetName(name));
+      MOZ_ALWAYS_SUCCEEDS(property->GetValue(getter_AddRefs(value)));
+
+      nsAutoCString str;
+      nsresult rv = value->GetAsACString(str);
+      if (NS_SUCCEEDED(rv)) {
+        LOG_I("%s: attribute name=%s value=%s", aFunc,
+              NS_ConvertUTF16toUTF8(name).get(), str.get());
+      } else {
+        uint16_t type;
+        MOZ_ALWAYS_SUCCEEDS(value->GetDataType(&type));
+        LOG_I("%s: attribute *unstringifiable* name=%s type=%d", aFunc,
+              NS_ConvertUTF16toUTF8(name).get(), (int)type);
+      }
+    }
+
+    LOG_I("%s: attributes end", aFunc);
+  }
+}
+
+NS_IMPL_ISUPPORTS(FlyWebMDNSService,
+                  nsIDNSServiceDiscoveryListener,
+                  nsIDNSServiceResolveListener,
+                  nsIDNSRegistrationListener,
+                  nsITimerCallback)
+
+FlyWebMDNSService::FlyWebMDNSService(
+        FlyWebService* aService,
+        const nsACString& aServiceType)
+  : mService(aService)
+  , mServiceType(aServiceType)
+  , mDiscoveryActive(false)
+  , mNumConsecutiveStartDiscoveryFailures(0)
+  , mDiscoveryState(DISCOVERY_IDLE)
+{}
+
+nsresult
+FlyWebMDNSService::OnDiscoveryStarted(const nsACString& aServiceType)
+{
+  MOZ_ASSERT(mDiscoveryState == DISCOVERY_STARTING);
+  mDiscoveryState = DISCOVERY_RUNNING;
+  // Reset consecutive start discovery failures.
+  mNumConsecutiveStartDiscoveryFailures = 0;
+  LOG_I("===========================================");
+  LOG_I("MDNSService::OnDiscoveryStarted(%s)", PromiseFlatCString(aServiceType).get());
+  LOG_I("===========================================");
+
+  // Clear the new service array.
+  mNewServiceSet.Clear();
+
+  // If service discovery is inactive, then stop network discovery immediately.
+  if (!mDiscoveryActive) {
+    // Set the stop timer to fire immediately.
+    NS_WARN_IF(NS_FAILED(mDiscoveryStopTimer->InitWithCallback(this, 0, nsITimer::TYPE_ONE_SHOT)));
+    return NS_OK;
+  }
+
+  // Otherwise, set the stop timer to fire in 5 seconds.
+  NS_WARN_IF(NS_FAILED(mDiscoveryStopTimer->InitWithCallback(this, 5 * 1000, nsITimer::TYPE_ONE_SHOT)));
+
+  return NS_OK;
+}
+
+nsresult
+FlyWebMDNSService::OnDiscoveryStopped(const nsACString& aServiceType)
+{
+  LOG_I("///////////////////////////////////////////");
+  LOG_I("MDNSService::OnDiscoveryStopped(%s)", PromiseFlatCString(aServiceType).get());
+  LOG_I("///////////////////////////////////////////");
+  MOZ_ASSERT(mDiscoveryState == DISCOVERY_STOPPING);
+  mDiscoveryState = DISCOVERY_IDLE;
+
+  // If service discovery is inactive, then discard all results and do not proceed.
+  if (!mDiscoveryActive) {
+    mServiceMap.Clear();
+    mNewServiceSet.Clear();
+    return NS_OK;
+  }
+
+  // Process the service map, add to the pair map.
+  for (auto iter = mServiceMap.Iter(); !iter.Done(); iter.Next()) {
+    DiscoveredInfo* service = iter.UserData();
+
+    if (!mNewServiceSet.Contains(service->mService.mServiceId)) {
+      iter.Remove();
+    }
+  }
+
+  // Notify FlyWebService of changed service list.
+  mService->NotifyDiscoveredServicesChanged();
+
+  // Start discovery again immediately.
+  NS_WARN_IF(NS_FAILED(mDiscoveryStartTimer->InitWithCallback(this, 0, nsITimer::TYPE_ONE_SHOT)));
+
+  return NS_OK;
+}
+
+nsresult
+FlyWebMDNSService::OnServiceFound(nsIDNSServiceInfo* aServiceInfo)
+{
+  LogDNSInfo(aServiceInfo, "FlyWebMDNSService::OnServiceFound");
+
+  // If discovery is not active, don't do anything with the result.
+  // If there is no discovery underway, ignore this.
+  if (!mDiscoveryActive || mDiscoveryState != DISCOVERY_RUNNING) {
+    return NS_OK;
+  }
+
+  // Discovery is underway - resolve the service.
+  nsresult rv = mDNSServiceDiscovery->ResolveService(aServiceInfo, this);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult
+FlyWebMDNSService::OnServiceLost(nsIDNSServiceInfo* aServiceInfo)
+{
+  LogDNSInfo(aServiceInfo, "FlyWebMDNSService::OnServiceLost");
+
+  return NS_OK;
+}
+
+nsresult
+FlyWebMDNSService::OnStartDiscoveryFailed(const nsACString& aServiceType, int32_t aErrorCode)
+{
+  LOG_E("MDNSService::OnStartDiscoveryFailed(%s): %d", PromiseFlatCString(aServiceType).get(), (int) aErrorCode);
+
+  MOZ_ASSERT(mDiscoveryState == DISCOVERY_STARTING);
+  mDiscoveryState = DISCOVERY_IDLE;
+  mNumConsecutiveStartDiscoveryFailures++;
+
+  // If discovery is active, and the number of consecutive failures is < 3, try starting again.
+  if (mDiscoveryActive && mNumConsecutiveStartDiscoveryFailures < 3) {
+    NS_WARN_IF(NS_FAILED(mDiscoveryStartTimer->InitWithCallback(this, 0, nsITimer::TYPE_ONE_SHOT)));
+  }
+
+  return NS_OK;
+}
+
+nsresult
+FlyWebMDNSService::OnStopDiscoveryFailed(const nsACString& aServiceType, int32_t aErrorCode)
+{
+  LOG_E("MDNSService::OnStopDiscoveryFailed(%s)", PromiseFlatCString(aServiceType).get());
+  MOZ_ASSERT(mDiscoveryState == DISCOVERY_STOPPING);
+  mDiscoveryState = DISCOVERY_IDLE;
+
+  // If discovery is active, start discovery again immediately.
+  if (mDiscoveryActive) {
+    NS_WARN_IF(NS_FAILED(mDiscoveryStartTimer->InitWithCallback(this, 0, nsITimer::TYPE_ONE_SHOT)));
+  }
+
+  return NS_OK;
+}
+
+static bool
+IsAcceptableServiceAddress(const nsCString& addr)
+{
+  PRNetAddr prNetAddr;
+  PRStatus status = PR_StringToNetAddr(addr.get(), &prNetAddr);
+  if (status == PR_FAILURE) {
+    return false;
+  }
+  // Only allow ipv4 addreses for now.
+  return prNetAddr.raw.family == PR_AF_INET;
+}
+
+nsresult
+FlyWebMDNSService::OnServiceResolved(nsIDNSServiceInfo* aServiceInfo)
+{
+  LogDNSInfo(aServiceInfo, "FlyWebMDNSService::OnServiceResolved");
+
+  // If discovery is not active, don't do anything with the result.
+  // If there is no discovery underway, ignore this resolve.
+  if (!mDiscoveryActive || mDiscoveryState != DISCOVERY_RUNNING) {
+    return NS_OK;
+  }
+
+  nsresult rv;
+
+  nsCString address;
+  rv = aServiceInfo->GetAddress(address);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (!IsAcceptableServiceAddress(address)) {
+    return NS_OK;
+  }
+
+  // Create a new serviceInfo and stuff it in the new service array.
+  UniquePtr<DiscoveredInfo> svc(new DiscoveredInfo(aServiceInfo));
+  mNewServiceSet.PutEntry(svc->mService.mServiceId);
+
+  DiscoveredInfo* existingSvc =
+    mServiceMap.Get(svc->mService.mServiceId);
+  if (existingSvc) {
+    // Update the underlying DNS service info, but leave the old object in place.
+    existingSvc->mDNSServiceInfo = aServiceInfo;
+  } else {
+    DiscoveredInfo* info = svc.release();
+    mServiceMap.Put(info->mService.mServiceId, info);
+  }
+
+  // Notify FlyWebService of changed service list.
+  mService->NotifyDiscoveredServicesChanged();
+
+  return NS_OK;
+}
+
+FlyWebMDNSService::DiscoveredInfo::DiscoveredInfo(nsIDNSServiceInfo* aDNSServiceInfo)
+  : mDNSServiceInfo(aDNSServiceInfo)
+{
+  nsCString tmp;
+  DebugOnly<nsresult> drv = aDNSServiceInfo->GetServiceName(tmp);
+  MOZ_ASSERT(NS_SUCCEEDED(drv));
+  CopyUTF8toUTF16(tmp, mService.mDisplayName);
+
+  mService.mTransport = NS_LITERAL_STRING("mdns");
+
+  drv = aDNSServiceInfo->GetServiceType(tmp);
+  MOZ_ASSERT(NS_SUCCEEDED(drv));
+  CopyUTF8toUTF16(tmp, mService.mServiceType);
+
+  nsCOMPtr<nsIPropertyBag2> attrs;
+  drv = aDNSServiceInfo->GetAttributes(getter_AddRefs(attrs));
+  MOZ_ASSERT(NS_SUCCEEDED(drv));
+  if (attrs) {
+    attrs->GetPropertyAsAString(NS_LITERAL_STRING("cert"), mService.mCert);
+    attrs->GetPropertyAsAString(NS_LITERAL_STRING("path"), mService.mPath);
+  }
+
+  // Construct a service id from the name, host, address, and port.
+  nsCString cHost;
+  drv = aDNSServiceInfo->GetHost(cHost);
+  MOZ_ASSERT(NS_SUCCEEDED(drv));
+
+  nsCString cAddress;
+  drv = aDNSServiceInfo->GetAddress(cAddress);
+  MOZ_ASSERT(NS_SUCCEEDED(drv));
+
+  uint16_t port;
+  drv = aDNSServiceInfo->GetPort(&port);
+  MOZ_ASSERT(NS_SUCCEEDED(drv));
+  nsAutoString portStr;
+  portStr.AppendInt(port, 10);
+
+  mService.mServiceId =
+    NS_ConvertUTF8toUTF16(cAddress) +
+    NS_LITERAL_STRING(":") +
+    portStr +
+    NS_LITERAL_STRING("|") +
+    mService.mServiceType +
+    NS_LITERAL_STRING("|") +
+    NS_ConvertUTF8toUTF16(cHost) +
+    NS_LITERAL_STRING("|") +
+    mService.mDisplayName;
+}
+
+
+nsresult
+FlyWebMDNSService::OnResolveFailed(nsIDNSServiceInfo* aServiceInfo, int32_t aErrorCode)
+{
+  LogDNSInfo(aServiceInfo, "FlyWebMDNSService::OnResolveFailed");
+
+  return NS_OK;
+}
+
+nsresult
+FlyWebMDNSService::OnServiceRegistered(nsIDNSServiceInfo* aServiceInfo)
+{
+  LogDNSInfo(aServiceInfo, "FlyWebMDNSService::OnServiceRegistered");
+
+  nsCString cName;
+  if (NS_WARN_IF(NS_FAILED(aServiceInfo->GetServiceName(cName)))) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsString name = NS_ConvertUTF8toUTF16(cName);
+  RefPtr<FlyWebPublishedServer> existingServer =
+    FlyWebService::GetOrCreate()->FindPublishedServerByName(name);
+  if (!existingServer) {
+    return NS_ERROR_FAILURE;
+  }
+
+  existingServer->DiscoveryStarted(NS_OK);
+
+  return NS_OK;
+}
+
+nsresult
+FlyWebMDNSService::OnServiceUnregistered(nsIDNSServiceInfo* aServiceInfo)
+{
+  LogDNSInfo(aServiceInfo, "FlyWebMDNSService::OnServiceUnregistered");
+
+  nsCString cName;
+  if (NS_WARN_IF(NS_FAILED(aServiceInfo->GetServiceName(cName)))) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsString name = NS_ConvertUTF8toUTF16(cName);
+  RefPtr<FlyWebPublishedServer> existingServer =
+    FlyWebService::GetOrCreate()->FindPublishedServerByName(name);
+  if (!existingServer) {
+    return NS_ERROR_FAILURE;
+  }
+
+  LOG_I("OnServiceRegistered(MDNS): De-advertised server with name %s.", cName.get());
+
+  return NS_OK;
+}
+
+nsresult
+FlyWebMDNSService::OnRegistrationFailed(nsIDNSServiceInfo* aServiceInfo, int32_t errorCode)
+{
+  LogDNSInfo(aServiceInfo, "FlyWebMDNSService::OnRegistrationFailed");
+
+  nsCString cName;
+  if (NS_WARN_IF(NS_FAILED(aServiceInfo->GetServiceName(cName)))) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsString name = NS_ConvertUTF8toUTF16(cName);
+  RefPtr<FlyWebPublishedServer> existingServer =
+    FlyWebService::GetOrCreate()->FindPublishedServerByName(name);
+  if (!existingServer) {
+    return NS_ERROR_FAILURE;
+  }
+
+  LOG_I("OnServiceRegistered(MDNS): Registration of server with name %s failed.", cName.get());
+
+  // Remove the nsICancelable from the published server.
+  existingServer->DiscoveryStarted(NS_ERROR_FAILURE);
+  return NS_OK;
+}
+
+nsresult
+FlyWebMDNSService::OnUnregistrationFailed(nsIDNSServiceInfo* aServiceInfo, int32_t errorCode)
+{
+  LogDNSInfo(aServiceInfo, "FlyWebMDNSService::OnUnregistrationFailed");
+
+  nsCString cName;
+  if (NS_WARN_IF(NS_FAILED(aServiceInfo->GetServiceName(cName)))) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsString name = NS_ConvertUTF8toUTF16(cName);
+  RefPtr<FlyWebPublishedServer> existingServer =
+    FlyWebService::GetOrCreate()->FindPublishedServerByName(name);
+  if (!existingServer) {
+    return NS_ERROR_FAILURE;
+  }
+
+  LOG_I("OnServiceRegistered(MDNS): Un-Advertisement of server with name %s failed.", cName.get());
+  return NS_OK;
+}
+
+nsresult
+FlyWebMDNSService::Notify(nsITimer* timer)
+{
+  if (timer == mDiscoveryStopTimer.get()) {
+    LOG_I("MDNSService::Notify() got discovery stop timeout");
+    // Internet discovery stop timer has fired.
+    nsresult rv = StopDiscovery();
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+    return NS_OK;
+  }
+
+  if (timer == mDiscoveryStartTimer.get()) {
+    LOG_I("MDNSService::Notify() got discovery start timeout");
+    // Internet discovery start timer has fired.
+    nsresult rv = StartDiscovery();
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+    return NS_OK;
+  }
+
+  LOG_E("MDNSService::Notify got unknown timeout.");
+  return NS_OK;
+}
+
+nsresult
+FlyWebMDNSService::Init()
+{
+  MOZ_ASSERT(mDiscoveryState == DISCOVERY_IDLE);
+
+  mDiscoveryStartTimer = do_CreateInstance("@mozilla.org/timer;1");
+  if (!mDiscoveryStartTimer) {
+    return NS_ERROR_FAILURE;
+  }
+
+  mDiscoveryStopTimer = do_CreateInstance("@mozilla.org/timer;1");
+  if (!mDiscoveryStopTimer) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsresult rv;
+  mDNSServiceDiscovery = do_GetService(DNSSERVICEDISCOVERY_CONTRACT_ID, &rv);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+FlyWebMDNSService::StartDiscovery()
+{
+  nsresult rv;
+
+  // Always cancel the timer.
+  rv = mDiscoveryStartTimer->Cancel();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    LOG_E("FlyWeb failed to cancel DNS service discovery start timer.");
+  }
+
+  // If discovery is not idle, don't start it.
+  if (mDiscoveryState != DISCOVERY_IDLE) {
+    return NS_OK;
+  }
+
+  LOG_I("FlyWeb starting dicovery.");
+  mDiscoveryState = DISCOVERY_STARTING;
+
+  // start the discovery.
+  rv = mDNSServiceDiscovery->StartDiscovery(mServiceType, this,
+                                            getter_AddRefs(mCancelDiscovery));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    LOG_E("FlyWeb failed to start DNS service discovery.");
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+FlyWebMDNSService::StopDiscovery()
+{
+  nsresult rv;
+
+  // Always cancel the timer.
+  rv = mDiscoveryStopTimer->Cancel();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    LOG_E("FlyWeb failed to cancel DNS service discovery stop timer.");
+  }
+
+  // If discovery is not running, do nothing.
+  if (mDiscoveryState != DISCOVERY_RUNNING) {
+    return NS_OK;
+  }
+
+  LOG_I("FlyWeb stopping dicovery.");
+
+  // Mark service discovery as stopping.
+  mDiscoveryState = DISCOVERY_STOPPING;
+
+  if (mCancelDiscovery) {
+    LOG_I("MDNSService::StopDiscovery() - mCancelDiscovery exists!");
+    nsCOMPtr<nsICancelable> cancelDiscovery = mCancelDiscovery.forget();
+    rv = cancelDiscovery->Cancel(NS_ERROR_ABORT);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      LOG_E("FlyWeb failed to cancel DNS stop service discovery.");
+    }
+  } else {
+    LOG_I("MDNSService::StopDiscovery() - mCancelDiscovery does not exist!");
+    mDiscoveryState = DISCOVERY_IDLE;
+  }
+
+  return NS_OK;
+}
+
+void
+FlyWebMDNSService::ListDiscoveredServices(nsTArray<FlyWebDiscoveredService>& aServices)
+{
+  for (auto iter = mServiceMap.Iter(); !iter.Done(); iter.Next()) {
+    aServices.AppendElement(iter.UserData()->mService);
+  }
+}
+
+bool
+FlyWebMDNSService::HasService(const nsAString& aServiceId)
+{
+  return mServiceMap.Contains(aServiceId);
+}
+
+nsresult
+FlyWebMDNSService::PairWithService(const nsAString& aServiceId,
+                                   UniquePtr<FlyWebService::PairedInfo>& aInfo)
+{
+  MOZ_ASSERT(HasService(aServiceId));
+
+  nsresult rv;
+  nsCOMPtr<nsIUUIDGenerator> uuidgen =
+    do_GetService("@mozilla.org/uuid-generator;1", &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsID id;
+  rv = uuidgen->GenerateUUIDInPlace(&id);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  aInfo.reset(new FlyWebService::PairedInfo());
+
+  char uuidChars[NSID_LENGTH];
+  id.ToProvidedString(uuidChars);
+  CopyUTF8toUTF16(Substring(uuidChars + 1, uuidChars + NSID_LENGTH - 2),
+                  aInfo->mService.mHostname);
+
+  DiscoveredInfo* discInfo = mServiceMap.Get(aServiceId);
+
+  nsAutoString url;
+  if (discInfo->mService.mCert.IsEmpty()) {
+    url.AssignLiteral("http://");
+  } else {
+    url.AssignLiteral("https://");
+  }
+  url.Append(aInfo->mService.mHostname + NS_LITERAL_STRING("/"));
+  nsCOMPtr<nsIURI> uiURL;
+  NS_NewURI(getter_AddRefs(uiURL), url);
+  MOZ_ASSERT(uiURL);
+  if (!discInfo->mService.mPath.IsEmpty()) {
+    nsCOMPtr<nsIURI> tmp = uiURL.forget();
+    NS_NewURI(getter_AddRefs(uiURL), discInfo->mService.mPath, nullptr, tmp);
+  }
+  if (uiURL) {
+    nsAutoCString spec;
+    uiURL->GetSpec(spec);
+    CopyUTF8toUTF16(spec, aInfo->mService.mUiUrl);
+  }
+
+  aInfo->mService.mDiscoveredService = discInfo->mService;
+  aInfo->mDNSServiceInfo = discInfo->mDNSServiceInfo;
+
+  return NS_OK;
+}
+
+nsresult
+FlyWebMDNSService::StartDiscoveryOf(FlyWebPublishedServer* aServer)
+{
+
+  RefPtr<FlyWebPublishedServer> existingServer =
+    FlyWebService::GetOrCreate()->FindPublishedServerByName(aServer->Name());
+  MOZ_ASSERT(existingServer);
+
+  // Advertise the service via mdns.
+  RefPtr<net::nsDNSServiceInfo> serviceInfo(new net::nsDNSServiceInfo());
+
+  serviceInfo->SetPort(aServer->Port());
+  serviceInfo->SetServiceType(mServiceType);
+
+  nsCString certKey;
+  aServer->GetCertKey(certKey);
+  nsString uiURL;
+  aServer->GetUiUrl(uiURL);
+
+  if (!uiURL.IsEmpty() || !certKey.IsEmpty()) {
+    RefPtr<nsHashPropertyBag> attrs = new nsHashPropertyBag();
+    if (!uiURL.IsEmpty()) {
+      attrs->SetPropertyAsAString(NS_LITERAL_STRING("path"), uiURL);
+    }
+    if (!certKey.IsEmpty()) {
+      attrs->SetPropertyAsACString(NS_LITERAL_STRING("cert"), certKey);
+    }
+    serviceInfo->SetAttributes(attrs);
+  }
+
+  nsCString cstrName = NS_ConvertUTF16toUTF8(aServer->Name());
+  LOG_I("MDNSService::StartDiscoveryOf() advertising service %s", cstrName.get());
+  serviceInfo->SetServiceName(cstrName);
+
+  LogDNSInfo(serviceInfo, "FlyWebMDNSService::StartDiscoveryOf");
+
+  // Advertise the service.
+  nsCOMPtr<nsICancelable> cancelRegister;
+  nsresult rv = mDNSServiceDiscovery->
+    RegisterService(serviceInfo, this, getter_AddRefs(cancelRegister));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // All done.
+  aServer->SetCancelRegister(cancelRegister);
+
+  return NS_OK;
+}
+
+void
+FlyWebMDNSService::EnsureDiscoveryStarted()
+{
+  mDiscoveryActive = true;
+  // If state is idle, start discovery immediately.
+  if (mDiscoveryState == DISCOVERY_IDLE) {
+    StartDiscovery();
+  }
+}
+
+void
+FlyWebMDNSService::EnsureDiscoveryStopped()
+{
+  // All we need to do is set the flag to false.
+  // If current state is IDLE, it's already the correct state.
+  // Otherwise, the handlers for the internal state
+  // transitions will check this flag and drive the state
+  // towards IDLE.
+  mDiscoveryActive = false;
+}
+
+static StaticRefPtr<FlyWebService> gFlyWebService;
+
+NS_IMPL_ISUPPORTS(FlyWebService, nsIObserver)
+
+FlyWebService::FlyWebService()
+  : mMonitor("FlyWebService::mMonitor")
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+  if (obs) {
+    obs->AddObserver(this, "inner-window-destroyed", false);
+  }
+}
+
+FlyWebService::~FlyWebService()
+{
+}
+
+FlyWebService*
+FlyWebService::GetExisting()
+{
+  return gFlyWebService;
+}
+
+FlyWebService*
+FlyWebService::GetOrCreate()
+{
+  if (!gFlyWebService) {
+    gFlyWebService = new FlyWebService();
+    ErrorResult rv = gFlyWebService->Init();
+    if (rv.Failed()) {
+      gFlyWebService = nullptr;
+      return nullptr;
+    }
+  }
+  return gFlyWebService;
+}
+
+ErrorResult
+FlyWebService::Init()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  if (!mMDNSHttpService) {
+    mMDNSHttpService = new FlyWebMDNSService(this, NS_LITERAL_CSTRING("_http._tcp."));
+    ErrorResult rv;
+
+    rv = mMDNSHttpService->Init();
+    if (rv.Failed()) {
+      LOG_E("FlyWebService failed to initialize MDNS _http._tcp.");
+      mMDNSHttpService = nullptr;
+      rv.SuppressException();
+    }
+  }
+
+  if (!mMDNSFlywebService) {
+    mMDNSFlywebService = new FlyWebMDNSService(this, NS_LITERAL_CSTRING("_flyweb._tcp."));
+    ErrorResult rv;
+
+    rv = mMDNSFlywebService->Init();
+    if (rv.Failed()) {
+      LOG_E("FlyWebService failed to initialize MDNS _flyweb._tcp.");
+      mMDNSFlywebService = nullptr;
+      rv.SuppressException();
+    }
+  }
+
+  return ErrorResult(NS_OK);
+}
+
+already_AddRefed<Promise>
+FlyWebService::PublishServer(const nsAString& aName,
+                             const FlyWebPublishOptions& aOptions,
+                             nsPIDOMWindowInner* aWindow,
+                             ErrorResult& aRv)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aWindow);
+  RefPtr<Promise> promise = Promise::Create(global, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  // Scan uiUrl for illegal characters
+
+  RefPtr<FlyWebPublishedServer> existingServer =
+    FlyWebService::GetOrCreate()->FindPublishedServerByName(aName);
+  if (existingServer) {
+    LOG_I("PublishServer: Trying to publish server with already-existing name %s.",
+          NS_ConvertUTF16toUTF8(aName).get());
+    promise->MaybeReject(NS_ERROR_FAILURE);
+    return promise.forget();
+  }
+
+  RefPtr<FlyWebPublishedServer> server =
+    new FlyWebPublishedServer(aWindow, aName, aOptions, promise);
+
+  mServers.AppendElement(server);
+
+  return promise.forget();
+}
+
+already_AddRefed<FlyWebPublishedServer>
+FlyWebService::FindPublishedServerByName(
+        const nsAString& aName)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  for (FlyWebPublishedServer* publishedServer : mServers) {
+    if (publishedServer->Name().Equals(aName)) {
+      RefPtr<FlyWebPublishedServer> server = publishedServer;
+      return server.forget();
+    }
+  }
+  return nullptr;
+}
+
+void
+FlyWebService::RegisterDiscoveryManager(FlyWebDiscoveryManager* aDiscoveryManager)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  mDiscoveryManagerTable.PutEntry(aDiscoveryManager);
+  if (mMDNSHttpService) {
+    mMDNSHttpService->EnsureDiscoveryStarted();
+  }
+  if (mMDNSFlywebService) {
+    mMDNSFlywebService->EnsureDiscoveryStarted();
+  }
+}
+
+void
+FlyWebService::UnregisterDiscoveryManager(FlyWebDiscoveryManager* aDiscoveryManager)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  mDiscoveryManagerTable.RemoveEntry(aDiscoveryManager);
+  if (mDiscoveryManagerTable.IsEmpty()) {
+    if (mMDNSHttpService) {
+      mMDNSHttpService->EnsureDiscoveryStopped();
+    }
+    if (mMDNSFlywebService) {
+      mMDNSFlywebService->EnsureDiscoveryStopped();
+    }
+  }
+}
+
+NS_IMETHODIMP
+FlyWebService::Observe(nsISupports* aSubject, const char* aTopic,
+                       const char16_t* aData)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  if (strcmp(aTopic, "inner-window-destroyed")) {
+    return NS_OK;
+  }
+
+  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);
+
+  for (FlyWebPublishedServer* server : mServers) {
+    if (server->OwnerWindowID() == innerID) {
+      server->Close();
+    }
+  }
+
+  return NS_OK;
+}
+
+void
+FlyWebService::UnregisterServer(FlyWebPublishedServer* aServer)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  DebugOnly<bool> removed = mServers.RemoveElement(aServer);
+  MOZ_ASSERT(removed);
+}
+
+bool
+FlyWebService::HasConnectionOrServer(uint64_t aWindowID)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  for (FlyWebPublishedServer* server : mServers) {
+    nsPIDOMWindowInner* win = server->GetOwner();
+    if (win && win->WindowID() == aWindowID) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+void
+FlyWebService::NotifyDiscoveredServicesChanged()
+{
+  // Process the service map, add to the pair map.
+  for (auto iter = mDiscoveryManagerTable.Iter(); !iter.Done(); iter.Next()) {
+    iter.Get()->GetKey()->NotifyDiscoveredServicesChanged();
+  }
+}
+
+void
+FlyWebService::ListDiscoveredServices(nsTArray<FlyWebDiscoveredService>& aServices)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  if (mMDNSHttpService) {
+    mMDNSHttpService->ListDiscoveredServices(aServices);
+  }
+  if (mMDNSFlywebService) {
+    mMDNSFlywebService->ListDiscoveredServices(aServices);
+  }
+}
+
+void
+FlyWebService::PairWithService(const nsAString& aServiceId,
+                               FlyWebPairingCallback& aCallback)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  // See if we have already paired with this service.  If so, re-use the
+  // FlyWebPairedService for that.
+  {
+    ReentrantMonitorAutoEnter pairedMapLock(mMonitor);
+    for (auto iter = mPairedServiceTable.Iter(); !iter.Done(); iter.Next()) {
+      PairedInfo* pairInfo = iter.UserData();
+      if (pairInfo->mService.mDiscoveredService.mServiceId.Equals(aServiceId)) {
+        ErrorResult er;
+        ReentrantMonitorAutoExit pairedMapRelease(mMonitor);
+        aCallback.PairingSucceeded(pairInfo->mService, er);
+        ENSURE_SUCCESS_VOID(er);
+        return;
+      }
+    }
+  }
+
+  UniquePtr<PairedInfo> pairInfo;
+
+  nsresult rv = NS_OK;
+  bool notFound = false;
+  if (mMDNSHttpService && mMDNSHttpService->HasService(aServiceId)) {
+    rv = mMDNSHttpService->PairWithService(aServiceId, pairInfo);
+  } else if (mMDNSFlywebService && mMDNSFlywebService->HasService(aServiceId)) {
+    rv = mMDNSFlywebService->PairWithService(aServiceId, pairInfo);
+  } else {
+    notFound = true;
+  }
+
+  if (NS_FAILED(rv)) {
+    ErrorResult result;
+    result.Throw(rv);
+    const nsAString& reason = NS_LITERAL_STRING("Error pairing.");
+    aCallback.PairingFailed(reason, result);
+    ENSURE_SUCCESS_VOID(result);
+    return;
+  }
+
+  if (!pairInfo) {
+    ErrorResult res;
+    const nsAString& reason = notFound ?
+      NS_LITERAL_STRING("No such service.") :
+      NS_LITERAL_STRING("Error pairing.");
+    aCallback.PairingFailed(reason, res);
+    ENSURE_SUCCESS_VOID(res);
+    return;
+  }
+
+  // Add fingerprint to certificate override database.
+  if (!pairInfo->mService.mDiscoveredService.mCert.IsEmpty()) {
+    nsCOMPtr<nsICertOverrideService> override =
+      do_GetService("@mozilla.org/security/certoverride;1");
+    if (!override ||
+        NS_FAILED(override->RememberTemporaryValidityOverrideUsingFingerprint(
+          NS_ConvertUTF16toUTF8(pairInfo->mService.mHostname),
+          -1,
+          NS_ConvertUTF16toUTF8(pairInfo->mService.mDiscoveredService.mCert),
+          nsICertOverrideService::ERROR_UNTRUSTED |
+          nsICertOverrideService::ERROR_MISMATCH))) {
+      ErrorResult res;
+      aCallback.PairingFailed(NS_LITERAL_STRING("Error adding certificate override."), res);
+      ENSURE_SUCCESS_VOID(res);
+      return;
+    }
+  }
+
+  // Grab a weak reference to the PairedInfo so that we can
+  // use it even after ownership has been transferred to mPairedServiceTable
+  PairedInfo* pairInfoWeak = pairInfo.release();
+
+  {
+    ReentrantMonitorAutoEnter pairedMapLock(mMonitor);
+    mPairedServiceTable.Put(
+      NS_ConvertUTF16toUTF8(pairInfoWeak->mService.mHostname), pairInfoWeak);
+  }
+
+  ErrorResult er;
+  aCallback.PairingSucceeded(pairInfoWeak->mService, er);
+  ENSURE_SUCCESS_VOID(er);
+}
+
+nsresult
+FlyWebService::CreateTransportForHost(const char **types,
+                                      uint32_t typeCount,
+                                      const nsACString &host,
+                                      int32_t port,
+                                      const nsACString &hostRoute,
+                                      int32_t portRoute,
+                                      nsIProxyInfo *proxyInfo,
+                                      nsISocketTransport **result)
+{
+  // This might be called on background threads
+
+  *result = nullptr;
+
+  nsCString ipAddrString;
+  uint16_t discPort;
+
+  {
+    ReentrantMonitorAutoEnter pairedMapLock(mMonitor);
+
+    PairedInfo* info = mPairedServiceTable.Get(host);
+
+    if (!info) {
+      return NS_OK;
+    }
+
+    // Get the ip address of the underlying service.
+    info->mDNSServiceInfo->GetAddress(ipAddrString);
+    info->mDNSServiceInfo->GetPort(&discPort);
+  }
+
+  // Parse it into an NetAddr.
+  PRNetAddr prNetAddr;
+  PRStatus status = PR_StringToNetAddr(ipAddrString.get(), &prNetAddr);
+  NS_ENSURE_FALSE(status == PR_FAILURE, NS_ERROR_FAILURE);
+
+  // Convert PRNetAddr to NetAddr.
+  mozilla::net::NetAddr netAddr;
+  PRNetAddrToNetAddr(&prNetAddr, &netAddr);
+  netAddr.inet.port = htons(discPort);
+
+  RefPtr<mozilla::net::nsSocketTransport> trans = new mozilla::net::nsSocketTransport();
+  nsresult rv = trans->InitPreResolved(
+    types, typeCount, host, port, hostRoute, portRoute, proxyInfo, &netAddr);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  trans.forget(result);
+  return NS_OK;
+}
+
+void
+FlyWebService::StartDiscoveryOf(FlyWebPublishedServer* aServer)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  nsresult rv = mMDNSFlywebService ?
+    mMDNSFlywebService->StartDiscoveryOf(aServer) :
+    NS_ERROR_FAILURE;
+
+  if (NS_FAILED(rv)) {
+    aServer->DiscoveryStarted(rv);
+  }
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/flyweb/FlyWebService.h
@@ -0,0 +1,109 @@
+/* -*- 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_dom_FlyWebService_h
+#define mozilla_dom_FlyWebService_h
+
+#include "nsISupportsImpl.h"
+#include "mozilla/ErrorResult.h"
+#include "nsIProtocolHandler.h"
+#include "nsDataHashtable.h"
+#include "nsClassHashtable.h"
+#include "nsIObserver.h"
+#include "mozilla/ReentrantMonitor.h"
+#include "mozilla/dom/FlyWebDiscoveryManagerBinding.h"
+#include "nsITimer.h"
+#include "nsICancelable.h"
+#include "nsIDNSServiceDiscovery.h"
+
+class nsPIDOMWindowInner;
+class nsIProxyInfo;
+class nsISocketTransport;
+
+namespace mozilla {
+namespace dom {
+
+class Promise;
+struct FlyWebPublishOptions;
+struct FlyWebFilter;
+class FlyWebPublishedServer;
+class FlyWebPairingCallback;
+class FlyWebDiscoveryManager;
+class FlyWebMDNSService;
+
+class FlyWebService final : public nsIObserver
+{
+  friend class FlyWebMDNSService;
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSIOBSERVER
+
+  static FlyWebService* GetExisting();
+  static FlyWebService* GetOrCreate();
+  static already_AddRefed<FlyWebService> GetOrCreateAddRefed()
+  {
+    return do_AddRef(GetOrCreate());
+  }
+
+  already_AddRefed<Promise> PublishServer(const nsAString& aName,
+                                          const FlyWebPublishOptions& aOptions,
+                                          nsPIDOMWindowInner* aWindow,
+                                          ErrorResult& aRv);
+
+  void UnregisterServer(FlyWebPublishedServer* aServer);
+
+  bool HasConnectionOrServer(uint64_t aWindowID);
+
+  void ListDiscoveredServices(nsTArray<FlyWebDiscoveredService>& aServices);
+  void PairWithService(const nsAString& aServiceId, FlyWebPairingCallback& aCallback);
+  nsresult CreateTransportForHost(const char **types,
+                                  uint32_t typeCount,
+                                  const nsACString &host,
+                                  int32_t port,
+                                  const nsACString &hostRoute,
+                                  int32_t portRoute,
+                                  nsIProxyInfo *proxyInfo,
+                                  nsISocketTransport **result);
+
+  already_AddRefed<FlyWebPublishedServer> FindPublishedServerByName(
+            const nsAString& aName);
+
+  void RegisterDiscoveryManager(FlyWebDiscoveryManager* aDiscoveryManager);
+  void UnregisterDiscoveryManager(FlyWebDiscoveryManager* aDiscoveryManager);
+
+  // Should only be called by FlyWebPublishedServer
+  void StartDiscoveryOf(FlyWebPublishedServer* aServer);
+
+private:
+  FlyWebService();
+  ~FlyWebService();
+
+  ErrorResult Init();
+
+  void NotifyDiscoveredServicesChanged();
+
+  // Might want to make these hashes for perf
+  nsTArray<FlyWebPublishedServer*> mServers;
+
+  RefPtr<FlyWebMDNSService> mMDNSHttpService;
+  RefPtr<FlyWebMDNSService> mMDNSFlywebService;
+
+  struct PairedInfo
+  {
+    FlyWebPairedService mService;
+    nsCOMPtr<nsIDNSServiceInfo> mDNSServiceInfo;
+  };
+  nsClassHashtable<nsCStringHashKey, PairedInfo>
+    mPairedServiceTable;
+  ReentrantMonitor mMonitor; // Protecting mPairedServiceTable
+
+  nsTHashtable<nsPtrHashKey<FlyWebDiscoveryManager>> mDiscoveryManagerTable;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_FlyWebService_h
new file mode 100644
--- /dev/null
+++ b/dom/flyweb/HttpServer.cpp
@@ -0,0 +1,1315 @@
+/* -*- 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 "mozilla/dom/HttpServer.h"
+#include "nsISocketTransport.h"
+#include "nsWhitespaceTokenizer.h"
+#include "nsNetUtil.h"
+#include "nsIStreamTransportService.h"
+#include "nsIAsyncStreamCopier2.h"
+#include "nsIPipe.h"
+#include "nsIOService.h"
+#include "nsIHttpChannelInternal.h"
+#include "Base64.h"
+#include "WebSocketChannel.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsIX509Cert.h"
+
+static NS_DEFINE_CID(kStreamTransportServiceCID, NS_STREAMTRANSPORTSERVICE_CID);
+
+namespace mozilla {
+namespace dom {
+
+static LazyLogModule gHttpServerLog("HttpServer");
+#undef LOG_I
+#define LOG_I(...) MOZ_LOG(gHttpServerLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
+#undef LOG_V
+#define LOG_V(...) MOZ_LOG(gHttpServerLog, mozilla::LogLevel::Verbose, (__VA_ARGS__))
+#undef LOG_E
+#define LOG_E(...) MOZ_LOG(gHttpServerLog, mozilla::LogLevel::Error, (__VA_ARGS__))
+
+
+NS_IMPL_ISUPPORTS(HttpServer,
+                  nsIServerSocketListener,
+                  nsILocalCertGetCallback)
+
+HttpServer::HttpServer()
+{
+}
+
+HttpServer::~HttpServer()
+{
+}
+
+void
+HttpServer::Init(int32_t aPort, bool aHttps, HttpServerListener* aListener)
+{
+  mPort = aPort;
+  mHttps = aHttps;
+  mListener = aListener;
+
+  nsCOMPtr<nsIPrefBranch> prefService;
+  prefService = do_GetService(NS_PREFSERVICE_CONTRACTID);
+
+  if (mHttps) {
+    nsCOMPtr<nsILocalCertService> lcs =
+      do_CreateInstance("@mozilla.org/security/local-cert-service;1");
+    nsresult rv = lcs->GetOrCreateCert(NS_LITERAL_CSTRING("flyweb"), this);
+    if (NS_FAILED(rv)) {
+      NotifyStarted(rv);
+    }
+  } else {
+    // Make sure to always have an async step before notifying callbacks
+    HandleCert(nullptr, NS_OK);
+  }
+}
+
+NS_IMETHODIMP
+HttpServer::HandleCert(nsIX509Cert* aCert, nsresult aResult)
+{
+  nsresult rv = aResult;
+  if (NS_SUCCEEDED(rv)) {
+    rv = StartServerSocket(aCert);
+  }
+
+  if (NS_FAILED(rv) && mServerSocket) {
+    mServerSocket->Close();
+    mServerSocket = nullptr;
+  }
+
+  NotifyStarted(rv);
+
+  return NS_OK;
+}
+
+void
+HttpServer::NotifyStarted(nsresult aStatus)
+{
+  RefPtr<HttpServerListener> listener = mListener;
+  nsCOMPtr<nsIRunnable> event = NS_NewRunnableFunction([listener, aStatus] ()
+  {
+    listener->OnServerStarted(aStatus);
+  });
+  NS_DispatchToCurrentThread(event);
+}
+
+nsresult
+HttpServer::StartServerSocket(nsIX509Cert* aCert)
+{
+  nsresult rv;
+  mServerSocket =
+    do_CreateInstance(aCert ? "@mozilla.org/network/tls-server-socket;1"
+                            : "@mozilla.org/network/server-socket;1", &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = mServerSocket->Init(mPort, false, -1);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (aCert) {
+    nsCOMPtr<nsITLSServerSocket> tls = do_QueryInterface(mServerSocket);
+    rv = tls->SetServerCert(aCert);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = tls->SetSessionTickets(false);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    mCert = aCert;
+  }
+
+  rv = mServerSocket->AsyncListen(this);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = mServerSocket->GetPort(&mPort);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  LOG_I("HttpServer::StartServerSocket(%p)", this);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpServer::OnSocketAccepted(nsIServerSocket* aServ,
+                             nsISocketTransport* aTransport)
+{
+  MOZ_ASSERT(SameCOMIdentity(aServ, mServerSocket));
+
+  nsresult rv;
+  RefPtr<Connection> conn = new Connection(aTransport, this, rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  LOG_I("HttpServer::OnSocketAccepted(%p) - Socket %p", this, conn.get());
+
+  mConnections.AppendElement(conn.forget());
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpServer::OnStopListening(nsIServerSocket* aServ,
+                            nsresult aStatus)
+{
+  MOZ_ASSERT(aServ == mServerSocket || !mServerSocket);
+
+  LOG_I("HttpServer::OnStopListening(%p) - status 0x%lx", this, aStatus);
+
+  Close();
+
+  return NS_OK;
+}
+
+void
+HttpServer::SendResponse(InternalRequest* aRequest, InternalResponse* aResponse)
+{
+  for (Connection* conn : mConnections) {
+    if (conn->TryHandleResponse(aRequest, aResponse)) {
+      return;
+    }
+  }
+
+  MOZ_ASSERT(false, "Unknown request");
+}
+
+already_AddRefed<nsITransportProvider>
+HttpServer::AcceptWebSocket(InternalRequest* aConnectRequest,
+                            const Optional<nsAString>& aProtocol,
+                            nsACString& aNegotiatedExtensions,
+                            ErrorResult& aRv)
+{
+  for (Connection* conn : mConnections) {
+    if (!conn->HasPendingWebSocketRequest(aConnectRequest)) {
+      continue;
+    }
+    nsCOMPtr<nsITransportProvider> provider =
+      conn->HandleAcceptWebSocket(aProtocol, aNegotiatedExtensions, aRv);
+    if (aRv.Failed()) {
+      conn->Close();
+    }
+    // This connection is now owned by the websocket, or we just closed it
+    mConnections.RemoveElement(conn);
+    return provider.forget();
+  }
+
+  aRv.Throw(NS_ERROR_UNEXPECTED);
+  MOZ_ASSERT(false, "Unknown request");
+
+  return nullptr;
+}
+
+void
+HttpServer::SendWebSocketResponse(InternalRequest* aConnectRequest,
+                                  InternalResponse* aResponse)
+{
+  for (Connection* conn : mConnections) {
+    if (conn->HasPendingWebSocketRequest(aConnectRequest)) {
+      conn->HandleWebSocketResponse(aResponse);
+      return;
+    }
+  }
+
+  MOZ_ASSERT(false, "Unknown request");
+}
+
+void
+HttpServer::Close()
+{
+  if (mServerSocket) {
+    mServerSocket->Close();
+    mServerSocket = nullptr;
+  }
+
+  if (mListener) {
+    mListener->OnServerClose();
+    mListener = nullptr;
+  }
+
+  for (Connection* conn : mConnections) {
+    conn->Close();
+  }
+  mConnections.Clear();
+}
+
+void
+HttpServer::GetCertKey(nsACString& aKey)
+{
+  nsAutoString tmp;
+  if (mCert) {
+    mCert->GetSha256Fingerprint(tmp);
+  }
+  LossyCopyUTF16toASCII(tmp, aKey);
+}
+
+NS_IMPL_ISUPPORTS(HttpServer::TransportProvider,
+                  nsITransportProvider)
+
+HttpServer::TransportProvider::~TransportProvider()
+{
+}
+
+NS_IMETHODIMP
+HttpServer::TransportProvider::SetListener(nsIHttpUpgradeListener* aListener)
+{
+  MOZ_ASSERT(!mListener);
+  MOZ_ASSERT(aListener);
+
+  mListener = aListener;
+
+  MaybeNotify();
+
+  return NS_OK;
+}
+
+void
+HttpServer::TransportProvider::SetTransport(nsISocketTransport* aTransport,
+                                            nsIAsyncInputStream* aInput,
+                                            nsIAsyncOutputStream* aOutput)
+{
+  MOZ_ASSERT(!mTransport);
+  MOZ_ASSERT(aTransport && aInput && aOutput);
+
+  mTransport = aTransport;
+  mInput = aInput;
+  mOutput = aOutput;
+
+  MaybeNotify();
+}
+
+void
+HttpServer::TransportProvider::MaybeNotify()
+{
+  if (mTransport && mListener) {
+    RefPtr<TransportProvider> self = this;
+    nsCOMPtr<nsIRunnable> event = NS_NewRunnableFunction([self, this] ()
+    {
+      mListener->OnTransportAvailable(mTransport, mInput, mOutput);
+    });
+    NS_DispatchToCurrentThread(event);
+  }
+}
+
+NS_IMPL_ISUPPORTS(HttpServer::Connection,
+                  nsIInputStreamCallback,
+                  nsIOutputStreamCallback)
+
+HttpServer::Connection::Connection(nsISocketTransport* aTransport,
+                                   HttpServer* aServer,
+                                   nsresult& rv)
+  : mServer(aServer)
+  , mTransport(aTransport)
+  , mState(eRequestLine)
+  , mCloseAfterRequest(false)
+{
+  nsCOMPtr<nsIInputStream> input;
+  rv = mTransport->OpenInputStream(0, 0, 0, getter_AddRefs(input));
+  NS_ENSURE_SUCCESS_VOID(rv);
+
+  mInput = do_QueryInterface(input);
+
+  nsCOMPtr<nsIOutputStream> output;
+  rv = mTransport->OpenOutputStream(0, 0, 0, getter_AddRefs(output));
+  NS_ENSURE_SUCCESS_VOID(rv);
+
+  mOutput = do_QueryInterface(output);
+
+  if (mServer->mHttps) {
+    SetSecurityObserver(true);
+  } else {
+    mInput->AsyncWait(this, 0, 0, NS_GetCurrentThread());
+  }
+}
+
+NS_IMETHODIMP
+HttpServer::Connection::OnHandshakeDone(nsITLSServerSocket* aServer,
+                                        nsITLSClientStatus* aStatus)
+{
+  LOG_I("HttpServer::Connection::OnHandshakeDone(%p)", this);
+
+  // XXX Verify connection security
+
+  SetSecurityObserver(false);
+  mInput->AsyncWait(this, 0, 0, NS_GetCurrentThread());
+
+  return NS_OK;
+}
+
+void
+HttpServer::Connection::SetSecurityObserver(bool aListen)
+{
+  LOG_I("HttpServer::Connection::SetSecurityObserver(%p) - %s", this,
+    aListen ? "On" : "Off");
+
+  nsCOMPtr<nsISupports> secInfo;
+  mTransport->GetSecurityInfo(getter_AddRefs(secInfo));
+  nsCOMPtr<nsITLSServerConnectionInfo> tlsConnInfo =
+    do_QueryInterface(secInfo);
+  MOZ_ASSERT(tlsConnInfo);
+  tlsConnInfo->SetSecurityObserver(aListen ? this : nullptr);
+}
+
+HttpServer::Connection::~Connection()
+{
+}
+
+NS_IMETHODIMP
+HttpServer::Connection::OnInputStreamReady(nsIAsyncInputStream* aStream)
+{
+  MOZ_ASSERT(!mInput || aStream == mInput);
+
+  LOG_I("HttpServer::Connection::OnInputStreamReady(%p)", this);
+
+  if (!mInput || mState == ePause) {
+    return NS_OK;
+  }
+
+  uint64_t avail;
+  nsresult rv = mInput->Available(&avail);
+  if (NS_FAILED(rv)) {
+    LOG_I("HttpServer::Connection::OnInputStreamReady(%p) - Connection closed", this);
+
+    mServer->mConnections.RemoveElement(this);
+    // Connection closed. Handle errors here.
+    return NS_OK;
+  }
+
+  uint32_t numRead;
+  rv = mInput->ReadSegments(ReadSegmentsFunc,
+                            this,
+                            UINT32_MAX,
+                            &numRead);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = mInput->AsyncWait(this, 0, 0, NS_GetCurrentThread());
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+NS_METHOD
+HttpServer::Connection::ReadSegmentsFunc(nsIInputStream* aIn,
+                                         void* aClosure,
+                                         const char* aBuffer,
+                                         uint32_t aToOffset,
+                                         uint32_t aCount,
+                                         uint32_t* aWriteCount)
+{
+  const char* buffer = aBuffer;
+  nsresult rv = static_cast<HttpServer::Connection*>(aClosure)->
+    ConsumeInput(buffer, buffer + aCount);
+
+  *aWriteCount = buffer - aBuffer;
+  MOZ_ASSERT(*aWriteCount <= aCount);
+
+  return rv;
+}
+
+static const char*
+findCRLF(const char* aBuffer, const char* aEnd)
+{
+  if (aBuffer + 1 >= aEnd) {
+    return nullptr;
+  }
+
+  const char* pos;
+  while ((pos = static_cast<const char*>(memchr(aBuffer,
+                                                '\r',
+                                                aEnd - aBuffer - 1)))) {
+    if (*(pos + 1) == '\n') {
+      return pos;
+    }
+    aBuffer = pos + 1;
+  }
+  return nullptr;
+}
+
+nsresult
+HttpServer::Connection::ConsumeInput(const char*& aBuffer,
+                                     const char* aEnd)
+{
+  nsresult rv;
+  while (mState == eRequestLine ||
+         mState == eHeaders) {
+    // Consume line-by-line
+
+    // Check if buffer boundry ended up right between the CR and LF
+    if (!mInputBuffer.IsEmpty() && mInputBuffer.Last() == '\r' &&
+        *aBuffer == '\n') {
+      aBuffer++;
+      rv = ConsumeLine(mInputBuffer.BeginReading(), mInputBuffer.Length() - 1);
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      mInputBuffer.Truncate();
+    }
+
+    // Look for a CRLF
+    const char* pos = findCRLF(aBuffer, aEnd);
+    if (!pos) {
+      mInputBuffer.Append(aBuffer, aEnd - aBuffer);
+      aBuffer = aEnd;
+      return NS_OK;
+    }
+
+    if (!mInputBuffer.IsEmpty()) {
+      mInputBuffer.Append(aBuffer, pos - aBuffer);
+      aBuffer = pos + 2;
+      rv = ConsumeLine(mInputBuffer.BeginReading(), mInputBuffer.Length() - 1);
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      mInputBuffer.Truncate();
+    } else {
+      rv = ConsumeLine(aBuffer, pos - aBuffer);
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      aBuffer = pos + 2;
+    }
+  }
+
+  if (mState == eBody) {
+    uint32_t size = std::min(mRemainingBodySize,
+                             static_cast<uint32_t>(aEnd - aBuffer));
+    uint32_t written = size;
+
+    if (mCurrentRequestBody) {
+      rv = mCurrentRequestBody->Write(aBuffer, size, &written);
+      // Since we've given the pipe unlimited size, we should never
+      // end up needing to block.
+      MOZ_ASSERT(rv != NS_BASE_STREAM_WOULD_BLOCK);
+      if (NS_FAILED(rv)) {
+        written = size;
+        mCurrentRequestBody = nullptr;
+      }
+    }
+
+    aBuffer += written;
+    mRemainingBodySize -= written;
+    if (!mRemainingBodySize) {
+      mCurrentRequestBody->Close();
+      mCurrentRequestBody = nullptr;
+      mState = eRequestLine;
+    }
+  }
+
+  return NS_OK;
+}
+
+static bool
+ContainsToken(const nsCString& aList, const nsCString& aToken)
+{
+  nsCCharSeparatedTokenizer tokens(aList, ',');
+  bool found = false;
+  while (!found && tokens.hasMoreTokens()) {
+    found = tokens.nextToken().Equals(aToken);
+  }
+  return found;
+}
+
+static bool
+IsWebSocketRequest(InternalRequest* aRequest, uint32_t aHttpVersion)
+{
+  if (aHttpVersion < 1) {
+    return false;
+  }
+
+  nsAutoCString str;
+  aRequest->GetMethod(str);
+  if (!str.EqualsLiteral("GET")) {
+    return false;
+  }
+
+  InternalHeaders* headers = aRequest->Headers();
+  ErrorResult res;
+
+  headers->Get(NS_LITERAL_CSTRING("upgrade"), str, res);
+  MOZ_ASSERT(!res.Failed());
+  if (!str.EqualsLiteral("websocket")) {
+    return false;
+  }
+
+  headers->Get(NS_LITERAL_CSTRING("connection"), str, res);
+  MOZ_ASSERT(!res.Failed());
+  if (!ContainsToken(str, NS_LITERAL_CSTRING("Upgrade"))) {
+    return false;
+  }
+
+  headers->Get(NS_LITERAL_CSTRING("sec-websocket-key"), str, res);
+  MOZ_ASSERT(!res.Failed());
+  nsAutoCString binary;
+  if (NS_FAILED(Base64Decode(str, binary)) || binary.Length() != 16) {
+    return false;
+  }
+
+  nsresult rv;
+  headers->Get(NS_LITERAL_CSTRING("sec-websocket-version"), str, res);
+  MOZ_ASSERT(!res.Failed());
+  if (str.ToInteger(&rv) != 13 || NS_FAILED(rv)) {
+    return false;
+  }
+
+  return true;
+}
+
+nsresult
+HttpServer::Connection::ConsumeLine(const char* aBuffer,
+                                    size_t aLength)
+{
+  MOZ_ASSERT(mState == eRequestLine ||
+             mState == eHeaders);
+
+  if (MOZ_LOG_TEST(gHttpServerLog, mozilla::LogLevel::Verbose)) {
+    nsCString line(aBuffer, aLength);
+    LOG_V("HttpServer::Connection::ConsumeLine(%p) - \"%s\"", this, line.get());
+  }
+
+  if (mState == eRequestLine) {
+    LOG_V("HttpServer::Connection::ConsumeLine(%p) - Parsing request line", this);
+    NS_ENSURE_FALSE(mCloseAfterRequest, NS_ERROR_UNEXPECTED);
+
+    if (aLength == 0) {
+      // Ignore empty lines before the request line
+      return NS_OK;
+    }
+    MOZ_ASSERT(!mPendingReq);
+
+    // Process request line
+    nsCWhitespaceTokenizer tokens(Substring(aBuffer, aLength));
+
+    NS_ENSURE_TRUE(tokens.hasMoreTokens(), NS_ERROR_UNEXPECTED);
+    nsDependentCSubstring method = tokens.nextToken();
+    NS_ENSURE_TRUE(NS_IsValidHTTPToken(method), NS_ERROR_UNEXPECTED);
+
+    NS_ENSURE_TRUE(tokens.hasMoreTokens(), NS_ERROR_UNEXPECTED);
+    nsDependentCSubstring url = tokens.nextToken();
+    // Seems like it's also allowed to pass full urls with scheme+host+port.
+    // May need to support that.
+    NS_ENSURE_TRUE(url.First() == '/', NS_ERROR_UNEXPECTED);
+
+    mPendingReq = new InternalRequest(url);
+    mPendingReq->SetMethod(method);
+
+    NS_ENSURE_TRUE(tokens.hasMoreTokens(), NS_ERROR_UNEXPECTED);
+    nsDependentCSubstring version = tokens.nextToken();
+    NS_ENSURE_TRUE(StringBeginsWith(version, NS_LITERAL_CSTRING("HTTP/1.")),
+                   NS_ERROR_UNEXPECTED);
+    nsresult rv;
+    // This integer parsing is likely not strict enough.
+    nsCString reqVersion;
+    reqVersion = Substring(version, MOZ_ARRAY_LENGTH("HTTP/1.") - 1);
+    mPendingReqVersion = reqVersion.ToInteger(&rv);
+    NS_ENSURE_SUCCESS(rv, NS_ERROR_UNEXPECTED);
+
+    NS_ENSURE_FALSE(tokens.hasMoreTokens(), NS_ERROR_UNEXPECTED);
+
+    LOG_V("HttpServer::Connection::ConsumeLine(%p) - Parsed request line", this);
+
+    mState = eHeaders;
+
+    return NS_OK;
+  }
+
+  if (aLength == 0) {
+    LOG_V("HttpServer::Connection::ConsumeLine(%p) - Found end of headers", this);
+
+    MaybeAddPendingHeader();
+
+    ErrorResult res;
+    mPendingReq->Headers()->SetGuard(HeadersGuardEnum::Immutable, res);
+
+    // Check for WebSocket
+    if (IsWebSocketRequest(mPendingReq, mPendingReqVersion)) {
+      LOG_V("HttpServer::Connection::ConsumeLine(%p) - Fire OnWebSocket", this);
+
+      mState = ePause;
+      mPendingWebSocketRequest = mPendingReq.forget();
+      mPendingReqVersion = 0;
+
+      RefPtr<HttpServerListener> listener = mServer->mListener;
+      RefPtr<InternalRequest> request = mPendingWebSocketRequest;
+      nsCOMPtr<nsIRunnable> event =
+        NS_NewRunnableFunction([listener, request] ()
+      {
+        listener->OnWebSocket(request);
+      });
+      NS_DispatchToCurrentThread(event);
+
+      return NS_OK;
+    }
+
+    nsAutoCString header;
+    mPendingReq->Headers()->Get(NS_LITERAL_CSTRING("connection"),
+                                header,
+                                res);
+    MOZ_ASSERT(!res.Failed());
+    // 1.0 defaults to closing connections.
+    // 1.1 and higher defaults to keep-alive.
+    if (ContainsToken(header, NS_LITERAL_CSTRING("close")) ||
+        (mPendingReqVersion == 0 &&
+         !ContainsToken(header, NS_LITERAL_CSTRING("keep-alive")))) {
+      mCloseAfterRequest = true;
+    }
+
+    mPendingReq->Headers()->Get(NS_LITERAL_CSTRING("content-length"),
+                                header,
+                                res);
+    MOZ_ASSERT(!res.Failed());
+
+    LOG_V("HttpServer::Connection::ConsumeLine(%p) - content-length is \"%s\"",
+          this, header.get());
+
+    if (!header.IsEmpty()) {
+      nsresult rv;
+      mRemainingBodySize = header.ToInteger(&rv);
+      NS_ENSURE_SUCCESS(rv, rv);
+    } else {
+      mRemainingBodySize = 0;
+    }
+
+    if (mRemainingBodySize) {
+      LOG_V("HttpServer::Connection::ConsumeLine(%p) - Starting consume body", this);
+      mState = eBody;
+
+      // We use an unlimited buffer size here to ensure
+      // that we get to the next request even if the webpage hangs on
+      // to the request indefinitely without consuming the body.
+      nsCOMPtr<nsIInputStream> input;
+      nsCOMPtr<nsIOutputStream> output;
+      nsresult rv = NS_NewPipe(getter_AddRefs(input),
+                               getter_AddRefs(output),
+                               0,          // Segment size
+                               UINT32_MAX, // Unlimited buffer size
+                               false,      // not nonBlockingInput
+                               true);      // nonBlockingOutput
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      mCurrentRequestBody = do_QueryInterface(output);
+      mPendingReq->SetBody(input);
+    } else {
+      LOG_V("HttpServer::Connection::ConsumeLine(%p) - No body", this);
+      mState = eRequestLine;
+    }
+
+    mPendingRequests.AppendElement(PendingRequest(mPendingReq, nullptr));
+
+    LOG_V("HttpServer::Connection::ConsumeLine(%p) - Fire OnRequest", this);
+
+    RefPtr<HttpServerListener> listener = mServer->mListener;
+    RefPtr<InternalRequest> request = mPendingReq.forget();
+    nsCOMPtr<nsIRunnable> event =
+      NS_NewRunnableFunction([listener, request] ()
+    {
+      listener->OnRequest(request);
+    });
+    NS_DispatchToCurrentThread(event);
+
+    mPendingReqVersion = 0;
+
+    return NS_OK;
+  }
+
+  // Parse header line
+  if (aBuffer[0] == ' ' || aBuffer[0] == '\t') {
+    LOG_V("HttpServer::Connection::ConsumeLine(%p) - Add to header %s",
+          this,
+          mPendingHeaderName.get());
+
+    NS_ENSURE_FALSE(mPendingHeaderName.IsEmpty(),
+                    NS_ERROR_UNEXPECTED);
+
+    // We might need to do whitespace trimming/compression here.
+    mPendingHeaderValue.Append(aBuffer, aLength);
+    return NS_OK;
+  }
+
+  MaybeAddPendingHeader();
+
+  const char* colon = static_cast<const char*>(memchr(aBuffer, ':', aLength));
+  NS_ENSURE_TRUE(colon, NS_ERROR_UNEXPECTED);
+
+  ToLowerCase(Substring(aBuffer, colon - aBuffer), mPendingHeaderName);
+  mPendingHeaderValue.Assign(colon + 1, aLength - (colon - aBuffer) - 1);
+
+  NS_ENSURE_TRUE(NS_IsValidHTTPToken(mPendingHeaderName),
+                 NS_ERROR_UNEXPECTED);
+
+  LOG_V("HttpServer::Connection::ConsumeLine(%p) - Parsed header %s",
+        this,
+        mPendingHeaderName.get());
+
+  return NS_OK;
+}
+
+void
+HttpServer::Connection::MaybeAddPendingHeader()
+{
+  if (mPendingHeaderName.IsEmpty()) {
+    return;
+  }
+
+  // We might need to do more whitespace trimming/compression here.
+  mPendingHeaderValue.Trim(" \t");
+
+  ErrorResult rv;
+  mPendingReq->Headers()->Append(mPendingHeaderName, mPendingHeaderValue, rv);
+  mPendingHeaderName.Truncate();
+}
+
+bool
+HttpServer::Connection::TryHandleResponse(InternalRequest* aRequest,
+                                          InternalResponse* aResponse)
+{
+  bool handledResponse = false;
+  for (uint32_t i = 0; i < mPendingRequests.Length(); ++i) {
+    PendingRequest& pending = mPendingRequests[i];
+    if (pending.first() == aRequest) {
+      MOZ_ASSERT(!handledResponse);
+      MOZ_ASSERT(!pending.second());
+
+      pending.second() = aResponse;
+      if (i != 0) {
+        return true;
+      }
+      handledResponse = true;
+    }
+
+    if (handledResponse && !pending.second()) {
+      // Shortcut if we've handled the response, and
+      // we don't have more responses to send
+      return true;
+    }
+
+    if (i == 0 && pending.second()) {
+      RefPtr<InternalResponse> resp = pending.second().forget();
+      mPendingRequests.RemoveElementAt(0);
+      QueueResponse(resp);
+      --i;
+    }
+  }
+
+  return handledResponse;
+}
+
+already_AddRefed<nsITransportProvider>
+HttpServer::Connection::HandleAcceptWebSocket(const Optional<nsAString>& aProtocol,
+                                              nsACString& aNegotiatedExtensions,
+                                              ErrorResult& aRv)
+{
+  MOZ_ASSERT(mPendingWebSocketRequest);
+
+  RefPtr<InternalResponse> response =
+    new InternalResponse(101, NS_LITERAL_CSTRING("Switching Protocols"));
+
+  InternalHeaders* headers = response->Headers();
+  headers->Set(NS_LITERAL_CSTRING("Upgrade"),
+               NS_LITERAL_CSTRING("websocket"),
+               aRv);
+  headers->Set(NS_LITERAL_CSTRING("Connection"),
+               NS_LITERAL_CSTRING("Upgrade"),
+               aRv);
+  if (aProtocol.WasPassed()) {
+    NS_ConvertUTF16toUTF8 protocol(aProtocol.Value());
+    nsAutoCString reqProtocols;
+    mPendingWebSocketRequest->Headers()->
+      Get(NS_LITERAL_CSTRING("Sec-WebSocket-Protocol"), reqProtocols, aRv);
+    if (!ContainsToken(reqProtocols, protocol)) {
+      // Should throw a better error here
+      aRv.Throw(NS_ERROR_FAILURE);
+      return nullptr;
+    }
+
+    headers->Set(NS_LITERAL_CSTRING("Sec-WebSocket-Protocol"),
+                 protocol, aRv);
+  }
+
+  nsAutoCString key, hash;
+  mPendingWebSocketRequest->Headers()->
+    Get(NS_LITERAL_CSTRING("Sec-WebSocket-Key"), key, aRv);
+  nsresult rv = mozilla::net::CalculateWebSocketHashedSecret(key, hash);
+  if (NS_FAILED(rv)) {
+    aRv.Throw(rv);
+    return nullptr;
+  }
+  headers->Set(NS_LITERAL_CSTRING("Sec-WebSocket-Accept"), hash, aRv);
+
+  nsAutoCString extensions;
+  mPendingWebSocketRequest->Headers()->
+    Get(NS_LITERAL_CSTRING("Sec-WebSocket-Extensions"), extensions, aRv);
+  mozilla::net::ProcessServerWebSocketExtensions(extensions,
+                                                 aNegotiatedExtensions);
+  if (!aNegotiatedExtensions.IsEmpty()) {
+    headers->Set(NS_LITERAL_CSTRING("Sec-WebSocket-Extensions"),
+                 aNegotiatedExtensions, aRv);
+  }
+
+  RefPtr<TransportProvider> result = new TransportProvider();
+  mWebSocketTransportProvider = result;
+
+  QueueResponse(response);
+
+  return result.forget();
+}
+
+void
+HttpServer::Connection::HandleWebSocketResponse(InternalResponse* aResponse)
+{
+  MOZ_ASSERT(mPendingWebSocketRequest);
+
+  mState = eRequestLine;
+  mPendingWebSocketRequest = nullptr;
+  mInput->AsyncWait(this, 0, 0, NS_GetCurrentThread());
+
+  QueueResponse(aResponse);
+}
+
+void
+HttpServer::Connection::QueueResponse(InternalResponse* aResponse)
+{
+  bool chunked = false;
+
+  RefPtr<InternalHeaders> headers = new InternalHeaders(*aResponse->Headers());
+  {
+    ErrorResult res;
+    headers->SetGuard(HeadersGuardEnum::None, res);
+  }
+  nsCOMPtr<nsIInputStream> body;
+  int64_t bodySize;
+  aResponse->GetBody(getter_AddRefs(body), &bodySize);
+
+  if (body && bodySize >= 0) {
+    nsCString sizeStr;
+    sizeStr.AppendInt(bodySize);
+
+    LOG_V("HttpServer::Connection::QueueResponse(%p) - "
+          "Setting content-length to %s",
+          this, sizeStr.get());
+
+    ErrorResult res;
+    headers->Set(NS_LITERAL_CSTRING("content-length"), sizeStr, res);
+  } else if (body) {
+    // Use chunked transfer encoding
+    LOG_V("HttpServer::Connection::QueueResponse(%p) - Chunked transfer-encoding",
+          this);
+
+    ErrorResult res;
+    headers->Set(NS_LITERAL_CSTRING("transfer-encoding"),
+                 NS_LITERAL_CSTRING("chunked"),
+                 res);
+    headers->Delete(NS_LITERAL_CSTRING("content-length"), res);
+    chunked = true;
+
+  } else {
+    LOG_V("HttpServer::Connection::QueueResponse(%p) - "
+          "No body - setting content-length to 0", this);
+
+    ErrorResult res;
+    headers->Set(NS_LITERAL_CSTRING("content-length"),
+                 NS_LITERAL_CSTRING("0"), res);
+  }
+
+  nsCString head(NS_LITERAL_CSTRING("HTTP/1.1 "));
+  head.AppendInt(aResponse->GetStatus());
+  // XXX is the statustext security checked?
+  head.Append(NS_LITERAL_CSTRING(" ") +
+              aResponse->GetStatusText() +
+              NS_LITERAL_CSTRING("\r\n"));
+
+  nsTArray<InternalHeaders::Entry> entries;
+  headers->GetEntries(entries);
+
+  for (auto header : entries) {
+    head.Append(header.mName +
+                NS_LITERAL_CSTRING(": ") +
+                header.mValue +
+                NS_LITERAL_CSTRING("\r\n"));
+  }
+
+  head.Append(NS_LITERAL_CSTRING("\r\n"));
+
+  mOutputBuffers.AppendElement()->mString = head;
+  if (body) {
+    OutputBuffer* bodyBuffer = mOutputBuffers.AppendElement();
+    bodyBuffer->mStream = body;
+    bodyBuffer->mChunked = chunked;
+  }
+
+  OnOutputStreamReady(mOutput);
+}
+
+namespace {
+
+typedef MozPromise<nsresult, bool, false> StreamCopyPromise;
+
+class StreamCopier final : public nsIOutputStreamCallback
+                         , public nsIInputStreamCallback
+                         , public nsIRunnable
+{
+public:
+  static RefPtr<StreamCopyPromise>
+    Copy(nsIInputStream* aSource, nsIAsyncOutputStream* aSink,
+         bool aChunked)
+  {
+    RefPtr<StreamCopier> copier = new StreamCopier(aSource, aSink, aChunked);
+
+    RefPtr<StreamCopyPromise> p = copier->mPromise.Ensure(__func__);
+
+    nsresult rv = copier->mTarget->Dispatch(copier, NS_DISPATCH_NORMAL);
+    if (NS_FAILED(rv)) {
+      copier->mPromise.Resolve(rv, __func__);
+    }
+
+    return p;
+  }
+
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSIINPUTSTREAMCALLBACK
+  NS_DECL_NSIOUTPUTSTREAMCALLBACK
+  NS_DECL_NSIRUNNABLE
+
+private:
+  StreamCopier(nsIInputStream* aSource, nsIAsyncOutputStream* aSink,
+               bool aChunked)
+    : mSource(aSource)
+    , mAsyncSource(do_QueryInterface(aSource))
+    , mSink(aSink)
+    , mTarget(do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID))
+    , mChunkRemaining(0)
+    , mChunked(aChunked)
+    , mAddedFinalSeparator(false)
+    , mFirstChunk(aChunked)
+    {
+    }
+  ~StreamCopier() {}
+
+  static NS_METHOD FillOutputBufferHelper(nsIOutputStream* aOutStr,
+                                          void* aClosure,
+                                          char* aBuffer,
+                                          uint32_t aOffset,
+                                          uint32_t aCount,
+                                          uint32_t* aCountRead);
+  nsresult FillOutputBuffer(char* aBuffer,
+                            uint32_t aCount,
+                            uint32_t* aCountRead);
+
+  nsCOMPtr<nsIInputStream> mSource;
+  nsCOMPtr<nsIAsyncInputStream> mAsyncSource;
+  nsCOMPtr<nsIAsyncOutputStream> mSink;
+  MozPromiseHolder<StreamCopyPromise> mPromise;
+  nsCOMPtr<nsIEventTarget> mTarget; // XXX we should cache this somewhere
+  uint32_t mChunkRemaining;
+  nsCString mSeparator;
+  bool mChunked;
+  bool mAddedFinalSeparator;
+  bool mFirstChunk;
+};
+
+NS_IMPL_ISUPPORTS(StreamCopier,
+                  nsIOutputStreamCallback,
+                  nsIInputStreamCallback,
+                  nsIRunnable)
+
+struct WriteState
+{
+  StreamCopier* copier;
+  nsresult sourceRv;
+};
+
+// This function only exists to enable FillOutputBuffer to be a non-static
+// function where we can use member variables more easily.
+NS_METHOD
+StreamCopier::FillOutputBufferHelper(nsIOutputStream* aOutStr,
+                                     void* aClosure,
+                                     char* aBuffer,
+                                     uint32_t aOffset,
+                                     uint32_t aCount,
+                                     uint32_t* aCountRead)
+{
+  WriteState* ws = static_cast<WriteState*>(aClosure);
+  ws->sourceRv = ws->copier->FillOutputBuffer(aBuffer, aCount, aCountRead);
+  return ws->sourceRv;
+}
+
+NS_METHOD
+CheckForEOF(nsIInputStream* aIn,
+            void* aClosure,
+            const char* aBuffer,
+            uint32_t aToOffset,
+            uint32_t aCount,
+            uint32_t* aWriteCount)
+{
+  *static_cast<bool*>(aClosure) = true;
+  *aWriteCount = 0;
+  return NS_BINDING_ABORTED;
+}
+
+nsresult
+StreamCopier::FillOutputBuffer(char* aBuffer,
+                               uint32_t aCount,
+                               uint32_t* aCountRead)
+{
+  nsresult rv;
+  while (mChunked && mSeparator.IsEmpty() && !mChunkRemaining &&
+         !mAddedFinalSeparator) {
+    uint64_t avail;
+    rv = mSource->Available(&avail);
+    if (rv == NS_BASE_STREAM_CLOSED) {
+      avail = 0;
+      rv = NS_OK;
+    }
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    mChunkRemaining = avail > UINT32_MAX ? UINT32_MAX :
+                      static_cast<uint32_t>(avail);
+
+    if (!mChunkRemaining) {
+      // Either it's an non-blocking stream without any data
+      // currently available, or we're at EOF. Sadly there's no way
+      // to tell other than to read from the stream.
+      bool hadData = false;
+      uint32_t numRead;
+      rv = mSource->ReadSegments(CheckForEOF, &hadData, 1, &numRead);
+      if (rv == NS_BASE_STREAM_CLOSED) {
+        avail = 0;
+        rv = NS_OK;
+      }
+      NS_ENSURE_SUCCESS(rv, rv);
+      MOZ_ASSERT(numRead == 0);
+
+      if (hadData) {
+        // The source received data between the call to Available and the
+        // call to ReadSegments. Restart with a new call to Available
+        continue;
+      }
+
+      // We're at EOF, write a separator with 0
+      mAddedFinalSeparator = true;
+    }
+
+    if (mFirstChunk) {
+      mFirstChunk = false;
+      MOZ_ASSERT(mSeparator.IsEmpty());
+    } else {
+      // For all chunks except the first, add the newline at the end
+      // of the previous chunk of data
+      mSeparator.AssignLiteral("\r\n");
+    }
+    mSeparator.AppendInt(mChunkRemaining, 16);
+    mSeparator.AppendLiteral("\r\n");
+
+    if (mAddedFinalSeparator) {
+      mSeparator.AppendLiteral("\r\n");
+    }
+
+    break;
+  }
+
+  // If we're doing chunked encoding, we should either have a chunk size,
+  // or we should have reached the end of the input stream.
+  MOZ_ASSERT_IF(mChunked, mChunkRemaining || mAddedFinalSeparator);
+  // We should only have a separator if we're doing chunked encoding
+  MOZ_ASSERT_IF(!mSeparator.IsEmpty(), mChunked);
+
+  if (!mSeparator.IsEmpty()) {
+    *aCountRead = std::min(mSeparator.Length(), aCount);
+    memcpy(aBuffer, mSeparator.BeginReading(), *aCountRead);
+    mSeparator.Cut(0, *aCountRead);
+    rv = NS_OK;
+  } else if (mChunked) {
+    *aCountRead = 0;
+    if (mChunkRemaining) {
+      rv = mSource->Read(aBuffer,
+                         std::min(aCount, mChunkRemaining),
+                         aCountRead);
+      mChunkRemaining -= *aCountRead;
+    }
+  } else {
+    rv = mSource->Read(aBuffer, aCount, aCountRead);
+  }
+
+  if (NS_SUCCEEDED(rv) && *aCountRead == 0) {
+    rv = NS_BASE_STREAM_CLOSED;
+  }
+
+  return rv;
+}
+
+NS_IMETHODIMP
+StreamCopier::Run()
+{
+  nsresult rv;
+  while (1) {
+    WriteState state = { this, NS_OK };
+    uint32_t written;
+    rv = mSink->WriteSegments(FillOutputBufferHelper, &state,
+                              mozilla::net::nsIOService::gDefaultSegmentSize,
+                              &written);
+    MOZ_ASSERT(NS_SUCCEEDED(rv) || NS_SUCCEEDED(state.sourceRv));
+    if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+      mSink->AsyncWait(this, 0, 0, mTarget);
+      return NS_OK;
+    }
+    if (NS_FAILED(rv)) {
+      mPromise.Resolve(rv, __func__);
+      return NS_OK;
+    }
+
+    if (state.sourceRv == NS_BASE_STREAM_WOULD_BLOCK) {
+      MOZ_ASSERT(mAsyncSource);
+      mAsyncSource->AsyncWait(this, 0, 0, mTarget);
+      mSink->AsyncWait(this, nsIAsyncInputStream::WAIT_CLOSURE_ONLY,
+                       0, mTarget);
+
+      return NS_OK;
+    }
+    if (state.sourceRv == NS_BASE_STREAM_CLOSED) {
+      // We're done!
+      // No longer interested in callbacks about either stream closing
+      mSink->AsyncWait(nullptr, 0, 0, nullptr);
+      if (mAsyncSource) {
+        mAsyncSource->AsyncWait(nullptr, 0, 0, nullptr);
+      }
+
+      mSource->Close();
+      mSource = nullptr;
+      mAsyncSource = nullptr;
+      mSink = nullptr;
+
+      mPromise.Resolve(NS_OK, __func__);
+
+      return NS_OK;
+    }
+
+    if (NS_FAILED(state.sourceRv)) {
+      mPromise.Resolve(state.sourceRv, __func__);
+      return NS_OK;
+    }
+  }
+
+  MOZ_ASSUME_UNREACHABLE_MARKER();
+}
+
+NS_IMETHODIMP
+StreamCopier::OnInputStreamReady(nsIAsyncInputStream* aStream)
+{
+  MOZ_ASSERT(aStream == mAsyncSource ||
+             (!mSource && !mAsyncSource && !mSink));
+  return mSource ? Run() : NS_OK;
+}
+
+NS_IMETHODIMP
+StreamCopier::OnOutputStreamReady(nsIAsyncOutputStream* aStream)
+{
+  MOZ_ASSERT(aStream == mSink ||
+             (!mSource && !mAsyncSource && !mSink));
+  return mSource ? Run() : NS_OK;
+}
+
+} // namespace
+
+NS_IMETHODIMP
+HttpServer::Connection::OnOutputStreamReady(nsIAsyncOutputStream* aStream)
+{
+  MOZ_ASSERT(aStream == mOutput || !mOutput);
+  if (!mOutput) {
+    return NS_OK;
+  }
+
+  nsresult rv;
+
+  while (!mOutputBuffers.IsEmpty()) {
+    if (!mOutputBuffers[0].mStream) {
+      nsCString& buffer = mOutputBuffers[0].mString;
+      while (!buffer.IsEmpty()) {
+        uint32_t written = 0;
+        rv = mOutput->Write(buffer.BeginReading(),
+                            buffer.Length(),
+                            &written);
+
+        buffer.Cut(0, written);
+
+        if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+          return mOutput->AsyncWait(this, 0, 0, NS_GetCurrentThread());
+        }
+
+        if (NS_FAILED(rv)) {
+          Close();
+          return NS_OK;
+        }
+      }
+      mOutputBuffers.RemoveElementAt(0);
+    } else {
+      if (mOutputCopy) {
+        // we're already copying the stream
+        return NS_OK;
+      }
+
+      mOutputCopy =
+        StreamCopier::Copy(mOutputBuffers[0].mStream,
+                           mOutput,
+                           mOutputBuffers[0].mChunked);
+
+      RefPtr<Connection> self = this;
+
+      mOutputCopy->
+        Then(AbstractThread::MainThread(),
+             __func__,
+             [self, this] (nsresult aStatus) {
+               MOZ_ASSERT(mOutputBuffers[0].mStream);
+               LOG_V("HttpServer::Connection::OnOutputStreamReady(%p) - "
+                     "Sent body. Status 0x%lx",
+                     this, aStatus);
+
+               mOutputBuffers.RemoveElementAt(0);
+               mOutputCopy = nullptr;
+               OnOutputStreamReady(mOutput);
+             },
+             [] (bool) { MOZ_ASSERT_UNREACHABLE("Reject unexpected"); });
+    }
+  }
+
+  if (mPendingRequests.IsEmpty()) {
+    if (mCloseAfterRequest) {
+      LOG_V("HttpServer::Connection::OnOutputStreamReady(%p) - Closing channel",
+            this);
+      Close();
+    } else if (mWebSocketTransportProvider) {
+      mInput->AsyncWait(nullptr, 0, 0, nullptr);
+      mOutput->AsyncWait(nullptr, 0, 0, nullptr);
+
+      mWebSocketTransportProvider->SetTransport(mTransport, mInput, mOutput);
+      mTransport = nullptr;
+      mInput = nullptr;
+      mOutput = nullptr;
+      mWebSocketTransportProvider = nullptr;
+    }
+  }
+
+  return NS_OK;
+}
+
+void
+HttpServer::Connection::Close()
+{
+  if (!mTransport) {
+    MOZ_ASSERT(!mOutput && !mInput);
+    return;
+  }
+
+  mTransport->Close(NS_BINDING_ABORTED);
+  if (mInput) {
+    mInput->Close();
+    mInput = nullptr;
+  }
+  if (mOutput) {
+    mOutput->Close();
+    mOutput = nullptr;
+  }
+
+  mTransport = nullptr;
+
+  mInputBuffer.Truncate();
+  mOutputBuffers.Clear();
+  mPendingRequests.Clear();
+}
+
+
+} // namespace net
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/flyweb/HttpServer.h
@@ -0,0 +1,192 @@
+/* -*- 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_dom_HttpServer_h
+#define mozilla_dom_HttpServer_h
+
+#include "nsISupportsImpl.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "nsITLSServerSocket.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "mozilla/Variant.h"
+#include "nsIRequestObserver.h"
+#include "mozilla/MozPromise.h"
+#include "nsITransportProvider.h"
+#include "nsILocalCertService.h"
+
+class nsIX509Cert;
+
+namespace mozilla {
+namespace dom {
+
+class InternalRequest;
+class InternalResponse;
+
+class HttpServerListener
+{
+public:
+  // switch to NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING when that lands
+  NS_IMETHOD_(MozExternalRefCountType) AddRef(void) = 0;
+  NS_IMETHOD_(MozExternalRefCountType) Release(void) = 0;
+
+  virtual void OnServerStarted(nsresult aStatus) = 0;
+  virtual void OnRequest(InternalRequest* aRequest) = 0;
+  virtual void OnWebSocket(InternalRequest* aConnectRequest) = 0;
+  virtual void OnServerClose() = 0;
+};
+
+class HttpServer final : public nsIServerSocketListener,
+                         public nsILocalCertGetCallback
+{
+public:
+  HttpServer();
+
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSISERVERSOCKETLISTENER
+  NS_DECL_NSILOCALCERTGETCALLBACK
+
+  void Init(int32_t aPort, bool aHttps, HttpServerListener* aListener);
+
+  void SendResponse(InternalRequest* aRequest, InternalResponse* aResponse);
+  already_AddRefed<nsITransportProvider>
+    AcceptWebSocket(InternalRequest* aConnectRequest,
+                    const Optional<nsAString>& aProtocol,
+                    nsACString& aNegotiatedExtensions,
+                    ErrorResult& aRv);
+  void SendWebSocketResponse(InternalRequest* aConnectRequest,
+                             InternalResponse* aResponse);
+
+  void Close();
+
+  void GetCertKey(nsACString& aKey);
+
+  int32_t GetPort()
+  {
+    return mPort;
+  }
+
+private:
+  ~HttpServer();
+
+  nsresult StartServerSocket(nsIX509Cert* aCert);
+  void NotifyStarted(nsresult aStatus);
+
+  class TransportProvider final : public nsITransportProvider
+  {
+  public:
+    NS_DECL_ISUPPORTS
+    NS_DECL_NSITRANSPORTPROVIDER
+
+    void SetTransport(nsISocketTransport* aTransport,
+                      nsIAsyncInputStream* aInput,
+                      nsIAsyncOutputStream* aOutput);
+
+  private:
+    virtual ~TransportProvider();
+    void MaybeNotify();
+
+    nsCOMPtr<nsIHttpUpgradeListener> mListener;
+    nsCOMPtr<nsISocketTransport> mTransport;
+    nsCOMPtr<nsIAsyncInputStream> mInput;
+    nsCOMPtr<nsIAsyncOutputStream> mOutput;
+  };
+
+  class Connection final : public nsIInputStreamCallback
+                         , public nsIOutputStreamCallback
+                         , public nsITLSServerSecurityObserver
+  {
+  public:
+    Connection(nsISocketTransport* aTransport,
+               HttpServer* aServer,
+               nsresult& rv);
+
+    NS_DECL_ISUPPORTS
+    NS_DECL_NSIINPUTSTREAMCALLBACK
+    NS_DECL_NSIOUTPUTSTREAMCALLBACK
+    NS_DECL_NSITLSSERVERSECURITYOBSERVER
+
+    bool TryHandleResponse(InternalRequest* aRequest,
+                           InternalResponse* aResponse);
+    already_AddRefed<nsITransportProvider>
+      HandleAcceptWebSocket(const Optional<nsAString>& aProtocol,
+                            nsACString& aNegotiatedExtensions,
+                            ErrorResult& aRv);
+    void HandleWebSocketResponse(InternalResponse* aResponse);
+    bool HasPendingWebSocketRequest(InternalRequest* aRequest)
+    {
+      return aRequest == mPendingWebSocketRequest;
+    }
+
+    void Close();
+
+    private:
+    ~Connection();
+
+    void SetSecurityObserver(bool aListen);
+
+    static NS_METHOD ReadSegmentsFunc(nsIInputStream* aIn,
+                                      void* aClosure,
+                                      const char* aBuffer,
+                                      uint32_t aToOffset,
+                                      uint32_t aCount,
+                                      uint32_t* aWriteCount);
+    nsresult ConsumeInput(const char*& aBuffer,
+                          const char* aEnd);
+    nsresult ConsumeLine(const char* aBuffer,
+                         size_t aLength);
+    void MaybeAddPendingHeader();
+
+    void QueueResponse(InternalResponse* aResponse);
+
+    RefPtr<HttpServer> mServer;
+    nsCOMPtr<nsISocketTransport> mTransport;
+    nsCOMPtr<nsIAsyncInputStream> mInput;
+    nsCOMPtr<nsIAsyncOutputStream> mOutput;
+
+    enum { eRequestLine, eHeaders, eBody, ePause } mState;
+    RefPtr<InternalRequest> mPendingReq;
+    uint32_t mPendingReqVersion;
+    nsCString mInputBuffer;
+    nsCString mPendingHeaderName;
+    nsCString mPendingHeaderValue;
+    uint32_t mRemainingBodySize;
+    nsCOMPtr<nsIAsyncOutputStream> mCurrentRequestBody;
+    bool mCloseAfterRequest;
+
+    typedef Pair<RefPtr<InternalRequest>,
+                 RefPtr<InternalResponse>> PendingRequest;
+    nsTArray<PendingRequest> mPendingRequests;
+    RefPtr<MozPromise<nsresult, bool, false>> mOutputCopy;
+
+    RefPtr<InternalRequest> mPendingWebSocketRequest;
+    RefPtr<TransportProvider> mWebSocketTransportProvider;
+
+    struct OutputBuffer {
+      nsCString mString;
+      nsCOMPtr<nsIInputStream> mStream;
+      bool mChunked;
+    };
+
+    nsTArray<OutputBuffer> mOutputBuffers;
+  };
+
+  friend class Connection;
+
+  RefPtr<HttpServerListener> mListener;
+  nsCOMPtr<nsIServerSocket> mServerSocket;
+  nsCOMPtr<nsIX509Cert> mCert;
+
+  nsTArray<RefPtr<Connection>> mConnections;
+
+  int32_t mPort;
+  bool mHttps;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_HttpServer_h
new file mode 100644
--- /dev/null
+++ b/dom/flyweb/moz.build
@@ -0,0 +1,36 @@
+# -*- 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/.
+
+EXPORTS.mozilla.dom += [
+    'FlyWebDiscoveryManager.h',
+    'FlyWebPublishedServer.h',
+    'FlyWebServerEvents.h',
+    'FlyWebService.h',
+    'HttpServer.h',
+]
+
+UNIFIED_SOURCES += [
+    'FlyWebDiscoveryManager.cpp',
+    'FlyWebPublishedServer.cpp',
+    'FlyWebServerEvents.cpp',
+    'FlyWebService.cpp',
+    'HttpServer.cpp'
+]
+
+#include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+    '/dom/base',
+    '/netwerk/base',
+    '/netwerk/dns',
+    '/netwerk/protocol/websocket',
+    '/xpcom/io'
+]
+
+if CONFIG['GNU_CXX']:
+    CXXFLAGS += ['-Wshadow']
--- a/dom/moz.build
+++ b/dom/moz.build
@@ -55,16 +55,17 @@ DIRS += [
     'phonenumberutils',
     'alarm',
     'devicestorage',
     'encoding',
     'events',
     'fetch',
     'filehandle',
     'filesystem',
+    'flyweb',
     'fmradio',
     'geolocation',
     'html',
     'icc',
     'inputport',
     'json',
     'jsurl',
     'asmjscache',
new file mode 100644
--- /dev/null
+++ b/dom/webidl/FlyWebDiscoveryManager.webidl
@@ -0,0 +1,39 @@
+/* -*- 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/.
+ */
+
+dictionary FlyWebDiscoveredService {
+  DOMString serviceId = "";
+  DOMString displayName = "";
+  DOMString transport = "";
+  DOMString serviceType = "";
+  DOMString cert = "";
+  DOMString path = "";
+};
+
+dictionary FlyWebPairedService {
+  FlyWebDiscoveredService discoveredService;
+  DOMString hostname = "";
+  DOMString uiUrl = "";
+};
+
+callback interface FlyWebPairingCallback {
+  void pairingSucceeded(optional FlyWebPairedService service);
+  void pairingFailed(DOMString error);
+};
+
+callback interface FlyWebDiscoveryCallback {
+  void onDiscoveredServicesChanged(sequence<FlyWebDiscoveredService> serviceList);
+};
+
+[ChromeOnly, ChromeConstructor, Exposed=(Window,System)]
+interface FlyWebDiscoveryManager {
+    sequence<FlyWebDiscoveredService> listServices();
+
+    unsigned long startDiscovery(FlyWebDiscoveryCallback aCallback);
+    void stopDiscovery(unsigned long aId);
+
+    void pairWithService(DOMString serviceId, FlyWebPairingCallback callback);
+};
new file mode 100644
--- /dev/null
+++ b/dom/webidl/FlyWebFetchEvent.webidl
@@ -0,0 +1,13 @@
+/* -*- 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/.
+ */
+
+[Pref="dom.flyweb.enabled"]
+interface FlyWebFetchEvent : Event {
+  [SameObject] readonly attribute Request request;
+
+  [Throws]
+  void respondWith(Promise<Response> r);
+};
new file mode 100644
--- /dev/null
+++ b/dom/webidl/FlyWebPublish.webidl
@@ -0,0 +1,29 @@
+/* -*- 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/.
+ */
+
+[Pref="dom.flyweb.enabled"]
+interface FlyWebPublishedServer : EventTarget {
+  readonly attribute DOMString name;
+  readonly attribute DOMString? category;
+  readonly attribute boolean http;
+  readonly attribute boolean message;
+  readonly attribute DOMString? uiUrl;
+
+  void close();
+
+  attribute EventHandler onclose;
+  attribute EventHandler onfetch;
+  attribute EventHandler onwebsocket;
+};
+
+dictionary FlyWebPublishOptions {
+  DOMString category = "";
+  boolean http = false;
+  boolean message = false;
+  DOMString? uiUrl = null; // URL to user interface. Can be different server. Makes
+                           // endpoint show up in browser's "local services" UI.
+                           // If relative, resolves against the root of the server.
+};
new file mode 100644
--- /dev/null
+++ b/dom/webidl/FlyWebWebSocketEvent.webidl
@@ -0,0 +1,16 @@
+/* -*- 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/.
+ */
+
+[Pref="dom.flyweb.enabled"]
+interface FlyWebWebSocketEvent : Event {
+  [SameObject] readonly attribute Request request;
+
+  [Throws]
+  WebSocket accept(optional DOMString protocol);
+
+  [Throws]
+  void respondWith(Promise<Response> r);
+};
--- a/dom/webidl/Navigator.webidl
+++ b/dom/webidl/Navigator.webidl
@@ -134,16 +134,22 @@ partial interface Navigator {
   Promise<BatteryManager> getBattery();
   // Deprecated. Use getBattery() instead.
   // XXXbz Per spec this should be non-nullable, but we return null in
   // torn-down windows.  See bug 884925.
   [Throws, Pref="dom.battery.enabled", BinaryName="deprecatedBattery"]
   readonly attribute BatteryManager? battery;
 };
 
+partial interface Navigator {
+  [NewObject, Pref="dom.flyweb.enabled"]
+  Promise<FlyWebPublishedServer> publishServer(DOMString name,
+                                               optional FlyWebPublishOptions options);
+};
+
 // http://www.w3.org/TR/vibration/#vibration-interface
 partial interface Navigator {
     // We don't support sequences in unions yet
     //boolean vibrate ((unsigned long or sequence<unsigned long>) pattern);
     boolean vibrate(unsigned long duration);
     boolean vibrate(sequence<unsigned long> pattern);
 };
 
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -155,16 +155,20 @@ WEBIDL_FILES = [
     'FakePluginTagInit.webidl',
     'Fetch.webidl',
     'FetchEvent.webidl',
     'File.webidl',
     'FileList.webidl',
     'FileMode.webidl',
     'FileReader.webidl',
     'FileReaderSync.webidl',
+    'FlyWebDiscoveryManager.webidl',
+    'FlyWebFetchEvent.webidl',
+    'FlyWebPublish.webidl',
+    'FlyWebWebSocketEvent.webidl',
     'FocusEvent.webidl',
     'FontFace.webidl',
     'FontFaceSet.webidl',
     'FontFaceSource.webidl',
     'FormData.webidl',
     'Function.webidl',
     'GainNode.webidl',
     'Geolocation.webidl',
--- a/layout/build/nsLayoutModule.cpp
+++ b/layout/build/nsLayoutModule.cpp
@@ -230,16 +230,18 @@ static void Shutdown();
 #include "nsIPowerManagerService.h"
 #include "nsIAlarmHalService.h"
 #include "nsIMediaManager.h"
 #include "mozilla/dom/nsMixedContentBlocker.h"
 
 #include "AudioChannelService.h"
 #include "mozilla/net/WebSocketEventService.h"
 
+#include "mozilla/dom/FlyWebService.h"
+
 #include "mozilla/dom/power/PowerManagerService.h"
 #include "mozilla/dom/alarm/AlarmHalService.h"
 #include "mozilla/dom/time/TimeService.h"
 #include "StreamingProtocolService.h"
 
 #include "nsIPresentationService.h"
 #include "nsITelephonyService.h"
 #include "nsIVoicemailService.h"
@@ -630,22 +632,27 @@ NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(Geol
   { 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_WEBSOCKETEVENT_SERVICE_CID \
   { 0x31689828, 0xda66, 0x49a6, { 0x87, 0x0c, 0xdf, 0x62, 0xb8, 0x3f, 0xe7, 0x89 }}
 
+#define NS_FLYWEB_SERVICE_CID \
+  { 0x5de19ef0, 0x895e, 0x4c0c, { 0xa6, 0xe0, 0xea, 0xe0, 0x23, 0x2b, 0x84, 0x5a } }
+
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsGeolocationService, nsGeolocationService::GetGeolocationService)
 
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(AudioChannelService, AudioChannelService::GetOrCreate)
 
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(WebSocketEventService, WebSocketEventService::GetOrCreate)
 
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(FlyWebService, FlyWebService::GetOrCreateAddRefed)
+
 #ifdef MOZ_WEBSPEECH_TEST_BACKEND
 NS_GENERIC_FACTORY_CONSTRUCTOR(FakeSpeechRecognitionService)
 #endif
 #ifdef MOZ_WEBSPEECH_POCKETSPHINX
 NS_GENERIC_FACTORY_CONSTRUCTOR(PocketSphinxSpeechRecognitionService)
 #endif
 
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsContentSecurityManager)
@@ -789,16 +796,17 @@ NS_DEFINE_NAMED_CID(NS_EDITORCONTROLLER_
 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_WEBSOCKETEVENT_SERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_FLYWEB_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);
 NS_DEFINE_NAMED_CID(NS_PARENTPROCESSMESSAGEMANAGER_CID);
@@ -1096,16 +1104,17 @@ static const mozilla::Module::CIDEntry k
   { &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_WEBSOCKETEVENT_SERVICE_CID, false, nullptr, WebSocketEventServiceConstructor },
+  { &kNS_FLYWEB_SERVICE_CID, false, nullptr, FlyWebServiceConstructor },
   { &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
 #ifdef MOZ_WEBSPEECH
@@ -1263,16 +1272,18 @@ static const mozilla::Module::ContractID
   { "@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/websocketevent/service;1", &kNS_WEBSOCKETEVENT_SERVICE_CID },
+  { "@mozilla.org/flyweb-service;1", &kNS_FLYWEB_SERVICE_CID },
+  { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "flyweb", &kNS_FLYWEB_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
 #ifdef MOZ_WEBSPEECH
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4956,16 +4956,18 @@ pref("captivedetect.maxRetryCount", 5);
 pref("dom.forms.inputmode", false);
 #else
 pref("dom.forms.inputmode", true);
 #endif
 
 // InputMethods for soft keyboards in B2G
 pref("dom.mozInputMethod.enabled", false);
 
+pref("dom.flyweb.enabled", false);
+
 // Telephony API
 #ifdef MOZ_B2G_RIL
 pref("dom.telephony.enabled", true);
 #else
 pref("dom.telephony.enabled", false);
 #endif
 // Numeric default service id for WebTelephony API calls with |serviceId|
 // parameter omitted.
--- a/netwerk/base/nsSocketTransport2.cpp
+++ b/netwerk/base/nsSocketTransport2.cpp
@@ -740,16 +740,17 @@ nsSocketTransport::nsSocketTransport()
     , mConnectionFlags(0)
     , mState(STATE_CLOSED)
     , mAttached(false)
     , mInputClosed(true)
     , mOutputClosed(true)
     , mResolving(false)
     , mNetAddrIsSet(false)
     , mSelfAddrIsSet(false)
+    , mNetAddrPreResolved(false)
     , mLock("nsSocketTransport.mLock")
     , mFD(this)
     , mFDref(0)
     , mFDconnected(false)
     , mSocketTransportService(gSocketTransportService)
     , mInput(this)
     , mOutput(this)
     , mQoSBits(0x00)
@@ -880,16 +881,33 @@ nsSocketTransport::Init(const char **typ
             }
         }
     }
 
     return NS_OK;
 }
 
 nsresult
+nsSocketTransport::InitPreResolved(const char **socketTypes, uint32_t typeCount,
+                                   const nsACString &host, uint16_t port,
+                                   const nsACString &hostRoute, uint16_t portRoute,
+                                   nsIProxyInfo *proxyInfo,
+                                   const mozilla::net::NetAddr* addr)
+{
+  nsresult rv = Init(socketTypes, typeCount, host, port, hostRoute, portRoute, proxyInfo);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  mNetAddr = *addr;
+  mNetAddrPreResolved = true;
+  return NS_OK;
+}
+
+nsresult
 nsSocketTransport::InitWithFilename(const char *filename)
 {
 #if defined(XP_UNIX)
     size_t filenameLength = strlen(filename);
 
     if (filenameLength > sizeof(mNetAddr.local.path) - 1)
         return NS_ERROR_FILE_NAME_TOO_LONG;
 
@@ -1010,16 +1028,21 @@ nsSocketTransport::ResolveHost()
 {
     SOCKET_LOG(("nsSocketTransport::ResolveHost [this=%p %s:%d%s]\n",
                 this, SocketHost().get(), SocketPort(),
                 mConnectionFlags & nsSocketTransport::BYPASS_CACHE ?
                 " bypass cache" : ""));
 
     nsresult rv;
 
+    if (mNetAddrPreResolved) {
+        mState = STATE_RESOLVING;
+        return PostEvent(MSG_DNS_LOOKUP_COMPLETE, NS_OK, nullptr);
+    }
+
     if (!mProxyHost.IsEmpty()) {
         if (!mProxyTransparent || mProxyTransparentResolvesHost) {
 #if defined(XP_UNIX)
             MOZ_ASSERT(!mNetAddrIsSet || mNetAddr.raw.family != AF_LOCAL,
                        "Unix domain sockets can't be used with proxies");
 #endif
             // When not resolving mHost locally, we still want to ensure that
             // it only contains valid characters.  See bug 304904 for details.
@@ -1872,18 +1895,19 @@ nsSocketTransport::OnSocketEvent(uint32_
             // transport resolves the real host here, so there's no fixup 
             // (see bug 226943).
             if ((status == NS_ERROR_UNKNOWN_HOST) && !mProxyTransparent &&
                 !mProxyHost.IsEmpty())
                 mCondition = NS_ERROR_UNKNOWN_PROXY_HOST;
             else
                 mCondition = status;
         }
-        else if (mState == STATE_RESOLVING)
+        else if (mState == STATE_RESOLVING) {
             mCondition = InitiateSocket();
+        }
         break;
 
     case MSG_RETRY_INIT_SOCKET:
         mCondition = InitiateSocket();
         break;
 
     case MSG_INPUT_CLOSED:
         SOCKET_LOG(("  MSG_INPUT_CLOSED\n"));
--- a/netwerk/base/nsSocketTransport2.h
+++ b/netwerk/base/nsSocketTransport2.h
@@ -128,16 +128,25 @@ public:
 
     // this method instructs the socket transport to open a socket of the
     // given type(s) to the given host or proxy.
     nsresult Init(const char **socketTypes, uint32_t typeCount,
                   const nsACString &host, uint16_t port,
                   const nsACString &hostRoute, uint16_t portRoute,
                   nsIProxyInfo *proxyInfo);
 
+    // Alternative Init method for when the IP-address of the host
+    // has been pre-resolved using a alternative means (e.g. FlyWeb service
+    // info).
+    nsresult InitPreResolved(const char **socketTypes, uint32_t typeCount,
+                             const nsACString &host, uint16_t port,
+                             const nsACString &hostRoute, uint16_t portRoute,
+                             nsIProxyInfo *proxyInfo,
+                             const mozilla::net::NetAddr* addr);
+
     // this method instructs the socket transport to use an already connected
     // socket with the given address.
     nsresult InitWithConnectedSocket(PRFileDesc *socketFD,
                                      const NetAddr *addr);
 
     // this method instructs the socket transport to use an already connected
     // socket with the given address, and additionally supplies security info.
     nsresult InitWithConnectedSocket(PRFileDesc* aSocketFD,
@@ -321,16 +330,17 @@ private:
 
     // mNetAddr/mSelfAddr is valid from GetPeerAddr()/GetSelfAddr() once we have
     // reached STATE_TRANSFERRING. It must not change after that.
     void                    SetSocketName(PRFileDesc *fd);
     NetAddr                 mNetAddr;
     NetAddr                 mSelfAddr; // getsockname()
     Atomic<bool, Relaxed>   mNetAddrIsSet;
     Atomic<bool, Relaxed>   mSelfAddrIsSet;
+    Atomic<bool, Relaxed>   mNetAddrPreResolved;
 
     nsAutoPtr<NetAddr>      mBindAddr;
 
     // socket methods (these can only be called on the socket thread):
 
     void     SendStatus(nsresult status);
     nsresult ResolveHost();
     nsresult BuildSocket(PRFileDesc *&, bool &, bool &); 
--- a/netwerk/base/nsSocketTransportService2.cpp
+++ b/netwerk/base/nsSocketTransportService2.cpp
@@ -21,16 +21,17 @@
 #include "mozilla/Likely.h"
 #include "mozilla/PublicSSL.h"
 #include "mozilla/ChaosMode.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/Telemetry.h"
 #include "nsThreadUtils.h"
 #include "nsIFile.h"
 #include "nsIWidget.h"
+#include "mozilla/dom/FlyWebService.h"
 
 #if defined(XP_WIN)
 #include "mozilla/WindowsVersion.h"
 #endif
 
 namespace mozilla {
 namespace net {
 
@@ -720,16 +721,30 @@ nsSocketTransportService::CreateRoutedTr
                                                 uint32_t typeCount,
                                                 const nsACString &host,
                                                 int32_t port,
                                                 const nsACString &hostRoute,
                                                 int32_t portRoute,
                                                 nsIProxyInfo *proxyInfo,
                                                 nsISocketTransport **result)
 {
+    // Check FlyWeb table for host mappings.  If one exists, then use that.
+    RefPtr<mozilla::dom::FlyWebService> fws =
+        mozilla::dom::FlyWebService::GetExisting();
+    if (fws) {
+        nsresult rv = fws->CreateTransportForHost(types, typeCount, host, port,
+                                                  hostRoute, portRoute,
+                                                  proxyInfo, result);
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        if (*result) {
+            return NS_OK;
+        }
+    }
+
     NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED);
     NS_ENSURE_TRUE(port >= 0 && port <= 0xFFFF, NS_ERROR_ILLEGAL_VALUE);
 
     RefPtr<nsSocketTransport> trans = new nsSocketTransport();
     nsresult rv = trans->Init(types, typeCount, host, port, hostRoute, portRoute, proxyInfo);
     if (NS_FAILED(rv)) {
         return rv;
     }