Cannot read property 'executeScript' of undefined - google-chrome-extension

I follow the 'Getting started' tutorial of chrome extensions, but I get the below error.
I search google, somebody say can't access 'executeScript' in content.js, but the error is from popup.js.
I had tried change 'chrome.scripting.executeScript' to 'chrome.tabs.executeScript', it didn't work too.
error image
manifest.json
{
"name": "Getting Started Example",
"version": "1.0",
"description": "Build an Extension!",
"permissions": ["storage", "declarativeContent", "activeTab"],
"background": {
"scripts": ["background.js"],
"persistent": false
},
"page_action": {
"default_popup": "popup.html"
},
"options_page": "options.html",
"manifest_version": 2
}
popup.js
let changeColor = document.getElementById('changeColor')
chrome.storage.sync.get('color', function(data) {
changeColor.style.backgroundColor = data.color;
changeColor.setAttribute('value', data.color)
});
changeColor.addEventListener('click', () =>
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.scripting.executeScript(
tabs[0].id,
{ function: setColor })
})
);
async function setColor() {
let {color} = await chrome.storage.sync.get(['color']);
document.body.style.backgroundColor = color;
};
The whole extension code
reference: chrome developers -> Getting started

The permissions in your manifest.json is missing one item, which is "scripting".
It should look like this:
…
"permissions": ["storage", "declarativeContent", "activeTab", "scripting"],
…
This is actually seen on the Getting Started page here.

You need scripting permissions to access scripting APIs. Add scripting to your manifest.js file.
"permissions": ["scripting", ...]

You either need to migrate to manifest v3 or use chrome.tabs.executeScript instead of chrome.scripting.executeScript

In my case, the exception log "Cannot read property 'executeScript' of undefined" was pretty misleading.
With correct manifests, it happened, and it was because of typo in the to be injected function, like below.
document.body.style.backgrounColor = color;
corrected it to
document.body.style.backgroundColor = color;
and it worked.

Remove and load the extension back! Despite adding "permissions": ["scripting"] this is what I had to do.

I found that chrome.scripting.executeScript works in popup but not in content scripts.
This is a super simple popup example in React. You click on the button and it shows the page title.
export function Popup() {
const [title, setTitle] = useState("");
const onClick = async () => {
const [tab] = await chrome.tabs.query({ currentWindow: true, active: true });
const [res] = await chrome.scripting.executeScript({
target: { tabId: tab.id },
func: () => document.title,
});
setTitle(res.result);
};
return (
<div className="popup">
<button onClick={onClick}>Click</button>
{title}
</div>
);
}

Related

How to access iframes using scripting API?

I have a chrome extension I need to access all the iframes on the attached page using the scripting API
{
"manifest_version": 3,
"name": "My example extension",
"version": "0.0.1",
"permissions": ["tabs", "scripting"],
"devtools_page": "devtools.html",
"host_permissions": [
"<all_urls>"
]
}
I can run the script on the inspectedWindow easily enough
// devtools.html -> js
chrome.devtools.panels.create("Sample Panel", "icon.png", "/panel.html", panel => {
// code invoked on panel creation
});
// panel.html -> js
chrome.scripting.executeScript({
target: { tabId: chrome.devtools.inspectedWindow.tabId },
injectImmediately: true,
func: () => {
console.log('foo')
}
})
I want this script to run on the page and all children iframes immediately (before anything else is run, just like a content_script).
It seems this only runs on the host page. When I try to query for child iframes, it doesn't seem to find anything
> await chrome.tabs.query({ windowId: chrome.devtools.inspectedWindow.tabId })
// []
Did I miss a permission?

<style> injected in content_scripts doesn't get applied to document element in background.js

I've got a script set to "run_at": "document_idle", and it injects a tag into the header. However, attempting to apply the class therein defined to an element later does not cause any change in the element.
manifest.json:
{
"manifest_version": 2,
"name": "Test",
"description": "make color",
"version": "1.0",
"background": {
"scripts": ["background.js"],
"persistent": false
},
"content_scripts": [
{
"matches": ["*://*/*"],
"js": ["pageload.js"],
"run_at": "document_idle"
}],
"browser_action": {},
"permissions": ["*://*/*","activeTab","tabs"]
}
pageload.js:
'use strict';
// onload, add our class "highlight"
var css = "\n\t.highlight { background-color: yellow; }\n",
rstyle = document.createElement('style');
// Append style element to head
document.head.appendChild(rstyle);
//rstyle.type = "text/css";
rstyle.appendChild(document.createTextNode(css));
background.js (run on button click):
'use strict';
// Called when the user clicks on the browser action.
chrome.browserAction.onClicked.addListener(function(tab) {
// toggle putting the class on the body
document.body.classList.toggle('highlight');
});
When a page loads, I see the element in the header. When I click the button, I see the "class='highlight'" appear in the stand-alone (background page) dev elements but not the browser dev elements.
Anybody know what I'm missing?
Thank you!
The background script executes in a DOM of its own. To modify the document in the active tab you'll need to use the "activeTab" permission in the manifest's permissions and execute your code in the following way
chrome.tabs.query({active: true, currentWindow: true},
function(tabs) {
chrome.tabs.executeScript(
tabs[0].id,
{code: "document.body.classList.toggle('highlight');"});
});
Here's ultimately what worked for me--Using messaging between the background and content sources:
'use strict';
// onload, add our class "highlight"
var css = "\n\t.highlight { background-color:yellow; }\n",
rstyle = document.createElement('style');
// Append style element to head
document.head.appendChild(rstyle);
rstyle.type = "text/css";
rstyle.appendChild(document.createTextNode(css));
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if( request.message === "toggle_highlight" ) {
document.body.classList.toggle('highlight');
}
}
);
and
'use strict';
chrome.browserAction.onClicked.addListener(function(tab) {
// Send a message to the active tab
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
var activeTab = tabs[0];
chrome.tabs.sendMessage(activeTab.id, {"message": "toggle_highlight"});
});
});

Chrome Extension: Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist

Recently, it is reported that the context menu of my app is vanished. If you remove the app and reinstall it, it works. But the vanishment happens again.
I found an error. I'm not sure if the error causes the vanishment of the context menu. But I'd like to fix this matter, because all I found is this.
This app shows texts you select in a page. When you select texts in an ordinaly page and click browser action button, it works without error. But if you try it on Google Docs, you will get error "Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist".
I'm afraid I don't know what to do with this. And I might have two problems. It'll be great help if you could give me some advice.
[manifest.js]
{
"manifest_version": 2,
"name": "Test Chrome Extension",
"short_name": "Test",
"version": "1.0",
"description": "This is a test.",
"icons": {
"128": "128.png"
},
"content_scripts": [{
"matches": ["<all_urls>"],
"js": ["googleDocsUtil.js", "content_scripts.js"]
}],
"background": {
"scripts": ["background.js"],
"persistent": true
},
"browser_action": {
"default_icon": {
"48": "48.png"
},
"default_title": "Test Chrome Extension"
},
"permissions": [
"contextMenus",
"tabs",
"background",
"http://*/*",
"https://*/*"
]
}
[background.js]
chrome.contextMenus.create({
type: 'normal',
id: 'testchromeextension',
title: 'Test Chrome Extension',
contexts:['selection']
});
chrome.contextMenus.onClicked.addListener(function(info,tab){
if( info.menuItemId == 'testchromeextension' ){
var selectedText = info.selectionText.replace(/ /g, "\n");
doSomething(selectedText);
}
});
chrome.browserAction.onClicked.addListener( function(tab) {
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, {method: "getSelection"}, function(response) {
doSomething(response.data);
});
});
});
function doSomething(selectedText) {
console.log(selectedText);
}
[content_scripts.js]
chrome.runtime.onMessage.addListener( function(request, sender, sendResponse) {
if (request.method == "getSelection") {
var post_val = window.getSelection().toString();
if ( !post_val ) {
var googleDocument = googleDocsUtil.getGoogleDocument();
post_val = googleDocument.selectedText;
}
sendResponse({data: post_val});
}
});
I believe this error is caused when you update the local version of an extension and then try to use the extension with its old/not-updated source code.
The fix: after you reload your local extension at chrome://extensions/, make sure you refresh the page you're using the extension on. You should no longer see the error.

In Chrome Extension development, how do I pass a variable from background.js to a content script that embedded as a script node?

Update: I am sorry that I wasn't clear about my case. The content script is not literally content script but a js file embedded into the page via the createElement method.
It's something like:
// this is "content.js"
var scriptElement = document.createElement('script');
scriptElement.src = 'chrome-extension://' + chrome.runtime.id + '/js/page-inject.js';
(document.head || document.documentElement).appendChild(scriptElement);
I want to use it in "page-inject.js", not the "content.js"
I thought it was very simple but failed after hours of retrying and searching.
I have a background.js like this:
chrome.tabs.query({active: true, currentWindow: true},
function(tabs) {
chrome.tabs.executeScript(tabs[0].id, {
code: 'window.my_var = 123'
}, function() {
chrome.tabs.executeScript(tabs[0].id, {file: 'js/content.js'});
});
});
After this, when I type "window.my_var" in the console of that page. It's not working. The wanted "123" is not showing up.
If I replace the code: 'window.my_var = 123' to alert(123), it works. But what I need is just to pass a variable from my background script (background.js) to the web page that my extension injected into.
Is there anything wrong with my try?
As #wOxxOm already pointed out you how to see the variable inject using the chrome.tabs.executeScript in the web page console by changing the Top in the console.
There is another way you can easily pass values from background to content script using messaging.
Se below a simple example:
manifest.json
{
"name": "test",
"version": "0.0.1",
"manifest_version": 2,
"description": "Test ",
"background": {
"scripts": [
"background.js"
]
},
"browser_action": {
"default_title": "Test "
},
"permissions": [
"*://*/*",
"tabs"
], "content_scripts" : [{
"matches" : ["*://*/*"],
"js" : ["content.js"]
}]
}
background.js
chrome.browserAction.onClicked.addListener(function(){
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, {data: "some value"});
});
});
content.js
chrome.extension.onMessage.addListener(handleMessage);
function handleMessage(request) {
console.log(request.data);
}
chrome.extension.getBackgroundPage() will return the global window object of the background page. You can access scripts and any data in the background worker.
Example
const backgroundDOMWindow = chrome.extension.getBackgroundPage();
const myGlobalVariable = backgroundDOMWindow.window['myGlobalVariable'];
Adding script to global background object "background.js"
document.write(
'<script src="main.js"></script>'+
'<script src="main2.js"></script>'
);

Google Chrome Extension Not Working As Expected

So I'm trying my hand at a google chrome extension which in theory should be extremely straight forward. I know there are tons of stack queries and I've tried a few, I've even copied and pasted direct solutions for testing and I have been unsuccessful. So here is the project. When a page loads, check the html content, and change the icon programmatically.
This is my content.js
chrome.tabs.onUpdated.addListener( function (tabId, changeInfo, tab) {
if (changeInfo.status == 'complete' && tab.active) {
var markup = document.documentElement.innerHTML;
var m = markup.indexOf("candy");
if (m > -1) {
chrome.browserAction.setIcon({path: "check.png"});
} else {
chrome.browserAction.setIcon({path: "cross.png"});
}
}
})
This is my manifest.json
{
"manifest_version": 2,
"name": "Tag Analyzer Plugin",
"description": "Check if tag exist on page",
"version": "1.0",
"browser_action": {
"default_icon": "cross.png"
},
"content_scripts": [
{
"matches": [
"<all_urls>"
],
"js": ["content.js"]
}
],
"permissions": ["<all_urls>"]
}
Right now I'm running this item as a content script because as a content script I can use the logic
var markup = document.documentElement.innerHTML;
var m = markup.indexOf("candy");
if (m > -1) {} else {}
However as a content script the chrome API stuff doesn't work. When I run this script as a background script the script works, except that
var markup = document.documentElement.innerHTML;
doesn't return the pages html, it returns the injected script html.
I've read this stack which was informative as to what the difference was and I've read and tested many stacks like here this, without much success. So obviously i'm missing something and doing something wrong. Thank in advanced for any help.
UPDATES:
So I've made some modifications, but it's still not working, though I think that it's closer to working.
As per the comments below I am now using a content script and background script. No errors are being thrown, however the background script isn't doesn't anything.
content.js
var markup = document.documentElement.innerHTML;
var m = markup.indexOf("candy");
if (m > -1) {
chrome.runtime.sendMessage({"found" : true});
} else {
chrome.runtime.sendMessage({"found": false});
}
background.js
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if request.found {
alert("HERE");
chrome.browserAction.setIcon({
path: "check.png",
tabId: sender.tab.id
});
} else {
alert("HERE2");
chrome.browserAction.setIcon({
path: "cross.png",
tabId: sender.tab.id
});
}
});
manifest.json
{
"manifest_version": 2,
"name": "Tag Analyzer Plugin",
"description": "find tag on page",
"version": "1.0",
"browser_action": {
"default_icon": "cross.png"
},
"background": {
"scripts": ["background.js"],
"persistent": false
},
"content_scripts": [
{
"matches": [
"<all_urls>"
],
"js": ["content.js"]
}
],
"permissions": ["<all_urls>"]
}
I've added some alerts on the background.js to see if it was being trigger and nothing, not does the icon change.

Resources