--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -119,17 +119,17 @@ DOMInterfaces = {
'CacheStorage': {
'implicitJSContext': [ 'match' ],
'nativeType': 'mozilla::dom::cache::CacheStorage',
},
'CanvasRenderingContext2D': {
'implicitJSContext': [
- 'createImageData', 'getImageData'
+ 'createImageData', 'getImageData', 'isPointInPath', 'isPointInStroke'
],
'binaryNames': {
'mozImageSmoothingEnabled': 'imageSmoothingEnabled'
}
},
'CaretPosition' : {
'nativeType': 'nsDOMCaretPosition',
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -4900,57 +4900,71 @@ CanvasRenderingContext2D::SetLineDashOff
}
double
CanvasRenderingContext2D::LineDashOffset() const {
return CurrentState().dashOffset;
}
bool
-CanvasRenderingContext2D::IsPointInPath(double aX, double aY, const CanvasWindingRule& aWinding)
+CanvasRenderingContext2D::IsPointInPath(JSContext* aCx, double aX, double aY, const CanvasWindingRule& aWinding)
{
if (!FloatValidate(aX, aY)) {
return false;
}
+ // Check for site-specific permission and return false if no permission.
+ if (mCanvasElement) {
+ nsCOMPtr<nsIDocument> ownerDoc = mCanvasElement->OwnerDoc();
+ if (!ownerDoc || !CanvasUtils::IsImageExtractionAllowed(ownerDoc, aCx))
+ return false;
+ }
+
EnsureUserSpacePath(aWinding);
if (!mPath) {
return false;
}
if (mPathTransformWillUpdate) {
return mPath->ContainsPoint(Point(aX, aY), mPathToDS);
}
return mPath->ContainsPoint(Point(aX, aY), mTarget->GetTransform());
}
-bool CanvasRenderingContext2D::IsPointInPath(const CanvasPath& aPath, double aX, double aY, const CanvasWindingRule& aWinding)
+bool CanvasRenderingContext2D::IsPointInPath(JSContext* aCx, const CanvasPath& aPath, double aX, double aY, const CanvasWindingRule& aWinding)
{
if (!FloatValidate(aX, aY)) {
return false;
}
EnsureTarget();
if (!IsTargetValid()) {
return false;
}
RefPtr<gfx::Path> tempPath = aPath.GetPath(aWinding, mTarget);
return tempPath->ContainsPoint(Point(aX, aY), mTarget->GetTransform());
}
bool
-CanvasRenderingContext2D::IsPointInStroke(double aX, double aY)
+CanvasRenderingContext2D::IsPointInStroke(JSContext* aCx, double aX, double aY)
{
if (!FloatValidate(aX, aY)) {
return false;
}
+ // Check for site-specific permission and return false if no permission.
+ if (mCanvasElement) {
+ nsCOMPtr<nsIDocument> ownerDoc = mCanvasElement->OwnerDoc();
+ if (!ownerDoc || !CanvasUtils::IsImageExtractionAllowed(ownerDoc, aCx))
+ return false;
+ }
+
EnsureUserSpacePath();
if (!mPath) {
return false;
}
const ContextState &state = CurrentState();
StrokeOptions strokeOptions(state.lineWidth,
@@ -4962,17 +4976,17 @@ CanvasRenderingContext2D::IsPointInStrok
state.dashOffset);
if (mPathTransformWillUpdate) {
return mPath->StrokeContainsPoint(strokeOptions, Point(aX, aY), mPathToDS);
}
return mPath->StrokeContainsPoint(strokeOptions, Point(aX, aY), mTarget->GetTransform());
}
-bool CanvasRenderingContext2D::IsPointInStroke(const CanvasPath& aPath, double aX, double aY)
+bool CanvasRenderingContext2D::IsPointInStroke(JSContext* aCx, const CanvasPath& aPath, double aX, double aY)
{
if (!FloatValidate(aX, aY)) {
return false;
}
EnsureTarget();
if (!IsTargetValid()) {
return false;
@@ -5803,36 +5817,53 @@ CanvasRenderingContext2D::GetImageDataAr
if (!readback || !readback->Map(DataSourceSurface::READ, &rawData)) {
return NS_ERROR_OUT_OF_MEMORY;
}
IntRect dstWriteRect = srcReadRect;
dstWriteRect.MoveBy(-aX, -aY);
- {
+ // Check for site-specific permission. This check is not needed if the
+ // canvas was created with a docshell (that is only done for special
+ // internal uses).
+ bool usePlaceholder = false;
+ if (mCanvasElement) {
+ nsCOMPtr<nsIDocument> ownerDoc = mCanvasElement->OwnerDoc();
+ usePlaceholder = !ownerDoc ||
+ !CanvasUtils::IsImageExtractionAllowed(ownerDoc, aCx);
+ }
+
+ do {
JS::AutoCheckCannotGC nogc;
bool isShared;
uint8_t* data = JS_GetUint8ClampedArrayData(darray, &isShared, nogc);
MOZ_ASSERT(!isShared); // Should not happen, data was created above
uint32_t srcStride = rawData.mStride;
uint8_t* src = rawData.mData + srcReadRect.y * srcStride + srcReadRect.x * 4;
+
+ // Return all-white, opaque pixel data if no permission.
+ if (usePlaceholder) {
+ memset(data, 0xFF, len.value());
+ break;
+ }
+
uint8_t* dst = data + dstWriteRect.y * (aWidth * 4) + dstWriteRect.x * 4;
if (mOpaque) {
SwizzleData(src, srcStride, SurfaceFormat::X8R8G8B8_UINT32,
dst, aWidth * 4, SurfaceFormat::R8G8B8A8,
dstWriteRect.Size());
} else {
UnpremultiplyData(src, srcStride, SurfaceFormat::A8R8G8B8_UINT32,
dst, aWidth * 4, SurfaceFormat::R8G8B8A8,
dstWriteRect.Size());
}
- }
+ } while (false);
readback->Unmap();
*aRetval = darray;
return NS_OK;
}
void
CanvasRenderingContext2D::EnsureErrorTarget()
--- a/dom/canvas/CanvasRenderingContext2D.h
+++ b/dom/canvas/CanvasRenderingContext2D.h
@@ -199,20 +199,20 @@ public:
void Fill(const CanvasWindingRule& aWinding);
void Fill(const CanvasPath& aPath, const CanvasWindingRule& aWinding);
void Stroke();
void Stroke(const CanvasPath& aPath);
void DrawFocusIfNeeded(mozilla::dom::Element& aElement, ErrorResult& aRv);
bool DrawCustomFocusRing(mozilla::dom::Element& aElement);
void Clip(const CanvasWindingRule& aWinding);
void Clip(const CanvasPath& aPath, const CanvasWindingRule& aWinding);
- bool IsPointInPath(double aX, double aY, const CanvasWindingRule& aWinding);
- bool IsPointInPath(const CanvasPath& aPath, double aX, double aY, const CanvasWindingRule& aWinding);
- bool IsPointInStroke(double aX, double aY);
- bool IsPointInStroke(const CanvasPath& aPath, double aX, double aY);
+ bool IsPointInPath(JSContext* aCx, double aX, double aY, const CanvasWindingRule& aWinding);
+ bool IsPointInPath(JSContext* aCx, const CanvasPath& aPath, double aX, double aY, const CanvasWindingRule& aWinding);
+ bool IsPointInStroke(JSContext* aCx, double aX, double aY);
+ bool IsPointInStroke(JSContext* aCx, const CanvasPath& aPath, double aX, double aY);
void FillText(const nsAString& aText, double aX, double aY,
const Optional<double>& aMaxWidth,
mozilla::ErrorResult& aError);
void StrokeText(const nsAString& aText, double aX, double aY,
const Optional<double>& aMaxWidth,
mozilla::ErrorResult& aError);
TextMetrics*
MeasureText(const nsAString& aRawText, mozilla::ErrorResult& aError);
--- a/dom/canvas/CanvasRenderingContextHelper.cpp
+++ b/dom/canvas/CanvasRenderingContextHelper.cpp
@@ -20,16 +20,17 @@ namespace mozilla {
namespace dom {
void
CanvasRenderingContextHelper::ToBlob(JSContext* aCx,
nsIGlobalObject* aGlobal,
BlobCallback& aCallback,
const nsAString& aType,
JS::Handle<JS::Value> aParams,
+ bool aUsePlaceholder,
ErrorResult& aRv)
{
// Encoder callback when encoding is complete.
class EncodeCallback : public EncodeCompleteCallback
{
public:
EncodeCallback(nsIGlobalObject* aGlobal, BlobCallback* aCallback)
: mGlobal(aGlobal)
@@ -53,25 +54,26 @@ CanvasRenderingContextHelper::ToBlob(JSC
nsCOMPtr<nsIGlobalObject> mGlobal;
RefPtr<BlobCallback> mBlobCallback;
};
RefPtr<EncodeCompleteCallback> callback =
new EncodeCallback(aGlobal, &aCallback);
- ToBlob(aCx, aGlobal, callback, aType, aParams, aRv);
+ ToBlob(aCx, aGlobal, callback, aType, aParams, aUsePlaceholder, aRv);
}
void
CanvasRenderingContextHelper::ToBlob(JSContext* aCx,
nsIGlobalObject* aGlobal,
EncodeCompleteCallback* aCallback,
const nsAString& aType,
JS::Handle<JS::Value> aParams,
+ bool aUsePlaceholder,
ErrorResult& aRv)
{
nsAutoString type;
nsContentUtils::ASCIIToLower(aType, type);
nsAutoString params;
bool usingCustomParseOptions;
aRv = ParseParams(aCx, type, aParams, params, &usingCustomParseOptions);
@@ -102,16 +104,17 @@ CanvasRenderingContextHelper::ToBlob(JSC
RefPtr<EncodeCompleteCallback> callback = aCallback;
aRv = ImageEncoder::ExtractDataAsync(type,
params,
usingCustomParseOptions,
Move(imageBuffer),
format,
GetWidthHeight(),
+ aUsePlaceholder,
callback);
}
already_AddRefed<nsICanvasRenderingContextInternal>
CanvasRenderingContextHelper::CreateContext(CanvasContextType aContextType)
{
return CreateContextHelper(aContextType, layers::LayersBackend::LAYERS_NONE);
}
--- a/dom/canvas/CanvasRenderingContextHelper.h
+++ b/dom/canvas/CanvasRenderingContextHelper.h
@@ -53,21 +53,21 @@ protected:
virtual nsresult ParseParams(JSContext* aCx,
const nsAString& aType,
const JS::Value& aEncoderOptions,
nsAString& outParams,
bool* const outCustomParseOptions);
void ToBlob(JSContext* aCx, nsIGlobalObject* global, BlobCallback& aCallback,
const nsAString& aType, JS::Handle<JS::Value> aParams,
- ErrorResult& aRv);
+ bool aUsePlaceholder, ErrorResult& aRv);
void ToBlob(JSContext* aCx, nsIGlobalObject* aGlobal, EncodeCompleteCallback* aCallback,
const nsAString& aType, JS::Handle<JS::Value> aParams,
- ErrorResult& aRv);
+ bool aUsePlaceholder, ErrorResult& aRv);
virtual already_AddRefed<nsICanvasRenderingContextInternal>
CreateContext(CanvasContextType aContextType);
already_AddRefed<nsICanvasRenderingContextInternal>
CreateContextHelper(CanvasContextType aContextType,
layers::LayersBackend aCompositorBackend);
--- a/dom/canvas/CanvasUtils.cpp
+++ b/dom/canvas/CanvasUtils.cpp
@@ -8,31 +8,171 @@
#include "nsIServiceManager.h"
#include "nsIConsoleService.h"
#include "nsIDOMCanvasRenderingContext2D.h"
#include "nsICanvasRenderingContextInternal.h"
#include "nsIHTMLCollection.h"
#include "mozilla/dom/HTMLCanvasElement.h"
+#include "mozilla/dom/TabChild.h"
#include "nsIPrincipal.h"
#include "nsGfxCIID.h"
#include "nsTArray.h"
#include "CanvasUtils.h"
#include "mozilla/gfx/Matrix.h"
#include "WebGL2Context.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsIPermissionManager.h"
+#include "nsIObserverService.h"
+#include "mozilla/Services.h"
+#include "mozIThirdPartyUtil.h"
+#include "nsContentUtils.h"
+#include "nsUnicharUtils.h"
+#include "nsPrintfCString.h"
+#include "nsIConsoleService.h"
+#include "jsapi.h"
+
+#define TOPIC_CANVAS_PERMISSIONS_PROMPT "canvas-permissions-prompt"
+#define PERMISSION_CANVAS_EXTRACT_DATA "canvas/extractData"
+
using namespace mozilla::gfx;
namespace mozilla {
namespace CanvasUtils {
+bool IsImageExtractionAllowed(nsIDocument *aDocument, JSContext *aCx)
+{
+ // Do the rest of the checks only if privacy.resistFingerprinting is on.
+ if (!nsContentUtils::ShouldResistFingerprinting()) {
+ return true;
+ }
+
+ // Don't proceed if we don't have a document or JavaScript context.
+ if (!aDocument || !aCx) {
+ return false;
+ }
+
+ // Documents with system principal can always extract canvas data.
+ nsPIDOMWindowOuter *win = aDocument->GetWindow();
+ nsCOMPtr<nsIScriptObjectPrincipal> sop(do_QueryInterface(win));
+ if (sop && nsContentUtils::IsSystemPrincipal(sop->GetPrincipal())) {
+ return true;
+ }
+
+ // Always give permission to chrome scripts (e.g. Page Inspector).
+ if (nsContentUtils::ThreadsafeIsCallerChrome()) {
+ return true;
+ }
+
+ // Get the document URI and its spec.
+ nsIURI *docURI = aDocument->GetDocumentURI();
+ nsCString docURISpec;
+ docURI->GetSpec(docURISpec);
+
+ // Allow local files to extract canvas data.
+ bool isFileURL;
+ (void) docURI->SchemeIs("file", &isFileURL);
+ if (isFileURL) {
+ return true;
+ }
+
+ // Get calling script file and line for logging.
+ JS::AutoFilename scriptFile;
+ unsigned scriptLine = 0;
+ bool isScriptKnown = false;
+ if (JS::DescribeScriptedCaller(aCx, &scriptFile, &scriptLine)) {
+ isScriptKnown = true;
+ // Don't show canvas prompt for PDF.js
+ if (scriptFile.get() &&
+ strcmp(scriptFile.get(), "resource://pdf.js/build/pdf.js") == 0) {
+ return true;
+ }
+ }
+
+ nsIDocument* topLevelDocument = aDocument->GetTopLevelContentDocument();
+ nsIURI *topLevelDocURI = topLevelDocument ? topLevelDocument->GetDocumentURI() : nullptr;
+ nsCString topLevelDocURISpec;
+ if (topLevelDocURI) {
+ topLevelDocURI->GetSpec(topLevelDocURISpec);
+ }
+
+ // Load Third Party Util service.
+ nsresult rv;
+ nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil =
+ do_GetService(THIRDPARTYUTIL_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // Block all third-party attempts to extract canvas.
+ bool isThirdParty = true;
+ rv = thirdPartyUtil->IsThirdPartyURI(topLevelDocURI, docURI, &isThirdParty);
+ NS_ENSURE_SUCCESS(rv, false);
+ if (isThirdParty) {
+ nsAutoCString message;
+ message.AppendPrintf("Blocked third party %s in page %s from extracting canvas data.",
+ docURISpec.get(), topLevelDocURISpec.get());
+ if (isScriptKnown) {
+ message.AppendPrintf(" %s:%u.", scriptFile.get(), scriptLine);
+ }
+ nsContentUtils::LogMessageToConsole(message.get());
+ return false;
+ }
+
+ // Load Permission Manager service.
+ nsCOMPtr<nsIPermissionManager> permissionManager =
+ do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // Check if the site has permission to extract canvas data.
+ // Either permit or block extraction if a stored permission setting exists.
+ uint32_t permission;
+ rv = permissionManager->TestPermission(topLevelDocURI,
+ PERMISSION_CANVAS_EXTRACT_DATA,
+ &permission);
+ NS_ENSURE_SUCCESS(rv, false);
+ switch (permission) {
+ case nsIPermissionManager::ALLOW_ACTION:
+ return true;
+ case nsIPermissionManager::DENY_ACTION:
+ return false;
+ default:
+ break;
+ }
+
+ // At this point, permission is unknown (nsIPermissionManager::UNKNOWN_ACTION).
+ nsAutoCString message;
+ message.AppendPrintf("Blocked %s in page %s from extracting canvas data.",
+ docURISpec.get(), topLevelDocURISpec.get());
+ if (isScriptKnown) {
+ message.AppendPrintf(" %s:%u.", scriptFile.get(), scriptLine);
+ }
+ nsContentUtils::LogMessageToConsole(message.get());
+
+ // Prompt the user (asynchronous).
+ if (XRE_IsContentProcess()) {
+ TabChild* tabChild = TabChild::GetFrom(win);
+ if (tabChild) {
+ tabChild->SendShowCanvasPermissionPrompt(topLevelDocURISpec);
+ }
+ } else {
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->NotifyObservers(win, TOPIC_CANVAS_PERMISSIONS_PROMPT,
+ NS_ConvertUTF8toUTF16(topLevelDocURISpec).get());
+ }
+ }
+
+ // We don't extract the image for now -- user may override at prompt.
+ return false;
+}
+
bool
GetCanvasContextType(const nsAString& str, dom::CanvasContextType* const out_type)
{
if (str.EqualsLiteral("2d")) {
*out_type = dom::CanvasContextType::Canvas2D;
return true;
}
--- a/dom/canvas/CanvasUtils.h
+++ b/dom/canvas/CanvasUtils.h
@@ -44,16 +44,19 @@ inline bool CheckSaneSubrectSize(int32_t
void DoDrawImageSecurityCheck(dom::HTMLCanvasElement *aCanvasElement,
nsIPrincipal *aPrincipal,
bool forceWriteOnly,
bool CORSUsed);
// Check if the context is chrome or has the permission to drawWindow
bool HasDrawWindowPrivilege(JSContext* aCx, JSObject* aObj);
+// Check site-specific permission and display prompt if appropriate.
+bool IsImageExtractionAllowed(nsIDocument *aDocument, JSContext *aCx);
+
// Make a double out of |v|, treating undefined values as 0.0 (for
// the sake of sparse arrays). Return true iff coercion
// succeeded.
bool CoerceDouble(const JS::Value& v, double* d);
/* Float validation stuff */
#define VALIDATE(_f) if (!IsFinite(_f)) return false
--- a/dom/canvas/OffscreenCanvas.cpp
+++ b/dom/canvas/OffscreenCanvas.cpp
@@ -279,18 +279,23 @@ OffscreenCanvas::ToBlob(JSContext* aCx,
nsCOMPtr<nsIGlobalObject> mGlobal;
RefPtr<Promise> mPromise;
};
RefPtr<EncodeCompleteCallback> callback =
new EncodeCallback(global, promise);
- CanvasRenderingContextHelper::ToBlob(aCx, global,
- callback, aType, aParams, aRv);
+ // TODO: Can we obtain the context and document here somehow
+ // so that we can decide when usePlaceholder should be true/false?
+ // See https://trac.torproject.org/18599
+ // For now, we always return a placeholder if fingerprinting resistance is on.
+ bool usePlaceholder = nsContentUtils::ShouldResistFingerprinting();
+ CanvasRenderingContextHelper::ToBlob(aCx, global, callback, aType, aParams,
+ usePlaceholder, aRv);
return promise.forget();
}
already_AddRefed<gfx::SourceSurface>
OffscreenCanvas::GetSurfaceSnapshot(gfxAlphaType* const aOutAlphaType)
{
if (!mCurrentContext) {
--- a/dom/html/HTMLCanvasElement.cpp
+++ b/dom/html/HTMLCanvasElement.cpp
@@ -39,16 +39,17 @@
#include "nsIXPConnect.h"
#include "nsJSUtils.h"
#include "nsLayoutUtils.h"
#include "nsMathUtils.h"
#include "nsNetUtil.h"
#include "nsRefreshDriver.h"
#include "nsStreamUtils.h"
#include "ActiveLayerTracker.h"
+#include "CanvasUtils.h"
#include "VRManagerChild.h"
#include "WebGL1Context.h"
#include "WebGL2Context.h"
using namespace mozilla::layers;
using namespace mozilla::gfx;
NS_IMPL_NS_NEW_HTML_ELEMENT(Canvas)
@@ -57,26 +58,29 @@ namespace mozilla {
namespace dom {
class RequestedFrameRefreshObserver : public nsARefreshObserver
{
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RequestedFrameRefreshObserver, override)
public:
RequestedFrameRefreshObserver(HTMLCanvasElement* const aOwningElement,
- nsRefreshDriver* aRefreshDriver)
+ nsRefreshDriver* aRefreshDriver,
+ bool aReturnPlaceholderData)
: mRegistered(false),
+ mReturnPlaceholderData(aReturnPlaceholderData),
mOwningElement(aOwningElement),
mRefreshDriver(aRefreshDriver)
{
MOZ_ASSERT(mOwningElement);
}
static already_AddRefed<DataSourceSurface>
- CopySurface(const RefPtr<SourceSurface>& aSurface)
+ CopySurface(const RefPtr<SourceSurface>& aSurface,
+ bool aReturnPlaceholderData)
{
RefPtr<DataSourceSurface> data = aSurface->GetDataSurface();
if (!data) {
return nullptr;
}
DataSourceSurface::ScopedMap read(data, DataSourceSurface::READ);
if (!read.IsMapped()) {
@@ -95,22 +99,33 @@ public:
if (!write.IsMapped()) {
return nullptr;
}
MOZ_ASSERT(read.GetStride() == write.GetStride());
MOZ_ASSERT(data->GetSize() == copy->GetSize());
MOZ_ASSERT(data->GetFormat() == copy->GetFormat());
- memcpy(write.GetData(), read.GetData(),
- write.GetStride() * copy->GetSize().height);
+ if (aReturnPlaceholderData) {
+ // If returning placeholder data, fill the frame copy with white pixels.
+ memset(write.GetData(), 0xFF,
+ write.GetStride() * copy->GetSize().height);
+ } else {
+ memcpy(write.GetData(), read.GetData(),
+ write.GetStride() * copy->GetSize().height);
+ }
return copy.forget();
}
+ void SetReturnPlaceholderData(bool aReturnPlaceholderData)
+ {
+ mReturnPlaceholderData = aReturnPlaceholderData;
+ }
+
void WillRefresh(TimeStamp aTime) override
{
MOZ_ASSERT(NS_IsMainThread());
AUTO_PROFILER_LABEL("RequestedFrameRefreshObserver::WillRefresh", OTHER);
if (!mOwningElement) {
return;
@@ -139,17 +154,17 @@ public:
return;
}
}
RefPtr<DataSourceSurface> copy;
{
AUTO_PROFILER_LABEL(
"RequestedFrameRefreshObserver::WillRefresh:CopySurface", OTHER);
- copy = CopySurface(snapshot);
+ copy = CopySurface(snapshot, mReturnPlaceholderData);
if (!copy) {
return;
}
}
{
AUTO_PROFILER_LABEL(
"RequestedFrameRefreshObserver::WillRefresh:SetFrame", OTHER);
@@ -196,16 +211,17 @@ public:
private:
virtual ~RequestedFrameRefreshObserver()
{
MOZ_ASSERT(!mRefreshDriver);
MOZ_ASSERT(!mRegistered);
}
bool mRegistered;
+ bool mReturnPlaceholderData;
HTMLCanvasElement* const mOwningElement;
RefPtr<nsRefreshDriver> mRefreshDriver;
};
// ---------------------------------------------------------------------------
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(HTMLCanvasPrintState, mCanvas,
mContext, mCallback)
@@ -749,33 +765,45 @@ HTMLCanvasElement::CaptureStream(const O
return nullptr;
}
RefPtr<MediaStreamTrack> track =
stream->CreateDOMTrack(videoTrackId, MediaSegment::VIDEO,
new CanvasCaptureTrackSource(principal, stream));
stream->AddTrackInternal(track);
- rv = RegisterFrameCaptureListener(stream->FrameCaptureListener());
+ // Check site-specific permission and display prompt if appropriate.
+ // If no permission, arrange for the frame capture listener to return
+ // all-white, opaque image data.
+ bool usePlaceholder = !CanvasUtils::IsImageExtractionAllowed(
+ OwnerDoc(),
+ nsContentUtils::GetCurrentJSContext());
+
+ rv = RegisterFrameCaptureListener(stream->FrameCaptureListener(), usePlaceholder);
if (NS_FAILED(rv)) {
aRv.Throw(rv);
return nullptr;
}
return stream.forget();
}
nsresult
-HTMLCanvasElement::ExtractData(nsAString& aType,
+HTMLCanvasElement::ExtractData(JSContext* aCx,
+ nsAString& aType,
const nsAString& aOptions,
nsIInputStream** aStream)
{
+ // Check site-specific permission and display prompt if appropriate.
+ // If no permission, return all-white, opaque image data.
+ bool usePlaceholder = !CanvasUtils::IsImageExtractionAllowed(OwnerDoc(), aCx);
return ImageEncoder::ExtractData(aType,
aOptions,
GetSize(),
+ usePlaceholder,
mCurrentContext,
mAsyncCanvasRenderer,
aStream);
}
nsresult
HTMLCanvasElement::ToDataURLImpl(JSContext* aCx,
const nsAString& aMimeType,
@@ -795,22 +823,22 @@ HTMLCanvasElement::ToDataURLImpl(JSConte
bool usingCustomParseOptions;
nsresult rv =
ParseParams(aCx, type, aEncoderOptions, params, &usingCustomParseOptions);
if (NS_FAILED(rv)) {
return rv;
}
nsCOMPtr<nsIInputStream> stream;
- rv = ExtractData(type, params, getter_AddRefs(stream));
+ rv = ExtractData(aCx, type, params, getter_AddRefs(stream));
// If there are unrecognized custom parse options, we should fall back to
// the default values for the encoder without any options at all.
if (rv == NS_ERROR_INVALID_ARG && usingCustomParseOptions) {
- rv = ExtractData(type, EmptyString(), getter_AddRefs(stream));
+ rv = ExtractData(aCx, type, EmptyString(), getter_AddRefs(stream));
}
NS_ENSURE_SUCCESS(rv, rv);
// build data URL string
aDataURL = NS_LITERAL_STRING("data:") + type + NS_LITERAL_STRING(";base64,");
uint64_t count;
@@ -850,18 +878,21 @@ HTMLCanvasElement::ToBlob(JSContext* aCx
&aCallback,
static_cast<void (BlobCallback::*)(Blob*, const char*)>(
&BlobCallback::Call),
nullptr,
nullptr));
return;
}
+ // Check site-specific permission and display prompt if appropriate.
+ // If no permission, return all-white, opaque image data.
+ bool usePlaceholder = !CanvasUtils::IsImageExtractionAllowed(OwnerDoc(), aCx);
CanvasRenderingContextHelper::ToBlob(aCx, global, aCallback, aType,
- aParams, aRv);
+ aParams, usePlaceholder, aRv);
}
OffscreenCanvas*
HTMLCanvasElement::TransferControlToOffscreen(ErrorResult& aRv)
{
if (mCurrentContext) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
@@ -920,17 +951,18 @@ HTMLCanvasElement::MozGetAsFile(const ns
nsresult
HTMLCanvasElement::MozGetAsFileImpl(const nsAString& aName,
const nsAString& aType,
File** aResult)
{
nsCOMPtr<nsIInputStream> stream;
nsAutoString type(aType);
- nsresult rv = ExtractData(type, EmptyString(), getter_AddRefs(stream));
+ nsresult rv = ExtractData(nsContentUtils::GetCurrentJSContext(),
+ type, EmptyString(), getter_AddRefs(stream));
NS_ENSURE_SUCCESS(rv, rv);
uint64_t imgSize;
rv = stream->Available(&imgSize);
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_TRUE(imgSize <= UINT32_MAX, NS_ERROR_FILE_TOO_BIG);
void* imgData = nullptr;
@@ -1241,17 +1273,18 @@ HTMLCanvasElement::MarkContextCleanForFr
bool
HTMLCanvasElement::IsContextCleanForFrameCapture()
{
return mCurrentContext && mCurrentContext->IsContextCleanForFrameCapture();
}
nsresult
-HTMLCanvasElement::RegisterFrameCaptureListener(FrameCaptureListener* aListener)
+HTMLCanvasElement::RegisterFrameCaptureListener(FrameCaptureListener* aListener,
+ bool aReturnPlaceholderData)
{
WeakPtr<FrameCaptureListener> listener = aListener;
if (mRequestedFrameListeners.Contains(listener)) {
return NS_OK;
}
if (!mRequestedFrameRefreshObserver) {
@@ -1280,17 +1313,19 @@ HTMLCanvasElement::RegisterFrameCaptureL
}
nsRefreshDriver* driver = context->RefreshDriver();
if (!driver) {
return NS_ERROR_FAILURE;
}
mRequestedFrameRefreshObserver =
- new RequestedFrameRefreshObserver(this, driver);
+ new RequestedFrameRefreshObserver(this, driver, aReturnPlaceholderData);
+ } else {
+ mRequestedFrameRefreshObserver->SetReturnPlaceholderData(aReturnPlaceholderData);
}
mRequestedFrameListeners.AppendElement(listener);
mRequestedFrameRefreshObserver->Register();
return NS_OK;
}
bool
--- a/dom/html/HTMLCanvasElement.h
+++ b/dom/html/HTMLCanvasElement.h
@@ -257,17 +257,18 @@ public:
/*
* Register a FrameCaptureListener with this canvas.
* The canvas hooks into the RefreshDriver while there are
* FrameCaptureListeners registered.
* The registered FrameCaptureListeners are stored as WeakPtrs, thus it's the
* caller's responsibility to keep them alive. Once a registered
* FrameCaptureListener is destroyed it will be automatically deregistered.
*/
- nsresult RegisterFrameCaptureListener(FrameCaptureListener* aListener);
+ nsresult RegisterFrameCaptureListener(FrameCaptureListener* aListener,
+ bool aReturnPlaceholderData);
/*
* Returns true when there is at least one registered FrameCaptureListener
* that has requested a frame capture.
*/
bool IsFrameCaptureRequested() const;
/*
@@ -343,17 +344,18 @@ protected:
virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
virtual nsIntSize GetWidthHeight() override;
virtual already_AddRefed<nsICanvasRenderingContextInternal>
CreateContext(CanvasContextType aContextType) override;
- nsresult ExtractData(nsAString& aType,
+ nsresult ExtractData(JSContext* aCx,
+ nsAString& aType,
const nsAString& aOptions,
nsIInputStream** aStream);
nsresult ToDataURLImpl(JSContext* aCx,
const nsAString& aMimeType,
const JS::Value& aEncoderOptions,
nsAString& aDataURL);
nsresult MozGetAsFileImpl(const nsAString& aName,
const nsAString& aType,
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -589,16 +589,24 @@ parent:
* When the session history is across multiple root docshells, this function
* is used to notify parent that it needs to navigate to an entry out of
* local index of the child.
*
* @param aGlobalIndex The global index of history entry to navigate to.
*/
async RequestCrossBrowserNavigation(uint32_t aGlobalIndex);
+ /**
+ * This function is used to notify the parent that it should display a
+ * canvas permission prompt.
+ *
+ * @param aFirstPartyURI first party of the tab that is requesting access.
+ */
+ async ShowCanvasPermissionPrompt(nsCString aFirstPartyURI);
+
child:
/**
* Notify the remote browser that it has been Show()n on this
* side, with the given |visibleRect|. This message is expected
* to trigger creation of the remote browser's "widget".
*
* |Show()| and |Move()| take IntSizes rather than Rects because
* content processes always render to a virtual <0, 0> top-left
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -3599,16 +3599,37 @@ TabParent::RecvRequestCrossBrowserNaviga
nsCOMPtr<nsISupports> promise;
if (NS_FAILED(frameLoader->RequestGroupedHistoryNavigation(aGlobalIndex,
getter_AddRefs(promise)))) {
return IPC_FAIL_NO_REASON(this);
}
return IPC_OK();
}
+mozilla::ipc::IPCResult
+TabParent::RecvShowCanvasPermissionPrompt(const nsCString& aFirstPartyURI)
+{
+ nsCOMPtr<nsIBrowser> browser = do_QueryInterface(mFrameElement);
+ if (!browser) {
+ // If the tab is being closed, the browser may not be available.
+ // In this case we can ignore the request.
+ return IPC_OK();
+ }
+ nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+ if (!os) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ nsresult rv = os->NotifyObservers(browser, "canvas-permissions-prompt",
+ NS_ConvertUTF8toUTF16(aFirstPartyURI).get());
+ if (NS_FAILED(rv)) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ return IPC_OK();
+}
+
void
TabParent::LiveResizeStarted()
{
SuppressDisplayport(true);
}
void
TabParent::LiveResizeStopped()
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -643,16 +643,17 @@ protected:
virtual mozilla::ipc::IPCResult RecvGetTabCount(uint32_t* aValue) override;
virtual mozilla::ipc::IPCResult RecvSHistoryUpdate(const uint32_t& aCount,
const uint32_t& aLocalIndex,
const bool& aTruncate) override;
virtual mozilla::ipc::IPCResult RecvRequestCrossBrowserNavigation(const uint32_t& aGlobalIndex) override;
+ virtual mozilla::ipc::IPCResult RecvShowCanvasPermissionPrompt(const nsCString& aFirstPartyURI) override;
ContentCacheInParent mContentCache;
nsIntRect mRect;
ScreenIntSize mDimensions;
ScreenOrientationInternal mOrientation;
float mDPI;
int32_t mRounding;
--- a/dom/media/imagecapture/CaptureTask.cpp
+++ b/dom/media/imagecapture/CaptureTask.cpp
@@ -152,16 +152,17 @@ CaptureTask::SetCurrentFrames(const Vide
nsresult rv;
nsAutoString type(NS_LITERAL_STRING("image/jpeg"));
nsAutoString options;
rv = dom::ImageEncoder::ExtractDataFromLayersImageAsync(
type,
options,
false,
image,
+ false,
new EncodeComplete(this));
if (NS_FAILED(rv)) {
PostTrackEndEvent();
}
return;
}
}
}