home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2009 June / PersonalComputerWorld-June2009-CoverdiscCD.iso / Software / Freeware / Firebug 1.3.3 / firebug-1.3.3-fx.xpi / content / firebug / tabWatcher.js < prev    next >
Encoding:
JavaScript  |  2009-02-19  |  19.6 KB  |  647 lines

  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. const observerService = CCSV("@joehewitt.com/firebug-http-observer;1", "nsIObserverService");
  33.  
  34. // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  35.  
  36. const tabBrowser = $("content");
  37.  
  38. // ************************************************************************************************
  39. // Globals
  40.  
  41. var contexts = [];
  42. var listeners = [];
  43.  
  44. // ************************************************************************************************
  45.  
  46. top.TabWatcher =
  47. {
  48.     initialize: function(owner)
  49.     {
  50.         this.contexts = contexts;
  51.  
  52.         this.owner = owner;  // Firebug object
  53.         this.addListener(owner);
  54.  
  55.         if (tabBrowser)
  56.             tabBrowser.addProgressListener(TabProgressListener, NOTIFY_STATE_DOCUMENT);
  57.  
  58.         observerService.addObserver(HttpObserver, "firebug-http-event", false);
  59.     },
  60.  
  61.     destroy: function()
  62.     {
  63.         observerService.removeObserver(HttpObserver, "firebug-http-event");
  64.  
  65.         if (tabBrowser)
  66.         {
  67.             tabBrowser.removeProgressListener(TabProgressListener);
  68.  
  69.             for (var i = 0; i < tabBrowser.browsers.length; ++i)
  70.             {
  71.                 var browser = tabBrowser.browsers[i];
  72.                 this.unwatchTopWindow(browser.contentWindow);
  73.             }
  74.         }
  75.  
  76.         this.removeListener(this.owner);
  77.         this.owner = null;
  78.     },
  79.  
  80.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  81.  
  82.     /**
  83.      * Attaches to a top-level window. Creates context unless we just re-activated on an existing context
  84.      */
  85.     watchTopWindow: function(win, uri)
  86.     {
  87.         if (tabBrowser.selectedBrowser.cancelNextLoad)
  88.         {
  89.             // We need to cancel this load and try again after a delay... this is used
  90.             // mainly to prevent chaos while when the debugger is active when a page
  91.             // is unloaded
  92.             delete tabBrowser.selectedBrowser.cancelNextLoad;
  93.             tabBrowser.selectedBrowser.webNavigation.stop(STOP_ALL);
  94.             delayBrowserLoad(tabBrowser.selectedBrowser, win.location.href);
  95.             return;
  96.         }
  97.  
  98.         var context = this.getContextByWindow(win);
  99.         if (!context)
  100.         {
  101.             if (!this.owner.enableContext(win,uri))
  102.             {
  103.                 this.watchContext(win, null);
  104.                 return false;  // we did not create a context
  105.             }
  106.  
  107.             var browser = this.getBrowserByWindow(win);  // sets browser.chrome to FirebugChrome
  108.             //if (!fbs.countContext(true))
  109.             //    return;
  110.  
  111.             // If the page is reloaded, store the persisted state from the previous
  112.             // page on the new context
  113.             var persistedState = browser.persistedState;
  114.             delete browser.persistedState;
  115.             if (!persistedState || persistedState.location != win.location.href)
  116.                 persistedState = null;
  117.  
  118.             context = this.owner.createTabContext(win, browser, browser.chrome, persistedState);
  119.             contexts.push(context);
  120.  
  121.             context.uid = FBL.getUniqueId();                                                                       /*@explore*/
  122.             dispatch(listeners, "initContext", [context]);
  123.  
  124.             if (!FirebugContext)
  125.                 FirebugContext = context; // let's make sure we have something for errors to land on.
  126.  
  127.             win.addEventListener("pagehide", onPageHideTopWindow, true);
  128.             win.addEventListener("pageshow", onLoadWindowContent, true);
  129.             win.addEventListener("DOMContentLoaded", onLoadWindowContent, true);
  130.         }
  131.  
  132.         // xxxHonza is this still valid comment? How this could happen?
  133.         // XXXjjb at this point we either have context or we just pushed null into contexts and sent it to init...
  134.         if (context)
  135.             this.watchWindow(win, context);
  136.  
  137.         // This is one of two places that loaded is set. The other is in watchLoadedTopWindow
  138.         if (context && !context.loaded)
  139.         {
  140.             context.loaded = !context.browser.webProgress.isLoadingDocument;
  141.  
  142.             // If the loaded flag is set, the proper event should be dispatched.
  143.             if (context.loaded)
  144.                 dispatch(listeners, "loadedContext", [context]);
  145.  
  146.         }
  147.  
  148.         // Call showContext only for currently active context.
  149.         if (tabBrowser.currentURI.spec != context.browser.currentURI.spec)
  150.         {
  151.             return context;  // we did create or find a context
  152.         }
  153.  
  154.         if (context && !context.loaded && !context.showContextTimeout)  
  155.         {
  156.             // still loading, we want to showContext one time but not too agressively
  157.             context.showContextTimeout = setTimeout(bindFixed( function delayShowContext()
  158.             {
  159.                 if (context.window)   // Sometimes context.window is not defined ?
  160.                     this.watchContext(win, context);  // calls showContext
  161.                 else
  162.                 {
  163.                 }
  164.             }, this), 400);
  165.         }
  166.         else
  167.         {
  168.             if (context.showContextTimeout)
  169.                 clearTimeout(context.showContextTimeout);
  170.             delete context.showContextTimeout;
  171.             
  172.             this.watchContext(win, context);  // calls showContext
  173.         }
  174.  
  175.         return context;  // we did create or find a context
  176.     },
  177.  
  178.     /**
  179.      * Called once the document within a tab is completely loaded.
  180.      */
  181.     watchLoadedTopWindow: function(win)
  182.     {
  183.         var isSystem = isSystemPage(win);
  184.  
  185.         var context = this.getContextByWindow(win);
  186.         if ((context && !context.window))
  187.         {
  188.             this.unwatchTopWindow(win);
  189.             this.watchContext(win, null, isSystem);
  190.             return;
  191.         }
  192.  
  193.         if (context && !context.loaded)
  194.         {
  195.             context.loaded = true;
  196.             dispatch(listeners, "loadedContext", [context]);
  197.         }
  198.     },
  199.  
  200.     /**
  201.      * Attaches to a window that may be either top-level or a frame within the page.
  202.      */
  203.     watchWindow: function(win, context)
  204.     {
  205.         if (!context)
  206.             context = this.getContextByWindow(getRootWindow(win));
  207.  
  208.         var href = win.location.href;
  209.                                                                                                                        /*@explore*/
  210.         // Unfortunately, dummy requests that trigger the call to watchWindow
  211.         // are called several times, so we have to avoid dispatching watchWindow
  212.         // more than once
  213.         if (context && context.windows.indexOf(win) == -1 && href != aboutBlank)
  214.         {
  215.             context.windows.push(win);
  216.  
  217.             var eventType = (win.parent == win) ? "pagehide" : "unload";
  218.             win.addEventListener(eventType, onUnloadWindow, false);
  219.             dispatch(listeners, "watchWindow", [context, win]);
  220.  
  221.         }
  222.     },
  223.  
  224.     /**
  225.      * Detaches from a top-level window. Destroys context
  226.      */
  227.     unwatchTopWindow: function(win)
  228.     {
  229.         var context = this.getContextByWindow(win);
  230.         this.unwatchContext(win, context);
  231.     },
  232.  
  233.     /**
  234.      * Detaches from a window, top-level or not.
  235.      */
  236.     unwatchWindow: function(win)
  237.     {
  238.         var context = this.getContextByWindow(win);
  239.  
  240.         var index = context ? context.windows.indexOf(win) : -1;
  241.         if (index != -1)
  242.         {
  243.             context.windows.splice(index, 1);
  244.             dispatch(listeners, "unwatchWindow", [context, win]);
  245.         }
  246.     },
  247.  
  248.     /**
  249.      * Attaches to the window inside a browser because of user-activation
  250.      * returns false if no context was created by the attach attempt, eg extension rejected page
  251.      */
  252.     watchBrowser: function(browser)
  253.     {
  254.         return this.watchTopWindow(browser.contentWindow, safeGetURI(browser));
  255.     },
  256.  
  257.     unwatchBrowser: function(browser)
  258.     {
  259.         this.unwatchTopWindow(browser.contentWindow);
  260.     },
  261.  
  262.     watchContext: function(win, context, isSystem)  // called when tabs change in firefox
  263.     {
  264.         var browser = context ? context.browser : this.getBrowserByWindow(win);
  265.         if (browser)
  266.             browser.isSystemPage = isSystem;
  267.  
  268.         dispatch(listeners, "showContext", [browser, context]); // context is null for unwatchContext
  269.     },
  270.  
  271.     unwatchContext: function(win, context)
  272.     {
  273.         if (!context)
  274.         {
  275.             var browser = this.getBrowserByWindow(win);
  276.             if (this.owner)
  277.                 this.owner.destroyTabContext(browser, null);
  278.             // else we are probably exiting anyway.
  279.             return;
  280.         }
  281.  
  282.         var persistedState = {location: context.window.location.href};
  283.         context.browser.persistedState = persistedState;  // store our state on FF browser elt
  284.  
  285.         iterateWindows(context.window, function(win)
  286.         {
  287.             dispatch(listeners, "unwatchWindow", [context, win]);
  288.         });
  289.  
  290.         dispatch(listeners, "destroyContext", [context, persistedState]);
  291.  
  292.         if (FirebugContext == context)
  293.             FirebugContext = null;
  294.  
  295.         if (this.cancelNextLoad)
  296.         {
  297.             delete this.cancelNextLoad;
  298.             context.browser.cancelNextLoad = true;
  299.         }
  300.  
  301.         fbs.countContext(false);
  302.  
  303.         this.owner.destroyTabContext(context.browser, context);
  304.         context.destroy(persistedState);
  305.  
  306.         remove(contexts, context);
  307.     },
  308.  
  309.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  310.  
  311.     getContextByWindow: function(winIn)
  312.     {
  313.         var rootWindow = getRootWindow(winIn);
  314.  
  315.         if (rootWindow)
  316.         {
  317.             for (var i = 0; i < contexts.length; ++i)
  318.             {
  319.                 var context = contexts[i];
  320.                 if (context.window == rootWindow)
  321.                     return context;
  322.             }
  323.         }
  324.     },
  325.  
  326.     getContextBySandbox: function(sandbox)
  327.     {
  328.         for (var i = 0; i < contexts.length; ++i)
  329.         {
  330.             var context = contexts[i];
  331.             if (context.sandboxes)
  332.             {
  333.                 for (var iframe = 0; iframe < context.sandboxes.length; iframe++)
  334.                 {
  335.                     if (context.sandboxes[iframe] == sandbox)
  336.                         return context;
  337.                 }
  338.             }
  339.         }
  340.         return null;
  341.     },
  342.  
  343.  
  344.     getBrowserByWindow: function(win)
  345.     {
  346.         for (var i = 0; i < tabBrowser.browsers.length; ++i)
  347.         {
  348.             var browser = tabBrowser.browsers[i];
  349.             if (browser.contentWindow == win)
  350.             {
  351.                 if (!browser.chrome)
  352.                     registerFrameListener(browser);  // sets browser.chrome to FirebugChrome
  353.  
  354.                 return browser;
  355.             }
  356.         }
  357.  
  358.         return null;
  359.     },
  360.  
  361.     iterateContexts: function(fn)
  362.     {
  363.         for (var i = 0; i < contexts.length; ++i)
  364.             fn(contexts[i]);
  365.     },
  366.  
  367.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  368.  
  369.     addListener: function(listener)
  370.     {
  371.         listeners.push(listener);
  372.     },
  373.  
  374.     removeListener: function(listener)
  375.     {
  376.         remove(listeners, listener);
  377.     }
  378. };
  379.  
  380. // ************************************************************************************************
  381.  
  382. var BaseProgressListener =
  383. {
  384.     QueryInterface : function(iid)
  385.     {
  386.         if (iid.equals(nsIWebProgressListener) ||
  387.             iid.equals(nsISupportsWeakReference) ||
  388.             iid.equals(nsISupports))
  389.         {
  390.             return this;
  391.         }
  392.  
  393.         throw Components.results.NS_NOINTERFACE;
  394.     },
  395.  
  396.     stateIsRequest: false,
  397.     onLocationChange: function() {},
  398.     onStateChange : function() {},
  399.     onProgressChange : function() {},
  400.     onStatusChange : function() {},
  401.     onSecurityChange : function() {},
  402.     onLinkIconAvailable : function() {}
  403. };
  404.  
  405. // ************************************************************************************************
  406.  
  407. var TabProgressListener = extend(BaseProgressListener,
  408. {
  409.     onLocationChange: function(progress, request, uri)
  410.     {
  411.         // Only watch windows that are their own parent - e.g. not frames
  412.         if (progress.DOMWindow.parent == progress.DOMWindow)
  413.         {
  414.             TabWatcher.watchTopWindow(progress.DOMWindow, uri);
  415.         }
  416.     },
  417.  
  418.     onStateChange: function(progress, request, flag, status)
  419.     {
  420.         /*if (flag & STATE_STOP)
  421.         {
  422.             var win = progress.DOMWindow;
  423.             if (win && win.parent == win)
  424.                 TabWatcher.watchLoadedTopWindow(progress.DOMWindow);
  425.         }*/
  426.     }
  427. });
  428.  
  429. // ************************************************************************************************
  430.  
  431. var FrameProgressListener = extend(BaseProgressListener,
  432. {
  433.     onStateChange: function(progress, request, flag, status)
  434.     {
  435.         if (flag & STATE_IS_REQUEST && flag & STATE_START)
  436.         {
  437.             // We need to get the hook in as soon as the new DOMWindow is created, but before
  438.             // it starts executing any scripts in the page.  After lengthy analysis, it seems
  439.             // that the start of these "dummy" requests is the only state that works.
  440.  
  441.             var safeName = safeGetName(request);
  442.             if (safeName && ((safeName == dummyURI) || safeName == "about:document-onload-blocker") )
  443.             {
  444.                 var win = progress.DOMWindow;
  445.                 // Another weird edge case here - when opening a new tab with about:blank,
  446.                 // "unload" is dispatched to the document, but onLocationChange is not called
  447.                 // again, so we have to call watchTopWindow here
  448.                 //if (win.parent == win && win.location.href == "about:blank")
  449.                 //    TabWatcher.watchTopWindow(win, win.location);
  450.                 // XXXms check this
  451.  
  452.                 // Fix for Issue #760
  453.                 // Don't call watchTopWindow id the about:document-onload-blocker dummy request is sent.
  454.                 // This request is sent also if the page is modified by DOM Inspector, which
  455.                 // causes to immediately stop the Inspectore mode.
  456.                 // xxxHonza This change should be made after real understanding of
  457.                 // how this code work.
  458.                 // xxxJJB, too many bugs by the dummy request, lose it.
  459.                 if (win.parent == win && (win.location.href == "about:blank"))
  460.                 {
  461.                     TabWatcher.watchTopWindow(win, win.location.href);
  462.                     return;  // new one under our thumb
  463.                 }
  464.                 else
  465.                     TabWatcher.watchWindow(win);
  466.             }
  467.         }
  468.  
  469.         // Later I discovered that XHTML documents don't dispatch the dummy requests, so this
  470.         // is our best shot here at hooking them.
  471.         if (flag & STATE_IS_DOCUMENT && flag & STATE_TRANSFERRING)
  472.         {
  473.             TabWatcher.watchWindow(progress.DOMWindow);
  474.             return;
  475.         }
  476.  
  477.     }
  478. });
  479.  
  480. // Registers frame listener for specified tab browser.
  481. function registerFrameListener(browser)
  482. {
  483.     if (browser.chrome)
  484.         return;
  485.  
  486.     browser.chrome = FirebugChrome;
  487.     browser.addProgressListener(FrameProgressListener, NOTIFY_STATE_DOCUMENT);
  488.  
  489. }
  490.  
  491. var HttpObserver = extend(Object,
  492. {
  493.     // nsIObserver
  494.     observe: function(aSubject, aTopic, aData)
  495.     {
  496.         try  
  497.         {
  498.             if (aTopic == "http-on-modify-request") 
  499.             {
  500.                 aSubject = aSubject.QueryInterface(Ci.nsIHttpChannel);
  501.                 this.onModifyRequest(aSubject);
  502.             }
  503.         }
  504.         catch (err) 
  505.         {
  506.             ERROR(err);
  507.         }
  508.     },
  509.  
  510.     onModifyRequest: function(request)
  511.     {
  512.         var win = getWindowForRequest(request);
  513.         var tabId = Firebug.getTabIdForWindow(win);
  514.  
  515.         // Tab watcher is only interested in tab related requests.
  516.         if (!tabId)
  517.             return;
  518.  
  519.         // Ignore redirects
  520.         if (request.URI.spec != request.originalURI.spec)
  521.             return;
  522.  
  523.         // A document request for the specified tab is here. It can be a top window
  524.         // request (win == win.parent) or embedded iframe request.
  525.         if (request.loadFlags & Ci.nsIHttpChannel.LOAD_DOCUMENT_URI)
  526.         {
  527.             if (win == win.parent)
  528.                 TabWatcher.getBrowserByWindow(win);
  529.         }
  530.     },
  531.  
  532.     QueryInterface : function (aIID)
  533.     {
  534.         if (aIID.equals(Ci.nsIObserver) ||
  535.             aIID.equals(Ci.nsISupportsWeakReference) ||
  536.             aIID.equals(Ci.nsISupports))
  537.         {
  538.             return this;
  539.         }
  540.  
  541.         throw Components.results.NS_NOINTERFACE;
  542.     }
  543. });
  544.  
  545. // ************************************************************************************************
  546. // Local Helpers
  547.  
  548. function onPageHideTopWindow(event)
  549. {
  550.     var win = event.currentTarget;
  551.     win.removeEventListener("pagehide", onPageHideTopWindow, true);
  552.     // http://developer.mozilla.org/en/docs/Using_Firefox_1.5_caching#pagehide_event
  553.     if (event.persisted) // then the page is cached and there cannot be an unload handler
  554.     {
  555.         TabWatcher.unwatchTopWindow(win);
  556.     }
  557.     else
  558.     {
  559.         // Page is not cached, there may be an unload
  560.         win.addEventListener("unload", onUnloadTopWindow, true);
  561.     }
  562. }
  563.  
  564. function onUnloadTopWindow(event)
  565. {
  566.     var win = event.currentTarget;
  567.     win.removeEventListener("unload", onUnloadTopWindow, true);
  568.     TabWatcher.unwatchTopWindow(win);
  569. }
  570.  
  571. function onLoadWindowContent(event)
  572. {
  573.     var win = event.currentTarget;
  574.     try
  575.     {
  576.         win.removeEventListener("pageshow", onLoadWindowContent, true);
  577.     }
  578.     catch (exc) {}
  579.  
  580.     try
  581.     {
  582.         win.removeEventListener("DOMContentLoaded", onLoadWindowContent, true);
  583.     }
  584.     catch (exc) {}
  585.  
  586.     // Signal that we got the onLoadWindowContent event. This prevents the FrameProgressListener from sending it.
  587.     var context = TabWatcher.getContextByWindow(win);
  588.     if (context)
  589.         context.onLoadWindowContent = true;
  590.  
  591.     // Calling this after a timeout because I'm finding some cases where calling
  592.     // it here causes freezeup when this results in loading a script file. This fixes that.
  593.     setTimeout(function()
  594.     {
  595.         try
  596.         {
  597.             TabWatcher.watchLoadedTopWindow(win);
  598.         }
  599.         catch(exc)
  600.         {
  601.             ERROR(exc);
  602.         }
  603.  
  604.     });
  605. }
  606.  
  607. function onUnloadWindow(event)
  608. {
  609.     var win = event.currentTarget;
  610.     var eventType = (win.parent == win) ? "pagehide" : "unload";
  611.     win.removeEventListener(eventType, onUnloadWindow, false);
  612.     TabWatcher.unwatchWindow(win);
  613. }
  614.  
  615. function delayBrowserLoad(browser, uri)
  616. {
  617.     setTimeout(function() { browser.loadURI(uri); }, 100);
  618. }
  619.  
  620. function safeGetName(request)
  621. {
  622.     try
  623.     {
  624.         return request.name;
  625.     }
  626.     catch (exc)
  627.     {
  628.         return null;
  629.     }
  630. }
  631.  
  632. function safeGetURI(browser)
  633. {
  634.     try
  635.     {
  636.         return browser.currentURI;
  637.     }
  638.     catch (exc)
  639.     {
  640.         return null;
  641.     }
  642. }
  643.  
  644. // ************************************************************************************************
  645.  
  646. }});
  647.