Backed out 2 changesets (bug 1190688) for browser_ext_tabs_executeScript.js permatimeouts

Backed out changeset 1d5e9f3d094d (bug 1190688)
Backed out changeset 4a10c564dfca (bug 1190688)
This commit is contained in:
Wes Kocher 2015-12-02 11:22:31 -08:00
parent cb5848480e
commit 1477d0b289
7 changed files with 66 additions and 366 deletions

View File

@ -32,8 +32,6 @@ 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) {
@ -76,7 +74,6 @@ 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,11 +43,8 @@ var menuBuilder = {
for (let [ext, menuItemMap] of contextMenuMap) {
let parentMap = new Map();
let topLevelItems = new Set();
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;
for (let [id, item] of menuItemMap) {
dump(id + " : " + item + "\n");
if (item.enabledForContext(contextData)) {
let element;
if (item.isMenu) {
@ -82,13 +79,15 @@ var menuBuilder = {
topLevelItems.add(element);
}
element.addEventListener("command", event => {
item.tabManager.addActiveTabPermission();
if (item.onclick) {
let clickData = item.getClickData(contextData, event);
runSafe(item.extContext, item.onclick, clickData);
if (item.onclick) {
function clickHandlerForItem(item) {
return event => {
let clickData = item.getClickData(contextData, event);
runSafe(item.extContext, item.onclick, clickData);
}
}
});
element.addEventListener("command", clickHandlerForItem(item));
}
}
}
if (topLevelItems.size > 1) {
@ -167,8 +166,6 @@ function MenuItem(extension, extContext, createProperties)
this.extension = extension;
this.extContext = extContext;
this.tabManager = TabManager.for(extension);
this.init(createProperties);
}

View File

@ -20,8 +20,6 @@ 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) {
@ -141,8 +139,6 @@ 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,8 +457,13 @@ extensions.registerAPI((extension, context) => {
}
let result = [];
for (let window of WindowListManager.browserWindows()) {
let tabs = TabManager.for(extension).getTabs(window);
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 tab of tabs) {
if (matches(window, tab)) {
result.push(tab);
@ -484,15 +489,9 @@ extensions.registerAPI((extension, context) => {
// window IDs and insufficient permissions need to result in a
// callback with |lastError| set.
innerWindowID: tab.linkedBrowser.innerWindowID,
};
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();
}
matchesHost: extension.whiteListedHosts.serialize(),
};
if (details.code) {
options[kind + 'Code'] = details.code;

View File

@ -267,87 +267,7 @@ TabContext.prototype = {
},
};
// 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.
// Manages mapping between XUL tabs and extension tab IDs.
global.TabManager = {
_tabs: new WeakMap(),
_nextId: 1,
@ -402,26 +322,44 @@ global.TabManager = {
},
convert(extension, tab) {
return TabManager.for(extension).convert(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));
},
};
// 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(),
@ -473,7 +411,7 @@ global.WindowManager = {
};
if (getInfo && getInfo.populate) {
results.tabs = TabManager.for(extension).getTabs(window);
results.tabs = TabManager.getTabs(extension, window);
}
return result;

View File

@ -1,31 +1,21 @@
"use strict";
function* testHasPermission(params) {
let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://mochi.test:8888/", true);
let contentSetup = params.contentSetup || (() => Promise.resolve());
add_task(function* () {
let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://mochi.test:8888/");
let extension = ExtensionTestUtils.loadExtension({
manifest: params.manifest,
manifest: {
"permissions": ["tabs"]
},
background: `(${function(contentSetup) {
background: function() {
browser.runtime.onMessage.addListener((msg, sender) => {
browser.test.assertEq(msg, "script ran", "script ran");
browser.test.notifyPass("executeScript");
});
browser.test.onMessage.addListener(msg => {
browser.test.assertEq(msg, "execute-script");
browser.tabs.executeScript({
file: "script.js"
});
browser.tabs.executeScript({
file: "script.js"
});
contentSetup().then(() => {
browser.test.sendMessage("ready");
});
}})(${contentSetup})`,
},
files: {
"script.js": function() {
@ -35,225 +25,8 @@ function* testHasPermission(params) {
});
yield extension.startup();
yield extension.awaitMessage("ready");
if (params.setup) {
yield params.setup(extension);
}
extension.sendMessage("execute-script");
yield extension.awaitFinish("executeScript");
yield extension.unload();
yield BrowserTestUtils.removeTab(tab);
}
// This is a pretty terrible hack, but it's the best we can do until we
// support |executeScript| callbacks and |lastError|.
function* testHasNoPermission(params) {
let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
let tab2 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://mochi.test:8888/");
let contentSetup = params.contentSetup || (() => Promise.resolve());
let extension = ExtensionTestUtils.loadExtension({
manifest: params.manifest,
background: `(${function(contentSetup) {
browser.runtime.onMessage.addListener((msg, sender) => {
browser.test.assertEq(msg, "second script ran", "second script ran");
browser.test.notifyPass("executeScript");
});
browser.test.onMessage.addListener(msg => {
browser.test.assertEq(msg, "execute-script");
browser.tabs.query({ activeWindow: true }, tabs => {
browser.tabs.executeScript({
file: "script.js"
});
// Execute a script we know we have permissions for in the
// second tab, in the hopes that it will execute after the
// first one. This has intermittent failure written all over
// it, but it's just about the best we can do until we
// support callbacks for executeScript.
browser.tabs.executeScript(tabs[1].id, {
file: "second-script.js"
});
});
});
contentSetup().then(() => {
browser.test.sendMessage("ready");
});
}})(${contentSetup})`,
files: {
"script.js": function() {
browser.runtime.sendMessage("first script ran");
},
"second-script.js": function() {
browser.runtime.sendMessage("second script ran");
}
}
});
yield extension.startup();
yield extension.awaitMessage("ready");
if (params.setup) {
yield params.setup(extension);
}
extension.sendMessage("execute-script");
yield extension.awaitFinish("executeScript");
yield extension.unload();
yield BrowserTestUtils.removeTab(tab2);
yield BrowserTestUtils.removeTab(tab1);
}
add_task(function* testGoodPermissions() {
info("Test explicit host permission");
yield testHasPermission({
manifest: { "permissions": ["http://mochi.test/"] }
});
info("Test explicit host subdomain permission");
yield testHasPermission({
manifest: { "permissions": ["http://*.mochi.test/"] }
});
info("Test explicit <all_urls> permission");
yield testHasPermission({
manifest: { "permissions": ["<all_urls>"] }
});
info("Test activeTab permission with a browser action click");
yield testHasPermission({
manifest: {
"permissions": ["activeTab"],
"browser_action": {},
},
setup: clickBrowserAction,
});
info("Test activeTab permission with a page action click");
yield testHasPermission({
manifest: {
"permissions": ["activeTab"],
"page_action": {},
},
contentSetup() {
return new Promise(resolve => {
browser.tabs.query({ active: true, currentWindow: true }, tabs => {
browser.pageAction.show(tabs[0].id);
resolve();
});
});
},
setup: clickPageAction,
});
info("Test activeTab permission with a browser action w/popup click");
yield testHasPermission({
manifest: {
"permissions": ["activeTab"],
"browser_action": { "default_popup": "_blank.html" },
},
setup: clickBrowserAction,
});
info("Test activeTab permission with a page action w/popup click");
yield testHasPermission({
manifest: {
"permissions": ["activeTab"],
"page_action": { "default_popup": "_blank.html" },
},
contentSetup() {
return new Promise(resolve => {
browser.tabs.query({ active: true, currentWindow: true }, tabs => {
browser.pageAction.show(tabs[0].id);
resolve();
});
});
},
setup: clickPageAction,
});
info("Test activeTab permission with a context menu click");
yield testHasPermission({
manifest: {
"permissions": ["activeTab", "contextMenus"],
},
contentSetup() {
browser.contextMenus.create({ title: "activeTab", contexts: ["all"] });
return Promise.resolve();
},
setup: function* (extension) {
info("setup");
let contextMenu = document.getElementById("contentAreaContextMenu");
let awaitPopupShown = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
let awaitPopupHidden = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden");
yield BrowserTestUtils.synthesizeMouseAtCenter("a[href]", { type: "contextmenu", button: 2 },
gBrowser.selectedBrowser);
info("clicked");
yield awaitPopupShown;
info("shown");
let item = contextMenu.querySelector("[label=activeTab]");
yield EventUtils.synthesizeMouseAtCenter(item, {}, window);
info("re-clicked");
yield awaitPopupHidden;
info("hidden");
},
});
});
add_task(function* testBadPermissions() {
info("Test no special permissions");
yield testHasNoPermission({
manifest: { "permissions": ["http://example.com/"] }
});
info("Test tabs permissions");
yield testHasNoPermission({
manifest: { "permissions": ["http://example.com/", "tabs"] }
});
info("Test active tab, browser action, no click");
yield testHasNoPermission({
manifest: {
"permissions": ["http://example.com/", "activeTab"],
"browser_action": {},
},
});
info("Test active tab, page action, no click");
yield testHasNoPermission({
manifest: {
"permissions": ["http://example.com/", "activeTab"],
"page_action": {},
},
contentSetup() {
return new Promise(resolve => {
browser.tabs.query({ active: true, currentWindow: true }, tabs => {
browser.pageAction.show(tabs[0].id);
resolve();
});
});
}
});
});
// TODO: Test that |executeScript| fails if the tab has navigated to a
// new page, and no longer matches our expected state. This involves
// intentionally trying to trigger a race condition, and is probably not
// even worth attempting until we have proper |executeScript| callbacks.

View File

@ -44,6 +44,6 @@ function clickPageAction(extension, win = window) {
let pageActionId = makeWidgetId(extension.id) + "-page-action";
let elem = win.document.getElementById(pageActionId);
EventUtils.synthesizeMouseAtCenter(elem, {}, win);
EventUtils.synthesizeMouse(elem, 8, 8, {}, win);
return new Promise(SimpleTest.executeSoon);
}