/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "CanvasUtils.h" #include #include #include "WebGL2Context.h" #include "jsapi.h" #include "mozIThirdPartyUtil.h" #include "mozilla/BasePrincipal.h" #include "mozilla/Services.h" #include "mozilla/StaticPrefs_gfx.h" #include "mozilla/StaticPrefs_privacy.h" #include "mozilla/StaticPrefs_webgl.h" #include "mozilla/dom/BrowserChild.h" #include "mozilla/dom/Document.h" #include "mozilla/dom/HTMLCanvasElement.h" #include "mozilla/dom/OffscreenCanvas.h" #include "mozilla/dom/UserActivation.h" #include "mozilla/dom/WindowGlobalParent.h" #include "mozilla/dom/WorkerCommon.h" #include "mozilla/dom/WorkerPrivate.h" #include "mozilla/dom/WorkerRunnable.h" #include "mozilla/gfx/Matrix.h" #include "mozilla/gfx/gfxVars.h" #include "nsContentUtils.h" #include "nsGfxCIID.h" #include "nsICanvasRenderingContextInternal.h" #include "nsIHTMLCollection.h" #include "nsIObserverService.h" #include "nsIPermissionManager.h" #include "nsIPrincipal.h" #include "nsIScriptError.h" #include "nsIScriptObjectPrincipal.h" #include "nsPrintfCString.h" #include "nsTArray.h" #include "nsUnicharUtils.h" #define TOPIC_CANVAS_PERMISSIONS_PROMPT "canvas-permissions-prompt" #define TOPIC_CANVAS_PERMISSIONS_PROMPT_HIDE_DOORHANGER \ "canvas-permissions-prompt-hide-doorhanger" #define PERMISSION_CANVAS_EXTRACT_DATA "canvas"_ns using namespace mozilla::gfx; static bool IsUnrestrictedPrincipal(nsIPrincipal* aPrincipal) { if (!aPrincipal) { return false; } // The system principal can always extract canvas data. if (aPrincipal->IsSystemPrincipal()) { return true; } // Allow chrome: and resource: (this especially includes PDF.js) if (aPrincipal->SchemeIs("chrome") || aPrincipal->SchemeIs("resource")) { return true; } // Allow extension principals. return aPrincipal->GetIsAddonOrExpandedAddonPrincipal(); } namespace mozilla::CanvasUtils { class OffscreenCanvasPermissionRunnable final : public dom::WorkerMainThreadRunnable { public: OffscreenCanvasPermissionRunnable(dom::WorkerPrivate* aWorkerPrivate, nsIPrincipal* aPrincipal) : WorkerMainThreadRunnable(aWorkerPrivate, "OffscreenCanvasPermissionRunnable"_ns), mPrincipal(aPrincipal) { MOZ_ASSERT(aWorkerPrivate); aWorkerPrivate->AssertIsOnWorkerThread(); } bool MainThreadRun() override { AssertIsOnMainThread(); mResult = GetCanvasExtractDataPermission(mPrincipal); return true; } uint32_t GetResult() const { return mResult; } private: nsCOMPtr mPrincipal; uint32_t mResult = nsIPermissionManager::UNKNOWN_ACTION; }; uint32_t GetCanvasExtractDataPermission(nsIPrincipal* aPrincipal) { if (!aPrincipal) { return nsIPermissionManager::UNKNOWN_ACTION; } if (IsUnrestrictedPrincipal(aPrincipal)) { return true; } if (NS_IsMainThread()) { nsresult rv; nsCOMPtr permissionManager = do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, nsIPermissionManager::UNKNOWN_ACTION); uint32_t permission; rv = permissionManager->TestPermissionFromPrincipal( aPrincipal, PERMISSION_CANVAS_EXTRACT_DATA, &permission); NS_ENSURE_SUCCESS(rv, nsIPermissionManager::UNKNOWN_ACTION); return permission; } if (auto* workerPrivate = dom::GetCurrentThreadWorkerPrivate()) { RefPtr runnable = new OffscreenCanvasPermissionRunnable(workerPrivate, aPrincipal); ErrorResult rv; runnable->Dispatch(workerPrivate, dom::WorkerStatus::Canceling, rv); if (rv.Failed()) { return nsIPermissionManager::UNKNOWN_ACTION; } return runnable->GetResult(); } return nsIPermissionManager::UNKNOWN_ACTION; } /* ┌──────────────────────────────────────────────────────────────────────────┐ │IsImageExtractionAllowed(dom::OffscreenCanvas*, JSContext*, nsIPrincipal*)│ └────────────────────────────────────┬─────────────────────────────────────┘ │ ┌─────────────────▼────────────────────┐ ┌─────No──────────│Any prompt RFP target enabled? See [1]│ ▼ └─────────────────┬────────────────────┘ │ │Yes │ ┌─────────────────▼────────┐ ├─────Yes─────────┤Is unrestricted principal?│ ▼ └─────────────────┬────────┘ │ │No │ ┌─────────────────▼────────┐ │ ┌──No──┤Are third parties blocked?│ │ │ └─────────────────┬────────┘ │ │ │Yes │ │ ┌─────────────────▼─────────────┐ │ │ │Are we in a third-party window?├───────Yes──────────┐ │ │ └─────────────────┬─────────────┘ ▼ │ │ │No │ │ │ ┌─────────────────▼──┐ │ │ └──────►Do we show a prompt?├────────────Yes─┐ │ │ └─────────────────┬──┘ ▼ │ │ │No │ │ │ ┌─────────────────▼─────────────┐ │ │ │ │Do we allow reading canvas data│ │ │ │ │in response to user input? ├─No──┤ │ │ └─────────────────┬─────────────┘ ▼ │ │ │Yes │ │ │ ┌─────────────────▼─────────┐ │ │ ├─────Yes─────────┼Are we handling user input?│ │ │ ▼ └─────────────────┬─────────┘ │ │ │ │No │ │ │ ┌─────────────────▼─────────────┐ │ │ ┌▼─────┐ │Show Permission Prompt (either ◄─────┘ ┌───▼──┐ │return│ │w/ doorhanger, or w/o depending│ │return│ │true │ │on User Input) ├────────────────►false │ └──────┘ └───────────────────────────────┘ └──────┘ [1]: CanvasImageExtractionPrompt, CanvasExtractionBeforeUserInputIsBlocked, CanvasExtractionFromThirdPartiesIsBlocked are the RFP targets mentioned. */ bool IsImageExtractionAllowed_impl( bool aCanvasImageExtractionPrompt, bool aCanvasExtractionBeforeUserInputIsBlocked, bool aCanvasExtractionFromThirdPartiesIsBlocked, JSContext* aCx, nsIPrincipal* aPrincipal, const std::function& aGetIsThirdPartyWindow, const std::function& aReportToConsole, const std::function& aTryPrompt) { /* * There are three RFPTargets that change the behavior here, and they can be * in any combination * - CanvasImageExtractionPrompt - whether or not to prompt the user for * canvas extraction. If enabled, before canvas is extracted we will ensure * the user has granted permission. * - CanvasExtractionBeforeUserInputIsBlocked - if enabled, canvas extraction * before user input has occurred is always blocked, regardless of any other * Target behavior * - CanvasExtractionFromThirdPartiesIsBlocked - if enabled, canvas extraction * by third parties is always blocked, regardless of any other Target behavior * * There are two odd cases: * 1) When CanvasImageExtractionPrompt=false but * CanvasExtractionBeforeUserInputIsBlocked=true Conceptually this is * "Always allow canvas extraction in response to user input, and never * allow it otherwise" * * That's fine as a concept, but it might be a little confusing, so we * still want to show the permission icon in the address bar, but never * the permission doorhanger. * 2) When CanvasExtractionFromThirdPartiesIsBlocked=false - we will prompt * the user for permission _for the frame_ (maybe with the doorhanger, * maybe not). The prompt shows the frame's origin, but it's easy to * mistake that for the origin of the top-level page and grant it when you * don't mean to. This combination isn't likely to be used by anyone * except those opting in, so that's alright. */ if (!aCanvasImageExtractionPrompt && !aCanvasExtractionBeforeUserInputIsBlocked && !aCanvasExtractionFromThirdPartiesIsBlocked) { return true; } // Don't proceed if we don't have a document or JavaScript context. if (!aCx) { return false; } if (IsUnrestrictedPrincipal(aPrincipal)) { return true; } Maybe origin = Nothing(); auto getOrigin = [&]() { if (origin.isSome()) { return origin->IsEmpty(); } nsAutoCString originResult; nsresult rv = NS_ERROR_FAILURE; if (aPrincipal) { rv = aPrincipal->GetOrigin(originResult); } origin = NS_SUCCEEDED(rv) ? Some(originResult) : Some(""_ns); return NS_SUCCEEDED(rv); }; if (aCanvasExtractionFromThirdPartiesIsBlocked) { if (aGetIsThirdPartyWindow()) { nsAutoString message; message.AppendPrintf( "Blocked %s third party from extracting canvas data.", getOrigin() ? origin->get() : "unknown"); aReportToConsole(message); return false; } } if (!aCanvasImageExtractionPrompt && !aCanvasExtractionBeforeUserInputIsBlocked) { return true; } // ------------------------------------------------------------------- // Check a site's permission // If the user has previously granted or not granted permission, we can return // immediately. Load Permission Manager service. uint64_t permission = GetCanvasExtractDataPermission(aPrincipal); switch (permission) { case nsIPermissionManager::ALLOW_ACTION: return true; case nsIPermissionManager::DENY_ACTION: return false; default: break; } // ------------------------------------------------------------------- // At this point, there's only one way to return true: if we are always // allowing canvas in response to user input, and not prompting bool hidePermissionDoorhanger = false; if (!aCanvasImageExtractionPrompt && aCanvasExtractionBeforeUserInputIsBlocked) { // If so, see if this is in response to user input. if (NS_IsMainThread() && dom::UserActivation::IsHandlingUserInput()) { return true; } hidePermissionDoorhanger = true; } // ------------------------------------------------------------------- // Now we know we're going to block it, and log something to the console, // and show some sort of prompt maybe with the doorhanger, maybe not hidePermissionDoorhanger |= aCanvasExtractionBeforeUserInputIsBlocked && (!NS_IsMainThread() || !dom::UserActivation::IsHandlingUserInput()); nsAutoString message; message.AppendPrintf("Blocked %s from extracting canvas data", getOrigin() ? origin->get() : "unknown"); message.AppendPrintf(hidePermissionDoorhanger ? " because no user input was detected" : " but prompting the user."); aReportToConsole(message); aTryPrompt(hidePermissionDoorhanger); return false; } bool IsImageExtractionAllowed(dom::Document* aDocument, JSContext* aCx, nsIPrincipal* aPrincipal) { if (NS_WARN_IF(!aDocument)) { return false; } bool canvasImageExtractionPrompt = aDocument->ShouldResistFingerprinting( RFPTarget::CanvasImageExtractionPrompt); bool canvasExtractionBeforeUserInputIsBlocked = aDocument->ShouldResistFingerprinting( RFPTarget::CanvasExtractionBeforeUserInputIsBlocked); bool canvasExtractionFromThirdPartiesIsBlocked = aDocument->ShouldResistFingerprinting( RFPTarget::CanvasExtractionFromThirdPartiesIsBlocked); // This part is duplicate but it helps us return faster // before we create bunch of lambdas if (!canvasImageExtractionPrompt && !canvasExtractionBeforeUserInputIsBlocked && !canvasExtractionFromThirdPartiesIsBlocked) { return true; } auto getIsThirdPartyWindow = [&]() { return aDocument->GetWindowContext() ? aDocument->GetWindowContext()->GetIsThirdPartyWindow() : false; }; auto reportToConsole = [&](const nsAutoString& message) { nsContentUtils::ReportToConsoleNonLocalized( message, nsIScriptError::warningFlag, "Security"_ns, aDocument); }; auto prompt = [&](bool hidePermissionDoorhanger) { if (!aPrincipal) { return; } nsAutoCString origin; nsresult rv = aPrincipal->GetOrigin(origin); if (NS_FAILED(rv)) { return; } if (!XRE_IsContentProcess()) { MOZ_ASSERT_UNREACHABLE( "Who's calling this from the parent process without a chrome window " "(it would have been exempt from the RFP targets)?"); return; } nsPIDOMWindowOuter* win = aDocument->GetWindow(); if (RefPtr browserChild = dom::BrowserChild::GetFrom(win)) { browserChild->SendShowCanvasPermissionPrompt(origin, hidePermissionDoorhanger); } }; return IsImageExtractionAllowed_impl( canvasImageExtractionPrompt, canvasExtractionBeforeUserInputIsBlocked, canvasExtractionFromThirdPartiesIsBlocked, aCx, aPrincipal, getIsThirdPartyWindow, reportToConsole, prompt); } ImageExtraction ImageExtractionResult(dom::HTMLCanvasElement* aCanvasElement, JSContext* aCx, nsIPrincipal* aPrincipal) { if (IsUnrestrictedPrincipal(aPrincipal)) { return ImageExtraction::Unrestricted; } nsCOMPtr ownerDoc = aCanvasElement->OwnerDoc(); if (!IsImageExtractionAllowed(ownerDoc, aCx, aPrincipal)) { return ImageExtraction::Placeholder; } if (ownerDoc->ShouldResistFingerprinting( RFPTarget::EfficientCanvasRandomization) && GetCanvasExtractDataPermission(aPrincipal) != nsIPermissionManager::ALLOW_ACTION) { return ImageExtraction::EfficientRandomize; } if ((ownerDoc->ShouldResistFingerprinting(RFPTarget::CanvasRandomization) || ownerDoc->ShouldResistFingerprinting(RFPTarget::WebGLRandomization)) && GetCanvasExtractDataPermission(aPrincipal) != nsIPermissionManager::ALLOW_ACTION) { return ImageExtraction::Randomize; } return ImageExtraction::Unrestricted; } bool IsImageExtractionAllowed(dom::OffscreenCanvas* aOffscreenCanvas, JSContext* aCx, nsIPrincipal* aPrincipal) { if (!aOffscreenCanvas) { return false; } bool canvasImageExtractionPrompt = aOffscreenCanvas->ShouldResistFingerprinting( RFPTarget::CanvasImageExtractionPrompt); bool canvasExtractionBeforeUserInputIsBlocked = aOffscreenCanvas->ShouldResistFingerprinting( RFPTarget::CanvasExtractionBeforeUserInputIsBlocked); bool canvasExtractionFromThirdPartiesIsBlocked = aOffscreenCanvas->ShouldResistFingerprinting( RFPTarget::CanvasExtractionFromThirdPartiesIsBlocked); // This part is duplicate but it helps us return faster // before we create bunch of lambdas if (!canvasImageExtractionPrompt && !canvasExtractionBeforeUserInputIsBlocked && !canvasExtractionFromThirdPartiesIsBlocked) { return true; } Maybe winId = aOffscreenCanvas->GetWindowID(); if (winId.isSome() && *winId == UINT64_MAX) { // Workers with no window return UINT64_MAX as their window ID. winId = Nothing(); } auto getIsThirdPartyWindow = [&]() { if (winId.isNothing()) { return false; } if (NS_IsMainThread()) { if (RefPtr win = dom::WindowGlobalParent::GetById(*winId)) { return win->GetIsThirdPartyWindow(); } } else if (auto* workerPrivate = dom::GetCurrentThreadWorkerPrivate()) { return workerPrivate->IsThirdPartyContext(); } return false; }; auto reportToConsole = [&](const nsAutoString& message) { if (winId.isNothing()) { return; } nsContentUtils::ReportToConsoleByWindowID( message, nsIScriptError::warningFlag, "Security"_ns, *winId); }; nsAutoCString origin; if (!aPrincipal || NS_FAILED(aPrincipal->GetOrigin(origin))) { origin = ""_ns; } RefPtr canvasRef = aOffscreenCanvas; auto prompt = [=](bool hidePermissionDoorhanger) { if (origin.IsEmpty()) { return; } if (!XRE_IsContentProcess()) { MOZ_ASSERT_UNREACHABLE( "Who's calling this from the parent process without a chrome " "window " "(it would have been exempt from the RFP targets)?"); return; } if (NS_IsMainThread()) { nsCOMPtr global = canvasRef->GetOwnerGlobal(); NS_ENSURE_TRUE_VOID(global); RefPtr window = global->GetAsInnerWindow(); NS_ENSURE_TRUE_VOID(window); RefPtr browserChild = dom::BrowserChild::GetFrom(window); NS_ENSURE_TRUE_VOID(browserChild); browserChild->SendShowCanvasPermissionPrompt(origin, hidePermissionDoorhanger); return; } class OffscreenCanvasPromptRunnable : public dom::WorkerProxyToMainThreadRunnable { public: explicit OffscreenCanvasPromptRunnable(const nsCString& aOrigin, bool aHidePermissionDoorhanger) : mOrigin(aOrigin), mHidePermissionDoorhanger(aHidePermissionDoorhanger) {} // Runnables don't support MOZ_CAN_RUN_SCRIPT, bug 1535398 MOZ_CAN_RUN_SCRIPT_BOUNDARY void RunOnMainThread( dom::WorkerPrivate* aWorkerPrivate) override { MOZ_ASSERT(aWorkerPrivate); AssertIsOnMainThread(); RefPtr inner = aWorkerPrivate->GetAncestorWindow(); RefPtr win = dom::BrowserChild::GetFrom(inner); NS_ENSURE_TRUE_VOID(win); win->SendShowCanvasPermissionPrompt(mOrigin, mHidePermissionDoorhanger); } void RunBackOnWorkerThreadForCleanup( dom::WorkerPrivate* aWorkerPrivate) override { MOZ_ASSERT(aWorkerPrivate); aWorkerPrivate->AssertIsOnWorkerThread(); } nsCString mOrigin; bool mHidePermissionDoorhanger; }; if (auto* workerPrivate = dom::GetCurrentThreadWorkerPrivate()) { RefPtr runnable = new OffscreenCanvasPromptRunnable(origin, hidePermissionDoorhanger); runnable->Dispatch(workerPrivate); return; } }; return IsImageExtractionAllowed_impl( canvasImageExtractionPrompt, canvasExtractionBeforeUserInputIsBlocked, canvasExtractionFromThirdPartiesIsBlocked, aCx, aPrincipal, getIsThirdPartyWindow, reportToConsole, prompt); } ImageExtraction ImageExtractionResult(dom::OffscreenCanvas* aOffscreenCanvas, JSContext* aCx, nsIPrincipal* aPrincipal) { if (IsUnrestrictedPrincipal(aPrincipal)) { return ImageExtraction::Unrestricted; } if (!IsImageExtractionAllowed(aOffscreenCanvas, aCx, aPrincipal)) { return ImageExtraction::Placeholder; } if (aOffscreenCanvas->ShouldResistFingerprinting( RFPTarget::CanvasRandomization) || aOffscreenCanvas->ShouldResistFingerprinting( RFPTarget::WebGLRandomization)) { if (GetCanvasExtractDataPermission(aPrincipal) == nsIPermissionManager::ALLOW_ACTION) { return ImageExtraction::Unrestricted; } return ImageExtraction::Randomize; } return ImageExtraction::Unrestricted; } bool GetCanvasContextType(const nsAString& str, dom::CanvasContextType* const out_type) { if (str.EqualsLiteral("2d")) { *out_type = dom::CanvasContextType::Canvas2D; return true; } if (str.EqualsLiteral("webgl") || str.EqualsLiteral("experimental-webgl")) { *out_type = dom::CanvasContextType::WebGL1; return true; } if (StaticPrefs::webgl_enable_webgl2()) { if (str.EqualsLiteral("webgl2")) { *out_type = dom::CanvasContextType::WebGL2; return true; } } if (gfxVars::AllowWebGPU()) { if (str.EqualsLiteral("webgpu")) { *out_type = dom::CanvasContextType::WebGPU; return true; } } if (str.EqualsLiteral("bitmaprenderer")) { *out_type = dom::CanvasContextType::ImageBitmap; return true; } return false; } /** * This security check utility might be called from an source that never * taints others. For example, while painting a CanvasPattern, which is * created from an ImageBitmap, onto a canvas. In this case, the caller could * set the CORSUsed true in order to pass this check and leave the aPrincipal * to be a nullptr since the aPrincipal is not going to be used. */ void DoDrawImageSecurityCheck(dom::HTMLCanvasElement* aCanvasElement, nsIPrincipal* aPrincipal, bool forceWriteOnly, bool CORSUsed) { // Callers should ensure that mCanvasElement is non-null before calling this if (!aCanvasElement) { NS_WARNING("DoDrawImageSecurityCheck called without canvas element!"); return; } if (aCanvasElement->IsWriteOnly() && !aCanvasElement->mExpandedReader) { return; } // If we explicitly set WriteOnly just do it and get out if (forceWriteOnly) { aCanvasElement->SetWriteOnly(); return; } // No need to do a security check if the image used CORS for the load if (CORSUsed) return; if (NS_WARN_IF(!aPrincipal)) { MOZ_ASSERT_UNREACHABLE("Must have a principal here"); aCanvasElement->SetWriteOnly(); return; } if (aCanvasElement->NodePrincipal()->Subsumes(aPrincipal)) { // This canvas has access to that image anyway return; } if (BasePrincipal::Cast(aPrincipal)->AddonPolicy()) { // This is a resource from an extension content script principal. if (aCanvasElement->mExpandedReader && aCanvasElement->mExpandedReader->Subsumes(aPrincipal)) { // This canvas already allows reading from this principal. return; } if (!aCanvasElement->mExpandedReader) { // Allow future reads from this same princial only. aCanvasElement->SetWriteOnly(aPrincipal); return; } // If we got here, this must be the *second* extension tainting // the canvas. Fall through to mark it WriteOnly for everyone. } aCanvasElement->SetWriteOnly(); } /** * This security check utility might be called from an source that never * taints others. For example, while painting a CanvasPattern, which is * created from an ImageBitmap, onto a canvas. In this case, the caller could * set the aCORSUsed true in order to pass this check and leave the aPrincipal * to be a nullptr since the aPrincipal is not going to be used. */ void DoDrawImageSecurityCheck(dom::OffscreenCanvas* aOffscreenCanvas, nsIPrincipal* aPrincipal, bool aForceWriteOnly, bool aCORSUsed) { // Callers should ensure that mCanvasElement is non-null before calling this if (NS_WARN_IF(!aOffscreenCanvas)) { return; } nsIPrincipal* expandedReader = aOffscreenCanvas->GetExpandedReader(); if (aOffscreenCanvas->IsWriteOnly() && !expandedReader) { return; } // If we explicitly set WriteOnly just do it and get out if (aForceWriteOnly) { aOffscreenCanvas->SetWriteOnly(); return; } // No need to do a security check if the image used CORS for the load if (aCORSUsed) { return; } // If we are on a worker thread, we might not have any principals at all. nsIGlobalObject* global = aOffscreenCanvas->GetOwnerGlobal(); nsIPrincipal* canvasPrincipal = global ? global->PrincipalOrNull() : nullptr; if (!aPrincipal || !canvasPrincipal) { aOffscreenCanvas->SetWriteOnly(); return; } if (canvasPrincipal->Subsumes(aPrincipal)) { // This canvas has access to that image anyway return; } if (BasePrincipal::Cast(aPrincipal)->AddonPolicy()) { // This is a resource from an extension content script principal. if (expandedReader && expandedReader->Subsumes(aPrincipal)) { // This canvas already allows reading from this principal. return; } if (!expandedReader) { // Allow future reads from this same princial only. aOffscreenCanvas->SetWriteOnly(aPrincipal); return; } // If we got here, this must be the *second* extension tainting // the canvas. Fall through to mark it WriteOnly for everyone. } aOffscreenCanvas->SetWriteOnly(); } bool CoerceDouble(const JS::Value& v, double* d) { if (v.isDouble()) { *d = v.toDouble(); } else if (v.isInt32()) { *d = double(v.toInt32()); } else if (v.isUndefined()) { *d = 0.0; } else { return false; } return true; } bool HasDrawWindowPrivilege(JSContext* aCx, JSObject* /* unused */) { return nsContentUtils::CallerHasPermission(aCx, nsGkAtoms::all_urlsPermission); } bool CheckWriteOnlySecurity(bool aCORSUsed, nsIPrincipal* aPrincipal, bool aHadCrossOriginRedirects) { if (!aPrincipal) { return true; } if (!aCORSUsed) { if (aHadCrossOriginRedirects) { return true; } nsIGlobalObject* incumbentSettingsObject = dom::GetIncumbentGlobal(); if (!incumbentSettingsObject) { return true; } nsIPrincipal* principal = incumbentSettingsObject->PrincipalOrNull(); if (NS_WARN_IF(!principal) || !(principal->Subsumes(aPrincipal))) { return true; } } return false; } } // namespace mozilla::CanvasUtils