adding items to the context menu on the extension icon [duplicate] - google-chrome-extension

A G Chrome extension can have a 'browser action'. Usually the ext developer displays the options when you click on it, meaning every action requires 2 clicks, even the default 99%-of-the-time action. Chrome itself adds a context menu with a few options: disable ext, uninstall ext, go to ext homepage etc.
Can I as ext developer add items to that context menu, so I can keep my 1-click-action under the normal/left/primary mouse click?
I know of chrome.contextMenus but that's only for context menus in the page (see property 'contexts').
I can't find it in the Chrome Extension dev guide, but you know more than I.

It is now possible, AdBlock chrome extensions has it. Below is working example of "context menu in browser action".
manifest.json:
{
"name": "Custom context menu in browser action",
"version": "1",
"manifest_version": 2,
"background": {
"scripts": ["background.js"]
},
"browser_action": {
"default_title": "Some tooltip",
"default_popup": "popup.html"
},
"permissions": [
"contextMenus"
],
"icons": {
"16": "icon16.png"
}
}
background.js:
chrome.contextMenus.removeAll();
chrome.contextMenus.create({
title: "first",
contexts: ["browser_action"],
onclick: function() {
alert('first');
}
});
Note that if you use an Event page, you cannot use the onclick attribute; you'll need to add a listener to chrome.contextMenus.onClicked instead.

Example (almost patttern)
It also provides a workaround for using a simple onclick listeners (here short property “act”), for now if you use the “Event page” you can not use native onclick
const menuA = [
{ id: 'ItemF', act: (info, tab) => { console.log('Clicked ItemF', info, tab, info.menuItemId); alert('Clicked ItemF') } },
{ id: 'ItemG', act: (info, tab) => { console.log('Clicked ItemG', info, tab, info.menuItemId); alert('Clicked ItemG') } },
{ id: 'ItemH', act: (info, tab) => { console.log('Clicked ItemH', info, tab, info.menuItemId); alert('Clicked ItemH') } },
{ id: 'ItemI', act: (info, tab) => { console.log('Clicked ItemI', info, tab, info.menuItemId); alert('Clicked ItemI') } },
];
const menuB = [
{ id: 'ItemJ', act: (info, tab) => { console.log('Clicked ItemJ', info, tab, info.menuItemId); alert('Clicked ItemJ') } },
{ id: 'ItemK', act: (info, tab) => { console.log('Clicked ItemK', info, tab, info.menuItemId); alert('Clicked ItemK') } },
{ id: 'ItemL', act: (info, tab) => { console.log('Clicked ItemL', info, tab, info.menuItemId); alert('Clicked ItemL') } },
{ id: 'ItemM', act: (info, tab) => { console.log('Clicked ItemM', info, tab, info.menuItemId); alert('Clicked ItemM') } },
];
const rootMenu = [
//
// In real practice you must read chrome.contextMenus.ACTION_MENU_TOP_LEVEL_LIMIT
//
{ id: 'ItemA', act: (info, tab) => { console.log('Clicked ItemA', info, tab, info.menuItemId); alert('Clicked ItemA') }, menu: menuA },
{ id: 'ItemB', act: (info, tab) => { console.log('Clicked ItemB', info, tab, info.menuItemId); alert('Clicked ItemB') }, menu: menuB },
{ id: 'ItemC', act: (info, tab) => { console.log('Clicked ItemC', info, tab, info.menuItemId); alert('Clicked ItemC') } },
{ id: 'ItemD', act: (info, tab) => { console.log('Clicked ItemD', info, tab, info.menuItemId); alert('Clicked ItemD') } },
{ id: 'ItemE', act: (info, tab) => { console.log('Clicked ItemE', info, tab, info.menuItemId); alert('Clicked ItemE') } },
];
const listeners = {};
const contexts = ['browser_action'];
const addMenu = (menu, root = null) => {
for (let item of menu) {
let {id, menu, act} = item;
chrome.contextMenus.create({
id: id,
title: chrome.i18n.getMessage(id),
contexts: contexts,
parentId: root
});
if (act) {
listeners[id] = act;
}
if (menu) {
addMenu(menu, id);
}
}
};
addMenu(rootMenu);
chrome.contextMenus.onClicked.addListener((info, tab) => {
console.log('Activate „chrome.contextMenus -> onClicked Listener“', info, tab);
listeners[info.menuItemId] (info, tab);
});
See some example of «chrome extension tree context menu pattern»

It is not possible to add any custom entries to the context menu.
You can, however, dynamically assign a panel to the button with chrome.browserAction.setPopup. You can use an options page to allow the user to choose their preferred option (single-click action, or two-clicks & multiple actions). The fact that the options page is just two clicks away from the button is also quite nice.
Here's sample code to illustrate the concept of toggling between panel and single-click.
background.js (used in your event / background page):
chrome.browserAction.onClicked.addListener(function() {
// Only called when there's no popup.
alert('Next time you will see a popup again.');
chrome.browserAction.setPopup({
popup: 'popup.html'
});
});
popup.html, just for the demo (use CSS to make it look better):
<button>Click</button>
<script src="popup.js"></script>
popup.js, just for the demo. JavaScript has to be placed in a separate file because of the CSP.
document.querySelector('button').onclick = function() {
chrome.browserAction.setPopup({
popup: '' // Empty string = no popup
});
alert('Next time, you will not see the popup.');
// Close panel
window.close();
};
As you can see in this example, the chrome.browserAction.setPopup is also available in a popup page.
PS. manifest.json, so you can copy-paste the example and play with this answer.
{
"name": "Test badge - minimal example",
"version": "1",
"manifest_version": 2,
"background": {
"scripts": ["background.js"]
},
"browser_action": {
"default_title": "Some tooltip"
}
}

Related

How can I get the HTML source code of the website the user is currently on using the tabId and the chrome extension API?

This is my manifest.json:
{
"manifest_version":2,
"name": "Name",
"description": "Description",
"version":"1.0",
"browser_action":
{
"default_icon": "icon.png",
"default_popup": "popup.html"
},
"background": {
"scripts": ["background.js"],
"persistent": false
},
"permissions": [
"tabs"
]
}
Here is my background.js:
chrome.tabs.onUpdated.addListener( (tabId, changeInfo, tab) => {
if (typeof changeInfo.url !== "undefined" && changeInfo.url.startsWith("chrome://newtab/") === false){
alert(changeInfo.url);
//somehow get the HTML of the page given tabId and store as string
}
});
It will be greatly appreciated if someone can help me out here. As I specified before, I need the exact HTML of the page as the user sees it.
Add the sites you want to be able to read (or all sites "*://*/*") to permissions in manifest.json, then use chrome.tabs.executeScript to extract its HTML as a string.
DOM elements or class objects like Map can't be extracted. Only JSON-compatible types such as strings, numbers, boolean, null and array/objects of these types can be extracted.
The popup is a separate window so it has its own separate devtools: right-click inside the popup and select "inspect" in the menu to see console.log messages.
chrome.tabs.onUpdated.addListener(async (tabId, info, tab) => {
if (info.url && !info.url.startsWith('chrome')) {
const html = await getHtml(tabId);
console.log(html);
}
});
function getHtml(tabId) {
return new Promise((resolve, reject) => {
chrome.tabs.executeScript(tabId, {
code: 'document.documentElement.outerHTML',
}, results =>
chrome.runtime.lastError
? reject(new Error(chrome.runtime.lastError.message))
: resolve(results[0]));
});
}

chrome.runtime.onMessage call multiple time

I create a extension. When user click on extension icon it will send message to the content script and then content script again call a function. In side that function it will send message to the background script. I face some strange behavior chrome.runtime.onMessage.addListener() in background script execute multiple times.
manifest.json
{
"manifest_version": 2,
"name": "Reportar",
"version": "1.0",
"description": "Loreipsum.",
"background": {
"scripts": ["bootstrap.js"],
"persistent": false
},
"browser_action": {
"default_icon": "img/icon48.png",
"default_title": "Gitlab Issue"
},
"web_accessible_resources": [
"templates/*.html"
],
"content_scripts": [{
"all_frames": false,
"css": ["content_style.css"],
"js": ["content_script.js"],
"matches": ["http://*/*", "*/*"]
}],
"icons": {
"16": "img/icon20.png",
"48": "img/icon48.png",
"128": "img/icon128.png"
},
"permissions": [
"tabs",
"activeTab",
"<all_urls>",
"storage"
]
}
background.js
function clickOnIssue() {
chrome.tabs.query({active: true, currentWindow: true}, function (tabs) {
console.log('Going to send message to content script that user click on browserAction icon');
chrome.tabs.sendMessage(tabs[0].id, {id: 111});
});
}
chrome.tabs.onUpdated.addListener(function (id, info, tab) {
if (info.status === 'complete') {
chrome.browserAction.onClicked.removeListener(clickOnIssue);
chrome.browserAction.onClicked.addListener(clickOnIssue);
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
var _t = new Date().getTime();
console.log("Request received for getProjectList(" + _t + ")");
sendResponse({t: _t});
return true;
});
}
});
content_script.js
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
console.log(request);
//sendResponse({msg: 'received'});
chrome.runtime.sendMessage({action: 'submitSettings'}, function (resp) {
console.log('Received the message that user click on browserAction icon');
updateProjectDropDown();
});
return true;
});
function updateProjectDropDown() {
console.log('Request dispatch for getProjectList');
chrome.runtime.sendMessage({action: 'getProjectList'}, function (resp) {
console.log(resp.t + ': bootstrap.js JS received the message');
});
}
This is browser's console
This is backgound js console
Edit: Add manifest file
I think bellow code will solve your issue
chrome.runtime.onInstalled is run once so your listeners will not bind multiple times.
chrome.runtime.onInstalled.addListener(function (details) {
chrome.browserAction.onClicked.addListener(clickOnIssue);
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
//TODO: your code
});
});

Chrome extension : Is it possible to keep extension open on reload?

I'm building an google extension that inserts html in page and shows a menu on browser action icon click and I don't find way to keep my extension open when I reload the page. So on every reload, we have to active it again from Browser Action icon.
Below the manifest file
{
"background": {
"scripts": ["background.js"]
},
"browser_action": {
"default_icon": "img/icone.png",
"default_title": "show menu"
},
"icons" : {
"128" : "img/icone_128.png",
"48" : "img/icone_48.png",
"32" : "img/icone_32.png",
"24" : "img/icone_24.png",
"16" : "img/icone_16.png"
},
"manifest_version": 2,
"name": "p|layer",
"version": "1.0.4",
"content_scripts": [
{
"matches": [ "<all_urls>"],
"css":["css/grid.css", "css/font-awesome.min.css"],
"js":["js/jquery-1.11.1.min.js","js/jquery-ui.js", "js/jquery.nicefileinput.min.js"]
}
],
"web_accessible_resources": [
"fonts/fontawesome-webfont.woff"
],
"permissions": [ "activeTab"]
}
script (background.js) injecting contentscript
chrome.browserAction.onClicked.addListener(function (tab) { //Fired when User Clicks ICON
chrome.tabs.executeScript(tab.id,
{"file": "js/contentscript.js"},
function () { // Execute your code
console.log("Script Executed .. "); // Notification on Completion
});
chrome.tabs.insertCSS(tab.id, {file: "css/grid.css"});
chrome.tabs.insertCSS(tab.id, {file: "css/font-awesome.min.css"});
chrome.tabs.insertCSS(tab.id, {file: "css/slider.css"});
});
any help will be appreciated
So, from the comments the problem was inferred: your button click activates your content script, but a page reload clears it. Assuming you want the button click to act as a toggle:
1) Always inject the content script / CSS, but don't show the UI immediately:
"content_scripts": [
{
"matches": [ "<all_urls>"],
"css": ["css/grid.css", "css/font-awesome.min.css", "css/slider.css"],
"js": [
"js/jquery-1.11.1.min.js", "js/jquery-ui.js",
"js/jquery.nicefileinput.min.js", "js/contentscript.js"
]
}
],
2) Keep track of "activated" tabs:
var activeTabs = {};
chrome.browserAction.onClicked.addListener( function(tab){
var active;
if(activeTabs[tab.id]){
delete activeTabs[tab.id];
active = false;
} else {
activeTabs[tab.id] = true;
active = true;
}
/* (part 3) */
});
chrome.tabs.onRemoved.addListener( function(tabId){
delete activeTabs[tabId];
});
chrome.tabs.onReplaced.addListener( function(newTabId, oldTabId){
if(activeTabs[oldTabId]) activeTabs[newTabId] = true;
delete activeTabs[oldTabId];
});
3) Use messaging to show/hide UI:
Content script:
chrome.runtime.onMessage.addListener( function(message. sender, sendResponse){
if(message.showUI) showUI();
if(message.hideUI) hideUI();
});
Background script:
chrome.browserAction.onClicked.addListener( function (tab) {
var active;
/* (part 2) */
if(active) {
chrome.tabs.sendMessage(tab.id, {showUI: true});
} else {
chrome.tabs.sendMessage(tab.id, {hideUI: true});
}
});
Additional robustness can be added for the cases of extension reload, but that's the gist of it.

chrome extension notification with tabs

I try make deskotp notification used chrome extension.
I would it can work like this - when user visit recomended page it will be show.
background.js
function show() {
var notification = window.webkitNotifications.createNotification(
'48.png',
'YOUR VISIT PAGE http://stackoverflow.com/!'
);
notification.show();
}
// Conditionally initialize the options.
if (!localStorage.isInitialized) {
localStorage.isActivated = true; // The display activation.
localStorage.frequency = 1; // The display frequency, in minutes.
localStorage.isInitialized = true; // The option initialization.
}
function checkForValidUrl(tabId, changeInfo, tab) {
if (tab.url.indexOf('stackoverflow') > -1) {
if (window.webkitNotifications) {
if (JSON.parse(localStorage.isActivated)) {
show();
}
}
}
}
chrome.tabs.onUpdated.addListener(checkForValidUrl);
manifest.json
{
"name": "YouVisit",
"version": "0.1",
"description":
"Show desktop notification when user visit page",
"icons": {"48": "48.png"},
"permissions": [
"notifications",
"tabs"
],
"background": { "scripts": ["background.js"] },
"manifest_version": 2,
"web_accessible_resources": [
"48.png"
]
}
any ideas why this code doesn't work? can someone give me some literature to make it proper?
You failed to provide the proper arguments for the createNotification() function:
According to the docs:
// Create a simple text notification:
var notification = webkitNotifications.createNotification(
'48.png', // icon url - can be relative
'Hello!', // notification title
'Lorem ipsum...' // notification body text
);

How to allow an extension for two domains only?

I've written a google chrome extension. It's okay and works now but I want the extension to be usebale only on two domains because it's written for these two websites only and is useless for others. There is a context menu only. For now it hasn't even popup, or action button in the top right corner (hidden by default). How can achieve this?
My current manifest.json:
{
"manifest_version": 2,
"background": {
"scripts": ["scripts/jquery.min.js", "scripts/background.js"]
},
"name": "Export Entries",
"description": "some descriptions here",
"version": "1.0",
"icons": {
"16": "images/logo.png",
"48": "images/logo.png",
"128": "images/logo.png"
},
"permissions": ["downloads", "tabs", "contextMenus", "http://my-own-domain-accessed-via-ajax-for-creating-some-files-there.com/*"],
"content_scripts": [{
"matches": ["*://allowed-domain1.com/*", "*://allowed-domain2.com/*"],
"css": ["styles/style.css"],
"js": ["scripts/jquery.min.js", "scripts/popup.js", "scripts/background.js"],
"run_at": "document_end"
}],
"web_accessible_resources": [
"images/logo.png"
]
}
As I understand the extension cannot be disabled absolutely, its process will run in background again. But it's not a problem. I just want to not display the context menu item on other websites.
background.js creates the context menu item and handles its click event:
function exportEntries(info, tab) {
if (info['linkUrl'].indexOf('domain1.com/user/') > -1) {
var user = info['linkUrl'].substr('27');
} else {
var user = null; // export all entries from this topic
}
$.ajax({
url: 'http://my-own-domain-which-creates-the-file.eu/exportEntries/create.php',
method: 'POST',
data: {
topic: tab.url,
user: user
}
}).done(function(url) {
forceDownload(url);
});
}
function forceDownload(url) {
var filename = url.replace(/^.*\/|\.[^.]*$/g, '');
chrome.downloads.download({
url: url,
saveAs: true
}, // options array
function(id) {
// callback function
}
);
};
document.addEventListener('DOMContentLoaded', function() {
chrome.contextMenus.create({
'title': 'Export Entries',
'contexts': ['link'],
'onclick': function(info, tab) {
exportEntries(info, tab);
}
});
});
create.php is on my own domain. It just gets the current page's URL and the user's nickname. Then export all entries from the given topic (i.e. page URL) for the given user, creates a file (.txt, .pdf etc.) and sends back url for downloading the file.
popup.html, popup.js, css file and other stuff is not used for now.
Remove all of your content scripts, they're useless, because the chrome.contextMenus API can only be used on the background page.
To limit the context menu entry to certain domains, pecify the documentUrlPatterns key when you create the context menu using the chrome.contextMenus.create:
chrome.contextMenus.create({
'title': 'Export Entries',
'contexts': ['link'],
'onclick': function(info, tab) {
exportEntries(info, tab);
},
'documentUrlPatterns': [
'*://allowed-domain1.com/*',
'*://allowed-domain2.com/*'
]
});
According to the content scripts documentation:
"If you want to inject the code only sometimes, use the permissions field instead, as described in Programmatic injection."
So instead of
"content_scripts": [
{
"matches": [ "http://allowed-domain.com" ]
}
],
use
permissions: [
"tabs", "http://allowed-domain.com"
],

Resources