Bear with me please if this sounds too simple.
I am writing a chrome extension that once the user right clicks on an object in the page, the context menu appears and there he has an option to get the ID the of clicked div-class + rate it.
I've been working on this for a few days and managed to add the extra buttons to the context menu. Once any of those buttons is clicked, the page's source is logged. Could have taken me less time to do that but it's my first time writing in javascript and I got stuck along the way.
Anyways I don't want the whole html code of the page, but the clicked div-class. I after googling it turns out that the best way to do this is by using jquery's "html" function instead of pure javascript. Problem is that it doesn't work for me.
Here is my code:
BackgroundPage (everything seems to be working fine here)
function genericOnClick(tab)
{
console.log(tab.pageUrl);
chrome.tabs.getSelected(null, function(tab)
{
chrome.tabs.sendMessage(tab.id, {action: "getSource"}, function(source) {
console.log(source);
});
});
}
// Create a parent item and two children.
var parent1 = chrome.contextMenus.create({"title": "Rank Trigger", "contexts":["all"]});
var child1 = chrome.contextMenus.create
(
{"title": "Rank 1", "contexts":["all"], "parentId": parent1, "onclick": genericOnClick}
);
var child2 = chrome.contextMenus.create
(
{"title": "Rank 2", "contexts":["all"], "parentId": parent1, "onclick": genericOnClick}
);
var child3 = chrome.contextMenus.create
(
{"title": "Rank 3", "contexts":["all"], "parentId": parent1, "onclick": genericOnClick}
);
ContentPage: (problem here)
chrome.extension.onMessage.addListener(function(request, sender, callback)
{
if (request.action == "getSource")
{
// callback(document.documentElement.outerHTML); //here i can get the page's whole source, I only want the clicked div class
callback($('div class).html(); <--- something wrong here
}
});
Manifest file:
{
"name": "x",
"description": "x",
"version": "0.1",
"permissions": ["contextMenus", "tabs"],
"content_scripts":
[
{
"matches": ["http://*/*","https://*/*"],
"js": ["jquery.js", "content.js"],
"run_at": "document_end"
}
],
"background":
{
"scripts": ["background.js"]
},
"manifest_version": 2
}
Any ideas? Again I know that my questions seems simple but I am self teaching myself all this, and for some reason I finding web-programming a little harder than application programming.
Much thanks.
EDIT: Just for clarification the html source I want is this:
<div class="story reviewed-product-story" data-story-id="488481648"....</div>.
I want this div class, so that I can parse it to get the data-story-id. If there is a way to get the ID without getting the div class first then that would also work (and maybe even preferable)
EDIT: Thanks to Adeneo, I can now get the ID of the first div-class in the page's source, but not the clicked one. A step forward on what I had but not exactly what I need.
Maybe I have to get the source of clicked div before using $('.story').data('story-id'). Any suggestions?
I'm guessing it's supposed to be:
chrome.extension.onMessage.addListener(function(request, sender, callback) {
console.log('message listener');
if (request.action == "getSource") {
callback( $('.story').data('story-id') );
}
});
Related
I want to send a message to the content script, when the user selects a context menu item. The content script will then, based on the message, format the webpage as required.
I was unable to do so and I have been able to locate the problem to the code responsible for sending the message (in the context menu script) or the one responsible for receiving message (in the content script).
Below, I try to present a simplified version of the code that tries to duplicate the problem:
manifest.json
{
"manifest_version": 2,
"name": "MCVE for SO",
"version": "1",
"description": "Demonstrate message passing from context menu to content script",
"author": "Nityesh Agarwal",
"permissions": [
"tabs",
"activeTab",
"contextMenus"
],
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["markr.js"]
}
],
"background": {
"scripts": ["backgroundContextMenus.js"]
}
}
backgroundContextMenus.js:
chrome.runtime.onInstalled.addListener(function(){
chrome.contextMenus.create({"title": "Testing",
"contexts": ["selection"],
"id": "testing"});
});
chrome.contextMenus.onClicked.addListener(function(info, tab){
console.log("testing..");
console.log("Before sending the bold message");
chrome.tabs.sendMessage(tab.id, {changeParameter: "bold"});
console.log("After sending the bold message");
});
markr.js:
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse){
console.log("Text toggled bold");
});
Upon selecting the context menu item, the console shows these messages:
backgroundContextMenus.js:8 testing..
backgroundContextMenus.js:9 Before sending the bold message
backgroundContextMenus.js:11 After sending the bold message
So, as you may notice, there is no log saying something like
Text toggled bold
This means that the code inside the markr.js file isn't getting executed. Therefore, I suspect that there must be something wrong with the code responsible for sending message: chrome.tabs.sendMessage(tabs[0].id, {changeParameter: "bold"});)
Here's another code snippet which I tried to duplicate but it gave the same problem - https://stackoverflow.com/a/14473739/7082018
I am unable to figure out what exactly is wrong with it. It would be of great help if someone could help tell me how I might be able to successfully pass messages between the context menu and the content page.
Taking a cue from this answer I modified my backgroundContextMenus.js as follows:
function ensureSendMessage(tabId, message, callback){
chrome.tabs.sendMessage(tabId, {ping: true}, function(response){
if(response && response.pong) { // Content script ready
chrome.tabs.sendMessage(tabId, message, callback);
} else { // No listener on the other end
console.log("Injecting script programmatically");
chrome.tabs.executeScript(tabId, {file: "markr.js"}, function(){
if(chrome.runtime.lastError) {
console.error(chrome.runtime.lastError);
throw Error("Unable to inject script into tab " + tabId);
}
// OK, now it's injected and ready
console.log("Sending msg now");
chrome.tabs.sendMessage(tabId, message, callback);
});
}
});
}
chrome.runtime.onInstalled.addListener(function(){
chrome.contextMenus.create({"title": "Testing",
"contexts": ["selection"],
"id": "testing"});
});
chrome.contextMenus.onClicked.addListener(function(info, tab){
ensureSendMessage(tab.id, {greeting: "hello"});
});
Then I modified the markr.js as follows:
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse){
if(request.ping){
sendResponse({pong: true});
return;
}
console.log("Text toggled bold");
});
Now the console logs are exactly what one may expect:
Console log:
markr.js:11 Text toggled bold
Remember that this log is present console log of the webpage's devtools and not the background script's inspect views.
I'm tried to run script on ContextMenu Click event. I'm used example ContextMenu which have manifest:
{
"name": "Context Menus Sample (with Event Page)",
"description": "Shows some of the features of the Context Menus API using an event page",
"version": "0.7",
"permissions": ["contextMenus"],
"background": {
"persistent": false,
"scripts": ["sample.js"]
},
"manifest_version": 2
}
sample.js:
function onClickHandler(info, tab) {
chrome.tabs.executeScript(null,
{code:"document.body.style.backgroundColor='" + e.target.id + "'"});
};
chrome.contextMenus.onClicked.addListener(onClickHandler);
chrome.runtime.onInstalled.addListener(function() {
var contexts = ["page","selection","link","editable","image","video",
"audio"];
for (var i = 0; i < contexts.length; i++) {
var context = contexts[i];
var title = "Test '" + context + "' menu item";
var id = chrome.contextMenus.create({"title": title, "contexts":[context],
"id": "context" + context});
}
chrome.contextMenus.create({"title": "Oops", "id": "child1"}, function() {
if (chrome.extension.lastError) {
console.log("Got expected error: " + chrome.extension.lastError.message);
}
});
});
and i also change onclick handler to:
function onClickHandler(info, tab) {
chrome.tabs.executeScript(null, {file: "content.js"});
};
when content.js is:
document.body.innerHTML = document.body.innerHTML.replace(new RegExp("text", "gi"), "replaced");
but both of that doesn't working. How to solve it?
Because this question has hundreds of views so I will answer this my old question to help anyone who has the same problem. The answer is based on wOxxOm's comment. I just need to add activeTab permission to the manifest. So should be like this:
{
"name": "Context Menus Sample (with Event Page)",
"description": "Shows some of the features of the Context Menus API using an event page",
"version": "0.7",
"permissions": ["contextMenus","activeTab"],
"background": {
"persistent": false,
"scripts": ["sample.js"]
},
"manifest_version": 2
}
My script before doesn't work because I have code that needs to read/change current active tab content. activeTab permission gives an extension temporary access to the currently active tab when the user invokes the extension - for example by clicking its browser action.
I load plagin for Inject code to page, the manifest code:
{
"name": "any",
"version": "1.0",
"permissions": [
"webNavigation",
"*://*/*"
],
"background": {
"scripts": ["background.js"],
"persistent": false
},
"manifest_version": 2
}
And the background.js:
chrome.webNavigation.onCompleted.addListener(function(details) {
chrome.tabs.executeScript(details.tabId, {
code: 'console.log("A")'
});
});
The problem is the event firing a few times after page load, I want the event just one time. What is my mistake?
I would appreciate any help on.
chrome.webNavigation.onCompleted is invoked even when the navigation occurs in a subframe. One way to capture it only once is to implement your code with a condition for frame id. frame id = 0 corresponds to the parent frame. Your code would look like :
chrome.webNavigation.onCompleted.addListener(function(tab) {
if(tab.frameId==0){
//logic
}
});
detailed documentation available here : https://developers.chrome.com/extensions/webNavigation
The best practice is to use named functions, register them when ready and, on the performance point of view, only for the requested URLs (if possible).
in the next example the listener is getting removed before registered plus only if the filter contains URLs:
if (filter.url.length < 1) {
resolve(dictionary);
return;
}
if (chrome.webNavigation.onBeforeNavigate.hasListener(onBeforeNavigate))
chrome.webNavigation.onBeforeNavigate.removeListener(onBeforeNavigate);
chrome.webNavigation.onBeforeNavigate.addListener(onBeforeNavigate, filter);
and the named should follow this pattern:
const onBeforeNavigate = (details) => {
if (details.frameId > 0)
return;
...
}
see more info here:
https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webNavigation/onCompleted
I am trying to fire a notification whenever I double click on a word/select it. Found several examples online but I still cannot get my example working:
Manifest:
{
"name": "hh",
"description": "hh!",
"manifest_version": 2,
"version": "0.0.0.1",
"content_scripts": [
{
"matches": [ "http://*/*", "https://*/*" ],
"js": [ "background.js" ],
"all_frames": true,
"run_at": "document_end"
}
],
"permissions": ["storage", "notifications"],
"icons": { "128": "neoprice.png" }
}
background.js
var listener = function(evt) {
var selection = window.getSelection();
if (selection.rangeCount > 0) {
displayPrice();
var range = selection.getRangeAt(0);
var text = range.cloneContents().textContent;
console.log(text);
}
};
document.addEventListener('dblclick', listener);
function displayPrice(){
chrome.notifications.create(getNotificationId(), {
title: "message.data.name",
iconUrl: 'hh.png',
type: 'basic',
message: "message.data.prompt"
}, function() {});
}
// Returns a new notification ID used in the notification.
function getNotificationId() {
var id = Math.floor(Math.random() * 9007199254740992) + 1;
return id.toString();
}
I was earlier adding the following but I saw people weren't using it, so I removed it
"app": {
"background": {
"scripts": ["background.js", "assets/jquery.min.js"]
}
},
What I am trying to achieve: Whenever they go to ANY page on selecting a word, it fires the function. Later, I wish to use this for a specific page. :)
Tried: How to keep the eventlistener real time for chrome extensions?
Chrome extension double click on a word
https://github.com/max99x/inline-search-chrome-ext
Both don't really work as I want them too. :(
Solution
It seems you are confused with background page and content script. Your background.js is a content script in fact, though its name is "background". While chrome.notifications api can be only called in background page, trying commenting displayPrice function will make your code work.
Next step
Take a look at above tutorials, wdblclick event triggers, use Message Passing to communicate with background page and call chrome.notications api in background page.
What's more
The following code is used in chrome apps rather than chrome extension.
"app": {
"background": {
"scripts": ["background.js", "assets/jquery.min.js"]
}
},
So, I need to pass messages from popup to content and vice versa. Currently here is what I am doing now, but this does not work how it's supposed to work:
manifest.json:
{
// Required
"manifest_version": 2,
"name": "Extension name",
"version": "0.1",
"content_scripts": [
{
"matches": ["*://*/*"],
"js": ["content.js"]
}
],
"browser_action" : {
"default_title" : "Extension name",
"default_popup" : "popup.html"
},
"permissions": [
"tabs",
"*://*/*",
"contextMenus"
]
}
popup.js
var port = "";
window.onload = function()
{
document.getElementById("captureImage").onclick = captureImage;
}
function captureImage(isBig = true)
{
chrome.tabs.query({'active': true}, function (tabs) {
port = chrome.tabs.connect(tabs[0].id, {name: "Besada"});
port.onMessage.addListener(function(msg) {
alert(msg.message);
});
port.postMessage({message: "getCoords"});
});
}
and content.js
chrome.runtime.onConnect.addListener(function(port){
port.onMessage.addListener(function(msg) {
});
port.postMessage({message: "hello!"}); // this message I receive
document.onmouseup = function(e) {
port.postMessage({message: "hello!"}); // this message produces an error
};
});
What I need is message to be sent when user releases the left mouse button. Here is what happens: when I am clicking a button in my popup.html, the alert with the text "Hello" shows, but when I am clicking anywhere in the page after that, I am getting an error: Uncaught Error: Attempting to use a disconnected port object
What am I doing wrong and how can I fix this?
My guess the port is disconnecting when popup closes when I click on page, but I'm not really sure about this.
The popup ceases to exist as a document when you click elsewhere. It's like a tab that has closed. As such, there's no longer anything to communicate to.
The proper way to fix it is to communicate with the background page instead, or use the non-volatile chrome.storage. Then, when the popup is opened, you can use either information source to show content.