In the distant nethermists of time, documents once briefly ruled the earth (or at least Mac OS), and to that end I managed to find a verrry interesting book recently, complete with an unopened CD-ROM. But seriously, though: what was Apple thinking with that name?
It’s not this cyberdog (image credit).
Not that cyberdog, though this is one of my favourite weirdo pinball machines.
Definitely not that cyberdog.
But thanks to all those other cyberdogs, Apple’s own Cyberdog — a seemingly ordinary web browser and Internet suite with some unusual capabilities — has since slid into search engine obscurity. Apple had some big plans for it, though, and even wanted to give developers a way to develop their own components they could run inside of it. Not just plugins, either: we’re talking viewers, UI elements and even entire protocol handlers, implemented using Apple’s version of OpenDoc embedding.
Cyberdog’s initial release belied some wild changes afoot, for in the world it inhabited, the document told the system what it needed to display, and the parts within the document displayed it. The document ruled all. The document was king.
And all of this came together in a special Cyberdog software development kit, complete with example code and the very first 1.0 release of the browser. Now that we have our PowerBook Duo 2300 rehabilitated, guess what we’re gonna look at?
At its core, Cyberdog was really just an overgrown demonstration of OpenDoc. We’re going to necessarily explore OpenDoc’s underpinnings in this article, but OpenDoc’s basic idea grew from what was initially just a standardized compound document format to defining an entire object-oriented approach, where reusable viewer and editor components could be pulled as instances into such a document and maintain their own views and state. The document, not the app you were running, thus determined its own functionality. Under John Sculley this concept became “Project Amber” in 1993 as a stepping stone to fully realizing Apple’s flavour of Taligent, the bundle of next-generation technologies to run on top of their intended successor to System 7, Pink. Eventually Taligent was to use it as its primary compound document format.
Taligent’s cross-platform focus made the Project Amber concept cross-platform too: if you had the components, an OpenDoc document theoretically could work the same on any OpenDoc platform. In June 1993 Apple, Novell, IBM and Borland officially announced OpenDoc, and in September Apple, IBM and Novell, along with a host of secondary partners like Adobe, Lotus, Oracle and Taligent itself, established Component Integration Laboratories (CI Labs for short) as a technology provider to support OpenDoc and other component technologies like CORBA. Apple subsequently released the first Developer Release of OpenDoc for System 7.5 in December 1994, with later versions developed by IBM for Windows 95, NT, OS/2 and AIX.
As System 7 grew long in the tooth, Pink transitioned to Copland and became notorious for its deeply protracted development cycle. Years before its formal demise, it was already painfully clear to Apple management that Copland would never be suitable to run Taligent on, and Apple could not financially sustain the effort. After over $100 million then invested to date in Taligent by the Apple-IBM-HP partnership ($200 million in 2023), Apple cut itself loose and sold their stake in Taligent to IBM in December 1995. OpenDoc thus became the remnant and at the time the only modern object-oriented framework Apple had left. Apple shipped OpenDoc 1.0 in November 1995.
Despite Apple’s efforts to seed the technology, OpenDoc didn’t get a lot of traction with developers and arguably even less with users, who didn’t understand what it was good for. It was also seen as a direct competitor to Microsoft’s Object Linking and Embedding (OLE) technology, causing Microsoft to wield its formidable internal synergies and powers of FUD to slow OpenDoc’s market progress further, irking IBM. Apple began work on OpenDoc Essentials, a suite of basic components for drawing, text and multimedia, but this in and of itself didn’t make a compelling show compared to existing software. On the other hand, Internet suites were the new hotness with end users and could provide developers with a new sales segment, so a parallel group worked on something something OpenDoc something Internet. After all, what could be a hotter document than an Internet document?
Thus was the nucleus of Cyberdog (named for the famous New Yorker cartoon), which entered public beta in February 1996. OpenDoc was of course an absolute requirement, and no 68K Macs need apply: its first release was strictly for Power Macintosh.
This is a screenshot from the beta showing Apple’s “Internet Pathfinder” home screen in Cyberdog, captured off some of Apple’s example developer videos. As was fashionable at the time, the suite had its own internal “home page,” centering around Apple-provided services (primarily hosted on the now-defunct
cyberdog.apple.com
, including the
cyberdog.*
Usenet hierarchy), but also collecting user bookmarks and history into what Cyberdog referred to as a notebook and log respectively. We’ll explore those concepts a little later.
Unlike most Internet suites that either used a pre-rendered image or some sort of markup language, the Pathfinder was nothing less than an OpenDoc document with buttons and GUI elements composed from standard components. Sharp eyes will have noticed that the standard Macintosh File menu has been replaced with Document. We’ll come back to that as well.
The core of Cyberdog beta was the browser, here viewing
agate.apple.com
(the name likely coming as a riff on Amber; this host is now lost, and not in the Wayback Machine), which appears to have been
cyberdog.apple.com
‘s direct ancestor. At first blush this doesn’t look too different from any other mid-1990’s web browser, and in basic operation it largely isn’t. Where the experience gets divergent is when it gets embedded. Also take note of the Cyberdog, Mail/News and Navigate menus, which are in fact provided and driven by the browser’s components.
It wouldn’t be an Internet suite without other protocols. We’re big fans of Gopherspace around here, though Cyberdog wasn’t actually a particularly good Gopher client even for the time and isn’t the main point of this article. Nevertheless, out of the box Cyberdog supported Gopher, E-mail, Telnet, Usenet news and FTP as well. Later versions could also view resources made available via AppleTalk — no HTTP required.
However, one interesting trick it’s showing in this clip is the ability to drag and drop files from a Gopher server directly onto the desktop as downloads. The same thing works for browsing FTP sites, which from an interface perspective shouldn’t be very surprising, since Gopher was (at least initially) intended to be a friendlier FTP.
To the best I can determine, Cyberdog was never part of the Apple Internet Connection Kit CD-ROM, but it was nonetheless intended to integrate with it (here surfing an early version of AICK’s hub site WebCity). Notably, this build still has the traditional File, Edit and View menus which were later eliminated in the beta. Incidentally, if this looks like the prototype Cyberdog is running on Mac OS 8, you’re right, except this video is dated over a year before Mac OS 8 was formally released — Apple wasn’t nearly so secretive about future product leaks in those days. Also note that whatever pre-release build of Mac OS 8 (7.7?) this was, it still had the System 7-era help icon instead of 8.0’s text Help menu, and it certainly wasn’t Copland.
Cyberdog reached 1.0 in May 1996, but before we get to actually running it, let’s now spend a moment talking about OpenDoc’s internals.
OpenDoc components, in OpenDoc lingo, are called “parts.” At the system level, an OpenDoc part was based on Apple’s implementation of IBM’s System Object Model, a language-independent specification for object-oriented programming. IBM intended to use SOM throughout its entire product line, most notably OS/2 where it is an integral portion of the Workplace Shell, and some portion of it remains in z/OS via OS/390. It was designed for a pervasively object-oriented world where applications written in one language would need to instantiate objects potentially written in another, and applications using such libraries would need to have a solution for the fragile binary interface problem or those libraries could not be easily upgraded. To deal with these and other pitfalls, SOM had an interface definition language for describing methods, made late-binding mandatory so that the runtime linker could automatically apply fixups (at a startup performance cost), provided more object-oriented features than competing technologies such as Microsoft COM — a/k/a ActiveX — like multiple inheritance, metaclasses and dynamic dispatch, and supported bindings theoretically in any programming language (though C was the only one ever implemented). There was a lot of potential there, but many developers didn’t take advantage of those features, and development of a component could involve a lot of boilerplate code. We’ll see this soon enough.
Apart from more abstract classes, concrete OpenDoc parts fell chiefly into two categories, namely editors and viewers. Parts could be hosted within container applications that presented the document which the part instances inhabited. Within such documents, they had a stacking order and could even clip or mask each other for complex effects. However, this also meant container apps at minimum had to be OpenDoc-aware and regular applications couldn’t use them without modification, one of OpenDoc’s many problems, though OpenDoc provided a default shell out of the box that parts could run in to avoid a chicken-and-egg situation. Part interfaces weren’t limited to their spatial boundaries within the document; an active part could also create global UI elements, most notably menus. The Cyberdog, Mail/News and Navigate menus above were from Cyberdog’s navigator part running in a document that contained only it. In more complex documents, OpenDoc parts had to all play nice with each other much like regular Mac OS applications, which tended to magnify the already existing deficiencies in the classic Mac OS’s limited multitasking.
It was also possible to bootstrap a document from scratch with an active part ready to go. System 7 introduced the concept of stationery, where a document could be saved as a template to create other documents, and OpenDoc expanded on this notion. For this and most of the rest of the article, we’ll demonstrate on the Duo 2300, running Mac OS 8.1, which bundles OpenDoc 1.2.1 as part of the operating system.
Let’s take the venerable BBEdit Lite as an example, which briefly existed as a fully-functional OpenDoc editor part called BBEdit Lite·od. The Editors folder inside the System Folder contains the various active OpenDoc components. You can just drag libraries in and out of it and OpenDoc will update its parts inventory as necessary; you don’t need to restart the Mac. From BBEdit Lite·od, which doesn’t exactly roll off the tongue, we dropped in the editor part and the Mercutio shell plugin it requires (equivalent with the Mercutio MDEF used for enhanced pull-down menus), and copied over the included stationery to the OpenDoc Stationery folder.
Like regular Mac OS stationary acts as a document factory (like tearing sheets off a steno pad), OpenDoc stationery acts as an instance factory. Stationery can be dragged (“torn off”) into existing documents or, if supported, directly opened to make new documents containing them. Conveniently you can create documents from stationery right in the Apple menu, or from the OpenDoc Stationery folder: since BBEdit Lite·od is a fully-fledged editor, opening its stationery will pop open a brand new document entirely driven by the BBEdit part.
While OpenDoc has provided the new document with a default container application (called the document shell), this document has just one part, and that part is all BBEdit. The editor part is able to present nearly its entire standard interface, even an About item in the Apple menu.
Still, to users a process like this would look only like a new and weird way to create documents; there’s nothing “compound” about what we just did and no new capabilities were obviously unlocked. While you could certainly put a BBEdit part into another compound document, that kind of defeats the purpose of a simple text editor. Bare Bones Software never went any further with the proof of concept, even before OpenDoc started circling the drain, but Apple had other demos to show off.
In this demonstration, an interactive physics demo is part of a compound document with a live view and a “movie controller” that manipulates the simulation’s time index. This product was called Dock’Em and was actually sold and used in education. Take that, Jupyter notebooks.
Another demonstration document was this one using a live database viewer part; the other UI elements on the document were also parts. Here Cyberdog’s browser component has additionally been embedded to allow the user to look up the shoes listed in the database table (using
, Jedi being another early code name for OpenDoc).
An extreme example might be this one: this screenshot shows two pre-release packages, the OpenDoc-enabled version of ClarisWorks 5.0, codenamed “Mars” (eventually scuttled in this form and reworked into the non-OpenDoc AppleWorks 5.0 in 1997) running on the pre-release version of Mac OS 8 we saw before. There are two Cyberdog-provided sections here, the browser and a button above it, plus the other pieces of the document created conventionally in ClarisWorks. We’ll play with both of those parts later on.
This leads to an obvious question: what if you get a document and don’t have the parts installed on your computer to view what’s embedded in it? This is the same situation people would have confronted with embedded web content requiring a separate plugin; you’d need a plugin finder service to see what could handle that particular MIME type. Products like Kantara’s Part Merchant were intended to act like an app store for OpenDoc parts, allowing developers to purchase and download parts on demand, while their PM Finder would dynamically hunt down compatible viewers and editors when an unsupported portion of a document was encountered.
This screenshot was strictly a mockup from one of their product circulars; PartMerchant transformed into PartBank, which offered not only OpenDoc parts — here’s their entry for BBEdit — but also Java, ActiveX and Netscape NSAPI Plugin downloads. It fizzled out late in 1997, becoming Java specialist Flashline, and was subsequently bought out by Sun.
Failing those sorts of services, such content could be referred to an existing part on the system that advertises support for it (multiple parts to handle the same sort of content could be present on one computer), or the content might include cached data in a standard format such as PICT that the system could display, or fallback text explaining the needed part could also be shown to the user.
Now that we understand a bit more of what’s happening under the hood, let’s get back to Cyberdog. However, first we’ll need to make it a bit more stable. While OpenDoc was already somewhat memory-hungry, you will find the Cyberdog experience substantially less bumpy with bigger allocations.
Before doing anything else, go to the OpenDoc Setup control panel and increase the default document size to at least 1440K, preferably 2048K. This will still fit into our Duo 2300 with a full 56MB of physical RAM and virtual memory pumped up to 64MB. Don’t start OpenDoc at system setup in case something goes wrong.
We’ll now double-click on the Cyberdog Starting Point (replacing the Internet Pathfinder) document’s goofy dog icon to open it from the Finder. The Starting Point is an OpenDoc document that contains the home screen then in vogue for Internet suites, but note well: you could open any document and use that as your starting point because we’re not explicitly opening a Cyberdog application. There is no spoon application. The document drives all by invoking Cyberdog parts within it.
In fact, there’s actually no way to open Cyberdog 1.0 directly except with a document containing a Cyberdog part. This was the last Cyberdog release where that was true, by the way.
Aside from a new, possibly less charming name, the buttons and the things they connect to are the same. Again, since Apple has long since decommissioned the Cyberdog home page and server, most of the buttons don’t do anything anymore.
For example, this was the Cyberdog 1.0 home page on Apple’s servers. The Wayback Machine doesn’t have a copy of this earliest incarnation, so these screenshots from the developer documentation are all that survive from it.
Internally, arbitrary individual browser windows are OpenDoc documents and rendered by the Cyberdog Navigator part, which resides in the Cyberdog Libraries folder within the Editors folder we saw before. Parts can have subparts; Cyberdog’s HTML viewer part is the primary subpart within this window’s navigator, but its satellite widgets are also parts.
This window also points out the current item it’s displaying, a core Cyberdog class that contains information about a given resource (web page, Usenet newsgroup, E-mail address, and so on). You can think of it as a “URL on steroids”: it necessarily includes a URL, but also the title of the resource and an icon. Items can be saved to the Finder, where they act rather like bookmarks, or committed to other sorts of storage. When an item is opened, the item determines what Cyberdog part needs to be instantiated to handle the resource the item refers to.
Those “other sorts of storage” include Cyberdog notebooks and the log, which are the bottom two windows in this shot. Like the navigator, the log and notebook are Cyberdog parts as well. The default notebook is more or less your bookmarks (to any protocol or service that Cyberdog supports), and the log contains your browsing history. Each terminal entry in the log and in a notebook is a Cyberdog item. A “finger” in the log indicates the user’s current position.
The top window was the Apple-provided search page at the time which just forwarded through to Digital’s AltaVista, then the state of the art in web searches. (Remember the Internet before Google? Cyberdog remembers.)
The Document menu is the true core of Cyberdog, and we’ll look at what it means to create a New Document further in, but the Cyberdog menu is how you operate the browser itself.
Notice there is no Quit in the Document menu: you quit by closing the document. The document, once again, is everything.
If you press Command-T or choose it from the Cyberdog menu, the Connect window appears. You can select any supported protocol from the Service box, or simply go to “URL” and enter a URL.
And here is Cyberdog 1.0 surfing the Floodgap home page, which I intentionally maintain to be compatible down to Netscape Navigator 3.0 or so. Although Cyberdog has SSL support, it does not support TLS (as Cyberdog predates TLS 1.0 in 1999), and I was not able to find a simple way to substitute in Crypto Ancienne. 1.0 is known to have a notorious bug with proxies and secure sites anyway, which would cause problems regardless for Crypto Ancienne’s proxied TLS approach, so perhaps we’ll hack in this support in a future article.
If we select Document Info from the Document menu, it explains more about the window we’re looking at. Cyberdog Navigator is listed as both its “kind” and editor. Kind, in this case, is OpenDoc’s equivalent of a filetype, usually internally specified to the system as a structured string like +//ISO 9070/ANSI::113722::US::CI LABS::Apple:Cyberdog:SomePart:Kind:SomeThing (along with another string to be shown to the user, as in this case). There are kinds listed for all the subparts as well. The Size button lets you adjust the document’s memory allocation, blurring the lines further between document and regular Mac application.
Since Cyberdog was OpenDoc’s most high-profile demonstration application, and everything Internet was hot, Apple published an SDK shortly after 1.0’s release in an attempt to build a Cyberdog-centric ecosystem around it specifically. Called the Cyberdog Programmer’s Kit, that was the book I showed you back at the beginning.
The Cyberdog 1.0 release we just played briefly with, plus all the movies with the beta screenshots, came on the included disc with an icon that looks like Cujo on dope. It also includes sample code in C++, headers and libraries. Because Cyberdog 1.0 is PowerPC-only, it only supports generating PowerPC OpenDoc parts with a suitable version of CodeWarrior or MPW. As the disc includes CodeWarrior project files, CodeWarrior Gold 9 will do nicely for our purposes, which was also released in 1996.
Cyberdog has five chief classes, among others: the global session (surfaced as an OpenDoc part with no interface) subsuming all open Cyberdog documents but not Cyberdog parts in other non-Cyberdog documents, items, services to specify a particular protocol, streams to pull down data for that protocol over the network, and display parts to show that data. Since Internet suites generally subdivide their functionality by protocol, let’s start with how a service in Cyberdog for an arbitrary protocol might be implemented.
When I want to do a simple Internet TCP socket test, my favourite service for example purposes is the finger protocol as set forth in RFC 742 et seq.: we connect to the server, we send a single ASCII line, the server emits a reply, and then the server disconnects. (In a very real implementational sense, Gopher is essentially the next step up, as it is also a single command and reply but adds a structured menu format. Some of you will recall exploiting this property to build a Gopher client on the Alpha Micro from a hacked finger client.) It’s an easy protocol to understand and build. Apple must have thought so too, because they include just such an example.
The Finger Service example is divided into three sections: the finger service proper, FSConnect as its linkage into the Cyberdog Connect window to request a hostname and username, and FSPrefs for exposing its single preference stored in Internet Config (to open the finger connection synchronously or asynchronously, which for the purposes of this article we’ll simply handwave away).
Before trying to build it, copy the examples to your hard disk — I just copied the entire disc over — and fix the access paths in CodeWarrior, which by default appear to be completely blank. All of the necessary libraries and headers are included as either part of CodeWarrior Gold 9 or on the SDK disc itself.
We’ll start with
FingerService
. This contains everything you would think would be needed: a subclassed item for references to finger resources and support for understanding finger URLs, the stream class itself, and a resource and handler for an example menu item. The component is linked against various stub dynamic libraries, notably Cyberdog’s stub shared library itself, and a number of OpenDoc stub shlbs. So far, so good.
But it’s what else gets rolled into it that raises eyebrows somewhat, and these pieces are unexpectedly linked in statically. Network access, rather than using MacTCP or OpenTransport directly, uses Cyberdog-specific classes such as a specialized domain name resolver and the
tcp_endpoint
, which is more or less a socket-like entity. These pieces don’t appear to be part of Cyberdog’s shlb — we have to compile them in. OpenDoc is even worse, with a mass of utility classes that also aren’t part of its own stubs.
The included resource file defines a
srvc
resource which points to standard
STR#
string resources to provide the protocol’s name and URL scheme (
finger
), the finger service’s C++ class name (
AppleCyberdog::FingerService
), the finger item’s class name (
AppleCyberdog::FingerItem
), and the OpenDoc kinds for its Connect interface (“
+//ISO 9070/ANSI::113722::US::CI LABS::Apple:Cyberdog:FingerSample:Kind:ConnectPanel
“) and preferences interface (“
+//ISO 9070/ANSI::113722::US::CI LABS::Apple:Cyberdog:FingerSample:Kind:PrefsPanel
“). It also specifies strings for a single menu item, called “Finger Beep.”
And that item beeps. Despite this being a C++ file, and despite implementing a C++ object backing the finger service, this function is not C++.
FingerService__DoMenuItemSelected
— not
FingerService::DoMenuItemSelected
— is actually called through SOM to override that method in
SimpleCyberService
(of which
FingerService
is a subclass), which is why we use a C declaration because the name mangling is different and SOM bindings only exist for C. Anything that needs to be accessible to a SOM call will need a SOM-compatible declaration. Fortunately, at least the pre-generated header files and stub glue code are all provided for you with the SDK, so you don’t need the SOM IDL compiler merely to build these demo projects.
This function takes an AppleCyberdog_FingerService (basically a this pointer), an SOM Environment pointer, and then the index of the selected menu item relative to this part’s menu (starting from 0), a pointer to the OpenDoc frame (ODFrame) associated with the menu event, and a pointer to the service’s menu data (CyberMenuData). Represented in SOM Object IDL, the function declaration is void DoMenuItemSelected (in long index, in ODFrame frame, in CyberMenuData menuData); (the self and environment pointers are implied arguments to every SOM call).
Setting up menus themselves is no picnic either. The Cyberdog programmer’s manual says that “[y]ou must anticipate changes in the menu bar between activations of your part. You should re-create the menu bar each time your part is activated or its menus are adjusted.” You also have to handle deactivating or removing the Cyberdog menu when your part loses focus (and activating it again when it does), and you have to allow menu items to adjust themselves and handle their own events. This turned out to be an exceptionally bad design choice for all kinds of reasons, which we’ll revisit at the end.
But once we’re past all that, the actual code to handle the menu event is very simple and much like handling pull-down menus otherwise: if the index is 0, which is the only menu item we provided in the srvc resource, then we call the Toolbox SysBeep routine. If we wanted more or different menu items, we’d add more strings to the specified menu item STR# resource, and check for and handle those indices here.
The implementation of the finger URL is pretty straightforward.
But then we start getting into a blizzard of SOM methods again when we implement the Cyberdog finger item. There is a C++ backing object, but everything else is SOM, subclassing
CyberItem
. Everything gets overridden. Everything. (To add further insult,
ctopstr
and
pstrcpy
handle conversion between Macintosh Pascal strings and regular C strings. You’d think tha