I am trying to add text to an editable field with a context menu.
I tried to follow this SO but I cannot seem to get it to add the text to the field.
This is my content, which seems to make sense. I believe it is adding the context for what the background script is looking for.
var clickedEl = null;
document.addEventListener("mousedown", function(event){
//right click
if(event.button == 2) {
clickedEl = event.target;
}
}, true);
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if(request == "getClickedEl") {
sendResponse({value: clickedEl.value});
}
});
And here is what I have for my Background script. This is the part where I am not sure if I am doing it correctly.
function onClickHandler(info, tab) {
if (info.menuItemId.indexOf("context") > -1) {
var type = info.menuItemId.replace('context', '');
theLog = type;
function mycallback(info, tab) {
chrome.tabs.sendMessage(tab.id, "getClickedEl", function(clickedEl) {
elt.value = theLog.value;
});
}
}
}
Your background script runs in a separate hidden page with its own URL and DOM, which cannot access the web page directly, see the architecture overview in the documentation. Simply send the text to the content script, which will then use document.execCommand to insert the value into the active element.
Solution 1.
content script:
chrome.runtime.onMessage.addListener(msg => {
document.execCommand('insertText', false, msg);
});
background script:
chrome.contextMenus.onClicked.addListener((info, tab) => {
if (info.menuItemId.includes('context')) {
const text = info.menuItemId.replace('context', '');
chrome.tabs.sendMessage(tab.id, text, {frameId: info.frameId || 0});
}
}
Note we're sending directly to the frame where the context menu was invoked, which is needed in the general case (maybe not in yours) with the content script running in all iframes which is declared in manifest.json:
"content_scripts": [{
"matches": ["<all_urls>"],
"all_frames": true,
"match_about_blank": true,
"js": ["content.js"]
}]
Solution 2.
However, if this is the only function of the content script, it's better not to declare it in manifest.json at all, but instead inject dynamically in the background script:
chrome.contextMenus.onClicked.addListener((info, tab) => {
if (info.menuItemId.includes('context')) {
const text = info.menuItemId.replace('context', '');
chrome.tabs.executeScript(tab.id, {
frameId: info.frameId || 0,
matchAboutBlank: true,
code: `document.execCommand('insertText', false, ${JSON.stringify(text)})`,
});
}
}
And add the permission in manifest.json that doesn't require a user confirmation on installation (documentation):
"permissions": ["activeTab"]
Related
I am trying to send some selected text from the current page to an HTML page. Since, I can not do it directly, I am using message passing: content script to background script, then background script to the HTML page. But get error if the HTML is not open already, even then I get an error first time:
background.js:1 Uncaught (in promise) Error: Could not establish
connection. Receiving end does not exist.
If I close the page, then run again, this error will keep happening.
What is the solution ?
My code is given below:
popup.js
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
console.log("from popup = \n"+request.bg);
})
background.js
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
chrome.tabs.create({url: 'popup.html'});
chrome.runtime.sendMessage({bg: request.exc})
})
content.js
string=[];
function doc_keyUp(e) {
if (getSelectionText() != "") {
if (e.key === '1') {
string=getSelectionText();
chrome.runtime.sendMessage({exc: string});
}
}
} // doc_keyUp(e)
console.log('The program has started!!!');
// register the handler
document.addEventListener('keyup', doc_keyUp, false);
function getSelectionText() {
var text = "";
text = window.getSelection().toString();
if (window.getSelection) {
text = window.getSelection().toString();
}
else if (document.selection && document.selection.type != "Control") {
text = document.selection.createRange().text;
}
return text;
}
popup.html
<html>
<script src="popup.js">
</script>
</html>
I am doing this to buid a chrome extension, so if required then:
manifest.json
{
"name": "",
"version": "1.0",
"description":"",
"manifest_version": 3,
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"]
}
],
"action": {
"default_title": "T"
},
"background":{
"service_worker": "background.js"
},
"permissions": ["tabs","storage"]
}
Post Script:
Is there an alternative way to send data directly from content script to script of an HTML (e.g. here popup.js is such script) without going through background script?
Is there a to get a variable from background script without message passing to any other script? I saw solutions using chrome.extension.getBackgroundPage() but it does not work anymore after manifest v3. Is there anything else?
You can use localStorage to send and get items. If your item not included in your localStorage you can initialize it.
content.js
localStorage.setItem("bg", "#000000"); // <- You can send json or whatever you want
popup.js
var bg = localStorage.getItem("bg");
or you can use chrome.storage API
content.js
chrome.storage.local.set({bg: value}, function() {
console.log('Value is set to ' + value);
});
popup.js
chrome.storage.local.get(['bg'], function(result) {
console.log('Key is ' + result.bg);
});
Add storage to permissions.
And you need to add to your extension permissions (manifest file
)
But you should be careful because
confidential user information should not be stored! The storage area isn't encrypted.
Working Code:
content.js
string=[];
function doc_keyUp(e) {
if (getSelectionText() != "") {
if (e.key === 'Enter') {
string=getSelectionText();
chrome.storage.local.set({bg: string}, function() {
console.log('Bg is set to ' + value);
});
chrome.runtime.sendMessage({});
}
}
} // doc_keyUp(e)
console.log('The program has started!!!');
// register the handler
document.addEventListener('keyup', doc_keyUp, false);
function getSelectionText() {
var text = "";
text = window.getSelection().toString();
if (window.getSelection) {
text = window.getSelection().toString();
}
else if (document.selection && document.selection.type != "Control") {
text = document.selection.createRange().text;
}
return text;
}
popup.js
chrome.storage.local.get(['bg'], function(result) {
alert('Bg is ' + result.bg);
});
background.js
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
chrome.tabs.create({url: 'popup.html'});
});
I changed e.key === 'Enter' in content.js for better testing. And I deleted chrome.runtime.onMessage.addListener from popup.js because you will call popup after background.js receive message so you don't need to get message everytime in popup.js.You need to get value once when popup created.
Selected Text Image:
And popup alert:
I'm writing a Chrome Extention to manipulate pdf file so I want to get selected text in the pdf. How can I do that.
Some thing like that:
You can use the internal undocumented commands of the built-in PDF viewer.
Here's an example of a content script:
function getPdfSelectedText() {
return new Promise(resolve => {
window.addEventListener('message', function onMessage(e) {
if (e.origin === 'chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai' &&
e.data && e.data.type === 'getSelectedTextReply') {
window.removeEventListener('message', onMessage);
resolve(e.data.selectedText);
}
});
// runs code in page context to access postMessage of the embedded plugin
const script = document.createElement('script');
if (chrome.runtime.getManifest().manifest_version > 2) {
script.src = chrome.runtime.getURL('query-pdf.js');
} else {
script.textContent = `(${() => {
document.querySelector('embed').postMessage({type: 'getSelectedText'}, '*');
}})()`;
}
document.documentElement.appendChild(script);
script.remove();
});
}
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
if (msg === 'getPdfSelection') {
getPdfSelectedText().then(sendResponse);
return true;
}
});
This example assumes you send a message from the popup or background script:
chrome.tabs.query({active: true, currentWindow: true}, ([tab]) => {
chrome.tabs.sendMessage(tab.id, 'getPdfSelection', sel => {
// do something
});
});
See also How to open the correct devtools console to see output from an extension script?
ManifestV3 extensions also need this:
manifest.json should expose query-pdf.js
"web_accessible_resources": [{
"resources": ["query-pdf.js"],
"matches": ["<all_urls>"],
"use_dynamic_url": true
}]
query-pdf.js
document.querySelector('embed').postMessage({type: 'getSelectedText'}, '*')
There is no one generic solution for all pdf extensions.
Every extention has is own API.
If you work with google-chrome extension i belive it's impossible.
How to get the selected text from an embedded pdf in a web page?
How extension get the text selected in chrome pdf viewer?
I am trying to write a google chrome extension where I use a contextmenu. This contextmenu is available on editable elements only (input texts for example). When the contextmenu is clicked and executed I would like to retrieve in the callback function the element (the input text) on which the contextmenu has been executed in order to update the value associated to this input text.
Here is the skeleton of my extension:
function mycallback(info, tab) {
// missing part that refers to the question:
// how to retrieve elt which is assumed to be
// the element on which the contextMenu has been executed ?
elt.value = "my new value"
}
var id = chrome.contextMenus.create({
"title": "Click me",
"contexts": ["editable"],
"onclick": mycallback
});
The parameters associated to the mycallback function contain no useful information to retrieve the right clicked element. It seems this is a known issue (http://code.google.com/p/chromium/issues/detail?id=39507) but there is no progress since several months. Does someone knows a workaround: without jquery and/or with jquery?
You can inject content script with contextmenu event listener and store element that was clicked:
manifest.json
"content_scripts": [{
"matches": ["<all_urls>"],
"js": ["content.js"],
"all_frames": true,
"match_about_blank": true
}]
content script.js
//content script
var clickedEl = null;
document.addEventListener("contextmenu", function(event){
clickedEl = event.target;
}, true);
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if(request == "getClickedEl") {
sendResponse({value: clickedEl.value});
}
});
background.js
//background
function mycallback(info, tab) {
chrome.tabs.sendMessage(tab.id, "getClickedEl", {frameId: info.frameId}, data => {
elt.value = data.value;
});
}
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. :)
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);
}
});