0

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

BrownBoii333
  • 85
  • 1
  • 8
  • See also [Is there a JavaScript / jQuery DOM change listener?](https://stackoverflow.com/a/39508954) – wOxxOm May 10 '20 at 05:52

1 Answers1

0

It will happen mainly for two reasons:

  1. Not all the img tags exist when you perform the replacement;
  2. 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).

gyohza
  • 736
  • 5
  • 18
  • I'm a little confused. Firstly, why are we assigning the target based on 'some-id'. Surely the id on any htmlElement would/could be unique to a specific webpage, so how does that help? Also the properties in the "config" object you defined are unfamiliar to me, what do they mean? Also what counts as a "mutation" of the dom? Would just scrolling down the screen count? – BrownBoii333 May 10 '20 at 17:30
  • As I said, I borrowed the example directly from MDN, I just changed as little as possible to mimic the behavior you wanted and created the element with the ID myself. Yes, you are right, assigning to a particular ID won't make it "universal". What is important is that you can observe mutations from **any** element, **including** `document.body` itself. I even tried editing my answer so that it does observe the body, but it seems the snippet blocks it for security reasons... it just hangs on forever. (although it seems to work on JSFiddle). Hang on... to be continued. – gyohza May 11 '20 at 04:33
  • Please try to read the docs that I linked on Mutation Observers. I know it might be a little too much as you said you're new to web dev but it is sometimes a necessary pain. I could point to some documentation by the notorious w3schools (they're not as bad as people make them out to be) but they don't have anything on Mutation Observers. MDN docs are trustworthy and provide nice examples. If you want details on the config object, visit [this link](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver/observe). It's part of the docs I pointed out earlier. – gyohza May 11 '20 at 04:43
  • And no, probably scrolling does not count, you'd have to listen for scroll **events**, since they do not **change** any element or its contents. In this case, however, you'd be better off if you stick to mutations, because they are **triggered directly by the mutation itself**. If you rely on listening to the scrolling motion that triggers the mutation, the event will fire both your action and the standard action from the application, there is no guarantee that when your action is triggered the element will already exist. I will try to elaborate some more on Mutation Observers in my answer. – gyohza May 11 '20 at 04:49
  • @TheYungGrandpa - have you looked into it? I have editted my answer to be a little more descriptive and included many links in the comments of my snippet so you know where to browse for info. – gyohza May 15 '20 at 15:45