Chrome Extension Popup Action not Working - javascript

I am working on an extension for Google Chrome. This allows our users to submit support tickets from our online product directly from the page they are on.
I have been using page rules to limit the extension to only our products pages. Some of our users are experiencing an issue were they popup no longer works.
This is what some of our users are seeing when they are one our products pages. Also the extensions icon is in full colour when the user sees this. Not sure if that is useful information.
I am having trouble recreating the issue. Most of our users are remote making is difficult to diagnose.
To recreate the issue I have tried force quitting chrome and reopening. Also a restart of my computer didn't recreate it.
It does seem that removing the extension and reinstalling it fixes the issue.
This is the code that I am using to enable/disable the extension.
const PAGE_RULE = [{
id: 'DISPLAY_RULE_SS',
conditions: [
new chrome.declarativeContent.PageStateMatcher({
pageUrl: {hostSuffix: '.localhost.com', pathPrefix: '/app/'},
}),
],
actions: [new chrome.declarativeContent.ShowPageAction()],
},]
chrome.runtime.onInstalled.addListener(function() {
chrome.tabs.onActivated.addListener((activeInfo) => {
chrome.declarativeContent.onPageChanged.getRules(['DISPLAY_RULE_SS'], (rules) => {
if(rules.length !== 0){
return;
}
chrome.declarativeContent.onPageChanged.addRules(PAGE_RULE);
});
});
});
This is my manifest.
{
"name" : "Support App",
"version" : "0.0.3",
"description" : "Fill in a brief description of your issue, along with a few details, and our support team will be notified immediately.",
"manifest_version" : 2,
"icons": {
"128" : "./img/murmuration_square_transparent.png"
},
"background" : {
"scripts" : ["./backgrounds/default_background.js"],
"persistent" : false
},
"permissions" : [
"history",
"tabs",
"declarativeContent",
"activeTab",
"https://*.localhost.com/*",
],
"page_action" : {
"default_icon" : "./img/murmuration_square_transparent.png",
"default_popup" : "../default_interface.html"
},
"web_accessible_resources": [
"src/default_index.js"
]
}
I suspect that the issue is related to the fact that all the logic is within chrome.runtime.onInstalled.addListener(function() {...} but I'm unsure.
Any help is appreciated.

I have returned to this after some downtime and found the solution in the Google documentation. The documentation on this page state
Listeners must be registered synchronously from the start of the page.
which is exactly what I was doing when setting the page rules.
I also found on this page that
Rules are persistent across browsing sessions. Therefore, you should
install rules during extension installation time using the
runtime.onInstalled event. Note that this event is also triggered when
an extension is updated. Therefore, you should first clear previously
installed rules and then register new rules.
So I have updated my background script to run the following
chrome.runtime.onInstalled.addListener(details => {
chrome.declarativeContent.onPageChanged.removeRules(undefined, () => {
chrome.declarativeContent.onPageChanged.addRules(PAGE_RULE);
});
});
Which is what is recommended in the documentation. This seems to have solved the issue we were encountering.

Related

Can't successfully run executeScript from the background script unless I load the popup page/script first

I've trying to run execute script from my background script using keyboard shortcuts, it doesn't work and returns:
Error: No window matching {"matchesHost":[]}
But if I just open the popup page, close it, and do the same, everything works.
I've recreated the problem in using the Beastify example with minimal changes. Here's the code:
manifest.json
{
... (not interesting part, same as in beastify)
"permissions": [
"activeTab"
],
"browser_action": {
"default_icon": "icons/beasts-32.png",
"default_title": "Beastify",
"default_popup": "popup/choose_beast.html"
},
"web_accessible_resources": [
"beasts/frog.jpg",
"beasts/turtle.jpg",
"beasts/snake.jpg"
],
My additions start here:
"background": {
"scripts": ["background_scripts/background_script.js"]
},
"commands": {
"run_content_test": {
"suggested_key": {
"default": "Alt+Shift+W"
}
}
}
}
popup/choose_beast.js (same as in original)
/*
Given the name of a beast, get the URL to the corresponding image.
*/
function beastNameToURL(beastName) {
switch (beastName) {
case "Frog":
return browser.extension.getURL("beasts/frog.jpg");
case "Snake":
return browser.extension.getURL("beasts/snake.jpg");
case "Turtle":
return browser.extension.getURL("beasts/turtle.jpg");
}
}
/*
Listen for clicks in the popup.
If the click is on one of the beasts:
Inject the "beastify.js" content script in the active tab.
Then get the active tab and send "beastify.js" a message
containing the URL to the chosen beast's image.
If it's on a button wich contains class "clear":
Reload the page.
Close the popup. This is needed, as the content script malfunctions after page reloads.
*/
document.addEventListener("click", (e) => {
if (e.target.classList.contains("beast")) {
var chosenBeast = e.target.textContent;
var chosenBeastURL = beastNameToURL(chosenBeast);
browser.tabs.executeScript(null, {
file: "/content_scripts/beastify.js"
});
var gettingActiveTab = browser.tabs.query({active: true, currentWindow: true});
gettingActiveTab.then((tabs) => {
browser.tabs.sendMessage(tabs[0].id, {beastURL: chosenBeastURL});
});
}
else if (e.target.classList.contains("clear")) {
browser.tabs.reload();
window.close();
return;
}
});
background_scripts/background_script.js (added by me)
browser.commands.onCommand.addListener(function(command) {
var executing = browser.tabs.executeScript(
null,
{file: "/content_scripts/content_test.js"});
executing.then(
function (res){
console.log("started content_test.js: " + res);
},
function (err){
console.log("haven't started, error: " + err);
});
});
content_scripts/content_test.js (added by me)
alert("0");
I'm skipping the whole content_scripts/beastify.js cause it has nothing to do with it (IMO), but it can be found here.
Now, I know that the background script runs and receives the messages even when the popup page hasn't been opened before, because I see it failing executing the script. I have no idea what causes this behavior and if there's a way to fix it.
Note: I tried adding permissions such as "tabs" and even "all_urls", but it didn't change anything.
Note 2: I'm running the add-on as a temporary add-on from the about:debugging page, but I'm trying to execute the script on a normal non-restricted page (on this page for example I can recreate the problem).
Thanks a lot guys!
// in manifest.json
"permissions": [
"<all_urls>",
"activeTab"
],
DOES work for me (Firefox 50, Mac OS X 10.11.6).
I had gotten the exact same error message you described when I had used the original
"permissions": [
"activeTab"
],
So the addition of "<all_urls>" seems to fix the problem. However, you said that you were still experiencing the issue when you included "all_urls" in your permissions, so I am not sure whether the way I did it fixes the issue in your own setup.
edit: Whether giving any webextension such broad permissions would be wise in terms of the security risks it might pose is a separate, important consideration, I would imagine.
(I would have posted this as a comment, but I don't have enough reputation yet to be able to add comments.)

Porting WebExtension from Chrome to Firefox?

I made a working Chrome extension that is not packaged and is just a directory on my computer. I found out that I should be able to port this to Firefox rather easily.
I followed the "Porting a Google Chrome extension" guide on MDN and found that my manifest file is perfect.
I then followed the instructions on how to perform "Temporary Installation in Firefox" of the extension.
However, when I click on any file inside the directory, nothing happens. The extension doesn't load. Any advice? I know the extension works in Chrome fine and loads without error.
manifest.json:
{
"manifest_version": 2,
"name": "ER",
"description": "P",
"version": "1.0",
"browser_action": {
"default_icon": "icon.png",
"default_popup": "popup.html"
},
"background": {
"scripts": [ "background.js" ]
},
"content_scripts": [
{
"matches": [ "SiteIwant" ],
"js": [ "ChromeFormFill.js" ],
"run_at": "document_idle"
}
],
"permissions": [
"*://*/*",
"cookies",
"activeTab",
"tabs",
"https://ajax.googleapis.com/"
],
"externally_connectable": {
"matches": ["SiteIwant"]
}
}
ChromeFormFill.js:
// JavaScript source c
console.log("inside content");
console.log(chrome.runtime.id);
document.getElementById("ID").value = chrome.runtime.id.toString();
Background.js
chrome.runtime.onMessage.addListener(
function (request, sender, sendResponse) {
if (request.data === "info") {
console.log("Recieving Info");
return true;
}
});
chrome.tabs.create(
{
url: 'myUrl'
active: true
}, function (tab) {
chrome.tabs.executeScript(tab.id, { file: 'Run.js', runAt: "document_idle" });
});
Run.js will just alert('hi').
It just won't do anything when I try to load it on Firefox; nothing will happen.
Issues:
In manifest.json:
externally_connectable is not supported:1
Firefox does not support externally_connectable. You can follow bug 1319168 for more information. There is, currently, no expected time when this will be implemented.
You will need to communicate between the code on your site and the WebExtension using a different method. The way to do so is to inject a content script and communicate between the site's code and the content script. The common ways to do this are CustomEvent() or window.postMessage(). My preference is CustomEvent().
Using window.postMessage() is like yelling your message outside and hoping that either nobody else is listening, or that they know that they should ignore the message. Other people's code that is also using window.postMessage() must have been written to ignore your messages. You have to write your code to ignore any potential messages from other code. If either of those were not done, then your code or the other code can malfunction.
Using CustomEvent() is like talking directly to someone in a room. Other people could be listening, but they need to know about the room's existence in order to do so, and specifically choose to be listening to your conversation. Custom events are only received by code that is listening for the event type which you have specified, which could be any valid identifier you choose. This makes it much less likely that interference will happen by mistake. You can also choose to use multiple different event types to mean different things, or just use one event type and have a defined format for your messages that allows discriminating between any possible types of messages you need.
matches value needs to be valid (assumed to be intentionally redacted):
You have two lines (one with a trailing ,, one without; both syntactically correct):
"matches": ["SiteIwant"]
"SiteIwant" needs to be a valid match pattern. I'm assuming that this was changed away from something valid to obfuscate the site that you are working with. I used:
"matches": [ "*://*.mozilla.org/*" ]
In Background.js:
The lines:
url: 'myUrl'
active: true
need to be:
url: 'myUrl',
active: true
[Note the , after 'myUrl'.] In addition, myUrl needs to be a valid URL. I used:
url: 'http://www.mozilla.org/',
A Firefox 48 bug (now long fixed):
Your line:
chrome.tabs.executeScript(tab.id, { file: 'Run.js', runAt: "document_idle" });
In Firefox 48 this line is executed prior to the tab being available. This is a bug. It is fixed in Firefox Developer Edition and Nightly. You will need one of those to test/continue development.
Issues in ChromeFormFill.js:
Another Firefox 48 bug (now long fixed):
chrome.runtime.id is undefined. This is fixed in Developer Edition and Nightly.
Potential content script issue:
I'm going to assume your HTML has an element with an ID = 'ID'. If not, your document.getElementById("ID") will be null. You don't check to see if the returned value is valid.
Running your example code
Once all those errors were resolved, and running under Firefox Nightly, or Developer Edition, it worked fine. However, you didn't have anything that relied on being externally_connectable, which won't function.
agaggi noticed that I had forgotten to include this issue in the original version of my answer.

How to trigger a function on specific websites from a chrome extension?

Recently started working with Chrome Extensions.
I am trying to figure out how I can execute a function on a specific website only.
For instance, I want to show an alert box on Stack Overflow only. I am using Chrome's Declarative Content API for this to match the host.
I haven't been able to find a similar question on SO for this.
My manifest.json file is running the following block of code in the background.
'use strict';
chrome.runtime.onInstalled.addListener(function() {
chrome.declarativeContent.onPageChanged.removeRules(undefined, function() {
chrome.declarativeContent.onPageChanged.addRules([{
conditions: [
new chrome.declarativeContent.PageStateMatcher({
pageUrl: {
hostEquals: 'stackoverflow.com'
}
})
],
actions: [new chrome.declarativeContent.SOOnlyFunction()]
}]);
});
});
function SOOnlyFunction()
{
alert('On SO');
}
You Can use the Chrome API to achieve this behaviour:
When a new Page is Loaded, you can call
chrome.tabs.onUpdated
here you can filter the url
and To Create Notifications.
chrome.notifications.create
In your Manifest add these objects:
"permissions": [ "activeTab","tabs","notifications" ],
"background": {
"scripts": ["background.js"],
"persistent": false
}
Here is How my background.js looks like.
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab){
if(changeInfo.url != null){
console.log("onUpdated." + " url is "+changeInfo.url);
// creates Notification.
showNotification();
}
});
function showNotification() {
chrome.notifications.create( {
iconUrl: chrome.runtime.getURL('img/logo128.png'),
title: 'Removal required',
type: 'basic',
message: 'URL is',
buttons: [{ title: 'Learn More' }],
isClickable: true,
priority: 2,
}, function() {});
}
It is incomplete but you will get the Idea.
You can't use chrome.declarativeContent for anything but what's already built in as actions.
Which is currently specifically ShowPageAction, SetIcon and RequestContentScript (still experimental).
The reason that this can't be easily extended is because declarativeContent is implemented in native code as a more efficient alternative to JavaScript event handlers.
In general, chalk it up as an ambitious but essentially unviable/underdeveloped/abandoned idea from Chrome devs, similar fate to declarativeWebRequest.
See the other answer for implementation ideas using said JS handlers.
Or alternatively, you can still make it "declarative" by using content scripts declared in the manifest that will only activate on the predefined domain (as long as you know the domain in advance as a constant). They can then do something themselves or poke the event/background page to do something.
Finally, if your goal is to redirect/block the request, webRequest API is the one to look at.

how to dynamically change chrome extension icon

I am developing a chrome extension. The scenario is kind of
When i click on the icon extension send POST request to server and on basis of the GET response it procceds on any of 3 different if/else if/ else statement. I am using page action to show the icon next to address bar.
I want my extension icon to change dynamically on each if/else if /else statement.
this is my backgound.js to make the icon visible next to address bar.
chrome.runtime.onInstalled.addListener(function() {
chrome.declarativeContent.onPageChanged.removeRules(undefined, function() {
chrome.declarativeContent.onPageChanged.addRules([
{
conditions: [
new chrome.declarativeContent.PageStateMatcher({
})
],
actions: [ new chrome.declarativeContent.ShowPageAction() ]
}
]);
});
});
this is my manifest.json
"page_action" :
{
"default_icon" : "icon-191.png",
"default_title" : "xxx",
"default_popup": "popup.html"
},
Any suggestion how can i change extension toolber icon dynamically on diffetent statement?
Thanks In Advance!
Well, it's there in the docs.
declarativeContent API can only execute a very limited number of actions instead of arbitrary code.
Thankfully for you, chrome.declarativeContent.setIcon is an action that does exactly what you need. Use it just like the one you're using already, except it expects a parameter.
And give that docs page a read in general.

Chrome extension that acts only when clicked on certain webpages

I'm trying to get my Chrome extension to pop up an alert when the user is on http://google.com/ and clicks on the extension icon.
I have the following manifest:
{
"manifest_version": 2,
"name": "One Megahurt",
"version": "0.1",
"permissions": [
"activeTab"
],
"background": {
"scripts": ["bg.js"],
"persistent": false
},
"browser_action": {
"default_icon": "icon.png"
}
}
and this is bg.js:
chrome.browserAction.onClicked.addListener(function(tab) {
alert('Test!');
})
This code will allow popup an alert on any website, as I don't have any restrictions on which websites this works on. I tried using
if(tab.url === "https://google.com/")
between the first and second lines, but that didn't work.
I'm not sure if I should even be using a background script rather than a content script. I looked in Google's examples and tried using the implementation in "Page action by URL", but that didn't work for me either.
Any help would be appreciated. I should note that I don't really care about the specific issues with the URL--google.com is merely an example. I want to learn to use this for other projects and websites.
EDIT: Adding urls to permissions doesn't restrict which websites the alert pops up on, either.
I ended up using page actions for my solution, per Felix King's suggestion. In retrospect, this was the best solution to use because it doesn't load the extension on every page and cause browser slowdowns (as far as I know).
In addition to adding domains to permissions in the manifest, add a the following code to a background.js.
// When the extension is installed or upgraded ...
chrome.runtime.onInstalled.addListener(function() {
// Replace all rules ...
chrome.declarativeContent.onPageChanged.removeRules(undefined, function() {
// With a new rule ...
chrome.declarativeContent.onPageChanged.addRules([
{
// That fires when a page's URL matches one of the following ...
conditions: [
new chrome.declarativeContent.PageStateMatcher({
pageUrl: { urlMatches: 'http://google.com/' }, // use https if necessary or add another line to match for both
}),
new chrome.declarativeContent.PageStateMatcher({
pageUrl: { urlMatches: 'http://facebook.com/*' },
}) // continue with more urls if needed
],
// And shows the extension's page action.
actions: [ new chrome.declarativeContent.ShowPageAction()]
}
]);
});
});
chrome.pageAction.onClicked.addListener(function(tab) {
chrome.tabs.executeScript(null, { file: "script.js" });
});
Key sections to add in manifest.js are:
"background": {
"scripts": ["res/background.js"],
"persistent": false
}
&
"permissions": [
"declarativeContent", "tabs", "activeTab", "http://google.com", "http://facebook.com/*"
]
I don't have much experience with this, but looking at the example manifests that I've seen, they usually have the a list of domains under permissions. I'm betting that if you used:
"permissions": ["http://www.google.com/", "https://www.google.com/", https://google.com, https://google.com],
it would only run the code on the permissible pages.
Pulled example from:
http://developer.chrome.com/extensions/overview
More detailed info here:
http://developer.chrome.com/extensions/declare_permissions

Categories

Resources