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