Bug 1521243 - Show a warning for invalid declarations and filter icon for overridden...
[gecko.git] / dom / canvas / CanvasUtils.cpp
blob000f578d7002ea6dc209bf899f5ab3085a8a503e
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/. */
6 #include <stdlib.h>
7 #include <stdarg.h>
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"
22 #include "nsTArray.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"
37 #include "jsapi.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;
46 namespace mozilla {
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()) {
53 return true;
56 // Don't proceed if we don't have a document or JavaScript context.
57 if (!aDocument || !aCx) {
58 return false;
61 // The system principal can always extract canvas data.
62 if (nsContentUtils::IsSystemPrincipal(&aPrincipal)) {
63 return true;
66 // Allow extension principals.
67 auto principal = BasePrincipal::Cast(&aPrincipal);
68 if (principal->AddonPolicy() || principal->ContentScriptAddonPolicy()) {
69 return true;
72 // Get the document URI and its spec.
73 nsIURI* docURI = aDocument->GetDocumentURI();
74 nsCString docURISpec;
75 docURI->GetSpec(docURISpec);
77 // Allow local files to extract canvas data.
78 bool isFileURL;
79 if (NS_SUCCEEDED(docURI->SchemeIs("file", &isFileURL)) && isFileURL) {
80 return true;
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)) {
88 isScriptKnown = true;
89 // Don't show canvas prompt for PDF.js
90 if (scriptFile.get() &&
91 strcmp(scriptFile.get(), "resource://pdf.js/build/pdf.js") == 0) {
92 return true;
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.
105 nsresult rv;
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);
114 if (isThirdParty) {
115 nsAutoCString message;
116 message.AppendPrintf(
117 "Blocked third party %s in page %s from extracting canvas data.",
118 docURISpec.get(), topLevelDocURISpec.get());
119 if (isScriptKnown) {
120 message.AppendPrintf(" %s:%u.", scriptFile.get(), scriptLine);
122 nsContentUtils::LogMessageToConsole(message.get());
123 return false;
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.
133 uint32_t permission;
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:
139 return true;
140 case nsIPermissionManager::DENY_ACTION:
141 return false;
142 default:
143 break;
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 =
151 StaticPrefs::
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());
161 if (isScriptKnown) {
162 message.AppendPrintf(" %s:%u.", scriptFile.get(), scriptLine);
164 nsContentUtils::LogMessageToConsole(message.get());
165 } else {
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 "
170 "user.",
171 docURISpec.get(), topLevelDocURISpec.get());
172 if (isScriptKnown) {
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);
182 if (tabChild) {
183 tabChild->SendShowCanvasPermissionPrompt(topLevelDocURISpec,
184 isAutoBlockCanvas);
186 } else {
187 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
188 if (obs) {
189 obs->NotifyObservers(win,
190 isAutoBlockCanvas
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.
198 return false;
201 bool GetCanvasContextType(const nsAString& str,
202 dom::CanvasContextType* const out_type) {
203 if (str.EqualsLiteral("2d")) {
204 *out_type = dom::CanvasContextType::Canvas2D;
205 return true;
208 if (str.EqualsLiteral("webgl") || str.EqualsLiteral("experimental-webgl")) {
209 *out_type = dom::CanvasContextType::WebGL1;
210 return true;
213 if (WebGL2Context::IsSupported()) {
214 if (str.EqualsLiteral("webgl2")) {
215 *out_type = dom::CanvasContextType::WebGL2;
216 return true;
220 if (str.EqualsLiteral("bitmaprenderer")) {
221 *out_type = dom::CanvasContextType::ImageBitmap;
222 return true;
225 return false;
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,
237 bool CORSUsed) {
238 // Callers should ensure that mCanvasElement is non-null before calling this
239 if (!aCanvasElement) {
240 NS_WARNING("DoDrawImageSecurityCheck called without canvas element!");
241 return;
244 if (aCanvasElement->IsWriteOnly() && !aCanvasElement->mExpandedReader) {
245 return;
248 // If we explicitly set WriteOnly just do it and get out
249 if (forceWriteOnly) {
250 aCanvasElement->SetWriteOnly();
251 return;
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
261 return;
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.
270 return;
273 if (!aCanvasElement->mExpandedReader) {
274 // Allow future reads from this same princial only.
275 aCanvasElement->SetWriteOnly(aPrincipal);
276 return;
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) {
287 if (v.isDouble()) {
288 *d = v.toDouble();
289 } else if (v.isInt32()) {
290 *d = double(v.toInt32());
291 } else if (v.isUndefined()) {
292 *d = 0.0;
293 } else {
294 return false;
296 return true;
299 bool HasDrawWindowPrivilege(JSContext* aCx, JSObject* /* unused */) {
300 return nsContentUtils::CallerHasPermission(aCx,
301 nsGkAtoms::all_urlsPermission);
304 } // namespace CanvasUtils
305 } // namespace mozilla