5

I'm working on a Chrome extension that replaces specified strings or RegEx's in the text of a web page.

It works well overall, but with two issues that I'd like to solve:

(1) The original, unchanged webpage text is displayed before the text replacement occurs.

(2) The text replacement doesn't affect Facebook posts that are dynamically loaded after you scroll to the bottom of the page.

Here's the code, adapted from https://stackoverflow.com/a/6012345#6012345 with minor changes.

// manifest.json

{
    "manifest_version": 2,
    "name": "Replace Text",
    "version": "1.0", 

    "content_scripts": [ {
        "js": [ "jquery.min.js", "replace.js" ],
        "matches": [ "<all_urls>" ],
        "run_at": "document_end"
    } ]
}


// replace.js

jQuery.fn.textWalk = function( fn ) {
    this.contents().each( jwalk );

    function jwalk() {
        var nn = this.nodeName.toLowerCase();
        if( nn === '#text') {
            fn.call( this );
        } else if( this.nodeType === 1 && this.childNodes && this.childNodes[0] && nn !== 'script' && nn !== 'textarea' ) {
            $(this).contents().each( jwalk );
        }
    }
    return this;
};

$('body').textWalk(function() {
    this.data = this.data.replace('This Text', 'That Text');
    this.data = this.data.replace(/[Rr]eplace\s[Ss]ome\s[Tt]ext/g, 'with other text');  
});

I found some partial answers online, but couldn’t get them to work right.

For example, one proposed solution was to change "run_at": "document_end" to "run_at": "document_start". This runs content scripts before the DOM is constructed, so in theory it should do the text replacement before anything is displayed. But in my case it caused the extension to stop replacing text altogether.

Community
  • 1
  • 1
  • Did you try my suggestion ? Did it work for you ? – gkalpak Oct 31 '13 at 14:44
  • It works on most sites, but I can't get it to work on Facebook (not even the first posts that appear when the page first loads). – leopard_keyboard Nov 01 '13 at 16:33
  • 1
    As an alternative, I could use an event listener to re-run the replace function whenever there was a new AJAX request... Think that's worth a try? – leopard_keyboard Nov 01 '13 at 16:54
  • As I point out in my answer, the **sample** code provided *still needs twicking, e.g. to handle dynamic node updates*. I would stick with MutationObservers, trying to find out what does actually happen "behind the scenes" on the FB page and then decide how to vwpture (and respond) to DOM changes. (Sorry, I can't help further, since I am not on FB.) – gkalpak Nov 01 '13 at 17:15
  • OK, I'll work on it some more and see what happens -- thanks very much for your assistance! – leopard_keyboard Nov 01 '13 at 17:58
  • Did you get your problem solved? I'm having the same problem on facebook. – mipsc Aug 30 '20 at 18:12

1 Answers1

2

A viable alternative would be to listen for DOM changes by means of a MutationObserver and change the content of TextNodes (or what ever) on the fly. Technically, this does not happen before anything is being rendered, but it should be close enough for the user not to notice (unless the changes you make are massive).

See, also, my answer to a similar question.

Sample code

(This still needs twicking, e.g. to handle dynamic node updates.)

content.js:

// Modify the content somehow...
var doFilter = function(textNode) {
    textNode.data = textNode.data + "<br />" + textNode.data;
}

// Create a MutationObserver to handle events
// (e.g. filtering TextNode elements)
var observer = new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
        if (mutation.addedNodes) {
            [].slice.call(mutation.addedNodes).forEach(function(node) {
                if (node.nodeName.toLowerCase() == "#text") {
                    doFilter(node);
                }
            });
        }
    });
});

// Start observing "childList" events in document and its descendants
observer.observe(document, {
    childList: true,
    subtree:   true
});

(The above code it for listening for added nodes. You might want to have an observer listen for characterData and childList changes in body and its descendants for "catching" dymanically loaded/changed content.)

manifest.json:

...
"content_scripts": [
    {
        "matches": [...],
        "js":         ["content.js"],
        "run_at":     "document_start",
        "all_frames": true
    }
],
...

If you decide to go for the MutationObserver approach, there is this JS library that is supposed to make your life easier: mutation-summary


Regarding your question, why executing your script at "document_start" does not have any effect:
This happens, because at that time ("document_start") there is nothing for your script to replace (i.e. it is loaded and run before any other content is added to the DOM).

Community
  • 1
  • 1
gkalpak
  • 47,844
  • 8
  • 105
  • 118