/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "PermissionObserver.h" #include "PermissionStatusSink.h" #include "PermissionUtils.h" #include "mozilla/Services.h" #include "mozilla/dom/BrowsingContext.h" #include "mozilla/dom/WindowGlobalChild.h" #include "nsIObserverService.h" #include "nsIPermission.h" #include "nsISupportsPrimitives.h" namespace mozilla::dom { namespace { PermissionObserver* gInstance = nullptr; } // namespace NS_IMPL_ISUPPORTS(PermissionObserver, nsIObserver, nsISupportsWeakReference) PermissionObserver::PermissionObserver() { MOZ_ASSERT_DEBUG_OR_FUZZING(NS_IsMainThread()); MOZ_ASSERT(!gInstance); } PermissionObserver::~PermissionObserver() { MOZ_ASSERT_DEBUG_OR_FUZZING(NS_IsMainThread()); MOZ_ASSERT(mSinks.IsEmpty()); MOZ_ASSERT(gInstance == this); gInstance = nullptr; } /* static */ already_AddRefed PermissionObserver::GetInstance() { MOZ_ASSERT_DEBUG_OR_FUZZING(NS_IsMainThread()); RefPtr instance = gInstance; if (!instance) { instance = new PermissionObserver(); nsCOMPtr obs = services::GetObserverService(); if (NS_WARN_IF(!obs)) { return nullptr; } nsresult rv = obs->AddObserver(instance, "perm-changed", true); if (NS_WARN_IF(NS_FAILED(rv))) { return nullptr; } rv = obs->AddObserver(instance, "perm-changed-notify-only", true); if (NS_WARN_IF(NS_FAILED(rv))) { return nullptr; } rv = obs->AddObserver(instance, "browser-perm-changed", true); if (NS_WARN_IF(NS_FAILED(rv))) { return nullptr; } gInstance = instance; } return instance.forget(); } void PermissionObserver::AddSink(PermissionStatusSink* aSink) { MOZ_ASSERT_DEBUG_OR_FUZZING(NS_IsMainThread()); MOZ_ASSERT(aSink); MOZ_ASSERT(!mSinks.Contains(aSink)); mSinks.AppendElement(aSink); } void PermissionObserver::RemoveSink(PermissionStatusSink* aSink) { MOZ_ASSERT_DEBUG_OR_FUZZING(NS_IsMainThread()); MOZ_ASSERT(aSink); MOZ_ASSERT(mSinks.Contains(aSink)); mSinks.RemoveElement(aSink); } NS_IMETHODIMP PermissionObserver::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) { MOZ_ASSERT_DEBUG_OR_FUZZING(NS_IsMainThread()); MOZ_ASSERT(!strcmp(aTopic, "perm-changed") || !strcmp(aTopic, "perm-changed-notify-only") || !strcmp(aTopic, "browser-perm-changed")); if (mSinks.IsEmpty()) { return NS_OK; } nsCOMPtr perm = nullptr; nsCOMPtr innerWindow = nullptr; nsAutoCString type; bool isBrowserPerm = !strcmp(aTopic, "browser-perm-changed"); if (isBrowserPerm && aData && !NS_strcmp(aData, u"cleared")) { // Bulk browser permission clear — subject is an nsISupportsPRUint64 // carrying the browserId. Only recompute sinks for that tab. uint64_t clearedBrowserId = 0; nsCOMPtr wrapper = do_QueryInterface(aSubject); if (wrapper) { wrapper->GetData(&clearedBrowserId); } for (PermissionStatusSink* sink : mSinks) { if (!clearedBrowserId || sink->MaybeAffectedByBrowserIdOnMainThread(clearedBrowserId)) { sink->PermissionChangedOnMainThread(); } } return NS_OK; } if (!strcmp(aTopic, "perm-changed") || isBrowserPerm) { perm = do_QueryInterface(aSubject); if (!perm) { return NS_OK; } perm->GetType(type); } else if (!strcmp(aTopic, "perm-changed-notify-only")) { innerWindow = do_QueryInterface(aSubject); if (!innerWindow) { return NS_OK; } type = NS_ConvertUTF16toUTF8(aData); } Maybe permission = TypeToPermissionName(type); if (permission) { for (PermissionStatusSink* sink : mSinks) { if (sink->Name() != permission.value()) { continue; } // Check for permissions that are changed for this sink's principal // via the "perm-changed" or "browser-perm-changed" notification. // These permissions affect the window the sink (PermissionStatus) // is held in directly. if (perm) { if (isBrowserPerm) { if (sink->MaybeUpdatedByBrowserPermOnMainThread(perm)) { sink->PermissionChangedOnMainThread(); } } else if (sink->MaybeUpdatedByOnMainThread(perm)) { sink->PermissionChangedOnMainThread(); } } // Check for permissions that are changed for this sink's principal // via the "perm-changed-notify-only" notification. These permissions // affect the window the sink (PermissionStatus) is held in indirectly- // if the window is same-party with the secondary key of a permission. // For example, a "3rdPartyFrameStorage^https://example.com" permission // would return true on these checks where sink is in a window that is // same-site with https://example.com. if (innerWindow && sink->MaybeUpdatedByNotifyOnlyOnMainThread(innerWindow)) { sink->PermissionChangedOnMainThread(); } } } return NS_OK; } } // namespace mozilla::dom