Communication Between JS/HTML in Chrome Extensions - javascript

I'm trying to make my first Chrome Extension without any prior JS knowledge, and I have some trouble doing it.
What does the extension do?
It's a page action extension for generating a string and copying it to the clipboard. The string includes certain element attributes from the DOM.
Scope
It's only applicable on two pages (the domains below are examples):
https://xxx.abc.com/CFM/Messages/CFMEWFA/*
https://xxx.abc.com/CFM/Messages/FraudPrevention/*
Elements of the extension
The extension has a popup.html with three clickable options to be chosen at the user's discretion:
No response
Invalid
Valid
The string is formatted based on the user's choice from the popup, and whether the tab URL contains "CFMEWFA" or "FraudPrevention".
popup.html
<!doctype html>
<html>
<body>
<script src="popup.js"></script>
<ul id="MENU">
<li id="MENUnoResponse">No reponse
</li>
<li id="MENUinValid">Invalid
</li>
<li id="MENUvalid">Valid
</li>
</ul>
</body>
</html>
popup.js is supposed to listen for clicks in popup.html, employ a multi item clickhandler, then message background.js at the event of a click. The message should include an argument corresponding to the li id in popup.html.
popup.js
var theParentMenu = document.querySelector("#MENU");
theParentMenu.addEventListener("click", userHasClicked, false);
function userHasClicked(e) {
if (e.target !== e.currentTarget) {
var clickedItem = e.target.id;
chrome.runtime.sendMessage({
directive: e.target.id
}, function(response) {
this.close();
});
};
e.stopPropagation();
}
background.js is governing where the extension icon is shown. It also listens for messages from popup.js (containing an argument determined by the user's choice from popup.html) before executing content.js, a script which runs in the tab.url fetching attributes from the DOM and generating the string. I have yet to start building content.js because of unresolved issues earlier in other files.
background.js
//Displays the page action extension only on specific pages
function checkForValidUrl(tabId, changeInfo, tab) {
if (tab.url.indexOf("https://xxx.abc.com/CFM/Messages/FraudPrevention/") == 0)
{
chrome.pageAction.show(tabId);
}
else if (tab.url.indexOf("https://xxx.abc.com/CFM/Messages/CFMEWFA/") == 0)
{
chrome.pageAction.show(tabId);
}
};
chrome.tabs.onUpdated.addListener(checkForValidUrl)
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
switch (request.directive) {
case "MENUnoReponse":
// execute the content script
chrome.tabs.executeScript(null, { // defaults to the current tab
//file: "contentscript.js", // script to inject into page and run in sandbox
//allFrames: true // This injects script into iframes in the page.
});
sendResponse({}); // sending back empty response to sender
case "MENUinValid":
// execute the content script
chrome.tabs.executeScript(null, { // defaults to the current tab
//file: "contentscript.js", // script to inject into page and run in sandbox
//allFrames: true // This injects script into iframes in the page.
});
sendResponse({}); // sending back empty response to sender
case "MENUvalid":
// execute the content script
chrome.tabs.executeScript(null, { // defaults to the current tab
//file: "contentscript.js", // script to inject into page and run in sandbox
//allFrames: true // This injects script into iframes in the page.
});
sendResponse({}); // sending back empty response to sender
break;
default:
// helps debug when request directive doesn't match
alert("Unmatched request of '" + request + "' from script to background.js from " + sender);
}
}
);
manifest.json
{
"manifest_version": 2,
"name": "EW logger",
"description": "This extension creates logs for early warning and fraud prevention cases",
"version": "1.0",
"page_action": {
"default_title": "EW",
"default_icon": "icon.png",
"default_popup": "popup.html"
},
"background": {
"scripts": ["background.js"]
},
"permissions": [
"tabs",
"clipboardWrite",
"https://xxx.abc.com/*"
]
}
What works:
The extension icon appears like it should.
My problems:
The options in popup.html are not working. Popup.js doesn't do anything when I click.
Do you have any suggestion to how I can "listen" for clicks in popup.html properly, and then send a message containing an argument to background.js?

Your script is running before the body is loaded, so the element is not found. You can fix this by moving the script tag to the bottom of the body. Alternatively, use <script src="popup.js" defer></script> to delay execution until the dom is loaded.
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-defer
Also, you should use console.log(message) and the Chrome Devtools console to debug and check for errors.
https://developer.mozilla.org/en-US/docs/Web/API/Console/log
https://developers.google.com/web/tools/chrome-devtools/

Related

Chrome Extension tab and contextmenu trouble

I am trying to sendMessage from my contextmenu.js file to my content.js file anytime the user right clicks on any website on chrome.
This will include sites
- that are on the current tab and is active
- that are popups and is inactive
- that are popups and is active
- on another window and is inactive
- on another window and is active
My code looks like this:
//contextmenu.js
chrome.contextMenus.onClicked.addListener((clickData, tab) => {
chrome.tabs.sendMessage(tab.id, {text: 'rightClicked'}, (response) => {
console.log(response)
})
})
//content.js
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
if (msg.text === 'rightClicked') {
sendResponse('performing operation')
}
})
I'm getting the error message:
"Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist."
Assuming contextmenu.js is declared in "background" section and content.js in "content_scripts" section of manifest.json, the posted code is fine, but extensions have many moving parts so the problem is elsewhere. The error message means there was no content script running at the time the message was sent, which could happen in these cases:
the page was still loading - to fix this add "run_at": "document_start" in manifest.json's content_scripts section, more info.
the extension was recently installed or updated or reloaded but the tab wasn't reloaded, see content script re-injection after upgrade or install or switch to programmatic injection instead of declaring content_scripts section in manifest.json, more info.
you clicked inside an iframe but you didn't allow the content script to run inside iframes - add "all_frames": true in manifest.json's content_scripts section, more info and specify frameId in sendMessage like this:
chrome.contextMenus.onClicked.addListener((clickData, tab) => {
const {frameId} = clickData;
chrome.tabs.sendMessage(tab.id, {text: 'rightClicked'}, {frameId}, response => {
console.log(response)
});
});
the page can't run content scripts at all (e.g. a chrome:// page or another extension) - can't be fixed in general but for a personal use you can start Chrome with --extensions-on-chrome-urls command line switch and add chrome://*/* pattern to the content_scripts section's matches list.
the page URL is blacklisted - check chrome://policy for the presence of runtime_blocked_hosts and contact your admin
I figured it out.
There is nothing wrong with my code. I was just not matching the right URLs, because I was testing the onClick on a blank page which has no URL or chrome://extension itself. The code works on a any website.
//manifest.json
"content_scripts": [
{
"matches": ["<all_urls>"]
...
}

How to observe changes in tab from Google Chrome extension?

I've developed a Chrome extension that injects a button into the toolbar of a rich-text editor of a specific web page, code available here. This basic extension is based on the concept of the "content script" and works well because the toolbar is present as soon as the page has loaded.
Now, however, I'm confronted by another page where I cannot simply inject my button as soon as the page loads because the user needs to interact with the page first (make a selection or press a button) before the toolbar appears.
So I'm looking for a way to track any changes in the active tab (I have a URL pattern for the page). I don't want or need a browser action (i.e. the little button on the right-hand side of the omnibox), so I was hoping to get away with a background.js event page where I can declare an event listener for certain user-originated events but somehow it's not working.
To explain: I've got my manifest.json, great:
{
"name": "basic tab test",
"description": "blah di blah",
"version": "1.0",
"permissions": [
"activeTab"
],
"background": {
"scripts": ["background.js"], // background script shown below
"persistent": false
},
"content_scripts": [
{
"matches": [
"file://*" // for testing only, of course
],
"js": [
"contentscript.js" // <-- content script shown below
]
}
],
"manifest_version": 2
}
The background.js script looks like this at the moment:
console.log("in background.js");
chrome.tabs.getCurrent(function(tab) {
tab.onActivated.addListener(function(){
console.log("GOT HERE onActivated (inside tab)");
});
});
chrome.tabs.getCurrent(function(tab) {
tab.onZoomChange.addListener(function(){
console.log("GOT HERE onZoomChange (inside tab)");
});
});
// this is actually part of the message passing test
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
console.log(sender.tab ?
"from a content script:" + sender.tab.url :
"from the extension");
if (request.greeting == "hello")
sendResponse({farewell: "goodbye"});
});
Of course, these are just tests, but none of the above events actually ever fire. Becoming slightly desperate, I then thought 'well, let's use the message passing method' to send messages from the contentscript.js to the background.js whenever the user presses a button. The contentscript.js looks like this:
document.addEventListener("DOMContentLoaded", function(event) {
console.log("just a canary - got here...");
var btn = document.getElementById("button");
if (btn) {
console.log("there is a button!");
} else {
console.log("there is NO button!");
}
btn.addEventListener("click", function () {
console.log("clicked the button!!!!");
chrome.runtime.sendMessage({greeting: "hello"}, function(response) {
console.log(response.farewell);
});
})
});
Also, we never arrive inside this event handler (but why oh why?! this is standard jquery-less code for when the DOM is completely loaded). So, this is when I thought I might ask the assembled experts for advice.
TL;DR: I want to track events on the activeTab and if a given DOM element makes its appearance manipulate it (by injecting an element).
By default, "run_at" property for Content Script is "document_idle", which means you scripts will be injected after window.onload event fires, and obviously later than DOMContentLoaded event. So in fact your code in content script is not executed at all.
To make your code work, either:
Remove the outer DOMContentLoaded event listener
Or add "run_at": "document_start" part in your manifest.json
You could take a look at run_at part fore more details.

Access window object from a background Chrome extension

I want to access the properties of a window object from a background script. I have this in manifest.json:
{
"..": "..",
"permissions": ["http://*.mysite.net/"],
"background": {
"scripts": ["extension.js"]
}
}
and this in extension.js:
chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) {
if (changeInfo.status === 'complete') {
var tabWindowObject = ??
setInterval(tabWindowObject.someFunction, 10);
}
});
I need it here, not in another place (no content scripts and no script injection). How do I get the tabWindowObject in extension.js? In other words, I want to access the context of a tab inside a background script Chrome extension.
You can't. The extension's background page runs in one process, while the tab that was updated runs in a separate process. Different processes can't share objects, so you can't directly access the window object of a tab from an extension's background page. You have to use a content script to get extension code to run inside the tab's process.

Content script is not executing

I have created Chrome extension which should change page body color. It is working when I am clicking on the extension but I want it be done when the page loads, where an alert shows that controls is passing through code, it is not calling the function. Please help to fix this.
----------manifest.json-----------
{
"name": "My Page changer",
"description": "Make the current page red",
"version": "2.0",
"permissions": [
"activeTab"
],
"background": {
"scripts": ["background.js"],
"persistent": false
},
"browser_action": {
"default_title": "Make this page blue"
},
"manifest_version": 2
}
------------------ background.js--------------------
debugger;
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
if (changeInfo.status == 'complete') {
debugger;
alert('I am here too ');
/*chrome.tabs.executeScript({
code: 'document.body.style.backgroundColor="blue"'
//code : 'doWhatYouWant()'
});*/
// Execute some script when the page is fully (DOM) ready
chrome.tabs.executeScript(null, {code:"doWhatYouWant();"});
}
});
function doWhatYouWant(){
alert('I am inside doWhatYouWant');
document.body.style.backgroundColor="blue";
}
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Called when the user clicks on the browser action.
chrome.browserAction.onClicked.addListener(function(tab) {
// No tabs or host permissions needed!
alert('i am here');
console.log('Turning ' + tab.url + ' red!');
chrome.tabs.executeScript({
code: 'document.body.style.backgroundColor="red"'
});
});
Your doWhatYouWant function lives in your background page, so you won't be able to call it from a content script.
If the code you want to run is more than a couple lines long, consider putting it into a file in your extension and using the file: field in the InjectDetails parameter when you invoke chrome.tabs.executeScript.
Also, if you want it to run on every page, you can declare it as a content script in your manifest, instead of having the chrome.tabs.onUpdated listener.
There is the "prince" example on google content script tutorial:
https://developer.chrome.com/extensions/content_scripts.html#pi
I tried when learning to "google-extenioning"(?) and it works fine.
Good Luck!
eidt - uh, forgot, files are here

How can I communicate between background.js and popup.js?

I have an extension, with a background script:
"background": {
"scripts": ["scripts/background.js"]
},
and a content script:
"content_scripts": [
{
"matches": ["*://*/*"],
"js": ["scripts/content_script.js"]
}
],
a popup window (popup.html), and a popup script (popup.js). popup.js is not registrated into manifest,and it deals with popup.html look, and listen for user actions made in popup.html, such as clicking a button.
I want to make an extension, what emails the current tab's page, and for this, I need to get the page DOM with the content_script, pass data (DOM) to the background script. After this, when the user triggers an event in popup.html, popup.js catches this event, and I want popup.js to be able to get the passed data(DOM) from background.js. How could I make this? So, my question is, how could I communicate between background.js and popup.js?
I found an answer to my own question:
Thanks Elvis, I think I solved the problem; I only need to get the DOM of site in content script, but my question's solution was this:
content_script.js
// SEND DOM structure to the background page
chrome.extension.sendRequest({dom: "page DOM here"});
background.html
<html>
<head>
<script>
chrome.extension.onRequest.addListener(function(request, sender, sendResponse) {
if(request.dom != "")
var theDOM = request.dom;
console.log(request.dom); // page DOM here -> works
chrome.extension.sendRequest({theDOM: theDOM}); // theDOM : "page DOM here"
});
</script>
</head>
<body>
</body>
</html>
popup.js
var dom;
chrome.extension.onRequest.addListener(function(request, sender, sendResponse) {
if(request.theDOM != ""){
console.log("popup request: "+request.theDOM);
dom = request.theDOM;
}
});
// HANDLE TAB_1 REQUESTS (EMAIL PAGE)
// ---------------------------------
$("#send").click(function(){
console.log(dom); // page DOM here
}
Thanks for the help ;)
You can do Message Passing. From the documentation:
Use this in your content script:
chrome.extension.sendRequest({greeting: "hello"}, function(response) {
console.log(response.farewell);
});
It sends {greeting: "hello"} to the background. Notice the callback specified
The background page can listen to these requests using:
chrome.extension.onRequest.addListener(
function(request, sender, sendResponse) {
if (request.greeting == "hello")
sendResponse({farewell: "goodbye"});
});
The arguments to the sendResponse function will be passed to the callback

Categories