Chrome Extension Page Action not showing next to omnibar - javascript

Manifest:
{
"manifest_version":2,
"name":"Optimize url",
"description":"Optimize url",
"page_action":{
"default_icon":{
"19":"url-icon16.png",
"38":"url-icon48.png"
},
"default_title":"Optimize url"
},
"background":{
"scripts":["background.js"]
},
"version":"0.1",
"permissions":[
"tabs",
"https://url.com/*"
]
}
Background JS:
function checkURL(){
var host = parseURL(tab.url).host;
if (host.indexOf("url.com") >= 0) {
chrome.pageAction.show(tabId);
}
}
chrome.tabs.onUpdated.addListener(checkURL);
Yet when I add it to the developing Extensions page. It doesn't show up anywhere. I was originally going to have this as a browser action but it made more since to use it as a page action since it's only going to be focused for one website only.
Can anyone explain to me what I may be doing wrong?

There are the following problems with your code:
The variable tab, which is used in checkURL, is nowhere defined.
The function parseURL is also nowhere defined (it is not a built-in function as you seem to assume).
It is, also, a good idea to filter the onUpdated events looking for status: 'complete', because several onUpdated events are triggered during a single tab update.
So, replace your background.js code with the following:
var hostRegex = /^[^:]+:\/\/[^\/]*url.com/i;
function checkURL(tabId, info, tab) {
if (info.status === "complete") {
if (hostRegex.test(tab.url)) {
chrome.pageAction.show(tabId);
}
}
}
chrome.tabs.onUpdated.addListener(checkURL);

Related

chrome extension call specific function

I have a popup.js:
function registerButtonAction(tabId, button, action) {
// clicking button will send a message to
// content script in the same tab as the popup
button.addEventListener('click', () => chrome.tabs.sendMessage(tabId, { [action]: true }));
}
function setupButtons(tabId) {
// add click actions to each 3 buttons
registerButtonAction(tabId, document.getElementById('start-btn'), 'startSearch');
registerButtonAction(tabId, document.getElementById('deals-btn'), 'startDeals');
}
function injectStartSearchScript() {
chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
// Injects JavaScript code into a page
// chrome.tabs.executeScript(tabs[0].id, { file: 'main.js' });
// dd click handlers for buttons
setupButtons(tabs[0].id);
});
}
injectStartSearchScript()
// document.getElementById('inject-btn').addEventListener('click', injectStartSearchScript);
Which I use to inject my script into the page with the "start-btn" inside my popup.html.
This is my main.js which includes my functions I would like to call on a page:
function pong() {
// do something
}
function ping() {
// do something else
}
my manifest.json:
{
"manifest_version": 2,
"name": "test app",
"description": "test desc",
"version": "1.0",
"browser_action": {
"default_icon": "icon.png",
"default_popup": "popup.html"
},
"permissions": ["tabs", "<all_urls>"],
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["main.js"]
}
]
}
So basically my setup is that I have a popup.html which includes 3 buttons and they should call one of the functions inside my main.js dpending on what button i press.
But I can not get that working. Currently I only can make at least one function call if I directly call pong() inside main.js on load. But I would need to call one of the functions after I click on a button inside my popup.html.
EDIT: I have updated the code as far as I understood. I am very sorry but I don't understand what would be needed to be changed else to fulfill your proposal. I mean how to write it to be more correct.
EDIT 2: I have removed the line chrome.tabs.executeScript(tabs[0].id, { file: 'main.js' }); as well as document.getElementById('inject-btn').addEventListener('click', injectStartSearchScript)and added injectStartSearchScript()into the popup.js file. Is that what you meant?
Updated and complete example with explanation (you are almost there!)
manifest
You manifest looks good, no changes needed there.
This configuration says to load the content script in each tab. So before popup even opens each tab will have "main.js" injected into it (exactly once).
popup.js
Popup script looks good, no changes needed there.
The tab lookup is still necessary, since to send a message to a specific tab, must know its id. The popup looks for the currently active tab in current window (same as the popup is in) and sets up the button click actions to send a message to the tab.
main.js
Will need minor changes here
Make sure to register the onMessage listener, as included in the example below.
note the conditions: message.startSearch and message.startDeals -- these must match the messages sent from the popup, i.e. when popup sends a message with content {startDeals: true}, the if condition is startDeals. It is matching by a key in the sent message and if the key does not match any condition, the message is going to be ignored.
function pong() {
// do something
alert('Start!');
}
function ping() {
// do something else
alert('Deals!');
}
// register listener to receive messages
chrome.runtime.onMessage.addListener(message => {
// what to do on each received message:
if (message.startSearch) pong();
else if (message.startDeals) ping();
});
// sanity check: content has loaded in the tab
console.log('content loaded');
One more note as it relates to debugging extensions (and perhaps a source of some of these debugging issues) when the content script has been configured to be injected in the manifest, Chrome will inject main.js into tabs, in a separate extension context. If, after this injection, the developer reloads the extension (circular arrow in chrome://extensions or other tool), this will invalidate the context of the content script in the tab. The tab has to be reloaded to reactivate the content script. This is not an issue with a real user, but it does happen during debugging, so double check this is not the cause of issues while debugging.

Detect page changes that'd don't refresh in Chrome Extension Javascript + Clay.js

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!

Firefox extensions: How to run function on every video change on YouTube

I have an extension that injects js code into YouTube pages. I've used the following declaration in manifest.json:
"content_scripts": [
{
"matches": [
"*://*.youtube.com/*"
],
"js": [
"background.js"
]
}
]
I would like to define the function that prints the name of the video, number of likes and dislikes to console when I move to another video.
I've written this in background.js:
window.onhashchange = function () {
console.log(
document.querySelector("h1.title > yt-formatted-string:nth-child(1)").innerHTML, "\n",
document.querySelector("ytd-toggle-button-renderer.ytd-menu-renderer:nth-child(1) > a:nth-child(1) > yt-formatted-string:nth-child(2)").getAttribute("aria-label"), "\n",
document.querySelector("ytd-toggle-button-renderer.style-scope:nth-child(2) > a:nth-child(1) > yt-formatted-string:nth-child(2)").getAttribute("aria-label"), "\n",
)
}
But it runs only once. If I select new video from "Recommended" it does not work. I also tried .onload, .onunload, etc.
UPD: Now the only way I found is to use .setInterval.
Several possible solutions using the WebExtensions API, all require a background script that will send messages to your content script. Modify your manifest.json to include:
"background": {
"scripts": ["background.js"]
}
I've named the background script background.js here, that would collide with what you currently have - you might want to consider to rename your content script to something like contentscript.js, so you don't get the two confused.
In the contentscript.js you have the message listener
browser.runtime.onMessage.addListener(message => {
if (message.videoChanged) {
// do stuff
}
});
Using tabs.onUpdated
Needed permission in the manifest.json
"permissions": [
"tabs"
]
In the background.js
browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
if (!changeInfo.url) {
// url didn't change
return;
}
const url = new URL(changeInfo.url);
if (!url.href.startsWith('https://www.youtube.com/watch?') ||
!url.searchParams.get('v')) {
// not a youtube video
return;
}
browser.tabs.sendMessage(tabId, {videoChanged: true});
});
This method will message the content script on first visits, while on-site navigation or auto play.
Using webNavigation.onHistoryStateUpdated
Needed permission in the manifest.json
"permissions": [
"webNavigation"
]
In the background.js
browser.webNavigation.onHistoryStateUpdated.addListener(history => {
const url = new URL(history.url);
if (!url.searchParams.get('v')) {
// not a video
return;
}
browser.tabs.sendMessage(history.tabId, {videoChanged: true});
},
{url: [{urlMatches: '^https://www.youtube.com/watch\?'}]}
);
This method messages the content script while on-site navigation or auto play.
Using webRequest.onBeforeRequest or webRequest.onCompleted
YouTube makes a xmlhttrequest when the video changes. You can see the requests by opening the Developer Tools (Ctrl+Shift+I), selecting the Network tab, select XHR, filter by watch? and then let YT switch to the next video. You'll see that two requests occur for the next video, one with a prefetch parameter in the URL shortly before the video changes, and one when the video actually changes without the prefetch parameter.
Needed permissions in the manifest.json
"permissions": [
"https://www.youtube.com/watch?*",
"webRequest"
]
In the background.js
browser.webRequest.onBeforeRequest.addListener(request => {
const url = new URL(request.url);
if (!url.searchParams.get('v') || url.searchParams.get('prefetch')) {
// not a video or it's prefetch
return;
}
browser.tabs.sendMessage(request.tabId, {videoChanged: true});
},
{urls: ['https://www.youtube.com/watch?*'], types: ['xmlhttprequest']}
);
onBeforeRequest might be a bit too fast and send the message to the content script before the new video actually finished loading. In this case you could just replace it with onCompleted.
This method messages the content script while on-site navigation or auto play.
Well the idea is to find a way to periodically check for URL change so the trick I use is to take advantage of the user's need to click on things like the play/pause button and of course on other videos to watch.
So inside your page onload event... (W being your iframe ID)
if(W.contentWindow.document.URL.indexOf('www.youtube.com/watch?v=')>-1){ // You may want to add some more permissable URL types here
W.contentWindow.document.body.addEventListener('click',function(e){ CheckForURLChange(W.contentWindow.document.title,W.contentWindow.document.location); },false);
}
And further down with the rest of your functions...
function CheckForURLChange(Title,URL){
// Your logic to test for URL change and take any required steps
if(StoredURL!==URL){}
}
It's not the best solution but it does work.

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.)

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.

Categories

Resources