fix(js): do not redefine img src when a page made it non-configurable (#418)

Booking.com's anti-bot script pre-defines a non-configurable own `src` on
<img> elements. The Image shim's load emulation then threw "TypeError:
Cannot redefine property: src" out of the constructor, crashing any page
that calls new Image() after that instrumentation runs.

Check the own descriptor first and skip the load emulation when `src` is
already non-configurable: a page that owns `src` is instrumenting loads
itself. The normal path (configurable src, emulated load event) is
unchanged and now covered by a test.

Fixes the crash half of #394.
This commit is contained in:
Jim Zhang @ wonderhoi authored and GitHub committed 2026-07-16 12:41:28 +02:00
1 parent 113b492ed5
commit 891d850d19
2 files changed
+124 -15

No files matched your search

+22 -15
View File
@@ -7398,21 +7398,28 @@ if (typeof Image === 'undefined') {
// `.src` flips `complete` and fires `load` on a microtask-later tick. Lazy
// loaders and preloaders that create `new Image()`, set `.src`, and wait for
// `onload` (or addEventListener('load')) would hang forever otherwise.
Object.defineProperty(img, 'src', {
configurable: true, enumerable: true,
get() { return _imgSrcDesc.get.call(img); },
set(v) {
_imgSrcDesc.set.call(img, v);
if (!img.getAttribute('src')) return;
img.complete = false;
setTimeout(function () {
img.complete = true;
img.naturalWidth = img.naturalWidth || img.width || 0;
img.naturalHeight = img.naturalHeight || img.height || 0;
try { img.dispatchEvent(new Event('load')); } catch (e) {}
}, 0);
},
});
// Anti-bot scripts (Booking.com, issue #394) pre-define a non-configurable
// own `src` on <img> elements; redefining it throws "Cannot redefine
// property: src" and kills the constructor. Skip the load emulation then:
// a page that owns `src` is instrumenting loads itself.
const ownSrc = Object.getOwnPropertyDescriptor(img, 'src');
if (!ownSrc || ownSrc.configurable) {
Object.defineProperty(img, 'src', {
configurable: true, enumerable: true,
get() { return _imgSrcDesc.get.call(img); },
set(v) {
_imgSrcDesc.set.call(img, v);
if (!img.getAttribute('src')) return;
img.complete = false;
setTimeout(function () {
img.complete = true;
img.naturalWidth = img.naturalWidth || img.width || 0;
img.naturalHeight = img.naturalHeight || img.height || 0;
try { img.dispatchEvent(new Event('load')); } catch (e) {}
}, 0);
},
});
}
return img;
};
globalThis.Image.prototype = globalThis.HTMLImageElement.prototype;