| Index: chrome/ext/background.js |
| =================================================================== |
| --- a/chrome/ext/background.js |
| +++ b/chrome/ext/background.js |
| @@ -99,6 +99,22 @@ |
| } |
| }; |
| + var BeforeNavigateTabEventTarget = function() |
| + { |
| + TabEventTarget.call(this, chrome.webNavigation.onBeforeNavigate); |
| + }; |
| + BeforeNavigateTabEventTarget.prototype = { |
| + __proto__: TabEventTarget.prototype, |
| + _wrapListener: function(listener) |
| + { |
| + return function(details) |
| + { |
| + if (details.frameId == 0) |
| + listener(new Tab({id: details.tabId, url: details.url})); |
| + }; |
| + } |
| + }; |
| + |
| var LoadingTabEventTarget = function() |
| { |
| TabEventTarget.call(this, chrome.tabs.onUpdated); |
| @@ -161,33 +177,16 @@ |
| } |
| }; |
| - var BeforeRequestEventTarget = function() |
| + var BackgroundMessageEventTarget = function() |
| { |
| - WrappedEventTarget.call(this, chrome.webRequest.onBeforeRequest); |
| - }; |
| - BeforeRequestEventTarget.prototype = { |
| - __proto__: WrappedEventTarget.prototype, |
| - _wrapListener: function(listener) |
| + MessageEventTarget.call(this); |
| + } |
| + BackgroundMessageEventTarget.prototype = { |
| + __proto__: MessageEventTarget.prototype, |
| + _wrapSender: function(sender) |
| { |
| - return function(details) |
| - { |
| - var tab = null; |
| - |
| - if (details.tabId != -1) |
| - tab = new Tab({id: details.tabId}); |
| - |
| - return {cancel: listener( |
| - details.url, |
| - details.type, |
| - tab, |
| - details.frameId, |
| - details.parentFrameId |
| - ) === false}; |
| - }; |
| - }, |
| - _prepareExtraArguments: function(urls) |
| - { |
| - return [urls ? {urls: urls} : {}, ["blocking"]]; |
| + var tab = new Tab(sender.tab); |
| + return {tab: tab, frame: new Frame({url: sender.url, tab: tab})}; |
| } |
| }; |
| @@ -241,16 +240,35 @@ |
| Tab = function(tab) |
| { |
| this._id = tab.id; |
| + this._url = tab.url; |
| - this.url = tab.url; |
| this.browserAction = new BrowserAction(tab.id); |
| + this.onBeforeNavigate = ext.tabs.onBeforeNavigate._bindToTab(this); |
| 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); |
| }; |
| 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); |
| @@ -265,10 +283,12 @@ |
| } |
| }; |
| - TabMap = function() |
| + TabMap = function(deleteTabOnBeforeNavigate) |
| { |
| this._map = {}; |
| - this.delete = this.delete.bind(this); |
| + |
| + this._delete = this._delete.bind(this); |
| + this._deleteTabOnBeforeNavigate = deleteTabOnBeforeNavigate; |
| }; |
| TabMap.prototype = { |
| get: function(tab) |
| @@ -278,7 +298,11 @@ |
| set: function(tab, value) |
| { |
| if (!(tab._id in this._map)) |
| - tab.onRemoved.addListener(this.delete); |
| + { |
| + tab.onRemoved.addListener(this._delete); |
| + if (this._deleteTabOnBeforeNavigate) |
| + tab.onBeforeNavigate.addListener(this._delete); |
| + } |
| this._map[tab._id] = {tab: tab, value: value}; |
| }, |
| @@ -290,12 +314,19 @@ |
| { |
| 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); |
| } |
| }; |
| TabMap.prototype["delete"] = function(tab) |
| { |
| delete this._map[tab._id]; |
| - tab.onRemoved.removeListener(this.delete); |
| + |
| + tab.onRemoved.removeListener(this._delete); |
| + tab.onBeforeNavigate.removeListener(this._delete); |
| }; |
| @@ -336,6 +367,119 @@ |
| }; |
| + /* Frames */ |
| + |
| + var framesOfTabs = new TabMap(); |
| + |
| + Frame = function(params) |
| + { |
| + this._tab = params.tab; |
| + this._id = params.id; |
| + this._url = params.url; |
| + }; |
| + Frame.prototype = { |
| + get url() |
| + { |
| + if (this._url != null) |
| + return this._url; |
| + |
| + var frames = framesOfTabs.get(this._tab); |
| + if (frames) |
| + { |
| + var frame = frames[this._id]; |
| + if (frame) |
| + return frame.url; |
| + } |
| + }, |
| + get parent() |
| + { |
| + var frames = framesOfTabs.get(this._tab); |
| + if (frames) |
| + { |
| + var frame; |
| + if (this._id != null) |
| + frame = frames[this._id]; |
| + else |
| + { |
| + // the frame ID wasn't available when we created |
| + // the Frame object (e.g. for the onMessage event), |
| + // so we have to find the frame details by their URL. |
| + for (var frameId in frames) |
| + { |
| + if (frames[frameId].url == this._url) |
| + { |
| + frame = frames[frameId]; |
| + break; |
| + } |
| + } |
| + } |
| + |
| + if (frame) |
|
Felix Dahlke
2014/01/18 13:39:19
This would return undefined if frame is falsy. I t
Sebastian Noack
2014/01/19 10:19:40
Done.
|
| + if (frame.parent != -1) |
| + return new Frame({id: frame.parent, tab: this._tab}); |
| + else |
| + return null; |
| + } |
| + } |
| + }; |
| + |
| + |
| + /* Web request blocking */ |
| + |
| + chrome.webRequest.onBeforeRequest.addListener(function(details) |
| + { |
| + // the high-level code isn't interested in requests that aren't related |
| + // to a tab and since those can only be handled in Chrome, we ignore |
| + // them here instead of in the browser independant high-level code. |
|
Felix Dahlke
2014/01/18 13:39:19
s/independant/independent/
Sebastian Noack
2014/01/19 10:19:40
Done.
|
| + if (details.tabId == -1) |
| + return; |
| + |
| + var tab = new Tab({id: details.tabId}); |
| + var frames = framesOfTabs.get(tab); |
| + |
| + if (!frames) |
| + { |
| + frames = []; |
| + framesOfTabs.set(tab, frames); |
| + |
| + // assume that the first request belongs to the top frame. Chrome |
| + // may give the top frame the type "object" instead of "main_frame". |
| + // https://code.google.com/p/chromium/issues/detail?id=281711 |
| + if (frameId == 0) |
| + details.type = "main_frame"; |
| + } |
| + |
| + var frameId; |
| + if (details.type == "main_frame" || details.type == "sub_frame") |
| + { |
| + frameId = details.parentFrameId; |
| + frames[details.frameId] = {url: details.url, parent: frameId}; |
| + |
| + // the high-level code isn't interested in top frame requests and |
| + // since those can only be handled in Chrome, we ignore them here |
| + // instead of in the browser independent high-level code. |
| + if (details.type == "main_frame") |
| + return; |
| + } |
| + else |
| + frameId = details.frameId; |
| + |
| + // the high-level code relies on the frame. However in case the frame can't |
| + // be controlled by the extension or the extension was (re)loaded after the |
| + // frame was loaded, the frame is unkown and we have to ignore the request. |
|
Felix Dahlke
2014/01/18 13:39:19
s/unkown/unknown/
Sebastian Noack
2014/01/19 10:19:40
Done.
|
| + if (!(frameId in frames)) |
| + return; |
| + |
| + var frame = new Frame({id: frameId, tab: tab}); |
| + |
| + for (var i = 0; i < ext.webRequest.onBeforeRequest._listeners.length; i++) |
| + { |
| + if (ext.webRequest.onBeforeRequest._listeners[i](details.url, details.type, tab, frame) === false) |
|
Felix Dahlke
2014/01/18 13:39:19
Why not the following?
if (!ext.webRequest.onBefo
Sebastian Noack
2014/01/19 10:19:40
In order to block the request, the handler should
|
| + return {cancel: true}; |
| + } |
| + }, {urls: ["<all_urls>"]}, ["blocking"]); |
| + |
| + |
| /* API */ |
| ext.windows = { |
| @@ -359,6 +503,7 @@ |
| }; |
| ext.tabs = { |
| + onBeforeNavigate: new BeforeNavigateTabEventTarget(), |
| onLoading: new LoadingTabEventTarget(), |
| onCompleted: new CompletedTabEventTarget(), |
| onActivated: new ActivatedTabEventTarget(), |
| @@ -366,7 +511,7 @@ |
| }; |
| ext.webRequest = { |
| - onBeforeRequest: new BeforeRequestEventTarget(), |
| + onBeforeRequest: new SimpleEventTarget(), |
| handlerBehaviorChanged: chrome.webRequest.handlerBehaviorChanged |
| }; |
| @@ -387,4 +532,6 @@ |
| chrome.contextMenus.removeAll(callback); |
| } |
| }; |
| + |
| + ext.onMessage = new BackgroundMessageEventTarget(); |
| })(); |