embed _generated_background_page to options page - javascript

I made Chrome extension which working fine, but need to be modified this way:
console.log of background.js should be monitored in real time by user constantly, while options page opened, as an element of this page. At the worst case, it may be duplicate of log, but generated in background.js.
How can I access and display this data, where background script process external data once a second?

You can't embed the background page in another page.
To do what you are looking to do, you can override console.log to do extra stuff:
// background.js
(function() {
if(window.console && console.log) {
var old = console.log;
console.log = function() {
// Do extra stuff here
chrome.runtime.sendMessage({
type: "backgroundLogEvent",
content: arguments
});
// Call normal console.log
old.apply(this, arguments);
}
}
})();
And in the options page, you can receive those messages:
// options.js
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
if(message.type == "backgroundLogEvent") {
// Do something with message.arguments
// I.e. log them here:
message.arguments.unshift('From background:');
console.log.apply(console, message.arguments);
}
});
This will transmit all console.log messages from background in realtime.
Exercise for the reader: implement a buffer that shows a short history before opening the options page.

Related

Why does checking for a runtime.lastError generate a port error?

After struggling for hours with a weird bug in my actual Chrome extension, I finally managed to nail down the following MCVE:
background.js
chrome.runtime.onMessage.addListener(function(request) {
if (typeof request.hello !== "undefined") {
console.log("I got it!");
}
});
options.js
// the following gives an error
// chrome.runtime.sendMessage({ hello: true }, function(response) {
// if (chrome.runtime.lastError) {
// console.log(chrome.runtime.lastError);
// }
// });
// the following does not
chrome.runtime.sendMessage({ hello: true });
As you can probably tell from the comments above, when I add an extra callback function to check for a runtime last error, it gives an error:
The message port closed before a response was received.
However, when I do not add the callback function, no error is generated!
As as as I can tell from the docs, this is the correct format for my callback function:
If you specify the responseCallback parameter, it should be a function that looks like this: function(any response) {...};
Thus, I am failing to understand this inconsistent behaviour. I checked similar questions, and adding return true to the background.js listener simply delays the occurrence of the error in the first case.
Here's a zip of the above files to test locally. I am using Chrome 75.
Update: I do not intend to send any response from my background js file. It is just a one time message from my options page to the background page.
It doesn't, well at least not in my Chrome 75.
The Error Message you see is because you don't answer from your background script, but it's not because you checked for runtime.lastError.
If you want to avoid this error, you need to answer from you background script;
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if(sender.tab && // check it's actually a tab that talked to us
typeof request.hello !== "undefined"
) {
sendResponse({received: true}); // or whatever you like
}
});

passing data from executed script to background in chrome extension

I have this idea for passing data form an injected script (getDOM.js) to my background.js
bacgkround.js
chrome.contextMenus.onClicked.addListener(function(info, tab){
chrome.tabs.executeScript(tab.id, {file: "getDOM.js"})
});
chrome.contextMenus.onClicked.addListener(function(info, tab){
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, {greeting: "GetURL"}, function(response) {
alert(response.navURL);
});
});
});
getDOM.js
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if (request.greeting === "GetURL")
sendResponse({navURL:'test'});
});
as you can see i used massage function to pass data, but there is a problem. i cant get data on right time, i will pass background.js previous data
content must be dynamic (not specific "test"), every time it will alert previous data. imagine getDOM.js will pass selected text, with this code it will pas previous selected text. how can i fix this ?
my example of dynamic data :
function getHTMLOfSelection () {
var range;
if (document.selection && document.selection.createRange) {
range = document.selection.createRange();
return range.htmlText;
}
else if (window.getSelection) {
var selection = window.getSelection();
if (selection.rangeCount > 0) {
range = selection.getRangeAt(0);
var clonedSelection = range.cloneContents();
var div = document.createElement('div');
div.appendChild(clonedSelection);
return div.innerHTML;
}
else {
return '';
}
}
else {
return '';
}
}
var dom = getHTMLOfSelection();
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if (request.greeting === "getDom")
sendResponse({DOM:dom});
});
it will pass selected dom to background.js
Lots of problems here. Let's see.
It does not make sense to execute getHTMLOfSelection() at the point when the script is injected. You probably should put that inside the message handler: to get the selection when asked to.
A much bigger problem is the fact that every time you inject a script. This, together with 1, leads to all kinds of fun. Let's look in more detail!
The user triggers the context menu.
Your first contextMenu.onClicked handler runs. The script injection is scheduled. It's asynchronous, so you don't know when it will finish unless you use a callback. Which you don't.
Your second contextMenu.onClicked handler runs. It sends a message to a script which potentially haven't finished executing. If you're lucky, you get a response.
User triggers the context menu again on the same page.
Your first contextMenu.onClicked handler runs, again. The script is going to be injected again, creating a second listener for the message that will compete with the first. It's again asynchronous, so maybe by the time your message arrives dom is up to date. Maybe not.
Your second contextMenu.onClicked handler runs, again. This time there sure is a message listener (maybe two!) that returns maybe up to date data.
And so on. You see the problem?
Furthermore, you can't pass a DOM object with sendResponse. The object needs to be JSON-serializable, and DOM objects contain circular references, which is a no-no. You need to extract the data you need on the content script side, and pass only that.
So, let's try to tackle those problems.
There are 2 ways of dealing with this. I'll present both, pick the one you prefer. Both will take care of problems 1 and 2.
First way is to ensure your message handler is only added once by including some kind of guard variable:
// getDOM.js
if(!getDOM_ready) { // This will be undefined the first time around
getDOM_ready = true;
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
// If only you knew how I hate this "greeting" example copied over
if (request.command === "GetURL") {
var dom = getHTMLOfSelection();
sendResponse({DOM: dom});
}
}
);
}
function getHTMLOfSelection() {
/* ... */
}
Then on the background side, we need to be sure we send a message only after the script finishes executing:
chrome.contextMenus.onClicked.addListener(function(info, tab){
chrome.tabs.executeScript(tab.id, {file: "getDOM.js"}, function() {
// This only executes after the content script runs
// Oh, and you most certainly don't need to query for tabs,
// you already have a tab and its id in `onClicked`
chrome.tabs.sendMessage(tab.id, {command: "GetURL"}, function(response) {
alert(response.navURL);
});
});
The second way is to drop Messaging altogether. executeScript actually returns the last value the content script evaluated. That makes the content script trivial and does not leave a message listener behind:
// getDOM.js
function getHTMLOfSelection() {
/* ... */
}
getHTMLOfSelection(); // Yes, that's it
On the background side, you need to adapt the listener:
chrome.contextMenus.onClicked.addListener(function(info, tab){
chrome.tabs.executeScript(tab.id, {file: "getDOM.js"}, function(results) {
// results is an array, because it can be executed in more than one frame
alert(results[0]);
});
The code is much simpler here, AND it does not leave an active event listener behind.
As for problem 3, you need to extract the info you need (say, a link) from the selection and pass only that instead of the object.
Finally, this is not a complete solution. Problem is, your selection can be inside an iframe, and this solution only injects code into the top frame. Solution to that is left as an exercise to the reader; using all_frames: true in the content script options will inject into all frames, and one of them will have a non-empty selection. You just need to see which.

Chrome Extension: Message Passing (Sending the DOM to popup.js) returns 'null'

I would like to use a Chrome Extension to download the current page's DOM. I'm not sure why, but when my download occurs, the result is just a text file with either 'null' or 'undefined', rather than the DOM. I've tried to assimilate the knowledge from here and here, but I can't seem to get the message from content.js through to popup.js.
Additionally, I'm not sure why this actually works. When I read the docs, it seems like I need to send the message from popup.js to content.js by selecting the active tab:
chrome.tabs.query({currentWindow: true, active: true}, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, {message: 'getMessageFromContent'}, function(response) {
//Code to handle response from content.js
}
});
My current code:
content.js
var page_html = DOMtoString(document);
chrome.runtime.sendMessage({method: 'downloadPageDOM', pageDOM: thisPage});
function DOMtoString(document_root) { ... }
background.js
chrome.tabs.query({currentWindow: true, active: true}, function(tab) {
var page_html;
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if (request.message == 'downloadPageDOM')
page_html = request.pageDOM;
else if (request.message == 'getPageDOM')
sendResponse(page_html);
});
});
popup.js
document.addEventListener('DOMContentLoaded', function() {
var download_button = document.getElementById('download_button');
download_button.addEventListener('click', function() {
chrome.runtime.sendMessage({message:'getPageDOM'}, function(response) {
download(response, "download.html", "text/html");
});
});
});
function download(data, fileName, mimeType) { ... }
I feel like I'm missing a crucial understanding of how message passing works. If anyone could take a second to help me understand why the file that downloads just has 'null', I would sincerely appreciate it.
You're over-complicating this, which leads to a lot of logical errors.
You've set up the background page to act like a message proxy, and the content script itself triggers updating your page_html variable. Then the popup pulls that data with another message.
Note that page_html will not contain the current tab's data in any case: you're overwriting this data with the last loaded tab.
What you can do is completely cut out the middleman (i.e. background.js). I guess you got confused by the fact that sending a message TO a popup is a generally a bad idea (no guarantee it's open), but the other way around is usually safe (and you can make it always safe).
Solution 1 (bad, but here for educational purposes)
The logic of your app is: once the user clicks the button, make the snapshot at that moment. So, instead of making your content script do its work immediately, add a message listener:
// content.js
chrome.runtime.onMessage(function(message, sender, sendResponse) {
else if (request.message == 'getPageDOM')
sendResponse(DOMtoString(document));
});
function DOMtoString(document_root) { ... }
And in your popup, request it:
// popup.js
// (Inside the click listener)
chrome.tabs.query({currentWindow: true, active: true}, function(tabs) {
// Note that sending a message to a content script is different
chrome.tabs.sendMessage(tabs[0].id, {message:'getPageDOM'}, function(response) {
download(response, "download.html", "text/html");
});
});
However, this solution is not 100% robust. It will fail if the content script is not injected into the page (and this can happen). But it's possible to fix this.
Solution 2
Let's not assume the content script is injected. In fact, most of the time you don't NEED to inject it automatically, only when the user clicks your button.
So, remove the content script from the manifest, make sure you have host permissions ("<all_urls>" works well, though consider activeTab permission), and the use programmatic injection.
There is a little-used form of programmatic injection that collects the value of the last executed statement. We're going to use that.
// content.js
DOMtoString(document); // This will be the last executed statement
function DOMtoString(document_root) { ... }
In the popup, execute script, collect results:
// popup.js
// (Inside the click listener)
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.executeScript(tabs[0].id, {file: "content.js"}, function(data) {
// Data is an array of values, in case it was executed in multiple tabs/frames
download(data[0], "download.html", "text/html");
});
});
NOTE: All of the above assumes that your function DOMtoString actually works.

Injected HTML accessing the Chrome API and global variables

I'm working on a Chrome Extension, and I'm new to the process. The extension I'm working on injects an HTML sidebar into the page, injects java-script functions to the header, then let's the user press the buttons on the sidebar to create/save my extension's data.
However, when I want to save the information, I use localStorage, however, the localStorage always saves with respect to the current website. How can I use localStorage to save with respect to our extension?
Furthermore, I would like to use some global javascript variables in the chrome-extension. Where do these belong? I know I can't currently access them from the injected Javascript.
I've looked into message passing, and I've had some trouble with it. I'm not sure how it works in the scope of injected javascript into a page's header. I've tried working with this example, but my extension doesn't seem to catch the message.
// This function is called in the injected header javascript.
function sendToExtension() {
setTimeout(function() {
console.log('page javascript sending message');
window.postMessage({ type: 'page_js_type',
text: "Hello from the page's javascript!"},
'*' /* targetOrigin: any */);
}, 10);
}
// This is installed in a background script.
window.addEventListener('message', function(event) {
console.log('content_script.js got message:', event);
});
You have to use Chrome's sendMessage function and onMessage listener. See below:
function sendToExtension() {
console.log('Sending message');
chrome.runtime.sendMessage({ext: "myExtension"}, function(response) {
console.log(response.ack);
});
}
// Listener - Put this in the background script to listen to all the events.
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if (request.ext) {
console.log('Message received from ' + request.ext);
sendResponse({ack:'received'}); // This send a response message to the requestor
}
});

Chrome extension development: Call a method in popup from background_html

I have a popup named mf_popup.html and my background page is named mf_background.html.
As I start the browser my background page fires (as is my understanding, I am new to Chrome development), and I would like to call a function in my mf_popup.html page from my background page.
For example:
Start chrome
background page fires
background page calls some function in popup page (which initiates some stuff)
How do I do the above?
if you place the needed code in both html-files in mf_javascript.js and includes the script in both with this line:
<script type="text/javascript" src="mf_javascript.js">
mf_popup.html
//sendRequest([any type] request, [function] responseCallback)
chrome.extension.sendRequest({
function: "foo",
params: [myParam1, myParam2],
function(response) {
alert("foo returns:"+response.result+");
}
});
mf_background.html
chrome.extension.onRequest.addListener(
function(request, sender, sendResponse) {
if(request.function == "foo")
var bar = foo(request.params[0], request.params[1]);
sendResponse({result: bar});
}
);
You also could just use sendRequest("foo") if you don't want send any params and/or use an callback function.

Categories

Resources