How to access page variables from Chrome extension background script - javascript

With a content script you can inject a script tag into the DOM in order to access variables in the original page (as explained in this question).
I want to avoid injecting my code into every page and instead only do that when the user clicks on the extension icon.
When I tried using the same code as for the content script the values were undefined, although the script was inserted correctly.
Is this possible? Otherwise is using a content script and communicating with it the preferred solution?
Here is the code I'm using:
var scr = document.createElement("script");
scr.type="text/javascript";
scr.innerHTML = "setInterval('console.log(window.testVar)', 1000)"
document.body.appendChild(scr)
Manifest excerpt:
"permissions": [
"tabs",
"http://*/*", "https://*/*"
],
"background": {
"scripts": ["inject.js"]
},

Nope. That's not possible. You may inject a script, but it only have an access to DOM and it could make DOM manipulations. It can not read javascript variables or execute functions in the context of the current page. Your javascript code is run in a sandbox and you are restricted only to the DOM elements.

You can create a new background script with the following code:
chrome.browserAction.onClicked.addListener( function() {
chrome.tabs.executeScript( { file: 'inject.js' } );
});
Your inject.js should stay as part of the extension, but you don't need to mention it in the manifest. This way, it will be run as a content script each time you press the extension icon. You didn't include the part of the manifest where you define the browserAction, but you just need to not specify a default_popup.

You can utilize a background script to execute JavaScript within the context of the page, however, your JavaScript is executed in an isolated environment from the JavaScript that is loaded from within the page.
Consider the following:
In page.html:
<button id="myButton" onclick="console.log('Message from within the page');" />
In background script:
chrome.tabs.executeScript({
code: '$("#myButton").click(function() { console.log("Message from background script."); });'
});
Now, if you click the button within the page, you will find both log messages in the console window. The JavaScript contexts are isolated from each other, with the notable exceptions of messaging via postMessage and through the shared DOM.

Related

Calling a JS library in my Content Script - WebExtension

I have a webextension, firefox but I think this applies to chrome as well, which executes (chrome.tabs.executeScript, called in the background.js) a content script on a browserAction.onClicked listener. This content script more or less creates an dom element on the current tab using some data from the page. This element has some html code inside and everything works swimmingly -- except I'm trying to get it's syntax highlighted. Now, there are a few different libraries which do this (Prism, HighlightjS, Rainbow, and some others).
The general idea with these libraries, if I'm not mistaken, is to call colorify, or some such function, on a <code> div and then using a css sheet highlight that syntax. I have tried a number of things to get both/either the relevant js or css scripts to be sourced onto a page:
such as, executeScript along with the content script in my background.js, in this case I'd include this script inside my background declaration in the manifest.json:
chrome.tabs.executeScript(null, {
file: "/content_scripts/highlight.pack.js"
});
chrome.tabs.executeScript(null, {
file: "/content_scripts/my_content_script.js"
});
I've also tried loading it in via the manifest (which is also what I've tried with the css):
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content_scripts/highlight.pack.js"],
"css" : ["content_scripts/solarized-light.css"]
}
],
In either case I've then tried a few things to load this/these libraries into the content script which is doing the aforementioned main lifting (creating a div on the page):
I've tried simply letting the library, such as Prism do it's thing (by putting my <code> inside a <pre> and giving it the appropriate className, or even calling it manually from the content script:
var Prism = { highlightElement: function() {} };
window.addEventListener('load', () => {var Prism = window.Prism; });
Or more simply
Rainbow.color(div, function() {
document.body.appendChild(div);
}
// or
window.Rainbow.color( //... etc
or simpliest of all:
window.hljs.initHighlightingOnLoad();
// or
hljs.initHighlightingOnLoad();
Part of my problem, and why I am discouraged, is that when I open the inspector/debugger sometimes I'll see the js file sourced, but I won't be able to call any functions from the console. And, moreover, the css files never seem to get sourced.
So the basics of what I'm looking to do are to call a 3rd party javascript library inside my content script and source a css sheet from my webextension into the activeTab my content script is modifying.
How should I include the libraries?
How can I debug the content script, to test how to use the libraries?
So #Xan helped be get around to debugging this using a breakpoint inside the content script inside the Firefox console.
I ran the background script, which executes the content script
My manifest includes the css necessary for syntax highlighting. I could also include the js file here, but instead I add it to the background.js as something I execute (chrome.tabs.executeScript()) So that way it's only run if I need it. (Otherwise it'll run all the time.
In the inspector Debugging tab I put a breakpoint inside my main content script
Inside the breakpoint I was able to use the console to call the syntax highlighting library as if it's a global variable.
From there I was able to test how to call the script etc. I eventually called highlightjs's hljs object without a window specifier. Just calling, inside my content script:
hljs.highlightBlock(myDiv);

Is there an equivalent to `run_at: start` in a background page?

I need to run some JavaScript code before any other code is executed on the page that's being loaded.
That works fine using a content script like this:
{
"matches": ["http://*/*", "https://*/*", "file://*/*"],
"js": ["contentScript.js"],
"run_at": "document_start"
}
But now I'd like to run the content script only on certain pages which the user selects by clicking on the browser action button.
I've tried listening to chrome.tabs.onUpdated and then calling executeScript, but other code is running on the page before my content script.
Is there a way to ensure that the code injected by a background page is run before other code?
I could also add a condition inside my content script, but that would mean I need access to the current tabId and a list of all tabs where my extension is activated.
According to the documentation chrome.tabs.executeScript has several useful parameters, including runAt (by default it's document_idle, which occurs after DOMContentLoaded event), so to inject a script as soon as possible use runAt: 'document_start'.
chrome.tabs.executeScript(tabId, {
runAt: 'document_start',
code: 'console.log(document.documentElement.innerHTML);',
});
Beware the DOM tree is usually empty at this stage especially if you inject from an early executed event listener such as tabs.onUpdated or webNavigation.onCommitted. Even HEAD or BODY elements may be still absent, so you'd have to use (document.head || document.documentElement) fallback as a container for any added nodes such as <style> or <script>.

When and why do I need background.html or background.js?

I'm building my first Chrome extension. I've gone through Chrome developer documentation, but I couldn't comprehend few topics.
What I've understood:
There are two actions:
Browser action (button outside address bar)
page action (button inside address bar)
For both actions we have background pages:
Background pages can be background.html or background.js for browser actions
Or background.html or eventPage.js for page actions
I am using a page action. For default popup page I used popup.html.
Now where does this background.html and eventPage.js fit in (especially background.html, as I already have my popup.html, do I need that?)
What should eventPage.js have? What should background.html have?
In your manifest.json, if you're using an "event page" (recommended to reduce your extension's memory use) you'll have something like
{
...
"background": {
"scripts": ["eventPage.js"],
"page": "eventPage.html"
"persistent": false
},
...
}
Now, the above manifest won't actually work: you can only have one of the "scripts" or "page" key in the "background" dictionary. The simplest thing to do is define "scripts". Then you can write a bunch of javascript, and have it run in response to various events like chrome.runtime.onMessage and chrome.pageAction.onClicked.
The javascript you list in the "scripts" key runs in the context of an HTML document and can use DOM APIs like document.createElement() while it's running. By default, Chrome will generate a document of the form:
<html>
<head></head>
<body>
<script src="event.js"></script>
</body>
</html>
to hold the scripts you list. You'll see this in chrome://extensions/ if you click on the "background page" link next to "Inspect views". However, sometimes you might want that document to have more elements pre-defined. I've seen examples like:
<html>
<head></head>
<body>
<script src="event.js"></script>
<canvas width="300" height="400" id="the_canvas"></canvas>
</body>
</html>
so event.js can use document.getElementById('the_canvas') to retrieve a canvas it can draw on. Without the custom document, your javascript could still run:
var canvas = document.createElement('canvas');
canvas.width = 300;
canvas.height = 400;
and get the same result, but the document can be easier. That HTML file is what you'd put in the background.page key in your manifest.
As #SirDemon mentioned, you can sometimes get away with no background/event page at all, if you put everything into a browser action's popup. Because a page action is supposed to only appear for pages it's relevant to, you'll usually need a background/event page to control the page action.
You do not necessarily need a background page or background script. If you intend to create an extension that does not require any human interaction, then you would need them.
Page and browser actions usually mean you intend to have some sort of UI, so no need for background stuff. Just add your scripts etc to your main 'default_popup' html page as you would normally.

Searching and highlighting text on current page for a Chrome Extension

How should I connect my pages to search and highlight text on current tab?
Currently I have:
manifest.json does defining content/backgr/event page do significant things,auto inject code etc?
popup.html essentially a shell for the search input which is used by search.js
search.js should this be in background/event/content/popup.html page?
What I still don't understand after reading:
What is a content page vs. background/event page?
I know one is constantly running vs injected, but that's as much as I got from the chrome extension manual, I still don't quite understand if the content script/page is seperate from the popup.html for example and what the difference between a script in the popup.html vs content page/script is.
What I know:
I know how to search for text on a page, and replace it or change its style etc. using JS.
I need to read up on the messaging API for Chrome Extensions.
I know I need to know how to use the messaging API, is it going to be required for page search and highlighting?
Summary:
I don't need a walk through or full answer, just a little help visualizing how Chrome extensions work, or at minimum how I should set mine up in relation to page interaction IE:
search.js content page injected >>>>> popup.html
and maybe a short bit about how injection works in chrome extensions(IE, do I only need to specify that it is content page in manifest.json to have it injected or is there more work to it)/expected behavior?
Apologies for the jumbled thoughts/question/possibly missing the things relevant to my questions while reading the manual.
I will start with making the purpose of each kind of page/script more clear.
First is the background page/script. The background script is where your extension lives. It isn't required, but in order to do most extension things, you need one. In it you can set up various event listeners and such depending on what you want it to do. It lives in its own little world and can only interact with other pages and scripts using the chrome.* apis. If you set it up as an event page it works exactly the same except that it unloads when not in use and loads back into memory when it has something to do.
Content scripts refer to injected Javascript and/or css. They are the primary tool used for interacting with web pages. They have very limited access to chrome.* apis, but they have full access to the DOM of the page they are injected into. We will come back to using them in a minute.
Now for Popup pages. Unlike the background script and content script, popups have both a HTML and JS portion. The HTML part is just like any other page, just small and as a overlay popup coming out from the icon. The script portion of it, however, can do all the things the background page does, except that it unloads whenever the popup is closed.
Now that the distinctions are more clear let's move on to what you want to do. It sounds like you want to open the popup, have the user enter text to search for in the current tab, then highlight that text on the page. Since you said that you already know how you plan on highlighting the text, I will leave that part to you.
First to set up our manifest file. For this particular action, we don't need a background script. What we do need is both the "tabs" and "activeTab" permissions. These will enable us to inject our script later. We also need to define the browser action with it's popup. Altogether it would look something like this:
"browser_action": {
"default_icon": "icon.png",
"default_popup": "popup.html"
},
"permissions": [
"tabs", "activeTab"
]
Now in our popup.html file, we can only have markup and css, no inline code at all. We will put it all in our js file and include it. Something like this should work:
<!DOCTYPE html>
<html>
<head>
<script src="popup.js"></script>
</head>
<body>
<input type="text" id="searchText">
<button id="searchButton">Search</button>
</body>
</html>
This is where we come back to the content script stuff. There are two ways to inject a content script, first is to define it in the manifest. This works best when you always want to inject it for a particular set of url's. Second, to use the chrome.tabs.executeScript method to inject it when we need to. That is what we will use.
window.onload = function(){
document.getElementById('searchButton').onclick = searchText;
};
function searchText(){
var search = document.getElementById('searchText').value;
if(search){
chrome.tabs.query({active:true,currentWindow:true},function(tabs){
chrome.tabs.executeScript(tabs[0].id,{file:search.js});
chrome.tabs.sendMessage(tabs[0].id,{method:'search',searchText:search});
});
}
}
With this, we have successfully injected our script and then send the search text to that script. Just make sure that the script is wrapped in a onMessage listener like this:
chrome.runtime.onMessage.addListener(function(message,sender,sendResponse){
// message.searchText is the text that was captured in the popup
// Search/Highlight code goes here
});
And that pretty much sums it up. With that, you should be able to get it working. If something is still not clear let me know and I will fix it.
I think what's confusing you is the non-existant concept of a "content page". There is no such thing. What you're likely referring to is a "content script". Let me explain the three main components of an extension:
Background Page
As you said, this is the persistent aspect of a Chrome Extension. Even though it can be HTML page it is never rendered. You simply use it to run JavaScript and other content that stays persistent. The only way to "refresh" the background page is to refresh the extension in the extension manager, or to re-install the extension.
This is most useful for saving information that should remain persistent, such as authentication credentials, or counters that should build up over time. Only use the background page when absolutely necessary, though, because it consumes resources as long as the user is running your extension.
You can add a background script like to manafest file like this:
"background": {
"scripts": [
"background.js"
]
},
Or like this:
"background": {
"page": "background.html"
},
Then simply add background.js to background.html via a typical tag.
Popup
This is what you see when you click the icon on the toolbar. It's simply a popup window with some HTML. It can contain HTML, JavaScript, CSS, and whatever you would put in a normal web page.
Not all extension need a popup window, but many do. For example, your highlight extension may not need a popup if all it's doing is highlighting text on a page. However, if you need to collect a search result (which seems likely) or provide the user with some settings or other UI then a popup is a good way to go about this.
You can add a popup to the manifest file like this:
"browser_action": {
"default_popup": "popup.html"
},
Content script
As I mentioned, this is not a "page" per se -- it a script, or set of scripts. A content script is what you use to infuse JavaScript into pages the user is browser. For example, a user goes to Facebook and a content script could change the background to red. This is almost certainly what you'll need to use to highlight text on a page. Simply infuse some JavaScript and any necessarily libraries to search the page or crawl the dom, and render changes to that page.
You can inject content scripts every time a user opens any URL like this:
"content_scripts": [
{
"matches" : [
"<all_urls>"
],
"js" : [
"content.js"
]
}
],
The above injects "content.js" into "all urls".
You'll also need to add this to the permissions:
"permissions": [
"<all_urls>",
]
You can even add JQuery to the list of content scripts. The nice thing about extensions is that the content scripts are sandboxed, so the version of JQuery you inject will not collide with JQuery on pages the user visits.

Javascript in browser bar vs in content script - 'die' doesn't work

I'm trying to run a javascript command in a content script using Personalized-Web (a Chrome extension). I'm new to javascript & jquery, but I've found that entering this code:
javascript:jQuery("div.photo-container").die();
into my browser bar on a particular page achieves the desired result: it undoes a .live call performed in one of the page's javascripts.
However, if I include that same code or $("div.photo-container").die(); in a content script, it does not work. I've also attempted including this script tag in the page context:
<script type="text/javascript">
$("div.photo-container").die();
</script>
and chrome claims that $ or jQuery are not defined. However, the page's own javascripts don't include or refer to the jQuery source at any point, as far as I can tell.
So, what's the difference between the browser bar, the content script, and the in-page <script> tag? How can I use one of the 'automatic' methods (i.e., not paste it into the browser bar)?
If you inject that script tag into the page, it should work. For example:
// Runs in the context of the webpage.
var injectScript = function() {
$("div.photo-container").die();
}
// Runs in the context of the content script. We basically just append the DOM
// with the injected script and execute it so it will run.
var script = document.createElement('script');
script.appendChild(document.createTextNode('(' + injectScript + ')();'));
document.body.appendChild(script);
The above should work assuming your page has defined the $. If the $ doesn't exist, that means jQuery didn't load yet. Make sure your manifest has document_end defined as well.
...
"content_scripts": [
{
"matches": ["http://www.rim.com/*"],
"js": ["content_script.js"],
"run_at": "document_end"
}
],
...
If that doesn't work then Sometimes depending on the website, it is good to lazy load or smart load till the content (script) gets loaded because the webdesigner is asynchronously loading the scripts or content. In that case, do some research to see how jquery is being loaded. For example, do a timer with setTimeout or DOM events to figure out when it is time to run your injection. I do that many times on difficult websites such as Google+ Extensions.

Categories

Resources