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
}
});
Related
I am trying to create a pretty simple chrome extension that enables me to navigate the browser back and forward using horizontal scrolling. I want to be able to enable and disable the extension; however I want the state to persist when the tab changes (whether the active tab is a new url or a new tab is launched).
Here is my current code that partially works for only the active tab:
manifest.json:
{
"name": "SwipeNav",
"action": {},
"manifest_version": 3,
"version": "0.1",
"description": "Navigates browser forward and back with 2 finger swiping",
"permissions": [
"activeTab",
"scripting",
"tabs",
"alarms"
],
"background": {
"service_worker": "background.js"
}
}
background.js
function twoFingerBack() {
console.log("Enabling 2 finger scrolling");
let timer;
window.addEventListener("wheel", function(event) {
clearTimeout(timer);
if (Math.abs(event.deltaX) > Math.abs(event.deltaY)) {
timer = setTimeout(function() {
if (event.deltaX > 0) {
history.back();
} else if (event.deltaX < 0) {
history.forward();
}
}, 200);
}
});
}
function removeExt() {
console.log("Disabling 2 finger scrolling");
window.removeEventListener("wheel", removeExt)
}
let enabled = true;
chrome.action.onClicked.addListener((tab) => {
enabled = !enabled;
if (enabled) {
if (!tab.url.includes("chrome://")) {
chrome.scripting.executeScript({
target: { tabId: tab.id },
function: twoFingerBack
});
}
} else {
if (!tab.url.includes("chrome://")) {
chrome.scripting.executeScript({
target: { tabId: tab.id },
function: removeExt
});
}
}
});
I have read that I can use chrome.tabs.onActivated however I could not get this to work at all. Not sure if that is the right path.
Any help is appreciated...thanks!
Save the state in storage. Since localStorage is not available in a service worker you can use chrome.storage.local.
Register/unregister the content script, so it runs automatically next time the tab is loaded.
Process all tabs using executeScript to apply the changes to the currently loaded pages.
Use a global function for the wheel listener and use its name in removeEventListener.
// manifest.json
Add "storage" inside "permissions"
Add "host_permissions": ["<all_urls>"]
// background.js
chrome.action.onClicked.addListener(async (tab) => {
let {enabled} = await chrome.storage.local.get('enabled');
enabled = !enabled;
chrome.storage.local.set({enabled});
await chrome.scripting.unregisterContentScripts({ids: ['foo']}).catch(() => {});
if (enabled) {
chrome.scripting.registerContentScripts([{
id: 'foo',
js: ['content.js'],
matches: ['<all_urls>'],
runAt: 'document_start',
}]);
}
const execOpts = enabled ? {files: ['content.js']} : {func: removeExt};
const tabs = (await chrome.tabs.query({}))
.sort(t => t.active ? -1 : 0); // processing the active tab(s) first
for (const {id} of tabs) {
chrome.scripting.executeScript({target: {tabId: id}, ...execOpts})
.catch(() => {});
}
});
function removeExt() {
console.log('Disabling');
// onWheel is the name of the global function used in content.js
if (typeof onWheel === 'function') removeEventListener('wheel', onWheel);
}
// content.js
console.log('Enabling');
if (typeof onWheel !== 'function') (() => {
let timer;
window.onWheel = e => {
clearTimeout(timer);
const x = e.deltaX;
if (x && Math.abs(x) > Math.abs(e.deltaY)) {
timer = setTimeout(() => x > 0 ? history.back() : history.forward(), 200);
}
};
addEventListener('wheel', onWheel);
})();
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 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.
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")
In background.js:
function checkForValidUrl(tabId, changeInfo, tab) {
if(changeInfo.status === "loading") {
if (tab.url.indexOf('google.com') > -1 || tab.url.indexOf('amazon.com') > -1) {
chrome.pageAction.show(tabId);
}
}
};
chrome.tabs.onUpdated.addListener(checkForValidUrl);
chrome.pageAction.onClicked.addListener(function(tab){
if (tab.url.indexOf('google.com') > -1){
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.sendMessage(tab.id, {google_go: "go"});
});
chrome.tabs.create({url: "http://facebook.com", "active":true});
chrome.runtime.onMessage.addListener( function(request, sender, sendResponse) {
chrome.runtime.onConnect.addListener(function(port) {
console.assert(port.name == "facebook_chanel");
port.onMessage.addListener(function(msg) {
if (msg.facebook_go == "go")
port.postMessage(request);
});
});
});
}
In facebook.js:
$(document).ready(function(){
var port = chrome.runtime.connect({name: "facebook_chanel"});
port.postMessage({facebook_go: "go"});
port.onMessage.addListener(function(msg) {
console.log(msg);
});
});
I go too google. Press on pageAction, I see tab with facebook, see one Object in console. In html/background.html console (Chrome) I see the error Assertion failed: in
console.assert(port.name == "facebook_chanel");
I go to google again, press pageAction, and I see at new facebook page one old and 2 new Objects.
How to fix it? Thanks.
UPDATE
in background.js
chrome.tabs.create({url: "http://facebook.com", "active":true}, function(tab){
chrome.runtime.onMessage.addListener( function(request, sender, sendResponse) {
chrome.tabs.query({active: true, currentWindow: true}, function(tab) {
chrome.tabs.sendMessage(tab[0].id, request);
});
});
});
in facebook.js:
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
console.log(request);
});
Its right way? But I have clear console, whats wrong?
It is still not very clear what you are trying to achieve and why you chose to use porsistent ports (instead of single messaging) and page-actions, but from what I did understand, context-menus could be a better/cleaner approach.
So, below is the source code of a sample extension that registers a context-menu for when some text is selected (i.e. select/highlight some text and right click) and then opens an Facebook tab and sends the selected text for further processing (by the FB tab's content script).
If you still need a page-action instead of a context-menu, change the code like this:
Add listener for chrome.pageAction.onClicked in background-page.
Inject some code into the original tab (e.g. google.com) to retrieve ad return the current selection (using chrome.tabs.executeScript()).
Handle the acquired selected text as demonstrated in the code below.
manifest.json:
{
"manifest_version": 2,
"name": "Test Extension",
"version": "0.0",
"offline_enabled": false,
"background": {
"persistent": false,
"scripts": [
"background.js"
]
},
"content_scripts": [{
"matches": ["*://*.facebook.com/"],
"js": ["content.js"],
"run_at": "document_end",
"all_frames": false
}],
"permissions": [
"contextMenus"
]
}
content.js:
chrome.runtime.onMessage.addListener(function(msg) {
/* Input validation */
if (!msg.source || !msg.text) {
console.error('Bad message format: ', msg);
return;
}
/* Handle the selected text */
switch (msg.source) {
case 'Amazon':
case 'Google':
alert('Selected on ' + msg.source + ':\n' + msg.text);
break;
default:
alert('Unknown source: ' + msg.source);
break;
}
});
background.js:
var contextMenuID = 'copyToFB';
var googleRegex = /^https?:\/\/(?:[^\.]+\.)?google\..+/;
var amazonRegex = /^https?:\/\/(?:[^\.]+\.)?amazon\..+/;
/* `onUpdated` listener factory (for when FB has loaded) */
var listenerFactory = function(trgTabID, msgSrc, selectedText) {
return function(tabId, info, tab) {
if ((trgTabID === tabId) && (info.status === 'complete')) {
chrome.tabs.onUpdated.removeListener(arguments.callee);
chrome.tabs.sendMessage(trgTabID, {
source: msgSrc,
text: selectedText
});
}
}
};
chrome.contextMenus.create({
type: 'normal',
id: contextMenuID,
title: 'Copy to Facebook',
contexts: ['selection'],
// For some reason documentsUrlPatterns
// does not seem to work (at least on Windows).
// Theoratically, you should be able to limit the
// context-menu on specific sites only
//documentUrlPatterns: [
//'*://*.google.*/*',
//'*://*.amazon.*/*'
//],
enabled: true
});
chrome.contextMenus.onClicked.addListener(function(info, tab) {
console.log('Context menu clicked: ', info.menuItemId);
if (info.menuItemId === contextMenuID) {
var selectedText = info.selectionText;
console.log('Selected text: ', selectedText);
if (!selectedText) {
alert('Nothing to copy to Facebook !');
return;
}
var url = info.frameUrl ? info.frameUrl : info.pageUrl;
if (googleRegex.test(url)) {
console.log('Google.com URL: ', url);
/* Handle text selected on `google.com` */
chrome.tabs.create({
url: 'http://facebook.com/',
active: true
}, function(tab) {
chrome.tabs.onUpdated.addListener(
listenerFactory(tab.id, 'Google', selectedText));
});
} else if (amazonRegex.test(url)) {
console.log('Amazon.com URL: ', url);
/* Handle text selected on `amazon.com` */
chrome.tabs.create({
url: 'http://facebook.com/',
active: true
}, function(tab) {
chrome.tabs.onUpdated.addListener(
listenerFactory(tab.id, 'Amazon', selectedText));
});
} else {
console.log('Non-matching URL: ', url);
}
}
});
Final notes:
Because documentUrlPatterns does not seem to work (at least on Windows), the context-menu is shown on every page (when you select some text). You could add extra listeners (e.g. for chrome.tabs.onActivated etc) to remove or disable the context menu when the user is not on one of the permitted URLs.
It might be a good idea to keep track of an ope FB tab and not create a new one every time. (Or you could also look for an already opened (e.g. by the user) FB tab.)