I am building an extension in which content script is injected on context menu click. This functionality is working. Now the problem I am facing is that, the first click on context menu is not working.
Now the problem I am facing is that, the first click on context menu is not working. Is this a bug?.
background.js
on_message = async(message, sender, sendResponse) => {
console.log("bg.on_message");
sendResponse("from bg");
chrome.storage.local.get("list_url", function (data) {
if (typeof data.list_url != "undefined") {
urls = data.list_url
}
});
chrome.storage.local.get("list_ip", function (data) {
if (typeof data.list_ip != "undefined") {
ips = data.list_ip
}
});
chrome.storage.local.get("list_hash", function (data) {
if (typeof data.list_hash != "undefined") {
hashes = data.list_hash;
}
});
if (hashes){
hash_report = await createHashReport(hashes)
hash_table = await createHashTable(hash_report)
await chrome.storage.local.set({
"scanHash": true,
"hash_table": hash_table
}, () => {});
}
if (ips){
ip_report = await createIpReport(ips)
ip_table = await createIpTable(ip_report)
await chrome.storage.local.set({
"scanIp": true,
"ip_table": ip_table
}, () => {});
}
if (urls){
url_report = await createUrlReport(urls)
url_table = await createUrlTable(url_report)
await chrome.storage.local.set({
"scanUrl": true,
"url_table": url_table
}, () => {});
}
if ( hashes.length>0 || urls.length>0 || ips.length>0 ){
chrome.windows.create({url: "output.html", type: "popup", height:1000, width:1000});
}
}
chrome.runtime.onMessage.addListener(on_message);
genericOnClick = async () => {
// Inject the payload.js script into the current tab after the backdround has loaded
chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
chrome.scripting.executeScript({
target: { tabId: tabs[0].id },
files: ["payload.js"]
},() => chrome.runtime.lastError);
});
}
// create context menu
chrome.runtime.onInstalled.addListener(() => {
chrome.contextMenus.create({
id: 'scrapper',
"title": "Scrapper",
"contexts": ["all"]
});
});
chrome.contextMenus.onClicked.addListener(genericOnClick);
payload.js
function extract() {
htmlInnerText = document.documentElement.innerText;
url_exp = /[-a-zA-Z0-9#:%_\+.~#?&//=]{2,256}\.[a-z]{2,4}\b(\/[-a-zA-Z0-9#:%_\+.~#?&//=]*)?/gi;
regex = new RegExp(url_exp)
list_url = htmlInnerText.match(url_exp)
ip_exp = /\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/;
list_ip = htmlInnerText.match(ip_exp)
hash_exp = /\b[A-Fa-f0-9]{32}\b|\b[A-Fa-f0-9]{40}\b|\b[A-Fa-f0-9]{64}\b/g
list_hash = htmlInnerText.match(hash_exp)
await chrome.storage.local.set({ list_url: removeEmails(removeDuplicates(list_url)), list_ip: removeDuplicates(list_ip), list_hash: removeDuplicates(list_hash) });
}
chrome.runtime.sendMessage( extract());
Assuming that "removeEmails" and "removeDuplicates" are synchronous functions (only you know this), the chrome.storage.local.set method instead returns a promise. Basically you send a message to the background hoping that it can read the updated values of list_url, list_ip and list_hash, when instead it has those available before the storage.local.set (so it won't find anything on the first round).
I thought you'd already figured it out yourself with my clue.
Add await keyword before each chrome.storage.local.set
Related
I`m planning to parse aliexpress items by chrome extension.
so here are the steps.
go to the main page of aliexpress.(opening the new tab).
put search keyword in search input and search.
get the list of item`s links and click those links one by one.(opening the new tab).
inside the item detail page get details information of the product.
close the current item tab.
get back to step 3. and loop until all of the items are parsed.
but the problem is when it loops the list of items, it won't wait till the other item parsing is done.
it opens all the links instantly...
so what can I do about it??
content script.
chrome.runtime.onMessage.addListener(async (message, sender, responce) => {
const { type, market, at } = message;
if (type == "WHERE" && market == "aliexpress") {
if (at == "main") {
console.log("main");
const searchInput = document.getElementsByClassName(
"search-key"
)[0] as HTMLInputElement;
console.log("알리익스프레스의 검색창", searchInput);
searchInput.value = "itemToSearch";
const searchBtn = document.getElementsByClassName(
"search-button"
)[0] as HTMLInputElement;
searchBtn.click();
}
}
});
chrome.runtime.onMessage.addListener(async (message, sender, response) => {
const { type, market, at } = message;
if (type == "WHERE" && market == "aliexpress") {
if (at == "wholesale") {
await scroll({ direction: "down", speed: "slow" });
const productContainer =
document.getElementsByClassName("product-container")[0];
const itemList = productContainer.children.item(1);
for (let index = 0; index < itemList.children.length; index++) {
const item: HTMLAnchorElement = itemList.children[
index
] as HTMLAnchorElement;
item.click();
if (at == "item") {
await scroll({ direction: "down", speed: "slow" });
const price = document.getElementsByClassName(
"product-price-value"
)[0];
console.log(price.textContent);
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
chrome.tabs.remove(tabs[0].id);
});
}
}
}
}
});
background script
// 알리익스프레스
chrome.tabs.onUpdated.addListener((tabId, _changeInfo, tab) => {
if (
tab.url &&
tab.url.includes("ko.aliexpress.com") &&
!tab.url.includes("wholesale") &&
!tab.url.includes("item")
) {
chrome.tabs.sendMessage(tabId, {
type: "WHERE",
market: "aliexpress",
at: "main",
});
}
});
chrome.tabs.onUpdated.addListener((tabId, _changeInfo, tab) => {
if (tab.url && tab.url.includes("ko.aliexpress.com/wholesale")) {
chrome.tabs.sendMessage(tabId, {
type: "WHERE",
market: "aliexpress",
at: "wholesale",
});
}
});
chrome.tabs.onUpdated.addListener((tabId, _changeInfo, tab) => {
if (tab.url && tab.url.includes("ko.aliexpress.com/item")) {
chrome.tabs.sendMessage(tabId, {
type: "WHERE",
market: "aliexpress",
at: "item",
});
}
});
I try to build a simple Text Editor using Electron.
At the moment I want to add a custom title bar which doesn't really work as the buttons are not clickable...
I added an onclick tag to the buttons.
main.js:
//-- variables --\\
const { BrowserWindow, app, Menu, dialog, ipcMain } = require("electron")
let window
let filePath = null //currently opened file
let openFiles = [] //opened files
//-- startup --\\
app.whenReady().then(function()
{
createWindow()
createMenu()
})
//-- functions --\\
function createWindow()
{
window = new BrowserWindow
({
width: 1000,
height: 600,
frame: false,
icon: "./assets/icon.png",
darkTheme: true,
autoHideMenuBar: true,
webPreferences:
{
nodeIntegration: true,
contextIsolation: false
}
})
//window.maximize()
window.loadFile("./frontend/index.html")
window.webContents.openDevTools()
}
function createMenu()
{
Menu.setApplicationMenu(Menu.buildFromTemplate
([
{
label: "File",
submenu:
[
{
label: "Save",
click: saveFile,
accelerator: "CmdOrCtrl+S"
},
{
label: "Open",
click: openFile,
accelerator: "CmdOrCtrl+O"
},
{
label: "New",
click: newFile,
accelerator: "CmdOrCtrl+N"
}
]
}
]))
}
async function promptFilePathOpen()
{
await dialog.showOpenDialog
({ properties: ["openFile"] }).then(function(res)
{
if(res.canceled) return
filePath = res.filePaths[0]
})
}
async function promptFilePathSave()
{
await dialog.showSaveDialog().then(function(res)
{
if(res.canceled) return
filePath = res.filePath
})
}
async function openFile()
{
await promptFilePathOpen()
//openFiles.push(filePath)
window.webContents.send("crd-openFile", filePath)
}
async function saveFile()
{
if(filePath == null || undefined) await promptFilePathSave()
window.webContents.send("crd-saveFile", filePath)
}
async function newFile()
{
filePath = null
await promptFilePathSave()
window.webContents.send("crd-resetEditor")
window.webContents.send("crd-saveFile", filePath)
}
//-- listeners --\\
app.on("window-all-closed", function()
{
if(process.platform != "darwin") app.quit()
})
ipcMain.on("crd-minimizeWindow", function() //does not get called by the renderer
{
//coming soon
})
ipcMain.on("crd-toggleWindowSize", function() //does not get called by the renderer
{
//coming soon
})
ipcMain.on("crd-closeWindow", function() //does not get called by the renderer
{
console.log("quit")
})
renderer/script.js:
//-- imports --\\
const { ipcRenderer } = require("electron")
const fs = require("fs")
const editorElem = document.querySelector("textarea.editor")
//-- functions --\\
function minimizeWindow() // does not get called by the button (index.html)
{
ipcRenderer.send("crd-minimizeWindow")
}
function toggleWindowSize() // does not get called by the button (index.html)
{
ipcRenderer.send("crd-toggleWindowSize")
}
function closeWindow() // does not get called by the button (index.html)
{
ipcRenderer.send("crd-closeWindow")
}
//-- listeners --\\
ipcRenderer.on("crd-openFile", function(e, path)
{
editorElem.value = fs.readFileSync(path, "utf-8")
})
ipcRenderer.on("crd-saveFile", function(e, path)
{
fs.writeFile(path, editorElem.value , function(res, err)
{
if(err) throw err
})
})
ipcRenderer.on("crd-resetEditor", function()
{
editorElem.value = ""
})
The entire code is avalable on my GitHub, because I do not want the whole question consisting of code :)
Two issues here:
You defined functions like closeWindow, but you didn't actually add an event listener for them. You mention onclick but I can't see that in your code. So the first step would be to add document.querySelector('.closeWindow').addEventListener('click', closeWindow) .
You made the whole title bar draggable, including the buttons. That means that the role of the buttons is also a draggable area, so when you click them, you start the drag operation instead of sending a click event. The solution is therefore to make sure the button area does not have the -webkit-app-region: drag style but only the area left to them has. This will probably require you to redesign the HTML layout for the title bar a bit, since this won't work well with the whole thing being a grid.
For more details, see this tutorial.
I am writing a chrome extension that makes requests to an API and I have noticed that after I create a notification from background script using chrome's notification API, the listeners on the buttons from the notification are executed multiple times. on the first run only once and then increasing. I figured that the listeners just add up on the page but I couldn't find a way to sort of refresh the background page.
This is the function that creates the notification and it's listeners.
var myNotificationID
const displayNotification=(userEmail, password, website,username) =>{
chrome.notifications.create("", {
type: "basic",
iconUrl: "./icon128.png",
title: "PERMISSION",
requireInteraction: true,
message: "question",
buttons: [{
title: "YES",
}, {
title: "NO",
}]
}, function(id) {
myNotificationID = id;
})
chrome.notifications.onButtonClicked.addListener(function(notifId, btnIdx) {
if (notifId === myNotificationID) {
if (btnIdx === 0) {
console.log('inserting')
try{
fetch (`http://localhost:8080/users/${userEmail}/accounts`,{
})
}catch(err){
}
} else if (btnIdx === 1) {
console.log('clearing')
chrome.notifications.clear(myNotificationID)
}
}
});
}
And this is where the function is called
chrome.runtime.onMessage.addListener((message, sender, response)=>{
if(message.message === 'showNotification'){
console.log('received insert')
displayNotification(message.userEmail,message.password, message.currentSite,message.username)
response({status:"received"})
}
})
the fetch within the listener is executed multiple times but the log from the onMessage listener is only displayed once, so the listener is the problem here.
I tried chrome.notifications.onButtonClicked.removeListener(), but as i mentioned there was no success.
Are there any other ways in which i could clean the listeners from the background script once they are used?
Using a notification store:
const notificationsByID = {};
chrome.notifications.onButtonClicked.addListener((notifId, btnIdx) => {
// Avoid access to the notification if not registered by displayNotification
if (!notificationsByID[ notifId ]) { return null; }
if (btnIdx === 0) {
console.log('inserting')
try{
fetch (`http://localhost:8080/users/${ notificationsByID[ notifId ].userEmail }/accounts`,{ /**/ });
}catch(err){
console.log(err);
}
delete notificationsByID[ notifId ]; // Cleanup
} else if (btnIdx === 1) {
console.log('clearing')
chrome.notifications.clear(myNotificationID);
delete notificationsByID[ notifId ]; // Cleanup
}
});
chrome.notifications.onClosed.addListener((notifId) => {
if (notificationsByID[ notifId ]) { delete notificationsByID[ notifId ]; }
});
const displayNotification=(userEmail, password, website,username) =>{
chrome.notifications.create("", {
type: "basic",
iconUrl: "./icon128.png",
title: "PERMISSION",
requireInteraction: true,
message: "question",
buttons: [{ title: "YES", }, { title: "NO", }]
}, function(id) {
// Insertion
notificationsByID[ id ] = { userEmail, password, website,username };
})
}
I'm writing a Chrome Extension that can get HTTP response for a site. I try to use debugger for getting response body:
var gCurrentTab;
chrome.debugger.onEvent.addListener(function (source, method, params) {
if (gCurrentTab.id != source.tabId) {
return;
}
if (method == "Network.loadingFinished") {
var tabId = source.tabId;
var requestId = params.requestId;
chrome.debugger.sendCommand(
source,
"Network.getResponseBody",
{"requestId": requestId},
function (body) {
console.log(body);
chrome.debugger.detach(source);
});
}
}
);
chrome.webRequest.onBeforeRequest.addListener(function (details) {
var url = details.url;
if (url.indexOf('/mypage') >= 0) {
chrome.tabs.query({
currentWindow: true,
active: true
}, function (tabs) {
gCurrentTab = tabs[0];
chrome.debugger.attach({
tabId: gCurrentTab.id
}, "1.0", function () {
chrome.debugger.sendCommand({
tabId: gCurrentTab.id
}, "Network.enable");
});
});
}
},
{urls: []}, ["requestBody", "blocking"]);
But I always get
Unchecked runtime.lastError while running debugger.sendCommand: {"code":-32000,"message":"No resource with given identifier found"}
at chrome-extension://ikphgobkghdkjkfplgokmapjlbdfeegl/background.js:11:29
error, and the body is undefined.
Does anyone have idea about why this happen? Thanks!
It was because the website sends many responses, and this code will see another request other than I want, then detach the debugger so I can't get the result.
To solve this, just use a single debugger and do not detach it, or only detach when it's safe to.
var gAttached = false;
var gRequests = [];
var gObjects = [];
chrome.debugger.onEvent.addListener(function (source, method, params) {
if (method == "Network.requestWillBeSent") {
// If we see a url need to be handled, push it into index queue
var rUrl = params.request.url;
if (getTarget(rUrl) >= 0) {
gRequests.push(rUrl);
}
}
if (method == "Network.responseReceived") {
// We get its request id here, write it down to object queue
var eUrl = params.response.url;
var target = getTarget(eUrl);
if (target >= 0) {
gObjects.push({
requestId: params.requestId,
target: target,
url: eUrl
});
}
}
if (method == "Network.loadingFinished" && gObjects.length > 0) {
// Pop out the request object from both object queue and request queue
var requestId = params.requestId;
var object = null;
for (var o in gObjects) {
if (requestId == gObjects[o].requestId) {
object = gObjects.splice(o, 1)[0];
break;
}
}
// Usually loadingFinished will be immediately after responseReceived
if (object == null) {
console.log('Failed!!');
return;
}
gRequests.splice(gRequests.indexOf(object.url), 1);
chrome.debugger.sendCommand(
source,
"Network.getResponseBody",
{"requestId": requestId},
function (response) {
if (response) {
dispatch(source.tabId, object.target, JSON.parse(response.body));
} else {
console.log("Empty response for " + object.url);
}
// If we don't have any request waiting for response, re-attach debugger
// since without this step it will lead to memory leak.
if (gRequests.length == 0) {
chrome.debugger.detach({
tabId: source.tabId
}, function () {
chrome.debugger.attach({
tabId: source.tabId
}, "1.0", function () {
chrome.debugger.sendCommand({
tabId: source.tabId
}, "Network.enable");
});
});
}
});
}
}
);
var initialListener = function (details) {
if (gAttached) return; // Only need once at the very first request, so block all following requests
var tabId = details.tabId;
if (tabId > 0) {
gAttached = true;
chrome.debugger.attach({
tabId: tabId
}, "1.0", function () {
chrome.debugger.sendCommand({
tabId: tabId
}, "Network.enable");
});
// Remove self since the debugger is attached already
chrome.webRequest.onBeforeRequest.removeListener(initialListener);
}
};
// Attach debugger on startup
chrome.webRequest.onBeforeRequest.addListener(initialListener, {urls: ["<all_urls>"]}, ["blocking"]);
// Filter if the url is what we want
function getTarget(url) {
for (var i in TARGETS) {
var target = TARGETS[i];
if (url.match(target.url)) {
return i;
}
}
return -1;
}
const TARGETS = [
{url: '/path1', desc: 'target1'},
{url: '/path2', desc: 'target2'}
]
I am facing similar issue. I figured that sendCommand was not executing immediately. I was facing the issue for the requests which are sent before sending "Network.enable" was complete. Try adding completion for
chrome.debugger.sendCommand({
tabId: gCurrentTab.id
}, "Network.enable")
I am trying to code a chrome extension, and I have a background.html with this code:
var x = "test";
function tabChanged(id, info, tab){
if(info.status == 'complete'){
chrome.tabs.executeScript(id, {code:"try{alert(x);}catch(e){alert(e);}"}, null);
}
}
chrome.tabs.onUpdated.addListener(tabChanged);
chrome.tabs.getAllInWindow(null,function(tabs){
for(var index=0; index < tabs.length; index++){
chrome.tabs.executeScript(tabs[index].id, {code:"try{alert(x);}catch(e){alert(e);}"}, null);
}
});
But variable "x" is always undefined inside executeScript.
How can I get/set x from executeScript? Without using messaging.
Content scripts executes in context of web page. See Content Scripts section in Chrome docs for more information.
If you want to pass string variable from background page to chrome.tabs.executeScript you must do something like this:
var x = "test";
chrome.tabs.executeScript(id, {code:"var x = '"+x+"'; try{alert(x);}catch(e){alert(e);}"},null);
If you want to modify variable, you have only one way - messaging:
var x = 1;
console.log('x=' + x);
chrome.extension.onRequest.addListener(function(request, sender, sendResponse) {
console.log(request);
if(request.command == 'setVar') {
window[request.name] = request.data;
}
});
chrome.browserAction.onClicked.addListener(function() {
var code = "chrome.extension.sendRequest({command: 'setVar', name: 'x', data: 2});";
chrome.tabs.executeScript(null, {code:code});
window.setTimeout(function(){console.log('x=' + x);}, 1000);
});
For chrome Chrome 92+, you can use args
chrome.tabs.query({active: true, currentWindow: true}).then(([tab])=>{
const para = "Hello world!!!"
chrome.scripting.executeScript({
target: {tabId: tab.id},
function: (args) => {
alert(args)
},
args: [para]
})
})
or you can use the chrome.storage
{
"manifest_version": 3,
"permissions": [
"storage",
"activeTab",
"scripting"
]
}
chrome.storage.sync.set({msg: "hello world"})
chrome.tabs.query({active: true, currentWindow: true}).then(([tab])=>{
chrome.scripting.executeScript({
target: {tabId: tab.id},
function: () => {
chrome.storage.sync.get(['msg'], ({msg})=> {
console.log(`${msg}`)
alert(`Command: ${msg}`)
})
}
})
})
Update for pass functions in args is OK?
You can't
Neither Storage nor Args, they both can't accept the function parameter.
Example:
const myObj = {
msg: "Hi",
myNum: 1,
myFunc: ()=>{return "hi"},
myFunc2: function() {return ""}
}
chrome.storage.sync.set({
msg: "my storage msg",
myObj,
})
chrome.tabs.query({active: true, currentWindow: true}).then(([tab])=>{
chrome.scripting.executeScript({
target: {tabId: tab.id},
function: (para1, para2MyObj) => {
console.log(para1) // Hello
console.log(JSON.stringify(para2MyObj)) // {"msg":"Hi","myNum":1} // myFunc, myFunc2 are missing. You can't get the variable if its type is function.
chrome.storage.sync.get(['msg', "myObj"], ({msg, myObj})=> {
console.log(`${msg}`) // "my storage msg"
console.log(JSON.stringify(myObj)) // {"msg":"Hi","myNum":1}
alert(`Command: ${msg}`)
})
},
args: ["Hello", myObj]
})
})
The reason your variable is undefined is because you are referencing your variable inside of a string so you can just simply do this:
var yourVar = "your variable"
//define your variable
chrome.tabs.executeScript({code: 'Your code here' + yourVar + ';}'})
//then format it like this ({code: "Your code as a string" + your variable + "code"})
and if you want to change the variable later than just do the same thing but with your new variable, it's pretty straight forward