Context menus not working firefox add-on WebExtensions - javascript

I'm trying to add a context menu to my firefox add-on using the WebExtensions API. I need the background script to listen to a click on the menu item and send a message to the content script.
This is what I have:
manifest.json
{
"manifest_version": 2,
"name": "MyExt",
"version": "0.0.1",
"description": "Test extension",
"icons": {
"48": "icons/icon-48.png"
},
"applications": {
"gecko": {
"id": "myext#local",
"strict_min_version": "45.0"
}
},
"permissions": ["contextMenus"],
"background": {
"scripts": ["background-scripts.js"]
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content-script.js"]
}
]
}
background-scripts.js
chrome.contextMenus.create({
id: "clickme",
title: "Click me!",
contexts: ["all"]
});
browser.contextMenus.onClicked.addListener(function(info, tab) {
console.log("Hello World!");
sendMessage(info, tab);
});
function sendMessage(info, tab) {
chrome.tabs.query(
{active: true, currentWindow: true },
function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, "Test message from background script.");
}
);
}
content-script.js
browser.runtime.onMessage.addListener(function(msg) {
console.log(msg);
});
The menu item is being created, but the messages are never displayed (I'm checking both the Web and Browser Console). Since the click event is not working, the message is not sent either.
I'm following this example from MDN, which does not work. It also creates the menu items, but they do nothing, which makes me think that something changed in the API and MDN didn't bother to update the documentation.
Any ideas? Thanks.

Your code works as written:
I strongly suspect that your issue is either of:
You are testing using a version of Firefox prior to Firefox 48. Firefox 48 is in Beta. The contextMenus "Browser compatibility" section clearly states that the first version in which it is functional is Firefox 48. The WebExtensions API is still in development. In general, you should be testing against Firefox Developer Edition, or Firefox Nightly. You can use earlier versions if all the APIs you are using are indicated to be working in an earlier version. However, if you experience problems, you should test with Nightly. I suspect that this is your most likely issue as you indicated that the contextMenus example code was not doing anything.
You have not navigated to an actual webpage. Your content-script.js is only loaded into pages that match one of the supported schemes: that is, "http", "https", "file", "ftp", "app". It is not loaded in about:* pages. If this was your issue, you would have had partial functionality from the contextMenus example code. In addition, using your code, the Browser Console would have, after a delay, generated an error message:
Error: Could not establish connection. Receiving end does not exist.
A note on your code:
Note, your sendMessage() function is, potentially, overly complex. You are searching for the active tab when the tabs.Tab object for the tab in which the context menu item was selected was already passed to your function as one of the arguments. A shorter version could be:
function sendMessage(info, tab) {
chrome.tabs.sendMessage(tab.id, "Test message from background script.");
}
I would be interested to know if you have encountered a situation where you needed to search for the active tab rather than use the tabs.Tab object provided by the context menu listener.

Related

browser.menus.onClicked.addListener not working in Firefox [duplicate]

This question already has answers here:
How to open the correct devtools console to see output from an extension script?
(3 answers)
Closed 12 months ago.
Currently I'm working on my own Firefox extension and I've met the problem with adding listener to an onclick event for a context menu item.
manifest.json
{
"manifest_version": 2,
"name": "My extension name",
"version": "1.0",
"description": "My extension description",
"icons": {
"48": "icons/icon.png"
},
"permissions": ["menus"],
"background": {
"scripts": ["index.js"]
}
}
index.js
browser.menus.create({
id: 'my-ext-item',
title: 'Custom ctx item',
contexts: ['selection']
});
browser.menus.onClicked.addListener(function(info, tab) {
console.log("Clicked!");
});
browser.menus.create() apparently works fine, because the new item appear in my context menu. The problem is with catching a click event - it is never fires.
I've wrote above code according to the MDN Web Docs. I test it on Firefox 97.0.1 x64.
What did I wrong and what should I repair?
PS. I was trying with older browser.contextMenus.create and browser.contextMenus.onClicked.addListener but it didn't work either.
I've found a solution - browser.menus.onClicked.addListener() working properly, just there is a necessity to enable logging in browser settings.
First, go to about:config and find key extensions.logging.enabled and switch it to true. Then, display Browser Console from Menu Bar -> Tools -> Browser Tools -> Browser Console or by shortcut Ctrl+Shift+J.
Be aware that Browser Console is not the same as Firefox Developer
Tools (from F12 or Ctrl+Shitft+I)!
Last, but not least enable Show Content Messages in Browser Console

Chrome Extension - Getting "tab was closed" error on injecting a script

I am writing a chrome extension which detects the type of file being opened and based on that injects a script on the page which does many other things. Here is the part of my code for the background.js which is injecting the script:
chrome.webRequest.onHeadersReceived.addListener(function(details){
console.log("Here: " + details.url + " Tab ID: " + details.tabId);
if(toInject(details))
{
console.log("PDF Detected: " + details.url);
if(some-condition)
{
//some code
}
else
{
chrome.tabs.executeScript(details.tabId, { file: "contentscript.js", runAt: "document_start"}, function(result){
if(chrome.runtime.lastError)
{
console.log(chrome.runtime.lastError.message + " Tab ID: " + details.tabId);
}
});
}
return {
responseHeaders: [{
name: 'X-Content-Type-Options',
value: 'nosniff'
},
{
name: 'X-Frame-Options',
/*
Deny rendering of the obtained data.
Cant use {cancel:true} as we still need the frame to be accessible.
*/
value: 'deny'
}]
};
}
}, {
urls: ['*://*/*'],
types: ['main_frame', 'sub_frame']
}, ['blocking', 'responseHeaders']);
Here is the manifest file:
{
"manifest_version": 2,
"name": "ABCD",
"description": "ABCD",
"version": "1.2",
"icons": {
"16" : "images/16.png",
"32" : "images/32.png",
"48" : "images/48.png",
"128" : "images/128.png"
},
"background": {
"scripts": ["chrome.tabs.executeScriptInFrame.js", "background.js"],
"persistent": true
},
"permissions": [
"webRequest",
"<all_urls>",
"webRequestBlocking",
"tabs",
"nativeMessaging"
],
"web_accessible_resources": [ "getFrameId", "aux.html", "chrome-extension:/*", "images/*.png", "images/*.gif", "style.css"]
}
The problem is that when injecting script the last error part runs and it shows the tab was closed and the script is not injected. If I press enter on the omnibox a several times the script is injected and things work fine. Here is a sample run of events:
Sorry for my naive photo editing :P
There are a few more things we can deduce from this image:
The first thing being loaded in the tab with tab id 86 is something related to my google account. I have logged out and also turned off the prerender feature of chrome.
On pressing enter several times the tab was closed error goes but the script which maintains a chrome.runtime connection with the background.js gets disconnected.
And then finally things work fine.
I have been banging my head around this for days. No other question on SO addresses this problem. Nor anywhere else on the internet as well.
EDIT:
One more thing to note: The sample run shown in the image above is one such. There are many different behaviors. Sometimes 3 enters wouldn't make it work. Sometimes just one will. Is there something wrong because of the custom headers i am sending?
UPDATE #1
One must notice the headers I am returning in OnHeadersReceived. It's being done to stop chrome from rendering the document. But on doing that all the data of the file is dumped on the screen and I don't want that to appear. So i think I need document_start so that I can hide the dumped data before my content script does other things like putting a custom UI on the page.
UPDATE #2
Noticed one more thing. If I open a new tab, and then paste a url there and then press enter the following is the output of the background page on the console.
So I guess, the location of the window is updated at a later time by chrome. Am I right? Any workarounds?
"The tab was closed" error message is a bit misleading, because the tab obviously is not closed. In chrome sources the variable with the string is called kRendererDestroyed. So the error is because the corresponding renderer is being destroyed for some reason.
I was getting the error if the the page opened in tab redirected (thus one renderer destroyed, another one created for the same tab, but different url this time), in this case extension will got tab updates with statuses like:
loading url: 'example.com', here tab is already returned to callbacks etc, but will get the error, if tried to inject script
loading url: 'example.com/other_url'
title: 'some title'
complete
I managed to get around by injecting script only after receiving status: 'complete' (but probably injecting on title should also do)
Did not try with pdfs, but chrome probably will replace renderer for those too like with a redirect. So look more into page statuses and redirects/renderer replaces. Hope this helps anyone stumbling upon this question.
A simple setTimeout call to wait for the page to load worked for me.

chrome extension changing host/domain warning

I am trying to create a chrome extension that will notify me of host/domain changes when I'm testing a specific website. Often links will be present that point towards developer or live environments and I'd like to be warned if I follow one of these links as the websites are often identical.
Edit for clarity: I want it to alert me when a link takes me away from http(s)://example.staging.something.com and ends up on the live site http(s)://www.example.com or the dev site http(s)://example.dev.something.com
So far I have managed to create a script that identifies when I am on a staging url (our test environment) however I've been unable to reverse this logic to give me a warning when I navigate to a url that doesn't contain 'staging'.
My manifest.json
{
"manifest_version": 2,
"name": "A What URL",
"description": "This extension monitors and warns you of domain changes",
"version": "1.0",
"browser_action": {
"default_icon": "icon.png",
"default_popup": "popup.html"
},
"background": { "scripts": ["background.js"],
"persistent": false
},
"permissions": [
"activeTab",
"webNavigation"
]
}
my background.js
chrome.webNavigation.onCommitted.addListener(function(e) {
alert ("you are still on staging!");
}, {url: [{hostContains: 'staging'}]});
I'm sure this is simple but it appears my brain is far simpler!
There are multiple ways to solve your problem.
Use the chrome.webRequest.onBeforeSendHeaders (or .onSendHeaders) event to get notified when a request is sent to your production website, and check whether the Referer header is set to your staging site. This only works if the document referrer is set (this won't be the case if you're navigating from https to http, or if the "noreferrer" referrer policy is set).
In the absence of a referer, use the webNavigation, webRequest and/or tabs APIs to track the navigation state of a page, and do whatever you want when you detect that the transition production -> dev occurs. Implementing this correctly is very difficult.
Here is a sample for the first method:
// background.js
chrome.webRequest.onBeforeSendHeaders.addListener(function(details) {
var referer;
for (var i = 0; i < details.requestHeaders.length; ++i) {
var header = details.requestHeaders[i];
if (header.name.toLowerCase() === 'referer' && header.value) {
referer = header.value;
break;
}
}
if (referer && /^https?:\/\/dev\.example\.com(\/|$)/.test(referer)) {
alert('Navigated from dev to production!');
}
}, {
urls: ['*://production.example.com/*', '*://prod.example.com/*'],
types: ['main_frame', 'sub_frame'] // Track navigations, not img/css/etc.
}, ['requestHeaders']);
Example of manifest.json to test the previous logic:
{
"name": "Detect nav from dev to prod",
"version": "1",
"manifest_version": 2,
"background": {
"scripts": ["background.js"],
"persistent": true
},
"permissions": [
"webRequest",
"*://*/*"
]
}
Unfortunately, you can't "invert" the filter, so you'll have to catch all events and filter in the code.
Here come the Chrome Events. Your code suggests you treat them like DOM events (with the e parameter). Instead, they pass arguments depending on the event in question, sometimes several.
If you look at the documentation, you'll see that the expected callback format is:
function(object details) {...};
Where details will contain, among other things, a url property.
So you'll have:
chrome.webNavigation.onCommitted.addListener(function(details) {
// Regexp matches first "//", followed by any number of non-/, followed by "staging"
if(!details.url.match(/[^\/]*\/\/[^\/]*staging/)) {
alert ("This is no longer staging!");
}
});
Note that this is going to be extremely annoying unless you can turn it off - it will match almost any page, after all.
Adding my final solution as an answer as requested in case anyone else is interested in the future.
Many thanks to both Xan and Rob with the help! If I could tick both I would but in the end I ended up ticking the one that led to my implementation.
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
var queryInfo = {
active: true,
currentWindow: true
};
chrome.tabs.query(queryInfo, function(tabs) {
var tab = tabs[0];
var url = tab.url;
if(!url.match(/[^\/]*\/\/[^\/]*staging/) && changeInfo.status=="complete"){
alert ("WTF!!! " +url);
}
});
});

What is causing my chrome extension's background.js to freeze up and not respond to messages

Every once in a while my chrome extension's background.js page freezes, i have no idea what is causing it.
When the background.js file has frozen, it no longer responds to messages from the content script, and when I try to open the background page via the extensions manager to inspect it, the window pops up but it stays blank, and no interface appears.
The only things im doing in the background page are message passing and retrieving localstorage variables.
I cant figure out what is causing this, the bug only seems to have happened since i transitioned to the new chrome.runtime api, from the chrome.extension api
Can anyone tell me what is going wrong here? or help me figure it out? Thanks!
Heres the background.js file's code in its entirety
if (!chrome.runtime) {
// Chrome 20-21
chrome.runtime = chrome.extension;
} else if(!chrome.runtime.onMessage) {
// Chrome 22-25
chrome.runtime.onMessage = chrome.extension.onMessage;
chrome.runtime.sendMessage = chrome.extension.sendMessage;
}
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if (request.method == "getLocalStorage")
sendResponse({data: localStorage[request.key]}); // decodeURIComponent
else if (request.method == "setLocalStorage")
sendResponse({data: localStorage[request.key]=request.value});
else
sendResponse({}); // send empty response
});
Is it possible a deadlock situation is occurring that is freezing the page? It doesnt cause the CPU to go mad, so im guessing its not an endless loop.
Update
here is the manifest.json as requested
{
"manifest_version": 2,
"content_scripts": [ {
"exclude_globs": [ "http://*.facebook.com/ajax/*", "https://*.facebook.com/ajax/*" , "http://www.facebook.com/ai.php?*", "https://www.facebook.com/ai.php?*", "http://www.facebook.com/ajax/*", "https://www.facebook.com/ajax/*"],
"include_globs": [ "http://*.facebook.com/*", "https://*.facebook.com/*" ],
"js": [ "script.js" ],
"matches": [ "http://*.facebook.com/*", "https://*.facebook.com/*" ],
"run_at": "document_start"
} ],
"converted_from_user_script": true,
"background": {"scripts": ["background.js"],
"persistent": false},
"icons": {
"128": "ET-128x128.png",
"48": "ET-48x48.png"
},
"key": "xxxxxxxxxxxxxxxxxxxx",
"name": "Extension Test",
"short_name": "ET",
"description": "ET Does this and that, but doesnt phone home",
"version": "999",
"homepage_url": "http://www.etphonehome.com"
}
Only disabling and re-enabling the extension get it to start working again, once the background page has frozen
Below is a screenshot of the frozen background page inspection window:
The localStorage API is problematic because in chrome it is a synchronous API to an inherently asynchronous operation (called from a renderer process, which must then communicate with the browser process that reads from / writes to a backing store in the filesystem and possibly replies back to the renderer process). While it should not in theory be possible to cause deadlocks from webpage or extension code, it's possible there are bugs in chrome's implementation.
One thing you might try is switching from localStorage to chrome.storage.local. It is a little more work to use since it has an asynchronous API, but does not suffer from the same implementation complexity as localStorage.
E.g.
sendResponse({data: localStorage[request.key]});
becomes
chrome.storage.local.get(request.key, function(storageResult) {
sendResponse(storageResult[request.key]);
});

Chrome extension not accessing target page DOM

After reviewing the suggested responses, I was not able to resolve the following issue:
My javascript is not accessing the page DOM, but it is running.
Manifest.json
{
"name": "Clicky",
"version": "1.0",
"background": { "scripts": ["jquery.js", "clickclickboom.js"] },
"permissions": [
"tabs", "http://*/*"
],
"browser_action": {
"name": "Find all links",
"icons": ["icon.jpg"]
},
"manifest_version": 2
}
clickclickboom.js
alert("script runs");
function clicky() {
alert ("clicky got called");
jQuery(".testClass").find("a");
}
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.tabs.executeScript(
null, {code: clicky()});
});
Both alerts pop up but when I debug, I see the extension accessing the background.html DOM but not the targeted pages' DOM.
Any help would be greatly appreciated, thank you in advance!
In your chrome.tabs.executeScript(), the code property must be a string containing the code.
What's happening currently with your code is: clicky() is executed, which returns undefined, which basically is making your executeScript a no-operation call...
One thing you can do is use clicky.toString() though I don't personally recommend it! In this case, the alert ("clicky got called") may work (if you are lucky). The next line will not work because jQuery is not defined in the content script. Your inclusion of jquery is limited to background page.
From your code, it seems you are not really familiar with chrome extension's architecture, so I suggest you start with http://code.google.com/chrome/extensions/getstarted.html

Categories