23.5. Making It Simple Is ComplicatedInternet Explorer's extensibility, particularly its Browser Helper Object facility, made Bugnosis's unobtrusive user interface possible. But things didn't always snap together as easily as we would have liked. In the following sections, we'll look at some of the details of the architecture that required special attention. 23.5.1. Using Browser Helper Objects and the Document Object ModelThe design decision with the largest impact was where to put the analysis engine. Fairly obviously, it had to run on the end user's computer (rather than on a remote server) in order to maintain acceptable speeds. We also considered the idea of running an HTTP proxy on the user's computer, which would be able to intercept the browser's HTTP requests involved in rendering its web pages. But there were many problems with the proxy approach:
So, instead of the proxy approach, we built the analysis directly into the Internet Explorer (IE) web browser using its Browser Helper Object (BHO) hooks.[11] Naturally, this restricted our audience to IE users. But it gave us direct access to the browser's view of the world, including its toolbars, status line, and sidebars. Most importantly, it provided read/write access to the Document Object Model (DOM) representation of the page.[12] This conferred a number of advantages:
23.5.2. The Event ModelBugnosis is an event-driven BHO. When IE starts up, it creates our Component Object Model (COM) object called IEMonitor, which then identifies itself as an event sink for the containing WebBrowser object. IE subsequently calls IEMonitor::Invoke( ) with appropriate parameters when it receives reportable events, such as UI manipulation and element download progress. The events of particular interest to us are:
23.5.2.1 Provisional analysisOf course, web pages don't load instantlythere can be a significant delay between the BeforeNavigate2 and DocumentComplete events. Bugnosis analyzes the document provisionally during this period so that it can update its toolbar status line as the embedded elements arrive. This makes the tool appear alive and responsive, and ensures that the status line always refers to the current page somehow, not to a page that it visited previously. (Bugnosis also puts the main page URL in its status line in order to avoid confusion about exactly what it's analyzing; see Figure 23-4). During the downloading period, it never alerts the user about a web bug, but it may begin to mark elements as suspicious. These provisional analyses are triggered by the arrival of ProgressChange events, which IE fires when it updates its visual meter indicating how much of the page has been loaded. The accompanying parameter is merely the percentage of the line that has been drawn on the screen so far; it is not a certain indicator of anything, but it arrives regularly and roughly in sync with page-loading activity, so it's a reasonable signal to rescan. DownloadComplete events also cause a provisional scan. 23.5.2.2 Rescanning, refreshing, and Old PaintThings change during the post-DocumentComplete period: Bugnosis remains alert so that it can see more web bugs arrive. This can happen if a client-side script loads an image after the main page has been fully delivered. If so, and the image is not served from the browser's cache, then the new image will trigger DownloadComplete and ProgressChange events, and so we will rescan the document and react to the addition. At the same time, we don't want Bugnosis to keep chirping "uh-oh!" whenever a web bug is found somewhere on the page. The goal is to alert the user once when the first web bug on a page is found, and perhaps again later if a new web bug appearsbut more than that would be really annoying! What if the user clicks the Refresh button to reload the page? This puts us in kind of a spot. Because the URL of the page doesn't change, no BeginNavigate2 event is posted: in fact, no event is delivered indicating that the page is being reloaded at all. All we can count on is some DownloadBegin, DownloadComplete, and ProgressChange activity: this is indistinguishable from client-side scripts loading data. But we'll probably recognize all of the images being loaded because they were loaded on this page already, so nothing will seem new, and so no alarm will soundeven if the user specifically pressed Reload in order to have Bugnosis rescan the page! Our compromise is that Bugnosis rescans the entire document whenever a triggering event arrives without considering whether the images are new or old, but it imposes a limit of one "uh-oh!" every three seconds. Finally, complications arise when Bugnosis makes web bugs visible:
To address these issues, before making any web bug visible, Bugnosis adds properties to the image object that record its previous dimensions and URL. When rescanning the document later on, Bugnosis recognizes them and reports these original characteristics rather than its updated ones, and excludes the image from further testing. Unfortunately, on HTTPS (encrypted) web pages, changing an embedded https:// URL to a file:// URL in order to make the web bug visible displays a warning dialog; IE complains that this encrypted web page contains unencrypted content. We couldn't find an acceptable workaround, so in this case, Bugnosis simply leaves the original invisible image in place. Sorry, Old Paint! 23.5.3. The Analysis Pane and ToolbarThe analysis pane in the lower part of the main IE window (Figure 23-1) is not intrinsically attached to the browser helper object; it's another type of IE extension called a comm bar. Comm bars (as well as explorer bars, which are oriented vertically instead of horizontally) serve only to take control of part of the IE window. Once claimed, IE provides some decoration for the pane including space for the pane's title ("Bugnosis 1.3" rotated 90°), a "close" button, and the horizontal line separating IE's main window from the comm bar. The user can move the separator with the pointer and in this way change the relative size of the two windows. 23.5.3.1 The discovery danceThe toolbar is yet another type of IE extension, similarly claiming screen space and window decoration (namely, the ability to move the toolbar around). IE invokes these three types of extensionsthe browser helper object for sinking events, the toolbar, and the comm barat startup time, by individually calling their IObjectWithSite::SetSite(IWebBrowser2*) functions with the surrounding web browser as a parameter. IE knows to do so because entries in the system registry indicate that these objects are installed. And, naturally, the three objects need to communicate with each other at runtime. However, IE doesn't call these objects' SetSite functions in a predictable order. Bugnosis therefore uses a data structure shared by all three of the objects (which luckily run in the same process) as a rendezvous point so that the objects can discover each other. 23.5.3.2 Making the Analysis Pane feel naturalAs mentioned earlier, IE doesn't manage the space it gives to the analysis pane (comm bar). Bugnosis populates this blank space with a newly created web browser controla self-contained web-rendering engine provided by IE itself that provides only an HTML rendering space, and no toolbars of any kind. Bugnosis causes its analysis to appear in this pane by obtaining the web browser control's <body> element and writing onto its innerHTML property. By providing this text output as HTML, Bugnosis matches the look-and-feel of the web browser, and also simplifies the problem of saving or printing Bugnosis output. Because the main IE window doesn't know that it contains an embedded IE window (only Bugnosis knows that), a little more work is required to make the embedded window feel natural. Specifically, when the user moves the mouse pointer over a link, we want the URL of the link to appear in the main browser window status bar. Although the web browser control does generate an event indicating that some status bar should be updated during a mouse-over, this event is not connected to the user's main web browser, because the main web browser doesn't even know that the embedded control is there. Bugnosis connects the dots by intercepting status bar update events emitted by the control and transforming them into explicit changes to the status bar text in the main web browser. Right-clicking the pointer within the analysis pane brings up a little menu (see Figure 23-5), allowing a user to export the analysis in various forms. This is essentially the same menu available through the drop-down menu on the Bugnosis toolbar, aside from some extra options available here (such as Copy text). The export options cause the analysis to be regenerated in a form that removes some of the embedded JavaScript and other constructs that would not work properly if viewed outside of a web browser control managed directly by Bugnosis. Figure 23-5. Context menu in the analysis pane23.5.3.3 Analyzing pop-up windowsPop-up windows were another challenge. Where and how can we present analysis results for these windowswindows that normally don't contain any toolbar at all? We considered taking screen space from pop-ups, just as we do for the main browser window, but decided against it. Whereas ordinary web pages don't have control over the dimensions of their window, pop-ups are often made to be just large enough to hold their content. Taking away screen space from pop-ups would usually mean rendering their content poorly. Instead, Bugnosis keeps a record of all of the analyses it has made, and allows the user to bring up an old analysis with the Pages Analyzed submenu in Figure 23-5. Therefore, the user can get the analysis by controlling the Bugnosis analysis that is visible. This also allows users to retrieve a page's analysis if the page has changed quicklyfor example, by an automatic redirect. Pop-up windows also caused us a fair number of coding headaches because of an apparent bug in IE 6. We observed that pop-up windows sometimes just don't generate a DocumentComplete event, which we need as our trigger to begin real (nonprovisional) scans of the document. To compensate for this, Bugnosis treats newly created windows very suspiciously: if no DocumentComplete arrives in a new window but other (less reliable) indicators of completeness are present, then Bugnosis performs its full scan anyway. We observed similar problems during "file download" operations (fetches of content that are not rendered natively by IE); sometimes, these generate DocumentComplete events carrying a parameter that doesn't appear to refer to any previously encountered window. In spite of Bugnosis's attempt to compensate for these irregularities, it still occasionally waits for a signal that never arrives and thus stops analyzing documents properly. Sometimes it seems that IE is itself confused: for example, we have seen the IE status line continuing to display "Opening page [URL]..." (and we can observe that Bugnosis is also still awaiting a completion signal), yet the animated icon indicating "document loading" in the upper-righthand corner of IE has stopped swirling and the page appears to be fully present. Bugs like these are hard to isolate and hard to fix; we are merely grateful that Bugnosis's own errors tend to under-alarm rather than over-alarm users. 23.5.4. Installation and UninstallationInstallation and uninstallation are extremely important because they form the first and last impression of a piece of software. On this account, the first release of Bugnosis failed miserably. Here's what went wrong. Because Bugnosis is packaged as a COM object inside a dynamic link library (DLL), it was possible to use IE's ActiveX delivery capability for installation. This technique is very straightforward: an <object> tag on a web page specifies a URL for the object. If it's not already present on the user's computer, IE fetches it. This appealed to uswe envisioned a potential user being able to read about Bugnosis at our web site, and then being able to install it with just a click or two. IE would post a security warning dialog showing information from the software publisher's certificate (the Privacy Foundation, in our case) and ask the user's permission before proceeding. To uninstall, users would select the Uninstall option from the Bugnosis menu, which would delete the Bugnosis entries from the registry and effectively decouple Bugnosis from IE for the future. 23.5.4.1 Installation/uninstallation problemsIn theory, and in closed test environments, our approach to installation and uninstallation worked reasonably well. But there were many disadvantages:
We soon realized this was becoming a disaster and put together a traditional installation mechanism: a self-extracting .EXE holding a wizard-driven installation dialog. This had the advantage of familiarity, which we should have valued more highly up front. To uninstall Bugnosis, the user is now directed to the Add/Remove Programs dialog in the control panel. 23.5.4.2 Windows XP Service Pack 2Microsoft's Service Pack 2 for Windows XP now restricts ActiveX installations even further, so it's good we got away from that installation model when we did. In SP2, IE also provides an "add-in manager" to give the user finer control over the currently installed browser extensions. On the whole, this is great, but it now also gives the user the ability to individually disable the Bugnosis toolbar or the Bugnosis browser helper object. This results in runtime configurations that previously weren't easily accessible. The next version of Bugnosis ought to ensure that all of the newly available configurations result in sensible behavior. |