| Index: chrome/ext/background.js |
| =================================================================== |
| --- a/chrome/ext/background.js |
| +++ b/chrome/ext/background.js |
| @@ -17,187 +17,111 @@ |
| (function() |
| { |
| - /* Events */ |
| + /* Pages */ |
| - var TabEventTarget = function() |
| + var sendMessage = chrome.tabs.sendMessage || chrome.tabs.sendRequest; |
| + |
| + var Page = ext.Page = function(tab) |
| { |
| - WrappedEventTarget.apply(this, arguments); |
| + this._id = tab.id; |
| + this._url = tab.url; |
| - this._tabs = {}; |
| + this.browserAction = new BrowserAction(tab.id); |
| + }; |
| + Page.prototype = { |
| + get url() |
| + { |
| + // usually our Page objects are created from Chrome's Tab objects, which |
| + // provide the url. So we can return the url given in the constructor. |
| + if (this._url != null) |
| + return this._url; |
| - this._sharedListener = this._sharedListener.bind(this); |
| - this._removeTab = this._removeTab.bind(this); |
| - }; |
| - TabEventTarget.prototype = { |
| - __proto__: WrappedEventTarget.prototype, |
| - _bindToTab: function(tab) |
| + // but sometimes we only have the tab id when we create a Page object. |
| + // In that case we get the url from top frame of the tab, recorded by |
| + // the onBeforeRequest handler. |
| + var frames = framesOfTabs[this._id]; |
| + if (frames) |
| + { |
| + var frame = frames[0]; |
| + if (frame) |
| + return frame.url; |
| + } |
| + }, |
| + activate: function() |
| { |
| - return { |
| - addListener: function(listener) |
| - { |
| - var listeners = this._tabs[tab._id]; |
| - |
| - if (!listeners) |
| - { |
| - this._tabs[tab._id] = listeners = []; |
| - |
| - if (Object.keys(this._tabs).length == 1) |
| - this.addListener(this._sharedListener); |
| - |
| - tab.onRemoved.addListener(this._removeTab); |
| - } |
| - |
| - listeners.push(listener); |
| - }.bind(this), |
| - removeListener: function(listener) |
| - { |
| - var listeners = this._tabs[tab._id]; |
| - if (!listeners) |
| - return; |
| - |
| - var idx = listeners.indexOf(listener); |
| - if (idx == -1) |
| - return; |
| - |
| - listeners.splice(idx, 1); |
| - |
| - if (listeners.length == 0) |
| - tab.onRemoved.removeListener(this._removeTab); |
| - else |
| - { |
| - if (listeners.length > 1) |
| - return; |
| - if (listeners[0] != this._removeTab) |
| - return; |
| - } |
| - |
| - this._removeTab(tab); |
| - }.bind(this) |
| - }; |
| + chrome.tabs.update(this._id, {selected: true}); |
| }, |
| - _sharedListener: function(tab) |
| + sendMessage: function(message, responseCallback) |
| { |
| - var listeners = this._tabs[tab._id]; |
| - |
| - if (!listeners) |
| - return; |
| - |
| - // copy listeners before calling them, because they might |
| - // add or remove other listeners, which must not be taken |
| - // into account before the next occurrence of the event |
| - listeners = listeners.slice(0); |
| - |
| - for (var i = 0; i < listeners.length; i++) |
| - listeners[i](tab); |
| - }, |
| - _removeTab: function(tab) |
| - { |
| - delete this._tabs[tab._id]; |
| - |
| - if (Object.keys(this._tabs).length == 0) |
| - this.removeListener(this._sharedListener); |
| + sendMessage(this._id, message, responseCallback); |
| } |
| }; |
| - var BeforeNavigateTabEventTarget = function() |
| - { |
| - TabEventTarget.call(this, chrome.webNavigation.onBeforeNavigate); |
| - }; |
| - BeforeNavigateTabEventTarget.prototype = { |
| - __proto__: TabEventTarget.prototype, |
| - _wrapListener: function(listener) |
| + ext.pages = { |
| + open: function(url, callback) |
| { |
| - return function(details) |
| + if (callback) |
| { |
| - if (details.frameId == 0) |
| - listener(new Tab({id: details.tabId, url: details.url})); |
| - }; |
| - } |
| + chrome.tabs.create({url: url}, function(openedTab) |
| + { |
| + var onUpdated = function(tabId, changeInfo, tab) |
| + { |
| + if (tabId == openedTab.id && changeInfo.status == "complete") |
| + { |
| + chrome.tabs.onUpdated.removeListener(onUpdated); |
| + callback(new Page(tab)); |
| + } |
| + }; |
| + chrome.tabs.onUpdated.addListener(onUpdated); |
| + }); |
| + } |
| + else |
| + chrome.tabs.create({url: url}); |
| + }, |
| + query: function(info, callback) |
| + { |
| + var rawInfo = {}; |
| + for (var property in info) |
| + { |
| + switch (property) |
| + { |
| + case "active": |
| + case "lastFocusedWindow": |
| + rawInfo[property] = info[property]; |
| + } |
| + } |
| + |
| + chrome.tabs.query(rawInfo, function(tabs) |
| + { |
| + callback(tabs.map(function(tab) |
| + { |
| + return new Page(tab); |
| + })); |
| + }); |
| + }, |
| + onLoading: new ext._EventTarget() |
| }; |
| - var LoadingTabEventTarget = function() |
| + chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) |
| { |
| - TabEventTarget.call(this, chrome.tabs.onUpdated); |
| - }; |
| - LoadingTabEventTarget.prototype = { |
| - __proto__: TabEventTarget.prototype, |
| - _wrapListener: function(listener) |
| - { |
| - return function(id, info, tab) |
| - { |
| - if (info.status == "loading") |
| - listener(new Tab(tab)); |
| - }; |
| - } |
| - }; |
| + if (changeInfo.status == "loading") |
| + ext.pages.onLoading._dispatch(new Page(tab)); |
| + }); |
| - var CompletedTabEventTarget = function() |
| + chrome.webNavigation.onBeforeNavigate.addListener(function(details) |
| { |
| - TabEventTarget.call(this, chrome.tabs.onUpdated); |
| - }; |
| - CompletedTabEventTarget.prototype = { |
| - __proto__: TabEventTarget.prototype, |
| - _wrapListener: function(listener) |
| - { |
| - return function(id, info, tab) |
| - { |
| - if (info.status == "complete") |
| - listener(new Tab(tab)); |
| - }; |
| - } |
| - }; |
| + if (details.frameId == 0) |
| + ext._removeFromAllPageMaps(details.tabId); |
| + }); |
| - var ActivatedTabEventTarget = function() |
| + chrome.tabs.onRemoved.addListener(function(tabId) |
| { |
| - TabEventTarget.call(this, chrome.tabs.onActivated); |
| - }; |
| - ActivatedTabEventTarget.prototype = { |
| - __proto__: TabEventTarget.prototype, |
| - _wrapListener: function(listener) |
| - { |
| - return function(info) |
| - { |
| - chrome.tabs.get(info.tabId, function(tab) |
| - { |
| - listener(new Tab(tab)); |
| - }); |
| - }; |
| - } |
| - } |
| + ext._removeFromAllPageMaps(tabId); |
| + delete framesOfTabs[tabId]; |
| + }); |
| - var RemovedTabEventTarget = function() |
| - { |
| - TabEventTarget.call(this, chrome.tabs.onRemoved); |
| - }; |
| - RemovedTabEventTarget.prototype = { |
| - __proto__: TabEventTarget.prototype, |
| - _wrapListener: function(listener) |
| - { |
| - return function(id) { listener(new Tab({id: id})); }; |
| - } |
| - }; |
| - var BackgroundMessageEventTarget = function() |
| - { |
| - MessageEventTarget.call(this); |
| - } |
| - BackgroundMessageEventTarget.prototype = { |
| - __proto__: MessageEventTarget.prototype, |
| - _wrapSender: function(sender) |
| - { |
| - var tab = new Tab(sender.tab); |
| - |
| - //url parameter is missing in sender object (Chrome v28 and below) |
| - if (!("url" in sender)) |
| - sender.url = tab.url; |
| - return {tab: tab, frame: new Frame({url: sender.url, tab: tab})}; |
| - } |
| - }; |
| - |
| - |
| - /* Tabs */ |
| - |
| - var sendMessage = chrome.tabs.sendMessage || chrome.tabs.sendRequest; |
| + /* Browser actions */ |
| var BrowserAction = function(tabId) |
| { |
| @@ -244,148 +168,15 @@ |
| } |
| }; |
| - Tab = function(tab) |
| - { |
| - this._id = tab.id; |
| - this._url = tab.url; |
| - |
| - this.browserAction = new BrowserAction(tab.id); |
| - |
| - this.onLoading = ext.tabs.onLoading._bindToTab(this); |
| - this.onCompleted = ext.tabs.onCompleted._bindToTab(this); |
| - this.onActivated = ext.tabs.onActivated._bindToTab(this); |
| - this.onRemoved = ext.tabs.onRemoved._bindToTab(this); |
| - |
| - // the "beforeNavigate" event in Safari isn't dispatched when a new URL |
| - // was entered into the address bar. So we can only use it only on Chrome, |
| - // but we have to hide it from the browser-independent high level code. |
| - this._onBeforeNavigate = ext.tabs._onBeforeNavigate._bindToTab(this); |
| - }; |
| - Tab.prototype = { |
| - get url() |
| - { |
| - // usually our Tab objects are created from chrome Tab objects, which |
| - // provide the url. So we can return the url given in the constructor. |
| - if (this._url != null) |
| - return this._url; |
| - |
| - // but sometimes we only have the id when we create a Tab object. |
| - // In that case we get the url from top frame of the tab, recorded by |
| - // the onBeforeRequest handler. |
| - var frames = framesOfTabs.get(this); |
| - if (frames) |
| - { |
| - var frame = frames[0]; |
| - if (frame) |
| - return frame.url; |
| - } |
| - }, |
| - close: function() |
| - { |
| - chrome.tabs.remove(this._id); |
| - }, |
| - activate: function() |
| - { |
| - chrome.tabs.update(this._id, {selected: true}); |
| - }, |
| - sendMessage: function(message, responseCallback) |
| - { |
| - sendMessage(this._id, message, responseCallback); |
| - } |
| - }; |
| - |
| - TabMap = function(deleteOnPageUnload) |
| - { |
| - this._map = {}; |
| - |
| - this._delete = this._delete.bind(this); |
| - this._deleteOnPageUnload = deleteOnPageUnload; |
| - }; |
| - TabMap.prototype = { |
| - get: function(tab) |
| - { |
| - return (this._map[tab._id] || {}).value; |
| - }, |
| - set: function(tab, value) |
| - { |
| - if (!(tab._id in this._map)) |
| - { |
| - tab.onRemoved.addListener(this._delete); |
| - if (this._deleteOnPageUnload) |
| - tab._onBeforeNavigate.addListener(this._delete); |
| - } |
| - |
| - this._map[tab._id] = {tab: tab, value: value}; |
| - }, |
| - has: function(tab) |
| - { |
| - return tab._id in this._map; |
| - }, |
| - clear: function() |
| - { |
| - for (var id in this._map) |
| - this.delete(this._map[id].tab); |
| - }, |
| - _delete: function(tab) |
| - { |
| - // delay so that other event handlers can still lookup this tab |
| - setTimeout(this.delete.bind(this, tab), 0); |
| - }, |
| - delete: function(tab) |
| - { |
| - delete this._map[tab._id]; |
| - |
| - tab.onRemoved.removeListener(this._delete); |
| - tab._onBeforeNavigate.removeListener(this._delete); |
| - } |
| - }; |
| - |
| - |
| - /* Windows */ |
| - |
| - Window = function(win) |
| - { |
| - this._id = win.id; |
| - this.visible = win.status != "minimized"; |
| - }; |
| - Window.prototype = { |
| - getAllTabs: function(callback) |
| - { |
| - chrome.tabs.query({windowId: this._id}, function(tabs) |
| - { |
| - callback(tabs.map(function(tab) { return new Tab(tab); })); |
| - }); |
| - }, |
| - getActiveTab: function(callback) |
| - { |
| - chrome.tabs.query({windowId: this._id, active: true}, function(tabs) |
| - { |
| - callback(new Tab(tabs[0])); |
| - }); |
| - }, |
| - openTab: function(url, callback) |
| - { |
| - var props = {windowId: this._id, url: url}; |
| - |
| - if (!callback) |
| - chrome.tabs.create(props); |
| - else |
| - chrome.tabs.create(props, function(tab) |
| - { |
| - callback(new Tab(tab)); |
| - }); |
| - } |
| - }; |
| - |
| /* Frames */ |
| - var framesOfTabs = new TabMap(); |
| + var framesOfTabs = {__proto__: null}; |
| - Frame = function(params) |
| + var Frame = ext.Frame = function(params) |
| { |
| - this._tab = params.tab; |
| - this._id = params.id; |
| + this._frameId = params.frameId; |
| + this._tabId = params.tabId; |
| this._url = params.url; |
| }; |
| Frame.prototype = { |
| @@ -394,22 +185,22 @@ |
| if (this._url != null) |
| return this._url; |
| - var frames = framesOfTabs.get(this._tab); |
| + var frames = framesOfTabs[this._tabId]; |
| if (frames) |
| { |
| - var frame = frames[this._id]; |
| + var frame = frames[this._frameId]; |
| if (frame) |
| return frame.url; |
| } |
| }, |
| get parent() |
| { |
| - var frames = framesOfTabs.get(this._tab); |
| + var frames = framesOfTabs[this._tabId]; |
| if (frames) |
| { |
| var frame; |
| - if (this._id != null) |
| - frame = frames[this._id]; |
| + if (this._frameId != null) |
| + frame = frames[this._frameId]; |
| else |
| { |
| // the frame ID wasn't available when we created |
| @@ -428,13 +219,18 @@ |
| if (!frame || frame.parent == -1) |
| return null; |
| - return new Frame({id: frame.parent, tab: this._tab}); |
| + return new Frame({frameId: frame.parent, tabId: this._tabId}); |
| } |
| } |
| }; |
| - /* Web request blocking */ |
| + /* Web requests */ |
| + |
| + ext.webRequest = { |
| + onBeforeRequest: new ext._EventTarget(true), |
| + handlerBehaviorChanged: chrome.webRequest.handlerBehaviorChanged |
| + }; |
| chrome.webRequest.onBeforeRequest.addListener(function(details) |
| { |
| @@ -446,13 +242,12 @@ |
| if (details.tabId == -1) |
| return; |
| - var tab = new Tab({id: details.tabId}); |
| - var frames = framesOfTabs.get(tab); |
| + var page = new Tab({id: details.tabId}); |
| + var frames = framesOfTabs[details.tabId]; |
| if (!frames) |
| { |
| - frames = []; |
| - framesOfTabs.set(tab, frames); |
| + frames = framesOfTabs[details.tabId] = []; |
| // assume that the first request belongs to the top frame. Chrome |
| // may give the top frame the type "object" instead of "main_frame". |
| @@ -493,13 +288,10 @@ |
| frames[details.frameId].parent = frameId; |
| } |
| - var frame = new Frame({id: frameId, tab: tab}); |
| + var frame = new Frame({id: frameId, tabId: details.tabId}); |
| - for (var i = 0; i < ext.webRequest.onBeforeRequest._listeners.length; i++) |
| - { |
| - if (ext.webRequest.onBeforeRequest._listeners[i](details.url, details.type, tab, frame) === false) |
| - return {cancel: true}; |
| - } |
| + if (!ext.webRequest.onBeforeRequest._dispatch(details.url, details.type, page, frame)) |
| + return {cancel: true}; |
| } |
| catch (e) |
| { |
| @@ -511,49 +303,11 @@ |
| }, {urls: ["<all_urls>"]}, ["blocking"]); |
| - /* API */ |
| - |
| - ext.windows = { |
| - getAll: function(callback) |
| - { |
| - chrome.windows.getAll(function(windows) |
| - { |
| - callback(windows.map(function(win) |
| - { |
| - return new Window(win); |
| - })); |
| - }); |
| - }, |
| - getLastFocused: function(callback) |
| - { |
| - chrome.windows.getLastFocused(function(win) |
| - { |
| - callback(new Window(win)); |
| - }); |
| - } |
| - }; |
| - |
| - ext.tabs = { |
| - onLoading: new LoadingTabEventTarget(), |
| - onCompleted: new CompletedTabEventTarget(), |
| - onActivated: new ActivatedTabEventTarget(), |
| - onRemoved: new RemovedTabEventTarget(), |
| - |
| - // the "beforeNavigate" event in Safari isn't dispatched when a new URL |
| - // was entered into the address bar. So we can only use it only on Chrome, |
| - // but we have to hide it from the browser-independent high level code. |
| - _onBeforeNavigate: new BeforeNavigateTabEventTarget() |
| - }; |
| - |
| - ext.webRequest = { |
| - onBeforeRequest: new SimpleEventTarget(), |
| - handlerBehaviorChanged: chrome.webRequest.handlerBehaviorChanged |
| - }; |
| - |
| - ext.storage = localStorage; |
| + /* Context menus */ |
| var contextMenuItems = []; |
| var isContextMenuHidden = true; |
| + |
| ext.contextMenus = { |
| addMenuItem: function(title, contexts, onclick) |
| { |
| @@ -562,7 +316,7 @@ |
| contexts: contexts, |
| onclick: function(info, tab) |
| { |
| - onclick(info.srcUrl, new Tab(tab)); |
| + onclick(info.srcUrl, new Page(tab)); |
| } |
| }); |
| this.showMenuItems(); |
| @@ -601,5 +355,19 @@ |
| } |
| }; |
| - ext.onMessage = new BackgroundMessageEventTarget(); |
| + |
| + /* Message passing */ |
| + |
| + ext._setupMessageListener(function(sender) |
| + { |
| + return { |
| + page: new Page(sender.tab), |
| + frame: new Frame({url: sender.url, tabId: sender.tab.id}) |
| + }; |
| + }); |
| + |
| + |
| + /* Storage */ |
| + |
| + ext.storage = localStorage; |
| })(); |