March 28, 2007

Copying objects to an application's clipboard

The system clipboards Mozilla offers to XULRunner apps restricts us to primitive values: strings, numbers, etc. (I haven't yet figured out how they store images, but that's another discussion.) If you're trying to copy a DOM node to the operating system's clipboard, you quickly find out it's not that easy.

Since Verbosio's a XML editor, heavily reliant on the DOM, what am I supposed to do? Well... I cheated. Read on for more details.

UPDATE: A few commenters have suggested I have erred. I'd really appreciate someone coming up with some sample code to demonstrate a better way, using the native clipboard. (I may or may not use it, but it's nice to know.)

Reading the fine print

Neil Deakin wrote a guide to using the clipboard. Too bad I had a really hard time understanding it. Eventually, I figured I could set my DOM node (which implements nsISupports) as the second argument of nsITransferable's setTransferData() method. Said argument takes a nsISupports value. No problem, right?

Wrong. Read the inline JavaDoc comments, and it says nsISupportsPrimitives or nsIFlavorDataProvider (in nsITransferable.idl). A closer look at nsIFlavorDataProvider suggests again nsISupports is fine... but the JavaDoc says nsISupportsPrimitives.

DOM nodes don't implement nsISupportsPrimitives.

(Note: I consider this lousy interface design. If you have a IDL method that can only take two different types of arguments, and you try to combine them through a commonly supported interface, you're better off breaking it up into two different IDL methods with the more specific types of arguments.)

Storing objects in the application

What did I do to cheat? I added code to a XPCOM service for Verbosio which stores the object I want as a private global variable.

I already have a xeIVerbosioUtilsService.idl interface, and it now has a few new methods: copySelection(), clipboardType, and getClipboardContents().

These methods handle the system clipboard. The copySelection() method copies from the current document wrapper whatever selection it is, whether text or a DOM node. In the latter case, it serializes the node first, and stores the string in the system clipboard. The method also stores the retrieved node (usually a DocumentFragment) in the component's JavaScript context.

What if someone else (say, Microsoft Word) copies data into the clipboard? How does the utility component know when to forget the DOM node? That's where nsIClipboardOwner comes in to play. By setting a custom clipboard owner from the utility service, the utility can find out when the content it copied to the system clipboard is going away from the system clipboard. When that happens, the utility service can clear its own private object storage.

The clipboardType property simply defines what the content-type of the object stored in the application "clipboard" is. For DOM nodes, I chose an arbitrary "application/x-vnd.verbosio.dom-node" content-type.

I then use getClipboardContents if I have an actual object there, and can then retrieve the object that way.

Use case: Copying and pasting DOM nodes

Steps to reproduce in Verbosio:

  1. Verbosio > Edit testing app
  2. Open chrome://verbosio/content/verbosio.xul
  3. Switch to the Inspector view of the opened document.
  4. Select the first three children of the window element.
  5. Context menu, select "Copy".
  6. Open chrome://verbosio/content/verbosioOverlay.xul
  7. In the Inspector view for verbosioOverlay.xul, select a child of the overlay element.
  8. Context menu, select "Paste".
  9. Edit > Undo
  10. Edit > Redo

That much works. What works and doesn't work beyond that, I don't know yet. :-)

Help! Chrome macros?

This was ten steps that a user has to perform manually to test a simple feature. Sure, the key part of it takes maybe two steps.

Why does this aggravate me so much? Well, the edge of my MacBook, where the wrist comes off the keyboard / mousepad, is pretty sharp... I haven't cut myself yet (or broken the skin), but I do fear I could hurt myself if I get any kind of pound-the-computer-in-frustration moments.

What I need is some sort of record / playback mechanism for chrome apps. I'd do it myself, except for legal reasons relating to a previous employer, I'm not sure I can. Recording user actions for later playback could be very useful, especially for testing. Rumor has it someone I know is working on that, with no active input from me. I'd really love it if it wasn't rumor, and if it was soon. :-)

Posted by WeirdAl at 10:23 PM | Comments (3)

March 23, 2007

Never trust a software geek with a vacuum cleaner

Heading into the Mountain View Dev Day this weekend, where I expect to be fairly serious, I'm going to allow myself a bit of levity and offer a true story of cluelessness which would make Tim Allen ("What is the headlight for on that thing, anyway?") proud - and maybe even Jeff Foxworthy.

Ah, the rigors of bachelor life...

Last Saturday, I was trying to vacuum my apartment using my almost-new vacuum cleaner, which I bought from Sears in the fall. The last few times I'd tried to use it, though, were disappointing at best. It's got a nice HEPA air filter which wasn't getting filled. Plus it was making an unusual sound about three seconds after I started it. I figured the hoses were blocked (I'm not entirely clueless), so I took the big one from the filter to the vacuum motor out and looked through it. All I saw was blackness.

Since the hose itself was black plastic and had a bend through it at the end, I thought the blockage was further upstream - in the vacuum head, where the spinning brushes meet the hose. So I grabbed the screwdriver, dismantled the head enough to get at the hose (after unplugging the vacuum - there's clueless, and then there's Darwin award clueless) and did remove a couple pieces of plastic film. I looked through to make sure it was clear, and sure enough, I saw a little light poking through.

That should've been a clue that I wasn't thinking clearly (pun not intended), but I missed it and put the vacuum cleaner back together... or at least, I tried to. I spent the next half hour wrestling the roller back in, keeping the rubber loop extended, and trying to screw the plate back in... and it wouldn't close. No matter how I tugged on the plate and pushed on the head, the two wouldn't meet.

Today, the technician arrived at my apartment (a couple hours later than the scheduled time, but I'd moved since I bought the vacuum, so it wasn't entirely his fault). He started doing the same things I had already tried - namely, trying to force the plate back on. I pointed out I'd removed the spinning brush piece and put it back in. He looked, and spotted that the round side of the mounts was pointing out when the flat side should have been out. It was a pretty obvious mistake, and probably in the manual. But who reads the manual? :-)

I wasn't satisfied, though: the original problem, the non-vacuuming vacuum, hadn't been solved yet. Since the tech's already here...

He had me take the big hose, from vacuum head to filter, out again and check it. I noted it was black through it, at which point he said I should see a little light bouncing off the inside of it. Ohhhhhhhh....

So I split the hose in the center (where there's a convenient attachment for a longer hose), and sure enough, a good clump of dust was right in the center. Here was the real blockage, the real reason it wasn't working. Also a couple clumps of old tissues. The tech said, "You're supposed to pick up the big stuff!" Well, yeah, but I didn't know I had to pick up the medium stuff too.

Now the vacuum works, and I'm happy - as well as mildly embarrassed. But I can laugh about it. It took twenty minutes, tops, and Sears made a little more off my gracious incompetence.

I guess I just suck at vacuums.

Posted by WeirdAl at 3:02 PM

March 17, 2007

Design by contract in JavaScript, part 3: XPCOM class invariants

In the last DBC-JS article, I wrote about precondition and postcondition support for JavaScript in general. At the time, I lamented the inability to implement any kind of invariant checking. Today, I created a way to implement class invariants for JavaScript-based XPCOM components, courtesy of mozilla.org's nsIScriptableInterfaceInfo interface. (A class invariant is something that must be true when public methods finish executing without errors, in my interpretation.)

Read on for more details, including why it can't work reliably for non-component JS.

Wrapping a whole component's public code

The nsIClassInfo interface, which I described in some detail, has a useful getInterfaces() method. This method reports the public interfaces a component implements. With nsIScriptableInterfaceInfo, you can go one level deeper and determine all the methods, readonly attributes, and settable attributes of a component as well.

The mozilla.org example for this is the (alas, poorly documented) nsInterfaceInfoToIDL.js file and its doInterface() method. Mozilla code (with webservices enabled, which almost every Mozilla build has - Camino being a possible exception) can use this component to show you what an interface would look like, "decompiled" back into XPIDL.

What IDL files give us are the public methods and properties of a component. (IDL calls properties "attributes".) If you know what they are named (which nsIScriptableInterfaceInfo provides) and what type they are (method, settable property or readonly property - also available from nsIScriptableInterfaceInfo), you can construct a JavaScript object with matching public methods and properties.

Getters, setters and methods

Mozilla's JavaScript has long supported __defineGetter__ and __defineSetter__ for assigning a generic object ways to get and set a JavaScript property. (See the Core JavaScript 1.5 Guide for details.) Since you can also assign methods directly to an object, and the property assignment methods themselves take functions as the second argument, all you need is the name of the function you're calling and a new function to actually call it:

this.__defineGetter__(m.name, this._createGetter(m.name));

In the above example, this._createGetter(m.name) actually returns a function.

Enforcing a class invariant

The whole point of this is to call a generic invariant() method on the component:

    var contract = {
      precondition: function invariant_precondition() {
        if (contract.method_precondition) {
          contract.method_precondition.apply(this, arguments);
        }
      },

      method_precondition: null,
      body: aFunc,
      method_postcondition: null,

      postcondition: function invariant_postcondition() {
        if (contract.method_postcondition) {
          contract.method_postcondition.apply(this, arguments);
        }
        base.invariant();
      }
    }

This contract, processed through the getContractFunction() function, sends each individual property getter, property setter, and method call in an interface through the class invariant check after the original component has finished the user's initial request. The method_precondition and method_postcondition calls refer to (presumably private!) methods on the component for additional DBC assertion checks.

Returning the invariant-wrapped component

To wrap the component in the invariant contract, a couple extra changes are necessary. In the component's factory code, the component itself isn't returned; the invariant contract is:

      createInstance: function createInstance(outer, iid) {
        loadSubscripts();
        if (outer != null)
          throw Components.results.NS_ERROR_NO_AGGREGATION;
        var component = new aConstructor();
        var ecmaDebug = getDebugPref();
        var retval = ecmaDebug ?
                     new InvariantContract(component) :
                     component;
        return retval.QueryInterface(iid);
      }

Obviously, QueryInterface still must be supported on the invariant contract:

  // nsISupports
  QueryInterface: function QueryInterface(aIID) {
    return (this._baseComponent.QueryInterface(aIID)) ? this : null;
  },
  
  toString: function toString() {
    return "[InvariantContract " + this._baseComponent.toString() + "]";
  }

So the real component is silently stored as a property of the invariant contract (what the user receives). Thanks to the interface info duplication and the __defineGetter__, __defineSetter__ and method definition work, the invariant contract essentially duplicates and wraps around the real component. The real component can work totally oblivious to the existence of the invariant contract. Ultimately, the end-user should see no difference between the real component and the contract wrapped around it. (Unless, of course, the contract is broken and a bug exists.) It might execute a bit slower, but debug code shouldn't show up for the end-user anyway.

Modifying XPCOM components to support class invariants

First, every XPCOM component which needs an invariant must implement an invariant() method:

DocumentWrapper.prototype = {
  // ...
  // Design-by contract invariant
  invariant: function invariant() {
    if (this._isInitialized) {
      dump("*** Entering invariant for XUL document wrapper\n");
      assert(this.wrappedDocument, "No wrapped document?");
      assert(this.selectionRange, "No selection range?");
      assert(this.pathToDocument, "No path to document?");
      assert(this.txmgr, "No transaction manager?");
      assert(this.wrappedSource, "No wrapped source?");
      assert(this.publicURI, "No public URI?");
      dump("*** Exiting invariant for XUL document wrapper: success\n");
    }
  },
  // ...
}

You may notice the invariant requires an initialization value be true. This is because creating a component and initializing it for use are two different things. In this respect it isn't a true class invariant, but within the restrictions of XPCOM it's quite reasonable. (XPCOM does have error codes specifically for NOT_INITIALIZED and ALREADY_INITIALIZED. Using them is strongly recommended.)

Second, you have to load the ECMA Debug script I wrote. Currently it lives at chrome://verbosio/content/tools/ecma-debug.js, but I think I'll move it (for Verbosio) to resource://app/ecma-debug.js. You can always find the latest version of the script in Verbosio's CVS repository. The code is under the GPL/LGPL/MPL tri-license.

Third, you have to modify your factory code as I showed above to return the invariant contract pseudo-component.

Fourth, to actually run the invariant contract, you have to set the javascript.debug preference to true.

Why couldn't this work with normal JavaScript?

Of course, you can define getters, define setters, set methods, and wrap around any JavaScript object. For most of JavaScript, it would work fine. However, arrays make this impossible.

With arrays, a user can set a property on the array and nobody ever finds out about it. For example, suppose your object says:

{
  a: 3,
  b: [4, 5, 6]
}

You can write code to watch the "a" property. You can write code to watch the "b" property (to make sure people don't replace it). But you can't write code to watch the elements of the "b" property (the array). If I say yourObj.b[3] = 7, there's no way your code can check for it in every case.

Sure, you can write code to watch for someone setting the '3' property on "b". But what if I set yourObj.b[1000000] = Infinity? It's simply not supported. Bug 312116 would fix this, but I wouldn't hold my breath. Similarly, properties of properties are hard to keep an eye on.

So why does this work in XPCOM? The answer is simple, and surprising. XPIDL doesn't define an primitive array attribute type for properties. Oh, yes, there's nsIArray, but that's an object with a length property, not a plain array value. XPIDL also lets you return an array from a method, but methods are okay. Bottom line: XPIDL asks you to create the array from scratch each time - and because this means your original component doesn't expose its native arrays to the world, you're protected. (You can still return a native array if you want that directly belongs to your component and affects how the component works, but that's not generally accepted practice.)

Conclusion

Given that I was able to implement this in a single afternoon, I'm really quite surprised to see there is no equivalent for C++-based XPCOM components. I would have at least expected to see a NS_INVARIANT macro established in nsDebug.h, but there's no such animal.

I do believe that design-by-contract is very important - as important as automated testing and documentation. Used in tandem with automated testing, it can make the code more robust. Contracts, when broken, define a bug - either in the code the contract checks, or in the contract itself. DBC adds reliability and assurance that things work. I'd very much like to see DBC included as a standard part of Mozilla 2 code, and in debug code written for XULRunner applications such as Firefox or Verbosio.

Thanks for reading!

Posted by WeirdAl at 9:40 PM | Comments (1)

March 10, 2007

Verbosio progress, 03/10/2007

I wouldn't normally post again so soon, except that I have finally fixed Verbosio bug 16341. A little string replacement, and I'm finally able to generate a clean DOM.

Thanks to Ben Bucksch, who was willing to give up a bit of sleep to help me decide what to work on next. That would be clipboard operations - copying being the more difficult one, because that would mean copying nodes. It's not as glamourous as other ideas I have to work on, but it is necessary.

Posted by WeirdAl at 7:55 AM

March 7, 2007

Verbosio progress, 03/07/2007

Robert Sayre recently told me DTD's are just about useless. I disagree: they're worse than useless - at least, the way Mozilla deals with them. :-)

Specifically, the only thing Mozilla code really uses DTD's for is chrome localizations of labels, accesskeys, etc. But if you're dealing with chrome:// URL's in the XUL application you're editing, you really don't want cross-contamination from the editor's own chrome files. Factor in DTD's calling on and loading other DTD's, and you have the beginnings of a royal mess.

It's a mess of my own making, though, because I want a complete object model for the XML documents I edit, not just the source code. Also, Mozilla's use of DTD's is appropriate for 99% of current uses; I just happen to require a bit more careful handling. It's the price I pay, although hacking around it required two whole weeks for me to figure out...

More in the extended entry.

The first thing I realized is I needed a DTD document wrapper - basically, a container for DTD documents. However, XML documents also have inline DTD's - in the form of internal subsets. Basically, every XML document can have a DOCTYPE declaration:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
              "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

Mozilla handles extra DTD code in a DOCTYPE tag by placing it in the internal subset:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
              "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" [
  <!ENTITY hello.world "Hello World! This is an entity declaration in the internal subset.">
]>

Localization DTD's load into a XUL document via a SYSTEM entity:

<!DOCTYPE window [
  <!ENTITY % verbosioLocale SYSTEM "chrome://verbosio/locale/verbosio.dtd">
  %verbosioLocale;
]>

DTD files themselves have the same constraint. So for Verbosio, I needed not only a DTD document wrapper (for DTD files), but also a DTD subset wrapper (for DTD entity declarations within a XML document). This meant yet another new interface, xeIDTDWrapper, and a pair of new components - one implementing xeIDTDWrapper and xeITextDocumentWrapper for the DTD files, and another implementing just xeIDTDWrapper for the internal subset. Throw in a reference to the subset wrapper on xeIXMLDocumentWrapper, and we're halfway home.

Next, I cleaned up the virtual:// protocol implementation, moving all three JavaScript components into a common file. I also rewrote the interface for the virtual file service, simplifying and modernizing it.

Then Benjamin Smedberg set us up the bomb and dropped the bomb on us. The new MozillaBuild system works beautifully... for building Mozilla's source code. My Verbosio project? Not so much. An ancient bug in CVS (unfixed in MSYS's packages, but fixed years ago in CVS itself) broke Verbosio's build process. My reliance on cygpath (which CygWin has but MSYS doesn't) was another build regression, which I've only partially fixed. (I still need to fix launching the test Verbosio from the default Verbosio.)

Back on DTD handling, I spent several hours teaching Verbosio how to get DTD's from its own sources and include them via a virtual:// protocol (replacing chrome://foo/locale/foo.dtd with virtual://chrome//foo/locale/dtd). Unfortunately, that didn't work. Mozilla (wisely, in my opinion) only loads DTD files in its internal subset from a chrome:// protocol. (Well, it might allow more, but it wouldn't allow virtual://). The result is a ton of missing DTD entities.

Next step: forego trying to load these external DTD's, and just copy them from the various DTD wrappers into the internal subset. It's a dirty, rotten thing to do, but ultimately necessary. Besides, it happens to be exactly what Mozilla does natively with chrome-based DTD's.

Once I fixed that, my test code for this was still failing. The cause of that failure, though, pleased me greatly: Verbosio was trying to look up chrome://global/locale/textcontext.dtd in the XUL application it was editing. The global and toolkit chrome packages come from native XULRunner code, and not from an individual XUL application. In other words, Verbosio was parsing everything from the target application that the target application knew about (that's what made me happy). The XUL application wouldn't have the details on these packages. But Verbosio - being based on the same XULRunner code - would have the details, and could safely load DTDs in these chrome packages from its own chrome repository. A few special exclusions for global and toolkit chrome, and everything finally worked!

Except for one little detail: I was still calling on the native Verbosio application for its own chrome files (not just XULRunner's). That was why I filed Verbosio bug 16341 in the first place. It's not something detectable from JavaScript, really - it all happens within the chrome protocol handler, which is not obligated to tell JavaScript anything. After re-reading the xeIDTDWrapper interface, I figured out how to remove the offending %entityName; declarations before parsing the XML document. Finally, I can say definitively I get a clean XML document without borrowing from Verbosio's own chrome DTD files.

However, I know for a fact there are a ton of bugs in my DTD handling. <!ELEMENT> and <!ATTLIST> declarations would break my DTD wrapper code completely. So would any other valid DTD tag, except <!ENTITY> and comments. I still haven't implemented DTD editing. There's still potential for cross-package locale confusion. Most significantly, I have not yet figured out how to tie XML entity references to the matching DTD entity and file. I have some ideas, but nothing solid.

My biggest disappointment over the last two weeks is that the virtual:// protocol code is still untested. I still think it should work, but the initial implementation - working with DTD's - proved impossible. I believe its first active testing will come when I adjust URL's in the rest of an editable XML document (the part before and after the doctype tag) to refer to virtual files.

Changelog:

2007-03-07 09:41  ajvincent

	* src/core/components/documentFile.js: Bug 16341, part one:
	  complete DTD fix-up for parsing of chrome:// DTD's.  Remove
	  entity declarations from the external source.

2007-03-07 01:06  ajvincent

	* src/:
	  document-packs/xul-application/components/xul-application.js,
	  markup-languages/xul/components/xul-document.js: Lose some very
	  annoying dump statements.

2007-03-07 01:04  ajvincent

	* src/core/components/: DTDDocumentWrapper.js, documentFile.js:
	  Fall back on getting DTD sources from stored wrappers.

2007-03-06 22:34  ajvincent

	* src/: core/components/DTDDocumentWrapper.js,
	  core/components/documentFile.js,
	  core/components/virtualProtocol.js, core/idl/xeIDTDWrapper.idl,
	  core/idl/xeIDocumentFile.idl, core/idl/xeIVirtualFile.idl,
	  core/idl/xeIVirtualFileService.idl,
	  markup-languages/xul/components/xul-document.js: Update virtual
	  protocol code for a cleaner API, and attempt (unsuccessfully, so
	  far) tying it into DTD handling for XUL documents.

2007-03-06 12:57  ajvincent

	* src/: core/components/DTDDocumentWrapper.js,
	  document-packs/xul-application/components/xul-application.js: Add
	  handling for global, toolkit chrome into DTD wrappers.

2007-03-06 12:55  ajvincent

	* src/core/components/documentFile.js: For files that don't exist,
	  throw an exception.

2007-03-06 12:54  ajvincent

	* src/core/components/xeBaseURIMap.js: Fix up local path for
	  Windows when pathToDocument has forward slashes.

2007-03-06 00:06  ajvincent

	* src/make-project.pl: Yet another regression:	need to create the
	  directory the XULWidgets, JSLib libraries will go into.

2007-03-05 23:47  ajvincent

	* src/make-project.pl: Build process regression:  XULWidgets, JSLib
	  weren't copied properly.

2007-03-05 23:28  ajvincent

	* src/: core/components/DTDDocumentWrapper.js,
	  core/components/xeTestEngine.js, core/idl/xeIDTDWrapper.idl,
	  document-packs/xul-application/components/xul-application.js,
	  markup-languages/xul/components/xul-document.js: Add DTD internal
	  subset parsing to XUL document wrapper, and clean up DTDWrapper
	  code.  Test engine almost passes, but dies trying to get a global
	  chrome file.

2007-03-05 23:09  ajvincent

	* src/core/: components/verbosio-utils.js,
	  idl/xeIVerbosioUtilsService.idl: getDTDFragment should not need
	  the index where the doctype tag begins.

2007-03-05 16:58  ajvincent

	* src/make-project.pl: Fix app.properties to refer to the Win32
	  file system under MSYS.  Not fixed yet:  Setting BIN_PATH.

2007-03-05 16:00  ajvincent

	* src/make-project.pl: Branched JSLib, XUL Widgets into Verbosio
	  project; use them instead of checking them out while building.

2007-03-05 13:50  ajvincent

	* src/externals/: jslib_current.xpi, xulwidgets_current_trunk.xpi:
	  Add jslib, XULWidgets packages as of March 5, 2007 to Verbosio's
	  source; stop trying to check them out for now.  (Workaround for
	  bug with CVS 1.11, included with MozillaBuild for Windows.)

2007-03-01 21:47  ajvincent

	* src/core/: components/DTDDocumentWrapper.js,
	  idl/xeIDTDWrapper.idl, idl/xeIXMLDocumentWrapper.idl: Make
	  xeIDTDWrapper a standalone interface, and allow XML documents to
	  have a DTD wrapper for the doctype's internal subset as a
	  property.

2007-03-01 19:04  ajvincent

	* src/misc/uuid.txt: Missed checkin for uuid reservations.

2007-03-01 16:49  ajvincent

	* src/core/: components/DTDDocumentWrapper.js,
	  idl/xeIDTDWrapper.idl, idl/xeIXMLDocumentWrapper.idl: Initial
	  landing of DTD document wrapper, xeIDTDWrapper interface.

2007-03-01 16:41  ajvincent

	* src/core/content/tools/ecma-debug.js: ECMA Debug:  Check the
	  debug preference only once per script load.  assertWithStack
	  should use dumpJSStack.

2007-03-01 10:42  ajvincent

	* src/core/components/: virtual-fileService.js,
	  virtual-nsIChannel.js, virtual-protocol.js, virtualProtocol.js:
	  Consolidate virtual protocol handler, channel, and file service
	  into one JavaScript file.
Posted by WeirdAl at 10:47 AM