Use case
Existing web page / URL is opened from a 3rd party app. Upon completion of the work on the web page, it is expected to close
itself.
Javascript does not allow it using window.close() or alike, ref.
window.close and self.close do not close the window in Chrome
Browser limitation (Firefox/Chrome) is well documented and explored over the past years. For the given use case, per my understanding closing of a browser tab can only be achieved from a background script, calling chrome.tabs.remove() API.
Browser extension
The approach that seemed logical to me is using the following artifacts.
content.js
document.addEventListener("myCloseTabEvent", function(e){
console.log("[content] originating domain: " + this.domain);
// Prepare message for background script
var myMsgContent = {
type: "myAction",
value: "CloseBrowserTab"}
browser.runtime.sendMessage(myMsgContent);
}, false);
background.js
chrome.runtime.onMessage.addListener(
function(msgData, sender, sendResponse) {
if (msgData.type == "myAction" && msgData.value == "CloseBrowserTab") {
chrome.tabs.remove(sender.tab.id);
} else {
console.log("[background] No action because !(myAction && CloseBrowserTab)");
}
});
Changes to web page to raise the new event
function mySendEvent() {
const myEvent = new Event("myCloseTabEvent"); // OR CustomEvent()
// Dispatch the event
document.dispatchEvent(myEvent);
}
In summary, the following happens:
The web page loads
Content script adds the event listener for a custom event myCloseTabEvent
Background script adds an onMessage listener using chrome.runtime.onMessage.addListener()
Once all work is done on the page, existing Javascript code dispatches the custom event by calling mySendEvent().
Content script runs its listener function(e), sending a message to the background script.
Background script onMessage listener function(msgData, sender, sendResponse) uses the sender object to determine the tabId, and closes the browser tab using chrome.tabs.remove(sender.tab.id);
Questions
In concept, is this approach the best option we have to achieve the goal of closing the browser tab? Would there be any better ways of achieving the same?
A background script is require to be able to close the tab (correct me if I'm wrong here). Therefore the browser extension cannot be restricted to be active only on a specific set of domains using content_scripts -> matches. What best practices exists to restrict the functionality of the browser extension like this to specific domains? (domain names are known to the users of the extension, but not while packaging the artifacts). This is especially is of interest, to prevent other (malicious) web pages from closing them selves by sending the same message to the background script of the extension.
Related
I have developed a WebExtension for Firefox and my website works with the extension as a prerequisite. I need to check programmatically whether the extension is installed or not and if not ask the user to install it.
I am not able to find a way how to check this operation of whether my extension is already installed in the user's browser.
Editor note: Methods available in Firefox differ from those available in Chrome, so this question is not a duplicate.
Important note to begin with: A page can't query if an extension is installed without explicit help from the extension. This is done to prevent browser fingerprinting and/or preventing sites from denying content if certain extensions are installed.
WebExtensions are largely built upon the same principles as Chrome extensions. As such, this question is relevant: Check whether user has a Chrome extension installed.
However, some of the best methods available in Chrome are currently unavailable in Firefox:
You can't use external messaging from a webpage (through externally_connectable) as it's not available in FF.
You can't use web-accessible resources for checking presence since Firefox intentionally shields them from fingerprinting:
The files will then be available using a URL like:
moz-extension://<random-UUID>/<path/to/resource>
This UUID is randomly generated for every browser instance and is not your extension's ID. This prevents websites from fingerprinting the extensions a user has installed.
As such, what are your options? The page can't talk directly to the extension context (background), and the background can't directly affect the page; you need a Content script to interact with the page content.
How can page code and a content script communicate? They are isolated from each other unless content script does something about it.
First off, generic tricks that work in both FF and Chrome:
You can create or modify a DOM element on the page from a content script and look for those modifications in the page.
// Content script
let beacon = document.createElement("div");
beacon.classname = browser.runtime.id;
document.body.appendChild(beacon);
// Page script
// Make sure this runs after the extension code
if (document.getElementsByClassName("expected-extension-id").length) {
// Installed
} else {
// Not installed
}
You can use postMessage to communicate between contexts, though it's clunky to use as a bidirectional channel.
Here's documentation and sample WebExtension.
// Content script code
window.postMessage({
direction: "from-content-script",
message: "Message from extension"
}, "*");
// Page code
window.addEventListener("message", function(event) {
if (event.source == window &&
event.data.direction &&
event.data.direction == "from-content-script") {
// Assume extension is now installed
}
});
You can use custom DOM events in a similar way.
There are interesting Firefox-specific approaches as well:
You can share code with the page using exportFunction or cloneInto:
// Content script
function usefulFunction() {
/* ... */
}
const extensionInterface = {
usefulFunction
}
window.wrappedJSObject.extensionInterface =
cloneInto(extensionInterface, window, {cloneFunctions: true});
// Page code
if (typeof window.extensionInterface !== "undefined") {
// Installed
window.extensionInterface.usefulFunction();
} else {
// Not installed
}
I've read up on how to avoid security issues when using window.postMessage() -- particularly the suggestions in this MDN doc.
But given all the preventative tips are all client-side, I'm having trouble understanding how they'd stop bad actors from simply editing changing the code in their developer tools.
Here's the situation I'm dealing with. I have a page that will contain an embedded iframe, and I have control over that iframe (it lives on a separate domain, but the vendor that provides it allows me to put custom JavaScript in the iframe source). The parent window and the iframe will communicate back and forth.
/**
window at https://firstgoodorigin.com
Receives message from iframe to indicate
its contents have loaded.
Once that message has been received,
send a message back to the iframe.
*/
function handleMessage(message) {
if (message.origin === 'https://secondgoodorigin.com') {
// verify and sanitize what's in message.data
// (it'll be something like "loaded")
// if it's good, send a message back
message.source.postMessage('foo', 'https://secondgoodorigin.com');
}
}
window.addEventListener('message', handleMessage, false);
/**
iframe at https://secondgoodorigin.com
Tell parent window it has loaded. Once that happens
it will receive a message from the parent window, for
which we add an event listener.
*/
window.addEventListener('load', () => {
window.parent.postMessage('loaded', https://firstgoodorigin.com);
});
window.addEventListener('message', (message) => {
if (message.origin === 'https://firstgoodorigin.com') {
// verify and sanitize what's in message.data
// do stuff
}
});
Given both the window source and iframe source will be editable inside someone's web inspector, what's to stop them from removing all the validation logic and replacing it with something malicious? What am I missing here?
As mentioned in the comment by Will any code in the browser can be edited by the end user if he or she may want to. The point of the locking down postmessages is to stop a third web site from posting unwanted messages.
If a user is logged into the site in question, and then loads a malicious website, that website could load the web site in question in an iframe or a popup, and then post messages unauthorized to the site.
Is it possible to launch a Google Chrome extension within a website? E.g run some javascript that will launch the extensions UI?
I'm building a web-app that will allow users to take screenshots of their desktop and edit them. I've got a sample extension up and running using dektopCapture but it is an 'app' style of an extension.
It allows to select a window to stream from, then take a
snapshot within the extension UI(using a button) that is saved as an image string
My question is:
Is it possible to fire up the desktopCapture UI (the window that gets the available windows to stream from), from within my web-app, maybe a button, take the stream and place it on a canvas/HTML5 video element within my web-app?
I'm figuring that I could hook-up an event-listener within the extension and use runtime.onMessage to post a message from within my app
Notes:
If there's a more intuitive way to do this, I can go that route - e.g If I could keep as much interaction within the web-app with just the extension running in the background, that would be even better.
The extension is of type browser_action but I want it to be applicable to a single page(the app's webpage) so if it can be used in a page_action I'd prefer that instead. There's really no need to have browser_action icon if I can trigger this from within a webpage
I'm also planning to build a FF extension so any insights there are also appreciated.
So I'm answering my own question.
I've managed to get it working using externally_connectables.
The externally_connectable manifest property declares which
extensions, apps, and web pages can connect to your extension via
runtime.connect and runtime.sendMessage.
1. Declare app/webpage in manifest.json
Just declare your web-app/page within your manifest.json as an externally_connectable.
E.g I wanted to connect my app is hosted on Github Pages and I have a domain name of https://nicholaswmin.github.io, so it does a bit like this:
"externally_connectable": {
"matches": ["https://nicholaswmin.github.io/*"]
}, //rest of manifest.json
2. Set up event listener for messages in background.js
Then set up an event listener in your background.js like so:
chrome.runtime.onMessageExternal.addListener(function(request, sender, sendResponse) {
//Stuff you want to run goes here, even desktopCapture calls
});
3. Send message from your web/app page
And call it from within your web-app/website like this:
chrome.runtime.sendMessage("APP ID GOES HERE",
{data: { key : "capture"}});
Make sure that your website is correctly declared as an externally_connectable in your manifest.json and that you are passing the app-id when sending the message
We have a legacy web application. At various places it opens a window with the help of Privilege Manager on Firefox to get the needed result.
Some of these windows open a Java applet or a PDF document.
The client machines are updating Firefox and Privilege Manager is gone.
What is the easiest way around it?
The problems are :
There must be only one instance of the pop-up at anyone time. This could be done by selecting appropriate window name on window.open() call.
If the window is opened again (by means of user action), it should not reload but just focus to bring it to the foreground (I have seen I can keep a reference to the window on JavaScript to do that)
It basically really must be transient/modal so that the client cannot leave the current page or reload or any other kind of interaction with the parent window (except opening/refocusing the child window) without closing the child window first. I have no idea how to do that.
Do anyone has an idea how to do that?
The client is only Firefox (it works in a special kiosk configuration) on Linux.
I read somewhere that I could somehow write an extension but I am basically clueless about extensions and its API.
Edit1:
Example of (simplified) legacy code. Not really sure if all the permissions were required, but this is it: This function opens a window that stays over the parent window and prevents any interaction from the user with the parent window.
function fWindowOpen(url, name) {
netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");
netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserWrite");
netscape.security.PrivilegeManager
.enablePrivilege("CapabilityPreferencesAccess");
netscape.security.PrivilegeManager
.enablePrivilege("UniversalPreferencesWrite");
netscape.security.PrivilegeManager
.enablePrivilege("UniversalPreferencesRead");
netscape.security.PrivilegeManager.enablePrivilege("UniversalFileRead");
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
window.open(
url,
name,
"screenX=70,dependent=yes,menubar=0,toolbar=0,width=900,height=700,modal=1,dialog=1"
);
}
function fnCapture(){
fWindowOpen("/path/to/document_or_japplet/page","_blank");
}
HTML:
<button value="Capture" property="btnCapture" onclick="javascript:fnCapture();"/>
Edit2: Solution
On a typical extension, on the xul code, define this javascript code:
var dialogExt = {
listener: function(evt) {
// Do work with parameters read through evt.target.getAttribute("attribute_name")
window.openDialog(evt.target.getAttribute("url"), evt.target.getAttribute("name"), evt.target.getAttribute("features"));
}
}
// from examples
document.addEventListener("dialogExtEvent", function(e){ dialogExt.listener(e); }, false, true);
Then, on the web page:
var element = document.createElement("dialogExtElement");
element.setAttribute("url", url);
element.setAttribute("name", name);
element.setAttribute("features", features);
document.documentElement.appendChild(element);
var evt = document.createEvent("Events");
evt.initEvent("dialogExtEvent", true, false);
element.dispatchEvent(evt);
Now, maybe I am missing some security checks to let the code work if it originates from the same host, and how to handle a reference to the document that requested the dialog as means of interaction between the dialog window and it's opener.
The Privilege Manager was deprecated in Firefox 12 and removed in Firefox 17 (briefly restored).
You might want to look into Window.showModalDialog(). However, it is deprecated and is expected to go away within the year, or in 2016 if you go with an extended service release (ESR) of Firefox 38. It may be a temporary solution while you develop an extension.
In order to accomplish the same tasks, you will need to write an extension and ask the user to install it (from Bypassing Security Restrictions and Signing Code, the old information about Privilege Manager):
Sites that require additional permissions should now ask Firefox users to install an extension, which can interact with non-privileged pages if needed.
It is possible to write such an extension using any of the three different extension types:
XUL overlay
Restartless/Bootstrap
Add-on SDK
For the first two types, you would use window.open(). The modal option is in "Features requiring privileges". You will probably also want to look at Window.openDialog().
For the Add-on SDK, you would normally use the open() function in the SDK's window/utils module. Here, again, you will probably want to look at openDialog().
It appears you may be opening content that is supplied from the web in these modal windows. It is unlikely that you will get an extension approved to be hosted on AMO which opens content in such windows which in not included in the add-on release. This does not mean you can not develop the extension and have it installed on your kiosk clients without hosting it on AMO. However, there are additional restrictions in development for Firefox this year which will make this significantly more difficult, see: "Introducing Extension Signing: A Safer Add-on Experience".
You should be able to get similiar window.open behavior, including support for the modal option from the sdk's window/utils module.
You will have to install the onclick listener with a content script, send a message to the addon-main through its port and then open that window from the addon main.
There is an excellent extension called Blipshot which takes page screenshots. I need to invoke the extension with page level javascript, instead of clicking its icon. Is this possible?
You cannot invoke any methods of an extension from within a web page. However, it's possible to inject a content script into the web page, and use sendMessage and onMessage, or onConnect and connect.
To edit an extension: Visit chrome://extensions page, and enable the Developer mode. Unpack an extension and/or visit the extension's directory. Edit the manifest.json file, and add the necessary lines (see here).
Add an event event listener at the background page. Add a poller in the content script, eg:
// Content script
var poller = window.setInterval(function() {
if (document.documentElement.getAttribute('extensionCalled')) {
chrome.extension.sendMessage({"anyname": "anything"}, function() {
/*optional callback function.*/alert("Something happened")
});
clearInterval(poller);
}
}, 200);
// Background
chrome.extension.onMessage.addListener(function(request, sender, callback) {
if (request.anyname == "anything") {
function_logic_here();
//Optionally, callback:
callback();
}
});
See also
Chrome extension - retrieving Gmail's original message - Using DOM events to communicate between a page and extension (recommended)
MDN: postMessage - It can be used to communicate between a page and extension (this method may cause conflicts when the page itself is also using the message events).
References:
Extension messaging
Content scripts
Content scripts in extensions
It would only be possible if the extension provides an interface to do it. Extensions run in an isolated environment, so you don't have direct access to any of their functions.
The closest they get is content scripts, which have access to the DOM. Because of that, you can communicate using events, but obviously the extension would need to set up event handlers for them, so it completely depends on the extension.