/* -*- 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 window; if (mWindow) { window = mWindow; nsIDocShell* docshell = window->GetDocShell(); nsString customUserAgent; if (docshell) { docshell->GetBrowsingContext()->GetCustomUserAgent(customUserAgent); if (!customUserAgent.IsEmpty()) { aUserAgent = customUserAgent; return; } } } nsCOMPtr 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 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 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 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& 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 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 languages; GetLanguages(languages); MOZ_ASSERT(languages.Length() >= 1); aLanguage.Assign(languages[0]); } void Navigator::GetLanguages(nsTArray& 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 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 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(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 loadContext = do_GetInterface(mWindow); uint32_t cookieBehavior = loadContext ? nsICookieManager::GetCookieBehavior( loadContext->UsePrivateBrowsing()) : nsICookieManager::GetCookieBehavior(false); bool cookieEnabled = cookieBehavior != nsICookieService::BEHAVIOR_REJECT; nsCOMPtr 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 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 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 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 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 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 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 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 pattern = std::move(mRequestedVibrationPattern); if (!mWindow) { return; } nsCOMPtr 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 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 pattern; pattern.AppendElement(aDuration); return Vibrate(pattern); } nsTArray SanitizeVibratePattern(const nsTArray& aPattern) { nsTArray 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& aPattern) { MOZ_ASSERT(NS_IsMainThread()); if (!mWindow) { return false; } nsCOMPtr doc = mWindow->GetExtantDoc(); if (!MayVibrate(doc)) { return false; } nsTArray 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 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 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 iter; nsReadingIterator 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 handler; nsCOMPtr 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 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 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 doc = mWindow->GetDoc(); // Determine if doc is allowed to assign this handler nsIURI* docURI = doc->GetDocumentURIObject(); nsCOMPtr 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::GetFrom(mWindow); browserChild->SendRegisterProtocolHandler(scheme, handlerURI, title, docURI); return; } nsCOMPtr 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 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& aData, ErrorResult& aRv) { if (aData.IsNull()) { return SendBeaconInternal(aUrl, nullptr, eBeaconTypeOther, aRv); } if (aData.Value().IsArrayBuffer()) { BodyExtractor body(&aData.Value().GetAsArrayBuffer()); return SendBeaconInternal(aUrl, &body, eBeaconTypeArrayBuffer, aRv); } if (aData.Value().IsArrayBufferView()) { BodyExtractor body( &aData.Value().GetAsArrayBufferView()); return SendBeaconInternal(aUrl, &body, eBeaconTypeArrayBuffer, aRv); } if (aData.Value().IsBlob()) { BodyExtractor body(&aData.Value().GetAsBlob()); return SendBeaconInternal(aUrl, &body, eBeaconTypeBlob, aRv); } if (aData.Value().IsFormData()) { BodyExtractor body(&aData.Value().GetAsFormData()); return SendBeaconInternal(aUrl, &body, eBeaconTypeOther, aRv); } if (aData.Value().IsUSVString()) { BodyExtractor body(&aData.Value().GetAsUSVString()); return SendBeaconInternal(aUrl, &body, eBeaconTypeOther, aRv); } if (aData.Value().IsURLSearchParams()) { BodyExtractor 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 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 uri; nsresult rv = nsContentUtils::NewURIWithDocumentCharset( getter_AddRefs(uri), aUrl, doc, doc->GetDocBaseURI()); if (NS_FAILED(rv)) { aRv.ThrowTypeError(NS_ConvertUTF16toUTF8(aUrl)); return false; } // Spec disallows any schemes save for HTTP/HTTPs if (!uri->SchemeIs("http") && !uri->SchemeIs("https")) { aRv.ThrowTypeError("Beacon", uri->GetSpecOrDefault()); return false; } nsCOMPtr 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 channel; rv = NS_NewChannel(getter_AddRefs(channel), uri, doc, securityFlags, nsIContentPolicy::TYPE_BEACON); if (NS_FAILED(rv)) { aRv.Throw(rv); return false; } nsCOMPtr 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(*doc); rv = httpChannel->SetReferrerInfoWithoutClone(referrerInfo); MOZ_ASSERT(NS_SUCCEEDED(rv)); if (aBody) { nsCOMPtr 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 p = do_QueryInterface(channel); if (p) { p->SetPriority(nsISupportsPriority::PRIORITY_LOWEST); } nsCOMPtr 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 loadGroup = do_CreateInstance(NS_LOADGROUP_CONTRACTID); nsCOMPtr callbacks = do_QueryInterface(mWindow->GetDocShell()); loadGroup->SetNotificationCallbacks(callbacks); channel->SetLoadGroup(loadGroup); RefPtr 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 sp; if (!MediaManager::IsOn(aConstraints.mVideo) && !MediaManager::IsOn(aConstraints.mAudio)) { sp = MediaManager::StreamPromise::CreateAndReject( MakeRefPtr(MediaMgrError::Name::TypeError, "audio and/or video is required"), __func__); } else { sp = mMediaDevices->GetUserMedia(mWindow, aConstraints, aCallerType); } RefPtr onsuccess(&aOnSuccess); RefPtr onerror(&aOnError); nsWeakPtr weakWindow = nsWeakPtr(do_GetWeakReference(mWindow)); sp->Then( GetMainThreadSerialEventTarget(), __func__, [weakWindow, onsuccess = std::move(onsuccess)]( const RefPtr& aStream) MOZ_CAN_RUN_SCRIPT { nsCOMPtr 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& aError) MOZ_CAN_RUN_SCRIPT { nsCOMPtr window = do_QueryReferent(weakWindow); if (!window || !window->GetOuterWindow() || window->GetOuterWindow()->GetCurrentInnerWindow() != window) { return; // Leave Promise pending after navigation by design. } auto error = MakeRefPtr(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 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 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 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 url; if (aData.mUrl.WasPassed()) { Document* doc = mWindow->GetExtantDoc(); Result, nsresult> result = doc->ResolveWithBaseURI(aData.mUrl.Value()); if (NS_WARN_IF(result.isErr())) { aRv.ThrowTypeError( 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("Share", url->GetSpecOrDefault()); return; } } } static bool ShouldResistFingerprinting(const Document* aDoc, RFPTarget aTarget) { return aDoc ? aDoc->ShouldResistFingerprinting(aTarget) : nsContentUtils::ShouldResistFingerprinting("Fallback", aTarget); } already_AddRefed Navigator::MozTCPSocket() { RefPtr socket = new LegacyMozTCPSocket(GetWindow()); return socket.forget(); } void Navigator::GetGamepads(nsTArray>& 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 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 p = Promise::Create(mWindow->AsGlobal(), aRv); if (aRv.Failed()) { return nullptr; } RefPtr browser(BrowserChild::GetFrom(mWindow)); if (!browser) { MOZ_ASSERT(XRE_IsParentProcess()); FinishGetVRDisplays(true, p); } else { RefPtr 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> 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> 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>& 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> 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> 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 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 Navigator::ServiceWorker() { MOZ_ASSERT(mWindow); if (!mServiceWorkerContainer) { mServiceWorkerContainer = ServiceWorkerContainer::Create(mWindow->AsGlobal()); } RefPtr ref = mServiceWorkerContainer; return ref.forget(); } already_AddRefed 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 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 Navigator::GetWindowFromGlobal( JSObject* aGlobal) { nsCOMPtr 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 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 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 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 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 doc = aWindow->GetExtantDoc(); if (!doc) { return NS_OK; } nsCOMPtr 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& 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 Navigator::RequestMediaKeySystemAccess( const nsAString& aKeySystem, const Sequence& aConfigs, ErrorResult& aRv) { EME_LOG("%s", RequestKeySystemAccessLogString(aKeySystem, aConfigs, mWindow->IsSecureContext()) .get()); if (!mWindow->IsSecureContext()) { Document* doc = mWindow->GetExtantDoc(); AutoTArray 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 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( "@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 marionette = do_GetService(NS_MARIONETTE_CONTRACTID); if (marionette) { bool marionetteRunning = false; marionette->GetRunning(&marionetteRunning); if (marionetteRunning) { return true; } } nsCOMPtr 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 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