Suppose I am writing a bookmarklet script to modify a webpage. Most websites (using webpack, etc) follow a structure something like this:
<html>
<script type="text/javascript">
const buttonClicked = (() => {
let internal_variable = 0;
const _internal_buttonClicked = () => {
internal_variable++;
document.getElementById("myButton").innerText = `Clicked ${internal_variable} times!`;
};
return _internal_buttonClicked.bind(this);
})();
</script>
<body>
<button id="myButton" onclick="buttonClicked()">Clicked 0 times!</button>
</body>
</html>
I would like to access internal_variable
and set it to -100. With a debugger, this is simple; I breakpoint inside _internal_buttonClicked()
, and then change internal_variable
directly.
How can I do this without
I'm aware that the simple answer is "you can't, intentionally", but I'm willing to accept any roundabout solutions, solutions that involve browser extensions/privileged execution, solutions specific to require
, etc.
Related:
It is not possible to assign a new value to internal_variable
as it is not in scope for your code. You can also not assign a new function to buttonClicked
, because it is a constant.
But depending on the actual situation you can go for some other approaches. Here are a few that would work for your example case:
innerText
Have it change the number that is displayed on the button by always subtracting 100 from the value that is about to be displayed.
Your code would have this:
Object.defineProperty(document.getElementById("myButton"), "innerText", {
set(text) {
// Subtract 100 from the number in the text "Clicked <number> times!"
text = text.replace(/(Clicked )(\d+)( times!)/, (_, a, b, c) => a + (b - 100) + c);
// Call the prototype setter for innerText
Object.getOwnPropertyDescriptor(HTMLElement.prototype, "innerText").set.call(this, text);
}
});
Flattened to a bookmarklet:
javascript: {Object.defineProperty(document.getElementById("myButton"),"innerText",{set(text) {text=text.replace(/(Clicked )(\d+)( times!)/,(_,a,b,c)=>a+(b-100)+c);Object.getOwnPropertyDescriptor(HTMLElement.prototype,"innerText").set.call(this, text);}});}
This is similar to the previous solution, but the change to the text is made after it has already been written to the button's innerText
. Add a click handler (that will execute after the one that is already attached) that performs this fix:
document.getElementById("myButton").addEventListener("click", function () {
this.innerText = this.innerText.replace(/(Clicked )(\d+)( times!)/, (_, a, b, c) => a + (b - 100) + c);
});
Flattened to a bookmarklet:
javascript: document.getElementById("myButton").addEventListener("click",function(){this.innerText=this.innerText.replace(/(Clicked )(\d+)( times!)/,(_,a,b,c)=>a+(b-100)+c);})
buttonClicked2
It should have the code with the modification to the counter. Then change every call to buttonClicked
to buttenClicked2
. Here I assume it is like in the example, where such reference is only present in the onclick
attribute of the button. The more places this call is made from, the more functions you may need to copy as new functions, rewiring also the refences to that function, ...etc. But here it is only the onclick
attribute that is affected:
var buttonClicked2 = (() => {
let internal_variable = -100; // Our update
const _internal_buttonClicked = () => {
internal_variable++;
document.getElementById("myButton").innerText = `Clicked ${internal_variable} times!`;
};
return _internal_buttonClicked.bind(this);
})();
// Redirect the click
document.getElementById("myButton").setAttribute("onclick", "buttonClicked2()");
Flattened to a bookmarklet:
javascript: {var buttonClicked2=(()=>{let internal_variable=-100;const _internal_buttonClicked=()=>{internal_variable++;document.getElementById("myButton").innerText=`Clicked ${internal_variable} times!`;};return _internal_buttonClicked.bind(this);})();document.getElementById("myButton").setAttribute("onclick", "buttonClicked2()");}
<iframe>
In that <iframe>
put the current HTML, but with the modification to the code you want to have. So the page will reload, but with your modification and in an <iframe>
.
if (window === top) {
// Patch the HTML
let html = document.documentElement.innerHTML
.replace("internal_variable = 0;", "internal_variable = -100;");
let iframe = `<body style="margin:0"><iframe style="display:block;border:0;height:100%;width:100%"></iframe></body>`;
// Replace the current document with just an <iframe>
document.write(iframe);
// Write the patched HTML to the frame document
document.querySelector("iframe").srcdoc = html;
}
Flattened to a bookmarklet:
javascript: if(window===top){let html = document.documentElement.innerHTML.replace("internal_variable = 0;", "internal_variable = -100;");let iframe = `<body style="margin:0"><iframe style="display:block;border:0;height:100%;width:100%"></iframe></body>`;document.write(iframe);document.querySelector("iframe").srcdoc=html;}
Each of the above solutions has its limitations. Whether one of them is suitable in real-life cases will depend much on the page's functionalities.