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; |
})(); |