Chrome Extensions
This page describes how to work with Chrome extensions.
JxBrowser provides the Extensions API that allows you to install, update, and uninstall Chrome extensions. The extensions work on a per-profile basis and are not shared with other profiles. To access the extensions of a specific profile, use the following approach:
var extensions = profile.extensions();
val extensions = profile.extensions()
If you delete the profile, all installed extensions for this profile will be deleted as well.
Installing extensions
You can install Chrome extensions manually from Chrome Web Store or programmatically from CRX files.
We recommend that you install extensions from CRX files if you trust the source of the extension. Comparing to installing extensions from Chrome Web Store, it gives you the following benefits:
- You control the version of the extension that you install. You can test the extension with a specific JxBrowser version and make sure that it works as expected with the Chromium build that JxBrowser uses. If you install the extension from Chrome Web Store, you install the latest version of the extension that may not be compatible with the JxBrowser version you use.
- You can deploy the extension with your application and install it automatically. So, you don’t need to ask users to install the extension manually from Chrome Web Store.
- You can install extensions that are not available in Chrome Web Store. If you develop an extension you would like to use in JxBrowser and you don’t want to publish it in Chrome Web Store, you can pack it into a CRX file and use it.
To install an extension from a CRX file, use the following approach:
var crx = Paths.get("path/to/extension.crx");
var extension = extensions.install(crx);
val crx = Path("path/to/extension.crx")
val extension = extensions.install(crx)
The method returns an instance of the installed extension. All the required permissions required by the extension are automatically granted.
Important: Be very cautious about the extensions you are installing. In Chrome, to install an extension, the CRX file must have publisher proof. Publisher proof is obtained upon extension publishing at the Chrome Web Store. In JxBrowser, we allow users to install the extensions without publisher proof, so be cautious about the resources you use to obtain CRX files.
Installing extensions from Chrome Web Store is disabled by default for security reasons. If you want to let users install extensions from Chrome Web Store, you need to allow installation using the following approach:
extensions.set(InstallExtensionCallback.class, (params, tell) -> tell.install());
extensions.register(InstallExtensionCallback { params, tell ->
tell.install()
})
Every time the user clicks the “Add to Chrome” button on the Chrome Web Store page, the InstallExtensionCallback
callback will be invoked. You can get info about the extension that the user wants to install from the params
object
and decide whether to allow the installation or not:
extensions.set(InstallExtensionCallback.class, (params, tell) -> {
var name = params.extensionName();
var version = params.extensionVersion();
if (name.equals("uBlock Origin") && version.equals("1.35.2")) {
tell.install();
} else {
tell.cancel();
}
});
extensions.register(InstallExtensionCallback { params, tell ->
val name = params.extensionName()
val version = params.extensionVersion()
if (name == "uBlock Origin" && version == "1.35.2") {
tell.install()
} else {
tell.cancel()
}
})
To get notified when the extension is installed, use the ExtensionInstalled
event:
extensions.on(ExtensionInstalled.class, event -> {
var installedExtension = event.extension();
});
extensions.subscribe<ExtensionInstalled> { event ->
val installedExtension = event.extension()
}
Updating extensions
If you install an extension from a CRX file, and you would like to update it with a new version, you just need to install it from a new CRX file:
var updatedCrx = Paths.get("path/to/updated_extension.crx");
var extension = extensions.install(updatedCrx);
val updatedCrx = Path("path/to/updated_extension.crx")
val extension = extensions.install(updatedCrx)
You don’t need to delete the previous version of the extension. JxBrowser will automatically update the extension to the newest version.
Important: When you are installing an extension without publisher proof, be sure to use
the same private key (.pem
file) for packaging. When a different private key is used or not used at all, the extension would be considered new and would be installed again without updating.
If you install an extension from Chrome Web Store, you can’t update it programmatically. The user should update the extension manually from the extension page in Chrome Web Store.
To get notified when the extension is updated, use the ExtensionUpdated
event:
extensions.on(ExtensionUpdated.class, event -> {
var updatedExtension = event.extension();
});
extensions.subscribe<ExtensionUpdated> { event ->
val updatedExtension = event.extension()
}
Uninstalling extensions
You can programmatically uninstall the extension you installed from a CRX file or Chrome Web Store:
extensions.uninstall(extension);
extensions.uninstall(extension)
If the extension was installed manually from Chrome Web Store, the user might want to uninstall it manually as well. By default, uninstalling extensions from Chrome Web Store is disabled for security reasons. If you want to allow users to uninstall extensions from Chrome Web Store or the chrome://extensions page, you need to allow uninstallation:
extensions.set(UninstallExtensionCallback.class, (params, tell) -> tell.uninstall());
extensions.register(UninstallExtensionCallback { params, tell ->
tell.uninstall()
})
To get notified when the extension is uninstalled, use the ExtensionUninstalled
event:
extensions.on(ExtensionUninstalled.class, event -> {
var uninstalledExtensionId = event.extensionId();
});
extensions.subscribe<ExtensionUninstalled> { event ->
val uninstalledExtensionId = event.extensionId()
}
An attempt to work with an uninstalled extension will result in an ObjectClosedException
.
Extension action
Chrome extensions may provide features that are available only in the “extension action popup”. This is a dialog that you see when clicking on the extension icon in the Chrome toolbar. Even though JxBrowser does not display Chrome toolbar, it provides an API for interacting with the extension action popup.
To simulate the click on the extension icon (extension action) in Chrome toolbar for a specific tab, execute the following code:
extension.action(browser).ifPresent(ExtensionAction::click);
extension.action(browser).ifPresent(ExtensionAction::click)
An extension, not Chromium, is responsible for picking a browser to interact with. Therefore,
the extension action may not be executed for the browser
it was obtained for.
If you want to display the extension icon in your application, you can access the extension action properties and subscribe to the action update notifications:
extension.action(browser).ifPresent(action -> {
// Get the properties of the action.
var icon = action.icon();
var badge = action.badge();
var tooltip = action.tooltip();
var enabled = action.isEnabled();
// Get notifications when the extension action is updated.
action.on(ExtensionActionUpdated.class, event -> {
var updatedAction = event.action();
});
});
extension.action(browser).ifPresent { action ->
// Get the properties of the action.
val icon = action.icon()
val badge = action.badge()
val tooltip = action.tooltip()
val enabled = action.isEnabled
// Get notifications when the extension action is updated.
action.subscribe<ExtensionActionUpdated> { event ->
val updatedAction = event.action()
}
}
Extension action popups
When you create a BrowserView
instance, it will register the default implementations of
the OpenExtensionActionPopupCallback
callback that will display
the extension action popup when the program simulates the click on the extension action icon.
If you want to display a custom extension action popup, register your own implementation of
the OpenExtensionActionPopupCallback
callback:
browser.set(OpenExtensionActionPopupCallback.class, (params, tell) -> {
var extensionActionPopup = params.popupBrowser();
tell.proceed();
});
browser.register(OpenExtensionActionPopupCallback { params, tell ->
val extensionActionPopup = params.popupBrowser()
tell.proceed()
})
Extension popups
Some extensions may want to open popup windows to show a website or some other content. By default, JxBrowser blocks such popups. To allow the extension to show popups, register your own implementation of the following callbacks:
extension.set(OpenExtensionPopupCallback.class, (params, tell) -> {
var extensionPopup = params.popupBrowser();
tell.proceed();
});
extension.register(OpenExtensionPopupCallback { params, tell ->
val extensionPopup = params.popupBrowser()
tell.proceed()
})
Or use the default implementation:
extension.set(OpenExtensionPopupCallback.class,
new DefaultOpenExtensionPopupCallback());
import com.teamdev.jxbrowser.view.swing.callback.DefaultOpenExtensionPopupCallback
...
extension.register(DefaultOpenExtensionPopupCallback())
Pay attention that “extension popups” and “extension action popups” are different things.
Peculiarities & limitations
We designed extension API with the goal to behave as Chromium as much as possible. But since JxBrowser is an embedded browser, we can’t ensure the same behavior for every case. Thus, there are peculiarities and limitations that you should have in mind when working with extensions.
- All tabs are opened as new windows.
chrome.tabs.query
never takes into account extension action popups.- Windows of the extension action popup are auto-resizable. The size is regulated by the extension action.
- When JxBrowser does not find the manifest for native messaging (
chrome.runtime.connectNative
) in the user data directory or Chromium application data, it checks it in Chrome application data.
There are certain limitations related to Chromium tabs, windows, and other UI elements. The windows.Window
is mapped
to a native window associated with the browser, not the Java window it is embedded into (if any). Thus, methods and
properties of the windows.Window
object, such as Window.width
, Window.height
, Window.focused
, etc., are bound to
that native window.
Unsupported extensions APIs
Below is the list of Chromium Extension APIs that are not supported in JxBrowser:
chrome.tabs.discard
chrome.tabs.remove
chrome.tabs.duplicate
chrome.windows.remove
chrome.windows.update
chrome.window.create
with more than one URL in parameterschrome.sessions.restore
If an extension calls an unsupported method that returns a Promise
, it will be rejected with an error. If a method
accepts a callback, the chrome.runtime.lastError
property will be set to an error.
Downloading CRX from Chrome Web Store
If you want to download a CRX file of an extension available in Chrome Web Store, then you can register
the InstallExtensionCallback
callback where you can get an absolute path to the CRX file of the extension that is
about to be installed from Chrome Web Store and make a copy of it to a different location:
extensions.set(InstallExtensionCallback.class, (params, tell) -> {
var name = params.extensionName();
var version = params.extensionVersion();
var sourceCrxFilePath = Paths.get(params.extensionCrxFile());
var targetCrxFilePath = Paths.get(name + "-" + version + ".crx");
try {
Files.copy(sourceCrxFilePath, targetCrxFilePath);
} catch (IOException e) {
throw new RuntimeException(e);
}
tell.cancel();
});
extensions.register(InstallExtensionCallback { params, tell ->
val name = params.extensionName()
val version = params.extensionVersion()
val sourceCrxFilePath = Path(params.extensionCrxFile())
val targetCrxFilePath = Path("$name-$version.crx")
try {
Files.copy(sourceCrxFilePath, targetCrxFilePath)
} catch (e: IOException) {
throw RuntimeException(e)
}
tell.cancel()
})
Now, you can load the required extension in Chrome Web Store and click the “Add to Chrome” button. The callback will be invoked, and the CRX file of the extension will be copied to the specified location.