CLOSED TREE Backed out changeset 6503123e95dd (bug 1139860) Backed out changeset b83bc163064d (bug 1203331) Backed out changeset 2f501bd57cd2 (bug 1202481) Backed out changeset 37e6ac7beb42 (bug 1202486) Backed out changeset f9b6e99e620e (bug 1202483) Backed out changeset 466af9f9baee (bug 1202482) Backed out changeset 6be690e265a2 (bug 1202479) Backed out changeset 57ff88bfccf4 (bug 1197475) Backed out changeset 7e8c04ff6049 (bug 1202478) Backed out changeset 525227997274 (bug 1202501) Backed out changeset da317cdb79d3 (bug 1199473) Backed out changeset 73b8ddd6dac9 (bug 1190662)
487 lines
16 KiB
JavaScript
487 lines
16 KiB
JavaScript
XPCOMUtils.defineLazyModuleGetter(this, "NewTabURL",
|
|
"resource:///modules/NewTabURL.jsm");
|
|
|
|
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
|
|
var {
|
|
EventManager,
|
|
ignoreEvent,
|
|
runSafe,
|
|
} = ExtensionUtils;
|
|
|
|
// This function is pretty tightly tied to Extension.jsm.
|
|
// Its job is to fill in the |tab| property of the sender.
|
|
function getSender(context, target, sender)
|
|
{
|
|
// The message was sent from a content script to a <browser> element.
|
|
// We can just get the |tab| from |target|.
|
|
if (target instanceof Ci.nsIDOMXULElement) {
|
|
// The message came from a content script.
|
|
let tabbrowser = target.ownerDocument.defaultView.gBrowser;
|
|
if (!tabbrowser) {
|
|
return;
|
|
}
|
|
let tab = tabbrowser.getTabForBrowser(target);
|
|
|
|
sender.tab = TabManager.convert(context.extension, tab);
|
|
} else {
|
|
// The message came from an ExtensionPage. In that case, it should
|
|
// include a tabId property (which is filled in by the page-open
|
|
// listener below).
|
|
if ("tabId" in sender) {
|
|
sender.tab = TabManager.convert(context.extension, TabManager.getTab(sender.tabId));
|
|
delete sender.tabId;
|
|
}
|
|
}
|
|
}
|
|
|
|
// WeakMap[ExtensionPage -> {tab, parentWindow}]
|
|
var pageDataMap = new WeakMap();
|
|
|
|
// This listener fires whenever an extension page opens in a tab
|
|
// (either initiated by the extension or the user). Its job is to fill
|
|
// in some tab-specific details and keep data around about the
|
|
// ExtensionPage.
|
|
extensions.on("page-load", (type, page, params, sender, delegate) => {
|
|
if (params.type == "tab") {
|
|
let browser = params.docShell.chromeEventHandler;
|
|
let parentWindow = browser.ownerDocument.defaultView;
|
|
let tab = parentWindow.gBrowser.getTabForBrowser(browser);
|
|
sender.tabId = TabManager.getId(tab);
|
|
|
|
pageDataMap.set(page, {tab, parentWindow});
|
|
}
|
|
|
|
delegate.getSender = getSender;
|
|
});
|
|
|
|
extensions.on("page-unload", (type, page) => {
|
|
pageDataMap.delete(page);
|
|
});
|
|
|
|
extensions.on("page-shutdown", (type, page) => {
|
|
if (pageDataMap.has(page)) {
|
|
let {tab, parentWindow} = pageDataMap.get(page);
|
|
pageDataMap.delete(page);
|
|
|
|
parentWindow.gBrowser.removeTab(tab);
|
|
}
|
|
});
|
|
|
|
extensions.on("fill-browser-data", (type, browser, data, result) => {
|
|
let tabId = TabManager.getBrowserId(browser);
|
|
if (tabId == -1) {
|
|
result.cancel = true;
|
|
return;
|
|
}
|
|
|
|
data.tabId = tabId;
|
|
});
|
|
|
|
// TODO: activeTab permission
|
|
|
|
extensions.registerAPI((extension, context) => {
|
|
let self = {
|
|
tabs: {
|
|
onActivated: new WindowEventManager(context, "tabs.onActivated", "TabSelect", (fire, event) => {
|
|
let tab = event.originalTarget;
|
|
let tabId = TabManager.getId(tab);
|
|
let windowId = WindowManager.getId(tab.ownerDocument.defaultView);
|
|
fire({tabId, windowId});
|
|
}).api(),
|
|
|
|
onCreated: new EventManager(context, "tabs.onCreated", fire => {
|
|
let listener = event => {
|
|
let tab = event.originalTarget;
|
|
fire({tab: TabManager.convert(extension, tab)});
|
|
};
|
|
|
|
let windowListener = window => {
|
|
for (let tab of window.gBrowser.tabs) {
|
|
fire({tab: TabManager.convert(extension, tab)});
|
|
}
|
|
};
|
|
|
|
WindowListManager.addOpenListener(windowListener, false);
|
|
AllWindowEvents.addListener("TabOpen", listener);
|
|
return () => {
|
|
WindowListManager.removeOpenListener(windowListener);
|
|
AllWindowEvents.removeListener("TabOpen", listener);
|
|
};
|
|
}).api(),
|
|
|
|
onUpdated: new EventManager(context, "tabs.onUpdated", fire => {
|
|
function sanitize(extension, changeInfo) {
|
|
let result = {};
|
|
let nonempty = false;
|
|
for (let prop in changeInfo) {
|
|
if ((prop != "favIconUrl" && prop != "url") || extension.hasPermission("tabs")) {
|
|
nonempty = true;
|
|
result[prop] = changeInfo[prop];
|
|
}
|
|
}
|
|
return [nonempty, result];
|
|
}
|
|
|
|
let listener = event => {
|
|
let tab = event.originalTarget;
|
|
let window = tab.ownerDocument.defaultView;
|
|
let tabId = TabManager.getId(tab);
|
|
|
|
let changeInfo = {};
|
|
let needed = false;
|
|
if (event.type == "TabAttrModified") {
|
|
if (event.detail.changed.indexOf("image") != -1) {
|
|
changeInfo.favIconUrl = window.gBrowser.getIcon(tab);
|
|
needed = true;
|
|
}
|
|
} else if (event.type == "TabPinned") {
|
|
changeInfo.pinned = true;
|
|
needed = true;
|
|
} else if (event.type == "TabUnpinned") {
|
|
changeInfo.pinned = false;
|
|
needed = true;
|
|
}
|
|
|
|
[needed, changeInfo] = sanitize(extension, changeInfo);
|
|
if (needed) {
|
|
fire(tabId, changeInfo, TabManager.convert(extension, tab));
|
|
}
|
|
};
|
|
let progressListener = {
|
|
onStateChange(browser, webProgress, request, stateFlags, statusCode) {
|
|
if (!webProgress.isTopLevel) {
|
|
return;
|
|
}
|
|
|
|
let status;
|
|
if (stateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) {
|
|
if (stateFlags & Ci.nsIWebProgressListener.STATE_START) {
|
|
status = "loading";
|
|
} else if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
|
|
status = "complete";
|
|
}
|
|
} else if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
|
|
statusCode == Cr.NS_BINDING_ABORTED) {
|
|
status = "complete";
|
|
}
|
|
|
|
let gBrowser = browser.ownerDocument.defaultView.gBrowser;
|
|
let tab = gBrowser.getTabForBrowser(browser);
|
|
let tabId = TabManager.getId(tab);
|
|
let [needed, changeInfo] = sanitize(extension, {status});
|
|
fire(tabId, changeInfo, TabManager.convert(extension, tab));
|
|
},
|
|
|
|
onLocationChange(browser, webProgress, request, locationURI, flags) {
|
|
let gBrowser = browser.ownerDocument.defaultView.gBrowser;
|
|
let tab = gBrowser.getTabForBrowser(browser);
|
|
let tabId = TabManager.getId(tab);
|
|
let [needed, changeInfo] = sanitize(extension, {url: locationURI.spec});
|
|
if (needed) {
|
|
fire(tabId, changeInfo, TabManager.convert(extension, tab));
|
|
}
|
|
},
|
|
};
|
|
|
|
AllWindowEvents.addListener("progress", progressListener);
|
|
AllWindowEvents.addListener("TabAttrModified", listener);
|
|
AllWindowEvents.addListener("TabPinned", listener);
|
|
AllWindowEvents.addListener("TabUnpinned", listener);
|
|
return () => {
|
|
AllWindowEvents.removeListener("progress", progressListener);
|
|
AllWindowEvents.addListener("TabAttrModified", listener);
|
|
AllWindowEvents.addListener("TabPinned", listener);
|
|
AllWindowEvents.addListener("TabUnpinned", listener);
|
|
};
|
|
}).api(),
|
|
|
|
onReplaced: ignoreEvent(),
|
|
|
|
onRemoved: new EventManager(context, "tabs.onRemoved", fire => {
|
|
let tabListener = event => {
|
|
let tab = event.originalTarget;
|
|
let tabId = TabManager.getId(tab);
|
|
let windowId = WindowManager.getId(tab.ownerDocument.defaultView);
|
|
let removeInfo = {windowId, isWindowClosing: false};
|
|
fire(tabId, removeInfo);
|
|
};
|
|
|
|
let windowListener = window => {
|
|
for (let tab of window.gBrowser.tabs) {
|
|
let tabId = TabManager.getId(tab);
|
|
let windowId = WindowManager.getId(window);
|
|
let removeInfo = {windowId, isWindowClosing: true};
|
|
fire(tabId, removeInfo);
|
|
}
|
|
};
|
|
|
|
WindowListManager.addCloseListener(windowListener);
|
|
AllWindowEvents.addListener("TabClose", tabListener);
|
|
return () => {
|
|
WindowListManager.removeCloseListener(windowListener);
|
|
AllWindowEvents.removeListener("TabClose", tabListener);
|
|
};
|
|
}).api(),
|
|
|
|
create: function(createProperties, callback) {
|
|
if (!createProperties) {
|
|
createProperties = {};
|
|
}
|
|
|
|
let url = createProperties.url || NewTabURL.get();
|
|
url = extension.baseURI.resolve(url);
|
|
|
|
function createInWindow(window) {
|
|
let tab = window.gBrowser.addTab(url);
|
|
|
|
let active = true;
|
|
if ("active" in createProperties) {
|
|
active = createProperties.active;
|
|
} else if ("selected" in createProperties) {
|
|
active = createProperties.selected;
|
|
}
|
|
if (active) {
|
|
window.gBrowser.selectedTab = tab;
|
|
}
|
|
|
|
if ("index" in createProperties) {
|
|
window.gBrowser.moveTabTo(tab, createProperties.index);
|
|
}
|
|
|
|
if (createProperties.pinned) {
|
|
window.gBrowser.pinTab(tab);
|
|
}
|
|
|
|
if (callback) {
|
|
runSafe(context, callback, TabManager.convert(extension, tab));
|
|
}
|
|
}
|
|
|
|
let window = createProperties.windowId ?
|
|
WindowManager.getWindow(createProperties.windowId) :
|
|
WindowManager.topWindow;
|
|
if (!window.gBrowser) {
|
|
let obs = (finishedWindow, topic, data) => {
|
|
if (finishedWindow != window) {
|
|
return;
|
|
}
|
|
Services.obs.removeObserver(obs, "browser-delayed-startup-finished");
|
|
createInWindow(window);
|
|
};
|
|
Services.obs.addObserver(obs, "browser-delayed-startup-finished", false);
|
|
} else {
|
|
createInWindow(window);
|
|
}
|
|
},
|
|
|
|
remove: function(tabs, callback) {
|
|
if (!Array.isArray(tabs)) {
|
|
tabs = [tabs];
|
|
}
|
|
|
|
for (let tabId of tabs) {
|
|
let tab = TabManager.getTab(tabId);
|
|
tab.ownerDocument.defaultView.gBrowser.removeTab(tab);
|
|
}
|
|
|
|
if (callback) {
|
|
runSafe(context, callback);
|
|
}
|
|
},
|
|
|
|
update: function(...args) {
|
|
let tabId, updateProperties, callback;
|
|
if (args.length == 1) {
|
|
updateProperties = args[0];
|
|
} else {
|
|
[tabId, updateProperties, callback] = args;
|
|
}
|
|
|
|
let tab = tabId ? TabManager.getTab(tabId) : TabManager.activeTab;
|
|
let tabbrowser = tab.ownerDocument.defaultView.gBrowser;
|
|
if ("url" in updateProperties) {
|
|
tab.linkedBrowser.loadURI(updateProperties.url);
|
|
}
|
|
if ("active" in updateProperties) {
|
|
if (updateProperties.active) {
|
|
tabbrowser.selectedTab = tab;
|
|
} else {
|
|
// Not sure what to do here? Which tab should we select?
|
|
}
|
|
}
|
|
if ("pinned" in updateProperties) {
|
|
if (updateProperties.pinned) {
|
|
tabbrowser.pinTab(tab);
|
|
} else {
|
|
tabbrowser.unpinTab(tab);
|
|
}
|
|
}
|
|
// FIXME: highlighted/selected, openerTabId
|
|
|
|
if (callback) {
|
|
runSafe(context, callback, TabManager.convert(extension, tab));
|
|
}
|
|
},
|
|
|
|
reload: function(tabId, reloadProperties, callback) {
|
|
let tab = tabId ? TabManager.getTab(tabId) : TabManager.activeTab;
|
|
let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
|
|
if (reloadProperties && reloadProperties.bypassCache) {
|
|
flags |= Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
|
|
}
|
|
tab.linkedBrowser.reloadWithFlags(flags);
|
|
|
|
if (callback) {
|
|
runSafe(context, callback);
|
|
}
|
|
},
|
|
|
|
get: function(tabId, callback) {
|
|
let tab = TabManager.getTab(tabId);
|
|
runSafe(context, callback, TabManager.convert(extension, tab));
|
|
},
|
|
|
|
getAllInWindow: function(...args) {
|
|
let window, callback;
|
|
if (args.length == 1) {
|
|
callbacks = args[0];
|
|
} else {
|
|
window = WindowManager.getWindow(args[0]);
|
|
callback = args[1];
|
|
}
|
|
|
|
if (!window) {
|
|
window = WindowManager.topWindow;
|
|
}
|
|
|
|
return self.tabs.query({windowId: WindowManager.getId(window)}, callback);
|
|
},
|
|
|
|
query: function(queryInfo, callback) {
|
|
if (!queryInfo) {
|
|
queryInfo = {};
|
|
}
|
|
|
|
function matches(window, tab) {
|
|
let props = ["active", "pinned", "highlighted", "status", "title", "url", "index"];
|
|
for (let prop of props) {
|
|
if (prop in queryInfo && queryInfo[prop] != tab[prop]) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
let lastFocused = window == WindowManager.topWindow;
|
|
if ("lastFocusedWindow" in queryInfo && queryInfo.lastFocusedWindow != lastFocused) {
|
|
return false;
|
|
}
|
|
|
|
let windowType = WindowManager.windowType(window);
|
|
if ("windowType" in queryInfo && queryInfo.windowType != windowType) {
|
|
return false;
|
|
}
|
|
|
|
if ("windowId" in queryInfo) {
|
|
if (queryInfo.windowId == WindowManager.WINDOW_ID_CURRENT) {
|
|
if (context.contentWindow != window) {
|
|
return false;
|
|
}
|
|
} else {
|
|
if (queryInfo.windowId != tab.windowId) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ("currentWindow" in queryInfo) {
|
|
let eq = window == context.contentWindow;
|
|
if (queryInfo.currentWindow != eq) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
let result = [];
|
|
let e = Services.wm.getEnumerator("navigator:browser");
|
|
while (e.hasMoreElements()) {
|
|
let window = e.getNext();
|
|
let tabs = TabManager.getTabs(extension, window);
|
|
for (let tab of tabs) {
|
|
if (matches(window, tab)) {
|
|
result.push(tab);
|
|
}
|
|
}
|
|
}
|
|
runSafe(context, callback, result);
|
|
},
|
|
|
|
_execute: function(tabId, details, kind, callback) {
|
|
let tab = tabId ? TabManager.getTab(tabId) : TabManager.activeTab;
|
|
let mm = tab.linkedBrowser.messageManager;
|
|
|
|
let options = {js: [], css: []};
|
|
if (details.code) {
|
|
options[kind + 'Code'] = details.code;
|
|
}
|
|
if (details.file) {
|
|
options[kind].push(extension.baseURI.resolve(details.file));
|
|
}
|
|
if (details.allFrames) {
|
|
options.all_frames = details.allFrames;
|
|
}
|
|
if (details.matchAboutBlank) {
|
|
options.match_about_blank = details.matchAboutBlank;
|
|
}
|
|
if (details.runAt) {
|
|
options.run_at = details.runAt;
|
|
}
|
|
mm.sendAsyncMessage("Extension:Execute",
|
|
{extensionId: extension.id, options});
|
|
|
|
// TODO: Call the callback with the result (which is what???).
|
|
},
|
|
|
|
executeScript: function(...args) {
|
|
if (args.length == 1) {
|
|
self.tabs._execute(undefined, args[0], 'js', undefined);
|
|
} else {
|
|
self.tabs._execute(args[0], args[1], 'js', args[2]);
|
|
}
|
|
},
|
|
|
|
insertCss: function(tabId, details, callback) {
|
|
if (args.length == 1) {
|
|
self.tabs._execute(undefined, args[0], 'css', undefined);
|
|
} else {
|
|
self.tabs._execute(args[0], args[1], 'css', args[2]);
|
|
}
|
|
},
|
|
|
|
connect: function(tabId, connectInfo) {
|
|
let tab = TabManager.getTab(tabId);
|
|
let mm = tab.linkedBrowser.messageManager;
|
|
|
|
let name = connectInfo.name || "";
|
|
let recipient = {extensionId: extension.id};
|
|
if ("frameId" in connectInfo) {
|
|
recipient.frameId = connectInfo.frameId;
|
|
}
|
|
return context.messenger.connect(mm, name, recipient);
|
|
},
|
|
|
|
sendMessage: function(tabId, message, options, responseCallback) {
|
|
let tab = TabManager.getTab(tabId);
|
|
let mm = tab.linkedBrowser.messageManager;
|
|
|
|
let recipient = {extensionId: extension.id};
|
|
if (options && "frameId" in options) {
|
|
recipient.frameId = options.frameId;
|
|
}
|
|
return context.messenger.sendMessage(mm, message, recipient, responseCallback);
|
|
},
|
|
},
|
|
};
|
|
return self;
|
|
});
|