Merge autoland to mozilla-central. a=merge

This commit is contained in:
Andreea Pavel 2018-04-10 00:54:26 +03:00
Родитель e71bd08575 c71f32b200
Коммит 92918fa21e
89 изменённых файлов: 2582 добавлений и 3928 удалений

Просмотреть файл

@ -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_ */