I have written a chrome extension that successfully records all of a users scrolling data (timestamp with pixel amount). This data is currently logging in the console.
My next step however is causing me a bit of trouble.
I want to then send every log of data that is created to an external webpage to be displayed. ie, the page will auto update and archive every new timestamped entry/pixel counter the extension records.
I've been able to communicate between the extension and webpage, by passing a simple message through a DOM event. But i'm having trouble getting the extension to send the scroll data it's collecting.
I feel like i'm close, and i've read Google's Messaging API's but I feel a bit lost.
Here's my code so far:
Manifest.json
{
"manifest_version": 2,
"name": "Caressing the Silver Rectangle",
"description": "Measures Jesse Bowling's distance scrolled in pixels on Google Chrome",
"version": "1.1",
"content_scripts": [
{
"matches": [
"<all_urls>"
],
"js": [
"scroller.js"
],
"run_at": "document_start"
}
],
"background": {
"scripts": ["background.js"]
},
"externally_connectable": {
"matches": ["http://*/*jessebowling.space/caressingthesilverrectangle/*"]
},
"browser_action": {
"default_icon":"icon.png",
"default_title": "Caressing the Silver Rectangle",
"default_popup": "popup.html"
},
"permissions": [
"activeTab",
"<all_urls>",
"background",
"tabs",
"storage"
]
}
Background.js
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
console.log("background.js got a message")
console.log(request);
console.log(sender);
sendResponse("bar");
}
);
Scroller.js
/*jslint devel: true */
// The ID of the extension we want to talk to.
var caressingthesilverrectangleId =
"http://*/*jessebowling.space/caressingthesilverrectangle/*"
var totalScroll;
var lastKnownScrollPos = 0;
window.addEventListener("scroll", function () {
"use strict";
console.log(lastKnownScrollPos);
totalScroll += Math.abs(window.scrollY - lastKnownScrollPos);
lastKnownScrollPos = window.scrollY;
chrome.storage.sync({ scroll: totalScroll });
function (response) {
console.log(response);
}
});
// send data through a DOM event
document.dispatchEvent(new CustomEvent('csEvent', {detail:
totalScroll}));
// Make a simple request:
chrome.runtime.sendMessage(caressingthesilverrectangleId,
{getTargetData: true},
function(response) {
if (targetInRange(response.targetData))
chrome.runtime.sendMessage(caressingthesilverrectangleId,
{activateLasers: true});
});
// Start a long-running conversation:
var port = chrome.runtime.connect(caressingthesilverrectangleId);
port.postMessage(chrome.storage.sync({ scroll: totalScroll }));
I'm guessing the part where you do
var port = chrome.runtime.connect(caressingthesilverrectangleId);
port.postMessage(chrome.storage.sync({
scroll: totalScroll
}));
is where you are trying to send the message. However the message you are sending is the result of chrome.storage.sync, which is always undefined. You might want to split these two calls up so you actually send some data like:
var port = chrome.runtime.connect(caressingthesilverrectangleId);
chrome.storage.sync({
scroll: totalScroll
});
port.postMessage({
scroll: totalScroll
});
Related
I have seen many similar questions, but none have solved my issue. I have a page running that has a specific div that changes every 5 seconds. I have a function in my popup.js script, which contacts my contentscript.js and asks for the value of that div. I am having a weird problem where the results from my content script on my localhost server are totally fine, but running this on the same html hosted on my domain is throwing a Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist, which leads to an undefined value . I know people said they had solutions such as disabling other extensions, but that is not a feasible solution since I plan on publishing this extension and can't force users to do that in order for it to work. I know the domain hosted on AWS works totally fine, since I can work around the website and make api calls to it through Postman etc.
popup.js
var currentNumber = 1000;
var lastUpdated = 1000;
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, {method: "getNumber"}, function(response) {
currentNumber = response.current;
lastUpdated = response.lastUp;
});
contentscript.js
chrome.runtime.onMessageExternal.addListener(
function(request, sender, sendResponse) {
if (request.method == "getNumber") {
var currentNumber = document.getElementById("1MainPart_lbUsersInLineAheadOfYou").innerText;
var lastUpdated = document.getElementById("MainPart_lbLastUpdateTimeText").innerText;
sendResponse({current: currentNumber, lastUp : lastUpdated})
return true;
}
return true;
});
manifest.json
{
"manifest_version": 2,
"name": "Extension",
"version": "0.1.0",
"permissions": [
"activeTab",
"tabs",
"storage",
"http://localhost/*",
"*Link to my domain*"
],
"content_scripts": [{
"js": ["contentscript.js"],
"matches": ["http://localhost/*", "*Link to my domain*"]
}],
"externally_connectable": {
"ids": [*inserted id*],
"matches": ["*Link to my domain*"],
"accepts_tls_channel_id": false
},
"background" : {
"scripts" : ["backgroundscript.js"]
},
"browser_action": {
"default_icon": "icon.png",
"default_popup": "popup.html"
}
}
Well according to the docs under...
https://developer.chrome.com/docs/extensions/reference/runtime/#event-onMessageExternal
It says...
onMessageExternal
runtime.onMessageExternal.addListener(listener: function)
Fired when a message is sent from another extension/app (by
sendMessage). Cannot be used in a content script.
So that only leaves a background script.
So I have this problem where I need to open a none active tab and once in a while to set it's URL, everything is working well until an undefined period of time passes, then it seems like the alarm that responsible to set the URL dies along with all of the background script data (variables are wiped), in my manifest I set permission of "background" but it didn't help, I also tried using setInterval but it didn't help much, here's some code for you:
async function setGetJobAlarm() {
// try {
// chrome.alarms.clear("getAndExecuteJobs");
// } catch { }
// chrome.alarms.create("getAndExecuteJobs", { periodInMinutes: 0.3 });
// chrome.alarms.onAlarm.addListener(async (alarm) => {
// if (alarm.name == "getAndExecuteJobs") {
// try {
// await getAndExecuteJobs();
// }
// catch (err) {
// console.log(err);
// }
// }
// });
if (getAndExecuteJobs > 0) {
clearInterval(getAndExecuteJobsInterval);
}
getAndExecuteJobsInterval = setInterval(async () => {
try {
await getAndExecuteJobs();
}
catch (err) {
console.log(err);
}
}, 30000);
}
Manifest:
{
"name": "aaaaa",
"version": "0.0.1",
"manifest_version": 3,
"background": {
"service_worker": "bgjob.js"
},
"permissions": [
"tabs",
"alarms",
"activeTab",
"background"
//"identity",
//"identity.email"
],
"host_permissions": [
"http://*/",
"https://*/"
],
"icons": {
"16": "aaaaa.png",
"48": "aaaa.png",
"128": "aaaa.png"
},
"action": {
"default_popup": "/popout/pop.html",
"default_title": "aaaaa"
},
"content_scripts": [
{
"matches": [
"http://*/*",
"https://*/*"
],
"js": [
"jquery-3.6.0.slim.min.js"
]
}
]
}
I'm not able to figure out what is missing, googled a lot but no use,
Second problem is that I'm trying to load a simple extension's html file named "hello.html", the html get's opened but I get this error:
Cannot access contents of URL"chrome-extension://locblcbeeombbgmpiofcnmhfimfpjipb/hello.html". Extension manifest must request permission to access this host.
I tried to add "chrome-extension://*/" but didn't work, thanks!
The background script automatically terminates after 30 seconds so setTimeout/setInterval with a delay like that or longer will never run.
Remove setTimeout/setInterval and use chrome.alarms API with a periodInMinutes at least 1 because this is the minimum interval allowed for published extensions in the web store.
If your workflow really needs intervals below 1 minute you'll have to prolong the service worker's life artificially, see the second part of this answer.
I am trying to receive some info from the content page to the popup page in chrome extension.
Here is my manifest.json:
{
"name": " Downloader",
"description": "history ",
"version": "1.0",
"permissions": [
"activeTab",
"notifications"
],
"background": {
"persistent": false,
"scripts": ["background.js"]
},
"content_scripts": [
{
"all_frames": false,
"matches": ["<all_urls>"],
"exclude_matches": [],
"js": [
"/src/jquery.js",
"/src/sheet-min.js",
"/src/file-saver-min.js"
]
// "css": [
// "js/content/page.css"
// ]
}
],
"content_scripts": [{
"matches": ["*://*.ebay.com/*"],
"js": ["content.js"],
"run_at": "document_idle",
"all_frames": false
}],
"browser_action": {
"default_title": "Download History.",
"default_icon": "icon.png",
"default_popup": "popup.html"
},
"manifest_version": 2
}
background.js
chrome.runtime.onMessage.addListener((msg, sender) => {
// First, validate the message's structure.
if ((msg.from === 'content') && (msg.subject === 'showPageAction')) {
// Enable the page-action for the requesting tab.
chrome.browserAction.show(sender.tab.id);
}
});
content.js
// Inform the background page that
// this tab should have a page-action.
function ping() {
chrome.runtime.sendMessage('ping', response => {
if(chrome.runtime.lastError) {
setTimeout(ping, 1000);
} else {
chrome.runtime.sendMessage({
from: 'content',
subject: 'showPageAction',
});
}
});
}
ping();
// Listen for messages from the popup.
chrome.runtime.onMessage.addListener((msg, sender, response) => {
// First, validate the message's structure.
if ((msg.from === 'popup') && (msg.subject === 'DOMInfo')) {
// Collect the necessary data.
// (For your specific requirements `document.querySelectorAll(...)`
// should be equivalent to jquery's `$(...)`.)
var domInfo = {
total: document.querySelectorAll('*').length,
inputs: document.querySelectorAll('input').length,
buttons: document.querySelectorAll('button').length,
};
// Directly respond to the sender (popup),
// through the specified callback.
response(domInfo);
}
});
popup.js
const setDOMInfo = info => {
console.log(info)
};
window.addEventListener('DOMContentLoaded', () => {
// ...query for the active tab...
chrome.tabs.query({
active: true,
currentWindow: true
}, tabs => {
// ...and send a request for the DOM info...
chrome.tabs.sendMessage(
tabs[0].id,
{from: 'popup', subject: 'DOMInfo'},
// ...also specifying a callback to be called
// from the receiving end (content script).
setDOMInfo);
});
});
I know that this error occurs when the content script sends message to the background script but the background script is not ready to receive the message. After looking for a solution on stackoverflow I decided to use the ping function but as you can see above but it still gives me the same error message.
There's no chrome.browserAction.show as you can see in the documentation so the listener in background.js throws and aborts the execution. The messaging cycle never completes so to the sender it looks just like an absence of any receiver.
Each part of an extension has its own devtools.
Open devtools for the background script and you'll see the error.
There's no need for the background script here.
No need for showPageAction message either because browser_action is enabled by default.
P.S. the entire code can be simplified by switching to programmatic injection (example) so you can remove content_scripts, background script, and messaging.
So I've found several pages on here, as well as various blog posts that seem to do pretty much exactly what I want to do, but they are all a few years old and seem really easy but don't work.
As the title says, On thisdomain.com there is a iframe from thatdomain.com and I want to get the value in a div in that iframe.
Manifest.json
{
"manifest_version": 1,
"name": "MyExtention",
"version": "1.0",
"description": "Nothing Yet",
"permissions": [
"storage",
"tabs",
"unlimitedStorage",
"webRequest",
"webNavigation",
"*://*.match-both-iframe-and-main-domain.com/*",
"*://*/*"
],
"background": {
"scripts": ["listener.js"],
"persistent": true
},
"content_scripts":
[
{
"matches": ["*://*.matchnothing.shshdjdjffkdj.com/*"],
"js": ["mainscript.js"],
"all_frames": true
}
]
}
The content script url matches nothing because it is fired from a listener (which works). Basically it waits for a request from one of 2 urls before it activates.
listener.js
var chrome = chrome || {};
var callback = function(listenerRes) {
console.log(listenerRes.url);
if (listenerRes.url.indexOf("listenurl1") > -1 ||
listenerRes.url.indexOf("listenurl2") > -1) {
chrome.tabs.get(listenerRes.tabId, function(tab) {
chrome.tabs.executeScript(tab.id, {file: "mainscript.js"});
});
}
};
chrome.webRequest.onBeforeRequest.addListener( callback, {urls: ["*://*.google.com/*"]} );
mainscript.js
var chrome = chrome || {};
... // helper functions and such
var iframe = document.getElementsByid('myiframe');
// Get all data.
var datas = [];
try {
datas = iframe.contentWindow.document.getElementsByClassName('mydata'); // error is here
}catch(e){
console.log(e);
}
... // do stuff with the data
On the commented line it throws a "Blocked a frame with origin URL from accessing a cross-origin frame."
So I am under the impression that some combination of all_frames = true, the chrome.tabs.executeScript, and the domains in the permissions should allow for this to work. But it doesn't.
It might be important to note, the reason for this listener is because the iframe isnt on the page to start.
Please help, Im an experienced web developer but this is my 1st foray into Chrome Extentions.
Here is my scenario: By clicking the browser icon, I create a sidebar (html and css) next to the whole page, thus creating two columns (one is my sidebar, the other one is the actual page).
What I to achieve is having the sidebar stay when I reload the page or navigate to another page WITHIN the same domain. What I have right now is just the creation of the sidebar, but I have to click the browser action every time I navigate or reload the web page.
Manifest:
{
"name": "apdrop",
"version": "0.1",
"manifest_version": 2,
"description": "first prototype for apdrop extension",
"icons": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
},"background": {
"scripts": ["background.js"],
"persistent": false
},
"browser_action": {
"default_icon": "icons/icon19.png",
"default_title": "apdrop"
},
"permissions": [
"background",
"tabs",
"http://*/*/",
"https://*/*/"
]
}
Background.js
function injectedScript(tab, method){
chrome.tabs.insertCSS(tab.id, {file:"style.css"});
//chrome.tabs.insertCSS(tab.id, {file:"bootstrap.css"});
chrome.tabs.executeScript(tab.id, { file: 'jquery-2.1.1.min.js'});
//chrome.tabs.executeScript(tab.id, { file: 'bootstrap.min.js'});
chrome.tabs.executeScript(tab.id, { file: 'inject.js'});
}
function click(tab){
console.log("browser action clicked");
injectedScript(tab, 'click');
//alert("action button was clicked");
}
chrome.browserAction.onClicked.addListener(click);
Inject.js
var ev = $("body > *");
if (!document.getElementById('contentxf343487d32'))
{
ev.wrapAll("<div id='insidecontent65675f526567'>");
$("#insidecontent65675f526567").wrapAll("<div id='contentxf343487d32'>");
$("<div id='sidebar343gf87897fh'><div id='insidesidebar87678bbbb'><p>this is my name</p></div></div>").insertBefore("#contentxf343487d32");
}
else
{
$("#sidebar343gf87897fh").remove();
$("#insidecontent65675f526567").unwrap();
$("#insidecontent65675f526567 > div").unwrap();
}
Hope this helps clarify a bit more.
The simplest strategy would be to save state in domain's sessionStorage and have a "detector" script that re-injects your UI.
Add setting the state in your content script:
// inject.js
if (!document.getElementById('contentxf343487d32'))
{
// ...
sessionStorage["contentxf343487d32"] = true;
}
else
{
// ...
sessionStorage["contentxf343487d32"] = false;
}
Add a "detector" script:
// detect.js
if(sessionStorage["contentxf343487d32"])
{
chrome.runtime.sendMessage({injectSidebar: true});
}
Always inject the script on page load, via the manifest (and change to a better permission):
"content_scripts" : [
{
"matches": ["<all_urls>"],
"js": ["detect.js"]
}
],
"permissions": [
"background",
"tabs",
"<all_urls>"
]
In the background, inject the script upon message:
// background.js
chrome.runtime.onMessage.addListener( function (message, sender, sendResponse){
if(message.injectSidebar)
{
click(sender.tab);
}
});
If you need more persistence than sessionStorage provides, use localStorage. If you need a different logic, you can still use this skeleton of a detector signalling the background.