So, I understand that you cannot have background scripts and a default popup together. If this is so, how can I have something similar to a default popup (where there is some simple HTML that appears when you clicked the extension's icon) and have the background script modify the contents of that popup?
Here's the manifest.json
"browser_action": {
"default_title": "Mark this position!",
"default_icon": "icon.png"
},
"background": {
"scripts": ["background.js"],
"persistent": false
},
"content_scripts": [
{
"matches": [
"http://*/*",
"https://*/*"
],
"js": ["content.js"]
}
],
You absolutely can have both a popup (i.e. default_popup set) and a background page. The background page will have its own lifecycle (with "persistent": false it's an Event page) and the popup will exist as long as it's open.
I guess your confusion stems from the fact that you cannot have a popup and a chrome.browserAction.onClicked listener at the same time.
This is true, but there are other ways to tell your background page that the popup has opened.
You can message the background page from the popup:
// popup.js, should be included in the popup
chrome.runtime.sendMessage({popupOpen: true}, function(response) {
/* process response */
});
// background.js
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
if(message.popupOpen) {
/* do stuff */
sendResponse(response);
}
});
If you put the above popup code on the top level, then the background will be informed as soon as the popup opens.
While you can directly access the popup's window from the background (see chrome.extension.getViews), it's recommended that you move the UI logic into the popup itself and communicate with the background using Messaging as above and shared chrome.storage.
Related
Although there are a lot of similar questions here, I couldn't solve my problem with any of the accepted solutions, so I'm creating a new question.
I have a content script that adds some functionality to a Jira Issue.
However, when navigating to the Jira Issue from a Filter Results Page, the content script doesn't run until I manually reload the page.
What I've tried so far:
Adding webNavigation Listeners (with the manifest permission), but none of them seem to trigger when the Jira Issue link is clicked from the Filter Page.
// background.js
const myLog = () => console.log('webNavigation event triggered');
browser.webNavigation.onDOMContentLoaded.addListener(myLog);
browser.webNavigation.onHistoryStateUpdated.addListener(myLog);
browser.webNavigation.onReferenceFragmentUpdated.addListener(myLog);
Adding "all_frames": true to the manifest.json, but that didn't make it work either:
// manifest.json
...
"background": {
"scripts": ["background.js"],
"persistent": false
},
"content_scripts": [
{
"matches": ["*://jira.atlassian.com/*"],
"js": ["content.js"],
"all_frames": true
}
],
"permissions": ["tabs", "webNavigation", "<all_urls>"]
Listening to window.onpopstate event inside the content script. Although it detects when I click the "Back" and "Forward" arrows in the browser, it doesn't fire when I click the links inside the Filter Results Page.
// content.js
window.onpopstate = () => {
browser.runtime.sendMessage('webNavigation event triggered');
}
How can I run this content script without having to manually reload the page?
It seeems that my content script document.URL always has the same value even when I click on a random <a href link.
In the below function in the content script, I have a function called performclick() which finds a random link on the current document, then clicks it.
Now shouldn't the next time I call performclick(), I get another document object? i.e. document of the currently focused active tab?
Like when I click a link on the main page and it opens a new tab with another website loaded in it, then I need the content script to give me the document of this new tab that has the clicked website link loaded in it.
But some how I always keep getting the document object of the same main page.
Please help
Manifest
{
"manifest_version": 2,
"name": "OSP Updater",
"version": "1.0.0",
"content_scripts": [{
"run_at": "document_start",
"js": ["content.js"],
"matches": [ "<all_urls>" ]
}],
"permissions": [
"background", "webRequest", "webRequestBlocking", "tabs", "<all_urls>"
],
"background": {
"scripts": ["background.js"],
"persistent": true
},
"web_accessible_resources": ["jsui.js"]
}
Content Script
window.onload = function () {
var elm = document.getElementById("my-container");
if (elm != undefined && elm != null) {
//alert("Main Page LOADED!!!");
}
}
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
if (request.clickit == "yes") {
performclick();
}
});
function performclick() {
//document.URL is always the same
var links = document.querySelectorAll("a");
if (links.length) {
var linkToClick = links[1];
var clickEvent = document.createEvent('MouseEvents');
clickEvent.initEvent('click', true, true);
linkToClick.dispatchEvent(clickEvent);
}
}
Update #1:
Silly me, it turns out that I was sending a message from my background script to my content script via the first tab.
I did my clicking decision in the background script than sends a signal to the content script so that the content script can do the actual clicking.
So since I was sending message from the background script to the content script in the 1st tab, it lead the document object to always belong to the first tab (i.e. main page).
Hope this helps someone, always check if you are doing messaging then check its logic.
After I read the documentation about Event Page I didn't got the advantage of using Event Page instead of Background Page .
Suppose I have the follow simple case -
manifest.json
"background": {
"scripts": ["background.js"],
"persistent": false
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"]
}
]
content.js
chrome.runtime.sendMessage("Hi Background")
background.js
chrome.runtime.onMessage.addListener(messageListener);
function messageListener (request, sender, sendResponse) {
alert(request);
}
In this case , whether persistent is "persistent": false or "persistent": true the listener in the background.js always should be awake in order to get the messages from the content.js and therefore the the background.js couldn't go to suspend mode .
So what is the benefit of the Event Page ("persistent": true) in such cases and in general ? Please provide an example.
The main advantage of the event pages is to release RAM and CPU resources by unloading background script when it is not used.
...background.js couldn't go to suspend mode.
It can. Even if your event page uses message listener it still will be unloaded after some time. Chrome remembers that the page has set the listener, so the browser will awake the page when a message will be sent.
You can try this experiment:
add this extension
manifest.json
{
"manifest_version": 2,
"name": "Test",
"version": "0.0.1",
"description": "",
"background": {
"scripts": ["background.js"],
"persistent": false
},
"browser_action": {
"default_popup": "popup.html"
}
}
background.js
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
sendResponse({
msg: "it's alive!"
});
}
);
popup.html
<html>
<body>
<div id="text"></div>
<script src="popup.js"></script>
</body>
</html>
popup.js
chrome.runtime.sendMessage('hello',
function (response) {
document.getElementById('text').textContent = response.msg;
}
);
Open Chrome's Task manager and wait few seconds until Test extension disappears (unloads). If you do not see it (it already unloaded), you can reload extension.
Click to the extension's browser action and you will see message inside the window which came from the event page. Also, you can see two processes in the task manager for the Test extension: one is a popup and the second is the event page.
After popup will be closed, the event page unloads again in a few seconds.
I want my Chrome extension to behave such that when a user clicks on the extension button, it sends a message and then closes the popup. I have nothing I want to display in the popup and ideally don't want it to even appear in the first place.
Half the time, my code works. The other half of the time, that little empty white bubble remains even after the message has been sent. Why is this non-determinism occurring? Is there a way to simply send a message on clicking the extension button and bypassing the pointless popup appearing?
Here is my popup.js:
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
// Go immediately to background script.
// This popup's thread of execution will get killed as soon as we move to another tab.
chrome.extension.sendRequest({
tab: tabs[0],
message: "Button was clicked"
});
window.close();
});
which is included in the following popup.html:
<head>
<script src="popup.js"></script>
</head>
<body>
</body>
My manifest.json (with irrelevant fields removed) is:
{
"name": "My Extension",
"description": "This is my extension",
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*"],
"js": ["content.js"]
}
],
"background": { "scripts": ["background.js"] },
"browser_action": {
"default_popup": "popup.html"
},
"permissions": [
"http://*/*",
"https://*/*",
"tabs"
],
}
You just declared default_popup in manifest.json, that means once browser action is clicked, the popup.html will show up. If you don't want that, just remove that field and listen to chrome.browserAction.onClicked in background.js.
manifest.json
{
...
"browser_action": {},
...
}
background.js
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.runtime.sendMessage({tab: tab, message: "Button was clicked"});
});
I'm working on a Google Chrome extension, and I essentially want the browser action to act as an on/off switch. Whenever it is "on", it will have a certain icon and the script is executed on the page. Whenever it is "off", it has a certain icon and the script is not executed.
Here's my manifest.json:
{
"manifest_version": 2,
"name": "Resource Control",
"description": "Controls what resources load from a website.",
"version": "1.0",
"icons": {
"16": "locked_16.png",
"48": "locked_48.png",
"128": "locked_128.png"
},
"browser_action": {
"default_icon": "locked_16_off.png" //icon is "off" by default
},
"background": {
"scripts": ["main.js"]
},
"permissions": [
"tabs", "http://*/*", "https://*/*"
]
}
And here's main.js:
var toggled = false;
chrome.browserAction.onClicked.addListener(function(tab) {
toggled = !toggled;
if(toggled){
chrome.browserAction.setIcon({path: "locked_16_on.png", tabId:tab.id});
chrome.tabs.executeScript(null,{code:"document.body.style.backgroundColor='red'"});
}
else{
chrome.browserAction.setIcon({path: "locked_16_off.png", tabId:tab.id});
chrome.tabs.executeScript(tab.id, {code:"alert()"});
}
});
if (toggled) {
chrome.tabs.executeScript(null,{code:"document.body.style.backgroundColor='red'"});
}
Right now I'm just working on getting this functionality working before moving on, so to test I'm just setting the page's background to red. However, whenever I load a new webpage, the icon is reverted back to the "off" icon even though the extension is still "on", and the script isn't injected into the new page. Can someone help me find where I'm going wrong?
When you pass a tabId to chrome.browserAction.setIcon it only sets the icon for when that specific tab is active. If you switch to a different tab, the icon will revert to the default.
You are only running chrome.tabs.executeScript when the browserAction is clicked on. If want code injected on every page you either need to use a content script that is injected on all tabs or listen for new tabs getting created and executeScript every time.