Why is the scope different in chrome extension's popup? - google-chrome-extension

I'm experimenting with Google Chrome extensions.
In a popup html I have a button that executes the readItalian function.
italianBtn.addEventListener("click", async () => {
let [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
chrome.scripting.executeScript({
target: { tabId: tab.id },
function: readItalian,
});
});
const readItalian = () => readPage("it");
function readPage(targetLanguage) {
//
}
The console says that readPage is not defined. It seems that there is a different scope. How does this work?

Related

Chrome Extension: How to communicate with Content.js from a newly opened Window?

I have created a new window in chrome.action.onClicked.addListener as given below.
On clicking of "Check" button in newly opened window I need to connect to content.js and print some message in the console of window. I dont know where it is going wrong! I am using Manifest version 3.
content.js
chrome.runtime.onMessage.addListener(function(msg, sender, sendResponse) {
if(msg.color === "#00FF00"){
document.body.style.backgroundColor = "green";
sendResponse({ status: "done" });
}
});
background.js
var urlRegex = /^(https?:\/\/)?[a-z0-9-]*\.?[a-z0-9-]+\.[a-z0-9-]+(\/[^<>]*)?$/;
chrome.action.onClicked.addListener(function(tab) {
/*...check the URL of the active tab against our pattern and... */
if (urlRegex.test(tab.url)) {
/* ...if it matches, send a message specifying a callback too */
chrome.windows.create({
tabId: tab.id,
type:"popup",
url:"popup.html",
focused:true
});
}
});
popup.html
<html>
<head>
<script defer src="popup.js"></script>
</head>
<body>
<h3>Test Extension Page</h3>
<input type="button" id="sendMessage" value="Check"/>
</body>
</html>
popup.js
let sendMessageButton = document.getElementById("sendMessage");
console.log(document.URL);
console.log(sendMessageButton.value);
function getTitle()
{
return document.title;
}
sendMessageButton.onclick = function() {
chrome.tabs.query({ active: true, currentWindow: true }, function(tabs){
var tab = tabs[0];
chrome.scripting.executeScript(
{
target: {tabId:tab.id},
func: getTitle,
},
() => {
// This executes only after your content script executes
chrome.tabs.sendMessage(
tab.id,
{ color: "#00FF00" },
function (response) {
console.log(response.status);
}
);
});
});
};
Error in console of newly opened window.
Unchecked runtime.lastError: Cannot access contents of url "chrome-extension://jjaaoafdfmabdajdckiacompibnnmnlh/popup.html". Extension manifest must request permission to access this host.
Error handling response: TypeError: Cannot read properties of undefined (reading 'status') at chrome-extension://jjaaoafdfmabdajdckiacompibnnmnlh/popup.js:25:34
Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist.
The problem is that the window you create becomes active and hence it becomes the result of chrome.tabs.query in your code, meaning that executeScript runs inside your own extension page, which can't work as this method is only for web sites.
The solution is to pass the tab id as URL parameter.
// background.js
chrome.action.onClicked.addListener(tab => {
chrome.windows.create({
type: 'popup',
url: 'popup.html?' + new URLSearchParams({
tabId: tab.id,
title: tab.title,
}),
});
});
// popup.js
const params = new URLSearchParams(location.search);
const tabId = +params.get('tabId');
let title = params.get('title'); // initial title
document.getElementById('sendMessage').onclick = async function () {
title = (await chrome.tabs.get(tabId)).title;
let res = await chrome.tabs.sendMessage(tabId, { color: "#00FF00" });
};

Chrome extension send message from onInstall in backgroundjs to own extension javascript code

I need to check when the extension is installed and change my React state accordingly.
I use chrome.runtime.onInstalled on my background.js where I sendMessage to my react code - which is the content script of my extension.
background.js
async function getCurrentTab() {
let queryOptions = { active: true, currentWindow: true };
let [tab] = await chrome.tabs.query(queryOptions);
return tab;
}
chrome.runtime.onInstalled.addListener((details) => {
if (details?.reason === 'install') {
console.log('installed backgroundjs')
const tab = await getCurrentTab()
chrome.tabs.sendMessage(tab.id, { target: 'onInstall' })
openNewTab()
}
})
In my react Component - Dashboard.js
useEffect(() => {
if (extensionApiObject?.runtime) {
chrome.runtime.sendMessage({ target: 'background', message: 'check_installation', })
console.log('extension')
chrome.runtime.onMessage.addListener(handleMessage)
}
return () => {
if (extensionApiObject?.runtime) {
chrome.runtime.onMessage.removeListener(handleMessage)
}
}
})
function handleMessage(msg) {
console.log('handle messag func', msg)
if (msg.target === 'onInstall') {
console.log('extension on Install')
setShowWelcomeMessage(true)
}
}
What confuse me is that I already have a similar implementation for a different message that works without problem but in there I listen to chrome.runtime.onMessage() not chrome.runtime.onInstalled()
I wonder if I misunderstand how onInstalled method work and I cannot sendMessage from it?
UPDATE:
I change my code as suggested by #wOxxOm, I used chrome.tabs.sendMessage but still no luck.
chrome.runtime.onInstalled doesn't take as argument req, sender, sendResponse like other listener and I wonder if that means it will not be able to send a message from there :/
After #wOxxOm suggestions, I ended up removing the sendMessage solution and simply add an extra parameter to the url whenever I open a new tab after installation:
background.js
function openNewTab(param) {
chrome.tabs.create({
url: param ? `chrome://newtab?${param}` : 'chrome://newtab',
})
}
chrome.runtime.onInstalled.addListener((details) => {
if (details?.reason === 'install') {
chrome.tabs.sendMessage(tab.id, { target: 'onInstall' })
openNewTab('installed')
}
})
on my web app I just need to check the param and I can decide which UI to show

unable to click or sendkeys on webElements in Protractor Cucumber Framework

My Code is working fine with out any failure but without performing any click or sendkeys or any other actions.. browser is closing automatically and it is not even responding for browser.sleep command..when using console.log it is printing text but element action is not working
StepDef file
var page=require("..\\src\\StepDefFiles\\testpage.js");
var test=function(){
Given('Load the URL', function () {
page.browserInit();
});
Given('Get the Title', function () {
// Write code here that turns the phrase above into concrete actions
browser.getTitle().then(function(title){
console.log(title);
})
});
Then('Login in to the account', function () {
page.gmailLink();
});
Then('validate the home page', function () {
browser.getTitle().then(function(Title){
if(Title.indexOf("sign in")!=-1){
console.log(Title);
}
})
});
}
module.exports=new test();
Test Page File
var testPage=function(){
this.browserInit=function(){
browser.ignoreSynchronization=true;
browser.get("https://google.com");
browser.sleep(5000);
browser.manage().window().maximize();
browser.sleep(5000);
}
this.gmailLink=function(){
element(By.xpath("//a[text()='Gmail']")).click();
}
}
module.exports=new testPage();
Config File
exports.config = {
seleniumAddress: 'http://localhost:4444/wd/hub',
getPageTimeout: 60000,
allScriptsTimeout: 500000,
framework: 'custom',
// path relative to the current config file
frameworkPath: require.resolve('C:\\Users\\DELL\\node_modules\\protractor-cucumber-framework'),
capabilities: {
'browserName': 'chrome'
},
// Spec patterns are relative to this directory.
specs: [
'..\\Protractor_Cucumber\\src\\FeatureFiles\\Test.feature'
],
cucumberOpts: {
require: '..\\Protractor_Cucumber\\src\\StepDefFiles\\stepDef.js',
tags: false,
profile: false,
'no-source': true
},
onPrepare: function () {
const {Given, Then, When, Before} = require('C:\\Users\\DELL\\node_modules\\cucumber');
global.Given = Given;
global.When = When;
global.Then = Then;
global.Before = Before;
}
};
Feature file
Feature: Title of your feature
I want to use this template for my feature file
Scenario: Title of your scenario
Given Load the URL
And Get the Title
Then Login in to the account
And validate the home page
Console log
21:41:37] I/launcher - Running 1 instances of WebDriver
[21:41:37] I/hosted - Using the selenium server at http://localhost:4444/wd/hub
.....
1 scenario (1 passed)
4 steps (4 passed)
0m00.030s
Google
[21:41:46] I/launcher - 0 instance(s) of WebDriver still running
[21:41:46] I/launcher - chrome #01 passed
it would be a great help for me if anybody able to answer this one
No need to wrap step function into another function and export, detail at here
You need to return a promise for each step function, detail at here
Change your StepDef file as following:
var page = require("../src/StepDefFiles/testpage.js");
Given('Load the URL', function() {
return page.browserInit();
});
Given('Get the Title', function() {
// Write code here that turns the phrase above into concrete actions
return browser.getTitle().then(function(title) {
console.log(title);
})
});
Then('Login in to the account', function() {
return page.gmailLink();
});
Then('validate the home page', function() {
return browser.getTitle().then(function(Title) {
if (Title.indexOf("sign in") != -1) {
console.log(Title);
}
})
});

"Extension context invalidated" error when calling chrome.runtime.sendMessage()

I have a content script in a Chrome Extension that's passing messages. Every so often, when the content script calls
chrome.runtime.sendMessage({
message: 'hello',
});
it throws an error:
Uncaught Error: Extension context invalidated.
What does this error mean? I couldn't find any documentation on it.
It doesn't happen consistently. In fact, it's hard to reproduce. Seems to happen if I just leave the page open for a while in the background.
Another clue: I've written many Chrome Extensions with content scripts that pass messages and I haven't seen this error before. The main difference is that this content script is injected by the background page using
chrome.tabs.executeScript({
file: 'contentScript.js',
});
Does using executeScript instead of the manifest file somehow change the lifecycle of the content script?
This is certainly related to the message listener being lost in the middle of the connection between content and background scripts.
I've been using this approach in my extensions, so that I have a single module that I can use in both background and content scripts.
messenger.js
const context = (typeof browser.runtime.getBackgroundPage !== 'function') ? 'content' : 'background'
chrome.runtime.onConnect.addListener(function (port) {
port.onMessage.addListener(function (request) {
try {
const object = window.myGlobalModule[request.class]
object[request.action].apply(module, request.data)
} catch () {
console.error(error)
}
})
})
export function postMessage (request) {
if (context === 'content') {
const port = chrome.runtime.connect()
port.postMessage(request)
}
if (context === 'background') {
if (request.allTabs) {
chrome.tabs.query({}, (tabs) => {
for (let i = 0; i < tabs.length; ++i) {
const port = chrome.tabs.connect(tabs[i].id)
port.postMessage(request)
}
})
} else if (request.tabId) {
const port = chrome.tabs.connect(request.tabId)
port.postMessage(request)
} else if (request.tabDomain) {
const url = `*://*.${request.tabDomain}/*`
chrome.tabs.query({ url }, (tabs) => {
tabs.forEach((tab) => {
const port = chrome.tabs.connect(tab.id)
port.postMessage(request)
})
})
} else {
query({ active: true, currentWindow: true }, (tabs) => {
const port = chrome.tabs.connect(tabs[0].id)
port.postMessage(request)
})
}
}
}
export default { postMessage }
Now you'll just need to import this module in both content and background script. If you want to send a message, just do:
messenger.postMessage({
class: 'someClassInMyGlobalModuçe',
action: 'someMethodOfThatClass',
data: [] // any data type you want to send
})
You can specify if you want to send to allTabs: true, a specific domain tabDomain: 'google.com' or a single tab tabId: 12.

how to get current tabId from background page

how to get current tabId from background page?
current tabId is the tab that user can see its content.
background.html
<html>
<head>
<script>
if(typeof localStorage.state == 'undefined')
localStorage.state = 'off'
chrome.browserAction.onClicked.addListener(function(tab) {
if(localStorage.state == 'on')
{
localStorage.state = 'off';
}
else
{
localStorage.state = 'on';
}
chrome.browserAction.setBadgeText({text: localStorage.state, tabId: tab.id});
chrome.tabs.sendRequest(tab.id, {state: localStorage.state});
//chrome.tabs.sendRequest(tab.id, {state: localStorage.state});
});
</script>
</head>
getSelected has been deprecated. The new way to do it is:
chrome.tabs.query(
{currentWindow: true, active : true},
function(tabArray){...}
)
If you want to perform some callback on the active tab, you can wrap the above as so:
function doInCurrentTab(tabCallback) {
chrome.tabs.query(
{ currentWindow: true, active: true },
function (tabArray) { tabCallback(tabArray[0]); }
);
}
For example
var activeTabId;
doInCurrentTab( function(tab){ activeTabId = tab.id } );
Run this in your background page
chrome.tabs.query({active:true,windowType:"normal", currentWindow: true},function(d){console.debug(d);})
or even better
chrome.tabs.query({active:true,windowType:"normal", currentWindow: true},function(d){console.debug(d[0].id);})
Many API methods interpret null as a current tab. chrome.tabs.sendRequest is one of them.
Otherwise:
chrome.tabs.getSelected(null, function(tab) { ... })
According to the official documentation: Manifest V3 (promise), which will be enforced since Jan 2023
https://developer.chrome.com/docs/extensions/reference/tabs/#get-the-current-tab
async function getCurrentTab() {
let queryOptions = { active: true, lastFocusedWindow: true };
let [tab] = await chrome.tabs.query(queryOptions);
return tab;
}
If you have tabs user permission, the query method is this: chrome.tabs.query
getCurrentWindowActiveTabIndex().then(tabIndex => {
// do something
});
// asnyc getter, not just a regular 'thunk'
function getCurrentWindowActiveTabIndex () {
return new Promise((resolve, reject) => {
chrome.tabs.query({
currentWindow: true,
active: true,
}, (currentWindowActiveTabs = []) => {
if (!currentWindowActiveTabs.length) reject();
resolve(currentWindowActiveTabs[0].index);
});
});
}
For manifest version 3
Here :
chrome.tabs.query({currentWindow: true, active: true}, function(tabs){
console.log(tabs[0].url);
my_tabid=tabs[0].id;
alert(my_tabid);
});
//then use it, my_tabid

Resources