I'm working on building an chrome extension that communicates with an external SignalR HUB on server side.
I've managed to configure both to communicate with one another if I write the JS code in the pop-up page, the problem with this method is that if the extension is not opened, then it can't get updates (doesn't respond to the raised event).
In order to solve it I read that I should write my event handlers in the background.js, so even if the extension pop up is not showing - it would still respond to the event. However, I still need to support a button click on my extension to fire an event - which after reading a bit more is not possible cause I don't have access to the DOM in a background file.
So my question is how do I tackle this issue? how can I, from the client side call a function (or make ajax requests) in the server SignalR HUB? and also receive a response from the server when the popup is not opened.
By response I mean a to update values in the background.js and a simple +1 to the value in the icon badge, Something that will notify the user that something happened.
I'm very new to chrome extensions so I would appreciate the help!
This is my current code:
manifest.json
{
"manifest_version": 2,
"name": "Getting started ex1ample",
"description": "This extension shows a Google Image search result for the current page",
"version": "1.0",
"background": {
"scripts": ["jquery-2.1.4.min.js", "jquery.signalR-2.2.0.min.js", "background.js"],
"persistent": false
},
"browser_action": {
"default_icon": "icon.png",
"default_popup": "popup.html"
},
"permissions": [
"activeTab",
"http://localhost:61275/"
]
}
background.js
var singalR = {};
$(document).ready(function(){
singalR.connection = $.hubConnection('http://localhost:61275/signalr/hubs', {useDefaultPath: false});
singalR.connection.logging = true;
singalR.roomIndexHubProxy = singalR.connection.createHubProxy('roomIndexHub');
singalR.connection.start().done(function() {
// Wire up Send button to call RoomIndexHubProxy on the server.
console.log('Hub has started');
$("#btn-join").click(function(){
singalR.roomIndexHubProxy.invoke('test', 111);
});
});
singalR.connection.error(function (error) {
console.log('SignalR error: ' + error)
});
singalR.roomIndexHubProxy.on('test', function (val) {
chrome.browserAction.setBadgeText({ text: val } );
});
});
popup.html
<!doctype html>
<html>
<head>
<script src="popup.js"></script> //empty for now
</head>
<body>
<button id="btn-join">Join</button>
</body>
</html>
Server Side HUB
public class RoomIndexHub : Hub
{
static int val = 0;
public RoomIndexHub(){
}
public async Task test(int k)
{
var hubContext = GlobalHost.ConnectionManager.GetHubContext<RoomIndexHub>();
val++;
await Task.Delay(4000);
hubContext.Clients.All.test(val.ToString());
}
}
Have you tried setting the lifetime of your background page? Set persistence to true so this hidden page in background will "live" all the time and is able to listen to incoming requests.
"background": {
"scripts": ["background.js"],
"persistent": true
},
This will give you a persistent background page.
Check this: Chrome documentation for event pages
Reards
Carsten
Related
I've taken a look at other related SO posts and the solutions haven't helped solve my issue. This is my first chrome extension, so please bear with me!
I'm writing a simple chrome extension that searches for user provided keywords on a webpage. I can't get the content script that returns the DOM content to run. Some of the code, I've taken from an answer in another SO post, but I can't seem to get it to work for me.
I put a console.log("hello world") at the top of the file, and it doesn't show up, so I think it might be the structure of my project.
manifest.json
{
"name": "keyword search",
"version": "0.0.1",
"manifest_version": 2,
"permissions": [ "tabs" , "storage", "activeTab", "<all_urls>"],
"browser_action": {
"default_popup": "html/form.html"
},
"content_scripts": [{
"matches": [ "<all_urls>" ],
"js": [ "js/jquery.min.js", "content_scripts/content_script.js" ]
}],
"homepage_url": "http://google.com/"
}
js/popup.js
function run() {
running = true;
console.log('running');
var url = "https://www.stackoverflow.com/"
// Get KW & category for search
chrome.storage.local.get(["kw"],
function (data) {
kw = data.kw;
console.log("redirecting to find kw: " + kw);
// Send current tab to url
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.update(tabs[0].id, {url: url});
chrome.tabs.sendMessage(tabs[0].id, {type: 'DOM_request'}, searchDOM);
});
}
);
}
function searchDOM(domContent) {
console.log("beginning dom search \n" + domContent);
}
content_scripts/content_script.js
// Listen for messages
console.log("hello world")
chrome.runtime.onMessageExternal.addListener(function (msg, sender, sendResponse) {
// If the received message has the expected format...
if (msg.type === 'DOM_request') {
// Call the specified callback, passing
// the web-page's DOM content as argument
sendResponse(document.all[0].outerHTML);
}
});
console
running
redirecting to find kw: TestKeyword
beginning dom search
undefined
First, onMessageExternal is the wrong event (it's for external messaging):
you should use the standard onMessage.
Second, chrome extensions API is asynchronous so it only registers a job, returns immediately to continue to the next statement in your code without waiting for the job to complete:
chrome.tabs.update enqueues a navigation to a new URL
chrome.tabs.sendMessage enqueues a message sending job
the current page context in the tab gets destroyed along with the running content scripts
the tab starts loading the new URL
the message is delivered into the tab but there are no listeners,
but this step may instead run right after step 2 depending on various factors so the content script running in the old page will receive it which is not what you want
the tab loads the served HTML and emits a DOMContentLoaded event
your content scripts run shortly after that because of the default "run_at": "document_idle"
There are at least three methods to properly time it all:
make your content script emit a message and add an onMessage listener in the popup
use chrome.tabs.onUpdated to wait for the tab to load
use chrome.tabs.onUpdated + chrome.tabs.executeScript to simplify the entire thing
Let's take the executeScript approach.
remove "content_scripts" from manifest.json
instead of chrome.tabs.query (it's not needed) use the following:
chrome.tabs.update({url}, tab => {
chrome.tabs.onUpdated.addListener(function onUpdated(tabId, change, updatedTab) {
if (tabId === tab.id && change.status === 'complete') {
chrome.tabs.onUpdated.removeListener(onUpdated);
chrome.tabs.executeScript(tab.id, {
code: 'document.documentElement.innerHTML',
}, results => {
searchDOM(results[0]);
});
}
});
});
Is there a way to only make my OWN browser (Chrome) not be able to go back / forward / refresh?
This happens rather often that when Im developing and playing around in devtools (Changing HTML and CSS just to try things out) I sometimes accidentally swipe back or out of habit hit refresh. I would like to be able to disable the back or forward button via some sort of extension?
I am NOT trying to disable the button on any live-website, just for me locally. Any ideas?
If you want to prevent accidental navigations, there's no need to install any extension. Just open the console, and run the following code:
window.onbeforeunload = function() {
return 'Want to unload?';
};
With this code, you will get a confirmation prompt.
If you really want to prevent the page from unloading via an extension, use the technique described in the answers to How to cancel webRequest silently in chrome extension.
Here's a minimal demo extension that adds a button to your browser. Upon click, you cannot navigate to a different page any more. You can still close the tab without any warning, though:
// background.js
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.webRequest.onBeforeRequest.addListener(function(details) {
var scheme = /^https/.test(details.url) ? 'https' : 'http';
return { redirectUrl: scheme + '://robwu.nl/204' };
// Or (seems to work now, but future support not guaranteed):
// return { redirectUrl: 'javascript:' };
}, {
urls: ['*://*/*'],
types: ['main_frame'],
tabId: tab.id
}, ['blocking']);
});
manifest.json for this extension:
{
"name": "Never unload the current page any more!",
"version": "1",
"manifest_version": 2,
"background": {
"scripts": ["background.js"],
"persistent": true
},
"browser_action": {
"default_title": ""
},
"permissions": [
"<all_urls>",
"webRequest",
"webRequestBlocking"
]
}
I'm writing my first Chrome Extension. I've used permission, but I'm seeing my button everywhere.
How can I only show the button on the addresses I'm writing the extension for?
Although the answer from #Sorter works, it is not the best way to solve the problem.
First and foremost, it does not always work. If the page used history.pushState, the page action will disappear and not come back until you trigger the onUpdated or onHighlighted event again Chromium issue 231075.
Secondly, the method is inefficient, because it's triggered for every update of tab state on all pages.
The most efficient and reliable way to get a page action to appear on certain domains is to use the declarativeContent API. This is only available since Chrome 33. Before that, the webNavigation API was the most suitable API. The advantage of these API over the method using the tabs API is that you can safely use event pages, because you can declare URL filters. With these URL filters, the events will only be triggered if you navigate to a page that matches the URL filters. Consequently, your extension/event page will not be activated until really needed (= no wasted RAM or CPU).
Here's a minimal example (background.js) using the webNavigation API:
function onWebNav(details) {
if (details.frameId === 0) {
// Top-level frame
chrome.pageAction.show(details.tabId);
}
}
var filter = {
url: [{
hostEquals: 'example.com'
}]
};
chrome.webNavigation.onCommitted.addListener(onWebNav, filter);
chrome.webNavigation.onHistoryStateUpdated.addListener(onWebNav, filter);
manifest.json:
{
"name": "Name ",
"version": "1",
"manifest_version": 2,
"background": {
"scripts": ["background.js"],
"persistent": false
},
"page_action": {
"default_title": "Only visible on stackoverflow.com"
},
"permissions": [
"webNavigation"
]
}
If you target Chrome 33 and higher, then you can also use the declarativeContent API instead. Simply replace the "webNavigation" permission with "declarativeContent", and use the following background script (background.js):
chrome.runtime.onInstalled.addListener(function() {
chrome.declarativeContent.onPageChanged.removeRules(undefined, function() {
chrome.declarativeContent.onPageChanged.addRules([{
conditions: [
new chrome.declarativeContent.PageStateMatcher({
pageUrl: {
hostEquals: 'example.com'
}
})
],
actions: [new chrome.declarativeContent.ShowPageAction()]
}]);
});
});
In both examples, I used a UrlFilter that matches the example.com domain.
Create background.js which checks for updated and highlighted tab.
function checkForValidUrl(tabId, changeInfo, tab) {
// If 'example.com' is the hostname for the tabs url.
var a = document.createElement ('a');
a.href = tab.url;
if (a.hostname == "example.com") {
// ... show the page action.
chrome.pageAction.show(tabId);
}
};
// Listen for any changes to the URL of any tab.
chrome.tabs.onUpdated.addListener(checkForValidUrl);
//For highlighted tab as well
chrome.tabs.onHighlighted.addListener(checkForValidUrl);
Create popup.html and popup.js in the similar manner.
You can use the variables defined in background.js in content scripts (popup.js) with
chrome.extension.getBackgroundPage().variableName
Here's the example extention download link.
For your reference and ease, here's the sample manifest.json file
{
"manifest_version": 2,
"name": "Example Extension",
"version": "1.0",
"background": {
"scripts": ["background.js"]
},
"page_action":{
"default_icon": "images/icon_16.png",
"default_popup": "popup.html",
"default_title": "Title for the extension"
},
"permissions": [
"tabs"
]
}
An Updated Way:
I use the following with great success:
chrome.tabs.onUpdated.addListener(function(tabId, info, tab) {
var url = info.url || tab.url;
if(url && url.indexOf('example.com') > -1)
chrome.pageAction.show(tabId);
else
chrome.pageAction.hide(tabId);
});
UPDATE
From what I can tell, it is impossible to send a message from the background script to the content script using the "sendMessage" function. However there is a horrible workaround,
In your content script's window.onload, send a message to the background script:
chrome.runtime.sendMessage( { action: "messaging", window: "app" }, this.listenForFutureMessages );
Also in the content script, have the following function:
listenForFutureMessages: function(someAction)
{
//Take some action based on the message
//If we want the background script to be able to contact
//us again, we need to give them another callback. This
//is because Chrome only allows one use per callback
chrome.runtime.sendMessage( { action: "messaging", window: "app" }, this.listenForFutureMessages );
},
In the background script, have a listener that does something like this:
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse)
{
if ( request.action === "messaging" )
{
//Save the callback for later
this.listeners[ request.window ] = sendResponse;
//Tell chrome we will be using the callback later
return true;
}
}
);
When your background script wants to send the content script a message, simply call it like this:
this.listeners[ "app" ]( { someProperty: "some value" } );
This is a stupid way to do this, but it makes this actually possible. Hope this helps anyone else who needs this functionality.
ORIGINAL
I'm unable to send a message from my background script to a content script. When I try to find the tab id, it tells me I don't have permissions even though my app has that permission. And when I receive a message from the content script, and print out the sender object, it shows tab.id = -1. The API to send a message to a content script requires a tab id!
chrome.tabs.sendMessage(integer tabId, any message, function responseCallback)
The error:
chrome.tabs is not available: You do not have permission to access this API. Ensure that the required permission or manifest property is included in your manifest.json.
Error in event handler for 'undefined': Cannot call method 'sendMessage' of undefined TypeError: Cannot call method 'sendMessage' of undefined
at chrome-extension://panoaieakcofaegcjfbmhndaekfgpijh/scripts/background.js:109:16
at Event.dispatchToListener (event_bindings:356:21)
at Event.dispatch_ (event_bindings:342:27)
at Event.dispatch (event_bindings:362:17)
at miscellaneous_bindings:167:33
at Event.dispatchToListener (event_bindings:356:21)
at Event.dispatch_ (event_bindings:342:27)
at Event.dispatch (event_bindings:362:17)
at Object.chromeHidden.Port.dispatchOnMessage (miscellaneous_bindings:253:22)
So how do I contact my content script? (I have multiple windows and need to be able to contact them individually)
My manifest:
{
"manifest_version": 2,
"name": "App",
"description": "App",
"version": "0.75",
"minimum_chrome_version": "27",
"offline_enabled": true,
"icons":
{
"16": "images/icon16.png",
"48": "images/icon48.png",
"128": "images/icon128.png"
},
"app":
{
"background":
{
"scripts":
[
"scripts/background.js"
]
}
},
"permissions":
[
"unlimitedStorage",
"fullscreen",
{
"fileSystem":
[
"write"
]
},
"background",
"<all_urls>",
"tabs"
],
"update_url": "http://192.168.1.121/app.xml"
}
There's not such a thing called "Content scripts" in a Chrome app. Your manifest file looks like a mixture of a Chrome extension. Open chrome://extensions/, enable developer mode, and you would see a warning that the "background" and "tabs" permissions are invalid for a Chrome app.
If you're implementing a Chrome app, just use chrome.runtime.sendMessage and chrome.runtime.onMessage. These messages can be send from and to your event page and the main page. For example:
// event page (aka background page)
chrome.app.runtime.onLaunched.addListener(function() {
chrome.app.window.create('main.html');
});
// Later, when you want to notify the app window
chrome.runtime.sendMessage(" ... any message ... ");
<!-- main.html -->
<script src="main.js"></script>
// main.js
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
// Do something with the message
});
I'm a newbie at Chrome extensions, and of course I stuck on every step, but this is specially hard. Maybe it is a silly mistake, but here is what I am trying to do:
Send a simple message from the content script to the background page and handle it as a variable. So I have this in my content script:
$(document).ready(function() {
var d = document.domain;
chrome.extension.sendMessage({dom: d});
});
And in my background script this:
chrome.extension.onMessage.addListener(function(request) {
alert(request.dom);
});
So, the alert works fine. But it "goes" to the page I am browing and not the HTML extension, this means, instead of poping up when clicking on my extension button, it will appear as it was coded into the content script when the page loads.
Please, any help would be appreciated.
My Demo extension is as follows
Files & Roles
a) manifest.json (Documentation)
b) myscript.js (Content Script See Documentation)
c) background.js (Background HTML File See Documentation)
d) popup.html (Browser Action Popup See Documentation)
e) popup.js (Receptor of Modified value from Background Page)
manifest.json
Registered all files to manifest(Viz background,popup,content scripts) with permissions
{
"name":"Communication Demo",
"description":"This demonstrates modes of communication",
"manifest_version":2,
"version":"1",
"permissions":["<all_urls>"],
"background":{
"scripts":["background.js"]
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["myscript.js"]
}
],
"browser_action":{
"default_icon":"screen.png",
"default_popup":"popup.html"
}
}
myscript.js
Used sendMessage() API for communicating with background page
var d = document.domain;
chrome.extension.sendMessage({
dom: d
});
background.js
Added Event Listeners for Content and popup.js using onMessage() and onConnect() Listeners
var modifiedDom;
chrome.extension.onMessage.addListener(function (request) {
modifiedDom = request.dom + "Trivial Info Appending";
});
chrome.extension.onConnect.addListener(function (port) {
port.onMessage.addListener(function (message) {
if (message == "Request Modified Value") {
port.postMessage(modifiedDom);
}
});
});
popup.html
Sample browser action HTML Page registering popup.js to avoid Inline Scripting
<!doctype html>
<html>
<head>
<script src="popup.js"></script>
</head>
<body></body>
</html>
popup.js
Used Port\Long Lived Connection for communicating with background page for fetching results
var port = chrome.extension.connect({
name: "Sample Communication"
});
port.postMessage("Request Modified Value");
port.onMessage.addListener(function (msg) {
console.log("Modified Value recieved is " + msg);
});
Hope this helps, let me know if you need more information