I'm trying to pass a message from my content script to my background page. This error occurs when the content script is executed:
Uncaught TypeError: Cannot call method 'sendRequest' of undefined
Content Script:
function injectFunction(func, exec) {
var script = document.createElement("script");
script.textContent = "-" + func + (exec ? "()" : "");
document.body.appendChild(script);
}
function login() {
chrome.extension.sendMessage({greeting: "hello"}, function(response) {
console.log(response.farewell);
});
var d = window.mainFrame.document;
d.getElementsByName("email")[0].value = "I need the response data here";
d.getElementsByName("passwort")[0].value = "Here too.";
d.forms["login"].submit();
}
injectFunction(login, true);
Background:
chrome.extension.onMessage.addListener(
function(request, sender, sendResponse) {
if (request.greeting == "hello")
sendResponse({farewell: "goodbye"});
});
manifest.json:
{
"name": "Sephir Auto-Login",
"version": "1.0",
"manifest_version": 2,
"description": "Contact x#x.com for support or further information.",
"options_page": "options.html",
"icons":{
"128":"icon.png"
},
"background": {
"scripts": ["eventPage.js"]
},
"content_scripts": [
{
"matches": ["https://somewebsite/*"],
"js": ["login.js"]
},
{
"matches": ["somewebsite/*"],
"js": ["changePicture.js"]
}
],
"permissions": [
"storage",
"http://*/*",
"https://*/*",
"tabs"
]
}
Those are the examples on the documentation from google, so they should work.
Any help? I'm completely lost.
The problem is caused by your misunderstanding of the script executing environment. Read Chrome extension code vs Content scripts vs Injected scripts for more information. To be precise, you're using a form of this method to execute code in the context of a web page. Web pages do not have any access to the chrome.extension API.
I suggest to rewrite your code to not use injected scripts, because it's not necessary in this case.
function login() {
chrome.extension.sendRequest({greeting: "hello"}, function(response) {
console.log(response.farewell);
});
var d = document.getElementById('mainFrame').contentDocument;
d.getElementsByName("email")[0].value = "I need the response data here";
d.getElementsByName("passwort")[0].value = "Here too.";
d.forms["login"].submit();
}
login();
* Only works if the frame is located at the same origin. Otherwise, you need this method to execute code correctly.
sendRequest and onRequest are deprecated. You need to use sendMessage and onMessage.
Also, you are injecting function to the DOM, that makes it run outside the content script context so chrome.extension API is no longer available for this function.
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.
I'm trying to access the activeTab DOM content from my popup. Here is my manifest:
{
"manifest_version": 2,
"name": "Test",
"description": "Test script",
"version": "0.1",
"permissions": [
"activeTab",
"https://api.domain.com/"
],
"background": {
"scripts": ["background.js"],
"persistent": false
},
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
"browser_action": {
"default_icon": "icon.png",
"default_title": "Chrome Extension test",
"default_popup": "index.html"
}
}
I'm really confused whether background scripts (event pages with persistence: false) or content_scripts are the way to go. I've read all the documentation and other SO posts and it still makes no sense to me.
Can someone explain why I might use one over the other.
Here is the background.js that I've been trying:
chrome.extension.onMessage.addListener(
function(request, sender, sendResponse) {
// LOG THE CONTENTS HERE
console.log(request.content);
}
);
And I'm just executing this from the popup console:
chrome.tabs.getSelected(null, function(tab) {
chrome.tabs.sendMessage(tab.id, { }, function(response) {
console.log(response);
});
});
I'm getting:
Port: Could not establish connection. Receiving end does not exist.
UPDATE:
{
"manifest_version": 2,
"name": "test",
"description": "test",
"version": "0.1",
"permissions": [
"tabs",
"activeTab",
"https://api.domain.com/"
],
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"]
}
],
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
"browser_action": {
"default_icon": "icon.png",
"default_title": "Test",
"default_popup": "index.html"
}
}
content.js
chrome.extension.onMessage.addListener(
function(request, sender, sendResponse) {
if (request.text && (request.text == "getDOM")) {
sendResponse({ dom: document.body.innerHTML });
}
}
);
popup.html
chrome.tabs.getSelected(null, function(tab) {
chrome.tabs.sendMessage(tab.id, { action: "getDOM" }, function(response) {
console.log(response);
});
});
When I run it, I still get the same error:
undefined
Port: Could not establish connection. Receiving end does not exist. lastError:30
undefined
The terms "background page", "popup", "content script" are still confusing you; I strongly suggest a more in-depth look at the Google Chrome Extensions Documentation.
Regarding your question if content scripts or background pages are the way to go:
Content scripts: Definitely
Content scripts are the only component of an extension that has access to the web-page's DOM.
Background page / Popup: Maybe (probably max. 1 of the two)
You may need to have the content script pass the DOM content to either a background page or the popup for further processing.
Let me repeat that I strongly recommend a more careful study of the available documentation!
That said, here is a sample extension that retrieves the DOM content on StackOverflow pages and sends it to the background page, which in turn prints it in the console:
background.js:
// Regex-pattern to check URLs against.
// It matches URLs like: http[s]://[...]stackoverflow.com[...]
var urlRegex = /^https?:\/\/(?:[^./?#]+\.)?stackoverflow\.com/;
// A function to use as callback
function doStuffWithDom(domContent) {
console.log('I received the following DOM content:\n' + domContent);
}
// When the browser-action button is clicked...
chrome.browserAction.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.tabs.sendMessage(tab.id, {text: 'report_back'}, doStuffWithDom);
}
});
content.js:
// Listen for messages
chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) {
// If the received message has the expected format...
if (msg.text === 'report_back') {
// Call the specified callback, passing
// the web-page's DOM content as argument
sendResponse(document.all[0].outerHTML);
}
});
manifest.json:
{
"manifest_version": 2,
"name": "Test Extension",
"version": "0.0",
...
"background": {
"persistent": false,
"scripts": ["background.js"]
},
"content_scripts": [{
"matches": ["*://*.stackoverflow.com/*"],
"js": ["content.js"]
}],
"browser_action": {
"default_title": "Test Extension"
},
"permissions": ["activeTab"]
}
Update for manifest v3
chrome.tabs.executeScript doesn't work in manifest v3, as noted in the comments of this answer. Instead, use chrome.scripting. You can specify a separate script to run instead of a function, or specify a function (without having to stringify it!).
Remember that your manifest.json will need to include
...
"manifest_version": 3,
"permissions": ["scripting"],
...
You don't have to use the message passing to obtain or modify DOM. I used chrome.tabs.executeScriptinstead. In my example I am using only activeTab permission, therefore the script is executed only on the active tab.
part of manifest.json
"browser_action": {
"default_title": "Test",
"default_popup": "index.html"
},
"permissions": [
"activeTab",
"<all_urls>"
]
index.html
<!DOCTYPE html>
<html>
<head></head>
<body>
<button id="test">TEST!</button>
<script src="test.js"></script>
</body>
</html>
test.js
document.getElementById("test").addEventListener('click', () => {
console.log("Popup DOM fully loaded and parsed");
function modifyDOM() {
//You can play with your DOM here or check URL against your regex
console.log('Tab script:');
console.log(document.body);
return document.body.innerHTML;
}
//We have permission to access the activeTab, so we can call chrome.tabs.executeScript:
chrome.tabs.executeScript({
code: '(' + modifyDOM + ')();' //argument here is a string but function.toString() returns function's code
}, (results) => {
//Here we have just the innerHTML and not DOM structure
console.log('Popup script:')
console.log(results[0]);
});
});
For those who tried gkalpak answer and it did not work,
be aware that chrome will add the content script to a needed page only when your extension enabled during chrome launch and also a good idea restart browser after making these changes
What I want to do.
Using a Chrome extension I need to inject a script into the page context in such a way that the injected script runs before any other javascript on the page.
Why do I need this?
I need to hijack all console commands for a specific page so my extension can listen to the messages.
My current issue
Currently I am catching some of the messages logged by the page but not all of them, specifically, all messages from web-directory-132a3f16cf1ea31e167fdf5294387073.js are not being caught. After some digging I discovered that the web-directory-132a3f16cf1ea31e167fdf5294387073.js is also hijacking console but doing so before my script has a chance to.
As a visual, if I look at the network tab after loading the page, I see this:
My injected script is consoleInterceptor.js. It correctly captures the output of the js files that are loaded here accept web-directory-132a3f16cf1ea31e167fdf5294387073.js
Inside of web-directory-132a3f16cf1ea31e167fdf5294387073.js is some code something like this:
....
_originalLogger: t.default.Logger,
...
// do stuff with logging ....
this._originalLogger[e].apply(this._originalLogger, s),
What I think the problem is
It seems to me that, web-directory-132a3f16cf1ea31e167fdf5294387073.js is grabbing the standard console functions and storing them internally before my script has had a chance to replace them with my own versions. So even though my script works, the web-directory-132a3f16cf1ea31e167fdf5294387073.js still uses the original standard console functions it saved.
Note that web-directory-132a3f16cf1ea31e167fdf5294387073.js is an ember application and I dont see any simple way to hook into that code to overwrite those functions too but Im open to that as a solution.
My current code:
manifest.js
...
"web_accessible_resources": [
"js/ajaxInterceptor.js",
"js/consoleInterceptor.js"
],
"version" : "5.2",
"manifest_version": 2,
"permissions": [
"<all_urls>",
"tabs",
"activeTab",
"storage",
"webNavigation",
"unlimitedStorage",
"notifications",
"clipboardWrite",
"downloads",
"tabCapture",
"cookies",
"browsingData",
"webRequest",
"*://*/*",
"gcm",
"contextMenus",
"management"
],
"externally_connectable": {
"matches": ["*://apps.mypurecloud.com/*","*://*.cloudfront.net/*"]
},
...
background.js
var options = {url: [{hostContains: 'apps.mypurecloud.com'}]};
chrome.webNavigation.onCommitted.addListener(function(details) {
// first inject the chrome extension's id
chrome.tabs.executeScript(details.tabId, {
code: "var chromeExtensionId = " + JSON.stringify(chrome.runtime.id)
});
// then inject the script which will use the dynamically added extension id
// to talk to the extension
chrome.tabs.executeScript(details.tabId, {
file: 'js/injectConsoleInterceptor.js'
});
},
options
);
chrome.runtime.onMessageExternal.addListener(
function(msg, sender, sendResponse) {
if(msg.action === 'console_intercepted'){
_this.processConsoleMessage(sender, msg.details.method, msg.details.arguments);
}
});
injectConsoleInterceptor.js
var interceptorScript = document.createElement('script');
interceptorScript.src = chrome.extension.getURL('js/consoleInterceptor.js');
interceptorScript.onload = function(){this.remove();};
(document.head || document.documentElement).prepend(interceptorScript);
consoleInterceptor.js
if(!window.hasConsoleInterceptor){
window.hasConsoleInterceptor = true;
console.log('overriding console functions');
var originals ={};
var console = window.console;
if (console){
function interceptConsoleMethod(method){
originals[method] = console[method];
console[method] = function(){
// send the data to the extension
// chromeExtensionId should be injected into the page separately and before this script
var data = {
action: 'console_intercepted',
details: {
method: method,
arguments: arguments
}
};
chrome.runtime.sendMessage(chromeExtensionId, data);
originals[method].apply(console, arguments)
}
}
// an array of the methods we want to observe
var methods = ['assert', 'count', 'debug', 'dir', 'dirxml', 'error', 'group','groupCollapsed','groupEnd','info','log', 'profile', 'profileEnd','time','timeEnd','timeStamp','trace','warn','table'];
for (var i = 0; i < methods.length; i++){
interceptConsoleMethod(methods[i])
}
console.log('Successfully overridden console functions: '+methods.join(','));
}
}
My question
What can I do to make consoleInterceptor.js run before web-directory-132a3f16cf1ea31e167fdf5294387073.js loads so that web-directory-132a3f16cf1ea31e167fdf5294387073.js uses my modified console functions rather than the default browser console funcitons?
You can try this.In manifest.json file:
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*"],
"js": ["script/inject.js"],
"run_at":"document_start"
}
]
In inject.js:
var ss = document.createElement("script");
ss.innerHTML= "xxx";
document.documentElement.appendChild(ss);
I am working on simple Chrome Extension with the aim of opening every link on a page with the class of entry. Currently, I have this....
manifest.json:
{
"manifest_version": 2,
"name": "Hello World",
"description": "A simple Chrome Extension",
"version": "1.0",
"background": {
"scripts": ["openlinks.js"],
"persistent": true
},
"permissions": [
"tabs",
"http://*/",
"https://*/"
],
"browser_action": {
"default_icon": "logo.png"
}
}
openlinks.js:
chrome.browserAction.onClicked.addListener(function(tab) {
var linkArray = ['https://www.google.com', 'http://www.bbc.co.uk']; // your links
for (var i = 0; i < linkArray.length; i++) {
chrome.tabs.create({
url: linkArray[i]
});
}
});
Now I am trying to replace the array of sample links with an array of links from the current tab. Is it just a case of using standard JavaScript or jQuery to achieve this?
Take a look at Chrome Extensions Overview # Architecture, because you'll need both an Event Page and a Content Script to make this happen.
Here's an outline of how I would go about solving this:
Manifest structure (Event Page + activeTab permission).
"background": { "scripts": ["bg.js"], "persistent": false },
"permissions": ["activeTab"],
"browser_action": {},
When the browser action is clicked, the browser grants permission to access the current tab, which we use to inject the script. See Content Scripts # Programmatic Injection.
// bg.js
chrome.browserAction.onClicked.addListener(tab =>
chrome.tabs.executeScript({file: 'content.js'});
});
The content script has permission to access the DOM and use message passing, but is restricted from most of the extension APIs (in particular chrome.tabs).
// content.js
message = {}
message.links = [...document.querySelectorAll(
'div.question-summary a.question-hyperlink')].map(e=>e.href);
chrome.runtime.sendMessage(message);
The background page listens for the message.
// bg.js
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
request.links.forEach(link => chrome.tabs.create({url: link});
});
I need to turn alert notification off by my extension. The alert function is a javascript built-in function and i override it like below in content script but it still works.
content script:
window.alert = function(){}
It does not turn it off. The problem is realy simple but it does not work and i am going crazy :)
manifest.json:
"content_scripts": [
{
"js": [ "assets/js/jquery-1.12.4.min.js", "assets/js/common.js", "assets/js/handlers.js" ],
"matches": [ "http://*/*", "https://*/*" ],
"run_at": "document_start"
}
],
handler.js:
window.alert = function (msg) {
debugger;
console.log(msg);
}
You can't switch off notifications globally in Chrome (at least, not that way).
By doing
window.alert = function(){}
you simply switch off notifications for your own extension (= your own window context). You can't do this globally for security reasons (think what malicious extension could do if this was possible).
Edit: you actually can inject your code into the content pages; but this is still not a "global" change that you seek. If you still want this, then:
manifest.js of your extension:
{
"name": "MyExtension",
...
"permissions": ["tabs", "http://*/*"],
"content_scripts" : [{
"js" : ["script.js"]
}]
}
And your script.js:
window.alert = function(){}
As Dmitriy mention it must be injected to the page. It works great please vote for Dimitry i just added the answer for sharing my code.
contend script:
function override_alert() {
window.alert = function alert(msg) {
console.log('Hidden Alert ' + msg);
};
}
if (!document.xmlVersion) {
var script = document.createElement('script');
script.appendChild(document.createTextNode('(' + override_alert + ')();'));
document.documentElement.appendChild(script);
}
Could you try something like this?
$(document).ready( function() {
window.alert = function(){};
});