Knockout: Bindings do not work when model is from Chrome extension background page

Created on 28 Sep 2014  Â·  14Comments  Â·  Source: knockout/knockout

In Chrome extensions, you can reference js variables from from different pages. So, in a standalone options.html page, you can reference JS objects from a background.html page like so:

// In background.js
window.myObj = { a: ko.observable('test'); };

// In options.js
var obj = chrome.extension.getBackgroundPage().myObj;

However, when I try to apply the model to my options.html page, it seems to not work correctly if I use the object from the background page.

ko.applyBindings(chrome.extension.getBackgroundPage().myObj);
// input box contains "function d(){if(0<argum..."

If I just do it with a local object, it works correctly.

ko.applyBindings({ a: ko.observable('test'); });

I've inspected both objects (the one referenced from the background page and the local one) in the console, and they look completely identical. Why does knockout not apply the bindings correctly when using the object from the background page?

Doesn't seem to be a timing issue either as I've tried unapplying and reapplying the model manually from the console. Works fine if I give it the model as a local object, but never works correctly when referencing the model from the background page.

I can provide an example/test extension if needed.

Most helpful comment

I agree, the patch is clunky; just a rough idea :). Thanks for referencing the other issue; it is pretty much the same thing (I suspect multiple pages in a Chrome Extensions are implemented much the same way as iFrames).

The "workaround" is simpler. Just do:
ko = chrome.extension.getBackgroundPage().ko
in any page that does not include the knockout js file, and only have one page ever actually include it.

Yea, documentation would help; maybe have a small section on Chrome Extensions and iFrames in the docs somewhere. Also, I'm not sure if there's a way to detect this state as some warning or error message would have been helpful too. However, I think being able to detect it is equivalent to just having a fix, and nothing very elegant comes to mind right now.

Thanks for the help!

All 14 comments

@jylin Thanks for reporting — I do not see immediately where the issue might lie, so an example might be illuminating. Cheers

It might be some sort of security issue.

Here's a gist:
https://gist.github.com/jylin/c8bfaf4860a5e0ae803e

You can clone it, then in Chrome go to the URL "about:extension". Check the developer mode button, load the unpacked extension by clicking the button and pointing to the folder just cloned. Click on the "Options" button in the extension page to open the page to see the problem.

Thanks @jylin.

I wonder if the item being returned is a prototype of ko.observable. I just wonder this because the little snippet function d(){if(0<argum looks like what the ko.observable() text looks like.

What does ko.isObservable(chrome.extension.getBackgroundPage().myObj) return?

What is the result of chrome.extension.getBackgroundPage().myObj()?

Sorry for not testing this out directly, but I am a little crunched for time - but hope the above may give a little insight.

Just an aside, if you wish to disable unsafe-eval in your content security policy you can (will need to) switch to a custom binding provider, e.g. knockout-secure-binding.

Thanks for your thoughts @brianmhunt. That pointed in the right direction. I think see what's going on now.

Basically, in this setup of a Chrome Extension, knockout exists in 2 different js contexts: background page and options page. So in the hasProperty() method, when comparing the "ko_proto" property of the object from the background page to ko.observable, it fails because it's not actually the same reference (i.e. it would be comparing ko.observable from options.js to chrome.extension.getBackgroundPage().ko.observable from background.js).

If I just hardcode that check to return true, then things seem to be correct. I'm wondering if there's a way for the hasProperty function to compare something that's a value to avoid this.

One way I can get around this with my code is also to just always use the 'ko' object from the background page (even in options.js).

Thanks for reporting back @jylin — that makes sense for what you were reporting.

Referencing a single ko object would seem to be the best workaround, if that resolves the issue.

I am not sure what might be an alternative preferable check for isObservable. That said, if the workaround fixes the issue (and there is no substantial, apparent way to improve Knockout here), are you okay closing this?

Cheers

I think it's fine to close if you don't think this is addressable, but I think angular seems to handle this correctly. It's a bit more annoying to debug when you need to always reference the ko object from the background page since you wouldn't be able to set the breakpoints in the current page.

I mean wouldn't something like just adding an isObservable method to the prototype of observables work? ko.isObservable could just call that. I mean it's a bit less 'pure', but that would fix the issue.

Thanks @jylin – Could you give a little code that spells out what you propose? Just want to make sure I understand. :)

Thanks for the suggestion with #1580, @jylin .

There is some general hesitation with adding magic properties, so I am not sure that is the "correct" answer for this problem (though it may be).

I feel like the workaround you suggest above by using the ko reference from the background page is the correct one. The question is whether Knockout should always do such a test (i.e. add a ko property to the observable/observable prototype), or whether it should be left to the user to inject the workaround.

In either case I believe it would help if ko.isObservable is documented to describe why it does not work in this scenario, and the workaround.

If I understand it correctly, the workaround might be e.g.

var originalIsObservable = ko.isObservable;
ko.isObservable = function (obs) {
  return originalIsObservable(obs) || other_page_ko.isObservable(obs);
}

What do you think @jylin ?

It might be useful to refer to the discussion we had before on this topic: #153.

I agree, the patch is clunky; just a rough idea :). Thanks for referencing the other issue; it is pretty much the same thing (I suspect multiple pages in a Chrome Extensions are implemented much the same way as iFrames).

The "workaround" is simpler. Just do:
ko = chrome.extension.getBackgroundPage().ko
in any page that does not include the knockout js file, and only have one page ever actually include it.

Yea, documentation would help; maybe have a small section on Chrome Extensions and iFrames in the docs somewhere. Also, I'm not sure if there's a way to detect this state as some warning or error message would have been helpful too. However, I think being able to detect it is equivalent to just having a fix, and nothing very elegant comes to mind right now.

Thanks for the help!

Thanks @jylin !

This worked for me! Thanks! My final code ended up looking like this:

    var backgroundWindow = chrome.extension.getBackgroundPage();
    ko = backgroundWindow.ko;
    //ko.bindingProvider.instance = new ko.secureBindingsProvider({});
    ko.applyBindings(vm,  document.body);
Was this page helpful?
0 / 5 - 0 ratings