I’m writing a chrome extension written in Kotlin and I ran into the following issues. In chrome there is a content script, a background script and a popup page. Now I load my Kotlin code into the background script and into the popup script. However, there seems to be two independent instances of my code that are completely separated, e.g. I can’t access any data from the background script in the popup.
If I understand it correctly this is because the Kotlin code compiles to a javascript module that has no public methods; it only calls the main method but is otherwise not accessible.
To circumvent this problem I store my shared data in the background window object:
window.data = data
From the popup I can access this data using:
val backgroundWindow: dynamic = chrome.extension.getBackgroundPage()
backgroundWindow.data
I have two issues.
- Is there a nicer solution than that? Can I compile Kotlin into a javascript lib format so that I can access method from a module?
- In my previous example, when I cast the data to the correct class it works fine:
val data: Data = backgroundWindow.data
However when I do:
process(backgroundWindow.data as Data)
the “as” throws an exception even though it is indeed of type Data. (Is that because there are two module instances and they both define the Data $metadata$ object?)
try compileKotlin2Js.kotlinOptions.moduleKind = “plain”
other options are plain, amd, commonjs, umd.
kotlinjs itself is compiled to umd
oooh.
I have looked at Kotlin.isType implementation.
I would just monkeypatch it to pass Since classes are actually same code it should work ok. I think.
If I understand it correctly this is because the Kotlin code compiles to a javascript module that has no public methods; it only calls the main method but is otherwise not accessible.
Incorrect, Kotlin exposes all public classes, properties and functions to JavaScript, see http://kotlinlang.org/docs/reference/js-to-kotlin-interop.html
However, there seems to be two independent instances of my code that are completely separated, e.g. I can’t access any data from the background script in the popup.
Do you experience similar issue when creating extension directly in JavaScript?
(Is that because there are two module instances and they both define the Data $metadata$ object?)
This depends on what is Data
. If it’s an interface, the issue is in $metadata$
property. However, if it’s a class, Kotlin.isType
uses JavaScript instanceof
operator. And the problem there is not in Kotlin, but in JavaScript classes with different prototypes (from different scripts). Did you try backgroundWindow.data.unsafeCast<Data>()
?
BTW,
val backgroundWindow: dynamic = chrome.extension.getBackgroundPage()
Do you know that you can declare typed headers, so that you don’t have to cast dynamic
. See http://kotlinlang.org/docs/reference/js-interop.html#external-modifier
@Konstantin-Khvan I’m actually using kotlinFrontend/webpack to also be able to import npm libs so unfortunately the “plain” option does not help.
@Alexey.Andreev Maybe I describe a bit more what I’m doing. My Kotlin generated webpack bundle is included in the background page and in the popup page. In both pages the main method is called and depending on where I am I start my background or my popup code from the main method. Now I need a way to share data between these two independent main method calls. I tried to use a singleton object to store some information in the background page but when I tried to access this object in the popup the information got lost. For this reason I believe the module is loaded twice, i.e. the singleton exist twice.
A solution would be to load the bundle only in the background script and then access the module from the popup.js but I was not able to do that. Could you please give an example of how to access methods from a kotlin generated webpack bundle from plain js? What I want to do in the plain popup.js that is loaded by chrome is something like that:
myKotlinModule.showPopup();
Regarding the cast. Yes it is a class and not an interface and the unsafeCast is working.
The reason why I did a dynamic cast is that I store my custom object in the backgroundWindow. I’m already using typed headers for defining the chrome api.
I ran into further problems with the unsafeCast approach of using an object coming from a different module. I realized that functionality that requires type information such as enum comparison or extension functions stops working. So a solution to access an already loaded Kotlin module would be much appreciated!
so extensions source loads twice in separate js envs and has api for interaction like web workers?
in that case “clean” js way is using serialized json dto’s. There is no need for actual serialization, external declarations should be enough (https://kotlinlang.org/docs/reference/js-interop.html).
No the chrome extension background page and the popup share the same js environment. What I’m doing at the moment is to only include the webback bundle in the background page. In the background page I store an helper object in the background window:
class MyModule() {
fun showPopup(popupWindow: Window) {
Popup().show(popupWindow)
}
}
fun main(args: Array<String>) {
/**
* Workaround to make background functionality available to the popup
*
* The problem is that the compiled Kotlin bundle has no public methods. By storing MyModule in the
* background window it can be access from the popup.
*/
val dynamicWindow: dynamic = window
dynamicWindow.myModule = MyModule()
}
I can access MyModule from the popup.js as follows:
var bgWindow = chrome.extension.getBackgroundPage();
var module = bgWindow.myModule;
// use the correct Kotlin method mangling:
module.showPopup_ofitn1$(window);
Not very nice but this works fine so far. I only still have some minor problems with “as” casts. For example, when I create new html elements in the popup. No idea why this still happens…
kotlin exports modules to window (because ‘this’ is window when called globally) in umd mode at least. No need to make manual exports.
Try window.kotlin in console.
Module name is defined by js output file name. In umd mode line 10 should look like
root.modulename = factory(typeof modulename === ‘undefined’ ? {} : modulename, kotlin);
where root is substituted by ‘this’ which is window when using script tag
Also class is not module.
This is not true for a webpack bundles. Both window.modulename and window.kotlin are undefined.
so where does cast errors are from? popup uses separate bundle compiled from kotlin code? or do you include bundle twice?
I use my latest solution, i.e. only include the bundle once when loading the background page. I did some debugging. Here is the “as” cast that fails:
window.document.createElement("p") as HTMLParagraphElement
It fails in:
Kotlin.isType = function(object, klass)
at this instanceof:
var klassMetadata = klass.$metadata$;
if (klassMetadata == null) {
return object instanceof klass;
}
This shouldn’t fail…
I guess the reason why it fails is that there are actually two window objects.
- The background window (where I load my js bundle)
- The popup window (where I create the new HTML element and do the “as” cast)
it is not guess, you showed code accessing second window
var bgWindow = chrome.extension.getBackgroundPage();
which reassures that there are two separate envs, and object sharing api. you just shared the js bundle with all your code
my guess in turn is supposed usage for chrome api is webworkers like, load same or different js bundle both in background page and popup, then implement interaction/sharing via json js objects.