JEP 108 - Background Page

12 views
Skip to first unread message

Myk Melez

unread,
Feb 19, 2010, 1:47:21 PM2/19/10
to mozilla-la...@googlegroups.com
It's not clear what the use cases for the background page JEP are. I was talking with Atul about it, and he mentioned that the idea originated in the hidden window iframes I used in my original implementation of dynamic personas, which Atul then reused in some later work.

Google Chrome implements a feature called Background Pages that satisfies the need for extensions to have "a single long-running script to manage some task or state". But in Jetpack, the "main" script of Jetpack extensions already provides this feature.

Jetpack's "main" script does differ from Chrome's Background Pages in that it provides only a Javascript context, whereas Chrome's Background Pages provides a DOM, but DOM manipulations in a Jetpack extension can be performed in the contexts of the documents to which the outcomes of those manipulations are applied.

I'd be interested in hearing more about the use cases that the JEP is intended to satisfy.

-myk

Daniel

unread,
Feb 19, 2010, 2:11:57 PM2/19/10
to mozilla-labs-jetpack
Say I wanted a script like Google Maps to instantiate and use it to
pipe in all sorts of data via the channels it creates then use that
data elsewhere?

An argument you could raise on the surface would be to "get the stuff
you need via XHR from within the jetpack's js context". The catch is
that Google Maps and many other script solutions assume the presence
of DOM elements that they then rely on to perform their actions (a
instance of such needs can be seen in Google Maps' map bound related
geo calls: http://code.google.com/apis/maps/documentation/introduction.html#GLatLng)

Background pages enable DOM reliant mash-ups and make DOM fragment
creation based on those mash-ups easier. I for one really would like
to see this in the 0.2 feature set.

- Daniel

On Feb 19, 10:47 am, Myk Melez <m...@mozilla.org> wrote:
> It's not clear what the use cases for the background page JEP

> <https://wiki.mozilla.org/Labs/Jetpack/Reboot/JEP/108> are. I was


> talking with Atul about it, and he mentioned that the idea originated in
> the hidden window iframes I used in my original implementation of
> dynamic personas, which Atul then reused in some later work.
>
> Google Chrome implements a feature called Background Pages

> <http://code.google.com/chrome/extensions/background_pages.html> that

Myk Melez

unread,
Feb 20, 2010, 5:52:26 PM2/20/10
to mozilla-la...@googlegroups.com
On 02/19/2010 11:11 AM, Daniel wrote:
> Say I wanted a script like Google Maps to instantiate and use it to
> pipe in all sorts of data via the channels it creates then use that
> data elsewhere?
>
That's a very good use case and one that is poorly satisfied by existing
mechanisms, since one cannot easily inject that Google Map page into an
existing document (nor execute it in the context of an extension's main
script).

Thanks, that makes the utility of this feature much clearer!

-myk

Myk Melez

unread,
Feb 22, 2010, 9:02:47 PM2/22/10
to mozilla-la...@googlegroups.com
However, upon reflection, I'd like to hear more about the specifics of
the use case. After loading a Google Maps page, what sorts of things
might an extension want to do with the page?

-myk

Felipe Gomes (:felipe)

unread,
Apr 9, 2010, 6:54:10 PM4/9/10
to mozilla-labs-jetpack
Hey Daniel, I have two questions about this proposed API:

- I didn't see on the API how the URL of the chosen page will be
specified. Will we allow both trusted and untrusted pages to be
opened?

- It is not very clear to me what are the directions of chrome access
to be allowed here. Will the webpage itself be able to have chrome
privileges (this is also related to the previous question), or just
the code that we specify to run [posibly periodally] on it?


Felipe

Felipe Gomes (:felipe)

unread,
Apr 9, 2010, 7:05:10 PM4/9/10
to mozilla-labs-jetpack
[sorry if this is post is duped, I think google groups ate my previous
e-mail]

Hey Daniel, I have two questions about this proposed API:

- How will the URL of the page that we want on the background be
specified? Will we allow both trusted and untrusted pages to be
opened?

- I didn't quite comprehend the chrome privileges directions for the
API. For example, will the page (possibly untrusted?) running be able
to access chrome, or is it just the code that we set to run [possibly
periodically] on it that will have the jetpack's chrome context?


Thanks!
Felipe

Daniel

unread,
Apr 12, 2010, 8:22:15 PM4/12/10
to mozilla-labs-jetpack
Essentially you would be given a hidden content frame that you could
inject code into to run.

Here is an example of one way to change the page location if there
wasn't a specific method exposed for it:

$page.run( function(){
window.location = 'addons.mozilla.org';
});

The functions you passed in would have their this contexts bound to
the window object of the Page Worker.

You could load trusted and untrusted pages into it, and here is an
example of a periodical function being run:

$page.run({ interval: 10000 }, function(){
return window.someNode;
});

Essentially, many web conventions assume the presence of a DOM, we
thought it would be nice to provide an open web sandbox for you in
addition to the Jetpack's more unique chrome sandbox.

The API examples are just strawman ideas, feedback is welcome! :)

On Apr 9, 4:05 pm, "Felipe Gomes (:felipe)" <felipe.go...@gmail.com>
wrote:

Felipe Gomes (:felipe)

unread,
Apr 14, 2010, 1:00:31 AM4/14/10
to mozilla-labs-jetpack
Thanks for the reply. At the moment I'm still learning how jetpack
works, and I'm mostly blocked by understanding how to inject code
correctly into the page's context.

Anyway, I have a somewhat "pseudo-code" implementation going on, which
made me notice some finer details of the api that I wanted to get
feedback:

- why do you propose $page to be a global object? Wouldn't it be
better to be a regular module like the others? in this case a jetpack
would also be able to load various background-pages if desided. e.g.:

let Page = require("background-page");

let mypage1 = new Page();
mypage1.reset();
mypage1.run(function() {
return window.location;
});

- you suggested that if called with a interval or timeout parameter,
the return value of _run_ should be an array as: [timerId to clear it,
the return value of the function]. However, if using timeouts or
intervals, it doesn't make sense for the function to return any
values, since it won't be executed immediately. Should we just return
the timer id then?

(also, at this point I'm not sure that it's possible to return a value
in the immediate case either. It depends on how the code will be
injected.. I believe most of the methods to inject code will be
async.. This doesn't matter to this thread right now, I just wanted to
add an observation)


- and just to confirm, at some point it was mentioned Web Workers..
but we don't want to use real Web Workers to run the actions here,
right? As far as I know, Web Workers can't modify the DOM, and since
one of the main use cases is to access the DOM, we probably have to
stick with traditional functions


Felipe

Daniel

unread,
Apr 14, 2010, 1:34:37 AM4/14/10
to mozilla-labs-jetpack
The API doesn't need to be a global, it was strawman spec'd that way
before we went the route of using constructed objects. It should
probably be changed to adhere to the same pattern as the others.

Web Workers are something we should enable, but they are not dynamic
because they require a static js file to evaluate passed arguments.
DOM mods can be done asyncronously using document fragments.

Our goal with Page Workers, is to have a wide-open mash-up engine that
you can do really interesting things with in the context of a
persistant DOM++ environment.


On Apr 13, 10:00 pm, "Felipe Gomes (:felipe)" <felipe.go...@gmail.com>

Atul Varma

unread,
Apr 14, 2010, 1:26:47 PM4/14/10
to mozilla-la...@googlegroups.com, Felipe Gomes (:felipe)
On 4/13/10 10:00 PM, Felipe Gomes (:felipe) wrote:
> - why do you propose $page to be a global object? Wouldn't it be
> better to be a regular module like the others? in this case a jetpack
> would also be able to load various background-pages if desided. e.g.:
>
> let Page = require("background-page");
>
> let mypage1 = new Page();
>
Unfortunately, this actually can't be done with CommonJS modules,
because the object provided by require() is the target module's exports
object, which is "owned" by the CommonJS loader: a module can add things
to it, but they can't e.g. replace it with a function/constructor.
There's a similar problem in Python-land where you see code like "import
StringIO; foo = StringIO.StringIO()".

In other words, the more CommonJS-compliant, but less usable way to
write your above code would be:

var backgroundPage = require("background-page");
var mypage1 = new backgroundPage.Page();

Ugh. Aside from the fact that it's very easy to forget new and spend the
next hour figuring out why your code doesn't work, this is part of why I
tend to favor factory functions over constructors, e.g.:

var mypage1 = require("background-page").create();

It's also worthwhile to note that due to the way JS' operator precedence
works, this code doesn't do what one would expect:

var mypage1 = new require("background-page").Page();

This actually associates the operand of new with require rather than
with Page. In other words, it's trying to use require as a constructor,
and the results are powerfully confusing. The "correct" way to write
the above code is something like:

var mypage1 = new (require("background-page")).Page();

But I'm not even sure, I'd have to check. :(

On the one hand, it's easy to bikeshed over this stuff, but on the
otherhand there are real developer ergonomics in play here, particularly
when we start considering how easy/hard it is for coders to accidentally
forget something and end up with hard-to-debug code.

- Atul

Drew Willcoxon

unread,
Apr 14, 2010, 1:34:38 PM4/14/10
to mozilla-la...@googlegroups.com
We decided awhile ago to write constructors in the high-level APIs such
that |new| doesn't need to be used. We haven't been following that
convention in the lower-level stuff, though, so I guess it doesn't apply
there.

For the context menu module I've been using this helper:

function makePublicConstructor(privateCtor) {
return function PublicCtor() {
let obj = {
constructor: PublicCtor,
__proto__: privateCtor.prototype
};
privateCtor.apply(obj, arguments);
return obj;
};
}

It wraps a "private" constructor -- i.e., one not exported that needs to
be called with |new| -- with a function that can be used with or without
|new|. Used like this:

exports.PublicCtor = makePublicConstructor(PrivateCtor);

It makes a liar out of instanceof though, but I don't see a way around
that if we want to let people omit |new|.

Drew

Atul Varma

unread,
Apr 14, 2010, 1:50:20 PM4/14/10
to mozilla-la...@googlegroups.com, Drew Willcoxon
Oh nice!!! I dig makePublicConstructor(). Not having instanceof is a
bit unfortunate, though I think that won't work with Array objects no
matter what (since each module has its own sandbox and therefore its own
Array prototype).

I also didn't realize that we actually decided to write constructors in
the high-level APIs such that new doesn't need to be used--my bad.
However, I don't think this resolves the case of "new
require("foo").Thing()"; though perhaps one solution there is to have
require() check to make sure its this isn't a freshly-minted object or
something, and raise a helpful exception if so. I guess another
alternative might be to just tell folks not to use new, though I'm not
sure how we could communicate that effectively to everyone who uses Jetpack.

Oh, wait... There's another solution here, I think. The problem we're
trying to solve isn't so much that we want to let folks leave out
new--in fact, I'm a fan of Python's "there's only one way to do it"
philosophy--but rather that if folks do things in the "wrong" way, they
should be notified as early and as clearly as possible so they don't
have to waste the next hour(s) figuring out what's wrong. So in other
words, creating constructors like this might be a solution:

function Foo(x) {
if (this.console) {
// console is a Jetpack global that's always truthy, which means
// someone just called us without the `new` operator!
throw new Error("Please call this constructor with the 'new'
operator.");
}
}

This way we solve the problem while still allowing instanceof to be
used, and preserving interface monotony (i.e., "there's only one way to
do it").

Thoughts?

- Atul

Myk Melez

unread,
Apr 14, 2010, 2:45:28 PM4/14/10
to mozilla-la...@googlegroups.com, Atul Varma, Felipe Gomes (:felipe)
On 04/14/2010 10:26 AM, Atul Varma wrote:
> There's a similar problem in Python-land where you see code like
> "import StringIO; foo = StringIO.StringIO()".
Presumably this would also be true of any future native implementation
of modules for JavaScript.

> In other words, the more CommonJS-compliant, but less usable way to
> write your above code would be:
>
> var backgroundPage = require("background-page");
> var mypage1 = new backgroundPage.Page();

Or (for the common case in which the symbols of modules you import don't
overlap):

var Page = require("background-page").Page;
var mypage1 = new Page();


And, for APIs that export multiple exported symbols that a consumer
wants to access:

var { Foo: Foo, Bar: Bar, Baz: Baz } = require("metasyntactic-variables");


> The "correct" way to write the above code is something like:
>
> var mypage1 = new (require("background-page")).Page();
>
> But I'm not even sure, I'd have to check. :(

I think it may actually be:

var mypage1 = new (require("background-page").Page)();


> On the one hand, it's easy to bikeshed over this stuff, but on the
> otherhand there are real developer ergonomics in play here,
> particularly when we start considering how easy/hard it is for coders
> to accidentally forget something and end up with hard-to-debug code.

Yup, so it's worth discussing at length and depth, despite the danger of
the bikeshed.

-myk

Atul Varma

unread,
Apr 14, 2010, 3:17:35 PM4/14/10
to Myk Melez, mozilla-la...@googlegroups.com, Drew Willcoxon
On 4/14/10 11:48 AM, Myk Melez wrote:
I'm a fan of Python's philosophy too, but John Resig makes a good case for simplifying JavaScript APIs by making |new| optional for constructor functions. And I don't think we gain much by enforcing one approach in our implementations.
Ah, good point. JS has so many current users and history behind it; a new language "designed from scratch" might be good to make monotonous, but not necessarily an existing one, in which enforcement of monotony--or obj-cap "language subsetting" for that matter--could just end up causing confusion and frustration. :)
However, we would benefit from sticking to a common style in all documentation and examples, so developers who look at them to figure out how to use the APIs don't receive mixed messages. And new-less constructors are preferable there, both because they are simpler and because they don't require an extra set of parentheses when accessed via dot notation on a require call.

And, FWIW, a new-less constructor is very much like a factory, except that, as with constructor functions, its name is indicative of the thing it creates, which is especially useful for APIs that let you construct multiple kinds of objects (f.e. Item, Menu, and Separator in the context-menu module).
Good points all! I am cool with this, yo.

- Atul

Reply all
Reply to author
Forward
0 new messages