Merge mozilla-central to mozilla-inbound. a=merge on a CLOSED TREE

This commit is contained in:
Andreea Pavel 2018-04-10 00:58:54 +03:00
Родитель a2485252b4 8e723a7457
Коммит 0724b513f9
93 изменённых файлов: 2803 добавлений и 4165 удалений

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

@ -158,7 +158,7 @@ ifdef MOZ_ARTIFACT_BUILDS
$(call BUILDSTATUS,TIER_FINISH artifact) $(call BUILDSTATUS,TIER_FINISH artifact)
endif endif
$(call BUILDSTATUS,TIER_START tup) $(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) $(call BUILDSTATUS,TIER_FINISH tup)
.PHONY: $(addprefix install-,$(install_manifests)) .PHONY: $(addprefix install-,$(install_manifests))

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

@ -1,5 +1,5 @@
<?xml version='1.0' encoding='UTF-8'?> <?xml version='1.0' encoding='UTF-8'?>
<blocklist lastupdate="1522103097333" xmlns="http://www.mozilla.org/2006/addons-blocklist"> <blocklist lastupdate="1523286321447" xmlns="http://www.mozilla.org/2006/addons-blocklist">
<emItems> <emItems>
<emItem blockID="i334" id="{0F827075-B026-42F3-885D-98981EE7B1AE}"> <emItem blockID="i334" id="{0F827075-B026-42F3-885D-98981EE7B1AE}">
<prefs/> <prefs/>
@ -2235,6 +2235,10 @@
<prefs/> <prefs/>
<versionRange minVersion="0" maxVersion="*" severity="1"/> <versionRange minVersion="0" maxVersion="*" severity="1"/>
</emItem> </emItem>
<emItem blockID="36f97298-8bef-4372-a548-eb829413bee9" id="/(__TEMPLATE__APPLICATION__@ruta-mapa\.com)|(application-3@findizer\.fr)|(application2@allo-pages\.fr)|(application2@bilan-imc\.fr)|(application2@lettres\.net)|(application2@search-maps-finder\.com)|(application-imcpeso@imc-peso\.com)|(application-meuimc@meu-imc\.com)|(application-us2@factorlove)|(application-us@misterdirections)|(application-us@yummmi\.es)|(application@amiouze\.fr)|(application@astrolignes\.com)|(application@blotyn\.com)|(application@bmi-result\.com)|(application@bmi-tw\.com)|(application@calcolo-bmi\.com)|(application@cartes-itineraires\.com)|(application@convertisseur\.pro)|(application@de-findizer\.fr)|(application@de-super-rezepte\.com)|(application@dermabeauty\.fr)|(application@dev\.squel\.v2)|(application@eu-my-drivingdirections\.com)|(application@fr-allo-pages\.fr)|(application@fr-catizz\.com)|(application@fr-mr-traduction\.com)|(application@good-recettes\.com)|(application@horaires\.voyage)|(application@imc-calcular\.com)|(application@imc-peso\.com)|(application@it-mio-percorso\.com)|(application@iti-maps\.fr)|(application@itineraire\.info)|(application@lbc-search\.com)|(application@les-pages\.com)|(application@lovincalculator\.com)|(application@lovintest\.com)|(application@masowe\.com)|(application@matchs\.direct)|(application@mein-bmi\.com)|(application@mes-resultats\.com)|(application@mestaf\.com)|(application@meu-imc\.com)|(application@mon-calcul-imc\.fr)|(application@mon-juste-poids\.com)|(application@mon-trajet\.com)|(application@my-drivingdirections\.com)|(application@people-show\.com)|(application@plans-reduc\.fr)|(application@point-meteo\.fr)|(application@poulixo\.com)|(application@quipage\.fr)|(application@quizdeamor\.com)|(application@quizdoamor\.com)|(application@quotient-retraite\.fr)|(application@recettes\.net)|(application@routenplaner-karten\.com)|(application@ruta-mapa\.com)|(application@satellite\.dev\.squel\.v2)|(application@search-bilan-imc\.fr)|(application@search-maps-finder\.com)|(application@slimness\.fr)|(application@start-bmi\.com)|(application@tests-moi\.com)|(application@tousmesjeux\.fr)|(application@toutlannuaire\.fr)|(application@tuto-diy\.com)|(application@ubersetzung-app\.com)|(application@uk-cookyummy\.com)|(application@uk-howlogin\.me)|(application@uk-myloap\.com)|(application@voyagevoyage\.co)|(application@wikimot\.fr)|(application@www\.plans-reduc\.fr)|(application@yummmi\.es)|(application@yummmies\.be)|(application@yummmies\.ch)|(application@yummmies\.fr)|(application@yummmies\.lu)|(application@zikplay\.fr)|(applicationY@search-maps-finder\.com)|(cmesapps@findizer\.fr)|(findizer-shopping@jetpack)|(\{8aaebb36-1488-4022-b7ec-29b790d12c17\})/">
<prefs/>
<versionRange minVersion="0" maxVersion="*" severity="3"/>
</emItem>
</emItems> </emItems>
<pluginItems> <pluginItems>
<pluginItem blockID="p332"> <pluginItem blockID="p332">

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

@ -129,6 +129,7 @@ class BasePopup {
} }
if (panel && panel.id !== REMOTE_PANEL_ID) { if (panel && panel.id !== REMOTE_PANEL_ID) {
panel.style.removeProperty("--arrowpanel-background"); panel.style.removeProperty("--arrowpanel-background");
panel.style.removeProperty("--arrowpanel-border-color");
panel.removeAttribute("remote"); panel.removeAttribute("remote");
} }
@ -355,9 +356,19 @@ class BasePopup {
this.browser.dispatchEvent(event); this.browser.dispatchEvent(event);
} }
setBackground(background = "") { setBackground(background) {
if (background) { // Panels inherit the applied theme (light, dark, etc) and there is a high
this.panel.style.setProperty("--arrowpanel-background", background); // 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; this.background = background;
} }

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

@ -33,21 +33,25 @@ this.devtools_network = class extends ExtensionAPI {
return { return {
devtools: { devtools: {
network: { network: {
onRequestFinished: new EventManager(context, "devtools.network.onRequestFinished", fire => { onRequestFinished: new EventManager({
let onFinished = (data) => { context,
const loader = new ChildNetworkResponseLoader(context, data.requestId); name: "devtools.network.onRequestFinished",
const harEntry = {...data.harEntry, ...loader.api()}; register: fire => {
const result = Cu.cloneInto(harEntry, context.cloneScope, { let onFinished = (data) => {
cloneFunctions: true, const loader = new ChildNetworkResponseLoader(context, data.requestId);
}); const harEntry = {...data.harEntry, ...loader.api()};
fire.asyncWithoutClone(result); const result = Cu.cloneInto(harEntry, context.cloneScope, {
}; cloneFunctions: true,
});
fire.asyncWithoutClone(result);
};
let parent = context.childManager.getParentEvent("devtools.network.onRequestFinished"); let parent = context.childManager.getParentEvent("devtools.network.onRequestFinished");
parent.addListener(onFinished); parent.addListener(onFinished);
return () => { return () => {
parent.removeListener(onFinished); parent.removeListener(onFinished);
}; };
},
}).api(), }).api(),
}, },
}, },

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

@ -94,8 +94,10 @@ class ChildDevToolsPanel extends ExtensionUtils.EventEmitter {
api() { api() {
return { return {
onShown: new EventManager( onShown: new EventManager({
this.context, "devtoolsPanel.onShown", fire => { context: this.context,
name: "devtoolsPanel.onShown",
register: fire => {
const listener = (eventName, panelContentWindow) => { const listener = (eventName, panelContentWindow) => {
fire.asyncWithoutClone(panelContentWindow); fire.asyncWithoutClone(panelContentWindow);
}; };
@ -103,10 +105,13 @@ class ChildDevToolsPanel extends ExtensionUtils.EventEmitter {
return () => { return () => {
this.off("shown", listener); this.off("shown", listener);
}; };
}).api(), },
}).api(),
onHidden: new EventManager( onHidden: new EventManager({
this.context, "devtoolsPanel.onHidden", fire => { context: this.context,
name: "devtoolsPanel.onHidden",
register: fire => {
const listener = () => { const listener = () => {
fire.async(); fire.async();
}; };
@ -114,7 +119,8 @@ class ChildDevToolsPanel extends ExtensionUtils.EventEmitter {
return () => { return () => {
this.off("hidden", listener); this.off("hidden", listener);
}; };
}).api(), },
}).api(),
// TODO(rpl): onSearch event and createStatusBarButton method // TODO(rpl): onSearch event and createStatusBarButton method
}; };
@ -189,8 +195,10 @@ class ChildDevToolsInspectorSidebar extends ExtensionUtils.EventEmitter {
const {context, id} = this; const {context, id} = this;
return { return {
onShown: new EventManager( onShown: new EventManager({
context, "devtoolsInspectorSidebar.onShown", fire => { context,
name: "devtoolsInspectorSidebar.onShown",
register: fire => {
const listener = (eventName, panelContentWindow) => { const listener = (eventName, panelContentWindow) => {
fire.asyncWithoutClone(panelContentWindow); fire.asyncWithoutClone(panelContentWindow);
}; };
@ -198,10 +206,13 @@ class ChildDevToolsInspectorSidebar extends ExtensionUtils.EventEmitter {
return () => { return () => {
this.off("shown", listener); this.off("shown", listener);
}; };
}).api(), },
}).api(),
onHidden: new EventManager( onHidden: new EventManager({
context, "devtoolsInspectorSidebar.onHidden", fire => { context,
name: "devtoolsInspectorSidebar.onHidden",
register: fire => {
const listener = () => { const listener = () => {
fire.async(); fire.async();
}; };
@ -209,7 +220,8 @@ class ChildDevToolsInspectorSidebar extends ExtensionUtils.EventEmitter {
return () => { return () => {
this.off("hidden", listener); this.off("hidden", listener);
}; };
}).api(), },
}).api(),
setObject(jsonObject, rootTitle) { setObject(jsonObject, rootTitle) {
return context.cloneScope.Promise.resolve().then(() => { return context.cloneScope.Promise.resolve().then(() => {
@ -279,8 +291,10 @@ this.devtools_panels = class extends ExtensionAPI {
get themeName() { get themeName() {
return themeChangeObserver.themeName; return themeChangeObserver.themeName;
}, },
onThemeChanged: new EventManager( onThemeChanged: new EventManager({
context, "devtools.panels.onThemeChanged", fire => { context,
name: "devtools.panels.onThemeChanged",
register: fire => {
const listener = (eventName, themeName) => { const listener = (eventName, themeName) => {
fire.async(themeName); fire.async(themeName);
}; };
@ -288,7 +302,8 @@ this.devtools_panels = class extends ExtensionAPI {
return () => { return () => {
themeChangeObserver.off("themeChanged", listener); themeChangeObserver.off("themeChanged", listener);
}; };
}).api(), },
}).api(),
}, },
}, },
}; };

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

@ -159,17 +159,21 @@ this.menusInternal = class extends ExtensionAPI {
return context.childManager.callParentAsyncFunction("menusInternal.removeAll", []); return context.childManager.callParentAsyncFunction("menusInternal.removeAll", []);
}, },
onClicked: new EventManager(context, "menus.onClicked", fire => { onClicked: new EventManager({
let listener = (info, tab) => { context,
withHandlingUserInput(context.contentWindow, name: "menus.onClicked",
() => fire.sync(info, tab)); register: fire => {
}; let listener = (info, tab) => {
withHandlingUserInput(context.contentWindow,
() => fire.sync(info, tab));
};
let event = context.childManager.getParentEvent("menusInternal.onClicked"); let event = context.childManager.getParentEvent("menusInternal.onClicked");
event.addListener(listener); event.addListener(listener);
return () => { return () => {
event.removeListener(listener); event.removeListener(listener);
}; };
},
}).api(), }).api(),
}, },
}; };

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

@ -6,19 +6,23 @@ this.omnibox = class extends ExtensionAPI {
getAPI(context) { getAPI(context) {
return { return {
omnibox: { omnibox: {
onInputChanged: new EventManager(context, "omnibox.onInputChanged", fire => { onInputChanged: new EventManager({
let listener = (text, id) => { context,
fire.asyncWithoutClone(text, suggestions => { name: "omnibox.onInputChanged",
context.childManager.callParentFunctionNoReturn("omnibox.addSuggestions", [ register: fire => {
id, let listener = (text, id) => {
suggestions, 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); };
}; context.childManager.getParentEvent("omnibox.onInputChanged").addListener(listener);
return () => {
context.childManager.getParentEvent("omnibox.onInputChanged").removeListener(listener);
};
},
}).api(), }).api(),
}, },
}; };

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

@ -7,7 +7,6 @@ module.exports = {
"Tab": true, "Tab": true,
"TabContext": true, "TabContext": true,
"Window": true, "Window": true,
"WindowEventManager": true,
"actionContextMenu": true, "actionContextMenu": true,
"browserActionFor": true, "browserActionFor": true,
"getContainerForCookieStoreId": true, "getContainerForCookieStoreId": true,

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

@ -329,56 +329,72 @@ this.bookmarks = class extends ExtensionAPI {
} }
}, },
onCreated: new EventManager(context, "bookmarks.onCreated", fire => { onCreated: new EventManager({
let listener = (event, bookmark) => { context,
fire.sync(bookmark.id, bookmark); name: "bookmarks.onCreated",
}; register: fire => {
let listener = (event, bookmark) => {
fire.sync(bookmark.id, bookmark);
};
observer.on("created", listener); observer.on("created", listener);
incrementListeners(); incrementListeners();
return () => { return () => {
observer.off("created", listener); observer.off("created", listener);
decrementListeners(); decrementListeners();
}; };
},
}).api(), }).api(),
onRemoved: new EventManager(context, "bookmarks.onRemoved", fire => { onRemoved: new EventManager({
let listener = (event, data) => { context,
fire.sync(data.guid, data.info); name: "bookmarks.onRemoved",
}; register: fire => {
let listener = (event, data) => {
fire.sync(data.guid, data.info);
};
observer.on("removed", listener); observer.on("removed", listener);
incrementListeners(); incrementListeners();
return () => { return () => {
observer.off("removed", listener); observer.off("removed", listener);
decrementListeners(); decrementListeners();
}; };
},
}).api(), }).api(),
onChanged: new EventManager(context, "bookmarks.onChanged", fire => { onChanged: new EventManager({
let listener = (event, data) => { context,
fire.sync(data.guid, data.info); name: "bookmarks.onChanged",
}; register: fire => {
let listener = (event, data) => {
fire.sync(data.guid, data.info);
};
observer.on("changed", listener); observer.on("changed", listener);
incrementListeners(); incrementListeners();
return () => { return () => {
observer.off("changed", listener); observer.off("changed", listener);
decrementListeners(); decrementListeners();
}; };
},
}).api(), }).api(),
onMoved: new EventManager(context, "bookmarks.onMoved", fire => { onMoved: new EventManager({
let listener = (event, data) => { context,
fire.sync(data.guid, data.info); name: "bookmarks.onMoved",
}; register: fire => {
let listener = (event, data) => {
fire.sync(data.guid, data.info);
};
observer.on("moved", listener); observer.on("moved", listener);
incrementListeners(); incrementListeners();
return () => { return () => {
observer.off("moved", listener); observer.off("moved", listener);
decrementListeners(); decrementListeners();
}; };
},
}).api(), }).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 { class TabTracker extends TabTrackerBase {
constructor() { constructor() {
super(); super();

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

@ -584,15 +584,20 @@ this.browserAction = class extends ExtensionAPI {
return { return {
browserAction: { browserAction: {
onClicked: new InputEventManager(context, "browserAction.onClicked", fire => { onClicked: new EventManager({
let listener = (event, browser) => { context,
context.withPendingBrowser(browser, () => name: "browserAction.onClicked",
fire.sync(tabManager.convert(tabTracker.activeTab))); inputHandling: true,
}; register: fire => {
browserAction.on("click", listener); let listener = (event, browser) => {
return () => { context.withPendingBrowser(browser, () =>
browserAction.off("click", listener); fire.sync(tabManager.convert(tabTracker.activeTab)));
}; };
browserAction.on("click", listener);
return () => {
browserAction.off("click", listener);
};
},
}).api(), }).api(),
enable: function(tabId) { enable: function(tabId) {

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

@ -364,14 +364,18 @@ this.commands = class extends ExtensionAPI {
this.registerKeys(commands); this.registerKeys(commands);
} }
}, },
onCommand: new EventManager(context, "commands.onCommand", fire => { onCommand: new EventManager({
let listener = (eventName, commandName) => { context,
fire.async(commandName); name: "commands.onCommand",
}; register: fire => {
this.on("command", listener); let listener = (eventName, commandName) => {
return () => { fire.async(commandName);
this.off("command", listener); };
}; this.on("command", listener);
return () => {
this.off("command", listener);
};
},
}).api(), }).api(),
}, },
}; };

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

@ -15,37 +15,45 @@ this.devtools_network = class extends ExtensionAPI {
return { return {
devtools: { devtools: {
network: { network: {
onNavigated: new EventManager(context, "devtools.onNavigated", fire => { onNavigated: new EventManager({
let listener = data => { context,
fire.async(data.url); name: "devtools.onNavigated",
}; register: fire => {
let listener = data => {
fire.async(data.url);
};
let targetPromise = getDevToolsTargetForContext(context); let targetPromise = getDevToolsTargetForContext(context);
targetPromise.then(target => {
target.on("navigate", listener);
});
return () => {
targetPromise.then(target => { targetPromise.then(target => {
target.off("navigate", listener); target.on("navigate", listener);
}); });
}; return () => {
targetPromise.then(target => {
target.off("navigate", listener);
});
};
},
}).api(), }).api(),
getHAR: function() { getHAR: function() {
return context.devToolsToolbox.getHARFromNetMonitor(); return context.devToolsToolbox.getHARFromNetMonitor();
}, },
onRequestFinished: new EventManager(context, "devtools.network.onRequestFinished", fire => { onRequestFinished: new EventManager({
const listener = (data) => { context,
fire.async(data); name: "devtools.network.onRequestFinished",
}; register: fire => {
const listener = (data) => {
fire.async(data);
};
const toolbox = context.devToolsToolbox; const toolbox = context.devToolsToolbox;
toolbox.addRequestFinishedListener(listener); toolbox.addRequestFinishedListener(listener);
return () => { return () => {
toolbox.removeRequestFinishedListener(listener); toolbox.removeRequestFinishedListener(listener);
}; };
},
}).api(), }).api(),
// The following method is used internally to allow the request API // The following method is used internally to allow the request API

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

@ -525,8 +525,10 @@ this.devtools_panels = class extends ExtensionAPI {
devtools: { devtools: {
panels: { panels: {
elements: { elements: {
onSelectionChanged: new EventManager( onSelectionChanged: new EventManager({
context, "devtools.panels.elements.onSelectionChanged", fire => { context,
name: "devtools.panels.elements.onSelectionChanged",
register: fire => {
const listener = (eventName) => { const listener = (eventName) => {
fire.async(); fire.async();
}; };
@ -534,7 +536,8 @@ this.devtools_panels = class extends ExtensionAPI {
return () => { return () => {
toolboxSelectionObserver.off("selectionChanged", listener); toolboxSelectionObserver.off("selectionChanged", listener);
}; };
}).api(), },
}).api(),
createSidebarPane(title) { createSidebarPane(title) {
const id = `devtools-inspector-sidebar-${makeWidgetId(newBasePanelId())}`; 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}.`); throw new Error(`Ran out of options to get symbols from library ${debugName} ${breakpadId}.`);
}, },
onRunning: new EventManager(context, "geckoProfiler.onRunning", fire => { onRunning: new EventManager({
isRunningObserver.addObserver(fire.async); context,
return () => { name: "geckoProfiler.onRunning",
isRunningObserver.removeObserver(fire.async); register: fire => {
}; isRunningObserver.addObserver(fire.async);
return () => {
isRunningObserver.removeObserver(fire.async);
};
},
}).api(), }).api(),
}, },
}; };

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

@ -220,37 +220,49 @@ this.history = class extends ExtensionAPI {
return Promise.resolve(results); return Promise.resolve(results);
}, },
onVisited: new EventManager(context, "history.onVisited", fire => { onVisited: new EventManager({
let listener = (event, data) => { context,
fire.sync(data); name: "history.onVisited",
}; register: fire => {
let listener = (event, data) => {
fire.sync(data);
};
getHistoryObserver().on("visited", listener); getHistoryObserver().on("visited", listener);
return () => { return () => {
getHistoryObserver().off("visited", listener); getHistoryObserver().off("visited", listener);
}; };
},
}).api(), }).api(),
onVisitRemoved: new EventManager(context, "history.onVisitRemoved", fire => { onVisitRemoved: new EventManager({
let listener = (event, data) => { context,
fire.sync(data); name: "history.onVisitRemoved",
}; register: fire => {
let listener = (event, data) => {
fire.sync(data);
};
getHistoryObserver().on("visitRemoved", listener); getHistoryObserver().on("visitRemoved", listener);
return () => { return () => {
getHistoryObserver().off("visitRemoved", listener); getHistoryObserver().off("visitRemoved", listener);
}; };
},
}).api(), }).api(),
onTitleChanged: new EventManager(context, "history.onTitleChanged", fire => { onTitleChanged: new EventManager({
let listener = (event, data) => { context,
fire.sync(data); name: "history.onTitleChanged",
}; register: fire => {
let listener = (event, data) => {
fire.sync(data);
};
getHistoryObserver().on("titleChanged", listener); getHistoryObserver().on("titleChanged", listener);
return () => { return () => {
getHistoryObserver().off("titleChanged", listener); getHistoryObserver().off("titleChanged", listener);
}; };
},
}).api(), }).api(),
}, },
}; };

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

@ -813,40 +813,48 @@ this.menusInternal = class extends ExtensionAPI {
gMenuBuilder.rebuildMenu(extension); gMenuBuilder.rebuildMenu(extension);
}, },
onShown: new EventManager(context, "menus.onShown", fire => { onShown: new EventManager({
let listener = (event, menuIds, contextData) => { context,
let info = { name: "menus.onShown",
menuIds, register: fire => {
contexts: Array.from(getMenuContexts(contextData)), let listener = (event, menuIds, contextData) => {
}; let info = {
menuIds,
contexts: Array.from(getMenuContexts(contextData)),
};
// The menus.onShown event is fired before the user has consciously // The menus.onShown event is fired before the user has consciously
// interacted with an extension, so we require permissions before // interacted with an extension, so we require permissions before
// exposing sensitive contextual data. // exposing sensitive contextual data.
let includeSensitiveData = let includeSensitiveData =
extension.tabManager.hasActiveTabPermission(contextData.tab) || extension.tabManager.hasActiveTabPermission(contextData.tab) ||
extension.whiteListedHosts.matches(contextData.inFrame ? contextData.frameUrl : contextData.pageUrl); extension.whiteListedHosts.matches(contextData.inFrame ? contextData.frameUrl : contextData.pageUrl);
addMenuEventInfo(info, contextData, includeSensitiveData); addMenuEventInfo(info, contextData, includeSensitiveData);
let tab = extension.tabManager.convert(contextData.tab); let tab = extension.tabManager.convert(contextData.tab);
fire.sync(info, tab); fire.sync(info, tab);
}; };
gOnShownSubscribers.add(extension); gOnShownSubscribers.add(extension);
extension.on("webext-menu-shown", listener); extension.on("webext-menu-shown", listener);
return () => { return () => {
gOnShownSubscribers.delete(extension); gOnShownSubscribers.delete(extension);
extension.off("webext-menu-shown", listener); extension.off("webext-menu-shown", listener);
}; };
},
}).api(), }).api(),
onHidden: new EventManager(context, "menus.onHidden", fire => { onHidden: new EventManager({
let listener = () => { context,
fire.sync(); name: "menus.onHidden",
}; register: fire => {
extension.on("webext-menu-hidden", listener); let listener = () => {
return () => { fire.sync();
extension.off("webext-menu-hidden", listener); };
}; extension.on("webext-menu-hidden", listener);
return () => {
extension.off("webext-menu-hidden", listener);
};
},
}).api(), }).api(),
}; };
@ -883,18 +891,22 @@ this.menusInternal = class extends ExtensionAPI {
} }
}, },
onClicked: new EventManager(context, "menusInternal.onClicked", fire => { onClicked: new EventManager({
let listener = (event, info, nativeTab) => { context,
let {linkedBrowser} = nativeTab || tabTracker.activeTab; name: "menusInternal.onClicked",
let tab = nativeTab && extension.tabManager.convert(nativeTab); register: fire => {
context.withPendingBrowser(linkedBrowser, let listener = (event, info, nativeTab) => {
() => fire.sync(info, tab)); 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); extension.on("webext-menu-menuitem-click", listener);
return () => { return () => {
extension.off("webext-menu-menuitem-click", listener); extension.off("webext-menu-menuitem-click", listener);
}; };
},
}).api(), }).api(),
}, },
}; };

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

@ -37,34 +37,46 @@ this.omnibox = class extends ExtensionAPI {
} }
}, },
onInputStarted: new EventManager(context, "omnibox.onInputStarted", fire => { onInputStarted: new EventManager({
let listener = (eventName) => { context,
fire.sync(); name: "omnibox.onInputStarted",
}; register: fire => {
extension.on(ExtensionSearchHandler.MSG_INPUT_STARTED, listener); let listener = (eventName) => {
return () => { fire.sync();
extension.off(ExtensionSearchHandler.MSG_INPUT_STARTED, listener); };
}; extension.on(ExtensionSearchHandler.MSG_INPUT_STARTED, listener);
return () => {
extension.off(ExtensionSearchHandler.MSG_INPUT_STARTED, listener);
};
},
}).api(), }).api(),
onInputCancelled: new EventManager(context, "omnibox.onInputCancelled", fire => { onInputCancelled: new EventManager({
let listener = (eventName) => { context,
fire.sync(); name: "omnibox.onInputCancelled",
}; register: fire => {
extension.on(ExtensionSearchHandler.MSG_INPUT_CANCELLED, listener); let listener = (eventName) => {
return () => { fire.sync();
extension.off(ExtensionSearchHandler.MSG_INPUT_CANCELLED, listener); };
}; extension.on(ExtensionSearchHandler.MSG_INPUT_CANCELLED, listener);
return () => {
extension.off(ExtensionSearchHandler.MSG_INPUT_CANCELLED, listener);
};
},
}).api(), }).api(),
onInputEntered: new EventManager(context, "omnibox.onInputEntered", fire => { onInputEntered: new EventManager({
let listener = (eventName, text, disposition) => { context,
fire.sync(text, disposition); name: "omnibox.onInputEntered",
}; register: fire => {
extension.on(ExtensionSearchHandler.MSG_INPUT_ENTERED, listener); let listener = (eventName, text, disposition) => {
return () => { fire.sync(text, disposition);
extension.off(ExtensionSearchHandler.MSG_INPUT_ENTERED, listener); };
}; extension.on(ExtensionSearchHandler.MSG_INPUT_ENTERED, listener);
return () => {
extension.off(ExtensionSearchHandler.MSG_INPUT_ENTERED, listener);
};
},
}).api(), }).api(),
// Internal APIs. // Internal APIs.
@ -77,14 +89,18 @@ this.omnibox = class extends ExtensionAPI {
} }
}, },
onInputChanged: new EventManager(context, "omnibox.onInputChanged", fire => { onInputChanged: new EventManager({
let listener = (eventName, text, id) => { context,
fire.sync(text, id); name: "omnibox.onInputChanged",
}; register: fire => {
extension.on(ExtensionSearchHandler.MSG_INPUT_CHANGED, listener); let listener = (eventName, text, id) => {
return () => { fire.sync(text, id);
extension.off(ExtensionSearchHandler.MSG_INPUT_CHANGED, listener); };
}; extension.on(ExtensionSearchHandler.MSG_INPUT_CHANGED, listener);
return () => {
extension.off(ExtensionSearchHandler.MSG_INPUT_CHANGED, listener);
};
},
}).api(), }).api(),
}, },
}; };

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

@ -346,16 +346,21 @@ this.pageAction = class extends ExtensionAPI {
return { return {
pageAction: { pageAction: {
onClicked: new InputEventManager(context, "pageAction.onClicked", fire => { onClicked: new EventManager({
let listener = (evt, tab) => { context,
context.withPendingBrowser(tab.linkedBrowser, () => name: "pageAction.onClicked",
fire.sync(tabManager.convert(tab))); inputHandling: true,
}; register: fire => {
let listener = (evt, tab) => {
context.withPendingBrowser(tab.linkedBrowser, () =>
fire.sync(tabManager.convert(tab)));
};
pageAction.on("click", listener); pageAction.on("click", listener);
return () => { return () => {
pageAction.off("click", listener); pageAction.off("click", listener);
}; };
},
}).api(), }).api(),
show(tabId) { show(tabId) {

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

@ -204,15 +204,19 @@ this.sessions = class extends ExtensionAPI {
SessionStore.deleteWindowValue(win, encodedKey); SessionStore.deleteWindowValue(win, encodedKey);
}, },
onChanged: new EventManager(context, "sessions.onChanged", fire => { onChanged: new EventManager({
let observer = () => { context,
fire.async(); name: "sessions.onChanged",
}; register: fire => {
let observer = () => {
fire.async();
};
Services.obs.addObserver(observer, SS_ON_CLOSED_OBJECTS_CHANGED); Services.obs.addObserver(observer, SS_ON_CLOSED_OBJECTS_CHANGED);
return () => { return () => {
Services.obs.removeObserver(observer, SS_ON_CLOSED_OBJECTS_CHANGED); Services.obs.removeObserver(observer, SS_ON_CLOSED_OBJECTS_CHANGED);
}; };
},
}).api(), }).api(),
}, },
}; };

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

@ -110,7 +110,7 @@ const allProperties = new Set([
const restricted = new Set(["url", "favIconUrl", "title"]); const restricted = new Set(["url", "favIconUrl", "title"]);
class TabsUpdateFilterEventManager extends EventManager { class TabsUpdateFilterEventManager extends EventManager {
constructor(context, eventName) { constructor(context) {
let {extension} = context; let {extension} = context;
let {tabManager} = extension; let {tabManager} = extension;
@ -286,7 +286,11 @@ class TabsUpdateFilterEventManager extends EventManager {
}; };
}; };
super(context, eventName, register); super({
context,
name: "tabs.onUpdated",
register,
});
} }
addListener(callback, filter) { addListener(callback, filter) {
@ -338,26 +342,34 @@ this.tabs = class extends ExtensionAPI {
let self = { let self = {
tabs: { tabs: {
onActivated: new EventManager(context, "tabs.onActivated", fire => { onActivated: new EventManager({
let listener = (eventName, event) => { context,
fire.async(event); name: "tabs.onActivated",
}; register: fire => {
let listener = (eventName, event) => {
fire.async(event);
};
tabTracker.on("tab-activated", listener); tabTracker.on("tab-activated", listener);
return () => { return () => {
tabTracker.off("tab-activated", listener); tabTracker.off("tab-activated", listener);
}; };
},
}).api(), }).api(),
onCreated: new EventManager(context, "tabs.onCreated", fire => { onCreated: new EventManager({
let listener = (eventName, event) => { context,
fire.async(tabManager.convert(event.nativeTab, event.currentTab)); name: "tabs.onCreated",
}; register: fire => {
let listener = (eventName, event) => {
fire.async(tabManager.convert(event.nativeTab, event.currentTab));
};
tabTracker.on("tab-created", listener); tabTracker.on("tab-created", listener);
return () => { return () => {
tabTracker.off("tab-created", listener); tabTracker.off("tab-created", listener);
}; };
},
}).api(), }).api(),
/** /**
@ -366,98 +378,122 @@ this.tabs = class extends ExtensionAPI {
* the tabId in an array to match the API. * the tabId in an array to match the API.
* @see https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/Tabs/onHighlighted * @see https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/Tabs/onHighlighted
*/ */
onHighlighted: new EventManager(context, "tabs.onHighlighted", fire => { onHighlighted: new EventManager({
let listener = (eventName, event) => { context,
fire.async({tabIds: [event.tabId], windowId: event.windowId}); name: "tabs.onHighlighted",
}; register: fire => {
let listener = (eventName, event) => {
fire.async({tabIds: [event.tabId], windowId: event.windowId});
};
tabTracker.on("tab-activated", listener); tabTracker.on("tab-activated", listener);
return () => { return () => {
tabTracker.off("tab-activated", listener); tabTracker.off("tab-activated", listener);
}; };
},
}).api(), }).api(),
onAttached: new EventManager(context, "tabs.onAttached", fire => { onAttached: new EventManager({
let listener = (eventName, event) => { context,
fire.async(event.tabId, {newWindowId: event.newWindowId, newPosition: event.newPosition}); name: "tabs.onAttached",
}; register: fire => {
let listener = (eventName, event) => {
fire.async(event.tabId, {newWindowId: event.newWindowId, newPosition: event.newPosition});
};
tabTracker.on("tab-attached", listener); tabTracker.on("tab-attached", listener);
return () => { return () => {
tabTracker.off("tab-attached", listener); tabTracker.off("tab-attached", listener);
}; };
},
}).api(), }).api(),
onDetached: new EventManager(context, "tabs.onDetached", fire => { onDetached: new EventManager({
let listener = (eventName, event) => { context,
fire.async(event.tabId, {oldWindowId: event.oldWindowId, oldPosition: event.oldPosition}); name: "tabs.onDetached",
}; register: fire => {
let listener = (eventName, event) => {
fire.async(event.tabId, {oldWindowId: event.oldWindowId, oldPosition: event.oldPosition});
};
tabTracker.on("tab-detached", listener); tabTracker.on("tab-detached", listener);
return () => { return () => {
tabTracker.off("tab-detached", listener); tabTracker.off("tab-detached", listener);
}; };
},
}).api(), }).api(),
onRemoved: new EventManager(context, "tabs.onRemoved", fire => { onRemoved: new EventManager({
let listener = (eventName, event) => { context,
fire.async(event.tabId, {windowId: event.windowId, isWindowClosing: event.isWindowClosing}); name: "tabs.onRemoved",
}; register: fire => {
let listener = (eventName, event) => {
fire.async(event.tabId, {windowId: event.windowId, isWindowClosing: event.isWindowClosing});
};
tabTracker.on("tab-removed", listener); tabTracker.on("tab-removed", listener);
return () => { return () => {
tabTracker.off("tab-removed", listener); tabTracker.off("tab-removed", listener);
}; };
},
}).api(), }).api(),
onReplaced: new EventManager(context, "tabs.onReplaced", fire => { onReplaced: new EventManager({
return () => {}; context,
name: "tabs.onReplaced",
register: fire => {
return () => {};
},
}).api(), }).api(),
onMoved: new EventManager(context, "tabs.onMoved", fire => { onMoved: new EventManager({
// There are certain circumstances where we need to ignore a move event. context,
// name: "tabs.onMoved",
// Namely, the first time the tab is moved after it's created, we need register: fire => {
// to report the final position as the initial position in the tab's // There are certain circumstances where we need to ignore a move event.
// onAttached or onCreated event. This is because most tabs are inserted //
// in a temporary location and then moved after the TabOpen event fires, // Namely, the first time the tab is moved after it's created, we need
// which generates a TabOpen event followed by a TabMove event, which // to report the final position as the initial position in the tab's
// does not match the contract of our API. // onAttached or onCreated event. This is because most tabs are inserted
let ignoreNextMove = new WeakSet(); // 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 => { let openListener = event => {
ignoreNextMove.add(event.target); ignoreNextMove.add(event.target);
// Remove the tab from the set on the next tick, since it will already // Remove the tab from the set on the next tick, since it will already
// have been moved by then. // have been moved by then.
Promise.resolve().then(() => { Promise.resolve().then(() => {
ignoreNextMove.delete(event.target); ignoreNextMove.delete(event.target);
}); });
}; };
let moveListener = event => { let moveListener = event => {
let nativeTab = event.originalTarget; let nativeTab = event.originalTarget;
if (ignoreNextMove.has(nativeTab)) { if (ignoreNextMove.has(nativeTab)) {
ignoreNextMove.delete(nativeTab); ignoreNextMove.delete(nativeTab);
return; return;
} }
fire.async(tabTracker.getId(nativeTab), { fire.async(tabTracker.getId(nativeTab), {
windowId: windowTracker.getId(nativeTab.ownerGlobal), windowId: windowTracker.getId(nativeTab.ownerGlobal),
fromIndex: event.detail, fromIndex: event.detail,
toIndex: nativeTab._tPos, toIndex: nativeTab._tPos,
}); });
}; };
windowTracker.addListener("TabMove", moveListener); windowTracker.addListener("TabMove", moveListener);
windowTracker.addListener("TabOpen", openListener); windowTracker.addListener("TabOpen", openListener);
return () => { return () => {
windowTracker.removeListener("TabMove", moveListener); windowTracker.removeListener("TabMove", moveListener);
windowTracker.removeListener("TabOpen", openListener); windowTracker.removeListener("TabOpen", openListener);
}; };
},
}).api(), }).api(),
onUpdated: new TabsUpdateFilterEventManager(context, "tabs.onUpdated").api(), onUpdated: new TabsUpdateFilterEventManager(context).api(),
create(createProperties) { create(createProperties) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -896,75 +932,79 @@ this.tabs = class extends ExtensionAPI {
return Promise.resolve(); return Promise.resolve();
}, },
onZoomChange: new EventManager(context, "tabs.onZoomChange", fire => { onZoomChange: new EventManager({
let getZoomLevel = browser => { context,
let {ZoomManager} = browser.ownerGlobal; 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. // Stores the last known zoom level for each tab's browser.
// WeakMap[<browser> -> number] // WeakMap[<browser> -> number]
let zoomLevels = new WeakMap(); let zoomLevels = new WeakMap();
// Store the zoom level for all existing tabs. // Store the zoom level for all existing tabs.
for (let window of windowTracker.browserWindows()) { for (let window of windowTracker.browserWindows()) {
for (let nativeTab of window.gBrowser.tabs) { for (let nativeTab of window.gBrowser.tabs) {
let browser = nativeTab.linkedBrowser; let browser = nativeTab.linkedBrowser;
zoomLevels.set(browser, getZoomLevel(browser));
}
}
let tabCreated = (eventName, event) => {
let browser = event.nativeTab.linkedBrowser;
zoomLevels.set(browser, getZoomLevel(browser)); zoomLevels.set(browser, getZoomLevel(browser));
} };
}
let tabCreated = (eventName, event) => {
let browser = event.nativeTab.linkedBrowser;
zoomLevels.set(browser, getZoomLevel(browser));
};
let zoomListener = event => { let zoomListener = event => {
let browser = event.originalTarget; let browser = event.originalTarget;
// For non-remote browsers, this event is dispatched on the document // For non-remote browsers, this event is dispatched on the document
// rather than on the <browser>. // rather than on the <browser>.
if (browser instanceof Ci.nsIDOMDocument) { if (browser instanceof Ci.nsIDOMDocument) {
browser = browser.docShell.chromeEventHandler; browser = browser.docShell.chromeEventHandler;
} }
let {gBrowser} = browser.ownerGlobal; let {gBrowser} = browser.ownerGlobal;
let nativeTab = gBrowser.getTabForBrowser(browser); let nativeTab = gBrowser.getTabForBrowser(browser);
if (!nativeTab) { if (!nativeTab) {
// We only care about zoom events in the top-level browser of a tab. // We only care about zoom events in the top-level browser of a tab.
return; return;
} }
let oldZoomFactor = zoomLevels.get(browser); let oldZoomFactor = zoomLevels.get(browser);
let newZoomFactor = getZoomLevel(browser); let newZoomFactor = getZoomLevel(browser);
if (oldZoomFactor != newZoomFactor) { if (oldZoomFactor != newZoomFactor) {
zoomLevels.set(browser, newZoomFactor); zoomLevels.set(browser, newZoomFactor);
let tabId = tabTracker.getId(nativeTab); let tabId = tabTracker.getId(nativeTab);
fire.async({ fire.async({
tabId, tabId,
oldZoomFactor, oldZoomFactor,
newZoomFactor, newZoomFactor,
zoomSettings: self.tabs._getZoomSettings(tabId), zoomSettings: self.tabs._getZoomSettings(tabId),
}); });
} }
}; };
tabTracker.on("tab-attached", tabCreated); tabTracker.on("tab-attached", tabCreated);
tabTracker.on("tab-created", tabCreated); tabTracker.on("tab-created", tabCreated);
windowTracker.addListener("FullZoomChange", zoomListener); windowTracker.addListener("FullZoomChange", zoomListener);
windowTracker.addListener("TextZoomChange", zoomListener); windowTracker.addListener("TextZoomChange", zoomListener);
return () => { return () => {
tabTracker.off("tab-attached", tabCreated); tabTracker.off("tab-attached", tabCreated);
tabTracker.off("tab-created", tabCreated); tabTracker.off("tab-created", tabCreated);
windowTracker.removeListener("FullZoomChange", zoomListener); windowTracker.removeListener("FullZoomChange", zoomListener);
windowTracker.removeListener("TextZoomChange", zoomListener); windowTracker.removeListener("TextZoomChange", zoomListener);
}; };
},
}).api(), }).api(),
print() { print() {

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

@ -16,6 +16,36 @@ const onXULFrameLoaderCreated = ({target}) => {
target.messageManager.sendAsyncMessage("AllowScriptsToClose", {}); 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 { this.windows = class extends ExtensionAPI {
getAPI(context) { getAPI(context) {
let {extension} = context; let {extension} = context;
@ -24,38 +54,40 @@ this.windows = class extends ExtensionAPI {
return { return {
windows: { windows: {
onCreated: onCreated: WindowEventManager(context, "windows.onCreated", "domwindowopened", (fire, window) => {
new WindowEventManager(context, "windows.onCreated", "domwindowopened", (fire, window) => {
fire.async(windowManager.convert(window)); fire.async(windowManager.convert(window));
}).api(), }),
onRemoved: onRemoved: WindowEventManager(context, "windows.onRemoved", "domwindowclosed", (fire, window) => {
new WindowEventManager(context, "windows.onRemoved", "domwindowclosed", (fire, window) => {
fire.async(windowTracker.getId(window)); fire.async(windowTracker.getId(window));
}).api(), }),
onFocusChanged: new EventManager(context, "windows.onFocusChanged", fire => { onFocusChanged: new EventManager({
// Keep track of the last windowId used to fire an onFocusChanged event context,
let lastOnFocusChangedWindowId; name: "windows.onFocusChanged",
register: fire => {
// Keep track of the last windowId used to fire an onFocusChanged event
let lastOnFocusChangedWindowId;
let listener = event => { let listener = event => {
// Wait a tick to avoid firing a superfluous WINDOW_ID_NONE // Wait a tick to avoid firing a superfluous WINDOW_ID_NONE
// event when switching focus between two Firefox windows. // event when switching focus between two Firefox windows.
Promise.resolve().then(() => { Promise.resolve().then(() => {
let window = Services.focus.activeWindow; let window = Services.focus.activeWindow;
let windowId = window ? windowTracker.getId(window) : Window.WINDOW_ID_NONE; let windowId = window ? windowTracker.getId(window) : Window.WINDOW_ID_NONE;
if (windowId !== lastOnFocusChangedWindowId) { if (windowId !== lastOnFocusChangedWindowId) {
fire.async(windowId); fire.async(windowId);
lastOnFocusChangedWindowId = windowId; lastOnFocusChangedWindowId = windowId;
} }
}); });
}; };
windowTracker.addListener("focus", listener); windowTracker.addListener("focus", listener);
windowTracker.addListener("blur", listener); windowTracker.addListener("blur", listener);
return () => { return () => {
windowTracker.removeListener("focus", listener); windowTracker.removeListener("focus", listener);
windowTracker.removeListener("blur", listener); windowTracker.removeListener("blur", listener);
}; };
},
}).api(), }).api(),
get: function(windowId, getInfo) { get: function(windowId, getInfo) {

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

@ -3,114 +3,123 @@
/* eslint-disable mozilla/no-arbitrary-setTimeout */ /* eslint-disable mozilla/no-arbitrary-setTimeout */
"use strict"; "use strict";
add_task(async function testPopupBackground() { async function testPanel(browser, standAlone, initial_background) {
let extension = ExtensionTestUtils.loadExtension({ let panel = getPanelForNode(browser);
background() { let arrowContent = document.getAnonymousElementByAttribute(panel, "class", "panel-arrowcontent");
browser.tabs.query({active: true, currentWindow: true}, tabs => { let arrow = document.getAnonymousElementByAttribute(panel, "anonid", "arrow");
browser.pageAction.show(tabs[0].id);
});
},
manifest: { let checkArrow = (background = null) => {
"browser_action": { if (background == null || !standAlone) {
"default_popup": "popup.html", ok(!arrow.style.hasOwnProperty("fill"), "Arrow fill should be the default one");
"browser_style": false, return;
},
"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 setBackground = color => { is(getComputedStyle(arrowContent).backgroundColor, background, "Arrow content should have correct background");
content.document.body.style.backgroundColor = color; is(getComputedStyle(arrow).fill, background, "Arrow should have correct background");
}; };
await new Promise(resolve => setTimeout(resolve, 100)); function getBackground(browser) {
return ContentTask.spawn(browser, null, async function() {
info("Test that initial background color is applied"); return content.getComputedStyle(content.document.body)
.backgroundColor;
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);
} }
{ let setBackground = color => {
info("Test stand-alone browserAction popup"); content.document.body.style.backgroundColor = color;
};
clickBrowserAction(extension); await new Promise(resolve => setTimeout(resolve, 100));
let browser = await awaitExtensionPanel(extension);
await testPanel(browser, true); info("Test that initial background color is applied");
await closeBrowserAction(extension); 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", iconURL: "resource:///chrome/browser/content/browser/defaultthemes/dark.icon.svg",
textcolor: "white", textcolor: "white",
accentcolor: "black", accentcolor: "black",
popup: "hsl(240, 5%, 5%)", popup: "#4a4a4f",
popup_text: "rgb(249, 249, 250)", popup_text: "rgba(249, 249, 250, 0.8)",
popup_border: "rgba(24, 26, 27, 0.14)", popup_border: "#27272b",
author: vendorShortName, author: vendorShortName,
}); });

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

@ -9,6 +9,11 @@
min-height: 20em; min-height: 20em;
} }
/* Show selected items in high contrast mode. */
#sitesList > richlistitem[selected] {
outline: 1px solid transparent;
}
#sitesList > richlistitem > hbox, #sitesList > richlistitem > hbox,
.item-box > label { .item-box > label {
-moz-box-flex: 1; -moz-box-flex: 1;

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

@ -705,15 +705,6 @@ DevTools.prototype = {
getToolboxes() { getToolboxes() {
return Array.from(this._toolboxes.values()); 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(); const gDevTools = exports.gDevTools = new DevTools();

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

@ -93,11 +93,6 @@ this.gDevTools = {
get _tools() { get _tools() {
return devtools._tools; return devtools._tools;
}, },
* [Symbol.iterator ]() {
for (let toolbox of this._toolboxes) {
yield toolbox;
}
}
}; };
gDevToolsMethods.forEach(name => { gDevToolsMethods.forEach(name => {
this.gDevTools[name] = (...args) => { this.gDevTools[name] = (...args) => {

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

@ -143,7 +143,7 @@ exports.viewSourceInScratchpad = async function(sourceURL, sourceLine) {
} }
// For scratchpads within toolbox // For scratchpads within toolbox
for (let [, toolbox] of gDevTools) { for (let toolbox of gDevTools.getToolboxes()) {
let scratchpadPanel = toolbox.getPanel("scratchpad"); let scratchpadPanel = toolbox.getPanel("scratchpad");
if (scratchpadPanel) { if (scratchpadPanel) {
let { scratchpad } = scratchpadPanel; let { scratchpad } = scratchpadPanel;

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

@ -4288,6 +4288,16 @@ SourceListener::SetEnabledFor(TrackID aTrackID, bool aEnable)
aTrackID == kAudioTrack ? "audio" : "video", aTrackID == kAudioTrack ? "audio" : "video",
aTrackID)); 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; state.mDeviceEnabled = aEnable;
if (mWindowListener) { if (mWindowListener) {

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

@ -77,21 +77,15 @@ struct LayersId {
return !(*this == aOther); 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 like so:
// std::unordered_map<LayersId, ValueType, LayersId::HashFn, LayersId::EqualFn> myMap; // std::unordered_map<LayersId, ValueType, LayersId::HashFn> myMap;
struct HashFn { struct HashFn {
std::size_t operator()(const LayersId& aKey) const std::size_t operator()(const LayersId& aKey) const
{ {
return std::hash<uint64_t>{}(aKey.mId); 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 { enum class LayersBackend : int8_t {

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

@ -779,8 +779,7 @@ private:
// protected by the mTestDataLock. // protected by the mTestDataLock.
std::unordered_map<LayersId, std::unordered_map<LayersId,
UniquePtr<APZTestData>, UniquePtr<APZTestData>,
LayersId::HashFn, LayersId::HashFn> mTestData;
LayersId::EqualFn> mTestData;
mutable mozilla::Mutex mTestDataLock; mutable mozilla::Mutex mTestDataLock;
// This must only be touched on the controller thread. // 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 // The set of focus targets received indexed by their layer tree ID
std::unordered_map<LayersId, std::unordered_map<LayersId,
FocusTarget, FocusTarget,
LayersId::HashFn, LayersId::HashFn> mFocusTree;
LayersId::EqualFn> mFocusTree;
// The focus sequence number of the last potentially focus changing event // The focus sequence number of the last potentially focus changing event
// processed by APZ. This number starts at one and increases monotonically. // 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() { @Override public void run() {
boolean startResult = boolean startResult =
startCaptureOnCameraThread(width, height, min_mfps, max_mfps); startCaptureOnCameraThread(width, height, min_mfps, max_mfps);
if (!startResult) {
Looper.myLooper().quit();
}
exchange(result, startResult); exchange(result, startResult);
} }
}); });
@ -316,6 +319,7 @@ public class VideoCaptureAndroid implements PreviewCallback, Callback {
cameraThreadHandler.post(new Runnable() { cameraThreadHandler.post(new Runnable() {
@Override public void run() { @Override public void run() {
boolean stopResult = stopCaptureOnCameraThread(); boolean stopResult = stopCaptureOnCameraThread();
Looper.myLooper().quit();
exchange(result, stopResult); exchange(result, stopResult);
} }
}); });
@ -348,7 +352,6 @@ public class VideoCaptureAndroid implements PreviewCallback, Callback {
} }
camera.release(); camera.release();
camera = null; camera = null;
Looper.myLooper().quit();
return true; return true;
} catch (IOException e) { } catch (IOException e) {
error = e; error = e;
@ -356,7 +359,6 @@ public class VideoCaptureAndroid implements PreviewCallback, Callback {
error = e; error = e;
} }
Log.e(TAG, "Failed to stop camera", error); Log.e(TAG, "Failed to stop camera", error);
Looper.myLooper().quit();
return false; return false;
} }

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

@ -116,7 +116,10 @@ int32_t VideoCaptureAndroid::OnIncomingFrame(uint8_t* videoFrame,
size_t videoFrameLength, size_t videoFrameLength,
int32_t degrees, int32_t degrees,
int64_t captureTime) { 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; return 0;
VideoRotation current_rotation = VideoRotation current_rotation =
@ -185,7 +188,7 @@ VideoCaptureAndroid::~VideoCaptureAndroid() {
int32_t VideoCaptureAndroid::StartCapture( int32_t VideoCaptureAndroid::StartCapture(
const VideoCaptureCapability& capability) { const VideoCaptureCapability& capability) {
CriticalSectionScoped cs(&_apiCs); _apiCs.Enter();
AttachThreadScoped ats(g_jvm_capture); AttachThreadScoped ats(g_jvm_capture);
JNIEnv* env = ats.env(); JNIEnv* env = ats.env();
@ -194,23 +197,32 @@ int32_t VideoCaptureAndroid::StartCapture(
WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, -1, WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, -1,
"%s: GetBestMatchedCapability failed: %dx%d", "%s: GetBestMatchedCapability failed: %dx%d",
__FUNCTION__, capability.width, capability.height); __FUNCTION__, capability.width, capability.height);
// Manual exit of critical section
_apiCs.Leave();
return -1; return -1;
} }
_captureDelay = _captureCapability.expectedCaptureDelay; _captureDelay = _captureCapability.expectedCaptureDelay;
jmethodID j_start = int width = _captureCapability.width;
env->GetMethodID(g_java_capturer_class, "startCapture", "(IIII)Z"); int height = _captureCapability.height;
assert(j_start);
int min_mfps = 0; int min_mfps = 0;
int max_mfps = 0; int max_mfps = 0;
_deviceInfo.GetMFpsRange(_deviceUniqueId, _captureCapability.maxFPS, _deviceInfo.GetMFpsRange(_deviceUniqueId, _captureCapability.maxFPS,
&min_mfps, &max_mfps); &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, bool started = env->CallBooleanMethod(_jCapturer, j_start,
_captureCapability.width, width, height,
_captureCapability.height,
min_mfps, max_mfps); min_mfps, max_mfps);
if (started) { if (started) {
CriticalSectionScoped cs(&_apiCs);
_requestedCapability = capability; _requestedCapability = capability;
_captureStarted = true; _captureStarted = true;
} }

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

@ -30,11 +30,11 @@ class GeckoViewSelectionActionContent extends GeckoViewContentModule {
this._actions = [{ this._actions = [{
id: "org.mozilla.geckoview.CUT", 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"), perform: _ => this._domWindowUtils.sendContentCommandEvent("cut"),
}, { }, {
id: "org.mozilla.geckoview.COPY", id: "org.mozilla.geckoview.COPY",
predicate: e => !e.collapsed, predicate: e => !e.collapsed && !this._isPasswordField(e),
perform: _ => this._domWindowUtils.sendContentCommandEvent("copy"), perform: _ => this._domWindowUtils.sendContentCommandEvent("copy"),
}, { }, {
id: "org.mozilla.geckoview.PASTE", id: "org.mozilla.geckoview.PASTE",
@ -70,6 +70,18 @@ class GeckoViewSelectionActionContent extends GeckoViewContentModule {
.getInterface(Ci.nsIDOMWindowUtils); .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) { _getSelectionController(aEvent) {
if (aEvent.selectionEditable) { if (aEvent.selectionEditable) {
const focus = aEvent.target.activeElement; const focus = aEvent.target.activeElement;
@ -118,12 +130,12 @@ class GeckoViewSelectionActionContent extends GeckoViewContentModule {
register() { register() {
debug("register"); debug("register");
addEventListener("mozcaretstatechanged", this); addEventListener("mozcaretstatechanged", this, { mozSystemGroup: true });
} }
unregister() { unregister() {
debug("unregister"); debug("unregister");
removeEventListener("mozcaretstatechanged", this); removeEventListener("mozcaretstatechanged", this, { mozSystemGroup: true });
} }
/** /**
@ -136,8 +148,7 @@ class GeckoViewSelectionActionContent extends GeckoViewContentModule {
if (this._isActive && !aEvent.caretVisible) { if (this._isActive && !aEvent.caretVisible) {
// For mozcaretstatechanged, "visibilitychange" means the caret is hidden. // For mozcaretstatechanged, "visibilitychange" means the caret is hidden.
reason = "visibilitychange"; reason = "visibilitychange";
} else if (this._isActive && } else if (!aEvent.collapsed &&
!aEvent.collapsed &&
!aEvent.selectionVisible) { !aEvent.selectionVisible) {
reason = "invisibleselection"; reason = "invisibleselection";
} else if (aEvent.selectionEditable && } else if (aEvent.selectionEditable &&
@ -161,13 +172,15 @@ class GeckoViewSelectionActionContent extends GeckoViewContentModule {
action => action.predicate.call(this, aEvent)); action => action.predicate.call(this, aEvent));
const offset = this._getFrameOffset(aEvent); const offset = this._getFrameOffset(aEvent);
const password = this._isPasswordField(aEvent);
const msg = { const msg = {
type: "GeckoView:ShowSelectionAction", type: "GeckoView:ShowSelectionAction",
seqNo: this._seqNo, seqNo: this._seqNo,
collapsed: aEvent.collapsed, collapsed: aEvent.collapsed,
editable: aEvent.selectionEditable, editable: aEvent.selectionEditable,
selection: aEvent.selectedTextContent, password,
selection: password ? "" : aEvent.selectedTextContent,
clientRect: !aEvent.boundingClientRect ? null : { clientRect: !aEvent.boundingClientRect ? null : {
left: aEvent.boundingClientRect.left + offset.left, left: aEvent.boundingClientRect.left + offset.left,
top: aEvent.boundingClientRect.top + offset.top, top: aEvent.boundingClientRect.top + offset.top,

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

@ -156,14 +156,18 @@ this.browserAction = class extends ExtensionAPI {
return { return {
browserAction: { browserAction: {
onClicked: new EventManager(context, "browserAction.onClicked", fire => { onClicked: new EventManager({
let listener = (event, tab) => { context,
fire.async(tabManager.convert(tab)); name: "browserAction.onClicked",
}; register: fire => {
browserActionMap.get(extension).on("click", listener); let listener = (event, tab) => {
return () => { fire.async(tabManager.convert(tab));
browserActionMap.get(extension).off("click", listener); };
}; browserActionMap.get(extension).on("click", listener);
return () => {
browserActionMap.get(extension).off("click", listener);
};
},
}).api(), }).api(),
setTitle: function(details) { setTitle: function(details) {

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

@ -232,14 +232,18 @@ this.pageAction = class extends ExtensionAPI {
return { return {
pageAction: { pageAction: {
onClicked: new EventManager(context, "pageAction.onClicked", fire => { onClicked: new EventManager({
let listener = (event, tab) => { context,
fire.async(tabManager.convert(tab)); name: "pageAction.onClicked",
}; register: fire => {
pageActionMap.get(extension).on("click", listener); let listener = (event, tab) => {
return () => { fire.async(tabManager.convert(tab));
pageActionMap.get(extension).off("click", listener); };
}; pageActionMap.get(extension).on("click", listener);
return () => {
pageActionMap.get(extension).off("click", listener);
};
},
}).api(), }).api(),
show(tabId) { show(tabId) {

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

@ -103,21 +103,25 @@ this.tabs = class extends ExtensionAPI {
let self = { let self = {
tabs: { 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); let tab = tabManager.get(data.id);
fire.async({tabId: tab.id, windowId: tab.windowId}); fire.async({tabId: tab.id, windowId: tab.windowId});
}).api(), }),
onCreated: new EventManager(context, "tabs.onCreated", fire => { onCreated: new EventManager({
let listener = (eventName, event) => { context,
fire.async(tabManager.convert(event.nativeTab)); name: "tabs.onCreated",
}; register: fire => {
let listener = (eventName, event) => {
fire.async(tabManager.convert(event.nativeTab));
};
tabTracker.on("tab-created", listener); tabTracker.on("tab-created", listener);
return () => { return () => {
tabTracker.off("tab-created", listener); tabTracker.off("tab-created", listener);
}; };
},
}).api(), }).api(),
/** /**
@ -126,114 +130,130 @@ this.tabs = class extends ExtensionAPI {
* the tabId in an array to match the API. * the tabId in an array to match the API.
* @see https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/Tabs/onHighlighted * @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); let tab = tabManager.get(data.id);
fire.async({tabIds: [tab.id], windowId: tab.windowId}); fire.async({tabIds: [tab.id], windowId: tab.windowId});
}),
onAttached: new EventManager({
context,
name: "tabs.onAttached",
register: fire => { return () => {}; },
}).api(), }).api(),
onAttached: new EventManager(context, "tabs.onAttached", fire => { onDetached: new EventManager({
return () => {}; context,
name: "tabs.onDetached",
register: fire => { return () => {}; },
}).api(), }).api(),
onDetached: new EventManager(context, "tabs.onDetached", fire => { onRemoved: new EventManager({
return () => {}; 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(), }).api(),
onRemoved: new EventManager(context, "tabs.onRemoved", fire => { onReplaced: new EventManager({
let listener = (eventName, event) => { context,
fire.async(event.tabId, {windowId: event.windowId, isWindowClosing: event.isWindowClosing}); name: "tabs.onReplaced",
}; register: fire => { return () => {}; },
tabTracker.on("tab-removed", listener);
return () => {
tabTracker.off("tab-removed", listener);
};
}).api(), }).api(),
onReplaced: new EventManager(context, "tabs.onReplaced", fire => { onMoved: new EventManager({
return () => {}; context,
name: "tabs.onMoved",
register: fire => { return () => {}; },
}).api(), }).api(),
onMoved: new EventManager(context, "tabs.onMoved", fire => { onUpdated: new EventManager({
return () => {}; context,
}).api(), name: "tabs.onUpdated",
register: fire => {
const restricted = ["url", "favIconUrl", "title"];
onUpdated: new EventManager(context, "tabs.onUpdated", fire => { function sanitize(extension, changeInfo) {
const restricted = ["url", "favIconUrl", "title"]; let result = {};
let nonempty = false;
function sanitize(extension, changeInfo) { for (let prop in changeInfo) {
let result = {}; if (extension.hasPermission("tabs") || !restricted.includes(prop)) {
let nonempty = false; nonempty = true;
for (let prop in changeInfo) { result[prop] = changeInfo[prop];
if (extension.hasPermission("tabs") || !restricted.includes(prop)) { }
nonempty = true;
result[prop] = changeInfo[prop];
} }
return [nonempty, result];
} }
return [nonempty, result];
}
let fireForTab = (tab, changed) => { let fireForTab = (tab, changed) => {
let [needed, changeInfo] = sanitize(extension, changed); let [needed, changeInfo] = sanitize(extension, changed);
if (needed) { if (needed) {
fire.async(tab.id, changeInfo, tab.convert()); fire.async(tab.id, changeInfo, tab.convert());
} }
}; };
let listener = event => { let listener = event => {
let needed = []; let needed = [];
let nativeTab; let nativeTab;
switch (event.type) { switch (event.type) {
case "DOMTitleChanged": { case "DOMTitleChanged": {
let {BrowserApp} = getBrowserWindow(event.target.ownerGlobal); let {BrowserApp} = getBrowserWindow(event.target.ownerGlobal);
nativeTab = BrowserApp.getTabForWindow(event.target.ownerGlobal); nativeTab = BrowserApp.getTabForWindow(event.target.ownerGlobal);
needed.push("title"); needed.push("title");
break; break;
}
case "DOMAudioPlaybackStarted":
case "DOMAudioPlaybackStopped": {
let {BrowserApp} = event.target.ownerGlobal;
nativeTab = BrowserApp.getTabForBrowser(event.originalTarget);
needed.push("audible");
break;
}
} }
case "DOMAudioPlaybackStarted": if (!nativeTab) {
case "DOMAudioPlaybackStopped": { return;
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;
} }
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); fireForTab(tab, changeInfo);
windowTracker.addListener("DOMTitleChanged", listener); };
return () => {
windowTracker.removeListener("status", statusListener); let statusListener = ({browser, status, url}) => {
windowTracker.removeListener("DOMTitleChanged", listener); 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(), }).api(),
async create(createProperties) { async create(createProperties) {

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

@ -187,11 +187,11 @@ class WindowTracker extends WindowTrackerBase {
} }
/** /**
* An event manager API provider which listens for an event in the Android * Helper to create an event manager which listens for an event in the Android
* global EventDispatcher, and calls the given listener function whenever an event * global EventDispatcher, and calls the given listener function whenever the
* is received. That listener function receives a `fire` object, which it can * event is received. That listener function receives a `fire` object,
* use to dispatch events to the extension, and an object detailing the * which it can use to dispatch events to the extension, and an object
* EventDispatcher event that was received. * detailing the EventDispatcher event that was received.
* *
* @param {BaseContext} context * @param {BaseContext} context
* The extension context which the event manager belongs to. * The extension context which the event manager belongs to.
@ -202,10 +202,14 @@ class WindowTracker extends WindowTrackerBase {
* @param {function} listener * @param {function} listener
* The listener function to call when an EventDispatcher event is * The listener function to call when an EventDispatcher event is
* recieved. * recieved.
*
* @returns {object} An injectable api for the new event.
*/ */
global.GlobalEventManager = class extends EventManager { global.makeGlobalEvent = function makeGlobalEvent(context, name, event, listener) {
constructor(context, name, event, listener) { return new EventManager({
super(context, name, fire => { context,
name,
register: fire => {
let listener2 = { let listener2 = {
onEvent(event, data, callback) { onEvent(event, data, callback) {
listener(fire, data); listener(fire, data);
@ -216,36 +220,8 @@ global.GlobalEventManager = class extends EventManager {
return () => { return () => {
GlobalEventDispatcher.unregisterListener(listener2, [event]); GlobalEventDispatcher.unregisterListener(listener2, [event]);
}; };
}); },
} }).api();
};
/**
* 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 { 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) = fun GeckoSession.getTestBytes(path: String) =
InstrumentationRegistry.getTargetContext().resources.assets InstrumentationRegistry.getTargetContext().resources.assets
.open(path.removePrefix("/assets/")).readBytes() .open(path.removePrefix("/assets/")).readBytes()

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

@ -22,12 +22,11 @@ class ContentDelegateTest : BaseSessionTest() {
@Test fun titleChange() { @Test fun titleChange() {
sessionRule.session.loadTestPath(TITLE_CHANGE_HTML_PATH) sessionRule.session.loadTestPath(TITLE_CHANGE_HTML_PATH)
val titles = mutableListOf("Title1", "Title2")
sessionRule.waitUntilCalled(object : Callbacks.ContentDelegate { sessionRule.waitUntilCalled(object : Callbacks.ContentDelegate {
@AssertCalled(count = 2) @AssertCalled(count = 2)
override fun onTitleChange(session: GeckoSession, title: String) { override fun onTitleChange(session: GeckoSession, title: String) {
assertThat("Title should match", title, 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 val info = sessionRule.currentCall
assertThat("Method info should be valid", info, notNullValue()) assertThat("Method info should be valid", info, notNullValue())
assertThat("Counter should be correct", assertThat("Counter should be correct",
info.counter, isOneOf(1, 2)) info.counter, equalTo(forEachCall(1, 2)))
assertThat("Order should equal counter", assertThat("Order should equal counter",
info.order, equalTo(info.counter)) info.order, equalTo(info.counter))
counter++ counter++

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

@ -94,8 +94,7 @@ class ProgressDelegateTest : BaseSessionTest() {
@AssertCalled(count = 2, order = intArrayOf(1, 3)) @AssertCalled(count = 2, order = intArrayOf(1, 3))
override fun onPageStart(session: GeckoSession, url: String) { override fun onPageStart(session: GeckoSession, url: String) {
assertThat("URL should match", url, assertThat("URL should match", url,
endsWith(if (sessionRule.currentCall.counter == 1) endsWith(forEachCall(INVALID_URI, HELLO_HTML_PATH)))
INVALID_URI else HELLO_HTML_PATH))
} }
@AssertCalled(count = 2, order = intArrayOf(2, 4)) @AssertCalled(count = 2, order = intArrayOf(2, 4))
@ -103,7 +102,7 @@ class ProgressDelegateTest : BaseSessionTest() {
// The first load is certain to fail because of interruption by the second load // 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. // or by invalid domain name, whereas the second load is certain to succeed.
assertThat("Success flag should match", success, assertThat("Success flag should match", success,
equalTo(sessionRule.currentCall.counter != 1)) equalTo(forEachCall(false, true)))
}; };
}) })
} }

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

@ -1267,8 +1267,9 @@ public class GeckoSessionTestRule extends UiThreadTestRule {
} }
/** /**
* Get information about the current call. Only valid during a {@link #forCallbacksDuringWait} * Get information about the current call. Only valid during a {@link
* callback. * #forCallbacksDuringWait}, {@link #delegateDuringNextWait}, or {@link
* #delegateUntilTestEnd} callback.
* *
* @return Call information * @return Call information
*/ */
@ -1412,4 +1413,22 @@ public class GeckoSessionTestRule extends UiThreadTestRule {
public GeckoSession createClosedSession(final GeckoSessionSettings settings) { public GeckoSession createClosedSession(final GeckoSessionSettings settings) {
return createSession(settings, /* open */ false); 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. * @return True if the action was performed.
*/ */
protected boolean performAction(final String id) { protected boolean performAction(final String id) {
if (mResponse == null) {
return false;
}
mResponse.respond(id); mResponse.respond(id);
// Android behavior is to clear selection on copy. // Android behavior is to clear selection on copy.

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

@ -1784,6 +1784,10 @@ public class GeckoSession extends LayerSession
* contentEditable node. * contentEditable node.
*/ */
final int FLAG_IS_EDITABLE = 2; final int FLAG_IS_EDITABLE = 2;
/**
* The selection is inside a password field.
*/
final int FLAG_IS_PASSWORD = 4;
@StringDef({ACTION_CUT, @StringDef({ACTION_CUT,
ACTION_COPY, ACTION_COPY,
@ -1859,7 +1863,9 @@ public class GeckoSession extends LayerSession
flags = (bundle.getBoolean("collapsed") ? flags = (bundle.getBoolean("collapsed") ?
SelectionActionDelegate.FLAG_IS_COLLAPSED : 0) | SelectionActionDelegate.FLAG_IS_COLLAPSED : 0) |
(bundle.getBoolean("editable") ? (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"); text = bundle.getString("selection");
final GeckoBundle rectBundle = bundle.getBundle("clientRect"); final GeckoBundle rectBundle = bundle.getBundle("clientRect");

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

@ -50,6 +50,8 @@ var ActionBarHandler = {
* (mozcaretstatechanged) events. * (mozcaretstatechanged) events.
*/ */
handleEvent: function(e) { handleEvent: function(e) {
e.stopImmediatePropagation();
// Close an open ActionBar, if carets no longer logically visible. // Close an open ActionBar, if carets no longer logically visible.
if (this._selectionID && !e.caretVisible) { if (this._selectionID && !e.caretVisible) {
this._uninit(false); this._uninit(false);

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

@ -1439,7 +1439,7 @@ pref("privacy.resistFingerprinting.autoDeclineNoUserInputCanvasPrompts", true);
// File.lastModified, audioContext.currentTime, canvas.captureStream.currentTime // File.lastModified, audioContext.currentTime, canvas.captureStream.currentTime
pref("privacy.reduceTimerPrecision", true); pref("privacy.reduceTimerPrecision", true);
// Dynamically tune the resolution of the timer reduction for both of the two above prefs // 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 // Enable jittering the clock one precision value forward
pref("privacy.resistFingerprinting.reduceTimerPrecision.jitter", true); pref("privacy.resistFingerprinting.reduceTimerPrecision.jitter", true);
// Lower the priority of network loads for resources on the tracking protection list. // 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. // Disable tab hiding API by default.
pref("extensions.webextensions.tabhide.enabled", false); pref("extensions.webextensions.tabhide.enabled", false);
pref("extensions.webextensions.background-delayed-startup", false);
// Report Site Issue button // Report Site Issue button
pref("extensions.webcompat-reporter.newIssueEndpoint", "https://webcompat.com/issues/new"); pref("extensions.webcompat-reporter.newIssueEndpoint", "https://webcompat.com/issues/new");
#if defined(MOZ_DEV_EDITION) || defined(NIGHTLY_BUILD) #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]; (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 // DohDecode() collects the TTL and the IP addresses in the response
// //
@ -458,6 +490,7 @@ TRR::DohDecode()
unsigned int index = 12; unsigned int index = 12;
uint8_t length; uint8_t length;
nsAutoCString host; nsAutoCString host;
nsresult rv;
LOG(("doh decode %s %d bytes\n", mHost.get(), mBodySize)); LOG(("doh decode %s %d bytes\n", mHost.get(), mBodySize));
@ -506,34 +539,9 @@ TRR::DohDecode()
answerRecords, mBodySize, host.get(), index)); answerRecords, mBodySize, host.get(), index));
while (answerRecords) { while (answerRecords) {
if (mBodySize < (index + 1)) { rv = PassQName(index);
LOG(("TRR: Dohdecode:%d fail at index %d\n", __LINE__, index)); if (NS_FAILED(rv)) {
return NS_ERROR_ILLEGAL_VALUE; return rv;
}
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);
} }
// 16 bit TYPE // 16 bit TYPE
if (mBodySize < (index + 2)) { if (mBodySize < (index + 2)) {
@ -590,7 +598,6 @@ TRR::DohDecode()
// - AAAA (TYPE 28): 16 bytes // - AAAA (TYPE 28): 16 bytes
// - NS (TYPE 2): N bytes // - NS (TYPE 2): N bytes
nsresult rv;
switch(TYPE) { switch(TYPE) {
case TRRTYPE_A: case TRRTYPE_A:
if (RDLENGTH != 4) { if (RDLENGTH != 4) {
@ -686,33 +693,9 @@ TRR::DohDecode()
uint16_t nsRecords = get16bit(mResponse, 8); uint16_t nsRecords = get16bit(mResponse, 8);
LOG(("TRR Decode: %d ns records (%u bytes body)\n", nsRecords, mBodySize)); LOG(("TRR Decode: %d ns records (%u bytes body)\n", nsRecords, mBodySize));
while (nsRecords) { while (nsRecords) {
if (mBodySize < (index + 1)) { rv = PassQName(index);
return NS_ERROR_ILLEGAL_VALUE; if (NS_FAILED(rv)) {
} return rv;
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);
} }
if (mBodySize < (index + 8)) { if (mBodySize < (index + 8)) {
@ -741,33 +724,9 @@ TRR::DohDecode()
LOG(("TRR Decode: %d additional resource records (%u bytes body)\n", LOG(("TRR Decode: %d additional resource records (%u bytes body)\n",
arRecords, mBodySize)); arRecords, mBodySize));
while (arRecords) { while (arRecords) {
if (mBodySize < (index + 1)) { rv = PassQName(index);
return NS_ERROR_ILLEGAL_VALUE; if (NS_FAILED(rv)) {
} return rv;
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);
} }
if (mBodySize < (index + 8)) { if (mBodySize < (index + 8)) {

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

@ -142,6 +142,7 @@ private:
~TRR() = default; ~TRR() = default;
nsresult SendHTTPRequest(); nsresult SendHTTPRequest();
nsresult DohEncode(nsCString &target); nsresult DohEncode(nsCString &target);
nsresult PassQName(unsigned int &index);
nsresult DohDecode(); nsresult DohDecode();
nsresult ReturnData(); nsresult ReturnData();
nsresult FailData(); nsresult FailData();

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

@ -240,11 +240,12 @@ class TupOnly(CommonBackend, PartialBackend):
for l in libs] for l in libs]
def _gen_shared_library(self, backend_file): 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. # This will fail to link currently due to missing rust symbols.
return return
if backend_file.shared_lib.cxx_link: if shlib.cxx_link:
mkshlib = ( mkshlib = (
[backend_file.environment.substs['CXX']] + [backend_file.environment.substs['CXX']] +
backend_file.local_flags['CXX_LDFLAGS'] backend_file.local_flags['CXX_LDFLAGS']
@ -258,15 +259,15 @@ class TupOnly(CommonBackend, PartialBackend):
mkshlib += ( mkshlib += (
backend_file.environment.substs['DSO_PIC_CFLAGS'] + backend_file.environment.substs['DSO_PIC_CFLAGS'] +
[backend_file.environment.substs['DSO_LDOPTS']] + [backend_file.environment.substs['DSO_LDOPTS']] +
['-Wl,-h,%s' % backend_file.shared_lib.soname] + ['-Wl,-h,%s' % shlib.soname] +
['-o', backend_file.shared_lib.lib_name] ['-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) static_libs = self._lib_paths(backend_file.objdir, static_libs)
shared_libs = self._lib_paths(backend_file.objdir, shared_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) list_file = self._make_list_file(backend_file.objdir, objs, list_file_name)
inputs = objs + static_libs + shared_libs inputs = objs + static_libs + shared_libs
@ -275,10 +276,10 @@ class TupOnly(CommonBackend, PartialBackend):
return return
symbols_file = [] symbols_file = []
if backend_file.shared_lib.symbols_file: if shlib.symbols_file:
inputs.append(backend_file.shared_lib.symbols_file) inputs.append(shlib.symbols_file)
# TODO: Assumes GNU LD # 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 = ( cmd = (
mkshlib + mkshlib +
@ -293,9 +294,14 @@ class TupOnly(CommonBackend, PartialBackend):
backend_file.rule( backend_file.rule(
cmd=cmd, cmd=cmd,
inputs=inputs, inputs=inputs,
outputs=[backend_file.shared_lib.lib_name], outputs=[shlib.lib_name],
display='LINK %o' 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): def _gen_program(self, backend_file):

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

@ -1163,4 +1163,4 @@ static const TransportSecurityPreload kPublicKeyPinningPreloadList[] = {
static const int32_t kUnknownId = -1; static const int32_t kUnknownId = -1;
static const PRTime kPreloadPKPinsExpirationTime = INT64_C(1531686800288000); static const PRTime kPreloadPKPinsExpirationTime = INT64_C(1531773471822000);

Разница между файлами не показана из-за своего большого размера Загрузить разницу

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

@ -8,7 +8,7 @@
/*****************************************************************************/ /*****************************************************************************/
#include <stdint.h> #include <stdint.h>
const PRTime gPreloadListExpirationTime = INT64_C(1534105987786000); const PRTime gPreloadListExpirationTime = INT64_C(1534192657699000);
%% %%
0-1.party, 1 0-1.party, 1
0.me.uk, 1 0.me.uk, 1
@ -1250,6 +1250,7 @@ ac-town.com, 1
ac0g.dyndns.org, 1 ac0g.dyndns.org, 1
academicexperts.us, 1 academicexperts.us, 1
academie-de-police.ch, 1 academie-de-police.ch, 1
academy4.net, 1
academytv.com.au, 1 academytv.com.au, 1
acaeum.com, 1 acaeum.com, 1
acampar.com.br, 1 acampar.com.br, 1
@ -1958,7 +1959,6 @@ alextsang.net, 1
alexvdveen.nl, 1 alexvdveen.nl, 1
alexvetter.de, 1 alexvetter.de, 1
alexwardweb.com, 1 alexwardweb.com, 1
alexyang.me, 1
alfa-tech.su, 1 alfa-tech.su, 1
alfaperfumes.com.br, 1 alfaperfumes.com.br, 1
alfaponny.se, 1 alfaponny.se, 1
@ -2241,6 +2241,7 @@ american.dating, 1
americandistribuidora.com, 1 americandistribuidora.com, 1
americanfoundationbr.com, 1 americanfoundationbr.com, 1
americanmediainstitute.com, 1 americanmediainstitute.com, 1
americanoutlawjeepparts.com, 1
americasbasementcontractor.com, 1 americasbasementcontractor.com, 1
americkykongres.cz, 1 americkykongres.cz, 1
amerigroup.com, 1 amerigroup.com, 1
@ -3764,7 +3765,6 @@ baldur.cc, 1
balenciaspa.com, 1 balenciaspa.com, 1
balia.de, 1 balia.de, 1
balicekzdravi.cz, 1 balicekzdravi.cz, 1
balidesignshop.com.br, 1
balikonos.cz, 1 balikonos.cz, 1
balinese.dating, 1 balinese.dating, 1
balist.es, 1 balist.es, 1
@ -5129,7 +5129,6 @@ bondskampeerder.nl, 1
bondtofte.dk, 1 bondtofte.dk, 1
bonesserver.com, 1 bonesserver.com, 1
bonfi.net, 1 bonfi.net, 1
bonibuty.com, 1
bonifacius.be, 1 bonifacius.be, 1
bonita.com.br, 1 bonita.com.br, 1
bonnant-associes.ch, 1 bonnant-associes.ch, 1
@ -6345,7 +6344,6 @@ casinocashflow.ru, 1
casinolistings.com, 1 casinolistings.com, 1
casinoonlinesicuri.com, 1 casinoonlinesicuri.com, 1
casinoreal.com, 1 casinoreal.com, 1
casjay.cloud, 1
casjay.com, 1 casjay.com, 1
casjay.info, 1 casjay.info, 1
casjaygames.com, 1 casjaygames.com, 1
@ -6520,6 +6518,7 @@ centos.pub, 1
centos.tips, 1 centos.tips, 1
central4.me, 1 central4.me, 1
centralbank.ae, 1 centralbank.ae, 1
centralcountiesservices.org, 1
centralebigmat.eu, 1 centralebigmat.eu, 1
centralegedimat.eu, 1 centralegedimat.eu, 1
centralfor.me, 1 centralfor.me, 1
@ -7673,6 +7672,7 @@ common.io, 1
commoncode.com.au, 1 commoncode.com.au, 1
commoncode.io, 1 commoncode.io, 1
commoncore4kids.com, 1 commoncore4kids.com, 1
community-cupboard.org, 1
communityblog.fedoraproject.org, 1 communityblog.fedoraproject.org, 1
communitycodeofconduct.com, 1 communitycodeofconduct.com, 1
communityflow.info, 1 communityflow.info, 1
@ -7896,6 +7896,7 @@ coolpickz.com, 1
coolprylar.se, 1 coolprylar.se, 1
coolrc.me, 1 coolrc.me, 1
coolviewthermostat.com, 1 coolviewthermostat.com, 1
coolvox.com, 1
coopens.com, 1 coopens.com, 1
coor.fun, 1 coor.fun, 1
coore.jp, 1 coore.jp, 1
@ -7988,7 +7989,6 @@ cosni.co, 1
cosplayer.com, 1 cosplayer.com, 1
cospol.ch, 1 cospol.ch, 1
costa-rica-reisen.ch, 1 costa-rica-reisen.ch, 1
costa-rica-reisen.de, 1
costablancavoorjou.com, 1 costablancavoorjou.com, 1
costcofinance.com, 1 costcofinance.com, 1
costinstefan.eu, 1 costinstefan.eu, 1
@ -8787,6 +8787,7 @@ darkside.re, 1
darktime.ru, 1 darktime.ru, 1
darkwater.info, 1 darkwater.info, 1
darkx.me, 1 darkx.me, 1
darlastudio66.com, 1
darlo.co.uk, 0 darlo.co.uk, 0
darom.jp, 1 darom.jp, 1
darookee.net, 1 darookee.net, 1
@ -10081,6 +10082,7 @@ doublestat.me, 1
doubleup.com.au, 1 doubleup.com.au, 1
doucheba.gs, 0 doucheba.gs, 0
dougferris.id.au, 1 dougferris.id.au, 1
douglasstafford.com, 1
doujin-domain.cz, 1 doujin-domain.cz, 1
doujin.nagoya, 1 doujin.nagoya, 1
doujinshi.info, 1 doujinshi.info, 1
@ -10403,6 +10405,7 @@ dustygroove.com, 1
dustyspokesbnb.ca, 1 dustyspokesbnb.ca, 1
dutch.desi, 1 dutch.desi, 1
dutch1.nl, 1 dutch1.nl, 1
dutchessuganda.com, 1
dutchrank.nl, 1 dutchrank.nl, 1
dutchwanderers.nl, 1 dutchwanderers.nl, 1
dutchweballiance.nl, 1 dutchweballiance.nl, 1
@ -10636,6 +10639,7 @@ echopaper.com, 1
echosim.io, 1 echosim.io, 1
echosixmonkey.com, 1 echosixmonkey.com, 1
echosystem.fr, 1 echosystem.fr, 1
echoteam.gq, 1
echoteen.com, 1 echoteen.com, 1
echoworld.ch, 1 echoworld.ch, 1
ecirtam.net, 1 ecirtam.net, 1
@ -10652,6 +10656,7 @@ ecodedi.com, 1
ecodigital.social, 1 ecodigital.social, 1
ecogen.com.au, 1 ecogen.com.au, 1
ecogen.net.au, 1 ecogen.net.au, 1
ecoheatcool.co.uk, 1
ecohostingservices.uk, 1 ecohostingservices.uk, 1
ecolala.my, 1 ecolala.my, 1
ecole-attalens.ch, 1 ecole-attalens.ch, 1
@ -10680,6 +10685,7 @@ ecovision.com.br, 1
ecpannualmeeting.com, 1 ecpannualmeeting.com, 1
ecrandouble.ch, 1 ecrandouble.ch, 1
ectora.com, 1 ectora.com, 1
ecupcafe.com, 0
ed.gs, 1 ed.gs, 1
ed4becky.net, 1 ed4becky.net, 1
edakoe.ru, 1 edakoe.ru, 1
@ -11338,30 +11344,7 @@ epmcentroitalia.it, 1
epo32.ru, 1 epo32.ru, 1
epoch.com, 1 epoch.com, 1
epolitiker.com, 1 epolitiker.com, 1
epos-distributor.co.uk, 1
eposbirmingham.co.uk, 1
eposbrighton.co.uk, 1
eposbristol.co.uk, 1
eposcardiff.co.uk, 1
eposcloud.net, 1
eposkent.co.uk, 1
eposleeds.co.uk, 1
eposleicester.co.uk, 1
eposliverpool.co.uk, 1
eposlondon.co.uk, 1
eposmidlands.co.uk, 1
eposnewport.co.uk, 1
eposnottingham.co.uk, 1
eposreading.co.uk, 1
eposreview.co.uk, 1
epossheffield.co.uk, 1
epossurrey.co.uk, 1
epossussex.co.uk, 1
eposswansea.co.uk, 1
epossystems.co.uk, 1
epostplus.li, 1 epostplus.li, 1
eposwales.co.uk, 1
eposyork.co.uk, 1
eppelblei.lu, 1 eppelblei.lu, 1
eppelduerferjugend.lu, 1 eppelduerferjugend.lu, 1
eppelpress.lu, 1 eppelpress.lu, 1
@ -11383,7 +11366,6 @@ equinox.io, 1
equipandoloja.net.br, 1 equipandoloja.net.br, 1
equipedefrance.tv, 1 equipedefrance.tv, 1
equipeferramentas.com.br, 1 equipeferramentas.com.br, 1
equippers.de, 1
equipsupply.com, 1 equipsupply.com, 1
er-music.com, 1 er-music.com, 1
er.tl, 1 er.tl, 1
@ -11687,6 +11669,7 @@ estcequejailaflemme.fr, 1
estcequonmetenprodaujourdhui.info, 1 estcequonmetenprodaujourdhui.info, 1
esteam.se, 1 esteam.se, 1
estedafah.com, 1 estedafah.com, 1
estespr.com, 1
esteticanorte.com.br, 1 esteticanorte.com.br, 1
estetista.net, 1 estetista.net, 1
estilopack-loja.com.br, 1 estilopack-loja.com.br, 1
@ -12047,6 +12030,7 @@ extreme.co.th, 1
extrememanual.net, 1 extrememanual.net, 1
exvs.org, 1 exvs.org, 1
exyplis.com, 1 exyplis.com, 1
eyasc.nl, 1
eydesignguidelines.com, 1 eydesignguidelines.com, 1
eyecandy.gr, 1 eyecandy.gr, 1
eyeglasses.com, 0 eyeglasses.com, 0
@ -12307,7 +12291,6 @@ fassadenverkleidung24.de, 1
fassi-sport.it, 1 fassi-sport.it, 1
fastaim.de, 1 fastaim.de, 1
fastbackmbg.be, 1 fastbackmbg.be, 1
fastbackmbm.be, 1
fastcash.com.br, 1 fastcash.com.br, 1
fastcommerce.org, 1 fastcommerce.org, 1
fastconfirm.com, 1 fastconfirm.com, 1
@ -12784,6 +12767,7 @@ flexinvesting.fi, 1
flexport.com, 1 flexport.com, 1
flexstart.me, 1 flexstart.me, 1
flextrack.dk, 1 flextrack.dk, 1
flextribly.xyz, 1
fliacuello.com.ar, 1 fliacuello.com.ar, 1
flightdeckfriend.com, 1 flightdeckfriend.com, 1
flightmedx.com, 1 flightmedx.com, 1
@ -12843,7 +12827,6 @@ floth.at, 1
flow.su, 1 flow.su, 1
flowcom.de, 1 flowcom.de, 1
flowcount.xyz, 1 flowcount.xyz, 1
flowerandplant.org, 1
flowersbylegacy.com, 1 flowersbylegacy.com, 1
flowinvoice.com, 1 flowinvoice.com, 1
flowreader.com, 1 flowreader.com, 1
@ -13043,8 +13026,6 @@ forty8creates.com, 1
fortytwo.cloud, 1 fortytwo.cloud, 1
forum-bonn.de, 1 forum-bonn.de, 1
forum-heg.ch, 1 forum-heg.ch, 1
forum-kinozal-tv.appspot.com, 1
forum-kinozal.appspot.com, 1
forum.linode.com, 0 forum.linode.com, 0
forum3.ru, 1 forum3.ru, 1
forumvoordemocratie.nl, 1 forumvoordemocratie.nl, 1
@ -13681,7 +13662,6 @@ gamerpoets.com, 1
gamerz-stream.com, 1 gamerz-stream.com, 1
gamerzdot.com, 1 gamerzdot.com, 1
games4theworld.org, 1 games4theworld.org, 1
gameserver-sponsor.me, 1
gameshowchallenge.ie, 1 gameshowchallenge.ie, 1
gamesplanet.com, 1 gamesplanet.com, 1
gamesputnik.ru, 1 gamesputnik.ru, 1
@ -14126,6 +14106,7 @@ giftedconsortium.com, 1
giftking.nl, 0 giftking.nl, 0
giftmaniabrilhos.com.br, 1 giftmaniabrilhos.com.br, 1
gifts365.co.uk, 1 gifts365.co.uk, 1
giftservices.nl, 1
giftsn.com.sg, 0 giftsn.com.sg, 0
gifzilla.net, 0 gifzilla.net, 0
gig-raiffeisen.de, 1 gig-raiffeisen.de, 1
@ -14216,7 +14197,7 @@ gjspunk.de, 0
gjung.com, 0 gjung.com, 0
gkimanyar.org, 1 gkimanyar.org, 1
gkralik.eu, 1 gkralik.eu, 1
gkvsc.de, 1 gkvsc.de, 0
gl.search.yahoo.com, 0 gl.search.yahoo.com, 0
glabiatoren-kst.de, 1 glabiatoren-kst.de, 1
glaciernursery.com, 1 glaciernursery.com, 1
@ -14324,7 +14305,6 @@ gnetwork.eu, 1
gnhub.org, 1 gnhub.org, 1
gnilebein.de, 1 gnilebein.de, 1
gnom.me, 1 gnom.me, 1
gnosticjade.net, 1
gnucashtoqif.us, 1 gnucashtoqif.us, 1
gnunet.org, 1 gnunet.org, 1
gnwp.eu, 1 gnwp.eu, 1
@ -14667,7 +14647,6 @@ grexx.de, 1
grey.house, 1 grey.house, 1
greybit.net, 1 greybit.net, 1
greyhash.se, 1 greyhash.se, 1
greysky.me, 1
greyskymedia.com, 1 greyskymedia.com, 1
greysolutions.it, 1 greysolutions.it, 1
greywizard.com, 1 greywizard.com, 1
@ -15027,7 +15006,6 @@ halkirkbouncycastles.co.uk, 1
halkyon.net, 1 halkyon.net, 1
hallelujahsoftware.com, 1 hallelujahsoftware.com, 1
halletienne.fr, 1 halletienne.fr, 1
hallettxn.com, 1
hallhuber.com, 1 hallhuber.com, 1
halliday.work, 1 halliday.work, 1
halligladen.de, 1 halligladen.de, 1
@ -15543,7 +15521,6 @@ heyfringe.com, 1
heyjournal.com, 1 heyjournal.com, 1
hf-tekst.nl, 1 hf-tekst.nl, 1
hf51.nl, 1 hf51.nl, 1
hfbg.nl, 1
hfi.me, 0 hfi.me, 0
hfu.io, 1 hfu.io, 1
hg.python.org, 1 hg.python.org, 1
@ -15796,6 +15773,7 @@ holidaysportugal.eu, 1
holisticacupuncture.com.au, 1 holisticacupuncture.com.au, 1
holistichealer.in, 1 holistichealer.in, 1
holisticon.de, 1 holisticon.de, 1
hollandguns.com, 1
hollermann.eu, 1 hollermann.eu, 1
hollo.me, 1 hollo.me, 1
hollowpoint.xyz, 1 hollowpoint.xyz, 1
@ -15858,6 +15836,7 @@ hommeatoutfaire.be, 1
homoglyph.net, 1 homoglyph.net, 1
homophoni.com, 1 homophoni.com, 1
hompus.nl, 0 hompus.nl, 0
homyremedies.com, 1
honda-centrum.cz, 1 honda-centrum.cz, 1
hondart.cz, 1 hondart.cz, 1
hondenoppasfraneker.nl, 1 hondenoppasfraneker.nl, 1
@ -16202,6 +16181,7 @@ hustle.life, 1
hustlehope.com, 1 hustlehope.com, 1
hustunique.com, 1 hustunique.com, 1
huto.ml, 1 huto.ml, 1
huutonauru.net, 1
huwcbjones.co.uk, 1 huwcbjones.co.uk, 1
huwcbjones.uk, 1 huwcbjones.uk, 1
huwjones.me, 1 huwjones.me, 1
@ -16496,7 +16476,6 @@ ig.com, 1
iga-semi.jp, 1 iga-semi.jp, 1
igamingforums.com, 1 igamingforums.com, 1
igcc.jp, 1 igcc.jp, 1
igd.chat, 1
igglabs.com, 1 igglabs.com, 1
iggprivate.com, 1 iggprivate.com, 1
iggsoft.com, 1 iggsoft.com, 1
@ -16758,6 +16737,7 @@ indianaberry.com, 1
indianaffairs.gov, 0 indianaffairs.gov, 0
indiawise.co.uk, 1 indiawise.co.uk, 1
indicateurs-flash.fr, 1 indicateurs-flash.fr, 1
indieethos.com, 1
indiegame.space, 1 indiegame.space, 1
indievelopment.nl, 1 indievelopment.nl, 1
indigoinflatables.com, 1 indigoinflatables.com, 1
@ -16804,7 +16784,6 @@ infinitiofmarinparts.com, 1
infinity.to, 1 infinity.to, 1
infinitybas.com, 1 infinitybas.com, 1
infinityengine.org, 1 infinityengine.org, 1
infinityepos.co.uk, 1
infirmiere-canadienne.com, 1 infirmiere-canadienne.com, 1
infirmieredevie.ch, 1 infirmieredevie.ch, 1
inflatablehire-scotland.co.uk, 1 inflatablehire-scotland.co.uk, 1
@ -17302,6 +17281,7 @@ islam.si, 1
islandhosting.com, 1 islandhosting.com, 1
islandinthenet.com, 1 islandinthenet.com, 1
islandoilsupply.com, 1 islandoilsupply.com, 1
islandpumpandtank.com, 1
islazia.fr, 1 islazia.fr, 1
isletech.net, 1 isletech.net, 1
isliada.org, 1 isliada.org, 1
@ -17556,6 +17536,7 @@ jaberg-rutschi.ch, 1
jabergrutschi.ch, 1 jabergrutschi.ch, 1
jability.ovh, 1 jability.ovh, 1
jabjab.de, 1 jabjab.de, 1
jaccblog.com, 1
jacekowski.org, 1 jacekowski.org, 1
jackdawphoto.co.uk, 1 jackdawphoto.co.uk, 1
jackdelik.de, 1 jackdelik.de, 1
@ -18364,7 +18345,6 @@ jurassicgolf.nl, 1
juridiqueo.com, 1 juridiqueo.com, 1
juridoc.com.br, 1 juridoc.com.br, 1
jurijbuga.de, 1 jurijbuga.de, 1
jurisprudent.by, 1
juristeo.com, 1 juristeo.com, 1
jurko.cz, 1 jurko.cz, 1
jurriaan.ninja, 1 jurriaan.ninja, 1
@ -18776,6 +18756,7 @@ kenny-peck.com, 1
kennynet.co.uk, 1 kennynet.co.uk, 1
keno.im, 1 keno.im, 1
kenokallinger.at, 1 kenokallinger.at, 1
kenoschwalb.com, 1
kenrogers.co, 0 kenrogers.co, 0
kens.pics, 1 kens.pics, 1
kensbouncycastles.co.uk, 1 kensbouncycastles.co.uk, 1
@ -19417,7 +19398,7 @@ kropkait.pl, 1
krouzkyliduska.cz, 1 krouzkyliduska.cz, 1
krsn.de, 1 krsn.de, 1
krugermillions.org, 1 krugermillions.org, 1
krugoval.hr, 1 krugoval.hr, 0
kruin.net, 1 kruin.net, 1
kruisselbrink.com, 1 kruisselbrink.com, 1
kruk.co, 1 kruk.co, 1
@ -21124,7 +21105,6 @@ mabulledu.net, 1
mac-i-tea.ch, 1 mac-i-tea.ch, 1
mac-world.pl, 1 mac-world.pl, 1
mac1.net, 1 mac1.net, 1
macandtonic.com, 1
macaque.io, 0 macaque.io, 0
macaw.nl, 1 macaw.nl, 1
macaws.org, 1 macaws.org, 1
@ -21775,7 +21755,6 @@ mattli.us, 1
mattmccutchen.net, 1 mattmccutchen.net, 1
mattmcshane.com, 1 mattmcshane.com, 1
mattonline.me, 1 mattonline.me, 1
mattwb65.com, 1
mattwservices.co.uk, 1 mattwservices.co.uk, 1
matviet.vn, 1 matviet.vn, 1
matze.co, 1 matze.co, 1
@ -21796,6 +21775,7 @@ mawidaca.com, 1
max-moeglich.de, 1 max-moeglich.de, 1
max-went.pl, 1 max-went.pl, 1
max.gov, 1 max.gov, 1
maxbeenen.de, 1
maxbruckner.de, 1 maxbruckner.de, 1
maxbruckner.org, 1 maxbruckner.org, 1
maxbytes.nl, 0 maxbytes.nl, 0
@ -22016,6 +21996,7 @@ medicinskavranje.edu.rs, 1
medicocompetente.it, 1 medicocompetente.it, 1
medicoresponde.com.br, 1 medicoresponde.com.br, 1
medienweite.de, 1 medienweite.de, 1
medifab.online, 1
medifi.com, 1 medifi.com, 1
medigap-quote.net, 1 medigap-quote.net, 1
medinside.ch, 1 medinside.ch, 1
@ -22115,7 +22096,6 @@ meisterritter.de, 1
meizufans.eu, 1 meizufans.eu, 1
meklon.net, 1 meklon.net, 1
mekongeye.com, 1 mekongeye.com, 1
melakaltenegger.at, 1
melaniebernhardt.com, 1 melaniebernhardt.com, 1
melaniegruber.de, 1 melaniegruber.de, 1
melbourne.dating, 1 melbourne.dating, 1
@ -22547,6 +22527,7 @@ minesouls.fr, 1
minetude.com, 1 minetude.com, 1
minez-nightswatch.com, 0 minez-nightswatch.com, 0
minf3-games.de, 1 minf3-games.de, 1
mingming.info, 1
mingram.net, 1 mingram.net, 1
mingwah.ch, 1 mingwah.ch, 1
mingy.ddns.net, 1 mingy.ddns.net, 1
@ -22604,7 +22585,6 @@ mirkofranz.de, 1
mirodasilva.be, 1 mirodasilva.be, 1
mironet.cz, 1 mironet.cz, 1
mirrorsedgearchive.de, 1 mirrorsedgearchive.de, 1
mirrorsedgearchive.ga, 1
mirshak.com, 1 mirshak.com, 1
mirtes.cz, 1 mirtes.cz, 1
mirtouf.fr, 1 mirtouf.fr, 1
@ -23148,6 +23128,7 @@ mrkapowski.com, 1
mrketolocksmith.com, 1 mrketolocksmith.com, 1
mrknee.gr, 1 mrknee.gr, 1
mrksk.com, 1 mrksk.com, 1
mrleonardo.com, 1
mrliu.me, 1 mrliu.me, 1
mrmoregame.de, 1 mrmoregame.de, 1
mrnh.de, 1 mrnh.de, 1
@ -23927,6 +23908,7 @@ ncconsumer.org, 1
ncdesigns-studio.com, 1 ncdesigns-studio.com, 1
ncea.net.au, 1 ncea.net.au, 1
nchangfong.com, 1 nchangfong.com, 1
nchristo.com, 1
nclvle.co.uk, 1 nclvle.co.uk, 1
ncrmnt.org, 1 ncrmnt.org, 1
ncsccs.com, 1 ncsccs.com, 1
@ -24121,6 +24103,7 @@ netraising.com, 1
netrelay.email, 1 netrelay.email, 1
netrider.net.au, 0 netrider.net.au, 0
netronix.be, 1 netronix.be, 1
netsafeid.biz, 1
netscaler.expert, 1 netscaler.expert, 1
netsight.org, 1 netsight.org, 1
netsigna.de, 1 netsigna.de, 1
@ -24212,7 +24195,6 @@ newizv.ru, 1
newjianzhi.com, 1 newjianzhi.com, 1
newkaliningrad.ru, 1 newkaliningrad.ru, 1
newknd.com, 1 newknd.com, 1
newline.online, 1
newmarketbouncycastlehire.co.uk, 1 newmarketbouncycastlehire.co.uk, 1
newmed.com.br, 1 newmed.com.br, 1
newmediaone.net, 1 newmediaone.net, 1
@ -26146,6 +26128,7 @@ penetrationstest.se, 1
penfold.fr, 1 penfold.fr, 1
pengi.me, 1 pengi.me, 1
pengisatelier.net, 1 pengisatelier.net, 1
pengui.uk, 1
penguinprotocols.com, 1 penguinprotocols.com, 1
pengumuman.id, 1 pengumuman.id, 1
penispumpen.se, 1 penispumpen.se, 1
@ -26254,6 +26237,7 @@ peterhuetz.com, 1
peterjohnson.io, 1 peterjohnson.io, 1
peterlew.is, 1 peterlew.is, 1
petersontoscano.com, 1 petersontoscano.com, 1
pethelpers.org, 1
petit-archer.com, 1 petit-archer.com, 1
petite-maison.ch, 1 petite-maison.ch, 1
petitsfrenchies.com, 1 petitsfrenchies.com, 1
@ -26772,7 +26756,6 @@ plumlocosoft.com, 1
plumnet.ch, 1 plumnet.ch, 1
plumpie.net, 0 plumpie.net, 0
plur.com.au, 1 plur.com.au, 1
plural.cafe, 1
plus-5.com, 1 plus-5.com, 1
plus.google.com, 1 plus.google.com, 1
plus.sandbox.google.com, 1 plus.sandbox.google.com, 1
@ -27006,7 +26989,6 @@ portalkla.com.br, 1
portalmundo.xyz, 1 portalmundo.xyz, 1
portalzine.de, 1 portalzine.de, 1
porte.roma.it, 1 porte.roma.it, 1
portefeuillesignalen.nl, 1
portercup.com, 1 portercup.com, 1
porterranchelectrical.com, 1 porterranchelectrical.com, 1
portofacil.com, 1 portofacil.com, 1
@ -27022,7 +27004,6 @@ portvaletickets.com, 1
porybox.com, 1 porybox.com, 1
porzgmbh.de, 1 porzgmbh.de, 1
posaunenchor-senden.de, 1 posaunenchor-senden.de, 1
posbank.co.uk, 1
poseidonwaterproofing.com, 1 poseidonwaterproofing.com, 1
poshcastles.co.uk, 1 poshcastles.co.uk, 1
poshlashes.se, 1 poshlashes.se, 1
@ -27081,6 +27062,7 @@ pouet.it, 1
pouets.ovh, 1 pouets.ovh, 1
poupatempo.org, 1 poupatempo.org, 1
pourlesenfants.info, 1 pourlesenfants.info, 1
pourout.org, 1
povareschka.ru, 1 povareschka.ru, 1
povesham.tk, 1 povesham.tk, 1
powaclub.com, 1 powaclub.com, 1
@ -27800,6 +27782,7 @@ quanwuji.com, 1
quanyin.eu.org, 1 quanyin.eu.org, 1
quareal.ru, 1 quareal.ru, 1
quarkdose.de, 1 quarkdose.de, 1
quarryhillrentals.com, 1
quarterfull.com, 1 quarterfull.com, 1
quartix.com, 1 quartix.com, 1
quartzclinical.com, 1 quartzclinical.com, 1
@ -28214,7 +28197,7 @@ recetasfacilesdehacer.com, 1
rechenknaecht.de, 1 rechenknaecht.de, 1
rechenwerk.net, 1 rechenwerk.net, 1
recht-freundlich.de, 1 recht-freundlich.de, 1
rechtenliteratuurleiden.nl, 1 rechtenliteratuurleiden.nl, 0
rechtsanwaeltin-vollmer.de, 1 rechtsanwaeltin-vollmer.de, 1
rechtsanwalt-koeppen-feucht.de, 1 rechtsanwalt-koeppen-feucht.de, 1
rechtschreibpruefung24.de, 1 rechtschreibpruefung24.de, 1
@ -28294,6 +28277,7 @@ redneck-gaming.de, 1
redneragenturen.org, 1 redneragenturen.org, 1
rednoseday.com, 1 rednoseday.com, 1
rednsx.org, 1 rednsx.org, 1
redperegrine.com, 1
redporno.cz, 1 redporno.cz, 1
redprice.by, 1 redprice.by, 1
redshield.co, 1 redshield.co, 1
@ -28469,6 +28453,7 @@ rentbrowser.com, 1
rentinsingapore.com.sg, 1 rentinsingapore.com.sg, 1
rentourhomeinprovence.com, 1 rentourhomeinprovence.com, 1
renuo.ch, 1 renuo.ch, 1
renyiyou.com, 1
reorz.com, 1 reorz.com, 1
reox.at, 0 reox.at, 0
repaik.com, 1 repaik.com, 1
@ -28802,7 +28787,7 @@ roave.com, 1
rob.uk.com, 1 rob.uk.com, 1
rob006.net, 1 rob006.net, 1
robandjanine.com, 1 robandjanine.com, 1
robbertt.com, 1 robbertt.com, 0
robdavidson.network, 1 robdavidson.network, 1
robert-flynn.de, 1 robert-flynn.de, 1
robertabittle.com, 1 robertabittle.com, 1
@ -28815,7 +28800,6 @@ robertlysik.com, 1
robertnemec.com, 1 robertnemec.com, 1
roberto-webhosting.nl, 1 roberto-webhosting.nl, 1
robertocasares.no-ip.biz, 1 robertocasares.no-ip.biz, 1
robertoentringer.com, 1
robertof.ovh, 1 robertof.ovh, 1
robertreiser.photography, 1 robertreiser.photography, 1
robertrijnders.nl, 1 robertrijnders.nl, 1
@ -28873,7 +28857,6 @@ rockymountainspice.com, 1
rocssti.net, 1 rocssti.net, 1
rodafe.sk, 1 rodafe.sk, 1
rodarion.pl, 1 rodarion.pl, 1
roddis.net, 1
rodehutskors.net, 1 rodehutskors.net, 1
rodeobull.biz, 1 rodeobull.biz, 1
rodeohire.com, 1 rodeohire.com, 1
@ -28922,6 +28905,7 @@ rohitagr.com, 1
rointe.online, 1 rointe.online, 1
roiscroll.com, 1 roiscroll.com, 1
roka9.de, 1 roka9.de, 1
roketix.co.uk, 1
rokki.ch, 1 rokki.ch, 1
rokort.dk, 1 rokort.dk, 1
roksolana.be, 1 roksolana.be, 1
@ -29021,7 +29005,6 @@ rosewoodranch.com, 1
rosi-royal.com, 1 rosi-royal.com, 1
roslynpad.net, 1 roslynpad.net, 1
rospa100.com, 1 rospa100.com, 1
rossclark.com, 1
rosset.me, 1 rosset.me, 1
rosset.net, 1 rosset.net, 1
rosslug.org.uk, 1 rosslug.org.uk, 1
@ -29407,7 +29390,6 @@ sallysubs.com, 1
salmo23.com.br, 1 salmo23.com.br, 1
salmododia.net, 1 salmododia.net, 1
salmonella.co.uk, 1 salmonella.co.uk, 1
salmonrecovery.gov, 1
salmonvision.com.tw, 0 salmonvision.com.tw, 0
salmos91.com, 1 salmos91.com, 1
salmotierra-salvatierra.com, 1 salmotierra-salvatierra.com, 1
@ -29544,6 +29526,7 @@ sapac.es, 1
sapereaude.com.pl, 1 sapereaude.com.pl, 1
sapien-ci.com, 1 sapien-ci.com, 1
sapience.com, 1 sapience.com, 1
sapk.fr, 1
saposute-s.jp, 1 saposute-s.jp, 1
sapphireblue.me, 1 sapphireblue.me, 1
sapphirepearl.com.sg, 1 sapphirepearl.com.sg, 1
@ -30125,6 +30108,7 @@ selectel.ru, 1
selectorders.com, 1 selectorders.com, 1
selegiline.com, 1 selegiline.com, 1
selent.me, 1 selent.me, 1
seleondar.ru, 1
self-evident.org, 1 self-evident.org, 1
self-signed.com, 1 self-signed.com, 1
self-xss.info, 1 self-xss.info, 1
@ -30204,6 +30188,7 @@ seocomposer.com, 1
seoexperte.berlin, 1 seoexperte.berlin, 1
seogeek.nl, 1 seogeek.nl, 1
seohochschule.de, 1 seohochschule.de, 1
seoinc.com, 1
seoium.com, 1 seoium.com, 1
seokay.com, 1 seokay.com, 1
seolib.org, 1 seolib.org, 1
@ -30325,7 +30310,6 @@ sevsey.ru, 1
sevsopr.ru, 1 sevsopr.ru, 1
sewafineseam.com, 1 sewafineseam.com, 1
sewinginsight.com, 1 sewinginsight.com, 1
sewoo.co.uk, 1
sex-education.com, 1 sex-education.com, 1
sexaki.com, 1 sexaki.com, 1
sexdocka.nu, 1 sexdocka.nu, 1
@ -30432,6 +30416,7 @@ shaobin.wang, 1
sharanyamunsi.net, 1 sharanyamunsi.net, 1
sharealo.org, 1 sharealo.org, 1
sharedhost.de, 1 sharedhost.de, 1
shareeri.com, 1
sharelovenotsecrets.com, 1 sharelovenotsecrets.com, 1
sharemessage.net, 1 sharemessage.net, 1
shareoffice.ch, 1 shareoffice.ch, 1
@ -30462,6 +30447,7 @@ shavegazette.com, 1
shavingks.com, 1 shavingks.com, 1
shawcentral.ca, 0 shawcentral.ca, 0
shawnhogan.com, 1 shawnhogan.com, 1
shawnstarrcustomhomes.com, 1
shawnwilkerson.com, 1 shawnwilkerson.com, 1
shawnwilson.info, 1 shawnwilson.info, 1
shazbots.org, 1 shazbots.org, 1
@ -30790,6 +30776,7 @@ silverlinkz.net, 1
silverseen.com, 1 silverseen.com, 1
silverstartup.sk, 1 silverstartup.sk, 1
silverwind.io, 1 silverwind.io, 1
silviamacallister.com, 1
silvine.xyz, 1 silvine.xyz, 1
silvistefi.com, 1 silvistefi.com, 1
silvobeat.blog, 1 silvobeat.blog, 1
@ -31122,6 +31109,7 @@ slotfara.com, 1
slotfara.net, 1 slotfara.net, 1
sloths.org, 1 sloths.org, 1
slotlist.info, 1 slotlist.info, 1
slovakiana.sk, 1
slovenskycestovatel.sk, 1 slovenskycestovatel.sk, 1
slow.zone, 1 slow.zone, 1
slowb.ro, 1 slowb.ro, 1
@ -31154,7 +31142,6 @@ smaltimento-rifiuti.org, 1
smaltimento.caserta.it, 1 smaltimento.caserta.it, 1
smaltimento.napoli.it, 1 smaltimento.napoli.it, 1
smaltimentoamianto.latina.it, 1 smaltimentoamianto.latina.it, 1
smaltimentorifiuti.veneto.it, 1
smares.de, 1 smares.de, 1
smart-cp.jp, 1 smart-cp.jp, 1
smart-informatics.com, 1 smart-informatics.com, 1
@ -31247,6 +31234,7 @@ smutba.se, 1
smutek.net, 1 smutek.net, 1
smx.net.br, 1 smx.net.br, 1
snackbesteld.nl, 1 snackbesteld.nl, 1
snafarms.com, 1
snafu.cz, 1 snafu.cz, 1
snakafya.com, 1 snakafya.com, 1
snake.dog, 1 snake.dog, 1
@ -31556,7 +31544,6 @@ sourcebox.be, 1
sourcecode.love, 1 sourcecode.love, 1
sourcely.net, 1 sourcely.net, 1
sourceway.de, 1 sourceway.de, 1
sourcitec.com, 1
souris.ch, 1 souris.ch, 1
sous-surveillance.net, 1 sous-surveillance.net, 1
southafrican.dating, 1 southafrican.dating, 1
@ -31730,7 +31717,6 @@ spornkuller.de, 1
sport-in-sundern.de, 1 sport-in-sundern.de, 1
sport-potreby.cz, 1 sport-potreby.cz, 1
sport-potreby.sk, 1 sport-potreby.sk, 1
sport-socken.net, 1
sport247.bet, 1 sport247.bet, 1
sporter.com, 1 sporter.com, 1
sportflash.info, 1 sportflash.info, 1
@ -32127,7 +32113,6 @@ stevenski.com, 0
steventress.com, 1 steventress.com, 1
steventruesdell.com, 1 steventruesdell.com, 1
stevenwooding.com, 1 stevenwooding.com, 1
stevenz.science, 1
stevesdrivingschooltyneside.com, 1 stevesdrivingschooltyneside.com, 1
stewartswines.com, 1 stewartswines.com, 1
stewonet.nl, 1 stewonet.nl, 1
@ -32190,6 +32175,7 @@ stoianlawfirm.com, 1
stolina.de, 0 stolina.de, 0
stolkpotplanten.nl, 1 stolkpotplanten.nl, 1
stolkschepen.nl, 1 stolkschepen.nl, 1
stomadental.com, 1
stomt.com, 1 stomt.com, 1
stonedworms.de, 1 stonedworms.de, 1
stonefusion.org.uk, 1 stonefusion.org.uk, 1
@ -32200,7 +32186,6 @@ stonewuu.com, 1
stony.com, 1 stony.com, 1
stonystratford.org, 1 stonystratford.org, 1
stopakwardhandshakes.org, 1 stopakwardhandshakes.org, 1
stopbreakupnow.org, 1
stopbullying.gov, 1 stopbullying.gov, 1
stopfraud.gov, 1 stopfraud.gov, 1
stopthethyroidmadness.com, 1 stopthethyroidmadness.com, 1
@ -32455,7 +32440,6 @@ sundayfundayjapan.com, 1
suneilpatel.com, 1 suneilpatel.com, 1
sunfeathers.net, 1 sunfeathers.net, 1
sunfireshop.com.br, 1 sunfireshop.com.br, 1
sunflyer.cn, 0
sunfox.cz, 1 sunfox.cz, 1
sunfulong.blog, 1 sunfulong.blog, 1
sunfulong.me, 1 sunfulong.me, 1
@ -32962,7 +32946,6 @@ taunhanh.us, 1
tavolaquadrada.com.br, 1 tavolaquadrada.com.br, 1
tavsys.net, 1 tavsys.net, 1
taxaroo.com, 1 taxaroo.com, 1
taxi-24std.de, 1
taxi-chamonix.fr, 1 taxi-chamonix.fr, 1
taxi-collectif.ch, 1 taxi-collectif.ch, 1
taxi-puck.pl, 1 taxi-puck.pl, 1
@ -33076,6 +33059,7 @@ tech-essential.com, 1
tech-rat.com, 1 tech-rat.com, 1
tech-seminar.jp, 1 tech-seminar.jp, 1
tech-value.eu, 1 tech-value.eu, 1
tech-zealots.com, 1
techace.jp, 1 techace.jp, 1
techademy.nl, 1 techademy.nl, 1
techarea.fr, 1 techarea.fr, 1
@ -33161,6 +33145,7 @@ teemperor.de, 1
teemulintula.fi, 1 teemulintula.fi, 1
teencounseling.com, 1 teencounseling.com, 1
teenerotic.net, 1 teenerotic.net, 1
teeplelaw.com, 1
teesypeesy.com, 1 teesypeesy.com, 1
teeworlds-friends.de, 1 teeworlds-friends.de, 1
tefek.cz, 1 tefek.cz, 1
@ -33429,6 +33414,7 @@ thebodyprinciple.com, 1
thebouncedepartment.co.uk, 1 thebouncedepartment.co.uk, 1
thebouncyman.co.uk, 1 thebouncyman.co.uk, 1
theboxofcarlos.com, 1 theboxofcarlos.com, 1
thebreakhotel.com, 1
thebreakroom.org, 1 thebreakroom.org, 1
thebte.com, 1 thebte.com, 1
thebuffalotavern.com, 1 thebuffalotavern.com, 1
@ -33574,6 +33560,7 @@ theobromos.fr, 1
theocharis.org, 1 theocharis.org, 1
theodorahome.co, 1 theodorahome.co, 1
theodorahome.com.br, 1 theodorahome.com.br, 1
theofleck.com, 1
theojones.name, 1 theojones.name, 1
theokonst.tk, 1 theokonst.tk, 1
theokouzelis.com, 1 theokouzelis.com, 1
@ -34100,6 +34087,7 @@ todocracy.com, 1
todoescine.com, 1 todoescine.com, 1
todoist.com, 1 todoist.com, 1
todon.fr, 1 todon.fr, 1
todoscomciro.com, 1
todosrv.com, 1 todosrv.com, 1
toeglhofer.at, 1 toeglhofer.at, 1
toeightycountries.com, 1 toeightycountries.com, 1
@ -34290,7 +34278,6 @@ toptexture.com, 1
toptheto.com, 1 toptheto.com, 1
topvertimai.lt, 1 topvertimai.lt, 1
topwin.la, 1 topwin.la, 1
topwindowcleaners.co.uk, 1
topworktops.co.uk, 1 topworktops.co.uk, 1
tor2web.org, 1 tor2web.org, 1
toracon.org, 1 toracon.org, 1
@ -34351,7 +34338,6 @@ touch-up-net.com, 1
touch.facebook.com, 0 touch.facebook.com, 0
touch.mail.ru, 1 touch.mail.ru, 1
touchoflife.in, 1 touchoflife.in, 1
touchscreentills.com, 1
touchweb.fr, 1 touchweb.fr, 1
touchwoodtrees.com.au, 1 touchwoodtrees.com.au, 1
tougetu.com, 1 tougetu.com, 1
@ -34949,7 +34935,6 @@ tysye.ca, 1
tyuo-keibi.co.jp, 1 tyuo-keibi.co.jp, 1
tzifas.com, 1 tzifas.com, 1
tzwe.com, 1 tzwe.com, 1
u-master.net, 1
u-metals.com, 1 u-metals.com, 1
u-tokyo.club, 1 u-tokyo.club, 1
u.nu, 1 u.nu, 1
@ -34992,6 +34977,7 @@ uc.ac.id, 1
ucac.nz, 0 ucac.nz, 0
ucangiller.com, 1 ucangiller.com, 1
ucch.be, 1 ucch.be, 1
ucfirst.nl, 1
uchargeapp.com, 1 uchargeapp.com, 1
uclanmasterplan.co.uk, 1 uclanmasterplan.co.uk, 1
uclip.club, 1 uclip.club, 1
@ -35285,7 +35271,6 @@ urbanmelbourne.info, 1
urbanmic.com, 1 urbanmic.com, 1
urbannewsservice.com, 1 urbannewsservice.com, 1
urbansparrow.in, 1 urbansparrow.in, 1
urbanstylestaging.com, 1
urbanwildlifealliance.org, 0 urbanwildlifealliance.org, 0
urbexdk.nl, 1 urbexdk.nl, 1
urcentral.com, 1 urcentral.com, 1
@ -35954,6 +35939,7 @@ vivamusic.es, 1
vivanosports.com.br, 1 vivanosports.com.br, 1
vivatv.com.tw, 1 vivatv.com.tw, 1
vivendi.de, 1 vivendi.de, 1
vivianmaier.cn, 1
vivid-academy.com, 1 vivid-academy.com, 1
vividinflatables.co.uk, 1 vividinflatables.co.uk, 1
vividlumen.com, 1 vividlumen.com, 1
@ -36790,7 +36776,6 @@ whisperinghoperanch.org, 1
whisperlab.org, 1 whisperlab.org, 1
whistleb.com, 1 whistleb.com, 1
whistleblower.gov, 1 whistleblower.gov, 1
whistler-transfers.com, 1
whitby-brewery.com, 1 whitby-brewery.com, 1
whitealps.at, 1 whitealps.at, 1
whitealps.be, 1 whitealps.be, 1
@ -37170,7 +37155,6 @@ workwithgo.com, 1
world-education-association.org, 1 world-education-association.org, 1
world-in-my-eyes.com, 1 world-in-my-eyes.com, 1
worldcareers.dk, 1 worldcareers.dk, 1
worldchess.london, 1
worldcigars.com.br, 1 worldcigars.com.br, 1
worldcrafts.org, 1 worldcrafts.org, 1
worldcubeassociation.org, 1 worldcubeassociation.org, 1
@ -37550,7 +37534,6 @@ xing.ml, 1
xingiahanvisa.net, 1 xingiahanvisa.net, 1
xiqi.us, 1 xiqi.us, 1
xirion.net, 1 xirion.net, 1
xiyu.it, 0
xiyu.moe, 1 xiyu.moe, 1
xj8876.com, 1 xj8876.com, 1
xjd.vision, 1 xjd.vision, 1
@ -37876,6 +37859,7 @@ ybscareers.co.uk, 1
ybsul.com, 1 ybsul.com, 1
ybti.net, 1 ybti.net, 1
ybzhao.com, 1 ybzhao.com, 1
ycaaz.com, 1
ych.art, 1 ych.art, 1
ycherbonnel.fr, 1 ycherbonnel.fr, 1
ychon.com, 1 ychon.com, 1
@ -38174,7 +38158,7 @@ yumeconcert.com, 1
yumli.net, 1 yumli.net, 1
yummylooks.com, 1 yummylooks.com, 1
yuna.love, 1 yuna.love, 1
yuna.tg, 1 yuna.tg, 0
yunity.org, 1 yunity.org, 1
yunjishou.pro, 1 yunjishou.pro, 1
yunzhu.li, 1 yunzhu.li, 1
@ -38270,6 +38254,7 @@ zargaripour.com, 1
zargescases.co.uk, 1 zargescases.co.uk, 1
zarmarket.org, 1 zarmarket.org, 1
zarpo.com.br, 1 zarpo.com.br, 1
zary.me, 1
zatsepin.by, 1 zatsepin.by, 1
zaufanatrzeciastrona.pl, 1 zaufanatrzeciastrona.pl, 1
zavec.com.ec, 1 zavec.com.ec, 1
@ -38374,6 +38359,7 @@ zhangfangzhou.com, 1
zhangge.net, 1 zhangge.net, 1
zhanghao.me, 1 zhanghao.me, 1
zhangheda.cf, 1 zhangheda.cf, 1
zhangsidan.com, 0
zhangsir.net, 1 zhangsir.net, 1
zhangyuhao.com, 1 zhangyuhao.com, 1
zhaochen.xyz, 1 zhaochen.xyz, 1

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

@ -119,14 +119,20 @@ class BaseNavigationTestCase(WindowManagerMixin, MarionetteTestCase):
class TestNavigate(BaseNavigationTestCase): class TestNavigate(BaseNavigationTestCase):
def test_set_location_through_execute_script(self): def test_set_location_through_execute_script(self):
test_element_locator = (By.ID, "testh1")
self.marionette.execute_script( self.marionette.execute_script(
"window.location.href = arguments[0];", "window.location.href = arguments[0];",
script_args=(self.test_page_remote,), sandbox=None) 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( Wait(self.marionette, timeout=self.marionette.timeout.page_load).until(
lambda mn: self.test_page_remote == mn.get_url(), expected.element_present(*test_element_locator),
message="'{}' hasn't been loaded".format(self.test_page_remote)) message="Target element 'testh1' has not been found")
self.assertEqual("Marionette Test", self.marionette.title)
self.assertEqual(self.test_page_remote, self.marionette.get_url())
def test_navigate_chrome_unsupported_error(self): def test_navigate_chrome_unsupported_error(self):
with self.marionette.using_context("chrome"): with self.marionette.using_context("chrome"):

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

@ -88,10 +88,10 @@ StructuredLogger.prototype = {
}, },
assertionCount(test, count, minExpected = 0, maxExpected = 0) { assertionCount(test, count, minExpected = 0, maxExpected = 0) {
var data = {test, var data = {test: this._testId(test),
min_expected: minExpected, min_expected: minExpected,
max_expected: maxExpected, max_expected: maxExpected,
count}; count};
this._logData("assertion_count", data); 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] [table-model-fixup-2.html]
disabled: 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] [Replaced elements outside a table cannot be table-row and are considered block -- input=text elements]
expected: FAIL expected: FAIL

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

@ -554,7 +554,7 @@ function handleRequest(req, res) {
// asking for cname.example.com // asking for cname.example.com
var content; var content;
// ... this always sends a CNAME back to pointing-elsewhere.example.com. Loop time! // ... 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-Type', 'application/dns-udpwireformat');
res.setHeader('Content-Length', content.length); res.setHeader('Content-Length', content.length);
res.writeHead(200); res.writeHead(200);

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

@ -1163,6 +1163,7 @@ class Extension extends ExtensionData {
} }
this.addonData = addonData; this.addonData = addonData;
this.startupData = addonData.startupData || {};
this.startupReason = startupReason; this.startupReason = startupReason;
if (["ADDON_UPGRADE", "ADDON_DOWNGRADE"].includes(startupReason)) { if (["ADDON_UPGRADE", "ADDON_DOWNGRADE"].includes(startupReason)) {
@ -1357,6 +1358,10 @@ class Extension extends ExtensionData {
this.isPrivileged); this.isPrivileged);
} }
saveStartupData() {
AddonManagerPrivate.setStartupData(this.id, this.startupData);
}
async _parseManifest() { async _parseManifest() {
let manifest = await super.parseManifest(); let manifest = await super.parseManifest();
if (manifest && manifest.permissions.has("mozillaAddons") && if (manifest && manifest.permissions.has("mozillaAddons") &&

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

@ -179,19 +179,27 @@ class Port {
this.postMessage(json); this.postMessage(json);
}, },
onDisconnect: new EventManager(this.context, "Port.onDisconnect", fire => { onDisconnect: new EventManager({
return this.registerOnDisconnect(holder => { context: this.context,
let error = holder && holder.deserialize(this.context.cloneScope); name: "Port.onDisconnect",
portError = error && this.context.normalizeError(error); register: fire => {
fire.asyncWithoutClone(portObj); return this.registerOnDisconnect(holder => {
}); let error = holder && holder.deserialize(this.context.cloneScope);
portError = error && this.context.normalizeError(error);
fire.asyncWithoutClone(portObj);
});
},
}).api(), }).api(),
onMessage: new EventManager(this.context, "Port.onMessage", fire => { onMessage: new EventManager({
return this.registerOnMessage(holder => { context: this.context,
let msg = holder.deserialize(this.context.cloneScope); name: "Port.onMessage",
fire.asyncWithoutClone(msg, portObj); register: fire => {
}); return this.registerOnMessage(holder => {
let msg = holder.deserialize(this.context.cloneScope);
fire.asyncWithoutClone(msg, portObj);
});
},
}).api(), }).api(),
get error() { get error() {
@ -403,63 +411,67 @@ class Messenger {
} }
_onMessage(name, filter) { _onMessage(name, filter) {
return new EventManager(this.context, name, fire => { return new EventManager({
const caller = this.context.getCaller(); context: this.context,
name,
register: fire => {
const caller = this.context.getCaller();
let listener = { let listener = {
messageFilterPermissive: this.optionalFilter, messageFilterPermissive: this.optionalFilter,
messageFilterStrict: this.filter, messageFilterStrict: this.filter,
filterMessage: (sender, recipient) => { filterMessage: (sender, recipient) => {
// Exclude messages coming from content scripts for the devtools extension contexts // Exclude messages coming from content scripts for the devtools extension contexts
// (See Bug 1383310). // (See Bug 1383310).
if (this.excludeContentScriptSender && sender.envType === "content_child") { if (this.excludeContentScriptSender && sender.envType === "content_child") {
return false; return false;
} }
// Ignore the message if it was sent by this Messenger. // Ignore the message if it was sent by this Messenger.
return (sender.contextId !== this.context.contextId && return (sender.contextId !== this.context.contextId &&
filter(sender, recipient)); filter(sender, recipient));
}, },
receiveMessage: ({target, data: holder, sender, recipient, channelId}) => { receiveMessage: ({target, data: holder, sender, recipient, channelId}) => {
if (!this.context.active) { if (!this.context.active) {
return; return;
} }
let sendResponse; let sendResponse;
let response = undefined; let response = undefined;
let promise = new Promise(resolve => { let promise = new Promise(resolve => {
sendResponse = value => { sendResponse = value => {
resolve(value); resolve(value);
response = promise; response = promise;
}; };
}); });
let message = holder.deserialize(this.context.cloneScope); let message = holder.deserialize(this.context.cloneScope);
holder = null; holder = null;
sender = Cu.cloneInto(sender, this.context.cloneScope); sender = Cu.cloneInto(sender, this.context.cloneScope);
sendResponse = Cu.exportFunction(sendResponse, this.context.cloneScope); sendResponse = Cu.exportFunction(sendResponse, this.context.cloneScope);
// Note: We intentionally do not use runSafe here so that any // Note: We intentionally do not use runSafe here so that any
// errors are propagated to the message sender. // errors are propagated to the message sender.
let result = fire.raw(message, sender, sendResponse); let result = fire.raw(message, sender, sendResponse);
message = null; message = null;
if (result instanceof this.context.cloneScope.Promise) { if (result instanceof this.context.cloneScope.Promise) {
return StrongPromise.wrap(result, channelId, caller); return StrongPromise.wrap(result, channelId, caller);
} else if (result === true) { } else if (result === true) {
return StrongPromise.wrap(promise, channelId, caller); return StrongPromise.wrap(promise, channelId, caller);
} }
return response; return response;
}, },
}; };
MessageChannel.addListener(this.messageManagers, "Extension:Message", listener); MessageChannel.addListener(this.messageManagers, "Extension:Message", listener);
return () => { return () => {
MessageChannel.removeListener(this.messageManagers, "Extension:Message", listener); MessageChannel.removeListener(this.messageManagers, "Extension:Message", listener);
}; };
},
}).api(); }).api();
} }
@ -506,41 +518,45 @@ class Messenger {
} }
_onConnect(name, filter) { _onConnect(name, filter) {
return new EventManager(this.context, name, fire => { return new EventManager({
let listener = { context: this.context,
messageFilterPermissive: this.optionalFilter, name,
messageFilterStrict: this.filter, register: fire => {
let listener = {
messageFilterPermissive: this.optionalFilter,
messageFilterStrict: this.filter,
filterMessage: (sender, recipient) => { filterMessage: (sender, recipient) => {
// Exclude messages coming from content scripts for the devtools extension contexts // Exclude messages coming from content scripts for the devtools extension contexts
// (See Bug 1383310). // (See Bug 1383310).
if (this.excludeContentScriptSender && sender.envType === "content_child") { if (this.excludeContentScriptSender && sender.envType === "content_child") {
return false; return false;
} }
// Ignore the port if it was created by this Messenger. // Ignore the port if it was created by this Messenger.
return (sender.contextId !== this.context.contextId && return (sender.contextId !== this.context.contextId &&
filter(sender, recipient)); filter(sender, recipient));
}, },
receiveMessage: ({target, data: message, sender}) => { receiveMessage: ({target, data: message, sender}) => {
let {name, portId} = message; let {name, portId} = message;
let mm = getMessageManager(target); let mm = getMessageManager(target);
let recipient = Object.assign({}, sender); let recipient = Object.assign({}, sender);
if (recipient.tab) { if (recipient.tab) {
recipient.tabId = recipient.tab.id; recipient.tabId = recipient.tab.id;
delete recipient.tab; delete recipient.tab;
} }
let port = new Port(this.context, mm, this.messageManagers, name, portId, sender, recipient); let port = new Port(this.context, mm, this.messageManagers, name, portId, sender, recipient);
fire.asyncWithoutClone(port.api()); fire.asyncWithoutClone(port.api());
return true; return true;
}, },
}; };
MessageChannel.addListener(this.messageManagers, "Extension:Connect", listener); MessageChannel.addListener(this.messageManagers, "Extension:Connect", listener);
return () => { return () => {
MessageChannel.removeListener(this.messageManagers, "Extension:Connect", listener); MessageChannel.removeListener(this.messageManagers, "Extension:Connect", listener);
}; };
},
}).api(); }).api();
} }

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

@ -21,6 +21,7 @@ ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetters(this, { XPCOMUtils.defineLazyModuleGetters(this, {
AppConstants: "resource://gre/modules/AppConstants.jsm",
ConsoleAPI: "resource://gre/modules/Console.jsm", ConsoleAPI: "resource://gre/modules/Console.jsm",
MessageChannel: "resource://gre/modules/MessageChannel.jsm", MessageChannel: "resource://gre/modules/MessageChannel.jsm",
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.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 * @example
* new EventManager(context, "api.subAPI", fire => { * new EventManager({
* let listener = (...) => { * context,
* // Fire any listeners registered with addListener. * name: "api.subAPI",
* fire.async(arg1, arg2); * register: fire => {
* }; * let listener = (...) => {
* // Register the listener. * // Fire any listeners registered with addListener.
* SomehowRegisterListener(listener); * fire.async(arg1, arg2);
* return () => { * };
* // Return a way to unregister the listener. * // Register the listener.
* SomehowUnregisterListener(listener); * SomehowRegisterListener(listener);
* }; * return () => {
* // Return a way to unregister the listener.
* SomehowUnregisterListener(listener);
* };
* }
* }).api() * }).api()
* *
* The result is an object with addListener, removeListener, and * The result is an object with addListener, removeListener, and
* hasListener methods. `context` is an add-on scope (either an * hasListener methods. `context` is an add-on scope (either an
* ExtensionContext in the chrome process or ExtensionContext in a * ExtensionContext in the chrome process or ExtensionContext in a
* content process). `name` is for debugging. `register` is a function * content process).
* 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.
*/ */
function EventManager(context, name, register) { class EventManager {
this.context = context; /*
this.name = name; * @param {object} params
this.register = register; * Parameters that control this EventManager.
this.unregister = new Map(); * @param {BaseContext} params.context
this.inputHandling = false; * 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) { addListener(callback, ...args) {
if (this.unregister.has(callback)) { if (this.unregister.has(callback)) {
return; 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.unregister.set(callback, unregister);
this.context.callOnClose(this); 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)) { if (!this.unregister.has(callback)) {
return; return;
} }
@ -1837,24 +2064,31 @@ EventManager.prototype = {
} catch (e) { } catch (e) {
Cu.reportError(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) { if (this.unregister.size == 0) {
this.context.forgetOnClose(this); this.context.forgetOnClose(this);
} }
}, }
hasListener(callback) { hasListener(callback) {
return this.unregister.has(callback); return this.unregister.has(callback);
}, }
revoke() { revoke() {
for (let callback of this.unregister.keys()) { for (let callback of this.unregister.keys()) {
this.removeListener(callback); this.removeListener(callback, false);
} }
}, }
close() { close() {
this.revoke(); this.revoke();
}, }
api() { api() {
return { return {
@ -1864,8 +2098,8 @@ EventManager.prototype = {
setUserInput: this.inputHandling, setUserInput: this.inputHandling,
[Schemas.REVOKE]: () => this.revoke(), [Schemas.REVOKE]: () => this.revoke(),
}; };
}, }
}; }
// Simple API for event listeners where events never fire. // Simple API for event listeners where events never fire.
function ignoreEvent(context, name) { function ignoreEvent(context, name) {

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

@ -148,20 +148,24 @@ this.storage = class extends ExtensionAPI {
}, },
}, },
onChanged: new EventManager(context, "storage.onChanged", fire => { onChanged: new EventManager({
let onChanged = (data, area) => { context,
let changes = new context.cloneScope.Object(); name: "storage.onChanged",
for (let [key, value] of Object.entries(data)) { register: fire => {
changes[key] = deserialize(value); let onChanged = (data, area) => {
} let changes = new context.cloneScope.Object();
fire.raw(changes, area); for (let [key, value] of Object.entries(data)) {
}; changes[key] = deserialize(value);
}
fire.raw(changes, area);
};
let parent = context.childManager.getParentEvent("storage.onChanged"); let parent = context.childManager.getParentEvent("storage.onChanged");
parent.addListener(onChanged); parent.addListener(onChanged);
return () => { return () => {
parent.removeListener(onChanged); parent.removeListener(onChanged);
}; };
},
}).api(), }).api(),
}, },
}; };

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

@ -177,15 +177,19 @@ this.test = class extends ExtensionAPI {
} }
}, },
onMessage: new TestEventManager(context, "test.onMessage", fire => { onMessage: new TestEventManager({
let handler = (event, ...args) => { context,
fire.async(...args); name: "test.onMessage",
}; register: fire => {
let handler = (event, ...args) => {
fire.async(...args);
};
extension.on("test-harness-message", handler); extension.on("test-harness-message", handler);
return () => { return () => {
extension.off("test-harness-message", handler); extension.off("test-harness-message", handler);
}; };
},
}).api(), }).api(),
}, },
}; };

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

@ -281,7 +281,8 @@ const BrowserListener = {
background = null; background = null;
} }
if (background !== this.oldBackground) { if (background === null ||
background !== this.oldBackground) {
sendAsyncMessage("Extension:BrowserBackgroundChanged", {background}); sendAsyncMessage("Extension:BrowserBackgroundChanged", {background});
} }
this.oldBackground = background; this.oldBackground = background;

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

@ -14,6 +14,8 @@ module.exports = {
"WindowBase": true, "WindowBase": true,
"WindowManagerBase": true, "WindowManagerBase": true,
"WindowTrackerBase": true, "WindowTrackerBase": true,
"browserPaintedPromise": true,
"browserStartupPromise": true,
"getContainerForCookieStoreId": true, "getContainerForCookieStoreId": true,
"getCookieStoreIdForContainer": true, "getCookieStoreIdForContainer": true,
"getCookieStoreIdForTab": true, "getCookieStoreIdForTab": true,

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

@ -125,15 +125,19 @@ this.alarms = class extends ExtensionAPI {
return Promise.resolve(cleared); return Promise.resolve(cleared);
}, },
onAlarm: new EventManager(context, "alarms.onAlarm", fire => { onAlarm: new EventManager({
let callback = alarm => { context,
fire.sync(alarm.data); name: "alarms.onAlarm",
}; register: fire => {
let callback = alarm => {
fire.sync(alarm.data);
};
self.callbacks.add(callback); self.callbacks.add(callback);
return () => { return () => {
self.callbacks.delete(callback); self.callbacks.delete(callback);
}; };
},
}).api(), }).api(),
}, },
}; };

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

@ -9,6 +9,9 @@ var {
promiseExtensionViewLoaded, promiseExtensionViewLoaded,
} = ExtensionParent; } = ExtensionParent;
XPCOMUtils.defineLazyPreferenceGetter(this, "DELAYED_STARTUP",
"extensions.webextensions.background-delayed-startup");
// Responsible for the background_page section of the manifest. // Responsible for the background_page section of the manifest.
class BackgroundPage extends HiddenExtensionPage { class BackgroundPage extends HiddenExtensionPage {
constructor(extension, options) { constructor(extension, options) {
@ -54,11 +57,37 @@ class BackgroundPage extends HiddenExtensionPage {
this.backgroundPage = class extends ExtensionAPI { this.backgroundPage = class extends ExtensionAPI {
onManifestEntry(entryName) { 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() { onShutdown() {

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

@ -224,46 +224,58 @@ this.contextualIdentities = class extends ExtensionAPI {
return convertedIdentity; return convertedIdentity;
}, },
onCreated: new EventManager(context, "contextualIdentities.onCreated", fire => { onCreated: new EventManager({
let observer = (subject, topic) => { context,
let convertedIdentity = convertIdentityFromObserver(subject); name: "contextualIdentities.onCreated",
if (convertedIdentity) { register: fire => {
fire.async({contextualIdentity: convertedIdentity}); let observer = (subject, topic) => {
} let convertedIdentity = convertIdentityFromObserver(subject);
}; if (convertedIdentity) {
fire.async({contextualIdentity: convertedIdentity});
}
};
Services.obs.addObserver(observer, "contextual-identity-created"); Services.obs.addObserver(observer, "contextual-identity-created");
return () => { return () => {
Services.obs.removeObserver(observer, "contextual-identity-created"); Services.obs.removeObserver(observer, "contextual-identity-created");
}; };
},
}).api(), }).api(),
onUpdated: new EventManager(context, "contextualIdentities.onUpdated", fire => { onUpdated: new EventManager({
let observer = (subject, topic) => { context,
let convertedIdentity = convertIdentityFromObserver(subject); name: "contextualIdentities.onUpdated",
if (convertedIdentity) { register: fire => {
fire.async({contextualIdentity: convertedIdentity}); let observer = (subject, topic) => {
} let convertedIdentity = convertIdentityFromObserver(subject);
}; if (convertedIdentity) {
fire.async({contextualIdentity: convertedIdentity});
}
};
Services.obs.addObserver(observer, "contextual-identity-updated"); Services.obs.addObserver(observer, "contextual-identity-updated");
return () => { return () => {
Services.obs.removeObserver(observer, "contextual-identity-updated"); Services.obs.removeObserver(observer, "contextual-identity-updated");
}; };
},
}).api(), }).api(),
onRemoved: new EventManager(context, "contextualIdentities.onRemoved", fire => { onRemoved: new EventManager({
let observer = (subject, topic) => { context,
let convertedIdentity = convertIdentityFromObserver(subject); name: "contextualIdentities.onRemoved",
if (convertedIdentity) { register: fire => {
fire.async({contextualIdentity: convertedIdentity}); let observer = (subject, topic) => {
} let convertedIdentity = convertIdentityFromObserver(subject);
}; if (convertedIdentity) {
fire.async({contextualIdentity: convertedIdentity});
}
};
Services.obs.addObserver(observer, "contextual-identity-deleted"); Services.obs.addObserver(observer, "contextual-identity-deleted");
return () => { return () => {
Services.obs.removeObserver(observer, "contextual-identity-deleted"); Services.obs.removeObserver(observer, "contextual-identity-deleted");
}; };
},
}).api(), }).api(),
}, },

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

@ -412,48 +412,52 @@ this.cookies = class extends ExtensionAPI {
return Promise.resolve(result); return Promise.resolve(result);
}, },
onChanged: new EventManager(context, "cookies.onChanged", fire => { onChanged: new EventManager({
let observer = (subject, topic, data) => { context,
let notify = (removed, cookie, cause) => { name: "cookies.onChanged",
cookie.QueryInterface(Ci.nsICookie2); register: fire => {
let observer = (subject, topic, data) => {
let notify = (removed, cookie, cause) => {
cookie.QueryInterface(Ci.nsICookie2);
if (extension.whiteListedHosts.matchesCookie(cookie)) { if (extension.whiteListedHosts.matchesCookie(cookie)) {
fire.async({removed, cookie: convertCookie({cookie, isPrivate: topic == "private-cookie-changed"}), cause}); 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. Services.obs.addObserver(observer, "cookie-changed");
switch (data) { Services.obs.addObserver(observer, "private-cookie-changed");
case "deleted": return () => {
notify(true, subject, "explicit"); Services.obs.removeObserver(observer, "cookie-changed");
break; Services.obs.removeObserver(observer, "private-cookie-changed");
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");
};
}).api(), }).api(),
}, },
}; };

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

@ -765,60 +765,72 @@ this.downloads = class extends ExtensionAPI {
// ... // ...
// } // }
onChanged: new EventManager(context, "downloads.onChanged", fire => { onChanged: new EventManager({
const handler = (what, item) => { context,
let changes = {}; name: "downloads.onChanged",
const noundef = val => (val === undefined) ? null : val; register: fire => {
DOWNLOAD_ITEM_CHANGE_FIELDS.forEach(fld => { const handler = (what, item) => {
if (item[fld] != item.prechange[fld]) { let changes = {};
changes[fld] = { const noundef = val => (val === undefined) ? null : val;
previous: noundef(item.prechange[fld]), DOWNLOAD_ITEM_CHANGE_FIELDS.forEach(fld => {
current: noundef(item[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(() => { let registerPromise = DownloadMap.getDownloadList().then(() => {
DownloadMap.on("change", handler); DownloadMap.on("change", handler);
});
return () => {
registerPromise.then(() => {
DownloadMap.off("change", handler);
}); });
}; return () => {
registerPromise.then(() => {
DownloadMap.off("change", handler);
});
};
},
}).api(), }).api(),
onCreated: new EventManager(context, "downloads.onCreated", fire => { onCreated: new EventManager({
const handler = (what, item) => { context,
fire.async(item.serialize()); name: "downloads.onCreated",
}; register: fire => {
let registerPromise = DownloadMap.getDownloadList().then(() => { const handler = (what, item) => {
DownloadMap.on("create", handler); fire.async(item.serialize());
}); };
return () => { let registerPromise = DownloadMap.getDownloadList().then(() => {
registerPromise.then(() => { DownloadMap.on("create", handler);
DownloadMap.off("create", handler);
}); });
}; return () => {
registerPromise.then(() => {
DownloadMap.off("create", handler);
});
};
},
}).api(), }).api(),
onErased: new EventManager(context, "downloads.onErased", fire => { onErased: new EventManager({
const handler = (what, item) => { context,
fire.async(item.id); name: "downloads.onErased",
}; register: fire => {
let registerPromise = DownloadMap.getDownloadList().then(() => { const handler = (what, item) => {
DownloadMap.on("erase", handler); fire.async(item.id);
}); };
return () => { let registerPromise = DownloadMap.getDownloadList().then(() => {
registerPromise.then(() => { DownloadMap.on("erase", handler);
DownloadMap.off("erase", handler);
}); });
}; return () => {
registerPromise.then(() => {
DownloadMap.off("erase", handler);
});
};
},
}).api(), }).api(),
onDeterminingFilename: ignoreEvent(context, "downloads.onDeterminingFilename"), onDeterminingFilename: ignoreEvent(context, "downloads.onDeterminingFilename"),

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

@ -70,15 +70,19 @@ this.idle = class extends ExtensionAPI {
setDetectionInterval: function(detectionIntervalInSeconds) { setDetectionInterval: function(detectionIntervalInSeconds) {
setDetectionInterval(extension, context, detectionIntervalInSeconds); setDetectionInterval(extension, context, detectionIntervalInSeconds);
}, },
onStateChanged: new EventManager(context, "idle.onStateChanged", fire => { onStateChanged: new EventManager({
let listener = (event, data) => { context,
fire.sync(data); name: "idle.onStateChanged",
}; register: fire => {
let listener = (event, data) => {
fire.sync(data);
};
getIdleObserver(extension, context).on("stateChanged", listener); getIdleObserver(extension, context).on("stateChanged", listener);
return () => { return () => {
getIdleObserver(extension, context).off("stateChanged", listener); getIdleObserver(extension, context).off("stateChanged", listener);
}; };
},
}).api(), }).api(),
}, },
}; };

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

@ -230,48 +230,64 @@ this.management = class extends ExtensionAPI {
addon.userDisabled = !enabled; addon.userDisabled = !enabled;
}, },
onDisabled: new EventManager(context, "management.onDisabled", fire => { onDisabled: new EventManager({
let listener = (event, data) => { context,
fire.async(data); name: "management.onDisabled",
}; register: fire => {
let listener = (event, data) => {
fire.async(data);
};
getManagementListener(extension, context).on("onDisabled", listener); getManagementListener(extension, context).on("onDisabled", listener);
return () => { return () => {
getManagementListener(extension, context).off("onDisabled", listener); getManagementListener(extension, context).off("onDisabled", listener);
}; };
},
}).api(), }).api(),
onEnabled: new EventManager(context, "management.onEnabled", fire => { onEnabled: new EventManager({
let listener = (event, data) => { context,
fire.async(data); name: "management.onEnabled",
}; register: fire => {
let listener = (event, data) => {
fire.async(data);
};
getManagementListener(extension, context).on("onEnabled", listener); getManagementListener(extension, context).on("onEnabled", listener);
return () => { return () => {
getManagementListener(extension, context).off("onEnabled", listener); getManagementListener(extension, context).off("onEnabled", listener);
}; };
},
}).api(), }).api(),
onInstalled: new EventManager(context, "management.onInstalled", fire => { onInstalled: new EventManager({
let listener = (event, data) => { context,
fire.async(data); name: "management.onInstalled",
}; register: fire => {
let listener = (event, data) => {
fire.async(data);
};
getManagementListener(extension, context).on("onInstalled", listener); getManagementListener(extension, context).on("onInstalled", listener);
return () => { return () => {
getManagementListener(extension, context).off("onInstalled", listener); getManagementListener(extension, context).off("onInstalled", listener);
}; };
},
}).api(), }).api(),
onUninstalled: new EventManager(context, "management.onUninstalled", fire => { onUninstalled: new EventManager({
let listener = (event, data) => { context,
fire.async(data); name: "management.onUninstalled",
}; register: fire => {
let listener = (event, data) => {
fire.async(data);
};
getManagementListener(extension, context).on("onUninstalled", listener); getManagementListener(extension, context).on("onUninstalled", listener);
return () => { return () => {
getManagementListener(extension, context).off("onUninstalled", listener); getManagementListener(extension, context).off("onUninstalled", listener);
}; };
},
}).api(), }).api(),
}, },

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

@ -117,38 +117,50 @@ this.notifications = class extends ExtensionAPI {
return Promise.resolve(result); return Promise.resolve(result);
}, },
onClosed: new EventManager(context, "notifications.onClosed", fire => { onClosed: new EventManager({
let listener = (event, notificationId) => { context,
// TODO Bug 1413188, Support the byUser argument. name: "notifications.onClosed",
fire.async(notificationId, true); register: fire => {
}; let listener = (event, notificationId) => {
// TODO Bug 1413188, Support the byUser argument.
fire.async(notificationId, true);
};
notificationsMap.on("closed", listener); notificationsMap.on("closed", listener);
return () => { return () => {
notificationsMap.off("closed", listener); notificationsMap.off("closed", listener);
}; };
},
}).api(), }).api(),
onClicked: new EventManager(context, "notifications.onClicked", fire => { onClicked: new EventManager({
let listener = (event, notificationId) => { context,
fire.async(notificationId, true); name: "notifications.onClicked",
}; register: fire => {
let listener = (event, notificationId) => {
fire.async(notificationId, true);
};
notificationsMap.on("clicked", listener); notificationsMap.on("clicked", listener);
return () => { return () => {
notificationsMap.off("clicked", listener); notificationsMap.off("clicked", listener);
}; };
},
}).api(), }).api(),
onShown: new EventManager(context, "notifications.onShown", fire => { onShown: new EventManager({
let listener = (event, notificationId) => { context,
fire.async(notificationId, true); name: "notifications.onShown",
}; register: fire => {
let listener = (event, notificationId) => {
fire.async(notificationId, true);
};
notificationsMap.on("shown", listener); notificationsMap.on("shown", listener);
return () => { return () => {
notificationsMap.off("shown", listener); notificationsMap.off("shown", listener);
}; };
},
}).api(), }).api(),
// TODO Bug 1190681, implement button support. // 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) { getAPI(context) {
let {extension} = context; let {extension} = context;
let onError = new EventManager(context, "proxy.onError", fire => { let onError = new EventManager({
let listener = (name, error) => { context,
fire.async(error); name: "proxy.onError",
}; register: fire => {
extension.on("proxy-error", listener); let listener = (name, error) => {
return () => { fire.async(error);
extension.off("proxy-error", listener); };
}; extension.on("proxy-error", listener);
return () => {
extension.off("proxy-error", listener);
};
},
}).api(); }).api();
return { return {

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

@ -16,64 +16,76 @@ this.runtime = class extends ExtensionAPI {
let {extension} = context; let {extension} = context;
return { return {
runtime: { runtime: {
onStartup: new EventManager(context, "runtime.onStartup", fire => { onStartup: new EventManager({
if (context.incognito) { context,
// This event should not fire if we are operating in a private profile. name: "runtime.onStartup",
return () => {}; register: fire => {
} if (context.incognito) {
let listener = () => { // This event should not fire if we are operating in a private profile.
if (extension.startupReason === "APP_STARTUP") { return () => {};
fire.sync();
} }
}; let listener = () => {
extension.on("startup", listener); if (extension.startupReason === "APP_STARTUP") {
return () => { fire.sync();
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,
}; };
fire.sync(details); extension.on("startup", listener);
}); return () => {
return () => { extension.off("startup", listener);
AddonManager.removeUpgradeListener(instanceID).catch(e => { };
// This can happen if we try this after shutdown is complete. },
}).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(), }).api(),
reload: () => { reload: () => {

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

@ -89,20 +89,24 @@ this.storage = class extends ExtensionAPI {
}, },
}, },
onChanged: new EventManager(context, "storage.onChanged", fire => { onChanged: new EventManager({
let listenerLocal = changes => { context,
fire.raw(changes, "local"); name: "storage.onChanged",
}; register: fire => {
let listenerSync = changes => { let listenerLocal = changes => {
fire.async(changes, "sync"); fire.raw(changes, "local");
}; };
let listenerSync = changes => {
fire.async(changes, "sync");
};
ExtensionStorage.addOnChangedListener(extension.id, listenerLocal); ExtensionStorage.addOnChangedListener(extension.id, listenerLocal);
extensionStorageSync.addOnChangedListener(extension, listenerSync, context); extensionStorageSync.addOnChangedListener(extension, listenerSync, context);
return () => { return () => {
ExtensionStorage.removeOnChangedListener(extension.id, listenerLocal); ExtensionStorage.removeOnChangedListener(extension.id, listenerLocal);
extensionStorageSync.removeOnChangedListener(extension, listenerSync); extensionStorageSync.removeOnChangedListener(extension, listenerSync);
}; };
},
}).api(), }).api(),
}, },
}; };

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

@ -411,19 +411,23 @@ this.theme = class extends ExtensionAPI {
Theme.unload(windowId); Theme.unload(windowId);
}, },
onUpdated: new EventManager(context, "theme.onUpdated", fire => { onUpdated: new EventManager({
let callback = (event, theme, windowId) => { context,
if (windowId) { name: "theme.onUpdated",
fire.async({theme, windowId}); register: fire => {
} else { let callback = (event, theme, windowId) => {
fire.async({theme}); if (windowId) {
} fire.async({theme, windowId});
}; } else {
fire.async({theme});
}
};
onUpdatedEmitter.on("theme-updated", callback); onUpdatedEmitter.on("theme-updated", callback);
return () => { return () => {
onUpdatedEmitter.off("theme-updated", callback); onUpdatedEmitter.off("theme-updated", callback);
}; };
},
}).api(), }).api(),
}, },
}; };

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

@ -21,12 +21,6 @@ ChromeUtils.import("resource://gre/modules/ExtensionCommon.jsm");
global.EventEmitter = ExtensionUtils.EventEmitter; global.EventEmitter = ExtensionUtils.EventEmitter;
global.EventManager = ExtensionCommon.EventManager; global.EventManager = ExtensionCommon.EventManager;
global.InputEventManager = class extends EventManager {
constructor(...args) {
super(...args);
this.inputHandling = true;
}
};
/* globals DEFAULT_STORE, PRIVATE_STORE, CONTAINER_STORE */ /* globals DEFAULT_STORE, PRIVATE_STORE, CONTAINER_STORE */
@ -80,3 +74,24 @@ global.isValidCookieStoreId = function(storeId) {
isPrivateCookieStoreId(storeId) || isPrivateCookieStoreId(storeId) ||
isContainerCookieStoreId(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. // Similar to WebRequestEventManager but for WebNavigation.
function WebNavigationEventManager(context, eventName) { class WebNavigationEventManager extends EventManager {
let name = `webNavigation.${eventName}`; constructor(context, eventName) {
let register = (fire, urlFilters) => { let name = `webNavigation.${eventName}`;
// Don't create a MatchURLFilters instance if the listener does not include any filter. let register = (fire, urlFilters) => {
let filters = urlFilters ? new MatchURLFilters(urlFilters.url) : null; // 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 => { let listener = data => {
if (!data.browser) { if (!data.browser) {
return; return;
} }
let data2 = { let data2 = {
url: data.url, url: data.url,
timeStamp: Date.now(), 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") { WebNavigation[eventName].addListener(listener, filters);
data2.error = data.error; return () => {
} WebNavigation[eventName].removeListener(listener);
};
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); super({context, name, register});
return () => { }
WebNavigation[eventName].removeListener(listener);
};
};
return EventManager.call(this, context, name, register);
} }
WebNavigationEventManager.prototype = Object.create(EventManager.prototype);
const convertGetFrameResult = (tabId, data) => { const convertGetFrameResult = (tabId, data) => {
return { return {
errorOccurred: data.errorOccurred, errorOccurred: data.errorOccurred,
@ -161,8 +161,12 @@ this.webNavigation = class extends ExtensionAPI {
return { return {
webNavigation: { webNavigation: {
onTabReplaced: new EventManager(context, "webNavigation.onTabReplaced", fire => { onTabReplaced: new EventManager({
return () => {}; context,
name: "webNavigation.onTabReplaced",
register: fire => {
return () => {};
},
}).api(), }).api(),
onBeforeNavigate: new WebNavigationEventManager(context, "onBeforeNavigate").api(), onBeforeNavigate: new WebNavigationEventManager(context, "onBeforeNavigate").api(),
onCommitted: new WebNavigationEventManager(context, "onCommitted").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 { this.webRequest = class extends ExtensionAPI {
getAPI(context) { getAPI(context) {
return { return {
webRequest: { webRequest: {
onBeforeRequest: new WebRequestEventManager(context, "onBeforeRequest").api(), onBeforeRequest: WebRequestEventManager(context, "onBeforeRequest"),
onBeforeSendHeaders: new WebRequestEventManager(context, "onBeforeSendHeaders").api(), onBeforeSendHeaders: WebRequestEventManager(context, "onBeforeSendHeaders"),
onSendHeaders: new WebRequestEventManager(context, "onSendHeaders").api(), onSendHeaders: WebRequestEventManager(context, "onSendHeaders"),
onHeadersReceived: new WebRequestEventManager(context, "onHeadersReceived").api(), onHeadersReceived: WebRequestEventManager(context, "onHeadersReceived"),
onAuthRequired: new WebRequestEventManager(context, "onAuthRequired").api(), onAuthRequired: WebRequestEventManager(context, "onAuthRequired"),
onBeforeRedirect: new WebRequestEventManager(context, "onBeforeRedirect").api(), onBeforeRedirect: WebRequestEventManager(context, "onBeforeRedirect"),
onResponseStarted: new WebRequestEventManager(context, "onResponseStarted").api(), onResponseStarted: WebRequestEventManager(context, "onResponseStarted"),
onErrorOccurred: new WebRequestEventManager(context, "onErrorOccurred").api(), onErrorOccurred: WebRequestEventManager(context, "onErrorOccurred"),
onCompleted: new WebRequestEventManager(context, "onCompleted").api(), onCompleted: WebRequestEventManager(context, "onCompleted"),
handlerBehaviorChanged: function() { handlerBehaviorChanged: function() {
// TODO: Flush all caches. // TODO: Flush all caches.
}, },

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

@ -65,11 +65,15 @@ add_task(async function test_post_unload_listeners() {
let context = new StubContext(); let context = new StubContext();
let fire; let fire;
let manager = new EventManager(context, "EventManager", _fire => { let manager = new EventManager({
fire = () => { context,
_fire.async(); name: "EventManager",
}; register: _fire => {
return () => {}; fire = () => {
_fire.async();
};
return () => {};
},
}); });
let fail = event => { 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] [test_ext_onmessage_removelistener.js]
skip-if = true # This test no longer tests what it is meant to test. skip-if = true # This test no longer tests what it is meant to test.
[test_ext_permission_xhr.js] [test_ext_permission_xhr.js]
[test_ext_persistent_events.js]
[test_ext_privacy.js] [test_ext_privacy.js]
[test_ext_privacy_disable.js] [test_ext_privacy_disable.js]
[test_ext_privacy_update.js] [test_ext_privacy_update.js]
@ -83,6 +84,7 @@ skip-if = true # bug 1315829
[test_ext_sandbox_var.js] [test_ext_sandbox_var.js]
[test_ext_schema.js] [test_ext_schema.js]
[test_ext_simple.js] [test_ext_simple.js]
[test_ext_startupData.js]
[test_ext_startup_cache.js] [test_ext_startup_cache.js]
skip-if = os == "android" skip-if = os == "android"
[test_ext_startup_perf.js] [test_ext_startup_perf.js]

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

@ -47,7 +47,7 @@ static mozilla::LazyLogModule gResistFingerprintingLog("nsResistFingerprinting")
#define RESIST_FINGERPRINTING_PREF "privacy.resistFingerprinting" #define RESIST_FINGERPRINTING_PREF "privacy.resistFingerprinting"
#define RFP_TIMER_PREF "privacy.reduceTimerPrecision" #define RFP_TIMER_PREF "privacy.reduceTimerPrecision"
#define RFP_TIMER_VALUE_PREF "privacy.resistFingerprinting.reduceTimerPrecision.microseconds" #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_PREF "privacy.resistFingerprinting.reduceTimerPrecision.jitter"
#define RFP_JITTER_VALUE_DEFAULT true #define RFP_JITTER_VALUE_DEFAULT true
#define RFP_SPOOFED_FRAMES_PER_SEC_PREF "privacy.resistFingerprinting.video_frames_per_sec" #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() { 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 => { await new Promise(resolve => {
video.addEventListener("canplaythrough", resolve, {once: true}); video.addEventListener("canplaythrough", resolve, {once: true});
video.src = "seek_with_sound.ogg"; 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> </script>
</pre> </pre>
</body> </body>

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

@ -1193,6 +1193,10 @@
return; return;
} }
if (element == this.controlBar) {
this.controlsSpacer.removeAttribute("hideCursor");
}
// Unhide // Unhide
element.hidden = false; element.hidden = false;
} else { } else {

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

@ -3121,6 +3121,26 @@ var AddonManagerPrivate = {
let provider = AddonManagerInternal._getProviderByName("XPIProvider"); let provider = AddonManagerInternal._getProviderByName("XPIProvider");
return provider ? provider.isDBLoaded : false; 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.version = aDBAddon.version;
this.type = aDBAddon.type; this.type = aDBAddon.type;
this.startupData = aDBAddon.startupData; if (aDBAddon.startupData) {
this.startupData = aDBAddon.startupData;
}
this.bootstrapped = !!aDBAddon.bootstrap; this.bootstrapped = !!aDBAddon.bootstrap;
if (this.bootstrapped) { if (this.bootstrapped) {
@ -3450,6 +3452,23 @@ var XPIProvider = {
return addon.wrapper; 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. * Returns an Addon corresponding to an instance ID.
* @param aInstanceID * @param aInstanceID

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

@ -38,6 +38,14 @@
#include "wayland/gtk-primary-selection-client-protocol.h" #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 void
DataOffer::AddMIMEType(const char *aMimeType) DataOffer::AddMIMEType(const char *aMimeType)
{ {
@ -64,6 +72,17 @@ DataOffer::GetTargets(int* aTargetNum)
return targetList; 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* char*
DataOffer::GetData(wl_display* aDisplay, const char* aMimeType, DataOffer::GetData(wl_display* aDisplay, const char* aMimeType,
uint32_t* aContentLength) uint32_t* aContentLength)
@ -699,6 +718,25 @@ nsRetrievalContextWayland::GetClipboardData(const char* aMimeType,
return reinterpret_cast<const char*>(mClipboardData); 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) void nsRetrievalContextWayland::ReleaseClipboardData(const char* aClipboardData)
{ {
NS_ASSERTION(aClipboardData == mClipboardData, NS_ASSERTION(aClipboardData == mClipboardData,

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

@ -23,6 +23,8 @@ public:
void AddMIMEType(const char *aMimeType); void AddMIMEType(const char *aMimeType);
GdkAtom* GetTargets(int* aTargetNum); GdkAtom* GetTargets(int* aTargetNum);
bool HasTarget(const char *aMimeType);
char* GetData(wl_display* aDisplay, const char* aMimeType, char* GetData(wl_display* aDisplay, const char* aMimeType,
uint32_t* aContentLength); uint32_t* aContentLength);
@ -65,6 +67,7 @@ public:
virtual const char* GetClipboardData(const char* aMimeType, virtual const char* GetClipboardData(const char* aMimeType,
int32_t aWhichClipboard, int32_t aWhichClipboard,
uint32_t* aContentLength) override; uint32_t* aContentLength) override;
virtual const char* GetClipboardText(int32_t aWhichClipboard) override;
virtual void ReleaseClipboardData(const char* aClipboardData) override; virtual void ReleaseClipboardData(const char* aClipboardData) override;
virtual GdkAtom* GetTargets(int32_t aWhichClipboard, virtual GdkAtom* GetTargets(int32_t aWhichClipboard,
@ -104,6 +107,11 @@ private:
int mClipboardRequestNumber; int mClipboardRequestNumber;
char* mClipboardData; char* mClipboardData;
uint32_t mClipboardDataLength; 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_ */ #endif /* __nsClipboardWayland_h_ */