Chrome extensions accessing GUM - javascript

I'm working on a little sideproject in form of a chrome extension which displays the local stream via the getUserMedia API.
First I had issues requesting the gum (getUserMedia) since I tried to access it via the popup.js and the background.js. After some testing I got it working within the content.js, but now the issue was acutally displaying the stream in the popup.html. Further investigation suggested using the inbuild sendMessage from chrome (since the content script has no access to the popup).
So my workflow is now a onClick listener on my popup, on user click sending a notification to my content script, requesting the gum there and executing a callback function from the received message which sets the stream on my video element. The issue now my send stream is altered by transferring it via the sendMessage function, which results in an error setting the stream on my video element, since the stream is no longer a MediaStream object.
My approach is a trial and error approach since I'm neither a webdeveloper nor do I have any experience with chrome-extensions. Any suggestions or help on how I can achive this are highly appreciated
-- popup.js
let startStream = document.getElementById('gum');
function setStream(stream) {
if(stream != null){
video = document.getElementById('localStream');
if("srcObject" in video) {
console.log("videoelement supports scrObject.")
}
console.log('stream: ', stream)
video.srcObject = stream;
} else {
console.log("popup: passed stream is not definded")
}
}
startStream.onclick = function(element) {
console.log("popup start stream clicked");
chrome.tabs.query({
active: true,
currentWindow: true
}, tabs => {
chrome.tabs.sendMessage(
tabs[0].id,
{from: 'popup', action: 'startStream'},
setStream);
});
};
-- popup.html
<!DOCTYPE html>
<html>
<head>
<style>
button {
height: 30px;
width: 30px;
outline: none;
}
</style>
</head>
<body>
<video controls id=localStream></video>
<button id="gum">Start</button>
<script src="popup.js"></script>
</body>
</html>
-- content.js
console.log('content script init')
chrome.runtime.onMessage.addListener((msg, sender, response) => {
console.log(sender.tab ?
"message from a content script:" + sender.tab.url :
"message from the extension");
if (msg.from == "popup" && msg.action == "startStream"){
console.log('content script start stream action')
navigator.mediaDevices.getUserMedia({video:true, audio:true})
.then((stream) => {
console.log('content gum successfull: ', toString(stream));
console.log('stream: ', stream);
response(stream);
})
.catch((err) => {
console.log('content gum error: ' + error);
response(null);
});
return true;
}
});
-- manifest.json
{
"version": "1.0",
"name": "Some test",
"description": "Build an Extension!",
"manifest_version": 2,
"permissions": ["declarativeContent", "storage", "activeTab", "tabs"],
"background": {
"scripts": ["background.js"],
"persistent": false
},
"content_scripts": [{
"matches": ["<all_urls>"],
"js": ["content.js"],
"run_at": "document_idle",
"all_frames": false
}],
"browser_action": {
"default_popup": "popup.html",
"default_icon": {
"16": "images/icon-off-16.png",
"32": "images/icon-off-32.png",
"48": "images/icon-off-48.png",
"64": "images/icon-off-64.png",
"128": "images/icon-off-128.png"
},
"default_title": "World Domination"
},
"icons": {
"16": "images/icon-off-16.png",
"32": "images/icon-off-32.png",
"48": "images/icon-off-48.png",
"64": "images/icon-off-64.png",
"128": "images/icon-off-128.png"
}
}

Related

Network requests don't return when closing Chrome Extension

The issue:
I have a chrome extension that fetches some data when the user clicks a hotkey. The fetch happens in the background. However, if you open and close the popup, the fetch for that request never returns. I can see it go out in the network requests, but it just sits as pending. If you don't open and close the popup, it returns just fine.
Here are the relevant files/code:
Manifest file:
{
"manifest_version": 3,
"name": "Simple hotkey extension",
"version": "0.0.1",
"description": "Extension",
"icons": {
"16": "icon.png",
"48": "icon.png",
"128": "icon.png"
},
"action": {
"default_icon": {
"16": "icon.png",
"24": "icon.png",
"32": "icon.png"
},
"default_title": "Title",
"default_popup": "popup/popup.html"
},
"host_permissions": ["<all_urls>"],
"options_page": "options.html",
"permissions": [
"activeTab",
"contentSettings",
"contextMenus",
"scripting",
"storage",
"unlimitedStorage"
],
"background": {
"persistent": true,
"service_worker": "background.js"
}
}
Relevant part of background:
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
if (request.message === 'SEND_URL_DETAIL') {
const {url, website} = request;
fetchPageData(url, website)
.then(data => {
console.log(data, 'this is my data');
appendStoredClicks(data);
})
.catch(err => console.log(err));
// It's unclear if Chrome extension development actually needs a response.
sendResponse({farewell: 'goodbye'});
}
});
The API request from the utils folder:
export async function fetchPageData(
url: string,
website: string,
): Promise<any> {
const wrappedUrl = `http://localhost:8080/fetch/website?url=${url}&website=${website}`;
console.log('About to fetch');
const res = await fetch(wrappedUrl);
if (!res.ok) {
throw new Error('Page not found');
}
const data = await res.json();
console.log('Data received', data);
return {...data, url};
}
Update based on #wOxxOm's comment
I use react so popup.html is compiled by npm. Here's the current output.
Popup.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hotkey Extension</title>
<meta name="viewport" content="width=device-width, initial-scale=1"></head>
<body>
<script src="popup.js"></script>
</body>
</html>
These are the only places where I can see onMessage called.
Popup.js only has 3 lines of code that use Chrome:
chrome.action.setBadgeText
chrome.storage.onChanged.addListener(listener);
- chrome.tabs.query(
{
active: true,
currentWindow: true,
},
tabs => {
chrome.tabs.sendMessage(tabs[0].id, {
msg: Messages.DELETE_STUFF,
});
Would any of these cause the problem?
Why does closing the chrome extension popup prevent these from returning and make the service worker inactive? It seems like such a weird behavior. How do I make them persist and not lose the response.

Content script doesn't communicate with background script - chrome extension

I'm trying to send new tab URL from background.js to content.js. The .sendMessage() performs but doesn't get to the content.js
background.js:
chrome.tabs.onUpdated.addListener(
function (tabId, changeInfo, tab) {
if (changeInfo.url) {
chrome.tabs.sendMessage(tabId, {
url: changeInfo.url
})
}
}
);
content.js:
chrome.runtime.onMessage.addListener(function(request, sender, callback) {
console.log('here');
});
manifest.json:
{
"manifest_version": 2,
"name": "Url tracker",
"description": "Track your latest visited URLs",
"version": "0.0.1",
"icons": {
"16": "logo-small.png",
"48": "logo-small.png",
"128": "logo-small.png"
},
"permissions": [
"activeTab",
"tabs"
],
"background": {
"scripts":["background.js"],
"persistent": false
},
"content_scripts": [{
"matches": ["<all_urls>"],
"all_frames": true,
"js": ["content.js"]
}]
}
Content scripts run after DOMContentLoaded by default (it can be changed) but the URL is reported to onUpdated when the tab starts loading i.e. before the content script runs.
The solution is to add a check to skip the first update because it's not needed: an instance of the content script runs in each matching web page and it already knows location.href of its page.
if (changeInfo.url && tab.status === 'complete') {
After you reload your extension on chrome://extensions page (or update it from the web store), all its content scripts get "orphanized" and can't receive messages.
The solution is to re-inject them explicitly.

Chrome extension message listener fires twice

I'm working on Chrome extensions. I try to learn messaging between content and background. I develop simple project for this. But I have issue.
Basic idea is
User click button on extension popup
A function (bot.js) find image from content of tab then extension (background.js) will download it.
The issue is port.onMessage.addListener() in background.js fired twice.
When background.js sends message to contentscript.js there are two same messages in console or when I try to download in background.js (the code line "Do Something") it download the file twice.
How can I solve this problem?
popup.html
<!doctype html>
<html>
<head>
<title>Test Plugin</title>
<script src="background.js"></script>
<script src="popup.js"></script>
</head>
<body>
<h1>Test Plugin</h1>
<button id="btnStart">Button</button>
</body>
</html>
popup.js
document.addEventListener('DOMContentLoaded', function() {
var checkPageButton = document.getElementById('btnStart');
checkPageButton.addEventListener('click', function() {
GetImages("Some URL");
}, false);
}, false);
var tab_title = '';
function GetImages(pageURL){
// Tab match for pageURL and return index
chrome.tabs.query({}, function(tabs) {
var tab=null;
for(var i=0;i<tabs.length;i++){
if(tabs[i].url==undefined || tabs[i].url=="" || tabs[i]==null){}
else{
if(tabs[i].url.includes(pageURL)){
tab=tabs[i];
break;
}
}
}
if(tab!=null){
chrome.tabs.executeScript(tab.id, {
file: "bot.js"
}, function(results){
console.log(results);
});
}
});
}
bot.js
var thumbImagesCount = document.querySelectorAll('.classifiedDetailThumbList .thmbImg').length;
var megaImageURL=document.querySelectorAll('.mega-photo-img img')[0].src;
console.log(megaImageURL + " from bot.js");
port.postMessage({key:"download", text: megaImageURL});
background.js
chrome.runtime.onConnect.addListener(function (port) {
console.assert(port.name == "content-script");
port.onMessage.addListener(function(message) {
console.log(message);
if(message.key=="download"){
// Do Something
// Event fires twice
port.postMessage({key:"download", text: "OK"});
}
})
});
contentscript.js
console.log("content script loaded!");
var port = chrome.runtime.connect({name: "content-script"});
port.onMessage.addListener(function(message){
console.log(message);
});
manifest.json
{
"manifest_version": 2,
"name": "Test Extension",
"description": "This extension will download images from gallery",
"version": "1.0",
"icons": {
"16": "bot16.png",
"48": "bot48.png",
"128": "bot128.png" },
"browser_action": {
"default_icon": "bot48.png",
"default_popup": "popup.html"
},
"permissions": [
"activeTab",
"downloads",
"http://*/",
"https://*/"
],
"background": {
"persistent": false,
"scripts": ["background.js"]
},
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*"],
"js": ["contentscript.js"]
}
]
}
The background script declared in manifest.json already has its own page, a hidden background page where it runs, so you should not load it in the popup as it makes no sense in case there are listeners for API events, the background page is already listening for them. In this case the copy also creates the second listener while the popup is open.
Solution: don't load background.js in popup.
See also Accessing console and devtools of extension's background.js.

Going bonkers over this small code, chrome.runtime.onMessage gives undefined, always. What can I do?

I know there are similar questions and I've tried almost everything in them. I'm trying to build a chrome extension and it needs to pass a message from content.js to background.js.
The code:
background.js
var xstext;
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
xstext=request.stext;
});
var wtf = "https://www.google.com/search?q="+xstext;
chrome.commands.onCommand.addListener(function(command) {
if(command=="ntab"){
chrome.tabs.create({ url: wtf});
}
});
content.js
var text = window.getSelection();
var stext=text.toString();
chrome.runtime.sendMessage({stext: stext});
manifest.json
"manifest_version": 2,
"name": "vind",
"version": "1.0",
"description": "Search stuff easily!",
"background": {
"persistent": false,
"scripts": ["background.js"]
},
"content_scripts": [
{
"all_frames": true,
"run_at": "document_start",
"matches": [
"<all_urls>"
],
"js": ["content.js"]
}
],
"browser_action": {
"default_icon": {
"16": "images/icon16.png",
"32": "images/icon32.png"
},
"default_popup": "popup.html"
},
"commands": {
"ntab": {
"suggested_key": {
"default": "Alt+G",
"windows": "Alt+G",
"mac": "Alt+G",
"chromeos": "Alt+G",
"linux": "Alt+G"
},
"description": "New tab for the query"
}
}
}
I want to pass the selected text from content.js to background.js, I have tried adding return: true; in the listener to no avail. I'm getting 'undefined' added to the main string, so nothing seems to get passed. what should I do?
This approach won't work because 1) your content script is saving the text at the moment it runs which happens just one time at page load and 2) since the background script is not persistent it'll unload after receiving a message and xstext will disappear.
Do the reverse: ask the content script when the hotkey is pressed.
background.js, entire contents:
chrome.commands.onCommand.addListener(command => {
if (command === 'ntab') {
chrome.tabs.query({active: true, lastFocusedWindow: true}, ([tab]) => {
if (!tab) return;
chrome.tabs.sendMessage(tab.id, 'getText', text => {
chrome.tabs.create({
url: 'https://www.google.com/search?q=' + encodeURIComponent(text),
});
});
});
}
});
content.js, entire contents:
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
if (msg === 'getText' && document.hasFocus()
&& (!document.activeElement || !/^I?FRAME$/.test(document.activeElement.tagName))) {
sendResponse(getSelection().toString());
}
});
P.S. An advanced solution would be to run the content script on demand (using chrome.tabs.executeScript in background.js onCommand listener) so you can remove content_scripts section from manifest.json and use activeTab permission instead.

Accessing Current Tab DOM Object from a Chrome Extension

I have been searching around on how to accomplish this. I have found some articles most notably
Accessing Current Tab DOM Object from "popup.html"?
However I am very new to JavaScript and making chrome extensions and I have hit a dead end.
My guess is that the response isn't being received which explains why document.write("Hellp")
isn't working. Any help to fix this up would be appreciated.
I have three main files
manifest.json
{
"name": "My First Extension",
"version": "1.0",
"description": "The first extension that I made.",
"browser_action":
{
"default_icon": "icon.png",
"popup": "popup.html"
},
"permissions":
[
"tabs"
],
"content_scripts":
[{
"matches": ["<all_urls>"],
"js": ["dom.js"]
}]
}
popup.html
<html>
<body>
</body>
<script>
chrome.tabs.getSelected(null, function(tab)
{
// Send a request to the content script.
chrome.tabs.sendRequest(tab.id, {action: "getDOM"}, function(response)
{
document.write("Hello");
document.write(response.title)
});
});
</script>
</html>
dom.js
chrome.extension.onRequest.addListener(function(request, sender, sendResponse)
{
if (request.action == "getDOM")
sendResponse({dom: document.getElementsByTagName("body")[0]});
else
sendResponse({}); // Send nothing..
});
I see this is an older question, but it's unanswered and I ran into the same issue. Maybe it's a security feature, but you don't seem to be able to return a DOM object. You can, however, return text. So for dom.js:
chrome.extension.onRequest.addListener(function(request, sender, sendResponse) {
if (request.action == "getDOM")
sendResponse({dom: document.body.innerHTML});
else
sendResponse({}); // Send nothing..
});
I'm workin on an extension that transfers the html of the element as a text and then rebuilding the element back using innerHTML.
Hope that clarifies how to get the DOM elements from the current page**
This is the way I've got the DOM:
Manifest.json
{
"manifest_version": 2,
"version" : "2.0",
"name": "Prettify search",
"description": "This extension shows a search result from the current page",
"icons":{
"128": "./img/icon128.png",
"48": "./img/icon48.png",
"16": "./img/icon16.png"
},
"page_action": {
"default_icon": "./img/icon16.png",
"default_popup": "popup.html",
"default_title": "Prettify Results!"
},
// If the context is the Mach url = sends a message to eventPage.js: active icon
"content_scripts": [
{
"matches": ["http://www.whatever.cat/*"],
"js": ["./js/content.js", "./js/jquery-3.1.1.min.js"]
}
],
"permissions": [
"tabs",
"http://www.whatever.cat/*",
"http://loripsum.net/*" //If you need to call and API here it goes
],
"background":{
"scripts": ["./js/eventPage.js"],
"persistent": false
}
}
Popup.js
$(function() {
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, {action: "getPage"}, function(response) {
var importedCode = response.searchResults;
var fakeHtml = document.createElement( 'html' );
fakeHtml.innerHTML = importedCode; // recieved String becomes html
});
});
Eventpage.js
>Able/disable the extension button
chrome.runtime.onMessage.addListener(function(req, sender, resp) {
if(req.todo === 'showPageAction'){
chrome.tabs.query({active:true, currentWindow:true}, function(tabs) {
chrome.pageAction.show(tabs[0].id);
});
}
});
content.js
Content_Scripts can not use the Chrome API to enable or disable the >extension icon. We pass a message to Event_Page, js, he can indeed
use the Api
chrome.runtime.sendMessage({ todo: "showPageAction"});
window.onload = function() {
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
console.log(sender.tab ?
"from a content script:" + sender.tab.url :
"from the extension");
if (request.action == "getPage"){
sendResponse({searchResults: document.body.innerHTML});
}
});
};
popup.html
Just link popup.js

Categories