forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			324 lines
		
	
	
	
		
			8.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			324 lines
		
	
	
	
		
			8.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
 | |
|                                   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 | |
| 
 | |
| Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 | |
| let {
 | |
|   EventManager,
 | |
| } = ExtensionUtils;
 | |
| 
 | |
| // This file provides some useful code for the |tabs| and |windows|
 | |
| // modules. All of the code is installed on |global|, which is a scope
 | |
| // shared among the different ext-*.js scripts.
 | |
| 
 | |
| // Manages mapping between XUL tabs and extension tab IDs.
 | |
| global.TabManager = {
 | |
|   _tabs: new WeakMap(),
 | |
|   _nextId: 1,
 | |
| 
 | |
|   getId(tab) {
 | |
|     if (this._tabs.has(tab)) {
 | |
|       return this._tabs.get(tab);
 | |
|     }
 | |
|     let id = this._nextId++;
 | |
|     this._tabs.set(tab, id);
 | |
|     return id;
 | |
|   },
 | |
| 
 | |
|   getBrowserId(browser) {
 | |
|     let gBrowser = browser.ownerDocument.defaultView.gBrowser;
 | |
|     // Some non-browser windows have gBrowser but not
 | |
|     // getTabForBrowser!
 | |
|     if (gBrowser && gBrowser.getTabForBrowser) {
 | |
|       let tab = gBrowser.getTabForBrowser(browser);
 | |
|       if (tab) {
 | |
|         return this.getId(tab);
 | |
|       }
 | |
|     }
 | |
|     return -1;
 | |
|   },
 | |
| 
 | |
|   getTab(tabId) {
 | |
|     // FIXME: Speed this up without leaking memory somehow.
 | |
|     let e = Services.wm.getEnumerator("navigator:browser");
 | |
|     while (e.hasMoreElements()) {
 | |
|       let window = e.getNext();
 | |
|       if (!window.gBrowser) {
 | |
|         continue;
 | |
|       }
 | |
|       for (let tab of window.gBrowser.tabs) {
 | |
|         if (this.getId(tab) == tabId) {
 | |
|           return tab;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     return null;
 | |
|   },
 | |
| 
 | |
|   get activeTab() {
 | |
|     let window = WindowManager.topWindow;
 | |
|     if (window && window.gBrowser) {
 | |
|       return window.gBrowser.selectedTab;
 | |
|     }
 | |
|     return null;
 | |
|   },
 | |
| 
 | |
|   getStatus(tab) {
 | |
|     return tab.getAttribute("busy") == "true" ? "loading" : "complete";
 | |
|   },
 | |
| 
 | |
|   convert(extension, tab) {
 | |
|     let window = tab.ownerDocument.defaultView;
 | |
|     let windowActive = window == WindowManager.topWindow;
 | |
|     let result = {
 | |
|       id: this.getId(tab),
 | |
|       index: tab._tPos,
 | |
|       windowId: WindowManager.getId(window),
 | |
|       selected: tab.selected,
 | |
|       highlighted: tab.selected,
 | |
|       active: tab.selected,
 | |
|       pinned: tab.pinned,
 | |
|       status: this.getStatus(tab),
 | |
|       incognito: PrivateBrowsingUtils.isBrowserPrivate(tab.linkedBrowser),
 | |
|       width: tab.linkedBrowser.clientWidth,
 | |
|       height: tab.linkedBrowser.clientHeight,
 | |
|     };
 | |
| 
 | |
|     if (extension.hasPermission("tabs")) {
 | |
|       result.url = tab.linkedBrowser.currentURI.spec;
 | |
|       if (tab.linkedBrowser.contentTitle) {
 | |
|         result.title = tab.linkedBrowser.contentTitle;
 | |
|       }
 | |
|       let icon = window.gBrowser.getIcon(tab);
 | |
|       if (icon) {
 | |
|         result.favIconUrl = icon;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     return result;
 | |
|   },
 | |
| 
 | |
|   getTabs(extension, window) {
 | |
|     if (!window.gBrowser) {
 | |
|       return [];
 | |
|     }
 | |
|     return [ for (tab of window.gBrowser.tabs) this.convert(extension, tab) ];
 | |
|   },
 | |
| };
 | |
| 
 | |
| // Manages mapping between XUL windows and extension window IDs.
 | |
| global.WindowManager = {
 | |
|   _windows: new WeakMap(),
 | |
|   _nextId: 0,
 | |
| 
 | |
|   WINDOW_ID_NONE: -1,
 | |
|   WINDOW_ID_CURRENT: -2,
 | |
| 
 | |
|   get topWindow() {
 | |
|     return Services.wm.getMostRecentWindow("navigator:browser");
 | |
|   },
 | |
| 
 | |
|   windowType(window) {
 | |
|     // TODO: Make this work.
 | |
|     return "normal";
 | |
|   },
 | |
| 
 | |
|   getId(window) {
 | |
|     if (this._windows.has(window)) {
 | |
|       return this._windows.get(window);
 | |
|     }
 | |
|     let id = this._nextId++;
 | |
|     this._windows.set(window, id);
 | |
|     return id;
 | |
|   },
 | |
| 
 | |
|   getWindow(id) {
 | |
|     let e = Services.wm.getEnumerator("navigator:browser");
 | |
|     while (e.hasMoreElements()) {
 | |
|       let window = e.getNext();
 | |
|       if (this.getId(window) == id) {
 | |
|         return window;
 | |
|       }
 | |
|     }
 | |
|     return null;
 | |
|   },
 | |
| 
 | |
|   convert(extension, window, getInfo) {
 | |
|     let result = {
 | |
|       id: this.getId(window),
 | |
|       focused: window == WindowManager.topWindow,
 | |
|       top: window.screenY,
 | |
|       left: window.screenX,
 | |
|       width: window.outerWidth,
 | |
|       height: window.outerHeight,
 | |
|       incognito: PrivateBrowsingUtils.isWindowPrivate(window),
 | |
| 
 | |
|       // We fudge on these next two.
 | |
|       type: this.windowType(window),
 | |
|       state: window.fullScreen ? "fullscreen" : "normal",
 | |
|     };
 | |
| 
 | |
|     if (getInfo && getInfo.populate) {
 | |
|       results.tabs = TabManager.getTabs(extension, window);
 | |
|     }
 | |
| 
 | |
|     return result;
 | |
|   },
 | |
| };
 | |
| 
 | |
| // Manages listeners for window opening and closing. A window is
 | |
| // considered open when the "load" event fires on it. A window is
 | |
| // closed when a "domwindowclosed" notification fires for it.
 | |
| global.WindowListManager = {
 | |
|   _openListeners: new Set(),
 | |
|   _closeListeners: new Set(),
 | |
| 
 | |
|   addOpenListener(listener, fireOnExisting = true) {
 | |
|     if (this._openListeners.length == 0 && this._closeListeners.length == 0) {
 | |
|       Services.ww.registerNotification(this);
 | |
|     }
 | |
|     this._openListeners.add(listener);
 | |
| 
 | |
|     let e = Services.wm.getEnumerator("navigator:browser");
 | |
|     while (e.hasMoreElements()) {
 | |
|       let window = e.getNext();
 | |
|       if (window.document.readyState != "complete") {
 | |
|         window.addEventListener("load", this);
 | |
|       } else if (fireOnExisting) {
 | |
|         listener(window);
 | |
|       }
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   removeOpenListener(listener) {
 | |
|     this._openListeners.delete(listener);
 | |
|     if (this._openListeners.length == 0 && this._closeListeners.length == 0) {
 | |
|       Services.ww.unregisterNotification(this);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   addCloseListener(listener) {
 | |
|     if (this._openListeners.length == 0 && this._closeListeners.length == 0) {
 | |
|       Services.ww.registerNotification(this);
 | |
|     }
 | |
|     this._closeListeners.add(listener);
 | |
|   },
 | |
| 
 | |
|   removeCloseListener(listener) {
 | |
|     this._closeListeners.delete(listener);
 | |
|     if (this._openListeners.length == 0 && this._closeListeners.length == 0) {
 | |
|       Services.ww.unregisterNotification(this);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   handleEvent(event) {
 | |
|     let window = event.target.defaultView;
 | |
|     window.removeEventListener("load", this.loadListener);
 | |
|     if (window.document.documentElement.getAttribute("windowtype") != "navigator:browser") {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     for (let listener of this._openListeners) {
 | |
|       listener(window);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   queryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIObserver]),
 | |
| 
 | |
|   observe(window, topic, data) {
 | |
|     if (topic == "domwindowclosed") {
 | |
|       if (window.document.documentElement.getAttribute("windowtype") != "navigator:browser") {
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       window.removeEventListener("load", this);
 | |
|       for (let listener of this._closeListeners) {
 | |
|         listener(window);
 | |
|       }
 | |
|     } else {
 | |
|       window.addEventListener("load", this);
 | |
|     }
 | |
|   },
 | |
| };
 | |
| 
 | |
| // Provides a facility to listen for DOM events across all XUL windows.
 | |
| global.AllWindowEvents = {
 | |
|   _listeners: new Map(),
 | |
| 
 | |
|   // If |type| is a normal event type, invoke |listener| each time
 | |
|   // that event fires in any open window. If |type| is "progress", add
 | |
|   // a web progress listener that covers all open windows.
 | |
|   addListener(type, listener) {
 | |
|     if (type == "domwindowopened") {
 | |
|       return WindowListManager.addOpenListener(listener);
 | |
|     } else if (type == "domwindowclosed") {
 | |
|       return WindowListManager.addCloseListener(listener);
 | |
|     }
 | |
| 
 | |
|     let needOpenListener = this._listeners.size == 0;
 | |
| 
 | |
|     if (!this._listeners.has(type)) {
 | |
|       this._listeners.set(type, new Set());
 | |
|     }
 | |
|     let list = this._listeners.get(type);
 | |
|     list.add(listener);
 | |
| 
 | |
|     if (needOpenListener) {
 | |
|       WindowListManager.addOpenListener(this.openListener);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   removeListener(type, listener) {
 | |
|     if (type == "domwindowopened") {
 | |
|       return WindowListManager.removeOpenListener(listener);
 | |
|     } else if (type == "domwindowclosed") {
 | |
|       return WindowListManager.removeCloseListener(listener);
 | |
|     }
 | |
| 
 | |
|     let listeners = this._listeners.get(type);
 | |
|     listeners.delete(listener);
 | |
|     if (listeners.length == 0) {
 | |
|       this._listeners.delete(type);
 | |
|       if (this._listeners.size == 0) {
 | |
|         WindowListManager.removeOpenListener(this.openListener);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     let e = Services.wm.getEnumerator("navigator:browser");
 | |
|     while (e.hasMoreElements()) {
 | |
|       let window = e.getNext();
 | |
|       if (type == "progress") {
 | |
|         window.gBrowser.removeTabsProgressListener(listener);
 | |
|       } else {
 | |
|         window.removeEventListener(type, listener);
 | |
|       }
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   // Runs whenever the "load" event fires for a new window.
 | |
|   openListener(window) {
 | |
|     for (let [eventType, listeners] of AllWindowEvents._listeners) {
 | |
|       for (let listener of listeners) {
 | |
|         if (eventType == "progress") {
 | |
|           window.gBrowser.addTabsProgressListener(listener);
 | |
|         } else {
 | |
|           window.addEventListener(eventType, listener);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   },
 | |
| };
 | |
| 
 | |
| // Subclass of EventManager where we just need to call
 | |
| // add/removeEventListener on each XUL window.
 | |
| global.WindowEventManager = function(context, name, event, listener)
 | |
| {
 | |
|   EventManager.call(this, context, name, fire => {
 | |
|     let listener2 = (...args) => listener(fire, ...args);
 | |
|     AllWindowEvents.addListener(event, listener2);
 | |
|     return () => {
 | |
|       AllWindowEvents.removeListener(event, listener2);
 | |
|     }
 | |
|   });
 | |
| }
 | |
| 
 | |
| WindowEventManager.prototype = Object.create(EventManager.prototype);
 | 
