I'm new to Javascript and Chrome extensions, and I'm working on one that triggers a prompt when the user visits a certain webpage, but It's not prompting when the user visits the webpage.
Here's a simplified version that reproduces the problem:
chrome.tabs.query({'active': true, 'lastFocusedWindow': true}, function (tabs) {
var url = tabs[0].url;
if (url === "example.com") {
confirm("You are visiting " + URL);
}
});
What I was hoping this would do is trigger the prompt when the user visits 'Example.com', but I think that I'm misunderstanding how Chrome pulls the URL from the active tab, and maybe how the event is triggered...
Any tips are appreciated.
In your answer, you quoted this question, and yet you picked a weird answer out of the bunch of answers there.
Instead of detecting a page load yourself, it's best to rely on Chrome's content script mechanism, which will ensure that your script is executed in every tab that loads the requested webpage.
If you know the list of the webpages in advance, you can filter by them in the manifest:
"content_scripts" : [{
"matches": ["*://*.example1.com/*", "*://*.example2.com/"],
"js": ["confirm.js"]
}],
And then have a very simple confirm.js:
confirm("You are visiting " + location.href);
This will be very efficient as Chrome will natively filter the requests for you.
If you don't know the list of the webpages in advance, you can use a content script on every page and filter it in your code:
"content_scripts" : [{
"matches": ["*://*/*"],
"js": ["confirm.js"]
}],
And the confirm.js (skeleton):
chrome.storage.local.get("hostFilterData", function(data){
if( checkUrl(location.host, data.hostFilterData) ) {
confirm("You are visiting " + location.href);
}
});
function checkUrl(location.host, filterData){
/* your logic goes here */
}
You might also want to add "run_at" : "document_start" if you want your JS to run at the earliest time possible.
If you really want to keep your logic in the background page, you can listen to various events. chrome.tabs.onUpdated filtered by changeInfo.status == 'complete' should work in most cases, but may not always be a good idea. It will fire very late if the page has a slow-loading resource, and might fire again if the page uses anchor-based navigation (you probably don't want that). Even if you go this route, there's little sense doing a tab query after that, since the event includes the tab ID of the page that fired it.
There's also chrome.webNavigation API providing relevant events.
Try changing
if(url === "example.com")
to
if(url === "example.com" || url === "http://example.com" || url === "https://example.com")
Related
Background
I am trying to write a Google Chrome extension to detect whenever a user scrolls down on Facebook, and if they successfully load a new set of posts, change the icon for one of the reaction options for all posts. I am using clay.js to detect if the div that contains the Facebook feed has resized, which means more posts have loaded / comments have been posted. This works fine.
Problem
The problem arises when you swap between pages on Facebook without refreshing. For example, if you start on your Home page, this will work fine. However, when you swap to your profile, the script no longer runs, until you refresh the page. Once refreshed, the script works perfectly again. I know I'm missing something about how my file is being loaded, so my question is: how do I run my script on every Facebook page, without having to refresh between each type of page?
Relevant Code (reaction-changer.js)
const fbContentId = "#content"
// on DOM load, watch for future feed scrolling
document.addEventListener('DOMContentLoaded', checkFeedUpdate(), false);
function checkFeedUpdate(){
let currFeed = new Clay(fbContentId)
// resize occurs whenever the user scrolls down or a comment loads
// on a prexisting post
currFeed.on('resize', function() {
switchAllIcons()
});
}
Manifest (some elements omitted for simplicity, notated by ...). change-icons.js is the script that actually changes icons, which will run fine, if the reaction-changer.js script actually runs.
{
...
"version": "1.0",
"manifest_version": 2,
"content_scripts": [
{
"matches": ["https://www.facebook.com/*"],
"js": ["extension/clay.js", "change-icons.js", "reaction-changer.js"],
"all_frames": true
}
],
"web_accessible_resources": [
"img/*.png"
]
...
}
Any help would be greatly appreciated! I've read the Chrome Extension documentation, as well as a bunch of other stack overflow posts, but must have missed a solution somewhere.
Alrighty, I spent the last 2 hours working on this, and I found a solution that I'm happy with for now (albeit not content with -- but what'll ya do). Basically, the big question that I had in my OP was:
how do I run my script on every Facebook page, without having to refresh between each type of page?
Well, what I realized is that, yes, refreshing is the solution. So... what if we force a refresh on Facebook's end, allowing the DOM to refresh, and the code to run as expected? I believe that this PROBABLY is actually an underlying issue with how the Clay.js library I'm using is implemented. Anyway, I basically approached the solution by:
First, creating a background.js file that takes advantage of chrome.tabs.onUpdated.addListener -- this function basically let me detect if a tab changed or if the page status was "completed" indicating it has loaded.
If it loaded, then I run the function checkFeedUpdate() exactly as above.
If it changed to a new page (e.g., user clicked from Home to Profile), I force a reload, and then wait for point 2 above to fire.
'background.jsis detecting whether or not these states have happened yet, and relaying the information toreaction-changer.js`.
Here's the updated bit of reaction-changer.js (in place of document.addEventListener):
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
// listen for messages sent from background.js
if (request.message === 'reload') {
location.reload();
} else if (request.message === 'start'){
checkFeedUpdate()
}
});
Here's the updated manifest:
"background": {
"scripts": ["background.js"]
},
"content_scripts": [
{
"matches": ["https://www.facebook.com/*"],
"js": ["extension/clay.js", "change-icons.js", "reaction-changer.js"],
"all_frames": true
}
],
"permissions": [
"tabs"
],
"web_accessible_resources": [
"img/*.png"
]
And here's what I created for background.js:
chrome.tabs.onUpdated.addListener(
function(tabId, changeInfo) {
// read changeInfo data and do something with it
if (changeInfo.url) {
chrome.tabs.sendMessage( tabId, {
message: 'reload'
})
} else if (changeInfo.status === 'complete'){
chrome.tabs.sendMessage( tabId, {
message: 'start'
})
}
}
);
If anyone ends up facing a similar issue (it seems like refreshing does the trick, but you can't get it to work without refreshing), it seems that just forcing a refresh might be a good solution. If there's a better one, please let me know!
Sorry for my poor English, i hope you can understand the issue.
I'm new to chrome extension development,and for sure in my code there are a lot of
thing to change or optimize;
anyway i've written a simple code that, (seems) works at least from my chrome.
The code clicks a button every X minutes in specific page, then wait and parse the result in page.
I've :
a content script (loaded from manifest.json) which "inject" some button and text Input box in page, so user can sets some "filter params" before click a "start button"; the start button then sendMessage() to background.js to set Alarm Event for the click ;
an eventPage (which is set persistent true in actually ) which handle the request from tabs and set a countdown alarm for each tab; when X min are passed fire a message to the interested tab;
I also have a popup.html e popup.js not important here (i think).
I've to distribuite this extension manually, so i would distribuite a zip that user can load with "developer mode ".
*Now the issue is: why the code was working only on my Chrome ? *
I've tested with others 2-3 laptop with Chrome, the background script is loaded (i can see the background page printint console log)
but in webpage the contents.js seems no way executed .
In my chrome works well: i can see in console some initial output (i print the name of dir extension to check) and
the dynamic created element (button,input box ect.) in page.
And all is working, i can fire the start button and receive results of parsing.
During the development i've never run the extension on other machine. Yesterday i've succssfully tested on 2-3 laptop.. then i made only few change but nothing serious.
Today i can run only in my chrome.
In other pc nothing, neither the simple console.log output first line of script.
I can read in console log :
"Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist."
but this also in my working istance in my laptop chrome .
The zip file is the same and the extraction is good, in fact i can actually load the extension and i see the background page debug console.log() sentences
In some case, in laptop where it dosen't work, i've received a message relative jQuery and the fact that chrome.runtime.sendMessage() is not defined; and it points to code in webpage, not mine.
I've see that in webpage code there is something like:
var extid = "mcmhdskbnejjjdjsdkksmeadjaibo";
var extVer = "1.5";
var extStatus = 0;
$(document).ready(function () {
///...
chrome.runtime.sendMessage(extid, {message: "version"},
function (reply) {
if (reply) {
if (reply.version) {
if (reply.version == extVer) {
if (reply.gminfo != 'OK') {
extStatus = 1; /// ...
Seems that chrome.runtime is undefined, and the webpage can't call the sendMessage().
EDIT: this undefined occurs only when my extension is loaded
Maybe there is some conflict when i load my extension? But in my chrome browser works...
Can some expert indicate in where direction i've to investigate?
Thanks a lot for any suggestions.
My Manifest.json :
{"manifest_version": 2,
"name": "myAlarm",
"description": "This extension alerts.",
"version": "0.1",
"permissions": [
"alarms",
"system.cpu",
"storage",
"tabs",
"webNavigation",
"https://www.mytargetsite.com/subUrl/"
],
"web_accessible_resources": [
"icon.png",
"vanillaSelectBox.css"],
"content_scripts": [
{
"matches": ["https://www.mytargetsite.com/subUrl/"],
"css": ["vanillaSelectBox.css"],
"js": ["jquery-3.3.1.min.js","vanillaSelectBox.js","taffy-min.js","content.js"],
"run_at": "document_end"
}
],
"background": {
"scripts": ["eventPage.js"],
"persistent": true
},
"browser_action": {
"default_icon": "icon.png",
"default_popup": "popup.html"
},
"icons": {
....
}
}
My contents,js (stripped):
chrome.runtime.onMessage.addListener(
function(request, sender) {
// here i parse message "time'up" from background js
});
window.addEventListener('load', function() {
var pt=chrome.runtime.getURL('filterOff.wav');
var p=pt.split("/");
console.log("[myAlarm v0.1] started" );
console.log("[myAlarm v0.1] folder : ("+p[2]+")");
// here i start an active wait for the presence in page of button with ID= btntarget_id
waitForElementToDisplay("#btntarget_id", 500); //when function find button then create and add button and input text to webpage
});
My eventPage.js :
var curr_alarms =[];
chrome.extension.onMessage.addListener(function(request, sender)
{ /// here receive start countdown message from content.js and set alarm ...
}
chrome.alarms.onAlarm.addListener(function(alarm) {
/// here i manage each alarm for each tab
});
chrome.tabs.onRemoved.addListener(function(tabid, removed) {
// ...
});
chrome.tabs.onUpdated.addListener(function
(tabId, changeInfo, tab) {
//
});
edit : in browser where it dosen't work i can read also :
Access to XMLHttpRequest at 'https://mytargetsite.com/suburl/grid.php' (redirected from 'https://mytargetsite.com/suburl/grid.php') from origin 'https://mytargetsite.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: Redirect is not allowed for a preflight request.
The fact that the declared content script runs or not, should be verified by inspecting in devtools => sources sub-tab => content scripts sub-sub-tab. If it really doesn't run, there can be just two explanations: the URL is different (for example not https) or extensions are blocked by their domain admin via runtime_blocked_hosts which you can see in chrome://policy.
Your development mode extension's id will be different on a different machine unless you pin it by adding a "key" in manifest.json
To use chrome.runtime to send messages to your extension from a webpage code (not from a content script!) your extension's manifest should declare "externally_connectable" and use a different event onMessageExternal, see also sending messages from web pages.
The CORS error may be irrelevant to your code (you can investigate the source of the error by expanding the error's call stack in devtools console).
I have made a Chrome extension to help using a small search engine in our company's intranet. That search engine is a very old webpage really convoluted, and it doesn't take parameters in the url. No chance that the original authors will assist:
The extension popup offers an input text box to type your query. Your
query is then saved in localStorage
There is a content script inserted in
the intranet page that reads the localStorage key and does a document.getElementById("textbox").value = "your query"; and then does
document.getElementById("textbox").click();
The expected result is that your search is performed. And that's all.
The problem is that the click gets performed unlimited times in an infinite loop, and I cannot see why it's repeating.
I would be grateful if you would be able to assist. This is my first Chrome extension and all what I have been learning about how to make them has been a great experience so far.
This is the relevant code:
The extension popup where you type your query
popup.html
<input type="search" id="cotext"><br>
<input type="button" value="Name Search" id="cobutton">
The attached js of the popup
popup.js
var csearch = document.getElementById("cotext");
var co = document.getElementById("cobutton");
co.addEventListener("click", function() {
localStorage["company"] = csearch.value;
window.open('url of intranet that has content script applied');
});
And now the background file to help with communication between parts:
background.js
chrome.extension.onRequest.addListener(function(request, sender, sendResponse) {
sendResponse({data: localStorage[request.key]});
});
And finally the content script that is configured in the manifest to be injected on the url of that search engine.
incomingsearch.js
chrome.extension.sendRequest(
{method: "getLocalStorage", key: "company"},
function(response) {
var str = response.data;
if (document.getElementById("txtQSearch").value === "") {
document.getElementById("txtQSearch").value = str;
}
document.getElementById("btnQSearch").click();
});
So as I mentioned before, the code works... not just once (as it should) but many many times. Do I really have an infinite loop somewhere? I don't see it... For the moment I have disabled .click() and I have put .focus() instead, but it's a workaround. I would really like to use .click() here.
Thanks in advance!
The loop is probably caused by clicking the button even if it has a value. Try putting it inside your if. That said, you are overcomplicating it.
You can access the extension's data inside content scripts directly by replacing localstorage with the chrome.storage extension api. Add the "storage" (silent) permission to your manifest.json, like this:
"permissions": ["storage"]
Remove the message passing code in background.js. Then replace the popup button listener contents with:
chrome.storage.local.set({ "company": csearch.value }, function() {
chrome.tabs.create({ url: "whatever url" })
})
Replace the content script with:
chrome.storage.local.get("company", function(items) {
if(document.querySelector("#txtQSearch").value == "") {
document.querySelector("#txtQSearch").value = items.company
document.querySelector("#btnQSearch").click()
}
})
document.querySelector() performs the same function here as getElementById, but it is much more robust. It also has less capital letters, which makes it easier to type in my opinion.
I am building an extension that requires access to history to provide one of the features.
After publishing a version which contained the permission as mandatory and consequently losing a part of my users because they got scared away by the big alert saying that the extension might be able to snoop into their history (I really didn't plan on doing that), I decided to publish a version with the offending part removed and the permission disabled as a temporary fix.
I'd like to implement this feature back using optional permissions.
First of all, I added the new optional permission to my manifest file:
...
"permissions": [
"https://news.ycombinator.com/",
"http://news.ycombinator.com/"
],
"optional_permissions": [ "history" ],
...
Then, I built a function to request permissions into the script which handles the extension's settings:
Settings.prototype.applyPermissions = function (permissions, map) {
Object.keys(permissions).forEach(function (key) {
if (map[key]) {
var checkbox = map[key].getElementsByTagName("input")[0];
checkbox.addEventListener("change", function (e) {
if (this.checked) {
chrome.permissions.request(permissions[key], function(granted) {
if (granted) {
// Permission has been granted
} else {
// Not granted
}
});
}
});
}
});
};
The key part here is this:
checkbox.addEventListener("change", function (e) {
if (this.checked) {
chrome.permissions.request(permissions[key], function(granted) {
if (granted) {
// Permission has been granted
} else {
// Not granted
}
});
}
});
I perform the request on an event caused by user interaction (the guide states that it won't work otherwise), and pass permissions[key], an object specified in my extension's settings which looks like this:
"permissions": {
"mark_as_read": {
"permissions": ["history"]
}
}
When accessing it as permissions[key], I get this part:
{
"permissions": ["history"]
}
Which is basically the format that the documentation shows for this kind of requests.
If I run this code and toggle the checkbox that should enable the feature, and look at the error log, I see this error:
chrome.permissions 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.
I also tried accessing this API from a background page, where it was actually available but I was not allowed to use because Chrome requires that you access it from a user interaction, and such interaction is lost if you send a message to the background page from your content script to request activation.
Am I missing something obvious here? Maybe I need to add something to the manifest, but I can't find any explicit documentation about it.
I assume you're trying to do this from a content script. You can't access most chrome.* APIs from content scripts, including chrome.permissions. However, you've correctly pointed out that a background page is also unsuitable, because you a permission change requires a direct user action.
Luckily, we have hardly exhausted our options. You could set the permission in:
The extension's options page
A browser action popup
A page action popup
Any page in your extension served through the chrome-extension:// scheme, provided you include the page and necessary sub-resources as web_accessible_resources in your manifest
In the last case, get the URL using chrome.extension.getURL. You could possibly use an iframe to inject it directly into the page, if you don't want the permission-requesting interface to be separate from the current page.
So, in fact, content scripts and background pages are the only two extension contexts where you can't use chrome.permissions.
I'm creating a Google Chrome extension and I need to detect when a page's title changes. The page's title is changed like in Twitter: (num) Twitter (see the screenshot below) - when a new tweet is posted, the number increments. Example:
I'm trying to detect the title changes of a URL that's loaded in one of my tabs and play a beep sound whenever there's a difference. This check is to be done in a repeated interval and I think that can be accomplished using setTimeOut() function.
I've created a manifest.json as follows:
{
"manifest_version": 2,
"name": "Detect Page Title Changes",
"description": "Blah",
"version": "1.0",
"browser_action": {
"default_icon": "icon.png",
"default_popup": "background.html"
},
"permissions": [
"tabs"
]
}
However, I'm clueless about the rest. I've searched through the docs 1 2 and tried the solutions on similar Stack Overflow threads such as this one I but couldn't find anything that suits my requirements.
Do you have any suggestions? Please include an example, if possible.
Instead of arguing in comments that a certain approach is better, let me be more constructive and add an answer by showing a particular implementation I co-wrote myself, and explain some gotchas you may run into. Code snippets refer to a service different from Twitter, but the goal was the same. In fact, this code's goal is to report the exact number of unread messages, so yours might be simpler.
My approach is based on an answer here on SO, and instead of being polling-driven (check condition at fixed intervals) is event-driven (be notified of potential changes in condition).
Advantages include immediate detection of a change (which would otherwise not be detected until the next poll) and not wasting resources on polls while the condition does not change. Admittedly, the second argument hardly applies here, but the first one still stands.
Architecture at a glance:
Inject a content script into the page in question.
Analyze initial state of the title, report to background page via sendMessage.
Register a handler for a title change event.
Whenever the event fires and the handler is called, analyze the new state of the title, report to background page via sendMessage.
Already step 1 has a gotcha to it. Normal content script injection mechanism, when the content script is defined in the manifest, will inject it in pages upon navigation to a page that matches the URL.
"content_scripts": [
{
"matches": [
"*://theoldreader.com/*"
],
"js": ["observer.js"],
"run_at": "document_idle"
}
]
This works pretty well, until your extension is reloaded. This can happen in development as you're applying changes you've made, or in deployed instances as it is auto-updated. What happens then is that content scripts are not re-injected in existing open pages (until navigation happens, like a reload). Therefore, if you rely on manifest-based injection, you should also consider including programmatic injection into already-open tabs when extension initializes:
function startupInject() {
chrome.tabs.query(
{url: "*://theoldreader.com/*"},
function (tabs) {
for (var i in tabs) {
chrome.tabs.executeScript(tabs[i].id, {file: "observer.js"});
}
}
);
}
On the other end, content script instances that were active at the time of extension reload are not terminated, but are orphaned: any sendMessage or similar request will fail. It is, therefore, recommended to always check for exceptions when trying to communicate with the parent extension, and self-terminate (by removing handlers) if it fails:
try {
chrome.runtime.sendMessage({'count' : count});
} catch(e) { // Happens when parent extension is no longer available or was reloaded
console.warn("Could not communicate with parent extension, deregistering observer");
observer.disconnect();
}
Step 2 also has a gotcha to it, though it depends on the specifics of the service you're watching. Some pages inside the scope of the content script will not show the number of unread items, but it does not mean that there are no new messages.
After observing how the web service works, I concluded that if the title changes to something without navigation, it's safe to assume the new value if correct, but for the initial title "no new items" should be ignored as unreliable.
So, the analysis code accounts for whether it's the initial reading or handling an update:
function notify(title, changed) {
// ...
var match = /^\((\d+)\)/.exec(title);
var match_zero = /^The Old Reader$/.exec(title);
if (match && match[1]) {
count = match[1];
} else if (match_zero && changed) {
count = 0;
}
// else, consider that we don't know the count
//...
}
It is called with the initial title and changed = false in step 2.
Steps 3 & 4 are the main answer to "how to watch for title changes" (in an event-driven way).
var target = document.querySelector('head > title');
var observer = new window.MutationObserver(
function(mutations) {
mutations.forEach(
function(mutation){
notify(mutation.target.textContent, true);
}
);
}
);
observer.observe(target, { subtree: true, characterData: true, childList: true });
For specifics as to why certain options of observer.observe are set, see the original answer.
Note that notify is called with changed = true, so going from "(1) The Old Reader" to "The Old Reader" without navigation is considered to be a "true" change to zero unread messages.
Put chrome.tabs.onUpdated.addListener in your background script:
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
console.log(changeInfo);
});
changeInfo is an object which includes title changes, e.g. here:
Can then filter on the object so that an action only occurs if changeInfo includes a title change. For additional manipulation, e.g. responding to page title changes with page content / actions, you can send a message to content script from inside the listener after whatever conditions are met.
Create an event page.
Create a content script that gets injected into a webpage when a webpage loads.
Within the content script, use setInterval to poll the page to see if window.document.title changes.
If the title has changed, use chrome.runtime.sendMessage to send a message to your event page.
On your event page, listen for messages with chrome.runtime.onMessage and play a sound.
After researching Chrome's tabs API, it doesn't look like anything stands out to help you directly. However, you should be able to attach an event listener to the title node of the tab(s) you're interested in. The DOMSubtreeModified mutation event works in Chrome, and a quick test in a normal html document proves to work for me - should be no different from within an extension.
var title = document.getElementsByTagName('title')[0];
if (title) {
title.addEventListener('DOMSubtreeModified', function (e) {
// title changed
}, false);
}