parse web site with chrome extension - javascript

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",
});
}
});

Related

In extension the callbacks are accumulating and persisting until browser restart

I am porting a chess game parser to new manifest v3 specification. When clicking context menu on a selected text, it plays visually the selected text as PGN chess game notation.
The problem is that background script on each game execution adds one more chrome.runtime.onMessage listener, and all the listeners persists until browser closed. So, all listeners are invoked and only first game is played each time.
There is the code:
//background.js
function playBoard (info, imgPath, windowAttributes)
{
console.log("play board with: " + info.selectionText);
let gameObject = {
chessObject:
{
gametype : "PGN_OR_FEN_board",
content : info.selectionText,
imgPath : imgPath
}
};
let windowPromise = chrome.windows.create(windowAttributes);
windowPromise.then((value) => {
chrome.runtime.onMessage.addListener(
(request, sender, sendResponse) =>
{
console.log("backg: chrome.runtime.onMessage()> " + gameObject.chessObject.content);
sendResponse(gameObject);
}
);
});
}
The above code is invoked in following context:
//background.js
console.log("enter global>");
var idParser = "parser parent";
var idMiniBoard = "board mini";
var idMediuBoard = "board medium";
chrome.runtime.onInstalled.addListener(
(details)=>
{
console.log("on installing");
idParser = chrome.contextMenus.create({"id":idParser, "title": "Chess Parser", "contexts":["selection"]});
idMiniBoard = chrome.contextMenus.create({"id":idMiniBoard, "title": "Play Small", "contexts":["selection"], "parentId": idRoatta});
idMediuBoard = chrome.contextMenus.create({"id":idMediuBoard, "title": "Play Medium", "contexts":["selection"], "parentId": idRoatta});
chrome.contextMenus.onClicked.addListener(menuItemClick);
console.log("on installed");
}
)
function menuItemClick(clickData, tab)
{
switch (clickData.menuItemId)
{
case idMiniBoard:
playBoard (clickData, "mini18", {url: "board.html", type:"popup", height : 350, width : 350});
return;
case idMediuBoard:
playBoard (clickData, "medium35", {url: "board.html", type:"popup", height: 450, width: 450});
return;
}
}
There is the popup window startup code
document.addEventListener('DOMContentLoaded',
(event) =>
{
chrome.runtime.sendMessage({ chessObject: { gametype : "PGN_OR_FEN_board" } },
(request) => //in fact is response, but is requested response
{
board_doc_main(request.chessObject);
});
}
);
I am trying to apply similar approach as I did for firefox, here Firefox extension, identification of context. Somehow for firefox this works. I am trying to implement like this: Popup open on click from background worker. Then popup window sends message to background worker to receive the required context, including the selection game text.
###################
UPDATE:
As suggested in response from #wOxxOm, this is exactly how I changed now
function playBoard (clickData, imgPath, windowAttributes)
{
let gameObject = {
chessObject:
{
gametype : "PGN_OR_FEN_board",
content : clickData.selectionText,
imgPath : imgPath
}
};
let windowPromise = chrome.windows.create(windowAttributes);
windowPromise.then((wnd) => {
chrome.runtime.onMessage.addListener (
function __playBoardCallback__ (request, sender, sendResponse)
{
if (sender.tab?.id === wnd.tabs[0].id)
{
chrome.runtime.onMessage.removeListener(__playBoardCallback__);
sendResponse(gameObject);
}
}
);
});
}
Unregister the listener immediately when it runs by using a named function:
async function playBoard(info, imgPath, windowAttributes) {
let gameObject = {
// .........
};
const wnd = await chrome.windows.create(windowAttributes);
const tabId = wnd.tabs[0].id;
chrome.runtime.onMessage.addListener(function _(msg, sender, sendResponse) {
if (sender.tab?.id === tabId) {
chrome.runtime.onMessage.removeListener(_);
sendResponse(gameObject);
}
});
}

Context menu not reponding for first click

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

Same messages being passed more that once while switching tabs fast : Chrome extension issue

I am currently new to building the chrome extension. However I have started to build a chrome extension that scrapes a webpage and stores the data in the google spreadsheet.
Here is my manifest.json:
"manifest_version": 2,
"version": "0.0.1",
"name": "A listener check",
"description": "Understanding the working of listeners in the application",
"content_scripts": [
{
"matches": [
"https://some.website.com/projects/*",
"http://some.website.com/projects/*"
],
"js": [
"content.js"
],
"all_frames": true,
"run_at": "document_end"
},
{
"matches": [
"https://embedded_website.net/*",
"https://embedded_website_2.net/*"
],
"js": [
"demeter.js"
],
"all_frames": true,
"run_at": "document_end"
}
],
"background":{
"scripts": [
"background.js"
]
},
"browser_action": {
"default_popup": "popup/popup.html"
},
"permissions": [
"tabs",
"background",
"management",
"storage"
]
}
Here is my background.js file
const loadedTabs = [];
const switchedTabs = [];
function updatedCallbackFunction(tabId, changeInfo, tab) {
const message = {
action: "load_page",
changeInfo: changeInfo,
tab: tab,
allowLoad: 1,
state: "loaded"
}
if (!loadedTabs.includes(tab.id))
loadedTabs.push(tab.id);
if (changeInfo.status === 'complete') {
chrome.tabs.query({active: true, currentWindow: true}, (tabs) => {
chrome.tabs.sendMessage(tab.id, message);
});
}
}
function tabActivatedCallbackFunction(activeInfo) {
const message = {
action: "load_page",
activeInfo: activeInfo,
state: "switched",
allowLoad: 1
}
console.log(`Switched Tabs : ${switchedTabs}`)
if (!switchedTabs.includes(activeInfo.tabId) ) {
switchedTabs.push(activeInfo.tabId);
chrome.tabs.query({active: true, currentWindow: true}, (tabs) => {
chrome.tabs.sendMessage(activeInfo.tabId, message);
});
}
}
async function backgroundRuntimeCallbackFunction(message, sender, sendResponse) {
switch (message.action) {
case "unload":
console.log('Wrong page loaded');
break;
case "listen":
console.log('Investigating current page');
chrome.tabs.query({active: true, currentWindow: true}, (tabs) => {
chrome.tabs.sendMessage(tabs[0].id, {action: "load_page"});
})
break;
case "demeter_page_direct":
console.log('Going to demeter page');
chrome.tabs.query({active: true, currentWindow: true}, (tabs) => {
chrome.tabs.sendMessage(tabs[0].id, {...message, action: 'demeter_page_load'});
});
break;
case "wait_for_page_load":
console.log('Waiting for demeter page load');
chrome.tabs.query({active: true, currentWindow: true}, (tabs) => {
chrome.tabs.sendMessage(tabs[0].id, {...message, action: 'demeter_page_load'});
});
break;
case "upload_google_sheet":
console.log('Updating to google sheet');
const {action, allowLoad, QApresent, ...googleSheetData} = message;
console.log(googleSheetData);
googleSheetData.hitlink = googleSheetData.hitlink.substring(0, googleSheetData.hitlink.lastIndexOf('&'));
const options = {
method: 'post',
headers: {
'Content-type': 'application/json'
},
body: JSON.stringify(googleSheetData)
}
const response = await fetch('my_backend_app.com/url_to_fetch', options);
const responseBody = await response.json();
console.log(responseBody);
const boxUpdateMessage = {
...message,
action: "load_page",
allowLoad: 0
};
chrome.tabs.query({active: true, currentWindow: true}, (tabs) => {
chrome.tabs.sendMessage(tabs[0].id, boxUpdateMessage);
});
break;
}
}
// creating the functions to get the listeners
function tabUpdateFunction() {
if (chrome.tabs.onUpdated.hasListener(updatedCallbackFunction)) {
console.log('The onupdated listener is already there so removing it!');
chrome.tabs.onUpdated.removeListener(updatedCallbackFunction);
}
// adding the onupdated listener again
console.log('No overhead update function');
chrome.tabs.onUpdated.addListener(updatedCallbackFunction);
}
function tabSwitchFunction() {
if (chrome.tabs.onActivated.hasListener(tabActivatedCallbackFunction)) {
console.log('The tab switch function already there so removing it');
chrome.tabs.onActivated.removeListener(tabActivatedCallbackFunction);
}
console.log('No overhead switch function');
chrome.tabs.onActivated.addListener(tabActivatedCallbackFunction);
}
function backgroundRuntimeFunction() {
console.log(`backgroundRuntimeFunction : ${chrome.runtime.onMessage.hasListener(backgroundRuntimeCallbackFunction)}`);
chrome.runtime.onMessage.removeListener(backgroundRuntimeCallbackFunction);
if (chrome.runtime.onMessage.hasListener(backgroundRuntimeCallbackFunction)) {
chrome.runtime.onMessage.removeListener(backgroundRuntimeCallbackFunction);
}
chrome.runtime.onMessage.addListener(backgroundRuntimeCallbackFunction);
}
// calling the functions
tabUpdateFunction();
tabSwitchFunction();
backgroundRuntimeFunction();
Here is content.js
function getDetails(msg) {
// extract info
return {
action: 'demeter_page_direct',
// more info
};
}
function contentRuntimeCallback(message, sender, sendResponse) {
switch (message.action) {
case "load_page":
const delimiterMessage = getDetails(message);
if (delimiterMessage.title) {
const returnBtnForms = document.getElementsByClassName('force-inline');
for (let returnBtnForm of returnBtnForms) {
returnBtnForm.addEventListener('submit', (e) => {
console.log('Return btn was clicked!');
e.preventDefault();
delimiterMessage.return = 1;
delimiterMessage.allowLoad = 0;
chrome.runtime.sendMessage(delimiterMessage);
})
}
chrome.runtime.sendMessage(delimiterMessage);
}
else {
chrome.runtime.sendMessage({...delimiterMessage, action: 'unload'});
}
break;
}
}
function contentRuntimeCheck() {
if (chrome.runtime.onMessage.hasListener(contentRuntimeCallback))
chrome.runtime.onMessage.removeListener(contentRuntimeCallback);
chrome.runtime.onMessage.addListener(contentRuntimeCallback);
}
contentRuntimeCheck();
and demeter.js
function demeterRuntimeCallbackFunction(message, sender, sendResponse) {
switch (message.action) {
case "demeter_page_load":
if (message.hittitle.includes("specifictitle")) {
const svgParentContainer = document.getElementById('SVG_PARENT_CONTAINER');
if (!svgParentContainer) {
console.log('svg container loading!');
chrome.runtime.sendMessage({...message, action: 'wait_for_page_load'});
break;
} else {
// counting the total number of boxes
const totalBoxes = svgParentContainer.querySelectorAll('rect').length;
// when page loads for the first time
if (message.allowLoad === 1) {
if (message.changeInfo)
console.log('Updated info done');
else if (message.currentTabInfo)
console.log('In port 1');
console.log(`still allowing to load`);
message.allowLoad = 0;
const finalSheetUploadMessage = {
...message,
action: 'upload_google_sheet',
boxcount: totalBoxes
}
chrome.runtime.sendMessage(finalSheetUploadMessage);
break;
} else if (message.return === 1) {
console.log('In port 2');
const finalSheetUploadMessage = {
...message,
hitqueue: message.hitqueue - 1,
action: 'upload_google_sheet',
boxcount: totalBoxes
}
chrome.runtime.sendMessage(finalSheetUploadMessage);
break;
} else {
console.log('In port 3');
const saveBtn = document.querySelector("[aria-label='save']");
const submitBtn = document.querySelector('[aria-label="submit"]');
console.log(submitBtn);
if (saveBtn) {
saveBtn.addEventListener('click', function (e) {
console.log('Save button clicked');
const totalBoxes = svgParentContainer.querySelectorAll('rect').length;
const finalSheetUploadMessage = {
...message,
action: 'upload_google_sheet',
boxcount: totalBoxes
};
chrome.runtime.sendMessage(finalSheetUploadMessage);
});
}
if (submitBtn) {
console.log(submitBtn);
const totalBoxes = svgParentContainer.querySelectorAll('rect').length;
submitBtn.addEventListener('click', (e) => {
const sheetUpdateMessage = {
...message,
action: 'upload_google_sheet',
boxcount: totalBoxes,
submit: 1,
return: 0
}
console.log('Submit btn was clicked');
chrome.runtime.sendMessage(sheetUpdateMessage);
});
break;
}
break;
}
}
}
}
}
if (chrome.runtime.onMessage.hasListener(demeterRuntimeCallbackFunction))
chrome.runtime.onMessage.removeListener(demeterRuntimeCallbackFunction);
chrome.runtime.onMessage.addListener(demeterRuntimeCallbackFunction);
Working
The website i am scraping has an embedded website inside it as an iframe, so I created another content script for it. What i did was scraped details on the original webpage, then sent those details to the embedded webpage to extract further information there and then I am clustering those information and uploading to my google sheet
here is my workflow
content.js -> background.js -> demeter.js -> upload_google_sheet
I have kept allowload = 1 for automatically update the google sheet once all the page loads. Since several instances of the webpage can be open so I tried to implement tab switch event. So that the extension starts firing messages again from scratch once tab switch happens.
Problem
I open let say 3 tabs of the website and rapidly switch tabs among them. I get quite a large number of duplicate rows. I did a lot in tracking and removing listeners. But I really can't find the exact solution or approach to solving this. I want that after rapid switching of tabs. I get the information on the current tab only and no information from other tabs.
Since I am very new to stack overflow. Pardon my long question.

Chrome Extension: "No resource with given identifier found" when trying to Network.getResponseBody

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")

Detect Tab duplication events

I'm developing a Chrome extension;
I need to detect when a tab is duplicated, I'm looking for a tab detection event like onduplicate.addListener()?
Is that possible with the Chrome extension API?
This is the closest implementation:
const newTabsId = new Set();
// Observe all new tabs with opener ID
chrome.tabs.onCreated.addListener(tab => {
if(tab.openerTabId) {
newTabsId.add(tab.id);
}
});
// Waiting for a new tab completeness
chrome.tabs.onUpdated.addListener((tabId, changes, tab) => {
if(newTabsId.has(tabId) && changes.status === 'complete') {
if(!tab.openerTabId) {
return;
}
// Retrieve opener (original) tab
getTabById(tab.openerTabId)
.then(originalTab => {
if(
originalTab.url === tab.url && // original and new tab should have same URL
originalTab.index + 1 === tab.index && // new tab should have next index
tab.active && tab.selected // new tab should be active and selected
// also we may compare scroll from top, but for that we need to use content-script
) {
console.log('Duplicate:', tab);
}
});
// Remove this tab from observable list
newTabsId.delete(tabId);
}
});
// Syntax sugar
function getTabById(id) {
return new Promise((resolve, reject) => {
chrome.tabs.get(id, resolve);
});
}
// Cleanup memory: remove from observables if tab has been closed
chrome.tabs.onRemoved.addListener(tabId => {
newTabsId.delete(tabId);
});
EDIT 1: But yeah, there is no clear solution now to detect real duplicate
Tab duplication preserves sessionStorage of the page so simply store some unique variable in your content script in each page and check if it's present in the beginning of your content script.
manifest:
"content_scripts": [{
"matches": ["<all_urls>"],
"run_at": "document_start",
"js": ["content.js"]
}],
content script:
if (sessionStorage[chrome.runtime.id]) {
chrome.runtime.sendMessage({
action: 'checkDup',
tabId: Number(sessionStorage[chrome.runtime.id]),
}, isDupe => {
console.log(isDupe);
});
} else {
chrome.runtime.sendMessage({
action: 'getTabId'
}, tabId => {
sessionStorage[chrome.runtime.id] = tabId;
});
}
background/event script:
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
switch (msg.action) {
case 'getTabId':
sendResponse(sender.tab.id);
return;
case 'checkDup':
chrome.tabs.get(msg.tabId, tab => {
if (tab
&& tab.index == sender.tab.index - 1
&& tab.url == sender.tab.url) {
sendResponse(true);
console.log('Tab duplicated: ', tab, '->', sender.tab);
}
});
return true; // keep the message channel open
}
});

Categories

Resources