Chrome Extension context menu, differentiate image and link event - google-chrome-extension

In my chrome extension I'm adding two context items "Get link" and "Get Image". The main difference being when setting them both up they have the "context" of link and image respectively. But when right clicking on an image that is acting as a link you get the option of both:
when either of those are clicked the data that comes into the listener seems to be identical, I need to be able to differentiate the two to know if the context is that of an image or a link to handle them differently. Here is my code:
chrome.runtime.onInstalled.addListener(function() {
var context = "image";
var title = "Copy Image";
var id = chrome.contextMenus.create({"title": title, "contexts":[context],
"id": "context" + context});
});
chrome.runtime.onInstalled.addListener(function() {
var context = "link";
var title = "Copy link";
var id = chrome.contextMenus.create({"title": title, "contexts":[context],
"id": "context" + context});
});
chrome.contextMenus.onClicked.addListener(onClickHandler);
function onClickHandler(info, tab) {
chrome.tabs.query({active: true, currentWindow: true}, function(tabs){
chrome.tabs.sendMessage(tabs[0].id, {action: "imageAdded", subject: info.srcUrl}, function(response) {
});

If you want know which menu item was clicked, you can get the id value of the clicked context menu item in the menuItemId property of the object passed into the onClicked handler:
function onClickHandler(info, tab) {
console.log(info.menuItemId);
//...
}

Take a look at Parameter of onClicked callback, you could differentiate the image/link via mediaType
One of 'image', 'video', or 'audio' if the context menu was activated on one of these types of elements.

Related

Chrome extension for selected text which matches a specific form [duplicate]

I am trying to create entries on the Chrome context menu based on what is selected.
I found several questions about this on Stackoverflow, and for all of them the answer is: use a content script with a "mousedown" listener that looks at the current selection and creates the Context Menu.
I implemented this, but it does not always work. Sometimes all the log messages say that the context menu was modified as I wanted, but the context menu that appears is not updated.
Based on this I suspected it was a race condition: sometimes chrome starts rendering the context menu before the code ran completely.
I tried adding a eventListener to "contextmenu" and "mouseup". The later triggers when the user selects the text with the mouse, so it changes the contextmenu much before it appears (even seconds). Even with this technique, I still see the same error happening!
This happens very often in Chrome 22.0.1229.94 (Mac), occasionally in Chromium 20.0.1132.47 (linux) and it did not happen in 2 minutes trying on Windows (Chrome 22.0.1229.94).
What is happening exactly? How can I fix that? Is there any other workaround?
Here is a simplified version of my code (not so simple because I am keeping the log messages):
manifest.json:
{
"name": "Test",
"version": "0.1",
"permissions": ["contextMenus"],
"content_scripts": [{
"matches": ["http://*/*", "https://*/*"],
"js": ["content_script.js"]
}],
"background": {
"scripts": ["background.js"]
},
"manifest_version": 2
}
content_script.js
function loadContextMenu() {
var selection = window.getSelection().toString().trim();
chrome.extension.sendMessage({request: 'loadContextMenu', selection: selection}, function (response) {
console.log('sendMessage callback');
});
}
document.addEventListener('mousedown', function(event){
if (event.button == 2) {
loadContextMenu();
}
}, true);
background.js
function SelectionType(str) {
if (str.match("^[0-9]+$"))
return "number";
else if (str.match("^[a-z]+$"))
return "lowercase string";
else
return "other";
}
chrome.extension.onMessage.addListener(function(msg, sender, sendResponse) {
console.log("msg.request = " + msg.request);
if (msg.request == "loadContextMenu") {
var type = SelectionType(msg.selection);
console.log("selection = " + msg.selection + ", type = " + type);
if (type == "number" || type == "lowercase string") {
console.log("Creating context menu with title = " + type);
chrome.contextMenus.removeAll(function() {
console.log("contextMenus.removeAll callback");
chrome.contextMenus.create(
{"title": type,
"contexts": ["selection"],
"onclick": function(info, tab) {alert(1);}},
function() {
console.log("ContextMenu.create callback! Error? " + chrome.extension.lastError);});
});
} else {
console.log("Removing context menu")
chrome.contextMenus.removeAll(function() {
console.log("contextMenus.removeAll callback");
});
}
console.log("handling message 'loadContextMenu' done.");
}
sendResponse({});
});
The contextMenus API is used to define context menu entries. It does not need to be called right before a context menu is opened. So, instead of creating the entries on the contextmenu event, use the selectionchange event to continuously update the contextmenu entry.
I will show a simple example which just displays the selected text in the context menu entry, to show that the entries are synchronized well.
Use this content script:
document.addEventListener('selectionchange', function() {
var selection = window.getSelection().toString().trim();
chrome.runtime.sendMessage({
request: 'updateContextMenu',
selection: selection
});
});
At the background, we're going to create the contextmenu entry only once. After that, we update the contextmenu item (using the ID which we get from chrome.contextMenus.create).
When the selection is empty, we remove the context menu entry if needed.
// ID to manage the context menu entry
var cmid;
var cm_clickHandler = function(clickData, tab) {
alert('Selected ' + clickData.selectionText + ' in ' + tab.url);
};
chrome.runtime.onMessage.addListener(function(msg, sender, sendResponse) {
if (msg.request === 'updateContextMenu') {
var type = msg.selection;
if (type == '') {
// Remove the context menu entry
if (cmid != null) {
chrome.contextMenus.remove(cmid);
cmid = null; // Invalidate entry now to avoid race conditions
} // else: No contextmenu ID, so nothing to remove
} else { // Add/update context menu entry
var options = {
title: type,
contexts: ['selection'],
onclick: cm_clickHandler
};
if (cmid != null) {
chrome.contextMenus.update(cmid, options);
} else {
// Create new menu, and remember the ID
cmid = chrome.contextMenus.create(options);
}
}
}
});
To keep this example simple, I assumed that there's only one context menu entry. If you want to support more entries, create an array or hash to store the IDs.
Tips
Optimization - To reduce the number of chrome.contextMenus API calls, cache the relevant values of the parameters. Then, use a simple === comparison to check whether the contextMenu item need to be created/updated.
Debugging - All chrome.contextMenus methods are asynchronous. To debug your code, pass a callback function to the .create, .remove or .update methods.
MDN doc for menus.create(), 'title' param
You can use "%s" in the string. If you do this in a menu item, and some text is selected in the page when the menu is shown, then the selected text will be interpolated into the title.
https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/menus/create
Thus
browser.contextMenus.create({
id: 'menu-search',
title: "Search '%s'", // selected text as %s
contexts: ['selection'], // show only if selection exist
})

dialog.showMessageBoxSync(null, options) getting hidden

I have an Electron app. If I use dialog.showmessageBoxSync normally it has to wait for user input. The options are: close, cancel or ok.
It is working fine but if I click outside of the dialog box (anywhere inside my app) then this message box hidden. I'm unable to click on any option.
How can I make the message box stay focused until the user chooses a button to click or closes the dialog box? The user should be forced to respond to the message box before continuing to work in the rest of the app.
dialog.showMessageBoxSync({
type: "info",
buttons: ["Ok,", "Cancel"],
defaultId: 0,
title: "",
message:""
cancelId: 1,
})
I'd suggest passing in a parent window
From the docs
The browserWindow argument allows the dialog to attach itself to a
parent window, making it modal.
const iconPath = upath.toUnix(upath.join(__dirname, "app", "assets", "icon.png"));
const dialogIcon = nativeImage.createFromPath(iconPath);
var options = {
type: 'question',
buttons: ['&Yes', '&No'],
title: 'Confirm Quit',
icon: dialogIcon,
normalizeAccessKeys: true,
message: 'Do you really want to close the application?'
};
const win = BrowserWindow.getFocusedWindow();
dialog.showMessageBox(win, options)
.then((choice) => {
if (choice.response === 0) {
quitApplication();
}
}).catch(err => {
console.log('ERROR', err);
});

How to put selection text in chrome.contextMenus?

How can I add selection text in context.Menus?
I want to create a Chrome extension which will work similarly to the right-click search function in Google Chrome (i.e. right click on selected text -> "Search 'selection text')
I made a preview
I assume this is something with chrome.contextMenus.update but i don't know how to make it work
background.js:
chrome.runtime.onInstalled.addListener(function () {
var context = "selection";
var title = "Search";
var id = chrome.contextMenus.create({
"title": title,
"contexts": [context],
"id": "context" + context
});
});
// add click event
chrome.contextMenus.onClicked.addListener(onClickHandler);
// The onClicked callback function.
function onClickHandler(info, tab) {
var sText = info.selectionText;
var url = "https://www.google.com/search?source=hp&q=" + encodeURIComponent(sText);
window.open(url, '_blank');
};

Multiple Tab Chrome Extension Issue

I've created a basic extension that searches Google if the URL/HTML content fulfill certain requirements. It works for the most part, but fails miserably when there are multiple instances of the extension. For example, if I load tab A and then tab B, but click on the page action for tab A, I will be directed to a search of tab B's content.
I don't know how to silo the script to each tab, so that clicking tab A's page action will always result in a search for tab A stuff. How can that be done? I'd appreciate your suggestions!
background.js
title = "";
luckySearchURL = "http://www.google.com/search?btnI=I%27m+Feeling+Lucky&ie=UTF-8&oe=UTF-8&q=";
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if (request.title != "") {
title = request.title;
sendResponse({confirm: "WE GOT IT."});
}
});
chrome.tabs.onUpdated.addListener(function(tabId, change, tab) {
if (change.status === "complete" && title !== "") {
chrome.pageAction.show(tabId);
}
});
chrome.pageAction.onClicked.addListener(function(tab) {
chrome.tabs.create({url: luckySearchURL + title})
})
contentscript.js
function getSearchContent() {
url = document.URL;
if (url.indexOf("example.com/") > -1)
return "example";
}
if (window === top) {
content = getSearchContent();
if (content !== null) {
chrome.runtime.sendMessage({title: content}, function(response) {
console.log(response.confirm); })
};
}
You could do something like store the title with its associated tabId, that way when you click on the pageAction it uses the correct title. The changes would be just these:
background.js
title= [];
[...]
chrome.runtime.onMessage.addListener(function(request,sender,sendResponse){
if (request.title != "") {
title.push({tabId:sender.tab.id, title:request.title});
sendResponse({confirm: "WE GOT IT."});
}
});
[...]
chrome.pageAction.onClicked.addListener(function(tab) {
title.forEach(function(v,i,a){
if(v.tabId == tab.id){
chrome.tabs.create({url: luckySearchURL + v.title});
// Here I am going to remove it from the array because otherwise the
// array would grow without bounds, but it would be better to remove
// it when the tab is closed so that you can use the pageAction more
// than once.
a.splice(i,1);
}
});
});
You're facing this issue because of window === top. So your title variable gets its value from the last opened tab. So if B is opened after A, title gets its value from B. Try this: Detect Tab Id which called the script, fetch the url of that tab, which then becomes your title variable. As below:
chrome.pageAction.onClicked.addListener(function(tab) {
chrome.tabs.query({active:true},function(tabs){
//this function gets tabs details of the active tab, the tab that clicked the pageAction
var urltab = tabs[0].url;
//get the url of the tab that called this script - in your case, tab A or B.
chrome.tabs.create({url: urltab + title});
});
});

Google Chrome extensions: popup windows handling

My extension should open a popup window (a normal popup window, not default extensions popup) with a following conditions:
only one window could be opened in the moment,
I want to save window size and position in localStorage, and restore them on next open.
How to do so?
I tried:
chrome.browserAction.onClicked.addListener(function() {
// 1. In this case, variable win is a window object. But it's empty. There
// is no properties/methods to operate with.
var win = window.open('http://example.com','windowname');
chrome.windows.create({
'url': 'http://example.com',
'type': 'popup'
}, function(win) {
// 2. In this case, variable win has some properties, but they are static
// and won't change on window resize/close.
});
});
Any ideas?
I think, you can do this with window.open().
1-only one window could be opened in the moment,
If you give same name, window.open(),
if there aren't any exist windows with same name, It will create a new one
else if there is a openned window with the same name, it will just refresh it.
2- I want to save window size and position in localStorage, and restore them on next open.
Yes, you can do this by attaching window.onresize event,
Here is a basic example,
chrome.browserAction.onClicked.addListener(function() {
var wOpt = getWindowDefaultOptions(); // options from localStorage
var win = window.open("http://www.example.com", "myscriptpopup", "width="+wOpt.width+", height="+wOpt.height+", top="+wOpt.top+", left="+wOpt.left);
win.onresize = function() {
var newOptions = {};
newOptions.width = this.innerWidth;
newOptions.height = this.innerHeight;
newOptions.top = this.screenY;
newOptions.left = this.screenX;
setWindowDefaultOptions(newOptions); //save Options to localStorage
}
});
//Default Options For Window
var winOptions = {
"width" : "200",
"height" : "100",
"top" : "50",
"left" : "50"
};

Resources