How to disable (gray out) page action for Chrome extension? - google-chrome

I want the Chrome extension icon to be disabled (grayed out) on all pages except for pages on docs.google.com. This is my code in background.js.
'use strict';
chrome.runtime.onInstalled.addListener(function() {
chrome.declarativeContent.onPageChanged.removeRules(undefined, function() {
chrome.declarativeContent.onPageChanged.addRules([{
conditions: [new chrome.declarativeContent.PageStateMatcher({
pageUrl: { urlContains: 'docs.google' },
})
],
actions: [new chrome.declarativeContent.ShowPageAction()]
}]);
});
});
From the documentation for pageActions this should result in my extension icon being gray on all pages except for ones that have docs.google in the URL. But the icon is active (NOT grayed out) on all pages. Tapping it on non docs.google pages results in it not doing anything, but I want it to be grayed out in the first place.
Any ideas on this?

This is a bug in Chrome and so far it's unclear if it's even fixable.
Meanwhile, you can maintain the icon yourself:
make a grayscale version of the icon(s) in any image editor and save them separately.
specify the gray icon in manifest.json:
ManifestV2:
"page_action": {
"default_icon": { "16": "icons/16-gray.png", "32": "icons/32-gray.png" }
}
ManifestV3 uses action instead of page_action
"action": {
"default_icon": { "16": "icons/16-gray.png", "32": "icons/32-gray.png" }
}
set the normal icon using SetIcon action:
chrome.declarativeContent.onPageChanged.removeRules(async () => {
chrome.declarativeContent.onPageChanged.addRules([{
conditions: [
new chrome.declarativeContent.PageStateMatcher({
pageUrl: { hostPrefix: 'docs.google.' },
}),
],
actions: [
new chrome.declarativeContent.SetIcon({
imageData: {
16: await loadImageData('icons/16.png'),
32: await loadImageData('icons/32.png'),
},
}),
chrome.declarativeContent.ShowAction
? new chrome.declarativeContent.ShowAction()
: new chrome.declarativeContent.ShowPageAction(),
],
}]);
});
// SVG icons aren't supported yet
async function loadImageData(url) {
const img = await createImageBitmap(await (await fetch(url)).blob());
const {width: w, height: h} = img;
const canvas = new OffscreenCanvas(w, h);
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, w, h);
return ctx.getImageData(0, 0, w, h);
}

If you use manifest version 2, just need to declare the colored icon in page_action, rather than the gray one.
// manifest.json
"manifest_version": 2
"page_action": {
"default_icon": "icon-color.png"
},
And then the icon will be gray in the URL out of permissions and matches.
You can see the description at pageAction/#manifest.
But in manifest v3, it seems the configures above no works longer.

Chrome documents this behavior in their docs: https://developer.chrome.com/docs/extensions/reference/action/#emulating-pageactions-with-declarativecontent. The following describes how to achieve it in manifest v3.
// background.js
chrome.runtime.onInstalled.addListener(() => {
// disable the action by default
chrome.action.disable();
// remove existing rules so only ours are applied
chrome.declarativeContent.onPageChanged.removeRules(undefined, () => {
// add a custom rule
chrome.declarativeContent.onPageChanged.addRules([
{
// define the rule's conditions
conditions: [
new chrome.declarativeContent.PageStateMatcher({
pageUrl: { hostSuffix: "reddit.com" },
}),
],
// show the action when conditions are met
actions: [new chrome.declarativeContent.ShowAction()],
},
]);
});
});
This will require the declarativeContent permission in manifest.json
{
...
"permissions": ["declarativeContent"]
...
}

Related

Can't inject JavaScript into new tab from popup.js

I'M creating an extension that autofill's forms on different websites. There is a portion of the script that records the actions of the user with event listeners. After the recording is done the user click the test button that will launch a new tab and autofill's the form. I know launching a new tab for testing is not necessary but this function will be used later for another feature.
This is what I got so far. I had started over several times. I tried sending a message to content script. The chrome.windows,create function only launched from the popup script. I started chrome development a week ago. I'm totally confused.
Popup.js
`(async () =\> { const tab = await chrome.tabs.create({url: 'https://www.listyourself.net/ListYourself/listing.jsp'});
const tabId = tab.id; if (!tab.url)
await onTabUrlUpdated(tabId);
await chrome.scripting.executeScript({
target: {tabId},
files: \['test.js'\],
});`
})();`
I think your extension has two problems.
onTabUrlUpdated is not defined.
Execution of chrome.tabs.create causes the popup to lose focus, so the JavaScript is also terminated.
Here is a sample that fixes it.
manifest.json
{
"manifest_version": 3,
"name": "hoge",
"version": "1.0",
"permissions": [
"scripting"
],
"host_permissions": [
"<all_urls>"
],
"background": {
"service_worker": "background.js"
},
"action": {
"default_title": "hoge"
}
}
background.js
chrome.action.onClicked.addListener(() => {
main();
});
const test = () => {
console.log("test");
}
const main = async () => {
const tab = await chrome.tabs.create({ url: "https://nory-soft.web.app/" });
const tabId = tab.id;
if (!tab.url) {
chrome.tabs.onUpdated.addListener((updateTabId, changeInfo) => {
if ((updateTabId == tabId) && (changeInfo.status == "complete")) {
chrome.scripting.executeScript({
target: { tabId },
func: test
});
}
});
}
}

How can I focus a document programmatically from a Chrome extension (to paste content)

What I try to do is simple:
I am making a chrome extension.
This extension has a shortcut (e.g. alt + o).
When I press this shortcut it switches to a tab,
and should paste clipboard content in the page.
Here's the background script that reacts to the shortcut and inject code in page to simulate paste event:
// --- background.js
chrome.commands.onCommand.addListener((command) => {
switch (command) {
case 'switch_tab_and_paste_content':
switchToTabAndPasteContent()
break;
}
})
async function switchToTabAndPasteContent() {
const tab = (await chrome.tabs.query({})).filter((tab) => tab.title.toLowerCase().includes('my page'))[0]
// focus the window
await chrome.windows.update(tab.windowId, { focused: true })
// and focus the tab
await chrome.tabs.update(tab.id, { active: true })
/* inject paste emulation script */
await chrome.scripting.executeScript({
target: { tabId: tab.id },
func: function () {
pasteContent()
}
})
}
pasteContent() in the code above is a function contained in a content script. It's used to simulate paste event (just like a user would use ctrl + v on some page.)
// --- paste.js
async function pasteContent() {
try {
const wizElement = document.querySelector('c-wiz')
const clipboardItems = await navigator.clipboard.read()
const firstItem = clipboardItems[0]
const blob = await firstItem.getType('image/png')
const file = new File([blob], 'image.png', { type: 'image/png' })
const dataTransfer = new DataTransfer()
dataTransfer.items.add(file)
const pasteEvent = new ClipboardEvent('paste', {
bubbles: true,
cancelable: true,
clipboardData: dataTransfer,
})
wizElement.dispatchEvent(pasteEvent)
}
catch (e) { console.log(e) }
}
and here's the simplified manifest.json
{
"name": "app-connector",
"version": 3,
"permissions": [ "tabs", "scripting", "activeTab" ],
"background": {
"service_worker": "background.js"
},
"content_scripts": [{
"matches": [ ... ],
"js": "paste.js"
}],
"commands": {
"switch_tab_and_paste_content": {
"description": "Switch tab"
}
}
}
However when I press the shortcut on my keyboard, the tab is focused but I get this exception
DOMException: Document is not focused.
I know the reason of that exception (it's because when I leave the tab the document loses focus, chrome.tabs.update(tab.id, { active: true }) activates the tab but it's not enough to focus back on the document)
What I want to know is if there is a way to bypass that (a chrome flag, a command-line argument, etc...).
Of course the ideal would be a native solution in the extension ecosystem but I didn't find/can't think of any solution. I tried opening/closing a popup fast to give focus back to the page but I still get this exception (it seems like it won't work unless I click in the page but it defeats the purpose of making an automated routine behind).
am I missing a permission?

Why Chrome DevTools Network shows no response?

I've been trying to fetch some url using Devtools 'copy as fetch' but I can see no response. If I try replay xhr, it will work. It's strange since it doesn't work for this particular site, but if I try 'copy as fetch' on some others it does bring a result.
What I'm trying to do is grab the response body and display it some other way (it's a schedling software, and I'm trying to modify the way it displays the calendar view since it displays everything together).
I have an extension that enables me to modify XMLHttpRequest so I can get the response of any XHR, but since the first async executes before I inject the script, then I'm always missing the first one.
I plan on using chrome webRequest to stop the first one and fetch it again.
manifest.json
{
"name": "jobber",
"version": "2.0",
"description": "Build an Extension!",
"manifest_version": 2,
"permissions": [
"webNavigation",
"webRequest",
"*://secure.name.com/*"
],
"content_scripts": [{
"matches": ["*://secure.name.com/calendar*"],
"js": ["contents.js"],
"run-at": "document_start"
}],
"externally_connectable": {
"matches": ["*://secure.name.com/calendar*"]
},
"background": {
"scripts": ["background.js"]
}
}
contents.js
(function () {
'use strict';
let s = document.createElement("script");
s.textContent = overloadXHR();
document.head.insertBefore(s, document.head.children[0]);
s = document.createElement("script");
s.textContent = displayCalendar;
document.head.insertBefore(s, document.head.children[0]);
})();
function overloadXHR() {
const text = `
console.log(\`overriding: \${Date.now()}\`);
const rawOpen = XMLHttpRequest.prototype.open;
let json = [];
(function(){
XMLHttpRequest.prototype.open = function () {
this.addEventListener("readystatechange", e => {
if (/secure.name.com.calendar.*?calendar=true/i.test(this.responseURL)) {
if ((this.status == 200) && (this.readyState == 4)) {
console.log(this.readyState);
try {
json = JSON.parse(this.response);
window.setTimeout(() => (window.displayCalendar({json}))({json}), 1000);
}
catch (e) { console.log(e); }
}
}
});
rawOpen.apply(this, arguments);
}
}())
`;
return text;
}
function displayCalendar({ json }) {
// do something
}
I tried POST requests too. I could see that they would work, but no response given tho.
original request:
copy as fetch
copy as fetch response
copy as fetch timing

Add PWA to home screen not working on chrome mobile

I recently got into front end developpement to make an interface for a nodejs server hosted on a raspberry pi.
I heard of progressive web app and wanted the user to be able to install it on his phone.
So here are the manifest and the service worker script.
Manifest:
{
"name": "Philips D6330",
"short_name": "Philips D6330",
"description": "A control app for the Philips D6330",
"icons": [
{
"src": "https://192.168.1.26/cdn/favicon.png",
"sizes": "512x512",
"type": "image/png"
}
],
"start_url": "https://192.168.1.26",
"display": "fullscreen",
"orientation": "portrait",
"theme_color": "#333333",
"background_color": "#333333",
"scope": "/"
}
Service Worker:
const CACHE_NAME = 'cache';
const CACHE = [
'/',
'/cdn/offline.html',
'/cdn/style.css',
'/cdn/favicon.png',
'/cdn/linetoB.ttf',
'/cdn/linetoL.ttf',
'/cdn/neon.ttf',
'/cdn/not.png',
'/cdn/next.png',
'/cdn/pause.png',
'/cdn/play.png',
'/cdn/previous.png',
'/cdn/dots.png',
'/cdn/rfid.png'
];
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open(CACHE_NAME)
.then(function(cache) {
return cache.addAll(CACHE)
})
.then(self.skipWaiting())
)
})
self.addEventListener('fetch', function(event) {
if (event.request.mode === 'navigate') {
event.respondWith(
fetch(event.request)
.catch(() => {
return caches.open(CACHE_NAME)
.then((cache) => {
return cache.match('/cdn/offline.html');
})
})
);
}
else {
event.respondWith(
fetch(event.request)
.catch(() => {
return caches.open(CACHE_NAME)
.then((cache) => {
return cache.match(event.request)
})
})
);
}
})
self.addEventListener('activate', function(event) {
event.waitUntil(
caches.keys()
.then((keyList) => {
return Promise.all(keyList.map((key) => {
if (key !== CACHE_NAME) {
console.log('[ServiceWorker] Removing old cache', key)
return caches.delete(key)
}
}))
})
.then(() => self.clients.claim())
)
})
I think also relevant to tell you that all of this happens on my local network so my node server uses https with a self-signed certificate made using this tutorial : https://medium.com/#tbusser/creating-a-browser-trusted-self-signed-ssl-certificate-2709ce43fd15
But even tho it's a self-signed certificate, the service worker seem to registers well on firefox and chrome, as it stores the files and displays the offline page when offline.
Here is my problem :
when I want to install it from the desktop version of chrome I can but not chrome mobile (or samsung internet)...
here is the piece of code i use to make it instalable :
<script defer>
window.addEventListener('beforeinstallprompt', (event) => {
console.log('👍', 'beforeinstallprompt', event);
// Stash the event so it can be triggered later.
window.deferredPrompt = event;
});
butInstall.addEventListener('click', () => {
console.log('👍', 'butInstall-clicked');
const promptEvent = window.deferredPrompt;
if (!promptEvent) {
// The deferred prompt isn't available.
return;
}
// Show the install prompt.
promptEvent.prompt();
// Log the result
promptEvent.userChoice.then((result) => {
console.log('👍', 'userChoice', result);
// Reset the deferred prompt variable, since
// prompt() can only be called once.
window.deferredPrompt = null;
});
});
window.addEventListener('appinstalled', (event) => {
console.log('👍', 'appinstalled', event);
});
</script>
It comes from here https://web.dev/codelab-make-installable/
Here is a screenshot of the before install prompt event with the lighthouse report if it can help (by the way the plus sign in the url shows it's working):
console log infos
But on mobile the plus sign doesn't show up and nothing happens when I click the button... And as I don't have acces to the console I can't see any errors...
------ Edit ------
After using alert to log what the console says, I think the problem comes from the service worker does register well because I get this :
"ServiceWorker registration failed: SecurityError: Failed to register a ServiceWorker for scope ('https://192.168.1.26/') with script ('https://192.168.1.26/sw.js'): An SSL certificate error occurred when fetching the script.".
Is there a way to make the browser trust my self-singed certificate ?
Any help, suggestion or comment is welcome ^^
Its a .webmanifest and you dont match the criteria. You need an 512x512 AND an 192x192 icon.
As start_url i would just use /?source=pwa
"icons": [
{
"src": "icon.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "big_icon.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": "/?source=pwa",
After that you need to link your webmanifest with <link>
<link rel="manifest" href="/manifest.webmanifest">
Check if the paths are all correct.
Here is my own little "library" to abstract things: https://github.com/ilijanovic/serviceHelper (its under development tho)
If you met all criteria, then you can make your app installable
Here its how it works with my library. You instantiate the class and pass the path to the service worker.
var sh = new ServiceHelper("./sw.js");
sh.init().then(response => {
console.log("Initialized");
})
sh.on("notinstalled", (e) => {
console.log("App is not installed");
//do some stuff. For example enable some button
//button.classList.remove("disabled");
})
butInstall.addEventListener('click', () => {
sh.installApp().then(e => {
// e = user choice event
console.log(e);
}).catch(err => {
// error if the app is already installed
console.log(err);
})

how to change the popup content based on the current url extension page action

am new in creating chrome extensions, I'm developing an extension page action it works in certain urls, I would like to put different text in the popup for each url, i can do it? please help me.
My background.js is thus
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
if (~tab.url.indexOf('url1.com.br')) {
chrome.pageAction.show(tabId);
}
if (~tab.url.indexOf('url2.com.br')) {
chrome.pageAction.show(tabId);
}
});
OK. First of all, to show page_action icon on specific URLs you can use declarative content.
// 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 on a specific URL
conditions: [
new chrome.declarativeContent.PageStateMatcher({
pageUrl: { urlContains: 'url1.com.br' },
}),
new chrome.declarativeContent.PageStateMatcher({
pageUrl: { urlContains: 'url2.com.br' }
})
],
// And shows the extension's page action.
actions: [ new chrome.declarativeContent.ShowPageAction() ]
}
]);
});
});
Don't forget adding a permission for declarative content in manifest.json. Another thing is different texts for different urls.
popup.js
chrome.tabs.query({'active': true, 'currentWindow': true}, function (tabs) {
var dynamicText = "You are here;"+ tabs[0].url;
document.getElementById("textbox").value = dynamicText ;
});
This sample gets the currentWindow's URL and insert it into the element that has textbox id. I hope samples are enough to solve the problem.