1 /* See license.txt for terms of usage */ 2 3 define([ 4 "firebug/lib/object", 5 "firebug/firebug", 6 "firebug/chrome/firefox", 7 "firebug/lib/xpcom", 8 "firebug/net/requestObserver", 9 "firebug/lib/events", 10 "firebug/lib/url", 11 "firebug/lib/http", 12 "firebug/chrome/window", 13 "firebug/lib/string", 14 "firebug/lib/array", 15 "firebug/trace/debug", 16 "firebug/trace/traceListener", 17 "firebug/trace/traceModule", 18 "firebug/chrome/tabContext", 19 ], 20 function(Obj, Firebug, Firefox, Xpcom, HttpRequestObserver, Events, Url, Http, Win, 21 Str, Arr, Debug, TraceListener, TraceModule) { 22 23 // ********************************************************************************************* // 24 // Constants 25 26 const Cc = Components.classes; 27 const Ci = Components.interfaces; 28 const nsIWebNavigation = Ci.nsIWebNavigation; 29 const nsIWebProgressListener = Ci.nsIWebProgressListener; 30 const nsIWebProgress = Ci.nsIWebProgress; 31 const nsISupportsWeakReference = Ci.nsISupportsWeakReference; 32 const nsISupports = Ci.nsISupports; 33 const nsIURI = Ci.nsIURI; 34 35 const NOTIFY_STATE_DOCUMENT = nsIWebProgress.NOTIFY_STATE_DOCUMENT; 36 37 const STATE_IS_WINDOW = nsIWebProgressListener.STATE_IS_WINDOW; 38 const STATE_IS_DOCUMENT = nsIWebProgressListener.STATE_IS_DOCUMENT; 39 const STATE_IS_REQUEST = nsIWebProgressListener.STATE_IS_REQUEST; 40 41 const STATE_START = nsIWebProgressListener.STATE_START; 42 const STATE_STOP = nsIWebProgressListener.STATE_STOP; 43 const STATE_TRANSFERRING = nsIWebProgressListener.STATE_TRANSFERRING; 44 45 const STOP_ALL = nsIWebNavigation.STOP_ALL; 46 47 const dummyURI = "about:layout-dummy-request"; 48 const aboutBlank = "about:blank"; 49 50 // ********************************************************************************************* // 51 // Globals 52 53 var contexts = []; 54 55 var showContextTimeout = 200; 56 57 // ********************************************************************************************* // 58 59 /** 60 * @object TabWatcher object is responsible for monitoring page load/unload events 61 * and triggering proper Firebug UI refresh by firing events. This object is also 62 * responsible for creation of a context object that contains meta-data about currently 63 * debugged page. 64 */ 65 Firebug.TabWatcher = Obj.extend(new Firebug.Listener(), 66 /** @lends Firebug.TabWatcher */ 67 { 68 // Store contexts where they can be accessed externally 69 contexts: contexts, 70 71 initialize: function() 72 { 73 this.traceListener = new TraceListener("->", "DBG_WINDOWS", true); 74 TraceModule.addListener(this.traceListener); 75 76 HttpRequestObserver.addObserver(TabWatcherHttpObserver, "firebug-http-event", false); 77 }, 78 79 initializeUI: function() 80 { 81 var tabBrowser = Firefox.getElementById("content"); 82 83 if (FBTrace.DBG_INITIALIZE) 84 FBTrace.sysout("-> tabWatcher initializeUI "+tabBrowser); 85 86 if (tabBrowser) 87 tabBrowser.addProgressListener(TabProgressListener); 88 }, 89 90 destroy: function() 91 { 92 if (FBTrace.DBG_WINDOWS) 93 FBTrace.sysout("-> tabWatcher destroy"); 94 95 this.shuttingDown = true; 96 97 HttpRequestObserver.removeObserver(TabWatcherHttpObserver, "firebug-http-event"); 98 99 var tabBrowser = Firefox.getElementById("content"); 100 if (tabBrowser) 101 { 102 try 103 { 104 // Exception thrown: tabBrowser.removeProgressListener is not a function 105 // when Firebug is in detached state and the origin browser window is closed. 106 tabBrowser.removeProgressListener(TabProgressListener); 107 } 108 catch (e) 109 { 110 FBTrace.sysout("tabWatcher.destroy; EXCEPTION " + e, e); 111 } 112 113 var browsers = Firefox.getBrowsers(); 114 for (var i = 0; i < browsers.length; ++i) 115 { 116 var browser = browsers[i]; 117 this.unwatchTopWindow(browser.contentWindow); 118 unregisterFrameListener(browser); 119 } 120 } 121 122 TraceModule.removeListener(this.traceListener); 123 124 var listeners = TabWatcherUnloader.listeners; 125 if (listeners.length > 0) 126 { 127 if (FBTrace.DBG_ERRORS) 128 { 129 FBTrace.sysout("-> tabWatcher.destroy; ERROR unregistered listeners! (" + 130 listeners.length + ")", listeners); 131 } 132 133 TabWatcherUnloader.unregisterAll(); 134 } 135 }, 136 137 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 138 139 /** 140 * Called when tabBrowser browsers get a new location OR when we get a explicit user op 141 * to open firebug. 142 * Attaches to a top-level window. Creates context unless we just re-activated on an 143 * existing context. 144 */ 145 watchTopWindow: function(win, uri, userCommands) 146 { 147 if (FBTrace.DBG_WINDOWS) 148 FBTrace.sysout("-> tabWatcher.watchTopWindow for: " + 149 (uri instanceof nsIURI?uri.spec:uri) + ", tab: " + 150 Win.getWindowProxyIdForWindow(win)); 151 152 if (!win) 153 { 154 if (FBTrace.DBG_ERRORS) 155 FBTrace.sysout("-> tabWatcher.watchTopWindow should not have a null window!"); 156 return false; 157 } 158 159 // Do not use Firefox.getCurrentBrowser(); since the current tab can be already 160 // different from what is passed into this function (see issue 4681) 161 // Can be also null, if the window is just closing. 162 var selectedBrowser = Win.getBrowserByWindow(win); 163 164 var context = this.getContextByWindow(win); 165 if (context) // then we've looked at this window before in this FF session... 166 { 167 if (FBTrace.DBG_ACTIVATION) 168 FBTrace.sysout("-> tabWatcher.watchTopWindow context exists "+context.getName()); 169 170 if (!this.shouldShowContext(context)) 171 { 172 // ...but now it is not wanted. 173 if (context.browser) 174 delete context.browser.showFirebug; 175 this.unwatchContext(win, context); 176 177 return; // did not create a context 178 } 179 180 // Special case for about:blank (see issue 5120) 181 // HTML panel's edit mode can cause onStateChange changes and context 182 // recreation. 183 if (context.loaded && context == Firebug.currentContext && 184 context.getName() == "about:blank") 185 { 186 FBTrace.sysout("tabWatcher.watchTopWindow; page already watched"); 187 return; 188 } 189 } 190 else // then we've not looked this window in this session 191 { 192 // decide whether this window will be debugged or not 193 var url = (uri instanceof nsIURI) ? uri.spec : uri; 194 if (!this.shouldCreateContext(selectedBrowser, url, userCommands)) 195 { 196 if (FBTrace.DBG_ACTIVATION) 197 FBTrace.sysout("-> tabWatcher will not create context "); 198 199 delete selectedBrowser.showFirebug; 200 this.watchContext(win, null); 201 202 return false; // we did not create a context 203 } 204 205 var browser = this.getBrowserByWindow(win); 206 207 context = this.createContext(win, browser, Firebug.getContextType()); 208 } 209 210 if (win instanceof Ci.nsIDOMWindow && win.parent == win && context) 211 { 212 // xxxHonza: This place can be called multiple times for one window, so 213 // make sure event listeners are not registered twice. 214 // There should be a better way to find out whether the listeneres are actually 215 // registered for the window. 216 context.removeEventListener(win, "pageshow", onLoadWindowContent, 217 onLoadWindowContent.capturing); 218 context.removeEventListener(win, "DOMContentLoaded", onLoadWindowContent, 219 onLoadWindowContent.capturing); 220 221 // Re-register again since it could have been done too soon before. 222 context.addEventListener(win, "pageshow", onLoadWindowContent, 223 onLoadWindowContent.capturing); 224 context.addEventListener(win, "DOMContentLoaded", onLoadWindowContent, 225 onLoadWindowContent.capturing); 226 227 if (FBTrace.DBG_WINDOWS) 228 FBTrace.sysout("-> tabWatcher.watchTopWindow addEventListener for pageshow, " + 229 "DomContentLoaded " + Win.safeGetWindowLocation(win)); 230 } 231 232 // Dispatch watchWindow for the outer most DOM window 233 this.watchWindow(win, context); 234 235 // This is one of two places that loaded is set. The other is in watchLoadedTopWindow 236 if (context && !context.loaded) 237 { 238 context.loaded = !context.browser.webProgress.isLoadingDocument; 239 240 // If the loaded flag is set, the proper event should be dispatched. 241 if (context.loaded) 242 Events.dispatch(this.fbListeners, "loadedContext", [context]); 243 244 if (FBTrace.DBG_WINDOWS) 245 FBTrace.sysout("-> tabWatcher context " + 246 (context.loaded ? '*** LOADED ***' : 'isLoadingDocument') + 247 " in watchTopWindow, id: "+context.uid+", uri: "+ 248 (uri instanceof nsIURI ? uri.spec : uri)); 249 } 250 251 if (context && !context.loaded && !context.showContextTimeout) 252 { 253 this.rushShowContextTimeout(win, context, 20); 254 } 255 else 256 { 257 if (FBTrace.DBG_WINDOWS) 258 FBTrace.sysout("-> watchTopWindow context.loaded:" + context.loaded + " for " + 259 context.getName()); 260 261 this.rushShowContext(win, context); 262 } 263 264 return context; // we did create or find a context 265 }, 266 267 rushShowContextTimeout: function(win, context, tryAgain) 268 { 269 if (FBTrace.DBG_WINDOWS) 270 FBTrace.sysout("-> rushShowContextTimeout: tryAgain: " + tryAgain); 271 272 // still loading, we want to showContext one time but not too agressively 273 var handler = Obj.bindFixed(function delayShowContext() 274 { 275 if (FBTrace.DBG_WINDOWS) 276 FBTrace.sysout("-> watchTopWindow delayShowContext id:" + 277 context.showContextTimeout, context); 278 279 if (context.browser && context.browser.webProgress.isLoadingDocument && --tryAgain > 0) 280 { 281 this.rushShowContextTimeout(win, context, tryAgain); 282 return; 283 } 284 285 // Sometimes context.window is not defined, especially when running tests. 286 if (context.window) 287 { 288 this.rushShowContext(win, context); // calls showContext 289 } 290 else 291 { 292 if (FBTrace.DBG_ERRORS) 293 FBTrace.sysout("tabWatcher watchTopWindow no context.window " + 294 (context.browser? context.browser.currentURI.spec : 295 " and no context.browser")); 296 } 297 }, this); 298 299 context.showContextTimeout = window.setTimeout(handler, showContextTimeout); 300 }, 301 302 rushShowContext: function(win, context) 303 { 304 // then the timeout even has not run, we'll not need it after all. 305 if (context.showContextTimeout) 306 clearTimeout(context.showContextTimeout); 307 delete context.showContextTimeout; 308 309 // Call showContext only for currently active tab. 310 var currentURI = Firefox.getCurrentURI(); 311 if (!currentURI || currentURI.spec != context.browser.currentURI.spec) 312 { 313 if (FBTrace.DBG_WINDOWS) 314 { 315 FBTrace.sysout("-> rushShowContext: Do not show context as it's not " + 316 "the active tab: " + context.browser.currentURI.spec); 317 } 318 return; 319 } 320 321 this.watchContext(win, context); // calls showContext 322 }, 323 324 // Listeners decide to show or not 325 shouldShowContext: function(context) 326 { 327 if (Events.dispatch2(this.fbListeners, "shouldShowContext", [context])) 328 return true; 329 else 330 return false; 331 }, 332 333 // Listeners given force-in and veto on URIs/Window. 334 shouldCreateContext: function(browser, url, userCommands) 335 { 336 // called when win has no context, answers the question: create one, true or false? 337 338 if (!this.fbListeners) 339 return userCommands; 340 341 // Do not Create if any Listener says true to shouldNotCreateContext 342 if (Events.dispatch2(this.fbListeners, "shouldNotCreateContext", 343 [browser, url, userCommands])) 344 { 345 if (FBTrace.DBG_ACTIVATION) 346 FBTrace.sysout("-> shouldNotCreateContext vetos create context for: " + url); 347 return false; 348 } 349 350 if (FBTrace.DBG_ACTIVATION) 351 FBTrace.sysout("-> shouldCreateContext FBLISTENERS", this.fbListeners); 352 353 // Create if any listener says true to showCreateContext 354 if (Events.dispatch2(this.fbListeners, "shouldCreateContext", 355 [browser, url, userCommands])) 356 { 357 if (FBTrace.DBG_ACTIVATION) 358 FBTrace.sysout("-> shouldCreateContext with user: "+userCommands+ 359 " one listener says yes to "+ url, this.fbListeners); 360 return true; 361 } 362 363 if (FBTrace.DBG_ACTIVATION) 364 FBTrace.sysout("-> shouldCreateContext with user: "+userCommands + 365 " no opinion for: "+ url); 366 367 // create if user said so and no one else has an opinion. 368 return userCommands; 369 }, 370 371 createContext: function(win, browser, contextType) 372 { 373 // If the page is reloaded, store the persisted state from the previous 374 // page on the new context 375 var persistedState = browser.persistedState; 376 delete browser.persistedState; 377 var location = Win.safeGetWindowLocation(win).toString(); 378 //if (!persistedState || persistedState.location != location) 379 // persistedState = null; 380 381 // xxxHonza, xxxJJB: web application detection. Based on domain check. 382 var prevDomain = persistedState ? Url.getDomain(persistedState.location) : null; 383 var domain = Url.getDomain(location); 384 // Remove this, see 3484 385 //if (!persistedState || prevDomain != domain) 386 // persistedState = null; 387 388 // The proper instance of Firebug.chrome object (different for detached Firebug and 389 // accessible as Firebug.chrome property) must be used for the context object. 390 // (the global context object Firebug.currentContext is also different for 391 // detached firebug). 392 var context = new contextType(win, browser, Firebug.chrome, persistedState); 393 contexts.push(context); 394 395 context.uid = Obj.getUniqueId(); 396 397 browser.showFirebug = true; // this is the only place we should set showFirebug. 398 399 if (FBTrace.DBG_WINDOWS || FBTrace.DBG_ACTIVATION) 400 { 401 FBTrace.sysout("-> tabWatcher *** INIT *** context, id: " + context.uid + 402 ", " + context.getName() + " browser " + browser.currentURI.spec + 403 " Firebug.chrome.window: " + Firebug.chrome.window.location + 404 " context.window: " + Win.safeGetWindowLocation(context.window)); 405 } 406 407 Events.dispatch(this.fbListeners, "initContext", [context, persistedState]); 408 409 return context; 410 }, 411 412 /** 413 * Called once the document within a tab is completely loaded. 414 */ 415 watchLoadedTopWindow: function(win) 416 { 417 var isSystem = Url.isSystemPage(win); 418 419 var context = this.getContextByWindow(win); 420 if (context && !context.window) 421 { 422 if (FBTrace.DBG_WINDOWS) 423 FBTrace.sysout("-> tabWatcher.watchLoadedTopWindow bailing !!!, context.window: " + 424 context.window + ", isSystem: " + isSystem); 425 426 this.unwatchTopWindow(win); 427 this.watchContext(win, null, isSystem); 428 return; 429 } 430 431 if (FBTrace.DBG_WINDOWS) 432 FBTrace.sysout("-> watchLoadedTopWindow context: " + 433 (context ? (context.uid + ", loaded=" + context.loaded) : "undefined")+ 434 ", " + Win.safeGetWindowLocation(win)); 435 436 if (context && !context.loaded) 437 { 438 context.loaded = true; 439 440 if (FBTrace.DBG_WINDOWS) 441 FBTrace.sysout("-> Context *** LOADED *** in watchLoadedTopWindow, id: " + 442 context.uid + ", uri: " + Win.safeGetWindowLocation(win)); 443 444 Events.dispatch(this.fbListeners, "loadedContext", [context]); 445 446 // DOMContentLoaded arrived. Whether or not we did showContext at 400ms, do it now. 447 this.rushShowContext(win, context); 448 } 449 }, 450 451 /** 452 * Attaches to a window that may be either top-level or a frame within the page. 453 */ 454 watchWindow: function(win, context, skipCompletedDocuments) 455 { 456 if (!context) 457 context = this.getContextByWindow(Win.getRootWindow(win)); 458 459 var location = Win.safeGetWindowLocation(win); 460 461 // For every window we watch, prepare for unwatch. It's OK if this is called 462 // more times (see 2695). 463 if (context) 464 TabWatcherUnloader.registerWindow(win); 465 466 try 467 { 468 // If the documents is already completed do not register the window 469 // it should be registered already at this point 470 // This condition avoids situation when "about:document-onload-blocker" 471 // and STATE_START is fired for a window, which is consequently never 472 // firing "unload" and so, stays registered within context.windows 473 // See issue 5582 (comment #4) 474 if (skipCompletedDocuments && win.document.readyState == "complete") 475 return; 476 } 477 catch (err) 478 { 479 } 480 481 // Unfortunately, dummy requests that trigger the call to watchWindow 482 // are called several times, so we have to avoid dispatching watchWindow 483 // more than once 484 if (context && context.windows.indexOf(win) == -1) 485 { 486 context.windows.push(win); 487 488 if (FBTrace.DBG_WINDOWS) 489 { 490 FBTrace.sysout("-> tabWatcher.watchWindow; " + Win.safeGetWindowLocation(win) + 491 " [" + Win.getWindowId(win).toString() + "] " + context.windows.length + 492 " - " + win.document.readyState); 493 } 494 495 Events.dispatch(this.fbListeners, "watchWindow", [context, win]); 496 497 if (FBTrace.DBG_WINDOWS) 498 { 499 FBTrace.sysout("-> watchWindow for: " + location + ", context: " + context.uid); 500 501 if (context) 502 { 503 for (var i = 0; i < context.windows.length; i++) 504 FBTrace.sysout("context: " + context.uid + ", window in context: " + 505 context.windows[i].location.href); 506 } 507 } 508 } 509 }, 510 511 /** 512 * Detaches from a top-level window. Destroys context 513 * Called when windows are closed, or user closes firebug 514 */ 515 unwatchTopWindow: function(win) 516 { 517 var context = this.getContextByWindow(win); 518 if (FBTrace.DBG_WINDOWS) 519 { 520 FBTrace.sysout("-> tabWatcher.unwatchTopWindow for: " + 521 (context ? context.getWindowLocation() : "NULL Context") + 522 ", context: " + context); 523 } 524 525 this.unwatchContext(win, context); 526 527 // Make sure all listeners ('unload' and 'pagehide') are removed. 528 Win.iterateWindows(win, function(win) 529 { 530 TabWatcherUnloader.unregisterWindow(win); 531 }); 532 533 // we might later allow extensions to reject unwatch 534 return true; 535 }, 536 537 /** 538 * Detaches from a window, top-level or frame (interior) 539 */ 540 unwatchWindow: function(win) 541 { 542 var context = this.getContextByWindow(win); 543 544 if (!context) 545 { 546 if (FBTrace.DBG_ERRORS) 547 { 548 FBTrace.sysout("unwatchWindow: ERROR no context for win " + 549 Win.safeGetWindowLocation(win)); 550 } 551 return; 552 } 553 554 var index = context.windows.indexOf(win); 555 if (FBTrace.DBG_WINDOWS) 556 { 557 FBTrace.sysout("-> tabWatcher.unwatchWindow; " + Win.safeGetWindowLocation(win) + 558 " [" + Win.getWindowId(win).toString() + "] " + context.windows.length + 559 " - " + win.document.readyState); 560 } 561 562 if (index != -1) 563 { 564 context.windows.splice(index, 1); 565 Events.dispatch(this.fbListeners, "unwatchWindow", [context, win]); 566 } 567 }, 568 569 /** 570 * Attaches to the window inside a browser because of user-activation 571 * returns false if no context was created by the attach attempt, eg extension rejected page 572 */ 573 watchBrowser: function(browser) 574 { 575 if (FBTrace.DBG_WINDOWS) 576 { 577 var uri = Http.safeGetURI(browser); 578 FBTrace.sysout("-> tabWatcher.watchBrowser for: " + 579 (uri instanceof nsIURI?uri.spec:uri)); 580 } 581 582 registerFrameListener(browser); 583 584 var shouldDispatch = this.watchTopWindow(browser.contentWindow, 585 Http.safeGetURI(browser), true); 586 if (shouldDispatch) 587 { 588 Events.dispatch(this.fbListeners, "watchBrowser", [browser]); 589 return true; 590 } 591 592 return false; 593 }, 594 595 /** 596 * User closes Firebug 597 */ 598 unwatchBrowser: function(browser, userCommands) 599 { 600 if (FBTrace.DBG_WINDOWS) 601 { 602 var uri = Http.safeGetURI(browser); 603 FBTrace.sysout("-> tabWatcher.unwatchBrowser for: " + 604 (uri instanceof nsIURI ? uri.spec : uri) + " user commands: " + userCommands + 605 (browser ? "" : "NULL BROWSER")); 606 } 607 608 if (!browser) 609 return; 610 611 delete browser.showFirebug; 612 613 unregisterFrameListener(browser); 614 615 var shouldDispatch = this.unwatchTopWindow(browser.contentWindow); 616 617 if (shouldDispatch) 618 { 619 Events.dispatch(this.fbListeners, "unwatchBrowser", [browser, userCommands]); 620 return true; 621 } 622 return false; 623 }, 624 625 // called when tabs change in firefox 626 watchContext: function(win, context, isSystem) 627 { 628 if (this.shuttingDown) 629 return; 630 631 var browser = context ? context.browser : this.getBrowserByWindow(win); 632 if (browser) 633 browser.isSystemPage = isSystem; 634 635 if (FBTrace.DBG_WINDOWS) 636 FBTrace.sysout("-> tabWatcher context *** SHOW *** (watchContext), id: " + 637 (context?context.uid:"null")+", uri: "+win.location.href); 638 639 // context is null if we don't want to debug this browser 640 Events.dispatch(this.fbListeners, "showContext", [browser, context]); 641 }, 642 643 unwatchContext: function(win, context) 644 { 645 if (!context) 646 { 647 var browser = this.getBrowserByWindow(win); 648 if (browser) 649 { 650 browser.persistedState = {}; 651 delete browser.showFirebug; 652 653 // context is null if we don't want to debug this browser 654 Events.dispatch(this.fbListeners, "showContext", [browser, null]); 655 } 656 657 Events.dispatch(this.fbListeners, "destroyContext", 658 [null, (browser?browser.persistedState:null), browser]); 659 return; 660 } 661 662 var persistedState = {location: context.getWindowLocation()}; 663 context.browser.persistedState = persistedState; // store our state on FF browser elt 664 665 Win.iterateWindows(context.window, function(win) 666 { 667 Events.dispatch(Firebug.TabWatcher.fbListeners, "unwatchWindow", [context, win]); 668 }); 669 670 Events.dispatch(this.fbListeners, "destroyContext", [context, persistedState, context.browser]); 671 672 if (FBTrace.DBG_WINDOWS || FBTrace.DBG_ACTIVATION) 673 FBTrace.sysout("-> tabWatcher.unwatchContext *** DESTROY *** context " + context.uid + 674 " for: " + (context.window && !context.window.closed?context.window.location : 675 "no window or closed ") + " aborted: " + context.aborted); 676 677 context.destroy(persistedState); 678 679 // Remove context from the list of contexts. 680 Arr.remove(contexts, context); 681 682 for (var name in context) 683 delete context[name]; 684 685 // unwatchContext can be called on an unload event after another tab is selected 686 var currentBrowser = Firefox.getCurrentBrowser(); 687 if (!currentBrowser.showFirebug) 688 { 689 // context is null if we don't want to debug this browser 690 Events.dispatch(this.fbListeners, "showContext", [browser, null]); 691 } 692 }, 693 694 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 695 696 getContextByWindow: function(winIn) 697 { 698 if (!winIn) 699 return; 700 701 var rootWindow = Win.getRootWindow(winIn); 702 703 if (FBTrace.DBG_ROOT_WINDOW) // too much output to use INITIALIZE 704 FBTrace.sysout("winIn: "+Win.safeGetWindowLocation(winIn).substr(0,50)+ 705 " rootWindow: "+Win.safeGetWindowLocation(rootWindow)); 706 707 if (rootWindow) 708 { 709 for (var i = 0; i < contexts.length; ++i) 710 { 711 var context = contexts[i]; 712 if (context.window == rootWindow) 713 return context; 714 } 715 } 716 }, 717 718 getContextBySandbox: function(sandbox) 719 { 720 for (var i = 0; i < contexts.length; ++i) 721 { 722 var context = contexts[i]; 723 if (context.sandboxes) 724 { 725 for (var iframe = 0; iframe < context.sandboxes.length; iframe++) 726 { 727 if (context.sandboxes[iframe] == sandbox) 728 return context; 729 } 730 } 731 } 732 return null; 733 }, 734 735 getContextByGlobal: function(global) 736 { 737 return this.getContextByWindow(global) || this.getContextBySandbox(global); 738 }, 739 740 // deprecated, use Win.getBrowserByWindow 741 getBrowserByWindow: function(win) 742 { 743 if (this.shuttingDown) 744 return null; 745 746 var browsers = Firefox.getBrowsers(); 747 for (var i = 0; i < browsers.length; ++i) 748 { 749 var browser = browsers[i]; 750 if (browser.contentWindow == win) 751 { 752 registerFrameListener(browser); // Yikes side effect! 753 return browser; 754 } 755 } 756 757 return null; 758 }, 759 760 iterateContexts: function(fn) 761 { 762 for (var i = 0; i < contexts.length; ++i) 763 { 764 var rc = fn(contexts[i]); 765 if (rc) 766 return rc; 767 } 768 }, 769 770 // Called by script panel, not sure where this belongs. 771 reloadPageFromMemory: function(context) 772 { 773 if (!context) 774 context = Firebug.currentContext; 775 776 if (context.browser) 777 context.browser.reloadWithFlags(Ci.nsIWebNavigation.LOAD_FLAGS_CHARSET_CHANGE) 778 else 779 context.window.location.reload(); 780 }, 781 }); 782 783 // ********************************************************************************************* // 784 785 var TabWatcherUnloader = 786 { 787 listeners: [], 788 789 registerWindow: function(win) 790 { 791 var root = (win.parent == win); 792 var eventName = (root && (win.location.href !== "about:blank")) ? "pagehide" : "unload"; 793 var listener = Obj.bind(root ? this.onPageHide : this.onUnload, this); 794 Events.addEventListener(win, eventName, listener, false); 795 796 if (FBTrace.DBG_WINDOWS) 797 FBTrace.sysout("-> tabWatcher.registerWindow; addEventListener for " + eventName+ 798 " on " + Win.safeGetWindowLocation(win)); 799 800 this.listeners.push({ 801 window: win, 802 listener: listener, 803 eventName: eventName 804 }); 805 }, 806 807 unregisterWindow: function(win) 808 { 809 var newListeners = []; 810 for (var i=0; i<this.listeners.length; i++) 811 { 812 var listener = this.listeners[i]; 813 if (listener.window != win) 814 { 815 newListeners.push(listener); 816 } 817 else 818 { 819 Events.removeEventListener(win, listener.eventName, listener.listener, false); 820 821 if (FBTrace.DBG_WINDOWS) 822 FBTrace.sysout("-> tabWatcher.unregisterWindow; removeEventListener for " + 823 listener.eventName + " on " + Win.safeGetWindowLocation(win)); 824 } 825 } 826 this.listeners = newListeners; 827 }, 828 829 unregisterAll: function() 830 { 831 for (var i=0; i<this.listeners.length; i++) 832 { 833 var listener = this.listeners[i]; 834 Events.removeEventListener(listener.win, listener.eventName, listener.listener, false); 835 } 836 837 this.listeners = []; 838 }, 839 840 onPageHide: function(event) 841 { 842 var win = event.currentTarget; 843 this.unregisterWindow(win); 844 845 if (FBTrace.DBG_WINDOWS) 846 FBTrace.sysout("-> tabWatcher.Unloader; PAGE HIDE (" + 847 this.listeners.length + ") " + win.location, event); 848 849 onPageHideTopWindow(event); 850 }, 851 852 onUnload: function(event) 853 { 854 var win = event.currentTarget; 855 this.unregisterWindow(win); 856 857 if (FBTrace.DBG_WINDOWS) 858 FBTrace.sysout("-> tabWatcher.Unloader; PAGE UNLOAD (" + 859 this.listeners.length + ") " + win.location, event); 860 861 onUnloadWindow(event); 862 }, 863 }; 864 865 Firebug.TabWatcherUnloader = TabWatcherUnloader; 866 867 // ********************************************************************************************* // 868 869 var TabProgressListener = Obj.extend(Http.BaseProgressListener, 870 { 871 onLocationChange: function(progress, request, uri) 872 { 873 // Only watch windows that are their own parent - e.g. not frames 874 if (progress.DOMWindow.parent == progress.DOMWindow) 875 { 876 var srcWindow = Http.getWindowForRequest(request); 877 var browser = srcWindow ? Firebug.TabWatcher.getBrowserByWindow(srcWindow) : null; 878 879 if (FBTrace.DBG_WINDOWS || FBTrace.DBG_ACTIVATION) 880 { 881 var requestFromFirebuggedWindow = browser && browser.showFirebug; 882 FBTrace.sysout("-> TabProgressListener.onLocationChange "+ 883 progress.DOMWindow.location+" to: "+ 884 (uri?uri.spec:"null location")+ 885 (requestFromFirebuggedWindow?" from firebugged window":" no firebug")); 886 } 887 888 // See issue 4040 889 // the onStateChange will deal with this troublesome case 890 //if (uri && uri.spec === "about:blank") 891 // return; 892 893 // document.open() was called, the document was cleared. 894 if (uri && uri.scheme === "wyciwyg") 895 evictTopWindow(progress.DOMWindow, uri); 896 897 if (uri) 898 Firebug.TabWatcher.watchTopWindow(progress.DOMWindow, uri); 899 else // the location change to a non-uri means we need to hide 900 Firebug.TabWatcher.watchContext(progress.DOMWindow, null, true); 901 } 902 }, 903 904 onStateChange: function(progress, request, flag, status) 905 { 906 if (FBTrace.DBG_WINDOWS) 907 { 908 var win = progress.DOMWindow; 909 FBTrace.sysout("-> TabProgressListener.onStateChanged for: " + 910 Http.safeGetRequestName(request) + ", win: " + win.location.href + 911 ", content URL: " + (win.document ? win.document.URL : "no content URL") + 912 " " + Http.getStateDescription(flag)); 913 } 914 } 915 }); 916 917 // ********************************************************************************************* // 918 // Obsolete 919 920 var FrameProgressListener = Obj.extend(Http.BaseProgressListener, 921 { 922 onStateChange: function(progress, request, flag, status) 923 { 924 if (FBTrace.DBG_WINDOWS) 925 { 926 var win = progress.DOMWindow; 927 FBTrace.sysout("-> FrameProgressListener.onStateChanged for: " + 928 Http.safeGetRequestName(request) + ", win: " + win.location.href + 929 ", content URL: " + (win.document ? win.document.URL : "no content URL") + 930 " " + Http.getStateDescription(flag) + ", " + status); 931 } 932 933 if (flag & STATE_IS_REQUEST && flag & STATE_START) 934 { 935 // We need to get the hook in as soon as the new DOMWindow is created, but before 936 // it starts executing any scripts in the page. After lengthy analysis, it seems 937 // that the start of these "dummy" requests is the only state that works. 938 939 var safeName = Http.safeGetRequestName(request); 940 if (safeName && ((safeName == dummyURI) || safeName == "about:document-onload-blocker")) 941 { 942 var win = progress.DOMWindow; 943 // Another weird edge case here - when opening a new tab with about:blank, 944 // "unload" is dispatched to the document, but onLocationChange is not called 945 // again, so we have to call watchTopWindow here 946 947 if (win.parent == win && (win.location.href == "about:blank")) 948 { 949 Firebug.TabWatcher.watchTopWindow(win, win.location.href); 950 return; 951 } 952 else 953 { 954 Firebug.TabWatcher.watchWindow(win, null, true); 955 } 956 } 957 } 958 959 // Later I discovered that XHTML documents don't dispatch the dummy requests, so this 960 // is our best shot here at hooking them. 961 if (flag & STATE_IS_DOCUMENT && flag & STATE_TRANSFERRING) 962 { 963 Firebug.TabWatcher.watchWindow(progress.DOMWindow); 964 return; 965 } 966 967 } 968 }); 969 970 // Obsolete 971 // Registers frame listener for specified tab browser. 972 function registerFrameListener(browser) 973 { 974 if (browser.frameListener) 975 return; 976 977 browser.frameListener = FrameProgressListener; // just a mark saying we've registered. TODO remove! 978 browser.addProgressListener(FrameProgressListener); 979 980 if (FBTrace.DBG_WINDOWS) 981 { 982 var win = browser.contentWindow; 983 FBTrace.sysout("-> tabWatcher register FrameProgressListener for: "+ 984 Win.safeGetWindowLocation(win)+", tab: "+Win.getWindowProxyIdForWindow(win)); 985 } 986 } 987 988 function unregisterFrameListener(browser) 989 { 990 if (browser.frameListener) 991 { 992 delete browser.frameListener; 993 browser.removeProgressListener(FrameProgressListener); 994 } 995 996 if (FBTrace.DBG_WINDOWS) 997 { 998 var win = browser.contentWindow; 999 FBTrace.sysout("-> tabWatcher unregister FrameProgressListener for: "+ 1000 Win.safeGetWindowLocation(win)+", tab: "+Win.getWindowProxyIdForWindow(win)); 1001 } 1002 } 1003 1004 // ********************************************************************************************* // 1005 1006 function getRefererHeader(request) 1007 { 1008 var http = Xpcom.QI(request, Ci.nsIHttpChannel); 1009 var referer = null; 1010 http.visitRequestHeaders({ 1011 visitHeader: function(name, value) 1012 { 1013 if (name == 'referer') 1014 referer = value; 1015 } 1016 }); 1017 return referer; 1018 } 1019 1020 // ********************************************************************************************* // 1021 1022 var TabWatcherHttpObserver = Obj.extend(Object, 1023 { 1024 dispatchName: "TabWatcherHttpObserver", 1025 1026 // nsIObserver 1027 observe: function(aSubject, aTopic, aData) 1028 { 1029 try 1030 { 1031 if (aTopic == "http-on-modify-request") 1032 { 1033 aSubject = aSubject.QueryInterface(Ci.nsIHttpChannel); 1034 this.onModifyRequest(aSubject); 1035 } 1036 } 1037 catch (err) 1038 { 1039 Debug.ERROR(err); 1040 } 1041 }, 1042 1043 onModifyRequest: function(request) 1044 { 1045 var win = Http.getWindowForRequest(request); 1046 if (win) 1047 var tabId = Win.getWindowProxyIdForWindow(win); 1048 1049 // Tab watcher is only interested in tab related requests. 1050 if (!tabId) 1051 return; 1052 1053 // Ignore redirects 1054 if (request.URI.spec != request.originalURI.spec) 1055 return; 1056 1057 // A document request for the specified tab is here. It can be a top window 1058 // request (win == win.parent) or embedded iframe request. 1059 if (request.loadFlags & Ci.nsIHttpChannel.LOAD_DOCUMENT_URI) 1060 { 1061 if ((FBTrace.DBG_ACTIVATION || FBTrace.DBG_WINDOWS) && win == win.parent) 1062 { 1063 FBTrace.sysout("-> tabWatcher Firebug.TabWatcherHttpObserver *** START *** " + 1064 "document request for: " + request.URI.spec + " window for request is "+ 1065 Win.safeGetWindowLocation(win)); 1066 } 1067 1068 if (win == win.parent) 1069 { 1070 // Make sure the frame listener is registered for top level window, so 1071 // we can get all onStateChange events and init context for all opened tabs. 1072 var browser = Firebug.TabWatcher.getBrowserByWindow(win); 1073 1074 if (!browser) 1075 return; 1076 1077 delete browser.FirebugLink; 1078 1079 // then this page is opened in new tab or window 1080 if (Win.safeGetWindowLocation(win).toString() == "about:blank") 1081 { 1082 var referer = getRefererHeader(request); 1083 if (referer) 1084 { 1085 try 1086 { 1087 var srcURI = Url.makeURI(referer); 1088 browser.FirebugLink = {src: srcURI, dst: request.URI}; 1089 } 1090 catch(e) 1091 { 1092 if (FBTrace.DBG_ERRORS) 1093 FBTrace.sysout("tabWatcher.onModifyRequest failed to make URI from "+ 1094 referer+" because "+exc, exc); 1095 } 1096 } 1097 } 1098 else 1099 { 1100 // Here we know the source of the request is 'win'. For viral activation 1101 // and web app tracking 1102 browser.FirebugLink = {src: browser.currentURI, dst: request.URI}; 1103 } 1104 if (FBTrace.DBG_ACTIVATION && browser.FirebugLink) 1105 FBTrace.sysout("tabWatcher.onModifyRequest created FirebugLink from "+ 1106 browser.FirebugLink.src.spec + " to "+browser.FirebugLink.dst.spec); 1107 } 1108 } 1109 }, 1110 1111 QueryInterface : function (aIID) 1112 { 1113 if (aIID.equals(Ci.nsIObserver) || 1114 aIID.equals(Ci.nsISupportsWeakReference) || 1115 aIID.equals(Ci.nsISupports)) 1116 { 1117 return this; 1118 } 1119 1120 throw Components.results.NS_NOINTERFACE; 1121 } 1122 }); 1123 1124 // ********************************************************************************************* // 1125 // Local Helpers 1126 1127 function onPageHideTopWindow(event) 1128 { 1129 var win = event.currentTarget; // we set the handler on a window 1130 var doc = event.target; // the pagehide is sent to the document. 1131 if (doc.defaultView != win) 1132 return; // ignore page hides on interior windows 1133 1134 if (FBTrace.DBG_WINDOWS) 1135 FBTrace.sysout("-> tabWatcher pagehide event.currentTarget "+ 1136 Win.safeGetWindowLocation(win), event); 1137 1138 // http://developer.mozilla.org/en/docs/Using_Firefox_1.5_caching#pagehide_event 1139 // then the page is cached and there cannot be an unload handler 1140 if (event.persisted || Win.safeGetWindowLocation(win) === aboutBlank) 1141 { 1142 // see Bug 484710 - add pageIgnore event for pages that are ejected from the bfcache 1143 if (FBTrace.DBG_WINDOWS) 1144 FBTrace.sysout("-> tabWatcher onPageHideTopWindow for: " + 1145 Win.safeGetWindowLocation(win)); 1146 1147 Firebug.TabWatcher.unwatchTopWindow(win); 1148 } 1149 else 1150 { 1151 // Page is not cached, there may be an unload 1152 Events.addEventListener(win, "unload", onUnloadTopWindow, true); 1153 1154 if (FBTrace.DBG_WINDOWS) 1155 FBTrace.sysout("-> tabWatcher onPageHideTopWindow set unload handler " + 1156 Win.safeGetWindowLocation(win)); 1157 } 1158 } 1159 1160 function evictTopWindow(win, uri) 1161 { 1162 if (FBTrace.DBG_WINDOWS) 1163 FBTrace.sysout("-> tabWatcher evictTopWindow win "+Win.safeGetWindowLocation(win) + 1164 " uri "+uri.spec); 1165 1166 Firebug.TabWatcher.unwatchTopWindow(win); 1167 } 1168 1169 function onUnloadTopWindow(event) 1170 { 1171 var win = event.currentTarget; 1172 Events.removeEventListener(win, "unload", onUnloadTopWindow, true); 1173 1174 if (FBTrace.DBG_WINDOWS) 1175 FBTrace.sysout("-> tabWatcher onUnloadTopWindow for: " + Win.safeGetWindowLocation(win) + 1176 " typeof: " + typeof(win)); 1177 1178 Firebug.TabWatcher.unwatchTopWindow(win); 1179 } 1180 1181 function onLoadWindowContent(event) 1182 { 1183 if (FBTrace.DBG_WINDOWS) 1184 FBTrace.sysout("-> tabWatcher.onLoadWindowContent event.type: " + event.type); 1185 1186 var win = event.currentTarget; 1187 try 1188 { 1189 Events.removeEventListener(win, "pageshow", onLoadWindowContent, 1190 onLoadWindowContent.capturing); 1191 1192 if (FBTrace.DBG_WINDOWS) 1193 FBTrace.sysout("-> tabWatcher.onLoadWindowContent pageshow removeEventListener " + 1194 Win.safeGetWindowLocation(win)); 1195 } 1196 catch (exc) 1197 { 1198 if (FBTrace.DBG_ERRORS) 1199 FBTrace.sysout("-> tabWatcher.onLoadWindowContent removeEventListener pageshow fails", 1200 exc); 1201 } 1202 1203 try 1204 { 1205 Events.removeEventListener(win, "DOMContentLoaded", onLoadWindowContent, 1206 onLoadWindowContent.capturing); 1207 1208 if (FBTrace.DBG_WINDOWS) 1209 FBTrace.sysout("-> tabWatcher.onLoadWindowContent DOMContentLoaded " + 1210 "removeEventListener " + Win.safeGetWindowLocation(win)); 1211 } 1212 catch (exc) 1213 { 1214 if (FBTrace.DBG_ERRORS) 1215 FBTrace.sysout("-> tabWatcher.onLoadWindowContent removeEventListener " + 1216 "DOMContentLoaded fails", exc); 1217 } 1218 1219 // Signal that we got the onLoadWindowContent event. This prevents the 1220 // FrameProgressListener from sending it. 1221 var context = Firebug.TabWatcher.getContextByWindow(win); 1222 if (context) 1223 context.onLoadWindowContent = true; 1224 1225 try 1226 { 1227 if (FBTrace.DBG_WINDOWS) 1228 FBTrace.sysout("-> tabWatcher.onLoadWindowContent:" + 1229 Win.safeGetWindowLocation(win), win); 1230 1231 Firebug.TabWatcher.watchLoadedTopWindow(win); 1232 } 1233 catch(exc) 1234 { 1235 if (FBTrace.DBG_ERRORS) 1236 FBTrace.sysout("-> tabWatchter onLoadWindowContent FAILS: "+exc, exc); 1237 } 1238 } 1239 1240 onLoadWindowContent.capturing = false; 1241 1242 function onUnloadWindow(event) 1243 { 1244 var win = event.currentTarget; 1245 var eventType = "unload"; 1246 1247 if (FBTrace.DBG_WINDOWS) 1248 FBTrace.sysout("-> tabWatcher.onUnloadWindow for: "+Win.safeGetWindowLocation(win) + 1249 " removeEventListener: "+ eventType); 1250 1251 Firebug.TabWatcher.unwatchWindow(win); 1252 } 1253 1254 // ********************************************************************************************* // 1255 1256 window.__defineGetter__("TabWatcher", function deprecatedTabWatcher() 1257 { 1258 if (FBTrace.DBG_ERRORS) 1259 FBTrace.sysout("deprecated TabWatcher global accessed"); 1260 1261 return Firebug.TabWatcher; 1262 }); 1263 1264 // ********************************************************************************************* // 1265 // Registration 1266 1267 return Firebug.TabWatcher; 1268 1269 // ********************************************************************************************* // 1270 }); 1271