I am trying to develop a Chrome extension to open Office documents stored in Confluence in a new tab using the IE Tab extension.
In the 'View Page Attachment' screen, there is an 'Edit in Office' link for Office file attachments. The link has a click event that creates a new instance of a URLLauncher, which is used to open the document. This feature is not supported in Chrome, so I want to add my own URLLauncher prototype into the web page to make it work.
In short, this is my idea:
Create a Chrome extension with a content script that injects a URLLauncher prototype into the 'View Page Attachment' page (I don't know if this is the right approach, so I am open to suggestions).
When the user clicks on the 'Edit in Office' link, the URLLauncher.open method opens the file attachment in a new tab by calling the IE Tab extension.
I can see the 'Hi there!' alert every time I load a web page, and that confirms that content.js is being injected. Nevertheless, the URLLauncher is not available in the web page. I think this is because the global window object of the content script is distinct from the page/extension's global namespace (i.e., window.URLLauncher is undefined). How could I reorganize my code to overcome this obstacle?
These are my files:
manifest.json
{
"manifest_version": 2,
"background": {
"scripts": [
"background.js"
]
},
"content_scripts": [ {
"js": [ "content.js" ],
"matches": [ "<all_urls>" ]
} ],
"description": "This is a test extension",
"permissions": [
"tabs", "http://*/*", "https://*/*"
],
"name": "Test extension",
"version": "1.0.0"
}
background.js
chrome.tabs.executeScript(null, {
code: "document.body.appendChild(document.createElement('script')).src='" +
chrome.extension.getURL("content.js") + "';"
}, null);
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
console.log(sender.tab ?
"from a content script:" + sender.tab.url :
"from the extension");
if (request.id == "doUrlLaunch") {
chrome.tabs.create({ url: request.nUrl, selected: true });
sendResponse({result: "goodbye"});
}
}
);
content.js
var prefixUrl = 'chrome-extension://hehijbfgiekmjfkfjpbkbammjbdenadd/iecontainer.html#url=';
alert('Hi there!');
function URLLauncher() {
}
URLLauncher.prototype = {
open : function(urlStr) {
var newUrl = prefixUrl + 'https://host.com' + encodeURI(urlStr);
chrome.runtime.sendMessage({id: "doUrlLaunch", nUrl: newUrl}, function(response) {
});
}
}
Thanks in advance.
UPDATE 1
I edited the files following the instructions given by Rob W and this page ('Message Passing'); now the code is injected in the page itself, but a major problem still remains. The actual JS code sends a message to the content script, but the message is not caught by the listener, so the new tab is not created and the callback function does not receive a response; the error message I got: Error in event handler for (unknown): TypeError: Cannot read property 'success' of undefined.
These are the updated files:
manifest.json
{
"manifest_version": 2,
"content_scripts": [ {
"js": [ "content.js" ],
"matches": [ "<all_urls>" ]
} ],
"web_accessible_resources": [ "script.js" ],
"description": "This is a test extension",
"permissions": [
"tabs", "http://*/*", "https://*/*"
],
"name": "Test extension",
"version": "1.0.0",
"externally_connectable": {
"ids": ["*"],
"matches": ["*://*.hostname.com/*"]
}
}
content.js
var s = document.createElement('script');
s.src = chrome.extension.getURL("script.js");
s.onload = function() {
this.parentNode.removeChild(this);
};
(document.head||document.documentElement).appendChild(s);
chrome.runtime.onMessage.addListener(
//Unreachable code!
function(request, sender, sendResponse) {
console.log(sender.tab ?
"from a content script:" + sender.tab.url :
"from the extension");
if (request.id == "doUrlLaunch") {
chrome.tabs.create({ url: request.nUrl, selected: true });
sendResponse({result: "goodbye"});
}
}
);
script.js
var prefixUrl = 'chrome-extension://hehijbfgiekmjfkfjpbkbammjbdenadd/iecontainer.html#url=';
function URLLauncher() {
}
URLLauncher.prototype = {
open : function(urlStr) {
var newUrl = prefixUrl + 'https://hostname.com' + encodeURI(urlStr);
chrome.runtime.sendMessage({id: "doUrlLaunch", nUrl: newUrl}, function(response) {
if (!response.success)
console.log('Something went wrong...');
});
}
}
Not sure if you're still interested in an answer, but in your edited files your problem is where your listener sits.
chrome.runtime.sendMessage does not reach content scripts; it is intended for extension pages. Message passing to content scripts works through chrome.tabs.sendMessage, but this is irrelevant for this task.
Content scripts can't call chrome.tabs as they do not have the required API access.
A solution would be to call a background script, that can receive those messages and can call the required API.
Therefore, you need a third script, taking out this code from content.js:
// background.js
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
console.log(sender.tab ?
"from a content script:" + sender.tab.url :
"from the extension");
if (request.id == "doUrlLaunch") {
chrome.tabs.create({ url: request.nUrl, selected: true });
sendResponse({result: "goodbye"});
}
}
);
And modify your manifest:
"background": { "scripts": [ "background.js" ] },
Related
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.
I'm building an google extension that inserts html in page and shows a menu on browser action icon click and I don't find way to keep my extension open when I reload the page. So on every reload, we have to active it again from Browser Action icon.
Below the manifest file
{
"background": {
"scripts": ["background.js"]
},
"browser_action": {
"default_icon": "img/icone.png",
"default_title": "show menu"
},
"icons" : {
"128" : "img/icone_128.png",
"48" : "img/icone_48.png",
"32" : "img/icone_32.png",
"24" : "img/icone_24.png",
"16" : "img/icone_16.png"
},
"manifest_version": 2,
"name": "p|layer",
"version": "1.0.4",
"content_scripts": [
{
"matches": [ "<all_urls>"],
"css":["css/grid.css", "css/font-awesome.min.css"],
"js":["js/jquery-1.11.1.min.js","js/jquery-ui.js", "js/jquery.nicefileinput.min.js"]
}
],
"web_accessible_resources": [
"fonts/fontawesome-webfont.woff"
],
"permissions": [ "activeTab"]
}
script (background.js) injecting contentscript
chrome.browserAction.onClicked.addListener(function (tab) { //Fired when User Clicks ICON
chrome.tabs.executeScript(tab.id,
{"file": "js/contentscript.js"},
function () { // Execute your code
console.log("Script Executed .. "); // Notification on Completion
});
chrome.tabs.insertCSS(tab.id, {file: "css/grid.css"});
chrome.tabs.insertCSS(tab.id, {file: "css/font-awesome.min.css"});
chrome.tabs.insertCSS(tab.id, {file: "css/slider.css"});
});
any help will be appreciated
So, from the comments the problem was inferred: your button click activates your content script, but a page reload clears it. Assuming you want the button click to act as a toggle:
1) Always inject the content script / CSS, but don't show the UI immediately:
"content_scripts": [
{
"matches": [ "<all_urls>"],
"css": ["css/grid.css", "css/font-awesome.min.css", "css/slider.css"],
"js": [
"js/jquery-1.11.1.min.js", "js/jquery-ui.js",
"js/jquery.nicefileinput.min.js", "js/contentscript.js"
]
}
],
2) Keep track of "activated" tabs:
var activeTabs = {};
chrome.browserAction.onClicked.addListener( function(tab){
var active;
if(activeTabs[tab.id]){
delete activeTabs[tab.id];
active = false;
} else {
activeTabs[tab.id] = true;
active = true;
}
/* (part 3) */
});
chrome.tabs.onRemoved.addListener( function(tabId){
delete activeTabs[tabId];
});
chrome.tabs.onReplaced.addListener( function(newTabId, oldTabId){
if(activeTabs[oldTabId]) activeTabs[newTabId] = true;
delete activeTabs[oldTabId];
});
3) Use messaging to show/hide UI:
Content script:
chrome.runtime.onMessage.addListener( function(message. sender, sendResponse){
if(message.showUI) showUI();
if(message.hideUI) hideUI();
});
Background script:
chrome.browserAction.onClicked.addListener( function (tab) {
var active;
/* (part 2) */
if(active) {
chrome.tabs.sendMessage(tab.id, {showUI: true});
} else {
chrome.tabs.sendMessage(tab.id, {hideUI: true});
}
});
Additional robustness can be added for the cases of extension reload, but that's the gist of it.
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"
],
I just updated my chrome extension to json version 2, and am trying to get my extension to work again. The problem is sendRequest was depreciated along the way. So I copy the code from https://developer.chrome.com/extensions/messaging.html into my script and modify it to my own variable names, and it doesn't work.
So then I go back and put in the original code and it still doesn't work. I have read multiple questions that are similar [and hopefully this won't get closed as a duplicate, because none of them were the same as my situation].
manifest.json:
{
"background": {
"page": "background.html"
},
... ... ...
"content_scripts": [ {
"css": [ "style.css" ],
"js": [ "jq.js", "script.js" ],
"matches": [ "http://*.craigslist.org/*/*.htm*" ]
} ],
... ... ...
"permissions": [ "tabs", "http://*.craigslist.org/*/*.htm*" ],
"manifest_version": 2,
"update_url": "http://clients2.google.com/service/update2/crx",
"version": "3.0"
}
background.html:
<html>
<script type='text/javascript'>
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
console.log(sender.tab ?
"from a content script:" + sender.tab.url :
"from the extension");
if (request.greeting == "hello")
sendResponse({farewell: "goodbye"});
});
});
</script>
</html>
script.js:
chrome.runtime.sendMessage({greeting: "hello"}, function(response) {
console.log(response.farewell);
});
Now I run a page [on craigslist], and go to the Console and this is the error:
Port error: Could not establish connection. Receiving end does not exist.
TypeError: Cannot read property 'farewell' of undefined
at chrome-extension://dhmjefbokfkjpdbigkadjpgjeflchgea/script.js:9:23
I use Chrome Beta on Ubuntu 12.10 64-bit (Google Chrome: 27.0.1453.15 (Official Build 191758) beta)
You are sending messages from both your background and your content script, but not trying to receive them at all. Try listening for messages in one or both of those places. Also, inline code is against the CSP so move it all to an external file.
For example:
manifest.json
"background": {
"scripts": ["background.js"]
},
background.js
chrome.runtime.onMessage.addListener(function(message,sender,sendResponse){
sendResponse({farewell:"goodbye"});
});
script.js
chrome.runtime.sendMessage({greeting: "hello"}, function(response) {
console.log(response.farewell);
});
Also, chrome.tabs.getSelected() has been deprecated as well, so use chrome.tabs.query() instead.
background script
chrome.tabs.getAllInWindow(null, function(tabs) {
$.each(tabs, function() {
chrome.tabs.sendRequest(this.id, {"action":"action_name"} );
});
});
content script
chrome.extension.onRequest.addListener(function(request, sender, sendResponse){
if(request.action === 'action_name')
{
alert('handle event in the content script!!!!')
}
});
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