chrome.tabs.create/executeScript > call function that belongs to the page - javascript

I'm developing an extension for Google Chrome and the problem I'm having is I need to be able to call a JavaScript function that belongs to the webpage that's opened in the tab.
For details, the website is my website, therefore I know that function does exist. That function does a lot of things based on a string value. I want the user to be able to highlight text on any webpage, click a button from the Chrome extension that automatically loads my webpage and calls that function with the highlighted text as it's value.
Here's what I got so far:
chrome.tabs.create({ url: "https://mywebsite.com" }, function (tab) {
var c = "initPlayer('" + request.text + "');"; ////'request.text' is the highlighted text which works
chrome.tabs.executeScript(tab.id, { code: c });
});
But Chrome console says: "Uncaught ReferenceError: initPlayer is not defined."
I know that function does exist as it is in my code on my own website.
Any help is highly appreciated. Thanks!

This happens because pages and content scripts run inside two separate javascript contexts. This means that content scripts cannot acces functions and variables inside a page directly: you'll need to inject a script into the page itself to make it work.
Here is a simple solution:
chrome.tabs.create({url: "https://mywebsite.com"}, function (tab) {
var c = "var s = document.createElement('script');\
s.textContent = \"initPlayer('" + request.text + "');\";\
document.head.appendChild(s);"
chrome.tabs.executeScript(tab.id, {code: c});
});
NOTE: since January 2021, use Manifest V3 with chrome.scripting.executeScript() instead of chrome.tabs.executeScript().
With the above code you will basically:
Create the tab
Inject the code (variable c) into it as a content script that will:
Create a script with the code you want to execute on the page
Inject the script in the page and, therefore, run its code in the page context

Related

How to ensure webpage DOM is fully loaded before using jQuery

I am trying to load a DOM of the currently open webpage in a Google Chrome extension app that I am developing and then retrieve some elements and store them in variables for manipulation. Currently, I am just trying to grab the sender email under the gD class for the specified span. As of now it will output whatever I have in the doStuffWithDOM function twice to the console. I have tried using several variations of ensuring that the page is fully loaded before calling the function like tab.status==complete or/and changeInfo.status==complete but it still calls the function multiple times. Any suggestions on how to ensure the webpages DOM is fully loaded before calling the doStuffWithDOM function?
background.js
function doStuffWithDOM(domContent) {
senderName = $(domContent).find("span.gD");
console.log(senderName);
}
chrome.tabs.onUpdated.addListener(function(id,changeInfo,tab){
if(changeInfo.status=='complete' && tab.status=='complete'){ //To send message after the webpage has loaded
chrome.tabs.sendMessage(tab.id, { text: "report_back" },function(response){
doStuffWithDOM(response);
});
}
})
console output:
test
jQuery.fn.init [prevObject: jQuery.fn.init(62)]
test
jQuery.fn.init [span.gD, prevObject: jQuery.fn.init(70)]

How do I navigate to bing.com and enter a search text using the chrome console?

Below is my code.
It is resulting in unexpected behaviour.
It navigates to bing.com but it does not fill in the text field. Also, I have noticed that the console get cleared after navigating to a new webpage.
window.location = "https://www.bing.com";
window.onload = function(){
var editSearch = document.getElementById("sb_form_q");
editSearch.value = "Quux";
}
You are binding the onload function to the existing window object.
When the browser loads the new page, it will make a new window object which won't have your property set on it.
JavaScript run in one page (even when you are running it through developer tools) can't persist variables onto a different page.
(Storage mechanisms like localStorage and cookies are available, but you would need code in the subsequent page to look for them).
JavaScript is only valid for the current page you are on. When you are executing code from DevTools console, you are executing code on that page itself. So, when you navigate to another page using window.location you loose the onload handler you have defined.
To add handlers to a different page, it must be connected to your page (the parent) in some way, like an iframe or a popup.
ifrm = document.getElementById('frame');
ifrm.src = 'http://example.com';
ifrm.contentWindow.onload = function () {
// do something here with
// ifrm.contentWindow.document.getElementById('form')
}
As #Quentin said.
But you can do another way like ..
var keyword = "Quux";
window.location = "https://www.bing.com/search?q="+keyword;

(Javascript , Chrome) query tab id and then access its elements

I would like to get elements from chrome tab with a certain URL, it does not have to be active. so far I have:
Test()
function Test() {
chrome.tabs.query({url: "https://www.somewebsite.com/*"}, function(results) {
chrome.tabs.executeScript(results[0].id,{code: 'El = document.getElementsByClassName("someclass")'});
console.log(El);
})
}
Maybe it has to be done through a content script file?
I have this code placed in my background.js file. Given the proper URL and Class this function will not return the Element. Why?
Thanks for any suggestions!
The background script is executed only once when chrome launches. Instead of writing this inside a function which you call in the same file, you have to put it in some kind of event listener. This code should stay in the background file, but has to be triggered by something, not just executed on extension load.
EDIT: Oh, I see, you're trying to pass the element to the background script. You can't.
Background scripts have access to the whole chrome APIs but not to the page content. The script you write in executeScript runs on the tab you specified, so the variables are available to other code within that tab, not within your background script. Extension code that can run on the page and edit its content is either sent using executeScript or put in content scripts.
If you want to share information between various layers of an extension, you need to use messages. They work like events and listeners. Read the docs here. You'll be able to pass data like numbers and strings, but not the actual HTMLElement. So any code that manipulates the DOM has to run on the tab itself.
If your code is small and simple, you could just write it in the executeScript call instead.
You are correct, there does not appear to be a way to manipulate elements in another tab from the background.js;
Test();
function Test() {
chrome.tabs.query({
url: "https://www.google.com/*"
}, function(results) {
if (results.length < 1) { //if there is no tab open with google.com
console.log("Tab not found, creating new tab");
chrome.tabs.create({
"url": "https://www.google.com/",
"selected": true
}, null);
chrome.tabs.query({
url: "https://www.google.com/*"
}, function(results) {
if (results.length >= 1) {
console.log("Found google.com in the new tab");
}
chrome.tabs.executeScript(results[0].id, {
code: "foo = document.getElementsByTagName('input'); console.log(foo,' This is sent from the active tab');
chrome.storage.local.set({
'foo': foo[0]
});
"},function(foo) {chrome.storage.local.get("
foo ", function(foo) {console.log(foo , 'This has retrieved foo right after saving it to storage.');});});
});
}
});
}
search()
function search(result) {
chrome.storage.local.get("foo", function(result) {
console.log(result, 'This has retrieved foo from storage in a separate function within background.js');});
}
the first console log will show the html collection object for the inputs from google.com inside the google.com tab console, the other two will show blank objects in the background.js console. Thanks, I will find another solution.

Chrome extensions: best method for communicating between background page and a web site page script

What I want to do is to run go() function in image.js file. I've googled around and I understand that is not possible to run inline scripts.
What is the best method to call the JavaScript I want? Events? Messages? Requests? Any other way?
Here is my code so far:
background.js
chrome.browserAction.onClicked.addListener(function(tab) {
var viewTabUrl = chrome.extension.getURL('image.html');
var newURL = "image.html";
chrome.tabs.create({
url : newURL
});
var tabs = chrome.tabs.query({}, function(tabs) {
for (var i = 0; i < tabs.length; i++) {
var tab = tabs[i];
if (tab.url == viewTabUrl) {
//here i want to call go() function from image.js
}
}
});
});
image.html
<html>
<body>
<script src="js/image.js"></script>
</body>
</html>
image.js
function go(){
alert('working!');
}
There are various ways to achieve this. Based on what exactly you are trying to achieve (which is not clear by your question), one way might be better than the other.
An easy way, would be to inject a content script and communicate with it through Message Passing, but it is not possible to inject content scripts into a page with the chrome-extension:// scheme (despite what the docs say - there is an open issue for correcting the docs).
So, here is one possibility: Use window.postMessage
E.g.:
In background.js:
var viewTabURL = chrome.extension.getURL("image.html");
var win = window.open(viewTabURL); // <-- you need to open the tab like this
// in order to be able to use `postMessage()`
function requestToInvokeGo() {
win.postMessage("Go", viewTabURL);
}
image.js:
window.addEventListener("message", function(evt) {
if (location.href.indexOf(evt.origin) !== -1) {
/* OK, I know this guy */
if (evt.data === "Go") {
/* Master says: "Go" */
alert("Went !");
}
}
});
In general, the easiest method to communicate between the background page and extension views is via direct access to the respective window objects. That way you can invoke functions or access defined properties in the other page.
Obtaining the window object of the background page from another extension page is straightforward: use chrome.extension.getBackgroundPage(), or chrome.runtime.getBackgroundPage(callback) if it's an event page.
To obtain the window object of an extension page from the background page you have at least three options:
Loop through the results of chrome.extension.getViews({type:'tab'}) to find the page you want.
Open the page in the first place using window.open, which directly returns the window object.
Make code in the extension page call a function in the background page to register itself, passing its window object as a parameter. See for instance this answer.
Once you have a reference to the window object of your page, you can call its functions directly: win.go()
As a side note, in your case you are opening an extension view, and then immediately want to invoke a function in it without passing any information from the background page. The easiest way to achieve that would be to simply make the view run the function when it loads. You just need to add the following line to the end of your image.js script:
go();
Note also that the code in your example will probably fail to find your tab, because chrome.tabs.create is asynchronous and will return before your tab is created.

Chrome DevTools extension: how to get selected element from elements panel in content script?

I've done my research and struggled with this for a while, but I need your help.
I'm building a Chrome DevTools extension. It should should pass the currently selected element from the 'Elements' panel as a reference to a JS object defined in a content script.
It is important that I pass the reference to the selected element, or some other way of identifying the element from the content script.
I understand the workflow with 'isolated worlds' in Chrome DevTools. I also understand messaging between extension pages, background page and content scripts. This only happens with JSON primitives, hence no JS scope passing.
How can I pass the element selected in devtools Elements panel to the content script that lives in the inspected page?
Edit
Here's what I know so far:
Getting a reference to the selected element:
chrome.devtools.inspectedWindow.eval("(" + function(){ console.log($0) }.toString() + ")()")
That function expression will run in the context of the inspected page, not in the context of the devtools extension and not in the context of the 'isolated world' of the content script. I don't believe it is possible to pass in a reference to a different context using closures.
The reference to the selected DOM element $0 can't be returned because it can't be serialized to JSON due to circular references.
The chrome.devtools namespace isn't available outside the devtools extension page. The $0 reference can't be used outside the evaluated expression in chrome.devtools.inspectedWindow
Workaround
As a workaround, I chose to use the shared DOM to mark the selected element with a data attribute and use that to re-select it in the context of the content script. Messaging is used to pass the data attribute marker around.
Here's a simplified version of the code:
In the devtools extension page:
// setup a communication port
port = chrome.runtime.connect({name: "devtools"});
chrome.devtools.panels.elements.onSelectionChanged.addListener(function(){
// expression to run in the context of the inspected page
var expression = "(" + mark.toString() + ")()"
// evaluate the expression and handle the result
chrome.devtools.inspectedWindow.eval(expression, dispatchToContentScript)
});
function mark(){
// mark the currently selected element
$0.setAttribute('data-selected')
// send the marker to the callback
return { marker: 'data-selected' }
}
function dispatchToContentScript(data){
// dispatch data to the content script which is listening to the same port.
port.postMessage(data)
}
In the content script:
var port = chrome.runtime.connect({name: "devtools"});
port.onMessage.addListener(function(data) {
// re-select the element in the context of the content script
var el = document.querySelector('['+ data.marker +']')
})
It's not a clean solution but I can use it for my needs.
Is there a simpler way to achieve the same result - identify from a content script the element selected in the devtools 'Elements' panel?
The API for chrome.devtools.inspectedWindow has been updated to support executing scripts in the context of the content script.
This update in the official Chrome API obsoletes our hacks described above. We can now achieve the expected result with:
chrome.devtools.inspectedWindow.eval("aContentScriptFunction($0)",
{ useContentScriptContext: true });
The $0 parameter will reference the element selected in the Elements panel.
my way of doing it is sort of a hack too..
instead of injecting a content script defined in your extention you can inject a script tag pointing to your files online (or locally, relative to inspected html ):
//devtools.js
var str = "var s = document.createElement('script');" +
"s.src = 'http://extentionDomain/extentionFile.js';" +
"document.body.appendChild(s);";
chrome.devtools.inspectedWindow.eval(str);
online file defines a global:
var myExtention = { doStuff: function(selectedElement){ ..}}
and devtools can call it and pass it the selected element:
chrome.devtools.panels.elements.onSelectionChanged.addListener(function(){
chrome.devtools.inspectedWindow.eval('myExtention.doStuff($0)');});
however i have not found a way to send a reference back from the inspected window to the devtools extention with this setup.

Categories

Resources