Extension issue when elements are accessed directly by their id, not getElementById - javascript

Even though it is not recommended, JavaScript allows you to access elements by their id without the use of getElementById.
<iframe id="myframe" />
<script>
var i = window.myframe;
</script>
This presents a problem for extensions that need to access said elements when they themselves are being accessed. The way to do this is to call Object.defineProperty() on the prototype for getElementById so that when it is called, you can then access the element. But when referencing an element directly getElementById is not called so consequently, they are a blind spot for the extension. I've tried the following but without any luck.
Iterating over the DOM in the content script doesn't seem to work because the full DOM isn't loaded yet.
Setting a listener for 'DOMContentLoaded' isn't an option because by that time the page's scripts would have already run and potentially accessed the elements.
Attempting to insert the script before the first script on the page but again, at that point nothing really exists yet.
Does anyone know the internal mechanism that JavaScript uses when an element is referenced like this? I'm curious if there is another function that it calls internally which could possibly be hooked like getElementById. Or is there a way to ensure the content script code is run after the DOM loads but before the page scripts runs?

Or is there a way to ensure the content script code is run after the DOM loads but before the page scripts runs?
A problem is that page scripts can run before the DOM is fully loaded. For example, below:
<div id="foo"></div>
<script>
foo.textContent = 'foo';
</script>
<div id="bar"></div>
the script will run before the bar element is created (before the DOM is loaded).
It's possible to find all elements with IDs currently in the DOM with [id], and it's possible to reassign those properties on the window by simply reassigning the property - or, if you want to run code when the element is retrieved, you can use Object.defineProperty to turn it into a getter. You need to be able to find the IDs and reassign them before scripts load, which can be accomplished with a subtree MutationObserver - right before a <script> is inserted, iterate over the [id]s in the DOM, and reassign them:
console.log('(empty JS block inserted by Stack Snippet editor)');
<script>
new MutationObserver((mutations) => {
// Check to see if a script was inserted
if (mutations.every(
mutation => [...mutation.addedNodes].every(
addedNode => addedNode.tagName !== 'SCRIPT'
)
)) {
// No scripts inserted
return;
}
console.log('id reassignment running');
for (const elm of document.querySelectorAll('[id]')) {
Object.defineProperty(window, elm.id, {
get() {
console.log('Custom code running');
return elm;
},
configurable: true
});
}
})
.observe(document.body, { childList: true, subtree: true });
</script>
<div id="foo"></div>
<script>
console.log('Lower script about to reference foo');
foo.textContent = 'foo';
console.log('Lower script has finished referencing foo');
</script>
<div id="bar"></div>
It can benefit from some polishing, but that's the general idea.

Related

How do I remove script element from the DOM in UI5?

I am trying to remove a script tag from the DOM in UI5. Here is my code and lang is parameter to the function that can change.
var something= document.getElementById("id");
if (lang.toLowerCase().indexOf("en") === 0 && !something) {
var s = document.createElement("script");
s.setAttribute(...);
s.setAttribute(...);
document.body.appendChild(s);
s.setAttribute(...);
s.setAttribute(...);
}else if(lang.toLowerCase().indexOf("en") !== 0 && something){
something.remove();
}else{
document.body.appendChild(something);
}
When ever something.remove() happens the functionality of script tag still displays in the webpage. I also get error when the code does document.body.appendChild(something);
The expected result is the script tag will be removed from the dom when something exists and lang is changed and added back when something is changed back to en.
The expected result is the script tag will be removed from the dom when something exists and lang is changed and added back when something is changed back to en.
Your code may or may not be removing the script element. You wouldn't be able to tell because removing the script element has no effect on the code that the script element loaded into the JavaScript environment. You can't "unload" code that's been loaded.
The script element is just a box the code comes in. Once the box has delivered the code to the JavaScript engine, the engine runs it and keeps anything that it creates around (although objects that nothing references can be garbage collected at some stage). The box is no longer needed, and removing it makes no difference.
Here's a simpler example in the browser:
<input id="btn" type="button" value="Click Me">
<script id="the-script">
function clickHandler() {
console.log("clickhandler called");
const script = document.getElementById("the-script");
if (script) {
script.remove();
console.log("First click, removed `script` element");
}
}
document.getElementById("btn").addEventListener("click", clickHandler);
</script>
You can't remove the code that the script element loaded.
You can write the code so that you can disable whatever code the script element loads and re-enable it later, by using a flag or unregistering/re-registering event handlers, etc.

Usage of MutationObserver in a page that is not dynamically modified

This seems like an obvious question but I can't find any definitive answer in any documentation.
I want to make sure that a function is ran when the page is loaded at first and when the page is modified by adding or removing a node.
Is a MutationObserver supposed to trigger on page load, at least once, when the DOM is parsed, even if there is no JS that adds or removes nodes?
My testing seems to indicate that it is. The Observer always gets at least one record, that includes the whole body of the document, when I get it to observe document.body with childList on true. However, I'd like to have a definitive answer, ideally from documentation (which I can't find), since perhaps this is browser dependant in some way.
This is the simple code I'm using to test:
<!DOCTYPE html>
<head>
<title>Observer test</title>
</head>
<body>
<script type='text/javascript'>
var config = { childList: true, subtree: true };
function mutationCallback (mutationsList, observer) {
console.log('Triggered');
for(let mutation of mutationsList) {
console.log(mutation.type);
console.log(mutation.target);
for (let node of mutation.addedNodes) {
console.log(node)
}
}
}
observer = new MutationObserver(mutationCallback);
observer.observe(document.body, config);
</script>
</body>
</html>
Is a MutationObserver supposed to trigger on page load, at least once, when the DOM is parsed, even if there is no JS that adds or removes nodes?
No, page load event is not related.
The observer is triggered only by DOM mutations (either in JS or in HTML parser) and you happen to have one here as well. To see it easily add a console.log for the entire list: console.log(mutationsList)
You will see that the only mutation is the addition of a text node containing spaces/newlines between the tags </script> and </body>.
It depends on where you invoke the observe() function when you observe document.body.
If you invoke it in the head, it will observe the whole loading process of the body.
If you invoke it at the end of the body, it will not observe the part of the body already loaded.

Div is not created before javascript run

I have a question about javascript/html.
First, I have this:
var post = document.body.getElementsByClassName("post");
var x=post[i].getElementsByClassName("MyDiv")[0].innerHTML;
I get from the debugger that x is not defined, it doesn't exists.
This javascript function runs onload of the body. I am sure that I gave the right classnames in my javascript, so it should find my div.
So, I read somewhere that sometimes javascript does not find an element because it is not yet there, it is not yet created in the browser ( whatever that means).
Is it possible that my function can't find the div with that classname because of this reason?
Is there a solution?
So, I read somewhere that sometimes javascript does not find an element because it is not yet there, it is not yet created in the browser ( whatever that means).
Browsers create the DOM progressively as they get the markup. When a script element is encountered, all processing of the markup stops (except where defer and async have an effect) while the script is run. If the script attempts to access an element that hasn't been created yet (probably because its markup hasn't been processed yet) then it won't be found.
This javascript function runs onload of the body.
If that means you are using something like:
<body onload="someFn()"...>
or perhaps
<script>
window.onload = function() {
someFn();
...
}
</script>
then when the function is called, all DOM nodes are available. Some, like images, may not be fully loaded, but their elements have been created.
If it means you have the script in the body and aren't using the load event, you should move the script to the bottom of the page (e.g. just before the closing body tag) and see if that fixes the issue.
Okay, instead of calling functions with
body onload, use jQuery's ready() function, or, if you don't want to use jQuery, you can use pure javascript, but this is up to you:
// jQuery
$(document).ready(function() {
var post = document.getElementsByClassName("post"),
x = post[i].getElementsByClassName("MyDiv")[0].innerHTML;
});
// JavaScript
window.onload = function initialization() {
var post = document.getElementsByClassName("post"),
x = post[i].getElementsByClassName("MyDiv")[0].innerHTML;
}
A few side notes, I don't know what the use of innerHTML
is, and also if you're doing a for loop with i then definitely
post that code, that's kind of important.
After some discussion, my answer seems to have worked for you, but you can also place your script at the end of your body tag as #RobG has suggested.

How do I know when DOM “body” element is available?

As soon as body DOM node is available, I'd like to add a class to it with JavaScript.
I want this to happen as soon as possible, before any of body's children are loaded.
Right now, I'm using an inline script right after opening body tag. Is there a less obtrusive way?
Might be a bit late to the party but...
You can just tap into the browser rendering cycle. Thus you don't have to deal with timeouts which leak memory (if improperly used).
var script = document.createElement('script');
script.src = '//localhost:4000/app.js';
(function appendScript() {
if (document.body) return document.body.appendChild(script);
window.requestAnimationFrame(appendScript);
})();
I would imagine this will differ between browsers.
One solution may be to test for it by placing a script immediately inside the opening <body> tag, then running your code at an interval to add the class.
<body>
<script>
function add_class() {
if(document.body)
document.body.className = 'some_class';
else
setTimeout(add_class, 10); // keep trying until body is available
}
add_class();
</script>
<!-- rest of your elements-->
</body>
jQuery does something similar internally to deal with a particular IE bug.
There isn't a guarantee that the descendant elements won't be loaded though, since again it will depend on when the particular implementation makes the body available.
Here's the source where jQuery takes a similar approach, testing for the existence of the body in its main jQuery.ready handler, and repeatedly invoking jQuery.ready via setTimeout if the body isn't available.
And here's an example to see if your browser can see the <body> element in a script at the top of the element, before the other elements. (Open your console)
Here's the same example without needing the console.

How to access XUL Overlay's DOM

In my Firefox Addon's overlay.xul, can I access it's DOM in javascript? I can't figure out how.
Thanks in advance.
An overlay is merged with the DOM of the document that it applies to, it doesn't have a DOM of its own. So you don't access the DOM of "the overlay", you access the DOM of the document that you overlaid. And that is being done the usual way, e.g. via document.getElementById(). You have to consider one thing however: never access the DOM before the document finished loading, this will cause various issues (like other overlays failing to apply). So if your overlay includes a script you can write:
window.addEventListener("load", function() {
// Window finished loading, now we can do something
var button = document.getElementById("my-extension-button");
button.style.backgroundColor = "green";
}, false)

Categories