Marc A. Najork
DEC Systems Research Center
130 Lytton Ave.
Palo Alto, CA 94301
415 853-2153 (tel)
415 853-2104 (fax)
najork@pa.dec.com
All of these browsers support mobile code, that is, textual or bytecode representations of programs that the browser downloads and runs. This paper describes a system we have built with the extra feature of support for distributed computation, where the computation can span machines across the Internet. High-level support for distributed computation makes it easy to write groupware, CSCW applications, and distributed multi-player games as active objects.
Our environment for writing distributed active objects is based on Obliq [Cardelli95], an objected-oriented scripting language that was specifically designed for constructing distributed applications in a heterogeneous environment. We call active objects written in Obliq "Oblets" (Obliq applets). We have also built a family of Web browsers (DeckScape [Brown94], WebCard [Brown95], and WebScape) capable of running Oblets.
Obliq supports distributed computation by implementing all objects as network objects [Birrell93]. The methods of a network object can be invoked by other processes, in addition to the process that created the object. The process that invokes a method does not need to know if the object is local or remote. Thus, from a programmer's perspective, there is no difference between local and remote objects. The initial connection between two processes occurs when one process registers an object with a name server under a unique name, and another process subsequently imports the object from the name server. Once the connection is established, network objects can be passed to other processes just as simply as passing any other type of data.
Network object method calls have the same syntax regardless of whether an object resides in the same address space as the caller, or in a different address space, either on the same machine or on some other (possibly different type of) machine. As a result, network objects provide a uniform way for communication among Oblets, regardless of whether the Oblets are on the same Web page or on different Web pages displayed by different browsers on different machines. Moreover, network objects communicate directly, without server intervention. Thus, Oblets do not impose any load on an http server, nor does a heavily-loaded server affect their performance.
The rest of this paper consists of four sections with increasingly complex examples, followed by a review of related work. The first section demonstrates the fundamentals of simple, non-distributed Oblets; the second shows how to build a simple distributed Oblet; the third shows a "chat room" that allows an arbitrary number of clients to participate; and the fourth shows an Oblet for algorithm animation that illustrates how to synchronize multiple views of an animation.
oblet
. This variable must contain an Obliq object with at
least two fields: vbt
and run
. The
vbt
field is bound to a widget that will be installed on
the screen when the page containing the Oblet is loaded. The run
field
is bound to a method that is invoked just after the vbt
field is evaluated.
Oblets are placed into HTML documents via insert
, an
HTML tag proposed by W3C for inserting multimedia objects into HTML3
pages [HTML3]. The markup for
putting the Oblet at URL foo.obl
into a document is:
<insert code="foo.obl" type="application/x-oblet"> </insert>The
insert
tag also supports a variety of standard
attributes, such as suggested dimensions, border size, and alignment.
If suggested dimensions are not specified, the preferred dimensions of
the widget contained in the Oblet's vbt
field are used.
The following screen dump (Figure 1) shows a simple Oblet for adding two numbers:
The user interface for the Oblet, defined by a FormsVBT s-expression
[Avrahami89], is stored in the
file adder.fv
:
(Rim (Pen 20) (HBox (Numeric %num1) "+" (Numeric %num2) "=" (Text %sum "0")))A user interface in FormsVBT is a hierarchical arrangement of components. These include passive visual elements (e.g., Text), basic interactors (e.g., Numeric), modifiers that add interactive behavior to other components (e.g., Button), and layout operators that organize other components geometrically (e.g., HBox). Components can be further categorized as a split, filter, or leaf, based on the number of children components they support. A split can have any number of children (e.g., HBox), a filter has exactly one child (e.g., Rim), and a leaf has no children (e.g., Text).
A component in FormsVBT can be given a name so that its attributes can be queried
and modified at runtime. Names are also used for attaching callback
procedures to interactors. In this Oblet, the two Numeric
components have been given the names num1
and
num2
, and the Text component where the sum will be
displayed is named sum
.
The source for this Oblet is as follows:
let doAdd = proc (fv) let n1 = form_getInt (fv, "num1"); let n2 = form_getInt (fv, "num2"); form_putText (fv, "sum", fmt_int (n1+n2)) end; let oblet = { vbt => form_fromURL (BaseURL & "adder.fv"), run => meth (self) form_attach (self.vbt, "num1", doAdd); form_attach (self.vbt, "num2", doAdd); end };This Obliq program defines two variables:
doAdd
and
oblet
. Variable doAdd
is a procedure that
retrieves the values of both numeric interactors, and stores their sum
in the component named sum
.
Variable oblet
is an object with two fields, vbt
and
run
. The vbt
field is bound to a "form", a
widget that displays a FormsVBT s-expression. The procedure
form_fromURL
takes a URL as an argument and returns a
widget whose description is stored at this URL. The run
method in this Oblet just attaches the callback procedure
doAdd
to the two numeric interactors. This procedure will
be invoked whenever the user clicks on the plus or minus buttons of
either interactor, or types a number into the editing field between
the buttons. The form in which the event occurred is passed as an
argument to the callback procedure.
Recall that when the Web page containing this Oblet is loaded, the
vbt
field will be evaluated and the result displayed on
the Web page, the run
method will be invoked, and finally
the page will become visible.
status
, indicates whose turn it is. The game grid
consists of nine squares; each square consists of a button and a
text. The buttons are named btn1
, ..., btn9
,
and the text components are named lab1
, ...,
lab9
. The button at the bottom of the board, named
reset
, is used to clear the squares of the game
grid. Finally, the top-level component of this form has the name
board
.The code for the Oblet is as follows:
let otherPlayer = proc (p) if p is "X" then "O" else "X" end; end; let oblet = { vbt => form_fromURL (BaseURL & "tic-tac-toe.fv"), c => ok, reset => meth (self) ... end, move => meth (self, label, player) ... end, nextTurn => meth (self) ... end, run => meth (self) self.c := "X"; let doReset = proc(fv) self.reset (); end; let doPress = proc (m) let label = "lab" & fmt_int(m); if form_getText (self.vbt, label) is "" then self.move (label, self.c); self.nextTurn (); end; end; form_attach (self.vbt, "reset", doReset); for i = 1 to 9 do form_attach (self.vbt, "btn" & fmt_int(i), proc(fv) doPress (i) end) end; end };This Oblet, in addition to the required
vbt
field and
run
method, also has a field c
, and methods
reset
, move
, and nextTurn
,
which will be fleshed out later. The field c
will be a
string indicating the player about to move, either "X" or "O". The
reset
method clears the label displayed in each square of
the game grid. The move
method puts the string
player
into the text component whose name is
label
(e.g., lab4
is the center square), and
also updates the message line at the top to indicate whose turn is
next. The nextTurn
method changes whose turn it is, that
is, it changes the value of the field c
.
The body of the run
method initializes field
c
, and then attaches callback procedures to the various
interactors on the board. Procedure doReset
is attached
to the reset button at the bottom, and an anonymous
procedure is attached to each of the nine buttons, btn1
,
..., btn9
. The anonymous
procedure effectively captures the value of i
, the index
of each square on the board. When this anonymous callback procedure
is invoked (in response to a user clicking a square of the board), it
calls procedure doPress(i)
.
Procedure doReset
invokes the reset
method
of the object oblet
. Procedure doPress
checks that the square of the grid is empty, and if so, invokes the
Oblet's move
and nextTurn
methods.
Finally, here are the bodies of the methods reset
,
move
, and nextTurn
:
reset => meth (self) for i = 1 to 9 do form_putText (self.vbt, "lab" & fmt_int(i), ""); end; end, move => meth (self, label, player) form_putText (self.vbt, label, player); form_putText (self.vbt, "status", otherPlayer(player) & " is next"); end, nextTurn => meth (self) self.c := otherPlayer(self.c); end,The last two methods use the procedure
otherPlayer
, which
takes one player's symbol and returns his opponent's symbol.We now convert the single-site version of tic-tac-toe into a two-site, distributed version. Figure 3 shows a snapshot of a game in progress. The left image shows the browser (WebScape) used by player "O", the right image shows the browser (DeckScape) used by player "X". The message line at the top of the game boards indicates that player "X" is next, and the game board of player "O" is greyed out, indicating that is is non-responsive for the time being.
oblet
to include an extra field, opp
,
which is the oblet
of the opponent. Second, we use the
field c
in a slightly different way: in the single-site
version, c
indicated whose turn it was; in the two-site
version, it indicates the player in whose browser the Oblet runs.
Finally, there are changes to the nextTurn
and
run
methods. Here is the entire Oblet, with unchanged
parts elided:
let otherPlayer = ...; let oblet = { vbt => form_fromURL (BaseURL & "tic-tac-toe.fv"), c => ok, opp => ok, reset => meth (self) ... end, move => meth (self, label, player) ... end, nextTurn => meth (self) if form_getReactivity(self.vbt, "board") is "active" then form_putReactivity(self.vbt, "board", "dormant"); else form_putReactivity(self.vbt, "board", "active"); end; end, run => meth (self) try self.opp := net_import ("TicTacToe", "ash.pa.dec.com"); self.opp.opp := self; self.c := "X"; except net_failure => net_export ("TicTacToe", "ash.pa.dec.com", self); form_putReactivity (self.vbt, "board", "dormant"); self.c := "O"; end; let doReset = proc(fv) self.reset (); self.opp.reset (); end; let doPress = proc (m) let label = "lab" & fmt_int(m); if form_getText (self.vbt, label) is "" then self.move (label, self.c); self.opp.move (label, self.c); self.nextTurn (); self.opp.nextTurn (); end; end; form_attach (self.vbt, "reset", doReset); for i = 1 to 9 do form_attach (self.vbt, "btn" & fmt_int(i), proc(fv) doPress (i) end) end; end };The first part of the
run
method attempts to import an
object called "TicTacToe" from the name server at machine
"ash.pa.dec.com". This call succeeds if there already is a player
waiting for a game to begin. In this case, the opponent's
oblet
is stored in our opp
field, our
oblet
is stored in our opponent's opp
field,
and we choose "X" to be our symbol. If the attempt to import
"TicTacToe" fails, then we export our oblet
to the name
server at "ash.pa.dec.com", make our game board dormant (i.e.,
grayed-out and unresponsive to mouse activity), and choose "O" as our
symbol.
The change to the doReset
callback is simple: we invoke
the reset
method not only on our oblet
, but
also on our opponent's oblet
. The change to the
doPress
callback is similar: rather than invoking
move
and nextTurn
only on our
oblet
, we also invoke these methods on our opponent's
oblet
. The rest of the run
method is
unchanged: callbacks are attached to the interactors.
The final change in the Oblet is to the nextTurn
method. In the single-site version, we changed the value of field
c
from "X" to "O" and vice versa. Here, we change the
reactivity of the game board, from active to dormant and vice versa.
This way, each player can only press a button when it is his turn to
move.
It is worth emphasizing that self.opp
denotes an object
that resides on the opponent's machine. This implies that the
assignment to self.opp.opp
and the execution of the
self.opp.reset
, self.opp.move
, and
self.opp.nextTurn
method calls take place on this other
machine.
oblet
objects performing peer-to-peer communication with
each other. In this example, we use a star topology to implement a
multi-person "chat room". At the center of the star, we have a
"conference manager" object; at the periphery are the Oblets belonging
to the participants. When a user types into his chat room Oblet, it
informs the conference manager of the new text, which then relays the
update to all the participating Oblets. Our chat room also provides a
mechanism for floor control.The following three images (Figure 4) show the "chat room" running in different browsers (WebScape, WebCard, and DeckScape, from left to right). Each browser is running on a different machine. The participants in the chat room are Moe, Larry, and Curly (from left to right). Currently the floor is with Moe, as indicated by the status line over the editing area and by the color of the editing area in Moe's browser.
(Rim (Pen 10) (VBox (Text %floorWith "The floor is free right now") (Glue 10) (Shape (Width 300) (Height 200) (Frame Lowered (Filter Passive (TextEdit (BgColor "White") %mainEditor)))) (Glue 10) (HBox (Text "Your Name:") (Frame Lowered (TypeIn (BgColor "White") %myName)) Fill (Button %grabFloor "Grab Floor"))))The
floorWith
Text component is the banner above the large text
region; it will contain a message indicating who owns the floor. The
mainEditor
is the large (300x200) text region. The
Filter component surrounding the region is used to set the reactivity of
the region; "Passive" means that the area is unresponsive to mouse and
keyboard activity, but it is not grayed-out (as it would be in the
"Dormant" state). The type-in field where each participant identifies
himself is named myName
. Finally, the "Grab Floor" button
has been given the name grabFloor
.As we shall see, callback procedures will be attached to the "Grab Floor" button and to the large text region. When the user clicks on the "Grab Floor" button, the banner on all participating Oblets will indicate who owns the floor (the contents of the type-in field), and the text region on all Oblets (other than the one owning the floor) will become passive. The text region in the Oblet owning the floor with become active and its color will change to pink. When the user who owns the floor types a keystroke into the text region, all of the participating Oblets will be notified of the updated text.
As mentioned, in this example, Oblets do not communicate with other Oblets directly. Rather, they use a "conference control" object to report the changes, and this object then relays the changes to the other Oblets. Here is the definition of the "conference control" object:
let ProtoConfControl = { oblets => [], onFloor => ok, contents => "", register => meth (self, oblet) self.oblets := self.oblets @ [oblet]; oblet.updateText (self.contents); if self.onFloor isnot ok then oblet.transferFloor (self.onFloor); end; end, transferFloor => meth (self, name) self.onFloor := name; foreach o in self.oblets do o.transferFloor (name); end; end, updateText => meth (self, contents) self.contents := contents; foreach o in self.oblets do o.updateText (contents); end; end };The
oblets
data field is an array of the Oblets that have
registered themselves with the conference control object. Each element
of this array is an oblet
that typically resides on a
different machine. The onFloor
data field is the name of
the user who currently has the floor, and the contents
data field contains the current contents of the text area. These two
fields are needed to initialize the display of a new participant who
enters the chat room.
The register
method will be called by a new Oblet
oblet
when it is initialized, as part of its
run
method. The new Oblet is appended to the
oblets
array, and then the new Oblet is notified both of
the current contents of the text area and of the owner of the floor,
if there is one.
The transferFloor
method will be called by an Oblet when
the user clicks on the "Grab Floor" button. This method stores in
onFloor
the Oblet that now owns the floor, and then
iterates through all of the Oblets in the conference, invoking the
transferFloor
method on each Oblet to inform it of the
new floor owner.
Finally, the updateText
method will be called by the
Oblet who owns the floor on each keystroke, passing in the current
contents of the text area. (Passing just the keystroke is not
sufficient, since a single character could result in various editing
actions, depending on the editor key bindings used by the Oblet.) The
updateText
method stores in contents
the new
contents and then updates all of the Oblets in the conference by
invoking the updateText
method on each Oblet.
We are now ready to examine the code for the "chat room" Oblet:
let oblet = { vbt => form_fromURL (BaseURL & "chatroom.fv"), transferFloor => meth (self, name) form_putReactivity (self.vbt, "mainEditor", "passive"); form_putColorProp (self.vbt, "mainEditor", "BgColor", color_named("white")); form_putText (self.vbt, "floorWith", "The floor is with " & name); end, updateText => meth (self, contents) form_putText (self.vbt, "mainEditor", contents); end, run => meth (self) var confControl = ok; try confControl := net_import("ConfControl", "ash.pa.dec.com"); except net_failure => confControl := ProtoConfControl; net_export("ConfControl", "ash.pa.dec.com", confControl); end; let doGrabFloor = proc (fv) confControl.transferFloor (form_getText (fv, "myName")); form_putReactivity (fv, "mainEditor", "active"); form_putColorProp (fv, "mainEditor", "BgColor", color_named("pink")); end; let doKeyEvent = proc (fv) confControl.updateText (form_getText (fv, "mainEditor")); end; confControl.register (self); form_attach (self.vbt, "grabFloor", doGrabFloor); form_attach (self.vbt, "mainEditor", doKeyEvent); end };The Oblet defines two methods,
transferFloor
and
updateText
; as we just saw, these methods will be invoked
by the centralized conference control object, in response to a user in
an arbitrary Oblet in the chat room grabbing the floor or typing into
the text area. These methods are straightforward: the
transferFloor
method makes the text area passive and sets its
background to be white, and then updates the banner. The
updateText
message changes the contents of the text area.
The Oblet's run
method first contacts the name server on
the machine "ash.pa.dec.com" to obtain a conference control object
registered under the name "ConfControl". If there is such an object,
it is stored in the variable confControl
. Otherwise, a
new conference control object is registered with the name server and
also stored in confControl
. After defining callback
procedures doGrabFloor
and doKeyEvent
, this
Oblet registers itself with the conference controller, and finally
attaches the callback procedures to the "Grab Floor" button and the
text area.
The doGrabFloor
callback procedure invokes the
transferFloor
method on the confControl
object (which then calls the transferFloor
method on all Oblets in the chat room, including this one), and then
makes its own text area active and colored pink. The
doKeyEvent
callback procedure simply invokes
the
updateText
method on the confControl
object,
passing to it the text in the text area.
Again, it is important to point out that invoking a method
m
on the confControl
object is done just by
calling confControl.m()
, regardless of where the
confControl
object resides. Because of the way the
conference controller is created, it will be a local to one Oblet, and
remote to all other Oblets.
There are many features that could be added to the chat room in a fairly straightforward way. For example, it would be nice to be able to prevent another user from taking away the floor, to allow users to leave the chat room, to interactively specify a name server and a chat room, to see existing chat rooms, to handle exceptions that might result from network partitions, and so on. In addition, one can easily imagine more efficient implementations, such as reporting only changes to the text area rather than merely reporting what the updated contents of the text area are after each keystroke.
This example uses network objects to synchronize multiple Oblets in the domain of algorithm animation [Brown84]. A typical algorithm animation system has a control panel and a collection of views, each in its own window. The control panel is used for specifying data, starting the algorithm, controlling the animation speed, and so on. In order to animate an algorithm, strategically important points of its code are annotated with procedure calls that generate "interesting events." These events are reported to the algorithm animation system, which in turn forwards them to all interested views. Each view responds to interesting events by updating their displays appropriately.
The following screen dump (Figure 5) shows an animation of first-fit binpacking. The control panel and the views are implemented by separate Oblets.
For example, when the algorithm is trying to insert a particular
weight w
into a bin b
that already contains
a number of weights totaling up to amt
, it calls
zeus.probe(w,b,amt)
. The probe
method of the
controller object zeus
is implemented as follows:
let zeus = { views => [], ... probe => meth (self,w,b,amt) let threads = foreach v in self.views map let closure = proc() v.probe(w,b,amt) end; thread_fork(closure) end; foreach t in threads do thread_join(t) end; end; ... };The image above showed the Oblets for the control panel and each view all on the same Web page. However, there is no need for the Oblets to be located on the same page. In fact, if we put each Oblet on a separate page, the user can dynamically select the set of views (or even have more than one copy of any view) visible. In the following screen dump (Figure 6), the page containing the control panel has links for pages containing the various views. Clicking on such a link brings up a page for the view, which the DeckScape browser can optionally display in a separate window.
The most serious potential competitor to Java-based browsers is probably Microsoft's Internet Explorer, which plans to integrate support for active objects written in Visual Basic (as well as for those written in Java) [Microsoft]. However, the current version of Internet Explorer does not support active objects.
In the research community, a number of browsers have been developed that support other languages for writing active objects. Most of these browsers are written in interpreted languages and support active objects written in the same language. Examples include Hush [vanDoorn95] and SurfIt! [SurfIt!] (both Tcl/Tk), MMM [MMM] (CAML/Tk), and Grail [Grail] (Python).
None of the browsers and active-object languages mentioned above has any high-level support for distributed programming. However, the HORB system [HORB] adds the equivalent of network objects to Java. It consists of a name server and a compiler that creates network object classes based on Java interface specifications. Unlike Obliq, HORB is a first-order language, meaning that only data, but not computations, can be migrated over the network. Also, HORB does not provide distributed garbage collection.
Obliq [Cardelli95] is a lexically-scoped language that supports distributed object-oriented computation. Previously, it has been used as the scripting language of a Visual Basic-like rapid prototyping environment for distributed applications [Bharat94]. It has also been integrated into commercial web browsers such as Mosaic by defining an Obliq MIME type and configuring the browser to use the Obliq interpreter as an external viewer [Bharat95].
Many other distributed languages exist, commercially (e.g., General Magic's Telescript [Telescript]) and in academia (e.g., Orca [Bal92]). However, we are not aware of any such language having been integrated with a Web browser.
Many analysts feel that two of the most important technology themes for the remainder of the decade are the Web and using computers for collaboration. Oblets provide an elegant programming framework for bring groupware, CSCW, and other collaborative applications to the Web.
[Bal92]
H.E.Bal, M.F.Kaashoek, and A.S.Tanenbaum.
Orca: A Language for Parallel Programming of Distributed Systems.
IEEE Transactions on Software Engineering, 18(3):190-205,
March 1992.
[Bharat94]
Krishna Bharat and Marc H. Brown.
Building Distributed, Multi-User Applications By Direct Manipulation.
Proc. of the 7th ACM Symposium on User Interface Software and Technology,
pages 71-81, November 1994.
[Bharat95]
Krishna Bharat and Luca Cardelli.
Distributed Applications in a Hypermedia Setting.
Proc. of the 1st Intl. Workshop on Hypermedia Design, Montpelier,
France, pages 185-192, June 1995.
[Birrell93]
Andrew D. Birrell, Greg Nelson, Susan Owicki, and Edward P. Wobber.
Network Objects.
Proc. of the 14th ACM Symposium on Operating System Principles,
pages 217-230, December 1993.
[Brown84]
Marc H. Brown and Robert Sedgwick.
A System for Algorithm Animation.
Computer Graphics, 18(3):177-186, July 1984.
[Brown94]
Marc H. Brown and Robert A. Shillner.
DeckScape: An Experimental Web Browser.
Computer Networks and ISDN Systems, 27(1995) 1097-1104.
[Brown95]
Marc H. Brown.
Browsing the Web with a Mail/News Reader.
Proc. of the 8th ACM Symposium User Interface Software and Technology,
pages 197-198, November 1995.
[Cardelli95]
Luca Cardelli.
A Language with Distributed Scope.
Computing Systems, 8(1):27-59, January 1995.
[Grail]
Grail Home Page.
http://monty.cnri.reston.va.us/grail-0.2/
[HTML3]
HTML3 Linking and Embedding Model.
http://www.w3.org/hypertext/WWW/TR/WD-insert-951221.html
[HORB]
HORB Home Page.
http://ring.etl.go.jp/openlab/horb/
[Java]
Java: Programming for the Internet.
http://java.sun.com/
[Microsoft]
Internet Development Toolbox.
http://www.microsoft.com/INTDEV/
[MMM]
MMM Browser Home page.
http://pauillac.inria.fr/~rouaix/mmm/
[SurfIt!]
SurfIt!
http://pastime.anu.edu.au/SurfIt/
[Telescript]
Telescript.
http://www.genmagic.com/Telescript/index.html
[vanDoorn95]
Matthijs van Doorn and Anton Eliëns.
Integrating Applications and the World-Wide Web.
Computer Networks and ISDN Systems, 27(1995) 1105-1110.