最近在研究爬蟲中驗證碼及 Canvas 相關的東西,簡單記錄一下 code snippets
{
const getImageData = CanvasRenderingContext2D.prototype.getImageData;
//
const noisify = function (canvas, context) {
if (context) {
const shift = {
'r': Math.floor(Math.random() * 10) - 5,
'g': Math.floor(Math.random() * 10) - 5,
'b': Math.floor(Math.random() * 10) - 5,
'a': Math.floor(Math.random() * 10) - 5
};
//
const width = canvas.width;
const height = canvas.height;
//
if (width && height) {
const imageData = getImageData.apply(context, [0, 0, width, height]);
//
for (let i = 0; i < height; i++) {
for (let j = 0; j < width; j++) {
const n = ((i * (width * 4)) + (j * 4));
imageData.data[n + 0] = imageData.data[n + 0] + shift.r;
imageData.data[n + 1] = imageData.data[n + 1] + shift.g;
imageData.data[n + 2] = imageData.data[n + 2] + shift.b;
imageData.data[n + 3] = imageData.data[n + 3] + shift.a;
}
}
//
window.top.postMessage("canvas-defender-alert", '*');
context.putImageData(imageData, 0, 0);
}
}
};
//
HTMLCanvasElement.prototype.toBlob = new Proxy(HTMLCanvasElement.prototype.toBlob, {
apply(target, self, args) {
noisify(self, self.getContext("2d"));
//
return Reflect.apply(target, self, args);
}
});
//
HTMLCanvasElement.prototype.toDataURL = new Proxy(HTMLCanvasElement.prototype.toDataURL, {
apply(target, self, args) {
noisify(self, self.getContext("2d"));
//
return Reflect.apply(target, self, args);
}
});
//
CanvasRenderingContext2D.prototype.getImageData = new Proxy(CanvasRenderingContext2D.prototype.getImageData, {
apply(target, self, args) {
noisify(self.canvas, self);
//
return Reflect.apply(target, self, args);
}
});
}
{
const mkey = "canvas-defender-sandboxed-frame";
document.documentElement.setAttribute(mkey, '');
//
window.addEventListener("message", function (e) {
if (e.data && e.data === mkey) {
e.preventDefault();
e.stopPropagation();
//
if (e.source) {
if (e.source.CanvasRenderingContext2D) {
e.source.CanvasRenderingContext2D.prototype.getImageData = CanvasRenderingContext2D.prototype.getImageData;
}
//
if (e.source.HTMLCanvasElement) {
e.source.HTMLCanvasElement.prototype.toBlob = HTMLCanvasElement.prototype.toBlob;
e.source.HTMLCanvasElement.prototype.toDataURL = HTMLCanvasElement.prototype.toDataURL;
}
}
}
}, false);
}
這段 JavaScript 代碼是用於對 Canvas 元素進行處理的。下面是對代碼的解釋:
第一部分:
const getImageData = CanvasRenderingContext2D.prototype.getImageData;
這行代碼創建了一個名為 getImageData
的變量,並將 CanvasRenderingContext2D.prototype.getImageData
的引用賦值給它。這意味著在後續的代碼中,可以通過 getImageData
變量來調用 CanvasRenderingContext2D
原型上的 getImageData
方法。
然後是一個名為 noisify
的函數,該函數用於給 Canvas 添加噪點效果。接下來的代碼是對 noisify
函數的定義和實現。
在 noisify
函數內部,存在以下邏輯:
- 首先,通過傳入的參數
canvas
和context
進行判斷。 - 然後,生成一個包含隨機數的
shift
對象,用於控制噪點的顏色偏移量。 - 接著,獲取傳入的 Canvas 的寬度和高度。
- 如果寬度和高度都存在,則使用
getImageData
方法獲取 Canvas 上的像素數據,並存儲在imageData
變量中。 - 然後,使用嵌套的循環遍歷每個像素,並根據
shift
對象中的隨機數對像素的顏色值進行偏移。 - 在處理完所有像素後,發送一條消息給頂層窗口以觸發 "canvas-defender-alert" 事件。
- 最後,使用
context.putImageData(imageData, 0, 0)
將經過處理的像素數據重新繪製到 Canvas 上。
第二部分:
HTMLCanvasElement.prototype.toBlob = new Proxy(HTMLCanvasElement.prototype.toBlob, {
apply(target, self, args) {
noisify(self, self.getContext("2d"));
return Reflect.apply(target, self, args);
}
});
HTMLCanvasElement.prototype.toDataURL = new Proxy(HTMLCanvasElement.prototype.toDataURL, {
apply(target, self, args) {
noisify(self, self.getContext("2d"));
return Reflect.apply(target, self, args);
}
});
CanvasRenderingContext2D.prototype.getImageData = new Proxy(CanvasRenderingContext2D.prototype.getImageData, {
apply(target, self, args) {
noisify(self.canvas, self);
return Reflect.apply(target, self, args);
}
});
這部分代碼重新定義了三個方法:toBlob
、toDataURL
和 getImageData
,並使用 Proxy
進行了代理處理。通過對這些方法進行代理,可以在調用它們時自動調用 noisify
函數給 Canvas 添加噪點效果。
第三部分:
const mkey = "canvas-defender-sandboxed-frame";
document.documentElement.setAttribute(mkey, '');
window.addEventListener("message", function (e) {
if (e.data && e.data === mkey) {
e.preventDefault();
e.stopPropagation();
if (e.source) {
if (e.source.CanvasRenderingContext2D) {
e.source.CanvasRenderingContext2D.prototype.getImageData = CanvasRenderingContext2D.prototype.getImageData;
}
if (e.source.HTMLCanvasElement) {
e.source.HTMLCanvasElement.prototype.toBlob = HTMLCanvasElement.prototype.toBlob;
e.source.HTMLCanvasElement.prototype.toDataURL = HTMLCanvasElement.prototype.toDataURL;
}
}
}
}, false);
這部分代碼定義了一個用於監聽消息的事件監聽器。當收到特定消息 mkey
時,會執行一系列操作。具體而言:
- 首先,將自定義屬性
mkey
添加給頁面的根元素(document.documentElement
)。 - 然後,註冊一個
"message"
事件的監聽器,並通過條件判斷來處理接收到的消息。 - 如果消息是等於
mkey
的,則阻止默認行為和事件傳播。 - 接著,通過
e.source
來訪問消息來源的窗口對象,並檢查其是否具有CanvasRenderingContext2D
和HTMLCanvasElement
這兩個原型上的方法。 - 如果是,將相應的方法重新定義為之前代理過的方法,從而保證對這些方法的調用會經過
noisify
處理。
總而言之,該 JavaScript 代碼實現了給 Canvas 元素添加噪點效果的功能,並在特定條件下對 Canvas 相關的方法進行重定義和代理處理。