1 /* See license.txt for terms of usage */
  2 
  3 // ********************************************************************************************* //
  4 // Module
  5 
  6 define([
  7     "firebug/lib/lib",
  8     "firebug/lib/events",
  9     "firebug/chrome/firefox",
 10     "firebug/chrome/window",
 11     "arch/webApp",
 12     "firebug/lib/options",
 13     "firebug/chrome/tabWatcher",
 14 ],
 15 function factoryBrowser(FBL, Events, Firefox, Win, WebApp, Options, TabWatcher) {
 16 
 17 // ********************************************************************************************* //
 18 // Browser
 19 
 20 /**
 21  * Proxy to a debuggable web browser. A browser may be remote and contain one or more
 22  * JavaScript execution contexts. Each JavaScript execution context may contain one or
 23  * more compilation units. A browser provides notification to registered listeners describing
 24  * events that occur in the browser.
 25  *
 26  * @constructor
 27  * @type Browser
 28  * @return a new Browser
 29  * @version 1.0
 30  */
 31 function Browser()
 32 {
 33     //this.contexts = []; // metadata instances
 34     this.activeContext = null;
 35     this.listeners = [];  // array of Browser.listener objects
 36     this.tools = {};  // registry of known tools
 37     this.connected = false;
 38 }
 39 
 40 // ********************************************************************************************* //
 41 // API
 42 
 43 Browser.debug = {handlers: true};
 44 Browser.onDebug = function()
 45 {
 46     if (Browser.debug)
 47         throw new Error("Browser.debug set but no Brower.onDebug is defined");
 48 }
 49 
 50 Browser.unimplementedHandler = function()
 51 {
 52     if (Browser.debug && Browser.debug.handlers)
 53         Browser.onDebug("Browser.listener unimplemented event handler called ",
 54             {handler: this, args: arguments});
 55 }
 56 
 57 Browser.listener =
 58 {
 59     onBreak: function()
 60     {
 61         Browser.unimplementedHandler.apply(this, arguments);
 62     },
 63 
 64     onConsoleDebug: function()
 65     {
 66         Browser.unimplementedHandler.apply(this, arguments);
 67     },
 68 
 69     onConsoleError: function()
 70     {
 71         Browser.unimplementedHandler.apply(this, arguments);
 72     },
 73 
 74     onConsoleInfo: function()
 75     {
 76         Browser.unimplementedHandler.apply(this, arguments);
 77     },
 78 
 79     onConsoleLog: function()
 80     {
 81         Browser.unimplementedHandler.apply(this, arguments);
 82     },
 83 
 84     onConsoleWarn: function()
 85     {
 86         Browser.unimplementedHandler.apply(this, arguments);
 87     },
 88 
 89     onContextCreated: function()
 90     {
 91         Browser.unimplementedHandler.apply(this, arguments);
 92     },
 93 
 94     onContextDestroyed: function()
 95     {
 96         Browser.unimplementedHandler.apply(this, arguments);
 97     },
 98 
 99     onContextChanged: function()
100     {
101         Browser.unimplementedHandler.apply(this, arguments);
102     },
103 
104     onContextLoaded: function()
105     {
106         Browser.unimplementedHandler.apply(this, arguments);
107     },
108 
109     onInspectNode: function()
110     {
111         Browser.unimplementedHandler.apply(this, arguments);
112     },
113 
114     onResume: function()
115     {
116         Browser.unimplementedHandler.apply(this, arguments);
117     },
118 
119     onScript: function()
120     {
121         Browser.unimplementedHandler.apply(this, arguments);
122     },
123 
124     onSuspend: function()
125     {
126         Browser.unimplementedHandler.apply(this, arguments);
127     },
128 
129     onToggleBreakpoint: function()
130     {
131         Browser.unimplementedHandler.apply(this, arguments);
132     },
133 
134     onBreakpointError: function()
135     {
136         Browser.unimplementedHandler.apply(this, arguments);
137     },
138 
139     onDisconnect: function()
140     {
141         Browser.unimplementedHandler.apply(this, arguments);
142     },
143 };
144 
145 /**
146  * Testing and sanity: clearAllBreakpoints
147  */
148 Browser.prototype.clearAllBreakpoints = function()
149 {
150     Firebug.Debugger.clearAllBreakpoints();
151 }
152 
153 /**
154  * Command: clearAnnotations
155  */
156 Browser.prototype.clearAnnotations = function()
157 {
158     // should trigger event onClearAnnotations
159     Firebug.Activation.clearAnnotations();
160 }
161 
162 Browser.prototype.getWebAppByWindow = function(win)
163 {
164     if (win && win.top)
165         return new WebApp(win.top);
166 }
167 
168 Browser.prototype.getContextByWebApp = function(webApp)
169 {
170     var topMost = webApp.getTopMostWindow();
171     var context = TabWatcher.getContextByWindow(topMost);
172     return context;
173 
174     /*for (var i = 0; i < this.contexts.length; i++)
175     {
176         var context = this.contexts[i];
177         if (context.window === topMost)
178             return context
179     }*/
180 }
181 
182 Browser.prototype.getContextByWindow = function(win)
183 {
184     var webApp = this.getWebAppByWindow(win);
185     if (webApp)
186         return this.getContextByWebApp(webApp);
187 }
188 
189 /**
190  * get local metadata for the remote WebApp if it exists
191  * @return ToolInterface.WebAppContext or null if the webApp is not being debugged
192  */
193 Browser.prototype.setContextByWebApp = function(webApp, context)
194 {
195     var topMost = webApp.getTopMostWindow();
196     if (context.window !== topMost)
197     {
198         FBTrace.sysout("Browser setContextByWebApp mismatched context ",
199             {context: context, win: topMost});
200     }
201 
202     // xxxHonza: possible mem leak, the context object isn't removed from the array sometimes
203     // Do not use for now (this will be used for remoting).
204     //this.contexts.push(context);
205 }
206 
207 /**
208  * Stop debugging a WebApp and cause the destruction of a ToolsInterface.WebAppContext
209  * @param webAppContext metadata for the page that we are not going to debug any more
210  * @param userCommands true if the user of this UI said to close (vs algorithm)
211  */
212 Browser.prototype.closeContext = function(context, userCommands)
213 {
214     if (context)
215     {
216         var topWindow = context.window;
217 
218         /*if (index === -1)
219         {
220             if (FBTrace.DBG_ERRORS)
221             {
222                 var loc = Win.safeGetWindowLocation(topWindow);
223                 FBTrace.sysout("Browser.closeContext ERROR, no context matching " + loc);
224             }
225         }
226         else
227         {
228             this.contexts.splice(index, 1);
229         }*/
230 
231         // TEMP
232         TabWatcher.unwatchWindow(topWindow);
233 
234         var browser = Win.getBrowserByWindow(topWindow);
235         if (!browser)
236             throw new Error("Browser.closeContext ERROR, no browser for top most window of context "+
237                 context.getName());
238 
239         delete browser.showFirebug;
240 
241         var shouldDispatch = TabWatcher.unwatchTopWindow(browser.contentWindow);
242 
243         if (shouldDispatch)
244         {
245             var userCommands;
246 
247             // TODO remove
248             Events.dispatch(TabWatcher.fbListeners, "unwatchBrowser", [browser, userCommands]);
249             return true;
250         }
251         return false;
252     }
253 }
254 
255 /**
256  * get local metadata for the remote WebApp or create one
257  * @param webApp, ToolsInterface.WebApp representing top level window
258  * @return ToolInterface.WebAppContext
259  */
260 Browser.prototype.getOrCreateContextByWebApp = function(webApp)
261 {
262     var context = this.getContextByWebApp(webApp);
263     if (!context)
264     {
265         var topWindow = webApp.getTopMostWindow();
266         var browser = Win.getBrowserByWindow(topWindow);
267         if (FBTrace.DBG_WINDOWS)
268             FBTrace.sysout("BTI.tabWatcher.watchBrowser for: " + (topWindow.location));
269 
270         // TEMP
271         var context = TabWatcher.watchTopWindow(topWindow, browser.currentURI, true);
272         this.setContextByWebApp(webApp, context);
273 
274         // TEMP; Watch also all iframes. Firebug has been initialized when the page is already
275         // loaded and so, we can't rely on auto-registration done by FrameProgressListener.
276         Win.iterateWindows(context.window, function (win)
277         {
278             TabWatcher.watchWindow(win, context, false);
279         });
280 
281         browser.showFirebug = true;
282 
283         // TODO remove
284         Events.dispatch(TabWatcher.fbListeners, "watchBrowser", [browser]);
285     }
286     return context;
287 }
288 
289 /**
290  * The WebApp on the selected tab of the selected window of this Browser
291  * @return WebApp ( never null )
292  */
293 Browser.prototype.getCurrentSelectedWebApp = function()
294 {
295     // Remote version must seek selected XUL window first.
296     var browser = Firefox.getCurrentBrowser();
297     var webApp = new WebApp(browser.contentWindow);
298     if (FBTrace.DBG_ACTIVATION)
299         FBTrace.sysout("BTI.WebApp ", {browser: browser, webApp: webApp});
300     return webApp;
301 }
302 
303 
304 /**
305  * Returns current status of tools
306  *
307  * @function
308  * @returns  an array of Tools, an object with {toolName: string, enabled: boolean,
309  *  enable:function(boolean, fnOfBoolean),}
310  */
311 Browser.prototype.getTools = function()
312 {
313     return [];
314 };
315 
316 /**
317  * Return the status of a tool
318  * @param name, eg "console"
319  * @returns an object with properties including toolName and enabled
320  */
321 Browser.prototype.getTool = function(name)
322 {
323     // This pollutes the FBTrace console too much.
324     //if (FBTrace.DBG_ERRORS && !this.tools[name])
325     //    FBTrace.sysout("BTI.Browser.getTool; Unknown tool: " + name);
326 
327     return this.tools[name];
328 }
329 
330 /**
331  * Call on the backend
332  */
333 Browser.prototype.registerTool = function(tool)
334 {
335     var name = tool.getName();
336     if (name)
337     {
338         if (FBTrace.DBG_ERRORS && this.tools[name])
339             FBTrace.sysout("BTI.Browser.unregisterTool; Already registered tool: " + name);
340 
341         this.tools[name] = tool;
342     }
343 }
344 
345 Browser.prototype.unregisterTool = function(tool)
346 {
347     var name = tool.getName();
348     if (name)
349     {
350         if (FBTrace.DBG_ERRORS && !this.tools[name])
351             FBTrace.sysout("BTI.Browser.unregisterTool; Unknown tool: " + name);
352         else
353             delete this.tools[name];
354     }
355 }
356 
357 Browser.prototype.eachContext = function(fnOfContext)
358 {
359     try
360     {
361         return Firebug.TabWatcher.iterateContexts(fnOfContext);
362     }
363     catch (e)
364     {
365         if (FBTrace.DBG_ERRORS)
366             FBTrace.sysout("BTI.browser.eachContext; EXCEPTION " + e, e);
367     }
368 };
369 
370 /**
371  * Returns the {@link BrowserContext} that currently has focus in the browser
372  * or <code>null</code> if none.
373  *
374  * @function
375  * @returns the {@link BrowserContext} that has focus or <code>null</code>
376  */
377 Browser.prototype.getFocusBrowserContext = function()
378 {
379     return this.activeContext;
380 };
381 
382 /**
383  * Returns whether this proxy is currently connected to the underlying browser it
384  * represents.
385  *
386  *  @function
387  *  @returns whether connected to the underlying browser
388  */
389 Browser.prototype.isConnected = function()
390 {
391     return this.connected;
392 };
393 
394 /**
395  * Registers a listener (function) for a specific type of event. Listener
396  * call back functions are specified in {@link BrowserEventListener}.
397  * <p>
398  * The supported event types are:
399  * <ul>
400  *   <li>onBreak</li>
401  *   <li>onConsoleDebug</li>
402  *   <li>onConsoleError</li>
403  *   <li>onConsoleInfo</li>
404  *   <li>onConsoleLog</li>
405  *   <li>onConsoleWarn</li>
406  *   <li>onContextCreated</li>
407  *   <li>onContextChanged</li>
408  *   <li>onContextDestroyed</li>
409  *   <li>onDisconnect</li>
410  *   <li>onInspectNode</li>
411  *   <li>onResume</li>
412  *   <li>onScript</li>
413  *   <li>onToggleBreakpoint</li>
414  * </ul>
415  * <ul>
416  * <li>TODO: how can clients remove (deregister) listeners?</li>
417  * </ul>
418  * </p>
419  * @function
420  * @param eventType an event type ({@link String}) listed above
421  * @param listener a listener (function) that handles the event as specified
422  *   by {@link BrowserEventListener}
423  * @exception Error if an unsupported event type is specified
424  */
425 Browser.prototype.addListener = function(listener)
426 {
427     var list = this.listeners;
428     var i = list.indexOf(listener);
429     if (i === -1)
430         list.push(listener);
431     else
432         FBTrace.sysout("BTI.Browser.addListener; ERROR The listener is already appended " +
433             (listener.dispatchName ? listener.dispatchName : ""));
434 };
435 
436 Browser.prototype.removeListener = function(listener)
437 {
438     var list = this.listeners;
439     var i = list.indexOf(listener);
440     if (i !== -1)
441         list.splice(i, 1);
442     else
443         FBTrace.sysout("BTI.Browser.removeListener; ERROR Unknown listener " +
444             (listener.dispatchName ? listener.dispatchName : ""));
445 };
446 
447 /**
448  * Among listeners, return the first truthy value of eventName(args) or false
449  */
450 Browser.prototype.dispatch = function(eventName, args)
451 {
452     try
453     {
454         return Events.dispatch2(this.listeners, eventName, args);
455     }
456     catch (exc)
457     {
458         FBTrace.sysout("BTI.Browser.dispatch; EXCEPTION " + exc, exc);
459     }
460 }
461 
462 /**
463  * Disconnects this client from the browser it is associated with.
464  *
465  * @function
466  */
467 Browser.prototype.disconnect = function()
468 {
469     this.removeListener(Firebug);
470     TabWatcher.destroy();
471 
472     // Remove the listener after the Firebug.TabWatcher.destroy() method is called, so
473     // that the destroyContext event is properly dispatched to the Firebug object and
474     // consequently to all registered modules.
475     TabWatcher.removeListener(this);
476 
477     this._setConnected(false);
478 }
479 
480 // ********************************************************************************************* //
481 // Private, subclasses may call these functions
482 
483 /**
484  * Command to resume/suspend backend
485  */
486 Browser.prototype.toggleResume = function(resume)
487 {
488     if (FBTrace.DBG_ACTIVATION)
489         FBTrace.sysout("BTI.toggleResume" + (Firebug.getSuspended() ? "OFF" : "ON") +
490             " -> " + (!!resume ? "ON" : "OFF"));
491 
492     // This should be the only method to call suspend() and resume().
493     // either a new context or revisiting an old one
494     if (resume)
495     {
496         if (Firebug.getSuspended())
497         {
498             // This will cause onResumeFirebug for every context including this one.
499             Firebug.resume();
500         }
501     }
502     // this browser has no context
503     else
504     {
505         Firebug.suspend();
506     }
507 },
508 
509 /**
510  * Sets the browser context that has focus, possibly <code>null</code>.
511  *
512  * @function
513  * @param context a {@link BrowserContext} or <code>null</code>
514  */
515 Browser.prototype._setFocusContext = function(context)
516 {
517     var prev = this.activeContext;
518     this.activeContext = context;
519     if (prev !== context)
520         this.dispatch("onContextChanged", [prev, this.activeContext]);
521 };
522 
523 /**
524  * Sets whether this proxy is connected to its underlying browser.
525  * Sends 'onDisconnect' notification when the browser becomes disconnected.
526  *
527  * @function
528  * @param connected whether this proxy is connected to its underlying browser
529  */
530 Browser.prototype._setConnected = function(connected)
531 {
532     if (FBTrace.DBG_ACTIVATION)
533         FBTrace.sysout("BTI.Browser._setConnected " + connected + " this.connected " +
534             this.connected);
535 
536     var wasConnected = this.connected;
537     this.connected = connected;
538 
539     if (wasConnected && !connected)
540         this.dispatch("onDisconnect", [this]);
541     else if (!wasConnected && connected)
542         this.dispatch("onConnect", [this]);
543 };
544 
545 // ********************************************************************************************* //
546 // Event Listener
547 
548 /**
549  * Describes the event listener functions supported by a {@link Browser}.
550  *
551  * @constructor
552  * @type BrowserEventListener
553  * @return a new {@link BrowserEventListener}
554  * @version 1.0
555  */
556 Browser.EventListener = {
557 
558     /**
559      * Notification that execution has suspended in the specified
560      * compilation unit.
561      *
562      * @function
563      * @param compilationUnit the {@link CompilationUnit} execution has suspended in
564      * @param lineNumber the line number execution has suspended at
565      */
566     onBreak: function(compilationUnit, lineNumber) {},
567 
568     /**
569      * TODO:
570      */
571     onConsoleDebug: function() {},
572 
573     /**
574      * TODO:
575      */
576     onConsoleError: function() {},
577 
578     /**
579      * Notification the specified information messages have been logged.
580      *
581      * @function
582      * @param browserContext the {@link BrowserContext} the messages were logged from
583      * @param messages array of messages as {@link String}'s
584      */
585     onConsoleInfo: function(browserContext, messages) {},
586 
587     /**
588      * Notification the specified messages have been logged.
589      *
590      * @function
591      * @param browserContext the {@link BrowserContext} the messages were logged from
592      * @param messages array of messages as {@link String}'s
593      */
594     onConsoleLog: function(browserContext, messages) {},
595 
596     /**
597      * Notification the specified warning messages have been logged.
598      *
599      * @function
600      * @param browserContext the {@link BrowserContext} the messages were logged from
601      * @param messages array of messages as {@link String}'s
602      */
603     onConsoleWarn: function(browserContext, messages) {},
604 
605     /**
606      * Notification the specified browser context has been created. This notification
607      * is sent when a new context is created and before any scripts are compiled in
608      * the new context.
609      *
610      * @function
611      * @param browserContext the {@link BrowserContext} that was created
612      */
613     onContextCreated: function(browserContext) {},
614 
615     /**
616      * Notification the focus browser context has been changed.
617      *
618      * @function
619      * @param fromContext the previous {@link BrowserContext} that had focus or <code>null</code>
620      * @param toContext the {@link BrowserContext} that now has focus or <code>null</code>
621      */
622     onContextChanged: function(fromContext, toContext) {},
623 
624     /**
625      * Notification the specified browser context has been destroyed.
626      *
627      * @function
628      * @param browserContext the {@link BrowserContext} that was destroyed
629      */
630     onContextDestroyed: function(browserContext) {},
631 
632     /**
633      * Notification the specified browser context has completed loading.
634      *
635      * @function
636      * @param browserContext the {@link BrowserContext} that has completed loading
637      */
638     onContextLoaded: function(browserContext) {},
639 
640     /**
641      * Notification the connection to the remote browser has been closed.
642      *
643      * @function
644      * @param browser the {@link Browser} that has been disconnected
645      */
646     onDisconnect: function(browser) {},
647 
648     /**
649      * TODO:
650      */
651     onInspectNode: function() {},
652 
653     /**
654      * Notification the specified execution context has resumed execution.
655      *
656      * @function
657      * @param stack the {@link JavaScriptStack} that has resumed
658      */
659     onResume: function(stack) {},
660 
661     /**
662      * Notification the specified compilation unit has been compiled (loaded)
663      * in its browser context.
664      *
665      * @function
666      * @param compilationUnit the {@link CompilationUnit} that has been compiled
667      */
668     onScript: function(compilationUnit) {},
669 
670     /**
671      * Notification the specified breakpoint has been installed or cleared.
672      * State can be retrieved from the breakpoint to determine whether the
673      * breakpoint is installed or cleared.
674      *
675      * @function
676      * @param breakpoint the {@link Breakpoint} that has been toggled
677      */
678     onToggleBreakpoint: function(breakpoint) {},
679 
680     /**
681      * Notification the specified breakpoint has failed to install or clear.
682      * State can be retrieved from the breakpoint to determine what failed.
683      *
684      * @function
685      * @param breakpoint the {@link Breakpoint} that failed to install or clear
686      */
687     onBreakpointError: function(breakpoint) {}
688 };
689 
690 // ********************************************************************************************* //
691 
692 var clearContextTimeout = 0;
693 
694 var TabWatchListener =
695 {
696     dispatchName: "TabWatchListener",
697 
698     // called after a context is created.
699     initContext: function(context, persistedState)
700     {
701         context.panelName = context.browser.panelName;
702         if (context.browser.sidePanelNames)
703             context.sidePanelNames = context.browser.sidePanelNames;
704 
705         if (FBTrace.DBG_ERRORS && !context.sidePanelNames)
706             FBTrace.sysout("BTI.firebug.initContext sidePanelNames:", context.sidePanelNames);
707 
708         Events.dispatch(Firebug.modules, "initContext", [context, persistedState]);
709 
710         // a newly created context becomes the default for the view
711         Firebug.chrome.setFirebugContext(context);
712 
713         // a newly created context is active
714         Firebug.connection.toggleResume(context);
715     },
716 
717     
718     // To be called from Firebug.TabWatcher only, see selectContext
719     // null as context means we don't debug that browser
720     showContext: function(browser, context)
721     {
722         // the context becomes the default for its view
723         Firebug.chrome.setFirebugContext(context);
724         // resume, after setting Firebug.currentContext
725         Firebug.connection.toggleResume(context);
726 
727         // tell modules we may show UI
728         Events.dispatch(Firebug.modules, "showContext", [browser, context]);
729 
730         Firebug.showContext(browser, context);
731     },
732 
733     // The context for this browser has been destroyed and removed.
734     unwatchBrowser: function(browser)
735     {
736         Firebug.connection.toggleResume(false);
737     },
738 
739     // Either a top level or a frame (interior window) for an existing context is seen by the TabWatcher.
740     watchWindow: function(context, win)
741     {
742         for (var panelName in context.panelMap)
743         {
744             var panel = context.panelMap[panelName];
745             panel.watchWindow(context, win);
746         }
747 
748         Events.dispatch(Firebug.modules, "watchWindow", [context, win]);
749     },
750 
751     unwatchWindow: function(context, win)
752     {
753         for (var panelName in context.panelMap)
754         {
755             var panel = context.panelMap[panelName];
756             panel.unwatchWindow(context, win);
757         }
758 
759         Events.dispatch(Firebug.modules, "unwatchWindow", [context, win]);
760     },
761 
762     loadedContext: function(context)
763     {
764         if (!context.browser.currentURI)
765             FBTrace.sysout("BTI.firebug.loadedContext problem browser ", context.browser);
766 
767         Events.dispatch(Firebug.modules, "loadedContext", [context]);
768     },
769 
770     destroyContext: function(context, persistedState, browser)
771     {
772         // then we are called just to clean up
773         if (!context)
774             return;
775 
776         Events.dispatch(Firebug.modules, "destroyContext", [context, persistedState]);
777 
778         // xxxHonza: Not sure if this code is correct. Test case: Firebug active, reload
779         // 1) The Firebug.currentContext can be already set to the new one
780         // 2) The Firebug.currentContext can be already null.
781         // Calling clearPanels() is important, because it also clears the statusPath, which
782         // contains references to panel objects (e.g. the page document in case of the HTML panel)
783         if (Firebug.currentContext == context || !Firebug.currentContext)
784         {
785             // disconnect the to-be-destroyed panels from the panelBar
786             Firebug.chrome.clearPanels();
787             // Firebug.currentContext is about to be destroyed
788             Firebug.chrome.setFirebugContext(null);
789         }
790 
791         var browser = context.browser;
792 
793         // Persist remnants of the context for restoration if the user reloads
794         try
795         {
796             browser.panelName = context.panelName;
797             browser.sidePanelNames = context.sidePanelNames;
798         }
799         catch (e)
800         {
801             if (FBTrace.DBG_ERRORS)
802                 FBTrace.sysout("browser.destroyContext; " + e, e);
803         }
804 
805         // Next time the context is deleted and removed from the Firebug.TabWatcher,
806         // we clean up in unWatchBrowser.
807     },
808 
809     onSourceFileCreated: function()
810     {
811         Events.dispatch(Firebug.modules, "onSourceFileCreated", arguments);
812     },
813 
814     shouldCreateContext: function()
815     {
816         if (Events.dispatch2(Firebug.modules, "shouldCreateContext", arguments))
817             return true;
818         else
819             return false;
820     },
821 
822     shouldNotCreateContext: function()
823     {
824         if (Events.dispatch2(Firebug.modules, "shouldNotCreateContext", arguments))
825             return true;
826         else
827             return false;
828     },
829 
830     shouldShowContext: function()
831     {
832         if (Events.dispatch2(Firebug.modules, "shouldShowContext", arguments))
833             return true;
834         else
835             return false;
836     }
837 };
838 
839 // ********************************************************************************************* //
840 
841 Browser.prototype.connect = function ()
842 {
843     // Events fired on browser are re-broadcasted to Firebug.modules
844     Firebug.connection.addListener(Firebug);
845 
846     // Listen for preference changes. This way the options module is not dependent on tools
847     // xxxHonza: can this be in Browser interface?
848     Options.addListener(
849     {
850         updateOption: function(name, value)
851         {
852             Firebug.connection.dispatch("updateOption", [name, value]);
853         }
854     });
855 
856     TabWatcher.initialize();
857     TabWatcher.addListener(TabWatchListener);
858 
859     this._setConnected(true);
860 }
861 
862 // ********************************************************************************************* //
863 
864 return exports = Browser;
865 
866 // ********************************************************************************************* //
867 });
868