After an evaluation, GNOME has moved from Bugzilla to GitLab. Learn more about GitLab.
No new issues can be reported in GNOME Bugzilla anymore.
To report an issue in a GNOME project, go to GNOME GitLab.
Do not go to GNOME Gitlab for: Bluefish, Doxygen, GnuCash, GStreamer, java-gnome, LDTP, NetworkManager, Tomboy.
Bug 599661 - in-progress work on extensionSystem
in-progress work on extensionSystem
Status: RESOLVED FIXED
Product: gnome-shell
Classification: Core
Component: general
unspecified
Other All
: Normal normal
: ---
Assigned To: gnome-shell-maint
gnome-shell-maint
Depends on:
Blocks:
 
 
Reported: 2009-10-26 14:37 UTC by Colin Walters
Modified: 2009-12-18 16:21 UTC
See Also:
GNOME target: ---
GNOME version: ---


Attachments
[StTheme] Add functions to dynamically load/unload stylesheets (7.17 KB, patch)
2009-10-26 14:37 UTC, Colin Walters
needs-work Details | Review
Add global.log, global.logError functions (3.74 KB, patch)
2009-10-26 14:37 UTC, Colin Walters
reviewed Details | Review
Add extensionSystem (7.97 KB, patch)
2009-10-26 14:37 UTC, Colin Walters
none Details | Review
[StTheme] Add functions to dynamically load/unload stylesheets (7.65 KB, patch)
2009-11-18 21:36 UTC, Colin Walters
committed Details | Review
Add global.log, global.logError functions (4.57 KB, patch)
2009-11-18 21:36 UTC, Colin Walters
committed Details | Review
Add extensionSystem (8.21 KB, patch)
2009-11-18 21:36 UTC, Colin Walters
needs-work Details | Review
[lookingGlass] Port more to CSS (3.36 KB, patch)
2009-11-18 21:36 UTC, Colin Walters
accepted-commit_now Details | Review
[lookingGlass] Add primitive extensions list (3.59 KB, patch)
2009-11-18 21:36 UTC, Colin Walters
committed Details | Review
Add extensionSystem (14.29 KB, patch)
2009-12-02 17:13 UTC, Colin Walters
needs-work Details | Review
Add extensionSystem (15.30 KB, patch)
2009-12-02 19:44 UTC, Colin Walters
committed Details | Review
Port more to CSS (3.35 KB, patch)
2009-12-02 19:44 UTC, Colin Walters
committed Details | Review
[St] Implement text-decoration: [underline|strikethrough] (4.87 KB, patch)
2009-12-02 19:44 UTC, Colin Walters
committed Details | Review
Make link.js into a St.Button, delete unused link imports (4.60 KB, patch)
2009-12-02 19:45 UTC, Colin Walters
committed Details | Review
Add primitive extensions list (5.83 KB, patch)
2009-12-02 19:45 UTC, Colin Walters
committed Details | Review

Description Colin Walters 2009-10-26 14:37:47 UTC
First two patches are review-able, extensionSystem needs a way
to easily create one (but you can do it now manually).
Comment 1 Colin Walters 2009-10-26 14:37:50 UTC
Created attachment 146265 [details] [review]
[StTheme] Add functions to dynamically load/unload stylesheets

For implementing extensions, we want the ability to add a stylesheet
dynamically, and unload it as well.
Comment 2 Colin Walters 2009-10-26 14:37:53 UTC
Created attachment 146266 [details] [review]
Add global.log, global.logError functions

These log to a display in lookingGlass; rough equivalent of
the Firebug "console.log".
Comment 3 Colin Walters 2009-10-26 14:37:56 UTC
Created attachment 146267 [details] [review]
Add extensionSystem
Comment 4 Dan Winship 2009-10-26 21:25:33 UTC
Comment on attachment 146265 [details] [review]
[StTheme] Add functions to dynamically load/unload stylesheets

>+  if (!g_file_get_contents (filename, &contents ,&length, error))
>+    return NULL;

Fix the comma before &length while you're there.

>+st_theme_load_stylesheet (StTheme    *theme,

this will at least leak memory and maybe worse if two extensions load the same stylesheet. (Although the existing code already has that problem if we @import the same stylesheet more than once...)

>+  application_stylesheet = parse_stylesheet (theme->application_stylesheet, NULL);
>+  theme_stylesheet = parse_stylesheet (theme->theme_stylesheet, NULL);
>+  default_stylesheet = parse_stylesheet (theme->default_stylesheet, NULL);

>+                  import_rule->sheet = parse_stylesheet (filename, NULL);

These probably need to pass a GError and g_warn or something on failure. Otherwise CSS-loading problems just turn into silent failure.

>- *Evaluates if a given additional selector matches an style node.
>+ *Evaluates if a given additional selec;tor matches an style node.

typo
Comment 5 Dan Winship 2009-10-26 21:44:39 UTC
Comment on attachment 146266 [details] [review]
Add global.log, global.logError functions

>+        this.text.clutter_text.line_wrap = true;

meh. StLabel should mirror ClutterText's properties or something.

>+    _onMappedNotify: function() {

it would be nice if it continued updating while it was visible too...

>+        for (let i = 0; i < Main._errorLogStack.length; i++) {

Although the contents of this loop are pretty simple, it would be safer to copy Main._errorLogStack to another variable first, and then iterate that, just in case something inside the loop causes an error to be logged... :)

(You could even just have a Main.getErrorLogStack() method, that returned and cleared Main._errorLogStack, and then I also wouldn't have to frown at the fact that you're modifying another module's private variables.)

>+    // Now monkey patch utility functions into the global proxy;
>+    // This is easier and faster than indirecting down into global
>+    // if we want to call back up into JS.
>+    window.global.logError = _logError;
>+    window.global.log = _logDebug;

You can just say "global" rather than "window.global" at that point.

Is there any reason to want "global.logError" rather than "Main.logError" though?

>+function _log(category, msg) {
>+    _errorLogStack.push({offset: new Date().getTime() - _startDate.getTime(),
>+                         category: category,
>+                         message: msg });
>+}

it would be swell if this used "Array.slice(arguments, 1).join(' ')" for the message, so you could say log("foo:", x, y, z)
Comment 6 Colin Walters 2009-11-18 17:37:36 UTC
(In reply to comment #4)

> >+st_theme_load_stylesheet (StTheme    *theme,
> 
> this will at least leak memory and maybe worse if two extensions load the same
> stylesheet. (Although the existing code already has that problem if we @import
> the same stylesheet more than once...)

Well, this isn't intended for direct use by an extension in the normal case; we automatically load an extension stylesheet from the well-known filename "stylesheet.css" in the extension directory, which will be a different full path for each one.
Comment 7 Colin Walters 2009-11-18 21:36:12 UTC
Created attachment 148080 [details] [review]
[StTheme] Add functions to dynamically load/unload stylesheets

For implementing extensions, we want the ability to add a stylesheet
dynamically, and unload it as well.
Comment 8 Colin Walters 2009-11-18 21:36:18 UTC
Created attachment 148081 [details] [review]
Add global.log, global.logError functions

These log to a display in lookingGlass; rough equivalent of
the Firebug "console.log".
Comment 9 Colin Walters 2009-11-18 21:36:26 UTC
Created attachment 148082 [details] [review]
Add extensionSystem

Fundamentals for loading extensions from a well known directory.
Comment 10 Colin Walters 2009-11-18 21:36:31 UTC
Created attachment 148083 [details] [review]
[lookingGlass] Port more to CSS
Comment 11 Colin Walters 2009-11-18 21:36:35 UTC
Created attachment 148084 [details] [review]
[lookingGlass] Add primitive extensions list

In the future this should be more like the Firefox Addons UI
probably with enable/disable/update checking, but stub it out
here for now.
Comment 12 Dan Winship 2009-11-23 17:41:55 UTC
Comment on attachment 148080 [details] [review]
[StTheme] Add functions to dynamically load/unload stylesheets

st_theme_load_stylesheet() calls insert_stylesheet(), but st_theme_unload_stylesheet() doesn't undo that, so even after unloading, the style sheet will still be in the hash tables.
Comment 13 Dan Winship 2009-11-23 17:47:09 UTC
Comment on attachment 148081 [details] [review]
Add global.log, global.logError functions

looks good. it seems like having a timestamp might be more useful than having an offset though?
Comment 14 Dan Winship 2009-11-23 17:50:41 UTC
Comment on attachment 148083 [details] [review]
[lookingGlass] Port more to CSS

yay css!
Comment 15 Owen Taylor 2009-11-24 23:13:57 UTC
Review of attachment 148082 [details] [review]:

Looks like a nice simple way of doing extensions. My only reservation is that unifying the manifest and the main Javascript file into a single JS file could conceivably cause problems if you installed an extension that was crashing by importing some buggy piece of C code; we could start in a emergency mode with all extensions disabled, but we couldn't show their names in the UI, because importing the extension.js would potentially trigger the problems. Would it make sense to make the manifest a JSON file separate from the main.js?

Some comments in detail.

::: data/sample-extension.js
@@ +4,3 @@
+
+const EXTENSION = {
+    name: __INSERT_NAME_HERE__,

Any sort of template like this should include comments that document the expected form (capitalization, sentence fragment or real sentence, etc.) of Name and Description, or the end result will be inconsistency.

@@ +7,3 @@
+    /* You must have a unique identifier for your extension.
+     * The simplest way to get one is to use the 'uuidgen' Unix utility.
+     * A uuid looks like this: 3636ab59-5f9c-4dc3-ba94-a637ef8741a8

Of the two styles of uuid supported by Firefox, I find 'firebug@software.joehewitt.com' a lot more self-documenting than '{e4a8a97b-f2ed-450b-b12d-ee082ba24781}' [that's GreaseMonkey, if it wasn't immediately obvious :-)]

::: js/ui/extensionSystem.js
@@ +13,3 @@
+    while ((info = fileEnum.next_file(null)) != null) {
+        let displayName = info.get_display_name();
+        allFiles[displayName] = info;

This feels like an abuse of Gio - it doesn't matter too much here - if you have multiple files that have the same display name in your extension you get what you deserve, but it seems if we write some "load a couple of files from a directory code", we should have some simple way of doing it that is generally correct, and not correct only making certain assumptions.

@@ +17,3 @@
+    fileEnum.close(null);
+    let baseErrorString = 'While loading extension from "' + dir.get_parse_name() + '": ';
+    let mainJs = allFiles['extension.js'];

If the file is extension.js the variable should be extensionJs.

@@ +38,3 @@
+    let extensionModule;
+    try {
+        global.add_extension_importer("imports.ui.extensionSystem.extensions", extensionName, dir.get_path());

I don't think gjs has relative imports, so I'd have to write:

 let Utils = imports.ui.extensionSystem.extensions.breadcrumbs.Utils;

? Why not shorten that to 'imports.extensions.breadcrumbs.Utils' ?

@@ +54,3 @@
+    //if (!meta.uuid) {
+    //    global.logError(baseErrorString + 'missing \'uuid\' property in EXTENSION');
+    //}

Don't see a point in having an *optional* uuid. The uuid seems like where we'd want to use for an "enabled" GConf key off of too.

@@ +71,3 @@
+function loadExtensions() {
+    let configPath = GLib.get_user_config_dir();
+    let extensionsPath = GLib.build_filenamev([configPath, 'gnome-shell', 'extensions']);

As discussed in person, I think we want to allow for system installed extensions from the get-go. The example I gave Colin was a company using specialized software (CAD, animation, etc.) that needed some customizations to the gnome-shell workflow, and wanted to put an extension on all their users computers. Distro packaging of extensions is another use case, though I don't know how much we want to emphasize that versus a user-installed model.

@@ +74,3 @@
+    let extensionsDir = Gio.file_new_for_path(extensionsPath);
+    try {
+        extensionsDir.make_directory_with_parents(null);

I don't like a loadExtensions function creating a directory as a side effect

@@ +76,3 @@
+        extensionsDir.make_directory_with_parents(null);
+    } catch (e) {
+        // Ignore existence errors

And what about typos, etc? Unfortunately, Javascript makes it hard to catch specific erorrs, and obviously we don't have the GError support for error codes yet either in gjs, but I think catch (e) { /* ignore everything without logging */; } should be stylistically verboten.

@@ +85,3 @@
+            continue;
+        let path = GLib.build_filenamev([extensionsPath, info.get_name()]);
+        loadExtension(Gio.file_new_for_path(path));

loadExtension(Gio.file_get_child(extensionsDir, info.get_name()));

::: src/shell-global.c
@@ +520,3 @@
 
+gboolean
+shell_global_add_extension_importer (ShellGlobal *global,

Function docs would be nice; I was able to decipher the operation, but it's not exactly obvious.

@@ +558,3 @@
+                  G_IO_ERROR,
+                  G_IO_ERROR_FAILED,
+                  "%s", "Target object is not object");

Why bother with the GError if the errors that are going to be produced are human-unreadable?
Comment 16 Owen Taylor 2009-11-24 23:17:55 UTC
Review of attachment 148084 [details] [review]:

Looks like a fine start except for one unused chunk of code.

::: js/ui/lookingGlass.js
@@ +351,3 @@
+
+    _createNewExtension: function() {
+        let scriptPath = global.datadir + "createExtension.js";

I didn't see this JS in your patches (and this function is unused). Extension creation doesn't seem hard enough to me to really require automation - a page on the wiki should be fine.

@@ +356,3 @@
+            proc.run();
+        } catch (e) {
+        }

See comment on other patch about catch (e) {}
Comment 17 Colin Walters 2009-12-01 20:13:58 UTC
Comment on attachment 148081 [details] [review]
Add global.log, global.logError functions

Attachment 148081 [details] pushed as a4405be - Add global.log, global.logError functions
Comment 18 Colin Walters 2009-12-01 20:47:48 UTC
(In reply to comment #15)
> Review of attachment 148082 [details] [review]:

> Any sort of template like this should include comments that document the
> expected form (capitalization, sentence fragment or real sentence, etc.) of
> Name and Description, or the end result will be inconsistency.

I'm going to kill the in-tree template for now I think, in favor of a commented version on the wiki.

> 
> ::: js/ui/extensionSystem.js
> @@ +13,3 @@
> +    while ((info = fileEnum.next_file(null)) != null) {
> +        let displayName = info.get_display_name();
> +        allFiles[displayName] = info;
> 
> This feels like an abuse of Gio - it doesn't matter too much here - if you have
> multiple files that have the same display name in your extension you get what
> you deserve, but it seems if we write some "load a couple of files from a
> directory code", we should have some simple way of doing it that is generally
> correct, and not correct only making certain assumptions.

Hm, you could only get multiple files with the same display name if they were identical except for one or more runs of invalid unicode.  I'm not very concerned about this.  Ideally we'd skip files with invalid unicode...I could search for the replacement character I guess.
 
> I don't think gjs has relative imports, so I'd have to write:
> 
>  let Utils = imports.ui.extensionSystem.extensions.breadcrumbs.Utils;
> 
> ? Why not shorten that to 'imports.extensions.breadcrumbs.Utils' ?

I'll try; I think this might not work unless "imports.extensions" is itself a valid module.
 
> I don't like a loadExtensions function creating a directory as a side effect

I'd like it to exist if the shell's been run once, to make finding the place to add extensions more failsafe.  I guess it could be an explicit init() method called from main.js or the like?
 
> @@ +76,3 @@
> +        extensionsDir.make_directory_with_parents(null);
> +    } catch (e) {
> +        // Ignore existence errors
> 
> And what about typos, etc? 

What typos?  This is a hardcoded dir in the source.

> Unfortunately, Javascript makes it hard to catch
> specific erorrs, and obviously we don't have the GError support for error codes
> yet either in gjs, but I think catch (e) { /* ignore everything without logging
> */; } should be stylistically verboten.

Yes, it would be nice if we could catch specific errors.
> @@ +558,3 @@
> +                  G_IO_ERROR,
> +                  G_IO_ERROR_FAILED,
> +                  "%s", "Target object is not object");
> 
> Why bother with the GError if the errors that are going to be produced are
> human-unreadable?

You're saying that the error message is unclear?  That's definitely true, but trying to explain the problem would be too much bother; It shouldn't happen in practice.  This error is basically just an index into the source code.  I suppose for errors like this it'd be nice to have some sort of error allocation scheme "E005105" or the like.  

I need to set the GError though because the one above it *can* throw in practice (it's where we parse the user code).
Comment 19 Owen Taylor 2009-12-01 21:58:19 UTC
(In reply to comment #18)

> > ::: js/ui/extensionSystem.js
> > @@ +13,3 @@
> > +    while ((info = fileEnum.next_file(null)) != null) {
> > +        let displayName = info.get_display_name();
> > +        allFiles[displayName] = info;
> > 
> > This feels like an abuse of Gio - it doesn't matter too much here - if you have
> > multiple files that have the same display name in your extension you get what
> > you deserve, but it seems if we write some "load a couple of files from a
> > directory code", we should have some simple way of doing it that is generally
> > correct, and not correct only making certain assumptions.
> 
> Hm, you could only get multiple files with the same display name if they were
> identical except for one or more runs of invalid unicode.  I'm not very
> concerned about this.  Ideally we'd skip files with invalid unicode...I could
> search for the replacement character I guess.

I think you are missing my point - if the only way of using Gio conveniently from Javascript is by a method that the Gio maintainer (Alex) is very clear is incorrect, then we have a problem.
 
> > I don't like a loadExtensions function creating a directory as a side effect
> 
> I'd like it to exist if the shell's been run once, to make finding the place to
> add extensions more failsafe.  I guess it could be an explicit init() method
> called from main.js or the like?

That would be OK.
 
> > @@ +76,3 @@
> > +        extensionsDir.make_directory_with_parents(null);
> > +    } catch (e) {
> > +        // Ignore existence errors
> > 
> > And what about typos, etc? 
> 
> What typos?  This is a hardcoded dir in the source.

Typos in the source code - if you wrote 

 extensionsDir.make_directory_with_pparents(null);

You'd have never have found the problem.

> > Unfortunately, Javascript makes it hard to catch
> > specific erorrs, and obviously we don't have the GError support for error codes
> > yet either in gjs, but I think catch (e) { /* ignore everything without logging
> > */; } should be stylistically verboten.
> 
> Yes, it would be nice if we could catch specific errors.

We either have to add the error codes, or search for string matches. (That obviously is bad for internationalization, so maybe you just need to fix the gjs bug.) Catching all errors without logging is not OK.

> > @@ +558,3 @@
> > +                  G_IO_ERROR,
> > +                  G_IO_ERROR_FAILED,
> > +                  "%s", "Target object is not object");
> > 
> > Why bother with the GError if the errors that are going to be produced are
> > human-unreadable?
> 
> You're saying that the error message is unclear?  That's definitely true, but
> trying to explain the problem would be too much bother; It shouldn't happen in
> practice.  This error is basically just an index into the source code.  I
> suppose for errors like this it'd be nice to have some sort of error allocation
> scheme "E005105" or the like.  

If it can't happen, then I'd say just g_error().
 
> I need to set the GError though because the one above it *can* throw in
> practice (it's where we parse the user code).

Are you sure? I think it doesn't get parsed until you do extensionModule = extensions[extensionName].extension;
Comment 20 Dan Winship 2009-12-01 22:05:21 UTC
(In reply to comment #19)
> Typos in the source code - if you wrote 
> 
>  extensionsDir.make_directory_with_pparents(null);
> 
> You'd have never have found the problem.

And for the record, I totally did that while hacking on the sidebar widgets, and it was incredibly confusing until I figured it out. (And then changed the catch{} clauses to all use logError.)
Comment 21 Colin Walters 2009-12-02 17:13:47 UTC
Created attachment 148931 [details] [review]
Add extensionSystem

Consumer documentation will live at http://live.gnome.org/GnomeShell/Extensions

In terms of implementation; basically we load extensions from the well-known
directories.  Add a GConf key to disable extensions by uuid.  There is a new
option --create-extension for the gnome-shell script which takes a bit of
interactive input, sets up some sample files, and launches gedit.

No extensions UI in this patch; that will come later.

https://bugzilla.gnome.org/show_bug.cgi?id=599661

ext
Comment 22 Colin Walters 2009-12-02 18:14:09 UTC
Comment on attachment 148931 [details] [review]
Add extensionSystem

inc updated patch
Comment 23 Colin Walters 2009-12-02 19:44:45 UTC
Created attachment 148942 [details] [review]
Add extensionSystem

Consumer documentation will live at http://live.gnome.org/GnomeShell/Extensions

In terms of implementation; basically we load extensions from the well-known
directories.  Add a GConf key to disable extensions by uuid.  There is a new
option --create-extension for the gnome-shell script which takes a bit of
interactive input, sets up some sample files, and launches gedit.

No extensions UI in this patch; that will come later.
Comment 24 Colin Walters 2009-12-02 19:44:52 UTC
Created attachment 148943 [details] [review]
Port more to CSS
Comment 25 Colin Walters 2009-12-02 19:44:57 UTC
Created attachment 148944 [details] [review]
[St] Implement text-decoration: [underline|strikethrough]

Move CSS handling of StLabel and StButton for their underlying
ClutterText objects into st_private, and implement support for
the underline and strikethrough St text-decoration properties.

Overline isn't implemented for lack of a corresponding Pango
attribute, and blink, well...
Comment 26 Colin Walters 2009-12-02 19:45:02 UTC
Created attachment 148945 [details] [review]
Make link.js into a St.Button, delete unused link imports

It's actually totally unused at the moment, but a future patch will use it.
Comment 27 Colin Walters 2009-12-02 19:45:10 UTC
Created attachment 148946 [details] [review]
Add primitive extensions list

In the future this should be more like the Firefox Addons UI
probably with enable/disable/update checking, but stub it out
here for now.
Comment 28 Colin Walters 2009-12-18 01:10:06 UTC
(In reply to comment #19)
> > Hm, you could only get multiple files with the same display name if they were
> > identical except for one or more runs of invalid unicode.  I'm not very
> > concerned about this.  Ideally we'd skip files with invalid unicode...I could
> > search for the replacement character I guess.
> 
> I think you are missing my point - if the only way of using Gio conveniently
> from Javascript is by a method that the Gio maintainer (Alex) is very clear is
> incorrect, then we have a problem.

This looks like the only unaddressed comment here...not sure how to proceed.  We're just testing this list for specific file names, it's not the case that we'll fail on seeing invalid utf8 files.

If we were instead looking for any file ending in .js, maybe then you need to pick some behavior (skip them, throw error etc.), but here we really just skip.

I don't see how this is specific to JS either, I would probably write similar code in c.
Comment 29 Owen Taylor 2009-12-18 15:33:39 UTC
(In reply to comment #28)
> (In reply to comment #19)
> > > Hm, you could only get multiple files with the same display name if they were
> > > identical except for one or more runs of invalid unicode.  I'm not very
> > > concerned about this.  Ideally we'd skip files with invalid unicode...I could
> > > search for the replacement character I guess.
> > 
> > I think you are missing my point - if the only way of using Gio conveniently
> > from Javascript is by a method that the Gio maintainer (Alex) is very clear is
> > incorrect, then we have a problem.
> 
> This looks like the only unaddressed comment here...not sure how to proceed. 
> We're just testing this list for specific file names, it's not the case that
> we'll fail on seeing invalid utf8 files.
> 
> If we were instead looking for any file ending in .js, maybe then you need to
> pick some behavior (skip them, throw error etc.), but here we really just skip.
> 
> I don't see how this is specific to JS either, I would probably write similar
> code in c.

I don't really understand why I'd write such code in JS or C. Could easily be:

+    let metadata = allFiles['metadata.json'];
+    if (!metadata) {
+        global.logError(baseErrorString + 'Missing metadata.json');
+        return;
+    }
+
+    let metadataFile = dir.get_child('metadata.json');

    let metadataFile = dir.get_child('metadata.json');
    if (!metadataFile.query_exists()) P
        global.logError(baseErrorString + 'Missing metadata.json');
        return;
    }

Etc. There certainly is a point that given enough files that you want to test for in a directory, it's more efficient to readdir() the directory instead of stat'ing files. If you were at that limit (I don't think you are close to it), then I think you could simply use .get_name() rather than the get_display_name() that you are using here ... gjs_string_from_filename()/gjs_to_from_filename() should take ASCII to ASCII and should produce something hashable (if not interpretable) for non-ASCII results.

That would require fixing:

      <method name="get_name" c:identifier="g_file_info_get_name">
        <return-value transfer-ownership="none">
          <type name="utf8" c:type="char*"/>
        </return-value>
      </method>
Comment 30 Colin Walters 2009-12-18 16:17:58 UTC
(In reply to comment #29)
> 
>     let metadataFile = dir.get_child('metadata.json');
>     if (!metadataFile.query_exists()) P
>         global.logError(baseErrorString + 'Missing metadata.json');
>         return;
>     }

Good idea, thanks!  Fixed.
Comment 31 Colin Walters 2009-12-18 16:21:19 UTC
Attachment 148084 [details] pushed as 2cb4dfb - [lookingGlass] Add primitive extensions list
Attachment 148942 [details] pushed as aa9d351 - Add extensionSystem
Attachment 148943 [details] pushed as c4a49b4 - Port more to CSS
Attachment 148944 [details] pushed as 55fbb9d - [St] Implement text-decoration: [underline|strikethrough]
Attachment 148945 [details] pushed as c0ff006 - Make link.js into a St.Button, delete unused link imports