diff options
Diffstat (limited to '')
-rw-r--r-- | dom/base/Navigator.cpp | 2316 |
1 files changed, 2316 insertions, 0 deletions
diff --git a/dom/base/Navigator.cpp b/dom/base/Navigator.cpp new file mode 100644 index 0000000000..151d992d6f --- /dev/null +++ b/dom/base/Navigator.cpp @@ -0,0 +1,2316 @@ +/* -*- 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/Components.h" +#include "mozilla/ContentBlockingNotifier.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/dom/BodyExtractor.h" +#include "mozilla/dom/FetchBinding.h" +#include "mozilla/dom/File.h" +#include "Geolocation.h" +#include "nsIClassOfService.h" +#include "nsIHttpProtocolHandler.h" +#include "nsIContentPolicy.h" +#include "nsContentPolicyUtils.h" +#include "nsISupportsPriority.h" +#include "nsIWebProtocolHandlerRegistrar.h" +#include "nsCharSeparatedTokenizer.h" +#include "nsContentUtils.h" +#include "nsUnicharUtils.h" +#include "mozilla/Preferences.h" +#include "mozilla/StaticPrefs_dom.h" +#ifdef FUZZING +# include "mozilla/StaticPrefs_fuzzing.h" +#endif +#include "mozilla/StaticPrefs_media.h" +#include "mozilla/StaticPrefs_network.h" +#include "mozilla/StaticPrefs_pdfjs.h" +#include "mozilla/StaticPrefs_privacy.h" +#include "mozilla/StorageAccess.h" +#include "mozilla/Telemetry.h" +#include "BatteryManager.h" +#include "mozilla/dom/CredentialsContainer.h" +#include "mozilla/dom/Clipboard.h" +#include "mozilla/dom/FeaturePolicyUtils.h" +#include "mozilla/dom/GamepadServiceTest.h" +#include "mozilla/dom/MediaCapabilities.h" +#include "mozilla/dom/MediaSession.h" +#include "mozilla/dom/power/PowerManagerService.h" +#include "mozilla/dom/LockManager.h" +#include "mozilla/dom/MIDIAccessManager.h" +#include "mozilla/dom/MIDIOptionsBinding.h" +#include "mozilla/dom/Permissions.h" +#include "mozilla/dom/ServiceWorkerContainer.h" +#include "mozilla/dom/StorageManager.h" +#include "mozilla/dom/TCPSocket.h" +#include "mozilla/dom/URLSearchParams.h" +#include "mozilla/dom/VRDisplay.h" +#include "mozilla/dom/VRDisplayEvent.h" +#include "mozilla/dom/VRServiceTest.h" +#include "mozilla/dom/XRSystem.h" +#include "mozilla/dom/workerinternals/RuntimeService.h" +#include "mozilla/Hal.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/StaticPtr.h" +#include "Connection.h" +#include "mozilla/dom/Event.h" // for Event +#include "nsGlobalWindow.h" +#include "nsIPermissionManager.h" +#include "nsMimeTypes.h" +#include "nsNetUtil.h" +#include "nsRFPService.h" +#include "nsStringStream.h" +#include "nsComponentManagerUtils.h" +#include "nsICookieManager.h" +#include "nsICookieService.h" +#include "nsIHttpChannel.h" +#ifdef ENABLE_WEBDRIVER +# include "nsIMarionette.h" +# include "nsIRemoteAgent.h" +#endif +#include "nsStreamUtils.h" +#include "WidgetUtils.h" +#include "nsIScriptError.h" +#include "ReferrerInfo.h" +#include "mozilla/PermissionDelegateHandler.h" + +#include "nsIExternalProtocolHandler.h" +#include "BrowserChild.h" +#include "mozilla/ipc/URIUtils.h" + +#include "mozilla/dom/MediaDevices.h" +#include "MediaManager.h" + +#include "nsJSUtils.h" + +#include "mozilla/dom/Promise.h" + +#include "nsIUploadChannel2.h" +#include "mozilla/dom/FormData.h" +#include "nsIDocShell.h" + +#include "mozilla/dom/WorkerPrivate.h" +#include "mozilla/dom/WorkerRunnable.h" + +#if defined(XP_WIN) +# include "mozilla/WindowsVersion.h" +#endif + +#include "mozilla/EMEUtils.h" +#include "mozilla/DetailedPromise.h" +#include "mozilla/Unused.h" + +#include "mozilla/webgpu/Instance.h" +#include "mozilla/dom/WindowGlobalChild.h" + +#include "mozilla/intl/LocaleService.h" +#include "mozilla/dom/AudioContext.h" +#include "mozilla/dom/HTMLMediaElement.h" +#include "AutoplayPolicy.h" + +namespace mozilla::dom { + +static const nsLiteralCString kVibrationPermissionType = "vibration"_ns; + +Navigator::Navigator(nsPIDOMWindowInner* aWindow) : mWindow(aWindow) {} + +Navigator::~Navigator() { Invalidate(); } + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Navigator) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(Navigator) +NS_IMPL_CYCLE_COLLECTING_RELEASE(Navigator) + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(Navigator) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Navigator) + tmp->Invalidate(); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mSharePromise) + 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(mPlugins) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPermissions) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGeolocation) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBatteryManager) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBatteryPromise) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConnection) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStorageManager) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCredentials) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaDevices) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mServiceWorkerContainer) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaCapabilities) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaSession) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAddonManager) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWebGpu) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocks) + + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaKeySystemAccessManager) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGamepadServiceTest) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVRGetDisplaysPromises) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVRServiceTest) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSharePromise) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mXRSystem) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +void Navigator::Invalidate() { + // Don't clear mWindow here so we know we've got a non-null mWindow + // until we're unlinked. + + mPlugins = nullptr; + + mPermissions = nullptr; + + if (mStorageManager) { + mStorageManager->Shutdown(); + mStorageManager = nullptr; + } + + // If there is a page transition, make sure delete the geolocation object. + if (mGeolocation) { + mGeolocation->Shutdown(); + mGeolocation = nullptr; + } + + if (mBatteryManager) { + mBatteryManager->Shutdown(); + mBatteryManager = nullptr; + } + + mBatteryPromise = nullptr; + + if (mConnection) { + mConnection->Shutdown(); + mConnection = nullptr; + } + + mMediaDevices = nullptr; + + mServiceWorkerContainer = nullptr; + + if (mMediaKeySystemAccessManager) { + mMediaKeySystemAccessManager->Shutdown(); + mMediaKeySystemAccessManager = nullptr; + } + + if (mGamepadServiceTest) { + mGamepadServiceTest->Shutdown(); + mGamepadServiceTest = nullptr; + } + + mVRGetDisplaysPromises.Clear(); + + if (mVRServiceTest) { + mVRServiceTest->Shutdown(); + mVRServiceTest = nullptr; + } + + if (mXRSystem) { + mXRSystem->Shutdown(); + mXRSystem = nullptr; + } + + mMediaCapabilities = nullptr; + + if (mMediaSession) { + mMediaSession->Shutdown(); + mMediaSession = nullptr; + } + + mAddonManager = nullptr; + + mWebGpu = nullptr; + + if (mLocks) { + // Unloading a page does not immediately destruct the lock manager actor, + // but we want to abort the lock requests as soon as possible. Explicitly + // call Shutdown() to do that. + mLocks->Shutdown(); + mLocks = nullptr; + } + + mSharePromise = nullptr; +} + +void Navigator::GetUserAgent(nsAString& aUserAgent, CallerType aCallerType, + ErrorResult& aRv) const { + nsCOMPtr<nsPIDOMWindowInner> window; + + if (mWindow) { + window = mWindow; + nsIDocShell* docshell = window->GetDocShell(); + nsString customUserAgent; + if (docshell) { + docshell->GetBrowsingContext()->GetCustomUserAgent(customUserAgent); + + if (!customUserAgent.IsEmpty()) { + aUserAgent = customUserAgent; + return; + } + } + } + + nsCOMPtr<Document> doc = mWindow->GetExtantDoc(); + nsresult rv = GetUserAgent( + mWindow, doc, aCallerType == CallerType::System ? Some(false) : Nothing(), + aUserAgent); + if (NS_WARN_IF(NS_FAILED(rv))) { + aRv.Throw(rv); + } +} + +void Navigator::GetAppCodeName(nsAString& aAppCodeName, ErrorResult& aRv) { + nsresult rv; + + nsCOMPtr<nsIHttpProtocolHandler> service( + do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http", &rv)); + if (NS_WARN_IF(NS_FAILED(rv))) { + aRv.Throw(rv); + return; + } + + nsAutoCString appName; + rv = service->GetAppName(appName); + if (NS_WARN_IF(NS_FAILED(rv))) { + aRv.Throw(rv); + return; + } + + CopyASCIItoUTF16(appName, aAppCodeName); +} + +void Navigator::GetAppVersion(nsAString& aAppVersion, CallerType aCallerType, + ErrorResult& aRv) const { + nsCOMPtr<Document> doc = mWindow->GetExtantDoc(); + + nsresult rv = GetAppVersion( + aAppVersion, doc, + /* aUsePrefOverriddenValue = */ aCallerType != CallerType::System); + if (NS_WARN_IF(NS_FAILED(rv))) { + aRv.Throw(rv); + } +} + +void Navigator::GetAppName(nsAString& aAppName, CallerType aCallerType) const { + nsCOMPtr<Document> doc = mWindow->GetExtantDoc(); + + AppName(aAppName, doc, + /* aUsePrefOverriddenValue = */ aCallerType != CallerType::System); +} + +/** + * 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. + * + * If there is no valid language, the value of getWebExposedLocales is + * used to ensure that locale spoofing is honored and to reduce + * fingerprinting. + * + * See RFC 7231, Section 9.7 "Browser Fingerprinting" and + * RFC 2616, Section 15.1.4 "Privacy Issues Connected to Accept Headers" + * for more detail. + */ +/* static */ +void Navigator::GetAcceptLanguages(nsTArray<nsString>& aLanguages) { + MOZ_ASSERT(NS_IsMainThread()); + + aLanguages.Clear(); + + // E.g. "de-de, en-us,en". + nsAutoString acceptLang; + Preferences::GetLocalizedString("intl.accept_languages", acceptLang); + + // Split values on commas. + for (nsDependentSubstring lang : + nsCharSeparatedTokenizer(acceptLang, ',').ToRange()) { + // 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) { + int32_t pos = 0; + bool first = true; + for (const nsAString& code : + nsCharSeparatedTokenizer(lang, '-').ToRange()) { + 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); + } + if (aLanguages.Length() == 0) { + nsTArray<nsCString> locales; + mozilla::intl::LocaleService::GetInstance()->GetWebExposedLocales(locales); + aLanguages.AppendElement(NS_ConvertUTF8toUTF16(locales[0])); + } +} + +/** + * Returns the first language from GetAcceptLanguages. + * + * Full details above in GetAcceptLanguages. + */ +void Navigator::GetLanguage(nsAString& aLanguage) { + nsTArray<nsString> languages; + GetLanguages(languages); + MOZ_ASSERT(languages.Length() >= 1); + aLanguage.Assign(languages[0]); +} + +void Navigator::GetLanguages(nsTArray<nsString>& aLanguages) { + GetAcceptLanguages(aLanguages); + + // The returned value is cached by the binding code. The window listens 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. +} + +void Navigator::GetPlatform(nsAString& aPlatform, CallerType aCallerType, + ErrorResult& aRv) const { + if (mWindow) { + BrowsingContext* bc = mWindow->GetBrowsingContext(); + nsString customPlatform; + if (bc) { + bc->GetCustomPlatform(customPlatform); + + if (!customPlatform.IsEmpty()) { + aPlatform = customPlatform; + return; + } + } + } + + nsCOMPtr<Document> doc = mWindow->GetExtantDoc(); + + nsresult rv = GetPlatform( + aPlatform, doc, + /* aUsePrefOverriddenValue = */ aCallerType != CallerType::System); + if (NS_WARN_IF(NS_FAILED(rv))) { + aRv.Throw(rv); + } +} + +void Navigator::GetOscpu(nsAString& aOSCPU, CallerType aCallerType, + ErrorResult& aRv) const { + if (aCallerType != CallerType::System) { + // If fingerprinting resistance is on, we will spoof this value. See + // nsRFPService.h for details about spoofed values. + if (nsContentUtils::ShouldResistFingerprinting(GetDocShell(), + RFPTarget::NavigatorOscpu)) { + aOSCPU.AssignLiteral(SPOOFED_OSCPU); + return; + } + + nsAutoString override; + nsresult rv = Preferences::GetString("general.oscpu.override", override); + if (NS_SUCCEEDED(rv)) { + aOSCPU = override; + return; + } + } + + nsresult rv; + nsCOMPtr<nsIHttpProtocolHandler> service( + do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http", &rv)); + if (NS_WARN_IF(NS_FAILED(rv))) { + aRv.Throw(rv); + return; + } + + nsAutoCString oscpu; + rv = service->GetOscpu(oscpu); + if (NS_WARN_IF(NS_FAILED(rv))) { + aRv.Throw(rv); + return; + } + + CopyASCIItoUTF16(oscpu, aOSCPU); +} + +void Navigator::GetVendor(nsAString& aVendor) { aVendor.Truncate(); } + +void Navigator::GetVendorSub(nsAString& aVendorSub) { aVendorSub.Truncate(); } + +void Navigator::GetProduct(nsAString& aProduct) { + aProduct.AssignLiteral("Gecko"); +} + +void Navigator::GetProductSub(nsAString& aProductSub) { + // Legacy build date hardcoded for backward compatibility (bug 776376) + aProductSub.AssignLiteral(LEGACY_UA_GECKO_TRAIL); +} + +nsMimeTypeArray* Navigator::GetMimeTypes(ErrorResult& aRv) { + auto* plugins = GetPlugins(aRv); + if (!plugins) { + return nullptr; + } + + return plugins->MimeTypeArray(); +} + +nsPluginArray* Navigator::GetPlugins(ErrorResult& aRv) { + if (!mPlugins) { + if (!mWindow) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } + mPlugins = MakeRefPtr<nsPluginArray>(mWindow); + } + + return mPlugins; +} + +bool Navigator::PdfViewerEnabled() { + // We ignore pdfjs.disabled when resisting fingerprinting. + // See bug 1756280 for an explanation. + return !StaticPrefs::pdfjs_disabled() || + nsContentUtils::ShouldResistFingerprinting(GetDocShell(), + RFPTarget::Unknown); +} + +Permissions* Navigator::GetPermissions(ErrorResult& aRv) { + if (!mWindow) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } + + if (!mPermissions) { + mPermissions = new Permissions(mWindow); + } + + return mPermissions; +} + +StorageManager* Navigator::Storage() { + MOZ_ASSERT(mWindow); + + if (!mStorageManager) { + mStorageManager = new StorageManager(mWindow->AsGlobal()); + } + + return mStorageManager; +} + +bool Navigator::CookieEnabled() { + // 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 nsICookieManager::GetCookieBehavior(false) != + nsICookieService::BEHAVIOR_REJECT; + } + + nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(mWindow); + uint32_t cookieBehavior = loadContext + ? nsICookieManager::GetCookieBehavior( + loadContext->UsePrivateBrowsing()) + : nsICookieManager::GetCookieBehavior(false); + bool cookieEnabled = cookieBehavior != nsICookieService::BEHAVIOR_REJECT; + + nsCOMPtr<Document> doc = mWindow->GetExtantDoc(); + if (!doc) { + return cookieEnabled; + } + + uint32_t rejectedReason = 0; + bool granted = false; + nsresult rv = doc->NodePrincipal()->HasFirstpartyStorageAccess( + mWindow, &rejectedReason, &granted); + if (NS_FAILED(rv)) { + // Not a content, so technically can't set cookies, but let's + // just return the default value. + return cookieEnabled; + } + + // We should return true if the cookie is partitioned because the cookie is + // still available in this case. + if (!granted && + StoragePartitioningEnabled(rejectedReason, doc->CookieJarSettings())) { + granted = true; + } + + ContentBlockingNotifier::OnDecision( + mWindow, + granted ? ContentBlockingNotifier::BlockingDecision::eAllow + : ContentBlockingNotifier::BlockingDecision::eBlock, + rejectedReason); + return granted; +} + +bool Navigator::OnLine() { return !NS_IsOffline(); } + +void Navigator::GetBuildID(nsAString& aBuildID, CallerType aCallerType, + ErrorResult& aRv) const { + if (aCallerType != CallerType::System) { + // If fingerprinting resistance is on, we will spoof this value. See + // nsRFPService.h for details about spoofed values. + if (nsContentUtils::ShouldResistFingerprinting( + GetDocShell(), RFPTarget::NavigatorBuildID)) { + aBuildID.AssignLiteral(LEGACY_BUILD_ID); + return; + } + + nsAutoString override; + nsresult rv = Preferences::GetString("general.buildID.override", override); + if (NS_SUCCEEDED(rv)) { + aBuildID = override; + return; + } + + nsAutoCString host; + bool isHTTPS = false; + if (mWindow) { + nsCOMPtr<Document> doc = mWindow->GetDoc(); + if (doc) { + nsIURI* uri = doc->GetDocumentURI(); + if (uri) { + isHTTPS = uri->SchemeIs("https"); + if (isHTTPS) { + MOZ_ALWAYS_SUCCEEDS(uri->GetHost(host)); + } + } + } + } + + // Spoof the buildID on pages not loaded from "https://*.mozilla.org". + if (!isHTTPS || !StringEndsWith(host, ".mozilla.org"_ns)) { + aBuildID.AssignLiteral(LEGACY_BUILD_ID); + return; + } + } + + nsCOMPtr<nsIXULAppInfo> appInfo = + do_GetService("@mozilla.org/xre/app-info;1"); + if (!appInfo) { + aRv.Throw(NS_ERROR_NOT_IMPLEMENTED); + return; + } + + nsAutoCString buildID; + nsresult rv = appInfo->GetAppBuildID(buildID); + if (NS_WARN_IF(NS_FAILED(rv))) { + aRv.Throw(rv); + return; + } + + aBuildID.Truncate(); + AppendASCIItoUTF16(buildID, aBuildID); +} + +void Navigator::GetDoNotTrack(nsAString& aResult) { + bool doNotTrack = StaticPrefs::privacy_donottrackheader_enabled(); + if (!doNotTrack) { + nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(mWindow); + doNotTrack = loadContext && loadContext->UseTrackingProtection(); + } + + if (doNotTrack) { + aResult.AssignLiteral("1"); + } else { + aResult.AssignLiteral("unspecified"); + } +} + +bool Navigator::GlobalPrivacyControl() { + return StaticPrefs::privacy_globalprivacycontrol_enabled() && + StaticPrefs::privacy_globalprivacycontrol_functionality_enabled(); +} + +uint64_t Navigator::HardwareConcurrency() { + workerinternals::RuntimeService* rts = + workerinternals::RuntimeService::GetOrCreateService(); + if (!rts) { + return 1; + } + + return rts->ClampedHardwareConcurrency( + nsGlobalWindowInner::Cast(mWindow)->ShouldResistFingerprinting( + RFPTarget::NavigatorHWConcurrency)); +} + +namespace { + +class VibrateWindowListener : public nsIDOMEventListener { + public: + VibrateWindowListener(nsPIDOMWindowInner* aWindow, Document* aDocument) { + mWindow = do_GetWeakReference(aWindow); + mDocument = do_GetWeakReference(aDocument); + + constexpr auto visibilitychange = u"visibilitychange"_ns; + aDocument->AddSystemEventListener(visibilitychange, this, /* listener */ + true, /* use capture */ + false /* wants untrusted */); + } + + void RemoveListener(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMEVENTLISTENER + + private: + virtual ~VibrateWindowListener() = default; + + nsWeakPtr mWindow; + nsWeakPtr mDocument; +}; + +NS_IMPL_ISUPPORTS(VibrateWindowListener, nsIDOMEventListener) + +StaticRefPtr<VibrateWindowListener> gVibrateWindowListener; + +static bool MayVibrate(Document* doc) { + // Hidden documents cannot start or stop a vibration. + return (doc && !doc->Hidden()); +} + +NS_IMETHODIMP +VibrateWindowListener::HandleEvent(Event* aEvent) { + nsCOMPtr<Document> doc = do_QueryInterface(aEvent->GetTarget()); + + if (!MayVibrate(doc)) { + // 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<nsPIDOMWindowInner> 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; + } + constexpr auto visibilitychange = u"visibilitychange"_ns; + target->RemoveSystemEventListener(visibilitychange, this, + true /* use capture */); +} + +} // namespace + +void Navigator::SetVibrationPermission(bool aPermitted, bool aPersistent) { + MOZ_ASSERT(NS_IsMainThread()); + + nsTArray<uint32_t> pattern = std::move(mRequestedVibrationPattern); + + if (!mWindow) { + return; + } + + nsCOMPtr<Document> doc = mWindow->GetExtantDoc(); + + if (!MayVibrate(doc)) { + return; + } + + if (aPermitted) { + // 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); + } + + if (aPersistent) { + nsCOMPtr<nsIPermissionManager> permMgr = + components::PermissionManager::Service(); + if (!permMgr) { + return; + } + permMgr->AddFromPrincipal(doc->NodePrincipal(), kVibrationPermissionType, + aPermitted ? nsIPermissionManager::ALLOW_ACTION + : nsIPermissionManager::DENY_ACTION, + nsIPermissionManager::EXPIRE_SESSION, 0); + } +} + +bool Navigator::Vibrate(uint32_t aDuration) { + AutoTArray<uint32_t, 1> pattern; + pattern.AppendElement(aDuration); + return Vibrate(pattern); +} + +nsTArray<uint32_t> SanitizeVibratePattern(const nsTArray<uint32_t>& aPattern) { + nsTArray<uint32_t> pattern(aPattern.Clone()); + + if (pattern.Length() > StaticPrefs::dom_vibrator_max_vibrate_list_len()) { + pattern.SetLength(StaticPrefs::dom_vibrator_max_vibrate_list_len()); + } + + for (size_t i = 0; i < pattern.Length(); ++i) { + pattern[i] = + std::min(StaticPrefs::dom_vibrator_max_vibrate_ms(), pattern[i]); + } + + return pattern; +} + +bool Navigator::Vibrate(const nsTArray<uint32_t>& aPattern) { + MOZ_ASSERT(NS_IsMainThread()); + + if (!mWindow) { + return false; + } + + nsCOMPtr<Document> doc = mWindow->GetExtantDoc(); + + if (!MayVibrate(doc)) { + return false; + } + + nsTArray<uint32_t> pattern = SanitizeVibratePattern(aPattern); + + // The spec says we check dom.vibrator.enabled after we've done the sanity + // checking on the pattern. + if (!StaticPrefs::dom_vibrator_enabled()) { + return true; + } + + mRequestedVibrationPattern = std::move(pattern); + + PermissionDelegateHandler* permissionHandler = + doc->GetPermissionDelegateHandler(); + if (NS_WARN_IF(!permissionHandler)) { + return false; + } + + uint32_t permission = nsIPermissionManager::UNKNOWN_ACTION; + + permissionHandler->GetPermission(kVibrationPermissionType, &permission, + false); + + if (permission == nsIPermissionManager::DENY_ACTION) { + // Abort without observer service or on denied session permission. + SetVibrationPermission(false /* permitted */, false /* persistent */); + return false; + } + + if (permission == nsIPermissionManager::ALLOW_ACTION || + mRequestedVibrationPattern.IsEmpty() || + (mRequestedVibrationPattern.Length() == 1 && + mRequestedVibrationPattern[0] == 0)) { + // Always allow cancelling vibration and respect session permissions. + SetVibrationPermission(true /* permitted */, false /* persistent */); + return true; + } + + // Request user permission. + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (!obs) { + return true; + } + + obs->NotifyObservers(ToSupports(this), "Vibration:Request", nullptr); + + return true; +} + +//***************************************************************************** +// Pointer Events interface +//***************************************************************************** + +uint32_t Navigator::MaxTouchPoints(CallerType aCallerType) { + nsIDocShell* docshell = GetDocShell(); + BrowsingContext* bc = docshell ? docshell->GetBrowsingContext() : nullptr; + + // Responsive Design Mode overrides the maxTouchPoints property when + // touch simulation is enabled. + if (bc && bc->InRDMPane()) { + return bc->GetMaxTouchPointsOverride(); + } + + // The maxTouchPoints is going to reveal the detail of users' hardware. So, + // we will spoof it into 0 if fingerprinting resistance is on. + if (aCallerType != CallerType::System && + nsContentUtils::ShouldResistFingerprinting(GetDocShell(), + RFPTarget::Unknown)) { + return 0; + } + + nsCOMPtr<nsIWidget> widget = + widget::WidgetUtils::DOMWindowToWidget(mWindow->GetOuterWindow()); + + NS_ENSURE_TRUE(widget, 0); + return widget->GetMaxTouchPoints(); +} + +//***************************************************************************** +// Navigator::nsIDOMClientInformation +//***************************************************************************** + +// This list should be kept up-to-date with the spec: +// https://html.spec.whatwg.org/multipage/system-state.html#custom-handlers +// If you change this list, please also update the copy in E10SUtils.sys.mjs. +static const char* const kSafeSchemes[] = { + // clang-format off + "bitcoin", + "ftp", + "ftps", + "geo", + "im", + "irc", + "ircs", + "magnet", + "mailto", + "matrix", + "mms", + "news", + "nntp", + "openpgp4fpr", + "sftp", + "sip", + "sms", + "smsto", + "ssh", + "tel", + "urn", + "webcal", + "wtai", + "xmpp", + // clang-format on +}; + +void Navigator::CheckProtocolHandlerAllowed(const nsAString& aScheme, + nsIURI* aHandlerURI, + nsIURI* aDocumentURI, + ErrorResult& aRv) { + auto raisePermissionDeniedHandler = [&] { + nsAutoCString spec; + aHandlerURI->GetSpec(spec); + nsPrintfCString message("Permission denied to add %s as a protocol handler", + spec.get()); + aRv.ThrowSecurityError(message); + }; + + auto raisePermissionDeniedScheme = [&] { + nsPrintfCString message( + "Permission denied to add a protocol handler for %s", + NS_ConvertUTF16toUTF8(aScheme).get()); + aRv.ThrowSecurityError(message); + }; + + if (!aDocumentURI || !aHandlerURI) { + aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); + return; + } + + nsCString spec; + aHandlerURI->GetSpec(spec); + // If the uri doesn't contain '%s', it won't be a good handler - the %s + // gets replaced with the handled URI. + if (!FindInReadable("%s"_ns, spec)) { + aRv.ThrowSyntaxError("Handler URI does not contain \"%s\"."); + return; + } + + // For security reasons we reject non-http(s) urls (see bug 354316), + nsAutoCString docScheme; + nsAutoCString handlerScheme; + aDocumentURI->GetScheme(docScheme); + aHandlerURI->GetScheme(handlerScheme); + if ((!docScheme.EqualsLiteral("https") && !docScheme.EqualsLiteral("http")) || + (!handlerScheme.EqualsLiteral("https") && + !handlerScheme.EqualsLiteral("http"))) { + raisePermissionDeniedHandler(); + return; + } + + // Should be same-origin: + nsAutoCString handlerHost; + aHandlerURI->GetHostPort(handlerHost); + nsAutoCString documentHost; + aDocumentURI->GetHostPort(documentHost); + if (!handlerHost.Equals(documentHost) || !handlerScheme.Equals(docScheme)) { + raisePermissionDeniedHandler(); + return; + } + + // Having checked the handler URI, check the scheme: + nsAutoCString scheme; + ToLowerCase(NS_ConvertUTF16toUTF8(aScheme), scheme); + if (StringBeginsWith(scheme, "web+"_ns)) { + // Check for non-ascii + nsReadingIterator<char> iter; + nsReadingIterator<char> iterEnd; + auto remainingScheme = Substring(scheme, 4 /* web+ */); + remainingScheme.BeginReading(iter); + remainingScheme.EndReading(iterEnd); + // Scheme suffix must be non-empty + if (iter == iterEnd) { + raisePermissionDeniedScheme(); + return; + } + for (; iter != iterEnd; iter++) { + if (*iter < 'a' || *iter > 'z') { + raisePermissionDeniedScheme(); + return; + } + } + } else { + bool matches = false; + for (const char* safeScheme : kSafeSchemes) { + if (scheme.Equals(safeScheme)) { + matches = true; + break; + } + } + if (!matches) { + raisePermissionDeniedScheme(); + return; + } + } + + nsCOMPtr<nsIProtocolHandler> handler; + nsCOMPtr<nsIIOService> io = components::IO::Service(); + if (NS_FAILED( + io->GetProtocolHandler(scheme.get(), getter_AddRefs(handler)))) { + raisePermissionDeniedScheme(); + return; + } + + // check if we have prefs set saying not to add this. + bool defaultExternal = + Preferences::GetBool("network.protocol-handler.external-default"); + nsPrintfCString specificPref("network.protocol-handler.external.%s", + scheme.get()); + if (!Preferences::GetBool(specificPref.get(), defaultExternal)) { + raisePermissionDeniedScheme(); + return; + } + + // Check to make sure this isn't already handled internally (we don't + // want to let them take over, say "chrome"). In theory, the checks above + // should have already taken care of this. + nsCOMPtr<nsIExternalProtocolHandler> externalHandler = + do_QueryInterface(handler); + MOZ_RELEASE_ASSERT( + externalHandler, + "We should never allow overriding a builtin protocol handler"); +} + +void Navigator::RegisterProtocolHandler(const nsAString& aScheme, + const nsAString& aURI, + ErrorResult& aRv) { + if (!mWindow || !mWindow->GetOuterWindow() || !mWindow->GetDocShell() || + !mWindow->GetDoc()) { + return; + } + nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(mWindow); + if (loadContext->UsePrivateBrowsing()) { + // If we're a private window, don't alert the user or webpage. We log to the + // console so that web developers have some way to tell what's going wrong. + nsContentUtils::ReportToConsole( + nsIScriptError::warningFlag, "DOM"_ns, mWindow->GetDoc(), + nsContentUtils::eDOM_PROPERTIES, + "RegisterProtocolHandlerPrivateBrowsingWarning"); + return; + } + + nsCOMPtr<Document> doc = mWindow->GetDoc(); + + // Determine if doc is allowed to assign this handler + nsIURI* docURI = doc->GetDocumentURIObject(); + nsCOMPtr<nsIURI> handlerURI; + NS_NewURI(getter_AddRefs(handlerURI), NS_ConvertUTF16toUTF8(aURI), + doc->GetDocumentCharacterSet(), docURI); + CheckProtocolHandlerAllowed(aScheme, handlerURI, docURI, aRv); + if (aRv.Failed()) { + return; + } + + // Determine a title from the document URI. + nsAutoCString docDisplayHostPort; + docURI->GetDisplayHostPort(docDisplayHostPort); + NS_ConvertASCIItoUTF16 title(docDisplayHostPort); + + if (XRE_IsContentProcess()) { + nsAutoString scheme(aScheme); + RefPtr<BrowserChild> browserChild = BrowserChild::GetFrom(mWindow); + browserChild->SendRegisterProtocolHandler(scheme, handlerURI, title, + docURI); + return; + } + + nsCOMPtr<nsIWebProtocolHandlerRegistrar> registrar = + do_GetService(NS_WEBPROTOCOLHANDLERREGISTRAR_CONTRACTID); + if (registrar) { + aRv = registrar->RegisterProtocolHandler(aScheme, handlerURI, title, docURI, + mWindow->GetOuterWindow()); + } +} + +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))) { + mGeolocation = nullptr; + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + return mGeolocation; +} + +class BeaconStreamListener final : public nsIStreamListener { + ~BeaconStreamListener() = default; + + 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) { + // release the loadgroup first + mLoadGroup = nullptr; + + return NS_ERROR_ABORT; +} + +NS_IMETHODIMP +BeaconStreamListener::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) { + return NS_OK; +} + +NS_IMETHODIMP +BeaconStreamListener::OnDataAvailable(nsIRequest* aRequest, + nsIInputStream* inStr, + uint64_t sourceOffset, uint32_t count) { + MOZ_ASSERT(false); + return NS_OK; +} + +bool Navigator::SendBeacon(const nsAString& aUrl, + const Nullable<fetch::BodyInit>& aData, + ErrorResult& aRv) { + if (aData.IsNull()) { + return SendBeaconInternal(aUrl, nullptr, eBeaconTypeOther, aRv); + } + + if (aData.Value().IsArrayBuffer()) { + BodyExtractor<const ArrayBuffer> body(&aData.Value().GetAsArrayBuffer()); + return SendBeaconInternal(aUrl, &body, eBeaconTypeArrayBuffer, aRv); + } + + if (aData.Value().IsArrayBufferView()) { + BodyExtractor<const ArrayBufferView> body( + &aData.Value().GetAsArrayBufferView()); + return SendBeaconInternal(aUrl, &body, eBeaconTypeArrayBuffer, aRv); + } + + if (aData.Value().IsBlob()) { + BodyExtractor<const Blob> body(&aData.Value().GetAsBlob()); + return SendBeaconInternal(aUrl, &body, eBeaconTypeBlob, aRv); + } + + if (aData.Value().IsFormData()) { + BodyExtractor<const FormData> body(&aData.Value().GetAsFormData()); + return SendBeaconInternal(aUrl, &body, eBeaconTypeOther, aRv); + } + + if (aData.Value().IsUSVString()) { + BodyExtractor<const nsAString> body(&aData.Value().GetAsUSVString()); + return SendBeaconInternal(aUrl, &body, eBeaconTypeOther, aRv); + } + + if (aData.Value().IsURLSearchParams()) { + BodyExtractor<const URLSearchParams> body( + &aData.Value().GetAsURLSearchParams()); + return SendBeaconInternal(aUrl, &body, eBeaconTypeOther, aRv); + } + + MOZ_CRASH("Invalid data type."); + return false; +} + +bool Navigator::SendBeaconInternal(const nsAString& aUrl, + BodyExtractorBase* aBody, BeaconType aType, + ErrorResult& aRv) { + if (!mWindow) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return false; + } + + nsCOMPtr<Document> 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.ThrowTypeError<MSG_INVALID_URL>(NS_ConvertUTF16toUTF8(aUrl)); + return false; + } + + // Spec disallows any schemes save for HTTP/HTTPs + if (!uri->SchemeIs("http") && !uri->SchemeIs("https")) { + aRv.ThrowTypeError<MSG_INVALID_URL_SCHEME>("Beacon", + uri->GetSpecOrDefault()); + return false; + } + + nsCOMPtr<nsIInputStream> in; + nsAutoCString contentTypeWithCharset; + nsAutoCString charset; + uint64_t length = 0; + if (aBody) { + aRv = aBody->GetAsStream(getter_AddRefs(in), &length, + contentTypeWithCharset, charset); + if (NS_WARN_IF(aRv.Failed())) { + return false; + } + } + + nsSecurityFlags securityFlags = nsILoadInfo::SEC_COOKIES_INCLUDE; + // Ensure that only streams with content types that are safelisted ignore CORS + // rules + if (aBody && !contentTypeWithCharset.IsVoid() && + !nsContentUtils::IsCORSSafelistedRequestHeader("content-type"_ns, + contentTypeWithCharset)) { + securityFlags |= nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT; + } else { + securityFlags |= nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT; + } + + nsCOMPtr<nsIChannel> channel; + rv = NS_NewChannel(getter_AddRefs(channel), uri, doc, securityFlags, + 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; + } + + auto referrerInfo = MakeRefPtr<ReferrerInfo>(*doc); + rv = httpChannel->SetReferrerInfoWithoutClone(referrerInfo); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + + if (aBody) { + nsCOMPtr<nsIUploadChannel2> uploadChannel = do_QueryInterface(channel); + if (!uploadChannel) { + aRv.Throw(NS_ERROR_FAILURE); + return false; + } + + uploadChannel->ExplicitSetUploadStream(in, contentTypeWithCharset, length, + "POST"_ns, false); + } else { + rv = httpChannel->SetRequestMethod("POST"_ns); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + } + + 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(); + rv = channel->AsyncOpen(beaconListener); + // do not throw if security checks fail within asyncOpen + NS_ENSURE_SUCCESS(rv, false); + + // make the beaconListener hold a strong reference to the loadgroup + // which is released in ::OnStartRequest + beaconListener->SetLoadGroup(loadGroup); + + return true; +} + +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, + CallerType aCallerType, ErrorResult& aRv) { + MOZ_ASSERT(NS_IsMainThread()); + if (!mWindow || !mWindow->IsFullyActive()) { + aRv.ThrowInvalidStateError("The document is not fully active."); + return; + } + GetMediaDevices(aRv); + if (aRv.Failed()) { + return; + } + MOZ_ASSERT(mMediaDevices); + if (Document* doc = mWindow->GetExtantDoc()) { + if (!mWindow->IsSecureContext()) { + doc->SetUseCounter(eUseCounter_custom_MozGetUserMediaInsec); + } + } + RefPtr<MediaManager::StreamPromise> sp; + if (!MediaManager::IsOn(aConstraints.mVideo) && + !MediaManager::IsOn(aConstraints.mAudio)) { + sp = MediaManager::StreamPromise::CreateAndReject( + MakeRefPtr<MediaMgrError>(MediaMgrError::Name::TypeError, + "audio and/or video is required"), + __func__); + } else { + sp = mMediaDevices->GetUserMedia(mWindow, aConstraints, aCallerType); + } + RefPtr<NavigatorUserMediaSuccessCallback> onsuccess(&aOnSuccess); + RefPtr<NavigatorUserMediaErrorCallback> onerror(&aOnError); + + nsWeakPtr weakWindow = nsWeakPtr(do_GetWeakReference(mWindow)); + sp->Then( + GetMainThreadSerialEventTarget(), __func__, + [weakWindow, onsuccess = std::move(onsuccess)]( + const RefPtr<DOMMediaStream>& aStream) MOZ_CAN_RUN_SCRIPT { + nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(weakWindow); + if (!window || !window->GetOuterWindow() || + window->GetOuterWindow()->GetCurrentInnerWindow() != window) { + return; // Leave Promise pending after navigation by design. + } + MediaManager::CallOnSuccess(*onsuccess, *aStream); + }, + [weakWindow, onerror = std::move(onerror)]( + const RefPtr<MediaMgrError>& aError) MOZ_CAN_RUN_SCRIPT { + nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(weakWindow); + if (!window || !window->GetOuterWindow() || + window->GetOuterWindow()->GetCurrentInnerWindow() != window) { + return; // Leave Promise pending after navigation by design. + } + auto error = MakeRefPtr<MediaStreamError>(window, *aError); + MediaManager::CallOnError(*onerror, *error); + }); +} + +//***************************************************************************** +// Navigator::nsINavigatorBattery +//***************************************************************************** + +Promise* Navigator::GetBattery(ErrorResult& aRv) { + if (mBatteryPromise) { + return mBatteryPromise; + } + + if (!mWindow || !mWindow->GetDocShell()) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } + + RefPtr<Promise> batteryPromise = Promise::Create(mWindow->AsGlobal(), 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; +} + +//***************************************************************************** +// Navigator::Share() - Web Share API +//***************************************************************************** + +already_AddRefed<Promise> Navigator::Share(const ShareData& aData, + ErrorResult& aRv) { + if (!mWindow || !mWindow->IsFullyActive()) { + aRv.ThrowInvalidStateError("The document is not fully active."); + return nullptr; + } + + if (NS_WARN_IF(!mWindow->GetDocShell() || !mWindow->GetExtantDoc())) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } + + if (!FeaturePolicyUtils::IsFeatureAllowed(mWindow->GetExtantDoc(), + u"web-share"_ns)) { + aRv.ThrowNotAllowedError( + "Document's Permissions Policy does not allow calling " + "share() from this context."); + return nullptr; + } + + if (mSharePromise) { + NS_WARNING("Only one share picker at a time per navigator instance"); + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return nullptr; + } + + // null checked above + Document* doc = mWindow->GetExtantDoc(); + + if (StaticPrefs::dom_webshare_requireinteraction() && + !doc->ConsumeTransientUserGestureActivation()) { + aRv.ThrowNotAllowedError( + "User activation was already consumed " + "or share() was not activated by a user gesture."); + return nullptr; + } + + ValidateShareData(aData, aRv); + + if (aRv.Failed()) { + return nullptr; + } + + // TODO: Process file member, which we don't currently support. + + // If data's url member is present, try to resolve it... + nsCOMPtr<nsIURI> url; + if (aData.mUrl.WasPassed()) { + auto result = doc->ResolveWithBaseURI(aData.mUrl.Value()); + url = result.unwrap(); + MOZ_ASSERT(url); + } + + // Process the title member... + nsCString title; + if (aData.mTitle.WasPassed()) { + title.Assign(NS_ConvertUTF16toUTF8(aData.mTitle.Value())); + } else { + title.SetIsVoid(true); + } + + // Process the text member... + nsCString text; + if (aData.mText.WasPassed()) { + text.Assign(NS_ConvertUTF16toUTF8(aData.mText.Value())); + } else { + text.SetIsVoid(true); + } + + // Let mSharePromise be a new promise. + mSharePromise = Promise::Create(mWindow->AsGlobal(), aRv); + if (aRv.Failed()) { + return nullptr; + } + + IPCWebShareData data(title, text, url); + auto wgc = mWindow->GetWindowGlobalChild(); + if (!wgc) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + // Do the share + wgc->SendShare(data)->Then( + GetCurrentSerialEventTarget(), __func__, + [self = RefPtr{this}]( + PWindowGlobalChild::SharePromise::ResolveOrRejectValue&& aResult) { + if (aResult.IsResolve()) { + if (NS_SUCCEEDED(aResult.ResolveValue())) { + self->mSharePromise->MaybeResolveWithUndefined(); + } else { + self->mSharePromise->MaybeReject(aResult.ResolveValue()); + } + } else if (self->mSharePromise) { + // IPC died + self->mSharePromise->MaybeReject(NS_BINDING_ABORTED); + } + self->mSharePromise = nullptr; + }); + return do_AddRef(mSharePromise); +} + +//***************************************************************************** +// Navigator::CanShare() - Web Share API +//***************************************************************************** +bool Navigator::CanShare(const ShareData& aData) { + if (!mWindow || !mWindow->IsFullyActive()) { + return false; + } + + if (!FeaturePolicyUtils::IsFeatureAllowed(mWindow->GetExtantDoc(), + u"web-share"_ns)) { + return false; + } + + IgnoredErrorResult rv; + ValidateShareData(aData, rv); + return !rv.Failed(); +} + +void Navigator::ValidateShareData(const ShareData& aData, ErrorResult& aRv) { + // TODO: remove this check when we support files share. + if (aData.mFiles.WasPassed() && !aData.mFiles.Value().IsEmpty()) { + aRv.ThrowTypeError("Passing files is currently not supported."); + return; + } + + bool titleTextOrUrlPassed = aData.mTitle.WasPassed() || + aData.mText.WasPassed() || aData.mUrl.WasPassed(); + + // At least one member must be present. + if (!titleTextOrUrlPassed) { + aRv.ThrowTypeError( + "Must have a title, text, or url member in the ShareData dictionary"); + return; + } + + // If data's url member is present, try to resolve it... + nsCOMPtr<nsIURI> url; + if (aData.mUrl.WasPassed()) { + Document* doc = mWindow->GetExtantDoc(); + Result<OwningNonNull<nsIURI>, nsresult> result = + doc->ResolveWithBaseURI(aData.mUrl.Value()); + if (NS_WARN_IF(result.isErr())) { + aRv.ThrowTypeError<MSG_INVALID_URL>( + NS_ConvertUTF16toUTF8(aData.mUrl.Value())); + return; + } + url = result.unwrap(); + // Check that we only share loadable URLs (e.g., http/https). + // we also exclude blobs, as it doesn't make sense to share those outside + // the context of the browser. + const uint32_t flags = + nsIScriptSecurityManager::DISALLOW_INHERIT_PRINCIPAL | + nsIScriptSecurityManager::DISALLOW_SCRIPT; + if (NS_FAILED( + nsContentUtils::GetSecurityManager()->CheckLoadURIWithPrincipal( + doc->NodePrincipal(), url, flags, doc->InnerWindowID())) || + url->SchemeIs("blob")) { + aRv.ThrowTypeError<MSG_INVALID_URL_SCHEME>("Share", + url->GetSpecOrDefault()); + return; + } + } +} + +static bool ShouldResistFingerprinting(const Document* aDoc, + RFPTarget aTarget) { + return aDoc ? aDoc->ShouldResistFingerprinting(aTarget) + : nsContentUtils::ShouldResistFingerprinting("Fallback", aTarget); +} + +already_AddRefed<LegacyMozTCPSocket> Navigator::MozTCPSocket() { + RefPtr<LegacyMozTCPSocket> socket = new LegacyMozTCPSocket(GetWindow()); + return socket.forget(); +} + +void Navigator::GetGamepads(nsTArray<RefPtr<Gamepad>>& aGamepads, + ErrorResult& aRv) { + if (!mWindow || !mWindow->IsFullyActive()) { + return; + } + NS_ENSURE_TRUE_VOID(mWindow->GetDocShell()); + nsGlobalWindowInner* win = nsGlobalWindowInner::Cast(mWindow); + + if (!FeaturePolicyUtils::IsFeatureAllowed(win->GetExtantDoc(), + u"gamepad"_ns)) { + aRv.ThrowSecurityError( + "Document's Permission Policy does not allow calling " + "getGamepads() from this context."); + return; + } + + win->SetHasGamepadEventListener(true); + win->GetGamepads(aGamepads); +} + +GamepadServiceTest* Navigator::RequestGamepadServiceTest(ErrorResult& aRv) { +#ifdef FUZZING + if (!StaticPrefs::fuzzing_enabled()) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } +#else + if (!xpc::IsInAutomation()) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } +#endif + + if (!mGamepadServiceTest) { + mGamepadServiceTest = GamepadServiceTest::CreateTestService(mWindow); + } + return mGamepadServiceTest; +} + +already_AddRefed<Promise> Navigator::GetVRDisplays(ErrorResult& aRv) { + if (!mWindow || !mWindow->GetDocShell() || !mWindow->GetExtantDoc()) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } + + if (!FeaturePolicyUtils::IsFeatureAllowed(mWindow->GetExtantDoc(), + u"vr"_ns)) { + aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); + return nullptr; + } + + RefPtr<Promise> p = Promise::Create(mWindow->AsGlobal(), aRv); + if (aRv.Failed()) { + return nullptr; + } + + RefPtr<BrowserChild> browser(BrowserChild::GetFrom(mWindow)); + if (!browser) { + MOZ_ASSERT(XRE_IsParentProcess()); + FinishGetVRDisplays(true, p); + } else { + RefPtr<Navigator> self(this); + int browserID = browser->ChromeOuterWindowID(); + + browser->SendIsWindowSupportingWebVR(browserID)->Then( + GetCurrentSerialEventTarget(), __func__, + [self, p](bool isSupported) { + self->FinishGetVRDisplays(isSupported, p); + }, + [p](const mozilla::ipc::ResponseRejectReason) { + p->MaybeRejectWithTypeError("Unable to start display enumeration"); + }); + } + + return p.forget(); +} + +void Navigator::FinishGetVRDisplays(bool isWebVRSupportedInwindow, Promise* p) { + if (!isWebVRSupportedInwindow) { + // WebVR in this window is not supported, so resolve the promise + // with no displays available + nsTArray<RefPtr<VRDisplay>> vrDisplaysEmpty; + p->MaybeResolve(vrDisplaysEmpty); + return; + } + + // Since FinishGetVRDisplays can be called asynchronously after an IPC + // response, it's possible that the Window can be torn down before this + // call. In that case, the Window's cyclic references to VR objects are + // also torn down and should not be recreated via + // NotifyHasXRSession. + nsGlobalWindowInner* win = nsGlobalWindowInner::Cast(mWindow); + if (win->IsDying()) { + // The Window has been torn down, so there is no further work that can + // be done. + p->MaybeRejectWithTypeError( + "Unable to return VRDisplays for a closed window."); + return; + } + + mVRGetDisplaysPromises.AppendElement(p); + win->RequestXRPermission(); +} + +void Navigator::OnXRPermissionRequestAllow() { + // The permission request that results in this callback could have + // been instantiated by WebVR, WebXR, or both. + nsGlobalWindowInner* win = nsGlobalWindowInner::Cast(mWindow); + bool usingWebXR = false; + + if (mXRSystem) { + usingWebXR = mXRSystem->OnXRPermissionRequestAllow(); + } + + bool rejectWebVR = true; + // If WebVR and WebXR both requested permission, only grant it to + // WebXR, which takes priority. + if (!usingWebXR) { + // We pass mWindow's id to RefreshVRDisplays, so NotifyVRDisplaysUpdated + // will be called asynchronously, resolving the promises in + // mVRGetDisplaysPromises. + rejectWebVR = !VRDisplay::RefreshVRDisplays(win->WindowID()); + } + // Even if WebXR took priority, reject requests for WebVR in case they were + // made simultaneously and coelesced into a single permission prompt. + if (rejectWebVR) { + for (auto& p : mVRGetDisplaysPromises) { + // Failed to refresh, reject the promise now + p->MaybeRejectWithTypeError("Failed to find attached VR displays."); + } + mVRGetDisplaysPromises.Clear(); + } +} + +void Navigator::OnXRPermissionRequestCancel() { + if (mXRSystem) { + mXRSystem->OnXRPermissionRequestCancel(); + } + + nsTArray<RefPtr<VRDisplay>> vrDisplays; + for (auto& p : mVRGetDisplaysPromises) { + // Resolve the promise with no vr displays when + // the user blocks access. + p->MaybeResolve(vrDisplays); + } + mVRGetDisplaysPromises.Clear(); +} + +void Navigator::GetActiveVRDisplays( + nsTArray<RefPtr<VRDisplay>>& aDisplays) const { + /** + * Get only the active VR displays. + * GetActiveVRDisplays should only enumerate displays that + * are already active without causing any other hardware to be + * activated. + * We must not call nsGlobalWindow::NotifyHasXRSession here, + * as that would cause enumeration and activation of other VR hardware. + * Activating VR hardware is intrusive to the end user, as it may + * involve physically powering on devices that the user did not + * intend to use. + */ + if (!mWindow || !mWindow->GetDocShell()) { + return; + } + nsGlobalWindowInner* win = nsGlobalWindowInner::Cast(mWindow); + nsTArray<RefPtr<VRDisplay>> displays; + if (win->UpdateVRDisplays(displays)) { + for (auto display : displays) { + if (display->IsPresenting()) { + aDisplays.AppendElement(display); + } + } + } +} + +void Navigator::NotifyVRDisplaysUpdated() { + // Synchronize the VR devices and resolve the promises in + // mVRGetDisplaysPromises + nsGlobalWindowInner* win = nsGlobalWindowInner::Cast(mWindow); + + nsTArray<RefPtr<VRDisplay>> vrDisplays; + if (win->UpdateVRDisplays(vrDisplays)) { + for (auto p : mVRGetDisplaysPromises) { + p->MaybeResolve(vrDisplays); + } + } else { + for (auto p : mVRGetDisplaysPromises) { + p->MaybeReject(NS_ERROR_FAILURE); + } + } + mVRGetDisplaysPromises.Clear(); +} + +void Navigator::NotifyActiveVRDisplaysChanged() { + Navigator_Binding::ClearCachedActiveVRDisplaysValue(this); +} + +VRServiceTest* Navigator::RequestVRServiceTest(ErrorResult& aRv) { + if (!xpc::IsInAutomation()) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } + + // Ensure that the Mock VR devices are not released prematurely + nsGlobalWindowInner* win = nsGlobalWindowInner::Cast(mWindow); + win->NotifyHasXRSession(); + + if (!mVRServiceTest) { + mVRServiceTest = VRServiceTest::CreateTestService(mWindow); + } + return mVRServiceTest; +} + +XRSystem* Navigator::GetXr(ErrorResult& aRv) { + if (!mWindow) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } + if (!mXRSystem) { + mXRSystem = XRSystem::Create(mWindow); + } + return mXRSystem; +} + +bool Navigator::IsWebVRContentDetected() const { + nsGlobalWindowInner* win = nsGlobalWindowInner::Cast(mWindow); + return win->IsVRContentDetected(); +} + +bool Navigator::IsWebVRContentPresenting() const { + nsGlobalWindowInner* win = nsGlobalWindowInner::Cast(mWindow); + return win->IsVRContentPresenting(); +} + +void Navigator::RequestVRPresentation(VRDisplay& aDisplay) { + nsGlobalWindowInner* win = nsGlobalWindowInner::Cast(mWindow); + win->DispatchVRDisplayActivate(aDisplay.DisplayId(), + VRDisplayEventReason::Requested); +} + +already_AddRefed<Promise> Navigator::RequestMIDIAccess( + const MIDIOptions& aOptions, ErrorResult& aRv) { + if (!mWindow) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } + MIDIAccessManager* accessMgr = MIDIAccessManager::Get(); + return accessMgr->RequestMIDIAccess(mWindow, aOptions, aRv); +} + +network::Connection* Navigator::GetConnection(ErrorResult& aRv) { + if (!mConnection) { + if (!mWindow) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } + mConnection = network::Connection::CreateForWindow( + mWindow, + nsGlobalWindowInner::Cast(mWindow)->ShouldResistFingerprinting()); + } + + return mConnection; +} + +already_AddRefed<ServiceWorkerContainer> Navigator::ServiceWorker() { + MOZ_ASSERT(mWindow); + + if (!mServiceWorkerContainer) { + mServiceWorkerContainer = + ServiceWorkerContainer::Create(mWindow->AsGlobal()); + } + + RefPtr<ServiceWorkerContainer> ref = mServiceWorkerContainer; + return ref.forget(); +} + +already_AddRefed<ServiceWorkerContainer> Navigator::ServiceWorkerJS() { + if (mWindow->AsGlobal()->GetStorageAccess() == + StorageAccess::ePrivateBrowsing) { + SetUseCounter(mWindow->AsGlobal()->GetGlobalJSObject(), + eUseCounter_custom_PrivateBrowsingNavigatorServiceWorker); + } + + return ServiceWorker(); +} + +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::OnNavigation() { + if (!mWindow) { + return; + } + + // If MediaManager is open let it inform any live streams or pending callbacks + MediaManager* manager = MediaManager::GetIfExists(); + if (manager) { + manager->OnNavigation(mWindow->WindowID()); + } +} + +JSObject* Navigator::WrapObject(JSContext* cx, + JS::Handle<JSObject*> aGivenProto) { + return Navigator_Binding::Wrap(cx, this, aGivenProto); +} + +/* static */ +bool Navigator::HasUserMediaSupport(JSContext* cx, JSObject* obj) { + // Make enabling peerconnection enable getUserMedia() as well. + // Emulate [SecureContext] unless media.devices.insecure.enabled=true + return (StaticPrefs::media_navigator_enabled() || + StaticPrefs::media_peerconnection_enabled()) && + (IsSecureContextOrObjectIsFromSecureContext(cx, obj) || + StaticPrefs::media_devices_insecure_enabled()); +} + +/* static */ +bool Navigator::HasShareSupport(JSContext* cx, JSObject* obj) { + if (!StaticPrefs::dom_webshare_enabled()) { + return false; + } +#if defined(XP_WIN) && !defined(__MINGW32__) + // The first public build that supports ShareCanceled API + return IsWindows10BuildOrLater(18956); +#else + return true; +#endif +} + +/* static */ +bool Navigator::HasMidiSupport(JSContext* cx, JSObject* obj) { + nsIPrincipal* principal = nsContentUtils::SubjectPrincipal(cx); + + // Enable on secure contexts but exclude file schemes. + return StaticPrefs::dom_webmidi_enabled() && + IsSecureContextOrObjectIsFromSecureContext(cx, obj) && + !principal->SchemeIs("file"); +} + +/* static */ +already_AddRefed<nsPIDOMWindowInner> Navigator::GetWindowFromGlobal( + JSObject* aGlobal) { + nsCOMPtr<nsPIDOMWindowInner> win = xpc::WindowOrNull(aGlobal); + return win.forget(); +} + +void Navigator::ClearPlatformCache() { + Navigator_Binding::ClearCachedPlatformValue(this); +} + +nsresult Navigator::GetPlatform(nsAString& aPlatform, Document* aCallerDoc, + bool aUsePrefOverriddenValue) { + MOZ_ASSERT(NS_IsMainThread()); + + if (aUsePrefOverriddenValue) { + // If fingerprinting resistance is on, we will spoof this value. See + // nsRFPService.h for details about spoofed values. + if (ShouldResistFingerprinting(aCallerDoc, RFPTarget::NavigatorPlatform)) { + aPlatform.AssignLiteral(SPOOFED_PLATFORM); + return NS_OK; + } + nsAutoString override; + nsresult rv = + mozilla::Preferences::GetString("general.platform.override", override); + + if (NS_SUCCEEDED(rv)) { + aPlatform = override; + return NS_OK; + } + } + +#if defined(WIN32) + aPlatform.AssignLiteral("Win32"); +#elif defined(XP_MACOSX) + // Always return "MacIntel", even on ARM64 macOS like Safari does. + aPlatform.AssignLiteral("MacIntel"); +#else + nsresult rv; + nsCOMPtr<nsIHttpProtocolHandler> service( + do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http", &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString plat; + rv = service->GetOscpu(plat); + NS_ENSURE_SUCCESS(rv, rv); + + CopyASCIItoUTF16(plat, aPlatform); +#endif + + return NS_OK; +} + +/* static */ +nsresult Navigator::GetAppVersion(nsAString& aAppVersion, Document* aCallerDoc, + bool aUsePrefOverriddenValue) { + MOZ_ASSERT(NS_IsMainThread()); + + if (aUsePrefOverriddenValue) { + // If fingerprinting resistance is on, we will spoof this value. See + // nsRFPService.h for details about spoofed values. + if (ShouldResistFingerprinting(aCallerDoc, + RFPTarget::NavigatorAppVersion)) { + aAppVersion.AssignLiteral(SPOOFED_APPVERSION); + return NS_OK; + } + nsAutoString override; + nsresult rv = mozilla::Preferences::GetString("general.appversion.override", + override); + + if (NS_SUCCEEDED(rv)) { + 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, Document* aCallerDoc, + bool aUsePrefOverriddenValue) { + MOZ_ASSERT(NS_IsMainThread()); + + if (aUsePrefOverriddenValue) { + // If fingerprinting resistance is on, we will spoof this value. See + // nsRFPService.h for details about spoofed values. + if (ShouldResistFingerprinting(aCallerDoc, RFPTarget::NavigatorAppName)) { + aAppName.AssignLiteral(SPOOFED_APPNAME); + return; + } + + nsAutoString override; + nsresult rv = + mozilla::Preferences::GetString("general.appname.override", override); + + if (NS_SUCCEEDED(rv)) { + aAppName = override; + return; + } + } + + aAppName.AssignLiteral("Netscape"); +} + +void Navigator::ClearUserAgentCache() { + Navigator_Binding::ClearCachedUserAgentValue(this); +} + +nsresult Navigator::GetUserAgent(nsPIDOMWindowInner* aWindow, + Document* aCallerDoc, + Maybe<bool> aShouldResistFingerprinting, + nsAString& aUserAgent) { + MOZ_ASSERT(NS_IsMainThread()); + + /* + ResistFingerprinting is migrating to fine-grained control based off + either a channel or Principal+OriginAttributes + + This function can be called from Workers, Main Thread, and at least one + other (unusual) case. + + For Main Thread, we will generally have a window and an associated + Document, for Workers we will not. + + If aShouldResistFingerprinting is provided, we should respect it. + If it is not provided, we will use aCallerDoc to determine our behavior. + */ + + bool shouldResistFingerprinting = + aShouldResistFingerprinting.isSome() + ? aShouldResistFingerprinting.value() + : ShouldResistFingerprinting(aCallerDoc, + RFPTarget::NavigatorUserAgent); + + // We will skip the override and pass to httpHandler to get spoofed userAgent + // when 'privacy.resistFingerprinting' is true. + if (!shouldResistFingerprinting) { + nsAutoString override; + nsresult rv = + mozilla::Preferences::GetString("general.useragent.override", override); + + if (NS_SUCCEEDED(rv)) { + aUserAgent = override; + return NS_OK; + } + } + + // When the caller is content and 'privacy.resistFingerprinting' is true, + // return a spoofed userAgent which reveals the platform but not the + // specific OS version, etc. + if (shouldResistFingerprinting) { + nsAutoCString spoofedUA; + nsRFPService::GetSpoofedUserAgent(spoofedUA, false); + CopyASCIItoUTF16(spoofedUA, aUserAgent); + 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) { + return NS_OK; + } + + // Copy the User-Agent header from the document channel which has already been + // subject to UA overrides. + nsCOMPtr<Document> doc = aWindow->GetExtantDoc(); + if (!doc) { + return NS_OK; + } + nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(doc->GetChannel()); + if (httpChannel) { + nsAutoCString userAgent; + rv = httpChannel->GetRequestHeader("User-Agent"_ns, userAgent); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + CopyASCIItoUTF16(userAgent, aUserAgent); + } + return NS_OK; +} + +static nsCString RequestKeySystemAccessLogString( + const nsAString& aKeySystem, + const Sequence<MediaKeySystemConfiguration>& aConfigs, + bool aIsSecureContext) { + nsCString str; + str.AppendPrintf( + "Navigator::RequestMediaKeySystemAccess(keySystem='%s' options=", + NS_ConvertUTF16toUTF8(aKeySystem).get()); + str.Append(MediaKeySystemAccess::ToCString(aConfigs)); + str.AppendLiteral(") secureContext="); + str.AppendInt(aIsSecureContext); + return str; +} + +already_AddRefed<Promise> Navigator::RequestMediaKeySystemAccess( + const nsAString& aKeySystem, + const Sequence<MediaKeySystemConfiguration>& aConfigs, ErrorResult& aRv) { + EME_LOG("%s", RequestKeySystemAccessLogString(aKeySystem, aConfigs, + mWindow->IsSecureContext()) + .get()); + + if (!mWindow->IsSecureContext()) { + Document* doc = mWindow->GetExtantDoc(); + AutoTArray<nsString, 1> params; + nsString* uri = params.AppendElement(); + if (doc) { + Unused << doc->GetDocumentURI(*uri); + } + nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "Media"_ns, + doc, nsContentUtils::eDOM_PROPERTIES, + "MediaEMEInsecureContextDeprecatedWarning", + params); + } + + Document* doc = mWindow->GetExtantDoc(); + if (doc && + !FeaturePolicyUtils::IsFeatureAllowed(doc, u"encrypted-media"_ns)) { + aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); + return nullptr; + } + + RefPtr<DetailedPromise> promise = DetailedPromise::Create( + mWindow->AsGlobal(), aRv, "navigator.requestMediaKeySystemAccess"_ns); + if (aRv.Failed()) { + return nullptr; + } + + if (!mMediaKeySystemAccessManager) { + mMediaKeySystemAccessManager = new MediaKeySystemAccessManager(mWindow); + } + + mMediaKeySystemAccessManager->Request(promise, aKeySystem, aConfigs); + return promise.forget(); +} + +CredentialsContainer* Navigator::Credentials() { + if (!mCredentials) { + mCredentials = new CredentialsContainer(GetWindow()); + } + return mCredentials; +} + +dom::MediaCapabilities* Navigator::MediaCapabilities() { + if (!mMediaCapabilities) { + mMediaCapabilities = new dom::MediaCapabilities(GetWindow()->AsGlobal()); + } + return mMediaCapabilities; +} + +dom::MediaSession* Navigator::MediaSession() { + if (!mMediaSession) { + mMediaSession = new dom::MediaSession(GetWindow()); + } + return mMediaSession; +} + +bool Navigator::HasCreatedMediaSession() const { + return mMediaSession != nullptr; +} + +Clipboard* Navigator::Clipboard() { + if (!mClipboard) { + mClipboard = new dom::Clipboard(GetWindow()); + } + return mClipboard; +} + +AddonManager* Navigator::GetMozAddonManager(ErrorResult& aRv) { + if (!mAddonManager) { + nsPIDOMWindowInner* win = GetWindow(); + if (!win) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } + + mAddonManager = ConstructJSImplementation<AddonManager>( + "@mozilla.org/addon-web-api/manager;1", win->AsGlobal(), aRv); + if (aRv.Failed()) { + return nullptr; + } + } + + return mAddonManager; +} + +webgpu::Instance* Navigator::Gpu() { + if (!mWebGpu) { + mWebGpu = webgpu::Instance::Create(GetWindow()->AsGlobal()); + } + return mWebGpu; +} + +dom::LockManager* Navigator::Locks() { + if (!mLocks) { + mLocks = new dom::LockManager(GetWindow()->AsGlobal()); + } + return mLocks; +} + +/* static */ +bool Navigator::Webdriver() { +#ifdef ENABLE_WEBDRIVER + nsCOMPtr<nsIMarionette> marionette = do_GetService(NS_MARIONETTE_CONTRACTID); + if (marionette) { + bool marionetteRunning = false; + marionette->GetRunning(&marionetteRunning); + if (marionetteRunning) { + return true; + } + } + + nsCOMPtr<nsIRemoteAgent> agent = do_GetService(NS_REMOTEAGENT_CONTRACTID); + if (agent) { + bool remoteAgentRunning = false; + agent->GetRunning(&remoteAgentRunning); + if (remoteAgentRunning) { + return true; + } + } +#endif + + return false; +} + +AutoplayPolicy Navigator::GetAutoplayPolicy(AutoplayPolicyMediaType aType) { + if (!mWindow) { + return AutoplayPolicy::Disallowed; + } + nsCOMPtr<Document> doc = mWindow->GetExtantDoc(); + if (!doc) { + return AutoplayPolicy::Disallowed; + } + return media::AutoplayPolicy::GetAutoplayPolicy(aType, *doc); +} + +AutoplayPolicy Navigator::GetAutoplayPolicy(HTMLMediaElement& aElement) { + return media::AutoplayPolicy::GetAutoplayPolicy(aElement); +} + +AutoplayPolicy Navigator::GetAutoplayPolicy(AudioContext& aContext) { + return media::AutoplayPolicy::GetAutoplayPolicy(aContext); +} + +} // namespace mozilla::dom |