I have the following code in my background.js:
chrome.webRequest.onHeadersReceived.addListener(function(details){
if(isfileTypeXYZ(details))
{
chrome.tabs.sendMessage(details.tabId, {isFileXYZ: true});
return { //Stop rendering of frame...
responseHeaders: [{
name: 'X-Content-Type-Options',
value: 'nosniff'
}, {
name: 'X-Frame-Options',
value: 'deny'
}]
};
}
}, {
urls: ['*://*/*'],
types: ['main_frame']
}, ['blocking', 'responseHeaders']);
And in my contentscript I have the following code:
var toLoadXYZ = 0;
chrome.runtime.onMessage.addListener(function(msg, _, sendMessage){
if(msg.isFileXYZ)
{
toLoadXYZ = 1;
}
});
$(document).ready(function(){
alert(toLoadXYZ);
});
What I want to do is to detect if a particular file type is being opened and then load an image from a server running on the system itself and display it. I will have to load the image using xhr but I need to get this detection thing working well first. There are two problems that I am facing:
The onMessage is not triggered when the url is first loaded - it is after that (refreshing using F5).
The value shown in the alert(toLoadXYZ) is 0 not 1 even though the debugger shows that the onMessage is triggered (after first load that is - after first load i am refreshing the page using F5).
Here is the manifest file if you want to refer:
{
"manifest_version": 2,
"name": "my Extension",
"version": "1.0",
"background": {
"scripts": ["background.js"],
"persistent": true
},
"permissions": [
"webRequest",
"<all_urls>",
"webRequestBlocking",
"tabs",
"webNavigation"
],
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["jquery-2.1.4.min","renderXYZ.js"]
}
],
"web_accessible_resources": [ "http:/*", "https:/*", "ftp:/*", "file:/*", "chrome-extension:/*"]
}
What is the cause for this problem? How to fix it? I searched a lot didn't get anything useful please help!!
UPDATE
function findContentType(responseHeaders)
{
for(var i = 0; i < responseHeaders.length; i++)
{
var header = responseHeaders[i];
if(header.name.toLowerCase() === "content-type")
return header.value.toLowerCase();
}
return "";
}
function isfileTypeXYZ(details)
{
var contentType = findContentType(details.responseHeaders);
if(contentType === "some-mime-type-here")
{
return true;
}
return false;
}
chrome.runtime.onMessage is beeing called, but it's being called on the content script of the previous request. Since you're refreshing the page, tabId doesn't change so you send the message to the content script of the previous page, right before the new body gets loaded, that's why you're seeing this behaviour. Maybe clearer like this:
First URL call.
onHeadersReceived gets called
You call chrome.tabs.sendMessage but it doesn't trigger anything since the content script is not loaded yet.
The tab loads the body of the request and the content scripts. Document ready gets called.
You refresh the tab. onHeadersReceived gets called for this new request but the contents of the tab are STILL the ones of the previous request. This is where you see your alert and why the toLoadXYZ is 0.
Related
I'm having fun with Google Chrome extension, and I just want to know how can I store the URL of the current tab in a variable?
Use chrome.tabs.query() like this:
chrome.tabs.query({active: true, lastFocusedWindow: true}, tabs => {
let url = tabs[0].url;
// use `url` here inside the callback because it's asynchronous!
});
This requires that you request access to the chrome.tabs API in your extension manifest:
"permissions": [ ...
"tabs"
]
It's important to note that the definition of your "current tab" may differ depending on your extension's needs.
Setting lastFocusedWindow: true in the query is appropriate when you want to access the current tab in the user's focused window (typically the topmost window).
Setting currentWindow: true allows you to get the current tab in the window where your extension's code is currently executing. For example, this might be useful if your extension creates a new window / popup (changing focus), but still wants to access tab information from the window where the extension was run.
I chose to use lastFocusedWindow: true in this example, because Google calls out cases in which currentWindow may not always be present.
You are free to further refine your tab query using any of the properties defined here: chrome.tabs.query
Warning! chrome.tabs.getSelected is deprecated. Please use chrome.tabs.query as shown in the other answers.
First, you've to set the permissions for the API in manifest.json:
"permissions": [
"tabs"
]
And to store the URL :
chrome.tabs.getSelected(null,function(tab) {
var tablink = tab.url;
});
Other answers assume you want to know it from a popup or background script.
In case you want to know the current URL from a content script, the standard JS way applies:
window.location.toString()
You can use properties of window.location to access individual parts of the URL, such as host, protocol or path.
The problem is that chrome.tabs.getSelected is asynchronous. This code below will generally not work as expected. The value of 'tablink' will still be undefined when it is written to the console because getSelected has not yet invoked the callback that resets the value:
var tablink;
chrome.tabs.getSelected(null,function(tab) {
tablink = tab.url;
});
console.log(tablink);
The solution is to wrap the code where you will be using the value in a function and have that invoked by getSelected. In this way you are guaranteed to always have a value set, because your code will have to wait for the value to be provided before it is executed.
Try something like:
chrome.tabs.getSelected(null, function(tab) {
myFunction(tab.url);
});
function myFunction(tablink) {
// do stuff here
console.log(tablink);
}
This is a pretty simple way
window.location.toString();
You probaly have to do this is the content script because it has all the functions that a js file on a wepage can have and more.
Hi here is an Google Chrome Sample which emails the current Site to an friend. The Basic idea behind is what you want...first of all it fetches the content of the page (not interessting for you)...afterwards it gets the URL (<-- good part)
Additionally it is a nice working code example, which i prefer motstly over reading Documents.
Can be found here:
Email this page
This Solution is already TESTED.
set permissions for API in manifest.json
"permissions": [ ...
"tabs",
"activeTab",
"<all_urls>"
]
On first load call function. https://developer.chrome.com/extensions/tabs#event-onActivated
chrome.tabs.onActivated.addListener((activeInfo) => {
sendCurrentUrl()
})
On change call function. https://developer.chrome.com/extensions/tabs#event-onSelectionChanged
chrome.tabs.onSelectionChanged.addListener(() => {
sendCurrentUrl()
})
the function to get the URL
function sendCurrentUrl() {
chrome.tabs.getSelected(null, function(tab) {
var tablink = tab.url
console.log(tablink)
})
async function getCurrentTabUrl () {
const tabs = await chrome.tabs.query({ active: true })
return tabs[0].url
}
You'll need to add "permissions": ["tabs"] in your manifest.
For those using the context menu api, the docs are not immediately clear on how to obtain tab information.
chrome.contextMenus.onClicked.addListener(function(info, tab) {
console.log(info);
return console.log(tab);
});
https://developer.chrome.com/extensions/contextMenus
You have to check on this.
HTML
<button id="saveActionId"> Save </button>
manifest.json
"permissions": [
"activeTab",
"tabs"
]
JavaScript
The below code will save all the urls of active window into JSON object as part of button click.
var saveActionButton = document.getElementById('saveActionId');
saveActionButton.addEventListener('click', function() {
myArray = [];
chrome.tabs.query({"currentWindow": true}, //{"windowId": targetWindow.id, "index": tabPosition});
function (array_of_Tabs) { //Tab tab
arrayLength = array_of_Tabs.length;
//alert(arrayLength);
for (var i = 0; i < arrayLength; i++) {
myArray.push(array_of_Tabs[i].url);
}
obj = JSON.parse(JSON.stringify(myArray));
});
}, false);
If you want the full extension that store the URLs that opened or seen by the use via chrome extension:
use this option in your background:
openOptionsPage = function (hash) {
chrome.tabs.query({ url: options_url }, function (tabs) {
if (tabs.length > 0) {
chrome.tabs.update(
tabs[0].id,
{ active: true, highlighted: true, currentWindow: true },
function (current_tab) {
chrome.windows.update(current_tab.windowId, { focused: true });
}
);
} else {
window.addEventListener(hash, function () {
//url hash # has changed
console.log(" //url hash # has changed 3");
});
chrome.tabs.create({
url: hash !== undefined ? options_url + "#" + hash : options_url,
});
}
});
};
you need index.html file also. which you can find in the this Github
the manifest file should be like this:
{
"manifest_version": 2,
"name": "ind count the Open Tabs in browser ",
"version": "0.3.2",
"description": "Show open tabs",
"homepage_url": "https://github.com/sylouuu/chrome-open-tabs",
"browser_action": {},
"content_security_policy": "script-src 'self' https://ajax.googleapis.com https://www.google-analytics.com; object-src 'self'",
"options_page": "options.html",
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"]
}
],
"background": {
"scripts": ["background.js"]
},
"web_accessible_resources": ["img/*.png"],
"permissions": ["tabs", "storage"]
}
The full version of simple app can be found here on this Github:
https://github.com/Farbod29/extract-and-find-the-new-tab-from-the-browser-with-chrome-extention
I'm having fun with Google Chrome extension, and I just want to know how can I store the URL of the current tab in a variable?
Use chrome.tabs.query() like this:
chrome.tabs.query({active: true, lastFocusedWindow: true}, tabs => {
let url = tabs[0].url;
// use `url` here inside the callback because it's asynchronous!
});
This requires that you request access to the chrome.tabs API in your extension manifest:
"permissions": [ ...
"tabs"
]
It's important to note that the definition of your "current tab" may differ depending on your extension's needs.
Setting lastFocusedWindow: true in the query is appropriate when you want to access the current tab in the user's focused window (typically the topmost window).
Setting currentWindow: true allows you to get the current tab in the window where your extension's code is currently executing. For example, this might be useful if your extension creates a new window / popup (changing focus), but still wants to access tab information from the window where the extension was run.
I chose to use lastFocusedWindow: true in this example, because Google calls out cases in which currentWindow may not always be present.
You are free to further refine your tab query using any of the properties defined here: chrome.tabs.query
Warning! chrome.tabs.getSelected is deprecated. Please use chrome.tabs.query as shown in the other answers.
First, you've to set the permissions for the API in manifest.json:
"permissions": [
"tabs"
]
And to store the URL :
chrome.tabs.getSelected(null,function(tab) {
var tablink = tab.url;
});
Other answers assume you want to know it from a popup or background script.
In case you want to know the current URL from a content script, the standard JS way applies:
window.location.toString()
You can use properties of window.location to access individual parts of the URL, such as host, protocol or path.
The problem is that chrome.tabs.getSelected is asynchronous. This code below will generally not work as expected. The value of 'tablink' will still be undefined when it is written to the console because getSelected has not yet invoked the callback that resets the value:
var tablink;
chrome.tabs.getSelected(null,function(tab) {
tablink = tab.url;
});
console.log(tablink);
The solution is to wrap the code where you will be using the value in a function and have that invoked by getSelected. In this way you are guaranteed to always have a value set, because your code will have to wait for the value to be provided before it is executed.
Try something like:
chrome.tabs.getSelected(null, function(tab) {
myFunction(tab.url);
});
function myFunction(tablink) {
// do stuff here
console.log(tablink);
}
This is a pretty simple way
window.location.toString();
You probaly have to do this is the content script because it has all the functions that a js file on a wepage can have and more.
Hi here is an Google Chrome Sample which emails the current Site to an friend. The Basic idea behind is what you want...first of all it fetches the content of the page (not interessting for you)...afterwards it gets the URL (<-- good part)
Additionally it is a nice working code example, which i prefer motstly over reading Documents.
Can be found here:
Email this page
This Solution is already TESTED.
set permissions for API in manifest.json
"permissions": [ ...
"tabs",
"activeTab",
"<all_urls>"
]
On first load call function. https://developer.chrome.com/extensions/tabs#event-onActivated
chrome.tabs.onActivated.addListener((activeInfo) => {
sendCurrentUrl()
})
On change call function. https://developer.chrome.com/extensions/tabs#event-onSelectionChanged
chrome.tabs.onSelectionChanged.addListener(() => {
sendCurrentUrl()
})
the function to get the URL
function sendCurrentUrl() {
chrome.tabs.getSelected(null, function(tab) {
var tablink = tab.url
console.log(tablink)
})
async function getCurrentTabUrl () {
const tabs = await chrome.tabs.query({ active: true })
return tabs[0].url
}
You'll need to add "permissions": ["tabs"] in your manifest.
For those using the context menu api, the docs are not immediately clear on how to obtain tab information.
chrome.contextMenus.onClicked.addListener(function(info, tab) {
console.log(info);
return console.log(tab);
});
https://developer.chrome.com/extensions/contextMenus
You have to check on this.
HTML
<button id="saveActionId"> Save </button>
manifest.json
"permissions": [
"activeTab",
"tabs"
]
JavaScript
The below code will save all the urls of active window into JSON object as part of button click.
var saveActionButton = document.getElementById('saveActionId');
saveActionButton.addEventListener('click', function() {
myArray = [];
chrome.tabs.query({"currentWindow": true}, //{"windowId": targetWindow.id, "index": tabPosition});
function (array_of_Tabs) { //Tab tab
arrayLength = array_of_Tabs.length;
//alert(arrayLength);
for (var i = 0; i < arrayLength; i++) {
myArray.push(array_of_Tabs[i].url);
}
obj = JSON.parse(JSON.stringify(myArray));
});
}, false);
If you want the full extension that store the URLs that opened or seen by the use via chrome extension:
use this option in your background:
openOptionsPage = function (hash) {
chrome.tabs.query({ url: options_url }, function (tabs) {
if (tabs.length > 0) {
chrome.tabs.update(
tabs[0].id,
{ active: true, highlighted: true, currentWindow: true },
function (current_tab) {
chrome.windows.update(current_tab.windowId, { focused: true });
}
);
} else {
window.addEventListener(hash, function () {
//url hash # has changed
console.log(" //url hash # has changed 3");
});
chrome.tabs.create({
url: hash !== undefined ? options_url + "#" + hash : options_url,
});
}
});
};
you need index.html file also. which you can find in the this Github
the manifest file should be like this:
{
"manifest_version": 2,
"name": "ind count the Open Tabs in browser ",
"version": "0.3.2",
"description": "Show open tabs",
"homepage_url": "https://github.com/sylouuu/chrome-open-tabs",
"browser_action": {},
"content_security_policy": "script-src 'self' https://ajax.googleapis.com https://www.google-analytics.com; object-src 'self'",
"options_page": "options.html",
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"]
}
],
"background": {
"scripts": ["background.js"]
},
"web_accessible_resources": ["img/*.png"],
"permissions": ["tabs", "storage"]
}
The full version of simple app can be found here on this Github:
https://github.com/Farbod29/extract-and-find-the-new-tab-from-the-browser-with-chrome-extention
I have built a chrome extension and I'm getting hit by a race condition that I need help with.
If you see the answer chrome extension: sharing an object between content scripts and background script it tells us that you cannot share a variable between content and background scripts.
My goal is to generate a unique ID per-browser tab and then share it in between the content.js and the background.js. Then I need to use this value in a content injected javascript as explained in this answer: In Chrome extensions, can you force some javascript to be injected before everything?
The only way I have been able to figure out how to do this is by doing the following async code then I just use the tab ID as the unique ID:
content.js
await pingBackground();
async function pingBackground() {
var info;
await new Promise(function (resolve, reject) {
chrome.runtime.sendMessage({ type: 1 }, function (response) {
if (response !== undefined) {
info = response;
resolve();
}
else {
reject();
}
});
});
console.log("Id is " + info.id);
}
background.js
chrome.runtime.onMessage.addListener(messageHandler);
function messageHandler(message, sender, reply) {
switch (message.type) {
case 1:
reply({ 'id': sender['tab'].id, 'active': true });
break;
}
}
manifest.json
{
"name": "oogi",
"version": "0.1",
"manifest_version": 2,
"background": {
"scripts": [
"common.js",
"background.js"
],
"persistent": true
},
"content_scripts": [
{
"matches": ["*://*/*"],
"js": ["content.js"],
"run_at": "document_start"
}
],
"permissions": [
"contentSettings",
"webRequest",
"webRequestBlocking",
"*://*/*"
]
}
But the problem with this is by the time I get the tab ID from background js, the script's content has already been loaded.
Is there some way to make it so this variable can be asynchronously shared between background.js and content.js? Or is this simply impossible?
Can I switch it around and have background.js load a variable from content.js asynchronously?
UPDATE:
A terrible hack which works is to do this in the foreground of the content.js:
var sleepScript = document.createElement('script');
var sleepCode = `function sleep (ms) {
var start = new Date().getTime();
while (new Date() < start + ms) {}
return 0;
}
sleep(500);`;
sleepScript.textContent = sleepCode;
(document.head || document.documentElement).appendChild(sleepScript);
This will force the page to wait for a bit giving the time to query the background before running the inline dom.
It works but that's awful.
This question was already answered previously, although it is hard to tell that this is the same issue at first glance.
https://stackoverflow.com/a/45105934
The answer is pretty descriptive so give it a read.
Here is the script changes that make it work:
// background.js
function addSeedCookie(details) {
details.responseHeaders.push({
name: "Set-Cookie",
value: `tab_id=${details.tabId}; Max-Age=2`
});
return {
responseHeaders: details.responseHeaders
};
}
chrome.webRequest.onHeadersReceived.addListener(
addSeedCookie, {urls: ["<all_urls>"]}, ["blocking", "responseHeaders"]
);
// inject.js
function getCookie(cookie) { // https://stackoverflow.com/a/19971550/934239
return document.cookie.split(';').reduce(function(prev, c) {
var arr = c.split('=');
return (arr[0].trim() === cookie) ? arr[1] : prev;
}, undefined);
}
var tabId = getCookie("tab_id");
I got an chrome extension that works like this:
Background every few seconds checks site x for changes, if they occur background opens new tab with page Y and does some page automation (fills survey in ajax - based page). Page automation is done by content script. Content script needs to ask Background for permission to start working. It does that by sending message and waiting for reply. When content script finishes it's job it sends message to Background with information about success or not. When failure tab should be updated with new link, when succes - tab should be closed. The problem is, sometimes - not always, bacground get's messages twice. Once - when tab fails it's job, and second time just after tab is updated with new link. I wish i could know why...
Here is script:
Background.js listener creation
function create_listerner(){
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
switch(request.greeting){
case "site_opened":
if (sender.tab.id == tabId) {
sendResponse({
farewell : "go"
});
} else {
sendResponse({
farewell : "stop"
});
}
break;
case "imDoneWithSuccess":
//update tab logic
break;
case "imDoneWithFail":
//close tab logic
actNgo(url,arr);
break;
}
});
}
Listener call is made once, when user starts script from context menu: background.js
chrome.contextMenus.create({
title : "Start",
contexts : ["browser_action"],
onclick : function () {
create_listerner();
}
});
tab update looks simple: background.js
function actNgo(url,arr){
chrome.storage.local.set({
array : arr,
ndz : 1
}, function () {
chrome.tabs.update(
tabId,
{
"url" : url,
"active" : false
}, function (tab) {
console.log("tabId "+tabId);
});
});
}
content script is injected into website, site doesn't reload as is ajax based, and even if - messages cannot be send without proper conditions met
content.js
function SR(st) {
var status ;
if(st){
status = "imDoneWithSuccess";
} else {
status = "imDoneWithFail";
}
chrome.runtime.sendMessage({
greeting : status
}, function (response) {
});
}
content script should work only on tabs that bacground.js opens
function askForPermission() {
chrome.runtime.sendMessage({
greeting : "site_opened"
}, function (response) {
if (response.farewell == 'go') {
start();
} else {
console.log("bad tab");
}
});
}
again - there is no possibility for them to fire up by themselves.
Log file looks like this:
Background: tab created;
content script: askForPermission(); <-sends message asking for permission to start
Bacground: go <- allows.
content script: (content script logs, working as intended)
Background listener: imDoneWithFail;
Background update tab;
background listener: imDoneWithFail; <- shouldn't happen, doesn't look like it conten't script even started to work as it didn't ask for permission. Looks like listener is fired up twice...
Background update tab;
content script: askForPermission(); <-sends message asking for permission to start
Bacground: go <- allows.
edit: manifest
{
"name": "Test script",
"version": "0.0.78",
"manifest_version": 2,
"background": {
"scripts": ["src/background/background.js"]
},
"icons": {
"19": "icons/icon19.png"
},
"default_locale": "en",
"options_ui": {
"page": "src/options/index.html",
"chrome_style": true
},
"browser_action": {
"default_icon": "icons/icon19.png",
"default_title": "Test",
"default_popup": "src/browser_action/browser_action.html"
},
"permissions": [
"http://localhost/survey/*",
"storage",
"contextMenus",
"tabs",
"webRequest"
],
"content_scripts": [
{
"matches": [
"http://localhost/survey/*",
"https://localhost/survey/*"
],
"js": [
"js/jquery/jquery.min.js",
"src/inject/content.js"
]
}
]
}
Before someone ask - I tried to use long-live connections, but i had same issue. Why does it happen?
Ok i found a answer, the problem is with function changing url of tab.
function actNgo(url,arr){
chrome.storage.local.set({
array : arr,
ndz : 1
}, function () {
chrome.tabs.update(
tabId,
{
"url" : url,
"active" : false
}, function (tab) {
console.log("tabId "+tabId);
});
});
}
Function, when given same url as current didn't refreshed tab, and content script couldn't fire up as new... Still don't know why listener fired up messages twice, but since my change it has stopped.
I don't know if this fix will help anyone, but i fixed this by first changing tab url to blank, and then to new one.
chrome.tabs.update(
tabId,
{
"url" : "about:blank",
"active" : false
}, function (tab) {
console.log("bcgr: aktualizujStroneIObstaw: pusta: tabId "+tabId);
chrome.tabs.update(
tabId,
{
"url" : url_beta,
"active" : false
}, function (tab) {
console.log("bcgr: aktualizujStroneIObstaw: wlasciwa: tabId "+tabId);
}
);
I'm building a chrome extension which communicates with a nodejs server through websockets. The point of it is to track browsing history with content. It all seems to work, but occasionally (30% of the time) the callback in a function passed to onMessage.addListener doesn't fire correctly. Let me show you the code:
background.js
var socket = io('http://localhost:3000/');
var tabLoad = function (tab) {
socket.emit('page load', tab);
};
var tabUpdate = function (tabid, changeinfo, tab) {
var url = tab.url;
if (url !== undefined && changeinfo.status == "complete") {
tab.user_agent = navigator.userAgent;
tab.description = '';
tab.content = '';
socket.emit('insert', tab);
}
};
socket.on('inserted', function(page){
socket.emit('event', 'Requesting page content\n');
//page = {tab: page, id: docs._id};
chrome.tabs.sendMessage(page.tab_id, {requested: "content", page: page}, function(data) {
socket.emit('content', data);
});
});
try {
chrome.tabs.onCreated.addListener(tabLoad);
chrome.tabs.onUpdated.addListener(tabUpdate);
} catch(e) {
alert('Error in background.js: ' + e.message);
}
content script - public.js
var messageHandler = function(request, sender, sendContent) {
if (request.requested == "content") {
var html = document.getElementsByTagName('html')[0].innerHTML;
var data = {
content: html,
page: request.page
};
sendContent(data);
return true;
}
};
chrome.extension.onMessage.addListener(messageHandler);
The problem is that sometimes data in sendContent is undefined, while sometimes it is alright. Any ideas how to debug this or what i'm doing wrong?
I've tried replacing document.getElementsByTagName('html')[0].innerHTML with a hardcoded 'test' string, but that didn't help.
Pages like youtube/wikipedia seem to never work, while facebook/google works.
Edit: The sendContent callback does fire 100% of the time it's just that the data passed to it is undefined.
Edit: Here's the manifest file
{
"manifest_version": 2,
"name": "Socket test",
"description": "sockets are cool",
"version": "1.0",
"permissions": [
"http://st-api.localhost/",
"http://localhost:3000/",
"tabs",
"background",
"history",
"idle",
"notifications"
],
"content_scripts": [{
"matches": ["*://*/"],
"js": ["public/public.js"]
//"run_at": "document_start"
}],
//"browser_action": {
// "default_icon": "logo.png",
// "default_popup": "index.html"
//},
"background": {
//"page" : "background.html",
"scripts": ["socket-io.js", "background.js"],
"persistent": true
}
}
First off, your understanding that sendContent is executed 100% of the time is wrong.
As established in the comments, the sendMessage callback also gets executed when there was an error; and this error is, in your case, "Receiving end does not exist"
The error lies in your manifest declaration of the content script. A match pattern "*://*/" will only match top-level pages on http and https URIs. I.e. http://example.com/ will match, while http://example.com/test will not.
The easiest fix is "*://*/*", but I would recommend the universal match pattern "<all_urls>".
With that fixed, there are still a couple of improvements to your code.
Replace chrome.extension.onMessage (which is deprecated) and use chrome.runtime.onMessage
Modify the sendMessage part to be more resilient, by checking for chrome.runtime.lastError. Despite the wide permission, Chrome still won't inject any content scripts into some pages (e.g. chrome:// pages, Chrome Web Store)
Make sure you use "run_at" : "document_start" in your content script, to make sure onUpdated with "complete" is not fired before your script is ready.