Chrome Extension Opening in New Tab - google-chrome-extension

I developed a Chrome Extension and in my Manifest file the permissions are "permissions": ["tabs", "storage"]. When the app got published it shows permissions as Replace the page you see when opening a new tab and Read your browsing history
The main issue is that every time a new tab it replaces the page with my extension.
In my background.js file I'm only using the tabs API to get the current tab's url like this:
const getCurrentTab = async () => {
let queryOptions = { active: true, currentWindow: true };
let [tab] = await chrome.tabs.query(queryOptions);
const recipe = await fetchParsedRecipe(tab.url);
return recipe;
};
This getCurrentTab function gets called in both tabs.onUpdated and tabs.onActivated
chrome.tabs.onUpdated.addListener(() => {
getCurrentTab();
});
chrome.tabs.onActivated.addListener(() => {
getCurrentTab();
});
How do I make sure that the extension doesn't open in the window of the new tab?

If you do not want to replace your New Tab page with your page in your Chrome extension manifest v3. You can do the following, this will only open your HTML page when you click the browser button. And it returns the current tab URL to that page (as a query string).
manifest.json
{
"name": "Open a new tab",
"action": {},
"manifest_version": 3,
"version": "0.1",
"description": "A click to open my own tab page",
"permissions": ["storage", "activeTab", "scripting"],
"background": {
"service_worker": "background.js"
},
"host_permissions": ["<all_urls>"]
}
background.js
chrome.action.onClicked.addListener((tab) => {
var newURL = chrome.runtime.getURL("tab.html");
chrome.tabs.create({ url: newURL + "?=" + tab.url });
});

Related

TypeError: Cannot read properties of undefined (reading 'getUserMedia') in background.js

Following several threads here on Stack Overflow I first request access to user media through an extension page, then send message to the background script to get user media from there. However I get the error that navigator isn't defined.
On a different note, I also get Error: Cannot access contents of the page. Extension manifest must request permission to access the respective host. and Error: Cannot access a chrome:// URL errors in the background script even though I have set "<all_urls>" in host_permissions (MV3).
Here is part of manifest.json:
"manifest_version": 3,
"background": {
"service_worker": "background.js"
},
"permissions": [
"storage",
"activeTab",
"scripting"
],
"host_permissions": [
"<all_urls>"
],
"web_accessible_resources": [
{
"resources": ["frame.html"],
"matches": ["http://*/*", "https://*/*"]
}
],
background.js
chrome.tabs.onUpdated.addListener( function (tabId, changeInfo, tab) {
if (changeInfo.status == 'complete' && tab.active) {
chrome.scripting.executeScript({
target: { tabId: tabId },
files: ['content-script.js']
});
}
})
chrome.runtime.onMessage.addListener(gotMessage);
function gotMessage(message) {
if(message.from === 'success') {
navigator.mediaDevices.getUserMedia({
audio: { echoCancellation: true }
})
.then((e) => {
console.log('Started.');
})
.catch((e) => {console.log(e);});
}
}
Content-script.js
function embedFrame() {
var iframe = document.createElement('iframe');
iframe.src = chrome.runtime.getURL('frame.html');
iframe.setAttribute('allow','microphone *');
document.body.appendChild(iframe);
}
embedFrame();
frame.html
<!DOCTYPE html>
<html>
<head></head>
<body>
<button id="getaccess"></button>
<button id="msgbackground"></button>
<script src="frame.js"></script>
</body>
</html>
frame.js
var button = document.getElementById("getaccess");
button.addEventListener("click", function () {
navigator.mediaDevices.getUserMedia({
audio: { echoCancellation: true }
})
.then((e) => {
console.log('Started.');
})
.catch((e) => {console.log(e);});
});
var button = document.getElementById("msgbackground");
button.addEventListener("click", function () {
chrome.runtime.sendMessage('hfafcmibegmeaajlogfpbijjfmoeknan', { from: 'success' })
});
Now, no matter if I open any tab and click button in injected iframe, or open frame.html directly using chrome-extension://.../frame.html, I get the following errors:
When I open frame.html directly, I can click the getaccess button and get asked for mic access, all good
When I open frame.html directly, click msgbackground button then i get Cannot read properties of undefined (reading getUserMedia)
When I open any tab and click getaccess button in iframe, get DOMException: Permission denied error (even if I have previously confirmed access through test 1
When I open any tab and click msgbackground button in iframe then i also get Cannot read properties of undefined (reading getUserMedia)

How to detect when website can’t be reached from popup.js with chrome extension

I have some code in my popup from my extension that depends on the content of the tab's current website, but I should be able to detect when the website don't exists (like in the image below) so when the user opens the popup in an invalid website I can show some message like "Extension won't work here because website don't exists"
Example of unreachable website
Use webNavigation API to check the errorOccurred field:
chrome.tabs.query({active: true, currentWindow: true}, tabs => {
chrome.webNavigation.getFrame({tabId: tabs[0].id, frameId: 0}, frame => {
if (frame.errorOccurred) {
document.body.textContent = 'ERROR';
}
});
});
You'll also need "permissions": ["webNavigation"] in manifest.json.
An alternative solution
Use "permissions": ["activeTab"] and test by injection:
chrome.tabs.executeScript({code: '1'}, ([ok] = []) => {
if (chrome.runtime.lastError || !ok) {
document.body.textContent = 'ERROR';
}
});

How to send full HTML (loaded after JS) via API on browser action click - Chrome extension?

For the past 2 days I'm trying to get HTML that is seen on a webpage to be sent via Chrome extension to a specific API endpoint (With URL).
The main problem is that not all HTML content that is seen on a webpage at a moment when Browser action is clicked is sent. For example if website is made in a way that injects HTML at a later time, say when a user clicks "Show more horses" after 1 min after reading through the page - these newly added horses will not be sent.
If user would have to click another button after Browser Action I would be fine with it. The main problem I believe I have is that I am grabbing the HTML right after the content is loaded. I've been trying to change this but with no luck.
After looking at various StackOverflow posts and Chrome Ext. docs I've got the following code that successfully sends the URL and HTML - but not all HTML if it has been added say after 1 min:
manifest.json
{
"manifest_version": 2,
"name": "Horses extension",
"version": "0.3",
"content_scripts": [
{
"matches": [
"<all_urls>"
],
"js": ["content.js"],
"run_at": "document_end",
"all_frames": true
}
],
"background": {
"scripts": ["background.js"]
},
"page_action": {
"default_title": "Horses extension",
"default_icon": "icon.png",
"default_popup": "popup.html"
},
"permissions": [
"activeTab",
"tabs"
]
}
background.js
// Called when the user clicks on the browser action.
chrome.runtime.onMessage.addListener(function (msg, sender) {
// First, validate the message's structure
if ((msg.from === 'content') && (msg.subject === 'showPageAction')) {
// Enable the page-action for the requesting tab
chrome.pageAction.show(sender.tab.id);
}
});
Content.js
// Inform the background page that
// this tab should have a page-action
chrome.runtime.sendMessage({
from: 'content',
subject: 'showPageAction'
});
// Listen for messages from the popup
chrome.runtime.onMessage.addListener(function (msg, sender, response) {
// First, validate the message's structure
if ((msg.from === 'popup') && (msg.subject === 'horsesInformation')) {
var a=[]
a.push(msg.url)
a.push(document.all[0].outerHTML)
response(a)
}
});
popup.js
window.addEventListener ("load", sendJobOpeningInfo, false);
function sendHorsesInformation(url_and_html) {
var jsInitChecktimer = setInterval (checkForJS_Finish, 5000);
function checkForJS_Finish (){
clearInterval (jsInitChecktimer);
//GOAL: Post HTML and Domain to API
$.ajax({
url: "http://lvh.me:3000/api_url_xyz
type: "POST",
data: {url: url_and_html[0], html_source: url_and_html[1]},
success: function (data) {
var horsesTitle = document.getElementById('horses-title')
horsesTitle.append("Success!!")
}
});
}
}
// ...query for the active tab...
chrome.tabs.query({
active: true,
currentWindow: true
}, function (tabs) {
// ...and send a request for the Domain info...
chrome.tabs.sendMessage(
tabs[0].id,
{from: 'popup', subject: 'horsesInformation', url: tabs[0].url},
// ...also specifying a callback to be
//called from the receiving end (content script)
sendHorsesInformation);
});
popup.html
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="popup.js"></script>
</head>
<body>
<h3 id="horses-title"></h3>
</body>
</html>
What would I have to change that all content that is seen on a webpage (even after JS injection) is sent to my API endpoint? Any help would be greatly appreciated!
OK got it. Just had to move the checkForJS_Finish function with ajax function from popup.js inside content.js and it works.

Why does my background page become unavailable?

I've had a long-running Chrome Extension where I'm used to always have access to the background.js page for viewing console, trouble-shooting etc.
I'm implementing Firebase login and have made some changes...and now, after I log into my app, the 'background page' becomes the html of the current popup.
Manifest below...
When you reload or first install the Extension I see what I'm used to...and can click on the link to view the "_generated_background_page.html". Buttons in the popup correctly communicate (via messaging) to run functions in background.js.
However, after logging in, the new popup (I redirect to a new popup for logged in users) replaces the background page (my words) and (most importantly) I can't access the background page anymore. Messaging doesn't have any effect and I can't "see" the console / inspect the background.js page.
In the past I've seen a background page AND another, open page and can inspect them both.
Any thoughts on how I have succeeded in painting myself into a corner? It's as if I'm closing the background.js file.
Manifest:
{
"manifest_version": 2,
"name": "Annotate PRO for Chrome",
"short_name": "Annotate PRO",
"description": "Right-click access to a pre-written library of comments. Write it once, to perfection, and reuse forever!",
"version": "3.1.1.0",
"permissions": [
"identity",
"identity.email",
"clipboardWrite",
"clipboardRead",
"activeTab",
"tabs",
"contextMenus",
"storage",
"webNavigation"
],
"content_security_policy": "script-src 'self' https://ssl.google-analytics.com; object-src 'self'",
"externally_connectable": {
"matches": ["http://*.11trees.com/*"]},
"commands": {
"_execute_browser_action": {
"suggested_key": {
"windows": "Alt+A",
"mac": "Alt+A",
"chromeos": "Alt+A",
"linux": "Alt+A"
}
}
},
"key": "XXX",
"oauth2": {
/*"client_id": "XXX",*/
"client_id": "XXX",
"scopes": [
/*"https://www.googleapis.com/auth/chromewebstore.readonly",*/
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/userinfo.profile"
]
},
"background": {
"scripts": ["/dscripts/jquery-3.1.1.min.js","/dscripts/firebase.js","/scripts/background.js"]},
"content_security_policy": "script-src 'self' https://ssl.google-analytics.com https://apis.google.com/ https://www.gstatic.com/ https://*.firebaseio.com https://www.googleapis.com; object-src 'self'",
"content_scripts": [
{
"all_frames" : true,
"matches": ["http://*/*","https://*/*"],
"js": ["/scripts/content.js"]
}
],
"icons": {
"16": "Annotate16.png",
"48": "Annotate48.png",
"128": "Annotate128.png"
},
"browser_action": {
"default_icon": {
"19": "Annotate128.png",
"38": "Annotate128.png"
},
"default_title": "Annotate PRO for Google Chrome",
"default_popup": "aHome.html"
}
}
background.js
//URLs for scripts
var baseURL = "http://localhost/AnnotateX/Scripts/Dev/"; //local server - macOS
var xmlhttp = new XMLHttpRequest(); //why put this up front? We need a new object on each call...
//Firebase constants
var config = {
apiKey: "XXX",
authDomain: "XXX",
databaseURL: "XXX",
storageBucket: "XXX",
// messagingSenderId: "XXX"
};
firebase.initializeApp(config);
//listener for chrome start
chrome.runtime.onStartup.addListener(initApp()); //This fires verification check...
function initApp() {
// Listen for auth state changes.
// [START authstatelistener]
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
// User is signed in.
var displayName = user.displayName;
var email = user.email;
var emailVerified = user.emailVerified;
// var photoURL = user.photoURL;
// var isAnonymous = user.isAnonymous;
var uid = user.uid;
var providerData = user.providerData;
console.log('We\'re a user...coming through: ' + providerData);
if (user.emailVerified) { //Account is verified
console.log('We\'re a VERIFIED user... ' + emailVerified);
var url1 = baseURL + "aCheckUsers.php"
var url2 = "&fbUserID=" + uid + "&UserEmail=" + email + "&fullName=" + displayName;
$.ajax({
type: "POST",
url: url1,
data: url2,
dataType: 'json',
success: function(arrResult){
arrUserData = arrResult;
console.log('User data: ') + console.log(arrUserData);
localStorage.userDetails = JSON.stringify(arrUserData);
localStorage.userID = arrUserData.userID;
localStorage.licType = arrUserData.LicType;
startup();
},
error: function (jqXHR, textStatus, errorThrown) {
console.log('Error: ' + errorThrown + ' / ' + textStatus) + console.log(jqXHR);
}
});
}
// [START_EXCLUDE]
// [END_EXCLUDE]
} else {
// Let's try to get a Google auth token programmatically.
// [START_EXCLUDE]
console.log('Not a user (background.js)');
// [END_EXCLUDE]
}
});
}
function signOut() {
console.log("Logging out via subroutine in background.js.");
firebase.auth().signOut();
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, {method: "logOut"});
});
chrome.browserAction.setPopup({ //Sets popup to last visited
popup: 'aHome.html' // Open this html file within the popup.
});
}
//function that determines whether userID exists and library can be loaded or if new user must be created first
function startup(){
console.log("Starting up...");
chrome.storage.sync.get('lastSave', function(obj) {
var syncSaveTime = obj.lastSave;
var localSaveTime = localStorage.lastSync;
console.log('local: ' + localSaveTime + ' | sync: ' + syncSaveTime);
// if (localSaveTime == null || syncSaveTime >= localSaveTime){ //test
if (localSaveTime == null || syncSaveTime > localSaveTime){ //production
// console.log("Current user: " + localStorage.userID);
console.log("Local version is outdated...should run db pulll...");
pullLibrary();
} //End process for running library load if outdated or NO data locally...
else {
console.log("We've got data - skip the heavyweight pull....use local");
processLibrary(JSON.parse(localStorage.library));
}
}); //End async storage GET
} //End STARTUP
// Firebase auth popups
function googleLoginPopUp() {
var provider = new firebase.auth.GoogleAuthProvider();
firebase.auth().signInWithPopup(provider).then(function(result) {
// This gives you a Google Access Token. You can use it to access the Google API.
var token = result.credential.accessToken;
// The signed-in user info.
var user = result.user;
// ...
}).catch(function(error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
// The email of the user's account used.
console.log(errorCode + ' - ' + errorMessage);
// ...
});
} //End Google Login
function facebookLoginPopUp() {
var provider = new firebase.auth.FacebookAuthProvider();
firebase.auth().signInWithPopup(provider).then(function(result) {
// This gives you a Facebook Access Token. You can use it to access the Facebook API.
var token = result.credential.accessToken;
// The signed-in user info.
var user = result.user;
// ...
}).catch(function(error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
console.log(errorCode + ' - ' + errorMessage);
});
}
Okay...good night's sleep and some basic debugging...
Part of my background.js startup process was to hand the user off to the post-login popup page.
self.location.href='aSearch.html'; //finally open core search page
I don't understand exactly why, but this line in background.js effectively replaced background.js with the aSearch.html and aSearch.js pages...background.js became unavailable to messaging etc.
Removing the line did the trick...and you can't open a page from background.js anyway.

Chrome extension tab.url undefined

I'm using a chrome extension with a button in the popup.html that opens a new tab. The destination URL of the new tab holds the URL of the current (original) tab as a parameter.
For instance: when fired from http://stackoverflow.com/, the new tab should have an URL like http://www.mydestination.com/index.php?url=http://stackoverflow.com/
Here's my js:
document.addEventListener('DOMContentLoaded', function (tab) {
document.getElementById('button').addEventListener("click", function(tab) {
chrome.tabs.create({url: 'http://www.mydestination.com/index.php?url=' + tab.url});
});
})
The new tab is opened perfectly, but the URL is http://www.mydestination.com/index.php?url=undefined (url = undefined).
I reckon the manifest.json holds the right permissions:
{
"manifest_version": 2,
"name": "My project",
"version" : "1.7",
"browser_action": {
"default_icon" : "img/icon.png",
"default_title" : "My project",
"default_popup": "html/main.html"
},
"permissions": [
"tabs"
],
"icons": {
"16": "img/icon.png"
}
}
Any clues on how to get the url transported properly?
The problem is that current tab is your chrome popup. In this case you don't have valid URL.
You have to select your tab. To do this you can use chrome.tabs.query. Select current window with active tab:
document.addEventListener('DOMContentLoaded', function () {
document.getElementById('button').addEventListener("click", function () {
chrome.tabs.query({
'active': true,
'windowId': chrome.windows.WINDOW_ID_CURRENT
}, function (tabs) {
chrome.tabs.create({
url: 'http://www.mydestination.com/index.php?url=' + tabs[0].url
});
});
});
});
The problem is that you are passing tab as a parameter when it has nothing to do with the events. While some chrome.* apis do include a tab object as a parameter, you can't just add it on like that and expect it to have the info you want. You could do something like this:
document.addEventListener('DOMContentLoaded', function () {
document.getElementById('button').addEventListener("click", function() {
chrome.tabs.query({active:true, currentWindow:true},function(tab){
// Just doing it like this to make it fit on the page
var newUrl = "http://www.mydestination.com/index.php?url=" + tab[0].url;
chrome.tabs.create({url:newUrl});
});
});
});

Resources