Chrome extension content_script add html with onchange function, function is undefined - google-chrome

This is my first try on creating a chrome extension and most stuff is already working out. Except for one thing.
I created a script that insert a SELECT the SELECT has a onchange that calls a function that is defined in my content script. But when the select onchange fires, the function that is calling is undefined.
Code samples:
manifest.js
{
"name": "Extra Filters",
"version": "0.0.1",
"manifest_version": 2,
"content_scripts": [
{
"matches": ["..."],
"js": ["content.js"]
}
]
}
content.js
function CreateSelect(index, options) {
if(options == null) return;
let html = `<select onchange='AddFilter(${index}, this)'>`;
options.forEach(option => {
html += `<option
value="${option}">
${option}
</option>`;
});
html += `</select>`;
return html;
}
function AddFilter(index, value) {
console.log(index, value);
}
So when the select changes, I get the error: Uncaught ReferenceError: AddFilter is not defined

Related

Why Chrome DevTools Network shows no response?

I've been trying to fetch some url using Devtools 'copy as fetch' but I can see no response. If I try replay xhr, it will work. It's strange since it doesn't work for this particular site, but if I try 'copy as fetch' on some others it does bring a result.
What I'm trying to do is grab the response body and display it some other way (it's a schedling software, and I'm trying to modify the way it displays the calendar view since it displays everything together).
I have an extension that enables me to modify XMLHttpRequest so I can get the response of any XHR, but since the first async executes before I inject the script, then I'm always missing the first one.
I plan on using chrome webRequest to stop the first one and fetch it again.
manifest.json
{
"name": "jobber",
"version": "2.0",
"description": "Build an Extension!",
"manifest_version": 2,
"permissions": [
"webNavigation",
"webRequest",
"*://secure.name.com/*"
],
"content_scripts": [{
"matches": ["*://secure.name.com/calendar*"],
"js": ["contents.js"],
"run-at": "document_start"
}],
"externally_connectable": {
"matches": ["*://secure.name.com/calendar*"]
},
"background": {
"scripts": ["background.js"]
}
}
contents.js
(function () {
'use strict';
let s = document.createElement("script");
s.textContent = overloadXHR();
document.head.insertBefore(s, document.head.children[0]);
s = document.createElement("script");
s.textContent = displayCalendar;
document.head.insertBefore(s, document.head.children[0]);
})();
function overloadXHR() {
const text = `
console.log(\`overriding: \${Date.now()}\`);
const rawOpen = XMLHttpRequest.prototype.open;
let json = [];
(function(){
XMLHttpRequest.prototype.open = function () {
this.addEventListener("readystatechange", e => {
if (/secure.name.com.calendar.*?calendar=true/i.test(this.responseURL)) {
if ((this.status == 200) && (this.readyState == 4)) {
console.log(this.readyState);
try {
json = JSON.parse(this.response);
window.setTimeout(() => (window.displayCalendar({json}))({json}), 1000);
}
catch (e) { console.log(e); }
}
}
});
rawOpen.apply(this, arguments);
}
}())
`;
return text;
}
function displayCalendar({ json }) {
// do something
}
I tried POST requests too. I could see that they would work, but no response given tho.
original request:
copy as fetch
copy as fetch response
copy as fetch timing

Receiving Error in Chrome Extension: Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist

I'm trying to create a very simple Chrome extension that will allow me to highlight a word on a webpage, right click to open the context menu, and then search it on a database called Whitaker's Words by simply appending the word to the search URL. I'm continuing to receive
"Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist."
as an error every time I run the code and attempt to use the context menu.
At the moment, I have already taken the steps to disable all other extensions and I attempted to use the port documentation on the Chrome Messaging Docs but I wasn't able to resolve the issue that way.
background.js
chrome.contextMenus.create({
title: "Search Whitaker's Words",
contexts: ["selection"]
});
chrome.contextMenus.onClicked.addListener(function() {
chrome.runtime.sendMessage({ method: "getSelection" }, function (response) {
sendToWW(response.data);
});
});
function sendToWW(selectedText) {
var serviceCall = 'http://archives.nd.edu/cgi-bin/wordz.pl?keyword=' + selectedText;
chrome.tabs.create({ url: serviceCall });
}
Here, I create a context menu and when the menu item is clicked, I send a message to the context script asking for the highlighted selection. I then return this to another function in background.js that will create a new tab with the search query.
content.js
chrome.runtime.onMessage.addListener(function (message) {
if (message.method === "getSelection"){
var word = window.getSelection().toString().trim();
console.log(word);
chrome.runtime.sendMessage({ data: word });
}
else
chrome.runtime.sendMessage({}); // snub them.
});
I listen here for the message and then take a selection from the window, trim, and send it back.
manifest.json
{
"manifest_version": 2,
"name": "Latinate",
"version": "0.1",
"description": "Aid in Latin translation using Whitaker's Words",
"content_scripts": [
{
"matches": [
"<all_urls>"
],
"js": [
"jquery-3.4.1.min.js",
"content.js"
]
}
],
"background": {
"scripts": [
"background.js"
]
},
"permissions": [
"contextMenus",
"tabs"
],
"browser_action": {
"default_icon": "icon.png",
"default_popup": "popup.html"
}
}
Any and all help would be appreciated! I've tried nearly everything I could find that seemed to apply.
The error message says there is no message listener on the other side. And indeed there is none because a message listener is a function registered with chrome.runtime.onMessage.addListener - in your extension only the content script has such a listener.
Instead of sending a new message back, send the response using sendResponse function which is passed as a parameter to the onMessage listener
(see also the messaging tutorial).
Another problem is that to send a message to a tab you need to use a different method: chrome.tabs.sendMessage with a tab id as the first parameter.
background script:
chrome.contextMenus.onClicked.addListener((info, tab) => {
chrome.tabs.sendMessage(tab.id, {method: 'getSelection'}, response => {
sendToWW(response.data);
});
});
content script:
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.method === 'getSelection') {
var word = window.getSelection().toString().trim();
console.log(word);
sendResponse({ data: word });
} else {
sendResponse({});
}
});

Chrome extension sendResponse not working

There are numerous versions of this question, and I have reviewed them and tried many things to the best of my ability without success. I am new to chrome extensions, and I could be missing many things, but I don't know what.
First, I want to point out that I have copied several claimed working examples from various answers, and none of them work for me. I am using Chrome Version 58.0.3029.81 on Windows 7 Professional.
Whether it's my code, or examples I have copied, the code in the content script executes, and calls sendResponse with the correct response. Beyond that, nothing - the specified callback function is never executed.
Here is my current code:
manifest.json
{
"manifest_version": 2,
"name": "TestExtension",
"description": "...",
"version": "1.0",
"content_scripts": [
{
"matches": [
"http://*/*",
"https://*/*"
],
"js": [ "content.js" ]
}
],
"browser_action": {
"default_icon": "icon.png",
"default_popup": "popup.html",
"default_title": "Send a checkpoint request"
},
"permissions": [
"activeTab",
"tabs",
"http://*/*", // allow any host
"https://*/*" // allow any secure host
]
}
popup.js
document.addEventListener('DOMContentLoaded', function ()
{
document.getElementById('extension_form').onsubmit = function ()
{
var elementName = "DataAccessURL";
chrome.tabs.query(
{
active: true,
currentWindow: true
},
function (tabs)
{
chrome.tabs.sendMessage(
tabs[0].id,
{ Method: "GetElement", data: elementName },
function (response)
{
debugger;
console.log("response:Element = " + response.Element);
alert(response.Element);
});
});
};
});
content.js
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse)
{
console.log("In Listener");
if (request.Method && request.Method == "GetElement")
{
var result = "";
var element = request.data;
console.log(element);
var e = document.getElementById(element);
if (e) result = e.innerHTML;
console.log(result);
sendResponse({ Element: result });
return true; // <-- the presence or absence of this line makes no difference
}
});
Any clues would be appreciated. I simply do not understand why my callback is not being called.
User wOxxOm provided this answer in his comments.
In the popup.js example, I am attaching to the form 'onsubmit' event to trigger my code.
I had previously read that if the popup gets closed, it's code would no longer be there to be run. I could not visually tell in my case that, as wOxxOm pointed out, that the submit event reloads the popup. That was disconnecting my code.
In order to prevent the popup from reloading, I needed to prevent the default action on the submit event. I added an 'evt' parameter to accept the event argument to the submit, and called preventDefault as shown below.
document.getElementById('extension_form').onsubmit = function (evt)
{
evt.preventDefault();
...
This allowed the sample to work as expected.
Thanks wOxxOm!

addListener's sendResponse called from callback returns previous message

In the beginning I thought that the problem is with chrome.runtime.sendMessage() I was sending two messages. One for accessing localstorage and another one to get/read the file data, but nothing changed after I merged them in one sendMessage which means that the actual problem is window.webkitRequestFileSystem() it's returning the previous file instead of the current one.
Is there a better/faster way of storing something client side ? (I'm willing to try everything) ?
manifest.json
{
"manifest_version": 2,
...
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*"],
"js": ["content.js"],
"run_at": "document_end"
}
],
"background": {
"scripts": ["background.js"]
},
"permissions": [
"unlimitedStorage"
]
}
background.js
var theme = '';
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if(request.method == "getTheme") {
themes = JSON.parse(localStorage["themes"]);
themeName = "";
for (var i = themes.length - 1; i >= 0; i--) {
if(request.url.indexOf(themes[i]) !== -1) {
themeName = themes[i];
}
};
window.webkitRequestFileSystem(window.PERSISTENT, 0, readFromFileStorage.bind(window, themeName), errorHandler);
sendResponse({data: theme});
}
});
function readFromFileStorage(filename, fs) {
fs.root.getFile(filename, {}, function(fileEntry) {
fileEntry.file(function(file) {
var reader = new FileReader();
reader.onloadend = function(e) {
theme = this.result;
};
reader.readAsText(file);
}, errorHandler);
}, errorHandler);
}
function errorHandler(e) {
console.log('Error: ' + e.message);
}
content.js
chrome.runtime.sendMessage({method: "getTheme", url: location.href}, function(response) {
console.log(response.data);
});
As your question is not an SSCCE it is hard to test it, but I think your problem is understanding of JS asynchronous nature.
So how your code will actually execute:
First window.webkitRequestFileSystem(PERSISTENT, 0, successCallback, errorHandler); will be executed
Then sendResponse({data: theme}); will send the response with whatever is stored in theme
Then either successCallback either errorHandler will be called depending on file request success. If successCallback will be called then you'll have your theme variable filled with the value you want. But it will be to late as you already sent the response.
The next time you pass the message you will receive previous theme value (point 2) as your code will find new value only after you'll send the value.
A solution may be to call sendResponse inside of successCallback after you find your desired value (you'll have to pass sendResponse into readFromFileStorage). If you'll do so then you may consider to add sendResponse into errorHandler function just to be sure that your code will always get a valid response.
In case that you'll move the sendResponse into a callback then you have to return true from addListener function, otherwise on addListener return channel will be closed and response will not be sent.

How do I get the context Menu "oncreate" Event

I write a Chrome extension. This extension should add items to the Chrome context menĂ¼ if a text is selected. This should happen dynamically everytime the contextmenu is opened.
As on this image:
The Problem:
I can't find any event that is triggered (and works) if the contextmenu opens. The following code sample doesn't work.
window.addEventListener('contextmenu', function(){
DynamicMenu.generateMenu(window.getSelection());
});
It's also impossible to trigger the adding of dynamic items from the "test" item:
chrome.contextMenus.create(
{
"title": "test",
"contexts" :["selection"],
"onclick" : DynamicMenu.generateMenu
});
Thanks for your help!
Update (how it now works):
Manifest:
...
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*"],
"js": ["document_event.js"],
"run_at" : "document_idle"
}
],
...
document_event.js:
window.addEventListener('mousedown', function(e) {
var rightclick;
if (!e) var e = window.event;
if (e.which) rightclick = (e.which == 3);
else if (e.button) rightclick = (e.button == 2);
if(rightclick){
var searchText = window.getSelection().toString();
chrome.extension.sendRequest({search: searchText}, function(response) {
...
});
}
});
content.js
...
chrome.extension.onRequest.addListener(
function(request, sender, sendResponse) {
var a = request.search;
...
sendResponse({});
});
...
UPDATE: contextMenu onCreate Event
It's less of an official event per se, as it is a simple async callback built into the chrome.extension.create() method signature:
[integer|string] chrome.contextMenus.create(objectcreateProps,function cb)
Parameters:
REQUIRED: an object representing optional createProperties. All the properties are optional, but, the createProperties object itself is required as the first parameter to the create() method. So, if you don't want to use an custom options/properties, simply supply an empty object {}
OPTIONAL: a function which it callbacks when the create() method completes.
Return Value:
Either:
an auto-generated integer if you do not provide an id property in the createProperties object parameter when calling the method.
a string representing the generatedId of the context menu/menu item. A string is only returned if you supplied one in the createProperties object parameter when you called the create() method, and if successfully created, the string you supplied will be the EXACT VALUE that is returned.
The latest docs for the chrome.contextMenus.create API are here:
https://developer.chrome.com/extensions/contextMenus#create
.
In your case/example, you should modify your code like so:
var gId; //the id that is generated if the menu is created successfully
gId = chrome.contextMenus.create(
{//start createProperties
"title": "test",
"contexts" :["selection"],
"onclick" : DynamicMenu.generateMenu
},//end createProperties
function cmItmCreated_handler(){ //your callback (create event, if you will)
if (typeof chrome.runtime.lastError === "undefined") {
console.log("context menu/item created. 'gId' var contains generated id");
}
else {
console.error(chrome.runtime.lastError);
}
}//end function cmItmCreated_handler()
);
Have you tried tracking the right click event? The chrome extension context menu APIs mostly deal with clicks on the new context menu items.