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