mirror of
https://github.com/rn10950/RetroZilla.git
synced 2024-11-15 04:00:12 +01:00
572 lines
20 KiB
JavaScript
572 lines
20 KiB
JavaScript
/* ***** BEGIN LICENSE BLOCK *****
|
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
*
|
|
* The contents of this file are subject to the Mozilla Public License Version
|
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
|
* the License. You may obtain a copy of the License at
|
|
* http://www.mozilla.org/MPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
* for the specific language governing rights and limitations under the
|
|
* License.
|
|
*
|
|
* The Original Code is Google Safe Browsing.
|
|
*
|
|
* The Initial Developer of the Original Code is Google Inc.
|
|
* Portions created by the Initial Developer are Copyright (C) 2006
|
|
* the Initial Developer. All Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
* Fritz Schneider <fritz@google.com> (original author)
|
|
*
|
|
* Alternatively, the contents of this file may be used under the terms of
|
|
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
|
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
|
* of those above. If you wish to allow use of your version of this file only
|
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
|
* use your version of this file under the terms of the MPL, indicate your
|
|
* decision by deleting the provisions above and replace them with the notice
|
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
|
* the provisions above, a recipient may use your version of this file under
|
|
* the terms of any one of the MPL, the GPL or the LGPL.
|
|
*
|
|
* ***** END LICENSE BLOCK ***** */
|
|
|
|
|
|
// This file implements a G_TabbedBrowserWatcher, an object
|
|
// encapsulating and abstracting the mechanics of working with tabs
|
|
// and the documents within them. The watcher provides notification of
|
|
// various DOM-related events (a document loaded, a document unloaded,
|
|
// tab was created/destroyed, user switched tabs, etc.) as well as
|
|
// commonly required methods (get me the current tab, find the tab to
|
|
// which this document belongs, etc.).
|
|
//
|
|
// This class does not do progresslistener-based notifications; for that,
|
|
// use the NavWatcher.
|
|
//
|
|
// Note: I use "browser" and "tab" interchangeably.
|
|
//
|
|
// This class adds a level of indirection to event registration. You
|
|
// initialize it with a tabbedbrowser, and then register to hear
|
|
// events from it instead of from the tabbedbrowser or browser itself.
|
|
// Your handlers are invoked with a custom object as an argument (see
|
|
// below). This object contains useful information such as a reference
|
|
// to the browser in which the event is happening and whether the
|
|
// event is occurring on the top-level document in that browser.
|
|
//
|
|
// The events you can register to hear are:
|
|
//
|
|
// EVENT DESCRIPTION
|
|
// ----- -----------
|
|
// load Fires when a page is shown in the browser window and
|
|
// this page wasn't in the bfcache
|
|
//
|
|
// unload Fires when a page is nav'd away from in the browser window
|
|
// and the page isn't going into the bfcache
|
|
//
|
|
// pageshow Fires when a page is shown in the browser window, whether
|
|
// it came from bfcache or not. (There is a "persisted"
|
|
// property we can get from the event object if we'd like.
|
|
// It indicates whether the page was loaded from bfcache.
|
|
// If false then we known load recently fired).
|
|
//
|
|
// pagehide Fires when a page is nav'd away from in the browser,
|
|
// whether its going into the bfcache or not. (There is
|
|
// a persisted property here as well that we're not
|
|
// propagating -- when its true the page is going into
|
|
// the bfcache, else it's not, and unload will shortly
|
|
// fire).
|
|
//
|
|
// domcontentloaded BROKEN BROKEN BROKEN BROKEN BROKEN BROKEN BROKEN BROKEN
|
|
// Fires when a doc's DOM is ready, but its externally linked
|
|
// content hasn't necessarily loaded. This event is
|
|
// currently broken: it doesn't fire when using the
|
|
// forward/back buttons in conjunction with the bfcache.
|
|
// Bryner is working on a fix.
|
|
//
|
|
// tabload Fires when a new tab has been created (but doesn't
|
|
// necessarily have content loaded in it)
|
|
//
|
|
// tabunload Fires when a tab is being destroyed (and might have had
|
|
// the content it contains destroyed)
|
|
//
|
|
// tabswitch Fires when the user switches tabs
|
|
//
|
|
// tabmove Fires when a user drags a tab to a different position
|
|
//
|
|
//
|
|
// For pageshow, pagehide, load, unload, and domcontentloaded, the event
|
|
// object you'll receive has the following properties:
|
|
//
|
|
// doc -- reference to Document on which the event fired
|
|
// browser -- the browser in which the document resides
|
|
// isTop -- boolean indicating if it is the top-level document
|
|
// inSelected -- boolean indicating if it is in the currently selected tab
|
|
//
|
|
// For tabload and unload it has:
|
|
//
|
|
// browser -- reference to the browser that is loading or closing
|
|
//
|
|
// For tabswitch it has:
|
|
//
|
|
// fromBrowser -- reference to the browser user is switching from
|
|
// toBrowser -- reference to the browser user is switching to
|
|
//
|
|
// For tabmove it has:
|
|
//
|
|
// tab -- the tab that was moved (Note that this is the actual
|
|
// tab widget that holds the document title, not the
|
|
// browser object. We use this because it's the target
|
|
// of the DOMNodeInserted event.)
|
|
// fromIndex -- the tab index before the move
|
|
// toIndex -- the tab index after the move
|
|
//
|
|
//
|
|
// The order of events is: tabload
|
|
// domcontentloaded
|
|
// load
|
|
// pageshow
|
|
// -- --
|
|
// pagehide
|
|
// unload
|
|
// tabunload
|
|
//
|
|
// Example:
|
|
//
|
|
// function handler(e /*event object*/) {
|
|
// foo(e.browser);
|
|
// };
|
|
// var watcher = new G_TabbedBrowserWatcher(document.getElementById(gBrowser));
|
|
// watcher.registerListener("load", handler); // register for events
|
|
// watcher.removeListener("load", handler); // unregister
|
|
//
|
|
//
|
|
// TODO, BUGS, ISSUES, COMPLICATIONS:
|
|
//
|
|
// The only major problem is in closing tabs:
|
|
//
|
|
// + When you close a tab, the reference to the Document you get in the
|
|
// unload event is undefined. We pass this along. This could easily
|
|
// be fixed by not listening for unload at all, but instead inferring
|
|
// it from the information in pagehide, and then firing our own "fake"
|
|
// unload after firing pagehide.
|
|
//
|
|
// + There's no docshell during the pagehide event, so we can't determine
|
|
// if the document is top-level. We pass undefined in this case.
|
|
//
|
|
// + Though the browser reference during tabunload will be valid, its
|
|
// members most likely have already been torn down. Use it in an
|
|
// objectsafemap to keep state if you need its members.
|
|
//
|
|
// + The event listener DOMNodeInserted has the potential to cause
|
|
// performance problems if there are too many events fired. It
|
|
// should be ok, since we inserted it as far as possible into
|
|
// the xul tree.
|
|
//
|
|
//
|
|
// TODO: need better enforcement of unique names. Two tabbedbrowserwatchers
|
|
// with the same name will clobber each other because they use that
|
|
// name to mark browsers they've seen.
|
|
//
|
|
// TODO: the functions that iterate of windows and documents badly need
|
|
// to be made cleaner. Right now we have multiple implementations
|
|
// that essentially do the same thing :(
|
|
//
|
|
// But good enough for government work.
|
|
|
|
/**
|
|
* Encapsulates tab-related information. You can use the
|
|
* G_TabbedBrowserWatcher to watch for events on tabs as well as to
|
|
* retrieve tab-related data (such as what tab is currently showing).
|
|
* It receives many event notifications from G_BrowserWatchers it
|
|
* attaches to newly opening tabs.
|
|
*
|
|
* @param tabBrowser A reference to the tabbed browser you wish to watch.
|
|
*
|
|
* @param name String containing a probabilistically unique name. Used to
|
|
* ensure that each tabbedbrowserwatcher can uniquely mark
|
|
* browser it has "seen."
|
|
*
|
|
* @param opt_filterAboutBlank Boolean indicating whether to filter events
|
|
* for about:blank. These events are often
|
|
* spurious since about:blank is the default
|
|
* page for an empty browser.
|
|
*
|
|
* @constructor
|
|
*/
|
|
|
|
function G_TabbedBrowserWatcher(tabBrowser, name, opt_filterAboutBlank) {
|
|
this.debugZone = "tabbedbrowserwatcher";
|
|
this.registrar_ = new EventRegistrar(G_TabbedBrowserWatcher.events);
|
|
this.tabBrowser_ = tabBrowser;
|
|
this.filterAboutBlank_ = !!opt_filterAboutBlank;
|
|
this.events = G_TabbedBrowserWatcher.events; // Convenience pointer
|
|
|
|
// We need some way to tell if we've seen a browser before, so we
|
|
// set a property on it with a probabilistically unique string. The
|
|
// string is a combination of a static string and one passed in by
|
|
// the caller.
|
|
G_Assert(this, typeof name == "string" && !!name,
|
|
"Need a probabilistically unique name");
|
|
this.name_ = name;
|
|
this.mark_ = G_TabbedBrowserWatcher.mark_ + "-" + this.name_;
|
|
|
|
this.tabbox_ = this.getTabBrowser().mTabBox;
|
|
|
|
// There's no tabswitch event in Firefox, so we fake it by watching
|
|
// for selects on the tabbox.
|
|
this.onTabSwitchClosure_ = BindToObject(this.onTabSwitch, this);
|
|
this.tabbox_.addEventListener("select",
|
|
this.onTabSwitchClosure_, true);
|
|
|
|
// Used to determine when the user has switched tabs
|
|
this.lastTab_ = this.getCurrentBrowser();
|
|
}
|
|
|
|
// Events for which listeners can register
|
|
G_TabbedBrowserWatcher.events = {
|
|
TABSWITCH: "tabswitch",
|
|
};
|
|
|
|
// We mark new tabs as we see them
|
|
G_TabbedBrowserWatcher.mark_ = "watcher-marked";
|
|
|
|
/**
|
|
* Remove all the event handlers and clean up circular refs.
|
|
*/
|
|
G_TabbedBrowserWatcher.prototype.shutdown = function() {
|
|
G_Debug(this, "Removing event listeners");
|
|
if (this.tabbox_) {
|
|
this.tabbox_.removeEventListener("select",
|
|
this.onTabSwitchClosure_, true);
|
|
// Break circular ref so we can be gc'ed.
|
|
this.tabbox_ = null;
|
|
}
|
|
// Break circular ref so we can be gc'ed.
|
|
if (this.lastTab_) {
|
|
this.lastTab_ = null;
|
|
}
|
|
|
|
if (this.tabBrowser_) {
|
|
this.tabBrowser_ = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check to see if we've seen a browser before
|
|
*
|
|
* @param browser Browser to check
|
|
* @returns Boolean indicating if we've attached a BrowserWatcher to this
|
|
* browser
|
|
*/
|
|
G_TabbedBrowserWatcher.prototype.isInstrumented_ = function(browser) {
|
|
return !!browser[this.mark_];
|
|
}
|
|
|
|
/**
|
|
* Attaches a BrowserWatcher to a browser and marks it as seen
|
|
*
|
|
* @param browser Browser to which to attach a G_BrowserWatcher
|
|
*/
|
|
G_TabbedBrowserWatcher.prototype.instrumentBrowser_ = function(browser) {
|
|
G_Assert(this, !this.isInstrumented_(browser),
|
|
"Browser already instrumented!");
|
|
|
|
// The browserwatcher will hook itself into the browser and its parent (us)
|
|
new G_BrowserWatcher(this, browser);
|
|
browser[this.mark_] = true;
|
|
}
|
|
|
|
/**
|
|
* Register to receive events of a particular type
|
|
*
|
|
* @param eventType String indicating the event (see
|
|
* G_TabbedBrowserWatcher.events)
|
|
* @param listener Function to invoke when the event occurs. See top-
|
|
* level comments for parameters.
|
|
*/
|
|
G_TabbedBrowserWatcher.prototype.registerListener = function(eventType,
|
|
listener) {
|
|
this.registrar_.registerListener(eventType, listener);
|
|
}
|
|
|
|
/**
|
|
* Unregister a listener.
|
|
*
|
|
* @param eventType String one of G_TabbedBrowserWatcher.events' members
|
|
* @param listener Function to remove as listener
|
|
*/
|
|
G_TabbedBrowserWatcher.prototype.removeListener = function(eventType,
|
|
listener) {
|
|
this.registrar_.removeListener(eventType, listener);
|
|
}
|
|
|
|
/**
|
|
* Send an event to all listeners for that type.
|
|
*
|
|
* @param eventType String indicating the event to trigger
|
|
* @param e Object to pass to each listener (NOT copied -- be careful)
|
|
*/
|
|
G_TabbedBrowserWatcher.prototype.fire = function(eventType, e) {
|
|
this.registrar_.fire(eventType, e);
|
|
}
|
|
|
|
/**
|
|
* Convenience function to send a document-related event. We use this
|
|
* convenience function because the event constructing logic and
|
|
* parameters are the same for all these events. (Document-related
|
|
* events are load, unload, pagehide, pageshow, and domcontentloaded).
|
|
*
|
|
* @param eventType String indicating the type of event to fire (one of
|
|
* the document-related events)
|
|
*
|
|
* @param doc Reference to the HTMLDocument the event is occuring to
|
|
*
|
|
* @param browser Reference to the browser in which the document is contained
|
|
*/
|
|
G_TabbedBrowserWatcher.prototype.fireDocEvent_ = function(eventType,
|
|
doc,
|
|
browser) {
|
|
// If we've already shutdown, don't bother firing any events.
|
|
if (!this.tabBrowser_) {
|
|
G_Debug(this, "Firing event after shutdown: " + eventType);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Could be that the browser's contentDocument has already been torn
|
|
// down. If so, this throws, and we can't tell without keeping more
|
|
// state whether doc was the top frame.
|
|
var isTop = (doc == browser.contentDocument);
|
|
} catch(e) {
|
|
var isTop = undefined;
|
|
}
|
|
|
|
var inSelected = (browser == this.getCurrentBrowser());
|
|
|
|
var location = doc ? doc.location.href : undefined;
|
|
|
|
// Only send notifications for about:config's if we're supposed to
|
|
if (!this.filterAboutBlank_ || location != "about:blank") {
|
|
|
|
G_Debug(this, "firing " + eventType + " for " + location +
|
|
(isTop ? " (isTop)" : "") + (inSelected ? " (inSelected)" : ""));
|
|
this.fire(eventType, { "doc": doc,
|
|
"isTop": isTop,
|
|
"inSelected": inSelected,
|
|
"browser": browser});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Invoked when the user might have switched tabs
|
|
*
|
|
* @param e Event object
|
|
*/
|
|
G_TabbedBrowserWatcher.prototype.onTabSwitch = function(e) {
|
|
// Filter spurious events
|
|
// The event target is usually tabs but can be tabpanels when tabs were opened
|
|
// programatically via tabbrowser.addTab().
|
|
if (e.target == null ||
|
|
(e.target.localName != "tabs" && e.target.localName != "tabpanels"))
|
|
return;
|
|
|
|
var fromBrowser = this.lastTab_;
|
|
var toBrowser = this.getCurrentBrowser();
|
|
|
|
if (fromBrowser != toBrowser) {
|
|
this.lastTab_ = toBrowser;
|
|
G_Debug(this, "firing tabswitch");
|
|
this.fire(this.events.TABSWITCH, { "fromBrowser": fromBrowser,
|
|
"toBrowser": toBrowser });
|
|
}
|
|
}
|
|
|
|
// Utility functions
|
|
|
|
/**
|
|
* Returns a reference to the tabbed browser this G_TabbedBrowserWatcher
|
|
* was initialized with.
|
|
*/
|
|
G_TabbedBrowserWatcher.prototype.getTabBrowser = function() {
|
|
return this.tabBrowser_;
|
|
}
|
|
|
|
/**
|
|
* Returns a reference to the currently selected tab.
|
|
*/
|
|
G_TabbedBrowserWatcher.prototype.getCurrentBrowser = function() {
|
|
return this.getTabBrowser().selectedBrowser;
|
|
}
|
|
|
|
/**
|
|
* Returns a reference to the top window in the currently selected tab.
|
|
*/
|
|
G_TabbedBrowserWatcher.prototype.getCurrentWindow = function() {
|
|
return this.getCurrentBrowser().contentWindow;
|
|
}
|
|
|
|
/**
|
|
* Find the browser corresponding to a Document
|
|
*
|
|
* @param doc Document we want the browser for
|
|
* @returns Reference to the browser in which the given document is found
|
|
* or null if not found
|
|
*/
|
|
G_TabbedBrowserWatcher.prototype.getBrowserFromDocument = function(doc) {
|
|
// Could instead get the top window of the browser in which the doc
|
|
// is found via doc.defaultView.top, but sometimes the document
|
|
// isn't in a browser at all (it's being unloaded, for example), so
|
|
// defaultView won't be valid.
|
|
|
|
// Helper: return true if doc is a sub-document of win
|
|
function docInWindow(doc, win) {
|
|
if (win.document == doc)
|
|
return true;
|
|
|
|
if (win.frames)
|
|
for (var i = 0; i < win.frames.length; i++)
|
|
if (docInWindow(doc, win.frames[i]))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
var browsers = this.getTabBrowser().browsers;
|
|
for (var i = 0; i < browsers.length; i++)
|
|
if (docInWindow(doc, browsers[i].contentWindow))
|
|
return browsers[i];
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Find the Document that has the given URL loaded. Returns on the
|
|
* _first_ such document found, so be careful.
|
|
*
|
|
* TODO make doc/window searches more elegant, and don't use inner functions
|
|
*
|
|
* @param url String indicating the URL we're searching for
|
|
* @param opt_browser Optional reference to a browser. If given, the
|
|
* search will be confined to only this browser.
|
|
* @returns Reference to the Document with that URL or null if not found
|
|
*/
|
|
G_TabbedBrowserWatcher.prototype.getDocumentFromURL = function(url,
|
|
opt_browser) {
|
|
|
|
// Helper function: return the Document in win that has location of url
|
|
function docWithURL(win, url) {
|
|
if (win.document.location.href == url)
|
|
return win.document;
|
|
|
|
if (win.frames)
|
|
for (var i = 0; i < win.frames.length; i++) {
|
|
var rv = docWithURL(win.frames[i], url);
|
|
if (rv)
|
|
return rv;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
if (opt_browser)
|
|
return docWithURL(opt_browser.contentWindow, url);
|
|
|
|
var browsers = this.getTabBrowser().browsers;
|
|
for (var i=0; i < browsers.length; i++) {
|
|
var rv = docWithURL(browsers[i].contentWindow, url);
|
|
if (rv)
|
|
return rv;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Find the all Documents that have the given URL loaded.
|
|
*
|
|
* TODO make doc/window searches more elegant, and don't use inner functions
|
|
*
|
|
* @param url String indicating the URL we're searching for
|
|
*
|
|
* @param opt_browser Optional reference to a browser. If given, the
|
|
* search will be confined to only this browser.
|
|
*
|
|
* @returns Array of Documents with the given URL (zero length if none found)
|
|
*/
|
|
G_TabbedBrowserWatcher.prototype.getDocumentsFromURL = function(url,
|
|
opt_browser) {
|
|
|
|
var docs = [];
|
|
|
|
// Helper function: add Docs in win with the location of url
|
|
function getDocsWithURL(win, url) {
|
|
if (win.document.location.href == url)
|
|
docs.push(win.document);
|
|
|
|
if (win.frames)
|
|
for (var i = 0; i < win.frames.length; i++)
|
|
getDocsWithURL(win.frames[i], url);
|
|
}
|
|
|
|
if (opt_browser)
|
|
return getDocsWithURL(opt_browser.contentWindow, url);
|
|
|
|
var browsers = this.getTabBrowser().browsers;
|
|
for (var i=0; i < browsers.length; i++)
|
|
getDocsWithURL(browsers[i].contentWindow, url);
|
|
|
|
return docs;
|
|
}
|
|
|
|
|
|
/**
|
|
* Finds the browser in which a Window resides.
|
|
*
|
|
* @param sub Window to find
|
|
* @returns Reference to the browser in which sub resides, else null
|
|
* if not found
|
|
*/
|
|
G_TabbedBrowserWatcher.prototype.getBrowserFromWindow = function(sub) {
|
|
|
|
// Helpfer function: return true if sub is a sub-window of win
|
|
function containsSubWindow(sub, win) {
|
|
if (win == sub)
|
|
return true;
|
|
|
|
if (win.frames)
|
|
for (var i=0; i < win.frames.length; i++)
|
|
if (containsSubWindow(sub, win.frames[i]))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
var browsers = this.getTabBrowser().browsers;
|
|
for (var i=0; i < browsers.length; i++)
|
|
if (containsSubWindow(sub, browsers[i].contentWindow))
|
|
return browsers[i];
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Finds the XUL <tab> tag corresponding to a given browser.
|
|
*
|
|
* @param tabBrowser Reference to the tabbed browser in which browser lives
|
|
* @param browser Reference to the browser we wish to find the tab of
|
|
* @returns Reference to the browser's tab element, or null
|
|
* @static
|
|
*/
|
|
G_TabbedBrowserWatcher.getTabElementFromBrowser = function(tabBrowser,
|
|
browser) {
|
|
|
|
for (var i=0; i < tabBrowser.browsers.length; i++)
|
|
if (tabBrowser.browsers[i] == browser)
|
|
return tabBrowser.mTabContainer.childNodes[i];
|
|
|
|
return null;
|
|
}
|