Subclassing OMNode

63 views
Skip to first unread message

Michael Allan

unread,
Aug 21, 2011, 6:29:21 PM8/21/11
to lib-gwt-svg
Greetings to the list,

I have a question about subclassing the OMNode wrappers. I wonder if
I am doing it properly. Here's how I subclass SVGTextElement, for
example:

class MyText extends OMSVGTextElement
{
MyText( com.google.gwt.dom.client.Document doc )
{
super( (SVGTextElement)DOMHelper.createElementNS( doc,
SVGConstants.SVG_NAMESPACE_URI,
SVGConstants.SVG_TEXT_TAG ).cast() );
}
}

It runs OK. Should I carry on with this pattern, or will I run into
problems down the road? I notice that OMSVGDocument's
createSVGTextElement() is instead calling a native JSNI method, which
in turn calls back to OMSVGTextElement's Java constructor, but I don't
understand why.

Thanks BTW for coding lib-gwt-svg! It's been working well for me,
--
Michael Allan

Toronto, +1 416-699-9528
http://zelea.com/

Lukas Laag

unread,
Aug 22, 2011, 4:40:46 PM8/22/11
to lib-g...@googlegroups.com

Hi Michael,

Actually this approach is not something I would recommend as indeed some
functionality will not work if the library is used this way.

I have written an article explaining the general design choices made for
the library.
http://www.vectomatic.org/lib-gwt-svg/lib-gwt-svg-goals-and-design

As explained in the article, the library needs to manipulate two layers
of objects: low-level overlay types, which are just a JSNI layer
exposing native javascript objects to GWT ; high-level wrapper types
(called wrapper because each wrapper object wraps an overlay type
object) ; the wrapper objects provide support for interfaces, needed for
polymorphism and event support ; this functionality cannot be
implemented with overlay types.

Obviously the wrapper type has a reference on the overlay type, to which
it delegates almost every method; however there cannot be a
back-reference from the overlay type to the wrapper type, because that
would create cyclic references and thus memory leaks (this explains why
createSVGTextElement is written in this manner).

For the parts of the library which need to convert an overlay type back
to a wrapper type (typically: methods which return an object), a new
wrapper is instantiated every time. However, from the end-user point of
view, this is transparent, for two reasons:
1/ the wrapper types are "hollow": the have no instance variables of
their own, because the state is entirely maintained by the overlay type,
so it does not matter that a new one is instantiated
2/ the equals and hashCode method is overloaded in OMNode so that two
wrappers are equal if they wrap the same overlay type.

Consider the following:
OMSVGGElement g = ...;
MyText t = ...;
g.appendChild(myText);
MyText t = (MyText)g.getLastChild(); // BOOM ! (because
OMSVGGElement.getLastChild() delegates to the overlay type, which
returns the parent, as another overlay type, which gets converted to
wrapper type; however, as the conversion is done in OMNode.convert which
has no knowledge of MyText, an OMSVGTextElement is created instead of a
MyText and the cast will fail).

Thus at the moment subclassing wrapper classes of the library is not
supported. Your approach will work, but only in limited cases and could
bite you back.

I am really not sure how I could add that to the library. Even if I
found a cleverer way to implement it, there would still be the problem
that some objects are created explicitly by the user (in that case, they
can specify the subclass to use), while other are just retrieved (for
instance by evaluating an XPath in an imported SVG graphic); for the
latter, how could one know if the user wants it to be represented by a
custom subclass and if so which subclass ?

Regards

Lukas


Michael Allan

unread,
Aug 23, 2011, 5:56:03 PM8/23/11
to lib-g...@googlegroups.com
Thanks for answering so clearly, Lukas. It's an interesting problem.
What I need are stateful UI components that render as SVG elements -
full "widgets" or "ui objects" in GWT parlance. My initial approach
was to use composition or "wrapping". I wrapped an OMElement and
added my state variables to the wrapper.

Then I confronted the problem you mention. My UI naturally follows
the structure of the SVG DOM, but whenever I dive into that DOM in
order to traverse the structure, I cannot resurface in my UI
component. The DOM is blind to the wrappers.

So I began to mirror the DOM structure in wrapper state, so I wouldn't
ever have to descend into the DOM itself. At that point, I figure I
was beginning to code the equivalent of GWT's Widget infrastructure.

That was some months ago. Recently I returned to working on the code
and I had to shake my head, because it seemed too complicated. I
leaped at the idea of using inheritance instead of composition; simply
subclassing whatever OMNode I want to decorate with state:

OMSVGTextElement > MyText

As you point out, however, that does not solve the DOM navigation
problem. At the end of the story, I still need an SVG equivalent for
GWT's Widget architecture, in particular for its panels and other
containers. I imagine these would be built on G elements instead of
DIV and SPAN.

But first I need a better understanding of lib-gwt-svg.

Lukas Laag wrote:
> ... explaining the general design choices made for the library.
> http://www.vectomatic.org/lib-gwt-svg/lib-gwt-svg-goals-and-design

I notice some typos in the section "DOM Level 2". The 1st paragraph
says GWT's high level widgets (comparable to your OMNode) are under
gwt.user.client. But those are simple subclasses of the native
overlays; they still inherit from JavaScriptObject. (I don't see
their purpose.) See for example:
http://google-web-toolkit.googlecode.com/svn/javadoc/latest/com/google/gwt/user/client/Element.html
But the widgets/ui objects that actually wrap these native elements
(like your OMNode does) are under gwt.user.client.ui.

The links in the 2nd paragraph have typos in both body and URL. I
think they should be:

Low level: org.vectomatic.dom.svg.impl
High level: org.vectomatic.dom.svg

> As explained in the article, the library needs to manipulate two
> layers of objects: low-level overlay types, which are just a JSNI
> layer exposing native javascript objects to GWT ; high-level wrapper
> types (called wrapper because each wrapper object wraps an overlay
> type object) ; the wrapper objects provide support for interfaces,
> needed for polymorphism and event support ; this functionality
> cannot be implemented with overlay types.

I guess this is part of the rationale for GWT's widgets, too. OTOH
GWT widgets are stateful. I can subclass them too, and decorate them
with additional state. The equivalence between OMNode and widgets
breaks down here.

> Obviously the wrapper type has a reference on the overlay type, to
> which it delegates almost every method; however there cannot be a
> back-reference from the overlay type to the wrapper type, because
> that would create cyclic references and thus memory leaks (this
> explains why createSVGTextElement is written in this manner).

I had thought of using one of these methods to set the back reference
to the wrapper:

* element.setPropertyObject( "wrapper", wrapper );
* node.wrapper = wrapper; // native JavaScript

I think the JavaScript garbage collector can handle cyclic references.
IE had a problem once, but it was apparently fixed in v.8:
http://msdn.microsoft.com/en-us/library/dd361842%28v=vs.85%29.aspx

If we *could* use this approach, then OMNode.convert() might do the
reverse lookup and return the wrapper. We could then navigate the DOM
and still resurface into the widget/wrapper at every step. We would
not have to reconstruct it (a weakness of OMNode, you admit) and this,
in turn, would open the door to stateful wrappers. Or is there
another problem I don't see?

GWT does not take this approach. A look at the code for SimplePanel
indicates that they mirror the DOM hierarchy within their container
widgets. That's how they solve the problem. Then again, GWT hides
the underlying DOM nodes, wheras lib-gwt-svg exposes them
transparently at the top level (an approach I prefer).

> ...


> I am really not sure how I could add that to the library. Even if I
> found a cleverer way to implement it, there would still be the
> problem that some objects are created explicitly by the user (in
> that case, they can specify the subclass to use), while other are
> just retrieved (for instance by evaluating an XPath in an imported
> SVG graphic); for the latter, how could one know if the user wants
> it to be represented by a custom subclass and if so which subclass ?

I guess the implicitly created (XPath) nodes would not point to custom
wrappers, wheras the explicitly created ones might, assuming that's
how they were constructed.

I could easily be overlooking something. A possible problem is that a
new wrapper might clobber an old one by stealing its underlying node
without knowledge of the old wrapper. But that doesn't seem like a
show stopper.

What do you think?

--
Michael Allan

Lukas Laag

unread,
Aug 24, 2011, 6:26:01 PM8/24/11
to lib-g...@googlegroups.com
Hi Michael,

First, thanks for point the typos in the article, I have fixed them.

Regarding memory leaks issues, my understanding is based on what I had
read at
http://code.google.com/p/google-web-toolkit/wiki/DomEventsAndMemoryLeaks
(and linked articles) at the time I wrote the initial version of the
library. I am not an expert in the field, I do not know if what was said
at the time still holds true or not. I would need to do some research
and testing on all supported browsers to see if cyclic references
between DOM and javascript objects are no longer an issue. If so,
changing the mechanism should be fairly straightforward, as every
conversion goes through a single point (OMNode.convert). I am obviously
very interested in the topic, as avoiding to create a new wrapper
whenever one converts an overlay type to a wrapper type would make the
code more efficient.

In the mean time I think your idea based on Element.setPropertyObject
could be a good bypass, as it lets you attach the additional state
directly to the overlay type. In this case you do not even need
inheritance; you can just package your additional state as a separate
object and use composition. With proper helper methods / patterns, that
could yield not too ugly/chatty code.

Lukas

Michael Allan

unread,
Aug 26, 2011, 5:48:47 AM8/26/11
to lib-g...@googlegroups.com
Hi Lukas,

I'm running a modified version of the library now, and it seems to be
working OK.

> Regarding memory leaks issues, my understanding is based on what I
> had read at
> http://code.google.com/p/google-web-toolkit/wiki/DomEventsAndMemoryLeaks
> (and linked articles) at the time I wrote the initial version of the
> library. I am not an expert in the field, I do not know if what was
> said at the time still holds true or not. I would need to do some
> research and testing on all supported browsers to see if cyclic
> references between DOM and javascript objects are no longer an
> issue. If so, changing the mechanism should be fairly
> straightforward, as every conversion goes through a single point
> (OMNode.convert). I am obviously very interested in the topic, as
> avoiding to create a new wrapper whenever one converts an overlay
> type to a wrapper type would make the code more efficient.

Those documents make no reference to the IE8 fix. I guess it's all
the same for GWT, because they need to support IE7 and older browsers
anyway. But I think we're on safer ground; no browser that renders
SVG is likely to have a broken garbage collector.

Looking at the code, I suspect they were at least as worried about the
possibility of app developers registering handlers on the DOM and then
forgetting to unregister them. It isn't only DOM handlers that can
leak memory that way, but the registrations between widget and node
are probably the most common. Putting a single event bridge there
(__listener) gives them a choke hold on it.

There's also a node -> OMNode event bridge in lib-gwt-svg (__wrapper),
and I guess it has similar benefits. I decided to expand on it and
make it a permanent link. Here's my changes:
http://zelea.com/var/db/repo/lib-gwt-svg/rev/b3387c83c8a0

Here's a modified jar for use with GWT 2.3.0:
http://zelea.com/project/lib-gwt-svg/release/
I'm running it here in a prototype app:
http://zelea.com/y/vw/xf/#s=G!p!sandbox*f&c=DV

If it tests out OK, then it should speed up DOM traversals. More
important (at least for my app), it lets us subclass SVG elements like
they were widgets, adding custom methods and state variables and still
being able to navigate using the standard DOM methods. In that sense
(thanks to the way you designed the library), SVG is even better than
widgets. :-)

I hope this approach is useful for others, too. Please let me know if
you see any problems,

Lukas Laag

unread,
Aug 28, 2011, 7:26:24 PM8/28/11
to lib-g...@googlegroups.com

Hi,

I have reviewed your patch. From a programming point of view, I have no
doubts that this is much better as the change has two benefits:
+ it lets one subclass the classes of the library
+ it improves performance when navigating from overlay types to wrapper
types.
However I am not as confident as you are that memory leaks will never be
a problem. I think I will add a method to OMNode to let developers free
the __wrapper back pointer, just in case. I will update the trunk svn
repository and let you know when this is done (hopefully in day or two)
Many thanks

Lukas

Michael Allan

unread,
Aug 28, 2011, 8:05:46 PM8/28/11
to lib-g...@googlegroups.com
Lukas Laag wrote:
> ... I will update the trunk svn repository and let you know when

> this is done (hopefully in day or two) Many thanks

You're welcome. I don't mind putting effort into lib-gwt-svg, because
I plan to use it extensively.

Here's the raw patch:
http://zelea.com/var/db/repo/lib-gwt-svg/raw-rev/b3387c83c8a0

Lukas Laag

unread,
Aug 29, 2011, 4:31:06 PM8/29/11
to lib-g...@googlegroups.com
Hi Michael,

I have added your patch to the trunk and updated the googlecode
repository (http://code.google.com/p/vectomatic/source/list, see rev
503).

I also added an OMNode.cleanup() method to let users break the
connection from overlay types to wrapper types in case this connection
creates DOM-javascript reference cycle collection problems. I changed
the patch a bit to use an assertion instead of a test + returned
boolean value. I also decided to add no-arg constructors to all the
wrapper classes which represent elements, to simplify instantiation
and subclassing. You can now write:
OMSVGCircleElement c = new OMSVGCircleElement();
and
public class MyCircle extends OMSVGCircleElement() {
}

Please note that the trunk contains what will become
lib-gwt-svg-0.5.3, and is currently based on gwt-2.4.0-rc1. There are
quite a lot of other stuff in there too, notably classes to manage SVG
paint and dash arrays. I plan to release all this with detailed
release notes and articles detailing the new features when gwt2.4.0 is
officially launched (which should be soon now).

Regards

Lukas

Reply all
Reply to author
Forward
0 new messages