最近在研究爬蟲中驗證碼及 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 相關的方法進行重定義和代理處理。