I started working on the drag and drop part of the
HTML5 spec. Since IE already supports drag and drop, and since Safari
implements the IE API for drag and drop, it makes a lot of sense to
simply use that API.
So I started doing research into the IE drag and drop API. It turns
out there is very little useful documentation out there. MSDN has a
vague hand-wavy description, Apple have a tutorial that explains how
you're supposed to use it, and the rest of the documentation consists of
tutorials that simply rehash what those two sources say.
In fact, basically all I got from the official sources of
documentation was the names of the events, attributes and methods that
form part of the API. Which is a start, but not even remotely enough
to actually write an implementable specification.
So I put my physicist training to use. As a scientist, you are
supposed to approach problems using a simple process:
- Form a hypothesis that matches everything you know so far.
- Make some testable predictions using this hypothesis.
- Test reality to see if your predictions can be disproved.
- Repeat those steps until you can no longer find anything in
reality that disagrees with what your hypothesis predicts.
- Publish your findings, along with all the data you collected, and
the hypothesis you ended up with.
- Wait for someone to find a prediction that your hypothesis makes
and which doesn't match reality.
- Repeat the entire process.
So I made some guesses as to how the various events that were
listed in the documentation were going to act, and I tested to see if
that is how they worked. Every time I found that my predictions didn't
match what IE did, I adjusted my theory and repeated.
There are seven events I decided to concentrate on:
dragstart
, drag
, dragenter
,
dragover
, dragleave
, drop
, and
dragend
. In addition, the event object has a
dataTransfer
member, which is an object with, amongst
other things, two attributes of interest: effectAllowed
(string; allowed values "copy", "link", "move", "copyLink",
"copyMove", "linkMove", "all", "none", and "uninitialized") and
dropEffect
(string; allowed values "copy", "link", "move",
and "none"). The dataTransfer
object also has a
setData()
method. It apparently takes two arguments: a
type (either "Text" or "URL") and a string representing the data.
I originally hypothesised that when you drag a selection from the
page to a textarea
element or another application, the
source is always left unchanged. It turns out I was wrong about this.
If you set event.dataTransfer.effectAllowed
to "move" in
the ondragstart
event, and then you drag it to something
that has a default drag-and-drop behaviour that accepts text, such as
a textarea
, then the text of the selection is removed
from the DOM by the browser. You don't have to do this
yourself.
Fair enough, I thought, new hypothesis: when the
effectAllowed
is "move", the UA always removes the
original selection from the DOM when you drop it.
Nope. Even if you set dropEffect
to "move" in
dragenter
, dragover
, and drop
,
and set effectAllowed
to "move" in
dragstart
, drag
, and dragend
,
the original selection is left alone if you are dragging a selection
to an element (such as a P
) that doesn't have a default
behaviour.
Ok, theory: the deletion is done in the drop event's default action
for elements like textarea
.
Nope. If you return false in the event handler for the
drop
event of the textarea
element, the
textarea
stops receiving the new text, but the text
is still removed from its original location.
Ok, maybe it happens as the default action of the
dragend
event.
It's possible. Cancelling the event had no effect at all; changing
the dataTransfer
object's attributes in that event's
listener had no discernible effect either.
Ok, try another hypothesis. The dragend
event is not
cancelable and its default action is to remove the selection if the
event target is a text field of some description (including one in a
native application external to the UA itself), and the final effect
used was "move", irrespective of the data given by
setData()
calls.
To test this I set up a test where I dragged a selection to a
textarea
, with the dragstart
event calling
dataTransfer.setData('URL', 'http://www.example.com/')
and setting effectAllowed
to "move". According to the
hypothesis, we should see the URL be ignored and the selected text be
added to the textarea
and removed from its original
placed in the DOM.
Nope. IE crashes if you do this.
Ok, let's investigate a different angle. When do the events get
fired?
Theory: The drop
event is fired when you release the
mouse over the element.
Nope. If you set dropEffect
to something that isn't
"compatible" with the value of effectAllowed
(e.g. set
one to "copy" and the other to "move") in the earlier events, then
drop
is never dispatched.
Theory: In the event sequence dragenter
,
dragover
, dragleave
, the next event is only
called if the previous event's event handler sets
dropEffect
to a value compatible with the
effectAllowed
value.
Nope. The value of dropEffect
has no discernible
effect on whether those three events are fired.
Theory: Once you set one of the effectAllowed
and
dropEffect
attributes to a value, it keeps that
value.
Not for dropEffect
. As far as I can tell, the
dropEffect
attribute is reset before each event. The
effectAllowed
attribute does seem to keep its value,
though, whenever you set it.
Theory: In the event sequence dragenter
,
dragover
, drop
, the cursor will be the value
common to the value of dropEffect
set by the last event
handler and the value of effectAllowed
, or a no-drop
cursor if they are not compatible, and the dragenter
and
dragover
events have a default action that is to set
dropEffect
to "none".
It does appear that the cursor acts as described, except that if
you don't set dropEffect
but do cancel the event, the
attribute seeems to get a value of its own.
Theory: The dropEffect
attribute is always reset to
"none" before an event is dispatched.
Nope. If you drag text from an element that sets
effectAllowed
to "copy" to a textarea
element, then the dropEffect
attribute is set to "copy"
in the dragend
event. And if you just cancel the
dragover
event (say), the dropEffect
takes
on one of the values that is compatible with the value of the allowedEffect
attribute.
Theory: During the dragend
event, the
dropEffect
attribute is set to the value that represents
the value common to the dropEffect
and
effectAllowed
attributes as they stood after the last
dragover
event.
Nope. Even if you set dropEffect
and
effectAllowed
to "copy" everywhere, if you don't return
false from the drop
handler, the dropEffect
is always set to "none" in the dragend
handler.
Theory: The dropEffect
attribute is set to the value
that represents the value common to the dropEffect
and
effectAllowed
attributes as they stood after the last
event before the dragend
event, and, the
drop
event's default action on elements that aren't
native drop targets is to set it to "none".
Possible. It seems that if you set the value explicitly during
drop
, it doesn't matter whether the value is compatible
with the effectAllowed
value, that is only checked for
the cursor to use. However, it also seems that the
dropEffect
attribute is set to one of the values
compatible with the effectAllowed
value before the
drop
event is dispatched.
Clearly this merits further study. I'll see if I can write a more
complete theory and then test that. Obviously not everything IE does
is sane (e.g. crashing) so the final spec won't be 100% compatible,
but hopefully it will be close enough.
One thing I discovered which surprised me is that IE actually
tracks every DOM and style change made during a drag and drop
operation, and lets the user invoke an Undo command that undoes those
changes (unless another DOM change has been made since, not counting
changes to style
objects). It doesn't include things like
window.status
, but it does include changes to
the style
object of every element, as well as attributes
like title
. And it includes all changes made between (and
including) dragstart
and dragend
, including
changes made by scripts running on timers.
It isn't perfect. For example, while it does track changes made
directly to the text of the title
element, it
doesn't track changes made indirectly to that element via the
document.title
API. (Also, oddly, while changing the
document.title
DOM attribute changes both the actual
window title and the element's contents, changing the element's
contents directly doesn't affect the window's title. So the undo
command never changes the window's title.)
Another discovery I made is that mouse and key events do not seem
to be dispatched during a drag-and-drop operation. To be precise, at
the start of a drag you get a mousedown
followed by a few
mousemove
s, but then it jumps into the drag events and
you never see the mouseup
.
Pingbacks:
1
I can't wait to see who will be the first to have a browser that
can render the Acid2
test correctly. Another question (and I expect the answer to be
quite different) is who will be the first to ship an official release
build that renders the test correctly.
There's been some
debate about the correctness of the bottom line of the test. It's
part of one of the CSS aspects of the test, so hopefully we'll address
that problem on Tuesday's CSS working group teleconference.
One of the things that the Acid2 test totally doesn't test is the
DOM. I think for Acid3 (whenever that comes) we're going to have to
address that.
Another thing that Acid2 doesn't address is the CSS inline box
model. David actually tried addressing that in an "inline acid test"
back when Acid1 (the box
acid test) first came out, but although he created a brilliant test,
varying and unpredictable font metrics meant the test could only be
truly tested with the Ahem font.
Acid2 had the same problem. We couldn't test inline box model
aspects in any kind of detail because unpredictable font metrics meant
there was no way of achieving a pixel-perfect look with inline boxes
around.
One solution is to simply use the Ahem font, of course, and that's
what true CSS tests do. The Acid2 test isn't a true test, it's more of
a demo, aimed at the public. As a true test it is terrible: it mixes a
bazillion things all at once. Debugging that kind of test is a
nightmare. Opera engineers, for example, aren't working on fixing the
Acid2 bugs directly: instead, I spent an hour or two minimising the
Acid2 test into eleven separate real test cases,
which show the real bugs our latest code has with Acid2. These are the
test cases that Opera engineers then work on.
Since Acid2 is aimed at the public, it has to work on most default
installs. And, sadly, OS vendors don't ship the Ahem font by default,
so we can't use it in tests aimed at the public.
Talking about minimising testcases, a few weeks ago I gave a number
of courses, at work, on how to create testcases for rendering bugs. It
was an interesting experience. Basically I just did my normal QA job,
creating testcases out of pages that weren't rendering correctly, but
with a projector, an audience, and while giving a running commentary.
I felt a bit like a performance artist! Apparently these courses were
well-received.
I've been tempted to write a book on how to create test cases for
Web browsers, but I figure I probably personally know half the people
who might buy the book, and am only one degree of separation from the
rest, so it probably wouldn't be worth it.
It's also really hard to explain. Hence why those courses I
mentioned just consisted of me showing people how I did it, instead of
telling them how to do it. Teaching by example works much better for
this kind of thing, I think.
Either that or I suck at teaching. Or both.
Eh, I probably just suck at teaching.
Moving on, I recently bought Power Grid, a game I've mentioned
before.
We played it with six players on Friday. It was great. Oddly enough,
this time the ending didn't seem quite so sudden. Maybe it was because
there were so many players, or maybe I'm just getting used to it.
I've also played 1856 a couple of times these last few weeks.
Haven't been doing so well, but I'm definitely getting better. The
last game I played we were all people who'd played multiple times, so
I didn't feel in the least bit bad about playing a very dirty trick
right at the start: I performed a hostile takeover of someone's first
company, in the first stock round. It's a lot easier to run a company
that has five shares' worth of capital straight away, rather than one
that has just two or three. I ran the company without loans the entire
game, and only withheld dividends three times (which, in retrospect,
was my downfall: I shouldn't have withheld at all).
The biggest problem was that that strategy meant I couldn't decide
which company to start, and I just had to use what was available and
affordable. That turned out to be London & Port Sarnia, which in
my opinion is a suboptimal company to start with.
Speaking of trains, I now have a Web interface to my model
trains. It's pretty cool. I have a little Perl script running on
one laptop which is running a little TCP/IP server that speaks a very
simple line-driven proprietary protocol I invented. This script takes
commands from the incoming TCP sockets and converts them into output
for the serial port (which then connects to the trains), and takes
input from the serial port (from the track sensors) and sends messages
to all the connected sockets. The Web interface is then just a Web
page that uses two CGI scripts: one which opens a persistent
connection to that aforementioned server and just converts all the
messages into JS commands that the Web page then uses to update its
UI, and one which just opens a connection to the train server, pipes
one command to it, and closes the connection.
I've also written an IRC bot interface to the train server, mostly
as a proof of concept.
Since Stargate is on hiatus these days I've had to find other shows
to watch. I've been watching Lost and Battlestar
Galactica. Lost is driving me crazy. It has the
potential to be really good, and the potential to fall spectacularly
flat on its face. Let's hope for the former.
Battlestar Galactica hasn't impressed me that much. I
first watched the miniseries, and was hugely impressed by the
three-and-a-half minute single shot at the start, but it was downhill
from there.
I've been reading a stack of books recently (something that Alex mentioned
in his post about our trip to Prague last month).
My favourite music at the moment is the Incredits
track from the The Incredibles soundtrack CD. That
notwithstanding, on Saturday I dropped over at Club Clave for a while, where I
met, and briefly danced with, one of the people I go to classes with.
I think it would probably be more fun to go with more people I know,
though.
All of this is just to say that I'm trying to write my paper for my X-Tech
presentation and finding it hard to express what I want to say, so
instead I figured I would write a lot of nonsense here and delay the
actual paper writing some more. Sadly the papers are due tomorrow.
Actually, that reminds me: I saw a presentation by Bjarne Stroustrup Friday
afternoon which was quite heart-warming. He was talking about the next
version of C++, and the running theme throughout his presentation was
"we have to keep backwards compatibility". Apparently, he, and his
peers on the C++ committee, are having the same problem that faces us
with WHATWG: we consider it
completely obvious that the most critical thing about any new features
is that we remain
compatible with existing content and that our new features fail
gracefully in old UAs, but we are continuously having to deal with
people who consider backwards compatibility to be of minor importance
at best.
This is the point I'm trying to make in my paper, and the theme
that will be running through my presentation.
2005-04-06 00:40 UTC
The ABC Railway: Photos from the early days
So! As I mentioned,
I went out and bought some track and rolling stock for my model
trains. People have been bugging me for pictures so I had a go at
making some using a low-end USB camera I got free with my Dell laptop
a few years back.
Oncoming train:
My series Re 421 and series Ae 6/6 electric locomotives:
My Re 421 stopped on the yard lead while classifying the Knie train:
Freight waiting at the freight yard:
My BR 362 Diesel switching engine pulling coal from the a/d track:
Freight in the classification tracks:
The wood cargo cars in closeup:
My Re 421:
The overall layout:
There are several
more pictures if you want to browse.
I'm quite happy with my actual layout, although I really need ten
times more room to make it truly usable. I spent a few days during the
Easter break studying how to make a
decent freight yard, and so I was careful to include an
arrival/departure track (with its own run-around), an engine services
track, a team track, a long yard lead, three classification tracks off
the ladder, a repairs-in-place track, and a run-around for the yard
lead. There are decoupling tracks in appropriate places and my
switching engine (a short red BR 362 diesel) has telex decouplers so
it can decouple anywhere as well.
In addition I have three "towns": Avon, Bath, and Cuba. They are
connected by a two-track mainline.
Cuba is a small end-of-line station with its own small
classification yard. It has an a/d track, a yard lead, a run-around,
engine services, and two classification tracks, but the whole thing is
much more compactly built than the main yard and doesn't have a
dedicated switching engine. (At least, not yet.)
Avon is simply a two-siding affair. It's very primitive, to the
point where if you go into the first siding when you arrive, you
actually have to pull back out into the main, then reverse back into
the second siding, then reverse again to leave the station. I'm not
expecting that siding to get much use.
Bath is built from the remnants of the pieces of track and points I
had after building the yard, Avon, and Cuba, and frankly bears more
resemblance to John Allen's Timesaver puzzle layout than to anything
you'd find in the real world. It's also mostly built out of curved
track, which makes coupling with freight there a real pain.
All the points (turnouts) as well as all
the locomotives have built-in digital decoders, so I can control the
entire layout via a computer interface. I've set up a little Web-based
application to run my trains, it's pretty sweet. I'll talk more about
that some other day, though.
Pingbacks:
1
2
3