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?
Related
Hi I am converting Google chrome extension from manifest version-2 to version-3
facing 2 issues those are mentioned below, but before that I will explain what extension in expected to do.
On click specific button on webpage I am calling console application that is copying JSON string in clipboard, then in chrome extension background.js I am getting clipboard data and passing it to content.js which is showing it in web page.
Errors / challenges:
1- Need to get clipboard text into a variable in background.js. I am able to get it in content.js but I need it to get it in background.js
2- I am getting these 2 error in background.js console, but extension is working
Unchecked runtime.lastError: Native host has exited.
Unchecked runtime.lastError: The message port closed before a response was received.
My Background.js looks like this
var port = null;
var tabId = null;
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
tabId=sender.tab.id;
var hostName = "my.console.app";
port = chrome.runtime.connectNative(hostName);
port.onDisconnect.addListener(onDisconnected);
sendResponse({status: 'ok'});
return true;
});
function onDisconnected() {
port = null;
SendResponse();
}
//this funciton need to upgrade to manifest version 3 because in v3 `chrome.extension.getBackgroundPage` is not compatible
function SendResponse() {
bg = chrome.extension.getBackgroundPage();
bg.document.body.innerHTML = ""; // clear the background page
var helper = null;
if (helper == null) {
helper = bg.document.createElement("textarea");
helper.style.position = "absolute";
helper.style.border = "none";
document.body.appendChild(helper);
}
//Focus the textarea
helper.select();
// perform a Paste in the selected control, here the textarea
bg.document.execCommand("Paste");
// Send data back to content_script
chrome.tabs.sendMessage(tabId, { action: "MY_CUSTOM_EVENT", response: helper.value });
}
content.js
document.addEventListener("MY_CUSTOM_EVENT", function (data) {
chrome.runtime.sendMessage({ runConsoleApp: true }, response => {
});
});
async function copyToTheClipboard(textToCopy){
navigator.clipboard.readText()
.then(text => {
//console.log('Pasted content: ', text);
$('.simulateEidResponse').html(text).trigger('click');
})
.catch(err => {
console.error('Failed to read clipboard contents: ', err);
});
}
Currently there's no way to do it in the background script due to crbug.com/1404835.
In the future the workaround will be execCommand + offscreen API.
The only reliable solution that works regardless of whether the tab is focused or not is to use document.execCommand("Paste") in the content script.
// background.js
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
if (msg.runConsoleApp) {
chrome.runtime.connectNative('my.console.app')
.onDisconnect.addListener(() => sendResponse(true));
return true;
}
});
// content.js
document.addEventListener('MY_CUSTOM_EVENT', async e => {
await chrome.runtime.sendMessage({ runConsoleApp: true });
document.querySelector('.simulateEidResponse').focus();
document.execCommand('selectAll');
document.execCommand('paste');
});
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 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.
I am developing a chrome extension for google calendar .
After an event I need to transverse to a particular url
So I use chrome.tabs.update(tabId, {url: ‘myurl’}) for updating the url of the current page.
But while updating the url , leave site pop up which is a default pop up for the browser is coming
Kindly suggest a way to remove it while updating the url in chrome extension
Ideally this is something to be handled by the extensions API so please star crbug/1031791. Meanwhile we can use the workarounds listed below.
Simplistic approach is to clear window.onbeforeunload
Works only for some sites.
Extension script (popup or background):
function clearUnloadPrompt() {
return new Promise(resolve => {
chrome.tabs.executeScript({
code: `(${() => {
const script = document.createElement('script');
script.textContent = 'window.onbeforeunload = null';
document.documentElement.appendChild(script).remove();
}})()`,
runAt: 'document_start',
}, () => {
chrome.runtime.lastError;
resolve();
});
});
}
clearUnloadPrompt().then(() => {
chrome.tabs.update({url: 'https://www.example.org/'});
});
Full exterminatus approach is to register beforeunload before the page does
Should work everywhere but the downside is that it requires a content script on all URLs where your extension needs to update the URL. Although not a problem if you already request these host permissions in manifest.json for your extension's main functionality.
manifest.json excerpt:
"content_scripts": [{
"matches": ["<all_urls>"],
"js": ["content.js"],
"run_at": "document_start"
}],
(don't forget to use the URL patterns that you actually need instead of <all_urls>)
content.js:
const pageEventId = chrome.runtime.id + Math.random;
runInPage(suppressor, pageEventId);
chrome.runtime.onMessage.addListener(msg => {
if (msg === 'suppressBeforeUnload') {
window.dispatchEvent(new Event(pageEventId));
}
});
function runInPage(fn, ...args) {
const script = document.createElement('script');
script.textContent = `(${fn})(${JSON.stringify(args).slice(1, -1)})`;
document.documentElement.appendChild(script);
script.remove();
}
function suppressor(pageEventId) {
let suppressBeforeUnload;
window.addEventListener(pageEventId, () => {
window.onbeforeunload = null;
suppressBeforeUnload = true;
});
window.addEventListener('beforeunload', e => {
if (suppressBeforeUnload) {
e.stopImmediatePropagation();
}
});
}
extension script (popup or background):
chrome.tabs.sendMessage(tab.id, 'suppressBeforeUnload', () => {
chrome.runtime.lastError;
chrome.tabs.update({url: 'https://www.example.org/'});
});
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"]