Chrome Extension page reload - google-chrome-extension

I'm writing a chrome extension and it has both a content.js to manipulate the current tab data as well as a popup that is generated upon execution. However, my problem is that when i make changes to the extension, I have to both reload the extension and refresh the page, otherwise it give me a "Error establishing connection, port does not exist" error. Here's my manifest.json file
{
"manifest_version": 2,
"name": "extension",
"version": "0.0",
"offline_enabled": true,
"content_scripts": [
{
"matches": [
"*://mail.google.com/*"
],
"css": ["css/jquery-ui.css"],
"js": ["js/jquery.js" , "js/jquery-ui.js" , "js/bootstrap.js" , "js/commons.js" , "js/content.js"],
"run_at": "document_end"
}
],
"browser_action": {
"default_title": "chrome",
"default_popup": "html/popup.html"
},
"permissions": [
"tabs",
"https://*.*.*/"
]
}

This is normal behaviour. When you make changes in your extension you have to reload it. But when you reload extension, all existing content script injections lost their connections with background page. More than that, if you try to reconnect on port.onDisconnected.addListener you will get an exception, for security reasons, because new background process will not be able to recognize its previous content script injections.
When I run into this issue I came up with this solution:
reconnect();
function reconnect() {
var port = chrome.runtime.connect({
name: 'injection'
});
port.onMessage.addListener(handleMessage);
port.onDisconnect.addListener(function() {
console.log('bg script disconnected');
setTimeout(function() {
try {
reconnect();
} catch (e) {
location.href = location.href;
}
}, 1000);
});
}
function handleMessage() {
}
I know, this is nothing just automatic page reloading, but there's no other way to reconnect from previous content script injection - updated extension code must be injected again in order to be able to connect to background script.

Related

How to access iframes using scripting API?

I have a chrome extension I need to access all the iframes on the attached page using the scripting API
{
"manifest_version": 3,
"name": "My example extension",
"version": "0.0.1",
"permissions": ["tabs", "scripting"],
"devtools_page": "devtools.html",
"host_permissions": [
"<all_urls>"
]
}
I can run the script on the inspectedWindow easily enough
// devtools.html -> js
chrome.devtools.panels.create("Sample Panel", "icon.png", "/panel.html", panel => {
// code invoked on panel creation
});
// panel.html -> js
chrome.scripting.executeScript({
target: { tabId: chrome.devtools.inspectedWindow.tabId },
injectImmediately: true,
func: () => {
console.log('foo')
}
})
I want this script to run on the page and all children iframes immediately (before anything else is run, just like a content_script).
It seems this only runs on the host page. When I try to query for child iframes, it doesn't seem to find anything
> await chrome.tabs.query({ windowId: chrome.devtools.inspectedWindow.tabId })
// []
Did I miss a permission?

Load content script into all tabs, but only if the content script is not already loaded

Problem
My chrome extension needs to inject the content script into all tabs when switched on and then when any new tabs are loaded etc.
By adding the content script to the manifest file I can satisfy the second requirement of having it loaded in newly loaded tabs.
To make it also inject the content script into tabs as soon as the extension is loaded or refreshed (there is no popup), I am using chrome.scripting.executeScript.
Problem is each time the extension is turned off/on or refreshed, the content script is loaded in again and any DOM manipulation from content script occurs multiple times.
So from reading another post, I liked the idea of sending a message to the content script, if I don't get a response (undefined) then I inject the script into this tab, if I do get a response then do nothing.
In my background logs I get this error Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist. which is what I would expect if the content script did not exist, but even when the content script is clearly being loaded, I still get the same error.
Any help greatly appreciated. And maybe this is not even the best approach for this, I am not sure. Ideally I would also want the extension to immediately stop working when it's turned off, but I haven't got around to this problem yet.
Manifest
{
"name": "Text highlighter",
"description": "Text highlighter",
"version": "1.0.0",
"manifest_version": 3,
"background": {
"service_worker": "background.js"
},
"options_ui": {
"page": "options.html",
"open_in_tab": false
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"],
"css": [ "styles.css" ]
}
],
"permissions": ["storage", "activeTab", "scripting", "tabs"],
"host_permissions": ["<all_urls>"]
}
self.oninstall = () => {
chrome.tabs.query({}, function(tabs) {
tabs.forEach((tab) => {
chrome.tabs.sendMessage(tab.id, true, (response) => {
console.log('response', response);
if(!response) {
chrome.scripting.executeScript({
target: {tabId: tab.id},
files: ['content.js']
});
chrome.scripting.insertCSS({
target: { tabId: tab.id },
files: ['styles.css']
});
}
});
});
});
};
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
console.log('request', request);
sendResponse(true);
return true;
});

Google Chrome Extension Not Working As Expected

So I'm trying my hand at a google chrome extension which in theory should be extremely straight forward. I know there are tons of stack queries and I've tried a few, I've even copied and pasted direct solutions for testing and I have been unsuccessful. So here is the project. When a page loads, check the html content, and change the icon programmatically.
This is my content.js
chrome.tabs.onUpdated.addListener( function (tabId, changeInfo, tab) {
if (changeInfo.status == 'complete' && tab.active) {
var markup = document.documentElement.innerHTML;
var m = markup.indexOf("candy");
if (m > -1) {
chrome.browserAction.setIcon({path: "check.png"});
} else {
chrome.browserAction.setIcon({path: "cross.png"});
}
}
})
This is my manifest.json
{
"manifest_version": 2,
"name": "Tag Analyzer Plugin",
"description": "Check if tag exist on page",
"version": "1.0",
"browser_action": {
"default_icon": "cross.png"
},
"content_scripts": [
{
"matches": [
"<all_urls>"
],
"js": ["content.js"]
}
],
"permissions": ["<all_urls>"]
}
Right now I'm running this item as a content script because as a content script I can use the logic
var markup = document.documentElement.innerHTML;
var m = markup.indexOf("candy");
if (m > -1) {} else {}
However as a content script the chrome API stuff doesn't work. When I run this script as a background script the script works, except that
var markup = document.documentElement.innerHTML;
doesn't return the pages html, it returns the injected script html.
I've read this stack which was informative as to what the difference was and I've read and tested many stacks like here this, without much success. So obviously i'm missing something and doing something wrong. Thank in advanced for any help.
UPDATES:
So I've made some modifications, but it's still not working, though I think that it's closer to working.
As per the comments below I am now using a content script and background script. No errors are being thrown, however the background script isn't doesn't anything.
content.js
var markup = document.documentElement.innerHTML;
var m = markup.indexOf("candy");
if (m > -1) {
chrome.runtime.sendMessage({"found" : true});
} else {
chrome.runtime.sendMessage({"found": false});
}
background.js
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if request.found {
alert("HERE");
chrome.browserAction.setIcon({
path: "check.png",
tabId: sender.tab.id
});
} else {
alert("HERE2");
chrome.browserAction.setIcon({
path: "cross.png",
tabId: sender.tab.id
});
}
});
manifest.json
{
"manifest_version": 2,
"name": "Tag Analyzer Plugin",
"description": "find tag on page",
"version": "1.0",
"browser_action": {
"default_icon": "cross.png"
},
"background": {
"scripts": ["background.js"],
"persistent": false
},
"content_scripts": [
{
"matches": [
"<all_urls>"
],
"js": ["content.js"]
}
],
"permissions": ["<all_urls>"]
}
I've added some alerts on the background.js to see if it was being trigger and nothing, not does the icon change.

How to allow an extension for two domains only?

I've written a google chrome extension. It's okay and works now but I want the extension to be usebale only on two domains because it's written for these two websites only and is useless for others. There is a context menu only. For now it hasn't even popup, or action button in the top right corner (hidden by default). How can achieve this?
My current manifest.json:
{
"manifest_version": 2,
"background": {
"scripts": ["scripts/jquery.min.js", "scripts/background.js"]
},
"name": "Export Entries",
"description": "some descriptions here",
"version": "1.0",
"icons": {
"16": "images/logo.png",
"48": "images/logo.png",
"128": "images/logo.png"
},
"permissions": ["downloads", "tabs", "contextMenus", "http://my-own-domain-accessed-via-ajax-for-creating-some-files-there.com/*"],
"content_scripts": [{
"matches": ["*://allowed-domain1.com/*", "*://allowed-domain2.com/*"],
"css": ["styles/style.css"],
"js": ["scripts/jquery.min.js", "scripts/popup.js", "scripts/background.js"],
"run_at": "document_end"
}],
"web_accessible_resources": [
"images/logo.png"
]
}
As I understand the extension cannot be disabled absolutely, its process will run in background again. But it's not a problem. I just want to not display the context menu item on other websites.
background.js creates the context menu item and handles its click event:
function exportEntries(info, tab) {
if (info['linkUrl'].indexOf('domain1.com/user/') > -1) {
var user = info['linkUrl'].substr('27');
} else {
var user = null; // export all entries from this topic
}
$.ajax({
url: 'http://my-own-domain-which-creates-the-file.eu/exportEntries/create.php',
method: 'POST',
data: {
topic: tab.url,
user: user
}
}).done(function(url) {
forceDownload(url);
});
}
function forceDownload(url) {
var filename = url.replace(/^.*\/|\.[^.]*$/g, '');
chrome.downloads.download({
url: url,
saveAs: true
}, // options array
function(id) {
// callback function
}
);
};
document.addEventListener('DOMContentLoaded', function() {
chrome.contextMenus.create({
'title': 'Export Entries',
'contexts': ['link'],
'onclick': function(info, tab) {
exportEntries(info, tab);
}
});
});
create.php is on my own domain. It just gets the current page's URL and the user's nickname. Then export all entries from the given topic (i.e. page URL) for the given user, creates a file (.txt, .pdf etc.) and sends back url for downloading the file.
popup.html, popup.js, css file and other stuff is not used for now.
Remove all of your content scripts, they're useless, because the chrome.contextMenus API can only be used on the background page.
To limit the context menu entry to certain domains, pecify the documentUrlPatterns key when you create the context menu using the chrome.contextMenus.create:
chrome.contextMenus.create({
'title': 'Export Entries',
'contexts': ['link'],
'onclick': function(info, tab) {
exportEntries(info, tab);
},
'documentUrlPatterns': [
'*://allowed-domain1.com/*',
'*://allowed-domain2.com/*'
]
});
According to the content scripts documentation:
"If you want to inject the code only sometimes, use the permissions field instead, as described in Programmatic injection."
So instead of
"content_scripts": [
{
"matches": [ "http://allowed-domain.com" ]
}
],
use
permissions: [
"tabs", "http://allowed-domain.com"
],

Chrome ext tabs :Port error: Could not establish connection. Receiving end does not exist

i try to connect to just created new tab , my end mission is to update html widget on all open tabs
any way each time i try to connect to tabs via id i fail with error . im using Version 23.0.1271.64 m
var channelTabs = [];
function isInclude(arr,obj) {
return (arr.indexOf(obj) != -1);
}
chrome.tabs.onCreated.addListener(function(tab) {
// add tab when opened
if (channelTabs.indexOf(tab.id) == -1) {
channelTabs.push(tab.id);
}
chrome.windows.getCurrent(function(win)
{
// get an array of the tabs in the window
chrome.tabs.getAllInWindow(win.id, function(tabs)
{
for (i in tabs) // loop over the tabs
{
// if the tab is not the selected one
if(isInclude(channelTabs,tabs[i].id))
{
if(/^(https?|file):/.test(tabs[i].url))
{
console.log('Debug Background sending update to open tab id:'+tabs[i].id);
var port = chrome.tabs.connect(tabs[i].id,{name: "content_tab_request"});
port.postMessage({resp: "tab_update",data:"some string song1"});
}
}
}
});
});
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo,tab) {
// Example: allow http:, https: and file:
if (/^(https?|file):/.test(tab.url)) {
console.log('Debug Background sending onUpdated to open tab id:'+tabId);
var port = chrome.tabs.connect(tabId,{name: "content_tab_request"});
port.postMessage({resp: "tab_update",data:"some string song1"});
}
});
});
but each time it try to chrome.tabs.connect it gives me :
Port error: Could not establish connection. Receiving end does not exist. miscellaneous_bindings:235
chromeHidden.Port.dispatchOnDisconnect miscellaneous_bindings:235
chrome.Event.dispatch_ event_bindings:371
dispatchArgs event_bindings:249
chromeHidden.Event.dispatchEvent
and the content script :
// Register this tab to the background script
var port = chrome.extension.connect({name: "content_request"});
port.postMessage({req: "Hello"});
port.onMessage.addListener(function(msg) {
if (msg.resp == "World")
{
port.postMessage({answer: "good"});
}
else if(msg.answer == "bye")
{
console.log('Debug contentscript.js reciving answer from background msg.answer:'+msg.answer);
}
else
{
console.log('Debug contentscript.js reciving answer from background is wrong:'+msg);
}
if(port.name == "content_tab_request")
{
console.log('Debug contentscript.js reciving request from background Tab function:'+msg);
if(msg.resp=="tab_update ")
{
var data_recive = msg.data;
console.log('Debug contentscript.js reciving request data from background Tab to update page data_recive:'+data_recive);
}
}
});
json file :
{
"background": {
"page": "background.html"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["contentscript.js"],
"run_at": "document_start",
"all_frames": true
}
],
"web_accessible_resources": [
"script_inpage.js"
],
"browser_action": {
"default_icon": "icon19.png",
"default_popup": "popup.html",
"default_title": "Simple test"
},
"content_security_policy": "script-src 'self'; media-src *; object-src 'self'",
"description": "Simple test.",
"icons": {
"128": "icon128.png",
"16": "icon16.png",
"32": "icon32.png",
"48": "icon48.png"
},
"manifest_version": 2,
"minimum_chrome_version": "20",
"name": "Simple test",
"permissions": [
"unlimitedStorage",
"http://*/",
"<all_urls>",
"tabs"
],
"version": "2.6"
}
by the way connection from content script to background working just fine !
Ok, it seems you are trying to synchronize asynchronous events, after simplification of your code i got it running with modifications and achieved your functional requirement of posting message to all tabs when a new tab is created.
Output:
I was able to see this message on all tabs
manifest.json
{
"name":"Sample communication from content to background",
"description":"This is a sample for Simulating communication from content to background",
"manifest_version":2,
"version":"2",
"background":{
"scripts":["background.js"]
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["myscript.js"],
"run_at":"document_start"
}
]
}
"background.js"
chrome.tabs.onCreated.addListener(function(tab) {
chrome.windows.getCurrent(function(win){
chrome.tabs.getAllInWindow(win.id, function(tabs){
for (i=0;i<tabs.length;i++){
console.log(tabs[i]);
chrome.tabs.sendMessage(tabs[i].id,{name: "content_tab_request"});
}
});
});
});
"myscript.js"
chrome.extension.onMessage.addListener(function(msg) {
console.log("Message Recieved "+ msg);
});
Also some chrome extentions may produce this error. i had this problem and the problem was Aside extention (for saving articles to Pocket).
the error was:
Port error: Could not establish connection. Receiving end does not exist. miscellaneous_bindings:235
chromeHidden.Port.dispatchOnDisconnect

Resources