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