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.
Related
In my chrome extension I want to have a background script for obvious reasons. However I now also want to have a background page in which I like right here discribed load some html (Chrome extension: loading a hidden page (without iframe)) using an iFrame which I can interact with using a content script. But when I'm trying to load both the background script and the background page like so:
...
"background":{
"scripts": ["background_script.js"]
},
"background": {
"page": "iFrameBackground.html",
"persistent": true
},
...
and then try to send a message from my content script to the background script I get this error:
Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist.
So either I'm missing something entirely here or are you really just able to use one of the two?
Thank you :)
Problems
Only one declaration method can be used.
Your json is invalid as it has two identical keys so Chrome won't even install it.
Solution
Choose just one method. Any method.
You can access DOM in your background script/page regardless of the way it's declared.
The result of each method is identical: they both create a background page. An extension can have just one background page where any amount of background scripts are loaded - just like in any other web page. The scripts method exists just for convenience: it simply generates a dummy HTML page as you can see in devtools inspector for the background page.
I'm making a Chrome App, and i'm using the web view tag, which is similar to an iframe.
Is there a way to load halfway down the webpage that's inside the web view?
I have tried:
<webview onLoad="window.scroll(0, 150)" class="test" src="http://example.co.uk/test.aspx"></webview>
But it seems that would be far to easy. I'm not convinced its even possible.
Thanks
Assuming for a moment that code would execute (it won't because of the CSP), window would refer to the app's page, not the embedded page.
You need to execute the code in the context of the embedded page.
If you worked with Chrome extensions before, you would know that the part interacting with web content is called a Content Script.
Apps have a similar concept for <webview> elements. You can either declare in advance which pages should get which content script while they load, or you can explicitly load a content script.
So, suppose you have a file content.js with the following contents (excuse the pun):
window.scroll(0, 150);
Also, assume you have a script app.js running inside your app page, and a variable webview is a reference to the <webview> in there.
Then, you can make the webview execute it:
Declaratively, by calling webview.addContentScripts before loading the page (e.g. before setting src from app.js code):
// Assuming <webview id="wv"></webview>
var webview = document.getElementById("wv");
webview.addContentScripts([{
name: "ExampleRule",
matches: ["http://example.co.uk/*"], // can be as narrow as you wish
js: ["content.js"]
}]);
webview.src = "http://example.co.uk/test.aspx";
Explicitly, when the page is already loaded:
// Assuming <webview id="wv" src="http://example.co.uk/test.aspx"></webview>
var webview = document.getElementById("wv");
webview.executeScript({file: "content.js"});
It is, of course, possible to make content.js much more sophisticated (for example, receive commands from the app) and/or more precisely control the timing of your operation - but those are at this point generic content script questions for which you can find solutions elsewhere or ask a new question.
I am building a Chrome extension that creates an iFrame inside any HTML page (i.e. any page being viewed in the browser window).
The iFrame is "injected" into the HTML page by the Chrome extension's content script. The resulting HTML looks like this:
<html>
<head>..</head>
<body>..</body>
<iframe id="myIframe">...</iframe>
</html>
After the content script adds the iFrame to the main page's DOM it goes on to populate the iframe with content by manipulating its own DOM. I use the Zurb CSS to style the iframe content.
And this all works fine.
I am now trying to add the Zurb Joyride to the content of the iframe, and it is this that I cannot get to work.
My manifest's content_scripts declaration looks like this:
"content_scripts": [{
"matches": ["*://*/*"],
"js": ["js/externalJS/jquery-2.1.4.min.js",
"js/externalJS/foundation.min.js",
"js/content_script.js"],
"run_at": "document_end"
}],
I suspect the iframe itself needs to have access to the Zurb scripts, so as well as foundation.min.js being loaded by the manifest as part of the extension, my content script also adds the relevant tags inside the iframe (by manipulating the DOM). My understanding is that the key elements within the iframe are:
tags for Zurb JS files
An object to attach the joyride to (id="testjoyride")
The joyride content itself
The initialisation call to foundation()
And so, with this in mind, I have the manifest's web_accessible_resources declaration looking like this:
"web_accessible_resources": [
"js/externalJS/jquery-2.1.4.min.js",
"js/externalJS/vendor/modernizr.js",
"js/externalJS/vendor/fastclick.js",
"js/externalJS/foundation.min.js",
"css/foundation.css",
"css/app.css"
],
And then use the content script to build up the following HTML inside the iframe:
<iframe id="myIframe">
<html>
<head>
<!-- the foundation style sheet (which works fine already) -->
<link type="text/css" rel="stylesheet" href="chrome-extension://extension_id/css/foundation.css">
<!-- the first two scripts -->
<script src="chrome-extension://extension_id/js/externalJS/vendor/modernizr.js"></script>
<script src="chrome-extension://extension_id/js/externalJS/jquery-2.1.4.min.js"></script>
</head>
<body>
<!-- a test object to attach the joyride too -->
<input type="submit" id="testjoyride">
<!-- now the rest of the iframe content -->
... content ...
<!-- the other two scripts -->
<script src="chrome-extension://extension_id/js/externalJS/vendor/fastclick.js"></script>
<script src="chrome-extension://extension_id/js/externalJS/foundation.min.js"></script>
<!-- default joyride code from foundation.zurb.com -->
<ol class="joyride-list" data-joyride="">
<li data-id="testjoyride" data-class="custom so-awesome" data-text="Next" data-prev-text="Prev">
<h4>Stop #1</h4>
</li>
<li data-button="end" data-prev-text="Prev">
<h4>Stop #3</h4>
</li>
</ol>
</body>
</html>
</iframe>
Then, finally, when all of the above content is attached to the iframe (and the iframe, of course, is attached to the parent page's DOM) I call foundation(), from the content_script.
I think I need to call it like this:
$(window.frames.myIframe.contentWindow.document).foundation('joyride', 'start');
i.e. passing in the document object of the iframe, not the parent page **
So I do all of that, and nothing happens. The joyride does not appear, nor does the HTML get transformed into the auto-generated output (as described here).
I have created a test case in a simple HTML page and it worked fine, I tried to move that test page into an iframe and the joyride stopped working. I therefore suspect, although without much confidence, that the issues lies with the iframe, not the Chrome extension.
** Note, I place this call right at the end of all the DOM manipulation. After I have added the new HTML I've created to the iframe's DOM, and after creating all my listeners etc. So I'm confident the scripts and the joyrides should be "there" by that point. However, if I stick an alert() just before the call to foundation('joyride','start'), the alert displays with an empty iframe behind it, and only after I dismiss the alert does the iframe populate. Whether that's important, or just a strange effect of the alert, I don't know.
Thanks to some helpful comments, I managed to get this working. The exact same solution got QTip2 working as well. So this is a solution for:
Third party javascript packages (specifically, JQuery, Foundation Zurb Joyride, and QTip2)...
... injected programmatically by a Chrome Extension...
... into an iFrame - that has itself been injected, by the Chrome Extension, into the user's current webpage
I will do my best to describe it below. I am self-taught and still a newbie, so my apologies for any confusing terminology or divergences from best practice.
Solution Overview
I ended up with three levels to my Chrome Extension:
A background script, controlling the extension (fairly inconsequential to this specific problem, but worth mentioning for completeness).
A content script, that receives commands from the background script and programmatically injects an iframe, the iframe HTML content, and the 3rd partly JS packages into the user's webpage.
An "iframe script", which is a custom JS script that is injected into the iframe along with the 3rd party packages. This iframe script can send/receive messages to/from the content script, whilst at the same time having access to the 3rd party JS packages - Zurb Joyride & QTips
The Manifest
Although the content script (content_script.js) controls all of the injection, it never actually runs any of the 3rd party JS packages itself. They are run by the user's webpage. Therefore my manifest's content_scripts declaration looks like this:
"content_scripts": [{
"matches": ["*://*/*"],
"js": ["js/content_script.js"],
"run_at": "document_end"
}],
Because the web page needs to access the 3rd party scripts, it must be given permission. It also needs access to our custom iframe script:
"web_accessible_resources": [
"js/iframe_script.js",
"js/externalJS/jquery-2.1.4.min.js",
"js/externalJS/jquery.qtip.min.js",
"js/externalJS/vendor/modernizr.js",
"js/externalJS/vendor/fastclick.js",
"js/externalJS/foundation.min.js",
"css/foundation.css",
"css/jquery.qtip.min.css"
],
Content Script: Inject Joyride HTML into the iframe
The content script creates an iframe element (id="myIframe") and appends it to the user's webpage. It then goes on to programatically fill it with content.
Some of that content will become the anchors for the joyride: HTML elements with their ids set to joyrideStop1, joyrideStop2, etc:
iframeContentHTML += <i class="fi-info brandSpielIcon" id="joyrideStop1"></i>
And the content script also builds up the joyride HTML, as per the Zurb docs, at the bottom of the iframe body.
joyrideHTML += '<ol class="joyride-list" data-joyride>';
joyrideHTML += ' <li data-id="joyrideStop1" data-text="Next (1 of 5)" data-prev-text="Prev" class="customJoyride">';
joyrideHTML += ' <h4>Title</h4>';
joyrideHTML += ' <p>Content</p>';
joyrideHTML += ' </li>';
...
My custom CSS class is simply:
.customJoyRide .joyride-nub {
visibility: hidden;
}
This makes the little pointy arrow on the joyride tooltip disappear. I had significant problems trying to get the joyride to display nicely inside the small iframe. Ideally I would like them to anchor to elements inside the iframe, but display as though they are part of the main page. In the end this seemed either too hard or impossible, so I just removed the nub and dodged the issue. This is a display issue, and only a problem for small iframes, so not a core part of this solution.
Content Script: Inject JS into the iframe
To get Joyride to work, we need to inject the 3rd party scripts and the custom iframe_script.js into the iframe. This is an example of the code from content_script.js for one of the 3rd party scripts:
// Inject foundation script into iframe body
var foundationScript = document.createElement('script');
foundationScript.src = chrome.extension.getURL('js/externalJS/foundation.min.js');
window.frames.myIframe.contentWindow.document.body.appendChild(foundationScript);
NB. This part of the solution corrects one of my original mistakes. The scripts do not seem to initialise if you simply append text to innerHTML like so: body.innerHTML += '<script src=".."></script>;'
So I repeat the above code for all the scripts. In the iframe head I put:
jquery.qtipmin.css
modernizr.js
jquery-2.1.4.min.js
And at the bottom of the iframe body I put:
fastclick.js
foundation.min.js
jquery.qtip.min.js
iframe_script.js.
In that order.
Iframe Script: initialise
When iframe_script.js initialises, I immediately get it to add a message listener (so the content script can communicate with it) and then send a message to the content script telling it it's ready (the content script already has its own message listener set up, the code for which is not detailed here):
(function initialise(){
// Listen for messages from the content script
window.addEventListener("message", contentScriptMessage_listener, false);
// Tell the content script we're ready to go
window.parent.postMessage({sender: 'iframe_script',
subject: 'iframeScriptHasInitialised'}, '*');
})();
NB. I struggled to make sure the iframe's listener was set up before my content_script sent its first message to the iframe. In the end this solution (waiting for a message back from the iframe) seemed to work best, although I suspect there may be a better way
Content Script: tell the iframe to activate the joyride
Now that the content script knows the iframe listeners are ready, it can tell it to activate the Zurb joyride (and/or the QTips). I do this by sending a message back to the iframe.
window.frames.myIframe.contentWindow.postMessage({
sender: 'content_script',
subject: 'pleaseActivateJoyride'}, '*');
Obviously it would be simpler to activate the joyride directly from the iframe script, without all these messages bouncing back and forth. But that's the whole point: it's the content script that knows whether or not the joyride should even be displayed, let alone all the other config details. Keeping control with the content script (and, ultimately, with the background script) makes for a much tidier implementation over all.
Iframe Script: activate Joyride
My iframe script's message handler receives the message from the content script and calls this function:
function activateJoyride(){
$(document).foundation({
joyride: {
... configuration ...
}
});
$(document).foundation('joyride', 'start');
}
NB. I have an outstanding issue here. This can sometimes run before JQuery is loaded, resulting in an error ("$ is not declared"). I have played with window.setTimeout in an effort to resolve this, and it works nearly all the time... but it's not 100%
And that's it!
There's a matching activate function for the QTips, with the appropriate content injected into the iframe earlier. Just like with the joyride, I struggled with the rendering of the Qtips inside the small iframe. QTip2 is more powerful and the docs suggest there are solutions.
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.
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.