With the commencement of my new job last week (okay, it's OfficeMax, part-time at $7/hr, but it's something), I decided I could finally afford to buy Ian Oeschger & Co.'s book, "Creating Applications with Mozilla". I'd read sample pages from books.mozdev.org several times, but having a book you can hold in your hand is very different from reading the sample pages online.
One thing the book doesn't cover (probably because it never occured to anyone that it would be necessary) is how to install an XPI from a command-line prompt. This matters because it's not clear how my poor Nvu 0.2, which wasn't really designed to browse the web, will install Abacus without hacking installed-chrome.txt (not every Nvu user will want to play with that file). The appropriate web-based installation instructions can be found in Chapter 6, Section 3.
It's becoming painfully obvious to me that Mozilla Composer wasn't really designed for extensibility along the lines of what I'm attempting with Abacus (namely, adding support for a secondary markup language).
For instance, Composer adds stylesheets based on the tab you've selected at the bottom. If you select the Tags tab, you get a special stylesheet with lots of yellow image boxes for your elements. So far, so good.
Except for how it applies the stylesheet...
I tried doing an XUL overlay for the stylesheet (EditorAllTags.css). That was unsuccessful. I figured out later that the stylesheet was applied through C++ code:
http://lxr.mozilla.org/seamonkey/source/editor/ui/composer/content/editor.js#1928
http://lxr.mozilla.org/seamonkey/source/editor/libeditor/html/nsHTMLEditor.cpp#3730
This stinks. It'd be much easier if we could just apply stylesheets from the Document Object Model, as DOM-2 Style suggests. I'm tinkering around with that idea (based on DOM-2 Events to tell me when to apply the sheet and when to kill it) to make the styling work properly.
It'd also make sense, from a undo/redo point of view, if the various settings used a single transaction manager, and created alternate "views" for the document being edited. The document itself would remain hidden from the user, though all manipulations the user carried out on a view of the document propagate down into the master doc through a transaction manager. I remember someone on the Composer team long, long ago saying this might be a possibility. (Until the Nvu source gets checked into Mozilla's tree, it'll be a little hard for me to check that...) Having that undo/redo done this way would make it easier/safer for external applications, like DOM Inspector, to become a part of Composer.
Finally, I cannot for the life of me figure out how in Composer a HTML 4.01 Transitional doctype gets in when the document I'm loading already has a doctype tag.
resource:///res/mathml.css , which I got used to using for XUL apps playing with MathML, wasn't in my Mozilla 1.7RC1 installed build. Bug 244384 @ b.m.o.
However, because I could be wrong on the correct reference of the MathML stylesheet, I've filed the bug as "UNCONFIRMED".
On another note, I've been wondering just where to put a post-editing stylesheet for the Abacus MathML editor. After all, documents which have been edited through Abacus should not require Abacus be installed to read them...
I goofed (a little). If you have an object literal:
foo = {
test: function() {
},test.precondition: function() { // WRONG
}
}
So I am adding a setContract() method to the ecmaDebug.js library. I'll upload the latest version in a few more days.
That way, I can say:
foo = {
test: function() {
},
test_precondition: function() { // WRONG
}
}
setContract(foo.test, foo.test_precondition, null);
I also added a bunch of asserts to the contract-management functions. There's no sense in applying a contract if the contract you apply is bogus (for example, if the precondition isn't a function or doesn't have a matching number of arguments).
(This entry is a follow-up of my design-by-contract blog entry a few days ago.)
Last night, while working on certain user-interface scripting, I caught myself using assert() to validate user input. This is not smart. The assert() function is intended for bugs in the source code. It only throws exceptions or warnings if there's a certain debug flag activated. Since the end-user product will have that debug flag off, the assert() does no good for checking user input.
There has to be some other bailout mechanism in place. Firing an exception with the throw statement works great... if you have something to catch it. But we only want to catch certain errors.
The first step is the creation of an exception constructor which is unique. In my assert() function, each error which leaves it has a name property of "AssertionError". This is unlike any other error JavaScript natively supports. It also identifies a particular class of exceptions to the application.
So, if I want a unique class of errors to catch, one of the best ways is to make sure each error created in that class has a specific name property.
function UserError(aMessage) {
var e = new Error(aMessage);
e.name = "UserError";
return e;
}
if (userDidSomethingWrong) {
throw new UserError("You broke one of my rules!");
}
In my particular application, I have a global object called editor. Each XUL button to execute a user-interface function would call a method of this editor object. For instance, if I want to do a search, I call editor.search().
Because of the beauty of the apply method of functions, I can do a little redirection and call another function instead which will then itself call search. (The apply method allowed me to write the constructNew function I talked about in my ecmaDebug.js script. The constructNew function may get renamed, though...) So, why not set up a special try...catch for user errors in an intermediary function?
editor = {
search: function() {
throw new UserError("search failed");
},watchUserErrors: function(methodName) {
var args = [];
for (var i = 1; i < arguments.length; i++) {
args[i - 1] = arguments[i];
}
try {
this[methodName].apply(this, args);
}
catch(e) {
if (e.name == "UserError") {
doSomethingWithTheError(e);
} else {
throw e;
}
}
},toString: function() {
return "[object AbacusEditor]";
}
}
One other possibility exists. If you note how the assert() function processes its results, it's almost a macro.
assert(false, "The sky is falling!", true);
works much like:
if (!false) {
throw new Error("The sky is falling!");
}
assertAboutInput(false, "The sky is falling!");
might work like:
if (!false) {
throw new UserError("The sky is falling!");
}
function assertAboutInput(mustBeTrue, errMsg) {
var throwException = arguments.length > 2 ? arguments[2] : false;
try {
if (!mustBeTrue) {
var aError = new UserError(errMsg);
throw aError;
}
}
catch(e) {
e.shiftStack(2) // this takes us to the actual stack error.
if (throwException) {
e.message = errMsg; // reset
throw e;
} else {
warn(e);
}
}
return mustBeTrue;
}
A couple years ago, I read an article in Dr. Dobb's Journal on a next-generation programming language called D. It was probably the only article in DDJ that I fully understood...
One of the concepts in the D language is “design-by-contract”. It was the original inspiration for my assert() function, which I rewrote to take advantage of Error.prototype.stack (more on this in a minute). Any function you write may have a set of “preconditions” which must be satisfied before the main function executes, and/or “postconditions” which must be satisfied after the main function executes.
The goal of DBC is to add sanity checks for a particular function. Quality Assurance personnel and developers can add assertions that demand the code act correctly (or at least, consistently). If a precondition, postcondition, or standard assert() fails, there is a bug: either in the actual function, or in the failed contract. (But of course, the code doesn't know which!)
With a debug flag enabled, the code executing the function runs the precondition and postcondition functions. Without the debug flag, it skips them.
The example from DDJ looked something like:
function getSquareRoot(x, rv) {
in {
assert(x >= 0);
}
out {
assert(rv * rv == x);
}
body {
rv = Math->sqrt(x);
return 0;
}
}
Tackling the first was relatively easy; I'd done something like it with a earlier assert() function (also blogged here). The catch was that I never got the scope to operate in the main function. It had to pass the expression to check as a string, it had to pass a contextual “this” object, etc. I cut that out, and simply made the first argument a Boolean expression, like the condition in an if() {} statement.
Of course, there is also the added challenge of making sure the JavaScript Console gets the right filename and line number information. Constructed errors always have the Error() constructor's filename and line number. (There's a bug to have these properties set with the original throw statement, but in my case they match.) So if I was going to throw any constructed Error(), the properties of that Error() would need resetting.
Enter the stack. Before the Mozilla 1.0 release, I came up with the idea that JavaScript errors should have stacks for debugging. I floated the idea past Boris Zbarsky and a couple others, and with their general support I filed bug 123177 for it. Brendan Eich leaped on it (I very rarely see so much enthusiasm to add a feature), and implemented Error.prototype.stack to give a basic breakdown of the functions by arguments, files and lines which were running when the error was thrown.
Assume I have the following:
function foo() {
assert(1 == 0);
}function assert(mustBeTrue) {
if (mustBeTrue) return;
try {
throw new Error(“bar error”);
}
catch(e) {
dump(e.stack + “\n”);
}
}
foo();
Error(“bar error”)@:0
assert(false)@chrome://foo/content/foo.xml:13
foo()@chrome://foo/content/foo.xml:7
@chrome://foo/content/foo.xml:19
So, to reset the Error() object's properties appropriately, I wrote this little function:
Error.prototype.shiftStack = function(aShiftLines) {
var aStackArray = this.stack.split("\n").splice(aShiftLines);
var lastColon = aStackArray[0].lastIndexOf(":");
this.fileName = aStackArray[0].substring(aStackArray[0].indexOf("@") + 1, lastColon);
this.lineNumber = aStackArray[0].substr(lastColon + 1);
this.stack = aStackArray.join("\n");
}
Thus, when I write:
assert(1 == 0, “1 does not equal 0.”, true)
AssertionError: 1 does not equal 0.
Filename: chrome://foo/content/foo.xml Line: 7
A good DBC code, in JavaScript, might look like this:
function getSquareRoot(x) {
return Math.sqrt(x);
}
getSquareRoot.precondition = function(x) {
assert(x >= 0, “x must be a real number greater than zero!”, true);
}
getSquareRoot.postcondition = function(x, rv) {
assert(rv * rv == x, “The returned value times itself does not equal the original argument!”, true);
}
I created an applyContract() function:
function applyContract(aFunction, thisObj) {
var args = [];
for (var x = 2; x < arguments.length; x++) {
args[x - 2] = arguments[x]; // we're just setting up arguments to pass.
}
if (typeof aFunction.precondition == 'function') {
aFunction.precondition.apply(thisObj, args);
}
var rv = aFunction.apply(thisObj, args);
if (typeof aFunction.postcondition == 'function') {
args.push(rv);
aFunction.postcondition.apply(thisObj, args);
}
return rv;
}
var root_3 = applyContract(getSquareRoot, this, 3); // == Math.sqrt(3) in the end...
What if you want to construct an object, and run that through preconditions and postconditions?
First, you need a function that can construct the object when called by the apply() method of functions. (Figuring out how to do so is obvious only in hindsight.)
function constructNew(aFunction) {
var args = [];
var argString = "";
for (var x = 1; x < arguments.length; x++) {
args[x - 1] = arguments[x];
argString += ",args[" + (x - 1) + "]";
}
argString = argString.substr(1);
alert("var rv = new aFunction(" + argString + ")");
eval("var rv = new aFunction(" + argString + ")");
return rv;
}
var x = constructNew(Array, 2, 4, 6);
is the same as saying
x = new Array(2, 4, 6);
The major difference is I can call constructNew.apply(thisObj, [Array, 2, 4, 6]). The thisObj variable gets ignored!
So, to apply the contract while constructing an object:
function applyContractConstructor(aFunction) {
var args = [];
for (var x = 1; x < arguments.length; x++) {
args[x - 1] = arguments[x];
}
if (typeof aFunction.precondition == 'function') {
aFunction.precondition.apply(this, args);
}
args.unshift(aFunction);
var rv = constructNew.apply(thisObj, args);
args.shift(aFunction);
if (typeof aFunction.postcondition == 'function') {
args.push(rv);
aFunction.postcondition.apply(this, args);
}
return rv;
}
I'll say this, though: DBC and assert() are coming in very handy in developing Abacus! As I said earlier, if a contract fails, there's a bug in either the executing code or the contract, and it behooves me to fix it!
A few weeks ago, my Linux operating system stopped talking to the Internet. So I made sure I could download the code via Windows (CygWin is so nice), and then copy it over. Right?
Well, not exactly. When I started building, the build would die hours later with a message, "No rule to make target 'README'. Stop." (This was under /xpfe/bootstrap/init.d .)
Someone told me to comment that out, and when I did that, I got the same error message for 'LICENSE'.
This morning I ran the 'gmake -d -f Makefile' command from the init.d directory. It listed a bunch of things it looked for: 'README', 'README.h', 'README.cpp', etc. It didn't find the README file... because somehow it got copied over as 'readme'. (Lowercase letters.)
Fixed that for 'README' and 'LICENSE', and now I can successfully complete a build. I can't access the Internet from home now (remember, I'm looking for a job), but I can develop and am developing several little tools and the Abacus MathML editor. If I keep up the current pace, I should have the editor, version 0.1, within about a week.
On another note, remember the Mozilla Runtime Engine idea? (I know, they've sometimes called it Gecko Runtime Engine, but...) Here's what it really is.
Another question I had been asking myself is, "How do I throw strict warnings from JavaScript?" Well, I figured it out. The result is a file called warn.js for your chrome.
I may attempt to add them to jslib formally. (There's a glitch with b.md.o right now though.)
I also took the time this morning to figure out how to add a chrome-based project to an unjarred Mozilla from scratch. rginda's XULKit project is nice; I just wish there was an XUL project implementation.
I have been having a lot of fun in the last 24 hours trying to "control the scroll" of an element's content. I don't think anyone really took the time to make scrolling friendly to scripting.
There are four general categories of scrollable content I'm aware of (bear in mind this is from a DOM point of view):
(1) <html:textarea/>
(2) <browser/>, <editor/> <iframe>, etc.
(3) <box style="scroll:overflow;" />
(4) <arrowscrollbox/>
Textareas provide basic properties (scrollTop, scrollLeft, scrollHeight, scrollWidth) for determining which portion of the content to show. This is good. But it doesn't easily expose the scrollbars themselves as objects a script can access. The same goes for the rest of them. There really is no native (that is, implemented directly in Mozilla) way to script the scrolling of such scrollable content directly.
It'd be really nice if there was an nsIScrollableContent.idl (or whatever) set of properties for any time you have a content box with at least one scrollbar. References to the scrollbars themselves (<xul:scrollbar/>?) would be great. References to the properties which determine where the content is scrolled to would be almost as good.
Solutions?
I thought to myself, this just won't do. So I started asking myself, how can I create my own scrollable content? One way to simulate content which has scrolled partially is by setting the following style:
.scrollableContent {
overflow: hidden;
margin-top: 0px;
margin-left: 0px;
clip: rect(0px 0px 0px 0px);
}
Then, by adjusting the margin-top and margin-left properties, the content could appear to scroll. After a little tinkering, I created a widget which is halfway to a scriptable scrolling content. You can see it here. (The demo currently also checks for DOMAttrModified elements bubbling up -- this lets me check for when a scrollbar in the demo actually moves.)
I haven't yet implemented the scrolling of content yet; there's some linear mathematics I need to do, including resizing the thumb of each bar appropriately. I've also filed bug 243128 for adding a few properties to <xul:scrollbar/> elements. They basically expose the scrollbar's attributes to JS as properties.
If the first patch passes muster, I'll propose code for percentage-based handling of scrollbars.
Use cases: This is where I usually get hung up. My current motivation is to create a line-numbered multiline textbox control. Other advantages of this would be to directly expose a scrolling capability to JavaScript; dynamic effects, anyone?