I have written a Chrome extension that loads a script with run_at set to document_start.
My script uses Mutation Observer in order to invoke some code upon the addition of an element with id X to a certain webpage.
That element is created when the page first loads, and then recreated when part of the page reloads following some button click.
My mutation observer fails to detect that element in either case.
However, if I duplicate the element (Developer Tools > Elements > element > context menu > Duplicate element), then my mutation observer does detect the duplicate.
I ran a test: I changed the id of some random element to X. It was not detected. That has made me suspect the original element is first created and then has its id assigned or updated. I am not sure how I could prove of refute this theory (the process of generating the webpage code looks very complex, with layers and layers of scripts involved, and I have very little experience).
Does this theory make sense? Can I observe an id change? If the theory does not make sense or if it is impossible to observe an id change, what alternatives do I have?
Thank you!
Edit:
My code looks like this:
const observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
for (let i = 0; i < mutation.addedNodes.length; i++) {
if (mutation.addedNodes[i].id == "X") {
alert("X detected!");
};
};
});
});
observer.observe(document, {
attributes: true,
characterData: true,
childList: true,
subtree: true
});
Related
I am getting a text from elements that pop up on the page on change dome event (mutation). For every element that shows up I am collecting text from it. For the most websites, this approach works well but for some websites .text() returns tags and js scripts (it happens in gmail, outlook...)
var observer = new MutationObserver((mutations) => {
mutations.forEach(async (mutation) => {
console.log($(mutation.target).text())
})
})
observer.observe(document, { childList: true, subtree: true })
It looks like it returns almost everything inside of the element. I have tried to return only mutations from current element that is mutated:
$(mutation.target).clone().children().remove().end().text()
This does not capture every text mutation on the page, unfortunately. Other things that I have tried are regex clean up (unreliable, cuts of some things that I need) And some js only code with no success. I wonder if there is another way to get a clean text from the dome element?
I am working on a browser extension.
It has two parts:
popup - which contains checkboxes
content script - which contains the code to alter the CSS property
I am saving the states of checkboxes so that the next time I open the popup again the same checkboxes are marked as checked.
When I use the checkboxes they change the DOM as intended, however when I try to alter the DOM after the page is loaded, changes are not reflected. This is probably because the element on which I want to perform the operation is loaded slow and thus required operations fail.
I tried to use onload and ready but nothing worked
$('.question-list-table').on('load', function() {
browser.storage.local.get(["options"], modifyThenApplyChanges)
});
I also tried, but nothing changed.
$('body').on('load','.question-list-table', function() {
browser.storage.local.get(["options"], modifyThenApplyChanges)
});
Also, there is no visible error with the popup or content script as I test in both Google Chrome and Mozilla Firefox.
Update:
As suspected earlier, the target element is loaded slowly so I used setTimeout for 5 seconds and the script is working as intended.
Loading time is variable and I want to show my changes as early as possible everything in a consistent manner.
After going through MutationObserver as suggested by #charlietfl in the comment section, this is what I coded and works for me
// Mutation Observer
const observer = new MutationObserver(function (mutations) {
mutations.forEach(function(mutation) {
if(mutation.addedNodes.length) {
//do stuff
}
});
});
el = document.getElementsById('elementId');
if(el) {
observer.observe(el, {
childList: true // specify the kind of change you are looking for
});
}
I am new to web dev, so I'm sorry if this is a silly question. I followed a very simple tutorial and made a chrome extension that replaces images on screen with that of random ones I picked from my computer (mostly crappy meme valentines cards). However I have noticed some peculiar behaviour.
1) it doesn't replace all the images. for example in the google images page, it replaces the first 5 or 6 lines perfectly, and after that replaces none
2) similarly, on netflix it also does 1), but on top of that, if I go left or right on one of the sliders that it worked on, the images go away and it goes back to the default images.
How can I fix this so it 1) replaces all images and 2) keeps those changes.
I have attached a gif demonstrating this issue below
https://i.imgur.com/4hZuzsX.gif
It will happen mainly for two reasons:
Not all the img tags exist when you perform the replacement;
The elements get rerendered when you perform some action on the page (like clicking or scrolling).
I tested in Google Image Search and yep - not all the img tags are there from the start. So you'd have to replace them, too, when they appear.
Go to Google Image Search and test this script once. Then scroll down to the bottom and test it once again to see if it changed:
// this will tell you how many img tags exist in the page
console.log(document.getElementsByTagName('img').length);
It most certainly increased as you were scrolling down. Right?
Possible workaround - observing element mutations
In days of old there were events you could listen to for changes whenever elements were added or removed, but they've been deprecated for quite some time. Let's get this out of the way: don't use mutation events!
Nowadays if you want to monitor changes in the page I suggest you take a look at Mutation Observers.
Mutation Observers
A mutation happens when an element or its contents or attributes are changed. So in short, Mutation Observers provide you a way to monitor changes in an element.
The most relevant difference (at least for extension developers) between observing mutations and listening to events is that mutations capture changes that are done programmatically, which makes it so useful since more often than not we must alter a page whose source we did not author.
Could you listen to a given event like, say, scrolling? Yes. But since there most likely are event listeners going on concurrently with your own that will cause mutations in the page, it's hard to make sure that the element you want to change yourself will already exist by the time the event is triggered (the page's native code might still be doing its thing).
Example
Here's an example borrowing from the above MDN link, plus the desired behavior you described - replacing images, whenever they are added:
EDIT: I'm going to be more descriptive in the comments and leave links to where you should browse for additional information in the MDN docs in case you need them. Note that the example is targeting a specific element (#some-id) but you could target document.body, but not in Stack Overflow's fiddle sandbox, apparently.
// Select the node that will be observed for mutations
// I'm targeting a specific node, but it COULD BE document.body,
// which would observe the entire document for changes
const targetNode = document.getElementById('some-id');
// Options for the observer (which mutations to observe)
// See https://developer.mozilla.org/en-US/docs/Web/API/MutationObserverInit
const config = {
attributes: true, // watch for changes to attributes on the node(s)
childList: true, // watch for the addition or removal of child nodes
subtree: true // all monitoring rules apply to child elements as well
};
/*
PS: all settings are 'optional' but at least one among
childList, attributes or characterData must be true.
Again, don't forget to take a look on the docs!
*/
// Callback function to execute when mutations are observed
// See https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver/MutationObserver#The_callback_function
const callback = function(mutationsList, observer) {
// mutationsList is an array of MutationRecord objects,
// describing each change that occurred.
// See https://developer.mozilla.org/en-US/docs/Web/API/MutationRecord
// observer is the MutationObserver instance that was triggered
for (let mutation of mutationsList) {
if (mutation.type === 'childList') {
// This will be executed when a new image is added
console.log('A child node has been added or removed.');
// Iterate over the added nodes to check if there are any images
for (let node of mutation.addedNodes) {
// Replace the .src attribute if the element is an img
if (node.tagName === 'IMG') {
node.src = 'https://pm1.narvii.com/6334/121fb1f34767638906fac47cf818dc9c326bc936_128.jpg';
}
}
console.log('Garmanarnar will rule above all');
} else if (mutation.type === 'attributes') {
// This will be executed when .src is changed
console.log('The ' + mutation.attributeName + ' attribute was modified.');
}
}
};
// Create an observer instance linked to the callback function
const observer = new MutationObserver(callback);
// Start observing the target node for configured mutations
observer.observe(targetNode, config);
// Later, you can stop observing
// observer.disconnect();
<script>
function addImg() {
console.clear();
let img = document.createElement('img');
img.src = 'https://loremflickr.com/320/240';
document.getElementById('some-id').appendChild(img);
}
</script>
<button style="cursor: pointer;" onclick="addImg()">
<strong>Click me</strong>
</button>
<div id="some-id">
<img src="https://pm1.narvii.com/6334/121fb1f34767638906fac47cf818dc9c326bc936_128.jpg" alt="Garmanarnar" />
</div>
With each click, you should see only Garmanarnars (blue friendly alien doing thumbs up), not any random pic of cats or anything else. This means the new img tag got its src attribute replaced successfully.
In Javascript we use lots and lots of callback functions, so if you're not used to them, you might have a lot to digest, but it will be worth your while learning them.
Additional considerations (you may skip if you're satisfied)
I get that it's just a prank and it would be a 'passable' flaw, but if you want to target every single image in a page, looking for img tags might not be always enough, since some elements will have images included through CSS background-image property, or even svg elements.
I made a Chrome extension myself a few years ago and maybe it's also worth mentioning that you must pay attention whether an image is part of an iframe or not. They're not part of the current document - it's a page from elsewhere and it's sandboxed for security reasons. Take a look at this answer from another Stack Overflow question to see how to enable accessing iframes within a page (you still have to create rules for the target page, though).
Is there a way to detect style changes made only in the developer-tools (inspect element)?
I want to allow to save them if the user is logged in as admin.
You can access the styles of a HTMLElement using element.style in JavaScript. So you could probably save the styles on page load, check them again when the user initiates a save and compare -- the difference would be the users changes.
As for detecting them as soon as they happen, you can use MutationObserver to get notified of attribute changes on the page. This works for when you add styles to a specific element in the Devtools, but not for editing existing styles and (I think) adding styles to existing selectors. I'm not sure if there is a way to get an event when these change.
Edit: actually, it seems as if checking element.style has the same limits as using MutationObserver: only styles added for the specific element (that is, styles that show up as inline styles in the DOM) show up. There might be a way to actually access all styles, but I'm not aware of it.
You could use the MutationObserver to be notified when the DOM elements are modified. See the snippet below to see it working.
Note that it does not tell you whether the mutation was triggered by Javascript on your page, or through the developer tools. So, you have the added burden of tracking when your JS changes something and when the user changes something.
var target = document.getElementById('targetDiv');
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
console.log("New Mutation:");
mutation.addedNodes.forEach(function(node) {
console.log("Added: " + node.nodeName);
});
mutation.removedNodes.forEach(function(node) {
console.log("Removed: " + node.nodeName);
});
});
});
var config = {
attributes: true,
childList: true,
characterData: true
};
observer.observe(target, config);
function mutate() {
target.innerHTML += Math.random() + "<br/>"
}
#targetDiv {
margin: 10px;
width: 500px;
padding: 5px;
}
<div id="targetDiv"></div>
<button id="mutate" onclick="mutate()">Mutate the Div</button>
I am trying to debug an element that loads about for the first .5 - 1 second on page load. It completely disappears after that; I am wondering if there is any way at all I can stop the page while it's loading at a certain time, either with a script or emulator so I can track down this element with developer tools.
You can call window.stop() to stop window loading, if that is what you are looking for.
It sounds like it would be easier to pause the script execution with the browsers javascript debugger though, by setting some breakpoints instead of manually inserting a stop statement.
Second attempt, now that I understand your question a little better.
You can use a MutationObserver to output changes to the DOM. Something as simple as
var observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
console.log(mutation);
});
})
observer.observe(document.body, {
attributes: true,
childList: true,
characterData: true,
subtree: true
});
var hiders = document.querySelectorAll('.hider'),
hiders = Array.prototype.slice.call(hiders);
hiders.forEach(function(el) {
el.parentNode.removeChild(el);
});
<div class="hider"></div>
<div class="hider"></div>
in the window's load handler will work, and will output a MutationRecord on each change. I'd bet one of those will be your element (specifically, the removedNodes of the record.) The browser console for this snippet will have what you'll see if you use this.