/* -*- 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/. */

// Needs to be first.
#include "base/basictypes.h"

#include "Navigator.h"
#include "nsIXULAppInfo.h"
#include "nsPluginArray.h"
#include "nsMimeTypeArray.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/dom/DesktopNotification.h"
#include "mozilla/dom/File.h"
#include "nsGeolocation.h"
#include "nsIClassOfService.h"
#include "nsIHttpProtocolHandler.h"
#include "nsIContentPolicy.h"
#include "nsIContentSecurityPolicy.h"
#include "nsContentPolicyUtils.h"
#include "nsCORSListenerProxy.h"
#include "nsISupportsPriority.h"
#include "nsICachingChannel.h"
#include "nsIWebContentHandlerRegistrar.h"
#include "nsICookiePermission.h"
#include "nsIScriptSecurityManager.h"
#include "nsCharSeparatedTokenizer.h"
#include "nsContentUtils.h"
#include "nsUnicharUtils.h"
#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/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"
#include "mozilla/dom/Voicemail.h"
#include "mozilla/dom/TVManager.h"
#include "mozilla/dom/VRDevice.h"
#include "mozilla/Hal.h"
#include "nsISiteSpecificUserAgent.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/StaticPtr.h"
#include "Connection.h"
#include "mozilla/dom/Event.h" // for nsIDOMEvent::InternalDOMEvent()
#include "nsGlobalWindow.h"
#ifdef MOZ_B2G
#include "nsIMobileIdentityService.h"
#endif
#ifdef MOZ_B2G_RIL
#include "mozilla/dom/MobileConnectionArray.h"
#endif
#include "nsIIdleObserver.h"
#include "nsIPermissionManager.h"
#include "nsMimeTypes.h"
#include "nsNetUtil.h"
#include "nsStringStream.h"
#include "nsComponentManagerUtils.h"
#include "nsIStringStream.h"
#include "nsIHttpChannel.h"
#include "nsIHttpChannelInternal.h"
#include "TimeManager.h"
#include "DeviceStorage.h"
#include "nsIDOMNavigatorSystemMessages.h"
#include "nsStreamUtils.h"
#include "nsIAppsService.h"
#include "mozIApplication.h"
#include "WidgetUtils.h"
#include "nsIPresentationService.h"

#ifdef MOZ_MEDIA_NAVIGATOR
#include "mozilla/dom/MediaDevices.h"
#include "MediaManager.h"
#endif
#ifdef MOZ_B2G_BT
#include "BluetoothManager.h"
#endif
#include "DOMCameraManager.h"

#ifdef MOZ_AUDIO_CHANNEL_MANAGER
#include "AudioChannelManager.h"
#endif

#ifdef MOZ_B2G_FM
#include "mozilla/dom/FMRadio.h"
#endif

#include "nsIDOMGlobalPropertyInitializer.h"
#include "mozilla/dom/DataStoreService.h"
#include "nsJSUtils.h"

#include "nsScriptNameSpaceManager.h"

#include "mozilla/dom/NavigatorBinding.h"
#include "mozilla/dom/Promise.h"

#include "nsIUploadChannel2.h"
#include "nsFormData.h"
#include "nsIDocShell.h"

#include "WorkerPrivate.h"
#include "WorkerRunnable.h"

#if defined(XP_LINUX)
#include "mozilla/Hal.h"
#endif
#include "mozilla/dom/ContentChild.h"

#include "mozilla/dom/FeatureList.h"

#ifdef MOZ_EME
#include "mozilla/EMEUtils.h"
#include "mozilla/DetailedPromise.h"
#endif

#ifdef MOZ_WIDGET_GONK
#include <cutils/properties.h>
#endif

namespace mozilla {
namespace dom {

static bool sDoNotTrackEnabled = false;
static bool sVibratorEnabled   = false;
static uint32_t sMaxVibrateMS  = 0;
static uint32_t sMaxVibrateListLen = 0;

/* static */
void
Navigator::Init()
{
  Preferences::AddBoolVarCache(&sDoNotTrackEnabled,
                               "privacy.donottrackheader.enabled",
                               false);
  Preferences::AddBoolVarCache(&sVibratorEnabled,
                               "dom.vibrator.enabled", true);
  Preferences::AddUintVarCache(&sMaxVibrateMS,
                               "dom.vibrator.max_vibrate_ms", 10000);
  Preferences::AddUintVarCache(&sMaxVibrateListLen,
                               "dom.vibrator.max_vibrate_list_len", 128);
}

Navigator::Navigator(nsPIDOMWindow* aWindow)
  : mWindow(aWindow)
{
  MOZ_ASSERT(aWindow->IsInnerWindow(), "Navigator must get an inner window!");
}

Navigator::~Navigator()
{
  Invalidate();
}

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Navigator)
  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMNavigator)
  NS_INTERFACE_MAP_ENTRY(nsIDOMNavigator)
  NS_INTERFACE_MAP_ENTRY(nsIMozNavigatorNetwork)
NS_INTERFACE_MAP_END

NS_IMPL_CYCLE_COLLECTING_ADDREF(Navigator)
NS_IMPL_CYCLE_COLLECTING_RELEASE(Navigator)

NS_IMPL_CYCLE_COLLECTION_CLASS(Navigator)

NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Navigator)
  tmp->Invalidate();
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mCachedResolveResults)
  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_UNLINK_END

NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Navigator)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMimeTypes)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPlugins)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPermissions)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGeolocation)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNotification)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBatteryManager)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBatteryPromise)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPowerManager)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCellBroadcast)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIccManager)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMobileMessageManager)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTelephony)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVoicemail)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTVManager)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInputPortManager)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConnection)
#ifdef MOZ_B2G_RIL
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMobileConnections)
#endif
#ifdef MOZ_B2G_BT
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBluetooth)
#endif
#ifdef MOZ_AUDIO_CHANNEL_MANAGER
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioChannelManager)
#endif
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCameraManager)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaDevices)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMessagesManager)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTimeManager)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mServiceWorkerContainer)

  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCachedResolveResults)
#ifdef MOZ_EME
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaKeySystemAccessManager)
#endif
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDeviceStorageAreaListener)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPresentation)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(Navigator)

void
Navigator::Invalidate()
{
  // Don't clear mWindow here so we know we've got a non-null mWindow
  // until we're unlinked.

  mMimeTypes = nullptr;

  if (mPlugins) {
    mPlugins->Invalidate();
    mPlugins = nullptr;
  }

  mPermissions = nullptr;

  // If there is a page transition, make sure delete the geolocation object.
  if (mGeolocation) {
    mGeolocation->Shutdown();
    mGeolocation = nullptr;
  }

  if (mNotification) {
    mNotification->Shutdown();
    mNotification = nullptr;
  }

  if (mBatteryManager) {
    mBatteryManager->Shutdown();
    mBatteryManager = nullptr;
  }

  mBatteryPromise = nullptr;

#ifdef MOZ_B2G_FM
  if (mFMRadio) {
    mFMRadio->Shutdown();
    mFMRadio = nullptr;
  }
#endif

  if (mPowerManager) {
    mPowerManager->Shutdown();
    mPowerManager = nullptr;
  }

  if (mCellBroadcast) {
    mCellBroadcast = nullptr;
  }

  if (mIccManager) {
    mIccManager->Shutdown();
    mIccManager = nullptr;
  }

  if (mMobileMessageManager) {
    mMobileMessageManager->Shutdown();
    mMobileMessageManager = nullptr;
  }

  if (mTelephony) {
    mTelephony = nullptr;
  }

  if (mVoicemail) {
    mVoicemail->Shutdown();
    mVoicemail = nullptr;
  }

  if (mTVManager) {
    mTVManager = nullptr;
  }

  if (mInputPortManager) {
    mInputPortManager = nullptr;
  }

  if (mConnection) {
    mConnection->Shutdown();
    mConnection = nullptr;
  }

#ifdef MOZ_B2G_RIL
  if (mMobileConnections) {
    mMobileConnections = nullptr;
  }
#endif

#ifdef MOZ_B2G_BT
  if (mBluetooth) {
    mBluetooth = nullptr;
  }
#endif

  mCameraManager = nullptr;
  mMediaDevices = nullptr;

  if (mMessagesManager) {
    mMessagesManager = nullptr;
  }

#ifdef MOZ_AUDIO_CHANNEL_MANAGER
  if (mAudioChannelManager) {
    mAudioChannelManager = nullptr;
  }
#endif

  uint32_t len = mDeviceStorageStores.Length();
  for (uint32_t i = 0; i < len; ++i) {
    RefPtr<nsDOMDeviceStorage> ds = do_QueryReferent(mDeviceStorageStores[i]);
    if (ds) {
      ds->Shutdown();
    }
  }
  mDeviceStorageStores.Clear();

  if (mTimeManager) {
    mTimeManager = nullptr;
  }

  if (mPresentation) {
    mPresentation = nullptr;
  }

  mServiceWorkerContainer = nullptr;

#ifdef MOZ_EME
  if (mMediaKeySystemAccessManager) {
    mMediaKeySystemAccessManager->Shutdown();
    mMediaKeySystemAccessManager = nullptr;
  }
#endif

  if (mDeviceStorageAreaListener) {
    mDeviceStorageAreaListener = nullptr;
  }
}

//*****************************************************************************
//    Navigator::nsIDOMNavigator
//*****************************************************************************

NS_IMETHODIMP
Navigator::GetUserAgent(nsAString& aUserAgent)
{
  nsCOMPtr<nsIURI> codebaseURI;
  nsCOMPtr<nsPIDOMWindow> window;

  if (mWindow && mWindow->GetDocShell()) {
    window = mWindow;
    nsIDocument* doc = mWindow->GetExtantDoc();
    if (doc) {
      doc->NodePrincipal()->GetURI(getter_AddRefs(codebaseURI));
    }
  }

  return GetUserAgent(window, codebaseURI, nsContentUtils::IsCallerChrome(),
                      aUserAgent);
}

NS_IMETHODIMP
Navigator::GetAppCodeName(nsAString& aAppCodeName)
{
  nsresult rv;

  nsCOMPtr<nsIHttpProtocolHandler>
    service(do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http", &rv));
  NS_ENSURE_SUCCESS(rv, rv);

  nsAutoCString appName;
  rv = service->GetAppName(appName);
  CopyASCIItoUTF16(appName, aAppCodeName);

  return rv;
}

NS_IMETHODIMP
Navigator::GetAppVersion(nsAString& aAppVersion)
{
  return GetAppVersion(aAppVersion, /* aUsePrefOverriddenValue */ true);
}

NS_IMETHODIMP
Navigator::GetAppName(nsAString& aAppName)
{
  AppName(aAppName, /* aUsePrefOverriddenValue */ true);
  return NS_OK;
}

/**
 * Returns the value of Accept-Languages (HTTP header) as a nsTArray of
 * languages. The value is set in the preference by the user ("Content
 * Languages").
 *
 * "en", "en-US" and "i-cherokee" and "" are valid languages tokens.
 *
 * An empty array will be returned if there is no valid languages.
 */
/* static */ void
Navigator::GetAcceptLanguages(nsTArray<nsString>& aLanguages)
{
  MOZ_ASSERT(NS_IsMainThread());

  aLanguages.Clear();

  // E.g. "de-de, en-us,en".
  const nsAdoptingString& acceptLang =
    Preferences::GetLocalizedString("intl.accept_languages");

  // Split values on commas.
  nsCharSeparatedTokenizer langTokenizer(acceptLang, ',');
  while (langTokenizer.hasMoreTokens()) {
    nsDependentSubstring lang = langTokenizer.nextToken();

    // Replace "_" with "-" to avoid POSIX/Windows "en_US" notation.
    // NOTE: we should probably rely on the pref being set correctly.
    if (lang.Length() > 2 && lang[2] == char16_t('_')) {
      lang.Replace(2, 1, char16_t('-'));
    }

    // Use uppercase for country part, e.g. "en-US", not "en-us", see BCP47
    // only uppercase 2-letter country codes, not "zh-Hant", "de-DE-x-goethe".
    // NOTE: we should probably rely on the pref being set correctly.
    if (lang.Length() > 2) {
      nsCharSeparatedTokenizer localeTokenizer(lang, '-');
      int32_t pos = 0;
      bool first = true;
      while (localeTokenizer.hasMoreTokens()) {
        const nsSubstring& code = localeTokenizer.nextToken();

        if (code.Length() == 2 && !first) {
          nsAutoString upper(code);
          ToUpperCase(upper);
          lang.Replace(pos, code.Length(), upper);
        }

        pos += code.Length() + 1; // 1 is the separator
        first = false;
      }
    }

    aLanguages.AppendElement(lang);
  }
}

/**
 * Do not use UI language (chosen app locale) here but the first value set in
 * the Accept Languages header, see ::GetAcceptLanguages().
 *
 * See RFC 2616, Section 15.1.4 "Privacy Issues Connected to Accept Headers" for
 * the reasons why.
 */
NS_IMETHODIMP
Navigator::GetLanguage(nsAString& aLanguage)
{
  nsTArray<nsString> languages;
  GetLanguages(languages);
  if (languages.Length() >= 1) {
    aLanguage.Assign(languages[0]);
  } else {
    aLanguage.Truncate();
  }

  return NS_OK;
}

void
Navigator::GetLanguages(nsTArray<nsString>& aLanguages)
{
  GetAcceptLanguages(aLanguages);

  // The returned value is cached by the binding code. The window listen to the
  // accept languages change and will clear the cache when needed. It has to
  // take care of dispatching the DOM event already and the invalidation and the
  // event has to be timed correctly.
}

NS_IMETHODIMP
Navigator::GetPlatform(nsAString& aPlatform)
{
  return GetPlatform(aPlatform, /* aUsePrefOverriddenValue */ true);
}

NS_IMETHODIMP
Navigator::GetOscpu(nsAString& aOSCPU)
{
  if (!nsContentUtils::IsCallerChrome()) {
    const nsAdoptingString& override =
      Preferences::GetString("general.oscpu.override");

    if (override) {
      aOSCPU = override;
      return NS_OK;
    }
  }

  nsresult rv;

  nsCOMPtr<nsIHttpProtocolHandler>
    service(do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http", &rv));
  NS_ENSURE_SUCCESS(rv, rv);

  nsAutoCString oscpu;
  rv = service->GetOscpu(oscpu);
  CopyASCIItoUTF16(oscpu, aOSCPU);

  return rv;
}

NS_IMETHODIMP
Navigator::GetVendor(nsAString& aVendor)
{
  aVendor.Truncate();
  return NS_OK;
}

NS_IMETHODIMP
Navigator::GetVendorSub(nsAString& aVendorSub)
{
  aVendorSub.Truncate();
  return NS_OK;
}

NS_IMETHODIMP
Navigator::GetProduct(nsAString& aProduct)
{
  aProduct.AssignLiteral("Gecko");
  return NS_OK;
}

NS_IMETHODIMP
Navigator::GetProductSub(nsAString& aProductSub)
{
  // Legacy build ID hardcoded for backward compatibility (bug 776376)
  aProductSub.AssignLiteral("20100101");
  return NS_OK;
}

nsMimeTypeArray*
Navigator::GetMimeTypes(ErrorResult& aRv)
{
  if (!mMimeTypes) {
    if (!mWindow) {
      aRv.Throw(NS_ERROR_UNEXPECTED);
      return nullptr;
    }
    mMimeTypes = new nsMimeTypeArray(mWindow);
  }

  return mMimeTypes;
}

nsPluginArray*
Navigator::GetPlugins(ErrorResult& aRv)
{
  if (!mPlugins) {
    if (!mWindow) {
      aRv.Throw(NS_ERROR_UNEXPECTED);
      return nullptr;
    }
    mPlugins = new nsPluginArray(mWindow);
    mPlugins->Init();
  }

  return mPlugins;
}

Permissions*
Navigator::GetPermissions(ErrorResult& aRv)
{
  if (!mWindow) {
    aRv.Throw(NS_ERROR_UNEXPECTED);
    return nullptr;
  }

  if (!mPermissions) {
    mPermissions = new Permissions(mWindow);
  }

  return mPermissions;
}

// Values for the network.cookie.cookieBehavior pref are documented in
// nsCookieService.cpp.
#define COOKIE_BEHAVIOR_REJECT 2

bool
Navigator::CookieEnabled()
{
  bool cookieEnabled =
    (Preferences::GetInt("network.cookie.cookieBehavior",
                         COOKIE_BEHAVIOR_REJECT) != COOKIE_BEHAVIOR_REJECT);

  // Check whether an exception overrides the global cookie behavior
  // Note that the code for getting the URI here matches that in
  // nsHTMLDocument::SetCookie.
  if (!mWindow || !mWindow->GetDocShell()) {
    return cookieEnabled;
  }

  nsCOMPtr<nsIDocument> doc = mWindow->GetExtantDoc();
  if (!doc) {
    return cookieEnabled;
  }

  nsCOMPtr<nsIURI> codebaseURI;
  doc->NodePrincipal()->GetURI(getter_AddRefs(codebaseURI));

  if (!codebaseURI) {
    // Not a codebase, so technically can't set cookies, but let's
    // just return the default value.
    return cookieEnabled;
  }

  nsCOMPtr<nsICookiePermission> permMgr =
    do_GetService(NS_COOKIEPERMISSION_CONTRACTID);
  NS_ENSURE_TRUE(permMgr, cookieEnabled);

  // Pass null for the channel, just like the cookie service does.
  nsCookieAccess access;
  nsresult rv = permMgr->CanAccess(codebaseURI, nullptr, &access);
  NS_ENSURE_SUCCESS(rv, cookieEnabled);

  if (access != nsICookiePermission::ACCESS_DEFAULT) {
    cookieEnabled = access != nsICookiePermission::ACCESS_DENY;
  }

  return cookieEnabled;
}

bool
Navigator::OnLine()
{
  if (mWindow && mWindow->GetDoc()) {
    return !NS_IsOffline() &&
      !NS_IsAppOffline(mWindow->GetDoc()->NodePrincipal());
  }

  return !NS_IsOffline();
}

NS_IMETHODIMP
Navigator::GetBuildID(nsAString& aBuildID)
{
  if (!nsContentUtils::IsCallerChrome()) {
    const nsAdoptingString& override =
      Preferences::GetString("general.buildID.override");

    if (override) {
      aBuildID = override;
      return NS_OK;
    }
  }

  nsCOMPtr<nsIXULAppInfo> appInfo =
    do_GetService("@mozilla.org/xre/app-info;1");
  if (!appInfo) {
    return NS_ERROR_NOT_IMPLEMENTED;
  }

  nsAutoCString buildID;
  nsresult rv = appInfo->GetAppBuildID(buildID);
  if (NS_FAILED(rv)) {
    return rv;
  }

  aBuildID.Truncate();
  AppendASCIItoUTF16(buildID, aBuildID);
  return NS_OK;
}

NS_IMETHODIMP
Navigator::GetDoNotTrack(nsAString &aResult)
{
  if (sDoNotTrackEnabled) {
    aResult.AssignLiteral("1");
  } else {
    aResult.AssignLiteral("unspecified");
  }

  return NS_OK;
}

bool
Navigator::JavaEnabled(ErrorResult& aRv)
{
  Telemetry::AutoTimer<Telemetry::CHECK_JAVA_ENABLED> telemetryTimer;

  // Return true if we have a handler for the java mime
  nsAdoptingString javaMIME = Preferences::GetString("plugin.java.mime");
  NS_ENSURE_TRUE(!javaMIME.IsEmpty(), false);

  if (!mMimeTypes) {
    if (!mWindow) {
      aRv.Throw(NS_ERROR_UNEXPECTED);
      return false;
    }
    mMimeTypes = new nsMimeTypeArray(mWindow);
  }

  RefreshMIMEArray();

  nsMimeType *mimeType = mMimeTypes->NamedItem(javaMIME);

  return mimeType && mimeType->GetEnabledPlugin();
}

void
Navigator::RefreshMIMEArray()
{
  if (mMimeTypes) {
    mMimeTypes->Refresh();
  }
}

namespace {

class VibrateWindowListener : public nsIDOMEventListener
{
public:
  VibrateWindowListener(nsIDOMWindow* aWindow, nsIDocument* aDocument)
  {
    mWindow = do_GetWeakReference(aWindow);
    mDocument = do_GetWeakReference(aDocument);

    NS_NAMED_LITERAL_STRING(visibilitychange, "visibilitychange");
    aDocument->AddSystemEventListener(visibilitychange,
                                      this, /* listener */
                                      true, /* use capture */
                                      false /* wants untrusted */);
  }

  void RemoveListener();

  NS_DECL_ISUPPORTS
  NS_DECL_NSIDOMEVENTLISTENER

private:
  virtual ~VibrateWindowListener()
  {
  }

  nsWeakPtr mWindow;
  nsWeakPtr mDocument;
};

NS_IMPL_ISUPPORTS(VibrateWindowListener, nsIDOMEventListener)

StaticRefPtr<VibrateWindowListener> gVibrateWindowListener;

NS_IMETHODIMP
VibrateWindowListener::HandleEvent(nsIDOMEvent* aEvent)
{
  nsCOMPtr<nsIDocument> doc =
    do_QueryInterface(aEvent->InternalDOMEvent()->GetTarget());

  if (!doc || doc->Hidden()) {
    // It's important that we call CancelVibrate(), not Vibrate() with an
    // empty list, because Vibrate() will fail if we're no longer focused, but
    // CancelVibrate() will succeed, so long as nobody else has started a new
    // vibration pattern.
    nsCOMPtr<nsIDOMWindow> window = do_QueryReferent(mWindow);
    hal::CancelVibrate(window);
    RemoveListener();
    gVibrateWindowListener = nullptr;
    // Careful: The line above might have deleted |this|!
  }

  return NS_OK;
}

void
VibrateWindowListener::RemoveListener()
{
  nsCOMPtr<EventTarget> target = do_QueryReferent(mDocument);
  if (!target) {
    return;
  }
  NS_NAMED_LITERAL_STRING(visibilitychange, "visibilitychange");
  target->RemoveSystemEventListener(visibilitychange, this,
                                    true /* use capture */);
}

} // namespace

void
Navigator::AddIdleObserver(MozIdleObserver& aIdleObserver, ErrorResult& aRv)
{
  if (!mWindow) {
    aRv.Throw(NS_ERROR_UNEXPECTED);
    return;
  }
  CallbackObjectHolder<MozIdleObserver, nsIIdleObserver> holder(&aIdleObserver);
  nsCOMPtr<nsIIdleObserver> obs = holder.ToXPCOMCallback();
  if (NS_FAILED(mWindow->RegisterIdleObserver(obs))) {
    NS_WARNING("Failed to add idle observer.");
  }
}

void
Navigator::RemoveIdleObserver(MozIdleObserver& aIdleObserver, ErrorResult& aRv)
{
  if (!mWindow) {
    aRv.Throw(NS_ERROR_UNEXPECTED);
    return;
  }
  CallbackObjectHolder<MozIdleObserver, nsIIdleObserver> holder(&aIdleObserver);
  nsCOMPtr<nsIIdleObserver> obs = holder.ToXPCOMCallback();
  if (NS_FAILED(mWindow->UnregisterIdleObserver(obs))) {
    NS_WARNING("Failed to remove idle observer.");
  }
}

bool
Navigator::Vibrate(uint32_t aDuration)
{
  nsAutoTArray<uint32_t, 1> pattern;
  pattern.AppendElement(aDuration);
  return Vibrate(pattern);
}

bool
Navigator::Vibrate(const nsTArray<uint32_t>& aPattern)
{
  if (!mWindow) {
    return false;
  }

  nsCOMPtr<nsIDocument> doc = mWindow->GetExtantDoc();
  if (!doc) {
    return false;
  }

  if (doc->Hidden()) {
    // Hidden documents cannot start or stop a vibration.
    return false;
  }

  nsTArray<uint32_t> pattern(aPattern);

  if (pattern.Length() > sMaxVibrateListLen) {
    pattern.SetLength(sMaxVibrateListLen);
  }

  for (size_t i = 0; i < pattern.Length(); ++i) {
    if (pattern[i] > sMaxVibrateMS) {
      pattern[i] = sMaxVibrateMS;
    }
  }

  // The spec says we check sVibratorEnabled after we've done the sanity
  // checking on the pattern.
  if (!sVibratorEnabled) {
    return true;
  }

  // Add a listener to cancel the vibration if the document becomes hidden,
  // and remove the old visibility listener, if there was one.

  if (!gVibrateWindowListener) {
    // If gVibrateWindowListener is null, this is the first time we've vibrated,
    // and we need to register a listener to clear gVibrateWindowListener on
    // shutdown.
    ClearOnShutdown(&gVibrateWindowListener);
  }
  else {
    gVibrateWindowListener->RemoveListener();
  }
  gVibrateWindowListener = new VibrateWindowListener(mWindow, doc);

  hal::Vibrate(pattern, mWindow);
  return true;
}

//*****************************************************************************
//  Pointer Events interface
//*****************************************************************************

uint32_t
Navigator::MaxTouchPoints()
{
  nsCOMPtr<nsIWidget> widget = widget::WidgetUtils::DOMWindowToWidget(mWindow);

  NS_ENSURE_TRUE(widget, 0);
  return widget->GetMaxTouchPoints();
}

//*****************************************************************************
//    Navigator::nsIDOMClientInformation
//*****************************************************************************

void
Navigator::RegisterContentHandler(const nsAString& aMIMEType,
                                  const nsAString& aURI,
                                  const nsAString& aTitle,
                                  ErrorResult& aRv)
{
  if (!mWindow || !mWindow->GetOuterWindow() || !mWindow->GetDocShell()) {
    return;
  }

  nsCOMPtr<nsIWebContentHandlerRegistrar> registrar =
    do_GetService(NS_WEBCONTENTHANDLERREGISTRAR_CONTRACTID);
  if (!registrar) {
    return;
  }

  aRv = registrar->RegisterContentHandler(aMIMEType, aURI, aTitle,
                                          mWindow->GetOuterWindow());
}

void
Navigator::RegisterProtocolHandler(const nsAString& aProtocol,
                                   const nsAString& aURI,
                                   const nsAString& aTitle,
                                   ErrorResult& aRv)
{
  if (!mWindow || !mWindow->GetOuterWindow() || !mWindow->GetDocShell()) {
    return;
  }

  nsCOMPtr<nsIWebContentHandlerRegistrar> registrar =
    do_GetService(NS_WEBCONTENTHANDLERREGISTRAR_CONTRACTID);
  if (!registrar) {
    return;
  }

  aRv = registrar->RegisterProtocolHandler(aProtocol, aURI, aTitle,
                                           mWindow->GetOuterWindow());
}

DeviceStorageAreaListener*
Navigator::GetDeviceStorageAreaListener(ErrorResult& aRv)
{
  if (!mDeviceStorageAreaListener) {
    if (!mWindow || !mWindow->GetOuterWindow() || !mWindow->GetDocShell()) {
      aRv.Throw(NS_ERROR_FAILURE);
      return nullptr;
    }
    mDeviceStorageAreaListener = new DeviceStorageAreaListener(mWindow);
  }

  return mDeviceStorageAreaListener;
}

already_AddRefed<nsDOMDeviceStorage>
Navigator::FindDeviceStorage(const nsAString& aName, const nsAString& aType)
{
  auto i = mDeviceStorageStores.Length();
  while (i > 0) {
    --i;
    RefPtr<nsDOMDeviceStorage> storage =
      do_QueryReferent(mDeviceStorageStores[i]);
    if (storage) {
      if (storage->Equals(mWindow, aName, aType)) {
        return storage.forget();
      }
    } else {
      mDeviceStorageStores.RemoveElementAt(i);
    }
  }
  return nullptr;
}

already_AddRefed<nsDOMDeviceStorage>
Navigator::GetDeviceStorage(const nsAString& aType, ErrorResult& aRv)
{
  if (!mWindow || !mWindow->GetOuterWindow() || !mWindow->GetDocShell()) {
    aRv.Throw(NS_ERROR_FAILURE);
    return nullptr;
  }

  nsString name;
  nsDOMDeviceStorage::GetDefaultStorageName(aType, name);
  RefPtr<nsDOMDeviceStorage> storage = FindDeviceStorage(name, aType);
  if (storage) {
    return storage.forget();
  }

  nsDOMDeviceStorage::CreateDeviceStorageFor(mWindow, aType,
                                             getter_AddRefs(storage));

  if (!storage) {
    return nullptr;
  }

  mDeviceStorageStores.AppendElement(
    do_GetWeakReference(static_cast<DOMEventTargetHelper*>(storage)));
  return storage.forget();
}

void
Navigator::GetDeviceStorages(const nsAString& aType,
                             nsTArray<RefPtr<nsDOMDeviceStorage> >& aStores,
                             ErrorResult& aRv)
{
  if (!mWindow || !mWindow->GetOuterWindow() || !mWindow->GetDocShell()) {
    aRv.Throw(NS_ERROR_FAILURE);
    return;
  }

  nsDOMDeviceStorage::VolumeNameArray volumes;
  nsDOMDeviceStorage::GetOrderedVolumeNames(aType, volumes);
  if (volumes.IsEmpty()) {
    RefPtr<nsDOMDeviceStorage> storage = GetDeviceStorage(aType, aRv);
    if (storage) {
      aStores.AppendElement(storage.forget());
    }
  } else {
    uint32_t len = volumes.Length();
    aStores.SetCapacity(len);
    for (uint32_t i = 0; i < len; ++i) {
      RefPtr<nsDOMDeviceStorage> storage =
        GetDeviceStorageByNameAndType(volumes[i], aType, aRv);
      if (aRv.Failed()) {
        break;
      }

      if (storage) {
        aStores.AppendElement(storage.forget());
      }
    }
  }
}

already_AddRefed<nsDOMDeviceStorage>
Navigator::GetDeviceStorageByNameAndType(const nsAString& aName,
                                         const nsAString& aType,
                                         ErrorResult& aRv)
{
  if (!mWindow || !mWindow->GetOuterWindow() || !mWindow->GetDocShell()) {
    aRv.Throw(NS_ERROR_FAILURE);
    return nullptr;
  }

  RefPtr<nsDOMDeviceStorage> storage = FindDeviceStorage(aName, aType);
  if (storage) {
    return storage.forget();
  }
  nsDOMDeviceStorage::CreateDeviceStorageByNameAndType(mWindow, aName, aType,
                                                       getter_AddRefs(storage));

  if (!storage) {
    return nullptr;
  }

  mDeviceStorageStores.AppendElement(
    do_GetWeakReference(static_cast<DOMEventTargetHelper*>(storage)));
  return storage.forget();
}

Geolocation*
Navigator::GetGeolocation(ErrorResult& aRv)
{
  if (mGeolocation) {
    return mGeolocation;
  }

  if (!mWindow || !mWindow->GetOuterWindow() || !mWindow->GetDocShell()) {
    aRv.Throw(NS_ERROR_FAILURE);
    return nullptr;
  }

  mGeolocation = new Geolocation();
  if (NS_FAILED(mGeolocation->Init(mWindow->GetOuterWindow()))) {
    mGeolocation = nullptr;
    aRv.Throw(NS_ERROR_FAILURE);
    return nullptr;
  }

  return mGeolocation;
}

class BeaconStreamListener final : public nsIStreamListener
{
    ~BeaconStreamListener() {}

  public:
    BeaconStreamListener() : mLoadGroup(nullptr) {}

    void SetLoadGroup(nsILoadGroup* aLoadGroup) {
      mLoadGroup = aLoadGroup;
    }

    NS_DECL_ISUPPORTS
    NS_DECL_NSISTREAMLISTENER
    NS_DECL_NSIREQUESTOBSERVER

  private:
    nsCOMPtr<nsILoadGroup> mLoadGroup;

};

NS_IMPL_ISUPPORTS(BeaconStreamListener,
                  nsIStreamListener,
                  nsIRequestObserver)

NS_IMETHODIMP
BeaconStreamListener::OnStartRequest(nsIRequest *aRequest,
                                     nsISupports *aContext)
{
  // release the loadgroup first
  mLoadGroup = nullptr;

  aRequest->Cancel(NS_ERROR_NET_INTERRUPT);
  return NS_BINDING_ABORTED;
}

NS_IMETHODIMP
BeaconStreamListener::OnStopRequest(nsIRequest *aRequest,
                                    nsISupports *aContext,
                                    nsresult aStatus)
{
  return NS_OK;
}

NS_IMETHODIMP
BeaconStreamListener::OnDataAvailable(nsIRequest *aRequest,
                                      nsISupports *ctxt,
                                      nsIInputStream *inStr,
                                      uint64_t sourceOffset,
                                      uint32_t count)
{
  MOZ_ASSERT(false);
  return NS_OK;
}

bool
Navigator::SendBeacon(const nsAString& aUrl,
                      const Nullable<ArrayBufferViewOrBlobOrStringOrFormData>& aData,
                      ErrorResult& aRv)
{
  if (!mWindow) {
    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
    return false;
  }

  nsCOMPtr<nsIDocument> doc = mWindow->GetDoc();
  if (!doc) {
    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
    return false;
  }

  nsIURI* documentURI = doc->GetDocumentURI();
  if (!documentURI) {
    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
    return false;
  }

  nsCOMPtr<nsIURI> uri;
  nsresult rv = nsContentUtils::NewURIWithDocumentCharset(
                  getter_AddRefs(uri),
                  aUrl,
                  doc,
                  doc->GetDocBaseURI());
  if (NS_FAILED(rv)) {
    aRv.Throw(NS_ERROR_DOM_URL_MISMATCH_ERR);
    return false;
  }

  // Explicitly disallow loading data: URIs
  bool isDataScheme = false;
  rv = uri->SchemeIs("data", &isDataScheme);
  if (NS_FAILED(rv) || isDataScheme) {
    aRv.Throw(NS_ERROR_CONTENT_BLOCKED);
    return false;
  }

  nsCOMPtr<nsIChannel> channel;
  rv = NS_NewChannel(getter_AddRefs(channel),
                     uri,
                     doc,
                     nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS,
                     nsIContentPolicy::TYPE_BEACON);

  if (NS_FAILED(rv)) {
    aRv.Throw(rv);
    return false;
  }

  nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel);
  if (!httpChannel) {
    // Beacon spec only supports HTTP requests at this time
    aRv.Throw(NS_ERROR_DOM_BAD_URI);
    return false;
  }
  httpChannel->SetReferrer(documentURI);

  nsCString mimeType;
  if (!aData.IsNull()) {
    nsCOMPtr<nsIInputStream> in;

    if (aData.Value().IsString()) {
      nsCString stringData = NS_ConvertUTF16toUTF8(aData.Value().GetAsString());
      nsCOMPtr<nsIStringInputStream> strStream = do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID, &rv);
      if (NS_FAILED(rv)) {
        aRv.Throw(NS_ERROR_FAILURE);
        return false;
      }
      rv = strStream->SetData(stringData.BeginReading(), stringData.Length());
      if (NS_FAILED(rv)) {
        aRv.Throw(NS_ERROR_FAILURE);
        return false;
      }
      mimeType.AssignLiteral("text/plain;charset=UTF-8");
      in = strStream;

    } else if (aData.Value().IsArrayBufferView()) {

      nsCOMPtr<nsIStringInputStream> strStream = do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID, &rv);
      if (NS_FAILED(rv)) {
        aRv.Throw(NS_ERROR_FAILURE);
        return false;
      }

      const ArrayBufferView& view = aData.Value().GetAsArrayBufferView();
      view.ComputeLengthAndData();
      rv = strStream->SetData(reinterpret_cast<char*>(view.Data()),
                              view.Length());

      if (NS_FAILED(rv)) {
        aRv.Throw(NS_ERROR_FAILURE);
        return false;
      }
      mimeType.AssignLiteral("application/octet-stream");
      in = strStream;

    } else if (aData.Value().IsBlob()) {
      Blob& blob = aData.Value().GetAsBlob();
      blob.GetInternalStream(getter_AddRefs(in), aRv);
      if (NS_WARN_IF(aRv.Failed())) {
        return false;
      }

      nsAutoString type;
      blob.GetType(type);
      mimeType = NS_ConvertUTF16toUTF8(type);

    } else if (aData.Value().IsFormData()) {
      nsFormData& form = aData.Value().GetAsFormData();
      uint64_t len;
      nsAutoCString charset;
      form.GetSendInfo(getter_AddRefs(in),
                       &len,
                       mimeType,
                       charset);
    } else {
      MOZ_ASSERT(false, "switch statements not in sync");
      aRv.Throw(NS_ERROR_FAILURE);
      return false;
    }

    nsCOMPtr<nsIUploadChannel2> uploadChannel = do_QueryInterface(channel);
    if (!uploadChannel) {
      aRv.Throw(NS_ERROR_FAILURE);
      return false;
    }
    uploadChannel->ExplicitSetUploadStream(in, mimeType, -1,
                                           NS_LITERAL_CSTRING("POST"),
                                           false);
  } else {
    httpChannel->SetRequestMethod(NS_LITERAL_CSTRING("POST"));
  }

  nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(channel);
  if (p) {
    p->SetPriority(nsISupportsPriority::PRIORITY_LOWEST);
  }

  nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(channel));
  if (cos) {
    cos->AddClassFlags(nsIClassOfService::Background);
  }

  // The channel needs to have a loadgroup associated with it, so that we can
  // cancel the channel and any redirected channels it may create.
  nsCOMPtr<nsILoadGroup> loadGroup = do_CreateInstance(NS_LOADGROUP_CONTRACTID);
  nsCOMPtr<nsIInterfaceRequestor> callbacks =
    do_QueryInterface(mWindow->GetDocShell());
  loadGroup->SetNotificationCallbacks(callbacks);
  channel->SetLoadGroup(loadGroup);

  RefPtr<BeaconStreamListener> beaconListener = new BeaconStreamListener();

  // Start a preflight if cross-origin and content type is not whitelisted
  nsCOMPtr<nsIScriptSecurityManager> secMan = nsContentUtils::GetSecurityManager();
  rv = secMan->CheckSameOriginURI(documentURI, uri, false);
  bool crossOrigin = NS_FAILED(rv);
  nsAutoCString contentType, parsedCharset;
  rv = NS_ParseRequestContentType(mimeType, contentType, parsedCharset);
  if (crossOrigin &&
      mimeType.Length() > 0 &&
      !contentType.Equals(APPLICATION_WWW_FORM_URLENCODED) &&
      !contentType.Equals(MULTIPART_FORM_DATA) &&
      !contentType.Equals(TEXT_PLAIN)) {

    // we need to set the sameOriginChecker as a notificationCallback
    // so we can tell the channel not to follow redirects
    nsCOMPtr<nsIInterfaceRequestor> soc = nsContentUtils::SameOriginChecker();
    channel->SetNotificationCallbacks(soc);

    nsCOMPtr<nsIHttpChannelInternal> internalChannel =
      do_QueryInterface(channel);
    if (!internalChannel) {
      aRv.Throw(NS_ERROR_FAILURE);
      return false;
    }
    nsTArray<nsCString> unsafeHeaders;
    unsafeHeaders.AppendElement(NS_LITERAL_CSTRING("Content-Type"));
    rv = internalChannel->SetCorsPreflightParameters(unsafeHeaders,
                                                     true,
                                                     doc->NodePrincipal());
    if (NS_WARN_IF(NS_FAILED(rv))) {
      aRv.Throw(rv);
      return false;
    }
  }

  rv = channel->AsyncOpen2(beaconListener);
  if (NS_FAILED(rv)) {
    aRv.Throw(rv);
    return false;
  }
  // make the beaconListener hold a strong reference to the loadgroup
  // which is released in ::OnStartRequest
  beaconListener->SetLoadGroup(loadGroup);

  return true;
}

#ifdef MOZ_MEDIA_NAVIGATOR
MediaDevices*
Navigator::GetMediaDevices(ErrorResult& aRv)
{
  if (!mMediaDevices) {
    if (!mWindow ||
        !mWindow->GetOuterWindow() ||
        mWindow->GetOuterWindow()->GetCurrentInnerWindow() != mWindow) {
      aRv.Throw(NS_ERROR_NOT_AVAILABLE);
      return nullptr;
    }
    mMediaDevices = new MediaDevices(mWindow);
  }
  return mMediaDevices;
}

void
Navigator::MozGetUserMedia(const MediaStreamConstraints& aConstraints,
                           NavigatorUserMediaSuccessCallback& aOnSuccess,
                           NavigatorUserMediaErrorCallback& aOnError,
                           ErrorResult& aRv)
{
  CallbackObjectHolder<NavigatorUserMediaSuccessCallback,
                       nsIDOMGetUserMediaSuccessCallback> holder1(&aOnSuccess);
  nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> onsuccess =
    holder1.ToXPCOMCallback();

  CallbackObjectHolder<NavigatorUserMediaErrorCallback,
                       nsIDOMGetUserMediaErrorCallback> holder2(&aOnError);
  nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onerror = holder2.ToXPCOMCallback();

  if (!mWindow || !mWindow->GetOuterWindow() ||
      mWindow->GetOuterWindow()->GetCurrentInnerWindow() != mWindow) {
    aRv.Throw(NS_ERROR_NOT_AVAILABLE);
    return;
  }

  MediaManager* manager = MediaManager::Get();
  aRv = manager->GetUserMedia(mWindow, aConstraints, onsuccess, onerror);
}

void
Navigator::MozGetUserMediaDevices(const MediaStreamConstraints& aConstraints,
                                  MozGetUserMediaDevicesSuccessCallback& aOnSuccess,
                                  NavigatorUserMediaErrorCallback& aOnError,
                                  uint64_t aInnerWindowID,
                                  ErrorResult& aRv)
{
  CallbackObjectHolder<MozGetUserMediaDevicesSuccessCallback,
                       nsIGetUserMediaDevicesSuccessCallback> holder1(&aOnSuccess);
  nsCOMPtr<nsIGetUserMediaDevicesSuccessCallback> onsuccess =
    holder1.ToXPCOMCallback();

  CallbackObjectHolder<NavigatorUserMediaErrorCallback,
                       nsIDOMGetUserMediaErrorCallback> holder2(&aOnError);
  nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onerror = holder2.ToXPCOMCallback();

  if (!mWindow || !mWindow->GetOuterWindow() ||
      mWindow->GetOuterWindow()->GetCurrentInnerWindow() != mWindow) {
    aRv.Throw(NS_ERROR_NOT_AVAILABLE);
    return;
  }

  MediaManager* manager = MediaManager::Get();
  aRv = manager->GetUserMediaDevices(mWindow, aConstraints, onsuccess, onerror,
                                     aInnerWindowID);
}
#endif

DesktopNotificationCenter*
Navigator::GetMozNotification(ErrorResult& aRv)
{
  if (mNotification) {
    return mNotification;
  }

  if (!mWindow || !mWindow->GetDocShell()) {
    aRv.Throw(NS_ERROR_FAILURE);
    return nullptr;
  }

  mNotification = new DesktopNotificationCenter(mWindow);
  return mNotification;
}

#ifdef MOZ_B2G_FM

using mozilla::dom::FMRadio;

FMRadio*
Navigator::GetMozFMRadio(ErrorResult& aRv)
{
  if (!mFMRadio) {
    if (!mWindow) {
      aRv.Throw(NS_ERROR_UNEXPECTED);
      return nullptr;
    }

    NS_ENSURE_TRUE(mWindow->GetDocShell(), nullptr);

    mFMRadio = new FMRadio();
    mFMRadio->Init(mWindow);
  }

  return mFMRadio;
}

#endif  // MOZ_B2G_FM

//*****************************************************************************
//    Navigator::nsINavigatorBattery
//*****************************************************************************

Promise*
Navigator::GetBattery(ErrorResult& aRv)
{
  if (mBatteryPromise) {
    return mBatteryPromise;
  }

  if (!mWindow || !mWindow->GetDocShell()) {
    aRv.Throw(NS_ERROR_UNEXPECTED);
    return nullptr;
  }

  nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(mWindow);
  RefPtr<Promise> batteryPromise = Promise::Create(go, aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }
  mBatteryPromise = batteryPromise;

  if (!mBatteryManager) {
    mBatteryManager = new battery::BatteryManager(mWindow);
    mBatteryManager->Init();
  }

  mBatteryPromise->MaybeResolve(mBatteryManager);

  return mBatteryPromise;
}

battery::BatteryManager*
Navigator::GetDeprecatedBattery(ErrorResult& aRv)
{
  if (!mBatteryManager) {
    if (!mWindow) {
      aRv.Throw(NS_ERROR_UNEXPECTED);
      return nullptr;
    }
    NS_ENSURE_TRUE(mWindow->GetDocShell(), nullptr);

    mBatteryManager = new battery::BatteryManager(mWindow);
    mBatteryManager->Init();
  }

  return mBatteryManager;
}

/* static */ already_AddRefed<Promise>
Navigator::GetDataStores(nsPIDOMWindow* aWindow,
                         const nsAString& aName,
                         const nsAString& aOwner,
                         ErrorResult& aRv)
{
  if (!aWindow || !aWindow->GetDocShell()) {
    aRv.Throw(NS_ERROR_UNEXPECTED);
    return nullptr;
  }

  RefPtr<DataStoreService> service = DataStoreService::GetOrCreate();
  if (!service) {
    aRv.Throw(NS_ERROR_FAILURE);
    return nullptr;
  }

  nsCOMPtr<nsISupports> promise;
  aRv = service->GetDataStores(aWindow, aName, aOwner, getter_AddRefs(promise));

  RefPtr<Promise> p = static_cast<Promise*>(promise.get());
  return p.forget();
}

already_AddRefed<Promise>
Navigator::GetDataStores(const nsAString& aName,
                         const nsAString& aOwner,
                         ErrorResult& aRv)
{
  return GetDataStores(mWindow, aName, aOwner, 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;
  }

#if defined(XP_LINUX)
  if (aName.EqualsLiteral("hardware.memory")) {
    // with seccomp enabled, fopen() should be in a non-sandboxed process
    if (XRE_IsParentProcess()) {
      uint32_t memLevel = mozilla::hal::GetTotalSystemMemoryLevel();
      if (memLevel == 0) {
        p->MaybeReject(NS_ERROR_NOT_AVAILABLE);
        return p.forget();
      }
      p->MaybeResolve((int)memLevel);
    } else {
      mozilla::dom::ContentChild* cc =
        mozilla::dom::ContentChild::GetSingleton();
      RefPtr<Promise> ipcRef(p);
      cc->SendGetSystemMemory(reinterpret_cast<uint64_t>(ipcRef.forget().take()));
    }
    return p.forget();
  } // hardware.memory
#endif

#ifdef MOZ_WIDGET_GONK
  if (StringBeginsWith(aName, NS_LITERAL_STRING("acl.")) &&
      (aName.EqualsLiteral("acl.version") || CheckPermission("external-app"))) {
    char value[PROPERTY_VALUE_MAX];
    nsCString propertyKey("persist.");
    propertyKey.Append(NS_ConvertUTF16toUTF8(aName));
    uint32_t len = property_get(propertyKey.get(), value, nullptr);
    if (len > 0) {
      p->MaybeResolve(NS_ConvertUTF8toUTF16(value));
      return p.forget();
    }
  }
#endif

  // Mirror the dom.apps.developer_mode pref to let apps get it read-only.
  if (aName.EqualsLiteral("dom.apps.developer_mode")) {
    p->MaybeResolve(Preferences::GetBool("dom.apps.developer_mode", false));
    return p.forget();
  }

  p->MaybeResolve(JS::UndefinedHandleValue);
  return p.forget();
}

already_AddRefed<Promise>
Navigator::HasFeature(const nsAString& aName, ErrorResult& aRv)
{
  nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(mWindow);
  RefPtr<Promise> p = Promise::Create(go, aRv);
  if (aRv.Failed()) {
    return nullptr;
  }

  // Hardcoded extensions features which are b2g specific.
#ifdef MOZ_B2G
  if (aName.EqualsLiteral("web-extensions") ||
      aName.EqualsLiteral("late-customization")) {
    p->MaybeResolve(true);
    return p.forget();
  }
#endif

  // Hardcoded manifest features. Some are still b2g specific.
  const char manifestFeatures[][64] = {
    "manifest.origin"
  , "manifest.redirects"
#ifdef MOZ_B2G
  , "manifest.chrome.navigation"
  , "manifest.precompile"
  , "manifest.role.homescreen"
#endif
  };

  nsAutoCString feature = NS_ConvertUTF16toUTF8(aName);
  for (uint32_t i = 0; i < MOZ_ARRAY_LENGTH(manifestFeatures); i++) {
    if (feature.Equals(manifestFeatures[i])) {
      p->MaybeResolve(true);
      return p.forget();
    }
  }

  NS_NAMED_LITERAL_STRING(apiWindowPrefix, "api.window.");
  if (StringBeginsWith(aName, apiWindowPrefix)) {
    const nsAString& featureName = Substring(aName, apiWindowPrefix.Length());

    // Temporary hardcoded entry points due to technical constraints
    if (featureName.EqualsLiteral("Navigator.mozTCPSocket")) {
      p->MaybeResolve(Preferences::GetBool("dom.mozTCPSocket.enabled"));
      return p.forget();
    }

    if (featureName.EqualsLiteral("Navigator.mozMobileConnections") ||
        featureName.EqualsLiteral("MozMobileNetworkInfo")) {
      p->MaybeResolve(Preferences::GetBool("dom.mobileconnection.enabled"));
      return p.forget();
    }

    if (featureName.EqualsLiteral("Navigator.mozInputMethod")) {
      p->MaybeResolve(Preferences::GetBool("dom.mozInputMethod.enabled"));
      return p.forget();
    }

    if (featureName.EqualsLiteral("Navigator.mozContacts")) {
      p->MaybeResolve(true);
      return p.forget();
    }

    if (featureName.EqualsLiteral("Navigator.getDeviceStorage")) {
      p->MaybeResolve(Preferences::GetBool("device.storage.enabled"));
      return p.forget();
    }

    if (featureName.EqualsLiteral("Navigator.mozNetworkStats")) {
      p->MaybeResolve(Preferences::GetBool("dom.mozNetworkStats.enabled"));
      return p.forget();
    }

    if (featureName.EqualsLiteral("Navigator.push")) {
      p->MaybeResolve(Preferences::GetBool("services.push.enabled"));
      return p.forget();
    }

    if (featureName.EqualsLiteral("Navigator.mozAlarms")) {
      p->MaybeResolve(Preferences::GetBool("dom.mozAlarms.enabled"));
      return p.forget();
    }

    if (featureName.EqualsLiteral("Navigator.mozCameras")) {
      p->MaybeResolve(true);
      return p.forget();
    }

#ifdef MOZ_B2G
    if (featureName.EqualsLiteral("Navigator.getMobileIdAssertion")) {
      p->MaybeResolve(true);
      return p.forget();
    }
#endif

    if (featureName.EqualsLiteral("XMLHttpRequest.mozSystem")) {
      p->MaybeResolve(true);
      return p.forget();
    }

    if (IsFeatureDetectible(featureName)) {
      p->MaybeResolve(true);
    } else {
      p->MaybeResolve(JS::UndefinedHandleValue);
    }
    return p.forget();
  }

  // resolve with <undefined> because the feature name is not supported
  p->MaybeResolve(JS::UndefinedHandleValue);

  return p.forget();
}

PowerManager*
Navigator::GetMozPower(ErrorResult& aRv)
{
  if (!mPowerManager) {
    if (!mWindow) {
      aRv.Throw(NS_ERROR_UNEXPECTED);
      return nullptr;
    }
    mPowerManager = PowerManager::CreateInstance(mWindow);
    if (!mPowerManager) {
      // We failed to get the power manager service?
      aRv.Throw(NS_ERROR_UNEXPECTED);
    }
  }

  return mPowerManager;
}

already_AddRefed<WakeLock>
Navigator::RequestWakeLock(const nsAString &aTopic, ErrorResult& aRv)
{
  if (!mWindow) {
    aRv.Throw(NS_ERROR_UNEXPECTED);
    return nullptr;
  }

  RefPtr<power::PowerManagerService> pmService =
    power::PowerManagerService::GetInstance();
  // Maybe it went away for some reason... Or maybe we're just called
  // from our XPCOM method.
  if (!pmService) {
    aRv.Throw(NS_ERROR_UNEXPECTED);
    return nullptr;
  }

  return pmService->NewWakeLock(aTopic, mWindow, aRv);
}

MobileMessageManager*
Navigator::GetMozMobileMessage()
{
  if (!mMobileMessageManager) {
    // Check that our window has not gone away
    NS_ENSURE_TRUE(mWindow, nullptr);
    NS_ENSURE_TRUE(mWindow->GetDocShell(), nullptr);

    mMobileMessageManager = new MobileMessageManager(mWindow);
    mMobileMessageManager->Init();
  }

  return mMobileMessageManager;
}

Telephony*
Navigator::GetMozTelephony(ErrorResult& aRv)
{
  if (!mTelephony) {
    if (!mWindow) {
      aRv.Throw(NS_ERROR_UNEXPECTED);
      return nullptr;
    }
    mTelephony = Telephony::Create(mWindow, aRv);
  }

  return mTelephony;
}

TVManager*
Navigator::GetTv()
{
  if (!mTVManager) {
    if (!mWindow) {
      return nullptr;
    }
    mTVManager = TVManager::Create(mWindow);
  }

  return mTVManager;
}

InputPortManager*
Navigator::GetInputPortManager(ErrorResult& aRv)
{
  if (!mInputPortManager) {
    if (!mWindow) {
      aRv.Throw(NS_ERROR_FAILURE);
      return nullptr;
    }
    mInputPortManager = InputPortManager::Create(mWindow, aRv);
    if (NS_WARN_IF(aRv.Failed())) {
      return nullptr;
    }
  }

  return mInputPortManager;
}

already_AddRefed<LegacyMozTCPSocket>
Navigator::MozTCPSocket()
{
  RefPtr<LegacyMozTCPSocket> socket = new LegacyMozTCPSocket(GetWindow());
  return socket.forget();
}

#ifdef MOZ_B2G
already_AddRefed<Promise>
Navigator::GetMobileIdAssertion(const MobileIdOptions& aOptions,
                                ErrorResult& aRv)
{
  if (!mWindow || !mWindow->GetDocShell()) {
    aRv.Throw(NS_ERROR_UNEXPECTED);
    return nullptr;
  }

  nsCOMPtr<nsIMobileIdentityService> service =
    do_GetService("@mozilla.org/mobileidentity-service;1");
  if (!service) {
    aRv.Throw(NS_ERROR_FAILURE);
    return nullptr;
  }

  JSContext *cx = nsContentUtils::GetCurrentJSContext();
  if (!cx) {
    aRv.Throw(NS_ERROR_FAILURE);
    return nullptr;
  }

  JS::Rooted<JS::Value> optionsValue(cx);
  if (!ToJSValue(cx, aOptions, &optionsValue)) {
    aRv.Throw(NS_ERROR_FAILURE);
    return nullptr;
  }

  nsCOMPtr<nsISupports> promise;
  aRv = service->GetMobileIdAssertion(mWindow,
                                      optionsValue,
                                      getter_AddRefs(promise));

  RefPtr<Promise> p = static_cast<Promise*>(promise.get());
  return p.forget();
}
#endif // MOZ_B2G

#ifdef MOZ_B2G_RIL

MobileConnectionArray*
Navigator::GetMozMobileConnections(ErrorResult& aRv)
{
  if (!mMobileConnections) {
    if (!mWindow) {
      aRv.Throw(NS_ERROR_UNEXPECTED);
      return nullptr;
    }
    mMobileConnections = new MobileConnectionArray(mWindow);
  }

  return mMobileConnections;
}

#endif // MOZ_B2G_RIL

CellBroadcast*
Navigator::GetMozCellBroadcast(ErrorResult& aRv)
{
  if (!mCellBroadcast) {
    if (!mWindow) {
      aRv.Throw(NS_ERROR_UNEXPECTED);
      return nullptr;
    }
    mCellBroadcast = CellBroadcast::Create(mWindow, aRv);
  }

  return mCellBroadcast;
}

Voicemail*
Navigator::GetMozVoicemail(ErrorResult& aRv)
{
  if (!mVoicemail) {
    if (!mWindow) {
      aRv.Throw(NS_ERROR_UNEXPECTED);
      return nullptr;
    }

    mVoicemail = Voicemail::Create(mWindow, aRv);
  }

  return mVoicemail;
}

IccManager*
Navigator::GetMozIccManager(ErrorResult& aRv)
{
  if (!mIccManager) {
    if (!mWindow) {
      aRv.Throw(NS_ERROR_UNEXPECTED);
      return nullptr;
    }
    NS_ENSURE_TRUE(mWindow->GetDocShell(), nullptr);

    mIccManager = new IccManager(mWindow);
  }

  return mIccManager;
}

#ifdef MOZ_GAMEPAD
void
Navigator::GetGamepads(nsTArray<RefPtr<Gamepad> >& aGamepads,
                       ErrorResult& aRv)
{
  if (!mWindow) {
    aRv.Throw(NS_ERROR_UNEXPECTED);
    return;
  }
  NS_ENSURE_TRUE_VOID(mWindow->GetDocShell());
  nsGlobalWindow* win = static_cast<nsGlobalWindow*>(mWindow.get());
  win->SetHasGamepadEventListener(true);
  win->GetGamepads(aGamepads);
}
#endif

already_AddRefed<Promise>
Navigator::GetVRDevices(ErrorResult& aRv)
{
  if (!mWindow || !mWindow->GetDocShell()) {
    aRv.Throw(NS_ERROR_UNEXPECTED);
    return nullptr;
  }

  nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(mWindow);
  RefPtr<Promise> p = Promise::Create(go, aRv);
  if (aRv.Failed()) {
    return nullptr;
  }

  nsGlobalWindow* win = static_cast<nsGlobalWindow*>(mWindow.get());

  nsTArray<RefPtr<VRDevice>> vrDevs;
  if (!win->GetVRDevices(vrDevs)) {
    p->MaybeReject(NS_ERROR_FAILURE);
  } else {
    p->MaybeResolve(vrDevs);
  }

  return p.forget();
}

//*****************************************************************************
//    Navigator::nsIMozNavigatorNetwork
//*****************************************************************************

NS_IMETHODIMP
Navigator::GetProperties(nsINetworkProperties** aProperties)
{
  ErrorResult rv;
  NS_IF_ADDREF(*aProperties = GetConnection(rv));
  return NS_OK;
}

network::Connection*
Navigator::GetConnection(ErrorResult& aRv)
{
  if (!mConnection) {
    if (!mWindow) {
      aRv.Throw(NS_ERROR_UNEXPECTED);
      return nullptr;
    }
    mConnection = new network::Connection(mWindow);
  }

  return mConnection;
}

#ifdef MOZ_B2G_BT
bluetooth::BluetoothManager*
Navigator::GetMozBluetooth(ErrorResult& aRv)
{
  if (!mBluetooth) {
    if (!mWindow) {
      aRv.Throw(NS_ERROR_UNEXPECTED);
      return nullptr;
    }
    mBluetooth = bluetooth::BluetoothManager::Create(mWindow);
  }

  return mBluetooth;
}
#endif //MOZ_B2G_BT

nsresult
Navigator::EnsureMessagesManager()
{
  if (mMessagesManager) {
    return NS_OK;
  }

  NS_ENSURE_STATE(mWindow);

  nsresult rv;
  nsCOMPtr<nsIDOMNavigatorSystemMessages> messageManager =
    do_CreateInstance("@mozilla.org/system-message-manager;1", &rv);

  nsCOMPtr<nsIDOMGlobalPropertyInitializer> gpi =
    do_QueryInterface(messageManager);
  NS_ENSURE_TRUE(gpi, NS_ERROR_FAILURE);

  // We don't do anything with the return value.
  AutoJSContext cx;
  JS::Rooted<JS::Value> prop_val(cx);
  rv = gpi->Init(mWindow, &prop_val);
  NS_ENSURE_SUCCESS(rv, rv);

  mMessagesManager = messageManager.forget();

  return NS_OK;
}

bool
Navigator::MozHasPendingMessage(const nsAString& aType, ErrorResult& aRv)
{
  // The WebIDL binding is responsible for the pref check here.
  nsresult rv = EnsureMessagesManager();
  if (NS_FAILED(rv)) {
    aRv.Throw(rv);
    return false;
  }

  bool result = false;
  rv = mMessagesManager->MozHasPendingMessage(aType, &result);
  if (NS_FAILED(rv)) {
    aRv.Throw(rv);
    return false;
  }
  return result;
}

void
Navigator::MozSetMessageHandlerPromise(Promise& aPromise,
                                       ErrorResult& aRv)
{
  // The WebIDL binding is responsible for the pref check here.
  aRv = EnsureMessagesManager();
  if (NS_WARN_IF(aRv.Failed())) {
    return;
  }

  bool result = false;
  aRv = mMessagesManager->MozIsHandlingMessage(&result);
  if (NS_WARN_IF(aRv.Failed())) {
    return;
  }

  if (!result) {
    aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
    return;
  }

  aRv = mMessagesManager->MozSetMessageHandlerPromise(&aPromise);
  if (NS_WARN_IF(aRv.Failed())) {
    return;
  }
}

void
Navigator::MozSetMessageHandler(const nsAString& aType,
                                systemMessageCallback* aCallback,
                                ErrorResult& aRv)
{
  // The WebIDL binding is responsible for the pref check here.
  nsresult rv = EnsureMessagesManager();
  if (NS_FAILED(rv)) {
    aRv.Throw(rv);
    return;
  }

  CallbackObjectHolder<systemMessageCallback, nsIDOMSystemMessageCallback>
    holder(aCallback);
  nsCOMPtr<nsIDOMSystemMessageCallback> callback = holder.ToXPCOMCallback();

  rv = mMessagesManager->MozSetMessageHandler(aType, callback);
  if (NS_FAILED(rv)) {
    aRv.Throw(rv);
  }
}

#ifdef MOZ_TIME_MANAGER
time::TimeManager*
Navigator::GetMozTime(ErrorResult& aRv)
{
  if (!mWindow) {
    aRv.Throw(NS_ERROR_UNEXPECTED);
    return nullptr;
  }

  if (!mTimeManager) {
    mTimeManager = new time::TimeManager(mWindow);
  }

  return mTimeManager;
}
#endif

nsDOMCameraManager*
Navigator::GetMozCameras(ErrorResult& aRv)
{
  if (!mCameraManager) {
    if (!mWindow ||
        !mWindow->GetOuterWindow() ||
        mWindow->GetOuterWindow()->GetCurrentInnerWindow() != mWindow) {
      aRv.Throw(NS_ERROR_NOT_AVAILABLE);
      return nullptr;
    }

    mCameraManager = nsDOMCameraManager::CreateInstance(mWindow);
  }

  return mCameraManager;
}

already_AddRefed<ServiceWorkerContainer>
Navigator::ServiceWorker()
{
  MOZ_ASSERT(mWindow);

  if (!mServiceWorkerContainer) {
    mServiceWorkerContainer = new ServiceWorkerContainer(mWindow);
  }

  RefPtr<ServiceWorkerContainer> ref = mServiceWorkerContainer;
  return ref.forget();
}

size_t
Navigator::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
{
  size_t n = aMallocSizeOf(this);

  // TODO: add SizeOfIncludingThis() to nsMimeTypeArray, bug 674113.
  // TODO: add SizeOfIncludingThis() to nsPluginArray, bug 674114.
  // TODO: add SizeOfIncludingThis() to Geolocation, bug 674115.
  // TODO: add SizeOfIncludingThis() to DesktopNotificationCenter, bug 674116.

  return n;
}

void
Navigator::SetWindow(nsPIDOMWindow *aInnerWindow)
{
  NS_ASSERTION(aInnerWindow->IsInnerWindow(),
               "Navigator must get an inner window!");
  mWindow = aInnerWindow;
}

void
Navigator::OnNavigation()
{
  if (!mWindow) {
    return;
  }

#ifdef MOZ_MEDIA_NAVIGATOR
  // If MediaManager is open let it inform any live streams or pending callbacks
  MediaManager *manager = MediaManager::GetIfExists();
  if (manager) {
    manager->OnNavigation(mWindow->WindowID());
  }
#endif
  if (mCameraManager) {
    mCameraManager->OnNavigation(mWindow->WindowID());
  }
}

bool
Navigator::CheckPermission(const char* type)
{
  return CheckPermission(mWindow, type);
}

/* static */
bool
Navigator::CheckPermission(nsPIDOMWindow* aWindow, const char* aType)
{
  if (!aWindow) {
    return false;
  }

  nsCOMPtr<nsIPermissionManager> permMgr =
    services::GetPermissionManager();
  NS_ENSURE_TRUE(permMgr, false);

  uint32_t permission = nsIPermissionManager::DENY_ACTION;
  permMgr->TestPermissionFromWindow(aWindow, aType, &permission);
  return permission == nsIPermissionManager::ALLOW_ACTION;
}

#ifdef MOZ_AUDIO_CHANNEL_MANAGER
system::AudioChannelManager*
Navigator::GetMozAudioChannelManager(ErrorResult& aRv)
{
  if (!mAudioChannelManager) {
    if (!mWindow) {
      aRv.Throw(NS_ERROR_UNEXPECTED);
      return nullptr;
    }
    mAudioChannelManager = new system::AudioChannelManager();
    mAudioChannelManager->Init(mWindow);
  }

  return mAudioChannelManager;
}
#endif

bool
Navigator::DoResolve(JSContext* aCx, JS::Handle<JSObject*> aObject,
                     JS::Handle<jsid> aId,
                     JS::MutableHandle<JSPropertyDescriptor> aDesc)
{
  // Note: Keep this in sync with MayResolve.
  if (!JSID_IS_STRING(aId)) {
    return true;
  }

  nsScriptNameSpaceManager* nameSpaceManager = GetNameSpaceManager();
  if (!nameSpaceManager) {
    return Throw(aCx, NS_ERROR_NOT_INITIALIZED);
  }

  nsAutoJSString name;
  if (!name.init(aCx, JSID_TO_STRING(aId))) {
    return false;
  }

  const nsGlobalNameStruct* name_struct =
    nameSpaceManager->LookupNavigatorName(name);
  if (!name_struct) {
    return true;
  }

  JS::Rooted<JSObject*> naviObj(aCx,
                                js::CheckedUnwrap(aObject,
                                                  /* stopAtOuter = */ false));
  if (!naviObj) {
    return Throw(aCx, NS_ERROR_DOM_SECURITY_ERR);
  }

  if (name_struct->mType == nsGlobalNameStruct::eTypeNewDOMBinding) {
    ConstructNavigatorProperty construct = name_struct->mConstructNavigatorProperty;
    MOZ_ASSERT(construct);

    JS::Rooted<JSObject*> domObject(aCx);
    {
      // Make sure to do the creation of our object in the compartment
      // of naviObj, especially since we plan to cache that object.
      JSAutoCompartment ac(aCx, naviObj);

      // Check whether our constructor is enabled after we unwrap Xrays, since
      // we don't want to define an interface on the Xray if it's disabled in
      // the target global, even if it's enabled in the Xray's global.
      if (name_struct->mConstructorEnabled &&
          !(*name_struct->mConstructorEnabled)(aCx, naviObj)) {
        return true;
      }

      if (name.EqualsLiteral("mozSettings")) {
        bool hasPermission = CheckPermission("settings-api-read") ||
          CheckPermission("settings-api-write");
        if (!hasPermission) {
          FillPropertyDescriptor(aDesc, aObject, JS::NullValue(), false);
          return true;
        }
      }

      if (name.EqualsLiteral("mozDownloadManager")) {
        if (!CheckPermission("downloads")) {
          FillPropertyDescriptor(aDesc, aObject, JS::NullValue(), false);
          return true;
        }
      }

      nsISupports* existingObject = mCachedResolveResults.GetWeak(name);
      if (existingObject) {
        // We know all of our WebIDL objects here are wrappercached, so just go
        // ahead and WrapObject() them.  We can't use GetOrCreateDOMReflector,
        // because we don't have the concrete type.
        JS::Rooted<JS::Value> wrapped(aCx);
        if (!dom::WrapObject(aCx, existingObject, &wrapped)) {
          return false;
        }
        domObject = &wrapped.toObject();
      } else {
        domObject = construct(aCx, naviObj);
        if (!domObject) {
          return Throw(aCx, NS_ERROR_FAILURE);
        }

        // Store the value in our cache
        nsISupports* native = UnwrapDOMObjectToISupports(domObject);
        MOZ_ASSERT(native);
        mCachedResolveResults.Put(name, native);
      }
    }

    if (!JS_WrapObject(aCx, &domObject)) {
      return false;
    }

    FillPropertyDescriptor(aDesc, aObject, JS::ObjectValue(*domObject), false);
    return true;
  }

  NS_ASSERTION(name_struct->mType == nsGlobalNameStruct::eTypeNavigatorProperty,
               "unexpected type");

  nsresult rv = NS_OK;

  nsCOMPtr<nsISupports> native;
  bool hadCachedNative = mCachedResolveResults.Get(name, getter_AddRefs(native));
  bool okToUseNative;
  JS::Rooted<JS::Value> prop_val(aCx);
  if (hadCachedNative) {
    okToUseNative = true;
  } else {
    native = do_CreateInstance(name_struct->mCID, &rv);
    if (NS_FAILED(rv)) {
      return Throw(aCx, rv);
    }

    nsCOMPtr<nsIDOMGlobalPropertyInitializer> gpi(do_QueryInterface(native));

    if (gpi) {
      if (!mWindow) {
        return Throw(aCx, NS_ERROR_UNEXPECTED);
      }

      rv = gpi->Init(mWindow, &prop_val);
      if (NS_FAILED(rv)) {
        return Throw(aCx, rv);
      }
    }

    okToUseNative = !prop_val.isObjectOrNull();
  }

  if (okToUseNative) {
    // Make sure to do the creation of our object in the compartment
    // of naviObj, especially since we plan to cache that object.
    JSAutoCompartment ac(aCx, naviObj);

    rv = nsContentUtils::WrapNative(aCx, native, &prop_val);

    if (NS_FAILED(rv)) {
      return Throw(aCx, rv);
    }

    // Now that we know we managed to wrap this thing properly, go ahead and
    // cache it as needed.
    if (!hadCachedNative) {
      mCachedResolveResults.Put(name, native);
    }
  }

  if (!JS_WrapValue(aCx, &prop_val)) {
    return Throw(aCx, NS_ERROR_UNEXPECTED);
  }

  FillPropertyDescriptor(aDesc, aObject, prop_val, false);
  return true;
}

/* static */
bool
Navigator::MayResolve(jsid aId)
{
  // Note: This function does not fail and may not have any side-effects.
  // Note: Keep this in sync with DoResolve.
  if (!JSID_IS_STRING(aId)) {
    return false;
  }

  nsScriptNameSpaceManager *nameSpaceManager = PeekNameSpaceManager();
  if (!nameSpaceManager) {
    // Really shouldn't happen here.  Fail safe.
    return true;
  }

  nsAutoString name;
  AssignJSFlatString(name, JSID_TO_FLAT_STRING(aId));

  return nameSpaceManager->LookupNavigatorName(name);
}

void
Navigator::GetOwnPropertyNames(JSContext* aCx, nsTArray<nsString>& aNames,
                               ErrorResult& aRv)
{
  nsScriptNameSpaceManager *nameSpaceManager = GetNameSpaceManager();
  if (!nameSpaceManager) {
    NS_ERROR("Can't get namespace manager.");
    aRv.Throw(NS_ERROR_UNEXPECTED);
    return;
  }

  JS::Rooted<JSObject*> wrapper(aCx, GetWrapper());
  for (auto i = nameSpaceManager->NavigatorNameIter(); !i.Done(); i.Next()) {
    const GlobalNameMapEntry* entry = i.Get();
    if (!entry->mGlobalName.mConstructorEnabled ||
        entry->mGlobalName.mConstructorEnabled(aCx, wrapper)) {
      aNames.AppendElement(entry->mKey);
    }
  }
}

JSObject*
Navigator::WrapObject(JSContext* cx, JS::Handle<JSObject*> aGivenProto)
{
  return NavigatorBinding::Wrap(cx, this, aGivenProto);
}

/* static */
bool
Navigator::HasWakeLockSupport(JSContext* /* unused*/, JSObject* /*unused */)
{
  nsCOMPtr<nsIPowerManagerService> pmService =
    do_GetService(POWERMANAGERSERVICE_CONTRACTID);
  // No service means no wake lock support
  return !!pmService;
}

/* static */
bool
Navigator::HasCameraSupport(JSContext* /* unused */, JSObject* aGlobal)
{
  nsCOMPtr<nsPIDOMWindow> win = GetWindowFromGlobal(aGlobal);
  return win && nsDOMCameraManager::CheckPermission(win);
}

/* static */
bool
Navigator::HasWifiManagerSupport(JSContext* /* unused */,
                                 JSObject* aGlobal)
{
  // On XBL scope, the global object is NOT |window|. So we have
  // to use nsContentUtils::GetObjectPrincipal to get the principal
  // and test directly with permission manager.

  nsIPrincipal* principal = nsContentUtils::ObjectPrincipal(aGlobal);

  nsCOMPtr<nsIPermissionManager> permMgr =
    services::GetPermissionManager();
  NS_ENSURE_TRUE(permMgr, false);

  uint32_t permission = nsIPermissionManager::DENY_ACTION;
  permMgr->TestPermissionFromPrincipal(principal, "wifi-manage", &permission);
  return nsIPermissionManager::ALLOW_ACTION == permission;
}

#ifdef MOZ_NFC
/* static */
bool
Navigator::HasNFCSupport(JSContext* /* unused */, JSObject* aGlobal)
{
  nsCOMPtr<nsPIDOMWindow> win = GetWindowFromGlobal(aGlobal);

  // Do not support NFC if NFC content helper does not exist.
  nsCOMPtr<nsISupports> contentHelper = do_GetService("@mozilla.org/nfc/content-helper;1");
  return !!contentHelper;
}
#endif // MOZ_NFC

#ifdef MOZ_MEDIA_NAVIGATOR
/* static */
bool
Navigator::HasUserMediaSupport(JSContext* /* unused */,
                               JSObject* /* unused */)
{
  // Make enabling peerconnection enable getUserMedia() as well
  return Preferences::GetBool("media.navigator.enabled", false) ||
         Preferences::GetBool("media.peerconnection.enabled", false);
}
#endif // MOZ_MEDIA_NAVIGATOR

/* static */
bool
Navigator::HasDataStoreSupport(nsIPrincipal* aPrincipal)
{
  workers::AssertIsOnMainThread();

  return DataStoreService::CheckPermission(aPrincipal);
}

// A WorkerMainThreadRunnable to synchronously dispatch the call of
// HasDataStoreSupport() from the worker thread to the main thread.
class HasDataStoreSupportRunnable final
  : public workers::WorkerMainThreadRunnable
{
public:
  bool mResult;

  explicit HasDataStoreSupportRunnable(workers::WorkerPrivate* aWorkerPrivate)
    : workers::WorkerMainThreadRunnable(aWorkerPrivate)
    , mResult(false)
  {
    MOZ_ASSERT(aWorkerPrivate);
    aWorkerPrivate->AssertIsOnWorkerThread();
  }

protected:
  virtual bool
  MainThreadRun() override
  {
    workers::AssertIsOnMainThread();

    mResult = Navigator::HasDataStoreSupport(mWorkerPrivate->GetPrincipal());

    return true;
  }
};

/* static */
bool
Navigator::HasDataStoreSupport(JSContext* aCx, JSObject* aGlobal)
{
  // If the caller is on the worker thread, dispatch this to the main thread.
  if (!NS_IsMainThread()) {
    workers::WorkerPrivate* workerPrivate =
      workers::GetWorkerPrivateFromContext(aCx);
    workerPrivate->AssertIsOnWorkerThread();

    RefPtr<HasDataStoreSupportRunnable> runnable =
      new HasDataStoreSupportRunnable(workerPrivate);
    runnable->Dispatch(aCx);

    return runnable->mResult;
  }

  workers::AssertIsOnMainThread();

  JS::Rooted<JSObject*> global(aCx, aGlobal);

  nsCOMPtr<nsPIDOMWindow> win = GetWindowFromGlobal(global);
  if (!win) {
    return false;
  }

  nsIDocument* doc = win->GetExtantDoc();
  if (!doc || !doc->NodePrincipal()) {
    return false;
  }

  return HasDataStoreSupport(doc->NodePrincipal());
}

#ifdef MOZ_B2G
/* static */
bool
Navigator::HasMobileIdSupport(JSContext* aCx, JSObject* aGlobal)
{
  nsCOMPtr<nsPIDOMWindow> win = GetWindowFromGlobal(aGlobal);
  if (!win) {
    return false;
  }

  nsIDocument* doc = win->GetExtantDoc();
  if (!doc) {
    return false;
  }

  nsIPrincipal* principal = doc->NodePrincipal();

  nsCOMPtr<nsIPermissionManager> permMgr = services::GetPermissionManager();
  NS_ENSURE_TRUE(permMgr, false);

  uint32_t permission = nsIPermissionManager::UNKNOWN_ACTION;
  permMgr->TestPermissionFromPrincipal(principal, "mobileid", &permission);
  return permission == nsIPermissionManager::PROMPT_ACTION ||
         permission == nsIPermissionManager::ALLOW_ACTION;
}
#endif

/* static */
bool
Navigator::HasTVSupport(JSContext* aCx, JSObject* aGlobal)
{
  JS::Rooted<JSObject*> global(aCx, aGlobal);

  nsCOMPtr<nsPIDOMWindow> win = GetWindowFromGlobal(global);
  if (!win) {
    return false;
  }

  // Just for testing, we can enable TV for any kind of app.
  if (Preferences::GetBool("dom.testing.tv_enabled_for_hosted_apps", false)) {
    return true;
  }

  nsIDocument* doc = win->GetExtantDoc();
  if (!doc || !doc->NodePrincipal()) {
    return false;
  }

  nsIPrincipal* principal = doc->NodePrincipal();
  uint16_t status;
  if (NS_FAILED(principal->GetAppStatus(&status))) {
    return false;
  }

  // Only support TV Manager API for certified apps for now.
  return status == nsIPrincipal::APP_STATUS_CERTIFIED;
}

/* static */
bool
Navigator::HasPresentationSupport(JSContext* aCx, JSObject* aGlobal)
{
  JS::Rooted<JSObject*> global(aCx, aGlobal);

  nsCOMPtr<nsPIDOMWindow> win = GetWindowFromGlobal(global);
  if (NS_WARN_IF(!win)) {
    return false;
  }

  // Grant access if it has the permission.
  if (CheckPermission(win, "presentation")) {
    return true;
  }

  // Grant access to browser receiving pages and their same-origin iframes. (App
  // pages should be controlled by "presentation" permission in app manifests.)
  mozilla::dom::ContentChild* cc =
    mozilla::dom::ContentChild::GetSingleton();
  if (!cc || !cc->IsForBrowser()) {
    return false;
  }

  win = win->GetOuterWindow();
  nsCOMPtr<nsPIDOMWindow> top = win->GetTop();
  nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(win);
  nsCOMPtr<nsIScriptObjectPrincipal> topSop = do_QueryInterface(top);
  if (!sop || !topSop) {
    return false;
  }

  nsIPrincipal* principal = sop->GetPrincipal();
  nsIPrincipal* topPrincipal = topSop->GetPrincipal();
  if (!principal || !topPrincipal || !principal->Subsumes(topPrincipal)) {
    return false;
  }

  if (!(top = top->GetCurrentInnerWindow())) {
    return false;
  }

  nsCOMPtr<nsIPresentationService> presentationService =
    do_GetService(PRESENTATION_SERVICE_CONTRACTID);
  if (NS_WARN_IF(!presentationService)) {
    return false;
  }

  nsAutoString sessionId;
  presentationService->GetExistentSessionIdAtLaunch(top->WindowID(), sessionId);
  return !sessionId.IsEmpty();
}

/* static */
bool
Navigator::IsE10sEnabled(JSContext* aCx, JSObject* aGlobal)
{
  return XRE_IsContentProcess();
}

bool
Navigator::MozE10sEnabled()
{
  // This will only be called if IsE10sEnabled() is true.
  return true;
}

/* static */
already_AddRefed<nsPIDOMWindow>
Navigator::GetWindowFromGlobal(JSObject* aGlobal)
{
  nsCOMPtr<nsPIDOMWindow> win =
    do_QueryInterface(nsJSUtils::GetStaticScriptGlobal(aGlobal));
  MOZ_ASSERT(!win || win->IsInnerWindow());
  return win.forget();
}

nsresult
Navigator::GetPlatform(nsAString& aPlatform, bool aUsePrefOverriddenValue)
{
  MOZ_ASSERT(NS_IsMainThread());

  if (aUsePrefOverriddenValue && !nsContentUtils::IsCallerChrome()) {
    const nsAdoptingString& override =
      mozilla::Preferences::GetString("general.platform.override");

    if (override) {
      aPlatform = override;
      return NS_OK;
    }
  }

  nsresult rv;

  nsCOMPtr<nsIHttpProtocolHandler>
    service(do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http", &rv));
  NS_ENSURE_SUCCESS(rv, rv);

  // Sorry for the #if platform ugliness, but Communicator is likewise
  // hardcoded and we are seeking backward compatibility here (bug 47080).
#if defined(_WIN64)
  aPlatform.AssignLiteral("Win64");
#elif defined(WIN32)
  aPlatform.AssignLiteral("Win32");
#elif defined(XP_MACOSX) && defined(__ppc__)
  aPlatform.AssignLiteral("MacPPC");
#elif defined(XP_MACOSX) && defined(__i386__)
  aPlatform.AssignLiteral("MacIntel");
#elif defined(XP_MACOSX) && defined(__x86_64__)
  aPlatform.AssignLiteral("MacIntel");
#else
  // XXX Communicator uses compiled-in build-time string defines
  // to indicate the platform it was compiled *for*, not what it is
  // currently running *on* which is what this does.
  nsAutoCString plat;
  rv = service->GetOscpu(plat);
  CopyASCIItoUTF16(plat, aPlatform);
#endif

  return rv;
}

/* static */ nsresult
Navigator::GetAppVersion(nsAString& aAppVersion, bool aUsePrefOverriddenValue)
{
  MOZ_ASSERT(NS_IsMainThread());

  if (aUsePrefOverriddenValue && !nsContentUtils::IsCallerChrome()) {
    const nsAdoptingString& override =
      mozilla::Preferences::GetString("general.appversion.override");

    if (override) {
      aAppVersion = override;
      return NS_OK;
    }
  }

  nsresult rv;

  nsCOMPtr<nsIHttpProtocolHandler>
    service(do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http", &rv));
  NS_ENSURE_SUCCESS(rv, rv);

  nsAutoCString str;
  rv = service->GetAppVersion(str);
  CopyASCIItoUTF16(str, aAppVersion);
  NS_ENSURE_SUCCESS(rv, rv);

  aAppVersion.AppendLiteral(" (");

  rv = service->GetPlatform(str);
  NS_ENSURE_SUCCESS(rv, rv);

  AppendASCIItoUTF16(str, aAppVersion);
  aAppVersion.Append(char16_t(')'));

  return rv;
}

/* static */ void
Navigator::AppName(nsAString& aAppName, bool aUsePrefOverriddenValue)
{
  MOZ_ASSERT(NS_IsMainThread());

  if (aUsePrefOverriddenValue && !nsContentUtils::IsCallerChrome()) {
    const nsAdoptingString& override =
      mozilla::Preferences::GetString("general.appname.override");

    if (override) {
      aAppName = override;
      return;
    }
  }

  aAppName.AssignLiteral("Netscape");
}

nsresult
Navigator::GetUserAgent(nsPIDOMWindow* aWindow, nsIURI* aURI,
                        bool aIsCallerChrome,
                        nsAString& aUserAgent)
{
  MOZ_ASSERT(NS_IsMainThread());

  if (!aIsCallerChrome) {
    const nsAdoptingString& override =
      mozilla::Preferences::GetString("general.useragent.override");

    if (override) {
      aUserAgent = override;
      return NS_OK;
    }
  }

  nsresult rv;
  nsCOMPtr<nsIHttpProtocolHandler>
    service(do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http", &rv));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  nsAutoCString ua;
  rv = service->GetUserAgent(ua);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  CopyASCIItoUTF16(ua, aUserAgent);

  if (!aWindow || !aURI) {
    return NS_OK;
  }

  MOZ_ASSERT(aWindow->GetDocShell());

  nsCOMPtr<nsISiteSpecificUserAgent> siteSpecificUA =
    do_GetService("@mozilla.org/dom/site-specific-user-agent;1");
  if (!siteSpecificUA) {
    return NS_OK;
  }

  return siteSpecificUA->GetUserAgentForURIAndWindow(aURI, aWindow, aUserAgent);
}

#ifdef MOZ_EME
static nsCString
ToCString(const nsString& aString)
{
  return NS_ConvertUTF16toUTF8(aString);
}

static nsCString
ToCString(const MediaKeySystemMediaCapability& aValue)
{
  nsCString str;
  str.AppendLiteral("{contentType='");
  if (!aValue.mContentType.IsEmpty()) {
    str.Append(ToCString(aValue.mContentType));
  }
  str.AppendLiteral("'}");
  return str;
}

template<class Type>
static nsCString
ToCString(const Sequence<Type>& aSequence)
{
  nsCString s;
  s.AppendLiteral("[");
  for (size_t i = 0; i < aSequence.Length(); i++) {
    if (i != 0) {
      s.AppendLiteral(",");
    }
    s.Append(ToCString(aSequence[i]));
  }
  s.AppendLiteral("]");
  return s;
}

static nsCString
ToCString(const MediaKeySystemConfiguration& aConfig)
{
  nsCString str;
  str.AppendLiteral("{");
  str.AppendPrintf("label='%s'", NS_ConvertUTF16toUTF8(aConfig.mLabel).get());

  if (aConfig.mInitDataTypes.WasPassed()) {
    str.AppendLiteral(", initDataTypes=");
    str.Append(ToCString(aConfig.mInitDataTypes.Value()));
  }

  if (aConfig.mAudioCapabilities.WasPassed()) {
    str.AppendLiteral(", audioCapabilities=");
    str.Append(ToCString(aConfig.mAudioCapabilities.Value()));
  }
  if (aConfig.mVideoCapabilities.WasPassed()) {
    str.AppendLiteral(", videoCapabilities=");
    str.Append(ToCString(aConfig.mVideoCapabilities.Value()));
  }

  if (!aConfig.mAudioType.IsEmpty()) {
    str.AppendPrintf(", audioType='%s'",
      NS_ConvertUTF16toUTF8(aConfig.mAudioType).get());
  }
  if (!aConfig.mInitDataType.IsEmpty()) {
    str.AppendPrintf(", initDataType='%s'",
      NS_ConvertUTF16toUTF8(aConfig.mInitDataType).get());
  }
  if (!aConfig.mVideoType.IsEmpty()) {
    str.AppendPrintf(", videoType='%s'",
      NS_ConvertUTF16toUTF8(aConfig.mVideoType).get());
  }
  str.AppendLiteral("}");

  return str;
}

static nsCString
RequestKeySystemAccessLogString(const nsAString& aKeySystem,
                                const Sequence<MediaKeySystemConfiguration>& aConfigs)
{
  nsCString str;
  str.AppendPrintf("Navigator::RequestMediaKeySystemAccess(keySystem='%s' options=",
                   NS_ConvertUTF16toUTF8(aKeySystem).get());
  str.Append(ToCString(aConfigs));
  str.AppendLiteral(")");
  return str;
}

already_AddRefed<Promise>
Navigator::RequestMediaKeySystemAccess(const nsAString& aKeySystem,
                                       const Sequence<MediaKeySystemConfiguration>& aConfigs,
                                       ErrorResult& aRv)
{
  EME_LOG("%s", RequestKeySystemAccessLogString(aKeySystem, aConfigs).get());

  nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(mWindow);
  RefPtr<DetailedPromise> promise =
    DetailedPromise::Create(go, aRv,
      NS_LITERAL_CSTRING("navigator.requestMediaKeySystemAccess"),
      Telemetry::VIDEO_EME_REQUEST_SUCCESS_LATENCY_MS,
      Telemetry::VIDEO_EME_REQUEST_FAILURE_LATENCY_MS);
  if (aRv.Failed()) {
    return nullptr;
  }

  if (!mMediaKeySystemAccessManager) {
    mMediaKeySystemAccessManager = new MediaKeySystemAccessManager(mWindow);
  }

  mMediaKeySystemAccessManager->Request(promise, aKeySystem, aConfigs);
  return promise.forget();
}
#endif

Presentation*
Navigator::GetPresentation(ErrorResult& aRv)
{
  if (!mPresentation) {
    if (!mWindow) {
      aRv.Throw(NS_ERROR_UNEXPECTED);
      return nullptr;
    }
    mPresentation = Presentation::Create(mWindow);
  }

  return mPresentation;
}

} // namespace dom
} // namespace mozilla
