зеркало из https://github.com/mozilla/gecko-dev.git
Merge autoland to mozilla-central. a=merge
This commit is contained in:
Коммит
92918fa21e
|
@ -158,7 +158,7 @@ ifdef MOZ_ARTIFACT_BUILDS
|
|||
$(call BUILDSTATUS,TIER_FINISH artifact)
|
||||
endif
|
||||
$(call BUILDSTATUS,TIER_START tup)
|
||||
@$(TUP) $(if $(MOZ_AUTOMATION),--quiet) $(if $(findstring s,$(filter-out --%,$(MAKEFLAGS))),,--verbose)
|
||||
@$(TUP) $(if $(MOZ_AUTOMATION),--quiet) $(if $(findstring s,$(filter-out --%,$(MAKEFLAGS))),,--verbose) $(topobjdir)
|
||||
$(call BUILDSTATUS,TIER_FINISH tup)
|
||||
|
||||
.PHONY: $(addprefix install-,$(install_manifests))
|
||||
|
|
|
@ -129,6 +129,7 @@ class BasePopup {
|
|||
}
|
||||
if (panel && panel.id !== REMOTE_PANEL_ID) {
|
||||
panel.style.removeProperty("--arrowpanel-background");
|
||||
panel.style.removeProperty("--arrowpanel-border-color");
|
||||
panel.removeAttribute("remote");
|
||||
}
|
||||
|
||||
|
@ -355,9 +356,19 @@ class BasePopup {
|
|||
this.browser.dispatchEvent(event);
|
||||
}
|
||||
|
||||
setBackground(background = "") {
|
||||
if (background) {
|
||||
this.panel.style.setProperty("--arrowpanel-background", background);
|
||||
setBackground(background) {
|
||||
// Panels inherit the applied theme (light, dark, etc) and there is a high
|
||||
// likelihood that most extension authors will not have tested with a dark theme.
|
||||
// If they have not set a background-color, we force it to white to ensure visibility
|
||||
// of the extension content. Passing `null` should be treated the same as no argument,
|
||||
// which is why we can't use default parameters here.
|
||||
if (!background) {
|
||||
background = "#fff";
|
||||
}
|
||||
this.panel.style.setProperty("--arrowpanel-background", background);
|
||||
if (background == "#fff") {
|
||||
// Set a usable default color that work with the default background-color.
|
||||
this.panel.style.setProperty("--arrowpanel-border-color", "hsla(210,4%,10%,.05)");
|
||||
}
|
||||
this.background = background;
|
||||
}
|
||||
|
|
|
@ -33,21 +33,25 @@ this.devtools_network = class extends ExtensionAPI {
|
|||
return {
|
||||
devtools: {
|
||||
network: {
|
||||
onRequestFinished: new EventManager(context, "devtools.network.onRequestFinished", fire => {
|
||||
let onFinished = (data) => {
|
||||
const loader = new ChildNetworkResponseLoader(context, data.requestId);
|
||||
const harEntry = {...data.harEntry, ...loader.api()};
|
||||
const result = Cu.cloneInto(harEntry, context.cloneScope, {
|
||||
cloneFunctions: true,
|
||||
});
|
||||
fire.asyncWithoutClone(result);
|
||||
};
|
||||
onRequestFinished: new EventManager({
|
||||
context,
|
||||
name: "devtools.network.onRequestFinished",
|
||||
register: fire => {
|
||||
let onFinished = (data) => {
|
||||
const loader = new ChildNetworkResponseLoader(context, data.requestId);
|
||||
const harEntry = {...data.harEntry, ...loader.api()};
|
||||
const result = Cu.cloneInto(harEntry, context.cloneScope, {
|
||||
cloneFunctions: true,
|
||||
});
|
||||
fire.asyncWithoutClone(result);
|
||||
};
|
||||
|
||||
let parent = context.childManager.getParentEvent("devtools.network.onRequestFinished");
|
||||
parent.addListener(onFinished);
|
||||
return () => {
|
||||
parent.removeListener(onFinished);
|
||||
};
|
||||
let parent = context.childManager.getParentEvent("devtools.network.onRequestFinished");
|
||||
parent.addListener(onFinished);
|
||||
return () => {
|
||||
parent.removeListener(onFinished);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
},
|
||||
},
|
||||
|
|
|
@ -94,8 +94,10 @@ class ChildDevToolsPanel extends ExtensionUtils.EventEmitter {
|
|||
|
||||
api() {
|
||||
return {
|
||||
onShown: new EventManager(
|
||||
this.context, "devtoolsPanel.onShown", fire => {
|
||||
onShown: new EventManager({
|
||||
context: this.context,
|
||||
name: "devtoolsPanel.onShown",
|
||||
register: fire => {
|
||||
const listener = (eventName, panelContentWindow) => {
|
||||
fire.asyncWithoutClone(panelContentWindow);
|
||||
};
|
||||
|
@ -103,10 +105,13 @@ class ChildDevToolsPanel extends ExtensionUtils.EventEmitter {
|
|||
return () => {
|
||||
this.off("shown", listener);
|
||||
};
|
||||
}).api(),
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onHidden: new EventManager(
|
||||
this.context, "devtoolsPanel.onHidden", fire => {
|
||||
onHidden: new EventManager({
|
||||
context: this.context,
|
||||
name: "devtoolsPanel.onHidden",
|
||||
register: fire => {
|
||||
const listener = () => {
|
||||
fire.async();
|
||||
};
|
||||
|
@ -114,7 +119,8 @@ class ChildDevToolsPanel extends ExtensionUtils.EventEmitter {
|
|||
return () => {
|
||||
this.off("hidden", listener);
|
||||
};
|
||||
}).api(),
|
||||
},
|
||||
}).api(),
|
||||
|
||||
// TODO(rpl): onSearch event and createStatusBarButton method
|
||||
};
|
||||
|
@ -189,8 +195,10 @@ class ChildDevToolsInspectorSidebar extends ExtensionUtils.EventEmitter {
|
|||
const {context, id} = this;
|
||||
|
||||
return {
|
||||
onShown: new EventManager(
|
||||
context, "devtoolsInspectorSidebar.onShown", fire => {
|
||||
onShown: new EventManager({
|
||||
context,
|
||||
name: "devtoolsInspectorSidebar.onShown",
|
||||
register: fire => {
|
||||
const listener = (eventName, panelContentWindow) => {
|
||||
fire.asyncWithoutClone(panelContentWindow);
|
||||
};
|
||||
|
@ -198,10 +206,13 @@ class ChildDevToolsInspectorSidebar extends ExtensionUtils.EventEmitter {
|
|||
return () => {
|
||||
this.off("shown", listener);
|
||||
};
|
||||
}).api(),
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onHidden: new EventManager(
|
||||
context, "devtoolsInspectorSidebar.onHidden", fire => {
|
||||
onHidden: new EventManager({
|
||||
context,
|
||||
name: "devtoolsInspectorSidebar.onHidden",
|
||||
register: fire => {
|
||||
const listener = () => {
|
||||
fire.async();
|
||||
};
|
||||
|
@ -209,7 +220,8 @@ class ChildDevToolsInspectorSidebar extends ExtensionUtils.EventEmitter {
|
|||
return () => {
|
||||
this.off("hidden", listener);
|
||||
};
|
||||
}).api(),
|
||||
},
|
||||
}).api(),
|
||||
|
||||
setObject(jsonObject, rootTitle) {
|
||||
return context.cloneScope.Promise.resolve().then(() => {
|
||||
|
@ -279,8 +291,10 @@ this.devtools_panels = class extends ExtensionAPI {
|
|||
get themeName() {
|
||||
return themeChangeObserver.themeName;
|
||||
},
|
||||
onThemeChanged: new EventManager(
|
||||
context, "devtools.panels.onThemeChanged", fire => {
|
||||
onThemeChanged: new EventManager({
|
||||
context,
|
||||
name: "devtools.panels.onThemeChanged",
|
||||
register: fire => {
|
||||
const listener = (eventName, themeName) => {
|
||||
fire.async(themeName);
|
||||
};
|
||||
|
@ -288,7 +302,8 @@ this.devtools_panels = class extends ExtensionAPI {
|
|||
return () => {
|
||||
themeChangeObserver.off("themeChanged", listener);
|
||||
};
|
||||
}).api(),
|
||||
},
|
||||
}).api(),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -159,17 +159,21 @@ this.menusInternal = class extends ExtensionAPI {
|
|||
return context.childManager.callParentAsyncFunction("menusInternal.removeAll", []);
|
||||
},
|
||||
|
||||
onClicked: new EventManager(context, "menus.onClicked", fire => {
|
||||
let listener = (info, tab) => {
|
||||
withHandlingUserInput(context.contentWindow,
|
||||
() => fire.sync(info, tab));
|
||||
};
|
||||
onClicked: new EventManager({
|
||||
context,
|
||||
name: "menus.onClicked",
|
||||
register: fire => {
|
||||
let listener = (info, tab) => {
|
||||
withHandlingUserInput(context.contentWindow,
|
||||
() => fire.sync(info, tab));
|
||||
};
|
||||
|
||||
let event = context.childManager.getParentEvent("menusInternal.onClicked");
|
||||
event.addListener(listener);
|
||||
return () => {
|
||||
event.removeListener(listener);
|
||||
};
|
||||
let event = context.childManager.getParentEvent("menusInternal.onClicked");
|
||||
event.addListener(listener);
|
||||
return () => {
|
||||
event.removeListener(listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -6,19 +6,23 @@ this.omnibox = class extends ExtensionAPI {
|
|||
getAPI(context) {
|
||||
return {
|
||||
omnibox: {
|
||||
onInputChanged: new EventManager(context, "omnibox.onInputChanged", fire => {
|
||||
let listener = (text, id) => {
|
||||
fire.asyncWithoutClone(text, suggestions => {
|
||||
context.childManager.callParentFunctionNoReturn("omnibox.addSuggestions", [
|
||||
id,
|
||||
suggestions,
|
||||
]);
|
||||
});
|
||||
};
|
||||
context.childManager.getParentEvent("omnibox.onInputChanged").addListener(listener);
|
||||
return () => {
|
||||
context.childManager.getParentEvent("omnibox.onInputChanged").removeListener(listener);
|
||||
};
|
||||
onInputChanged: new EventManager({
|
||||
context,
|
||||
name: "omnibox.onInputChanged",
|
||||
register: fire => {
|
||||
let listener = (text, id) => {
|
||||
fire.asyncWithoutClone(text, suggestions => {
|
||||
context.childManager.callParentFunctionNoReturn("omnibox.addSuggestions", [
|
||||
id,
|
||||
suggestions,
|
||||
]);
|
||||
});
|
||||
};
|
||||
context.childManager.getParentEvent("omnibox.onInputChanged").addListener(listener);
|
||||
return () => {
|
||||
context.childManager.getParentEvent("omnibox.onInputChanged").removeListener(listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -7,7 +7,6 @@ module.exports = {
|
|||
"Tab": true,
|
||||
"TabContext": true,
|
||||
"Window": true,
|
||||
"WindowEventManager": true,
|
||||
"actionContextMenu": true,
|
||||
"browserActionFor": true,
|
||||
"getContainerForCookieStoreId": true,
|
||||
|
|
|
@ -329,56 +329,72 @@ this.bookmarks = class extends ExtensionAPI {
|
|||
}
|
||||
},
|
||||
|
||||
onCreated: new EventManager(context, "bookmarks.onCreated", fire => {
|
||||
let listener = (event, bookmark) => {
|
||||
fire.sync(bookmark.id, bookmark);
|
||||
};
|
||||
onCreated: new EventManager({
|
||||
context,
|
||||
name: "bookmarks.onCreated",
|
||||
register: fire => {
|
||||
let listener = (event, bookmark) => {
|
||||
fire.sync(bookmark.id, bookmark);
|
||||
};
|
||||
|
||||
observer.on("created", listener);
|
||||
incrementListeners();
|
||||
return () => {
|
||||
observer.off("created", listener);
|
||||
decrementListeners();
|
||||
};
|
||||
observer.on("created", listener);
|
||||
incrementListeners();
|
||||
return () => {
|
||||
observer.off("created", listener);
|
||||
decrementListeners();
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onRemoved: new EventManager(context, "bookmarks.onRemoved", fire => {
|
||||
let listener = (event, data) => {
|
||||
fire.sync(data.guid, data.info);
|
||||
};
|
||||
onRemoved: new EventManager({
|
||||
context,
|
||||
name: "bookmarks.onRemoved",
|
||||
register: fire => {
|
||||
let listener = (event, data) => {
|
||||
fire.sync(data.guid, data.info);
|
||||
};
|
||||
|
||||
observer.on("removed", listener);
|
||||
incrementListeners();
|
||||
return () => {
|
||||
observer.off("removed", listener);
|
||||
decrementListeners();
|
||||
};
|
||||
observer.on("removed", listener);
|
||||
incrementListeners();
|
||||
return () => {
|
||||
observer.off("removed", listener);
|
||||
decrementListeners();
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onChanged: new EventManager(context, "bookmarks.onChanged", fire => {
|
||||
let listener = (event, data) => {
|
||||
fire.sync(data.guid, data.info);
|
||||
};
|
||||
onChanged: new EventManager({
|
||||
context,
|
||||
name: "bookmarks.onChanged",
|
||||
register: fire => {
|
||||
let listener = (event, data) => {
|
||||
fire.sync(data.guid, data.info);
|
||||
};
|
||||
|
||||
observer.on("changed", listener);
|
||||
incrementListeners();
|
||||
return () => {
|
||||
observer.off("changed", listener);
|
||||
decrementListeners();
|
||||
};
|
||||
observer.on("changed", listener);
|
||||
incrementListeners();
|
||||
return () => {
|
||||
observer.off("changed", listener);
|
||||
decrementListeners();
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onMoved: new EventManager(context, "bookmarks.onMoved", fire => {
|
||||
let listener = (event, data) => {
|
||||
fire.sync(data.guid, data.info);
|
||||
};
|
||||
onMoved: new EventManager({
|
||||
context,
|
||||
name: "bookmarks.onMoved",
|
||||
register: fire => {
|
||||
let listener = (event, data) => {
|
||||
fire.sync(data.guid, data.info);
|
||||
};
|
||||
|
||||
observer.on("moved", listener);
|
||||
incrementListeners();
|
||||
return () => {
|
||||
observer.off("moved", listener);
|
||||
decrementListeners();
|
||||
};
|
||||
observer.on("moved", listener);
|
||||
incrementListeners();
|
||||
return () => {
|
||||
observer.off("moved", listener);
|
||||
decrementListeners();
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -169,34 +169,6 @@ class WindowTracker extends WindowTrackerBase {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An event manager API provider which listens for a DOM event in any browser
|
||||
* window, and calls the given listener function whenever an event is received.
|
||||
* That listener function receives a `fire` object, which it can use to dispatch
|
||||
* events to the extension, and a DOM event object.
|
||||
*
|
||||
* @param {BaseContext} context
|
||||
* The extension context which the event manager belongs to.
|
||||
* @param {string} name
|
||||
* The API name of the event manager, e.g.,"runtime.onMessage".
|
||||
* @param {string} event
|
||||
* The name of the DOM event to listen for.
|
||||
* @param {function} listener
|
||||
* The listener function to call when a DOM event is received.
|
||||
*/
|
||||
global.WindowEventManager = class extends EventManager {
|
||||
constructor(context, name, event, listener) {
|
||||
super(context, name, fire => {
|
||||
let listener2 = listener.bind(null, fire);
|
||||
|
||||
windowTracker.addListener(event, listener2);
|
||||
return () => {
|
||||
windowTracker.removeListener(event, listener2);
|
||||
};
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
class TabTracker extends TabTrackerBase {
|
||||
constructor() {
|
||||
super();
|
||||
|
|
|
@ -584,15 +584,20 @@ this.browserAction = class extends ExtensionAPI {
|
|||
|
||||
return {
|
||||
browserAction: {
|
||||
onClicked: new InputEventManager(context, "browserAction.onClicked", fire => {
|
||||
let listener = (event, browser) => {
|
||||
context.withPendingBrowser(browser, () =>
|
||||
fire.sync(tabManager.convert(tabTracker.activeTab)));
|
||||
};
|
||||
browserAction.on("click", listener);
|
||||
return () => {
|
||||
browserAction.off("click", listener);
|
||||
};
|
||||
onClicked: new EventManager({
|
||||
context,
|
||||
name: "browserAction.onClicked",
|
||||
inputHandling: true,
|
||||
register: fire => {
|
||||
let listener = (event, browser) => {
|
||||
context.withPendingBrowser(browser, () =>
|
||||
fire.sync(tabManager.convert(tabTracker.activeTab)));
|
||||
};
|
||||
browserAction.on("click", listener);
|
||||
return () => {
|
||||
browserAction.off("click", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
enable: function(tabId) {
|
||||
|
|
|
@ -364,14 +364,18 @@ this.commands = class extends ExtensionAPI {
|
|||
this.registerKeys(commands);
|
||||
}
|
||||
},
|
||||
onCommand: new EventManager(context, "commands.onCommand", fire => {
|
||||
let listener = (eventName, commandName) => {
|
||||
fire.async(commandName);
|
||||
};
|
||||
this.on("command", listener);
|
||||
return () => {
|
||||
this.off("command", listener);
|
||||
};
|
||||
onCommand: new EventManager({
|
||||
context,
|
||||
name: "commands.onCommand",
|
||||
register: fire => {
|
||||
let listener = (eventName, commandName) => {
|
||||
fire.async(commandName);
|
||||
};
|
||||
this.on("command", listener);
|
||||
return () => {
|
||||
this.off("command", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -15,37 +15,45 @@ this.devtools_network = class extends ExtensionAPI {
|
|||
return {
|
||||
devtools: {
|
||||
network: {
|
||||
onNavigated: new EventManager(context, "devtools.onNavigated", fire => {
|
||||
let listener = data => {
|
||||
fire.async(data.url);
|
||||
};
|
||||
onNavigated: new EventManager({
|
||||
context,
|
||||
name: "devtools.onNavigated",
|
||||
register: fire => {
|
||||
let listener = data => {
|
||||
fire.async(data.url);
|
||||
};
|
||||
|
||||
let targetPromise = getDevToolsTargetForContext(context);
|
||||
targetPromise.then(target => {
|
||||
target.on("navigate", listener);
|
||||
});
|
||||
return () => {
|
||||
let targetPromise = getDevToolsTargetForContext(context);
|
||||
targetPromise.then(target => {
|
||||
target.off("navigate", listener);
|
||||
target.on("navigate", listener);
|
||||
});
|
||||
};
|
||||
return () => {
|
||||
targetPromise.then(target => {
|
||||
target.off("navigate", listener);
|
||||
});
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
getHAR: function() {
|
||||
return context.devToolsToolbox.getHARFromNetMonitor();
|
||||
},
|
||||
|
||||
onRequestFinished: new EventManager(context, "devtools.network.onRequestFinished", fire => {
|
||||
const listener = (data) => {
|
||||
fire.async(data);
|
||||
};
|
||||
onRequestFinished: new EventManager({
|
||||
context,
|
||||
name: "devtools.network.onRequestFinished",
|
||||
register: fire => {
|
||||
const listener = (data) => {
|
||||
fire.async(data);
|
||||
};
|
||||
|
||||
const toolbox = context.devToolsToolbox;
|
||||
toolbox.addRequestFinishedListener(listener);
|
||||
const toolbox = context.devToolsToolbox;
|
||||
toolbox.addRequestFinishedListener(listener);
|
||||
|
||||
return () => {
|
||||
toolbox.removeRequestFinishedListener(listener);
|
||||
};
|
||||
return () => {
|
||||
toolbox.removeRequestFinishedListener(listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
// The following method is used internally to allow the request API
|
||||
|
|
|
@ -525,8 +525,10 @@ this.devtools_panels = class extends ExtensionAPI {
|
|||
devtools: {
|
||||
panels: {
|
||||
elements: {
|
||||
onSelectionChanged: new EventManager(
|
||||
context, "devtools.panels.elements.onSelectionChanged", fire => {
|
||||
onSelectionChanged: new EventManager({
|
||||
context,
|
||||
name: "devtools.panels.elements.onSelectionChanged",
|
||||
register: fire => {
|
||||
const listener = (eventName) => {
|
||||
fire.async();
|
||||
};
|
||||
|
@ -534,7 +536,8 @@ this.devtools_panels = class extends ExtensionAPI {
|
|||
return () => {
|
||||
toolboxSelectionObserver.off("selectionChanged", listener);
|
||||
};
|
||||
}).api(),
|
||||
},
|
||||
}).api(),
|
||||
createSidebarPane(title) {
|
||||
const id = `devtools-inspector-sidebar-${makeWidgetId(newBasePanelId())}`;
|
||||
|
||||
|
|
|
@ -473,11 +473,15 @@ this.geckoProfiler = class extends ExtensionAPI {
|
|||
throw new Error(`Ran out of options to get symbols from library ${debugName} ${breakpadId}.`);
|
||||
},
|
||||
|
||||
onRunning: new EventManager(context, "geckoProfiler.onRunning", fire => {
|
||||
isRunningObserver.addObserver(fire.async);
|
||||
return () => {
|
||||
isRunningObserver.removeObserver(fire.async);
|
||||
};
|
||||
onRunning: new EventManager({
|
||||
context,
|
||||
name: "geckoProfiler.onRunning",
|
||||
register: fire => {
|
||||
isRunningObserver.addObserver(fire.async);
|
||||
return () => {
|
||||
isRunningObserver.removeObserver(fire.async);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -220,37 +220,49 @@ this.history = class extends ExtensionAPI {
|
|||
return Promise.resolve(results);
|
||||
},
|
||||
|
||||
onVisited: new EventManager(context, "history.onVisited", fire => {
|
||||
let listener = (event, data) => {
|
||||
fire.sync(data);
|
||||
};
|
||||
onVisited: new EventManager({
|
||||
context,
|
||||
name: "history.onVisited",
|
||||
register: fire => {
|
||||
let listener = (event, data) => {
|
||||
fire.sync(data);
|
||||
};
|
||||
|
||||
getHistoryObserver().on("visited", listener);
|
||||
return () => {
|
||||
getHistoryObserver().off("visited", listener);
|
||||
};
|
||||
getHistoryObserver().on("visited", listener);
|
||||
return () => {
|
||||
getHistoryObserver().off("visited", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onVisitRemoved: new EventManager(context, "history.onVisitRemoved", fire => {
|
||||
let listener = (event, data) => {
|
||||
fire.sync(data);
|
||||
};
|
||||
onVisitRemoved: new EventManager({
|
||||
context,
|
||||
name: "history.onVisitRemoved",
|
||||
register: fire => {
|
||||
let listener = (event, data) => {
|
||||
fire.sync(data);
|
||||
};
|
||||
|
||||
getHistoryObserver().on("visitRemoved", listener);
|
||||
return () => {
|
||||
getHistoryObserver().off("visitRemoved", listener);
|
||||
};
|
||||
getHistoryObserver().on("visitRemoved", listener);
|
||||
return () => {
|
||||
getHistoryObserver().off("visitRemoved", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onTitleChanged: new EventManager(context, "history.onTitleChanged", fire => {
|
||||
let listener = (event, data) => {
|
||||
fire.sync(data);
|
||||
};
|
||||
onTitleChanged: new EventManager({
|
||||
context,
|
||||
name: "history.onTitleChanged",
|
||||
register: fire => {
|
||||
let listener = (event, data) => {
|
||||
fire.sync(data);
|
||||
};
|
||||
|
||||
getHistoryObserver().on("titleChanged", listener);
|
||||
return () => {
|
||||
getHistoryObserver().off("titleChanged", listener);
|
||||
};
|
||||
getHistoryObserver().on("titleChanged", listener);
|
||||
return () => {
|
||||
getHistoryObserver().off("titleChanged", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -813,40 +813,48 @@ this.menusInternal = class extends ExtensionAPI {
|
|||
gMenuBuilder.rebuildMenu(extension);
|
||||
},
|
||||
|
||||
onShown: new EventManager(context, "menus.onShown", fire => {
|
||||
let listener = (event, menuIds, contextData) => {
|
||||
let info = {
|
||||
menuIds,
|
||||
contexts: Array.from(getMenuContexts(contextData)),
|
||||
};
|
||||
onShown: new EventManager({
|
||||
context,
|
||||
name: "menus.onShown",
|
||||
register: fire => {
|
||||
let listener = (event, menuIds, contextData) => {
|
||||
let info = {
|
||||
menuIds,
|
||||
contexts: Array.from(getMenuContexts(contextData)),
|
||||
};
|
||||
|
||||
// The menus.onShown event is fired before the user has consciously
|
||||
// interacted with an extension, so we require permissions before
|
||||
// exposing sensitive contextual data.
|
||||
let includeSensitiveData =
|
||||
// The menus.onShown event is fired before the user has consciously
|
||||
// interacted with an extension, so we require permissions before
|
||||
// exposing sensitive contextual data.
|
||||
let includeSensitiveData =
|
||||
extension.tabManager.hasActiveTabPermission(contextData.tab) ||
|
||||
extension.whiteListedHosts.matches(contextData.inFrame ? contextData.frameUrl : contextData.pageUrl);
|
||||
|
||||
addMenuEventInfo(info, contextData, includeSensitiveData);
|
||||
addMenuEventInfo(info, contextData, includeSensitiveData);
|
||||
|
||||
let tab = extension.tabManager.convert(contextData.tab);
|
||||
fire.sync(info, tab);
|
||||
};
|
||||
gOnShownSubscribers.add(extension);
|
||||
extension.on("webext-menu-shown", listener);
|
||||
return () => {
|
||||
gOnShownSubscribers.delete(extension);
|
||||
extension.off("webext-menu-shown", listener);
|
||||
};
|
||||
let tab = extension.tabManager.convert(contextData.tab);
|
||||
fire.sync(info, tab);
|
||||
};
|
||||
gOnShownSubscribers.add(extension);
|
||||
extension.on("webext-menu-shown", listener);
|
||||
return () => {
|
||||
gOnShownSubscribers.delete(extension);
|
||||
extension.off("webext-menu-shown", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
onHidden: new EventManager(context, "menus.onHidden", fire => {
|
||||
let listener = () => {
|
||||
fire.sync();
|
||||
};
|
||||
extension.on("webext-menu-hidden", listener);
|
||||
return () => {
|
||||
extension.off("webext-menu-hidden", listener);
|
||||
};
|
||||
onHidden: new EventManager({
|
||||
context,
|
||||
name: "menus.onHidden",
|
||||
register: fire => {
|
||||
let listener = () => {
|
||||
fire.sync();
|
||||
};
|
||||
extension.on("webext-menu-hidden", listener);
|
||||
return () => {
|
||||
extension.off("webext-menu-hidden", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
};
|
||||
|
||||
|
@ -883,18 +891,22 @@ this.menusInternal = class extends ExtensionAPI {
|
|||
}
|
||||
},
|
||||
|
||||
onClicked: new EventManager(context, "menusInternal.onClicked", fire => {
|
||||
let listener = (event, info, nativeTab) => {
|
||||
let {linkedBrowser} = nativeTab || tabTracker.activeTab;
|
||||
let tab = nativeTab && extension.tabManager.convert(nativeTab);
|
||||
context.withPendingBrowser(linkedBrowser,
|
||||
() => fire.sync(info, tab));
|
||||
};
|
||||
onClicked: new EventManager({
|
||||
context,
|
||||
name: "menusInternal.onClicked",
|
||||
register: fire => {
|
||||
let listener = (event, info, nativeTab) => {
|
||||
let {linkedBrowser} = nativeTab || tabTracker.activeTab;
|
||||
let tab = nativeTab && extension.tabManager.convert(nativeTab);
|
||||
context.withPendingBrowser(linkedBrowser,
|
||||
() => fire.sync(info, tab));
|
||||
};
|
||||
|
||||
extension.on("webext-menu-menuitem-click", listener);
|
||||
return () => {
|
||||
extension.off("webext-menu-menuitem-click", listener);
|
||||
};
|
||||
extension.on("webext-menu-menuitem-click", listener);
|
||||
return () => {
|
||||
extension.off("webext-menu-menuitem-click", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -37,34 +37,46 @@ this.omnibox = class extends ExtensionAPI {
|
|||
}
|
||||
},
|
||||
|
||||
onInputStarted: new EventManager(context, "omnibox.onInputStarted", fire => {
|
||||
let listener = (eventName) => {
|
||||
fire.sync();
|
||||
};
|
||||
extension.on(ExtensionSearchHandler.MSG_INPUT_STARTED, listener);
|
||||
return () => {
|
||||
extension.off(ExtensionSearchHandler.MSG_INPUT_STARTED, listener);
|
||||
};
|
||||
onInputStarted: new EventManager({
|
||||
context,
|
||||
name: "omnibox.onInputStarted",
|
||||
register: fire => {
|
||||
let listener = (eventName) => {
|
||||
fire.sync();
|
||||
};
|
||||
extension.on(ExtensionSearchHandler.MSG_INPUT_STARTED, listener);
|
||||
return () => {
|
||||
extension.off(ExtensionSearchHandler.MSG_INPUT_STARTED, listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onInputCancelled: new EventManager(context, "omnibox.onInputCancelled", fire => {
|
||||
let listener = (eventName) => {
|
||||
fire.sync();
|
||||
};
|
||||
extension.on(ExtensionSearchHandler.MSG_INPUT_CANCELLED, listener);
|
||||
return () => {
|
||||
extension.off(ExtensionSearchHandler.MSG_INPUT_CANCELLED, listener);
|
||||
};
|
||||
onInputCancelled: new EventManager({
|
||||
context,
|
||||
name: "omnibox.onInputCancelled",
|
||||
register: fire => {
|
||||
let listener = (eventName) => {
|
||||
fire.sync();
|
||||
};
|
||||
extension.on(ExtensionSearchHandler.MSG_INPUT_CANCELLED, listener);
|
||||
return () => {
|
||||
extension.off(ExtensionSearchHandler.MSG_INPUT_CANCELLED, listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onInputEntered: new EventManager(context, "omnibox.onInputEntered", fire => {
|
||||
let listener = (eventName, text, disposition) => {
|
||||
fire.sync(text, disposition);
|
||||
};
|
||||
extension.on(ExtensionSearchHandler.MSG_INPUT_ENTERED, listener);
|
||||
return () => {
|
||||
extension.off(ExtensionSearchHandler.MSG_INPUT_ENTERED, listener);
|
||||
};
|
||||
onInputEntered: new EventManager({
|
||||
context,
|
||||
name: "omnibox.onInputEntered",
|
||||
register: fire => {
|
||||
let listener = (eventName, text, disposition) => {
|
||||
fire.sync(text, disposition);
|
||||
};
|
||||
extension.on(ExtensionSearchHandler.MSG_INPUT_ENTERED, listener);
|
||||
return () => {
|
||||
extension.off(ExtensionSearchHandler.MSG_INPUT_ENTERED, listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
// Internal APIs.
|
||||
|
@ -77,14 +89,18 @@ this.omnibox = class extends ExtensionAPI {
|
|||
}
|
||||
},
|
||||
|
||||
onInputChanged: new EventManager(context, "omnibox.onInputChanged", fire => {
|
||||
let listener = (eventName, text, id) => {
|
||||
fire.sync(text, id);
|
||||
};
|
||||
extension.on(ExtensionSearchHandler.MSG_INPUT_CHANGED, listener);
|
||||
return () => {
|
||||
extension.off(ExtensionSearchHandler.MSG_INPUT_CHANGED, listener);
|
||||
};
|
||||
onInputChanged: new EventManager({
|
||||
context,
|
||||
name: "omnibox.onInputChanged",
|
||||
register: fire => {
|
||||
let listener = (eventName, text, id) => {
|
||||
fire.sync(text, id);
|
||||
};
|
||||
extension.on(ExtensionSearchHandler.MSG_INPUT_CHANGED, listener);
|
||||
return () => {
|
||||
extension.off(ExtensionSearchHandler.MSG_INPUT_CHANGED, listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -346,16 +346,21 @@ this.pageAction = class extends ExtensionAPI {
|
|||
|
||||
return {
|
||||
pageAction: {
|
||||
onClicked: new InputEventManager(context, "pageAction.onClicked", fire => {
|
||||
let listener = (evt, tab) => {
|
||||
context.withPendingBrowser(tab.linkedBrowser, () =>
|
||||
fire.sync(tabManager.convert(tab)));
|
||||
};
|
||||
onClicked: new EventManager({
|
||||
context,
|
||||
name: "pageAction.onClicked",
|
||||
inputHandling: true,
|
||||
register: fire => {
|
||||
let listener = (evt, tab) => {
|
||||
context.withPendingBrowser(tab.linkedBrowser, () =>
|
||||
fire.sync(tabManager.convert(tab)));
|
||||
};
|
||||
|
||||
pageAction.on("click", listener);
|
||||
return () => {
|
||||
pageAction.off("click", listener);
|
||||
};
|
||||
pageAction.on("click", listener);
|
||||
return () => {
|
||||
pageAction.off("click", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
show(tabId) {
|
||||
|
|
|
@ -204,15 +204,19 @@ this.sessions = class extends ExtensionAPI {
|
|||
SessionStore.deleteWindowValue(win, encodedKey);
|
||||
},
|
||||
|
||||
onChanged: new EventManager(context, "sessions.onChanged", fire => {
|
||||
let observer = () => {
|
||||
fire.async();
|
||||
};
|
||||
onChanged: new EventManager({
|
||||
context,
|
||||
name: "sessions.onChanged",
|
||||
register: fire => {
|
||||
let observer = () => {
|
||||
fire.async();
|
||||
};
|
||||
|
||||
Services.obs.addObserver(observer, SS_ON_CLOSED_OBJECTS_CHANGED);
|
||||
return () => {
|
||||
Services.obs.removeObserver(observer, SS_ON_CLOSED_OBJECTS_CHANGED);
|
||||
};
|
||||
Services.obs.addObserver(observer, SS_ON_CLOSED_OBJECTS_CHANGED);
|
||||
return () => {
|
||||
Services.obs.removeObserver(observer, SS_ON_CLOSED_OBJECTS_CHANGED);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -110,7 +110,7 @@ const allProperties = new Set([
|
|||
const restricted = new Set(["url", "favIconUrl", "title"]);
|
||||
|
||||
class TabsUpdateFilterEventManager extends EventManager {
|
||||
constructor(context, eventName) {
|
||||
constructor(context) {
|
||||
let {extension} = context;
|
||||
let {tabManager} = extension;
|
||||
|
||||
|
@ -286,7 +286,11 @@ class TabsUpdateFilterEventManager extends EventManager {
|
|||
};
|
||||
};
|
||||
|
||||
super(context, eventName, register);
|
||||
super({
|
||||
context,
|
||||
name: "tabs.onUpdated",
|
||||
register,
|
||||
});
|
||||
}
|
||||
|
||||
addListener(callback, filter) {
|
||||
|
@ -338,26 +342,34 @@ this.tabs = class extends ExtensionAPI {
|
|||
|
||||
let self = {
|
||||
tabs: {
|
||||
onActivated: new EventManager(context, "tabs.onActivated", fire => {
|
||||
let listener = (eventName, event) => {
|
||||
fire.async(event);
|
||||
};
|
||||
onActivated: new EventManager({
|
||||
context,
|
||||
name: "tabs.onActivated",
|
||||
register: fire => {
|
||||
let listener = (eventName, event) => {
|
||||
fire.async(event);
|
||||
};
|
||||
|
||||
tabTracker.on("tab-activated", listener);
|
||||
return () => {
|
||||
tabTracker.off("tab-activated", listener);
|
||||
};
|
||||
tabTracker.on("tab-activated", listener);
|
||||
return () => {
|
||||
tabTracker.off("tab-activated", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onCreated: new EventManager(context, "tabs.onCreated", fire => {
|
||||
let listener = (eventName, event) => {
|
||||
fire.async(tabManager.convert(event.nativeTab, event.currentTab));
|
||||
};
|
||||
onCreated: new EventManager({
|
||||
context,
|
||||
name: "tabs.onCreated",
|
||||
register: fire => {
|
||||
let listener = (eventName, event) => {
|
||||
fire.async(tabManager.convert(event.nativeTab, event.currentTab));
|
||||
};
|
||||
|
||||
tabTracker.on("tab-created", listener);
|
||||
return () => {
|
||||
tabTracker.off("tab-created", listener);
|
||||
};
|
||||
tabTracker.on("tab-created", listener);
|
||||
return () => {
|
||||
tabTracker.off("tab-created", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
/**
|
||||
|
@ -366,98 +378,122 @@ this.tabs = class extends ExtensionAPI {
|
|||
* the tabId in an array to match the API.
|
||||
* @see https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/Tabs/onHighlighted
|
||||
*/
|
||||
onHighlighted: new EventManager(context, "tabs.onHighlighted", fire => {
|
||||
let listener = (eventName, event) => {
|
||||
fire.async({tabIds: [event.tabId], windowId: event.windowId});
|
||||
};
|
||||
onHighlighted: new EventManager({
|
||||
context,
|
||||
name: "tabs.onHighlighted",
|
||||
register: fire => {
|
||||
let listener = (eventName, event) => {
|
||||
fire.async({tabIds: [event.tabId], windowId: event.windowId});
|
||||
};
|
||||
|
||||
tabTracker.on("tab-activated", listener);
|
||||
return () => {
|
||||
tabTracker.off("tab-activated", listener);
|
||||
};
|
||||
tabTracker.on("tab-activated", listener);
|
||||
return () => {
|
||||
tabTracker.off("tab-activated", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onAttached: new EventManager(context, "tabs.onAttached", fire => {
|
||||
let listener = (eventName, event) => {
|
||||
fire.async(event.tabId, {newWindowId: event.newWindowId, newPosition: event.newPosition});
|
||||
};
|
||||
onAttached: new EventManager({
|
||||
context,
|
||||
name: "tabs.onAttached",
|
||||
register: fire => {
|
||||
let listener = (eventName, event) => {
|
||||
fire.async(event.tabId, {newWindowId: event.newWindowId, newPosition: event.newPosition});
|
||||
};
|
||||
|
||||
tabTracker.on("tab-attached", listener);
|
||||
return () => {
|
||||
tabTracker.off("tab-attached", listener);
|
||||
};
|
||||
tabTracker.on("tab-attached", listener);
|
||||
return () => {
|
||||
tabTracker.off("tab-attached", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onDetached: new EventManager(context, "tabs.onDetached", fire => {
|
||||
let listener = (eventName, event) => {
|
||||
fire.async(event.tabId, {oldWindowId: event.oldWindowId, oldPosition: event.oldPosition});
|
||||
};
|
||||
onDetached: new EventManager({
|
||||
context,
|
||||
name: "tabs.onDetached",
|
||||
register: fire => {
|
||||
let listener = (eventName, event) => {
|
||||
fire.async(event.tabId, {oldWindowId: event.oldWindowId, oldPosition: event.oldPosition});
|
||||
};
|
||||
|
||||
tabTracker.on("tab-detached", listener);
|
||||
return () => {
|
||||
tabTracker.off("tab-detached", listener);
|
||||
};
|
||||
tabTracker.on("tab-detached", listener);
|
||||
return () => {
|
||||
tabTracker.off("tab-detached", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onRemoved: new EventManager(context, "tabs.onRemoved", fire => {
|
||||
let listener = (eventName, event) => {
|
||||
fire.async(event.tabId, {windowId: event.windowId, isWindowClosing: event.isWindowClosing});
|
||||
};
|
||||
onRemoved: new EventManager({
|
||||
context,
|
||||
name: "tabs.onRemoved",
|
||||
register: fire => {
|
||||
let listener = (eventName, event) => {
|
||||
fire.async(event.tabId, {windowId: event.windowId, isWindowClosing: event.isWindowClosing});
|
||||
};
|
||||
|
||||
tabTracker.on("tab-removed", listener);
|
||||
return () => {
|
||||
tabTracker.off("tab-removed", listener);
|
||||
};
|
||||
tabTracker.on("tab-removed", listener);
|
||||
return () => {
|
||||
tabTracker.off("tab-removed", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onReplaced: new EventManager(context, "tabs.onReplaced", fire => {
|
||||
return () => {};
|
||||
onReplaced: new EventManager({
|
||||
context,
|
||||
name: "tabs.onReplaced",
|
||||
register: fire => {
|
||||
return () => {};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onMoved: new EventManager(context, "tabs.onMoved", fire => {
|
||||
// There are certain circumstances where we need to ignore a move event.
|
||||
//
|
||||
// Namely, the first time the tab is moved after it's created, we need
|
||||
// to report the final position as the initial position in the tab's
|
||||
// onAttached or onCreated event. This is because most tabs are inserted
|
||||
// in a temporary location and then moved after the TabOpen event fires,
|
||||
// which generates a TabOpen event followed by a TabMove event, which
|
||||
// does not match the contract of our API.
|
||||
let ignoreNextMove = new WeakSet();
|
||||
onMoved: new EventManager({
|
||||
context,
|
||||
name: "tabs.onMoved",
|
||||
register: fire => {
|
||||
// There are certain circumstances where we need to ignore a move event.
|
||||
//
|
||||
// Namely, the first time the tab is moved after it's created, we need
|
||||
// to report the final position as the initial position in the tab's
|
||||
// onAttached or onCreated event. This is because most tabs are inserted
|
||||
// in a temporary location and then moved after the TabOpen event fires,
|
||||
// which generates a TabOpen event followed by a TabMove event, which
|
||||
// does not match the contract of our API.
|
||||
let ignoreNextMove = new WeakSet();
|
||||
|
||||
let openListener = event => {
|
||||
ignoreNextMove.add(event.target);
|
||||
// Remove the tab from the set on the next tick, since it will already
|
||||
// have been moved by then.
|
||||
Promise.resolve().then(() => {
|
||||
ignoreNextMove.delete(event.target);
|
||||
});
|
||||
};
|
||||
let openListener = event => {
|
||||
ignoreNextMove.add(event.target);
|
||||
// Remove the tab from the set on the next tick, since it will already
|
||||
// have been moved by then.
|
||||
Promise.resolve().then(() => {
|
||||
ignoreNextMove.delete(event.target);
|
||||
});
|
||||
};
|
||||
|
||||
let moveListener = event => {
|
||||
let nativeTab = event.originalTarget;
|
||||
let moveListener = event => {
|
||||
let nativeTab = event.originalTarget;
|
||||
|
||||
if (ignoreNextMove.has(nativeTab)) {
|
||||
ignoreNextMove.delete(nativeTab);
|
||||
return;
|
||||
}
|
||||
if (ignoreNextMove.has(nativeTab)) {
|
||||
ignoreNextMove.delete(nativeTab);
|
||||
return;
|
||||
}
|
||||
|
||||
fire.async(tabTracker.getId(nativeTab), {
|
||||
windowId: windowTracker.getId(nativeTab.ownerGlobal),
|
||||
fromIndex: event.detail,
|
||||
toIndex: nativeTab._tPos,
|
||||
});
|
||||
};
|
||||
fire.async(tabTracker.getId(nativeTab), {
|
||||
windowId: windowTracker.getId(nativeTab.ownerGlobal),
|
||||
fromIndex: event.detail,
|
||||
toIndex: nativeTab._tPos,
|
||||
});
|
||||
};
|
||||
|
||||
windowTracker.addListener("TabMove", moveListener);
|
||||
windowTracker.addListener("TabOpen", openListener);
|
||||
return () => {
|
||||
windowTracker.removeListener("TabMove", moveListener);
|
||||
windowTracker.removeListener("TabOpen", openListener);
|
||||
};
|
||||
windowTracker.addListener("TabMove", moveListener);
|
||||
windowTracker.addListener("TabOpen", openListener);
|
||||
return () => {
|
||||
windowTracker.removeListener("TabMove", moveListener);
|
||||
windowTracker.removeListener("TabOpen", openListener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onUpdated: new TabsUpdateFilterEventManager(context, "tabs.onUpdated").api(),
|
||||
onUpdated: new TabsUpdateFilterEventManager(context).api(),
|
||||
|
||||
create(createProperties) {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
@ -896,75 +932,79 @@ this.tabs = class extends ExtensionAPI {
|
|||
return Promise.resolve();
|
||||
},
|
||||
|
||||
onZoomChange: new EventManager(context, "tabs.onZoomChange", fire => {
|
||||
let getZoomLevel = browser => {
|
||||
let {ZoomManager} = browser.ownerGlobal;
|
||||
onZoomChange: new EventManager({
|
||||
context,
|
||||
name: "tabs.onZoomChange",
|
||||
register: fire => {
|
||||
let getZoomLevel = browser => {
|
||||
let {ZoomManager} = browser.ownerGlobal;
|
||||
|
||||
return ZoomManager.getZoomForBrowser(browser);
|
||||
};
|
||||
return ZoomManager.getZoomForBrowser(browser);
|
||||
};
|
||||
|
||||
// Stores the last known zoom level for each tab's browser.
|
||||
// WeakMap[<browser> -> number]
|
||||
let zoomLevels = new WeakMap();
|
||||
// Stores the last known zoom level for each tab's browser.
|
||||
// WeakMap[<browser> -> number]
|
||||
let zoomLevels = new WeakMap();
|
||||
|
||||
// Store the zoom level for all existing tabs.
|
||||
for (let window of windowTracker.browserWindows()) {
|
||||
for (let nativeTab of window.gBrowser.tabs) {
|
||||
let browser = nativeTab.linkedBrowser;
|
||||
// Store the zoom level for all existing tabs.
|
||||
for (let window of windowTracker.browserWindows()) {
|
||||
for (let nativeTab of window.gBrowser.tabs) {
|
||||
let browser = nativeTab.linkedBrowser;
|
||||
zoomLevels.set(browser, getZoomLevel(browser));
|
||||
}
|
||||
}
|
||||
|
||||
let tabCreated = (eventName, event) => {
|
||||
let browser = event.nativeTab.linkedBrowser;
|
||||
zoomLevels.set(browser, getZoomLevel(browser));
|
||||
}
|
||||
}
|
||||
|
||||
let tabCreated = (eventName, event) => {
|
||||
let browser = event.nativeTab.linkedBrowser;
|
||||
zoomLevels.set(browser, getZoomLevel(browser));
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
let zoomListener = event => {
|
||||
let browser = event.originalTarget;
|
||||
let zoomListener = event => {
|
||||
let browser = event.originalTarget;
|
||||
|
||||
// For non-remote browsers, this event is dispatched on the document
|
||||
// rather than on the <browser>.
|
||||
if (browser instanceof Ci.nsIDOMDocument) {
|
||||
browser = browser.docShell.chromeEventHandler;
|
||||
}
|
||||
// For non-remote browsers, this event is dispatched on the document
|
||||
// rather than on the <browser>.
|
||||
if (browser instanceof Ci.nsIDOMDocument) {
|
||||
browser = browser.docShell.chromeEventHandler;
|
||||
}
|
||||
|
||||
let {gBrowser} = browser.ownerGlobal;
|
||||
let nativeTab = gBrowser.getTabForBrowser(browser);
|
||||
if (!nativeTab) {
|
||||
// We only care about zoom events in the top-level browser of a tab.
|
||||
return;
|
||||
}
|
||||
let {gBrowser} = browser.ownerGlobal;
|
||||
let nativeTab = gBrowser.getTabForBrowser(browser);
|
||||
if (!nativeTab) {
|
||||
// We only care about zoom events in the top-level browser of a tab.
|
||||
return;
|
||||
}
|
||||
|
||||
let oldZoomFactor = zoomLevels.get(browser);
|
||||
let newZoomFactor = getZoomLevel(browser);
|
||||
let oldZoomFactor = zoomLevels.get(browser);
|
||||
let newZoomFactor = getZoomLevel(browser);
|
||||
|
||||
if (oldZoomFactor != newZoomFactor) {
|
||||
zoomLevels.set(browser, newZoomFactor);
|
||||
if (oldZoomFactor != newZoomFactor) {
|
||||
zoomLevels.set(browser, newZoomFactor);
|
||||
|
||||
let tabId = tabTracker.getId(nativeTab);
|
||||
fire.async({
|
||||
tabId,
|
||||
oldZoomFactor,
|
||||
newZoomFactor,
|
||||
zoomSettings: self.tabs._getZoomSettings(tabId),
|
||||
});
|
||||
}
|
||||
};
|
||||
let tabId = tabTracker.getId(nativeTab);
|
||||
fire.async({
|
||||
tabId,
|
||||
oldZoomFactor,
|
||||
newZoomFactor,
|
||||
zoomSettings: self.tabs._getZoomSettings(tabId),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
tabTracker.on("tab-attached", tabCreated);
|
||||
tabTracker.on("tab-created", tabCreated);
|
||||
tabTracker.on("tab-attached", tabCreated);
|
||||
tabTracker.on("tab-created", tabCreated);
|
||||
|
||||
windowTracker.addListener("FullZoomChange", zoomListener);
|
||||
windowTracker.addListener("TextZoomChange", zoomListener);
|
||||
return () => {
|
||||
tabTracker.off("tab-attached", tabCreated);
|
||||
tabTracker.off("tab-created", tabCreated);
|
||||
windowTracker.addListener("FullZoomChange", zoomListener);
|
||||
windowTracker.addListener("TextZoomChange", zoomListener);
|
||||
return () => {
|
||||
tabTracker.off("tab-attached", tabCreated);
|
||||
tabTracker.off("tab-created", tabCreated);
|
||||
|
||||
windowTracker.removeListener("FullZoomChange", zoomListener);
|
||||
windowTracker.removeListener("TextZoomChange", zoomListener);
|
||||
};
|
||||
windowTracker.removeListener("FullZoomChange", zoomListener);
|
||||
windowTracker.removeListener("TextZoomChange", zoomListener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
print() {
|
||||
|
|
|
@ -16,6 +16,36 @@ const onXULFrameLoaderCreated = ({target}) => {
|
|||
target.messageManager.sendAsyncMessage("AllowScriptsToClose", {});
|
||||
};
|
||||
|
||||
/**
|
||||
* An event manager API provider which listens for a DOM event in any browser
|
||||
* window, and calls the given listener function whenever an event is received.
|
||||
* That listener function receives a `fire` object, which it can use to dispatch
|
||||
* events to the extension, and a DOM event object.
|
||||
*
|
||||
* @param {BaseContext} context
|
||||
* The extension context which the event manager belongs to.
|
||||
* @param {string} name
|
||||
* The API name of the event manager, e.g.,"runtime.onMessage".
|
||||
* @param {string} event
|
||||
* The name of the DOM event to listen for.
|
||||
* @param {function} listener
|
||||
* The listener function to call when a DOM event is received.
|
||||
*
|
||||
* @returns {object} An injectable api for the new event.
|
||||
*/
|
||||
function WindowEventManager(context, name, event, listener) {
|
||||
let register = fire => {
|
||||
let listener2 = listener.bind(null, fire);
|
||||
|
||||
windowTracker.addListener(event, listener2);
|
||||
return () => {
|
||||
windowTracker.removeListener(event, listener2);
|
||||
};
|
||||
};
|
||||
|
||||
return new EventManager({context, name, register}).api();
|
||||
}
|
||||
|
||||
this.windows = class extends ExtensionAPI {
|
||||
getAPI(context) {
|
||||
let {extension} = context;
|
||||
|
@ -24,38 +54,40 @@ this.windows = class extends ExtensionAPI {
|
|||
|
||||
return {
|
||||
windows: {
|
||||
onCreated:
|
||||
new WindowEventManager(context, "windows.onCreated", "domwindowopened", (fire, window) => {
|
||||
onCreated: WindowEventManager(context, "windows.onCreated", "domwindowopened", (fire, window) => {
|
||||
fire.async(windowManager.convert(window));
|
||||
}).api(),
|
||||
}),
|
||||
|
||||
onRemoved:
|
||||
new WindowEventManager(context, "windows.onRemoved", "domwindowclosed", (fire, window) => {
|
||||
onRemoved: WindowEventManager(context, "windows.onRemoved", "domwindowclosed", (fire, window) => {
|
||||
fire.async(windowTracker.getId(window));
|
||||
}).api(),
|
||||
}),
|
||||
|
||||
onFocusChanged: new EventManager(context, "windows.onFocusChanged", fire => {
|
||||
// Keep track of the last windowId used to fire an onFocusChanged event
|
||||
let lastOnFocusChangedWindowId;
|
||||
onFocusChanged: new EventManager({
|
||||
context,
|
||||
name: "windows.onFocusChanged",
|
||||
register: fire => {
|
||||
// Keep track of the last windowId used to fire an onFocusChanged event
|
||||
let lastOnFocusChangedWindowId;
|
||||
|
||||
let listener = event => {
|
||||
// Wait a tick to avoid firing a superfluous WINDOW_ID_NONE
|
||||
// event when switching focus between two Firefox windows.
|
||||
Promise.resolve().then(() => {
|
||||
let window = Services.focus.activeWindow;
|
||||
let windowId = window ? windowTracker.getId(window) : Window.WINDOW_ID_NONE;
|
||||
if (windowId !== lastOnFocusChangedWindowId) {
|
||||
fire.async(windowId);
|
||||
lastOnFocusChangedWindowId = windowId;
|
||||
}
|
||||
});
|
||||
};
|
||||
windowTracker.addListener("focus", listener);
|
||||
windowTracker.addListener("blur", listener);
|
||||
return () => {
|
||||
windowTracker.removeListener("focus", listener);
|
||||
windowTracker.removeListener("blur", listener);
|
||||
};
|
||||
let listener = event => {
|
||||
// Wait a tick to avoid firing a superfluous WINDOW_ID_NONE
|
||||
// event when switching focus between two Firefox windows.
|
||||
Promise.resolve().then(() => {
|
||||
let window = Services.focus.activeWindow;
|
||||
let windowId = window ? windowTracker.getId(window) : Window.WINDOW_ID_NONE;
|
||||
if (windowId !== lastOnFocusChangedWindowId) {
|
||||
fire.async(windowId);
|
||||
lastOnFocusChangedWindowId = windowId;
|
||||
}
|
||||
});
|
||||
};
|
||||
windowTracker.addListener("focus", listener);
|
||||
windowTracker.addListener("blur", listener);
|
||||
return () => {
|
||||
windowTracker.removeListener("focus", listener);
|
||||
windowTracker.removeListener("blur", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
get: function(windowId, getInfo) {
|
||||
|
|
|
@ -3,114 +3,123 @@
|
|||
/* eslint-disable mozilla/no-arbitrary-setTimeout */
|
||||
"use strict";
|
||||
|
||||
add_task(async function testPopupBackground() {
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
background() {
|
||||
browser.tabs.query({active: true, currentWindow: true}, tabs => {
|
||||
browser.pageAction.show(tabs[0].id);
|
||||
});
|
||||
},
|
||||
async function testPanel(browser, standAlone, initial_background) {
|
||||
let panel = getPanelForNode(browser);
|
||||
let arrowContent = document.getAnonymousElementByAttribute(panel, "class", "panel-arrowcontent");
|
||||
let arrow = document.getAnonymousElementByAttribute(panel, "anonid", "arrow");
|
||||
|
||||
manifest: {
|
||||
"browser_action": {
|
||||
"default_popup": "popup.html",
|
||||
"browser_style": false,
|
||||
},
|
||||
|
||||
"page_action": {
|
||||
"default_popup": "popup.html",
|
||||
"browser_style": false,
|
||||
},
|
||||
},
|
||||
|
||||
files: {
|
||||
"popup.html": `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
<body style="width: 100px; height: 100px; background-color: green;">
|
||||
</body>
|
||||
</html>`,
|
||||
},
|
||||
});
|
||||
|
||||
await extension.startup();
|
||||
|
||||
async function testPanel(browser, standAlone) {
|
||||
let panel = getPanelForNode(browser);
|
||||
let arrowContent = document.getAnonymousElementByAttribute(panel, "class", "panel-arrowcontent");
|
||||
let arrow = document.getAnonymousElementByAttribute(panel, "anonid", "arrow");
|
||||
|
||||
let checkArrow = (background = null) => {
|
||||
if (background == null || !standAlone) {
|
||||
ok(!arrow.style.hasOwnProperty("fill"), "Arrow fill should be the default one");
|
||||
return;
|
||||
}
|
||||
|
||||
is(getComputedStyle(arrowContent).backgroundColor, background, "Arrow content should have correct background");
|
||||
is(getComputedStyle(arrow).fill, background, "Arrow should have correct background");
|
||||
};
|
||||
|
||||
function getBackground(browser) {
|
||||
return ContentTask.spawn(browser, null, async function() {
|
||||
return content.getComputedStyle(content.document.body)
|
||||
.backgroundColor;
|
||||
});
|
||||
let checkArrow = (background = null) => {
|
||||
if (background == null || !standAlone) {
|
||||
ok(!arrow.style.hasOwnProperty("fill"), "Arrow fill should be the default one");
|
||||
return;
|
||||
}
|
||||
|
||||
let setBackground = color => {
|
||||
content.document.body.style.backgroundColor = color;
|
||||
};
|
||||
is(getComputedStyle(arrowContent).backgroundColor, background, "Arrow content should have correct background");
|
||||
is(getComputedStyle(arrow).fill, background, "Arrow should have correct background");
|
||||
};
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
info("Test that initial background color is applied");
|
||||
|
||||
checkArrow(await getBackground(browser));
|
||||
|
||||
info("Test that dynamically-changed background color is applied");
|
||||
|
||||
await alterContent(browser, setBackground, "black");
|
||||
|
||||
checkArrow(await getBackground(browser));
|
||||
|
||||
info("Test that non-opaque background color results in default styling");
|
||||
|
||||
await alterContent(browser, setBackground, "rgba(1, 2, 3, .9)");
|
||||
|
||||
checkArrow(null);
|
||||
function getBackground(browser) {
|
||||
return ContentTask.spawn(browser, null, async function() {
|
||||
return content.getComputedStyle(content.document.body)
|
||||
.backgroundColor;
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
info("Test stand-alone browserAction popup");
|
||||
let setBackground = color => {
|
||||
content.document.body.style.backgroundColor = color;
|
||||
};
|
||||
|
||||
clickBrowserAction(extension);
|
||||
let browser = await awaitExtensionPanel(extension);
|
||||
await testPanel(browser, true);
|
||||
await closeBrowserAction(extension);
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
info("Test that initial background color is applied");
|
||||
checkArrow(initial_background);
|
||||
|
||||
info("Test that dynamically-changed background color is applied");
|
||||
await alterContent(browser, setBackground, "black");
|
||||
checkArrow(await getBackground(browser));
|
||||
|
||||
info("Test that non-opaque background color results in default styling");
|
||||
await alterContent(browser, setBackground, "rgba(1, 2, 3, .9)");
|
||||
checkArrow(null);
|
||||
}
|
||||
|
||||
add_task(async function testPopupBackground() {
|
||||
let testCases = [{
|
||||
"browser_style": false,
|
||||
"background": "background-color: green;",
|
||||
"initial_background": "rgb(0, 128, 0)",
|
||||
}, {
|
||||
"browser_style": true,
|
||||
// Use white here instead of transparent, because
|
||||
// when no background is supplied we will fill
|
||||
// with white by default.
|
||||
"initial_background": "rgb(255, 255, 255)",
|
||||
}];
|
||||
for (let testCase of testCases) {
|
||||
info(`Testing browser_style: ${testCase.browser_style} with background? ${!!testCase.background}`);
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
background() {
|
||||
browser.tabs.query({active: true, currentWindow: true}, tabs => {
|
||||
browser.pageAction.show(tabs[0].id);
|
||||
});
|
||||
},
|
||||
|
||||
manifest: {
|
||||
"browser_action": {
|
||||
"default_popup": "popup.html",
|
||||
"browser_style": testCase.browser_style,
|
||||
},
|
||||
|
||||
"page_action": {
|
||||
"default_popup": "popup.html",
|
||||
"browser_style": testCase.browser_style,
|
||||
},
|
||||
},
|
||||
|
||||
files: {
|
||||
"popup.html": `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
<body style="width: 100px; height: 100px; ${testCase.background || ""}">
|
||||
</body>
|
||||
</html>`,
|
||||
},
|
||||
});
|
||||
|
||||
await extension.startup();
|
||||
|
||||
{
|
||||
info("Test stand-alone browserAction popup");
|
||||
|
||||
clickBrowserAction(extension);
|
||||
let browser = await awaitExtensionPanel(extension);
|
||||
await testPanel(browser, true, testCase.initial_background);
|
||||
await closeBrowserAction(extension);
|
||||
}
|
||||
|
||||
{
|
||||
info("Test menu panel browserAction popup");
|
||||
|
||||
let widget = getBrowserActionWidget(extension);
|
||||
CustomizableUI.addWidgetToArea(widget.id, getCustomizableUIPanelID());
|
||||
|
||||
clickBrowserAction(extension);
|
||||
let browser = await awaitExtensionPanel(extension);
|
||||
await testPanel(browser, false, testCase.initial_background);
|
||||
await closeBrowserAction(extension);
|
||||
}
|
||||
|
||||
{
|
||||
info("Test pageAction popup");
|
||||
|
||||
clickPageAction(extension);
|
||||
let browser = await awaitExtensionPanel(extension);
|
||||
await testPanel(browser, true, testCase.initial_background);
|
||||
await closePageAction(extension);
|
||||
}
|
||||
|
||||
await extension.unload();
|
||||
}
|
||||
|
||||
{
|
||||
info("Test menu panel browserAction popup");
|
||||
|
||||
let widget = getBrowserActionWidget(extension);
|
||||
CustomizableUI.addWidgetToArea(widget.id, getCustomizableUIPanelID());
|
||||
|
||||
clickBrowserAction(extension);
|
||||
let browser = await awaitExtensionPanel(extension);
|
||||
await testPanel(browser, false);
|
||||
await closeBrowserAction(extension);
|
||||
}
|
||||
|
||||
{
|
||||
info("Test pageAction popup");
|
||||
|
||||
clickPageAction(extension);
|
||||
let browser = await awaitExtensionPanel(extension);
|
||||
await testPanel(browser, true);
|
||||
await closePageAction(extension);
|
||||
}
|
||||
|
||||
await extension.unload();
|
||||
});
|
||||
|
|
|
@ -710,9 +710,9 @@ BrowserGlue.prototype = {
|
|||
iconURL: "resource:///chrome/browser/content/browser/defaultthemes/dark.icon.svg",
|
||||
textcolor: "white",
|
||||
accentcolor: "black",
|
||||
popup: "hsl(240, 5%, 5%)",
|
||||
popup_text: "rgb(249, 249, 250)",
|
||||
popup_border: "rgba(24, 26, 27, 0.14)",
|
||||
popup: "#4a4a4f",
|
||||
popup_text: "rgba(249, 249, 250, 0.8)",
|
||||
popup_border: "#27272b",
|
||||
author: vendorShortName,
|
||||
});
|
||||
|
||||
|
|
|
@ -9,6 +9,11 @@
|
|||
min-height: 20em;
|
||||
}
|
||||
|
||||
/* Show selected items in high contrast mode. */
|
||||
#sitesList > richlistitem[selected] {
|
||||
outline: 1px solid transparent;
|
||||
}
|
||||
|
||||
#sitesList > richlistitem > hbox,
|
||||
.item-box > label {
|
||||
-moz-box-flex: 1;
|
||||
|
|
|
@ -705,15 +705,6 @@ DevTools.prototype = {
|
|||
getToolboxes() {
|
||||
return Array.from(this._toolboxes.values());
|
||||
},
|
||||
|
||||
/**
|
||||
* Iterator that yields each of the toolboxes.
|
||||
*/
|
||||
* [Symbol.iterator ]() {
|
||||
for (let toolbox of this._toolboxes) {
|
||||
yield toolbox;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const gDevTools = exports.gDevTools = new DevTools();
|
||||
|
|
|
@ -93,11 +93,6 @@ this.gDevTools = {
|
|||
get _tools() {
|
||||
return devtools._tools;
|
||||
},
|
||||
* [Symbol.iterator ]() {
|
||||
for (let toolbox of this._toolboxes) {
|
||||
yield toolbox;
|
||||
}
|
||||
}
|
||||
};
|
||||
gDevToolsMethods.forEach(name => {
|
||||
this.gDevTools[name] = (...args) => {
|
||||
|
|
|
@ -143,7 +143,7 @@ exports.viewSourceInScratchpad = async function(sourceURL, sourceLine) {
|
|||
}
|
||||
|
||||
// For scratchpads within toolbox
|
||||
for (let [, toolbox] of gDevTools) {
|
||||
for (let toolbox of gDevTools.getToolboxes()) {
|
||||
let scratchpadPanel = toolbox.getPanel("scratchpad");
|
||||
if (scratchpadPanel) {
|
||||
let { scratchpad } = scratchpadPanel;
|
||||
|
|
|
@ -4288,6 +4288,16 @@ SourceListener::SetEnabledFor(TrackID aTrackID, bool aEnable)
|
|||
aTrackID == kAudioTrack ? "audio" : "video",
|
||||
aTrackID));
|
||||
|
||||
if (mRemoved) {
|
||||
// Listener was removed between timer resolving and this runnable.
|
||||
return DeviceOperationPromise::CreateAndResolve(NS_ERROR_ABORT, __func__);
|
||||
}
|
||||
|
||||
if (state.mStopped) {
|
||||
// Source was stopped between timer resolving and this runnable.
|
||||
return DeviceOperationPromise::CreateAndResolve(NS_ERROR_ABORT, __func__);
|
||||
}
|
||||
|
||||
state.mDeviceEnabled = aEnable;
|
||||
|
||||
if (mWindowListener) {
|
||||
|
|
|
@ -77,21 +77,15 @@ struct LayersId {
|
|||
return !(*this == aOther);
|
||||
}
|
||||
|
||||
// Helper operators that allow this class to be used as a key in
|
||||
// Helper struct that allow this class to be used as a key in
|
||||
// std::unordered_map like so:
|
||||
// std::unordered_map<LayersId, ValueType, LayersId::HashFn, LayersId::EqualFn> myMap;
|
||||
// std::unordered_map<LayersId, ValueType, LayersId::HashFn> myMap;
|
||||
struct HashFn {
|
||||
std::size_t operator()(const LayersId& aKey) const
|
||||
{
|
||||
return std::hash<uint64_t>{}(aKey.mId);
|
||||
}
|
||||
};
|
||||
struct EqualFn {
|
||||
bool operator()(const LayersId& lhs, const LayersId& rhs) const
|
||||
{
|
||||
return lhs.mId == rhs.mId;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
enum class LayersBackend : int8_t {
|
||||
|
|
|
@ -779,8 +779,7 @@ private:
|
|||
// protected by the mTestDataLock.
|
||||
std::unordered_map<LayersId,
|
||||
UniquePtr<APZTestData>,
|
||||
LayersId::HashFn,
|
||||
LayersId::EqualFn> mTestData;
|
||||
LayersId::HashFn> mTestData;
|
||||
mutable mozilla::Mutex mTestDataLock;
|
||||
|
||||
// This must only be touched on the controller thread.
|
||||
|
|
|
@ -144,8 +144,7 @@ private:
|
|||
// The set of focus targets received indexed by their layer tree ID
|
||||
std::unordered_map<LayersId,
|
||||
FocusTarget,
|
||||
LayersId::HashFn,
|
||||
LayersId::EqualFn> mFocusTree;
|
||||
LayersId::HashFn> mFocusTree;
|
||||
|
||||
// The focus sequence number of the last potentially focus changing event
|
||||
// processed by APZ. This number starts at one and increases monotonically.
|
||||
|
|
|
@ -121,6 +121,9 @@ public class VideoCaptureAndroid implements PreviewCallback, Callback {
|
|||
@Override public void run() {
|
||||
boolean startResult =
|
||||
startCaptureOnCameraThread(width, height, min_mfps, max_mfps);
|
||||
if (!startResult) {
|
||||
Looper.myLooper().quit();
|
||||
}
|
||||
exchange(result, startResult);
|
||||
}
|
||||
});
|
||||
|
@ -316,6 +319,7 @@ public class VideoCaptureAndroid implements PreviewCallback, Callback {
|
|||
cameraThreadHandler.post(new Runnable() {
|
||||
@Override public void run() {
|
||||
boolean stopResult = stopCaptureOnCameraThread();
|
||||
Looper.myLooper().quit();
|
||||
exchange(result, stopResult);
|
||||
}
|
||||
});
|
||||
|
@ -348,7 +352,6 @@ public class VideoCaptureAndroid implements PreviewCallback, Callback {
|
|||
}
|
||||
camera.release();
|
||||
camera = null;
|
||||
Looper.myLooper().quit();
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
error = e;
|
||||
|
@ -356,7 +359,6 @@ public class VideoCaptureAndroid implements PreviewCallback, Callback {
|
|||
error = e;
|
||||
}
|
||||
Log.e(TAG, "Failed to stop camera", error);
|
||||
Looper.myLooper().quit();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -116,7 +116,10 @@ int32_t VideoCaptureAndroid::OnIncomingFrame(uint8_t* videoFrame,
|
|||
size_t videoFrameLength,
|
||||
int32_t degrees,
|
||||
int64_t captureTime) {
|
||||
if (!_captureStarted)
|
||||
// _captureStarted is written on the controlling thread in
|
||||
// StartCapture/StopCapture. This is the camera thread.
|
||||
// CaptureStarted() will access it under a lock.
|
||||
if (!CaptureStarted())
|
||||
return 0;
|
||||
|
||||
VideoRotation current_rotation =
|
||||
|
@ -185,7 +188,7 @@ VideoCaptureAndroid::~VideoCaptureAndroid() {
|
|||
|
||||
int32_t VideoCaptureAndroid::StartCapture(
|
||||
const VideoCaptureCapability& capability) {
|
||||
CriticalSectionScoped cs(&_apiCs);
|
||||
_apiCs.Enter();
|
||||
AttachThreadScoped ats(g_jvm_capture);
|
||||
JNIEnv* env = ats.env();
|
||||
|
||||
|
@ -194,23 +197,32 @@ int32_t VideoCaptureAndroid::StartCapture(
|
|||
WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, -1,
|
||||
"%s: GetBestMatchedCapability failed: %dx%d",
|
||||
__FUNCTION__, capability.width, capability.height);
|
||||
// Manual exit of critical section
|
||||
_apiCs.Leave();
|
||||
return -1;
|
||||
}
|
||||
|
||||
_captureDelay = _captureCapability.expectedCaptureDelay;
|
||||
|
||||
jmethodID j_start =
|
||||
env->GetMethodID(g_java_capturer_class, "startCapture", "(IIII)Z");
|
||||
assert(j_start);
|
||||
int width = _captureCapability.width;
|
||||
int height = _captureCapability.height;
|
||||
int min_mfps = 0;
|
||||
int max_mfps = 0;
|
||||
_deviceInfo.GetMFpsRange(_deviceUniqueId, _captureCapability.maxFPS,
|
||||
&min_mfps, &max_mfps);
|
||||
|
||||
// Exit critical section to avoid blocking camera thread inside
|
||||
// onIncomingFrame() call.
|
||||
_apiCs.Leave();
|
||||
|
||||
jmethodID j_start =
|
||||
env->GetMethodID(g_java_capturer_class, "startCapture", "(IIII)Z");
|
||||
assert(j_start);
|
||||
bool started = env->CallBooleanMethod(_jCapturer, j_start,
|
||||
_captureCapability.width,
|
||||
_captureCapability.height,
|
||||
width, height,
|
||||
min_mfps, max_mfps);
|
||||
if (started) {
|
||||
CriticalSectionScoped cs(&_apiCs);
|
||||
_requestedCapability = capability;
|
||||
_captureStarted = true;
|
||||
}
|
||||
|
|
|
@ -30,11 +30,11 @@ class GeckoViewSelectionActionContent extends GeckoViewContentModule {
|
|||
|
||||
this._actions = [{
|
||||
id: "org.mozilla.geckoview.CUT",
|
||||
predicate: e => !e.collapsed && e.selectionEditable,
|
||||
predicate: e => !e.collapsed && e.selectionEditable && !this._isPasswordField(e),
|
||||
perform: _ => this._domWindowUtils.sendContentCommandEvent("cut"),
|
||||
}, {
|
||||
id: "org.mozilla.geckoview.COPY",
|
||||
predicate: e => !e.collapsed,
|
||||
predicate: e => !e.collapsed && !this._isPasswordField(e),
|
||||
perform: _ => this._domWindowUtils.sendContentCommandEvent("copy"),
|
||||
}, {
|
||||
id: "org.mozilla.geckoview.PASTE",
|
||||
|
@ -70,6 +70,18 @@ class GeckoViewSelectionActionContent extends GeckoViewContentModule {
|
|||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
}
|
||||
|
||||
_isPasswordField(aEvent) {
|
||||
if (!aEvent.selectionEditable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const win = aEvent.target.defaultView;
|
||||
const focus = aEvent.target.activeElement;
|
||||
return win && win.HTMLInputElement &&
|
||||
focus instanceof win.HTMLInputElement &&
|
||||
!focus.mozIsTextField(/* excludePassword */ true);
|
||||
}
|
||||
|
||||
_getSelectionController(aEvent) {
|
||||
if (aEvent.selectionEditable) {
|
||||
const focus = aEvent.target.activeElement;
|
||||
|
@ -118,12 +130,12 @@ class GeckoViewSelectionActionContent extends GeckoViewContentModule {
|
|||
|
||||
register() {
|
||||
debug("register");
|
||||
addEventListener("mozcaretstatechanged", this);
|
||||
addEventListener("mozcaretstatechanged", this, { mozSystemGroup: true });
|
||||
}
|
||||
|
||||
unregister() {
|
||||
debug("unregister");
|
||||
removeEventListener("mozcaretstatechanged", this);
|
||||
removeEventListener("mozcaretstatechanged", this, { mozSystemGroup: true });
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -136,8 +148,7 @@ class GeckoViewSelectionActionContent extends GeckoViewContentModule {
|
|||
if (this._isActive && !aEvent.caretVisible) {
|
||||
// For mozcaretstatechanged, "visibilitychange" means the caret is hidden.
|
||||
reason = "visibilitychange";
|
||||
} else if (this._isActive &&
|
||||
!aEvent.collapsed &&
|
||||
} else if (!aEvent.collapsed &&
|
||||
!aEvent.selectionVisible) {
|
||||
reason = "invisibleselection";
|
||||
} else if (aEvent.selectionEditable &&
|
||||
|
@ -161,13 +172,15 @@ class GeckoViewSelectionActionContent extends GeckoViewContentModule {
|
|||
action => action.predicate.call(this, aEvent));
|
||||
|
||||
const offset = this._getFrameOffset(aEvent);
|
||||
const password = this._isPasswordField(aEvent);
|
||||
|
||||
const msg = {
|
||||
type: "GeckoView:ShowSelectionAction",
|
||||
seqNo: this._seqNo,
|
||||
collapsed: aEvent.collapsed,
|
||||
editable: aEvent.selectionEditable,
|
||||
selection: aEvent.selectedTextContent,
|
||||
password,
|
||||
selection: password ? "" : aEvent.selectedTextContent,
|
||||
clientRect: !aEvent.boundingClientRect ? null : {
|
||||
left: aEvent.boundingClientRect.left + offset.left,
|
||||
top: aEvent.boundingClientRect.top + offset.top,
|
||||
|
|
|
@ -156,14 +156,18 @@ this.browserAction = class extends ExtensionAPI {
|
|||
|
||||
return {
|
||||
browserAction: {
|
||||
onClicked: new EventManager(context, "browserAction.onClicked", fire => {
|
||||
let listener = (event, tab) => {
|
||||
fire.async(tabManager.convert(tab));
|
||||
};
|
||||
browserActionMap.get(extension).on("click", listener);
|
||||
return () => {
|
||||
browserActionMap.get(extension).off("click", listener);
|
||||
};
|
||||
onClicked: new EventManager({
|
||||
context,
|
||||
name: "browserAction.onClicked",
|
||||
register: fire => {
|
||||
let listener = (event, tab) => {
|
||||
fire.async(tabManager.convert(tab));
|
||||
};
|
||||
browserActionMap.get(extension).on("click", listener);
|
||||
return () => {
|
||||
browserActionMap.get(extension).off("click", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
setTitle: function(details) {
|
||||
|
|
|
@ -232,14 +232,18 @@ this.pageAction = class extends ExtensionAPI {
|
|||
|
||||
return {
|
||||
pageAction: {
|
||||
onClicked: new EventManager(context, "pageAction.onClicked", fire => {
|
||||
let listener = (event, tab) => {
|
||||
fire.async(tabManager.convert(tab));
|
||||
};
|
||||
pageActionMap.get(extension).on("click", listener);
|
||||
return () => {
|
||||
pageActionMap.get(extension).off("click", listener);
|
||||
};
|
||||
onClicked: new EventManager({
|
||||
context,
|
||||
name: "pageAction.onClicked",
|
||||
register: fire => {
|
||||
let listener = (event, tab) => {
|
||||
fire.async(tabManager.convert(tab));
|
||||
};
|
||||
pageActionMap.get(extension).on("click", listener);
|
||||
return () => {
|
||||
pageActionMap.get(extension).off("click", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
show(tabId) {
|
||||
|
|
|
@ -103,21 +103,25 @@ this.tabs = class extends ExtensionAPI {
|
|||
|
||||
let self = {
|
||||
tabs: {
|
||||
onActivated: new GlobalEventManager(context, "tabs.onActivated", "Tab:Selected", (fire, data) => {
|
||||
onActivated: makeGlobalEvent(context, "tabs.onActivated", "Tab:Selected", (fire, data) => {
|
||||
let tab = tabManager.get(data.id);
|
||||
|
||||
fire.async({tabId: tab.id, windowId: tab.windowId});
|
||||
}).api(),
|
||||
}),
|
||||
|
||||
onCreated: new EventManager(context, "tabs.onCreated", fire => {
|
||||
let listener = (eventName, event) => {
|
||||
fire.async(tabManager.convert(event.nativeTab));
|
||||
};
|
||||
onCreated: new EventManager({
|
||||
context,
|
||||
name: "tabs.onCreated",
|
||||
register: fire => {
|
||||
let listener = (eventName, event) => {
|
||||
fire.async(tabManager.convert(event.nativeTab));
|
||||
};
|
||||
|
||||
tabTracker.on("tab-created", listener);
|
||||
return () => {
|
||||
tabTracker.off("tab-created", listener);
|
||||
};
|
||||
tabTracker.on("tab-created", listener);
|
||||
return () => {
|
||||
tabTracker.off("tab-created", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
/**
|
||||
|
@ -126,114 +130,130 @@ this.tabs = class extends ExtensionAPI {
|
|||
* the tabId in an array to match the API.
|
||||
* @see https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/Tabs/onHighlighted
|
||||
*/
|
||||
onHighlighted: new GlobalEventManager(context, "tabs.onHighlighted", "Tab:Selected", (fire, data) => {
|
||||
onHighlighted: makeGlobalEvent(context, "tabs.onHighlighted", "Tab:Selected", (fire, data) => {
|
||||
let tab = tabManager.get(data.id);
|
||||
|
||||
fire.async({tabIds: [tab.id], windowId: tab.windowId});
|
||||
}),
|
||||
|
||||
onAttached: new EventManager({
|
||||
context,
|
||||
name: "tabs.onAttached",
|
||||
register: fire => { return () => {}; },
|
||||
}).api(),
|
||||
|
||||
onAttached: new EventManager(context, "tabs.onAttached", fire => {
|
||||
return () => {};
|
||||
onDetached: new EventManager({
|
||||
context,
|
||||
name: "tabs.onDetached",
|
||||
register: fire => { return () => {}; },
|
||||
}).api(),
|
||||
|
||||
onDetached: new EventManager(context, "tabs.onDetached", fire => {
|
||||
return () => {};
|
||||
onRemoved: new EventManager({
|
||||
context,
|
||||
name: "tabs.onRemoved",
|
||||
register: fire => {
|
||||
let listener = (eventName, event) => {
|
||||
fire.async(event.tabId, {windowId: event.windowId, isWindowClosing: event.isWindowClosing});
|
||||
};
|
||||
|
||||
tabTracker.on("tab-removed", listener);
|
||||
return () => {
|
||||
tabTracker.off("tab-removed", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onRemoved: new EventManager(context, "tabs.onRemoved", fire => {
|
||||
let listener = (eventName, event) => {
|
||||
fire.async(event.tabId, {windowId: event.windowId, isWindowClosing: event.isWindowClosing});
|
||||
};
|
||||
|
||||
tabTracker.on("tab-removed", listener);
|
||||
return () => {
|
||||
tabTracker.off("tab-removed", listener);
|
||||
};
|
||||
onReplaced: new EventManager({
|
||||
context,
|
||||
name: "tabs.onReplaced",
|
||||
register: fire => { return () => {}; },
|
||||
}).api(),
|
||||
|
||||
onReplaced: new EventManager(context, "tabs.onReplaced", fire => {
|
||||
return () => {};
|
||||
onMoved: new EventManager({
|
||||
context,
|
||||
name: "tabs.onMoved",
|
||||
register: fire => { return () => {}; },
|
||||
}).api(),
|
||||
|
||||
onMoved: new EventManager(context, "tabs.onMoved", fire => {
|
||||
return () => {};
|
||||
}).api(),
|
||||
onUpdated: new EventManager({
|
||||
context,
|
||||
name: "tabs.onUpdated",
|
||||
register: fire => {
|
||||
const restricted = ["url", "favIconUrl", "title"];
|
||||
|
||||
onUpdated: new EventManager(context, "tabs.onUpdated", fire => {
|
||||
const restricted = ["url", "favIconUrl", "title"];
|
||||
|
||||
function sanitize(extension, changeInfo) {
|
||||
let result = {};
|
||||
let nonempty = false;
|
||||
for (let prop in changeInfo) {
|
||||
if (extension.hasPermission("tabs") || !restricted.includes(prop)) {
|
||||
nonempty = true;
|
||||
result[prop] = changeInfo[prop];
|
||||
function sanitize(extension, changeInfo) {
|
||||
let result = {};
|
||||
let nonempty = false;
|
||||
for (let prop in changeInfo) {
|
||||
if (extension.hasPermission("tabs") || !restricted.includes(prop)) {
|
||||
nonempty = true;
|
||||
result[prop] = changeInfo[prop];
|
||||
}
|
||||
}
|
||||
return [nonempty, result];
|
||||
}
|
||||
return [nonempty, result];
|
||||
}
|
||||
|
||||
let fireForTab = (tab, changed) => {
|
||||
let [needed, changeInfo] = sanitize(extension, changed);
|
||||
if (needed) {
|
||||
fire.async(tab.id, changeInfo, tab.convert());
|
||||
}
|
||||
};
|
||||
let fireForTab = (tab, changed) => {
|
||||
let [needed, changeInfo] = sanitize(extension, changed);
|
||||
if (needed) {
|
||||
fire.async(tab.id, changeInfo, tab.convert());
|
||||
}
|
||||
};
|
||||
|
||||
let listener = event => {
|
||||
let needed = [];
|
||||
let nativeTab;
|
||||
switch (event.type) {
|
||||
case "DOMTitleChanged": {
|
||||
let {BrowserApp} = getBrowserWindow(event.target.ownerGlobal);
|
||||
let listener = event => {
|
||||
let needed = [];
|
||||
let nativeTab;
|
||||
switch (event.type) {
|
||||
case "DOMTitleChanged": {
|
||||
let {BrowserApp} = getBrowserWindow(event.target.ownerGlobal);
|
||||
|
||||
nativeTab = BrowserApp.getTabForWindow(event.target.ownerGlobal);
|
||||
needed.push("title");
|
||||
break;
|
||||
nativeTab = BrowserApp.getTabForWindow(event.target.ownerGlobal);
|
||||
needed.push("title");
|
||||
break;
|
||||
}
|
||||
|
||||
case "DOMAudioPlaybackStarted":
|
||||
case "DOMAudioPlaybackStopped": {
|
||||
let {BrowserApp} = event.target.ownerGlobal;
|
||||
nativeTab = BrowserApp.getTabForBrowser(event.originalTarget);
|
||||
needed.push("audible");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
case "DOMAudioPlaybackStarted":
|
||||
case "DOMAudioPlaybackStopped": {
|
||||
let {BrowserApp} = event.target.ownerGlobal;
|
||||
nativeTab = BrowserApp.getTabForBrowser(event.originalTarget);
|
||||
needed.push("audible");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!nativeTab) {
|
||||
return;
|
||||
}
|
||||
|
||||
let tab = tabManager.getWrapper(nativeTab);
|
||||
let changeInfo = {};
|
||||
for (let prop of needed) {
|
||||
changeInfo[prop] = tab[prop];
|
||||
}
|
||||
|
||||
fireForTab(tab, changeInfo);
|
||||
};
|
||||
|
||||
let statusListener = ({browser, status, url}) => {
|
||||
let {BrowserApp} = browser.ownerGlobal;
|
||||
let nativeTab = BrowserApp.getTabForBrowser(browser);
|
||||
if (nativeTab) {
|
||||
let changed = {status};
|
||||
if (url) {
|
||||
changed.url = url;
|
||||
if (!nativeTab) {
|
||||
return;
|
||||
}
|
||||
|
||||
fireForTab(tabManager.wrapTab(nativeTab), changed);
|
||||
}
|
||||
};
|
||||
let tab = tabManager.getWrapper(nativeTab);
|
||||
let changeInfo = {};
|
||||
for (let prop of needed) {
|
||||
changeInfo[prop] = tab[prop];
|
||||
}
|
||||
|
||||
windowTracker.addListener("status", statusListener);
|
||||
windowTracker.addListener("DOMTitleChanged", listener);
|
||||
return () => {
|
||||
windowTracker.removeListener("status", statusListener);
|
||||
windowTracker.removeListener("DOMTitleChanged", listener);
|
||||
};
|
||||
fireForTab(tab, changeInfo);
|
||||
};
|
||||
|
||||
let statusListener = ({browser, status, url}) => {
|
||||
let {BrowserApp} = browser.ownerGlobal;
|
||||
let nativeTab = BrowserApp.getTabForBrowser(browser);
|
||||
if (nativeTab) {
|
||||
let changed = {status};
|
||||
if (url) {
|
||||
changed.url = url;
|
||||
}
|
||||
|
||||
fireForTab(tabManager.wrapTab(nativeTab), changed);
|
||||
}
|
||||
};
|
||||
|
||||
windowTracker.addListener("status", statusListener);
|
||||
windowTracker.addListener("DOMTitleChanged", listener);
|
||||
return () => {
|
||||
windowTracker.removeListener("status", statusListener);
|
||||
windowTracker.removeListener("DOMTitleChanged", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
async create(createProperties) {
|
||||
|
|
|
@ -187,11 +187,11 @@ class WindowTracker extends WindowTrackerBase {
|
|||
}
|
||||
|
||||
/**
|
||||
* An event manager API provider which listens for an event in the Android
|
||||
* global EventDispatcher, and calls the given listener function whenever an event
|
||||
* is received. That listener function receives a `fire` object, which it can
|
||||
* use to dispatch events to the extension, and an object detailing the
|
||||
* EventDispatcher event that was received.
|
||||
* Helper to create an event manager which listens for an event in the Android
|
||||
* global EventDispatcher, and calls the given listener function whenever the
|
||||
* event is received. That listener function receives a `fire` object,
|
||||
* which it can use to dispatch events to the extension, and an object
|
||||
* detailing the EventDispatcher event that was received.
|
||||
*
|
||||
* @param {BaseContext} context
|
||||
* The extension context which the event manager belongs to.
|
||||
|
@ -202,10 +202,14 @@ class WindowTracker extends WindowTrackerBase {
|
|||
* @param {function} listener
|
||||
* The listener function to call when an EventDispatcher event is
|
||||
* recieved.
|
||||
*
|
||||
* @returns {object} An injectable api for the new event.
|
||||
*/
|
||||
global.GlobalEventManager = class extends EventManager {
|
||||
constructor(context, name, event, listener) {
|
||||
super(context, name, fire => {
|
||||
global.makeGlobalEvent = function makeGlobalEvent(context, name, event, listener) {
|
||||
return new EventManager({
|
||||
context,
|
||||
name,
|
||||
register: fire => {
|
||||
let listener2 = {
|
||||
onEvent(event, data, callback) {
|
||||
listener(fire, data);
|
||||
|
@ -216,36 +220,8 @@ global.GlobalEventManager = class extends EventManager {
|
|||
return () => {
|
||||
GlobalEventDispatcher.unregisterListener(listener2, [event]);
|
||||
};
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* An event manager API provider which listens for a DOM event in any browser
|
||||
* window, and calls the given listener function whenever an event is received.
|
||||
* That listener function receives a `fire` object, which it can use to dispatch
|
||||
* events to the extension, and a DOM event object.
|
||||
*
|
||||
* @param {BaseContext} context
|
||||
* The extension context which the event manager belongs to.
|
||||
* @param {string} name
|
||||
* The API name of the event manager, e.g.,"runtime.onMessage".
|
||||
* @param {string} event
|
||||
* The name of the DOM event to listen for.
|
||||
* @param {function} listener
|
||||
* The listener function to call when a DOM event is received.
|
||||
*/
|
||||
global.WindowEventManager = class extends EventManager {
|
||||
constructor(context, name, event, listener) {
|
||||
super(context, name, fire => {
|
||||
let listener2 = listener.bind(null, fire);
|
||||
|
||||
windowTracker.addListener(event, listener2);
|
||||
return () => {
|
||||
windowTracker.removeListener(event, listener2);
|
||||
};
|
||||
});
|
||||
}
|
||||
},
|
||||
}).api();
|
||||
};
|
||||
|
||||
class TabTracker extends TabTrackerBase {
|
||||
|
|
|
@ -44,6 +44,8 @@ open class BaseSessionTest(noErrorCollector: Boolean = false) {
|
|||
}
|
||||
}
|
||||
|
||||
fun <T> forEachCall(vararg values: T): T = sessionRule.forEachCall(*values)
|
||||
|
||||
fun GeckoSession.getTestBytes(path: String) =
|
||||
InstrumentationRegistry.getTargetContext().resources.assets
|
||||
.open(path.removePrefix("/assets/")).readBytes()
|
||||
|
|
|
@ -22,12 +22,11 @@ class ContentDelegateTest : BaseSessionTest() {
|
|||
@Test fun titleChange() {
|
||||
sessionRule.session.loadTestPath(TITLE_CHANGE_HTML_PATH)
|
||||
|
||||
val titles = mutableListOf("Title1", "Title2")
|
||||
sessionRule.waitUntilCalled(object : Callbacks.ContentDelegate {
|
||||
@AssertCalled(count = 2)
|
||||
override fun onTitleChange(session: GeckoSession, title: String) {
|
||||
assertThat("Title should match", title,
|
||||
equalTo(titles.removeAt(0)))
|
||||
equalTo(forEachCall("Title1", "Title2")))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -245,7 +245,7 @@ class GeckoSessionTestRuleTest : BaseSessionTest(noErrorCollector = true) {
|
|||
val info = sessionRule.currentCall
|
||||
assertThat("Method info should be valid", info, notNullValue())
|
||||
assertThat("Counter should be correct",
|
||||
info.counter, isOneOf(1, 2))
|
||||
info.counter, equalTo(forEachCall(1, 2)))
|
||||
assertThat("Order should equal counter",
|
||||
info.order, equalTo(info.counter))
|
||||
counter++
|
||||
|
|
|
@ -93,8 +93,7 @@ class ProgressDelegateTest : BaseSessionTest() {
|
|||
@AssertCalled(count = 2, order = intArrayOf(1, 3))
|
||||
override fun onPageStart(session: GeckoSession, url: String) {
|
||||
assertThat("URL should match", url,
|
||||
endsWith(if (sessionRule.currentCall.counter == 1)
|
||||
INVALID_URI else HELLO_HTML_PATH))
|
||||
endsWith(forEachCall(INVALID_URI, HELLO_HTML_PATH)))
|
||||
}
|
||||
|
||||
@AssertCalled(count = 2, order = intArrayOf(2, 4))
|
||||
|
@ -102,7 +101,7 @@ class ProgressDelegateTest : BaseSessionTest() {
|
|||
// The first load is certain to fail because of interruption by the second load
|
||||
// or by invalid domain name, whereas the second load is certain to succeed.
|
||||
assertThat("Success flag should match", success,
|
||||
equalTo(sessionRule.currentCall.counter != 1))
|
||||
equalTo(forEachCall(false, true)))
|
||||
};
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1248,8 +1248,9 @@ public class GeckoSessionTestRule extends UiThreadTestRule {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get information about the current call. Only valid during a {@link #forCallbacksDuringWait}
|
||||
* callback.
|
||||
* Get information about the current call. Only valid during a {@link
|
||||
* #forCallbacksDuringWait}, {@link #delegateDuringNextWait}, or {@link
|
||||
* #delegateUntilTestEnd} callback.
|
||||
*
|
||||
* @return Call information
|
||||
*/
|
||||
|
@ -1393,4 +1394,22 @@ public class GeckoSessionTestRule extends UiThreadTestRule {
|
|||
public GeckoSession createClosedSession(final GeckoSessionSettings settings) {
|
||||
return createSession(settings, /* open */ false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a value from the given array indexed by the current call counter. Only valid
|
||||
* during a {@link #forCallbacksDuringWait}, {@link #delegateDuringNextWait}, or
|
||||
* {@link #delegateUntilTestEnd} callback.
|
||||
* <p><p>
|
||||
* Asserts that {@code foo} is equal to {@code "bar"} during the first call and {@code
|
||||
* "baz"} during the second call:
|
||||
* <pre>{@code assertThat("Foo should match", foo, equalTo(forEachCall("bar",
|
||||
* "baz")));}</pre>
|
||||
*
|
||||
* @param values Input array
|
||||
* @return Value from input array indexed by the current call counter.
|
||||
*/
|
||||
public <T> T forEachCall(T... values) {
|
||||
assertThat("Should be in a method call", mCurrentMethodCall, notNullValue());
|
||||
return values[Math.min(mCurrentMethodCall.getCurrentCount(), values.length) - 1];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -133,6 +133,9 @@ public class BasicSelectionActionDelegate implements ActionMode.Callback,
|
|||
* @return True if the action was performed.
|
||||
*/
|
||||
protected boolean performAction(final String id) {
|
||||
if (mResponse == null) {
|
||||
return false;
|
||||
}
|
||||
mResponse.respond(id);
|
||||
|
||||
// Android behavior is to clear selection on copy.
|
||||
|
|
|
@ -1812,6 +1812,10 @@ public class GeckoSession extends LayerSession
|
|||
* contentEditable node.
|
||||
*/
|
||||
final int FLAG_IS_EDITABLE = 2;
|
||||
/**
|
||||
* The selection is inside a password field.
|
||||
*/
|
||||
final int FLAG_IS_PASSWORD = 4;
|
||||
|
||||
@StringDef({ACTION_CUT,
|
||||
ACTION_COPY,
|
||||
|
@ -1887,7 +1891,9 @@ public class GeckoSession extends LayerSession
|
|||
flags = (bundle.getBoolean("collapsed") ?
|
||||
SelectionActionDelegate.FLAG_IS_COLLAPSED : 0) |
|
||||
(bundle.getBoolean("editable") ?
|
||||
SelectionActionDelegate.FLAG_IS_EDITABLE : 0);
|
||||
SelectionActionDelegate.FLAG_IS_EDITABLE : 0) |
|
||||
(bundle.getBoolean("password") ?
|
||||
SelectionActionDelegate.FLAG_IS_PASSWORD : 0);
|
||||
text = bundle.getString("selection");
|
||||
|
||||
final GeckoBundle rectBundle = bundle.getBundle("clientRect");
|
||||
|
|
|
@ -50,6 +50,8 @@ var ActionBarHandler = {
|
|||
* (mozcaretstatechanged) events.
|
||||
*/
|
||||
handleEvent: function(e) {
|
||||
e.stopImmediatePropagation();
|
||||
|
||||
// Close an open ActionBar, if carets no longer logically visible.
|
||||
if (this._selectionID && !e.caretVisible) {
|
||||
this._uninit(false);
|
||||
|
|
|
@ -1439,7 +1439,7 @@ pref("privacy.resistFingerprinting.autoDeclineNoUserInputCanvasPrompts", true);
|
|||
// File.lastModified, audioContext.currentTime, canvas.captureStream.currentTime
|
||||
pref("privacy.reduceTimerPrecision", true);
|
||||
// Dynamically tune the resolution of the timer reduction for both of the two above prefs
|
||||
pref("privacy.resistFingerprinting.reduceTimerPrecision.microseconds", 100);
|
||||
pref("privacy.resistFingerprinting.reduceTimerPrecision.microseconds", 1000);
|
||||
// Enable jittering the clock one precision value forward
|
||||
pref("privacy.resistFingerprinting.reduceTimerPrecision.jitter", true);
|
||||
// Lower the priority of network loads for resources on the tracking protection list.
|
||||
|
@ -5051,6 +5051,8 @@ pref("extensions.webextensions.protocol.remote", true);
|
|||
// Disable tab hiding API by default.
|
||||
pref("extensions.webextensions.tabhide.enabled", false);
|
||||
|
||||
pref("extensions.webextensions.background-delayed-startup", false);
|
||||
|
||||
// Report Site Issue button
|
||||
pref("extensions.webcompat-reporter.newIssueEndpoint", "https://webcompat.com/issues/new");
|
||||
#if defined(MOZ_DEV_EDITION) || defined(NIGHTLY_BUILD)
|
||||
|
|
|
@ -436,6 +436,38 @@ static uint32_t get32bit(unsigned char *aData, int index)
|
|||
(aData[index+2] << 8) | aData[index+3];
|
||||
}
|
||||
|
||||
nsresult
|
||||
TRR::PassQName(unsigned int &index)
|
||||
{
|
||||
uint8_t length;
|
||||
do {
|
||||
if (mBodySize < (index + 1)) {
|
||||
LOG(("TRR: PassQName:%d fail at index %d\n", __LINE__, index));
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
length = static_cast<uint8_t>(mResponse[index]);
|
||||
if ((length & 0xc0) == 0xc0) {
|
||||
// name pointer, advance over it and be done
|
||||
if (mBodySize < (index + 2)) {
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
index += 2;
|
||||
break;
|
||||
}
|
||||
if (length & 0xc0) {
|
||||
LOG(("TRR: illegal label length byte (%x) at index %d\n", length, index));
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
// pass label
|
||||
if (mBodySize < (index + 1 + length)) {
|
||||
LOG(("TRR: PassQName:%d fail at index %d\n", __LINE__, index));
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
index += 1 + length;
|
||||
} while (length);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
//
|
||||
// DohDecode() collects the TTL and the IP addresses in the response
|
||||
//
|
||||
|
@ -458,6 +490,7 @@ TRR::DohDecode()
|
|||
unsigned int index = 12;
|
||||
uint8_t length;
|
||||
nsAutoCString host;
|
||||
nsresult rv;
|
||||
|
||||
LOG(("doh decode %s %d bytes\n", mHost.get(), mBodySize));
|
||||
|
||||
|
@ -506,34 +539,9 @@ TRR::DohDecode()
|
|||
answerRecords, mBodySize, host.get(), index));
|
||||
|
||||
while (answerRecords) {
|
||||
if (mBodySize < (index + 1)) {
|
||||
LOG(("TRR: Dohdecode:%d fail at index %d\n", __LINE__, index));
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
length = static_cast<uint8_t>(mResponse[index]);
|
||||
if ((length & 0xc0) == 0xc0) {
|
||||
// name pointer, advance over it
|
||||
if (mBodySize < (index + 2)) {
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
index += 2;
|
||||
} else if (length & 0xc0) {
|
||||
// illegal length, bail out
|
||||
LOG(("TRR: illegal label length byte (%x)\n", length));
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
} else {
|
||||
// iterate over host name in answer
|
||||
do {
|
||||
if (mBodySize < (index + 1)) {
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
length = static_cast<uint8_t>(mResponse[index]);
|
||||
if (mBodySize < (index + 1 + length)) {
|
||||
LOG(("TRR: Dohdecode:%d fail at index %d\n", __LINE__, index));
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
index += 1 + length;
|
||||
} while (length);
|
||||
rv = PassQName(index);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
// 16 bit TYPE
|
||||
if (mBodySize < (index + 2)) {
|
||||
|
@ -590,7 +598,6 @@ TRR::DohDecode()
|
|||
// - AAAA (TYPE 28): 16 bytes
|
||||
// - NS (TYPE 2): N bytes
|
||||
|
||||
nsresult rv;
|
||||
switch(TYPE) {
|
||||
case TRRTYPE_A:
|
||||
if (RDLENGTH != 4) {
|
||||
|
@ -686,33 +693,9 @@ TRR::DohDecode()
|
|||
uint16_t nsRecords = get16bit(mResponse, 8);
|
||||
LOG(("TRR Decode: %d ns records (%u bytes body)\n", nsRecords, mBodySize));
|
||||
while (nsRecords) {
|
||||
if (mBodySize < (index + 1)) {
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
length = static_cast<uint8_t>(mResponse[index]);
|
||||
if ((length & 0xc0) == 0xc0) {
|
||||
// name pointer, advance over it
|
||||
if (mBodySize < (index + 2)) {
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
index += 2;
|
||||
} else if (length & 0xc0) {
|
||||
// illegal length, bail out
|
||||
LOG(("TRR: illegal label length byte (%x)\n", length));
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
} else {
|
||||
// iterate over host name in answer
|
||||
do {
|
||||
if (mBodySize < (index + 1)) {
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
length = static_cast<uint8_t>(mResponse[index]);
|
||||
if (mBodySize < (index + 1 + length)) {
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
index += 1 + length;
|
||||
LOG(("TRR: move over %d bytes\n", 1 + length));
|
||||
} while (length);
|
||||
rv = PassQName(index);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
if (mBodySize < (index + 8)) {
|
||||
|
@ -741,33 +724,9 @@ TRR::DohDecode()
|
|||
LOG(("TRR Decode: %d additional resource records (%u bytes body)\n",
|
||||
arRecords, mBodySize));
|
||||
while (arRecords) {
|
||||
if (mBodySize < (index + 1)) {
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
length = static_cast<uint8_t>(mResponse[index]);
|
||||
if ((length & 0xc0) == 0xc0) {
|
||||
// name pointer, advance over it
|
||||
if (mBodySize < (index + 2)) {
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
index += 2;
|
||||
} else if (length & 0xc0) {
|
||||
// illegal length, bail out
|
||||
LOG(("TRR: illegal label length byte (%x)\n", length));
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
} else {
|
||||
// iterate over host name in answer
|
||||
do {
|
||||
if (mBodySize < (index + 1)) {
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
length = static_cast<uint8_t>(mResponse[index]);
|
||||
if (mBodySize < (index + 1 + length)) {
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
index += 1 + length;
|
||||
LOG(("TRR: move over %d bytes\n", 1 + length));
|
||||
} while (length);
|
||||
rv = PassQName(index);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
if (mBodySize < (index + 8)) {
|
||||
|
|
|
@ -142,6 +142,7 @@ private:
|
|||
~TRR() = default;
|
||||
nsresult SendHTTPRequest();
|
||||
nsresult DohEncode(nsCString &target);
|
||||
nsresult PassQName(unsigned int &index);
|
||||
nsresult DohDecode();
|
||||
nsresult ReturnData();
|
||||
nsresult FailData();
|
||||
|
|
|
@ -240,11 +240,12 @@ class TupOnly(CommonBackend, PartialBackend):
|
|||
for l in libs]
|
||||
|
||||
def _gen_shared_library(self, backend_file):
|
||||
if backend_file.shared_lib.name == 'libxul.so':
|
||||
shlib = backend_file.shared_lib
|
||||
if shlib.name == 'libxul.so':
|
||||
# This will fail to link currently due to missing rust symbols.
|
||||
return
|
||||
|
||||
if backend_file.shared_lib.cxx_link:
|
||||
if shlib.cxx_link:
|
||||
mkshlib = (
|
||||
[backend_file.environment.substs['CXX']] +
|
||||
backend_file.local_flags['CXX_LDFLAGS']
|
||||
|
@ -258,15 +259,15 @@ class TupOnly(CommonBackend, PartialBackend):
|
|||
mkshlib += (
|
||||
backend_file.environment.substs['DSO_PIC_CFLAGS'] +
|
||||
[backend_file.environment.substs['DSO_LDOPTS']] +
|
||||
['-Wl,-h,%s' % backend_file.shared_lib.soname] +
|
||||
['-o', backend_file.shared_lib.lib_name]
|
||||
['-Wl,-h,%s' % shlib.soname] +
|
||||
['-o', shlib.lib_name]
|
||||
)
|
||||
|
||||
objs, _, shared_libs, os_libs, static_libs = self._expand_libs(backend_file.shared_lib)
|
||||
objs, _, shared_libs, os_libs, static_libs = self._expand_libs(shlib)
|
||||
static_libs = self._lib_paths(backend_file.objdir, static_libs)
|
||||
shared_libs = self._lib_paths(backend_file.objdir, shared_libs)
|
||||
|
||||
list_file_name = '%s.list' % backend_file.shared_lib.name.replace('.', '_')
|
||||
list_file_name = '%s.list' % shlib.name.replace('.', '_')
|
||||
list_file = self._make_list_file(backend_file.objdir, objs, list_file_name)
|
||||
|
||||
inputs = objs + static_libs + shared_libs
|
||||
|
@ -275,10 +276,10 @@ class TupOnly(CommonBackend, PartialBackend):
|
|||
return
|
||||
|
||||
symbols_file = []
|
||||
if backend_file.shared_lib.symbols_file:
|
||||
inputs.append(backend_file.shared_lib.symbols_file)
|
||||
if shlib.symbols_file:
|
||||
inputs.append(shlib.symbols_file)
|
||||
# TODO: Assumes GNU LD
|
||||
symbols_file = ['-Wl,--version-script,%s' % backend_file.shared_lib.symbols_file]
|
||||
symbols_file = ['-Wl,--version-script,%s' % shlib.symbols_file]
|
||||
|
||||
cmd = (
|
||||
mkshlib +
|
||||
|
@ -293,9 +294,14 @@ class TupOnly(CommonBackend, PartialBackend):
|
|||
backend_file.rule(
|
||||
cmd=cmd,
|
||||
inputs=inputs,
|
||||
outputs=[backend_file.shared_lib.lib_name],
|
||||
outputs=[shlib.lib_name],
|
||||
display='LINK %o'
|
||||
)
|
||||
backend_file.symlink_rule(mozpath.join(backend_file.objdir,
|
||||
shlib.lib_name),
|
||||
output=mozpath.join(self.environment.topobjdir,
|
||||
shlib.install_target,
|
||||
shlib.lib_name))
|
||||
|
||||
|
||||
def _gen_program(self, backend_file):
|
||||
|
|
|
@ -119,14 +119,20 @@ class BaseNavigationTestCase(WindowManagerMixin, MarionetteTestCase):
|
|||
class TestNavigate(BaseNavigationTestCase):
|
||||
|
||||
def test_set_location_through_execute_script(self):
|
||||
test_element_locator = (By.ID, "testh1")
|
||||
|
||||
self.marionette.execute_script(
|
||||
"window.location.href = arguments[0];",
|
||||
script_args=(self.test_page_remote,), sandbox=None)
|
||||
|
||||
# We cannot use get_url() to wait until the target page has been loaded,
|
||||
# because it will return the URL of the top browsing context and doesn't
|
||||
# wait for the page load to be complete.
|
||||
Wait(self.marionette, timeout=self.marionette.timeout.page_load).until(
|
||||
lambda mn: self.test_page_remote == mn.get_url(),
|
||||
message="'{}' hasn't been loaded".format(self.test_page_remote))
|
||||
self.assertEqual("Marionette Test", self.marionette.title)
|
||||
expected.element_present(*test_element_locator),
|
||||
message="Target element 'testh1' has not been found")
|
||||
|
||||
self.assertEqual(self.test_page_remote, self.marionette.get_url())
|
||||
|
||||
def test_navigate_chrome_unsupported_error(self):
|
||||
with self.marionette.using_context("chrome"):
|
||||
|
|
|
@ -88,10 +88,10 @@ StructuredLogger.prototype = {
|
|||
},
|
||||
|
||||
assertionCount(test, count, minExpected = 0, maxExpected = 0) {
|
||||
var data = {test,
|
||||
min_expected: minExpected,
|
||||
max_expected: maxExpected,
|
||||
count};
|
||||
var data = {test: this._testId(test),
|
||||
min_expected: minExpected,
|
||||
max_expected: maxExpected,
|
||||
count};
|
||||
|
||||
this._logData("assertion_count", data);
|
||||
},
|
||||
|
|
|
@ -1,118 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/**
|
||||
* nodejs script to generate: testing/talos/talos/tests/devtools/addon/content/pages/custom/inspector/index.html
|
||||
*
|
||||
* Execute it like this:
|
||||
* $ nodejs generate-inspector-index-html.js > testing/talos/talos/tests/devtools/addon/content/pages/custom/inspector/index.html
|
||||
*/
|
||||
|
||||
// We first create a deep tree with ${deep} nested children
|
||||
let deep = 50;
|
||||
// Then we create ${n} element after the deep tree
|
||||
let n = 50;
|
||||
// Number of attributes set on the repeated elements
|
||||
let attributes = 50;
|
||||
|
||||
// Build the <div> with $attributes data attributes
|
||||
let div = "<div";
|
||||
for (var i = 1; i <= attributes; i++) {
|
||||
div += ` data-a${i}="${i}"`;
|
||||
}
|
||||
div += ">";
|
||||
|
||||
// Build the tree of $deep elements
|
||||
let tree = "";
|
||||
for (i = 1; i <= deep; i++) {
|
||||
tree += new Array(i).join(" ");
|
||||
tree += div + " " + i + "\n";
|
||||
}
|
||||
for (i = deep; i >= 1; i--) {
|
||||
tree += new Array(i).join(" ");
|
||||
tree += "</div>\n";
|
||||
}
|
||||
|
||||
// Build the list of $n elements
|
||||
let repeat = "";
|
||||
for (i = 1; i <= n; i++) {
|
||||
repeat += div + " " + i + " </div>\n";
|
||||
}
|
||||
|
||||
// Prepare CSS rules to add to the document <style>.
|
||||
let CSS_RULES_COUNT = 200;
|
||||
let manyCssRules = "";
|
||||
for (i = 0; i < CSS_RULES_COUNT; i++) {
|
||||
manyCssRules += `
|
||||
.many-css-rules {
|
||||
font-size: ${i}px;
|
||||
margin: 10px;
|
||||
padding: 10px;
|
||||
font-family: Arial;
|
||||
margin: 20px;
|
||||
}`;
|
||||
}
|
||||
let expandManyChildren = new Array(100).join(" <div attr='my-attr'>content</div>\n");
|
||||
|
||||
let maxBalancedDepth = 8;
|
||||
function createBalancedMarkup(level = 0) {
|
||||
let tab = new Array(level + 1).join(" ");
|
||||
if (level < maxBalancedDepth) {
|
||||
let child = createBalancedMarkup(level + 1);
|
||||
return `${tab}<div>
|
||||
${child}
|
||||
${child}
|
||||
${tab}</div>`;
|
||||
} else {
|
||||
return tab + "<div class='leaf'>leaf</div>";
|
||||
}
|
||||
}
|
||||
let expandBalanced = createBalancedMarkup();
|
||||
|
||||
console.log(`
|
||||
<!DOCTYPE html>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
- You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<!-- This file is a generated file, do not edit it directly.
|
||||
- See generate-inspector-html.js for instructions to update this file -->
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Custom page for the Inspector</title>
|
||||
<style>
|
||||
div {
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
/* Styles for custom.inspector.manyrules tests */`);
|
||||
console.log(manyCssRules);
|
||||
console.log(`
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- <div> elements with ${deep} nested childs, all with ${attributes} attributes -->
|
||||
<!-- The deepest <div> has id="deep"> -->
|
||||
`);
|
||||
console.log(tree);
|
||||
console.log(`
|
||||
<!-- ${n} <div> elements without any children -->
|
||||
`);
|
||||
console.log(repeat);
|
||||
console.log(`
|
||||
<!-- Elements for custom.inspector.manyrules tests -->
|
||||
<div class="no-css-rules"></div>
|
||||
<div class="many-css-rules"></div>
|
||||
<div class="expand-many-children">
|
||||
`);
|
||||
console.log(expandManyChildren);
|
||||
console.log(`
|
||||
</div>
|
||||
<div class="expand-balanced">
|
||||
`);
|
||||
console.log(expandBalanced);
|
||||
console.log(`
|
||||
</div>
|
||||
</body>
|
||||
</html>`);
|
||||
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,6 +1,6 @@
|
|||
[table-model-fixup-2.html]
|
||||
disabled:
|
||||
if webrender and (os == "linux"): https://bugzilla.mozilla.org/show_bug.cgi?id=1445164
|
||||
if os == "linux": https://bugzilla.mozilla.org/show_bug.cgi?id=1445164
|
||||
|
||||
[Replaced elements outside a table cannot be table-row and are considered block -- input=text elements]
|
||||
expected: FAIL
|
||||
|
|
|
@ -554,7 +554,7 @@ function handleRequest(req, res) {
|
|||
// asking for cname.example.com
|
||||
var content;
|
||||
// ... this always sends a CNAME back to pointing-elsewhere.example.com. Loop time!
|
||||
content = new Buffer("00000100000100010000000005636E616D65076578616D706C6503636F6D0000050001C00C0005000100000037002012706F696E74696E672D656C73657768657265076578616D706C6503636F6D00", "hex");
|
||||
content = new Buffer("00000100000100010000000005636E616D65076578616D706C6503636F6D0000050001C00C0005000100000037002012706F696E74696E672D656C73657768657265076578616D706C65C01A00", "hex");
|
||||
res.setHeader('Content-Type', 'application/dns-udpwireformat');
|
||||
res.setHeader('Content-Length', content.length);
|
||||
res.writeHead(200);
|
||||
|
|
|
@ -1163,6 +1163,7 @@ class Extension extends ExtensionData {
|
|||
}
|
||||
|
||||
this.addonData = addonData;
|
||||
this.startupData = addonData.startupData || {};
|
||||
this.startupReason = startupReason;
|
||||
|
||||
if (["ADDON_UPGRADE", "ADDON_DOWNGRADE"].includes(startupReason)) {
|
||||
|
@ -1357,6 +1358,10 @@ class Extension extends ExtensionData {
|
|||
this.isPrivileged);
|
||||
}
|
||||
|
||||
saveStartupData() {
|
||||
AddonManagerPrivate.setStartupData(this.id, this.startupData);
|
||||
}
|
||||
|
||||
async _parseManifest() {
|
||||
let manifest = await super.parseManifest();
|
||||
if (manifest && manifest.permissions.has("mozillaAddons") &&
|
||||
|
|
|
@ -179,19 +179,27 @@ class Port {
|
|||
this.postMessage(json);
|
||||
},
|
||||
|
||||
onDisconnect: new EventManager(this.context, "Port.onDisconnect", fire => {
|
||||
return this.registerOnDisconnect(holder => {
|
||||
let error = holder && holder.deserialize(this.context.cloneScope);
|
||||
portError = error && this.context.normalizeError(error);
|
||||
fire.asyncWithoutClone(portObj);
|
||||
});
|
||||
onDisconnect: new EventManager({
|
||||
context: this.context,
|
||||
name: "Port.onDisconnect",
|
||||
register: fire => {
|
||||
return this.registerOnDisconnect(holder => {
|
||||
let error = holder && holder.deserialize(this.context.cloneScope);
|
||||
portError = error && this.context.normalizeError(error);
|
||||
fire.asyncWithoutClone(portObj);
|
||||
});
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onMessage: new EventManager(this.context, "Port.onMessage", fire => {
|
||||
return this.registerOnMessage(holder => {
|
||||
let msg = holder.deserialize(this.context.cloneScope);
|
||||
fire.asyncWithoutClone(msg, portObj);
|
||||
});
|
||||
onMessage: new EventManager({
|
||||
context: this.context,
|
||||
name: "Port.onMessage",
|
||||
register: fire => {
|
||||
return this.registerOnMessage(holder => {
|
||||
let msg = holder.deserialize(this.context.cloneScope);
|
||||
fire.asyncWithoutClone(msg, portObj);
|
||||
});
|
||||
},
|
||||
}).api(),
|
||||
|
||||
get error() {
|
||||
|
@ -403,63 +411,67 @@ class Messenger {
|
|||
}
|
||||
|
||||
_onMessage(name, filter) {
|
||||
return new EventManager(this.context, name, fire => {
|
||||
const caller = this.context.getCaller();
|
||||
return new EventManager({
|
||||
context: this.context,
|
||||
name,
|
||||
register: fire => {
|
||||
const caller = this.context.getCaller();
|
||||
|
||||
let listener = {
|
||||
messageFilterPermissive: this.optionalFilter,
|
||||
messageFilterStrict: this.filter,
|
||||
let listener = {
|
||||
messageFilterPermissive: this.optionalFilter,
|
||||
messageFilterStrict: this.filter,
|
||||
|
||||
filterMessage: (sender, recipient) => {
|
||||
// Exclude messages coming from content scripts for the devtools extension contexts
|
||||
// (See Bug 1383310).
|
||||
if (this.excludeContentScriptSender && sender.envType === "content_child") {
|
||||
return false;
|
||||
}
|
||||
filterMessage: (sender, recipient) => {
|
||||
// Exclude messages coming from content scripts for the devtools extension contexts
|
||||
// (See Bug 1383310).
|
||||
if (this.excludeContentScriptSender && sender.envType === "content_child") {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ignore the message if it was sent by this Messenger.
|
||||
return (sender.contextId !== this.context.contextId &&
|
||||
filter(sender, recipient));
|
||||
},
|
||||
// Ignore the message if it was sent by this Messenger.
|
||||
return (sender.contextId !== this.context.contextId &&
|
||||
filter(sender, recipient));
|
||||
},
|
||||
|
||||
receiveMessage: ({target, data: holder, sender, recipient, channelId}) => {
|
||||
if (!this.context.active) {
|
||||
return;
|
||||
}
|
||||
receiveMessage: ({target, data: holder, sender, recipient, channelId}) => {
|
||||
if (!this.context.active) {
|
||||
return;
|
||||
}
|
||||
|
||||
let sendResponse;
|
||||
let response = undefined;
|
||||
let promise = new Promise(resolve => {
|
||||
sendResponse = value => {
|
||||
resolve(value);
|
||||
response = promise;
|
||||
};
|
||||
});
|
||||
let sendResponse;
|
||||
let response = undefined;
|
||||
let promise = new Promise(resolve => {
|
||||
sendResponse = value => {
|
||||
resolve(value);
|
||||
response = promise;
|
||||
};
|
||||
});
|
||||
|
||||
let message = holder.deserialize(this.context.cloneScope);
|
||||
holder = null;
|
||||
let message = holder.deserialize(this.context.cloneScope);
|
||||
holder = null;
|
||||
|
||||
sender = Cu.cloneInto(sender, this.context.cloneScope);
|
||||
sendResponse = Cu.exportFunction(sendResponse, this.context.cloneScope);
|
||||
sender = Cu.cloneInto(sender, this.context.cloneScope);
|
||||
sendResponse = Cu.exportFunction(sendResponse, this.context.cloneScope);
|
||||
|
||||
// Note: We intentionally do not use runSafe here so that any
|
||||
// errors are propagated to the message sender.
|
||||
let result = fire.raw(message, sender, sendResponse);
|
||||
message = null;
|
||||
// Note: We intentionally do not use runSafe here so that any
|
||||
// errors are propagated to the message sender.
|
||||
let result = fire.raw(message, sender, sendResponse);
|
||||
message = null;
|
||||
|
||||
if (result instanceof this.context.cloneScope.Promise) {
|
||||
return StrongPromise.wrap(result, channelId, caller);
|
||||
} else if (result === true) {
|
||||
return StrongPromise.wrap(promise, channelId, caller);
|
||||
}
|
||||
return response;
|
||||
},
|
||||
};
|
||||
if (result instanceof this.context.cloneScope.Promise) {
|
||||
return StrongPromise.wrap(result, channelId, caller);
|
||||
} else if (result === true) {
|
||||
return StrongPromise.wrap(promise, channelId, caller);
|
||||
}
|
||||
return response;
|
||||
},
|
||||
};
|
||||
|
||||
MessageChannel.addListener(this.messageManagers, "Extension:Message", listener);
|
||||
return () => {
|
||||
MessageChannel.removeListener(this.messageManagers, "Extension:Message", listener);
|
||||
};
|
||||
MessageChannel.addListener(this.messageManagers, "Extension:Message", listener);
|
||||
return () => {
|
||||
MessageChannel.removeListener(this.messageManagers, "Extension:Message", listener);
|
||||
};
|
||||
},
|
||||
}).api();
|
||||
}
|
||||
|
||||
|
@ -506,41 +518,45 @@ class Messenger {
|
|||
}
|
||||
|
||||
_onConnect(name, filter) {
|
||||
return new EventManager(this.context, name, fire => {
|
||||
let listener = {
|
||||
messageFilterPermissive: this.optionalFilter,
|
||||
messageFilterStrict: this.filter,
|
||||
return new EventManager({
|
||||
context: this.context,
|
||||
name,
|
||||
register: fire => {
|
||||
let listener = {
|
||||
messageFilterPermissive: this.optionalFilter,
|
||||
messageFilterStrict: this.filter,
|
||||
|
||||
filterMessage: (sender, recipient) => {
|
||||
// Exclude messages coming from content scripts for the devtools extension contexts
|
||||
// (See Bug 1383310).
|
||||
if (this.excludeContentScriptSender && sender.envType === "content_child") {
|
||||
return false;
|
||||
}
|
||||
filterMessage: (sender, recipient) => {
|
||||
// Exclude messages coming from content scripts for the devtools extension contexts
|
||||
// (See Bug 1383310).
|
||||
if (this.excludeContentScriptSender && sender.envType === "content_child") {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ignore the port if it was created by this Messenger.
|
||||
return (sender.contextId !== this.context.contextId &&
|
||||
filter(sender, recipient));
|
||||
},
|
||||
// Ignore the port if it was created by this Messenger.
|
||||
return (sender.contextId !== this.context.contextId &&
|
||||
filter(sender, recipient));
|
||||
},
|
||||
|
||||
receiveMessage: ({target, data: message, sender}) => {
|
||||
let {name, portId} = message;
|
||||
let mm = getMessageManager(target);
|
||||
let recipient = Object.assign({}, sender);
|
||||
if (recipient.tab) {
|
||||
recipient.tabId = recipient.tab.id;
|
||||
delete recipient.tab;
|
||||
}
|
||||
let port = new Port(this.context, mm, this.messageManagers, name, portId, sender, recipient);
|
||||
fire.asyncWithoutClone(port.api());
|
||||
return true;
|
||||
},
|
||||
};
|
||||
receiveMessage: ({target, data: message, sender}) => {
|
||||
let {name, portId} = message;
|
||||
let mm = getMessageManager(target);
|
||||
let recipient = Object.assign({}, sender);
|
||||
if (recipient.tab) {
|
||||
recipient.tabId = recipient.tab.id;
|
||||
delete recipient.tab;
|
||||
}
|
||||
let port = new Port(this.context, mm, this.messageManagers, name, portId, sender, recipient);
|
||||
fire.asyncWithoutClone(port.api());
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
MessageChannel.addListener(this.messageManagers, "Extension:Connect", listener);
|
||||
return () => {
|
||||
MessageChannel.removeListener(this.messageManagers, "Extension:Connect", listener);
|
||||
};
|
||||
MessageChannel.addListener(this.messageManagers, "Extension:Connect", listener);
|
||||
return () => {
|
||||
MessageChannel.removeListener(this.messageManagers, "Extension:Connect", listener);
|
||||
};
|
||||
},
|
||||
}).api();
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ ChromeUtils.import("resource://gre/modules/Services.jsm");
|
|||
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
AppConstants: "resource://gre/modules/AppConstants.jsm",
|
||||
ConsoleAPI: "resource://gre/modules/Console.jsm",
|
||||
MessageChannel: "resource://gre/modules/MessageChannel.jsm",
|
||||
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
|
||||
|
@ -1735,46 +1736,226 @@ defineLazyGetter(LocaleData.prototype, "availableLocales", function() {
|
|||
});
|
||||
|
||||
/**
|
||||
* This is a generic class for managing event listeners.
|
||||
* This is a generic class for managing event listeners.
|
||||
*
|
||||
* @example
|
||||
* new EventManager(context, "api.subAPI", fire => {
|
||||
* let listener = (...) => {
|
||||
* // Fire any listeners registered with addListener.
|
||||
* fire.async(arg1, arg2);
|
||||
* };
|
||||
* // Register the listener.
|
||||
* SomehowRegisterListener(listener);
|
||||
* return () => {
|
||||
* // Return a way to unregister the listener.
|
||||
* SomehowUnregisterListener(listener);
|
||||
* };
|
||||
* new EventManager({
|
||||
* context,
|
||||
* name: "api.subAPI",
|
||||
* register: fire => {
|
||||
* let listener = (...) => {
|
||||
* // Fire any listeners registered with addListener.
|
||||
* fire.async(arg1, arg2);
|
||||
* };
|
||||
* // Register the listener.
|
||||
* SomehowRegisterListener(listener);
|
||||
* return () => {
|
||||
* // Return a way to unregister the listener.
|
||||
* SomehowUnregisterListener(listener);
|
||||
* };
|
||||
* }
|
||||
* }).api()
|
||||
*
|
||||
* The result is an object with addListener, removeListener, and
|
||||
* hasListener methods. `context` is an add-on scope (either an
|
||||
* ExtensionContext in the chrome process or ExtensionContext in a
|
||||
* content process). `name` is for debugging. `register` is a function
|
||||
* to register the listener. `register` should return an
|
||||
* unregister function that will unregister the listener.
|
||||
* @constructor
|
||||
*
|
||||
* @param {BaseContext} context
|
||||
* An object representing the extension instance using this event.
|
||||
* @param {string} name
|
||||
* A name used only for debugging.
|
||||
* @param {function} register
|
||||
* A function called whenever a new listener is added.
|
||||
* content process).
|
||||
*/
|
||||
function EventManager(context, name, register) {
|
||||
this.context = context;
|
||||
this.name = name;
|
||||
this.register = register;
|
||||
this.unregister = new Map();
|
||||
this.inputHandling = false;
|
||||
}
|
||||
class EventManager {
|
||||
/*
|
||||
* @param {object} params
|
||||
* Parameters that control this EventManager.
|
||||
* @param {BaseContext} params.context
|
||||
* An object representing the extension instance using this event.
|
||||
* @param {string} params.name
|
||||
* A name used only for debugging.
|
||||
* @param {functon} params.register
|
||||
* A function called whenever a new listener is added.
|
||||
* @param {boolean} [params.inputHandling=false]
|
||||
* If true, the "handling user input" flag is set while handlers
|
||||
* for this event are executing.
|
||||
* @param {object} [params.persistent]
|
||||
* Details for persistent event listeners
|
||||
* @param {string} params.persistent.module
|
||||
* The name of the module in which this event is defined.
|
||||
* @param {string} params.persistent.event
|
||||
* The name of this event.
|
||||
*/
|
||||
constructor(params) {
|
||||
// Maintain compatibility with the old EventManager API in which
|
||||
// the constructor took parameters (contest, name, register).
|
||||
// Remove this in bug 1451212.
|
||||
if (arguments.length > 1) {
|
||||
[this.context, this.name, this.register] = arguments;
|
||||
this.inputHandling = false;
|
||||
this.persistent = null;
|
||||
} else {
|
||||
let {context, name, register, inputHandling = false, persistent = null} = params;
|
||||
this.context = context;
|
||||
this.name = name;
|
||||
this.register = register;
|
||||
this.inputHandling = inputHandling;
|
||||
this.persistent = persistent;
|
||||
}
|
||||
|
||||
this.unregister = new Map();
|
||||
this.remove = new Map();
|
||||
|
||||
if (this.persistent) {
|
||||
if (this.context.viewType !== "background") {
|
||||
this.persistent = null;
|
||||
}
|
||||
if (AppConstants.DEBUG) {
|
||||
if (this.context.envType !== "addon_parent") {
|
||||
throw new Error("Persistent event managers can only be created for addon_parent");
|
||||
}
|
||||
if (!this.persistent.module || !this.persistent.event) {
|
||||
throw new Error("Persistent event manager must specify module and event");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Information about listeners to persistent events is associated with
|
||||
* the extension to which they belong. Any extension thas has such
|
||||
* listeners has a property called `persistentListeners` that is a
|
||||
* 3-level Map. The first 2 keys are the module name (e.g., webRequest)
|
||||
* and the name of the event within the module (e.g., onBeforeRequest).
|
||||
* The third level of the map is used to track multiple listeners for
|
||||
* the same event, these listeners are distinguished by the extra arguments
|
||||
* passed to addListener(). For quick lookups, the key to the third Map
|
||||
* is the result of calling uneval() on the array of extra arguments.
|
||||
*
|
||||
* The value stored in the Map is a plain object with a property called
|
||||
* `params` that is the original (ie, not uneval()ed) extra arguments to
|
||||
* addListener(). For a primed listener (i.e., the stub listener created
|
||||
* during browser startup before the extension background page is started,
|
||||
* the object also has a `primed` property that holds the things needed
|
||||
* to handle events during startup and eventually connect the listener
|
||||
* with a callback registered from the extension.
|
||||
*/
|
||||
static _initPersistentListeners(extension) {
|
||||
if (extension.persistentListeners) {
|
||||
return;
|
||||
}
|
||||
|
||||
let listeners = new DefaultMap(() => new DefaultMap(() => new Map()));
|
||||
extension.persistentListeners = listeners;
|
||||
|
||||
let {persistentListeners} = extension.startupData;
|
||||
if (!persistentListeners) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let [module, entry] of Object.entries(persistentListeners)) {
|
||||
for (let [event, paramlists] of Object.entries(entry)) {
|
||||
for (let paramlist of paramlists) {
|
||||
let key = uneval(paramlist);
|
||||
listeners.get(module).get(event).set(key, {params: paramlist});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Extract just the information needed at startup for all persistent
|
||||
// listeners, and arrange for it to be saved. This should be called
|
||||
// whenever the set of persistent listeners for an extension changes.
|
||||
static _writePersistentListeners(extension) {
|
||||
let startupListeners = {};
|
||||
for (let [module, moduleEntry] of extension.persistentListeners) {
|
||||
startupListeners[module] = {};
|
||||
for (let [event, eventEntry] of moduleEntry) {
|
||||
startupListeners[module][event] = Array.from(eventEntry.values(),
|
||||
listener => listener.params);
|
||||
}
|
||||
}
|
||||
|
||||
extension.startupData.persistentListeners = startupListeners;
|
||||
extension.saveStartupData();
|
||||
}
|
||||
|
||||
// Set up "primed" event listeners for any saved event listeners
|
||||
// in an extension's startup data.
|
||||
// This function is only called during browser startup, it stores details
|
||||
// about all primed listeners in the extension's persistentListeners Map.
|
||||
static primeListeners(extension) {
|
||||
EventManager._initPersistentListeners(extension);
|
||||
|
||||
for (let [module, moduleEntry] of extension.persistentListeners) {
|
||||
let api = extension.apiManager.getAPI(module, extension, "addon_parent");
|
||||
for (let [event, eventEntry] of moduleEntry) {
|
||||
for (let listener of eventEntry.values()) {
|
||||
let primed = {pendingEvents: []};
|
||||
listener.primed = primed;
|
||||
|
||||
let wakeup = (...args) => new Promise((resolve, reject) => {
|
||||
primed.pendingEvents.push({args, resolve, reject});
|
||||
extension.emit("background-page-event");
|
||||
});
|
||||
|
||||
let fire = {
|
||||
sync: wakeup,
|
||||
async: wakeup,
|
||||
};
|
||||
|
||||
let {unregister, convert} = api.primeListener(extension, event, fire, listener.params);
|
||||
Object.assign(primed, {unregister, convert});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove any primed listeners that were not re-registered.
|
||||
// This function is called after the background page has started.
|
||||
static clearPrimedListeners(extension) {
|
||||
for (let [module, moduleEntry] of extension.persistentListeners) {
|
||||
for (let [event, listeners] of moduleEntry) {
|
||||
for (let [key, listener] of listeners) {
|
||||
let {primed} = listener;
|
||||
if (!primed) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (let evt of primed.pendingEvents) {
|
||||
evt.reject(new Error("listener not re-registered"));
|
||||
}
|
||||
|
||||
EventManager.clearPersistentListener(extension, module, event, key);
|
||||
primed.unregister();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Record the fact that there is a listener for the given event in
|
||||
// the given extension. `args` is an Array containing any extra
|
||||
// arguments that were passed to addListener().
|
||||
static savePersistentListener(extension, module, event, args) {
|
||||
EventManager._initPersistentListeners(extension);
|
||||
let key = uneval(args);
|
||||
extension.persistentListeners.get(module).get(event).set(key, {params: args});
|
||||
EventManager._writePersistentListeners(extension);
|
||||
}
|
||||
|
||||
// Remove the record for the given event listener from the extension's
|
||||
// startup data. `key` must be a string, the result of calling uneval()
|
||||
// on the array of extra arguments originally passed to addListener().
|
||||
static clearPersistentListener(extension, module, event, key) {
|
||||
let listeners = extension.persistentListeners.get(module).get(event);
|
||||
listeners.delete(key);
|
||||
|
||||
if (listeners.size == 0) {
|
||||
let moduleEntry = extension.persistentListeners.get(module);
|
||||
moduleEntry.delete(event);
|
||||
if (moduleEntry.size == 0) {
|
||||
extension.persistentListeners.delete(module);
|
||||
}
|
||||
}
|
||||
|
||||
EventManager._writePersistentListeners(extension);
|
||||
}
|
||||
|
||||
EventManager.prototype = {
|
||||
addListener(callback, ...args) {
|
||||
if (this.unregister.has(callback)) {
|
||||
return;
|
||||
|
@ -1819,13 +2000,59 @@ EventManager.prototype = {
|
|||
},
|
||||
};
|
||||
|
||||
let {extension} = this.context;
|
||||
|
||||
let unregister = null;
|
||||
let recordStartupData = false;
|
||||
|
||||
// If this is a persistent event, check for a listener that was already
|
||||
// created during startup. If there is one, use it and don't create a
|
||||
// new one.
|
||||
if (this.persistent) {
|
||||
recordStartupData = true;
|
||||
let {module, event} = this.persistent;
|
||||
|
||||
let key = uneval(args);
|
||||
EventManager._initPersistentListeners(extension);
|
||||
let listener = extension.persistentListeners
|
||||
.get(module).get(event).get(key);
|
||||
if (listener) {
|
||||
let {primed} = listener;
|
||||
listener.primed = null;
|
||||
|
||||
primed.convert(fire);
|
||||
unregister = primed.unregister;
|
||||
|
||||
for (let evt of primed.pendingEvents) {
|
||||
evt.resolve(fire.async(...evt.args));
|
||||
}
|
||||
|
||||
recordStartupData = false;
|
||||
this.remove.set(callback, () => {
|
||||
EventManager.clearPersistentListener(extension, module, event, uneval(args));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!unregister) {
|
||||
unregister = this.register(fire, ...args);
|
||||
}
|
||||
|
||||
let unregister = this.register(fire, ...args);
|
||||
this.unregister.set(callback, unregister);
|
||||
this.context.callOnClose(this);
|
||||
},
|
||||
|
||||
removeListener(callback) {
|
||||
// If this is a new listener for a persistent event, record
|
||||
// the details for subsequent startups.
|
||||
if (recordStartupData) {
|
||||
let {module, event} = this.persistent;
|
||||
EventManager.savePersistentListener(extension, module, event, args);
|
||||
this.remove.set(callback, () => {
|
||||
EventManager.clearPersistentListener(extension, module, event, uneval(args));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
removeListener(callback, clearPersistentListener = true) {
|
||||
if (!this.unregister.has(callback)) {
|
||||
return;
|
||||
}
|
||||
|
@ -1837,24 +2064,31 @@ EventManager.prototype = {
|
|||
} catch (e) {
|
||||
Cu.reportError(e);
|
||||
}
|
||||
|
||||
if (clearPersistentListener && this.remove.has(callback)) {
|
||||
let cleanup = this.remove.get(callback);
|
||||
this.remove.delete(callback);
|
||||
cleanup();
|
||||
}
|
||||
|
||||
if (this.unregister.size == 0) {
|
||||
this.context.forgetOnClose(this);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
hasListener(callback) {
|
||||
return this.unregister.has(callback);
|
||||
},
|
||||
}
|
||||
|
||||
revoke() {
|
||||
for (let callback of this.unregister.keys()) {
|
||||
this.removeListener(callback);
|
||||
this.removeListener(callback, false);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
close() {
|
||||
this.revoke();
|
||||
},
|
||||
}
|
||||
|
||||
api() {
|
||||
return {
|
||||
|
@ -1864,8 +2098,8 @@ EventManager.prototype = {
|
|||
setUserInput: this.inputHandling,
|
||||
[Schemas.REVOKE]: () => this.revoke(),
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Simple API for event listeners where events never fire.
|
||||
function ignoreEvent(context, name) {
|
||||
|
|
|
@ -148,20 +148,24 @@ this.storage = class extends ExtensionAPI {
|
|||
},
|
||||
},
|
||||
|
||||
onChanged: new EventManager(context, "storage.onChanged", fire => {
|
||||
let onChanged = (data, area) => {
|
||||
let changes = new context.cloneScope.Object();
|
||||
for (let [key, value] of Object.entries(data)) {
|
||||
changes[key] = deserialize(value);
|
||||
}
|
||||
fire.raw(changes, area);
|
||||
};
|
||||
onChanged: new EventManager({
|
||||
context,
|
||||
name: "storage.onChanged",
|
||||
register: fire => {
|
||||
let onChanged = (data, area) => {
|
||||
let changes = new context.cloneScope.Object();
|
||||
for (let [key, value] of Object.entries(data)) {
|
||||
changes[key] = deserialize(value);
|
||||
}
|
||||
fire.raw(changes, area);
|
||||
};
|
||||
|
||||
let parent = context.childManager.getParentEvent("storage.onChanged");
|
||||
parent.addListener(onChanged);
|
||||
return () => {
|
||||
parent.removeListener(onChanged);
|
||||
};
|
||||
let parent = context.childManager.getParentEvent("storage.onChanged");
|
||||
parent.addListener(onChanged);
|
||||
return () => {
|
||||
parent.removeListener(onChanged);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -177,15 +177,19 @@ this.test = class extends ExtensionAPI {
|
|||
}
|
||||
},
|
||||
|
||||
onMessage: new TestEventManager(context, "test.onMessage", fire => {
|
||||
let handler = (event, ...args) => {
|
||||
fire.async(...args);
|
||||
};
|
||||
onMessage: new TestEventManager({
|
||||
context,
|
||||
name: "test.onMessage",
|
||||
register: fire => {
|
||||
let handler = (event, ...args) => {
|
||||
fire.async(...args);
|
||||
};
|
||||
|
||||
extension.on("test-harness-message", handler);
|
||||
return () => {
|
||||
extension.off("test-harness-message", handler);
|
||||
};
|
||||
extension.on("test-harness-message", handler);
|
||||
return () => {
|
||||
extension.off("test-harness-message", handler);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -281,7 +281,8 @@ const BrowserListener = {
|
|||
background = null;
|
||||
}
|
||||
|
||||
if (background !== this.oldBackground) {
|
||||
if (background === null ||
|
||||
background !== this.oldBackground) {
|
||||
sendAsyncMessage("Extension:BrowserBackgroundChanged", {background});
|
||||
}
|
||||
this.oldBackground = background;
|
||||
|
|
|
@ -14,6 +14,8 @@ module.exports = {
|
|||
"WindowBase": true,
|
||||
"WindowManagerBase": true,
|
||||
"WindowTrackerBase": true,
|
||||
"browserPaintedPromise": true,
|
||||
"browserStartupPromise": true,
|
||||
"getContainerForCookieStoreId": true,
|
||||
"getCookieStoreIdForContainer": true,
|
||||
"getCookieStoreIdForTab": true,
|
||||
|
|
|
@ -125,15 +125,19 @@ this.alarms = class extends ExtensionAPI {
|
|||
return Promise.resolve(cleared);
|
||||
},
|
||||
|
||||
onAlarm: new EventManager(context, "alarms.onAlarm", fire => {
|
||||
let callback = alarm => {
|
||||
fire.sync(alarm.data);
|
||||
};
|
||||
onAlarm: new EventManager({
|
||||
context,
|
||||
name: "alarms.onAlarm",
|
||||
register: fire => {
|
||||
let callback = alarm => {
|
||||
fire.sync(alarm.data);
|
||||
};
|
||||
|
||||
self.callbacks.add(callback);
|
||||
return () => {
|
||||
self.callbacks.delete(callback);
|
||||
};
|
||||
self.callbacks.add(callback);
|
||||
return () => {
|
||||
self.callbacks.delete(callback);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -9,6 +9,9 @@ var {
|
|||
promiseExtensionViewLoaded,
|
||||
} = ExtensionParent;
|
||||
|
||||
XPCOMUtils.defineLazyPreferenceGetter(this, "DELAYED_STARTUP",
|
||||
"extensions.webextensions.background-delayed-startup");
|
||||
|
||||
// Responsible for the background_page section of the manifest.
|
||||
class BackgroundPage extends HiddenExtensionPage {
|
||||
constructor(extension, options) {
|
||||
|
@ -54,11 +57,37 @@ class BackgroundPage extends HiddenExtensionPage {
|
|||
|
||||
this.backgroundPage = class extends ExtensionAPI {
|
||||
onManifestEntry(entryName) {
|
||||
let {manifest} = this.extension;
|
||||
let {extension} = this;
|
||||
let {manifest} = extension;
|
||||
|
||||
this.bgPage = new BackgroundPage(this.extension, manifest.background);
|
||||
this.bgPage = new BackgroundPage(extension, manifest.background);
|
||||
if (extension.startupReason !== "APP_STARTUP" || !DELAYED_STARTUP) {
|
||||
return this.bgPage.build();
|
||||
}
|
||||
|
||||
return this.bgPage.build();
|
||||
EventManager.primeListeners(extension);
|
||||
|
||||
extension.once("start-background-page", async () => {
|
||||
await this.bgPage.build();
|
||||
EventManager.clearPrimedListeners(extension);
|
||||
});
|
||||
|
||||
// There are two ways to start the background page:
|
||||
// 1. If a primed event fires, then start the background page as
|
||||
// soon as we have painted a browser window. Note that we have
|
||||
// to touch browserPaintedPromise here to initialize the listener
|
||||
// or else we can miss it if the event occurs after the first
|
||||
// window is painted but before #2
|
||||
// 2. After all windows have been restored.
|
||||
void browserPaintedPromise;
|
||||
extension.once("background-page-event", async () => {
|
||||
await browserPaintedPromise;
|
||||
extension.emit("start-background-page");
|
||||
});
|
||||
|
||||
browserStartupPromise.then(() => {
|
||||
extension.emit("start-background-page");
|
||||
});
|
||||
}
|
||||
|
||||
onShutdown() {
|
||||
|
|
|
@ -224,46 +224,58 @@ this.contextualIdentities = class extends ExtensionAPI {
|
|||
return convertedIdentity;
|
||||
},
|
||||
|
||||
onCreated: new EventManager(context, "contextualIdentities.onCreated", fire => {
|
||||
let observer = (subject, topic) => {
|
||||
let convertedIdentity = convertIdentityFromObserver(subject);
|
||||
if (convertedIdentity) {
|
||||
fire.async({contextualIdentity: convertedIdentity});
|
||||
}
|
||||
};
|
||||
onCreated: new EventManager({
|
||||
context,
|
||||
name: "contextualIdentities.onCreated",
|
||||
register: fire => {
|
||||
let observer = (subject, topic) => {
|
||||
let convertedIdentity = convertIdentityFromObserver(subject);
|
||||
if (convertedIdentity) {
|
||||
fire.async({contextualIdentity: convertedIdentity});
|
||||
}
|
||||
};
|
||||
|
||||
Services.obs.addObserver(observer, "contextual-identity-created");
|
||||
return () => {
|
||||
Services.obs.removeObserver(observer, "contextual-identity-created");
|
||||
};
|
||||
Services.obs.addObserver(observer, "contextual-identity-created");
|
||||
return () => {
|
||||
Services.obs.removeObserver(observer, "contextual-identity-created");
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onUpdated: new EventManager(context, "contextualIdentities.onUpdated", fire => {
|
||||
let observer = (subject, topic) => {
|
||||
let convertedIdentity = convertIdentityFromObserver(subject);
|
||||
if (convertedIdentity) {
|
||||
fire.async({contextualIdentity: convertedIdentity});
|
||||
}
|
||||
};
|
||||
onUpdated: new EventManager({
|
||||
context,
|
||||
name: "contextualIdentities.onUpdated",
|
||||
register: fire => {
|
||||
let observer = (subject, topic) => {
|
||||
let convertedIdentity = convertIdentityFromObserver(subject);
|
||||
if (convertedIdentity) {
|
||||
fire.async({contextualIdentity: convertedIdentity});
|
||||
}
|
||||
};
|
||||
|
||||
Services.obs.addObserver(observer, "contextual-identity-updated");
|
||||
return () => {
|
||||
Services.obs.removeObserver(observer, "contextual-identity-updated");
|
||||
};
|
||||
Services.obs.addObserver(observer, "contextual-identity-updated");
|
||||
return () => {
|
||||
Services.obs.removeObserver(observer, "contextual-identity-updated");
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onRemoved: new EventManager(context, "contextualIdentities.onRemoved", fire => {
|
||||
let observer = (subject, topic) => {
|
||||
let convertedIdentity = convertIdentityFromObserver(subject);
|
||||
if (convertedIdentity) {
|
||||
fire.async({contextualIdentity: convertedIdentity});
|
||||
}
|
||||
};
|
||||
onRemoved: new EventManager({
|
||||
context,
|
||||
name: "contextualIdentities.onRemoved",
|
||||
register: fire => {
|
||||
let observer = (subject, topic) => {
|
||||
let convertedIdentity = convertIdentityFromObserver(subject);
|
||||
if (convertedIdentity) {
|
||||
fire.async({contextualIdentity: convertedIdentity});
|
||||
}
|
||||
};
|
||||
|
||||
Services.obs.addObserver(observer, "contextual-identity-deleted");
|
||||
return () => {
|
||||
Services.obs.removeObserver(observer, "contextual-identity-deleted");
|
||||
};
|
||||
Services.obs.addObserver(observer, "contextual-identity-deleted");
|
||||
return () => {
|
||||
Services.obs.removeObserver(observer, "contextual-identity-deleted");
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
},
|
||||
|
|
|
@ -412,48 +412,52 @@ this.cookies = class extends ExtensionAPI {
|
|||
return Promise.resolve(result);
|
||||
},
|
||||
|
||||
onChanged: new EventManager(context, "cookies.onChanged", fire => {
|
||||
let observer = (subject, topic, data) => {
|
||||
let notify = (removed, cookie, cause) => {
|
||||
cookie.QueryInterface(Ci.nsICookie2);
|
||||
onChanged: new EventManager({
|
||||
context,
|
||||
name: "cookies.onChanged",
|
||||
register: fire => {
|
||||
let observer = (subject, topic, data) => {
|
||||
let notify = (removed, cookie, cause) => {
|
||||
cookie.QueryInterface(Ci.nsICookie2);
|
||||
|
||||
if (extension.whiteListedHosts.matchesCookie(cookie)) {
|
||||
fire.async({removed, cookie: convertCookie({cookie, isPrivate: topic == "private-cookie-changed"}), cause});
|
||||
if (extension.whiteListedHosts.matchesCookie(cookie)) {
|
||||
fire.async({removed, cookie: convertCookie({cookie, isPrivate: topic == "private-cookie-changed"}), cause});
|
||||
}
|
||||
};
|
||||
|
||||
// We do our best effort here to map the incompatible states.
|
||||
switch (data) {
|
||||
case "deleted":
|
||||
notify(true, subject, "explicit");
|
||||
break;
|
||||
case "added":
|
||||
notify(false, subject, "explicit");
|
||||
break;
|
||||
case "changed":
|
||||
notify(true, subject, "overwrite");
|
||||
notify(false, subject, "explicit");
|
||||
break;
|
||||
case "batch-deleted":
|
||||
subject.QueryInterface(Ci.nsIArray);
|
||||
for (let i = 0; i < subject.length; i++) {
|
||||
let cookie = subject.queryElementAt(i, Ci.nsICookie2);
|
||||
if (!cookie.isSession && cookie.expiry * 1000 <= Date.now()) {
|
||||
notify(true, cookie, "expired");
|
||||
} else {
|
||||
notify(true, cookie, "evicted");
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
// We do our best effort here to map the incompatible states.
|
||||
switch (data) {
|
||||
case "deleted":
|
||||
notify(true, subject, "explicit");
|
||||
break;
|
||||
case "added":
|
||||
notify(false, subject, "explicit");
|
||||
break;
|
||||
case "changed":
|
||||
notify(true, subject, "overwrite");
|
||||
notify(false, subject, "explicit");
|
||||
break;
|
||||
case "batch-deleted":
|
||||
subject.QueryInterface(Ci.nsIArray);
|
||||
for (let i = 0; i < subject.length; i++) {
|
||||
let cookie = subject.queryElementAt(i, Ci.nsICookie2);
|
||||
if (!cookie.isSession && cookie.expiry * 1000 <= Date.now()) {
|
||||
notify(true, cookie, "expired");
|
||||
} else {
|
||||
notify(true, cookie, "evicted");
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
Services.obs.addObserver(observer, "cookie-changed");
|
||||
Services.obs.addObserver(observer, "private-cookie-changed");
|
||||
return () => {
|
||||
Services.obs.removeObserver(observer, "cookie-changed");
|
||||
Services.obs.removeObserver(observer, "private-cookie-changed");
|
||||
};
|
||||
Services.obs.addObserver(observer, "cookie-changed");
|
||||
Services.obs.addObserver(observer, "private-cookie-changed");
|
||||
return () => {
|
||||
Services.obs.removeObserver(observer, "cookie-changed");
|
||||
Services.obs.removeObserver(observer, "private-cookie-changed");
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -765,60 +765,72 @@ this.downloads = class extends ExtensionAPI {
|
|||
// ...
|
||||
// }
|
||||
|
||||
onChanged: new EventManager(context, "downloads.onChanged", fire => {
|
||||
const handler = (what, item) => {
|
||||
let changes = {};
|
||||
const noundef = val => (val === undefined) ? null : val;
|
||||
DOWNLOAD_ITEM_CHANGE_FIELDS.forEach(fld => {
|
||||
if (item[fld] != item.prechange[fld]) {
|
||||
changes[fld] = {
|
||||
previous: noundef(item.prechange[fld]),
|
||||
current: noundef(item[fld]),
|
||||
};
|
||||
onChanged: new EventManager({
|
||||
context,
|
||||
name: "downloads.onChanged",
|
||||
register: fire => {
|
||||
const handler = (what, item) => {
|
||||
let changes = {};
|
||||
const noundef = val => (val === undefined) ? null : val;
|
||||
DOWNLOAD_ITEM_CHANGE_FIELDS.forEach(fld => {
|
||||
if (item[fld] != item.prechange[fld]) {
|
||||
changes[fld] = {
|
||||
previous: noundef(item.prechange[fld]),
|
||||
current: noundef(item[fld]),
|
||||
};
|
||||
}
|
||||
});
|
||||
if (Object.keys(changes).length > 0) {
|
||||
changes.id = item.id;
|
||||
fire.async(changes);
|
||||
}
|
||||
});
|
||||
if (Object.keys(changes).length > 0) {
|
||||
changes.id = item.id;
|
||||
fire.async(changes);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
let registerPromise = DownloadMap.getDownloadList().then(() => {
|
||||
DownloadMap.on("change", handler);
|
||||
});
|
||||
return () => {
|
||||
registerPromise.then(() => {
|
||||
DownloadMap.off("change", handler);
|
||||
let registerPromise = DownloadMap.getDownloadList().then(() => {
|
||||
DownloadMap.on("change", handler);
|
||||
});
|
||||
};
|
||||
return () => {
|
||||
registerPromise.then(() => {
|
||||
DownloadMap.off("change", handler);
|
||||
});
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onCreated: new EventManager(context, "downloads.onCreated", fire => {
|
||||
const handler = (what, item) => {
|
||||
fire.async(item.serialize());
|
||||
};
|
||||
let registerPromise = DownloadMap.getDownloadList().then(() => {
|
||||
DownloadMap.on("create", handler);
|
||||
});
|
||||
return () => {
|
||||
registerPromise.then(() => {
|
||||
DownloadMap.off("create", handler);
|
||||
onCreated: new EventManager({
|
||||
context,
|
||||
name: "downloads.onCreated",
|
||||
register: fire => {
|
||||
const handler = (what, item) => {
|
||||
fire.async(item.serialize());
|
||||
};
|
||||
let registerPromise = DownloadMap.getDownloadList().then(() => {
|
||||
DownloadMap.on("create", handler);
|
||||
});
|
||||
};
|
||||
return () => {
|
||||
registerPromise.then(() => {
|
||||
DownloadMap.off("create", handler);
|
||||
});
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onErased: new EventManager(context, "downloads.onErased", fire => {
|
||||
const handler = (what, item) => {
|
||||
fire.async(item.id);
|
||||
};
|
||||
let registerPromise = DownloadMap.getDownloadList().then(() => {
|
||||
DownloadMap.on("erase", handler);
|
||||
});
|
||||
return () => {
|
||||
registerPromise.then(() => {
|
||||
DownloadMap.off("erase", handler);
|
||||
onErased: new EventManager({
|
||||
context,
|
||||
name: "downloads.onErased",
|
||||
register: fire => {
|
||||
const handler = (what, item) => {
|
||||
fire.async(item.id);
|
||||
};
|
||||
let registerPromise = DownloadMap.getDownloadList().then(() => {
|
||||
DownloadMap.on("erase", handler);
|
||||
});
|
||||
};
|
||||
return () => {
|
||||
registerPromise.then(() => {
|
||||
DownloadMap.off("erase", handler);
|
||||
});
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onDeterminingFilename: ignoreEvent(context, "downloads.onDeterminingFilename"),
|
||||
|
|
|
@ -70,15 +70,19 @@ this.idle = class extends ExtensionAPI {
|
|||
setDetectionInterval: function(detectionIntervalInSeconds) {
|
||||
setDetectionInterval(extension, context, detectionIntervalInSeconds);
|
||||
},
|
||||
onStateChanged: new EventManager(context, "idle.onStateChanged", fire => {
|
||||
let listener = (event, data) => {
|
||||
fire.sync(data);
|
||||
};
|
||||
onStateChanged: new EventManager({
|
||||
context,
|
||||
name: "idle.onStateChanged",
|
||||
register: fire => {
|
||||
let listener = (event, data) => {
|
||||
fire.sync(data);
|
||||
};
|
||||
|
||||
getIdleObserver(extension, context).on("stateChanged", listener);
|
||||
return () => {
|
||||
getIdleObserver(extension, context).off("stateChanged", listener);
|
||||
};
|
||||
getIdleObserver(extension, context).on("stateChanged", listener);
|
||||
return () => {
|
||||
getIdleObserver(extension, context).off("stateChanged", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -230,48 +230,64 @@ this.management = class extends ExtensionAPI {
|
|||
addon.userDisabled = !enabled;
|
||||
},
|
||||
|
||||
onDisabled: new EventManager(context, "management.onDisabled", fire => {
|
||||
let listener = (event, data) => {
|
||||
fire.async(data);
|
||||
};
|
||||
onDisabled: new EventManager({
|
||||
context,
|
||||
name: "management.onDisabled",
|
||||
register: fire => {
|
||||
let listener = (event, data) => {
|
||||
fire.async(data);
|
||||
};
|
||||
|
||||
getManagementListener(extension, context).on("onDisabled", listener);
|
||||
return () => {
|
||||
getManagementListener(extension, context).off("onDisabled", listener);
|
||||
};
|
||||
getManagementListener(extension, context).on("onDisabled", listener);
|
||||
return () => {
|
||||
getManagementListener(extension, context).off("onDisabled", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onEnabled: new EventManager(context, "management.onEnabled", fire => {
|
||||
let listener = (event, data) => {
|
||||
fire.async(data);
|
||||
};
|
||||
onEnabled: new EventManager({
|
||||
context,
|
||||
name: "management.onEnabled",
|
||||
register: fire => {
|
||||
let listener = (event, data) => {
|
||||
fire.async(data);
|
||||
};
|
||||
|
||||
getManagementListener(extension, context).on("onEnabled", listener);
|
||||
return () => {
|
||||
getManagementListener(extension, context).off("onEnabled", listener);
|
||||
};
|
||||
getManagementListener(extension, context).on("onEnabled", listener);
|
||||
return () => {
|
||||
getManagementListener(extension, context).off("onEnabled", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onInstalled: new EventManager(context, "management.onInstalled", fire => {
|
||||
let listener = (event, data) => {
|
||||
fire.async(data);
|
||||
};
|
||||
onInstalled: new EventManager({
|
||||
context,
|
||||
name: "management.onInstalled",
|
||||
register: fire => {
|
||||
let listener = (event, data) => {
|
||||
fire.async(data);
|
||||
};
|
||||
|
||||
getManagementListener(extension, context).on("onInstalled", listener);
|
||||
return () => {
|
||||
getManagementListener(extension, context).off("onInstalled", listener);
|
||||
};
|
||||
getManagementListener(extension, context).on("onInstalled", listener);
|
||||
return () => {
|
||||
getManagementListener(extension, context).off("onInstalled", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onUninstalled: new EventManager(context, "management.onUninstalled", fire => {
|
||||
let listener = (event, data) => {
|
||||
fire.async(data);
|
||||
};
|
||||
onUninstalled: new EventManager({
|
||||
context,
|
||||
name: "management.onUninstalled",
|
||||
register: fire => {
|
||||
let listener = (event, data) => {
|
||||
fire.async(data);
|
||||
};
|
||||
|
||||
getManagementListener(extension, context).on("onUninstalled", listener);
|
||||
return () => {
|
||||
getManagementListener(extension, context).off("onUninstalled", listener);
|
||||
};
|
||||
getManagementListener(extension, context).on("onUninstalled", listener);
|
||||
return () => {
|
||||
getManagementListener(extension, context).off("onUninstalled", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
},
|
||||
|
|
|
@ -117,38 +117,50 @@ this.notifications = class extends ExtensionAPI {
|
|||
return Promise.resolve(result);
|
||||
},
|
||||
|
||||
onClosed: new EventManager(context, "notifications.onClosed", fire => {
|
||||
let listener = (event, notificationId) => {
|
||||
// TODO Bug 1413188, Support the byUser argument.
|
||||
fire.async(notificationId, true);
|
||||
};
|
||||
onClosed: new EventManager({
|
||||
context,
|
||||
name: "notifications.onClosed",
|
||||
register: fire => {
|
||||
let listener = (event, notificationId) => {
|
||||
// TODO Bug 1413188, Support the byUser argument.
|
||||
fire.async(notificationId, true);
|
||||
};
|
||||
|
||||
notificationsMap.on("closed", listener);
|
||||
return () => {
|
||||
notificationsMap.off("closed", listener);
|
||||
};
|
||||
notificationsMap.on("closed", listener);
|
||||
return () => {
|
||||
notificationsMap.off("closed", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onClicked: new EventManager(context, "notifications.onClicked", fire => {
|
||||
let listener = (event, notificationId) => {
|
||||
fire.async(notificationId, true);
|
||||
};
|
||||
onClicked: new EventManager({
|
||||
context,
|
||||
name: "notifications.onClicked",
|
||||
register: fire => {
|
||||
let listener = (event, notificationId) => {
|
||||
fire.async(notificationId, true);
|
||||
};
|
||||
|
||||
notificationsMap.on("clicked", listener);
|
||||
return () => {
|
||||
notificationsMap.off("clicked", listener);
|
||||
};
|
||||
notificationsMap.on("clicked", listener);
|
||||
return () => {
|
||||
notificationsMap.off("clicked", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onShown: new EventManager(context, "notifications.onShown", fire => {
|
||||
let listener = (event, notificationId) => {
|
||||
fire.async(notificationId, true);
|
||||
};
|
||||
onShown: new EventManager({
|
||||
context,
|
||||
name: "notifications.onShown",
|
||||
register: fire => {
|
||||
let listener = (event, notificationId) => {
|
||||
fire.async(notificationId, true);
|
||||
};
|
||||
|
||||
notificationsMap.on("shown", listener);
|
||||
return () => {
|
||||
notificationsMap.off("shown", listener);
|
||||
};
|
||||
notificationsMap.on("shown", listener);
|
||||
return () => {
|
||||
notificationsMap.off("shown", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
// TODO Bug 1190681, implement button support.
|
||||
|
|
|
@ -43,7 +43,7 @@ class ProxyFilterEventManager extends EventManager {
|
|||
};
|
||||
};
|
||||
|
||||
super(context, name, register);
|
||||
super({context, name, register});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -61,14 +61,18 @@ this.proxy = class extends ExtensionAPI {
|
|||
getAPI(context) {
|
||||
let {extension} = context;
|
||||
|
||||
let onError = new EventManager(context, "proxy.onError", fire => {
|
||||
let listener = (name, error) => {
|
||||
fire.async(error);
|
||||
};
|
||||
extension.on("proxy-error", listener);
|
||||
return () => {
|
||||
extension.off("proxy-error", listener);
|
||||
};
|
||||
let onError = new EventManager({
|
||||
context,
|
||||
name: "proxy.onError",
|
||||
register: fire => {
|
||||
let listener = (name, error) => {
|
||||
fire.async(error);
|
||||
};
|
||||
extension.on("proxy-error", listener);
|
||||
return () => {
|
||||
extension.off("proxy-error", listener);
|
||||
};
|
||||
},
|
||||
}).api();
|
||||
|
||||
return {
|
||||
|
|
|
@ -16,64 +16,76 @@ this.runtime = class extends ExtensionAPI {
|
|||
let {extension} = context;
|
||||
return {
|
||||
runtime: {
|
||||
onStartup: new EventManager(context, "runtime.onStartup", fire => {
|
||||
if (context.incognito) {
|
||||
// This event should not fire if we are operating in a private profile.
|
||||
return () => {};
|
||||
}
|
||||
let listener = () => {
|
||||
if (extension.startupReason === "APP_STARTUP") {
|
||||
fire.sync();
|
||||
onStartup: new EventManager({
|
||||
context,
|
||||
name: "runtime.onStartup",
|
||||
register: fire => {
|
||||
if (context.incognito) {
|
||||
// This event should not fire if we are operating in a private profile.
|
||||
return () => {};
|
||||
}
|
||||
};
|
||||
extension.on("startup", listener);
|
||||
return () => {
|
||||
extension.off("startup", listener);
|
||||
};
|
||||
}).api(),
|
||||
|
||||
onInstalled: new EventManager(context, "runtime.onInstalled", fire => {
|
||||
let temporary = !!extension.addonData.temporarilyInstalled;
|
||||
|
||||
let listener = () => {
|
||||
switch (extension.startupReason) {
|
||||
case "APP_STARTUP":
|
||||
if (AddonManagerPrivate.browserUpdated) {
|
||||
fire.sync({reason: "browser_update", temporary});
|
||||
}
|
||||
break;
|
||||
case "ADDON_INSTALL":
|
||||
fire.sync({reason: "install", temporary});
|
||||
break;
|
||||
case "ADDON_UPGRADE":
|
||||
fire.sync({
|
||||
reason: "update",
|
||||
previousVersion: extension.addonData.oldVersion,
|
||||
temporary,
|
||||
});
|
||||
break;
|
||||
}
|
||||
};
|
||||
extension.on("startup", listener);
|
||||
return () => {
|
||||
extension.off("startup", listener);
|
||||
};
|
||||
}).api(),
|
||||
|
||||
onUpdateAvailable: new EventManager(context, "runtime.onUpdateAvailable", fire => {
|
||||
let instanceID = extension.addonData.instanceID;
|
||||
AddonManager.addUpgradeListener(instanceID, upgrade => {
|
||||
extension.upgrade = upgrade;
|
||||
let details = {
|
||||
version: upgrade.version,
|
||||
let listener = () => {
|
||||
if (extension.startupReason === "APP_STARTUP") {
|
||||
fire.sync();
|
||||
}
|
||||
};
|
||||
fire.sync(details);
|
||||
});
|
||||
return () => {
|
||||
AddonManager.removeUpgradeListener(instanceID).catch(e => {
|
||||
// This can happen if we try this after shutdown is complete.
|
||||
extension.on("startup", listener);
|
||||
return () => {
|
||||
extension.off("startup", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onInstalled: new EventManager({
|
||||
context,
|
||||
name: "runtime.onInstalled",
|
||||
register: fire => {
|
||||
let temporary = !!extension.addonData.temporarilyInstalled;
|
||||
|
||||
let listener = () => {
|
||||
switch (extension.startupReason) {
|
||||
case "APP_STARTUP":
|
||||
if (AddonManagerPrivate.browserUpdated) {
|
||||
fire.sync({reason: "browser_update", temporary});
|
||||
}
|
||||
break;
|
||||
case "ADDON_INSTALL":
|
||||
fire.sync({reason: "install", temporary});
|
||||
break;
|
||||
case "ADDON_UPGRADE":
|
||||
fire.sync({
|
||||
reason: "update",
|
||||
previousVersion: extension.addonData.oldVersion,
|
||||
temporary,
|
||||
});
|
||||
break;
|
||||
}
|
||||
};
|
||||
extension.on("startup", listener);
|
||||
return () => {
|
||||
extension.off("startup", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onUpdateAvailable: new EventManager({
|
||||
context,
|
||||
name: "runtime.onUpdateAvailable",
|
||||
register: fire => {
|
||||
let instanceID = extension.addonData.instanceID;
|
||||
AddonManager.addUpgradeListener(instanceID, upgrade => {
|
||||
extension.upgrade = upgrade;
|
||||
let details = {
|
||||
version: upgrade.version,
|
||||
};
|
||||
fire.sync(details);
|
||||
});
|
||||
};
|
||||
return () => {
|
||||
AddonManager.removeUpgradeListener(instanceID).catch(e => {
|
||||
// This can happen if we try this after shutdown is complete.
|
||||
});
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
reload: () => {
|
||||
|
|
|
@ -89,20 +89,24 @@ this.storage = class extends ExtensionAPI {
|
|||
},
|
||||
},
|
||||
|
||||
onChanged: new EventManager(context, "storage.onChanged", fire => {
|
||||
let listenerLocal = changes => {
|
||||
fire.raw(changes, "local");
|
||||
};
|
||||
let listenerSync = changes => {
|
||||
fire.async(changes, "sync");
|
||||
};
|
||||
onChanged: new EventManager({
|
||||
context,
|
||||
name: "storage.onChanged",
|
||||
register: fire => {
|
||||
let listenerLocal = changes => {
|
||||
fire.raw(changes, "local");
|
||||
};
|
||||
let listenerSync = changes => {
|
||||
fire.async(changes, "sync");
|
||||
};
|
||||
|
||||
ExtensionStorage.addOnChangedListener(extension.id, listenerLocal);
|
||||
extensionStorageSync.addOnChangedListener(extension, listenerSync, context);
|
||||
return () => {
|
||||
ExtensionStorage.removeOnChangedListener(extension.id, listenerLocal);
|
||||
extensionStorageSync.removeOnChangedListener(extension, listenerSync);
|
||||
};
|
||||
ExtensionStorage.addOnChangedListener(extension.id, listenerLocal);
|
||||
extensionStorageSync.addOnChangedListener(extension, listenerSync, context);
|
||||
return () => {
|
||||
ExtensionStorage.removeOnChangedListener(extension.id, listenerLocal);
|
||||
extensionStorageSync.removeOnChangedListener(extension, listenerSync);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -411,19 +411,23 @@ this.theme = class extends ExtensionAPI {
|
|||
|
||||
Theme.unload(windowId);
|
||||
},
|
||||
onUpdated: new EventManager(context, "theme.onUpdated", fire => {
|
||||
let callback = (event, theme, windowId) => {
|
||||
if (windowId) {
|
||||
fire.async({theme, windowId});
|
||||
} else {
|
||||
fire.async({theme});
|
||||
}
|
||||
};
|
||||
onUpdated: new EventManager({
|
||||
context,
|
||||
name: "theme.onUpdated",
|
||||
register: fire => {
|
||||
let callback = (event, theme, windowId) => {
|
||||
if (windowId) {
|
||||
fire.async({theme, windowId});
|
||||
} else {
|
||||
fire.async({theme});
|
||||
}
|
||||
};
|
||||
|
||||
onUpdatedEmitter.on("theme-updated", callback);
|
||||
return () => {
|
||||
onUpdatedEmitter.off("theme-updated", callback);
|
||||
};
|
||||
onUpdatedEmitter.on("theme-updated", callback);
|
||||
return () => {
|
||||
onUpdatedEmitter.off("theme-updated", callback);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -21,12 +21,6 @@ ChromeUtils.import("resource://gre/modules/ExtensionCommon.jsm");
|
|||
|
||||
global.EventEmitter = ExtensionUtils.EventEmitter;
|
||||
global.EventManager = ExtensionCommon.EventManager;
|
||||
global.InputEventManager = class extends EventManager {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this.inputHandling = true;
|
||||
}
|
||||
};
|
||||
|
||||
/* globals DEFAULT_STORE, PRIVATE_STORE, CONTAINER_STORE */
|
||||
|
||||
|
@ -80,3 +74,24 @@ global.isValidCookieStoreId = function(storeId) {
|
|||
isPrivateCookieStoreId(storeId) ||
|
||||
isContainerCookieStoreId(storeId);
|
||||
};
|
||||
|
||||
function makeEventPromise(name, event) {
|
||||
Object.defineProperty(global, name, {
|
||||
get() {
|
||||
let promise = ExtensionUtils.promiseObserved(event);
|
||||
Object.defineProperty(global, name, {value: promise});
|
||||
return promise;
|
||||
},
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
});
|
||||
}
|
||||
|
||||
// browserPaintedPromise and browserStartupPromise are promises that
|
||||
// resolve after the first browser window is painted and after browser
|
||||
// windows have been restored, respectively.
|
||||
// These promises must be referenced during startup to be valid -- if the
|
||||
// first reference happens after the corresponding event has occurred,
|
||||
// the Promise will never resolve.
|
||||
makeEventPromise("browserPaintedPromise", "browser-delayed-startup-finished");
|
||||
makeEventPromise("browserStartupPromise", "sessionstore-windows-restored");
|
||||
|
|
|
@ -90,61 +90,61 @@ const fillTransitionProperties = (eventName, src, dst) => {
|
|||
};
|
||||
|
||||
// Similar to WebRequestEventManager but for WebNavigation.
|
||||
function WebNavigationEventManager(context, eventName) {
|
||||
let name = `webNavigation.${eventName}`;
|
||||
let register = (fire, urlFilters) => {
|
||||
// Don't create a MatchURLFilters instance if the listener does not include any filter.
|
||||
let filters = urlFilters ? new MatchURLFilters(urlFilters.url) : null;
|
||||
class WebNavigationEventManager extends EventManager {
|
||||
constructor(context, eventName) {
|
||||
let name = `webNavigation.${eventName}`;
|
||||
let register = (fire, urlFilters) => {
|
||||
// Don't create a MatchURLFilters instance if the listener does not include any filter.
|
||||
let filters = urlFilters ? new MatchURLFilters(urlFilters.url) : null;
|
||||
|
||||
let listener = data => {
|
||||
if (!data.browser) {
|
||||
return;
|
||||
}
|
||||
let listener = data => {
|
||||
if (!data.browser) {
|
||||
return;
|
||||
}
|
||||
|
||||
let data2 = {
|
||||
url: data.url,
|
||||
timeStamp: Date.now(),
|
||||
let data2 = {
|
||||
url: data.url,
|
||||
timeStamp: Date.now(),
|
||||
};
|
||||
|
||||
if (eventName == "onErrorOccurred") {
|
||||
data2.error = data.error;
|
||||
}
|
||||
|
||||
if (data.frameId != undefined) {
|
||||
data2.frameId = data.frameId;
|
||||
data2.parentFrameId = data.parentFrameId;
|
||||
}
|
||||
|
||||
if (data.sourceFrameId != undefined) {
|
||||
data2.sourceFrameId = data.sourceFrameId;
|
||||
}
|
||||
|
||||
// Fills in tabId typically.
|
||||
Object.assign(data2, tabTracker.getBrowserData(data.browser));
|
||||
if (data2.tabId < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.sourceTabBrowser) {
|
||||
data2.sourceTabId = tabTracker.getBrowserData(data.sourceTabBrowser).tabId;
|
||||
}
|
||||
|
||||
fillTransitionProperties(eventName, data, data2);
|
||||
|
||||
fire.async(data2);
|
||||
};
|
||||
|
||||
if (eventName == "onErrorOccurred") {
|
||||
data2.error = data.error;
|
||||
}
|
||||
|
||||
if (data.frameId != undefined) {
|
||||
data2.frameId = data.frameId;
|
||||
data2.parentFrameId = data.parentFrameId;
|
||||
}
|
||||
|
||||
if (data.sourceFrameId != undefined) {
|
||||
data2.sourceFrameId = data.sourceFrameId;
|
||||
}
|
||||
|
||||
// Fills in tabId typically.
|
||||
Object.assign(data2, tabTracker.getBrowserData(data.browser));
|
||||
if (data2.tabId < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.sourceTabBrowser) {
|
||||
data2.sourceTabId = tabTracker.getBrowserData(data.sourceTabBrowser).tabId;
|
||||
}
|
||||
|
||||
fillTransitionProperties(eventName, data, data2);
|
||||
|
||||
fire.async(data2);
|
||||
WebNavigation[eventName].addListener(listener, filters);
|
||||
return () => {
|
||||
WebNavigation[eventName].removeListener(listener);
|
||||
};
|
||||
};
|
||||
|
||||
WebNavigation[eventName].addListener(listener, filters);
|
||||
return () => {
|
||||
WebNavigation[eventName].removeListener(listener);
|
||||
};
|
||||
};
|
||||
|
||||
return EventManager.call(this, context, name, register);
|
||||
super({context, name, register});
|
||||
}
|
||||
}
|
||||
|
||||
WebNavigationEventManager.prototype = Object.create(EventManager.prototype);
|
||||
|
||||
const convertGetFrameResult = (tabId, data) => {
|
||||
return {
|
||||
errorOccurred: data.errorOccurred,
|
||||
|
@ -161,8 +161,12 @@ this.webNavigation = class extends ExtensionAPI {
|
|||
|
||||
return {
|
||||
webNavigation: {
|
||||
onTabReplaced: new EventManager(context, "webNavigation.onTabReplaced", fire => {
|
||||
return () => {};
|
||||
onTabReplaced: new EventManager({
|
||||
context,
|
||||
name: "webNavigation.onTabReplaced",
|
||||
register: fire => {
|
||||
return () => {};
|
||||
},
|
||||
}).api(),
|
||||
onBeforeNavigate: new WebNavigationEventManager(context, "onBeforeNavigate").api(),
|
||||
onCommitted: new WebNavigationEventManager(context, "onCommitted").api(),
|
||||
|
|
|
@ -81,24 +81,22 @@ function WebRequestEventManager(context, eventName) {
|
|||
};
|
||||
};
|
||||
|
||||
return EventManager.call(this, context, name, register);
|
||||
return new EventManager({context, name, register}).api();
|
||||
}
|
||||
|
||||
WebRequestEventManager.prototype = Object.create(EventManager.prototype);
|
||||
|
||||
this.webRequest = class extends ExtensionAPI {
|
||||
getAPI(context) {
|
||||
return {
|
||||
webRequest: {
|
||||
onBeforeRequest: new WebRequestEventManager(context, "onBeforeRequest").api(),
|
||||
onBeforeSendHeaders: new WebRequestEventManager(context, "onBeforeSendHeaders").api(),
|
||||
onSendHeaders: new WebRequestEventManager(context, "onSendHeaders").api(),
|
||||
onHeadersReceived: new WebRequestEventManager(context, "onHeadersReceived").api(),
|
||||
onAuthRequired: new WebRequestEventManager(context, "onAuthRequired").api(),
|
||||
onBeforeRedirect: new WebRequestEventManager(context, "onBeforeRedirect").api(),
|
||||
onResponseStarted: new WebRequestEventManager(context, "onResponseStarted").api(),
|
||||
onErrorOccurred: new WebRequestEventManager(context, "onErrorOccurred").api(),
|
||||
onCompleted: new WebRequestEventManager(context, "onCompleted").api(),
|
||||
onBeforeRequest: WebRequestEventManager(context, "onBeforeRequest"),
|
||||
onBeforeSendHeaders: WebRequestEventManager(context, "onBeforeSendHeaders"),
|
||||
onSendHeaders: WebRequestEventManager(context, "onSendHeaders"),
|
||||
onHeadersReceived: WebRequestEventManager(context, "onHeadersReceived"),
|
||||
onAuthRequired: WebRequestEventManager(context, "onAuthRequired"),
|
||||
onBeforeRedirect: WebRequestEventManager(context, "onBeforeRedirect"),
|
||||
onResponseStarted: WebRequestEventManager(context, "onResponseStarted"),
|
||||
onErrorOccurred: WebRequestEventManager(context, "onErrorOccurred"),
|
||||
onCompleted: WebRequestEventManager(context, "onCompleted"),
|
||||
handlerBehaviorChanged: function() {
|
||||
// TODO: Flush all caches.
|
||||
},
|
||||
|
|
|
@ -65,11 +65,15 @@ add_task(async function test_post_unload_listeners() {
|
|||
let context = new StubContext();
|
||||
|
||||
let fire;
|
||||
let manager = new EventManager(context, "EventManager", _fire => {
|
||||
fire = () => {
|
||||
_fire.async();
|
||||
};
|
||||
return () => {};
|
||||
let manager = new EventManager({
|
||||
context,
|
||||
name: "EventManager",
|
||||
register: _fire => {
|
||||
fire = () => {
|
||||
_fire.async();
|
||||
};
|
||||
return () => {};
|
||||
},
|
||||
});
|
||||
|
||||
let fail = event => {
|
||||
|
|
|
@ -0,0 +1,355 @@
|
|||
"use strict";
|
||||
|
||||
PromiseTestUtils.whitelistRejectionsGlobally(/Message manager disconnected/);
|
||||
|
||||
Cu.importGlobalProperties(["Blob", "URL"]);
|
||||
|
||||
ChromeUtils.import("resource://gre/modules/ExtensionCommon.jsm");
|
||||
const {ExtensionAPI} = ExtensionCommon;
|
||||
|
||||
const SCHEMA = [
|
||||
{
|
||||
namespace: "eventtest",
|
||||
events: [
|
||||
{
|
||||
name: "onEvent1",
|
||||
type: "function",
|
||||
extraParameters: [{type: "any"}],
|
||||
},
|
||||
{
|
||||
name: "onEvent2",
|
||||
type: "function",
|
||||
extraParameters: [{type: "any"}],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// The code in this class does not actually run in this test scope, it is
|
||||
// serialized into a string which is later loaded by the WebExtensions
|
||||
// framework in the same context as other extension APIs. By writing it
|
||||
// this way rather than as a big string constant we get lint coverage.
|
||||
// But eslint doesn't understand that this code runs in a different context
|
||||
// where the EventManager class is available so just tell it here:
|
||||
/* global EventManager */
|
||||
const API = class extends ExtensionAPI {
|
||||
primeListener(extension, event, fire, params) {
|
||||
let data = {wrappedJSObject: {event, params}};
|
||||
Services.obs.notifyObservers(data, "prime-event-listener");
|
||||
|
||||
const FIRE_TOPIC = `fire-${event}`;
|
||||
|
||||
async function listener(subject, topic, _data) {
|
||||
try {
|
||||
await fire.async(subject.wrappedJSObject);
|
||||
} catch (err) {
|
||||
Services.obs.notifyObservers(data, "listener-callback-exception");
|
||||
}
|
||||
}
|
||||
Services.obs.addObserver(listener, FIRE_TOPIC);
|
||||
|
||||
return {
|
||||
unregister() {
|
||||
Services.obs.notifyObservers(data, "unregister-primed-listener");
|
||||
Services.obs.removeObserver(listener, FIRE_TOPIC);
|
||||
},
|
||||
convert(_fire) {
|
||||
Services.obs.notifyObservers(data, "convert-event-listener");
|
||||
fire = _fire;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
getAPI(context) {
|
||||
return {
|
||||
eventtest: {
|
||||
onEvent1: new EventManager({
|
||||
context,
|
||||
name: "test.event1",
|
||||
persistent: {
|
||||
module: "eventtest",
|
||||
event: "onEvent1",
|
||||
},
|
||||
register: (fire, ...params) => {
|
||||
let data = {wrappedJSObject: {event: "onEvent1", params}};
|
||||
Services.obs.notifyObservers(data, "register-event-listener");
|
||||
return () => {
|
||||
Services.obs.notifyObservers(data, "unregister-event-listener");
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onEvent2: new EventManager({
|
||||
context,
|
||||
name: "test.event1",
|
||||
persistent: {
|
||||
module: "eventtest",
|
||||
event: "onEvent2",
|
||||
},
|
||||
register: (fire, ...params) => {
|
||||
let data = {wrappedJSObject: {event: "onEvent2", params}};
|
||||
Services.obs.notifyObservers(data, "register-event-listener");
|
||||
return () => {
|
||||
Services.obs.notifyObservers(data, "unregister-event-listener");
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const API_SCRIPT = `this.eventtest = ${API.toString()}`;
|
||||
|
||||
const MODULE_INFO = {
|
||||
eventtest: {
|
||||
schema: `data:,${JSON.stringify(SCHEMA)}`,
|
||||
scopes: ["addon_parent"],
|
||||
paths: [["eventtest"]],
|
||||
url: URL.createObjectURL(new Blob([API_SCRIPT])),
|
||||
},
|
||||
};
|
||||
|
||||
const global = this;
|
||||
|
||||
// Wait for the given event (topic) to occur a specific number of times
|
||||
// (count). If fn is not supplied, the Promise returned from this function
|
||||
// resolves as soon as that many instances of the event have been observed.
|
||||
// If fn is supplied, this function also waits for the Promise that fn()
|
||||
// returns to complete and ensures that the given event does not occur more
|
||||
// than `count` times before then. On success, resolves with an array
|
||||
// of the subjects from each of the observed events.
|
||||
async function promiseObservable(topic, count, fn = null) {
|
||||
let _countResolve;
|
||||
let results = [];
|
||||
function listener(subject, _topic, data) {
|
||||
results.push(subject.wrappedJSObject);
|
||||
if (results.length > count) {
|
||||
ok(false, `Got unexpected ${topic} event`);
|
||||
} else if (results.length == count) {
|
||||
_countResolve();
|
||||
}
|
||||
}
|
||||
Services.obs.addObserver(listener, topic);
|
||||
|
||||
try {
|
||||
await Promise.all([
|
||||
new Promise(resolve => { _countResolve = resolve; }),
|
||||
fn && fn(),
|
||||
]);
|
||||
} finally {
|
||||
Services.obs.removeObserver(listener, topic);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
add_task(async function() {
|
||||
Services.prefs.setBoolPref("extensions.webextensions.background-delayed-startup", true);
|
||||
|
||||
AddonTestUtils.init(global);
|
||||
AddonTestUtils.overrideCertDB();
|
||||
AddonTestUtils.createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "43");
|
||||
|
||||
await AddonTestUtils.promiseStartupManager();
|
||||
|
||||
ExtensionParent.apiManager.registerModules(MODULE_INFO);
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
useAddonManager: "permanent",
|
||||
background() {
|
||||
let register1 = true, register2 = true;
|
||||
if (localStorage.getItem("skip1")) {
|
||||
register1 = false;
|
||||
}
|
||||
if (localStorage.getItem("skip2")) {
|
||||
register2 = false;
|
||||
}
|
||||
|
||||
let listener1 = arg => browser.test.sendMessage("listener1", arg);
|
||||
let listener2 = arg => browser.test.sendMessage("listener2", arg);
|
||||
let listener3 = arg => browser.test.sendMessage("listener3", arg);
|
||||
|
||||
if (register1) {
|
||||
browser.eventtest.onEvent1.addListener(listener1, "listener1");
|
||||
}
|
||||
if (register2) {
|
||||
browser.eventtest.onEvent1.addListener(listener2, "listener2");
|
||||
browser.eventtest.onEvent2.addListener(listener3, "listener3");
|
||||
}
|
||||
|
||||
browser.test.onMessage.addListener(msg => {
|
||||
if (msg == "unregister2") {
|
||||
browser.eventtest.onEvent2.removeListener(listener3);
|
||||
localStorage.setItem("skip2", true);
|
||||
} else if (msg == "unregister1") {
|
||||
localStorage.setItem("skip1", true);
|
||||
browser.test.sendMessage("unregistered");
|
||||
}
|
||||
});
|
||||
|
||||
browser.test.sendMessage("ready");
|
||||
},
|
||||
});
|
||||
|
||||
function check(info, what, {listener1 = true, listener2 = true, listener3 = true} = {}) {
|
||||
let count = (listener1 ? 1 : 0) + (listener2 ? 1 : 0) + (listener3 ? 1 : 0);
|
||||
equal(info.length, count, `Got ${count} ${what} events`);
|
||||
|
||||
let i = 0;
|
||||
if (listener1) {
|
||||
equal(info[i].event, "onEvent1", `Got ${what} on event1 for listener 1`);
|
||||
deepEqual(info[i].params, ["listener1"], `Got event1 ${what} args for listener 1`);
|
||||
++i;
|
||||
}
|
||||
|
||||
if (listener2) {
|
||||
equal(info[i].event, "onEvent1", `Got ${what} on event1 for listener 2`);
|
||||
deepEqual(info[i].params, ["listener2"], `Got event1 ${what} args for listener 2`);
|
||||
++i;
|
||||
}
|
||||
|
||||
if (listener3) {
|
||||
equal(info[i].event, "onEvent2", `Got ${what} on event2 for listener 3`);
|
||||
deepEqual(info[i].params, ["listener3"], `Got event2 ${what} args for listener 3`);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the regular event registration process occurs when
|
||||
// the extension is installed.
|
||||
let [info] = await Promise.all([
|
||||
promiseObservable("register-event-listener", 3),
|
||||
extension.startup(),
|
||||
]);
|
||||
check(info, "register");
|
||||
|
||||
await extension.awaitMessage("ready");
|
||||
|
||||
// Check that the regular unregister process occurs when
|
||||
// the browser shuts down.
|
||||
[info] = await Promise.all([
|
||||
promiseObservable("unregister-event-listener", 3),
|
||||
new Promise(resolve => extension.extension.once("shutdown", resolve)),
|
||||
AddonTestUtils.promiseShutdownManager(),
|
||||
]);
|
||||
check(info, "unregister");
|
||||
|
||||
// Check that listeners are primed at the next browser startup.
|
||||
[info] = await Promise.all([
|
||||
promiseObservable("prime-event-listener", 3),
|
||||
AddonTestUtils.promiseStartupManager(false),
|
||||
]);
|
||||
check(info, "prime");
|
||||
|
||||
// Check that primed listeners are converted to regular listeners
|
||||
// when the background page is started after browser startup.
|
||||
let p = promiseObservable("convert-event-listener", 3);
|
||||
Services.obs.notifyObservers(null, "sessionstore-windows-restored");
|
||||
info = await p;
|
||||
|
||||
check(info, "convert");
|
||||
|
||||
await extension.awaitMessage("ready");
|
||||
|
||||
// Check that when the event is triggered, all the plumbing worked
|
||||
// correctly for the primed-then-converted listener.
|
||||
let eventDetails = {test: "kaboom"};
|
||||
let eventSubject = {wrappedJSObject: eventDetails};
|
||||
Services.obs.notifyObservers(eventSubject, "fire-onEvent1");
|
||||
|
||||
let details = await extension.awaitMessage("listener1");
|
||||
deepEqual(details, eventDetails, "Listener 1 fired");
|
||||
details = await extension.awaitMessage("listener2");
|
||||
deepEqual(details, eventDetails, "Listener 2 fired");
|
||||
|
||||
// Check that the converted listener is properly unregistered at
|
||||
// browser shutdown.
|
||||
[info] = await Promise.all([
|
||||
promiseObservable("unregister-primed-listener", 3),
|
||||
AddonTestUtils.promiseShutdownManager(),
|
||||
]);
|
||||
check(info, "unregister");
|
||||
|
||||
// Start up again, listener should be primed
|
||||
[info] = await Promise.all([
|
||||
promiseObservable("prime-event-listener", 3),
|
||||
AddonTestUtils.promiseStartupManager(false),
|
||||
]);
|
||||
check(info, "prime");
|
||||
|
||||
// Check that triggering the event before the listener has been converted
|
||||
// causes the background page to be loaded and the listener to be converted,
|
||||
// and the listener is invoked.
|
||||
p = promiseObservable("convert-event-listener", 3);
|
||||
eventDetails.test = "startup event";
|
||||
Services.obs.notifyObservers(eventSubject, "fire-onEvent2");
|
||||
info = await p;
|
||||
|
||||
check(info, "convert");
|
||||
|
||||
details = await extension.awaitMessage("listener3");
|
||||
deepEqual(details, eventDetails, "Listener 3 fired for event during startup");
|
||||
|
||||
await extension.awaitMessage("ready");
|
||||
|
||||
// Check that the unregister process works when we manually remove
|
||||
// a listener.
|
||||
p = promiseObservable("unregister-primed-listener", 1);
|
||||
extension.sendMessage("unregister2");
|
||||
info = await p;
|
||||
check(info, "unregister", {listener1: false, listener2: false});
|
||||
|
||||
// Check that we only get unregisters for the remaining events after
|
||||
// one listener has been removed.
|
||||
info = await promiseObservable("unregister-primed-listener", 2,
|
||||
() => AddonTestUtils.promiseShutdownManager());
|
||||
check(info, "unregister", {listener3: false});
|
||||
|
||||
// Check that after restart, only listeners that were present at
|
||||
// the end of the last session are primed.
|
||||
info = await promiseObservable("prime-event-listener", 2,
|
||||
() => AddonTestUtils.promiseStartupManager(false));
|
||||
check(info, "prime", {listener3: false});
|
||||
|
||||
// Check that if the background script does not re-register listeners,
|
||||
// the primed listeners are unregistered after the background page
|
||||
// starts up.
|
||||
p = promiseObservable("unregister-primed-listener", 1,
|
||||
() => extension.awaitMessage("ready"));
|
||||
Services.obs.notifyObservers(null, "sessionstore-windows-restored");
|
||||
info = await p;
|
||||
check(info, "unregister", {listener1: false, listener3: false});
|
||||
|
||||
// Just listener1 should be registered now, fire event1 to confirm.
|
||||
eventDetails.test = "third time";
|
||||
Services.obs.notifyObservers(eventSubject, "fire-onEvent1");
|
||||
details = await extension.awaitMessage("listener1");
|
||||
deepEqual(details, eventDetails, "Listener 1 fired");
|
||||
|
||||
// Tell the extension not to re-register listener1 on the next startup
|
||||
extension.sendMessage("unregister1");
|
||||
await extension.awaitMessage("unregistered");
|
||||
|
||||
// Shut down, start up
|
||||
info = await promiseObservable("unregister-primed-listener", 1,
|
||||
() => AddonTestUtils.promiseShutdownManager());
|
||||
check(info, "unregister", {listener2: false, listener3: false});
|
||||
|
||||
info = await promiseObservable("prime-event-listener", 1,
|
||||
() => AddonTestUtils.promiseStartupManager(false));
|
||||
check(info, "register", {listener2: false, listener3: false});
|
||||
|
||||
// Check that firing event1 causes the listener fire callback to
|
||||
// reject.
|
||||
p = promiseObservable("listener-callback-exception", 1);
|
||||
Services.obs.notifyObservers(eventSubject, "fire-onEvent1");
|
||||
await p;
|
||||
ok(true, "Primed listener that was not re-registered received an error when event was triggered during startup");
|
||||
|
||||
await extension.awaitMessage("ready");
|
||||
|
||||
await extension.unload();
|
||||
|
||||
await AddonTestUtils.promiseShutdownManager();
|
||||
});
|
|
@ -0,0 +1,40 @@
|
|||
"use strict";
|
||||
|
||||
AddonTestUtils.init(this);
|
||||
AddonTestUtils.overrideCertDB();
|
||||
AddonTestUtils.createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1");
|
||||
|
||||
// Tests that startupData is persisted and is available at startup
|
||||
add_task(async function test_startupData() {
|
||||
await AddonTestUtils.promiseStartupManager();
|
||||
|
||||
let wrapper = ExtensionTestUtils.loadExtension({useAddonManager: "permanent"});
|
||||
await wrapper.startup();
|
||||
|
||||
let {extension} = wrapper;
|
||||
|
||||
deepEqual(extension.startupData, {}, "startupData for a new extension defaults to empty object");
|
||||
|
||||
const DATA = {test: "i am some startup data"};
|
||||
extension.startupData = DATA;
|
||||
extension.saveStartupData();
|
||||
|
||||
await AddonTestUtils.promiseRestartManager();
|
||||
await wrapper.startupPromise;
|
||||
|
||||
({extension} = wrapper);
|
||||
deepEqual(extension.startupData, DATA, "startupData is present on restart");
|
||||
|
||||
const DATA2 = {other: "this is different data"};
|
||||
extension.startupData = DATA2;
|
||||
extension.saveStartupData();
|
||||
|
||||
await AddonTestUtils.promiseRestartManager();
|
||||
await wrapper.startupPromise;
|
||||
|
||||
({extension} = wrapper);
|
||||
deepEqual(extension.startupData, DATA2, "updated startupData is present on restart");
|
||||
|
||||
await wrapper.unload();
|
||||
await AddonTestUtils.promiseShutdownManager();
|
||||
});
|
|
@ -61,6 +61,7 @@ skip-if = (os == "win" && !debug) #Bug 1419183 disable on Windows
|
|||
[test_ext_onmessage_removelistener.js]
|
||||
skip-if = true # This test no longer tests what it is meant to test.
|
||||
[test_ext_permission_xhr.js]
|
||||
[test_ext_persistent_events.js]
|
||||
[test_ext_privacy.js]
|
||||
[test_ext_privacy_disable.js]
|
||||
[test_ext_privacy_update.js]
|
||||
|
@ -83,6 +84,7 @@ skip-if = true # bug 1315829
|
|||
[test_ext_sandbox_var.js]
|
||||
[test_ext_schema.js]
|
||||
[test_ext_simple.js]
|
||||
[test_ext_startupData.js]
|
||||
[test_ext_startup_cache.js]
|
||||
skip-if = os == "android"
|
||||
[test_ext_startup_perf.js]
|
||||
|
|
|
@ -47,7 +47,7 @@ static mozilla::LazyLogModule gResistFingerprintingLog("nsResistFingerprinting")
|
|||
#define RESIST_FINGERPRINTING_PREF "privacy.resistFingerprinting"
|
||||
#define RFP_TIMER_PREF "privacy.reduceTimerPrecision"
|
||||
#define RFP_TIMER_VALUE_PREF "privacy.resistFingerprinting.reduceTimerPrecision.microseconds"
|
||||
#define RFP_TIMER_VALUE_DEFAULT 100
|
||||
#define RFP_TIMER_VALUE_DEFAULT 1000
|
||||
#define RFP_JITTER_VALUE_PREF "privacy.resistFingerprinting.reduceTimerPrecision.jitter"
|
||||
#define RFP_JITTER_VALUE_DEFAULT true
|
||||
#define RFP_SPOOFED_FRAMES_PER_SEC_PREF "privacy.resistFingerprinting.video_frames_per_sec"
|
||||
|
|
|
@ -106,7 +106,14 @@ async function waitForEvent(...eventTypes) {
|
|||
}
|
||||
|
||||
add_task(async function setup() {
|
||||
await SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]});
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
"set": [
|
||||
["media.cache_size", 40000],
|
||||
["full-screen-api.enabled", true],
|
||||
["full-screen-api.allow-trusted-requests-only", false],
|
||||
["full-screen-api.transition-duration.enter", "0 0"],
|
||||
["full-screen-api.transition-duration.leave", "0 0"]
|
||||
]});
|
||||
await new Promise(resolve => {
|
||||
video.addEventListener("canplaythrough", resolve, {once: true});
|
||||
video.src = "seek_with_sound.ogg";
|
||||
|
@ -378,6 +385,28 @@ add_task(async function ensure_video_pause() {
|
|||
}
|
||||
});
|
||||
|
||||
// Bug 1452342: Make sure the cursor hides and shows on full screen mode.
|
||||
add_task(async function ensure_fullscreen_cursor() {
|
||||
video.removeAttribute("mozNoDynamicControls");
|
||||
video.play();
|
||||
await waitForEvent("play");
|
||||
|
||||
video.mozRequestFullScreen();
|
||||
await waitForEvent("mozfullscreenchange");
|
||||
|
||||
const controlsSpacer = getAnonElementWithinVideoByAttribute(video, "anonid", "controlsSpacer");
|
||||
is(controlsSpacer.hasAttribute("hideCursor"), true, "Cursor is hidden");
|
||||
|
||||
// Wiggle the mouse a bit
|
||||
synthesizeMouse(video, playButtonCenterX, playButtonCenterY, { type: "mousemove" });
|
||||
|
||||
is(controlsSpacer.hasAttribute("hideCursor"), false, "Cursor is shown");
|
||||
|
||||
// Restore
|
||||
video.setAttribute("mozNoDynamicControls", "");
|
||||
document.mozCancelFullScreen();
|
||||
});
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
|
|
|
@ -1193,6 +1193,10 @@
|
|||
return;
|
||||
}
|
||||
|
||||
if (element == this.controlBar) {
|
||||
this.controlsSpacer.removeAttribute("hideCursor");
|
||||
}
|
||||
|
||||
// Unhide
|
||||
element.hidden = false;
|
||||
} else {
|
||||
|
|
|
@ -3121,6 +3121,26 @@ var AddonManagerPrivate = {
|
|||
let provider = AddonManagerInternal._getProviderByName("XPIProvider");
|
||||
return provider ? provider.isDBLoaded : false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets startupData for the given addon. The provided data will be stored
|
||||
* in addonsStartup.json so it is available early during browser startup.
|
||||
* Note that this file is read synchronously at startup, so startupData
|
||||
* should be used with care.
|
||||
*
|
||||
* @param {string} aID
|
||||
* The id of the addon to save startup data for.
|
||||
* @param {any} aData
|
||||
* The data to store. Must be JSON serializable.
|
||||
*/
|
||||
setStartupData(aID, aData) {
|
||||
if (!gStarted)
|
||||
throw Components.Exception("AddonManager is not initialized",
|
||||
Cr.NS_ERROR_NOT_INITIALIZED);
|
||||
|
||||
AddonManagerInternal._getProviderByName("XPIProvider")
|
||||
.setStartupData(aID, aData);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -1297,7 +1297,9 @@ class XPIState {
|
|||
|
||||
this.version = aDBAddon.version;
|
||||
this.type = aDBAddon.type;
|
||||
this.startupData = aDBAddon.startupData;
|
||||
if (aDBAddon.startupData) {
|
||||
this.startupData = aDBAddon.startupData;
|
||||
}
|
||||
|
||||
this.bootstrapped = !!aDBAddon.bootstrap;
|
||||
if (this.bootstrapped) {
|
||||
|
@ -3450,6 +3452,23 @@ var XPIProvider = {
|
|||
return addon.wrapper;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets startupData for the given addon. The provided data will be stored
|
||||
* in addonsStartup.json so it is available early during browser startup.
|
||||
* Note that this file is read synchronously at startup, so startupData
|
||||
* should be used with care.
|
||||
*
|
||||
* @param {string} aID
|
||||
* The id of the addon to save startup data for.
|
||||
* @param {any} aData
|
||||
* The data to store. Must be JSON serializable.
|
||||
*/
|
||||
setStartupData(aID, aData) {
|
||||
let state = XPIStates.findAddon(aID);
|
||||
state.startupData = aData;
|
||||
XPIStates.save();
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns an Addon corresponding to an instance ID.
|
||||
* @param aInstanceID
|
||||
|
|
|
@ -38,6 +38,14 @@
|
|||
|
||||
#include "wayland/gtk-primary-selection-client-protocol.h"
|
||||
|
||||
const char*
|
||||
nsRetrievalContextWayland::sTextMimeTypes[TEXT_MIME_TYPES_NUM] =
|
||||
{
|
||||
"text/plain;charset=utf-8",
|
||||
"UTF8_STRING",
|
||||
"COMPOUND_TEXT"
|
||||
};
|
||||
|
||||
void
|
||||
DataOffer::AddMIMEType(const char *aMimeType)
|
||||
{
|
||||
|
@ -64,6 +72,17 @@ DataOffer::GetTargets(int* aTargetNum)
|
|||
return targetList;
|
||||
}
|
||||
|
||||
bool
|
||||
DataOffer::HasTarget(const char *aMimeType)
|
||||
{
|
||||
int length = mTargetMIMETypes.Length();
|
||||
for (int32_t j = 0; j < length; j++) {
|
||||
if (mTargetMIMETypes[j] == gdk_atom_intern(aMimeType, FALSE))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
char*
|
||||
DataOffer::GetData(wl_display* aDisplay, const char* aMimeType,
|
||||
uint32_t* aContentLength)
|
||||
|
@ -699,6 +718,25 @@ nsRetrievalContextWayland::GetClipboardData(const char* aMimeType,
|
|||
return reinterpret_cast<const char*>(mClipboardData);
|
||||
}
|
||||
|
||||
const char*
|
||||
nsRetrievalContextWayland::GetClipboardText(int32_t aWhichClipboard)
|
||||
{
|
||||
GdkAtom selection = GetSelectionAtom(aWhichClipboard);
|
||||
DataOffer* dataOffer = (selection == GDK_SELECTION_PRIMARY) ?
|
||||
mPrimaryOffer : mClipboardOffer;
|
||||
if (!dataOffer)
|
||||
return nullptr;
|
||||
|
||||
for (unsigned int i = 0; i < sizeof(sTextMimeTypes); i++) {
|
||||
if (dataOffer->HasTarget(sTextMimeTypes[i])) {
|
||||
uint32_t unused;
|
||||
return GetClipboardData(sTextMimeTypes[i], aWhichClipboard,
|
||||
&unused);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void nsRetrievalContextWayland::ReleaseClipboardData(const char* aClipboardData)
|
||||
{
|
||||
NS_ASSERTION(aClipboardData == mClipboardData,
|
||||
|
|
|
@ -23,6 +23,8 @@ public:
|
|||
void AddMIMEType(const char *aMimeType);
|
||||
|
||||
GdkAtom* GetTargets(int* aTargetNum);
|
||||
bool HasTarget(const char *aMimeType);
|
||||
|
||||
char* GetData(wl_display* aDisplay, const char* aMimeType,
|
||||
uint32_t* aContentLength);
|
||||
|
||||
|
@ -65,6 +67,7 @@ public:
|
|||
virtual const char* GetClipboardData(const char* aMimeType,
|
||||
int32_t aWhichClipboard,
|
||||
uint32_t* aContentLength) override;
|
||||
virtual const char* GetClipboardText(int32_t aWhichClipboard) override;
|
||||
virtual void ReleaseClipboardData(const char* aClipboardData) override;
|
||||
|
||||
virtual GdkAtom* GetTargets(int32_t aWhichClipboard,
|
||||
|
@ -104,6 +107,11 @@ private:
|
|||
int mClipboardRequestNumber;
|
||||
char* mClipboardData;
|
||||
uint32_t mClipboardDataLength;
|
||||
|
||||
// Mime types used for text data at Gtk+, see request_text_received_func()
|
||||
// at gtkclipboard.c.
|
||||
#define TEXT_MIME_TYPES_NUM 3
|
||||
static const char* sTextMimeTypes[TEXT_MIME_TYPES_NUM];
|
||||
};
|
||||
|
||||
#endif /* __nsClipboardWayland_h_ */
|
||||
|
|
Загрузка…
Ссылка в новой задаче