зеркало из https://github.com/mozilla/gecko-dev.git
Merge mozilla-central to mozilla-inbound. a=merge on a CLOSED TREE
This commit is contained in:
Коммит
0724b513f9
|
@ -158,7 +158,7 @@ ifdef MOZ_ARTIFACT_BUILDS
|
|||
$(call BUILDSTATUS,TIER_FINISH artifact)
|
||||
endif
|
||||
$(call BUILDSTATUS,TIER_START tup)
|
||||
@$(TUP) $(if $(MOZ_AUTOMATION),--quiet) $(if $(findstring s,$(filter-out --%,$(MAKEFLAGS))),,--verbose)
|
||||
@$(TUP) $(if $(MOZ_AUTOMATION),--quiet) $(if $(findstring s,$(filter-out --%,$(MAKEFLAGS))),,--verbose) $(topobjdir)
|
||||
$(call BUILDSTATUS,TIER_FINISH tup)
|
||||
|
||||
.PHONY: $(addprefix install-,$(install_manifests))
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?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>
|
||||
<emItem blockID="i334" id="{0F827075-B026-42F3-885D-98981EE7B1AE}">
|
||||
<prefs/>
|
||||
|
@ -2235,6 +2235,10 @@
|
|||
<prefs/>
|
||||
<versionRange minVersion="0" maxVersion="*" severity="1"/>
|
||||
</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>
|
||||
<pluginItems>
|
||||
<pluginItem blockID="p332">
|
||||
|
|
|
@ -129,6 +129,7 @@ class BasePopup {
|
|||
}
|
||||
if (panel && panel.id !== REMOTE_PANEL_ID) {
|
||||
panel.style.removeProperty("--arrowpanel-background");
|
||||
panel.style.removeProperty("--arrowpanel-border-color");
|
||||
panel.removeAttribute("remote");
|
||||
}
|
||||
|
||||
|
@ -355,9 +356,19 @@ class BasePopup {
|
|||
this.browser.dispatchEvent(event);
|
||||
}
|
||||
|
||||
setBackground(background = "") {
|
||||
if (background) {
|
||||
this.panel.style.setProperty("--arrowpanel-background", background);
|
||||
setBackground(background) {
|
||||
// Panels inherit the applied theme (light, dark, etc) and there is a high
|
||||
// likelihood that most extension authors will not have tested with a dark theme.
|
||||
// If they have not set a background-color, we force it to white to ensure visibility
|
||||
// of the extension content. Passing `null` should be treated the same as no argument,
|
||||
// which is why we can't use default parameters here.
|
||||
if (!background) {
|
||||
background = "#fff";
|
||||
}
|
||||
this.panel.style.setProperty("--arrowpanel-background", background);
|
||||
if (background == "#fff") {
|
||||
// Set a usable default color that work with the default background-color.
|
||||
this.panel.style.setProperty("--arrowpanel-border-color", "hsla(210,4%,10%,.05)");
|
||||
}
|
||||
this.background = background;
|
||||
}
|
||||
|
|
|
@ -33,21 +33,25 @@ this.devtools_network = class extends ExtensionAPI {
|
|||
return {
|
||||
devtools: {
|
||||
network: {
|
||||
onRequestFinished: new EventManager(context, "devtools.network.onRequestFinished", fire => {
|
||||
let onFinished = (data) => {
|
||||
const loader = new ChildNetworkResponseLoader(context, data.requestId);
|
||||
const harEntry = {...data.harEntry, ...loader.api()};
|
||||
const result = Cu.cloneInto(harEntry, context.cloneScope, {
|
||||
cloneFunctions: true,
|
||||
});
|
||||
fire.asyncWithoutClone(result);
|
||||
};
|
||||
onRequestFinished: new EventManager({
|
||||
context,
|
||||
name: "devtools.network.onRequestFinished",
|
||||
register: fire => {
|
||||
let onFinished = (data) => {
|
||||
const loader = new ChildNetworkResponseLoader(context, data.requestId);
|
||||
const harEntry = {...data.harEntry, ...loader.api()};
|
||||
const result = Cu.cloneInto(harEntry, context.cloneScope, {
|
||||
cloneFunctions: true,
|
||||
});
|
||||
fire.asyncWithoutClone(result);
|
||||
};
|
||||
|
||||
let parent = context.childManager.getParentEvent("devtools.network.onRequestFinished");
|
||||
parent.addListener(onFinished);
|
||||
return () => {
|
||||
parent.removeListener(onFinished);
|
||||
};
|
||||
let parent = context.childManager.getParentEvent("devtools.network.onRequestFinished");
|
||||
parent.addListener(onFinished);
|
||||
return () => {
|
||||
parent.removeListener(onFinished);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
},
|
||||
},
|
||||
|
|
|
@ -94,8 +94,10 @@ class ChildDevToolsPanel extends ExtensionUtils.EventEmitter {
|
|||
|
||||
api() {
|
||||
return {
|
||||
onShown: new EventManager(
|
||||
this.context, "devtoolsPanel.onShown", fire => {
|
||||
onShown: new EventManager({
|
||||
context: this.context,
|
||||
name: "devtoolsPanel.onShown",
|
||||
register: fire => {
|
||||
const listener = (eventName, panelContentWindow) => {
|
||||
fire.asyncWithoutClone(panelContentWindow);
|
||||
};
|
||||
|
@ -103,10 +105,13 @@ class ChildDevToolsPanel extends ExtensionUtils.EventEmitter {
|
|||
return () => {
|
||||
this.off("shown", listener);
|
||||
};
|
||||
}).api(),
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onHidden: new EventManager(
|
||||
this.context, "devtoolsPanel.onHidden", fire => {
|
||||
onHidden: new EventManager({
|
||||
context: this.context,
|
||||
name: "devtoolsPanel.onHidden",
|
||||
register: fire => {
|
||||
const listener = () => {
|
||||
fire.async();
|
||||
};
|
||||
|
@ -114,7 +119,8 @@ class ChildDevToolsPanel extends ExtensionUtils.EventEmitter {
|
|||
return () => {
|
||||
this.off("hidden", listener);
|
||||
};
|
||||
}).api(),
|
||||
},
|
||||
}).api(),
|
||||
|
||||
// TODO(rpl): onSearch event and createStatusBarButton method
|
||||
};
|
||||
|
@ -189,8 +195,10 @@ class ChildDevToolsInspectorSidebar extends ExtensionUtils.EventEmitter {
|
|||
const {context, id} = this;
|
||||
|
||||
return {
|
||||
onShown: new EventManager(
|
||||
context, "devtoolsInspectorSidebar.onShown", fire => {
|
||||
onShown: new EventManager({
|
||||
context,
|
||||
name: "devtoolsInspectorSidebar.onShown",
|
||||
register: fire => {
|
||||
const listener = (eventName, panelContentWindow) => {
|
||||
fire.asyncWithoutClone(panelContentWindow);
|
||||
};
|
||||
|
@ -198,10 +206,13 @@ class ChildDevToolsInspectorSidebar extends ExtensionUtils.EventEmitter {
|
|||
return () => {
|
||||
this.off("shown", listener);
|
||||
};
|
||||
}).api(),
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onHidden: new EventManager(
|
||||
context, "devtoolsInspectorSidebar.onHidden", fire => {
|
||||
onHidden: new EventManager({
|
||||
context,
|
||||
name: "devtoolsInspectorSidebar.onHidden",
|
||||
register: fire => {
|
||||
const listener = () => {
|
||||
fire.async();
|
||||
};
|
||||
|
@ -209,7 +220,8 @@ class ChildDevToolsInspectorSidebar extends ExtensionUtils.EventEmitter {
|
|||
return () => {
|
||||
this.off("hidden", listener);
|
||||
};
|
||||
}).api(),
|
||||
},
|
||||
}).api(),
|
||||
|
||||
setObject(jsonObject, rootTitle) {
|
||||
return context.cloneScope.Promise.resolve().then(() => {
|
||||
|
@ -279,8 +291,10 @@ this.devtools_panels = class extends ExtensionAPI {
|
|||
get themeName() {
|
||||
return themeChangeObserver.themeName;
|
||||
},
|
||||
onThemeChanged: new EventManager(
|
||||
context, "devtools.panels.onThemeChanged", fire => {
|
||||
onThemeChanged: new EventManager({
|
||||
context,
|
||||
name: "devtools.panels.onThemeChanged",
|
||||
register: fire => {
|
||||
const listener = (eventName, themeName) => {
|
||||
fire.async(themeName);
|
||||
};
|
||||
|
@ -288,7 +302,8 @@ this.devtools_panels = class extends ExtensionAPI {
|
|||
return () => {
|
||||
themeChangeObserver.off("themeChanged", listener);
|
||||
};
|
||||
}).api(),
|
||||
},
|
||||
}).api(),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -159,17 +159,21 @@ this.menusInternal = class extends ExtensionAPI {
|
|||
return context.childManager.callParentAsyncFunction("menusInternal.removeAll", []);
|
||||
},
|
||||
|
||||
onClicked: new EventManager(context, "menus.onClicked", fire => {
|
||||
let listener = (info, tab) => {
|
||||
withHandlingUserInput(context.contentWindow,
|
||||
() => fire.sync(info, tab));
|
||||
};
|
||||
onClicked: new EventManager({
|
||||
context,
|
||||
name: "menus.onClicked",
|
||||
register: fire => {
|
||||
let listener = (info, tab) => {
|
||||
withHandlingUserInput(context.contentWindow,
|
||||
() => fire.sync(info, tab));
|
||||
};
|
||||
|
||||
let event = context.childManager.getParentEvent("menusInternal.onClicked");
|
||||
event.addListener(listener);
|
||||
return () => {
|
||||
event.removeListener(listener);
|
||||
};
|
||||
let event = context.childManager.getParentEvent("menusInternal.onClicked");
|
||||
event.addListener(listener);
|
||||
return () => {
|
||||
event.removeListener(listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -6,19 +6,23 @@ this.omnibox = class extends ExtensionAPI {
|
|||
getAPI(context) {
|
||||
return {
|
||||
omnibox: {
|
||||
onInputChanged: new EventManager(context, "omnibox.onInputChanged", fire => {
|
||||
let listener = (text, id) => {
|
||||
fire.asyncWithoutClone(text, suggestions => {
|
||||
context.childManager.callParentFunctionNoReturn("omnibox.addSuggestions", [
|
||||
id,
|
||||
suggestions,
|
||||
]);
|
||||
});
|
||||
};
|
||||
context.childManager.getParentEvent("omnibox.onInputChanged").addListener(listener);
|
||||
return () => {
|
||||
context.childManager.getParentEvent("omnibox.onInputChanged").removeListener(listener);
|
||||
};
|
||||
onInputChanged: new EventManager({
|
||||
context,
|
||||
name: "omnibox.onInputChanged",
|
||||
register: fire => {
|
||||
let listener = (text, id) => {
|
||||
fire.asyncWithoutClone(text, suggestions => {
|
||||
context.childManager.callParentFunctionNoReturn("omnibox.addSuggestions", [
|
||||
id,
|
||||
suggestions,
|
||||
]);
|
||||
});
|
||||
};
|
||||
context.childManager.getParentEvent("omnibox.onInputChanged").addListener(listener);
|
||||
return () => {
|
||||
context.childManager.getParentEvent("omnibox.onInputChanged").removeListener(listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -7,7 +7,6 @@ module.exports = {
|
|||
"Tab": true,
|
||||
"TabContext": true,
|
||||
"Window": true,
|
||||
"WindowEventManager": true,
|
||||
"actionContextMenu": true,
|
||||
"browserActionFor": true,
|
||||
"getContainerForCookieStoreId": true,
|
||||
|
|
|
@ -329,56 +329,72 @@ this.bookmarks = class extends ExtensionAPI {
|
|||
}
|
||||
},
|
||||
|
||||
onCreated: new EventManager(context, "bookmarks.onCreated", fire => {
|
||||
let listener = (event, bookmark) => {
|
||||
fire.sync(bookmark.id, bookmark);
|
||||
};
|
||||
onCreated: new EventManager({
|
||||
context,
|
||||
name: "bookmarks.onCreated",
|
||||
register: fire => {
|
||||
let listener = (event, bookmark) => {
|
||||
fire.sync(bookmark.id, bookmark);
|
||||
};
|
||||
|
||||
observer.on("created", listener);
|
||||
incrementListeners();
|
||||
return () => {
|
||||
observer.off("created", listener);
|
||||
decrementListeners();
|
||||
};
|
||||
observer.on("created", listener);
|
||||
incrementListeners();
|
||||
return () => {
|
||||
observer.off("created", listener);
|
||||
decrementListeners();
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onRemoved: new EventManager(context, "bookmarks.onRemoved", fire => {
|
||||
let listener = (event, data) => {
|
||||
fire.sync(data.guid, data.info);
|
||||
};
|
||||
onRemoved: new EventManager({
|
||||
context,
|
||||
name: "bookmarks.onRemoved",
|
||||
register: fire => {
|
||||
let listener = (event, data) => {
|
||||
fire.sync(data.guid, data.info);
|
||||
};
|
||||
|
||||
observer.on("removed", listener);
|
||||
incrementListeners();
|
||||
return () => {
|
||||
observer.off("removed", listener);
|
||||
decrementListeners();
|
||||
};
|
||||
observer.on("removed", listener);
|
||||
incrementListeners();
|
||||
return () => {
|
||||
observer.off("removed", listener);
|
||||
decrementListeners();
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onChanged: new EventManager(context, "bookmarks.onChanged", fire => {
|
||||
let listener = (event, data) => {
|
||||
fire.sync(data.guid, data.info);
|
||||
};
|
||||
onChanged: new EventManager({
|
||||
context,
|
||||
name: "bookmarks.onChanged",
|
||||
register: fire => {
|
||||
let listener = (event, data) => {
|
||||
fire.sync(data.guid, data.info);
|
||||
};
|
||||
|
||||
observer.on("changed", listener);
|
||||
incrementListeners();
|
||||
return () => {
|
||||
observer.off("changed", listener);
|
||||
decrementListeners();
|
||||
};
|
||||
observer.on("changed", listener);
|
||||
incrementListeners();
|
||||
return () => {
|
||||
observer.off("changed", listener);
|
||||
decrementListeners();
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onMoved: new EventManager(context, "bookmarks.onMoved", fire => {
|
||||
let listener = (event, data) => {
|
||||
fire.sync(data.guid, data.info);
|
||||
};
|
||||
onMoved: new EventManager({
|
||||
context,
|
||||
name: "bookmarks.onMoved",
|
||||
register: fire => {
|
||||
let listener = (event, data) => {
|
||||
fire.sync(data.guid, data.info);
|
||||
};
|
||||
|
||||
observer.on("moved", listener);
|
||||
incrementListeners();
|
||||
return () => {
|
||||
observer.off("moved", listener);
|
||||
decrementListeners();
|
||||
};
|
||||
observer.on("moved", listener);
|
||||
incrementListeners();
|
||||
return () => {
|
||||
observer.off("moved", listener);
|
||||
decrementListeners();
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -169,34 +169,6 @@ class WindowTracker extends WindowTrackerBase {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An event manager API provider which listens for a DOM event in any browser
|
||||
* window, and calls the given listener function whenever an event is received.
|
||||
* That listener function receives a `fire` object, which it can use to dispatch
|
||||
* events to the extension, and a DOM event object.
|
||||
*
|
||||
* @param {BaseContext} context
|
||||
* The extension context which the event manager belongs to.
|
||||
* @param {string} name
|
||||
* The API name of the event manager, e.g.,"runtime.onMessage".
|
||||
* @param {string} event
|
||||
* The name of the DOM event to listen for.
|
||||
* @param {function} listener
|
||||
* The listener function to call when a DOM event is received.
|
||||
*/
|
||||
global.WindowEventManager = class extends EventManager {
|
||||
constructor(context, name, event, listener) {
|
||||
super(context, name, fire => {
|
||||
let listener2 = listener.bind(null, fire);
|
||||
|
||||
windowTracker.addListener(event, listener2);
|
||||
return () => {
|
||||
windowTracker.removeListener(event, listener2);
|
||||
};
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
class TabTracker extends TabTrackerBase {
|
||||
constructor() {
|
||||
super();
|
||||
|
|
|
@ -584,15 +584,20 @@ this.browserAction = class extends ExtensionAPI {
|
|||
|
||||
return {
|
||||
browserAction: {
|
||||
onClicked: new InputEventManager(context, "browserAction.onClicked", fire => {
|
||||
let listener = (event, browser) => {
|
||||
context.withPendingBrowser(browser, () =>
|
||||
fire.sync(tabManager.convert(tabTracker.activeTab)));
|
||||
};
|
||||
browserAction.on("click", listener);
|
||||
return () => {
|
||||
browserAction.off("click", listener);
|
||||
};
|
||||
onClicked: new EventManager({
|
||||
context,
|
||||
name: "browserAction.onClicked",
|
||||
inputHandling: true,
|
||||
register: fire => {
|
||||
let listener = (event, browser) => {
|
||||
context.withPendingBrowser(browser, () =>
|
||||
fire.sync(tabManager.convert(tabTracker.activeTab)));
|
||||
};
|
||||
browserAction.on("click", listener);
|
||||
return () => {
|
||||
browserAction.off("click", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
enable: function(tabId) {
|
||||
|
|
|
@ -364,14 +364,18 @@ this.commands = class extends ExtensionAPI {
|
|||
this.registerKeys(commands);
|
||||
}
|
||||
},
|
||||
onCommand: new EventManager(context, "commands.onCommand", fire => {
|
||||
let listener = (eventName, commandName) => {
|
||||
fire.async(commandName);
|
||||
};
|
||||
this.on("command", listener);
|
||||
return () => {
|
||||
this.off("command", listener);
|
||||
};
|
||||
onCommand: new EventManager({
|
||||
context,
|
||||
name: "commands.onCommand",
|
||||
register: fire => {
|
||||
let listener = (eventName, commandName) => {
|
||||
fire.async(commandName);
|
||||
};
|
||||
this.on("command", listener);
|
||||
return () => {
|
||||
this.off("command", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -15,37 +15,45 @@ this.devtools_network = class extends ExtensionAPI {
|
|||
return {
|
||||
devtools: {
|
||||
network: {
|
||||
onNavigated: new EventManager(context, "devtools.onNavigated", fire => {
|
||||
let listener = data => {
|
||||
fire.async(data.url);
|
||||
};
|
||||
onNavigated: new EventManager({
|
||||
context,
|
||||
name: "devtools.onNavigated",
|
||||
register: fire => {
|
||||
let listener = data => {
|
||||
fire.async(data.url);
|
||||
};
|
||||
|
||||
let targetPromise = getDevToolsTargetForContext(context);
|
||||
targetPromise.then(target => {
|
||||
target.on("navigate", listener);
|
||||
});
|
||||
return () => {
|
||||
let targetPromise = getDevToolsTargetForContext(context);
|
||||
targetPromise.then(target => {
|
||||
target.off("navigate", listener);
|
||||
target.on("navigate", listener);
|
||||
});
|
||||
};
|
||||
return () => {
|
||||
targetPromise.then(target => {
|
||||
target.off("navigate", listener);
|
||||
});
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
getHAR: function() {
|
||||
return context.devToolsToolbox.getHARFromNetMonitor();
|
||||
},
|
||||
|
||||
onRequestFinished: new EventManager(context, "devtools.network.onRequestFinished", fire => {
|
||||
const listener = (data) => {
|
||||
fire.async(data);
|
||||
};
|
||||
onRequestFinished: new EventManager({
|
||||
context,
|
||||
name: "devtools.network.onRequestFinished",
|
||||
register: fire => {
|
||||
const listener = (data) => {
|
||||
fire.async(data);
|
||||
};
|
||||
|
||||
const toolbox = context.devToolsToolbox;
|
||||
toolbox.addRequestFinishedListener(listener);
|
||||
const toolbox = context.devToolsToolbox;
|
||||
toolbox.addRequestFinishedListener(listener);
|
||||
|
||||
return () => {
|
||||
toolbox.removeRequestFinishedListener(listener);
|
||||
};
|
||||
return () => {
|
||||
toolbox.removeRequestFinishedListener(listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
// The following method is used internally to allow the request API
|
||||
|
|
|
@ -525,8 +525,10 @@ this.devtools_panels = class extends ExtensionAPI {
|
|||
devtools: {
|
||||
panels: {
|
||||
elements: {
|
||||
onSelectionChanged: new EventManager(
|
||||
context, "devtools.panels.elements.onSelectionChanged", fire => {
|
||||
onSelectionChanged: new EventManager({
|
||||
context,
|
||||
name: "devtools.panels.elements.onSelectionChanged",
|
||||
register: fire => {
|
||||
const listener = (eventName) => {
|
||||
fire.async();
|
||||
};
|
||||
|
@ -534,7 +536,8 @@ this.devtools_panels = class extends ExtensionAPI {
|
|||
return () => {
|
||||
toolboxSelectionObserver.off("selectionChanged", listener);
|
||||
};
|
||||
}).api(),
|
||||
},
|
||||
}).api(),
|
||||
createSidebarPane(title) {
|
||||
const id = `devtools-inspector-sidebar-${makeWidgetId(newBasePanelId())}`;
|
||||
|
||||
|
|
|
@ -473,11 +473,15 @@ this.geckoProfiler = class extends ExtensionAPI {
|
|||
throw new Error(`Ran out of options to get symbols from library ${debugName} ${breakpadId}.`);
|
||||
},
|
||||
|
||||
onRunning: new EventManager(context, "geckoProfiler.onRunning", fire => {
|
||||
isRunningObserver.addObserver(fire.async);
|
||||
return () => {
|
||||
isRunningObserver.removeObserver(fire.async);
|
||||
};
|
||||
onRunning: new EventManager({
|
||||
context,
|
||||
name: "geckoProfiler.onRunning",
|
||||
register: fire => {
|
||||
isRunningObserver.addObserver(fire.async);
|
||||
return () => {
|
||||
isRunningObserver.removeObserver(fire.async);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -220,37 +220,49 @@ this.history = class extends ExtensionAPI {
|
|||
return Promise.resolve(results);
|
||||
},
|
||||
|
||||
onVisited: new EventManager(context, "history.onVisited", fire => {
|
||||
let listener = (event, data) => {
|
||||
fire.sync(data);
|
||||
};
|
||||
onVisited: new EventManager({
|
||||
context,
|
||||
name: "history.onVisited",
|
||||
register: fire => {
|
||||
let listener = (event, data) => {
|
||||
fire.sync(data);
|
||||
};
|
||||
|
||||
getHistoryObserver().on("visited", listener);
|
||||
return () => {
|
||||
getHistoryObserver().off("visited", listener);
|
||||
};
|
||||
getHistoryObserver().on("visited", listener);
|
||||
return () => {
|
||||
getHistoryObserver().off("visited", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onVisitRemoved: new EventManager(context, "history.onVisitRemoved", fire => {
|
||||
let listener = (event, data) => {
|
||||
fire.sync(data);
|
||||
};
|
||||
onVisitRemoved: new EventManager({
|
||||
context,
|
||||
name: "history.onVisitRemoved",
|
||||
register: fire => {
|
||||
let listener = (event, data) => {
|
||||
fire.sync(data);
|
||||
};
|
||||
|
||||
getHistoryObserver().on("visitRemoved", listener);
|
||||
return () => {
|
||||
getHistoryObserver().off("visitRemoved", listener);
|
||||
};
|
||||
getHistoryObserver().on("visitRemoved", listener);
|
||||
return () => {
|
||||
getHistoryObserver().off("visitRemoved", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onTitleChanged: new EventManager(context, "history.onTitleChanged", fire => {
|
||||
let listener = (event, data) => {
|
||||
fire.sync(data);
|
||||
};
|
||||
onTitleChanged: new EventManager({
|
||||
context,
|
||||
name: "history.onTitleChanged",
|
||||
register: fire => {
|
||||
let listener = (event, data) => {
|
||||
fire.sync(data);
|
||||
};
|
||||
|
||||
getHistoryObserver().on("titleChanged", listener);
|
||||
return () => {
|
||||
getHistoryObserver().off("titleChanged", listener);
|
||||
};
|
||||
getHistoryObserver().on("titleChanged", listener);
|
||||
return () => {
|
||||
getHistoryObserver().off("titleChanged", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -813,40 +813,48 @@ this.menusInternal = class extends ExtensionAPI {
|
|||
gMenuBuilder.rebuildMenu(extension);
|
||||
},
|
||||
|
||||
onShown: new EventManager(context, "menus.onShown", fire => {
|
||||
let listener = (event, menuIds, contextData) => {
|
||||
let info = {
|
||||
menuIds,
|
||||
contexts: Array.from(getMenuContexts(contextData)),
|
||||
};
|
||||
onShown: new EventManager({
|
||||
context,
|
||||
name: "menus.onShown",
|
||||
register: fire => {
|
||||
let listener = (event, menuIds, contextData) => {
|
||||
let info = {
|
||||
menuIds,
|
||||
contexts: Array.from(getMenuContexts(contextData)),
|
||||
};
|
||||
|
||||
// The menus.onShown event is fired before the user has consciously
|
||||
// interacted with an extension, so we require permissions before
|
||||
// exposing sensitive contextual data.
|
||||
let includeSensitiveData =
|
||||
// The menus.onShown event is fired before the user has consciously
|
||||
// interacted with an extension, so we require permissions before
|
||||
// exposing sensitive contextual data.
|
||||
let includeSensitiveData =
|
||||
extension.tabManager.hasActiveTabPermission(contextData.tab) ||
|
||||
extension.whiteListedHosts.matches(contextData.inFrame ? contextData.frameUrl : contextData.pageUrl);
|
||||
|
||||
addMenuEventInfo(info, contextData, includeSensitiveData);
|
||||
addMenuEventInfo(info, contextData, includeSensitiveData);
|
||||
|
||||
let tab = extension.tabManager.convert(contextData.tab);
|
||||
fire.sync(info, tab);
|
||||
};
|
||||
gOnShownSubscribers.add(extension);
|
||||
extension.on("webext-menu-shown", listener);
|
||||
return () => {
|
||||
gOnShownSubscribers.delete(extension);
|
||||
extension.off("webext-menu-shown", listener);
|
||||
};
|
||||
let tab = extension.tabManager.convert(contextData.tab);
|
||||
fire.sync(info, tab);
|
||||
};
|
||||
gOnShownSubscribers.add(extension);
|
||||
extension.on("webext-menu-shown", listener);
|
||||
return () => {
|
||||
gOnShownSubscribers.delete(extension);
|
||||
extension.off("webext-menu-shown", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
onHidden: new EventManager(context, "menus.onHidden", fire => {
|
||||
let listener = () => {
|
||||
fire.sync();
|
||||
};
|
||||
extension.on("webext-menu-hidden", listener);
|
||||
return () => {
|
||||
extension.off("webext-menu-hidden", listener);
|
||||
};
|
||||
onHidden: new EventManager({
|
||||
context,
|
||||
name: "menus.onHidden",
|
||||
register: fire => {
|
||||
let listener = () => {
|
||||
fire.sync();
|
||||
};
|
||||
extension.on("webext-menu-hidden", listener);
|
||||
return () => {
|
||||
extension.off("webext-menu-hidden", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
};
|
||||
|
||||
|
@ -883,18 +891,22 @@ this.menusInternal = class extends ExtensionAPI {
|
|||
}
|
||||
},
|
||||
|
||||
onClicked: new EventManager(context, "menusInternal.onClicked", fire => {
|
||||
let listener = (event, info, nativeTab) => {
|
||||
let {linkedBrowser} = nativeTab || tabTracker.activeTab;
|
||||
let tab = nativeTab && extension.tabManager.convert(nativeTab);
|
||||
context.withPendingBrowser(linkedBrowser,
|
||||
() => fire.sync(info, tab));
|
||||
};
|
||||
onClicked: new EventManager({
|
||||
context,
|
||||
name: "menusInternal.onClicked",
|
||||
register: fire => {
|
||||
let listener = (event, info, nativeTab) => {
|
||||
let {linkedBrowser} = nativeTab || tabTracker.activeTab;
|
||||
let tab = nativeTab && extension.tabManager.convert(nativeTab);
|
||||
context.withPendingBrowser(linkedBrowser,
|
||||
() => fire.sync(info, tab));
|
||||
};
|
||||
|
||||
extension.on("webext-menu-menuitem-click", listener);
|
||||
return () => {
|
||||
extension.off("webext-menu-menuitem-click", listener);
|
||||
};
|
||||
extension.on("webext-menu-menuitem-click", listener);
|
||||
return () => {
|
||||
extension.off("webext-menu-menuitem-click", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -37,34 +37,46 @@ this.omnibox = class extends ExtensionAPI {
|
|||
}
|
||||
},
|
||||
|
||||
onInputStarted: new EventManager(context, "omnibox.onInputStarted", fire => {
|
||||
let listener = (eventName) => {
|
||||
fire.sync();
|
||||
};
|
||||
extension.on(ExtensionSearchHandler.MSG_INPUT_STARTED, listener);
|
||||
return () => {
|
||||
extension.off(ExtensionSearchHandler.MSG_INPUT_STARTED, listener);
|
||||
};
|
||||
onInputStarted: new EventManager({
|
||||
context,
|
||||
name: "omnibox.onInputStarted",
|
||||
register: fire => {
|
||||
let listener = (eventName) => {
|
||||
fire.sync();
|
||||
};
|
||||
extension.on(ExtensionSearchHandler.MSG_INPUT_STARTED, listener);
|
||||
return () => {
|
||||
extension.off(ExtensionSearchHandler.MSG_INPUT_STARTED, listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onInputCancelled: new EventManager(context, "omnibox.onInputCancelled", fire => {
|
||||
let listener = (eventName) => {
|
||||
fire.sync();
|
||||
};
|
||||
extension.on(ExtensionSearchHandler.MSG_INPUT_CANCELLED, listener);
|
||||
return () => {
|
||||
extension.off(ExtensionSearchHandler.MSG_INPUT_CANCELLED, listener);
|
||||
};
|
||||
onInputCancelled: new EventManager({
|
||||
context,
|
||||
name: "omnibox.onInputCancelled",
|
||||
register: fire => {
|
||||
let listener = (eventName) => {
|
||||
fire.sync();
|
||||
};
|
||||
extension.on(ExtensionSearchHandler.MSG_INPUT_CANCELLED, listener);
|
||||
return () => {
|
||||
extension.off(ExtensionSearchHandler.MSG_INPUT_CANCELLED, listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onInputEntered: new EventManager(context, "omnibox.onInputEntered", fire => {
|
||||
let listener = (eventName, text, disposition) => {
|
||||
fire.sync(text, disposition);
|
||||
};
|
||||
extension.on(ExtensionSearchHandler.MSG_INPUT_ENTERED, listener);
|
||||
return () => {
|
||||
extension.off(ExtensionSearchHandler.MSG_INPUT_ENTERED, listener);
|
||||
};
|
||||
onInputEntered: new EventManager({
|
||||
context,
|
||||
name: "omnibox.onInputEntered",
|
||||
register: fire => {
|
||||
let listener = (eventName, text, disposition) => {
|
||||
fire.sync(text, disposition);
|
||||
};
|
||||
extension.on(ExtensionSearchHandler.MSG_INPUT_ENTERED, listener);
|
||||
return () => {
|
||||
extension.off(ExtensionSearchHandler.MSG_INPUT_ENTERED, listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
// Internal APIs.
|
||||
|
@ -77,14 +89,18 @@ this.omnibox = class extends ExtensionAPI {
|
|||
}
|
||||
},
|
||||
|
||||
onInputChanged: new EventManager(context, "omnibox.onInputChanged", fire => {
|
||||
let listener = (eventName, text, id) => {
|
||||
fire.sync(text, id);
|
||||
};
|
||||
extension.on(ExtensionSearchHandler.MSG_INPUT_CHANGED, listener);
|
||||
return () => {
|
||||
extension.off(ExtensionSearchHandler.MSG_INPUT_CHANGED, listener);
|
||||
};
|
||||
onInputChanged: new EventManager({
|
||||
context,
|
||||
name: "omnibox.onInputChanged",
|
||||
register: fire => {
|
||||
let listener = (eventName, text, id) => {
|
||||
fire.sync(text, id);
|
||||
};
|
||||
extension.on(ExtensionSearchHandler.MSG_INPUT_CHANGED, listener);
|
||||
return () => {
|
||||
extension.off(ExtensionSearchHandler.MSG_INPUT_CHANGED, listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -346,16 +346,21 @@ this.pageAction = class extends ExtensionAPI {
|
|||
|
||||
return {
|
||||
pageAction: {
|
||||
onClicked: new InputEventManager(context, "pageAction.onClicked", fire => {
|
||||
let listener = (evt, tab) => {
|
||||
context.withPendingBrowser(tab.linkedBrowser, () =>
|
||||
fire.sync(tabManager.convert(tab)));
|
||||
};
|
||||
onClicked: new EventManager({
|
||||
context,
|
||||
name: "pageAction.onClicked",
|
||||
inputHandling: true,
|
||||
register: fire => {
|
||||
let listener = (evt, tab) => {
|
||||
context.withPendingBrowser(tab.linkedBrowser, () =>
|
||||
fire.sync(tabManager.convert(tab)));
|
||||
};
|
||||
|
||||
pageAction.on("click", listener);
|
||||
return () => {
|
||||
pageAction.off("click", listener);
|
||||
};
|
||||
pageAction.on("click", listener);
|
||||
return () => {
|
||||
pageAction.off("click", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
show(tabId) {
|
||||
|
|
|
@ -204,15 +204,19 @@ this.sessions = class extends ExtensionAPI {
|
|||
SessionStore.deleteWindowValue(win, encodedKey);
|
||||
},
|
||||
|
||||
onChanged: new EventManager(context, "sessions.onChanged", fire => {
|
||||
let observer = () => {
|
||||
fire.async();
|
||||
};
|
||||
onChanged: new EventManager({
|
||||
context,
|
||||
name: "sessions.onChanged",
|
||||
register: fire => {
|
||||
let observer = () => {
|
||||
fire.async();
|
||||
};
|
||||
|
||||
Services.obs.addObserver(observer, SS_ON_CLOSED_OBJECTS_CHANGED);
|
||||
return () => {
|
||||
Services.obs.removeObserver(observer, SS_ON_CLOSED_OBJECTS_CHANGED);
|
||||
};
|
||||
Services.obs.addObserver(observer, SS_ON_CLOSED_OBJECTS_CHANGED);
|
||||
return () => {
|
||||
Services.obs.removeObserver(observer, SS_ON_CLOSED_OBJECTS_CHANGED);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -110,7 +110,7 @@ const allProperties = new Set([
|
|||
const restricted = new Set(["url", "favIconUrl", "title"]);
|
||||
|
||||
class TabsUpdateFilterEventManager extends EventManager {
|
||||
constructor(context, eventName) {
|
||||
constructor(context) {
|
||||
let {extension} = context;
|
||||
let {tabManager} = extension;
|
||||
|
||||
|
@ -286,7 +286,11 @@ class TabsUpdateFilterEventManager extends EventManager {
|
|||
};
|
||||
};
|
||||
|
||||
super(context, eventName, register);
|
||||
super({
|
||||
context,
|
||||
name: "tabs.onUpdated",
|
||||
register,
|
||||
});
|
||||
}
|
||||
|
||||
addListener(callback, filter) {
|
||||
|
@ -338,26 +342,34 @@ this.tabs = class extends ExtensionAPI {
|
|||
|
||||
let self = {
|
||||
tabs: {
|
||||
onActivated: new EventManager(context, "tabs.onActivated", fire => {
|
||||
let listener = (eventName, event) => {
|
||||
fire.async(event);
|
||||
};
|
||||
onActivated: new EventManager({
|
||||
context,
|
||||
name: "tabs.onActivated",
|
||||
register: fire => {
|
||||
let listener = (eventName, event) => {
|
||||
fire.async(event);
|
||||
};
|
||||
|
||||
tabTracker.on("tab-activated", listener);
|
||||
return () => {
|
||||
tabTracker.off("tab-activated", listener);
|
||||
};
|
||||
tabTracker.on("tab-activated", listener);
|
||||
return () => {
|
||||
tabTracker.off("tab-activated", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onCreated: new EventManager(context, "tabs.onCreated", fire => {
|
||||
let listener = (eventName, event) => {
|
||||
fire.async(tabManager.convert(event.nativeTab, event.currentTab));
|
||||
};
|
||||
onCreated: new EventManager({
|
||||
context,
|
||||
name: "tabs.onCreated",
|
||||
register: fire => {
|
||||
let listener = (eventName, event) => {
|
||||
fire.async(tabManager.convert(event.nativeTab, event.currentTab));
|
||||
};
|
||||
|
||||
tabTracker.on("tab-created", listener);
|
||||
return () => {
|
||||
tabTracker.off("tab-created", listener);
|
||||
};
|
||||
tabTracker.on("tab-created", listener);
|
||||
return () => {
|
||||
tabTracker.off("tab-created", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
/**
|
||||
|
@ -366,98 +378,122 @@ this.tabs = class extends ExtensionAPI {
|
|||
* the tabId in an array to match the API.
|
||||
* @see https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/Tabs/onHighlighted
|
||||
*/
|
||||
onHighlighted: new EventManager(context, "tabs.onHighlighted", fire => {
|
||||
let listener = (eventName, event) => {
|
||||
fire.async({tabIds: [event.tabId], windowId: event.windowId});
|
||||
};
|
||||
onHighlighted: new EventManager({
|
||||
context,
|
||||
name: "tabs.onHighlighted",
|
||||
register: fire => {
|
||||
let listener = (eventName, event) => {
|
||||
fire.async({tabIds: [event.tabId], windowId: event.windowId});
|
||||
};
|
||||
|
||||
tabTracker.on("tab-activated", listener);
|
||||
return () => {
|
||||
tabTracker.off("tab-activated", listener);
|
||||
};
|
||||
tabTracker.on("tab-activated", listener);
|
||||
return () => {
|
||||
tabTracker.off("tab-activated", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onAttached: new EventManager(context, "tabs.onAttached", fire => {
|
||||
let listener = (eventName, event) => {
|
||||
fire.async(event.tabId, {newWindowId: event.newWindowId, newPosition: event.newPosition});
|
||||
};
|
||||
onAttached: new EventManager({
|
||||
context,
|
||||
name: "tabs.onAttached",
|
||||
register: fire => {
|
||||
let listener = (eventName, event) => {
|
||||
fire.async(event.tabId, {newWindowId: event.newWindowId, newPosition: event.newPosition});
|
||||
};
|
||||
|
||||
tabTracker.on("tab-attached", listener);
|
||||
return () => {
|
||||
tabTracker.off("tab-attached", listener);
|
||||
};
|
||||
tabTracker.on("tab-attached", listener);
|
||||
return () => {
|
||||
tabTracker.off("tab-attached", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onDetached: new EventManager(context, "tabs.onDetached", fire => {
|
||||
let listener = (eventName, event) => {
|
||||
fire.async(event.tabId, {oldWindowId: event.oldWindowId, oldPosition: event.oldPosition});
|
||||
};
|
||||
onDetached: new EventManager({
|
||||
context,
|
||||
name: "tabs.onDetached",
|
||||
register: fire => {
|
||||
let listener = (eventName, event) => {
|
||||
fire.async(event.tabId, {oldWindowId: event.oldWindowId, oldPosition: event.oldPosition});
|
||||
};
|
||||
|
||||
tabTracker.on("tab-detached", listener);
|
||||
return () => {
|
||||
tabTracker.off("tab-detached", listener);
|
||||
};
|
||||
tabTracker.on("tab-detached", listener);
|
||||
return () => {
|
||||
tabTracker.off("tab-detached", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onRemoved: new EventManager(context, "tabs.onRemoved", fire => {
|
||||
let listener = (eventName, event) => {
|
||||
fire.async(event.tabId, {windowId: event.windowId, isWindowClosing: event.isWindowClosing});
|
||||
};
|
||||
onRemoved: new EventManager({
|
||||
context,
|
||||
name: "tabs.onRemoved",
|
||||
register: fire => {
|
||||
let listener = (eventName, event) => {
|
||||
fire.async(event.tabId, {windowId: event.windowId, isWindowClosing: event.isWindowClosing});
|
||||
};
|
||||
|
||||
tabTracker.on("tab-removed", listener);
|
||||
return () => {
|
||||
tabTracker.off("tab-removed", listener);
|
||||
};
|
||||
tabTracker.on("tab-removed", listener);
|
||||
return () => {
|
||||
tabTracker.off("tab-removed", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onReplaced: new EventManager(context, "tabs.onReplaced", fire => {
|
||||
return () => {};
|
||||
onReplaced: new EventManager({
|
||||
context,
|
||||
name: "tabs.onReplaced",
|
||||
register: fire => {
|
||||
return () => {};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onMoved: new EventManager(context, "tabs.onMoved", fire => {
|
||||
// There are certain circumstances where we need to ignore a move event.
|
||||
//
|
||||
// Namely, the first time the tab is moved after it's created, we need
|
||||
// to report the final position as the initial position in the tab's
|
||||
// onAttached or onCreated event. This is because most tabs are inserted
|
||||
// in a temporary location and then moved after the TabOpen event fires,
|
||||
// which generates a TabOpen event followed by a TabMove event, which
|
||||
// does not match the contract of our API.
|
||||
let ignoreNextMove = new WeakSet();
|
||||
onMoved: new EventManager({
|
||||
context,
|
||||
name: "tabs.onMoved",
|
||||
register: fire => {
|
||||
// There are certain circumstances where we need to ignore a move event.
|
||||
//
|
||||
// Namely, the first time the tab is moved after it's created, we need
|
||||
// to report the final position as the initial position in the tab's
|
||||
// onAttached or onCreated event. This is because most tabs are inserted
|
||||
// in a temporary location and then moved after the TabOpen event fires,
|
||||
// which generates a TabOpen event followed by a TabMove event, which
|
||||
// does not match the contract of our API.
|
||||
let ignoreNextMove = new WeakSet();
|
||||
|
||||
let openListener = event => {
|
||||
ignoreNextMove.add(event.target);
|
||||
// Remove the tab from the set on the next tick, since it will already
|
||||
// have been moved by then.
|
||||
Promise.resolve().then(() => {
|
||||
ignoreNextMove.delete(event.target);
|
||||
});
|
||||
};
|
||||
let openListener = event => {
|
||||
ignoreNextMove.add(event.target);
|
||||
// Remove the tab from the set on the next tick, since it will already
|
||||
// have been moved by then.
|
||||
Promise.resolve().then(() => {
|
||||
ignoreNextMove.delete(event.target);
|
||||
});
|
||||
};
|
||||
|
||||
let moveListener = event => {
|
||||
let nativeTab = event.originalTarget;
|
||||
let moveListener = event => {
|
||||
let nativeTab = event.originalTarget;
|
||||
|
||||
if (ignoreNextMove.has(nativeTab)) {
|
||||
ignoreNextMove.delete(nativeTab);
|
||||
return;
|
||||
}
|
||||
if (ignoreNextMove.has(nativeTab)) {
|
||||
ignoreNextMove.delete(nativeTab);
|
||||
return;
|
||||
}
|
||||
|
||||
fire.async(tabTracker.getId(nativeTab), {
|
||||
windowId: windowTracker.getId(nativeTab.ownerGlobal),
|
||||
fromIndex: event.detail,
|
||||
toIndex: nativeTab._tPos,
|
||||
});
|
||||
};
|
||||
fire.async(tabTracker.getId(nativeTab), {
|
||||
windowId: windowTracker.getId(nativeTab.ownerGlobal),
|
||||
fromIndex: event.detail,
|
||||
toIndex: nativeTab._tPos,
|
||||
});
|
||||
};
|
||||
|
||||
windowTracker.addListener("TabMove", moveListener);
|
||||
windowTracker.addListener("TabOpen", openListener);
|
||||
return () => {
|
||||
windowTracker.removeListener("TabMove", moveListener);
|
||||
windowTracker.removeListener("TabOpen", openListener);
|
||||
};
|
||||
windowTracker.addListener("TabMove", moveListener);
|
||||
windowTracker.addListener("TabOpen", openListener);
|
||||
return () => {
|
||||
windowTracker.removeListener("TabMove", moveListener);
|
||||
windowTracker.removeListener("TabOpen", openListener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onUpdated: new TabsUpdateFilterEventManager(context, "tabs.onUpdated").api(),
|
||||
onUpdated: new TabsUpdateFilterEventManager(context).api(),
|
||||
|
||||
create(createProperties) {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
@ -896,75 +932,79 @@ this.tabs = class extends ExtensionAPI {
|
|||
return Promise.resolve();
|
||||
},
|
||||
|
||||
onZoomChange: new EventManager(context, "tabs.onZoomChange", fire => {
|
||||
let getZoomLevel = browser => {
|
||||
let {ZoomManager} = browser.ownerGlobal;
|
||||
onZoomChange: new EventManager({
|
||||
context,
|
||||
name: "tabs.onZoomChange",
|
||||
register: fire => {
|
||||
let getZoomLevel = browser => {
|
||||
let {ZoomManager} = browser.ownerGlobal;
|
||||
|
||||
return ZoomManager.getZoomForBrowser(browser);
|
||||
};
|
||||
return ZoomManager.getZoomForBrowser(browser);
|
||||
};
|
||||
|
||||
// Stores the last known zoom level for each tab's browser.
|
||||
// WeakMap[<browser> -> number]
|
||||
let zoomLevels = new WeakMap();
|
||||
// Stores the last known zoom level for each tab's browser.
|
||||
// WeakMap[<browser> -> number]
|
||||
let zoomLevels = new WeakMap();
|
||||
|
||||
// Store the zoom level for all existing tabs.
|
||||
for (let window of windowTracker.browserWindows()) {
|
||||
for (let nativeTab of window.gBrowser.tabs) {
|
||||
let browser = nativeTab.linkedBrowser;
|
||||
// Store the zoom level for all existing tabs.
|
||||
for (let window of windowTracker.browserWindows()) {
|
||||
for (let nativeTab of window.gBrowser.tabs) {
|
||||
let browser = nativeTab.linkedBrowser;
|
||||
zoomLevels.set(browser, getZoomLevel(browser));
|
||||
}
|
||||
}
|
||||
|
||||
let tabCreated = (eventName, event) => {
|
||||
let browser = event.nativeTab.linkedBrowser;
|
||||
zoomLevels.set(browser, getZoomLevel(browser));
|
||||
}
|
||||
}
|
||||
|
||||
let tabCreated = (eventName, event) => {
|
||||
let browser = event.nativeTab.linkedBrowser;
|
||||
zoomLevels.set(browser, getZoomLevel(browser));
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
let zoomListener = event => {
|
||||
let browser = event.originalTarget;
|
||||
let zoomListener = event => {
|
||||
let browser = event.originalTarget;
|
||||
|
||||
// For non-remote browsers, this event is dispatched on the document
|
||||
// rather than on the <browser>.
|
||||
if (browser instanceof Ci.nsIDOMDocument) {
|
||||
browser = browser.docShell.chromeEventHandler;
|
||||
}
|
||||
// For non-remote browsers, this event is dispatched on the document
|
||||
// rather than on the <browser>.
|
||||
if (browser instanceof Ci.nsIDOMDocument) {
|
||||
browser = browser.docShell.chromeEventHandler;
|
||||
}
|
||||
|
||||
let {gBrowser} = browser.ownerGlobal;
|
||||
let nativeTab = gBrowser.getTabForBrowser(browser);
|
||||
if (!nativeTab) {
|
||||
// We only care about zoom events in the top-level browser of a tab.
|
||||
return;
|
||||
}
|
||||
let {gBrowser} = browser.ownerGlobal;
|
||||
let nativeTab = gBrowser.getTabForBrowser(browser);
|
||||
if (!nativeTab) {
|
||||
// We only care about zoom events in the top-level browser of a tab.
|
||||
return;
|
||||
}
|
||||
|
||||
let oldZoomFactor = zoomLevels.get(browser);
|
||||
let newZoomFactor = getZoomLevel(browser);
|
||||
let oldZoomFactor = zoomLevels.get(browser);
|
||||
let newZoomFactor = getZoomLevel(browser);
|
||||
|
||||
if (oldZoomFactor != newZoomFactor) {
|
||||
zoomLevels.set(browser, newZoomFactor);
|
||||
if (oldZoomFactor != newZoomFactor) {
|
||||
zoomLevels.set(browser, newZoomFactor);
|
||||
|
||||
let tabId = tabTracker.getId(nativeTab);
|
||||
fire.async({
|
||||
tabId,
|
||||
oldZoomFactor,
|
||||
newZoomFactor,
|
||||
zoomSettings: self.tabs._getZoomSettings(tabId),
|
||||
});
|
||||
}
|
||||
};
|
||||
let tabId = tabTracker.getId(nativeTab);
|
||||
fire.async({
|
||||
tabId,
|
||||
oldZoomFactor,
|
||||
newZoomFactor,
|
||||
zoomSettings: self.tabs._getZoomSettings(tabId),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
tabTracker.on("tab-attached", tabCreated);
|
||||
tabTracker.on("tab-created", tabCreated);
|
||||
tabTracker.on("tab-attached", tabCreated);
|
||||
tabTracker.on("tab-created", tabCreated);
|
||||
|
||||
windowTracker.addListener("FullZoomChange", zoomListener);
|
||||
windowTracker.addListener("TextZoomChange", zoomListener);
|
||||
return () => {
|
||||
tabTracker.off("tab-attached", tabCreated);
|
||||
tabTracker.off("tab-created", tabCreated);
|
||||
windowTracker.addListener("FullZoomChange", zoomListener);
|
||||
windowTracker.addListener("TextZoomChange", zoomListener);
|
||||
return () => {
|
||||
tabTracker.off("tab-attached", tabCreated);
|
||||
tabTracker.off("tab-created", tabCreated);
|
||||
|
||||
windowTracker.removeListener("FullZoomChange", zoomListener);
|
||||
windowTracker.removeListener("TextZoomChange", zoomListener);
|
||||
};
|
||||
windowTracker.removeListener("FullZoomChange", zoomListener);
|
||||
windowTracker.removeListener("TextZoomChange", zoomListener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
print() {
|
||||
|
|
|
@ -16,6 +16,36 @@ const onXULFrameLoaderCreated = ({target}) => {
|
|||
target.messageManager.sendAsyncMessage("AllowScriptsToClose", {});
|
||||
};
|
||||
|
||||
/**
|
||||
* An event manager API provider which listens for a DOM event in any browser
|
||||
* window, and calls the given listener function whenever an event is received.
|
||||
* That listener function receives a `fire` object, which it can use to dispatch
|
||||
* events to the extension, and a DOM event object.
|
||||
*
|
||||
* @param {BaseContext} context
|
||||
* The extension context which the event manager belongs to.
|
||||
* @param {string} name
|
||||
* The API name of the event manager, e.g.,"runtime.onMessage".
|
||||
* @param {string} event
|
||||
* The name of the DOM event to listen for.
|
||||
* @param {function} listener
|
||||
* The listener function to call when a DOM event is received.
|
||||
*
|
||||
* @returns {object} An injectable api for the new event.
|
||||
*/
|
||||
function WindowEventManager(context, name, event, listener) {
|
||||
let register = fire => {
|
||||
let listener2 = listener.bind(null, fire);
|
||||
|
||||
windowTracker.addListener(event, listener2);
|
||||
return () => {
|
||||
windowTracker.removeListener(event, listener2);
|
||||
};
|
||||
};
|
||||
|
||||
return new EventManager({context, name, register}).api();
|
||||
}
|
||||
|
||||
this.windows = class extends ExtensionAPI {
|
||||
getAPI(context) {
|
||||
let {extension} = context;
|
||||
|
@ -24,38 +54,40 @@ this.windows = class extends ExtensionAPI {
|
|||
|
||||
return {
|
||||
windows: {
|
||||
onCreated:
|
||||
new WindowEventManager(context, "windows.onCreated", "domwindowopened", (fire, window) => {
|
||||
onCreated: WindowEventManager(context, "windows.onCreated", "domwindowopened", (fire, window) => {
|
||||
fire.async(windowManager.convert(window));
|
||||
}).api(),
|
||||
}),
|
||||
|
||||
onRemoved:
|
||||
new WindowEventManager(context, "windows.onRemoved", "domwindowclosed", (fire, window) => {
|
||||
onRemoved: WindowEventManager(context, "windows.onRemoved", "domwindowclosed", (fire, window) => {
|
||||
fire.async(windowTracker.getId(window));
|
||||
}).api(),
|
||||
}),
|
||||
|
||||
onFocusChanged: new EventManager(context, "windows.onFocusChanged", fire => {
|
||||
// Keep track of the last windowId used to fire an onFocusChanged event
|
||||
let lastOnFocusChangedWindowId;
|
||||
onFocusChanged: new EventManager({
|
||||
context,
|
||||
name: "windows.onFocusChanged",
|
||||
register: fire => {
|
||||
// Keep track of the last windowId used to fire an onFocusChanged event
|
||||
let lastOnFocusChangedWindowId;
|
||||
|
||||
let listener = event => {
|
||||
// Wait a tick to avoid firing a superfluous WINDOW_ID_NONE
|
||||
// event when switching focus between two Firefox windows.
|
||||
Promise.resolve().then(() => {
|
||||
let window = Services.focus.activeWindow;
|
||||
let windowId = window ? windowTracker.getId(window) : Window.WINDOW_ID_NONE;
|
||||
if (windowId !== lastOnFocusChangedWindowId) {
|
||||
fire.async(windowId);
|
||||
lastOnFocusChangedWindowId = windowId;
|
||||
}
|
||||
});
|
||||
};
|
||||
windowTracker.addListener("focus", listener);
|
||||
windowTracker.addListener("blur", listener);
|
||||
return () => {
|
||||
windowTracker.removeListener("focus", listener);
|
||||
windowTracker.removeListener("blur", listener);
|
||||
};
|
||||
let listener = event => {
|
||||
// Wait a tick to avoid firing a superfluous WINDOW_ID_NONE
|
||||
// event when switching focus between two Firefox windows.
|
||||
Promise.resolve().then(() => {
|
||||
let window = Services.focus.activeWindow;
|
||||
let windowId = window ? windowTracker.getId(window) : Window.WINDOW_ID_NONE;
|
||||
if (windowId !== lastOnFocusChangedWindowId) {
|
||||
fire.async(windowId);
|
||||
lastOnFocusChangedWindowId = windowId;
|
||||
}
|
||||
});
|
||||
};
|
||||
windowTracker.addListener("focus", listener);
|
||||
windowTracker.addListener("blur", listener);
|
||||
return () => {
|
||||
windowTracker.removeListener("focus", listener);
|
||||
windowTracker.removeListener("blur", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
get: function(windowId, getInfo) {
|
||||
|
|
|
@ -3,114 +3,123 @@
|
|||
/* eslint-disable mozilla/no-arbitrary-setTimeout */
|
||||
"use strict";
|
||||
|
||||
add_task(async function testPopupBackground() {
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
background() {
|
||||
browser.tabs.query({active: true, currentWindow: true}, tabs => {
|
||||
browser.pageAction.show(tabs[0].id);
|
||||
});
|
||||
},
|
||||
async function testPanel(browser, standAlone, initial_background) {
|
||||
let panel = getPanelForNode(browser);
|
||||
let arrowContent = document.getAnonymousElementByAttribute(panel, "class", "panel-arrowcontent");
|
||||
let arrow = document.getAnonymousElementByAttribute(panel, "anonid", "arrow");
|
||||
|
||||
manifest: {
|
||||
"browser_action": {
|
||||
"default_popup": "popup.html",
|
||||
"browser_style": false,
|
||||
},
|
||||
|
||||
"page_action": {
|
||||
"default_popup": "popup.html",
|
||||
"browser_style": false,
|
||||
},
|
||||
},
|
||||
|
||||
files: {
|
||||
"popup.html": `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
<body style="width: 100px; height: 100px; background-color: green;">
|
||||
</body>
|
||||
</html>`,
|
||||
},
|
||||
});
|
||||
|
||||
await extension.startup();
|
||||
|
||||
async function testPanel(browser, standAlone) {
|
||||
let panel = getPanelForNode(browser);
|
||||
let arrowContent = document.getAnonymousElementByAttribute(panel, "class", "panel-arrowcontent");
|
||||
let arrow = document.getAnonymousElementByAttribute(panel, "anonid", "arrow");
|
||||
|
||||
let checkArrow = (background = null) => {
|
||||
if (background == null || !standAlone) {
|
||||
ok(!arrow.style.hasOwnProperty("fill"), "Arrow fill should be the default one");
|
||||
return;
|
||||
}
|
||||
|
||||
is(getComputedStyle(arrowContent).backgroundColor, background, "Arrow content should have correct background");
|
||||
is(getComputedStyle(arrow).fill, background, "Arrow should have correct background");
|
||||
};
|
||||
|
||||
function getBackground(browser) {
|
||||
return ContentTask.spawn(browser, null, async function() {
|
||||
return content.getComputedStyle(content.document.body)
|
||||
.backgroundColor;
|
||||
});
|
||||
let checkArrow = (background = null) => {
|
||||
if (background == null || !standAlone) {
|
||||
ok(!arrow.style.hasOwnProperty("fill"), "Arrow fill should be the default one");
|
||||
return;
|
||||
}
|
||||
|
||||
let setBackground = color => {
|
||||
content.document.body.style.backgroundColor = color;
|
||||
};
|
||||
is(getComputedStyle(arrowContent).backgroundColor, background, "Arrow content should have correct background");
|
||||
is(getComputedStyle(arrow).fill, background, "Arrow should have correct background");
|
||||
};
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
info("Test that initial background color is applied");
|
||||
|
||||
checkArrow(await getBackground(browser));
|
||||
|
||||
info("Test that dynamically-changed background color is applied");
|
||||
|
||||
await alterContent(browser, setBackground, "black");
|
||||
|
||||
checkArrow(await getBackground(browser));
|
||||
|
||||
info("Test that non-opaque background color results in default styling");
|
||||
|
||||
await alterContent(browser, setBackground, "rgba(1, 2, 3, .9)");
|
||||
|
||||
checkArrow(null);
|
||||
function getBackground(browser) {
|
||||
return ContentTask.spawn(browser, null, async function() {
|
||||
return content.getComputedStyle(content.document.body)
|
||||
.backgroundColor;
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
info("Test stand-alone browserAction popup");
|
||||
let setBackground = color => {
|
||||
content.document.body.style.backgroundColor = color;
|
||||
};
|
||||
|
||||
clickBrowserAction(extension);
|
||||
let browser = await awaitExtensionPanel(extension);
|
||||
await testPanel(browser, true);
|
||||
await closeBrowserAction(extension);
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
info("Test that initial background color is applied");
|
||||
checkArrow(initial_background);
|
||||
|
||||
info("Test that dynamically-changed background color is applied");
|
||||
await alterContent(browser, setBackground, "black");
|
||||
checkArrow(await getBackground(browser));
|
||||
|
||||
info("Test that non-opaque background color results in default styling");
|
||||
await alterContent(browser, setBackground, "rgba(1, 2, 3, .9)");
|
||||
checkArrow(null);
|
||||
}
|
||||
|
||||
add_task(async function testPopupBackground() {
|
||||
let testCases = [{
|
||||
"browser_style": false,
|
||||
"background": "background-color: green;",
|
||||
"initial_background": "rgb(0, 128, 0)",
|
||||
}, {
|
||||
"browser_style": true,
|
||||
// Use white here instead of transparent, because
|
||||
// when no background is supplied we will fill
|
||||
// with white by default.
|
||||
"initial_background": "rgb(255, 255, 255)",
|
||||
}];
|
||||
for (let testCase of testCases) {
|
||||
info(`Testing browser_style: ${testCase.browser_style} with background? ${!!testCase.background}`);
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
background() {
|
||||
browser.tabs.query({active: true, currentWindow: true}, tabs => {
|
||||
browser.pageAction.show(tabs[0].id);
|
||||
});
|
||||
},
|
||||
|
||||
manifest: {
|
||||
"browser_action": {
|
||||
"default_popup": "popup.html",
|
||||
"browser_style": testCase.browser_style,
|
||||
},
|
||||
|
||||
"page_action": {
|
||||
"default_popup": "popup.html",
|
||||
"browser_style": testCase.browser_style,
|
||||
},
|
||||
},
|
||||
|
||||
files: {
|
||||
"popup.html": `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
<body style="width: 100px; height: 100px; ${testCase.background || ""}">
|
||||
</body>
|
||||
</html>`,
|
||||
},
|
||||
});
|
||||
|
||||
await extension.startup();
|
||||
|
||||
{
|
||||
info("Test stand-alone browserAction popup");
|
||||
|
||||
clickBrowserAction(extension);
|
||||
let browser = await awaitExtensionPanel(extension);
|
||||
await testPanel(browser, true, testCase.initial_background);
|
||||
await closeBrowserAction(extension);
|
||||
}
|
||||
|
||||
{
|
||||
info("Test menu panel browserAction popup");
|
||||
|
||||
let widget = getBrowserActionWidget(extension);
|
||||
CustomizableUI.addWidgetToArea(widget.id, getCustomizableUIPanelID());
|
||||
|
||||
clickBrowserAction(extension);
|
||||
let browser = await awaitExtensionPanel(extension);
|
||||
await testPanel(browser, false, testCase.initial_background);
|
||||
await closeBrowserAction(extension);
|
||||
}
|
||||
|
||||
{
|
||||
info("Test pageAction popup");
|
||||
|
||||
clickPageAction(extension);
|
||||
let browser = await awaitExtensionPanel(extension);
|
||||
await testPanel(browser, true, testCase.initial_background);
|
||||
await closePageAction(extension);
|
||||
}
|
||||
|
||||
await extension.unload();
|
||||
}
|
||||
|
||||
{
|
||||
info("Test menu panel browserAction popup");
|
||||
|
||||
let widget = getBrowserActionWidget(extension);
|
||||
CustomizableUI.addWidgetToArea(widget.id, getCustomizableUIPanelID());
|
||||
|
||||
clickBrowserAction(extension);
|
||||
let browser = await awaitExtensionPanel(extension);
|
||||
await testPanel(browser, false);
|
||||
await closeBrowserAction(extension);
|
||||
}
|
||||
|
||||
{
|
||||
info("Test pageAction popup");
|
||||
|
||||
clickPageAction(extension);
|
||||
let browser = await awaitExtensionPanel(extension);
|
||||
await testPanel(browser, true);
|
||||
await closePageAction(extension);
|
||||
}
|
||||
|
||||
await extension.unload();
|
||||
});
|
||||
|
|
|
@ -710,9 +710,9 @@ BrowserGlue.prototype = {
|
|||
iconURL: "resource:///chrome/browser/content/browser/defaultthemes/dark.icon.svg",
|
||||
textcolor: "white",
|
||||
accentcolor: "black",
|
||||
popup: "hsl(240, 5%, 5%)",
|
||||
popup_text: "rgb(249, 249, 250)",
|
||||
popup_border: "rgba(24, 26, 27, 0.14)",
|
||||
popup: "#4a4a4f",
|
||||
popup_text: "rgba(249, 249, 250, 0.8)",
|
||||
popup_border: "#27272b",
|
||||
author: vendorShortName,
|
||||
});
|
||||
|
||||
|
|
|
@ -9,6 +9,11 @@
|
|||
min-height: 20em;
|
||||
}
|
||||
|
||||
/* Show selected items in high contrast mode. */
|
||||
#sitesList > richlistitem[selected] {
|
||||
outline: 1px solid transparent;
|
||||
}
|
||||
|
||||
#sitesList > richlistitem > hbox,
|
||||
.item-box > label {
|
||||
-moz-box-flex: 1;
|
||||
|
|
|
@ -705,15 +705,6 @@ DevTools.prototype = {
|
|||
getToolboxes() {
|
||||
return Array.from(this._toolboxes.values());
|
||||
},
|
||||
|
||||
/**
|
||||
* Iterator that yields each of the toolboxes.
|
||||
*/
|
||||
* [Symbol.iterator ]() {
|
||||
for (let toolbox of this._toolboxes) {
|
||||
yield toolbox;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const gDevTools = exports.gDevTools = new DevTools();
|
||||
|
|
|
@ -93,11 +93,6 @@ this.gDevTools = {
|
|||
get _tools() {
|
||||
return devtools._tools;
|
||||
},
|
||||
* [Symbol.iterator ]() {
|
||||
for (let toolbox of this._toolboxes) {
|
||||
yield toolbox;
|
||||
}
|
||||
}
|
||||
};
|
||||
gDevToolsMethods.forEach(name => {
|
||||
this.gDevTools[name] = (...args) => {
|
||||
|
|
|
@ -143,7 +143,7 @@ exports.viewSourceInScratchpad = async function(sourceURL, sourceLine) {
|
|||
}
|
||||
|
||||
// For scratchpads within toolbox
|
||||
for (let [, toolbox] of gDevTools) {
|
||||
for (let toolbox of gDevTools.getToolboxes()) {
|
||||
let scratchpadPanel = toolbox.getPanel("scratchpad");
|
||||
if (scratchpadPanel) {
|
||||
let { scratchpad } = scratchpadPanel;
|
||||
|
|
|
@ -4288,6 +4288,16 @@ SourceListener::SetEnabledFor(TrackID aTrackID, bool aEnable)
|
|||
aTrackID == kAudioTrack ? "audio" : "video",
|
||||
aTrackID));
|
||||
|
||||
if (mRemoved) {
|
||||
// Listener was removed between timer resolving and this runnable.
|
||||
return DeviceOperationPromise::CreateAndResolve(NS_ERROR_ABORT, __func__);
|
||||
}
|
||||
|
||||
if (state.mStopped) {
|
||||
// Source was stopped between timer resolving and this runnable.
|
||||
return DeviceOperationPromise::CreateAndResolve(NS_ERROR_ABORT, __func__);
|
||||
}
|
||||
|
||||
state.mDeviceEnabled = aEnable;
|
||||
|
||||
if (mWindowListener) {
|
||||
|
|
|
@ -77,21 +77,15 @@ struct LayersId {
|
|||
return !(*this == aOther);
|
||||
}
|
||||
|
||||
// Helper operators that allow this class to be used as a key in
|
||||
// Helper struct that allow this class to be used as a key in
|
||||
// std::unordered_map like so:
|
||||
// std::unordered_map<LayersId, ValueType, LayersId::HashFn, LayersId::EqualFn> myMap;
|
||||
// std::unordered_map<LayersId, ValueType, LayersId::HashFn> myMap;
|
||||
struct HashFn {
|
||||
std::size_t operator()(const LayersId& aKey) const
|
||||
{
|
||||
return std::hash<uint64_t>{}(aKey.mId);
|
||||
}
|
||||
};
|
||||
struct EqualFn {
|
||||
bool operator()(const LayersId& lhs, const LayersId& rhs) const
|
||||
{
|
||||
return lhs.mId == rhs.mId;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
enum class LayersBackend : int8_t {
|
||||
|
|
|
@ -779,8 +779,7 @@ private:
|
|||
// protected by the mTestDataLock.
|
||||
std::unordered_map<LayersId,
|
||||
UniquePtr<APZTestData>,
|
||||
LayersId::HashFn,
|
||||
LayersId::EqualFn> mTestData;
|
||||
LayersId::HashFn> mTestData;
|
||||
mutable mozilla::Mutex mTestDataLock;
|
||||
|
||||
// This must only be touched on the controller thread.
|
||||
|
|
|
@ -144,8 +144,7 @@ private:
|
|||
// The set of focus targets received indexed by their layer tree ID
|
||||
std::unordered_map<LayersId,
|
||||
FocusTarget,
|
||||
LayersId::HashFn,
|
||||
LayersId::EqualFn> mFocusTree;
|
||||
LayersId::HashFn> mFocusTree;
|
||||
|
||||
// The focus sequence number of the last potentially focus changing event
|
||||
// processed by APZ. This number starts at one and increases monotonically.
|
||||
|
|
|
@ -121,6 +121,9 @@ public class VideoCaptureAndroid implements PreviewCallback, Callback {
|
|||
@Override public void run() {
|
||||
boolean startResult =
|
||||
startCaptureOnCameraThread(width, height, min_mfps, max_mfps);
|
||||
if (!startResult) {
|
||||
Looper.myLooper().quit();
|
||||
}
|
||||
exchange(result, startResult);
|
||||
}
|
||||
});
|
||||
|
@ -316,6 +319,7 @@ public class VideoCaptureAndroid implements PreviewCallback, Callback {
|
|||
cameraThreadHandler.post(new Runnable() {
|
||||
@Override public void run() {
|
||||
boolean stopResult = stopCaptureOnCameraThread();
|
||||
Looper.myLooper().quit();
|
||||
exchange(result, stopResult);
|
||||
}
|
||||
});
|
||||
|
@ -348,7 +352,6 @@ public class VideoCaptureAndroid implements PreviewCallback, Callback {
|
|||
}
|
||||
camera.release();
|
||||
camera = null;
|
||||
Looper.myLooper().quit();
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
error = e;
|
||||
|
@ -356,7 +359,6 @@ public class VideoCaptureAndroid implements PreviewCallback, Callback {
|
|||
error = e;
|
||||
}
|
||||
Log.e(TAG, "Failed to stop camera", error);
|
||||
Looper.myLooper().quit();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -116,7 +116,10 @@ int32_t VideoCaptureAndroid::OnIncomingFrame(uint8_t* videoFrame,
|
|||
size_t videoFrameLength,
|
||||
int32_t degrees,
|
||||
int64_t captureTime) {
|
||||
if (!_captureStarted)
|
||||
// _captureStarted is written on the controlling thread in
|
||||
// StartCapture/StopCapture. This is the camera thread.
|
||||
// CaptureStarted() will access it under a lock.
|
||||
if (!CaptureStarted())
|
||||
return 0;
|
||||
|
||||
VideoRotation current_rotation =
|
||||
|
@ -185,7 +188,7 @@ VideoCaptureAndroid::~VideoCaptureAndroid() {
|
|||
|
||||
int32_t VideoCaptureAndroid::StartCapture(
|
||||
const VideoCaptureCapability& capability) {
|
||||
CriticalSectionScoped cs(&_apiCs);
|
||||
_apiCs.Enter();
|
||||
AttachThreadScoped ats(g_jvm_capture);
|
||||
JNIEnv* env = ats.env();
|
||||
|
||||
|
@ -194,23 +197,32 @@ int32_t VideoCaptureAndroid::StartCapture(
|
|||
WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, -1,
|
||||
"%s: GetBestMatchedCapability failed: %dx%d",
|
||||
__FUNCTION__, capability.width, capability.height);
|
||||
// Manual exit of critical section
|
||||
_apiCs.Leave();
|
||||
return -1;
|
||||
}
|
||||
|
||||
_captureDelay = _captureCapability.expectedCaptureDelay;
|
||||
|
||||
jmethodID j_start =
|
||||
env->GetMethodID(g_java_capturer_class, "startCapture", "(IIII)Z");
|
||||
assert(j_start);
|
||||
int width = _captureCapability.width;
|
||||
int height = _captureCapability.height;
|
||||
int min_mfps = 0;
|
||||
int max_mfps = 0;
|
||||
_deviceInfo.GetMFpsRange(_deviceUniqueId, _captureCapability.maxFPS,
|
||||
&min_mfps, &max_mfps);
|
||||
|
||||
// Exit critical section to avoid blocking camera thread inside
|
||||
// onIncomingFrame() call.
|
||||
_apiCs.Leave();
|
||||
|
||||
jmethodID j_start =
|
||||
env->GetMethodID(g_java_capturer_class, "startCapture", "(IIII)Z");
|
||||
assert(j_start);
|
||||
bool started = env->CallBooleanMethod(_jCapturer, j_start,
|
||||
_captureCapability.width,
|
||||
_captureCapability.height,
|
||||
width, height,
|
||||
min_mfps, max_mfps);
|
||||
if (started) {
|
||||
CriticalSectionScoped cs(&_apiCs);
|
||||
_requestedCapability = capability;
|
||||
_captureStarted = true;
|
||||
}
|
||||
|
|
|
@ -30,11 +30,11 @@ class GeckoViewSelectionActionContent extends GeckoViewContentModule {
|
|||
|
||||
this._actions = [{
|
||||
id: "org.mozilla.geckoview.CUT",
|
||||
predicate: e => !e.collapsed && e.selectionEditable,
|
||||
predicate: e => !e.collapsed && e.selectionEditable && !this._isPasswordField(e),
|
||||
perform: _ => this._domWindowUtils.sendContentCommandEvent("cut"),
|
||||
}, {
|
||||
id: "org.mozilla.geckoview.COPY",
|
||||
predicate: e => !e.collapsed,
|
||||
predicate: e => !e.collapsed && !this._isPasswordField(e),
|
||||
perform: _ => this._domWindowUtils.sendContentCommandEvent("copy"),
|
||||
}, {
|
||||
id: "org.mozilla.geckoview.PASTE",
|
||||
|
@ -70,6 +70,18 @@ class GeckoViewSelectionActionContent extends GeckoViewContentModule {
|
|||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
}
|
||||
|
||||
_isPasswordField(aEvent) {
|
||||
if (!aEvent.selectionEditable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const win = aEvent.target.defaultView;
|
||||
const focus = aEvent.target.activeElement;
|
||||
return win && win.HTMLInputElement &&
|
||||
focus instanceof win.HTMLInputElement &&
|
||||
!focus.mozIsTextField(/* excludePassword */ true);
|
||||
}
|
||||
|
||||
_getSelectionController(aEvent) {
|
||||
if (aEvent.selectionEditable) {
|
||||
const focus = aEvent.target.activeElement;
|
||||
|
@ -118,12 +130,12 @@ class GeckoViewSelectionActionContent extends GeckoViewContentModule {
|
|||
|
||||
register() {
|
||||
debug("register");
|
||||
addEventListener("mozcaretstatechanged", this);
|
||||
addEventListener("mozcaretstatechanged", this, { mozSystemGroup: true });
|
||||
}
|
||||
|
||||
unregister() {
|
||||
debug("unregister");
|
||||
removeEventListener("mozcaretstatechanged", this);
|
||||
removeEventListener("mozcaretstatechanged", this, { mozSystemGroup: true });
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -136,8 +148,7 @@ class GeckoViewSelectionActionContent extends GeckoViewContentModule {
|
|||
if (this._isActive && !aEvent.caretVisible) {
|
||||
// For mozcaretstatechanged, "visibilitychange" means the caret is hidden.
|
||||
reason = "visibilitychange";
|
||||
} else if (this._isActive &&
|
||||
!aEvent.collapsed &&
|
||||
} else if (!aEvent.collapsed &&
|
||||
!aEvent.selectionVisible) {
|
||||
reason = "invisibleselection";
|
||||
} else if (aEvent.selectionEditable &&
|
||||
|
@ -161,13 +172,15 @@ class GeckoViewSelectionActionContent extends GeckoViewContentModule {
|
|||
action => action.predicate.call(this, aEvent));
|
||||
|
||||
const offset = this._getFrameOffset(aEvent);
|
||||
const password = this._isPasswordField(aEvent);
|
||||
|
||||
const msg = {
|
||||
type: "GeckoView:ShowSelectionAction",
|
||||
seqNo: this._seqNo,
|
||||
collapsed: aEvent.collapsed,
|
||||
editable: aEvent.selectionEditable,
|
||||
selection: aEvent.selectedTextContent,
|
||||
password,
|
||||
selection: password ? "" : aEvent.selectedTextContent,
|
||||
clientRect: !aEvent.boundingClientRect ? null : {
|
||||
left: aEvent.boundingClientRect.left + offset.left,
|
||||
top: aEvent.boundingClientRect.top + offset.top,
|
||||
|
|
|
@ -156,14 +156,18 @@ this.browserAction = class extends ExtensionAPI {
|
|||
|
||||
return {
|
||||
browserAction: {
|
||||
onClicked: new EventManager(context, "browserAction.onClicked", fire => {
|
||||
let listener = (event, tab) => {
|
||||
fire.async(tabManager.convert(tab));
|
||||
};
|
||||
browserActionMap.get(extension).on("click", listener);
|
||||
return () => {
|
||||
browserActionMap.get(extension).off("click", listener);
|
||||
};
|
||||
onClicked: new EventManager({
|
||||
context,
|
||||
name: "browserAction.onClicked",
|
||||
register: fire => {
|
||||
let listener = (event, tab) => {
|
||||
fire.async(tabManager.convert(tab));
|
||||
};
|
||||
browserActionMap.get(extension).on("click", listener);
|
||||
return () => {
|
||||
browserActionMap.get(extension).off("click", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
setTitle: function(details) {
|
||||
|
|
|
@ -232,14 +232,18 @@ this.pageAction = class extends ExtensionAPI {
|
|||
|
||||
return {
|
||||
pageAction: {
|
||||
onClicked: new EventManager(context, "pageAction.onClicked", fire => {
|
||||
let listener = (event, tab) => {
|
||||
fire.async(tabManager.convert(tab));
|
||||
};
|
||||
pageActionMap.get(extension).on("click", listener);
|
||||
return () => {
|
||||
pageActionMap.get(extension).off("click", listener);
|
||||
};
|
||||
onClicked: new EventManager({
|
||||
context,
|
||||
name: "pageAction.onClicked",
|
||||
register: fire => {
|
||||
let listener = (event, tab) => {
|
||||
fire.async(tabManager.convert(tab));
|
||||
};
|
||||
pageActionMap.get(extension).on("click", listener);
|
||||
return () => {
|
||||
pageActionMap.get(extension).off("click", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
show(tabId) {
|
||||
|
|
|
@ -103,21 +103,25 @@ this.tabs = class extends ExtensionAPI {
|
|||
|
||||
let self = {
|
||||
tabs: {
|
||||
onActivated: new GlobalEventManager(context, "tabs.onActivated", "Tab:Selected", (fire, data) => {
|
||||
onActivated: makeGlobalEvent(context, "tabs.onActivated", "Tab:Selected", (fire, data) => {
|
||||
let tab = tabManager.get(data.id);
|
||||
|
||||
fire.async({tabId: tab.id, windowId: tab.windowId});
|
||||
}).api(),
|
||||
}),
|
||||
|
||||
onCreated: new EventManager(context, "tabs.onCreated", fire => {
|
||||
let listener = (eventName, event) => {
|
||||
fire.async(tabManager.convert(event.nativeTab));
|
||||
};
|
||||
onCreated: new EventManager({
|
||||
context,
|
||||
name: "tabs.onCreated",
|
||||
register: fire => {
|
||||
let listener = (eventName, event) => {
|
||||
fire.async(tabManager.convert(event.nativeTab));
|
||||
};
|
||||
|
||||
tabTracker.on("tab-created", listener);
|
||||
return () => {
|
||||
tabTracker.off("tab-created", listener);
|
||||
};
|
||||
tabTracker.on("tab-created", listener);
|
||||
return () => {
|
||||
tabTracker.off("tab-created", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
/**
|
||||
|
@ -126,114 +130,130 @@ this.tabs = class extends ExtensionAPI {
|
|||
* the tabId in an array to match the API.
|
||||
* @see https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/Tabs/onHighlighted
|
||||
*/
|
||||
onHighlighted: new GlobalEventManager(context, "tabs.onHighlighted", "Tab:Selected", (fire, data) => {
|
||||
onHighlighted: makeGlobalEvent(context, "tabs.onHighlighted", "Tab:Selected", (fire, data) => {
|
||||
let tab = tabManager.get(data.id);
|
||||
|
||||
fire.async({tabIds: [tab.id], windowId: tab.windowId});
|
||||
}),
|
||||
|
||||
onAttached: new EventManager({
|
||||
context,
|
||||
name: "tabs.onAttached",
|
||||
register: fire => { return () => {}; },
|
||||
}).api(),
|
||||
|
||||
onAttached: new EventManager(context, "tabs.onAttached", fire => {
|
||||
return () => {};
|
||||
onDetached: new EventManager({
|
||||
context,
|
||||
name: "tabs.onDetached",
|
||||
register: fire => { return () => {}; },
|
||||
}).api(),
|
||||
|
||||
onDetached: new EventManager(context, "tabs.onDetached", fire => {
|
||||
return () => {};
|
||||
onRemoved: new EventManager({
|
||||
context,
|
||||
name: "tabs.onRemoved",
|
||||
register: fire => {
|
||||
let listener = (eventName, event) => {
|
||||
fire.async(event.tabId, {windowId: event.windowId, isWindowClosing: event.isWindowClosing});
|
||||
};
|
||||
|
||||
tabTracker.on("tab-removed", listener);
|
||||
return () => {
|
||||
tabTracker.off("tab-removed", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onRemoved: new EventManager(context, "tabs.onRemoved", fire => {
|
||||
let listener = (eventName, event) => {
|
||||
fire.async(event.tabId, {windowId: event.windowId, isWindowClosing: event.isWindowClosing});
|
||||
};
|
||||
|
||||
tabTracker.on("tab-removed", listener);
|
||||
return () => {
|
||||
tabTracker.off("tab-removed", listener);
|
||||
};
|
||||
onReplaced: new EventManager({
|
||||
context,
|
||||
name: "tabs.onReplaced",
|
||||
register: fire => { return () => {}; },
|
||||
}).api(),
|
||||
|
||||
onReplaced: new EventManager(context, "tabs.onReplaced", fire => {
|
||||
return () => {};
|
||||
onMoved: new EventManager({
|
||||
context,
|
||||
name: "tabs.onMoved",
|
||||
register: fire => { return () => {}; },
|
||||
}).api(),
|
||||
|
||||
onMoved: new EventManager(context, "tabs.onMoved", fire => {
|
||||
return () => {};
|
||||
}).api(),
|
||||
onUpdated: new EventManager({
|
||||
context,
|
||||
name: "tabs.onUpdated",
|
||||
register: fire => {
|
||||
const restricted = ["url", "favIconUrl", "title"];
|
||||
|
||||
onUpdated: new EventManager(context, "tabs.onUpdated", fire => {
|
||||
const restricted = ["url", "favIconUrl", "title"];
|
||||
|
||||
function sanitize(extension, changeInfo) {
|
||||
let result = {};
|
||||
let nonempty = false;
|
||||
for (let prop in changeInfo) {
|
||||
if (extension.hasPermission("tabs") || !restricted.includes(prop)) {
|
||||
nonempty = true;
|
||||
result[prop] = changeInfo[prop];
|
||||
function sanitize(extension, changeInfo) {
|
||||
let result = {};
|
||||
let nonempty = false;
|
||||
for (let prop in changeInfo) {
|
||||
if (extension.hasPermission("tabs") || !restricted.includes(prop)) {
|
||||
nonempty = true;
|
||||
result[prop] = changeInfo[prop];
|
||||
}
|
||||
}
|
||||
return [nonempty, result];
|
||||
}
|
||||
return [nonempty, result];
|
||||
}
|
||||
|
||||
let fireForTab = (tab, changed) => {
|
||||
let [needed, changeInfo] = sanitize(extension, changed);
|
||||
if (needed) {
|
||||
fire.async(tab.id, changeInfo, tab.convert());
|
||||
}
|
||||
};
|
||||
let fireForTab = (tab, changed) => {
|
||||
let [needed, changeInfo] = sanitize(extension, changed);
|
||||
if (needed) {
|
||||
fire.async(tab.id, changeInfo, tab.convert());
|
||||
}
|
||||
};
|
||||
|
||||
let listener = event => {
|
||||
let needed = [];
|
||||
let nativeTab;
|
||||
switch (event.type) {
|
||||
case "DOMTitleChanged": {
|
||||
let {BrowserApp} = getBrowserWindow(event.target.ownerGlobal);
|
||||
let listener = event => {
|
||||
let needed = [];
|
||||
let nativeTab;
|
||||
switch (event.type) {
|
||||
case "DOMTitleChanged": {
|
||||
let {BrowserApp} = getBrowserWindow(event.target.ownerGlobal);
|
||||
|
||||
nativeTab = BrowserApp.getTabForWindow(event.target.ownerGlobal);
|
||||
needed.push("title");
|
||||
break;
|
||||
nativeTab = BrowserApp.getTabForWindow(event.target.ownerGlobal);
|
||||
needed.push("title");
|
||||
break;
|
||||
}
|
||||
|
||||
case "DOMAudioPlaybackStarted":
|
||||
case "DOMAudioPlaybackStopped": {
|
||||
let {BrowserApp} = event.target.ownerGlobal;
|
||||
nativeTab = BrowserApp.getTabForBrowser(event.originalTarget);
|
||||
needed.push("audible");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
case "DOMAudioPlaybackStarted":
|
||||
case "DOMAudioPlaybackStopped": {
|
||||
let {BrowserApp} = event.target.ownerGlobal;
|
||||
nativeTab = BrowserApp.getTabForBrowser(event.originalTarget);
|
||||
needed.push("audible");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!nativeTab) {
|
||||
return;
|
||||
}
|
||||
|
||||
let tab = tabManager.getWrapper(nativeTab);
|
||||
let changeInfo = {};
|
||||
for (let prop of needed) {
|
||||
changeInfo[prop] = tab[prop];
|
||||
}
|
||||
|
||||
fireForTab(tab, changeInfo);
|
||||
};
|
||||
|
||||
let statusListener = ({browser, status, url}) => {
|
||||
let {BrowserApp} = browser.ownerGlobal;
|
||||
let nativeTab = BrowserApp.getTabForBrowser(browser);
|
||||
if (nativeTab) {
|
||||
let changed = {status};
|
||||
if (url) {
|
||||
changed.url = url;
|
||||
if (!nativeTab) {
|
||||
return;
|
||||
}
|
||||
|
||||
fireForTab(tabManager.wrapTab(nativeTab), changed);
|
||||
}
|
||||
};
|
||||
let tab = tabManager.getWrapper(nativeTab);
|
||||
let changeInfo = {};
|
||||
for (let prop of needed) {
|
||||
changeInfo[prop] = tab[prop];
|
||||
}
|
||||
|
||||
windowTracker.addListener("status", statusListener);
|
||||
windowTracker.addListener("DOMTitleChanged", listener);
|
||||
return () => {
|
||||
windowTracker.removeListener("status", statusListener);
|
||||
windowTracker.removeListener("DOMTitleChanged", listener);
|
||||
};
|
||||
fireForTab(tab, changeInfo);
|
||||
};
|
||||
|
||||
let statusListener = ({browser, status, url}) => {
|
||||
let {BrowserApp} = browser.ownerGlobal;
|
||||
let nativeTab = BrowserApp.getTabForBrowser(browser);
|
||||
if (nativeTab) {
|
||||
let changed = {status};
|
||||
if (url) {
|
||||
changed.url = url;
|
||||
}
|
||||
|
||||
fireForTab(tabManager.wrapTab(nativeTab), changed);
|
||||
}
|
||||
};
|
||||
|
||||
windowTracker.addListener("status", statusListener);
|
||||
windowTracker.addListener("DOMTitleChanged", listener);
|
||||
return () => {
|
||||
windowTracker.removeListener("status", statusListener);
|
||||
windowTracker.removeListener("DOMTitleChanged", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
async create(createProperties) {
|
||||
|
|
|
@ -187,11 +187,11 @@ class WindowTracker extends WindowTrackerBase {
|
|||
}
|
||||
|
||||
/**
|
||||
* An event manager API provider which listens for an event in the Android
|
||||
* global EventDispatcher, and calls the given listener function whenever an event
|
||||
* is received. That listener function receives a `fire` object, which it can
|
||||
* use to dispatch events to the extension, and an object detailing the
|
||||
* EventDispatcher event that was received.
|
||||
* Helper to create an event manager which listens for an event in the Android
|
||||
* global EventDispatcher, and calls the given listener function whenever the
|
||||
* event is received. That listener function receives a `fire` object,
|
||||
* which it can use to dispatch events to the extension, and an object
|
||||
* detailing the EventDispatcher event that was received.
|
||||
*
|
||||
* @param {BaseContext} context
|
||||
* The extension context which the event manager belongs to.
|
||||
|
@ -202,10 +202,14 @@ class WindowTracker extends WindowTrackerBase {
|
|||
* @param {function} listener
|
||||
* The listener function to call when an EventDispatcher event is
|
||||
* recieved.
|
||||
*
|
||||
* @returns {object} An injectable api for the new event.
|
||||
*/
|
||||
global.GlobalEventManager = class extends EventManager {
|
||||
constructor(context, name, event, listener) {
|
||||
super(context, name, fire => {
|
||||
global.makeGlobalEvent = function makeGlobalEvent(context, name, event, listener) {
|
||||
return new EventManager({
|
||||
context,
|
||||
name,
|
||||
register: fire => {
|
||||
let listener2 = {
|
||||
onEvent(event, data, callback) {
|
||||
listener(fire, data);
|
||||
|
@ -216,36 +220,8 @@ global.GlobalEventManager = class extends EventManager {
|
|||
return () => {
|
||||
GlobalEventDispatcher.unregisterListener(listener2, [event]);
|
||||
};
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* An event manager API provider which listens for a DOM event in any browser
|
||||
* window, and calls the given listener function whenever an event is received.
|
||||
* That listener function receives a `fire` object, which it can use to dispatch
|
||||
* events to the extension, and a DOM event object.
|
||||
*
|
||||
* @param {BaseContext} context
|
||||
* The extension context which the event manager belongs to.
|
||||
* @param {string} name
|
||||
* The API name of the event manager, e.g.,"runtime.onMessage".
|
||||
* @param {string} event
|
||||
* The name of the DOM event to listen for.
|
||||
* @param {function} listener
|
||||
* The listener function to call when a DOM event is received.
|
||||
*/
|
||||
global.WindowEventManager = class extends EventManager {
|
||||
constructor(context, name, event, listener) {
|
||||
super(context, name, fire => {
|
||||
let listener2 = listener.bind(null, fire);
|
||||
|
||||
windowTracker.addListener(event, listener2);
|
||||
return () => {
|
||||
windowTracker.removeListener(event, listener2);
|
||||
};
|
||||
});
|
||||
}
|
||||
},
|
||||
}).api();
|
||||
};
|
||||
|
||||
class TabTracker extends TabTrackerBase {
|
||||
|
|
|
@ -44,6 +44,8 @@ open class BaseSessionTest(noErrorCollector: Boolean = false) {
|
|||
}
|
||||
}
|
||||
|
||||
fun <T> forEachCall(vararg values: T): T = sessionRule.forEachCall(*values)
|
||||
|
||||
fun GeckoSession.getTestBytes(path: String) =
|
||||
InstrumentationRegistry.getTargetContext().resources.assets
|
||||
.open(path.removePrefix("/assets/")).readBytes()
|
||||
|
|
|
@ -22,12 +22,11 @@ class ContentDelegateTest : BaseSessionTest() {
|
|||
@Test fun titleChange() {
|
||||
sessionRule.session.loadTestPath(TITLE_CHANGE_HTML_PATH)
|
||||
|
||||
val titles = mutableListOf("Title1", "Title2")
|
||||
sessionRule.waitUntilCalled(object : Callbacks.ContentDelegate {
|
||||
@AssertCalled(count = 2)
|
||||
override fun onTitleChange(session: GeckoSession, title: String) {
|
||||
assertThat("Title should match", title,
|
||||
equalTo(titles.removeAt(0)))
|
||||
equalTo(forEachCall("Title1", "Title2")))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -245,7 +245,7 @@ class GeckoSessionTestRuleTest : BaseSessionTest(noErrorCollector = true) {
|
|||
val info = sessionRule.currentCall
|
||||
assertThat("Method info should be valid", info, notNullValue())
|
||||
assertThat("Counter should be correct",
|
||||
info.counter, isOneOf(1, 2))
|
||||
info.counter, equalTo(forEachCall(1, 2)))
|
||||
assertThat("Order should equal counter",
|
||||
info.order, equalTo(info.counter))
|
||||
counter++
|
||||
|
|
|
@ -94,8 +94,7 @@ class ProgressDelegateTest : BaseSessionTest() {
|
|||
@AssertCalled(count = 2, order = intArrayOf(1, 3))
|
||||
override fun onPageStart(session: GeckoSession, url: String) {
|
||||
assertThat("URL should match", url,
|
||||
endsWith(if (sessionRule.currentCall.counter == 1)
|
||||
INVALID_URI else HELLO_HTML_PATH))
|
||||
endsWith(forEachCall(INVALID_URI, HELLO_HTML_PATH)))
|
||||
}
|
||||
|
||||
@AssertCalled(count = 2, order = intArrayOf(2, 4))
|
||||
|
@ -103,7 +102,7 @@ class ProgressDelegateTest : BaseSessionTest() {
|
|||
// The first load is certain to fail because of interruption by the second load
|
||||
// or by invalid domain name, whereas the second load is certain to succeed.
|
||||
assertThat("Success flag should match", success,
|
||||
equalTo(sessionRule.currentCall.counter != 1))
|
||||
equalTo(forEachCall(false, true)))
|
||||
};
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1267,8 +1267,9 @@ public class GeckoSessionTestRule extends UiThreadTestRule {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get information about the current call. Only valid during a {@link #forCallbacksDuringWait}
|
||||
* callback.
|
||||
* Get information about the current call. Only valid during a {@link
|
||||
* #forCallbacksDuringWait}, {@link #delegateDuringNextWait}, or {@link
|
||||
* #delegateUntilTestEnd} callback.
|
||||
*
|
||||
* @return Call information
|
||||
*/
|
||||
|
@ -1412,4 +1413,22 @@ public class GeckoSessionTestRule extends UiThreadTestRule {
|
|||
public GeckoSession createClosedSession(final GeckoSessionSettings settings) {
|
||||
return createSession(settings, /* open */ false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a value from the given array indexed by the current call counter. Only valid
|
||||
* during a {@link #forCallbacksDuringWait}, {@link #delegateDuringNextWait}, or
|
||||
* {@link #delegateUntilTestEnd} callback.
|
||||
* <p><p>
|
||||
* Asserts that {@code foo} is equal to {@code "bar"} during the first call and {@code
|
||||
* "baz"} during the second call:
|
||||
* <pre>{@code assertThat("Foo should match", foo, equalTo(forEachCall("bar",
|
||||
* "baz")));}</pre>
|
||||
*
|
||||
* @param values Input array
|
||||
* @return Value from input array indexed by the current call counter.
|
||||
*/
|
||||
public <T> T forEachCall(T... values) {
|
||||
assertThat("Should be in a method call", mCurrentMethodCall, notNullValue());
|
||||
return values[Math.min(mCurrentMethodCall.getCurrentCount(), values.length) - 1];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -133,6 +133,9 @@ public class BasicSelectionActionDelegate implements ActionMode.Callback,
|
|||
* @return True if the action was performed.
|
||||
*/
|
||||
protected boolean performAction(final String id) {
|
||||
if (mResponse == null) {
|
||||
return false;
|
||||
}
|
||||
mResponse.respond(id);
|
||||
|
||||
// Android behavior is to clear selection on copy.
|
||||
|
|
|
@ -1784,6 +1784,10 @@ public class GeckoSession extends LayerSession
|
|||
* contentEditable node.
|
||||
*/
|
||||
final int FLAG_IS_EDITABLE = 2;
|
||||
/**
|
||||
* The selection is inside a password field.
|
||||
*/
|
||||
final int FLAG_IS_PASSWORD = 4;
|
||||
|
||||
@StringDef({ACTION_CUT,
|
||||
ACTION_COPY,
|
||||
|
@ -1859,7 +1863,9 @@ public class GeckoSession extends LayerSession
|
|||
flags = (bundle.getBoolean("collapsed") ?
|
||||
SelectionActionDelegate.FLAG_IS_COLLAPSED : 0) |
|
||||
(bundle.getBoolean("editable") ?
|
||||
SelectionActionDelegate.FLAG_IS_EDITABLE : 0);
|
||||
SelectionActionDelegate.FLAG_IS_EDITABLE : 0) |
|
||||
(bundle.getBoolean("password") ?
|
||||
SelectionActionDelegate.FLAG_IS_PASSWORD : 0);
|
||||
text = bundle.getString("selection");
|
||||
|
||||
final GeckoBundle rectBundle = bundle.getBundle("clientRect");
|
||||
|
|
|
@ -50,6 +50,8 @@ var ActionBarHandler = {
|
|||
* (mozcaretstatechanged) events.
|
||||
*/
|
||||
handleEvent: function(e) {
|
||||
e.stopImmediatePropagation();
|
||||
|
||||
// Close an open ActionBar, if carets no longer logically visible.
|
||||
if (this._selectionID && !e.caretVisible) {
|
||||
this._uninit(false);
|
||||
|
|
|
@ -1439,7 +1439,7 @@ pref("privacy.resistFingerprinting.autoDeclineNoUserInputCanvasPrompts", true);
|
|||
// File.lastModified, audioContext.currentTime, canvas.captureStream.currentTime
|
||||
pref("privacy.reduceTimerPrecision", true);
|
||||
// Dynamically tune the resolution of the timer reduction for both of the two above prefs
|
||||
pref("privacy.resistFingerprinting.reduceTimerPrecision.microseconds", 100);
|
||||
pref("privacy.resistFingerprinting.reduceTimerPrecision.microseconds", 1000);
|
||||
// Enable jittering the clock one precision value forward
|
||||
pref("privacy.resistFingerprinting.reduceTimerPrecision.jitter", true);
|
||||
// Lower the priority of network loads for resources on the tracking protection list.
|
||||
|
@ -5051,6 +5051,8 @@ pref("extensions.webextensions.protocol.remote", true);
|
|||
// Disable tab hiding API by default.
|
||||
pref("extensions.webextensions.tabhide.enabled", false);
|
||||
|
||||
pref("extensions.webextensions.background-delayed-startup", false);
|
||||
|
||||
// Report Site Issue button
|
||||
pref("extensions.webcompat-reporter.newIssueEndpoint", "https://webcompat.com/issues/new");
|
||||
#if defined(MOZ_DEV_EDITION) || defined(NIGHTLY_BUILD)
|
||||
|
|
|
@ -436,6 +436,38 @@ static uint32_t get32bit(unsigned char *aData, int index)
|
|||
(aData[index+2] << 8) | aData[index+3];
|
||||
}
|
||||
|
||||
nsresult
|
||||
TRR::PassQName(unsigned int &index)
|
||||
{
|
||||
uint8_t length;
|
||||
do {
|
||||
if (mBodySize < (index + 1)) {
|
||||
LOG(("TRR: PassQName:%d fail at index %d\n", __LINE__, index));
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
length = static_cast<uint8_t>(mResponse[index]);
|
||||
if ((length & 0xc0) == 0xc0) {
|
||||
// name pointer, advance over it and be done
|
||||
if (mBodySize < (index + 2)) {
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
index += 2;
|
||||
break;
|
||||
}
|
||||
if (length & 0xc0) {
|
||||
LOG(("TRR: illegal label length byte (%x) at index %d\n", length, index));
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
// pass label
|
||||
if (mBodySize < (index + 1 + length)) {
|
||||
LOG(("TRR: PassQName:%d fail at index %d\n", __LINE__, index));
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
index += 1 + length;
|
||||
} while (length);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
//
|
||||
// DohDecode() collects the TTL and the IP addresses in the response
|
||||
//
|
||||
|
@ -458,6 +490,7 @@ TRR::DohDecode()
|
|||
unsigned int index = 12;
|
||||
uint8_t length;
|
||||
nsAutoCString host;
|
||||
nsresult rv;
|
||||
|
||||
LOG(("doh decode %s %d bytes\n", mHost.get(), mBodySize));
|
||||
|
||||
|
@ -506,34 +539,9 @@ TRR::DohDecode()
|
|||
answerRecords, mBodySize, host.get(), index));
|
||||
|
||||
while (answerRecords) {
|
||||
if (mBodySize < (index + 1)) {
|
||||
LOG(("TRR: Dohdecode:%d fail at index %d\n", __LINE__, index));
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
length = static_cast<uint8_t>(mResponse[index]);
|
||||
if ((length & 0xc0) == 0xc0) {
|
||||
// name pointer, advance over it
|
||||
if (mBodySize < (index + 2)) {
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
index += 2;
|
||||
} else if (length & 0xc0) {
|
||||
// illegal length, bail out
|
||||
LOG(("TRR: illegal label length byte (%x)\n", length));
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
} else {
|
||||
// iterate over host name in answer
|
||||
do {
|
||||
if (mBodySize < (index + 1)) {
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
length = static_cast<uint8_t>(mResponse[index]);
|
||||
if (mBodySize < (index + 1 + length)) {
|
||||
LOG(("TRR: Dohdecode:%d fail at index %d\n", __LINE__, index));
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
index += 1 + length;
|
||||
} while (length);
|
||||
rv = PassQName(index);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
// 16 bit TYPE
|
||||
if (mBodySize < (index + 2)) {
|
||||
|
@ -590,7 +598,6 @@ TRR::DohDecode()
|
|||
// - AAAA (TYPE 28): 16 bytes
|
||||
// - NS (TYPE 2): N bytes
|
||||
|
||||
nsresult rv;
|
||||
switch(TYPE) {
|
||||
case TRRTYPE_A:
|
||||
if (RDLENGTH != 4) {
|
||||
|
@ -686,33 +693,9 @@ TRR::DohDecode()
|
|||
uint16_t nsRecords = get16bit(mResponse, 8);
|
||||
LOG(("TRR Decode: %d ns records (%u bytes body)\n", nsRecords, mBodySize));
|
||||
while (nsRecords) {
|
||||
if (mBodySize < (index + 1)) {
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
length = static_cast<uint8_t>(mResponse[index]);
|
||||
if ((length & 0xc0) == 0xc0) {
|
||||
// name pointer, advance over it
|
||||
if (mBodySize < (index + 2)) {
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
index += 2;
|
||||
} else if (length & 0xc0) {
|
||||
// illegal length, bail out
|
||||
LOG(("TRR: illegal label length byte (%x)\n", length));
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
} else {
|
||||
// iterate over host name in answer
|
||||
do {
|
||||
if (mBodySize < (index + 1)) {
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
length = static_cast<uint8_t>(mResponse[index]);
|
||||
if (mBodySize < (index + 1 + length)) {
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
index += 1 + length;
|
||||
LOG(("TRR: move over %d bytes\n", 1 + length));
|
||||
} while (length);
|
||||
rv = PassQName(index);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
if (mBodySize < (index + 8)) {
|
||||
|
@ -741,33 +724,9 @@ TRR::DohDecode()
|
|||
LOG(("TRR Decode: %d additional resource records (%u bytes body)\n",
|
||||
arRecords, mBodySize));
|
||||
while (arRecords) {
|
||||
if (mBodySize < (index + 1)) {
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
length = static_cast<uint8_t>(mResponse[index]);
|
||||
if ((length & 0xc0) == 0xc0) {
|
||||
// name pointer, advance over it
|
||||
if (mBodySize < (index + 2)) {
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
index += 2;
|
||||
} else if (length & 0xc0) {
|
||||
// illegal length, bail out
|
||||
LOG(("TRR: illegal label length byte (%x)\n", length));
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
} else {
|
||||
// iterate over host name in answer
|
||||
do {
|
||||
if (mBodySize < (index + 1)) {
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
length = static_cast<uint8_t>(mResponse[index]);
|
||||
if (mBodySize < (index + 1 + length)) {
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
index += 1 + length;
|
||||
LOG(("TRR: move over %d bytes\n", 1 + length));
|
||||
} while (length);
|
||||
rv = PassQName(index);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
if (mBodySize < (index + 8)) {
|
||||
|
|
|
@ -142,6 +142,7 @@ private:
|
|||
~TRR() = default;
|
||||
nsresult SendHTTPRequest();
|
||||
nsresult DohEncode(nsCString &target);
|
||||
nsresult PassQName(unsigned int &index);
|
||||
nsresult DohDecode();
|
||||
nsresult ReturnData();
|
||||
nsresult FailData();
|
||||
|
|
|
@ -240,11 +240,12 @@ class TupOnly(CommonBackend, PartialBackend):
|
|||
for l in libs]
|
||||
|
||||
def _gen_shared_library(self, backend_file):
|
||||
if backend_file.shared_lib.name == 'libxul.so':
|
||||
shlib = backend_file.shared_lib
|
||||
if shlib.name == 'libxul.so':
|
||||
# This will fail to link currently due to missing rust symbols.
|
||||
return
|
||||
|
||||
if backend_file.shared_lib.cxx_link:
|
||||
if shlib.cxx_link:
|
||||
mkshlib = (
|
||||
[backend_file.environment.substs['CXX']] +
|
||||
backend_file.local_flags['CXX_LDFLAGS']
|
||||
|
@ -258,15 +259,15 @@ class TupOnly(CommonBackend, PartialBackend):
|
|||
mkshlib += (
|
||||
backend_file.environment.substs['DSO_PIC_CFLAGS'] +
|
||||
[backend_file.environment.substs['DSO_LDOPTS']] +
|
||||
['-Wl,-h,%s' % backend_file.shared_lib.soname] +
|
||||
['-o', backend_file.shared_lib.lib_name]
|
||||
['-Wl,-h,%s' % shlib.soname] +
|
||||
['-o', shlib.lib_name]
|
||||
)
|
||||
|
||||
objs, _, shared_libs, os_libs, static_libs = self._expand_libs(backend_file.shared_lib)
|
||||
objs, _, shared_libs, os_libs, static_libs = self._expand_libs(shlib)
|
||||
static_libs = self._lib_paths(backend_file.objdir, static_libs)
|
||||
shared_libs = self._lib_paths(backend_file.objdir, shared_libs)
|
||||
|
||||
list_file_name = '%s.list' % backend_file.shared_lib.name.replace('.', '_')
|
||||
list_file_name = '%s.list' % shlib.name.replace('.', '_')
|
||||
list_file = self._make_list_file(backend_file.objdir, objs, list_file_name)
|
||||
|
||||
inputs = objs + static_libs + shared_libs
|
||||
|
@ -275,10 +276,10 @@ class TupOnly(CommonBackend, PartialBackend):
|
|||
return
|
||||
|
||||
symbols_file = []
|
||||
if backend_file.shared_lib.symbols_file:
|
||||
inputs.append(backend_file.shared_lib.symbols_file)
|
||||
if shlib.symbols_file:
|
||||
inputs.append(shlib.symbols_file)
|
||||
# TODO: Assumes GNU LD
|
||||
symbols_file = ['-Wl,--version-script,%s' % backend_file.shared_lib.symbols_file]
|
||||
symbols_file = ['-Wl,--version-script,%s' % shlib.symbols_file]
|
||||
|
||||
cmd = (
|
||||
mkshlib +
|
||||
|
@ -293,9 +294,14 @@ class TupOnly(CommonBackend, PartialBackend):
|
|||
backend_file.rule(
|
||||
cmd=cmd,
|
||||
inputs=inputs,
|
||||
outputs=[backend_file.shared_lib.lib_name],
|
||||
outputs=[shlib.lib_name],
|
||||
display='LINK %o'
|
||||
)
|
||||
backend_file.symlink_rule(mozpath.join(backend_file.objdir,
|
||||
shlib.lib_name),
|
||||
output=mozpath.join(self.environment.topobjdir,
|
||||
shlib.install_target,
|
||||
shlib.lib_name))
|
||||
|
||||
|
||||
def _gen_program(self, backend_file):
|
||||
|
|
|
@ -1163,4 +1163,4 @@ static const TransportSecurityPreload kPublicKeyPinningPreloadList[] = {
|
|||
|
||||
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>
|
||||
const PRTime gPreloadListExpirationTime = INT64_C(1534105987786000);
|
||||
const PRTime gPreloadListExpirationTime = INT64_C(1534192657699000);
|
||||
%%
|
||||
0-1.party, 1
|
||||
0.me.uk, 1
|
||||
|
@ -1250,6 +1250,7 @@ ac-town.com, 1
|
|||
ac0g.dyndns.org, 1
|
||||
academicexperts.us, 1
|
||||
academie-de-police.ch, 1
|
||||
academy4.net, 1
|
||||
academytv.com.au, 1
|
||||
acaeum.com, 1
|
||||
acampar.com.br, 1
|
||||
|
@ -1958,7 +1959,6 @@ alextsang.net, 1
|
|||
alexvdveen.nl, 1
|
||||
alexvetter.de, 1
|
||||
alexwardweb.com, 1
|
||||
alexyang.me, 1
|
||||
alfa-tech.su, 1
|
||||
alfaperfumes.com.br, 1
|
||||
alfaponny.se, 1
|
||||
|
@ -2241,6 +2241,7 @@ american.dating, 1
|
|||
americandistribuidora.com, 1
|
||||
americanfoundationbr.com, 1
|
||||
americanmediainstitute.com, 1
|
||||
americanoutlawjeepparts.com, 1
|
||||
americasbasementcontractor.com, 1
|
||||
americkykongres.cz, 1
|
||||
amerigroup.com, 1
|
||||
|
@ -3764,7 +3765,6 @@ baldur.cc, 1
|
|||
balenciaspa.com, 1
|
||||
balia.de, 1
|
||||
balicekzdravi.cz, 1
|
||||
balidesignshop.com.br, 1
|
||||
balikonos.cz, 1
|
||||
balinese.dating, 1
|
||||
balist.es, 1
|
||||
|
@ -5129,7 +5129,6 @@ bondskampeerder.nl, 1
|
|||
bondtofte.dk, 1
|
||||
bonesserver.com, 1
|
||||
bonfi.net, 1
|
||||
bonibuty.com, 1
|
||||
bonifacius.be, 1
|
||||
bonita.com.br, 1
|
||||
bonnant-associes.ch, 1
|
||||
|
@ -6345,7 +6344,6 @@ casinocashflow.ru, 1
|
|||
casinolistings.com, 1
|
||||
casinoonlinesicuri.com, 1
|
||||
casinoreal.com, 1
|
||||
casjay.cloud, 1
|
||||
casjay.com, 1
|
||||
casjay.info, 1
|
||||
casjaygames.com, 1
|
||||
|
@ -6520,6 +6518,7 @@ centos.pub, 1
|
|||
centos.tips, 1
|
||||
central4.me, 1
|
||||
centralbank.ae, 1
|
||||
centralcountiesservices.org, 1
|
||||
centralebigmat.eu, 1
|
||||
centralegedimat.eu, 1
|
||||
centralfor.me, 1
|
||||
|
@ -7673,6 +7672,7 @@ common.io, 1
|
|||
commoncode.com.au, 1
|
||||
commoncode.io, 1
|
||||
commoncore4kids.com, 1
|
||||
community-cupboard.org, 1
|
||||
communityblog.fedoraproject.org, 1
|
||||
communitycodeofconduct.com, 1
|
||||
communityflow.info, 1
|
||||
|
@ -7896,6 +7896,7 @@ coolpickz.com, 1
|
|||
coolprylar.se, 1
|
||||
coolrc.me, 1
|
||||
coolviewthermostat.com, 1
|
||||
coolvox.com, 1
|
||||
coopens.com, 1
|
||||
coor.fun, 1
|
||||
coore.jp, 1
|
||||
|
@ -7988,7 +7989,6 @@ cosni.co, 1
|
|||
cosplayer.com, 1
|
||||
cospol.ch, 1
|
||||
costa-rica-reisen.ch, 1
|
||||
costa-rica-reisen.de, 1
|
||||
costablancavoorjou.com, 1
|
||||
costcofinance.com, 1
|
||||
costinstefan.eu, 1
|
||||
|
@ -8787,6 +8787,7 @@ darkside.re, 1
|
|||
darktime.ru, 1
|
||||
darkwater.info, 1
|
||||
darkx.me, 1
|
||||
darlastudio66.com, 1
|
||||
darlo.co.uk, 0
|
||||
darom.jp, 1
|
||||
darookee.net, 1
|
||||
|
@ -10081,6 +10082,7 @@ doublestat.me, 1
|
|||
doubleup.com.au, 1
|
||||
doucheba.gs, 0
|
||||
dougferris.id.au, 1
|
||||
douglasstafford.com, 1
|
||||
doujin-domain.cz, 1
|
||||
doujin.nagoya, 1
|
||||
doujinshi.info, 1
|
||||
|
@ -10403,6 +10405,7 @@ dustygroove.com, 1
|
|||
dustyspokesbnb.ca, 1
|
||||
dutch.desi, 1
|
||||
dutch1.nl, 1
|
||||
dutchessuganda.com, 1
|
||||
dutchrank.nl, 1
|
||||
dutchwanderers.nl, 1
|
||||
dutchweballiance.nl, 1
|
||||
|
@ -10636,6 +10639,7 @@ echopaper.com, 1
|
|||
echosim.io, 1
|
||||
echosixmonkey.com, 1
|
||||
echosystem.fr, 1
|
||||
echoteam.gq, 1
|
||||
echoteen.com, 1
|
||||
echoworld.ch, 1
|
||||
ecirtam.net, 1
|
||||
|
@ -10652,6 +10656,7 @@ ecodedi.com, 1
|
|||
ecodigital.social, 1
|
||||
ecogen.com.au, 1
|
||||
ecogen.net.au, 1
|
||||
ecoheatcool.co.uk, 1
|
||||
ecohostingservices.uk, 1
|
||||
ecolala.my, 1
|
||||
ecole-attalens.ch, 1
|
||||
|
@ -10680,6 +10685,7 @@ ecovision.com.br, 1
|
|||
ecpannualmeeting.com, 1
|
||||
ecrandouble.ch, 1
|
||||
ectora.com, 1
|
||||
ecupcafe.com, 0
|
||||
ed.gs, 1
|
||||
ed4becky.net, 1
|
||||
edakoe.ru, 1
|
||||
|
@ -11338,30 +11344,7 @@ epmcentroitalia.it, 1
|
|||
epo32.ru, 1
|
||||
epoch.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
|
||||
eposwales.co.uk, 1
|
||||
eposyork.co.uk, 1
|
||||
eppelblei.lu, 1
|
||||
eppelduerferjugend.lu, 1
|
||||
eppelpress.lu, 1
|
||||
|
@ -11383,7 +11366,6 @@ equinox.io, 1
|
|||
equipandoloja.net.br, 1
|
||||
equipedefrance.tv, 1
|
||||
equipeferramentas.com.br, 1
|
||||
equippers.de, 1
|
||||
equipsupply.com, 1
|
||||
er-music.com, 1
|
||||
er.tl, 1
|
||||
|
@ -11687,6 +11669,7 @@ estcequejailaflemme.fr, 1
|
|||
estcequonmetenprodaujourdhui.info, 1
|
||||
esteam.se, 1
|
||||
estedafah.com, 1
|
||||
estespr.com, 1
|
||||
esteticanorte.com.br, 1
|
||||
estetista.net, 1
|
||||
estilopack-loja.com.br, 1
|
||||
|
@ -12047,6 +12030,7 @@ extreme.co.th, 1
|
|||
extrememanual.net, 1
|
||||
exvs.org, 1
|
||||
exyplis.com, 1
|
||||
eyasc.nl, 1
|
||||
eydesignguidelines.com, 1
|
||||
eyecandy.gr, 1
|
||||
eyeglasses.com, 0
|
||||
|
@ -12307,7 +12291,6 @@ fassadenverkleidung24.de, 1
|
|||
fassi-sport.it, 1
|
||||
fastaim.de, 1
|
||||
fastbackmbg.be, 1
|
||||
fastbackmbm.be, 1
|
||||
fastcash.com.br, 1
|
||||
fastcommerce.org, 1
|
||||
fastconfirm.com, 1
|
||||
|
@ -12784,6 +12767,7 @@ flexinvesting.fi, 1
|
|||
flexport.com, 1
|
||||
flexstart.me, 1
|
||||
flextrack.dk, 1
|
||||
flextribly.xyz, 1
|
||||
fliacuello.com.ar, 1
|
||||
flightdeckfriend.com, 1
|
||||
flightmedx.com, 1
|
||||
|
@ -12843,7 +12827,6 @@ floth.at, 1
|
|||
flow.su, 1
|
||||
flowcom.de, 1
|
||||
flowcount.xyz, 1
|
||||
flowerandplant.org, 1
|
||||
flowersbylegacy.com, 1
|
||||
flowinvoice.com, 1
|
||||
flowreader.com, 1
|
||||
|
@ -13043,8 +13026,6 @@ forty8creates.com, 1
|
|||
fortytwo.cloud, 1
|
||||
forum-bonn.de, 1
|
||||
forum-heg.ch, 1
|
||||
forum-kinozal-tv.appspot.com, 1
|
||||
forum-kinozal.appspot.com, 1
|
||||
forum.linode.com, 0
|
||||
forum3.ru, 1
|
||||
forumvoordemocratie.nl, 1
|
||||
|
@ -13681,7 +13662,6 @@ gamerpoets.com, 1
|
|||
gamerz-stream.com, 1
|
||||
gamerzdot.com, 1
|
||||
games4theworld.org, 1
|
||||
gameserver-sponsor.me, 1
|
||||
gameshowchallenge.ie, 1
|
||||
gamesplanet.com, 1
|
||||
gamesputnik.ru, 1
|
||||
|
@ -14126,6 +14106,7 @@ giftedconsortium.com, 1
|
|||
giftking.nl, 0
|
||||
giftmaniabrilhos.com.br, 1
|
||||
gifts365.co.uk, 1
|
||||
giftservices.nl, 1
|
||||
giftsn.com.sg, 0
|
||||
gifzilla.net, 0
|
||||
gig-raiffeisen.de, 1
|
||||
|
@ -14216,7 +14197,7 @@ gjspunk.de, 0
|
|||
gjung.com, 0
|
||||
gkimanyar.org, 1
|
||||
gkralik.eu, 1
|
||||
gkvsc.de, 1
|
||||
gkvsc.de, 0
|
||||
gl.search.yahoo.com, 0
|
||||
glabiatoren-kst.de, 1
|
||||
glaciernursery.com, 1
|
||||
|
@ -14324,7 +14305,6 @@ gnetwork.eu, 1
|
|||
gnhub.org, 1
|
||||
gnilebein.de, 1
|
||||
gnom.me, 1
|
||||
gnosticjade.net, 1
|
||||
gnucashtoqif.us, 1
|
||||
gnunet.org, 1
|
||||
gnwp.eu, 1
|
||||
|
@ -14667,7 +14647,6 @@ grexx.de, 1
|
|||
grey.house, 1
|
||||
greybit.net, 1
|
||||
greyhash.se, 1
|
||||
greysky.me, 1
|
||||
greyskymedia.com, 1
|
||||
greysolutions.it, 1
|
||||
greywizard.com, 1
|
||||
|
@ -15027,7 +15006,6 @@ halkirkbouncycastles.co.uk, 1
|
|||
halkyon.net, 1
|
||||
hallelujahsoftware.com, 1
|
||||
halletienne.fr, 1
|
||||
hallettxn.com, 1
|
||||
hallhuber.com, 1
|
||||
halliday.work, 1
|
||||
halligladen.de, 1
|
||||
|
@ -15543,7 +15521,6 @@ heyfringe.com, 1
|
|||
heyjournal.com, 1
|
||||
hf-tekst.nl, 1
|
||||
hf51.nl, 1
|
||||
hfbg.nl, 1
|
||||
hfi.me, 0
|
||||
hfu.io, 1
|
||||
hg.python.org, 1
|
||||
|
@ -15796,6 +15773,7 @@ holidaysportugal.eu, 1
|
|||
holisticacupuncture.com.au, 1
|
||||
holistichealer.in, 1
|
||||
holisticon.de, 1
|
||||
hollandguns.com, 1
|
||||
hollermann.eu, 1
|
||||
hollo.me, 1
|
||||
hollowpoint.xyz, 1
|
||||
|
@ -15858,6 +15836,7 @@ hommeatoutfaire.be, 1
|
|||
homoglyph.net, 1
|
||||
homophoni.com, 1
|
||||
hompus.nl, 0
|
||||
homyremedies.com, 1
|
||||
honda-centrum.cz, 1
|
||||
hondart.cz, 1
|
||||
hondenoppasfraneker.nl, 1
|
||||
|
@ -16202,6 +16181,7 @@ hustle.life, 1
|
|||
hustlehope.com, 1
|
||||
hustunique.com, 1
|
||||
huto.ml, 1
|
||||
huutonauru.net, 1
|
||||
huwcbjones.co.uk, 1
|
||||
huwcbjones.uk, 1
|
||||
huwjones.me, 1
|
||||
|
@ -16496,7 +16476,6 @@ ig.com, 1
|
|||
iga-semi.jp, 1
|
||||
igamingforums.com, 1
|
||||
igcc.jp, 1
|
||||
igd.chat, 1
|
||||
igglabs.com, 1
|
||||
iggprivate.com, 1
|
||||
iggsoft.com, 1
|
||||
|
@ -16758,6 +16737,7 @@ indianaberry.com, 1
|
|||
indianaffairs.gov, 0
|
||||
indiawise.co.uk, 1
|
||||
indicateurs-flash.fr, 1
|
||||
indieethos.com, 1
|
||||
indiegame.space, 1
|
||||
indievelopment.nl, 1
|
||||
indigoinflatables.com, 1
|
||||
|
@ -16804,7 +16784,6 @@ infinitiofmarinparts.com, 1
|
|||
infinity.to, 1
|
||||
infinitybas.com, 1
|
||||
infinityengine.org, 1
|
||||
infinityepos.co.uk, 1
|
||||
infirmiere-canadienne.com, 1
|
||||
infirmieredevie.ch, 1
|
||||
inflatablehire-scotland.co.uk, 1
|
||||
|
@ -17302,6 +17281,7 @@ islam.si, 1
|
|||
islandhosting.com, 1
|
||||
islandinthenet.com, 1
|
||||
islandoilsupply.com, 1
|
||||
islandpumpandtank.com, 1
|
||||
islazia.fr, 1
|
||||
isletech.net, 1
|
||||
isliada.org, 1
|
||||
|
@ -17556,6 +17536,7 @@ jaberg-rutschi.ch, 1
|
|||
jabergrutschi.ch, 1
|
||||
jability.ovh, 1
|
||||
jabjab.de, 1
|
||||
jaccblog.com, 1
|
||||
jacekowski.org, 1
|
||||
jackdawphoto.co.uk, 1
|
||||
jackdelik.de, 1
|
||||
|
@ -18364,7 +18345,6 @@ jurassicgolf.nl, 1
|
|||
juridiqueo.com, 1
|
||||
juridoc.com.br, 1
|
||||
jurijbuga.de, 1
|
||||
jurisprudent.by, 1
|
||||
juristeo.com, 1
|
||||
jurko.cz, 1
|
||||
jurriaan.ninja, 1
|
||||
|
@ -18776,6 +18756,7 @@ kenny-peck.com, 1
|
|||
kennynet.co.uk, 1
|
||||
keno.im, 1
|
||||
kenokallinger.at, 1
|
||||
kenoschwalb.com, 1
|
||||
kenrogers.co, 0
|
||||
kens.pics, 1
|
||||
kensbouncycastles.co.uk, 1
|
||||
|
@ -19417,7 +19398,7 @@ kropkait.pl, 1
|
|||
krouzkyliduska.cz, 1
|
||||
krsn.de, 1
|
||||
krugermillions.org, 1
|
||||
krugoval.hr, 1
|
||||
krugoval.hr, 0
|
||||
kruin.net, 1
|
||||
kruisselbrink.com, 1
|
||||
kruk.co, 1
|
||||
|
@ -21124,7 +21105,6 @@ mabulledu.net, 1
|
|||
mac-i-tea.ch, 1
|
||||
mac-world.pl, 1
|
||||
mac1.net, 1
|
||||
macandtonic.com, 1
|
||||
macaque.io, 0
|
||||
macaw.nl, 1
|
||||
macaws.org, 1
|
||||
|
@ -21775,7 +21755,6 @@ mattli.us, 1
|
|||
mattmccutchen.net, 1
|
||||
mattmcshane.com, 1
|
||||
mattonline.me, 1
|
||||
mattwb65.com, 1
|
||||
mattwservices.co.uk, 1
|
||||
matviet.vn, 1
|
||||
matze.co, 1
|
||||
|
@ -21796,6 +21775,7 @@ mawidaca.com, 1
|
|||
max-moeglich.de, 1
|
||||
max-went.pl, 1
|
||||
max.gov, 1
|
||||
maxbeenen.de, 1
|
||||
maxbruckner.de, 1
|
||||
maxbruckner.org, 1
|
||||
maxbytes.nl, 0
|
||||
|
@ -22016,6 +21996,7 @@ medicinskavranje.edu.rs, 1
|
|||
medicocompetente.it, 1
|
||||
medicoresponde.com.br, 1
|
||||
medienweite.de, 1
|
||||
medifab.online, 1
|
||||
medifi.com, 1
|
||||
medigap-quote.net, 1
|
||||
medinside.ch, 1
|
||||
|
@ -22115,7 +22096,6 @@ meisterritter.de, 1
|
|||
meizufans.eu, 1
|
||||
meklon.net, 1
|
||||
mekongeye.com, 1
|
||||
melakaltenegger.at, 1
|
||||
melaniebernhardt.com, 1
|
||||
melaniegruber.de, 1
|
||||
melbourne.dating, 1
|
||||
|
@ -22547,6 +22527,7 @@ minesouls.fr, 1
|
|||
minetude.com, 1
|
||||
minez-nightswatch.com, 0
|
||||
minf3-games.de, 1
|
||||
mingming.info, 1
|
||||
mingram.net, 1
|
||||
mingwah.ch, 1
|
||||
mingy.ddns.net, 1
|
||||
|
@ -22604,7 +22585,6 @@ mirkofranz.de, 1
|
|||
mirodasilva.be, 1
|
||||
mironet.cz, 1
|
||||
mirrorsedgearchive.de, 1
|
||||
mirrorsedgearchive.ga, 1
|
||||
mirshak.com, 1
|
||||
mirtes.cz, 1
|
||||
mirtouf.fr, 1
|
||||
|
@ -23148,6 +23128,7 @@ mrkapowski.com, 1
|
|||
mrketolocksmith.com, 1
|
||||
mrknee.gr, 1
|
||||
mrksk.com, 1
|
||||
mrleonardo.com, 1
|
||||
mrliu.me, 1
|
||||
mrmoregame.de, 1
|
||||
mrnh.de, 1
|
||||
|
@ -23927,6 +23908,7 @@ ncconsumer.org, 1
|
|||
ncdesigns-studio.com, 1
|
||||
ncea.net.au, 1
|
||||
nchangfong.com, 1
|
||||
nchristo.com, 1
|
||||
nclvle.co.uk, 1
|
||||
ncrmnt.org, 1
|
||||
ncsccs.com, 1
|
||||
|
@ -24121,6 +24103,7 @@ netraising.com, 1
|
|||
netrelay.email, 1
|
||||
netrider.net.au, 0
|
||||
netronix.be, 1
|
||||
netsafeid.biz, 1
|
||||
netscaler.expert, 1
|
||||
netsight.org, 1
|
||||
netsigna.de, 1
|
||||
|
@ -24212,7 +24195,6 @@ newizv.ru, 1
|
|||
newjianzhi.com, 1
|
||||
newkaliningrad.ru, 1
|
||||
newknd.com, 1
|
||||
newline.online, 1
|
||||
newmarketbouncycastlehire.co.uk, 1
|
||||
newmed.com.br, 1
|
||||
newmediaone.net, 1
|
||||
|
@ -26146,6 +26128,7 @@ penetrationstest.se, 1
|
|||
penfold.fr, 1
|
||||
pengi.me, 1
|
||||
pengisatelier.net, 1
|
||||
pengui.uk, 1
|
||||
penguinprotocols.com, 1
|
||||
pengumuman.id, 1
|
||||
penispumpen.se, 1
|
||||
|
@ -26254,6 +26237,7 @@ peterhuetz.com, 1
|
|||
peterjohnson.io, 1
|
||||
peterlew.is, 1
|
||||
petersontoscano.com, 1
|
||||
pethelpers.org, 1
|
||||
petit-archer.com, 1
|
||||
petite-maison.ch, 1
|
||||
petitsfrenchies.com, 1
|
||||
|
@ -26772,7 +26756,6 @@ plumlocosoft.com, 1
|
|||
plumnet.ch, 1
|
||||
plumpie.net, 0
|
||||
plur.com.au, 1
|
||||
plural.cafe, 1
|
||||
plus-5.com, 1
|
||||
plus.google.com, 1
|
||||
plus.sandbox.google.com, 1
|
||||
|
@ -27006,7 +26989,6 @@ portalkla.com.br, 1
|
|||
portalmundo.xyz, 1
|
||||
portalzine.de, 1
|
||||
porte.roma.it, 1
|
||||
portefeuillesignalen.nl, 1
|
||||
portercup.com, 1
|
||||
porterranchelectrical.com, 1
|
||||
portofacil.com, 1
|
||||
|
@ -27022,7 +27004,6 @@ portvaletickets.com, 1
|
|||
porybox.com, 1
|
||||
porzgmbh.de, 1
|
||||
posaunenchor-senden.de, 1
|
||||
posbank.co.uk, 1
|
||||
poseidonwaterproofing.com, 1
|
||||
poshcastles.co.uk, 1
|
||||
poshlashes.se, 1
|
||||
|
@ -27081,6 +27062,7 @@ pouet.it, 1
|
|||
pouets.ovh, 1
|
||||
poupatempo.org, 1
|
||||
pourlesenfants.info, 1
|
||||
pourout.org, 1
|
||||
povareschka.ru, 1
|
||||
povesham.tk, 1
|
||||
powaclub.com, 1
|
||||
|
@ -27800,6 +27782,7 @@ quanwuji.com, 1
|
|||
quanyin.eu.org, 1
|
||||
quareal.ru, 1
|
||||
quarkdose.de, 1
|
||||
quarryhillrentals.com, 1
|
||||
quarterfull.com, 1
|
||||
quartix.com, 1
|
||||
quartzclinical.com, 1
|
||||
|
@ -28214,7 +28197,7 @@ recetasfacilesdehacer.com, 1
|
|||
rechenknaecht.de, 1
|
||||
rechenwerk.net, 1
|
||||
recht-freundlich.de, 1
|
||||
rechtenliteratuurleiden.nl, 1
|
||||
rechtenliteratuurleiden.nl, 0
|
||||
rechtsanwaeltin-vollmer.de, 1
|
||||
rechtsanwalt-koeppen-feucht.de, 1
|
||||
rechtschreibpruefung24.de, 1
|
||||
|
@ -28294,6 +28277,7 @@ redneck-gaming.de, 1
|
|||
redneragenturen.org, 1
|
||||
rednoseday.com, 1
|
||||
rednsx.org, 1
|
||||
redperegrine.com, 1
|
||||
redporno.cz, 1
|
||||
redprice.by, 1
|
||||
redshield.co, 1
|
||||
|
@ -28469,6 +28453,7 @@ rentbrowser.com, 1
|
|||
rentinsingapore.com.sg, 1
|
||||
rentourhomeinprovence.com, 1
|
||||
renuo.ch, 1
|
||||
renyiyou.com, 1
|
||||
reorz.com, 1
|
||||
reox.at, 0
|
||||
repaik.com, 1
|
||||
|
@ -28802,7 +28787,7 @@ roave.com, 1
|
|||
rob.uk.com, 1
|
||||
rob006.net, 1
|
||||
robandjanine.com, 1
|
||||
robbertt.com, 1
|
||||
robbertt.com, 0
|
||||
robdavidson.network, 1
|
||||
robert-flynn.de, 1
|
||||
robertabittle.com, 1
|
||||
|
@ -28815,7 +28800,6 @@ robertlysik.com, 1
|
|||
robertnemec.com, 1
|
||||
roberto-webhosting.nl, 1
|
||||
robertocasares.no-ip.biz, 1
|
||||
robertoentringer.com, 1
|
||||
robertof.ovh, 1
|
||||
robertreiser.photography, 1
|
||||
robertrijnders.nl, 1
|
||||
|
@ -28873,7 +28857,6 @@ rockymountainspice.com, 1
|
|||
rocssti.net, 1
|
||||
rodafe.sk, 1
|
||||
rodarion.pl, 1
|
||||
roddis.net, 1
|
||||
rodehutskors.net, 1
|
||||
rodeobull.biz, 1
|
||||
rodeohire.com, 1
|
||||
|
@ -28922,6 +28905,7 @@ rohitagr.com, 1
|
|||
rointe.online, 1
|
||||
roiscroll.com, 1
|
||||
roka9.de, 1
|
||||
roketix.co.uk, 1
|
||||
rokki.ch, 1
|
||||
rokort.dk, 1
|
||||
roksolana.be, 1
|
||||
|
@ -29021,7 +29005,6 @@ rosewoodranch.com, 1
|
|||
rosi-royal.com, 1
|
||||
roslynpad.net, 1
|
||||
rospa100.com, 1
|
||||
rossclark.com, 1
|
||||
rosset.me, 1
|
||||
rosset.net, 1
|
||||
rosslug.org.uk, 1
|
||||
|
@ -29407,7 +29390,6 @@ sallysubs.com, 1
|
|||
salmo23.com.br, 1
|
||||
salmododia.net, 1
|
||||
salmonella.co.uk, 1
|
||||
salmonrecovery.gov, 1
|
||||
salmonvision.com.tw, 0
|
||||
salmos91.com, 1
|
||||
salmotierra-salvatierra.com, 1
|
||||
|
@ -29544,6 +29526,7 @@ sapac.es, 1
|
|||
sapereaude.com.pl, 1
|
||||
sapien-ci.com, 1
|
||||
sapience.com, 1
|
||||
sapk.fr, 1
|
||||
saposute-s.jp, 1
|
||||
sapphireblue.me, 1
|
||||
sapphirepearl.com.sg, 1
|
||||
|
@ -30125,6 +30108,7 @@ selectel.ru, 1
|
|||
selectorders.com, 1
|
||||
selegiline.com, 1
|
||||
selent.me, 1
|
||||
seleondar.ru, 1
|
||||
self-evident.org, 1
|
||||
self-signed.com, 1
|
||||
self-xss.info, 1
|
||||
|
@ -30204,6 +30188,7 @@ seocomposer.com, 1
|
|||
seoexperte.berlin, 1
|
||||
seogeek.nl, 1
|
||||
seohochschule.de, 1
|
||||
seoinc.com, 1
|
||||
seoium.com, 1
|
||||
seokay.com, 1
|
||||
seolib.org, 1
|
||||
|
@ -30325,7 +30310,6 @@ sevsey.ru, 1
|
|||
sevsopr.ru, 1
|
||||
sewafineseam.com, 1
|
||||
sewinginsight.com, 1
|
||||
sewoo.co.uk, 1
|
||||
sex-education.com, 1
|
||||
sexaki.com, 1
|
||||
sexdocka.nu, 1
|
||||
|
@ -30432,6 +30416,7 @@ shaobin.wang, 1
|
|||
sharanyamunsi.net, 1
|
||||
sharealo.org, 1
|
||||
sharedhost.de, 1
|
||||
shareeri.com, 1
|
||||
sharelovenotsecrets.com, 1
|
||||
sharemessage.net, 1
|
||||
shareoffice.ch, 1
|
||||
|
@ -30462,6 +30447,7 @@ shavegazette.com, 1
|
|||
shavingks.com, 1
|
||||
shawcentral.ca, 0
|
||||
shawnhogan.com, 1
|
||||
shawnstarrcustomhomes.com, 1
|
||||
shawnwilkerson.com, 1
|
||||
shawnwilson.info, 1
|
||||
shazbots.org, 1
|
||||
|
@ -30790,6 +30776,7 @@ silverlinkz.net, 1
|
|||
silverseen.com, 1
|
||||
silverstartup.sk, 1
|
||||
silverwind.io, 1
|
||||
silviamacallister.com, 1
|
||||
silvine.xyz, 1
|
||||
silvistefi.com, 1
|
||||
silvobeat.blog, 1
|
||||
|
@ -31122,6 +31109,7 @@ slotfara.com, 1
|
|||
slotfara.net, 1
|
||||
sloths.org, 1
|
||||
slotlist.info, 1
|
||||
slovakiana.sk, 1
|
||||
slovenskycestovatel.sk, 1
|
||||
slow.zone, 1
|
||||
slowb.ro, 1
|
||||
|
@ -31154,7 +31142,6 @@ smaltimento-rifiuti.org, 1
|
|||
smaltimento.caserta.it, 1
|
||||
smaltimento.napoli.it, 1
|
||||
smaltimentoamianto.latina.it, 1
|
||||
smaltimentorifiuti.veneto.it, 1
|
||||
smares.de, 1
|
||||
smart-cp.jp, 1
|
||||
smart-informatics.com, 1
|
||||
|
@ -31247,6 +31234,7 @@ smutba.se, 1
|
|||
smutek.net, 1
|
||||
smx.net.br, 1
|
||||
snackbesteld.nl, 1
|
||||
snafarms.com, 1
|
||||
snafu.cz, 1
|
||||
snakafya.com, 1
|
||||
snake.dog, 1
|
||||
|
@ -31556,7 +31544,6 @@ sourcebox.be, 1
|
|||
sourcecode.love, 1
|
||||
sourcely.net, 1
|
||||
sourceway.de, 1
|
||||
sourcitec.com, 1
|
||||
souris.ch, 1
|
||||
sous-surveillance.net, 1
|
||||
southafrican.dating, 1
|
||||
|
@ -31730,7 +31717,6 @@ spornkuller.de, 1
|
|||
sport-in-sundern.de, 1
|
||||
sport-potreby.cz, 1
|
||||
sport-potreby.sk, 1
|
||||
sport-socken.net, 1
|
||||
sport247.bet, 1
|
||||
sporter.com, 1
|
||||
sportflash.info, 1
|
||||
|
@ -32127,7 +32113,6 @@ stevenski.com, 0
|
|||
steventress.com, 1
|
||||
steventruesdell.com, 1
|
||||
stevenwooding.com, 1
|
||||
stevenz.science, 1
|
||||
stevesdrivingschooltyneside.com, 1
|
||||
stewartswines.com, 1
|
||||
stewonet.nl, 1
|
||||
|
@ -32190,6 +32175,7 @@ stoianlawfirm.com, 1
|
|||
stolina.de, 0
|
||||
stolkpotplanten.nl, 1
|
||||
stolkschepen.nl, 1
|
||||
stomadental.com, 1
|
||||
stomt.com, 1
|
||||
stonedworms.de, 1
|
||||
stonefusion.org.uk, 1
|
||||
|
@ -32200,7 +32186,6 @@ stonewuu.com, 1
|
|||
stony.com, 1
|
||||
stonystratford.org, 1
|
||||
stopakwardhandshakes.org, 1
|
||||
stopbreakupnow.org, 1
|
||||
stopbullying.gov, 1
|
||||
stopfraud.gov, 1
|
||||
stopthethyroidmadness.com, 1
|
||||
|
@ -32455,7 +32440,6 @@ sundayfundayjapan.com, 1
|
|||
suneilpatel.com, 1
|
||||
sunfeathers.net, 1
|
||||
sunfireshop.com.br, 1
|
||||
sunflyer.cn, 0
|
||||
sunfox.cz, 1
|
||||
sunfulong.blog, 1
|
||||
sunfulong.me, 1
|
||||
|
@ -32962,7 +32946,6 @@ taunhanh.us, 1
|
|||
tavolaquadrada.com.br, 1
|
||||
tavsys.net, 1
|
||||
taxaroo.com, 1
|
||||
taxi-24std.de, 1
|
||||
taxi-chamonix.fr, 1
|
||||
taxi-collectif.ch, 1
|
||||
taxi-puck.pl, 1
|
||||
|
@ -33076,6 +33059,7 @@ tech-essential.com, 1
|
|||
tech-rat.com, 1
|
||||
tech-seminar.jp, 1
|
||||
tech-value.eu, 1
|
||||
tech-zealots.com, 1
|
||||
techace.jp, 1
|
||||
techademy.nl, 1
|
||||
techarea.fr, 1
|
||||
|
@ -33161,6 +33145,7 @@ teemperor.de, 1
|
|||
teemulintula.fi, 1
|
||||
teencounseling.com, 1
|
||||
teenerotic.net, 1
|
||||
teeplelaw.com, 1
|
||||
teesypeesy.com, 1
|
||||
teeworlds-friends.de, 1
|
||||
tefek.cz, 1
|
||||
|
@ -33429,6 +33414,7 @@ thebodyprinciple.com, 1
|
|||
thebouncedepartment.co.uk, 1
|
||||
thebouncyman.co.uk, 1
|
||||
theboxofcarlos.com, 1
|
||||
thebreakhotel.com, 1
|
||||
thebreakroom.org, 1
|
||||
thebte.com, 1
|
||||
thebuffalotavern.com, 1
|
||||
|
@ -33574,6 +33560,7 @@ theobromos.fr, 1
|
|||
theocharis.org, 1
|
||||
theodorahome.co, 1
|
||||
theodorahome.com.br, 1
|
||||
theofleck.com, 1
|
||||
theojones.name, 1
|
||||
theokonst.tk, 1
|
||||
theokouzelis.com, 1
|
||||
|
@ -34100,6 +34087,7 @@ todocracy.com, 1
|
|||
todoescine.com, 1
|
||||
todoist.com, 1
|
||||
todon.fr, 1
|
||||
todoscomciro.com, 1
|
||||
todosrv.com, 1
|
||||
toeglhofer.at, 1
|
||||
toeightycountries.com, 1
|
||||
|
@ -34290,7 +34278,6 @@ toptexture.com, 1
|
|||
toptheto.com, 1
|
||||
topvertimai.lt, 1
|
||||
topwin.la, 1
|
||||
topwindowcleaners.co.uk, 1
|
||||
topworktops.co.uk, 1
|
||||
tor2web.org, 1
|
||||
toracon.org, 1
|
||||
|
@ -34351,7 +34338,6 @@ touch-up-net.com, 1
|
|||
touch.facebook.com, 0
|
||||
touch.mail.ru, 1
|
||||
touchoflife.in, 1
|
||||
touchscreentills.com, 1
|
||||
touchweb.fr, 1
|
||||
touchwoodtrees.com.au, 1
|
||||
tougetu.com, 1
|
||||
|
@ -34949,7 +34935,6 @@ tysye.ca, 1
|
|||
tyuo-keibi.co.jp, 1
|
||||
tzifas.com, 1
|
||||
tzwe.com, 1
|
||||
u-master.net, 1
|
||||
u-metals.com, 1
|
||||
u-tokyo.club, 1
|
||||
u.nu, 1
|
||||
|
@ -34992,6 +34977,7 @@ uc.ac.id, 1
|
|||
ucac.nz, 0
|
||||
ucangiller.com, 1
|
||||
ucch.be, 1
|
||||
ucfirst.nl, 1
|
||||
uchargeapp.com, 1
|
||||
uclanmasterplan.co.uk, 1
|
||||
uclip.club, 1
|
||||
|
@ -35285,7 +35271,6 @@ urbanmelbourne.info, 1
|
|||
urbanmic.com, 1
|
||||
urbannewsservice.com, 1
|
||||
urbansparrow.in, 1
|
||||
urbanstylestaging.com, 1
|
||||
urbanwildlifealliance.org, 0
|
||||
urbexdk.nl, 1
|
||||
urcentral.com, 1
|
||||
|
@ -35954,6 +35939,7 @@ vivamusic.es, 1
|
|||
vivanosports.com.br, 1
|
||||
vivatv.com.tw, 1
|
||||
vivendi.de, 1
|
||||
vivianmaier.cn, 1
|
||||
vivid-academy.com, 1
|
||||
vividinflatables.co.uk, 1
|
||||
vividlumen.com, 1
|
||||
|
@ -36790,7 +36776,6 @@ whisperinghoperanch.org, 1
|
|||
whisperlab.org, 1
|
||||
whistleb.com, 1
|
||||
whistleblower.gov, 1
|
||||
whistler-transfers.com, 1
|
||||
whitby-brewery.com, 1
|
||||
whitealps.at, 1
|
||||
whitealps.be, 1
|
||||
|
@ -37170,7 +37155,6 @@ workwithgo.com, 1
|
|||
world-education-association.org, 1
|
||||
world-in-my-eyes.com, 1
|
||||
worldcareers.dk, 1
|
||||
worldchess.london, 1
|
||||
worldcigars.com.br, 1
|
||||
worldcrafts.org, 1
|
||||
worldcubeassociation.org, 1
|
||||
|
@ -37550,7 +37534,6 @@ xing.ml, 1
|
|||
xingiahanvisa.net, 1
|
||||
xiqi.us, 1
|
||||
xirion.net, 1
|
||||
xiyu.it, 0
|
||||
xiyu.moe, 1
|
||||
xj8876.com, 1
|
||||
xjd.vision, 1
|
||||
|
@ -37876,6 +37859,7 @@ ybscareers.co.uk, 1
|
|||
ybsul.com, 1
|
||||
ybti.net, 1
|
||||
ybzhao.com, 1
|
||||
ycaaz.com, 1
|
||||
ych.art, 1
|
||||
ycherbonnel.fr, 1
|
||||
ychon.com, 1
|
||||
|
@ -38174,7 +38158,7 @@ yumeconcert.com, 1
|
|||
yumli.net, 1
|
||||
yummylooks.com, 1
|
||||
yuna.love, 1
|
||||
yuna.tg, 1
|
||||
yuna.tg, 0
|
||||
yunity.org, 1
|
||||
yunjishou.pro, 1
|
||||
yunzhu.li, 1
|
||||
|
@ -38270,6 +38254,7 @@ zargaripour.com, 1
|
|||
zargescases.co.uk, 1
|
||||
zarmarket.org, 1
|
||||
zarpo.com.br, 1
|
||||
zary.me, 1
|
||||
zatsepin.by, 1
|
||||
zaufanatrzeciastrona.pl, 1
|
||||
zavec.com.ec, 1
|
||||
|
@ -38374,6 +38359,7 @@ zhangfangzhou.com, 1
|
|||
zhangge.net, 1
|
||||
zhanghao.me, 1
|
||||
zhangheda.cf, 1
|
||||
zhangsidan.com, 0
|
||||
zhangsir.net, 1
|
||||
zhangyuhao.com, 1
|
||||
zhaochen.xyz, 1
|
||||
|
|
|
@ -119,14 +119,20 @@ class BaseNavigationTestCase(WindowManagerMixin, MarionetteTestCase):
|
|||
class TestNavigate(BaseNavigationTestCase):
|
||||
|
||||
def test_set_location_through_execute_script(self):
|
||||
test_element_locator = (By.ID, "testh1")
|
||||
|
||||
self.marionette.execute_script(
|
||||
"window.location.href = arguments[0];",
|
||||
script_args=(self.test_page_remote,), sandbox=None)
|
||||
|
||||
# We cannot use get_url() to wait until the target page has been loaded,
|
||||
# because it will return the URL of the top browsing context and doesn't
|
||||
# wait for the page load to be complete.
|
||||
Wait(self.marionette, timeout=self.marionette.timeout.page_load).until(
|
||||
lambda mn: self.test_page_remote == mn.get_url(),
|
||||
message="'{}' hasn't been loaded".format(self.test_page_remote))
|
||||
self.assertEqual("Marionette Test", self.marionette.title)
|
||||
expected.element_present(*test_element_locator),
|
||||
message="Target element 'testh1' has not been found")
|
||||
|
||||
self.assertEqual(self.test_page_remote, self.marionette.get_url())
|
||||
|
||||
def test_navigate_chrome_unsupported_error(self):
|
||||
with self.marionette.using_context("chrome"):
|
||||
|
|
|
@ -88,10 +88,10 @@ StructuredLogger.prototype = {
|
|||
},
|
||||
|
||||
assertionCount(test, count, minExpected = 0, maxExpected = 0) {
|
||||
var data = {test,
|
||||
min_expected: minExpected,
|
||||
max_expected: maxExpected,
|
||||
count};
|
||||
var data = {test: this._testId(test),
|
||||
min_expected: minExpected,
|
||||
max_expected: maxExpected,
|
||||
count};
|
||||
|
||||
this._logData("assertion_count", data);
|
||||
},
|
||||
|
|
|
@ -1,118 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/**
|
||||
* nodejs script to generate: testing/talos/talos/tests/devtools/addon/content/pages/custom/inspector/index.html
|
||||
*
|
||||
* Execute it like this:
|
||||
* $ nodejs generate-inspector-index-html.js > testing/talos/talos/tests/devtools/addon/content/pages/custom/inspector/index.html
|
||||
*/
|
||||
|
||||
// We first create a deep tree with ${deep} nested children
|
||||
let deep = 50;
|
||||
// Then we create ${n} element after the deep tree
|
||||
let n = 50;
|
||||
// Number of attributes set on the repeated elements
|
||||
let attributes = 50;
|
||||
|
||||
// Build the <div> with $attributes data attributes
|
||||
let div = "<div";
|
||||
for (var i = 1; i <= attributes; i++) {
|
||||
div += ` data-a${i}="${i}"`;
|
||||
}
|
||||
div += ">";
|
||||
|
||||
// Build the tree of $deep elements
|
||||
let tree = "";
|
||||
for (i = 1; i <= deep; i++) {
|
||||
tree += new Array(i).join(" ");
|
||||
tree += div + " " + i + "\n";
|
||||
}
|
||||
for (i = deep; i >= 1; i--) {
|
||||
tree += new Array(i).join(" ");
|
||||
tree += "</div>\n";
|
||||
}
|
||||
|
||||
// Build the list of $n elements
|
||||
let repeat = "";
|
||||
for (i = 1; i <= n; i++) {
|
||||
repeat += div + " " + i + " </div>\n";
|
||||
}
|
||||
|
||||
// Prepare CSS rules to add to the document <style>.
|
||||
let CSS_RULES_COUNT = 200;
|
||||
let manyCssRules = "";
|
||||
for (i = 0; i < CSS_RULES_COUNT; i++) {
|
||||
manyCssRules += `
|
||||
.many-css-rules {
|
||||
font-size: ${i}px;
|
||||
margin: 10px;
|
||||
padding: 10px;
|
||||
font-family: Arial;
|
||||
margin: 20px;
|
||||
}`;
|
||||
}
|
||||
let expandManyChildren = new Array(100).join(" <div attr='my-attr'>content</div>\n");
|
||||
|
||||
let maxBalancedDepth = 8;
|
||||
function createBalancedMarkup(level = 0) {
|
||||
let tab = new Array(level + 1).join(" ");
|
||||
if (level < maxBalancedDepth) {
|
||||
let child = createBalancedMarkup(level + 1);
|
||||
return `${tab}<div>
|
||||
${child}
|
||||
${child}
|
||||
${tab}</div>`;
|
||||
} else {
|
||||
return tab + "<div class='leaf'>leaf</div>";
|
||||
}
|
||||
}
|
||||
let expandBalanced = createBalancedMarkup();
|
||||
|
||||
console.log(`
|
||||
<!DOCTYPE html>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
- You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<!-- This file is a generated file, do not edit it directly.
|
||||
- See generate-inspector-html.js for instructions to update this file -->
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Custom page for the Inspector</title>
|
||||
<style>
|
||||
div {
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
/* Styles for custom.inspector.manyrules tests */`);
|
||||
console.log(manyCssRules);
|
||||
console.log(`
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- <div> elements with ${deep} nested childs, all with ${attributes} attributes -->
|
||||
<!-- The deepest <div> has id="deep"> -->
|
||||
`);
|
||||
console.log(tree);
|
||||
console.log(`
|
||||
<!-- ${n} <div> elements without any children -->
|
||||
`);
|
||||
console.log(repeat);
|
||||
console.log(`
|
||||
<!-- Elements for custom.inspector.manyrules tests -->
|
||||
<div class="no-css-rules"></div>
|
||||
<div class="many-css-rules"></div>
|
||||
<div class="expand-many-children">
|
||||
`);
|
||||
console.log(expandManyChildren);
|
||||
console.log(`
|
||||
</div>
|
||||
<div class="expand-balanced">
|
||||
`);
|
||||
console.log(expandBalanced);
|
||||
console.log(`
|
||||
</div>
|
||||
</body>
|
||||
</html>`);
|
||||
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,6 +1,6 @@
|
|||
[table-model-fixup-2.html]
|
||||
disabled:
|
||||
if webrender and (os == "linux"): https://bugzilla.mozilla.org/show_bug.cgi?id=1445164
|
||||
if os == "linux": https://bugzilla.mozilla.org/show_bug.cgi?id=1445164
|
||||
|
||||
[Replaced elements outside a table cannot be table-row and are considered block -- input=text elements]
|
||||
expected: FAIL
|
||||
|
|
|
@ -554,7 +554,7 @@ function handleRequest(req, res) {
|
|||
// asking for cname.example.com
|
||||
var content;
|
||||
// ... this always sends a CNAME back to pointing-elsewhere.example.com. Loop time!
|
||||
content = new Buffer("00000100000100010000000005636E616D65076578616D706C6503636F6D0000050001C00C0005000100000037002012706F696E74696E672D656C73657768657265076578616D706C6503636F6D00", "hex");
|
||||
content = new Buffer("00000100000100010000000005636E616D65076578616D706C6503636F6D0000050001C00C0005000100000037002012706F696E74696E672D656C73657768657265076578616D706C65C01A00", "hex");
|
||||
res.setHeader('Content-Type', 'application/dns-udpwireformat');
|
||||
res.setHeader('Content-Length', content.length);
|
||||
res.writeHead(200);
|
||||
|
|
|
@ -1163,6 +1163,7 @@ class Extension extends ExtensionData {
|
|||
}
|
||||
|
||||
this.addonData = addonData;
|
||||
this.startupData = addonData.startupData || {};
|
||||
this.startupReason = startupReason;
|
||||
|
||||
if (["ADDON_UPGRADE", "ADDON_DOWNGRADE"].includes(startupReason)) {
|
||||
|
@ -1357,6 +1358,10 @@ class Extension extends ExtensionData {
|
|||
this.isPrivileged);
|
||||
}
|
||||
|
||||
saveStartupData() {
|
||||
AddonManagerPrivate.setStartupData(this.id, this.startupData);
|
||||
}
|
||||
|
||||
async _parseManifest() {
|
||||
let manifest = await super.parseManifest();
|
||||
if (manifest && manifest.permissions.has("mozillaAddons") &&
|
||||
|
|
|
@ -179,19 +179,27 @@ class Port {
|
|||
this.postMessage(json);
|
||||
},
|
||||
|
||||
onDisconnect: new EventManager(this.context, "Port.onDisconnect", fire => {
|
||||
return this.registerOnDisconnect(holder => {
|
||||
let error = holder && holder.deserialize(this.context.cloneScope);
|
||||
portError = error && this.context.normalizeError(error);
|
||||
fire.asyncWithoutClone(portObj);
|
||||
});
|
||||
onDisconnect: new EventManager({
|
||||
context: this.context,
|
||||
name: "Port.onDisconnect",
|
||||
register: fire => {
|
||||
return this.registerOnDisconnect(holder => {
|
||||
let error = holder && holder.deserialize(this.context.cloneScope);
|
||||
portError = error && this.context.normalizeError(error);
|
||||
fire.asyncWithoutClone(portObj);
|
||||
});
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onMessage: new EventManager(this.context, "Port.onMessage", fire => {
|
||||
return this.registerOnMessage(holder => {
|
||||
let msg = holder.deserialize(this.context.cloneScope);
|
||||
fire.asyncWithoutClone(msg, portObj);
|
||||
});
|
||||
onMessage: new EventManager({
|
||||
context: this.context,
|
||||
name: "Port.onMessage",
|
||||
register: fire => {
|
||||
return this.registerOnMessage(holder => {
|
||||
let msg = holder.deserialize(this.context.cloneScope);
|
||||
fire.asyncWithoutClone(msg, portObj);
|
||||
});
|
||||
},
|
||||
}).api(),
|
||||
|
||||
get error() {
|
||||
|
@ -403,63 +411,67 @@ class Messenger {
|
|||
}
|
||||
|
||||
_onMessage(name, filter) {
|
||||
return new EventManager(this.context, name, fire => {
|
||||
const caller = this.context.getCaller();
|
||||
return new EventManager({
|
||||
context: this.context,
|
||||
name,
|
||||
register: fire => {
|
||||
const caller = this.context.getCaller();
|
||||
|
||||
let listener = {
|
||||
messageFilterPermissive: this.optionalFilter,
|
||||
messageFilterStrict: this.filter,
|
||||
let listener = {
|
||||
messageFilterPermissive: this.optionalFilter,
|
||||
messageFilterStrict: this.filter,
|
||||
|
||||
filterMessage: (sender, recipient) => {
|
||||
// Exclude messages coming from content scripts for the devtools extension contexts
|
||||
// (See Bug 1383310).
|
||||
if (this.excludeContentScriptSender && sender.envType === "content_child") {
|
||||
return false;
|
||||
}
|
||||
filterMessage: (sender, recipient) => {
|
||||
// Exclude messages coming from content scripts for the devtools extension contexts
|
||||
// (See Bug 1383310).
|
||||
if (this.excludeContentScriptSender && sender.envType === "content_child") {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ignore the message if it was sent by this Messenger.
|
||||
return (sender.contextId !== this.context.contextId &&
|
||||
filter(sender, recipient));
|
||||
},
|
||||
// Ignore the message if it was sent by this Messenger.
|
||||
return (sender.contextId !== this.context.contextId &&
|
||||
filter(sender, recipient));
|
||||
},
|
||||
|
||||
receiveMessage: ({target, data: holder, sender, recipient, channelId}) => {
|
||||
if (!this.context.active) {
|
||||
return;
|
||||
}
|
||||
receiveMessage: ({target, data: holder, sender, recipient, channelId}) => {
|
||||
if (!this.context.active) {
|
||||
return;
|
||||
}
|
||||
|
||||
let sendResponse;
|
||||
let response = undefined;
|
||||
let promise = new Promise(resolve => {
|
||||
sendResponse = value => {
|
||||
resolve(value);
|
||||
response = promise;
|
||||
};
|
||||
});
|
||||
let sendResponse;
|
||||
let response = undefined;
|
||||
let promise = new Promise(resolve => {
|
||||
sendResponse = value => {
|
||||
resolve(value);
|
||||
response = promise;
|
||||
};
|
||||
});
|
||||
|
||||
let message = holder.deserialize(this.context.cloneScope);
|
||||
holder = null;
|
||||
let message = holder.deserialize(this.context.cloneScope);
|
||||
holder = null;
|
||||
|
||||
sender = Cu.cloneInto(sender, this.context.cloneScope);
|
||||
sendResponse = Cu.exportFunction(sendResponse, this.context.cloneScope);
|
||||
sender = Cu.cloneInto(sender, this.context.cloneScope);
|
||||
sendResponse = Cu.exportFunction(sendResponse, this.context.cloneScope);
|
||||
|
||||
// Note: We intentionally do not use runSafe here so that any
|
||||
// errors are propagated to the message sender.
|
||||
let result = fire.raw(message, sender, sendResponse);
|
||||
message = null;
|
||||
// Note: We intentionally do not use runSafe here so that any
|
||||
// errors are propagated to the message sender.
|
||||
let result = fire.raw(message, sender, sendResponse);
|
||||
message = null;
|
||||
|
||||
if (result instanceof this.context.cloneScope.Promise) {
|
||||
return StrongPromise.wrap(result, channelId, caller);
|
||||
} else if (result === true) {
|
||||
return StrongPromise.wrap(promise, channelId, caller);
|
||||
}
|
||||
return response;
|
||||
},
|
||||
};
|
||||
if (result instanceof this.context.cloneScope.Promise) {
|
||||
return StrongPromise.wrap(result, channelId, caller);
|
||||
} else if (result === true) {
|
||||
return StrongPromise.wrap(promise, channelId, caller);
|
||||
}
|
||||
return response;
|
||||
},
|
||||
};
|
||||
|
||||
MessageChannel.addListener(this.messageManagers, "Extension:Message", listener);
|
||||
return () => {
|
||||
MessageChannel.removeListener(this.messageManagers, "Extension:Message", listener);
|
||||
};
|
||||
MessageChannel.addListener(this.messageManagers, "Extension:Message", listener);
|
||||
return () => {
|
||||
MessageChannel.removeListener(this.messageManagers, "Extension:Message", listener);
|
||||
};
|
||||
},
|
||||
}).api();
|
||||
}
|
||||
|
||||
|
@ -506,41 +518,45 @@ class Messenger {
|
|||
}
|
||||
|
||||
_onConnect(name, filter) {
|
||||
return new EventManager(this.context, name, fire => {
|
||||
let listener = {
|
||||
messageFilterPermissive: this.optionalFilter,
|
||||
messageFilterStrict: this.filter,
|
||||
return new EventManager({
|
||||
context: this.context,
|
||||
name,
|
||||
register: fire => {
|
||||
let listener = {
|
||||
messageFilterPermissive: this.optionalFilter,
|
||||
messageFilterStrict: this.filter,
|
||||
|
||||
filterMessage: (sender, recipient) => {
|
||||
// Exclude messages coming from content scripts for the devtools extension contexts
|
||||
// (See Bug 1383310).
|
||||
if (this.excludeContentScriptSender && sender.envType === "content_child") {
|
||||
return false;
|
||||
}
|
||||
filterMessage: (sender, recipient) => {
|
||||
// Exclude messages coming from content scripts for the devtools extension contexts
|
||||
// (See Bug 1383310).
|
||||
if (this.excludeContentScriptSender && sender.envType === "content_child") {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ignore the port if it was created by this Messenger.
|
||||
return (sender.contextId !== this.context.contextId &&
|
||||
filter(sender, recipient));
|
||||
},
|
||||
// Ignore the port if it was created by this Messenger.
|
||||
return (sender.contextId !== this.context.contextId &&
|
||||
filter(sender, recipient));
|
||||
},
|
||||
|
||||
receiveMessage: ({target, data: message, sender}) => {
|
||||
let {name, portId} = message;
|
||||
let mm = getMessageManager(target);
|
||||
let recipient = Object.assign({}, sender);
|
||||
if (recipient.tab) {
|
||||
recipient.tabId = recipient.tab.id;
|
||||
delete recipient.tab;
|
||||
}
|
||||
let port = new Port(this.context, mm, this.messageManagers, name, portId, sender, recipient);
|
||||
fire.asyncWithoutClone(port.api());
|
||||
return true;
|
||||
},
|
||||
};
|
||||
receiveMessage: ({target, data: message, sender}) => {
|
||||
let {name, portId} = message;
|
||||
let mm = getMessageManager(target);
|
||||
let recipient = Object.assign({}, sender);
|
||||
if (recipient.tab) {
|
||||
recipient.tabId = recipient.tab.id;
|
||||
delete recipient.tab;
|
||||
}
|
||||
let port = new Port(this.context, mm, this.messageManagers, name, portId, sender, recipient);
|
||||
fire.asyncWithoutClone(port.api());
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
MessageChannel.addListener(this.messageManagers, "Extension:Connect", listener);
|
||||
return () => {
|
||||
MessageChannel.removeListener(this.messageManagers, "Extension:Connect", listener);
|
||||
};
|
||||
MessageChannel.addListener(this.messageManagers, "Extension:Connect", listener);
|
||||
return () => {
|
||||
MessageChannel.removeListener(this.messageManagers, "Extension:Connect", listener);
|
||||
};
|
||||
},
|
||||
}).api();
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ ChromeUtils.import("resource://gre/modules/Services.jsm");
|
|||
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
AppConstants: "resource://gre/modules/AppConstants.jsm",
|
||||
ConsoleAPI: "resource://gre/modules/Console.jsm",
|
||||
MessageChannel: "resource://gre/modules/MessageChannel.jsm",
|
||||
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
|
||||
|
@ -1735,46 +1736,226 @@ defineLazyGetter(LocaleData.prototype, "availableLocales", function() {
|
|||
});
|
||||
|
||||
/**
|
||||
* This is a generic class for managing event listeners.
|
||||
* This is a generic class for managing event listeners.
|
||||
*
|
||||
* @example
|
||||
* new EventManager(context, "api.subAPI", fire => {
|
||||
* let listener = (...) => {
|
||||
* // Fire any listeners registered with addListener.
|
||||
* fire.async(arg1, arg2);
|
||||
* };
|
||||
* // Register the listener.
|
||||
* SomehowRegisterListener(listener);
|
||||
* return () => {
|
||||
* // Return a way to unregister the listener.
|
||||
* SomehowUnregisterListener(listener);
|
||||
* };
|
||||
* new EventManager({
|
||||
* context,
|
||||
* name: "api.subAPI",
|
||||
* register: fire => {
|
||||
* let listener = (...) => {
|
||||
* // Fire any listeners registered with addListener.
|
||||
* fire.async(arg1, arg2);
|
||||
* };
|
||||
* // Register the listener.
|
||||
* SomehowRegisterListener(listener);
|
||||
* return () => {
|
||||
* // Return a way to unregister the listener.
|
||||
* SomehowUnregisterListener(listener);
|
||||
* };
|
||||
* }
|
||||
* }).api()
|
||||
*
|
||||
* The result is an object with addListener, removeListener, and
|
||||
* hasListener methods. `context` is an add-on scope (either an
|
||||
* ExtensionContext in the chrome process or ExtensionContext in a
|
||||
* content process). `name` is for debugging. `register` is a function
|
||||
* to register the listener. `register` should return an
|
||||
* unregister function that will unregister the listener.
|
||||
* @constructor
|
||||
*
|
||||
* @param {BaseContext} context
|
||||
* An object representing the extension instance using this event.
|
||||
* @param {string} name
|
||||
* A name used only for debugging.
|
||||
* @param {function} register
|
||||
* A function called whenever a new listener is added.
|
||||
* content process).
|
||||
*/
|
||||
function EventManager(context, name, register) {
|
||||
this.context = context;
|
||||
this.name = name;
|
||||
this.register = register;
|
||||
this.unregister = new Map();
|
||||
this.inputHandling = false;
|
||||
}
|
||||
class EventManager {
|
||||
/*
|
||||
* @param {object} params
|
||||
* Parameters that control this EventManager.
|
||||
* @param {BaseContext} params.context
|
||||
* An object representing the extension instance using this event.
|
||||
* @param {string} params.name
|
||||
* A name used only for debugging.
|
||||
* @param {functon} params.register
|
||||
* A function called whenever a new listener is added.
|
||||
* @param {boolean} [params.inputHandling=false]
|
||||
* If true, the "handling user input" flag is set while handlers
|
||||
* for this event are executing.
|
||||
* @param {object} [params.persistent]
|
||||
* Details for persistent event listeners
|
||||
* @param {string} params.persistent.module
|
||||
* The name of the module in which this event is defined.
|
||||
* @param {string} params.persistent.event
|
||||
* The name of this event.
|
||||
*/
|
||||
constructor(params) {
|
||||
// Maintain compatibility with the old EventManager API in which
|
||||
// the constructor took parameters (contest, name, register).
|
||||
// Remove this in bug 1451212.
|
||||
if (arguments.length > 1) {
|
||||
[this.context, this.name, this.register] = arguments;
|
||||
this.inputHandling = false;
|
||||
this.persistent = null;
|
||||
} else {
|
||||
let {context, name, register, inputHandling = false, persistent = null} = params;
|
||||
this.context = context;
|
||||
this.name = name;
|
||||
this.register = register;
|
||||
this.inputHandling = inputHandling;
|
||||
this.persistent = persistent;
|
||||
}
|
||||
|
||||
this.unregister = new Map();
|
||||
this.remove = new Map();
|
||||
|
||||
if (this.persistent) {
|
||||
if (this.context.viewType !== "background") {
|
||||
this.persistent = null;
|
||||
}
|
||||
if (AppConstants.DEBUG) {
|
||||
if (this.context.envType !== "addon_parent") {
|
||||
throw new Error("Persistent event managers can only be created for addon_parent");
|
||||
}
|
||||
if (!this.persistent.module || !this.persistent.event) {
|
||||
throw new Error("Persistent event manager must specify module and event");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Information about listeners to persistent events is associated with
|
||||
* the extension to which they belong. Any extension thas has such
|
||||
* listeners has a property called `persistentListeners` that is a
|
||||
* 3-level Map. The first 2 keys are the module name (e.g., webRequest)
|
||||
* and the name of the event within the module (e.g., onBeforeRequest).
|
||||
* The third level of the map is used to track multiple listeners for
|
||||
* the same event, these listeners are distinguished by the extra arguments
|
||||
* passed to addListener(). For quick lookups, the key to the third Map
|
||||
* is the result of calling uneval() on the array of extra arguments.
|
||||
*
|
||||
* The value stored in the Map is a plain object with a property called
|
||||
* `params` that is the original (ie, not uneval()ed) extra arguments to
|
||||
* addListener(). For a primed listener (i.e., the stub listener created
|
||||
* during browser startup before the extension background page is started,
|
||||
* the object also has a `primed` property that holds the things needed
|
||||
* to handle events during startup and eventually connect the listener
|
||||
* with a callback registered from the extension.
|
||||
*/
|
||||
static _initPersistentListeners(extension) {
|
||||
if (extension.persistentListeners) {
|
||||
return;
|
||||
}
|
||||
|
||||
let listeners = new DefaultMap(() => new DefaultMap(() => new Map()));
|
||||
extension.persistentListeners = listeners;
|
||||
|
||||
let {persistentListeners} = extension.startupData;
|
||||
if (!persistentListeners) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let [module, entry] of Object.entries(persistentListeners)) {
|
||||
for (let [event, paramlists] of Object.entries(entry)) {
|
||||
for (let paramlist of paramlists) {
|
||||
let key = uneval(paramlist);
|
||||
listeners.get(module).get(event).set(key, {params: paramlist});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Extract just the information needed at startup for all persistent
|
||||
// listeners, and arrange for it to be saved. This should be called
|
||||
// whenever the set of persistent listeners for an extension changes.
|
||||
static _writePersistentListeners(extension) {
|
||||
let startupListeners = {};
|
||||
for (let [module, moduleEntry] of extension.persistentListeners) {
|
||||
startupListeners[module] = {};
|
||||
for (let [event, eventEntry] of moduleEntry) {
|
||||
startupListeners[module][event] = Array.from(eventEntry.values(),
|
||||
listener => listener.params);
|
||||
}
|
||||
}
|
||||
|
||||
extension.startupData.persistentListeners = startupListeners;
|
||||
extension.saveStartupData();
|
||||
}
|
||||
|
||||
// Set up "primed" event listeners for any saved event listeners
|
||||
// in an extension's startup data.
|
||||
// This function is only called during browser startup, it stores details
|
||||
// about all primed listeners in the extension's persistentListeners Map.
|
||||
static primeListeners(extension) {
|
||||
EventManager._initPersistentListeners(extension);
|
||||
|
||||
for (let [module, moduleEntry] of extension.persistentListeners) {
|
||||
let api = extension.apiManager.getAPI(module, extension, "addon_parent");
|
||||
for (let [event, eventEntry] of moduleEntry) {
|
||||
for (let listener of eventEntry.values()) {
|
||||
let primed = {pendingEvents: []};
|
||||
listener.primed = primed;
|
||||
|
||||
let wakeup = (...args) => new Promise((resolve, reject) => {
|
||||
primed.pendingEvents.push({args, resolve, reject});
|
||||
extension.emit("background-page-event");
|
||||
});
|
||||
|
||||
let fire = {
|
||||
sync: wakeup,
|
||||
async: wakeup,
|
||||
};
|
||||
|
||||
let {unregister, convert} = api.primeListener(extension, event, fire, listener.params);
|
||||
Object.assign(primed, {unregister, convert});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove any primed listeners that were not re-registered.
|
||||
// This function is called after the background page has started.
|
||||
static clearPrimedListeners(extension) {
|
||||
for (let [module, moduleEntry] of extension.persistentListeners) {
|
||||
for (let [event, listeners] of moduleEntry) {
|
||||
for (let [key, listener] of listeners) {
|
||||
let {primed} = listener;
|
||||
if (!primed) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (let evt of primed.pendingEvents) {
|
||||
evt.reject(new Error("listener not re-registered"));
|
||||
}
|
||||
|
||||
EventManager.clearPersistentListener(extension, module, event, key);
|
||||
primed.unregister();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Record the fact that there is a listener for the given event in
|
||||
// the given extension. `args` is an Array containing any extra
|
||||
// arguments that were passed to addListener().
|
||||
static savePersistentListener(extension, module, event, args) {
|
||||
EventManager._initPersistentListeners(extension);
|
||||
let key = uneval(args);
|
||||
extension.persistentListeners.get(module).get(event).set(key, {params: args});
|
||||
EventManager._writePersistentListeners(extension);
|
||||
}
|
||||
|
||||
// Remove the record for the given event listener from the extension's
|
||||
// startup data. `key` must be a string, the result of calling uneval()
|
||||
// on the array of extra arguments originally passed to addListener().
|
||||
static clearPersistentListener(extension, module, event, key) {
|
||||
let listeners = extension.persistentListeners.get(module).get(event);
|
||||
listeners.delete(key);
|
||||
|
||||
if (listeners.size == 0) {
|
||||
let moduleEntry = extension.persistentListeners.get(module);
|
||||
moduleEntry.delete(event);
|
||||
if (moduleEntry.size == 0) {
|
||||
extension.persistentListeners.delete(module);
|
||||
}
|
||||
}
|
||||
|
||||
EventManager._writePersistentListeners(extension);
|
||||
}
|
||||
|
||||
EventManager.prototype = {
|
||||
addListener(callback, ...args) {
|
||||
if (this.unregister.has(callback)) {
|
||||
return;
|
||||
|
@ -1819,13 +2000,59 @@ EventManager.prototype = {
|
|||
},
|
||||
};
|
||||
|
||||
let {extension} = this.context;
|
||||
|
||||
let unregister = null;
|
||||
let recordStartupData = false;
|
||||
|
||||
// If this is a persistent event, check for a listener that was already
|
||||
// created during startup. If there is one, use it and don't create a
|
||||
// new one.
|
||||
if (this.persistent) {
|
||||
recordStartupData = true;
|
||||
let {module, event} = this.persistent;
|
||||
|
||||
let key = uneval(args);
|
||||
EventManager._initPersistentListeners(extension);
|
||||
let listener = extension.persistentListeners
|
||||
.get(module).get(event).get(key);
|
||||
if (listener) {
|
||||
let {primed} = listener;
|
||||
listener.primed = null;
|
||||
|
||||
primed.convert(fire);
|
||||
unregister = primed.unregister;
|
||||
|
||||
for (let evt of primed.pendingEvents) {
|
||||
evt.resolve(fire.async(...evt.args));
|
||||
}
|
||||
|
||||
recordStartupData = false;
|
||||
this.remove.set(callback, () => {
|
||||
EventManager.clearPersistentListener(extension, module, event, uneval(args));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!unregister) {
|
||||
unregister = this.register(fire, ...args);
|
||||
}
|
||||
|
||||
let unregister = this.register(fire, ...args);
|
||||
this.unregister.set(callback, unregister);
|
||||
this.context.callOnClose(this);
|
||||
},
|
||||
|
||||
removeListener(callback) {
|
||||
// If this is a new listener for a persistent event, record
|
||||
// the details for subsequent startups.
|
||||
if (recordStartupData) {
|
||||
let {module, event} = this.persistent;
|
||||
EventManager.savePersistentListener(extension, module, event, args);
|
||||
this.remove.set(callback, () => {
|
||||
EventManager.clearPersistentListener(extension, module, event, uneval(args));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
removeListener(callback, clearPersistentListener = true) {
|
||||
if (!this.unregister.has(callback)) {
|
||||
return;
|
||||
}
|
||||
|
@ -1837,24 +2064,31 @@ EventManager.prototype = {
|
|||
} catch (e) {
|
||||
Cu.reportError(e);
|
||||
}
|
||||
|
||||
if (clearPersistentListener && this.remove.has(callback)) {
|
||||
let cleanup = this.remove.get(callback);
|
||||
this.remove.delete(callback);
|
||||
cleanup();
|
||||
}
|
||||
|
||||
if (this.unregister.size == 0) {
|
||||
this.context.forgetOnClose(this);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
hasListener(callback) {
|
||||
return this.unregister.has(callback);
|
||||
},
|
||||
}
|
||||
|
||||
revoke() {
|
||||
for (let callback of this.unregister.keys()) {
|
||||
this.removeListener(callback);
|
||||
this.removeListener(callback, false);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
close() {
|
||||
this.revoke();
|
||||
},
|
||||
}
|
||||
|
||||
api() {
|
||||
return {
|
||||
|
@ -1864,8 +2098,8 @@ EventManager.prototype = {
|
|||
setUserInput: this.inputHandling,
|
||||
[Schemas.REVOKE]: () => this.revoke(),
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Simple API for event listeners where events never fire.
|
||||
function ignoreEvent(context, name) {
|
||||
|
|
|
@ -148,20 +148,24 @@ this.storage = class extends ExtensionAPI {
|
|||
},
|
||||
},
|
||||
|
||||
onChanged: new EventManager(context, "storage.onChanged", fire => {
|
||||
let onChanged = (data, area) => {
|
||||
let changes = new context.cloneScope.Object();
|
||||
for (let [key, value] of Object.entries(data)) {
|
||||
changes[key] = deserialize(value);
|
||||
}
|
||||
fire.raw(changes, area);
|
||||
};
|
||||
onChanged: new EventManager({
|
||||
context,
|
||||
name: "storage.onChanged",
|
||||
register: fire => {
|
||||
let onChanged = (data, area) => {
|
||||
let changes = new context.cloneScope.Object();
|
||||
for (let [key, value] of Object.entries(data)) {
|
||||
changes[key] = deserialize(value);
|
||||
}
|
||||
fire.raw(changes, area);
|
||||
};
|
||||
|
||||
let parent = context.childManager.getParentEvent("storage.onChanged");
|
||||
parent.addListener(onChanged);
|
||||
return () => {
|
||||
parent.removeListener(onChanged);
|
||||
};
|
||||
let parent = context.childManager.getParentEvent("storage.onChanged");
|
||||
parent.addListener(onChanged);
|
||||
return () => {
|
||||
parent.removeListener(onChanged);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -177,15 +177,19 @@ this.test = class extends ExtensionAPI {
|
|||
}
|
||||
},
|
||||
|
||||
onMessage: new TestEventManager(context, "test.onMessage", fire => {
|
||||
let handler = (event, ...args) => {
|
||||
fire.async(...args);
|
||||
};
|
||||
onMessage: new TestEventManager({
|
||||
context,
|
||||
name: "test.onMessage",
|
||||
register: fire => {
|
||||
let handler = (event, ...args) => {
|
||||
fire.async(...args);
|
||||
};
|
||||
|
||||
extension.on("test-harness-message", handler);
|
||||
return () => {
|
||||
extension.off("test-harness-message", handler);
|
||||
};
|
||||
extension.on("test-harness-message", handler);
|
||||
return () => {
|
||||
extension.off("test-harness-message", handler);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -281,7 +281,8 @@ const BrowserListener = {
|
|||
background = null;
|
||||
}
|
||||
|
||||
if (background !== this.oldBackground) {
|
||||
if (background === null ||
|
||||
background !== this.oldBackground) {
|
||||
sendAsyncMessage("Extension:BrowserBackgroundChanged", {background});
|
||||
}
|
||||
this.oldBackground = background;
|
||||
|
|
|
@ -14,6 +14,8 @@ module.exports = {
|
|||
"WindowBase": true,
|
||||
"WindowManagerBase": true,
|
||||
"WindowTrackerBase": true,
|
||||
"browserPaintedPromise": true,
|
||||
"browserStartupPromise": true,
|
||||
"getContainerForCookieStoreId": true,
|
||||
"getCookieStoreIdForContainer": true,
|
||||
"getCookieStoreIdForTab": true,
|
||||
|
|
|
@ -125,15 +125,19 @@ this.alarms = class extends ExtensionAPI {
|
|||
return Promise.resolve(cleared);
|
||||
},
|
||||
|
||||
onAlarm: new EventManager(context, "alarms.onAlarm", fire => {
|
||||
let callback = alarm => {
|
||||
fire.sync(alarm.data);
|
||||
};
|
||||
onAlarm: new EventManager({
|
||||
context,
|
||||
name: "alarms.onAlarm",
|
||||
register: fire => {
|
||||
let callback = alarm => {
|
||||
fire.sync(alarm.data);
|
||||
};
|
||||
|
||||
self.callbacks.add(callback);
|
||||
return () => {
|
||||
self.callbacks.delete(callback);
|
||||
};
|
||||
self.callbacks.add(callback);
|
||||
return () => {
|
||||
self.callbacks.delete(callback);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -9,6 +9,9 @@ var {
|
|||
promiseExtensionViewLoaded,
|
||||
} = ExtensionParent;
|
||||
|
||||
XPCOMUtils.defineLazyPreferenceGetter(this, "DELAYED_STARTUP",
|
||||
"extensions.webextensions.background-delayed-startup");
|
||||
|
||||
// Responsible for the background_page section of the manifest.
|
||||
class BackgroundPage extends HiddenExtensionPage {
|
||||
constructor(extension, options) {
|
||||
|
@ -54,11 +57,37 @@ class BackgroundPage extends HiddenExtensionPage {
|
|||
|
||||
this.backgroundPage = class extends ExtensionAPI {
|
||||
onManifestEntry(entryName) {
|
||||
let {manifest} = this.extension;
|
||||
let {extension} = this;
|
||||
let {manifest} = extension;
|
||||
|
||||
this.bgPage = new BackgroundPage(this.extension, manifest.background);
|
||||
this.bgPage = new BackgroundPage(extension, manifest.background);
|
||||
if (extension.startupReason !== "APP_STARTUP" || !DELAYED_STARTUP) {
|
||||
return this.bgPage.build();
|
||||
}
|
||||
|
||||
return this.bgPage.build();
|
||||
EventManager.primeListeners(extension);
|
||||
|
||||
extension.once("start-background-page", async () => {
|
||||
await this.bgPage.build();
|
||||
EventManager.clearPrimedListeners(extension);
|
||||
});
|
||||
|
||||
// There are two ways to start the background page:
|
||||
// 1. If a primed event fires, then start the background page as
|
||||
// soon as we have painted a browser window. Note that we have
|
||||
// to touch browserPaintedPromise here to initialize the listener
|
||||
// or else we can miss it if the event occurs after the first
|
||||
// window is painted but before #2
|
||||
// 2. After all windows have been restored.
|
||||
void browserPaintedPromise;
|
||||
extension.once("background-page-event", async () => {
|
||||
await browserPaintedPromise;
|
||||
extension.emit("start-background-page");
|
||||
});
|
||||
|
||||
browserStartupPromise.then(() => {
|
||||
extension.emit("start-background-page");
|
||||
});
|
||||
}
|
||||
|
||||
onShutdown() {
|
||||
|
|
|
@ -224,46 +224,58 @@ this.contextualIdentities = class extends ExtensionAPI {
|
|||
return convertedIdentity;
|
||||
},
|
||||
|
||||
onCreated: new EventManager(context, "contextualIdentities.onCreated", fire => {
|
||||
let observer = (subject, topic) => {
|
||||
let convertedIdentity = convertIdentityFromObserver(subject);
|
||||
if (convertedIdentity) {
|
||||
fire.async({contextualIdentity: convertedIdentity});
|
||||
}
|
||||
};
|
||||
onCreated: new EventManager({
|
||||
context,
|
||||
name: "contextualIdentities.onCreated",
|
||||
register: fire => {
|
||||
let observer = (subject, topic) => {
|
||||
let convertedIdentity = convertIdentityFromObserver(subject);
|
||||
if (convertedIdentity) {
|
||||
fire.async({contextualIdentity: convertedIdentity});
|
||||
}
|
||||
};
|
||||
|
||||
Services.obs.addObserver(observer, "contextual-identity-created");
|
||||
return () => {
|
||||
Services.obs.removeObserver(observer, "contextual-identity-created");
|
||||
};
|
||||
Services.obs.addObserver(observer, "contextual-identity-created");
|
||||
return () => {
|
||||
Services.obs.removeObserver(observer, "contextual-identity-created");
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onUpdated: new EventManager(context, "contextualIdentities.onUpdated", fire => {
|
||||
let observer = (subject, topic) => {
|
||||
let convertedIdentity = convertIdentityFromObserver(subject);
|
||||
if (convertedIdentity) {
|
||||
fire.async({contextualIdentity: convertedIdentity});
|
||||
}
|
||||
};
|
||||
onUpdated: new EventManager({
|
||||
context,
|
||||
name: "contextualIdentities.onUpdated",
|
||||
register: fire => {
|
||||
let observer = (subject, topic) => {
|
||||
let convertedIdentity = convertIdentityFromObserver(subject);
|
||||
if (convertedIdentity) {
|
||||
fire.async({contextualIdentity: convertedIdentity});
|
||||
}
|
||||
};
|
||||
|
||||
Services.obs.addObserver(observer, "contextual-identity-updated");
|
||||
return () => {
|
||||
Services.obs.removeObserver(observer, "contextual-identity-updated");
|
||||
};
|
||||
Services.obs.addObserver(observer, "contextual-identity-updated");
|
||||
return () => {
|
||||
Services.obs.removeObserver(observer, "contextual-identity-updated");
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onRemoved: new EventManager(context, "contextualIdentities.onRemoved", fire => {
|
||||
let observer = (subject, topic) => {
|
||||
let convertedIdentity = convertIdentityFromObserver(subject);
|
||||
if (convertedIdentity) {
|
||||
fire.async({contextualIdentity: convertedIdentity});
|
||||
}
|
||||
};
|
||||
onRemoved: new EventManager({
|
||||
context,
|
||||
name: "contextualIdentities.onRemoved",
|
||||
register: fire => {
|
||||
let observer = (subject, topic) => {
|
||||
let convertedIdentity = convertIdentityFromObserver(subject);
|
||||
if (convertedIdentity) {
|
||||
fire.async({contextualIdentity: convertedIdentity});
|
||||
}
|
||||
};
|
||||
|
||||
Services.obs.addObserver(observer, "contextual-identity-deleted");
|
||||
return () => {
|
||||
Services.obs.removeObserver(observer, "contextual-identity-deleted");
|
||||
};
|
||||
Services.obs.addObserver(observer, "contextual-identity-deleted");
|
||||
return () => {
|
||||
Services.obs.removeObserver(observer, "contextual-identity-deleted");
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
},
|
||||
|
|
|
@ -412,48 +412,52 @@ this.cookies = class extends ExtensionAPI {
|
|||
return Promise.resolve(result);
|
||||
},
|
||||
|
||||
onChanged: new EventManager(context, "cookies.onChanged", fire => {
|
||||
let observer = (subject, topic, data) => {
|
||||
let notify = (removed, cookie, cause) => {
|
||||
cookie.QueryInterface(Ci.nsICookie2);
|
||||
onChanged: new EventManager({
|
||||
context,
|
||||
name: "cookies.onChanged",
|
||||
register: fire => {
|
||||
let observer = (subject, topic, data) => {
|
||||
let notify = (removed, cookie, cause) => {
|
||||
cookie.QueryInterface(Ci.nsICookie2);
|
||||
|
||||
if (extension.whiteListedHosts.matchesCookie(cookie)) {
|
||||
fire.async({removed, cookie: convertCookie({cookie, isPrivate: topic == "private-cookie-changed"}), cause});
|
||||
if (extension.whiteListedHosts.matchesCookie(cookie)) {
|
||||
fire.async({removed, cookie: convertCookie({cookie, isPrivate: topic == "private-cookie-changed"}), cause});
|
||||
}
|
||||
};
|
||||
|
||||
// We do our best effort here to map the incompatible states.
|
||||
switch (data) {
|
||||
case "deleted":
|
||||
notify(true, subject, "explicit");
|
||||
break;
|
||||
case "added":
|
||||
notify(false, subject, "explicit");
|
||||
break;
|
||||
case "changed":
|
||||
notify(true, subject, "overwrite");
|
||||
notify(false, subject, "explicit");
|
||||
break;
|
||||
case "batch-deleted":
|
||||
subject.QueryInterface(Ci.nsIArray);
|
||||
for (let i = 0; i < subject.length; i++) {
|
||||
let cookie = subject.queryElementAt(i, Ci.nsICookie2);
|
||||
if (!cookie.isSession && cookie.expiry * 1000 <= Date.now()) {
|
||||
notify(true, cookie, "expired");
|
||||
} else {
|
||||
notify(true, cookie, "evicted");
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
// We do our best effort here to map the incompatible states.
|
||||
switch (data) {
|
||||
case "deleted":
|
||||
notify(true, subject, "explicit");
|
||||
break;
|
||||
case "added":
|
||||
notify(false, subject, "explicit");
|
||||
break;
|
||||
case "changed":
|
||||
notify(true, subject, "overwrite");
|
||||
notify(false, subject, "explicit");
|
||||
break;
|
||||
case "batch-deleted":
|
||||
subject.QueryInterface(Ci.nsIArray);
|
||||
for (let i = 0; i < subject.length; i++) {
|
||||
let cookie = subject.queryElementAt(i, Ci.nsICookie2);
|
||||
if (!cookie.isSession && cookie.expiry * 1000 <= Date.now()) {
|
||||
notify(true, cookie, "expired");
|
||||
} else {
|
||||
notify(true, cookie, "evicted");
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
Services.obs.addObserver(observer, "cookie-changed");
|
||||
Services.obs.addObserver(observer, "private-cookie-changed");
|
||||
return () => {
|
||||
Services.obs.removeObserver(observer, "cookie-changed");
|
||||
Services.obs.removeObserver(observer, "private-cookie-changed");
|
||||
};
|
||||
Services.obs.addObserver(observer, "cookie-changed");
|
||||
Services.obs.addObserver(observer, "private-cookie-changed");
|
||||
return () => {
|
||||
Services.obs.removeObserver(observer, "cookie-changed");
|
||||
Services.obs.removeObserver(observer, "private-cookie-changed");
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -765,60 +765,72 @@ this.downloads = class extends ExtensionAPI {
|
|||
// ...
|
||||
// }
|
||||
|
||||
onChanged: new EventManager(context, "downloads.onChanged", fire => {
|
||||
const handler = (what, item) => {
|
||||
let changes = {};
|
||||
const noundef = val => (val === undefined) ? null : val;
|
||||
DOWNLOAD_ITEM_CHANGE_FIELDS.forEach(fld => {
|
||||
if (item[fld] != item.prechange[fld]) {
|
||||
changes[fld] = {
|
||||
previous: noundef(item.prechange[fld]),
|
||||
current: noundef(item[fld]),
|
||||
};
|
||||
onChanged: new EventManager({
|
||||
context,
|
||||
name: "downloads.onChanged",
|
||||
register: fire => {
|
||||
const handler = (what, item) => {
|
||||
let changes = {};
|
||||
const noundef = val => (val === undefined) ? null : val;
|
||||
DOWNLOAD_ITEM_CHANGE_FIELDS.forEach(fld => {
|
||||
if (item[fld] != item.prechange[fld]) {
|
||||
changes[fld] = {
|
||||
previous: noundef(item.prechange[fld]),
|
||||
current: noundef(item[fld]),
|
||||
};
|
||||
}
|
||||
});
|
||||
if (Object.keys(changes).length > 0) {
|
||||
changes.id = item.id;
|
||||
fire.async(changes);
|
||||
}
|
||||
});
|
||||
if (Object.keys(changes).length > 0) {
|
||||
changes.id = item.id;
|
||||
fire.async(changes);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
let registerPromise = DownloadMap.getDownloadList().then(() => {
|
||||
DownloadMap.on("change", handler);
|
||||
});
|
||||
return () => {
|
||||
registerPromise.then(() => {
|
||||
DownloadMap.off("change", handler);
|
||||
let registerPromise = DownloadMap.getDownloadList().then(() => {
|
||||
DownloadMap.on("change", handler);
|
||||
});
|
||||
};
|
||||
return () => {
|
||||
registerPromise.then(() => {
|
||||
DownloadMap.off("change", handler);
|
||||
});
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onCreated: new EventManager(context, "downloads.onCreated", fire => {
|
||||
const handler = (what, item) => {
|
||||
fire.async(item.serialize());
|
||||
};
|
||||
let registerPromise = DownloadMap.getDownloadList().then(() => {
|
||||
DownloadMap.on("create", handler);
|
||||
});
|
||||
return () => {
|
||||
registerPromise.then(() => {
|
||||
DownloadMap.off("create", handler);
|
||||
onCreated: new EventManager({
|
||||
context,
|
||||
name: "downloads.onCreated",
|
||||
register: fire => {
|
||||
const handler = (what, item) => {
|
||||
fire.async(item.serialize());
|
||||
};
|
||||
let registerPromise = DownloadMap.getDownloadList().then(() => {
|
||||
DownloadMap.on("create", handler);
|
||||
});
|
||||
};
|
||||
return () => {
|
||||
registerPromise.then(() => {
|
||||
DownloadMap.off("create", handler);
|
||||
});
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onErased: new EventManager(context, "downloads.onErased", fire => {
|
||||
const handler = (what, item) => {
|
||||
fire.async(item.id);
|
||||
};
|
||||
let registerPromise = DownloadMap.getDownloadList().then(() => {
|
||||
DownloadMap.on("erase", handler);
|
||||
});
|
||||
return () => {
|
||||
registerPromise.then(() => {
|
||||
DownloadMap.off("erase", handler);
|
||||
onErased: new EventManager({
|
||||
context,
|
||||
name: "downloads.onErased",
|
||||
register: fire => {
|
||||
const handler = (what, item) => {
|
||||
fire.async(item.id);
|
||||
};
|
||||
let registerPromise = DownloadMap.getDownloadList().then(() => {
|
||||
DownloadMap.on("erase", handler);
|
||||
});
|
||||
};
|
||||
return () => {
|
||||
registerPromise.then(() => {
|
||||
DownloadMap.off("erase", handler);
|
||||
});
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onDeterminingFilename: ignoreEvent(context, "downloads.onDeterminingFilename"),
|
||||
|
|
|
@ -70,15 +70,19 @@ this.idle = class extends ExtensionAPI {
|
|||
setDetectionInterval: function(detectionIntervalInSeconds) {
|
||||
setDetectionInterval(extension, context, detectionIntervalInSeconds);
|
||||
},
|
||||
onStateChanged: new EventManager(context, "idle.onStateChanged", fire => {
|
||||
let listener = (event, data) => {
|
||||
fire.sync(data);
|
||||
};
|
||||
onStateChanged: new EventManager({
|
||||
context,
|
||||
name: "idle.onStateChanged",
|
||||
register: fire => {
|
||||
let listener = (event, data) => {
|
||||
fire.sync(data);
|
||||
};
|
||||
|
||||
getIdleObserver(extension, context).on("stateChanged", listener);
|
||||
return () => {
|
||||
getIdleObserver(extension, context).off("stateChanged", listener);
|
||||
};
|
||||
getIdleObserver(extension, context).on("stateChanged", listener);
|
||||
return () => {
|
||||
getIdleObserver(extension, context).off("stateChanged", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -230,48 +230,64 @@ this.management = class extends ExtensionAPI {
|
|||
addon.userDisabled = !enabled;
|
||||
},
|
||||
|
||||
onDisabled: new EventManager(context, "management.onDisabled", fire => {
|
||||
let listener = (event, data) => {
|
||||
fire.async(data);
|
||||
};
|
||||
onDisabled: new EventManager({
|
||||
context,
|
||||
name: "management.onDisabled",
|
||||
register: fire => {
|
||||
let listener = (event, data) => {
|
||||
fire.async(data);
|
||||
};
|
||||
|
||||
getManagementListener(extension, context).on("onDisabled", listener);
|
||||
return () => {
|
||||
getManagementListener(extension, context).off("onDisabled", listener);
|
||||
};
|
||||
getManagementListener(extension, context).on("onDisabled", listener);
|
||||
return () => {
|
||||
getManagementListener(extension, context).off("onDisabled", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onEnabled: new EventManager(context, "management.onEnabled", fire => {
|
||||
let listener = (event, data) => {
|
||||
fire.async(data);
|
||||
};
|
||||
onEnabled: new EventManager({
|
||||
context,
|
||||
name: "management.onEnabled",
|
||||
register: fire => {
|
||||
let listener = (event, data) => {
|
||||
fire.async(data);
|
||||
};
|
||||
|
||||
getManagementListener(extension, context).on("onEnabled", listener);
|
||||
return () => {
|
||||
getManagementListener(extension, context).off("onEnabled", listener);
|
||||
};
|
||||
getManagementListener(extension, context).on("onEnabled", listener);
|
||||
return () => {
|
||||
getManagementListener(extension, context).off("onEnabled", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onInstalled: new EventManager(context, "management.onInstalled", fire => {
|
||||
let listener = (event, data) => {
|
||||
fire.async(data);
|
||||
};
|
||||
onInstalled: new EventManager({
|
||||
context,
|
||||
name: "management.onInstalled",
|
||||
register: fire => {
|
||||
let listener = (event, data) => {
|
||||
fire.async(data);
|
||||
};
|
||||
|
||||
getManagementListener(extension, context).on("onInstalled", listener);
|
||||
return () => {
|
||||
getManagementListener(extension, context).off("onInstalled", listener);
|
||||
};
|
||||
getManagementListener(extension, context).on("onInstalled", listener);
|
||||
return () => {
|
||||
getManagementListener(extension, context).off("onInstalled", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onUninstalled: new EventManager(context, "management.onUninstalled", fire => {
|
||||
let listener = (event, data) => {
|
||||
fire.async(data);
|
||||
};
|
||||
onUninstalled: new EventManager({
|
||||
context,
|
||||
name: "management.onUninstalled",
|
||||
register: fire => {
|
||||
let listener = (event, data) => {
|
||||
fire.async(data);
|
||||
};
|
||||
|
||||
getManagementListener(extension, context).on("onUninstalled", listener);
|
||||
return () => {
|
||||
getManagementListener(extension, context).off("onUninstalled", listener);
|
||||
};
|
||||
getManagementListener(extension, context).on("onUninstalled", listener);
|
||||
return () => {
|
||||
getManagementListener(extension, context).off("onUninstalled", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
},
|
||||
|
|
|
@ -117,38 +117,50 @@ this.notifications = class extends ExtensionAPI {
|
|||
return Promise.resolve(result);
|
||||
},
|
||||
|
||||
onClosed: new EventManager(context, "notifications.onClosed", fire => {
|
||||
let listener = (event, notificationId) => {
|
||||
// TODO Bug 1413188, Support the byUser argument.
|
||||
fire.async(notificationId, true);
|
||||
};
|
||||
onClosed: new EventManager({
|
||||
context,
|
||||
name: "notifications.onClosed",
|
||||
register: fire => {
|
||||
let listener = (event, notificationId) => {
|
||||
// TODO Bug 1413188, Support the byUser argument.
|
||||
fire.async(notificationId, true);
|
||||
};
|
||||
|
||||
notificationsMap.on("closed", listener);
|
||||
return () => {
|
||||
notificationsMap.off("closed", listener);
|
||||
};
|
||||
notificationsMap.on("closed", listener);
|
||||
return () => {
|
||||
notificationsMap.off("closed", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onClicked: new EventManager(context, "notifications.onClicked", fire => {
|
||||
let listener = (event, notificationId) => {
|
||||
fire.async(notificationId, true);
|
||||
};
|
||||
onClicked: new EventManager({
|
||||
context,
|
||||
name: "notifications.onClicked",
|
||||
register: fire => {
|
||||
let listener = (event, notificationId) => {
|
||||
fire.async(notificationId, true);
|
||||
};
|
||||
|
||||
notificationsMap.on("clicked", listener);
|
||||
return () => {
|
||||
notificationsMap.off("clicked", listener);
|
||||
};
|
||||
notificationsMap.on("clicked", listener);
|
||||
return () => {
|
||||
notificationsMap.off("clicked", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onShown: new EventManager(context, "notifications.onShown", fire => {
|
||||
let listener = (event, notificationId) => {
|
||||
fire.async(notificationId, true);
|
||||
};
|
||||
onShown: new EventManager({
|
||||
context,
|
||||
name: "notifications.onShown",
|
||||
register: fire => {
|
||||
let listener = (event, notificationId) => {
|
||||
fire.async(notificationId, true);
|
||||
};
|
||||
|
||||
notificationsMap.on("shown", listener);
|
||||
return () => {
|
||||
notificationsMap.off("shown", listener);
|
||||
};
|
||||
notificationsMap.on("shown", listener);
|
||||
return () => {
|
||||
notificationsMap.off("shown", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
// TODO Bug 1190681, implement button support.
|
||||
|
|
|
@ -43,7 +43,7 @@ class ProxyFilterEventManager extends EventManager {
|
|||
};
|
||||
};
|
||||
|
||||
super(context, name, register);
|
||||
super({context, name, register});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -61,14 +61,18 @@ this.proxy = class extends ExtensionAPI {
|
|||
getAPI(context) {
|
||||
let {extension} = context;
|
||||
|
||||
let onError = new EventManager(context, "proxy.onError", fire => {
|
||||
let listener = (name, error) => {
|
||||
fire.async(error);
|
||||
};
|
||||
extension.on("proxy-error", listener);
|
||||
return () => {
|
||||
extension.off("proxy-error", listener);
|
||||
};
|
||||
let onError = new EventManager({
|
||||
context,
|
||||
name: "proxy.onError",
|
||||
register: fire => {
|
||||
let listener = (name, error) => {
|
||||
fire.async(error);
|
||||
};
|
||||
extension.on("proxy-error", listener);
|
||||
return () => {
|
||||
extension.off("proxy-error", listener);
|
||||
};
|
||||
},
|
||||
}).api();
|
||||
|
||||
return {
|
||||
|
|
|
@ -16,64 +16,76 @@ this.runtime = class extends ExtensionAPI {
|
|||
let {extension} = context;
|
||||
return {
|
||||
runtime: {
|
||||
onStartup: new EventManager(context, "runtime.onStartup", fire => {
|
||||
if (context.incognito) {
|
||||
// This event should not fire if we are operating in a private profile.
|
||||
return () => {};
|
||||
}
|
||||
let listener = () => {
|
||||
if (extension.startupReason === "APP_STARTUP") {
|
||||
fire.sync();
|
||||
onStartup: new EventManager({
|
||||
context,
|
||||
name: "runtime.onStartup",
|
||||
register: fire => {
|
||||
if (context.incognito) {
|
||||
// This event should not fire if we are operating in a private profile.
|
||||
return () => {};
|
||||
}
|
||||
};
|
||||
extension.on("startup", listener);
|
||||
return () => {
|
||||
extension.off("startup", listener);
|
||||
};
|
||||
}).api(),
|
||||
|
||||
onInstalled: new EventManager(context, "runtime.onInstalled", fire => {
|
||||
let temporary = !!extension.addonData.temporarilyInstalled;
|
||||
|
||||
let listener = () => {
|
||||
switch (extension.startupReason) {
|
||||
case "APP_STARTUP":
|
||||
if (AddonManagerPrivate.browserUpdated) {
|
||||
fire.sync({reason: "browser_update", temporary});
|
||||
}
|
||||
break;
|
||||
case "ADDON_INSTALL":
|
||||
fire.sync({reason: "install", temporary});
|
||||
break;
|
||||
case "ADDON_UPGRADE":
|
||||
fire.sync({
|
||||
reason: "update",
|
||||
previousVersion: extension.addonData.oldVersion,
|
||||
temporary,
|
||||
});
|
||||
break;
|
||||
}
|
||||
};
|
||||
extension.on("startup", listener);
|
||||
return () => {
|
||||
extension.off("startup", listener);
|
||||
};
|
||||
}).api(),
|
||||
|
||||
onUpdateAvailable: new EventManager(context, "runtime.onUpdateAvailable", fire => {
|
||||
let instanceID = extension.addonData.instanceID;
|
||||
AddonManager.addUpgradeListener(instanceID, upgrade => {
|
||||
extension.upgrade = upgrade;
|
||||
let details = {
|
||||
version: upgrade.version,
|
||||
let listener = () => {
|
||||
if (extension.startupReason === "APP_STARTUP") {
|
||||
fire.sync();
|
||||
}
|
||||
};
|
||||
fire.sync(details);
|
||||
});
|
||||
return () => {
|
||||
AddonManager.removeUpgradeListener(instanceID).catch(e => {
|
||||
// This can happen if we try this after shutdown is complete.
|
||||
extension.on("startup", listener);
|
||||
return () => {
|
||||
extension.off("startup", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onInstalled: new EventManager({
|
||||
context,
|
||||
name: "runtime.onInstalled",
|
||||
register: fire => {
|
||||
let temporary = !!extension.addonData.temporarilyInstalled;
|
||||
|
||||
let listener = () => {
|
||||
switch (extension.startupReason) {
|
||||
case "APP_STARTUP":
|
||||
if (AddonManagerPrivate.browserUpdated) {
|
||||
fire.sync({reason: "browser_update", temporary});
|
||||
}
|
||||
break;
|
||||
case "ADDON_INSTALL":
|
||||
fire.sync({reason: "install", temporary});
|
||||
break;
|
||||
case "ADDON_UPGRADE":
|
||||
fire.sync({
|
||||
reason: "update",
|
||||
previousVersion: extension.addonData.oldVersion,
|
||||
temporary,
|
||||
});
|
||||
break;
|
||||
}
|
||||
};
|
||||
extension.on("startup", listener);
|
||||
return () => {
|
||||
extension.off("startup", listener);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onUpdateAvailable: new EventManager({
|
||||
context,
|
||||
name: "runtime.onUpdateAvailable",
|
||||
register: fire => {
|
||||
let instanceID = extension.addonData.instanceID;
|
||||
AddonManager.addUpgradeListener(instanceID, upgrade => {
|
||||
extension.upgrade = upgrade;
|
||||
let details = {
|
||||
version: upgrade.version,
|
||||
};
|
||||
fire.sync(details);
|
||||
});
|
||||
};
|
||||
return () => {
|
||||
AddonManager.removeUpgradeListener(instanceID).catch(e => {
|
||||
// This can happen if we try this after shutdown is complete.
|
||||
});
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
reload: () => {
|
||||
|
|
|
@ -89,20 +89,24 @@ this.storage = class extends ExtensionAPI {
|
|||
},
|
||||
},
|
||||
|
||||
onChanged: new EventManager(context, "storage.onChanged", fire => {
|
||||
let listenerLocal = changes => {
|
||||
fire.raw(changes, "local");
|
||||
};
|
||||
let listenerSync = changes => {
|
||||
fire.async(changes, "sync");
|
||||
};
|
||||
onChanged: new EventManager({
|
||||
context,
|
||||
name: "storage.onChanged",
|
||||
register: fire => {
|
||||
let listenerLocal = changes => {
|
||||
fire.raw(changes, "local");
|
||||
};
|
||||
let listenerSync = changes => {
|
||||
fire.async(changes, "sync");
|
||||
};
|
||||
|
||||
ExtensionStorage.addOnChangedListener(extension.id, listenerLocal);
|
||||
extensionStorageSync.addOnChangedListener(extension, listenerSync, context);
|
||||
return () => {
|
||||
ExtensionStorage.removeOnChangedListener(extension.id, listenerLocal);
|
||||
extensionStorageSync.removeOnChangedListener(extension, listenerSync);
|
||||
};
|
||||
ExtensionStorage.addOnChangedListener(extension.id, listenerLocal);
|
||||
extensionStorageSync.addOnChangedListener(extension, listenerSync, context);
|
||||
return () => {
|
||||
ExtensionStorage.removeOnChangedListener(extension.id, listenerLocal);
|
||||
extensionStorageSync.removeOnChangedListener(extension, listenerSync);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -411,19 +411,23 @@ this.theme = class extends ExtensionAPI {
|
|||
|
||||
Theme.unload(windowId);
|
||||
},
|
||||
onUpdated: new EventManager(context, "theme.onUpdated", fire => {
|
||||
let callback = (event, theme, windowId) => {
|
||||
if (windowId) {
|
||||
fire.async({theme, windowId});
|
||||
} else {
|
||||
fire.async({theme});
|
||||
}
|
||||
};
|
||||
onUpdated: new EventManager({
|
||||
context,
|
||||
name: "theme.onUpdated",
|
||||
register: fire => {
|
||||
let callback = (event, theme, windowId) => {
|
||||
if (windowId) {
|
||||
fire.async({theme, windowId});
|
||||
} else {
|
||||
fire.async({theme});
|
||||
}
|
||||
};
|
||||
|
||||
onUpdatedEmitter.on("theme-updated", callback);
|
||||
return () => {
|
||||
onUpdatedEmitter.off("theme-updated", callback);
|
||||
};
|
||||
onUpdatedEmitter.on("theme-updated", callback);
|
||||
return () => {
|
||||
onUpdatedEmitter.off("theme-updated", callback);
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -21,12 +21,6 @@ ChromeUtils.import("resource://gre/modules/ExtensionCommon.jsm");
|
|||
|
||||
global.EventEmitter = ExtensionUtils.EventEmitter;
|
||||
global.EventManager = ExtensionCommon.EventManager;
|
||||
global.InputEventManager = class extends EventManager {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this.inputHandling = true;
|
||||
}
|
||||
};
|
||||
|
||||
/* globals DEFAULT_STORE, PRIVATE_STORE, CONTAINER_STORE */
|
||||
|
||||
|
@ -80,3 +74,24 @@ global.isValidCookieStoreId = function(storeId) {
|
|||
isPrivateCookieStoreId(storeId) ||
|
||||
isContainerCookieStoreId(storeId);
|
||||
};
|
||||
|
||||
function makeEventPromise(name, event) {
|
||||
Object.defineProperty(global, name, {
|
||||
get() {
|
||||
let promise = ExtensionUtils.promiseObserved(event);
|
||||
Object.defineProperty(global, name, {value: promise});
|
||||
return promise;
|
||||
},
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
});
|
||||
}
|
||||
|
||||
// browserPaintedPromise and browserStartupPromise are promises that
|
||||
// resolve after the first browser window is painted and after browser
|
||||
// windows have been restored, respectively.
|
||||
// These promises must be referenced during startup to be valid -- if the
|
||||
// first reference happens after the corresponding event has occurred,
|
||||
// the Promise will never resolve.
|
||||
makeEventPromise("browserPaintedPromise", "browser-delayed-startup-finished");
|
||||
makeEventPromise("browserStartupPromise", "sessionstore-windows-restored");
|
||||
|
|
|
@ -90,61 +90,61 @@ const fillTransitionProperties = (eventName, src, dst) => {
|
|||
};
|
||||
|
||||
// Similar to WebRequestEventManager but for WebNavigation.
|
||||
function WebNavigationEventManager(context, eventName) {
|
||||
let name = `webNavigation.${eventName}`;
|
||||
let register = (fire, urlFilters) => {
|
||||
// Don't create a MatchURLFilters instance if the listener does not include any filter.
|
||||
let filters = urlFilters ? new MatchURLFilters(urlFilters.url) : null;
|
||||
class WebNavigationEventManager extends EventManager {
|
||||
constructor(context, eventName) {
|
||||
let name = `webNavigation.${eventName}`;
|
||||
let register = (fire, urlFilters) => {
|
||||
// Don't create a MatchURLFilters instance if the listener does not include any filter.
|
||||
let filters = urlFilters ? new MatchURLFilters(urlFilters.url) : null;
|
||||
|
||||
let listener = data => {
|
||||
if (!data.browser) {
|
||||
return;
|
||||
}
|
||||
let listener = data => {
|
||||
if (!data.browser) {
|
||||
return;
|
||||
}
|
||||
|
||||
let data2 = {
|
||||
url: data.url,
|
||||
timeStamp: Date.now(),
|
||||
let data2 = {
|
||||
url: data.url,
|
||||
timeStamp: Date.now(),
|
||||
};
|
||||
|
||||
if (eventName == "onErrorOccurred") {
|
||||
data2.error = data.error;
|
||||
}
|
||||
|
||||
if (data.frameId != undefined) {
|
||||
data2.frameId = data.frameId;
|
||||
data2.parentFrameId = data.parentFrameId;
|
||||
}
|
||||
|
||||
if (data.sourceFrameId != undefined) {
|
||||
data2.sourceFrameId = data.sourceFrameId;
|
||||
}
|
||||
|
||||
// Fills in tabId typically.
|
||||
Object.assign(data2, tabTracker.getBrowserData(data.browser));
|
||||
if (data2.tabId < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.sourceTabBrowser) {
|
||||
data2.sourceTabId = tabTracker.getBrowserData(data.sourceTabBrowser).tabId;
|
||||
}
|
||||
|
||||
fillTransitionProperties(eventName, data, data2);
|
||||
|
||||
fire.async(data2);
|
||||
};
|
||||
|
||||
if (eventName == "onErrorOccurred") {
|
||||
data2.error = data.error;
|
||||
}
|
||||
|
||||
if (data.frameId != undefined) {
|
||||
data2.frameId = data.frameId;
|
||||
data2.parentFrameId = data.parentFrameId;
|
||||
}
|
||||
|
||||
if (data.sourceFrameId != undefined) {
|
||||
data2.sourceFrameId = data.sourceFrameId;
|
||||
}
|
||||
|
||||
// Fills in tabId typically.
|
||||
Object.assign(data2, tabTracker.getBrowserData(data.browser));
|
||||
if (data2.tabId < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.sourceTabBrowser) {
|
||||
data2.sourceTabId = tabTracker.getBrowserData(data.sourceTabBrowser).tabId;
|
||||
}
|
||||
|
||||
fillTransitionProperties(eventName, data, data2);
|
||||
|
||||
fire.async(data2);
|
||||
WebNavigation[eventName].addListener(listener, filters);
|
||||
return () => {
|
||||
WebNavigation[eventName].removeListener(listener);
|
||||
};
|
||||
};
|
||||
|
||||
WebNavigation[eventName].addListener(listener, filters);
|
||||
return () => {
|
||||
WebNavigation[eventName].removeListener(listener);
|
||||
};
|
||||
};
|
||||
|
||||
return EventManager.call(this, context, name, register);
|
||||
super({context, name, register});
|
||||
}
|
||||
}
|
||||
|
||||
WebNavigationEventManager.prototype = Object.create(EventManager.prototype);
|
||||
|
||||
const convertGetFrameResult = (tabId, data) => {
|
||||
return {
|
||||
errorOccurred: data.errorOccurred,
|
||||
|
@ -161,8 +161,12 @@ this.webNavigation = class extends ExtensionAPI {
|
|||
|
||||
return {
|
||||
webNavigation: {
|
||||
onTabReplaced: new EventManager(context, "webNavigation.onTabReplaced", fire => {
|
||||
return () => {};
|
||||
onTabReplaced: new EventManager({
|
||||
context,
|
||||
name: "webNavigation.onTabReplaced",
|
||||
register: fire => {
|
||||
return () => {};
|
||||
},
|
||||
}).api(),
|
||||
onBeforeNavigate: new WebNavigationEventManager(context, "onBeforeNavigate").api(),
|
||||
onCommitted: new WebNavigationEventManager(context, "onCommitted").api(),
|
||||
|
|
|
@ -81,24 +81,22 @@ function WebRequestEventManager(context, eventName) {
|
|||
};
|
||||
};
|
||||
|
||||
return EventManager.call(this, context, name, register);
|
||||
return new EventManager({context, name, register}).api();
|
||||
}
|
||||
|
||||
WebRequestEventManager.prototype = Object.create(EventManager.prototype);
|
||||
|
||||
this.webRequest = class extends ExtensionAPI {
|
||||
getAPI(context) {
|
||||
return {
|
||||
webRequest: {
|
||||
onBeforeRequest: new WebRequestEventManager(context, "onBeforeRequest").api(),
|
||||
onBeforeSendHeaders: new WebRequestEventManager(context, "onBeforeSendHeaders").api(),
|
||||
onSendHeaders: new WebRequestEventManager(context, "onSendHeaders").api(),
|
||||
onHeadersReceived: new WebRequestEventManager(context, "onHeadersReceived").api(),
|
||||
onAuthRequired: new WebRequestEventManager(context, "onAuthRequired").api(),
|
||||
onBeforeRedirect: new WebRequestEventManager(context, "onBeforeRedirect").api(),
|
||||
onResponseStarted: new WebRequestEventManager(context, "onResponseStarted").api(),
|
||||
onErrorOccurred: new WebRequestEventManager(context, "onErrorOccurred").api(),
|
||||
onCompleted: new WebRequestEventManager(context, "onCompleted").api(),
|
||||
onBeforeRequest: WebRequestEventManager(context, "onBeforeRequest"),
|
||||
onBeforeSendHeaders: WebRequestEventManager(context, "onBeforeSendHeaders"),
|
||||
onSendHeaders: WebRequestEventManager(context, "onSendHeaders"),
|
||||
onHeadersReceived: WebRequestEventManager(context, "onHeadersReceived"),
|
||||
onAuthRequired: WebRequestEventManager(context, "onAuthRequired"),
|
||||
onBeforeRedirect: WebRequestEventManager(context, "onBeforeRedirect"),
|
||||
onResponseStarted: WebRequestEventManager(context, "onResponseStarted"),
|
||||
onErrorOccurred: WebRequestEventManager(context, "onErrorOccurred"),
|
||||
onCompleted: WebRequestEventManager(context, "onCompleted"),
|
||||
handlerBehaviorChanged: function() {
|
||||
// TODO: Flush all caches.
|
||||
},
|
||||
|
|
|
@ -65,11 +65,15 @@ add_task(async function test_post_unload_listeners() {
|
|||
let context = new StubContext();
|
||||
|
||||
let fire;
|
||||
let manager = new EventManager(context, "EventManager", _fire => {
|
||||
fire = () => {
|
||||
_fire.async();
|
||||
};
|
||||
return () => {};
|
||||
let manager = new EventManager({
|
||||
context,
|
||||
name: "EventManager",
|
||||
register: _fire => {
|
||||
fire = () => {
|
||||
_fire.async();
|
||||
};
|
||||
return () => {};
|
||||
},
|
||||
});
|
||||
|
||||
let fail = event => {
|
||||
|
|
|
@ -0,0 +1,355 @@
|
|||
"use strict";
|
||||
|
||||
PromiseTestUtils.whitelistRejectionsGlobally(/Message manager disconnected/);
|
||||
|
||||
Cu.importGlobalProperties(["Blob", "URL"]);
|
||||
|
||||
ChromeUtils.import("resource://gre/modules/ExtensionCommon.jsm");
|
||||
const {ExtensionAPI} = ExtensionCommon;
|
||||
|
||||
const SCHEMA = [
|
||||
{
|
||||
namespace: "eventtest",
|
||||
events: [
|
||||
{
|
||||
name: "onEvent1",
|
||||
type: "function",
|
||||
extraParameters: [{type: "any"}],
|
||||
},
|
||||
{
|
||||
name: "onEvent2",
|
||||
type: "function",
|
||||
extraParameters: [{type: "any"}],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// The code in this class does not actually run in this test scope, it is
|
||||
// serialized into a string which is later loaded by the WebExtensions
|
||||
// framework in the same context as other extension APIs. By writing it
|
||||
// this way rather than as a big string constant we get lint coverage.
|
||||
// But eslint doesn't understand that this code runs in a different context
|
||||
// where the EventManager class is available so just tell it here:
|
||||
/* global EventManager */
|
||||
const API = class extends ExtensionAPI {
|
||||
primeListener(extension, event, fire, params) {
|
||||
let data = {wrappedJSObject: {event, params}};
|
||||
Services.obs.notifyObservers(data, "prime-event-listener");
|
||||
|
||||
const FIRE_TOPIC = `fire-${event}`;
|
||||
|
||||
async function listener(subject, topic, _data) {
|
||||
try {
|
||||
await fire.async(subject.wrappedJSObject);
|
||||
} catch (err) {
|
||||
Services.obs.notifyObservers(data, "listener-callback-exception");
|
||||
}
|
||||
}
|
||||
Services.obs.addObserver(listener, FIRE_TOPIC);
|
||||
|
||||
return {
|
||||
unregister() {
|
||||
Services.obs.notifyObservers(data, "unregister-primed-listener");
|
||||
Services.obs.removeObserver(listener, FIRE_TOPIC);
|
||||
},
|
||||
convert(_fire) {
|
||||
Services.obs.notifyObservers(data, "convert-event-listener");
|
||||
fire = _fire;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
getAPI(context) {
|
||||
return {
|
||||
eventtest: {
|
||||
onEvent1: new EventManager({
|
||||
context,
|
||||
name: "test.event1",
|
||||
persistent: {
|
||||
module: "eventtest",
|
||||
event: "onEvent1",
|
||||
},
|
||||
register: (fire, ...params) => {
|
||||
let data = {wrappedJSObject: {event: "onEvent1", params}};
|
||||
Services.obs.notifyObservers(data, "register-event-listener");
|
||||
return () => {
|
||||
Services.obs.notifyObservers(data, "unregister-event-listener");
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
|
||||
onEvent2: new EventManager({
|
||||
context,
|
||||
name: "test.event1",
|
||||
persistent: {
|
||||
module: "eventtest",
|
||||
event: "onEvent2",
|
||||
},
|
||||
register: (fire, ...params) => {
|
||||
let data = {wrappedJSObject: {event: "onEvent2", params}};
|
||||
Services.obs.notifyObservers(data, "register-event-listener");
|
||||
return () => {
|
||||
Services.obs.notifyObservers(data, "unregister-event-listener");
|
||||
};
|
||||
},
|
||||
}).api(),
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const API_SCRIPT = `this.eventtest = ${API.toString()}`;
|
||||
|
||||
const MODULE_INFO = {
|
||||
eventtest: {
|
||||
schema: `data:,${JSON.stringify(SCHEMA)}`,
|
||||
scopes: ["addon_parent"],
|
||||
paths: [["eventtest"]],
|
||||
url: URL.createObjectURL(new Blob([API_SCRIPT])),
|
||||
},
|
||||
};
|
||||
|
||||
const global = this;
|
||||
|
||||
// Wait for the given event (topic) to occur a specific number of times
|
||||
// (count). If fn is not supplied, the Promise returned from this function
|
||||
// resolves as soon as that many instances of the event have been observed.
|
||||
// If fn is supplied, this function also waits for the Promise that fn()
|
||||
// returns to complete and ensures that the given event does not occur more
|
||||
// than `count` times before then. On success, resolves with an array
|
||||
// of the subjects from each of the observed events.
|
||||
async function promiseObservable(topic, count, fn = null) {
|
||||
let _countResolve;
|
||||
let results = [];
|
||||
function listener(subject, _topic, data) {
|
||||
results.push(subject.wrappedJSObject);
|
||||
if (results.length > count) {
|
||||
ok(false, `Got unexpected ${topic} event`);
|
||||
} else if (results.length == count) {
|
||||
_countResolve();
|
||||
}
|
||||
}
|
||||
Services.obs.addObserver(listener, topic);
|
||||
|
||||
try {
|
||||
await Promise.all([
|
||||
new Promise(resolve => { _countResolve = resolve; }),
|
||||
fn && fn(),
|
||||
]);
|
||||
} finally {
|
||||
Services.obs.removeObserver(listener, topic);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
add_task(async function() {
|
||||
Services.prefs.setBoolPref("extensions.webextensions.background-delayed-startup", true);
|
||||
|
||||
AddonTestUtils.init(global);
|
||||
AddonTestUtils.overrideCertDB();
|
||||
AddonTestUtils.createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "43");
|
||||
|
||||
await AddonTestUtils.promiseStartupManager();
|
||||
|
||||
ExtensionParent.apiManager.registerModules(MODULE_INFO);
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
useAddonManager: "permanent",
|
||||
background() {
|
||||
let register1 = true, register2 = true;
|
||||
if (localStorage.getItem("skip1")) {
|
||||
register1 = false;
|
||||
}
|
||||
if (localStorage.getItem("skip2")) {
|
||||
register2 = false;
|
||||
}
|
||||
|
||||
let listener1 = arg => browser.test.sendMessage("listener1", arg);
|
||||
let listener2 = arg => browser.test.sendMessage("listener2", arg);
|
||||
let listener3 = arg => browser.test.sendMessage("listener3", arg);
|
||||
|
||||
if (register1) {
|
||||
browser.eventtest.onEvent1.addListener(listener1, "listener1");
|
||||
}
|
||||
if (register2) {
|
||||
browser.eventtest.onEvent1.addListener(listener2, "listener2");
|
||||
browser.eventtest.onEvent2.addListener(listener3, "listener3");
|
||||
}
|
||||
|
||||
browser.test.onMessage.addListener(msg => {
|
||||
if (msg == "unregister2") {
|
||||
browser.eventtest.onEvent2.removeListener(listener3);
|
||||
localStorage.setItem("skip2", true);
|
||||
} else if (msg == "unregister1") {
|
||||
localStorage.setItem("skip1", true);
|
||||
browser.test.sendMessage("unregistered");
|
||||
}
|
||||
});
|
||||
|
||||
browser.test.sendMessage("ready");
|
||||
},
|
||||
});
|
||||
|
||||
function check(info, what, {listener1 = true, listener2 = true, listener3 = true} = {}) {
|
||||
let count = (listener1 ? 1 : 0) + (listener2 ? 1 : 0) + (listener3 ? 1 : 0);
|
||||
equal(info.length, count, `Got ${count} ${what} events`);
|
||||
|
||||
let i = 0;
|
||||
if (listener1) {
|
||||
equal(info[i].event, "onEvent1", `Got ${what} on event1 for listener 1`);
|
||||
deepEqual(info[i].params, ["listener1"], `Got event1 ${what} args for listener 1`);
|
||||
++i;
|
||||
}
|
||||
|
||||
if (listener2) {
|
||||
equal(info[i].event, "onEvent1", `Got ${what} on event1 for listener 2`);
|
||||
deepEqual(info[i].params, ["listener2"], `Got event1 ${what} args for listener 2`);
|
||||
++i;
|
||||
}
|
||||
|
||||
if (listener3) {
|
||||
equal(info[i].event, "onEvent2", `Got ${what} on event2 for listener 3`);
|
||||
deepEqual(info[i].params, ["listener3"], `Got event2 ${what} args for listener 3`);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the regular event registration process occurs when
|
||||
// the extension is installed.
|
||||
let [info] = await Promise.all([
|
||||
promiseObservable("register-event-listener", 3),
|
||||
extension.startup(),
|
||||
]);
|
||||
check(info, "register");
|
||||
|
||||
await extension.awaitMessage("ready");
|
||||
|
||||
// Check that the regular unregister process occurs when
|
||||
// the browser shuts down.
|
||||
[info] = await Promise.all([
|
||||
promiseObservable("unregister-event-listener", 3),
|
||||
new Promise(resolve => extension.extension.once("shutdown", resolve)),
|
||||
AddonTestUtils.promiseShutdownManager(),
|
||||
]);
|
||||
check(info, "unregister");
|
||||
|
||||
// Check that listeners are primed at the next browser startup.
|
||||
[info] = await Promise.all([
|
||||
promiseObservable("prime-event-listener", 3),
|
||||
AddonTestUtils.promiseStartupManager(false),
|
||||
]);
|
||||
check(info, "prime");
|
||||
|
||||
// Check that primed listeners are converted to regular listeners
|
||||
// when the background page is started after browser startup.
|
||||
let p = promiseObservable("convert-event-listener", 3);
|
||||
Services.obs.notifyObservers(null, "sessionstore-windows-restored");
|
||||
info = await p;
|
||||
|
||||
check(info, "convert");
|
||||
|
||||
await extension.awaitMessage("ready");
|
||||
|
||||
// Check that when the event is triggered, all the plumbing worked
|
||||
// correctly for the primed-then-converted listener.
|
||||
let eventDetails = {test: "kaboom"};
|
||||
let eventSubject = {wrappedJSObject: eventDetails};
|
||||
Services.obs.notifyObservers(eventSubject, "fire-onEvent1");
|
||||
|
||||
let details = await extension.awaitMessage("listener1");
|
||||
deepEqual(details, eventDetails, "Listener 1 fired");
|
||||
details = await extension.awaitMessage("listener2");
|
||||
deepEqual(details, eventDetails, "Listener 2 fired");
|
||||
|
||||
// Check that the converted listener is properly unregistered at
|
||||
// browser shutdown.
|
||||
[info] = await Promise.all([
|
||||
promiseObservable("unregister-primed-listener", 3),
|
||||
AddonTestUtils.promiseShutdownManager(),
|
||||
]);
|
||||
check(info, "unregister");
|
||||
|
||||
// Start up again, listener should be primed
|
||||
[info] = await Promise.all([
|
||||
promiseObservable("prime-event-listener", 3),
|
||||
AddonTestUtils.promiseStartupManager(false),
|
||||
]);
|
||||
check(info, "prime");
|
||||
|
||||
// Check that triggering the event before the listener has been converted
|
||||
// causes the background page to be loaded and the listener to be converted,
|
||||
// and the listener is invoked.
|
||||
p = promiseObservable("convert-event-listener", 3);
|
||||
eventDetails.test = "startup event";
|
||||
Services.obs.notifyObservers(eventSubject, "fire-onEvent2");
|
||||
info = await p;
|
||||
|
||||
check(info, "convert");
|
||||
|
||||
details = await extension.awaitMessage("listener3");
|
||||
deepEqual(details, eventDetails, "Listener 3 fired for event during startup");
|
||||
|
||||
await extension.awaitMessage("ready");
|
||||
|
||||
// Check that the unregister process works when we manually remove
|
||||
// a listener.
|
||||
p = promiseObservable("unregister-primed-listener", 1);
|
||||
extension.sendMessage("unregister2");
|
||||
info = await p;
|
||||
check(info, "unregister", {listener1: false, listener2: false});
|
||||
|
||||
// Check that we only get unregisters for the remaining events after
|
||||
// one listener has been removed.
|
||||
info = await promiseObservable("unregister-primed-listener", 2,
|
||||
() => AddonTestUtils.promiseShutdownManager());
|
||||
check(info, "unregister", {listener3: false});
|
||||
|
||||
// Check that after restart, only listeners that were present at
|
||||
// the end of the last session are primed.
|
||||
info = await promiseObservable("prime-event-listener", 2,
|
||||
() => AddonTestUtils.promiseStartupManager(false));
|
||||
check(info, "prime", {listener3: false});
|
||||
|
||||
// Check that if the background script does not re-register listeners,
|
||||
// the primed listeners are unregistered after the background page
|
||||
// starts up.
|
||||
p = promiseObservable("unregister-primed-listener", 1,
|
||||
() => extension.awaitMessage("ready"));
|
||||
Services.obs.notifyObservers(null, "sessionstore-windows-restored");
|
||||
info = await p;
|
||||
check(info, "unregister", {listener1: false, listener3: false});
|
||||
|
||||
// Just listener1 should be registered now, fire event1 to confirm.
|
||||
eventDetails.test = "third time";
|
||||
Services.obs.notifyObservers(eventSubject, "fire-onEvent1");
|
||||
details = await extension.awaitMessage("listener1");
|
||||
deepEqual(details, eventDetails, "Listener 1 fired");
|
||||
|
||||
// Tell the extension not to re-register listener1 on the next startup
|
||||
extension.sendMessage("unregister1");
|
||||
await extension.awaitMessage("unregistered");
|
||||
|
||||
// Shut down, start up
|
||||
info = await promiseObservable("unregister-primed-listener", 1,
|
||||
() => AddonTestUtils.promiseShutdownManager());
|
||||
check(info, "unregister", {listener2: false, listener3: false});
|
||||
|
||||
info = await promiseObservable("prime-event-listener", 1,
|
||||
() => AddonTestUtils.promiseStartupManager(false));
|
||||
check(info, "register", {listener2: false, listener3: false});
|
||||
|
||||
// Check that firing event1 causes the listener fire callback to
|
||||
// reject.
|
||||
p = promiseObservable("listener-callback-exception", 1);
|
||||
Services.obs.notifyObservers(eventSubject, "fire-onEvent1");
|
||||
await p;
|
||||
ok(true, "Primed listener that was not re-registered received an error when event was triggered during startup");
|
||||
|
||||
await extension.awaitMessage("ready");
|
||||
|
||||
await extension.unload();
|
||||
|
||||
await AddonTestUtils.promiseShutdownManager();
|
||||
});
|
|
@ -0,0 +1,40 @@
|
|||
"use strict";
|
||||
|
||||
AddonTestUtils.init(this);
|
||||
AddonTestUtils.overrideCertDB();
|
||||
AddonTestUtils.createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1");
|
||||
|
||||
// Tests that startupData is persisted and is available at startup
|
||||
add_task(async function test_startupData() {
|
||||
await AddonTestUtils.promiseStartupManager();
|
||||
|
||||
let wrapper = ExtensionTestUtils.loadExtension({useAddonManager: "permanent"});
|
||||
await wrapper.startup();
|
||||
|
||||
let {extension} = wrapper;
|
||||
|
||||
deepEqual(extension.startupData, {}, "startupData for a new extension defaults to empty object");
|
||||
|
||||
const DATA = {test: "i am some startup data"};
|
||||
extension.startupData = DATA;
|
||||
extension.saveStartupData();
|
||||
|
||||
await AddonTestUtils.promiseRestartManager();
|
||||
await wrapper.startupPromise;
|
||||
|
||||
({extension} = wrapper);
|
||||
deepEqual(extension.startupData, DATA, "startupData is present on restart");
|
||||
|
||||
const DATA2 = {other: "this is different data"};
|
||||
extension.startupData = DATA2;
|
||||
extension.saveStartupData();
|
||||
|
||||
await AddonTestUtils.promiseRestartManager();
|
||||
await wrapper.startupPromise;
|
||||
|
||||
({extension} = wrapper);
|
||||
deepEqual(extension.startupData, DATA2, "updated startupData is present on restart");
|
||||
|
||||
await wrapper.unload();
|
||||
await AddonTestUtils.promiseShutdownManager();
|
||||
});
|
|
@ -61,6 +61,7 @@ skip-if = (os == "win" && !debug) #Bug 1419183 disable on Windows
|
|||
[test_ext_onmessage_removelistener.js]
|
||||
skip-if = true # This test no longer tests what it is meant to test.
|
||||
[test_ext_permission_xhr.js]
|
||||
[test_ext_persistent_events.js]
|
||||
[test_ext_privacy.js]
|
||||
[test_ext_privacy_disable.js]
|
||||
[test_ext_privacy_update.js]
|
||||
|
@ -83,6 +84,7 @@ skip-if = true # bug 1315829
|
|||
[test_ext_sandbox_var.js]
|
||||
[test_ext_schema.js]
|
||||
[test_ext_simple.js]
|
||||
[test_ext_startupData.js]
|
||||
[test_ext_startup_cache.js]
|
||||
skip-if = os == "android"
|
||||
[test_ext_startup_perf.js]
|
||||
|
|
|
@ -47,7 +47,7 @@ static mozilla::LazyLogModule gResistFingerprintingLog("nsResistFingerprinting")
|
|||
#define RESIST_FINGERPRINTING_PREF "privacy.resistFingerprinting"
|
||||
#define RFP_TIMER_PREF "privacy.reduceTimerPrecision"
|
||||
#define RFP_TIMER_VALUE_PREF "privacy.resistFingerprinting.reduceTimerPrecision.microseconds"
|
||||
#define RFP_TIMER_VALUE_DEFAULT 100
|
||||
#define RFP_TIMER_VALUE_DEFAULT 1000
|
||||
#define RFP_JITTER_VALUE_PREF "privacy.resistFingerprinting.reduceTimerPrecision.jitter"
|
||||
#define RFP_JITTER_VALUE_DEFAULT true
|
||||
#define RFP_SPOOFED_FRAMES_PER_SEC_PREF "privacy.resistFingerprinting.video_frames_per_sec"
|
||||
|
|
|
@ -106,7 +106,14 @@ async function waitForEvent(...eventTypes) {
|
|||
}
|
||||
|
||||
add_task(async function setup() {
|
||||
await SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]});
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
"set": [
|
||||
["media.cache_size", 40000],
|
||||
["full-screen-api.enabled", true],
|
||||
["full-screen-api.allow-trusted-requests-only", false],
|
||||
["full-screen-api.transition-duration.enter", "0 0"],
|
||||
["full-screen-api.transition-duration.leave", "0 0"]
|
||||
]});
|
||||
await new Promise(resolve => {
|
||||
video.addEventListener("canplaythrough", resolve, {once: true});
|
||||
video.src = "seek_with_sound.ogg";
|
||||
|
@ -378,6 +385,28 @@ add_task(async function ensure_video_pause() {
|
|||
}
|
||||
});
|
||||
|
||||
// Bug 1452342: Make sure the cursor hides and shows on full screen mode.
|
||||
add_task(async function ensure_fullscreen_cursor() {
|
||||
video.removeAttribute("mozNoDynamicControls");
|
||||
video.play();
|
||||
await waitForEvent("play");
|
||||
|
||||
video.mozRequestFullScreen();
|
||||
await waitForEvent("mozfullscreenchange");
|
||||
|
||||
const controlsSpacer = getAnonElementWithinVideoByAttribute(video, "anonid", "controlsSpacer");
|
||||
is(controlsSpacer.hasAttribute("hideCursor"), true, "Cursor is hidden");
|
||||
|
||||
// Wiggle the mouse a bit
|
||||
synthesizeMouse(video, playButtonCenterX, playButtonCenterY, { type: "mousemove" });
|
||||
|
||||
is(controlsSpacer.hasAttribute("hideCursor"), false, "Cursor is shown");
|
||||
|
||||
// Restore
|
||||
video.setAttribute("mozNoDynamicControls", "");
|
||||
document.mozCancelFullScreen();
|
||||
});
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
|
|
|
@ -1193,6 +1193,10 @@
|
|||
return;
|
||||
}
|
||||
|
||||
if (element == this.controlBar) {
|
||||
this.controlsSpacer.removeAttribute("hideCursor");
|
||||
}
|
||||
|
||||
// Unhide
|
||||
element.hidden = false;
|
||||
} else {
|
||||
|
|
|
@ -3121,6 +3121,26 @@ var AddonManagerPrivate = {
|
|||
let provider = AddonManagerInternal._getProviderByName("XPIProvider");
|
||||
return provider ? provider.isDBLoaded : false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets startupData for the given addon. The provided data will be stored
|
||||
* in addonsStartup.json so it is available early during browser startup.
|
||||
* Note that this file is read synchronously at startup, so startupData
|
||||
* should be used with care.
|
||||
*
|
||||
* @param {string} aID
|
||||
* The id of the addon to save startup data for.
|
||||
* @param {any} aData
|
||||
* The data to store. Must be JSON serializable.
|
||||
*/
|
||||
setStartupData(aID, aData) {
|
||||
if (!gStarted)
|
||||
throw Components.Exception("AddonManager is not initialized",
|
||||
Cr.NS_ERROR_NOT_INITIALIZED);
|
||||
|
||||
AddonManagerInternal._getProviderByName("XPIProvider")
|
||||
.setStartupData(aID, aData);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -1297,7 +1297,9 @@ class XPIState {
|
|||
|
||||
this.version = aDBAddon.version;
|
||||
this.type = aDBAddon.type;
|
||||
this.startupData = aDBAddon.startupData;
|
||||
if (aDBAddon.startupData) {
|
||||
this.startupData = aDBAddon.startupData;
|
||||
}
|
||||
|
||||
this.bootstrapped = !!aDBAddon.bootstrap;
|
||||
if (this.bootstrapped) {
|
||||
|
@ -3450,6 +3452,23 @@ var XPIProvider = {
|
|||
return addon.wrapper;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets startupData for the given addon. The provided data will be stored
|
||||
* in addonsStartup.json so it is available early during browser startup.
|
||||
* Note that this file is read synchronously at startup, so startupData
|
||||
* should be used with care.
|
||||
*
|
||||
* @param {string} aID
|
||||
* The id of the addon to save startup data for.
|
||||
* @param {any} aData
|
||||
* The data to store. Must be JSON serializable.
|
||||
*/
|
||||
setStartupData(aID, aData) {
|
||||
let state = XPIStates.findAddon(aID);
|
||||
state.startupData = aData;
|
||||
XPIStates.save();
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns an Addon corresponding to an instance ID.
|
||||
* @param aInstanceID
|
||||
|
|
|
@ -38,6 +38,14 @@
|
|||
|
||||
#include "wayland/gtk-primary-selection-client-protocol.h"
|
||||
|
||||
const char*
|
||||
nsRetrievalContextWayland::sTextMimeTypes[TEXT_MIME_TYPES_NUM] =
|
||||
{
|
||||
"text/plain;charset=utf-8",
|
||||
"UTF8_STRING",
|
||||
"COMPOUND_TEXT"
|
||||
};
|
||||
|
||||
void
|
||||
DataOffer::AddMIMEType(const char *aMimeType)
|
||||
{
|
||||
|
@ -64,6 +72,17 @@ DataOffer::GetTargets(int* aTargetNum)
|
|||
return targetList;
|
||||
}
|
||||
|
||||
bool
|
||||
DataOffer::HasTarget(const char *aMimeType)
|
||||
{
|
||||
int length = mTargetMIMETypes.Length();
|
||||
for (int32_t j = 0; j < length; j++) {
|
||||
if (mTargetMIMETypes[j] == gdk_atom_intern(aMimeType, FALSE))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
char*
|
||||
DataOffer::GetData(wl_display* aDisplay, const char* aMimeType,
|
||||
uint32_t* aContentLength)
|
||||
|
@ -699,6 +718,25 @@ nsRetrievalContextWayland::GetClipboardData(const char* aMimeType,
|
|||
return reinterpret_cast<const char*>(mClipboardData);
|
||||
}
|
||||
|
||||
const char*
|
||||
nsRetrievalContextWayland::GetClipboardText(int32_t aWhichClipboard)
|
||||
{
|
||||
GdkAtom selection = GetSelectionAtom(aWhichClipboard);
|
||||
DataOffer* dataOffer = (selection == GDK_SELECTION_PRIMARY) ?
|
||||
mPrimaryOffer : mClipboardOffer;
|
||||
if (!dataOffer)
|
||||
return nullptr;
|
||||
|
||||
for (unsigned int i = 0; i < sizeof(sTextMimeTypes); i++) {
|
||||
if (dataOffer->HasTarget(sTextMimeTypes[i])) {
|
||||
uint32_t unused;
|
||||
return GetClipboardData(sTextMimeTypes[i], aWhichClipboard,
|
||||
&unused);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void nsRetrievalContextWayland::ReleaseClipboardData(const char* aClipboardData)
|
||||
{
|
||||
NS_ASSERTION(aClipboardData == mClipboardData,
|
||||
|
|
|
@ -23,6 +23,8 @@ public:
|
|||
void AddMIMEType(const char *aMimeType);
|
||||
|
||||
GdkAtom* GetTargets(int* aTargetNum);
|
||||
bool HasTarget(const char *aMimeType);
|
||||
|
||||
char* GetData(wl_display* aDisplay, const char* aMimeType,
|
||||
uint32_t* aContentLength);
|
||||
|
||||
|
@ -65,6 +67,7 @@ public:
|
|||
virtual const char* GetClipboardData(const char* aMimeType,
|
||||
int32_t aWhichClipboard,
|
||||
uint32_t* aContentLength) override;
|
||||
virtual const char* GetClipboardText(int32_t aWhichClipboard) override;
|
||||
virtual void ReleaseClipboardData(const char* aClipboardData) override;
|
||||
|
||||
virtual GdkAtom* GetTargets(int32_t aWhichClipboard,
|
||||
|
@ -104,6 +107,11 @@ private:
|
|||
int mClipboardRequestNumber;
|
||||
char* mClipboardData;
|
||||
uint32_t mClipboardDataLength;
|
||||
|
||||
// Mime types used for text data at Gtk+, see request_text_received_func()
|
||||
// at gtkclipboard.c.
|
||||
#define TEXT_MIME_TYPES_NUM 3
|
||||
static const char* sTextMimeTypes[TEXT_MIME_TYPES_NUM];
|
||||
};
|
||||
|
||||
#endif /* __nsClipboardWayland_h_ */
|
||||
|
|
Загрузка…
Ссылка в новой задаче