we've set up a simple inline webstore installer for our app.
The app site has been verified. The inline installation does work correctly for half of us inside our company, but it doesn't work for the other half. They would get "Uncaught TypeError: Cannot call method 'install' of undefined testsupport.html:15
Uncaught SyntaxError: Unexpected token ). It's as though the chrome or chrome.web variable isn't initialized.
Why does the inline installation work only on some machines but not on others? All these machines have the same chrome browser version.
TIA
I've not seen this issue before but I will try to provide a breakdown of the setup I use to manage inline installations for the multiple Chrome extensions on my website.
Within the head node of every page (optionally, only pages that may include one or more Install links) I add the required links to each extension/app page on the Chrome Web Store. This allows me to easily add install links anywhere on the page for various extensions/apps. The JavaScript simply binds an event handler to each of the install links once the DOM has finished loading. This event handler's sole purpose is to install the extension/app that it links to when clicked and then to change its state to prevent further install attempts.
<!DOCTYPE html>
<html>
<head>
...
<!-- Link for each extension/app page -->
<link rel="chrome-webstore-item" href="https://chrome.google.com/webstore/detail/dcjnfaoifoefmnbhhlbppaebgnccfddf">
<script>
// Ensure that the DOM has fully loaded
document.addEventListener('DOMContentLoaded', function() {
// Support other browsers
var chrome = window.chrome || {};
if (chrome.app && chrome.webstore) {
// Fetch all install links
var links = document.querySelectorAll('.js-chrome-install');
// Create "click" event listener
var onClick = function(e) {
var that = this;
// Attempt to install the extension/app
chrome.webstore.install(that.href, function() {
// Change the state of the button
that.innerHTML = 'Installed';
that.classList.remove('js-chrome-install');
// Prevent any further clicks from attempting an install
that.removeEventListener('click', onClick);
});
// Prevent the opening of the Web Store page
e.preventDefault();
};
// Bind "click" event listener to links
for (var i = 0; i < links.length; i++) {
links[i].addEventListener('click', onClick);
}
}
});
</script>
...
</head>
<body>
...
<!-- Allow inline installation links to be easily identified -->
Install
...
</body>
</html>
In order for this system to work fully you also need to support scenarios where the user has returned to your website after installing your extension/app. Although the official documentation suggests using chrome.app.isInstalled this doesn't work when multiple extensions/apps can be installed from a single page. To get around this issue you can simply add a content script to your extension/app like the following install.js file;
// Fetch all install links for this extension/app running
var links = document.querySelectorAll('.js-chrome-install[href$=dcjnfaoifoefmnbhhlbppaebgnccfddf]');
// Change the state of all links
for (var i = 0; i < links.length; i++) {
links[i].innerHTML = 'Installed';
// Website script will no longer bind "click" event listener as this will be executed first
links[i].classList.remove('js-chrome-install');
}
Then you just need to modify your manifest.json file to ensure this content script is executed on your domain.
{
...
"content_scripts": [
{
"js": ["install.js"],
"matches": ["*://*.yourdomain.com/*"],
"run_at": "document_end"
}
]
...
}
This will result in the content script being run before the JavaScript on your website so there will be no install links with the js-chrome-install class by the time it is executed, thus no event handlers will be bound etc.
Below is an example of how I use this system;
Homepage: http://neocotic.com
Project Homepage: http://neocotic.com/template
Project Source Code: https://github.com/neocotic/template
Your inline installation markup is:
<a href="#" onclick="chrome.webstore.install()">
CaptureToCloud Chrome Extension Installation
</a>
(per one of the comments, it used javascript:void(0) before, which is equivalent to # in this case).
Your <a> tag both navigates the page and has an onclick handler. In some cases, the navigation takes place before the onclick handler finishes running, which disturbs the code that supports inline installation.
If you switch to using a plain <span> (styled to look like a like, if you'd like), then you should no longer have this problem:
<span onclick="chrome.webstore.install()" style="text-decoration: underline; color:blue">
CaptureToCloud Chrome Extension Installation
</span>
Alternatively, you can return false from your onclick handler to prevent the navigation:
<a href="#" onclick="chrome.webstore.install(); return false;">
CaptureToCloud Chrome Extension Installation
</a>
(though since you're not actually linking anywhere, there isn't much point in using the <a> tag)
I get the error you mentioned AND a popup window that allows me to install the extension. So probably everybody get the error but for some it is preventing installation.
I got rid of the error by replacing javascript:void() by # in href.
CaptureToCloud Chrome Extension Installation
Related
I have a UserScript running with #grant none, installed directly in chrome://extensions without Tampermonkey, and I am trying to define a function into the host page global namespace:
window.setRefinedTwitterLiteFeatures = features => {
settings = Object.assign(settings, features)
localStorage.setItem(storageKey, JSON.stringify(settings))
setFeatures()
}
Unfortunately this doesn't seem to be working. I've also tried to use unsafeWindow without luck.
Seems like you install it as described in the repo - that is directly into chrome://extensions - but Chrome doesn't support nonsandboxed environment unlike Tampermonkey. The only way to mimic it is to create a DOM script element, assign the code you want to expose to textContent and append to document.body, for example. Just as shown in the canonic answer “Insert code into the page context using a content script”.
The next problem is twitter's CSP that disallows the code in inline script elements. Luckily, as we can see in devtools network inspector for the main page request, the CSP has a nonce exception, which we can reuse:
function runScriptInPageContext(text) {
const script = document.createElement('script');
script.text = text;
script.nonce = (document.querySelector('script[nonce]') || {}).nonce;
document.documentElement.appendChild(script);
script.remove();
}
Usage examples running code immediately:
runScriptInPageContext("alert('foo')");
runScriptInPageContext(`(${() => {
alert('foo');
})()`);
Usage example exposing a global function:
runScriptInPageContext(function foo() {
alert('foo');
});
I have a chrome extension which injects an iframe into every open tab. I have a chrome.runtime.onInstalled listener in my background.js which manually injects the required scripts as follows (Details of the API here : http://developer.chrome.com/extensions/runtime.html#event-onInstalled ) :
background.js
var injectIframeInAllTabs = function(){
console.log("reinject content scripts into all tabs");
var manifest = chrome.app.getDetails();
chrome.windows.getAll({},function(windows){
for( var win in windows ){
chrome.tabs.getAllInWindow(win.id, function reloadTabs(tabs) {
for (var i in tabs) {
var scripts = manifest.content_scripts[0].js;
console.log("content scripts ", scripts);
var k = 0, s = scripts.length;
for( ; k < s; k++ ) {
chrome.tabs.executeScript(tabs[i].id, {
file: scripts[k]
});
}
}
});
}
});
};
This works fine when I first install the extension. I want to do the same when my extension is updated. If I run the same script on update as well, I do not see a new iframe injected. Not only that, if I try to send a message to my content script AFTER the update, none of the messages go through to the content script. I have seen other people also running into the same issue on SO (Chrome: message content-script on runtime.onInstalled). What is the correct way of removing old content scripts and injecting new ones after chrome extension update?
When the extension is updated Chrome automatically cuts off all the "old" content scripts from talking to the background page and they also throw an exception if the old content script does try to communicate with the runtime. This was the missing piece for me. All I did was, in chrome.runtime.onInstalled in bg.js, I call the same method as posted in the question. That injects another iframe that talks to the correct runtime. At some point in time, the old content scripts tries to talk to the runtime which fails. I catch that exception and just wipeout the old content script. Also note that, each iframe gets injected into its own "isolated world" (Isolated world is explained here: http://www.youtube.com/watch?v=laLudeUmXHM) hence newly injected iframe cannot clear out the old lingering iframe.
Hope this helps someone in future!
There is no way to "remove" old content scripts (Apart from reloading the page in question using window.location.reload, which would be bad)
If you want to be more flexible about what code you execute in your content script, use the "code" parameter in the executeScript function, that lets you pass in a raw string with javascript code. If your content script is just one big function (i.e. content_script_function) which lives in background.js
in background.js:
function content_script_function(relevant_background_script_info) {
// this function will be serialized as a string using .toString()
// and will be called in the context of the content script page
// do your content script stuff here...
}
function execute_script_in_content_page(info) {
chrome.tabs.executeScript(tabid,
{code: "(" + content_script_function.toString() + ")(" +
JSON.stringify(info) + ");"});
}
chrome.tabs.onUpdated.addListener(
execute_script_in_content_page.bind( { reason: 'onUpdated',
otherinfo: chrome.app.getDetails() });
chrome.runtime.onInstalled.addListener(
execute_script_in_content_page.bind( { reason: 'onInstalled',
otherinfo: chrome.app.getDetails() });
)
Where relevant_background_script_info contains information about the background page, i.e. which version it is, whether there was an upgrade event, and why the function is being called. The content script page still maintains all its relevant state. This way you have full control over how to handle an "upgrade" event.
I studied the loading behavior of Chrome/FireFox/Safari, all of them will be blocked while inline JavaScript running. Any impacts if do a rough sanning for remaining unparsed document and launch new sub resource downloading task?
Below is my testing page:
<script>
i=0;
while(i<100000)
{
i = i+1;
document.getElementById("output").innerHTML = i;
}
</script>
<img src="http://images.csdn.net/20130516/HK.jpg" />
Looking forward to learn detail of why browser block until the inline script is performed finished?
These days it's considered best to put all your Javascript includes and code just before you close the <body> tag, so that the browser can get on with downloading all the stuff in parallel that it can (e.g. images, css) in the mean time.
I have a google chrome extension that shares some code between it's content script and background process / popup. If it some easy and straightforward way for this code to check if it's executed as content script or not? (message passing behavior differs).
I can include additional "marker" javascript in manifest or call some chrome fnction unavailable from content script and check for exceptions - but these methods looks awkward to be. Maybe it's some easy and clean way to make this check?
To check whether or not your script is running as a content script, check if it is not being executed on a chrome-extension scheme.
if (location.protocol == 'chrome-extension:') {
// Running in the extension's process
// Background-specific code (actually, it could also be a popup/options page)
} else {
// Content script code
}
If you further want to know if you're running in a background page, use chrome.extension.getBackgroundPage()=== window. If it's true, the code is running in the background. If not, you're running in the context of a popup / options page / ...
(If you want to detect if the code is running in the context of an extension, ie not in the context of a regular web page, check if chrome.extension exists.)
Explanation of revised answer
Previously, my answer suggested to check whether background-specific APIs such as chrome.tabs were defined. Since Chrome 27 / Opera 15, this approach comes with an unwanted side-effect: Even if you don't use the method, the following error is logged to the console (at most once per page load per API):
chrome.tabs is not available: You do not have permission to access this API. Ensure that the required permission or manifest property is included in your manifest.json.
This doesn't affect your code (!!chrome.tabs will still be false), but users (developers) may get annoyed, and uninstall your extension.
The function chrome.extension.getBackgroundPage is not defined at all in content scripts, so alone it can be used to detect whether the code is running in a content script:
if (chrome.extension.getBackgroundPage) {
// background page, options page, popup, etc
} else {
// content script
}
There are more robust ways to detect each context separately in a module I wrote
function runningScript() {
// This function will return the currently running script of a Chrome extension
if (location.protocol == 'chrome-extension:') {
if (location.pathname == "/_generated_background_page.html")
return "background";
else
return location.pathname; // Will return "/popup.html" if that is the name of your popup
}
else
return "content";
}
In the tutorial for migrating a Google Chrome Extension to Manifest Version 2, I am directed to Remove inline event handlers (like onclick, etc) from the HTML code, move them into an external JS file and use addEventListener() instead.
OK, I currently have a background.html page that looks like this…
<html>
<script type="text/javascript">
// Lots of script code here, snipped
…
</script>
<body onload="checkInMyNPAPIPlugin('pluginId');">
<object type="application/x-mynpapiplugin" id="pluginId">
</body>
</html>
Following another directive, I've moved that Lots of script code into a separate .js file, and following this directive, I need to remove the onload= from the body tag, and instead cal addEventListener() in my script code. I've tried several approaches, but am apparently guessing wrong. What will that code look like? In particular, upon what object do I invoke addEventListener()?
Thanks!
I normally use this for body onload event...
document.addEventListener('DOMContentLoaded', function () {
// My code here.. ( Your code here )
});
For somethings it is working.. but really, I think we should use..
window.addEventListener('load', function () {
document.getElementById("#Our_DOM_Element").addEventListener('change - or - click..', function(){
// code..
});
});