Chrome Extension workaround with popup html - javascript

My project is a Chrome extension that will do the following.
Push the extension icon.
Popup will appear (from popup.html)
5 buttons will be in the popup.
When you click one of the four buttons, one javascript code will be executed.
close popup window.
So depending on the answer of this post over here
Detect a button click in the browser_action form of a Google Chrome Extension
(big ups to Michael for his enormous help)
This example is only for one button. Created it with only one of my javascript code and works perfect.
But when it comes to put all of the 5 buttons i 've tried to make this kind of coding but it didnt work at all (im new at javascript code so dont hate)
Here are the codes
MANIFEST.JSON
{
"background": {
"scripts": [ "background.js" ]
},
"browser_action": {
"default_icon": "img/icon.png",
"default_title": "TITLE",
"default_popup": "popup.html"
},
"icons": {
"128": "img/icon_128.png",
"19": "img/icon19.png",
"38": "img/icon38.png",
"48": "img/icon_48_2.png"
},
"manifest_version": 2,
"name": " NAME",
"description": " DESCR ",
"permissions": [ "activeTab" ],
"version": "2.0"
}
POPUP.HTML
<html>
<head>
<script src="popup.js"></script>
<style type="text/css" media="screen">
body { min-width:250px; text-align: center; }
#click-me-l { font-size: 20px; }
#click-me-f { font-size: 20px; }
</style>
</head>
<body>
<button id='click-me-l'>Click1</button>
<button id='click-me-f'>Click2</button>
</body>
</html>
POPUP.JS
function clickHandler(e) {
chrome.extension.sendMessage({directive: "popup-click-l"}, function(response) {
this.close(); // close the popup when the background finishes processing request
});
}
document.addEventListener('DOMContentLoaded', function () {
document.getElementById('click-me-l').addEventListener('click', clickHandler);
})
function clickHandler(e) {
chrome.extension.sendMessage({directive: "popup-click-f"}, function(response) {
this.close(); // close the popup when the background finishes processing request
});
}
document.addEventListener('DOMContentLoaded', function () {
document.getElementById('click-me-f').addEventListener('click', clickHandler);
})
BACKGROUND.JS
chrome.extension.onMessage.addListener(
function(request, sender, sendResponse) {
switch (request.directive) {
case 1 "popup-click-l":
// execute the content script
chrome.tabs.executeScript(null, { // defaults to the current tab
file: "script1.js", // script to inject into page and run in sandbox
allFrames: true // This injects script into iframes in the page and doesn't work before 4.0.266.0.
});
case 2 "popup-click-f":
// execute the content script
chrome.tabs.executeScript(null, { // defaults to the current tab
file: "script2.js", // script to inject into page and run in sandbox
allFrames: true // This injects script into iframes in the page and doesn't work before 4.0.266.0.
});
sendResponse({}); // sending back empty response to sender
break;
default:
// helps debug when request directive doesn't match
alert("Unmatched request of '" + request + "' from script to background.js from " + sender);
}
}
);
So the codes in the link are working PERFECT for only 1 button.
in this example i am trying to make it work for 2 buttons but i cant find what im doing wrong. If anyone has any idea i would appreciate it.
Thanks a lot for your time!!!
(UPDATE 2. Updated codes for 2 buttons but not working.)

You’re defining clickHandler twice, so only the second one counts. One fix would be:
function clickHandler(e) {
chrome.extension.sendMessage({"directive": e.target.id}, function(response) {
this.close(); // close the popup when the background finishes processing request
});
}
In general, you’re repeating yourself too much. You could combine your DOMContentLoaded events into one:
document.addEventListener('DOMContentLoaded', function () {
document.getElementById('click-me-l').addEventListener('click', clickHandler);
document.getElementById('click-me-f').addEventListener('click', clickHandler);
})
but even better would be to put all the buttons into an array, so that popup.js is now:
function clickHandler(e) {
chrome.extension.sendMessage({"directive": e.target.id}, function(response) {
this.close(); // close the popup when the background finishes processing request
});
}
document.addEventListener('DOMContentLoaded', function () {
var buttons = document.getElementsByTagName("button");
for ( var i = 0 ; i < buttons.length ; i++ ) {
buttons[i].addEventListener('click',clickHandler);
}
})
(And I’d recommend button { font-size: 20px; } in your style instead of five separate ids.)
Finally, your switch statement is buggy. Once you start a case, you’ll keep going until you get to a break, so that case "popup-click-l" hits both cases. You could have a separate executeScript for each case, but even better would be to assign to fileName based on the case, and have a single injection at the end. Or best of all would be to have a javascript object define which files go with which ids, so that background.js is now:
chrome.extension.onMessage.addListener(
function(request, sender, sendResponse) {
var injected = {
"click-me-l": "script1.js",
"click-me-f": "script2.js"
};
chrome.tabs.executeScript(null, {
"file": injected[request.directive],
"allFrames": true
});
sendResponse({});
}
);
Fundamentally, this comes back to a point I made in a comment: browser extensions are a bad way to learn javascript, because you’re learning two separate things at the same time. Your difficulties with switch, {}, and generally following the code is a javascript problem. Not seeing when the console tells you about syntax errors is more of a browser extension problem. And your biggest problem is that you’re not seeing which error is which.

Related

Firefox extension, identification of context

I've built an AddIn, a chess game visualiser, which worked many years on desktop browsers Firefox, Chrome, Opera, Safari and IE. The AddIn runs when invoked from context menu and parses the selected text from the current page. The selected text is expected to be Chess Notation Format, which can be FEN or PGN.
It is the second time when the addin becomes incompatible with Firefox when FF version changes, and I have to do architectural changes. The new firefox architecture is meant to be compatible with Chrome, but it is only partially compatible, so I can't fully reuse the code from chrome. I also used info from
Communicate data from popup to content script injected by popup with executeScript()
and Communicate data from popup to content script injected by popup with executeScript()
Here are my questions:
See the code of background.js, browser.runtime.sendMessage is invoked before the invocation browser.runtime.onMessage.addListener from pop.js. I don't understand how to send the message from background.js after the window opened by browser.windows.create is fully created, all scripts ended execution and document fully loaded. Also I can't do it on invocation of document.addEventListener('DOMContentLoaded', when I call browser.runtime.onMessage.addListener it generates the error Error: sendRemoveListener on closed conduit b212f9bfb4f0394efe56168a583c91346caf2d00#temporary-addon.412316861125 and probably is not executed at all. Maybe there are special events I can handle, I don't see much documentation on that. In Chrome the approach mostly similar, but it works pretty well.
I still can catch the message sent from background.js in some unusable scenario: if I opened two windows, yet good for debugging purposes. The problem is that inside the handler from pop.js the document.innerHTML is undefined no matter that I am trying to do. I can't handle the content of the window from extension api handlers. I think the document is fully loaded because it is handled by the first of the two opened windows.
Note, I can send the selection text to newly opened popup via invocation URL parameter. It works, but I think it is a workaround. I use it on Opera and Safari, it also works on Firefox, but I am thinking getting rid of this where it is possible.
There is the scenario, simplified:
background.js:
{//Firefox create context menus
var id = chrome.contextMenus.create({"title": "Chess game", "contexts":["selection", "page"]});
var childId = chrome.contextMenus.create
({
"title": "Play Small","contexts":["selection", "page"],
"parentId": id,
"onclick": (info, tab) => { playBoard (info, tab, "mini18"); }
});
}
function onCreated(windowInfo, request)
{
browser.windows.get(windowInfo.id).then
(
(wnd) => { browser.runtime.sendMessage(request); }
);
}
function playBoard (info, tab, imagePath)
{
let creating = browser.windows.create({ type:"detached_panel", url:"pop.html", width:250, height:100 });
creating.then
(
(inf) =>
{onCreated
(
inf,
{
chessObject:
{
gametype : "PGN_OR_FEN_board",
content : selection,
imgPath : info.selectionText
}
}
)
}
);
}
pop.js:
browser.runtime.onMessage.addListener
(
(request) =>
{
console.log("popup chrome listen to game: " + request.chessObject.content);
console.log("popup chrome listen to game: document inner html: " + document.innerHTML);
}
);
pop.html:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>hello world</title>
<script language="javascript" src="pop.js"></script>
</head>
<body>
hello popup
</body>
</html>
manifest.json:
{
"manifest_version": 2,
"name": "Roatta Waaayyyy!!!",
"version": "1.0.0.4",
"description": "Chess AddIn for Firefox",
"icons": { "32": "icons/roatta_32_32.jpg" },
"permissions": [ "contextMenus" ],
"background": { "scripts": ["background.js"] }
}
For reference here is the full source code on GIT, the one I try to implement now is Firefox65 which is meant to replace the previous Firefox. The basic version is the one for Chrome, initially it was the one for Firefox.
As suggested by #wOxxOm I applied the inverse message pattern, and it works.
Now the background.js register a listener and waits to be called, then it removes the listener:
{//Firefox
console.log("Creating context menus");
var id = chrome.contextMenus.create({"title": "Chess game", "contexts":["selection", "page"]});
var childId = chrome.contextMenus.create
({
"title": "Play Small","contexts":["selection", "page"],
"parentId": id,
"onclick": (info, tab) => { playBoard (info, tab, "mini18"); }
});
}
function playBoard (info, tab, imagePath)
{
var createData =
{
type : "detached_panel",
url : "pop.html",
width : 250,
height: 100
};
let requestData =
{
chessObject:
{
gametype : "PGN_OR_FEN_board",
content : info.selectionText,
imgPath : imagePath
}
};
try
{
console.log ("Play Board: " + imagePath);
console.log ("Selection: " + info.selectionText);
let creating = browser.windows.create(createData);
function onGameDataExchange(request)
{
browser.runtime.onMessage.removeListener(onGameDataExchange);
return Promise.resolve(requestData);
}
browser.runtime.onMessage.addListener (onGameDataExchange);
console.log ("opened pop.html");
}
catch (err)
{
console.log ("Error: main background playBoard() " + err);
}
}
The pop.js is the one requesting the message:
document.addEventListener('DOMContentLoaded',
function (event)
{
browser.runtime.sendMessage ( { chessObject: { gametype : "PGN_OR_FEN_board" } } )
.then
(
(request) =>
{
console.log("on DOMContentLoaded message: " + request.chessObject.gametype);
console.log("on DOMContentLoaded message: " + request.chessObject.content);
console.log("on DOMContentLoaded message: " + request.chessObject.imgPath);
}
);
console.log("Popup DOMContentLoaded send message end");
}
);

chrome.runtime.sendMessage not working on the 1st click when running normally. it works while debugging though

I have a function in the context.js which loads a panel and sends a message to panel.js at the last. The panel.js function updates the ui on receiving that msg. But it is not working for the first click i.e. it just loads normal ui, not the one that is expected that is updated one after the msg is received. while debugging it works fine.
manifest.json
"background": {
"scripts": ["background.js"],
"persistent": false
},
"content_scripts": [{
"all_frames": false,
"matches": ["<all_urls>"],
"js":["context.js"]
}],
"permissions": ["activeTab","<all_urls>", "storage","tabs"],
"web_accessible_resources":
"panel.html",
"panel.js"
]
context.js - code
fillUI (){
var iframeNode = document.createElement('iframe');
iframeNode.id = "panel"
iframeNode.style.height = "100%";
iframeNode.style.width = "400px";
iframeNode.style.position = "fixed";
iframeNode.style.top = "0px";
iframeNode.style.left = "0px";
iframeNode.style.zIndex = "9000000000000000000";
iframeNode.frameBorder = "none";
iframeNode.src = chrome.extension.getURL("panel.html")
document.body.appendChild(iframeNode);
var dataForUI = "some string data"
chrome.runtime.sendMessage({action: "update UI", results: dataForUI},
(response)=> {
console.log(response.message)
})
}
}
panel.js - code
var handleRequest = function(request, sender, cb) {
console.log(request.results)
if (request.action === 'update Not UI') {
//do something
} else if (request.action === 'update UI') {
document.getElementById("displayContent").value = request.results
}
};
chrome.runtime.onMessage.addListener(handleRequest);
background.js
chrome.runtime.onMessage.addListener((request,sender,sendResponse) => {
chrome.tabs.sendMessage(sender.tab.id,request,function(response){
console.log(response)`
});
});
panel.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="panel.css" />
</head>
<body>
<textarea id="displayContent" rows="10" cols="40"></textarea>
</body>
</html>
Any suggestions on what I am doing wrong or what can I do instead?
An iframe with a real URL loads asynchronously so its code runs after the embedding code finishes - hence, your message is sent too early and is lost. The URL in your case points to an extension resource so it's a real URL. For reference, a synchronously loading iframe would have a dummy URL e.g. no src at all (or an empty string) or it would be something like about:blank or javascript:/*some code here*/, possibly srcdoc as well.
Solution 1: send a message in iframe's onload event
Possible disadvantage: all extension frames in all tabs will receive it, including the background script and any other open extension pages such the popup, options, if they also have an onMessage listener.
iframeNode.onload = () => {
chrome.runtime.sendMessage('foo', res => { console.log(res); });
};
document.body.appendChild(iframeNode);
Solution 2: let iframe send a message to its embedder
Possible disadvantage: wrong data may be sent in case you add several such extension frames in one tab and for example the 2nd one loads earlier than the 1st one due to a bug or an optimization in the browser - in this case you may have to use direct DOM messaging (solution 3).
iframe script (panel.js):
chrome.tabs.getCurrent(ownTab => {
chrome.tabs.sendMessage(ownTab.id, 'getData', data => {
console.log('frame got data');
// process data here
});
});
content script (context.js):
document.body.appendChild(iframeNode);
chrome.runtime.onMessage.addListener(
function onMessage(msg, sender, sendResponse) {
if (msg === 'getData') {
chrome.runtime.onMessage.removeListener(onMessage)
sendResponse({ action: 'update UI', results: 'foo' });
}
});
Solution 3: direct messaging via postMessage
Use in case of multiple extension frames in one tab.
Disadvantage: no way to tell if the message was forged by the page or by another extension's content script.
The iframe script declares a one-time listener for message event:
window.addEventListener('message', function onMessage(e) {
if (typeof e.data === 'string' && e.data.startsWith(chrome.runtime.id)) {
window.removeEventListener('message', onMessage);
const data = JSON.parse(e.data.slice(chrome.runtime.id.length));
// process data here
}
});
Then, additionally, use one of the following:
if content script is the initiator
iframeNode.onload = () => {
iframeNode.contentWindow.postMessage(
chrome.runtime.id + JSON.stringify({foo: 'data'}), '*');
};
document.body.appendChild(iframeNode);
if iframe is the initiator
iframe script:
parent.postMessage('getData', '*');
content script:
document.body.appendChild(iframeNode);
window.addEventListener('message', function onMessage(e) {
if (e.source === iframeNode) {
window.removeEventListener('message', onMessage);
e.source.postMessage(chrome.runtime.id + JSON.stringify({foo: 'data'}), '*');
}
});
one possible way that worked for me is by using functionality in setTimeout() method.
in context.js
setTimeout(() => {
chrome.runtime.sendMessage({action: "update UI", results: dataForUI},
(response)=> {
console.log(response.message)
}
)
}, 100);
But I am not sure if this is the best way.

Disable Extension on clicking the icon

I am trying to write a Chrome extension which can be disabled with a simple click on the browser action icon. I want to give the users this option because the extension raises a javascript alert when the alarm is triggered. The problem is that even after clearing the alarms, I am seeing alerts being raised. Also the icon click, which I want to work as a switch, isn't working as intended.
I have declared background.js as my background javascript file in manifest.json
"background": {
"scripts": ["alert.js"],
"persistent": true
},
"browser_action": {
"default_icon": "images/green.png",
"default_action": "popup.html",
"default_title": "Toggle Productiwitty"
}
background.js
var ExtensionOn = true;
function SwitchOn(e)
{
chrome.alarms.create("Alarm", {delayInMinutes: 0.1, periodInMinutes: 1} );
}
function SwitchOff(e)
{
chrome.alarms.clear("Alarm");
}
function showpopup()
{
alert("Inside function showpopup");
console.log("alert shown");
}
function click(e)
{
if(ExtensionOn)
{
SwitchOff();
console.log("switched off");
chrome.browserAction.setBadgeText({text: "Off"});
}
else if(!ExtensionOn)
{
SwitchOn();
console.log("switched on");
chrome.browserAction.setBadgeText({text: "ON"});
// Replace 15.0 with user selected time in minutes
}
//Toggle ExtensionOn
ExtensionOn = ~ ExtensionOn;
}
if(ExtensionOn)
{
chrome.alarms.onAlarm.addListener(showpopup);
}
chrome.browserAction.onClicked.addListener(click);
My "default_action": "popup.html" calls popup.js which creates the alarm
chrome.alarms.create("Alarm", {delayInMinutes: 0.1, periodInMinutes: 1} );
The idea is that once the extension is loaded, it should show a popup every 1 minute and if you click the icon, the extension gets disabled temporarily. On clicking the icon again, the same alert will be raised periodically.
ExtensionOn = true
> true
ExtensionOn = ~ ExtensionOn
> -2
Boolean(ExtensionOn)
> true
If you want to toggle a boolean, use !, not ~.
if(ExtensionOn)
{
chrome.alarms.onAlarm.addListener(showpopup);
}
This is only called once, when a page first loads. If the extension isn’t on at that time, the listener won’t be added, and the function will never be called. I’d recommend moving the if test into showpopup.
From https://developer.chrome.com/extensions/management#method-setEnabled
chrome.management.setEnabled(ExtId, true/false, callback)
But i'm not sure disabling the extension is what you actually want to do,
once you disable it you have to go to chrome://extension and manually re-enable it.

How can I determine if a Chrome extension is in a popup from the content script?

Background
I have a Chrome extension with a browser action to launch index.html in a new tab.
I'd like to update the extension to open index.html in a popup first, and then include a button users can click to optionally open the app in a new tab.
I don't want this button to show when it's not a popup (since it wouldn't make sense), which means the content script needs to know whether it is a popup in order to show the button.
Questions
This is a two part question:
How does a Chrome extension popup know it's a popup?
How do I pass that information to a content script before the popup is rendered?
What I've tried
I've tried to use chrome.extension.getViews in background.js to firstly determine if a popup is open. Then, I send a message to the content script which then shows the button. However I haven't gotten it to work - views is always an empty array, and the message doesn't seem to ever be received by the content script.
Here are the relevant parts of my manifest.json file:
"background": {
"scripts": ["background.js"]
},
"browser_action": {
"default_icon": {
"19": "img/icon19.png",
"38": "img/icon38.png"
},
"default_title": "Super Simple Tasks",
"default_popup": "index.html"
}
And here's what I've been trying in my background.js:
// Get all popups
var views = chrome.extension.getViews({ type: "popup" });
// Send a message if there is a popup
if (views.length > 0){
chrome.tabs.query({active: true, currentWindow: true}, function(tabs){
chrome.tabs.sendMessage(tabs[0].id, {action: "popup_open"}, function(response) {});
});
};
And then in my content script, I listen for the message and then add a class to the body:
// Listen for the message
chrome.extension.onMessage.addListener(function(msg, sender, sendResponse) {
if (msg.action === 'popup_open') {
// My code here to show the button
}
});
After talking with a friend I discovered an elegant solution that doesn't involve messaging or even a background.js script at all.
I can specify ?popup=true in manifest.json and check for that parameter in my extension's content script. Here's the code:
manifest.json now looks like this:
"browser_action": {
"default_icon": {
"19": "img/icon19.png",
"38": "img/icon38.png"
},
"default_title": "Super Simple Tasks",
"default_popup": "index.html?popup=true"
}
The following code in my content script (taken from this answer) checks for ?popup=true. Worth noting that this function can handle multiple URL parameters split by the & character.
function getUrlParameter(sParam) {
var sPageURL = window.location.search.substring(1);
var sURLVariables = sPageURL.split('&');
for (var i = 0; i < sURLVariables.length; i++) {
var sParameterName = sURLVariables[i].split('=');
if (sParameterName[0] == sParam) {
return sParameterName[1];
}
}
}
var isPopup;
isPopup = getUrlParameter('popup') === 'true';
Finally, add a class to the body if it's a popup:
$('body').toggleClass('popup', isPopup)
In the manifest file add a hash to the url:
"browser_action": {
"default_popup": "index.html#popup"
}
In JavaScript:
if (location.hash === '#popup')
// do something awesome!
I needed something similar as i wanted to create some cross-compatible code for all script types.
I found that this worked quite well.
const SCRIPT_TYPE = (() => {
if (chrome && chrome.extension && chrome.extension.getBackgroundPage && chrome.extension.getBackgroundPage() === window) {
return 'BACKGROUND';
} else if (chrome && chrome.extension && chrome.extension.getBackgroundPage && chrome.extension.getBackgroundPage() !== window) {
return 'POPUP';
} else if (!chrome || !chrome.runtime || !chrome.runtime.onMessage) {
return 'WEB';
} else {
return 'CONTENT';
}
})();
chrome.tabs.getCurrent(function(tab) {
if(tab == undefined)
document.getElementById('mButton').style.display = 'inline-block';
});
I initially set the button's display: none; if the returned tab is undefined, means it's not a tab (so it is popup) and then I display button. You can reverse it of course.
======
Well the sending parameter also works, which in that case you won't need to add the query string in the manifest, just adding it in button's click listener would suffice.
btn.addEventListener('click', function() {
chrome.tabs.create({url: "index.html?popup=false"});
});
And then the same process (reading the query string and comparing, etc).
======
Alternatively you can make a copy of index.html say index2.html, remove the button from index.html, use index2.html in the manifest and index.html for button click. :)

Chrome Extension: How to reload tab from anywhere using keyboard events?

I am having issues with getting access to the Chrome's tab ID. I can fetch it, but it remains inside the extension and I cannot use it outside the extension, despite the fact that I was able to record keyboard events outside the extension.
Here's what I'm trying to do:
User navigates to a tab and fetches the tabId with a 'capture' button
The tabId is stored as a global variable
User then can navigate to any other tab inside his browser and from there with a key combination the user can reload the captured tab at any given moment by pressing CTRL + SHIFT simultaneously
extension.html
<!DOCTYPE html>
<html>
<head>
<title>Extension</title>
<style>
body {
min-width: 357px;
overflow-x: hidden;
}
</style>
<p>Step 1. Navigate to tab you want to refresh and click the 'capture' button</p>
<button type="button" id="capture">Capture!</button>
<p id="page"></p>
<p>Step 2. Now you can reload that tab from anywhere by pressing CTRL+SHIFT simultaneously</p>
</div>
<script src="contentscript.js"></script>
</head>
<body>
</body>
</html>
manifest.json
{
"manifest_version": 2,
"name": "Extension",
"description": "This extension allows you to trigger page refresh on key combinations from anywhere",
"version": "1.0",
"content_scripts": [
{
"matches": ["http://*/*","https://*/*"],
"run_at": "document_end",
"js": ["contentscript.js"]
}
],
"browser_action": {
"default_icon": "icon.png",
"default_popup": "extension.html"
},
"web_accessible_resources": ["script.js"],
"permissions": [
"tabs"
],
}
contentscript.js
var s = document.createElement('script');
s.src = chrome.extension.getURL("script.js");
(document.head||document.documentElement).appendChild(s);
s.parentNode.removeChild(s);
script.js
'use strict';
var isCtrl = false;
var tabId = 0;
document.onkeyup=function(e){
if(e.which === 17) {
isCtrl=false;
}
};
document.onkeydown=function(e){
if(e.which === 17) {
isCtrl=true;
}
if(e.which === 16 && isCtrl === true) {
/* the code below will execute when CTRL + SHIFT are pressed */
/* end of code */
return false;
}
};
document.getElementById('capture').onclick = function(){
chrome.tabs.getSelected(null, function(tab) {
tabId = tab.id;
document.getElementById('page').innerText = tab.id;
});
};
I thought this would be the solution, but it didn't work:
/* the code below will execute when CTRL + SHIFT are pressed */
chrome.tabs.getSelected(null, function(tab) {
chrome.tabs.reload(tabId);
});
/* end of code */
Having var tabId = 0; as a global variable seems pointless so I thought message passing should be the solution, but the problem with that is that I don't understand how I should implement it.
Any suggestions on how to refresh the tab from anywhere based on its ID?
Your contentscript.js is just a file with programmatic instructions written in JavaScript. Those instructions are interpreted as fresh and new each time they are loaded into a particular execution environment. Your popup and your content scripts are separate execution environments.
The contentscript.js file itself does not store state. When contentscript.js is loaded in a content script environment, the content script execution environment has no idea where else contentscript.js has been included.
The correct pattern to use here would be to have a background page maintain state and remember the tab ID of the last captured tab. The popup would use message passing to send the current tab ID to the background page (using chrome.runtime.sendMessage in the popup and chrome.runtime.onMessage in the background page). Then, later, the content script would send a message to the background page when it saw a Ctrl+Shift press, and the background page would invoke chrome.tabs.reload(tabId).
Inside extension.html, instead of your current <script> tag:
document.getElementById("capture").onclick = function() {
chrome.tabs.getSelected(null, function(tab) {
tabId = tab.id;
// send a request to the background page to store a new tabId
chrome.runtime.sendMessage({type:"new tabid", tabid:tabId});
});
};
Inside contentscript.js:
/* the code below will execute when CTRL + SHIFT are pressed */
// signal to the background page that it's time to refresh
chrome.runtime.sendMessage({type:"refresh"});
/* end of code */
background.js:
// maintaining state in the background
var tabId = null;
// listening for new tabIds and refresh requests
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
// if this is a store request, save the tabid
if(request.type == "new tabid") {
tabId = request.tabid;
}
// if this is a refresh request, refresh the tab if it has been set
else if(request.type == "refresh" && tabId !== null) {
chrome.tabs.reload(tabId);
}
});

Categories