1 /* See license.txt for terms of usage */ 2 3 FBL.ns(function() { with (FBL) { 4 5 // ************************************************************************************************ 6 // Constants 7 8 const Cc = Components.classes; 9 const Ci = Components.interfaces; 10 const nsIWebNavigation = Ci.nsIWebNavigation; 11 const nsIWebProgressListener = Ci.nsIWebProgressListener; 12 const nsIWebProgress = Ci.nsIWebProgress; 13 const nsISupportsWeakReference = Ci.nsISupportsWeakReference; 14 const nsISupports = Ci.nsISupports; 15 const nsIURI = Ci.nsIURI; 16 17 const NOTIFY_STATE_DOCUMENT = nsIWebProgress.NOTIFY_STATE_DOCUMENT; 18 19 const STATE_IS_WINDOW = nsIWebProgressListener.STATE_IS_WINDOW; 20 const STATE_IS_DOCUMENT = nsIWebProgressListener.STATE_IS_DOCUMENT; 21 const STATE_IS_REQUEST = nsIWebProgressListener.STATE_IS_REQUEST; 22 23 const STATE_START = nsIWebProgressListener.STATE_START; 24 const STATE_STOP = nsIWebProgressListener.STATE_STOP; 25 const STATE_TRANSFERRING = nsIWebProgressListener.STATE_TRANSFERRING; 26 27 const STOP_ALL = nsIWebNavigation.STOP_ALL; 28 29 const dummyURI = "about:layout-dummy-request"; 30 const aboutBlank = "about:blank"; 31 32 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 33 34 const tabBrowser = $("content"); 35 36 // ************************************************************************************************ 37 // Globals 38 39 var contexts = []; 40 41 // ************************************************************************************************ 42 43 top.TabWatcher = extend(new Firebug.Listener(), 44 { 45 // Store contexts where they can be accessed externally 46 contexts: contexts, 47 48 initialize: function() 49 { 50 if (Firebug.TraceModule) 51 Firebug.TraceModule.addListener(TraceListener); 52 53 if (FBTrace.DBG_INITIALIZE) 54 FBTrace.sysout("-> tabWatcher initialize "+tabBrowser); 55 56 if (tabBrowser) 57 tabBrowser.addProgressListener(TabProgressListener, NOTIFY_STATE_DOCUMENT); 58 59 httpObserver.addObserver(TabWatcherHttpObserver, "firebug-http-event", false); 60 }, 61 62 destroy: function() 63 { 64 if (FBTrace.DBG_WINDOWS) 65 FBTrace.sysout("-> tabWatcher destroy\n"); 66 67 this.shuttingDown = true; 68 69 httpObserver.removeObserver(TabWatcherHttpObserver, "firebug-http-event"); 70 71 if (tabBrowser) 72 { 73 tabBrowser.removeProgressListener(TabProgressListener); 74 75 var browsers = Firebug.chrome.getBrowsers(); 76 for (var i = 0; i < browsers.length; ++i) 77 { 78 var browser = browsers[i]; 79 this.unwatchTopWindow(browser.contentWindow); 80 } 81 } 82 83 if (Firebug.TraceModule) 84 Firebug.TraceModule.removeListener(TraceListener); 85 }, 86 87 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 88 89 /** 90 * Called when tabBrowser browsers get a new location OR when we get a explicit user op to open firebug 91 * Attaches to a top-level window. Creates context unless we just re-activated on an existing context 92 */ 93 watchTopWindow: function(win, uri, userCommands) 94 { 95 if (FBTrace.DBG_WINDOWS) 96 FBTrace.sysout("-> tabWatcher.watchTopWindow for: "+(uri instanceof nsIURI?uri.spec:uri)+ 97 ", tab: "+Firebug.getTabIdForWindow(win)+"\n"); 98 99 if (!win) 100 { 101 if (FBTrace.DBG_ERRORS) 102 FBTrace.sysout("-> tabWatcher.watchTopWindow should not have a null window!"); 103 return false; 104 } 105 106 var selectedBrowser = Firebug.chrome.getCurrentBrowser(); 107 if (selectedBrowser.cancelNextLoad) 108 { 109 // We need to cancel this load and try again after a delay... this is used 110 // mainly to prevent chaos while when the debugger is active when a page 111 // is unloaded 112 delete selectedBrowser.cancelNextLoad; 113 selectedBrowser.webNavigation.stop(STOP_ALL); 114 var url = (uri instanceof nsIURI?uri.spec:uri); 115 delayBrowserLoad(selectedBrowser, url); 116 if (FBTrace.DBG_WINDOWS) 117 FBTrace.sysout("-> tabWatcher.watchTopWindow **CANCEL&RETRY** for: "+url+ 118 ", tab: "+Firebug.getTabIdForWindow(win)+"\n"); 119 return; 120 } 121 122 var context = this.getContextByWindow(win); 123 if (context) // then we've looked at this window before in this FF session... 124 { 125 if (FBTrace.DBG_ACTIVATION) 126 FBTrace.sysout("-> tabWatcher.watchTopWindow context exists "+context.getName()); 127 if (!this.shouldShowContext(context)) 128 { 129 // ...but now it is not wanted. 130 if (context.browser) 131 delete context.browser.showFirebug; 132 this.unwatchContext(win, context); 133 134 // There shouldn't be context for this window so, remove it from the 135 // global array. 136 remove(contexts, context); 137 138 return; // did not create a context 139 } 140 // else we should show 141 } 142 else // then we've not looked this window in this session 143 { 144 // decide whether this window will be debugged or not 145 var url = (uri instanceof nsIURI) ? uri.spec : uri; 146 if (!this.shouldCreateContext(selectedBrowser, url, userCommands)) 147 { 148 if (FBTrace.DBG_ACTIVATION) 149 FBTrace.sysout("-> tabWatcher will not create context "); 150 151 delete selectedBrowser.showFirebug; 152 this.watchContext(win, null); 153 154 return false; // we did not create a context 155 } 156 157 var browser = this.getBrowserByWindow(win); 158 159 context = this.createContext(win, browser, Firebug.getContextType()); 160 } 161 162 if (win instanceof Ci.nsIDOMWindow && win.parent == win) 163 { 164 win.addEventListener("pageshow", onLoadWindowContent, onLoadWindowContent.capturing); 165 win.addEventListener("DOMContentLoaded", onLoadWindowContent, onLoadWindowContent.capturing); 166 if (FBTrace.DBG_WINDOWS) 167 FBTrace.sysout("-> tabWatcher.watchTopWindow addEventListener for pageshow, DomContentLoaded "+safeGetWindowLocation(win)); 168 } 169 170 // Dispatch watchWindow for the outer most DOM window 171 this.watchWindow(win, context); 172 173 // This is one of two places that loaded is set. The other is in watchLoadedTopWindow 174 if (context && !context.loaded) 175 { 176 context.loaded = !context.browser.webProgress.isLoadingDocument; 177 178 // If the loaded flag is set, the proper event should be dispatched. 179 if (context.loaded) 180 dispatch(this.fbListeners, "loadedContext", [context]); 181 182 if (FBTrace.DBG_WINDOWS) 183 FBTrace.sysout("-> tabWatcher context "+(context.loaded ? '*** LOADED ***' : 'isLoadingDocument')+" in watchTopWindow, id: "+context.uid+", uri: "+ 184 (uri instanceof nsIURI ? uri.spec : uri)+"\n"); 185 } 186 187 if (context && !context.loaded && !context.showContextTimeout) 188 { 189 // still loading, we want to showContext one time but not too agressively 190 // xxxHonza: In case where the timeout is fired before the context is actually 191 // loaded (i.e. context.loaded == false) and the showContext is called 192 // some panels (css, html, dome) are not initialized and remain empty. 193 // These panels use if (context.loaded) condidtion to execute the init process. 194 // I am increasing the timeout to 2000, I guess that in most cases this shouldn't 195 // caus any real delay since showContext should be called through 196 // watchLoadedTopWindow and this timeout cancelled. 197 context.showContextTimeout = setTimeout(bindFixed( function delayShowContext() 198 { 199 if (FBTrace.DBG_WINDOWS) 200 FBTrace.sysout("-> watchTopWindow delayShowContext id:"+context.showContextTimeout, context); 201 if (context.window) // Sometimes context.window is not defined ? 202 this.rushShowContext(win, context); // calls showContext 203 else 204 { 205 if(FBTrace.DBG_ERRORS) 206 FBTrace.sysout("tabWatcher watchTopWindow no context.window "+(context.browser? context.browser.currentURI.spec : " and no context.browser")+"\n"); 207 } 208 }, this), 2000); 209 } 210 else 211 { 212 if (FBTrace.DBG_WINDOWS) 213 FBTrace.sysout("-> watchTopWindow context.loaded:"+context.loaded+ " for "+context.getName()); 214 this.rushShowContext(win, context); 215 } 216 217 return context; // we did create or find a context 218 }, 219 220 rushShowContext: function(win, context) 221 { 222 if (context.showContextTimeout) // then the timeout even has not run, we'll not need it after all. 223 clearTimeout(context.showContextTimeout); 224 delete context.showContextTimeout; 225 226 // Call showContext only for currently active tab. 227 var currentURI = Firebug.chrome.getCurrentURI(); 228 if (!currentURI || currentURI.spec != context.browser.currentURI.spec) 229 { 230 if (FBTrace.DBG_WINDOWS) 231 FBTrace.sysout("-> rushShowContext: Do not show context as it's not the active tab: " + 232 context.browser.currentURI.spec + "\n"); 233 return; 234 } 235 236 this.watchContext(win, context); // calls showContext 237 }, 238 239 // Listeners decide to show or not 240 shouldShowContext: function(context) 241 { 242 if ( dispatch2(this.fbListeners, "shouldShowContext", [context])) 243 return true; 244 else 245 return false; 246 }, 247 248 // Listeners given force-in and veto on URIs/Window. 249 250 shouldCreateContext: function(browser, url, userCommands) 251 { 252 // called when win has no context, answers the question: create one, true or false? 253 254 if (!this.fbListeners) 255 return userCommands; 256 257 // Create if any listener says true to showCreateContext 258 if (dispatch2(this.fbListeners, "shouldCreateContext", [browser, url, userCommands])) 259 return true; 260 261 if (FBTrace.DBG_ACTIVATION) 262 FBTrace.sysout("-> shouldCreateContext with user: "+userCommands+ " no opinion for: "+ url); 263 264 // Do not Create if any Listener says true to shouldNotCreateContext 265 if (dispatch2(this.fbListeners, "shouldNotCreateContext", [browser, url, userCommands])) 266 return false; 267 268 if (FBTrace.DBG_ACTIVATION) 269 FBTrace.sysout("-> shouldNotCreateContext no opinion for: "+ url); 270 271 // create if user said so and no one else has an opinion. 272 return userCommands; 273 }, 274 275 createContext: function(win, browser, contextType) 276 { 277 if (contexts.length == 0) 278 Firebug.broadcast('enableXULWindow', []); 279 280 // If the page is reloaded, store the persisted state from the previous 281 // page on the new context 282 var persistedState = browser.persistedState; 283 delete browser.persistedState; 284 var location = safeGetWindowLocation(win).toString(); 285 //if (!persistedState || persistedState.location != location) 286 // persistedState = null; 287 288 // xxxHonza, xxxJJB: web application detection. Based on domain check. 289 var prevDomain = persistedState ? getDomain(persistedState.location) : null; 290 var domain = getDomain(location); 291 if (!persistedState || prevDomain != domain) 292 persistedState = null; 293 294 // The proper instance of FirebugChrome object (different for detached Firebug and 295 // accessible as Firebug.chrome property) must be used for the context object. 296 // (the global context object FirebugContext is also different for detached firebug). 297 var context = new contextType(win, browser, Firebug.chrome, persistedState); 298 contexts.push(context); 299 300 context.uid = FBL.getUniqueId(); 301 302 browser.showFirebug = true; // this is the only place we should set showFirebug. 303 304 if (FBTrace.DBG_WINDOWS || FBTrace.DBG_ACTIVATION) { 305 FBTrace.sysout("-> tabWatcher *** INIT *** context, id: "+context.uid+ 306 ", "+context.getName()+" browser "+browser.currentURI.spec+" Firebug.chrome.window: "+Firebug.chrome.window.location+" context.window: "+safeGetWindowLocation(context.window)); 307 } 308 309 dispatch(this.fbListeners, "initContext", [context, persistedState]); 310 311 return context; 312 }, 313 314 /** 315 * Called once the document within a tab is completely loaded. 316 */ 317 watchLoadedTopWindow: function(win) 318 { 319 var isSystem = isSystemPage(win); 320 321 var context = this.getContextByWindow(win); 322 if ((context && !context.window)) 323 { 324 if (FBTrace.DBG_WINDOWS) 325 FBTrace.sysout("-> tabWatcher.watchLoadedTopWindow bailing !!!, context.window: "+ 326 context.window+", isSystem: "+isSystem+"\n"); 327 328 this.unwatchTopWindow(win); 329 this.watchContext(win, null, isSystem); 330 return; 331 } 332 333 if (FBTrace.DBG_WINDOWS) 334 FBTrace.sysout("-> watchLoadedTopWindow context: "+ 335 (context?(context.uid+", loaded="+context.loaded):'undefined')+ 336 ", "+safeGetWindowLocation(win)+"\n"); 337 338 if (context && !context.loaded) 339 { 340 context.loaded = true; 341 342 if (FBTrace.DBG_WINDOWS) 343 FBTrace.sysout("-> Context *** LOADED *** in watchLoadedTopWindow, id: "+context.uid+ 344 ", uri: "+safeGetWindowLocation(win)+"\n"); 345 346 dispatch(this.fbListeners, "loadedContext", [context]); 347 348 // DOMContentLoaded arrived. Whether or not we did showContext at 400ms, do it now. 349 this.rushShowContext(win, context); 350 } 351 }, 352 353 /** 354 * Attaches to a window that may be either top-level or a frame within the page. 355 */ 356 watchWindow: function(win, context) 357 { 358 if (!context) 359 context = this.getContextByWindow(getRootWindow(win)); 360 361 var location = safeGetWindowLocation(win); 362 363 // For every window we watch, prepare for unwatch. It's OK if this is called 364 // more times (see 2695). 365 if (context && location != aboutBlank) 366 TabWatcherUnloader.registerWindow(win); 367 368 // Unfortunately, dummy requests that trigger the call to watchWindow 369 // are called several times, so we have to avoid dispatching watchWindow 370 // more than once 371 if (context && context.windows.indexOf(win) == -1 && location != aboutBlank) 372 { 373 context.windows.push(win); 374 375 if (FBTrace.DBG_WINDOWS) 376 FBTrace.sysout("-> watchWindow register *** FRAME *** to context for win.location: "+location+"\n"); 377 378 dispatch(this.fbListeners, "watchWindow", [context, win]); 379 380 if (FBTrace.DBG_WINDOWS) 381 { 382 FBTrace.sysout("-> watchWindow for: "+location+", context: "+context.uid+"\n"); 383 if (context) 384 for (var i = 0; i < context.windows.length; i++) 385 FBTrace.sysout(" context: "+context.uid+", window in context: "+context.windows[i].location.href+"\n"); 386 } 387 } 388 }, 389 390 /** 391 * Detaches from a top-level window. Destroys context 392 * Called when windows are closed, or user closes firebug 393 */ 394 unwatchTopWindow: function(win) 395 { 396 var context = this.getContextByWindow(win); 397 if (FBTrace.DBG_WINDOWS) 398 FBTrace.sysout("-> tabWatcher.unwatchTopWindow for: " + 399 (context ? context.getWindowLocation() : "NULL Context") + 400 ", context: " + context); 401 402 this.unwatchContext(win, context); 403 404 return true; // we might later allow extensions to reject unwatch 405 }, 406 407 /** 408 * Detaches from a window, top-level or frame (interior) 409 */ 410 unwatchWindow: function(win) 411 { 412 var context = this.getContextByWindow(win); 413 414 if (!context) 415 { 416 if (FBTrace.DBG_ERRORS) 417 FBTrace.sysout("unwatchWindow: no context for win "+safeGetWindowLocation(win)); 418 return; 419 } 420 421 var index = context.windows.indexOf(win); 422 if (FBTrace.DBG_WINDOWS) 423 FBTrace.sysout("-> tabWatcher.unwatchWindow context: "+context.getName()+" index of win: "+index+"/"+context.windows.length, context.windows); 424 if (index != -1) 425 { 426 context.windows.splice(index, 1); 427 dispatch(this.fbListeners, "unwatchWindow", [context, win]); 428 } 429 }, 430 431 /** 432 * Attaches to the window inside a browser because of user-activation 433 * returns false if no context was created by the attach attempt, eg extension rejected page 434 */ 435 watchBrowser: function(browser) 436 { 437 if (FBTrace.DBG_WINDOWS) 438 { 439 var uri = safeGetURI(browser); 440 FBTrace.sysout("-> tabWatcher.watchBrowser for: " + (uri instanceof nsIURI?uri.spec:uri) + "\n"); 441 } 442 443 registerFrameListener(browser); 444 445 var shouldDispatch = this.watchTopWindow(browser.contentWindow, safeGetURI(browser), true); 446 447 if (shouldDispatch) 448 { 449 dispatch(this.fbListeners, "watchBrowser", [browser]); 450 return true; 451 } 452 return false; 453 }, 454 455 /* 456 * User closes Firebug 457 */ 458 459 unwatchBrowser: function(browser, userCommands) 460 { 461 if (FBTrace.DBG_WINDOWS) 462 { 463 var uri = safeGetURI(browser); 464 FBTrace.sysout("-> tabWatcher.unwatchBrowser for: " + (uri instanceof nsIURI?uri.spec:uri) + " user commands: "+userCommands+(browser?"":"NULL BROWSER")); 465 } 466 if (!browser) 467 return; 468 469 delete browser.showFirebug; 470 471 var shouldDispatch = this.unwatchTopWindow(browser.contentWindow); 472 473 if (shouldDispatch) 474 { 475 dispatch(this.fbListeners, "unwatchBrowser", [browser, userCommands]); 476 return true; 477 } 478 return false; 479 }, 480 481 watchContext: function(win, context, isSystem) // called when tabs change in firefox 482 { 483 if (this.shuttingDown) 484 return; 485 486 var browser = context ? context.browser : this.getBrowserByWindow(win); 487 if (browser) 488 browser.isSystemPage = isSystem; 489 490 if (FBTrace.DBG_WINDOWS) 491 FBTrace.sysout("-> tabWatcher context *** SHOW *** (watchContext), id: " + 492 (context?context.uid:"null")+", uri: "+win.location.href+"\n"); 493 494 dispatch(this.fbListeners, "showContext", [browser, context]); // context is null if we don't want to debug this browser 495 }, 496 497 unwatchContext: function(win, context) 498 { 499 if (!context) 500 { 501 var browser = this.getBrowserByWindow(win); 502 if (browser) 503 { 504 browser.persistedState = {}; 505 delete browser.showFirebug; 506 dispatch(this.fbListeners, "showContext", [browser, null]); // context is null if we don't want to debug this browser 507 } 508 dispatch(this.fbListeners, "destroyContext", [null, (browser?browser.persistedState:null), browser]); 509 return; 510 } 511 512 var persistedState = {location: context.getWindowLocation()}; 513 context.browser.persistedState = persistedState; // store our state on FF browser elt 514 515 iterateWindows(context.window, function(win) 516 { 517 dispatch(TabWatcher.fbListeners, "unwatchWindow", [context, win]); 518 }); 519 520 dispatch(this.fbListeners, "destroyContext", [context, persistedState, context.browser]); 521 522 if (FBTrace.DBG_WINDOWS || FBTrace.DBG_ACTIVATION) 523 FBTrace.sysout("-> tabWatcher.unwatchContext *** DESTROY *** context "+context.uid+" for: "+ 524 (context.window?context.window.location:"no window")+" this.cancelNextLoad: "+this.cancelNextLoad+"\n"); 525 526 // this flag may be set by the debugger.destroyContext 527 if (this.cancelNextLoad) 528 { 529 delete this.cancelNextLoad; 530 context.browser.cancelNextLoad = true; 531 } 532 533 context.destroy(persistedState); 534 remove(contexts, context); 535 536 var currentBrowser = Firebug.chrome.getCurrentBrowser(); 537 if (!currentBrowser.showFirebug) // unwatchContext can be called on an unload event after another tab is selected 538 dispatch(this.fbListeners, "showContext", [browser, null]); // context is null if we don't want to debug this browser 539 540 if (contexts.length == 0) 541 Firebug.broadcast("disableXULWindow", []); 542 }, 543 544 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 545 546 getContextByWindow: function(winIn) 547 { 548 if (!winIn) 549 return; 550 551 var rootWindow = getRootWindow(winIn); 552 553 //if (FBTrace.DBG_INITIALIZE) 554 // FBTrace.sysout("winIn: "+safeGetWindowLocation(winIn).substr(0,50)+" rootWindow: "+safeGetWindowLocation(rootWindow)); 555 556 if (rootWindow) 557 { 558 for (var i = 0; i < contexts.length; ++i) 559 { 560 var context = contexts[i]; 561 if (context.window == rootWindow) 562 return context; 563 } 564 } 565 }, 566 567 getContextBySandbox: function(sandbox) 568 { 569 for (var i = 0; i < contexts.length; ++i) 570 { 571 var context = contexts[i]; 572 if (context.sandboxes) 573 { 574 for (var iframe = 0; iframe < context.sandboxes.length; iframe++) 575 { 576 if (context.sandboxes[iframe] == sandbox) 577 return context; 578 } 579 } 580 } 581 return null; 582 }, 583 584 getBrowserByWindow: function(win) 585 { 586 var browsers = Firebug.chrome.getBrowsers(); 587 for (var i = 0; i < browsers.length; ++i) 588 { 589 var browser = browsers[i]; 590 if (browser.contentWindow == win) 591 { 592 registerFrameListener(browser); 593 return browser; 594 } 595 } 596 597 return null; 598 }, 599 600 iterateContexts: function(fn) 601 { 602 for (var i = 0; i < contexts.length; ++i) 603 { 604 var rc = fn(contexts[i]); 605 if (rc) 606 return rc; 607 } 608 }, 609 }); 610 611 // ************************************************************************************************ 612 613 var TabWatcherUnloader = 614 { 615 listeners: [], 616 617 registerWindow: function(win) 618 { 619 var root = (win.parent == win); 620 var eventName = root ? "pagehide" : "unload"; 621 var listener = bind(root ? this.onPageHide : this.onUnload, this); 622 win.addEventListener(eventName, listener, false); 623 624 if (FBTrace.DBG_WINDOWS) 625 FBTrace.sysout("-> tabWatcher.watchWindow addEventListener for " + eventName); 626 627 this.listeners.push({ 628 window: win, 629 listener: listener, 630 eventName: eventName 631 }); 632 }, 633 634 unregisterWindow: function(win) 635 { 636 var newListeners = []; 637 for (var i=0; i<this.listeners.length; i++) 638 { 639 var listener = this.listeners[i]; 640 if (listener.window != win) 641 newListeners.push(listener); 642 else 643 win.removeEventListener(listener.eventName, listener.listener, false); 644 } 645 this.listeners = newListeners; 646 }, 647 648 onPageHide: function(event) 649 { 650 var win = event.currentTarget; 651 this.unregisterWindow(win); 652 653 if (FBTrace.DBG_WINDOWS) 654 FBTrace.sysout("-> tabWatcher.Unloader; PAGE HIDE (" + 655 this.listeners.length + ") " + win.location, event); 656 657 onPageHideTopWindow(event); 658 }, 659 660 onUnload: function(event) 661 { 662 var win = event.currentTarget; 663 this.unregisterWindow(win); 664 665 if (FBTrace.DBG_WINDOWS) 666 FBTrace.sysout("-> tabWatcher.Unloader; PAGE UNLOAD (" + 667 this.listeners.length + ") " + win.location, event); 668 669 onUnloadWindow(event); 670 } 671 }; 672 673 // ************************************************************************************************ 674 675 var TabProgressListener = extend(BaseProgressListener, 676 { 677 onLocationChange: function(progress, request, uri) 678 { 679 // Only watch windows that are their own parent - e.g. not frames 680 if (progress.DOMWindow.parent == progress.DOMWindow) 681 { 682 var srcWindow = getWindowForRequest(request); 683 var browser = srcWindow ? TabWatcher.getBrowserByWindow(srcWindow) : null; 684 var requestFromFirebuggedWindow = browser && browser.showFirebug; 685 686 if (FBTrace.DBG_WINDOWS || FBTrace.DBG_ACTIVATION) 687 { 688 FBTrace.sysout("-> TabProgressListener.onLocationChange "+ 689 progress.DOMWindow.location+" to: "+ 690 (uri?uri.spec:"null location")+ 691 (requestFromFirebuggedWindow?" from firebugged window":" no firebug")); 692 } 693 694 if (uri && uri.scheme === "wyciwyg") // document.open() was called, the document was cleared. 695 evictTopWindow(progress.DOMWindow, uri); 696 697 if (uri) 698 TabWatcher.watchTopWindow(progress.DOMWindow, uri); 699 else // the location change to a non-uri means we need to hide 700 TabWatcher.watchContext(progress.DOMWindow, null, true); 701 } 702 }, 703 704 onStateChange: function(progress, request, flag, status) 705 { 706 if (FBTrace.DBG_WINDOWS) 707 FBTrace.sysout("-> TabProgressListener.onStateChange to: " 708 +safeGetName(request)+" "+getStateDescription(flag)+"\n"); 709 } 710 }); 711 712 // ************************************************************************************************ 713 714 var FrameProgressListener = extend(BaseProgressListener, 715 { 716 onStateChange: function(progress, request, flag, status) 717 { 718 if (FBTrace.DBG_WINDOWS) 719 { 720 // xxxHonza: there is too much of these messages so, disable it for now. 721 FBTrace.sysout("-> FrameProgressListener.onStateChanged for: "+safeGetName(request)+ 722 ", win: "+progress.DOMWindow.location.href+ " "+getStateDescription(flag)); 723 } 724 725 if (flag & STATE_IS_REQUEST && flag & STATE_START) 726 { 727 // We need to get the hook in as soon as the new DOMWindow is created, but before 728 // it starts executing any scripts in the page. After lengthy analysis, it seems 729 // that the start of these "dummy" requests is the only state that works. 730 731 var safeName = safeGetName(request); 732 if (safeName && ((safeName == dummyURI) || safeName == "about:document-onload-blocker") ) 733 { 734 var win = progress.DOMWindow; 735 // Another weird edge case here - when opening a new tab with about:blank, 736 // "unload" is dispatched to the document, but onLocationChange is not called 737 // again, so we have to call watchTopWindow here 738 739 if (win.parent == win && (win.location.href == "about:blank")) 740 { 741 TabWatcher.watchTopWindow(win, win.location.href); 742 return; 743 } 744 else 745 TabWatcher.watchWindow(win); 746 } 747 } 748 749 // Later I discovered that XHTML documents don't dispatch the dummy requests, so this 750 // is our best shot here at hooking them. 751 if (flag & STATE_IS_DOCUMENT && flag & STATE_TRANSFERRING) 752 { 753 TabWatcher.watchWindow(progress.DOMWindow); 754 return; 755 } 756 757 } 758 }); 759 760 // Registers frame listener for specified tab browser. 761 function registerFrameListener(browser) 762 { 763 if (browser.frameListener) 764 return; 765 766 browser.frameListener = FrameProgressListener; // just a mark saying we've registered. TODO remove! 767 browser.addProgressListener(FrameProgressListener, NOTIFY_STATE_DOCUMENT); 768 769 if (FBTrace.DBG_WINDOWS) 770 { 771 var win = browser.contentWindow; 772 FBTrace.sysout("-> tabWatcher register FrameProgressListener for: "+ 773 safeGetWindowLocation(win)+", tab: "+Firebug.getTabIdForWindow(win)+"\n"); 774 } 775 } 776 777 function getRefererHeader(request) 778 { 779 var http = QI(request, Ci.nsIHttpChannel); 780 var referer = null; 781 http.visitRequestHeaders({ 782 visitHeader: function(name, value) 783 { 784 if (name == 'referer') 785 referer = value; 786 } 787 }); 788 return referer; 789 } 790 791 var TabWatcherHttpObserver = extend(Object, 792 { 793 // nsIObserver 794 observe: function(aSubject, aTopic, aData) 795 { 796 try 797 { 798 if (aTopic == "http-on-modify-request") 799 { 800 aSubject = aSubject.QueryInterface(Ci.nsIHttpChannel); 801 this.onModifyRequest(aSubject); 802 } 803 } 804 catch (err) 805 { 806 ERROR(err); 807 } 808 }, 809 810 onModifyRequest: function(request) 811 { 812 var win = getWindowForRequest(request); 813 var tabId = Firebug.getTabIdForWindow(win); 814 815 // Tab watcher is only interested in tab related requests. 816 if (!tabId) 817 return; 818 819 // Ignore redirects 820 if (request.URI.spec != request.originalURI.spec) 821 return; 822 823 // A document request for the specified tab is here. It can be a top window 824 // request (win == win.parent) or embedded iframe request. 825 if (request.loadFlags & Ci.nsIHttpChannel.LOAD_DOCUMENT_URI) 826 { 827 if ( (FBTrace.DBG_ACTIVATION || FBTrace.DBG_WINDOWS) && win == win.parent) 828 { 829 FBTrace.sysout("-> tabWatcher TabWatcherHttpObserver *** START *** " + 830 "document request for: " + request.URI.spec + " window for request is "+safeGetWindowLocation(win)+"\n"); 831 } 832 833 if (win == win.parent) 834 { 835 // Make sure the frame listener is registered for top level window so, 836 // we can get all onStateChange events and init context for all opened tabs. 837 var browser = TabWatcher.getBrowserByWindow(win); 838 839 if (!browser) 840 return; 841 842 delete browser.FirebugLink; 843 844 if (safeGetWindowLocation(win).toString() == "about:blank") // then this page is opened in new tab or window 845 { 846 var referer = getRefererHeader(request); 847 if (referer) 848 { 849 try 850 { 851 var srcURI = makeURI(referer); 852 browser.FirebugLink = {src: srcURI, dst: request.URI}; 853 } 854 catch(e) 855 { 856 if (FBTrace.DBG_ERRORS) 857 FBTrace.sysout("tabWatcher.onModifyRequest failed to make URI from "+referer+" because "+exc, exc); 858 } 859 } 860 } 861 else 862 { 863 // Here we know the source of the request is 'win'. For viral activation and web app tracking 864 browser.FirebugLink = {src: browser.currentURI, dst: request.URI}; 865 } 866 if (FBTrace.DBG_ACTIVATION && browser.FirebugLink) 867 FBTrace.sysout("tabWatcher.onModifyRequest created FirebugLink from "+browser.FirebugLink.src.spec + " to "+browser.FirebugLink.dst.spec); 868 } 869 } 870 }, 871 872 QueryInterface : function (aIID) 873 { 874 if (aIID.equals(Ci.nsIObserver) || 875 aIID.equals(Ci.nsISupportsWeakReference) || 876 aIID.equals(Ci.nsISupports)) 877 { 878 return this; 879 } 880 881 throw Components.results.NS_NOINTERFACE; 882 } 883 }); 884 885 // ************************************************************************************************ 886 // Local Helpers 887 888 function onPageHideTopWindow(event) 889 { 890 var win = event.currentTarget; // we set the handler on a window 891 var doc = event.target; // the pagehide is sent to the document. 892 if (doc.defaultView != win) 893 return; // ignore page hides on interior windows 894 895 if (FBTrace.DBG_WINDOWS) 896 FBTrace.sysout("-> tabWatcher pagehide event.currentTarget "+safeGetWindowLocation(win), event); 897 898 // http://developer.mozilla.org/en/docs/Using_Firefox_1.5_caching#pagehide_event 899 if (event.persisted) // then the page is cached and there cannot be an unload handler 900 { 901 // see Bug 484710 - add pageIgnore event for pages that are ejected from the bfcache 902 903 if (FBTrace.DBG_WINDOWS) 904 FBTrace.sysout("-> tabWatcher onPageHideTopWindow for: "+safeGetWindowLocation(win)+"\n"); 905 TabWatcher.unwatchTopWindow(win); 906 } 907 else 908 { 909 // Page is not cached, there may be an unload 910 win.addEventListener("unload", onUnloadTopWindow, true); 911 if (FBTrace.DBG_WINDOWS) 912 FBTrace.sysout("-> tabWatcher onPageHideTopWindow set unload handler "+safeGetWindowLocation(win)+"\n"); 913 } 914 } 915 916 function evictTopWindow(win, uri) 917 { 918 if (FBTrace.DBG_WINDOWS) 919 FBTrace.sysout("-> tabWatcher evictTopWindow win "+safeGetWindowLocation(win)+" uri "+uri.spec); 920 TabWatcher.unwatchTopWindow(win); 921 } 922 923 function onUnloadTopWindow(event) 924 { 925 var win = event.currentTarget; 926 win.removeEventListener("unload", onUnloadTopWindow, true); 927 if (FBTrace.DBG_WINDOWS) 928 FBTrace.sysout("-> tabWatcher onUnloadTopWindow for: "+safeGetWindowLocation(win)+" typeof :"+typeof(win)+"\n"); 929 TabWatcher.unwatchTopWindow(win); 930 } 931 932 function onLoadWindowContent(event) 933 { 934 if (FBTrace.DBG_WINDOWS) 935 FBTrace.sysout("-> tabWatcher.onLoadWindowContent event.type: "+event.type+"\n"); 936 937 var win = event.currentTarget; 938 try 939 { 940 win.removeEventListener("pageshow", onLoadWindowContent, onLoadWindowContent.capturing); 941 if (FBTrace.DBG_WINDOWS) FBTrace.sysout("-> tabWatcher.onLoadWindowContent pageshow removeEventListener "+safeGetWindowLocation(win)); 942 } 943 catch (exc) 944 { 945 if (FBTrace.DBG_ERRORS) 946 FBTrace.sysout("-> tabWatcher.onLoadWindowContent removeEventListener pageshow fails", exc); 947 } 948 949 try 950 { 951 win.removeEventListener("DOMContentLoaded", onLoadWindowContent, onLoadWindowContent.capturing); 952 if (FBTrace.DBG_WINDOWS) FBTrace.sysout("-> tabWatcher.onLoadWindowContent DOMContentLoaded removeEventListener "+safeGetWindowLocation(win)); 953 } 954 catch (exc) 955 { 956 if (FBTrace.DBG_ERRORS) 957 FBTrace.sysout("-> tabWatcher.onLoadWindowContent removeEventListener DOMContentLoaded fails", exc); 958 } 959 960 // Signal that we got the onLoadWindowContent event. This prevents the FrameProgressListener from sending it. 961 var context = TabWatcher.getContextByWindow(win); 962 if (context) 963 context.onLoadWindowContent = true; 964 965 try 966 { 967 if (FBTrace.DBG_WINDOWS) 968 FBTrace.sysout("-> tabWatcher.onLoadWindowContent:"+safeGetWindowLocation(win), win); 969 TabWatcher.watchLoadedTopWindow(win); 970 } 971 catch(exc) 972 { 973 if (FBTrace.DBG_ERRORS) 974 FBTrace.sysout("-> tabWatchter onLoadWindowContent FAILS: "+exc, exc); 975 } 976 977 } 978 onLoadWindowContent.capturing = false; 979 980 function onUnloadWindow(event) 981 { 982 var win = event.currentTarget; 983 var eventType = "unload"; 984 if (FBTrace.DBG_WINDOWS) 985 FBTrace.sysout("-> tabWatcher.onUnloadWindow for: "+safeGetWindowLocation(win) +" removeEventListener: "+ eventType+"\n"); 986 TabWatcher.unwatchWindow(win); 987 } 988 989 function delayBrowserLoad(browser, uri) 990 { 991 setTimeout(function delayBrowserLoad100() 992 { 993 if (FBTrace.DBG_WINDOWS) 994 FBTrace.sysout("tabWatcher delayBrowserLoad100:"+uri, browser); 995 browser.loadURI(uri); 996 }, 100); 997 } 998 999 function safeGetName(request) 1000 { 1001 try 1002 { 1003 return request.name; 1004 } 1005 catch (exc) 1006 { 1007 return null; 1008 } 1009 } 1010 1011 function safeGetURI(browser) 1012 { 1013 try 1014 { 1015 return browser.currentURI; 1016 } 1017 catch (exc) 1018 { 1019 return null; 1020 } 1021 } 1022 1023 // ************************************************************************************************ 1024 1025 var TraceListener = 1026 { 1027 onDump: function(message) 1028 { 1029 var prefix = "->"; 1030 if (message.text.indexOf(prefix) == 0) 1031 { 1032 message.text = message.text.substr(prefix.length); 1033 message.text = trim(message.text); 1034 message.type = "DBG_WINDOWS"; 1035 } 1036 } 1037 }; 1038 1039 // ************************************************************************************************ 1040 1041 }}); 1042