Bug 1190688: Part 1 - [webext] Implement the activeTab permission. r=billm

This commit is contained in:
Kris Maglione 2015-12-01 20:37:41 -08:00
parent ee06b6660a
commit 294c025113
5 changed files with 129 additions and 56 deletions

View File

@ -32,6 +32,8 @@ function BrowserAction(options, extension)
this.id = makeWidgetId(extension.id) + "-browser-action";
this.widget = null;
this.tabManager = TabManager.for(extension);
let title = extension.localize(options.default_title || "");
let popup = extension.localize(options.default_popup || "");
if (popup) {
@ -74,6 +76,7 @@ BrowserAction.prototype = {
node.addEventListener("command", event => {
let tab = tabbrowser.selectedTab;
let popup = this.getProperty(tab, "popup");
this.tabManager.addActiveTabPermission(tab);
if (popup) {
this.togglePopup(node, popup);
} else {

View File

@ -43,8 +43,11 @@ var menuBuilder = {
for (let [ext, menuItemMap] of contextMenuMap) {
let parentMap = new Map();
let topLevelItems = new Set();
for (let [id, item] of menuItemMap) {
dump(id + " : " + item + "\n");
for (let entry of menuItemMap) {
// We need a closure over |item|, and we don't currently get a
// fresh binding per loop if we declare it in the loop head.
let [id, item] = entry;
if (item.enabledForContext(contextData)) {
let element;
if (item.isMenu) {
@ -79,15 +82,13 @@ var menuBuilder = {
topLevelItems.add(element);
}
if (item.onclick) {
function clickHandlerForItem(item) {
return event => {
let clickData = item.getClickData(contextData, event);
runSafe(item.extContext, item.onclick, clickData);
}
element.addEventListener("command", event => {
item.tabManager.addActiveTabPermission();
if (item.onclick) {
let clickData = item.getClickData(contextData, event);
runSafe(item.extContext, item.onclick, clickData);
}
element.addEventListener("command", clickHandlerForItem(item));
}
});
}
}
if (topLevelItems.size > 1) {
@ -166,6 +167,8 @@ function MenuItem(extension, extContext, createProperties)
this.extension = extension;
this.extContext = extContext;
this.tabManager = TabManager.for(extension);
this.init(createProperties);
}

View File

@ -20,6 +20,8 @@ function PageAction(options, extension)
this.extension = extension;
this.id = makeWidgetId(extension.id) + "-page-action";
this.tabManager = TabManager.for(extension);
let title = extension.localize(options.default_title || "");
let popup = extension.localize(options.default_popup || "");
if (popup) {
@ -139,6 +141,8 @@ PageAction.prototype = {
let tab = window.gBrowser.selectedTab;
let popup = this.tabContext.get(tab).popup;
this.tabManager.addActiveTabPermission(tab);
if (popup) {
openPanel(this.getButton(window), popup, this.extension);
} else {

View File

@ -457,13 +457,8 @@ extensions.registerAPI((extension, context) => {
}
let result = [];
let e = Services.wm.getEnumerator("navigator:browser");
while (e.hasMoreElements()) {
let window = e.getNext();
if (window.document.readyState != "complete") {
continue;
}
let tabs = TabManager.getTabs(extension, window);
for (let window of WindowListManager.browserWindows()) {
let tabs = TabManager.for(extension).getTabs(window);
for (let tab of tabs) {
if (matches(window, tab)) {
result.push(tab);
@ -489,10 +484,16 @@ extensions.registerAPI((extension, context) => {
// window IDs and insufficient permissions need to result in a
// callback with |lastError| set.
innerWindowID: tab.linkedBrowser.innerWindowID,
matchesHost: extension.whiteListedHosts.serialize(),
};
if (TabManager.for(extension).hasActiveTabPermission(tab)) {
// If we have the "activeTab" permission for this tab, ignore
// the host whitelist.
options.matchesHost = ["<all_urls>"];
} else {
options.matchesHost = extension.whiteListedHosts.serialize();
}
if (details.code) {
options[kind + 'Code'] = details.code;
}

View File

@ -267,7 +267,87 @@ TabContext.prototype = {
},
};
// Manages mapping between XUL tabs and extension tab IDs.
// Manages tab mappings and permissions for a specific extension.
function ExtensionTabManager(extension) {
this.extension = extension;
// A mapping of tab objects to the inner window ID the extension currently has
// the active tab permission for. The active permission for a given tab is
// valid only for the inner window that was active when the permission was
// granted. If the tab navigates, the inner window ID changes, and the
// permission automatically becomes stale.
//
// WeakMap[tab => inner-window-id<int>]
this.hasTabPermissionFor = new WeakMap();
}
ExtensionTabManager.prototype = {
addActiveTabPermission(tab = TabManager.activeTab) {
if (this.extension.hasPermission("activeTab")) {
// Note that, unlike Chrome, we don't currently clear this permission with
// the tab navigates. If the inner window is revived from BFCache before
// we've granted this permission to a new inner window, the extension
// maintains its permissions for it.
this.hasTabPermissionFor.set(tab, tab.linkedBrowser.innerWindowID);
}
},
// Returns true if the extension has the "activeTab" permission for this tab.
// This is somewhat more permissive than the generic "tabs" permission, as
// checked by |hasTabPermission|, in that it also allows programmatic script
// injection without an explicit host permission.
hasActiveTabPermission(tab) {
// This check is redundant with addTabPermission, but cheap.
if (this.extension.hasPermission("activeTab")) {
return (this.hasTabPermissionFor.has(tab) &&
this.hasTabPermissionFor.get(tab) === tab.linkedBrowser.innerWindowID);
}
return false;
},
hasTabPermission(tab) {
return this.extension.hasPermission("tabs") || this.hasActiveTabPermission(tab);
},
convert(tab) {
let window = tab.ownerDocument.defaultView;
let windowActive = window == WindowManager.topWindow;
let result = {
id: TabManager.getId(tab),
index: tab._tPos,
windowId: WindowManager.getId(window),
selected: tab.selected,
highlighted: tab.selected,
active: tab.selected,
pinned: tab.pinned,
status: TabManager.getStatus(tab),
incognito: PrivateBrowsingUtils.isBrowserPrivate(tab.linkedBrowser),
width: tab.linkedBrowser.clientWidth,
height: tab.linkedBrowser.clientHeight,
};
if (this.hasTabPermission(tab)) {
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(window) {
return Array.from(window.gBrowser.tabs, tab => this.convert(tab));
},
};
// Manages global mappings between XUL tabs and extension tab IDs.
global.TabManager = {
_tabs: new WeakMap(),
_nextId: 1,
@ -322,44 +402,26 @@ global.TabManager = {
},
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 Array.map(window.gBrowser.tabs, tab => this.convert(extension, tab));
return TabManager.for(extension).convert(tab);
},
};
// WeakMap[Extension -> ExtensionTabManager]
let tabManagers = new WeakMap();
// Returns the extension-specific tab manager for the given extension, or
// creates one if it doesn't already exist.
TabManager.for = function (extension) {
if (!tabManagers.has(extension)) {
tabManagers.set(extension, new ExtensionTabManager(extension));
}
return tabManagers.get(extension);
};
extensions.on("shutdown", (type, extension) => {
tabManagers.delete(extension);
});
// Manages mapping between XUL windows and extension window IDs.
global.WindowManager = {
_windows: new WeakMap(),
@ -411,7 +473,7 @@ global.WindowManager = {
};
if (getInfo && getInfo.populate) {
results.tabs = TabManager.getTabs(extension, window);
results.tabs = TabManager.for(extension).getTabs(window);
}
return result;