How To Send a Message From Popup.js to Content Script in Chrome Extension on Active Tab? - google-chrome-extension

When I click on the toolbar icon from my chrome extension, it brings up the default popup.html along with popup.js. From that popup.js, I want to send a message to the current active tab. I can't figure out how to get the active tab.
My code in the popup.js looks like this:
window.onload = function() {
console.log(`popup.js:window.onload`);
const timeNow = document.getElementById("timeNowId");
timeNow.innerText = new Date().toLocaleTimeString();
var resultsButton = document.getElementById("buttonId");
resultsButton.onclick = () => {
chrome.runtime.sendMessage(
"FromPopupToBackgroundToContent:" + timeNow.innerText,
function(response) {
response === undefined
? alert("popup.js:return from sendMessage:undefined")
: alert("popup.js:return from sendMessage:", response);
}
);
};
};
The full, non-working example is in this github repo at this branch linked to.
https://github.com/pkellner/chrome-extension-message-popup-to-content/tree/feature-popup-to-content-directly-getcurrent

Example:
popup.js
function popup() {
chrome.tabs.query({currentWindow: true, active: true}, function (tabs){
var activeTab = tabs[0];
chrome.tabs.sendMessage(activeTab.id, {"message": "start"});
});
}
document.addEventListener("DOMContentLoaded", function() {
document.getElementById("button1").addEventListener("click", popup);
});
content.js
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if( request.message === "start" ) {
start();
}
}
);
function start(){
alert("started");
}
popup.html
<!DOCTYPE html>
<html>
<head></head>
<script src="popup.js"></script>
<body>
<input id="button1" type=button value=clickme>
</body>
</html>

Related

Chrome Extension: How to communicate with Content.js from a newly opened Window?

I have created a new window in chrome.action.onClicked.addListener as given below.
On clicking of "Check" button in newly opened window I need to connect to content.js and print some message in the console of window. I dont know where it is going wrong! I am using Manifest version 3.
content.js
chrome.runtime.onMessage.addListener(function(msg, sender, sendResponse) {
if(msg.color === "#00FF00"){
document.body.style.backgroundColor = "green";
sendResponse({ status: "done" });
}
});
background.js
var urlRegex = /^(https?:\/\/)?[a-z0-9-]*\.?[a-z0-9-]+\.[a-z0-9-]+(\/[^<>]*)?$/;
chrome.action.onClicked.addListener(function(tab) {
/*...check the URL of the active tab against our pattern and... */
if (urlRegex.test(tab.url)) {
/* ...if it matches, send a message specifying a callback too */
chrome.windows.create({
tabId: tab.id,
type:"popup",
url:"popup.html",
focused:true
});
}
});
popup.html
<html>
<head>
<script defer src="popup.js"></script>
</head>
<body>
<h3>Test Extension Page</h3>
<input type="button" id="sendMessage" value="Check"/>
</body>
</html>
popup.js
let sendMessageButton = document.getElementById("sendMessage");
console.log(document.URL);
console.log(sendMessageButton.value);
function getTitle()
{
return document.title;
}
sendMessageButton.onclick = function() {
chrome.tabs.query({ active: true, currentWindow: true }, function(tabs){
var tab = tabs[0];
chrome.scripting.executeScript(
{
target: {tabId:tab.id},
func: getTitle,
},
() => {
// This executes only after your content script executes
chrome.tabs.sendMessage(
tab.id,
{ color: "#00FF00" },
function (response) {
console.log(response.status);
}
);
});
});
};
Error in console of newly opened window.
Unchecked runtime.lastError: Cannot access contents of url "chrome-extension://jjaaoafdfmabdajdckiacompibnnmnlh/popup.html". Extension manifest must request permission to access this host.
Error handling response: TypeError: Cannot read properties of undefined (reading 'status') at chrome-extension://jjaaoafdfmabdajdckiacompibnnmnlh/popup.js:25:34
Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist.
The problem is that the window you create becomes active and hence it becomes the result of chrome.tabs.query in your code, meaning that executeScript runs inside your own extension page, which can't work as this method is only for web sites.
The solution is to pass the tab id as URL parameter.
// background.js
chrome.action.onClicked.addListener(tab => {
chrome.windows.create({
type: 'popup',
url: 'popup.html?' + new URLSearchParams({
tabId: tab.id,
title: tab.title,
}),
});
});
// popup.js
const params = new URLSearchParams(location.search);
const tabId = +params.get('tabId');
let title = params.get('title'); // initial title
document.getElementById('sendMessage').onclick = async function () {
title = (await chrome.tabs.get(tabId)).title;
let res = await chrome.tabs.sendMessage(tabId, { color: "#00FF00" });
};

Enable only one settings page open in chome extension

I chrome extention in my popup.html I have a "settings" button, for now when I click this button it opens the settings page in a new tab, and it's possible to open multiple pages, my question is how to prevent multiple settings instances, i.e I want to enable only one settings page open.
My code:
<button type="button">
<a id="settings-btn"> <i class="fa fa-cog fa-lg fa-fw" style="font-size:27px;"></i></a>
</button>
And in popup.js:
document.addEventListener('DOMContentLoaded', function () {
document.getElementById("settings-btn").addEventListener("click", openIndex);
})
function openIndex() {
chrome.tabs.create({
url: "Options.html"
});
}
Update:
#wOxxOm's answer worked after I added tabs permission in manifest file
There are two methods.
Using tabs permission and chrome.tabs.query to find the existing tab
manifest.json:
"permissions": ["tabs"]
popup.js
openOrActivate('Options.html');
function openOrActivate(url) {
if (!url.includes(':')) url = chrome.runtime.getURL(url);
chrome.tabs.query({url: url + '*'}, tabs => {
const [tab] = tabs;
if (!tab) {
chrome.tabs.create({url});
} else {
chrome.tabs.update(tab.id, {active: true});
chrome.windows.update(tab.windowId, {focused: true});
}
});
}
Send a message that will be received by an options page if it's open
In case of no response we'll open a new tab.
popup.js:
openOrActivate('Options.html');
function openOrActivate(path) {
if (!path.startsWith('/')) path = `/${path}`;
chrome.runtime.sendMessage(path, tab => {
if (chrome.runtime.lastError || !tab) {
chrome.tabs.create({ url: path });
} else {
chrome.tabs.update(tab.id, { active: true });
chrome.windows.update(tab.windowId, { focused: true });
}
});
}
Options.js (the script of Options.html):
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message === location.pathname) {
chrome.tabs.getCurrent(sendResponse);
return true;
}
});

Modifying chrome.storage.sync from popup script

I have a chrome extension with options.js and popup.js.
I have a setting that I want the user to control both from options and popup alternatively.
In options it's straight forward:
options.html
<!DOCTYPE html>
<html>
<head><title>Random options placeholder</title></head>
<body>
<label>
<input type="checkbox" id="activate">
Active
</label>
<div id="status"></div>
<button id="save">Save</button>
<script src="options.js"></script>
</body>
</html>
options.js
function save_options() {
var isActive = document.getElementById('activate').checked;
chrome.storage.sync.set({
isActive: true
}, function() {
// Update status to let user know options were saved.
var status = document.getElementById('status');
status.textContent = 'Options saved.';
setTimeout(function() {
status.textContent = '';
}, 750);
});
}
but in popup.js I don't understand how to use the chrome.storage.sync.set
to update the same shared value (isActive).
popup.js (fail)
var isActive = document.getElementById('activate').checked;
chrome.storage.sync.set({
isActive: true
});
Any suggestions?

Download PDF from node API via FTP

I have PDF on a remote server. I have API with node and I want, from my website, download the PDF.
I'm using jsftp to upload and read PDF. It works fine :
let str = '';
FTP.get('path/to/my/file', (err, socket) => {
socket.on("data", d => {
str += d.toString();
});
socket.on("close", err => {
if (err) {
console.error("There was an error retrieving the file.", err);
}
// HERE I HAVE THE FILE IN STRING
});
socket.resume();
});
On the close event I have the file in String, but I don't succeed to send it to the browser. I've tried things like :
let s = new Readable();
s.push(str);
s.push(null);
s.pipe(res);
OR
res.end(str);
But nothing happen in browser
I'm using Polymer for my ajax request
<iron-ajax
id="openPdf"
content-type="application/json"
method="POST"
url="/api/karaweb/pdf"
on-response="handleOpenPdfResponse"
on-error="errorMessage"></webservice-request>
Any solutions ?
Thanks
I have a mixin called PdfDownloaderMixin; here is the whole code:
<link rel="import" href="../../bower_components/polymer/polymer-element.html">
<dom-template id="pdf-downloader-mixin">
<script>
/* #polymerMixin */
const PdfDownloaderMixin = (superClass) => class extends superClass {
constructor() {
super();
}
downloadPdf(blobData) {
let fileObjectUrl = window.URL.createObjectURL(blobData);
window.open(fileObjectUrl);
}
}
</script>
</dom-template>
Then you use like this in your element:
<link rel="import" href="../../bower_components/polymer/polymer-element.html">
<link rel="import" href="../../bower_components/iron-ajax/iron-ajax.html" />
<link rel="import" href="pdf-downloader-mixin.html" />
<dom-module id="attachment-handler">
<template>
<iron-ajax id="getAttachmentAjax"
url="[[rootPath]]api/session/getattachment"
debounce-duration="300"
handle-as="blob"></iron-ajax>
</template>
<script>
class AttachmentHandler extends PdfDownloaderMixin(Polymer.Element) {
static get is() { return 'attachment-handler'; }
getAttachment() {
this.$.getAttachmentAjax.generateRequest().completes.then((req) => {
req.succeeded && this.downloadPdf(req.response);
});
}
}
customElements.define(AttachmentHandler.is, AttachmentHandler);
</script>
</dom-module>

how to add callbacks to kendo dialog actions

I've tried using the Kendo UI DialogService to call up my own component in a dialog. The issue I'm having is in using custom actions for my dialog.
Including an ng-template with custom buttons and actions somewhat defeats the purpose of using the dialogService and clutters my template with markup not directly related to it.
I've tried using code like this:
const saveAction = { text: 'Save', primary: true };
const cancelAction = { text: 'Cancel' };
const dialog = this.dialogService.open({
title: 'Edit data',
content: FormComponent,
actions: [
cancelAction,
saveAction
]
});
const form = dialog.content.instance;
form.data = data;
dialog.result.subscribe((result) => {
if (result === saveAction) {
form.save();
}
});
This will let me run a save function from my FormComponent, but won't allow me to stop the dialog from closing if the form validation is off or the save fails.
I have managed to prevent the dialog from closing after you click an action by taking a copy of the dialogs action event emitter, and replacing it with my own.
It's a hack solution to this. Hopefully Kendo will provide something better in future.
const saveAction = { text: 'Save', primary: true };
const cancelAction = { text: 'Cancel' };
const dialog = this.dialogService.open({
title: 'Edit data',
content: FormComponent,
actions: [
cancelAction,
saveAction
]
});
const form = dialog.content.instance;
form.data = data;
const actionEmitter = dialog.dialog.instance.action;
dialog.dialog.instance.action = new EventEmitter<any>();
const sub = dialog.dialog.instance.action.subscribe(action => {
// Perform any check here based on whether you want the dialog to close or not
if(form.validate()) {
// Only call this if you want the dialog to close
actionEmitter.emit(action);
}
});
dialog.result.subscribe((result) => {
sub.unsubscribe(); // clean up
if (result === saveAction) {
form.save();
}
});
You can use method 'setOptions', but I don't know why this method doesn't exist in Telerik Document: https://docs.telerik.com/kendo-ui/api/javascript/ui/dialog
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Untitled</title>
<link rel="stylesheet" href="https://kendo.cdn.telerik.com/2019.2.619/styles/kendo.common.min.css">
<link rel="stylesheet" href="https://kendo.cdn.telerik.com/2019.2.619/styles/kendo.rtl.min.css">
<link rel="stylesheet" href="https://kendo.cdn.telerik.com/2019.2.619/styles/kendo.default.min.css">
<link rel="stylesheet" href="https://kendo.cdn.telerik.com/2019.2.619/styles/kendo.mobile.all.min.css">
<script src="https://code.jquery.com/jquery-1.12.3.min.js"></script>
<script src="https://kendo.cdn.telerik.com/2019.2.619/js/angular.min.js"></script>
<script src="https://kendo.cdn.telerik.com/2019.2.619/js/jszip.min.js"></script>
<script src="https://kendo.cdn.telerik.com/2019.2.619/js/kendo.all.min.js"></script>
</head>
<body>
<div id="dialog"></div>
<input type="button" value="show dialog" onclick="showDialog()" />
<script>
$("#dialog").kendoDialog({
visible: false,
content: 'first content',
actions: [{
text: "OK",
action: function (e) {
return false;
},
primary: true
}, {
text: "Cancel"
}]
});
function showDialog() {
var dialog = $("#dialog").data("kendoDialog");
dialog.setOptions({
closable: false,
content: 're-open content',
actions: [{
text: 'test1',
primary: true
},
{
text: 'test2'
}
]
});
dialog.open();
console.log(dialog.options.actions)
}
</script>
</body>
</html>

Resources