59

I am writing a Google Chrome extension to automate some common tasks. The functionality I want is as follows:

  1. Create a new tab and navigate to my webmail
  2. enter username and password
  3. click "submit" button
  4. Wait until the webmail page appears, and choose the "roundcube" client.

I have completed steps 1,2,and 3 and they work. I am having a lot of trouble trying to listen for the url change after my credentials are submitted so that the function that selects roundcube client can run

I know I can run a script when client selection page appears by adding to my manifest but I want to use "chrome.tabs.executeScript" instead so that roundcube is chosen only if I run the script from the chrome extension and not if I go to client selection page manually.

Here is my manifest.json:

{
  "manifest_version": 2,

  "name"       : "Chrome Autobot",
  "description": "This extension will run various automation scripts for google chrome",
  "version"    : "1.0",

  "browser_action" : {
    "default_icon" : "icon.png",
    "default_popup": "index.html"
  },
  "permissions": [
    "activeTab",
    "webNavigation",
    "tabs",
    "http://*/*",
    "https://*/*"
  ]
}

Here is my chrome script:

jQuery(function($) {
    "Use Strict";

    var openWebmail = function() {
        chrome.tabs.create({
            url: 'http://mywebmaillogin.com:2095/'
        }, function() {
            chrome.tabs.executeScript(null, {file: "scripts/openEmail.js"});
        });
        chrome.tabs.onUpdated.addListener(function(){
            chrome.tabs.executeScript(null, {file: "scripts/openEmail.js"});
            alert('i work');
        });
    };

    var init = $('.script-init');
    init.on('click', function() {
        openWebmail();
    });

});

and here is the content script to be executed as a callback of tab creation (when the email login page is fetched and the DOM has loaded), and also when the email credentials are submitted and the client selection page's DOM has loaded (which is not working right now)

var openEmail = function() {
    var loc = window.location.href;
    if(loc === 'http://mywebmaillogin.com:2095/') {
        var submit = document.getElementById('login_submit');
        user.value = 'myusername';
        pass.value = 'mypassword';
        if(user.value === 'myusername' && pass.value === 'mypassword') {
            submit.click();
        }
        else {
            openEmail();
        }
    }
    if(loc.indexOf('http://mywebmaillogin:2095/') > -1 && loc.indexOf('login=1') > -1) {
        alert('I work');
    }
}()

any help would be appreciated... thanks!

HelloWorld
  • 2,480
  • 3
  • 28
  • 45

3 Answers3

100

As mentioned by @NycCompSci, you cannot call the chrome api from content scripts. I was able to pass api data to content scripts with message passing though, so thought I'd share that here. First call onUpdated in background.js:

Manifest

{
  "name": "My test extension",
  "version": "1",
  "manifest_version": 2,
  "background": {
    "scripts":["background.js"]
  },
  "content_scripts": [
    {
      "matches": ["http://*/*", "https://*/*"],
      "js": ["contentscript.js"]
    }
  ],
  "permissions": [
    "tabs"
  ]
}

background.js

chrome.tabs.onUpdated.addListener(function
  (tabId, changeInfo, tab) {
    // read changeInfo data and do something with it (like read the url)
    if (changeInfo.url) {
      // do something here

    }
  }
);

Then you can expand that script to send data (including the new url and other chrome.tabs.onUpdated info) from background.js to your content script like this:

background.js

chrome.tabs.onUpdated.addListener(
  function(tabId, changeInfo, tab) {
    // read changeInfo data and do something with it
    // like send the new url to contentscripts.js
    if (changeInfo.url) {
      chrome.tabs.sendMessage( tabId, {
        message: 'hello!',
        url: changeInfo.url
      })
    }
  }
);

Now you just need to listen for that data in your content script:

contentscript.js

chrome.runtime.onMessage.addListener(
  function(request, sender, sendResponse) {
    // listen for messages sent from background.js
    if (request.message === 'hello!') {
      console.log(request.url) // new url is now in content scripts!
    }
});
starball
  • 20,030
  • 7
  • 43
  • 238
ztrat4dkyle
  • 1,652
  • 2
  • 13
  • 13
  • What “callback” are you referring to? It could be that your permissions aren’t allowing data transfer, so make sure your manifest is setup like the above. I’d suggest starting with the above example and tweaking it because it definitely works in a fresh extension. – ztrat4dkyle Feb 17 '19 at 16:18
  • 2
    I have all permissions, including tabs, but in this schema I don´t received any call in function. Sad. – Marcelo Gomes Feb 20 '19 at 01:17
  • Sorry, Marcelo, you’re not being very specific so it’s hard to be helpful here. I would suggest trouble shooting by adding a `console.log(data)` on the first line within the function, where `data` is the data you’re trying to use, and view the console to see if it’s there. If not you’ll most likely find an error that you can search for help :) – ztrat4dkyle Feb 23 '19 at 17:51
  • @ztrat4dkyle how to fire message on first page load too? – ZiiMakc May 25 '20 at 13:48
  • Hi @RTW it's not super clear what you're asking, I think you should open a new issue for your question and provide some examples. – ztrat4dkyle May 25 '20 at 18:14
  • 2
    This is a good approach, but the `changeInfo` object only had the `status` property in my case, so went with @SynergyChen answer---where you test for the `changeInfo.status` property, and then get the URL from within the content script after the message is conveyed. – Keith DC Jun 25 '20 at 04:53
  • is there any way to listen for url change in iframe? I see only "loading" and "complete" state in logs, but not url. –  van9petryk Jan 01 '23 at 20:54
  • @van9petryk I'm not 100% sure but it's best to open a new question for that – ztrat4dkyle Jan 02 '23 at 22:14
  • @ztrat4dkyle i found that it can be done with chrome.webNavigation https://developer.chrome.com/docs/extensions/mv2/background_pages/#filters –  van9petryk Jan 02 '23 at 22:25
17

use chrome.tabs.onUpdated

Maifest.json

{
  "name": "My test extension",
  "version": "1",
  "manifest_version": 2,
  "background": {
    "scripts":["background.js"]
  },
  "content_scripts": [
    {
      "matches": ["http://*/*", "https://*/*"],
      "js": ["contentscript.js"]
    }
  ],
  "permissions": [
    "tabs"
  ]
}

contentscript.js

 chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
    alert('updated from contentscript');
 });

background.js

chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
    alert('updated from background');
});
SwankyLegg
  • 473
  • 4
  • 14
Shubham
  • 451
  • 4
  • 9
  • 4
    Why you need to add this tabs.onUpdated listener twice, at contentscript.js and background.js respectively? – lsheng Jul 14 '17 at 07:15
  • 1
    You don't have to use both, pick the one where you need to handle it. Note: In `background.js` the callback function receives the same parameters. – Blaise Oct 25 '17 at 15:29
  • 62
    The contentscript.js doesn't work, you cannot call chrome api from content scripts, see this for reference: https://stackoverflow.com/questions/24024682/google-chrome-exstension-chrome-tabs-onupdated-addlistener – NycCompSci Jan 19 '18 at 07:27
7

Based on / Thanks to @ztrat4dkyle's answer, what works for me:

manifest.json

{
  ...
  "background": {
    "scripts":["background.js"]
  },
  "content_scripts": [
    {
      "matches": ["http://*/*", "https://*/*"],
      "js": ["content.js"]
    }
  ],
  "permissions": [
    "tabs"
  ]
}

background.js

chrome.runtime.onInstalled.addListener(function() {
  // ...

  chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) {
    // changeInfo object: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs/onUpdated#changeInfo
    // status is more reliable (in my case)
    // use "alert(JSON.stringify(changeInfo))" to check what's available and works in your case
    if (changeInfo.status === 'complete') {
      chrome.tabs.sendMessage(tabId, {
        message: 'TabUpdated'
      });
    }
  })
});

content.js

chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
  if (request.message === 'TabUpdated') {
    console.log(document.location.href);
  }
})
SynergyChen
  • 1,161
  • 9
  • 4