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,118 @@ |
}; |
+ /* 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 || frame.parent == -1) |
+ return null; |
+ |
+ return new Frame({id: frame.parent, tab: this._tab}); |
+ } |
+ } |
+ }; |
+ |
+ |
+ /* 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 independent high-level code. |
+ 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 unknown and we have to ignore the request. |
+ 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) |
+ return {cancel: true}; |
+ } |
+ }, {urls: ["<all_urls>"]}, ["blocking"]); |
+ |
+ |
/* API */ |
ext.windows = { |
@@ -359,6 +502,7 @@ |
}; |
ext.tabs = { |
+ onBeforeNavigate: new BeforeNavigateTabEventTarget(), |
onLoading: new LoadingTabEventTarget(), |
onCompleted: new CompletedTabEventTarget(), |
onActivated: new ActivatedTabEventTarget(), |
@@ -366,7 +510,7 @@ |
}; |
ext.webRequest = { |
- onBeforeRequest: new BeforeRequestEventTarget(), |
+ onBeforeRequest: new SimpleEventTarget(), |
handlerBehaviorChanged: chrome.webRequest.handlerBehaviorChanged |
}; |
@@ -418,4 +562,6 @@ |
isContextMenuHidden = true; |
} |
}; |
+ |
+ ext.onMessage = new BackgroundMessageEventTarget(); |
})(); |