1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
9 #include "nsIServiceManager.h"
11 #include "nsIConsoleService.h"
12 #include "nsICanvasRenderingContextInternal.h"
13 #include "nsIHTMLCollection.h"
14 #include "mozilla/dom/HTMLCanvasElement.h"
15 #include "mozilla/dom/TabChild.h"
16 #include "mozilla/EventStateManager.h"
17 #include "mozilla/StaticPrefs.h"
18 #include "nsIPrincipal.h"
20 #include "nsGfxCIID.h"
24 #include "CanvasUtils.h"
25 #include "mozilla/gfx/Matrix.h"
26 #include "WebGL2Context.h"
28 #include "nsIScriptObjectPrincipal.h"
29 #include "nsIPermissionManager.h"
30 #include "nsIObserverService.h"
31 #include "mozilla/Services.h"
32 #include "mozIThirdPartyUtil.h"
33 #include "nsContentUtils.h"
34 #include "nsUnicharUtils.h"
35 #include "nsPrintfCString.h"
36 #include "nsIConsoleService.h"
39 #define TOPIC_CANVAS_PERMISSIONS_PROMPT "canvas-permissions-prompt"
40 #define TOPIC_CANVAS_PERMISSIONS_PROMPT_HIDE_DOORHANGER \
41 "canvas-permissions-prompt-hide-doorhanger"
42 #define PERMISSION_CANVAS_EXTRACT_DATA "canvas"
44 using namespace mozilla::gfx
;
47 namespace CanvasUtils
{
49 bool IsImageExtractionAllowed(Document
* aDocument
, JSContext
* aCx
,
50 nsIPrincipal
& aPrincipal
) {
51 // Do the rest of the checks only if privacy.resistFingerprinting is on.
52 if (!nsContentUtils::ShouldResistFingerprinting()) {
56 // Don't proceed if we don't have a document or JavaScript context.
57 if (!aDocument
|| !aCx
) {
61 // The system principal can always extract canvas data.
62 if (nsContentUtils::IsSystemPrincipal(&aPrincipal
)) {
66 // Allow extension principals.
67 auto principal
= BasePrincipal::Cast(&aPrincipal
);
68 if (principal
->AddonPolicy() || principal
->ContentScriptAddonPolicy()) {
72 // Get the document URI and its spec.
73 nsIURI
* docURI
= aDocument
->GetDocumentURI();
75 docURI
->GetSpec(docURISpec
);
77 // Allow local files to extract canvas data.
79 if (NS_SUCCEEDED(docURI
->SchemeIs("file", &isFileURL
)) && isFileURL
) {
83 // Get calling script file and line for logging.
84 JS::AutoFilename scriptFile
;
85 unsigned scriptLine
= 0;
86 bool isScriptKnown
= false;
87 if (JS::DescribeScriptedCaller(aCx
, &scriptFile
, &scriptLine
)) {
89 // Don't show canvas prompt for PDF.js
90 if (scriptFile
.get() &&
91 strcmp(scriptFile
.get(), "resource://pdf.js/build/pdf.js") == 0) {
96 Document
* topLevelDocument
= aDocument
->GetTopLevelContentDocument();
97 nsIURI
* topLevelDocURI
=
98 topLevelDocument
? topLevelDocument
->GetDocumentURI() : nullptr;
99 nsCString topLevelDocURISpec
;
100 if (topLevelDocURI
) {
101 topLevelDocURI
->GetSpec(topLevelDocURISpec
);
104 // Load Third Party Util service.
106 nsCOMPtr
<mozIThirdPartyUtil
> thirdPartyUtil
=
107 do_GetService(THIRDPARTYUTIL_CONTRACTID
, &rv
);
108 NS_ENSURE_SUCCESS(rv
, false);
110 // Block all third-party attempts to extract canvas.
111 bool isThirdParty
= true;
112 rv
= thirdPartyUtil
->IsThirdPartyURI(topLevelDocURI
, docURI
, &isThirdParty
);
113 NS_ENSURE_SUCCESS(rv
, false);
115 nsAutoCString message
;
116 message
.AppendPrintf(
117 "Blocked third party %s in page %s from extracting canvas data.",
118 docURISpec
.get(), topLevelDocURISpec
.get());
120 message
.AppendPrintf(" %s:%u.", scriptFile
.get(), scriptLine
);
122 nsContentUtils::LogMessageToConsole(message
.get());
126 // Load Permission Manager service.
127 nsCOMPtr
<nsIPermissionManager
> permissionManager
=
128 do_GetService(NS_PERMISSIONMANAGER_CONTRACTID
, &rv
);
129 NS_ENSURE_SUCCESS(rv
, false);
131 // Check if the site has permission to extract canvas data.
132 // Either permit or block extraction if a stored permission setting exists.
134 rv
= permissionManager
->TestPermission(
135 topLevelDocURI
, PERMISSION_CANVAS_EXTRACT_DATA
, &permission
);
136 NS_ENSURE_SUCCESS(rv
, false);
137 switch (permission
) {
138 case nsIPermissionManager::ALLOW_ACTION
:
140 case nsIPermissionManager::DENY_ACTION
:
146 // At this point, permission is unknown
147 // (nsIPermissionManager::UNKNOWN_ACTION).
149 // Check if the request is in response to user input
150 bool isAutoBlockCanvas
=
152 privacy_resistFingerprinting_autoDeclineNoUserInputCanvasPrompts() &&
153 !EventStateManager::IsHandlingUserInput();
155 if (isAutoBlockCanvas
) {
156 nsAutoCString message
;
157 message
.AppendPrintf(
158 "Blocked %s in page %s from extracting canvas data because no user "
159 "input was detected.",
160 docURISpec
.get(), topLevelDocURISpec
.get());
162 message
.AppendPrintf(" %s:%u.", scriptFile
.get(), scriptLine
);
164 nsContentUtils::LogMessageToConsole(message
.get());
166 // It was in response to user input, so log and display the prompt.
167 nsAutoCString message
;
168 message
.AppendPrintf(
169 "Blocked %s in page %s from extracting canvas data, but prompting the "
171 docURISpec
.get(), topLevelDocURISpec
.get());
173 message
.AppendPrintf(" %s:%u.", scriptFile
.get(), scriptLine
);
175 nsContentUtils::LogMessageToConsole(message
.get());
178 // Prompt the user (asynchronous).
179 nsPIDOMWindowOuter
* win
= aDocument
->GetWindow();
180 if (XRE_IsContentProcess()) {
181 TabChild
* tabChild
= TabChild::GetFrom(win
);
183 tabChild
->SendShowCanvasPermissionPrompt(topLevelDocURISpec
,
187 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
189 obs
->NotifyObservers(win
,
191 ? TOPIC_CANVAS_PERMISSIONS_PROMPT_HIDE_DOORHANGER
192 : TOPIC_CANVAS_PERMISSIONS_PROMPT
,
193 NS_ConvertUTF8toUTF16(topLevelDocURISpec
).get());
197 // We don't extract the image for now -- user may override at prompt.
201 bool GetCanvasContextType(const nsAString
& str
,
202 dom::CanvasContextType
* const out_type
) {
203 if (str
.EqualsLiteral("2d")) {
204 *out_type
= dom::CanvasContextType::Canvas2D
;
208 if (str
.EqualsLiteral("webgl") || str
.EqualsLiteral("experimental-webgl")) {
209 *out_type
= dom::CanvasContextType::WebGL1
;
213 if (WebGL2Context::IsSupported()) {
214 if (str
.EqualsLiteral("webgl2")) {
215 *out_type
= dom::CanvasContextType::WebGL2
;
220 if (str
.EqualsLiteral("bitmaprenderer")) {
221 *out_type
= dom::CanvasContextType::ImageBitmap
;
229 * This security check utility might be called from an source that never taints
230 * others. For example, while painting a CanvasPattern, which is created from an
231 * ImageBitmap, onto a canvas. In this case, the caller could set the CORSUsed
232 * true in order to pass this check and leave the aPrincipal to be a nullptr
233 * since the aPrincipal is not going to be used.
235 void DoDrawImageSecurityCheck(dom::HTMLCanvasElement
* aCanvasElement
,
236 nsIPrincipal
* aPrincipal
, bool forceWriteOnly
,
238 // Callers should ensure that mCanvasElement is non-null before calling this
239 if (!aCanvasElement
) {
240 NS_WARNING("DoDrawImageSecurityCheck called without canvas element!");
244 if (aCanvasElement
->IsWriteOnly() && !aCanvasElement
->mExpandedReader
) {
248 // If we explicitly set WriteOnly just do it and get out
249 if (forceWriteOnly
) {
250 aCanvasElement
->SetWriteOnly();
254 // No need to do a security check if the image used CORS for the load
255 if (CORSUsed
) return;
257 MOZ_ASSERT(aPrincipal
, "Must have a principal here");
259 if (aCanvasElement
->NodePrincipal()->Subsumes(aPrincipal
)) {
260 // This canvas has access to that image anyway
264 if (BasePrincipal::Cast(aPrincipal
)->AddonPolicy()) {
265 // This is a resource from an extension content script principal.
267 if (aCanvasElement
->mExpandedReader
&&
268 aCanvasElement
->mExpandedReader
->Subsumes(aPrincipal
)) {
269 // This canvas already allows reading from this principal.
273 if (!aCanvasElement
->mExpandedReader
) {
274 // Allow future reads from this same princial only.
275 aCanvasElement
->SetWriteOnly(aPrincipal
);
279 // If we got here, this must be the *second* extension tainting
280 // the canvas. Fall through to mark it WriteOnly for everyone.
283 aCanvasElement
->SetWriteOnly();
286 bool CoerceDouble(const JS::Value
& v
, double* d
) {
289 } else if (v
.isInt32()) {
290 *d
= double(v
.toInt32());
291 } else if (v
.isUndefined()) {
299 bool HasDrawWindowPrivilege(JSContext
* aCx
, JSObject
* /* unused */) {
300 return nsContentUtils::CallerHasPermission(aCx
,
301 nsGkAtoms::all_urlsPermission
);
304 } // namespace CanvasUtils
305 } // namespace mozilla