1
0
mirror of https://github.com/kkapsner/CanvasBlocker synced 2024-11-11 15:48:51 +01:00
CanvasBlocker/lib/modifiedAPI.js

398 lines
12 KiB
JavaScript
Raw Permalink Normal View History

/* 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/. */
(function(){
"use strict";
2017-06-25 22:33:12 +02:00
var scope;
if ((typeof exports) !== "undefined"){
scope = exports;
}
else {
window.scope.modifiedAPI = {};
scope = window.scope.modifiedAPI;
}
2017-08-07 21:03:34 +02:00
const colorStatistics = require("./colorStatistics");
const logging = require("./logging");
const {copyCanvasToWebgl} = require("./webgl");
const getWrapped = require("sdk/getWrapped");
2017-08-07 21:03:34 +02:00
2016-08-06 19:17:36 +02:00
var randomSupply = null;
function getContext(window, canvas){
return window.HTMLCanvasElement.prototype.getContext.call(canvas, "2d") ||
window.HTMLCanvasElement.prototype.getContext.call(canvas, "webgl") ||
window.HTMLCanvasElement.prototype.getContext.call(canvas, "experimental-webgl") ||
window.HTMLCanvasElement.prototype.getContext.call(canvas, "webgl2") ||
window.HTMLCanvasElement.prototype.getContext.call(canvas, "experimental-webgl2");
}
function getImageData(window, context){
var imageData;
var source;
if ((context.canvas.width || 0) * (context.canvas.height || 0) === 0){
2017-10-10 21:09:26 +02:00
imageData = new (getWrapped(window).ImageData)(0, 0);
source = new (getWrapped(window).ImageData)(0, 0);
}
else if (context instanceof window.CanvasRenderingContext2D){
imageData = window.CanvasRenderingContext2D.prototype.getImageData.call(
context,
0, 0,
context.canvas.width, context.canvas.height
);
source = imageData.data;
2015-09-10 01:35:49 +02:00
}
else {
2017-10-10 21:09:26 +02:00
imageData = new (getWrapped(window).ImageData)(context.canvas.width, context.canvas.height);
source = new Uint8Array(imageData.data.length);
2017-10-10 21:09:26 +02:00
(
context instanceof window.WebGLRenderingContext?
window.WebGLRenderingContext:
window.WebGL2RenderingContext
).prototype.readPixels.call(
2015-09-10 01:35:49 +02:00
context,
0, 0, context.canvas.width, context.canvas.height,
2015-09-10 01:35:49 +02:00
context.RGBA, context.UNSIGNED_BYTE,
source
2015-09-10 01:35:49 +02:00
);
}
return {
imageData,
source
};
}
2017-08-08 18:08:18 +02:00
var canvasCache = Object.create(null);
2017-08-07 21:03:34 +02:00
function getFakeCanvas(window, original, prefs){
try {
2017-08-08 18:08:18 +02:00
if (prefs("useCanvasCache")){
var originalDataURL = original.toDataURL();
var cached = canvasCache[originalDataURL];
if (cached){
return cached;
}
}
// original may not be a canvas -> we must not leak an error
var context = getContext(window, original);
var {imageData, source} = getImageData(window, context);
var desc = imageData.data;
var l = desc.length;
var ignoredColors = {};
var statistic;
2017-08-07 21:03:34 +02:00
if (prefs("ignoreFrequentColors")){
statistic = colorStatistics.compute(source);
2017-08-07 21:03:34 +02:00
ignoredColors = statistic.getMaxColors(prefs("ignoreFrequentColors"));
}
if (prefs("minColors")){
if (!colorStatistics.hasMoreColors(source, prefs("minColors"), statistic)){
return original;
}
}
2017-08-07 21:03:34 +02:00
var rng = randomSupply.getPixelRng(l, window, ignoredColors);
var fakeAlphaChannel = prefs("fakeAlphaChannel");
for (var i = 0; i < l; i += 4){
var [r, g, b, a] = rng(
2017-10-03 15:35:31 +02:00
source[i + 0],
source[i + 1],
source[i + 2],
source[i + 3],
i / 4
);
desc[i + 0] = r;
desc[i + 1] = g;
desc[i + 2] = b;
desc[i + 3] = fakeAlphaChannel? a: source[i + 3];
}
var canvas = original.cloneNode(true);
context = window.HTMLCanvasElement.prototype.getContext.call(canvas, "2d");
context.putImageData(imageData, 0, 0);
2017-08-08 18:08:18 +02:00
if (prefs("useCanvasCache")){
canvasCache[originalDataURL] = canvas;
canvasCache[canvas.toDataURL()] = canvas;
2017-08-08 18:08:18 +02:00
}
return canvas;
}
catch (e){
logging.warning("Error while faking:", e);
return original;
}
}
function randomMixImageData(window, imageData1, imageData2){
var data1 = imageData1.data;
var data2 = imageData2.data;
var l = data1.length;
if (l === data2.length){
var rng = randomSupply.getRng(l, window);
for (var i = 0; i < l; i += 1){
if (data1[i] > data2[i]){
data2[i] = data1[i] - rng(data1[i] - data2[i], i);
}
else if (data1[i] < data2[i]){
data2[i] = data1[i] + rng(data2[i] - data1[i], i);
}
}
}
return imageData2;
}
function canvasSizeShouldBeFaked(canvas, prefs){
if (canvas){
var size = canvas.height * canvas.width;
var maxSize = prefs("maxFakeSize") || Number.POSITIVE_INFINITY;
var minSize = prefs("minFakeSize") || 0;
return size > minSize & size <= maxSize;
}
else {
2017-10-03 15:35:31 +02:00
return true;
}
}
function hasType(status, type){
return status.type.indexOf(type) !== -1;
}
2016-08-06 19:17:36 +02:00
2017-06-25 22:33:12 +02:00
scope.setRandomSupply = function(supply){
2016-08-06 19:17:36 +02:00
randomSupply = supply;
};
var canvasContextType = new WeakMap();
// changed functions and their fakes
2017-06-25 22:33:12 +02:00
scope.changedFunctions = {
getContext: {
type: "context",
getStatus: function(obj, status){
if (hasType(status, "internal")){
return {
mode: "allow",
type: status.type,
active: false
2017-10-03 15:35:31 +02:00
};
}
else if (hasType(status, "context") || hasType(status, "input")){
return {
mode: status.mode,
type: status.type,
active: true
};
}
else {
2017-10-03 15:35:31 +02:00
status = Object.create(status);
status.active = false;
return status;
}
},
object: "HTMLCanvasElement",
fakeGenerator: function(prefs, notify, window, original){
return function(context, contextAttributes){
canvasContextType.set(this, context);
return original.apply(this, window.Array.from(arguments));
};
}
},
toDataURL: {
type: "readout",
getStatus: function(obj, status){
2017-10-03 15:35:31 +02:00
status = Object.create(status);
status.active = hasType(status, "readout");
if (!status.active && hasType(status, "input")){
var contextType = canvasContextType.get(obj);
status.active = contextType !== "2d";
}
return status;
},
object: "HTMLCanvasElement",
fakeGenerator: function(prefs, notify, window, original){
return function toDataURL(){
if (canvasSizeShouldBeFaked(this, prefs)){
var fakeCanvas = getFakeCanvas(window, this, prefs);
if (fakeCanvas !== this){
notify.call(this, "fakedReadout");
}
return original.apply(fakeCanvas, window.Array.from(arguments));
}
else {
return original.apply(this, window.Array.from(arguments));
}
};
}
},
toBlob: {
type: "readout",
getStatus: function(obj, status){
2017-10-03 15:35:31 +02:00
status = Object.create(status);
status.active = hasType(status, "readout");
if (!status.active && hasType(status, "input")){
var contextType = canvasContextType.get(obj);
status.active = contextType !== "2d";
}
return status;
},
object: "HTMLCanvasElement",
fakeGenerator: function(prefs, notify, window, original){
return function toBlob(callback){
if (canvasSizeShouldBeFaked(this, prefs)){
var fakeCanvas = getFakeCanvas(window, this, prefs);
if (fakeCanvas !== this){
notify.call(this, "fakedReadout");
}
return original.apply(fakeCanvas, window.Array.from(arguments));
}
else {
return original.apply(this, window.Array.from(arguments));
}
};
},
exportOptions: {allowCallbacks: true}
},
mozGetAsFile: {
type: "readout",
getStatus: function(obj, status){
2017-10-03 15:35:31 +02:00
status = Object.create(status);
status.active = hasType(status, "readout");
if (!status.active && hasType(status, "input")){
var contextType = canvasContextType.get(obj);
status.active = contextType !== "2d";
}
return status;
},
object: "HTMLCanvasElement",
fakeGenerator: function(prefs, notify, window, original){
return function mozGetAsFile(callback){
if (canvasSizeShouldBeFaked(this, prefs)){
var fakeCanvas = getFakeCanvas(window, this, prefs);
if (fakeCanvas !== this){
notify.call(this, "fakedReadout");
}
return original.apply(fakeCanvas, window.Array.from(arguments));
}
else {
return original.apply(this, window.Array.from(arguments));
}
};
}
},
getImageData: {
type: "readout",
getStatus: function(obj, status){
2017-10-03 15:35:31 +02:00
status = Object.create(status);
status.active = hasType(status, "readout");
return status;
},
object: "CanvasRenderingContext2D",
fakeGenerator: function(prefs, notify, window, original){
2016-05-10 08:01:21 +02:00
return function getImageData(sx, sy, sw, sh){
if (!this || canvasSizeShouldBeFaked(this.canvas, prefs)){
var fakeCanvas;
var context = this;
if (this && this.canvas) {
2017-08-07 21:03:34 +02:00
fakeCanvas = getFakeCanvas(window, this.canvas, prefs);
}
if (fakeCanvas && fakeCanvas !== this.canvas){
notify.call(this, "fakedReadout");
context = window.HTMLCanvasElement.prototype.getContext.call(
2017-08-07 21:03:34 +02:00
fakeCanvas,
"2d"
);
}
2016-12-03 17:29:30 +01:00
return original.apply(context, window.Array.from(arguments));
}
else {
return original.apply(this, window.Array.from(arguments));
}
2016-08-06 19:17:36 +02:00
};
}
},
fillText: {
type: "input",
getStatus: function(obj, status){
2017-10-03 15:35:31 +02:00
status = Object.create(status);
status.active = hasType(status, "input");
return status;
},
object: "CanvasRenderingContext2D",
fakeGenerator: function(prefs, notify, window, original){
return function fillText(str, x, y){
if (!this || canvasSizeShouldBeFaked(this.canvas, prefs)){
notify.call(this, "fakedInput");
var oldImageData;
try {
// "this" is not trustable - it may be not a context
oldImageData = getImageData(window, this).imageData;
}
2017-10-03 15:35:31 +02:00
catch (e){
// nothing to do here
}
// if "this" is not a correct context the next line will throw an error
var ret = original.apply(this, window.Array.from(arguments));
var newImageData = getImageData(window, this).imageData;
this.putImageData(randomMixImageData(window, oldImageData, newImageData), 0, 0);
return ret;
}
else {
return original.apply(this, window.Array.from(arguments));
}
};
}
},
strokeText: {
type: "input",
getStatus: function(obj, status){
2017-10-03 15:35:31 +02:00
status = Object.create(status);
status.active = hasType(status, "input");
return status;
},
object: "CanvasRenderingContext2D",
fakeGenerator: function(prefs, notify, window, original){
return function strokeText(str, x, y){
if (!this || canvasSizeShouldBeFaked(this.canvas, prefs)){
notify.call(this, "fakedInput");
var oldImageData;
try {
// "this" is not trustable - it may be not a context
oldImageData = getImageData(window, this).imageData;
}
2017-10-03 15:35:31 +02:00
catch (e){
// nothing to do here
}
// if "this" is not a correct context the next line will throw an error
var ret = original.apply(this, window.Array.from(arguments));
var newImageData = getImageData(window, this).imageData;
this.putImageData(randomMixImageData(window, oldImageData, newImageData), 0, 0);
return ret;
}
else {
return original.apply(this, window.Array.from(arguments));
}
};
}
},
readPixels: {
type: "readout",
getStatus: function(obj, status){
2017-10-03 15:35:31 +02:00
status = Object.create(status);
status.active = hasType(status, "readout") || hasType(status, "input");
return status;
},
object: ["WebGLRenderingContext", "WebGL2RenderingContext"],
fakeGenerator: function(prefs, notify, window, original){
return function readPixels(x, y, width, height, format, type, pixels){ // eslint-disable-line max-params
if (!this || canvasSizeShouldBeFaked(this.canvas, prefs)){
notify.call(this, "fakedReadout");
var fakeCanvas = getFakeCanvas(window, this.canvas, prefs);
var {context} = copyCanvasToWebgl(
window,
fakeCanvas,
this instanceof window.WebGLRenderingContext? "webgl": "webgl2"
);
return original.apply(context, window.Array.from(arguments));
}
else {
return original.apply(this, window.Array.from(arguments));
}
};
}
}
};
}());