Change DOM Content With Chrome Extension

46,109

Solution 1

You need to use this query to send data to DOM from the current tab being viewed.

    chrome.tabs.executeScript(null, {
        code: 'var config = ' + JSON.stringify(getKeywords)
    }, function() {
        chrome.tabs.executeScript(null, {file: 'custom.js'});
    });

and in the custom.js file you can write you function that you want to apply on the DOM element. like if you want to hide something than you need that query in custom.js. So if you wanted to use that example you would need to modify it according to your requirements.

var all = document.getElementsByTagName("div");
var searchValue=config.toString().split(',');
alert('Example:' + searchValue[0]);
for(j=0; j < searchValue.length; j++) {
for(i=0; i < all.length; i++) {
    if(all[i].innerHTML.indexOf(searchValue[j]) > -1){
    all[i].innerHTML = ""
    }
}
}

Solution 2

You should send command from background.js or popup.js, receiving that and change the dom in content.js. The following code demonstrate a simple scenario: Click the browserAction and append a div to the current page. You can use the same logic to show/hide any elements.

manifest.json

{
  "name": "Test",
  "version": "1.0",
  "permissions": [
    "tabs"
  ],
  "description": "Test",
  "background": {
    "persistent": false,
    "scripts": [
      "background.js"
    ]
  },
  "content_scripts": [
    {
      "matches": [
        "*://*/*"
      ],
      "js": [
        "content.js"
      ],
      "run_at": "document_end",
      "all_frames": true
    }
  ],
  "browser_action": {
    "title": "Test"
  },
  "manifest_version": 2
}

background.js

chrome.browserAction.onClicked.addListener(function() {
    chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
        chrome.tabs.sendMessage(tabs[0].id, {command: "append"}, function(response) {
            console.log(response.result);
        });
    });
});

content.js

chrome.runtime.onMessage.addListener(function(request, sender, sendResponse)    {
    console.log(request.command);

    var div = document.createElement('div');
    var label = document.createElement('span');
    label.textContent = "Hello, world";
    div.appendChild(label);
    document.body.appendChild(div);

    sendResponse({result: "success"});
});

Solution 3

I'll try to answer to this question as simply as possible, because making fewer changes to your code will help you learn quicker.

Normally, I write the following lines of code when the end user press a START button on popup (refer to your popup.js):

chrome.runtime.sendMessage({key: 'popupInit'}, function (response) {
 ;
});
window.close();  // this line closes the popup

What is very important is to understand the response is non a communication system but simply an answer on the fly coming from the corresponding listener. The listener for me is the background.js. In this file you may take advantage about the localStorage so that you can have something like:

chrome.tabs.onUpdated.addListener(function(tabid, changeInfo, tab) {
  if (typeof changeInfo.status == 'string' && changeInfo.status == 'complete') {
    chrome.tabs.query({active: true, currentWindow: true}, function (tabs) {
      var keyString = JSON.parse(localStorage.getItem('keyWords'));
      chrome.tabs.sendMessage(tabs[0].id, {key: 'init', wordToHide: keyString}, function (response) {
        ;
      });
    });
  }
});

chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
  var rq = request.key;
  if (rq != undefined && typeof rq == 'string') {
    switch (rq) {
      case 'popupInit':
        chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
          var keyString = JSON.parse(localStorage.getItem('keyWords'));
          chrome.tabs.sendMessage(tabs[0].id, msgToSend, function(response) {
            ;
          });
        });
        break;
    }
  }
});

The chrome.tabs.onUpdated.addListener is important because every time the active current page is updated you can send the message to the content script getting the values from the localstorage, the same happens when the popup is closed and if there are words in localstorage you can use the chrome.runtime.onMessage.addListener to listen for the signal: popup closed so start the work in the content script sending it a message with the array of words.

Now let's take a look to your content.js file.

// Listen for messages from the backgound.js
chrome.runtime.onMessage.addListener(function (msg, sender, response) {
  // First, validate the message's structure
  if ((msg.key === 'init') && (Object.prototype.toString.call(msg.wordToHide) === '[object Array]')) {

    var elements = document.querySelectorAll("body, body *");
    var results = [];
    var child;
    var regwordToHide = [];
    if (msg.wordToHide.length > 0) {
      msg.wordToHide.forEach(function(element, index, array) {
        regwordToHide.push(new RegExp('\\b' + element + '\\b', 'g'));
      });
    }
    for(var i = 0; i < elements.length; i++) {
      child = elements[i].childNodes[0];
      if (elements[i].hasChildNodes() && child.nodeType == 3) {
        var nodeStr = child.textContent;
        if (nodeStr.trim().replace(/\n\r\t/g, '').length > 0 && nodeStr.trim().charAt(0) != '<') {
          regwordToHide.forEach(function(element, index, array) {
            nodeStr = nodeStr.replace(element, '');
          });
          child.textContent = nodeStr;
        }
      }
    }
    document.getElementsByTagName("html")[0].style.visibility = "visible";
  }
});
document.getElementsByTagName("html")[0].style.visibility = "hidden";

I try to hide the whole page: pay attention to this because it can be complicated (remember the end user must always see something while the page is loading....).

After, the content.js wait for a message coming from the background and when this happens the content script starts its work! That's all.

Sorry for the confusion in my writing. If you need some other help let me know. I tested your ext and corrected it in the places you can see.

For the communication between components in a chrome extension you need to remember:

  • the background is the big listener
  • the popup communicate with the background
  • the background communicate with the content

To send/receive messages you may take a look to Chrome messaging

  1. the function chrome.runtime.sendMessage({greeting: "hello"}, function(response) { console.log(response.farewell); }); is used to send
  2. chrome.runtime.onMessage.addListener is the server (receiver)

The sendResponse and response are used just for a check on the fly regarding the communication: something like: Hello --> Ok I understood, Now I continue by myself.

I completed also your word replacing part!

Share:
46,109
wuno
Author by

wuno

Director of Software Engineer (CEH)

Updated on February 04, 2021

Comments

  • wuno
    wuno over 3 years

    I am building a Chrome extension. I am trying to get my app to communicate with each page in the extension and the page the user is viewing in the browser. I need to access the dom from the extension and then update it.

    manifest.json 
    popup.html
    popup.js
    background.js 
    content.js
    

    and the current page the user is viewing.

    My goal is on page load modify the dom and show the user the new version of the page before they ever see it. in popup.js users are allowed to enter keywords into the popup. The keywords are saved to localStorage and while they view the web the keywords are censored out of their view by making the parent div of the keywords hidden if it is found on any pages they are viewing.

    I need help getting each page to communicate and I think the way I am hiding the parent divs in popup.js won't work. I am confused on how to perform the action on the dom from the front.

    Send the dom to the background.js Find keywords on the page and change their parent divs to hidden. push the dom back to the viewing page.

    I think this line is saying if I match any url then run my app but I am not sure.

      "matches":    ["*://*/*"],
    

    My manifest.json

    {
     "name": "Wuno Zensoring",
      "version" : "1.0",
       "permissions": [
       "activeTab",
       "tabs",
       "storage"
       ],
      "description": "This extension will search the document file for keywords and hide their parent div.",
      "icons": {                   
        "19": "icon19.png",
        "38": "icon38.png",
        "48": "icon48.png",
        "128": "icon128.png"  
      },    
        "background": {
        "persistent": false,
        "scripts": ["jquery-1.11.3.min.js","background.js"]
      },
         "content_scripts": [{
            "matches":    ["*://*/*"],
            "js":         ["content.js"],
            "run_at": "document_end",
            "all_frames": true
        }],
         "web_accessible_resources": [
            "popup.js", "content.js"
            ],
      "browser_action": {
        "default_icon": "icon.png128",
        "default_popup": "popup.html",
        "default_icon": {                   
          "19": "icon19.png",
          "38": "icon38.png",
          "48": "icon48.png",
          "128": "icon128.png"        
      }
      },
         "manifest_version": 2
    }
    

    popup.html

    <!doctype html>
    <html>
      <head>
        <title>Wuno Zensorship</title>
        <script src="jquery-1.11.3.min.js"></script>
            <script src="popup.js"></script>
        <link rel="stylesheet" type="text/css" href="styles.css">
      </head>
      <body>
        <img src="icon48.png">
    
     <section>
    <form id="form" action="#" method="POST">
    <input id="description" name="description" type="text" />
    <input id="add" type="submit" value="Add" />
    <button id="clearChecked">Clear Checked Items</button>
    <button id="clear">Clear All</button>
    </form>
    <div id="alert"></div>
    <ul id="keyWords"></ul>
    </body>
    </html>
    

    popup.js

       $(document).ready(function () {
    localArray = [];
    
    if (!localStorage.keyWords) {
      localStorage.setItem('keyWords', JSON.stringify(localArray));
    }
    
    loadKeyWords();
    
    function loadKeyWords() {
        $('#keyWords').html('');
        localArray = JSON.parse(localStorage.getItem('keyWords'));
        for(var i = 0; i < localArray.length; i++) {
          $('#keyWords').prepend('<li><input class="check" name="check" type="checkbox">'+localArray[i]+'</li>'); 
            }
        }
    
    $('#add').click( function() {
       var Description = $('#description').val();
      if($("#description").val() === '') {
        $('#alert').html("<strong>Warning!</strong> You left the to-do empty");
        $('#alert').fadeIn().delay(1000).fadeOut();
        return false;
       }
       $('#form')[0].reset();
       var keyWords = $('#keyWords').html();
       localArray.push(Description);
       localStorage.setItem('keyWords', JSON.stringify(localArray));
       loadKeyWords();
       return false;
    });
    
    $('#clear').click( function() {
    window.localStorage.clear();
    location.reload();
    return false;
    });
    
    $('#clearChecked').click(function() {
      currentArray = [];
      $('.check').each(function() {
        var $curr = $(this);
        if (!$curr.is(':checked')) {
          var value = $curr.parent().text();
          currentArray.push(value);
          localStorage.setItem('keyWords', JSON.stringify(currentArray));
          loadKeyWords();
        } else {
          $curr.parent().remove();
        }
      });
    });
    
    
    // Update the relevant fields with the new data
    function setDOMInfo(info) {
      $("div p:contains(localStorage.getItem('keyWords')).parent('div').hide()");
    }
    
    // Once the DOM is ready...
    window.addEventListener('DOMContentLoaded', function () {
      // ...query for the active tab...
      chrome.tabs.query({
        active: true,
        currentWindow: true
      }, function (tabs) {
        // ...and send a request for the DOM info...
        chrome.tabs.sendMessage(
            tabs[0].id,
            {from: 'popup', subject: 'DOMInfo'},
            // ...also specifying a callback to be called 
            //    from the receiving end (content script)
            setDOMInfo);
      });
    });
    
    
    }); // End of document ready function
    

    background.js

    chrome.runtime.onMessage.addListener(function (msg, sender) {
      // First, validate the message's structure
      if ((msg.from === 'content') && (msg.subject === 'showPageAction')) {
        // Enable the page-action for the requesting tab
        chrome.pageAction.show(sender.tab.id);
      }
    });
    

    content.js

    // Inform the background page that 
    // this tab should have a page-action
    chrome.runtime.sendMessage({
      from:    'content',
      subject: 'showPageAction'
    });
    
    // Listen for messages from the popup
    chrome.runtime.onMessage.addListener(function (msg, sender, response) {
      // First, validate the message's structure
      if ((msg.from === 'popup') && (msg.subject === 'DOMInfo')) {
        // Collect the necessary data 
        // (For your specific requirements `document.querySelectorAll(...)`
        //  should be equivalent to jquery's `$(...)`)
        var domInfo = {
          total:   document.querySelectorAll('*').length,
          inputs:  document.querySelectorAll('input').length,
          buttons: document.querySelectorAll('button').length
        };
    
        // Directly respond to the sender (popup), 
        // through the specified callback */
        response(domInfo);
      }
    });
    
    • gkalpak
      gkalpak over 8 years
      Without looking at the whole code, creating a new DOM in the background page and sending it to the content script does not sounds liek a good plan :) It will break most pages. The DOM manipulation should happen in place (e.g. by injecting a script with the necessary logic into the page).
    • gkalpak
      gkalpak over 8 years
      Just put the DOM-manipulation logic in a content script and send the keywords to it (instead of sending the HTML, filtering it in the background or popup page and send it back to the content script).
    • karmiphuc
      karmiphuc over 8 years
      Not totally solving your problem, but I suggest you take a look at this solution to search for text containers stackoverflow.com/a/18089011/1705006. Searching for direct parent nodes is more efficient and more logical than div nodes.
  • wuno
    wuno over 8 years
    Are you able to change the example you showed to use my situation?
  • Jonathan
    Jonathan over 4 years
    Where do vars getKeyWords and config come from?
  • Sølve T.
    Sølve T. over 4 years
    You really just need chrome.tabs.executeScript(null, {file: 'custom.js'});