Search code examples
javascriptgoogle-chrome-extensionchrome-extension-manifest-v3

How to prevent hide and show flickering of MutationObserver for iframe


I'm developing chrome extension MV3 which hide article.

MutationObserver hide article without flickering at main frame.

For example, below code hides odd's article at Reddit when there's new article by scrolling down.

function eraseDealer(){
    console.log("erase Function Call");
    let ls = document.querySelectorAll('#SHORTCUT_FOCUSABLE_DIV > div:nth-child(4) > div > div > div > div._3ozFtOe6WpJEMUtxDOIvtU > div._31N0dvxfpsO6Ur5AKx4O5d > div._1OVBBWLtHoSPfGCRaPzpTf._2OVNlZuUd8L9v0yVECZ2iA > div.rpBJOHq2PR60pnwJlUyP0 > div');
    let idx;

    for(idx=ls.length-1; idx>=0; idx--){
        if(idx%2===0) continue
        ls[idx].setAttribute('style', 'display: none;');
    }
}

eraseDealer();

var config = { childList: true, characterData: false };
var htmlBody = document.getElementsByClassName('rpBJOHq2PR60pnwJlUyP0')[0];

var observerErase = new MutationObserver(function(mutations, observer) {
    mutations.forEach(function(mutation) {
        console.log("Mutation call - erase");
        eraseDealer();

    });
});

observerErase.observe(htmlBody, config);

But if MutationObserver(in main frame) observe article(in iframe), there's flickering(first show article, then hide article)

Below code works at Korea community site and what code does is similar to the code above.

See how it works at loom

function eraseDealer(){
    let inner_iframe = document.getElementById('cafe_main').contentWindow;
    let post_table = inner_iframe.document.querySelectorAll('#main-area > div.article-board.m-tcol-c')[1];
    if(post_table !== undefined){
        let ls = post_table.querySelectorAll('div.article-board.m-tcol-c > table > tbody > tr');

        let idx;
        let dealer_link = 'https://cafe.pstatic.net/levelicon/1/1_150.gif';

        for(idx=ls.length-1; idx>=0; idx--){
            let src = ls[idx].querySelector('td.td_name > div > table > tbody > tr > td > span > img').getAttribute('src');
            let nickName = ls[idx].querySelector('td.td_name > div > table > tbody > tr > td > a').textContent;
            if(src === dealer_link){
                ls[idx].setAttribute('style', 'display: none;');
            }
        }
    }
}

eraseDealer();

var config = { attributes: true, childList: true, subtree: true, characterData: false };
var target = document.querySelector('#main-area');

var observerErase = new MutationObserver(function(mutations, observer) {
    mutations.forEach(function(mutation) {
        console.log("Mutation call - erase");
        eraseDealer();
    });
});

observerErase.observe(target, config);

Why I insert mutationObserver not in iframe but in main frame is that mutationObserver is deleted when iframe url is changed

See my before question concerned about this

Is there any MutationObserver property need to stop flickering concerned with iframe?

I searched a lot with stackoverflow and chrome documentation, but I can't find solution


Solution

  • Instead of MutationObserver in the main document you can detect the moment the iframe is navigated inside, then add a MutationObserver for the iframe's contents.

    1. The checkFrameReady listener will run after iframe's URL changes, but before its navigation is complete.
    2. During this time span, DOMException will keep occurring inside the listener when it tries to start the observer.
    3. The listener will repeat itself via requestAnimationFrame until the iframe finally finishes its navigation to the new URL. At that moment the iframe becomes same-origin to the main page and our code will be able to access the contents of the iframe and start the observer.
    var SEL = '.td_name img[src="https://cafe.pstatic.net/levelicon/1/1_150.gif"]';
    var fw = document.querySelector('#cafe_main').contentWindow;
    fw.addEventListener('unload', checkFrameReady);
    
    function checkFrameReady(e) {
      if (!e.type) {
        try {
          startObserver(new fw.MutationObserver(eraseDealer));
          fw.addEventListener('unload', checkFrameReady);
          return;
        } catch (e) {}
      }
      requestAnimationFrame(checkFrameReady);
    }
    function eraseDealer(mutations, observer) {
      let stopped;
      for (const { addedNodes } of mutations) {
        for (const n of addedNodes) {
          if (!n.tagName) continue;
          const elems = n.matches(SEL) && [n] ||
            n.firstElementChild && n.querySelectorAll(SEL);
          if (!elems || !elems.length) continue;
          if (!stopped) { stopped = true; observer.disconnect(); }
          elems.forEach(el => el.closest('.td_name').closest('tr').remove());
        }
      }
      if (stopped) startObserver(observer);
    }
    function startObserver(observer) {
      observer.observe(fw.document.body || fw.document.documentElement,
        {childList: true, subtree: true});
    }