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

This commit is contained in:
Andreea Pavel
2018-04-10 00:58:54 +03:00
93 changed files with 2803 additions and 4165 deletions

View File

@@ -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))

View File

@@ -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">

View File

@@ -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
// 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); 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;
} }

View File

@@ -33,7 +33,10 @@ this.devtools_network = class extends ExtensionAPI {
return { return {
devtools: { devtools: {
network: { network: {
onRequestFinished: new EventManager(context, "devtools.network.onRequestFinished", fire => { onRequestFinished: new EventManager({
context,
name: "devtools.network.onRequestFinished",
register: fire => {
let onFinished = (data) => { let onFinished = (data) => {
const loader = new ChildNetworkResponseLoader(context, data.requestId); const loader = new ChildNetworkResponseLoader(context, data.requestId);
const harEntry = {...data.harEntry, ...loader.api()}; const harEntry = {...data.harEntry, ...loader.api()};
@@ -48,6 +51,7 @@ this.devtools_network = class extends ExtensionAPI {
return () => { return () => {
parent.removeListener(onFinished); parent.removeListener(onFinished);
}; };
},
}).api(), }).api(),
}, },
}, },

View File

@@ -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,6 +119,7 @@ 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,6 +220,7 @@ class ChildDevToolsInspectorSidebar extends ExtensionUtils.EventEmitter {
return () => { return () => {
this.off("hidden", listener); this.off("hidden", listener);
}; };
},
}).api(), }).api(),
setObject(jsonObject, rootTitle) { setObject(jsonObject, rootTitle) {
@@ -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,6 +302,7 @@ this.devtools_panels = class extends ExtensionAPI {
return () => { return () => {
themeChangeObserver.off("themeChanged", listener); themeChangeObserver.off("themeChanged", listener);
}; };
},
}).api(), }).api(),
}, },
}, },

View File

@@ -159,7 +159,10 @@ 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({
context,
name: "menus.onClicked",
register: fire => {
let listener = (info, tab) => { let listener = (info, tab) => {
withHandlingUserInput(context.contentWindow, withHandlingUserInput(context.contentWindow,
() => fire.sync(info, tab)); () => fire.sync(info, tab));
@@ -170,6 +173,7 @@ this.menusInternal = class extends ExtensionAPI {
return () => { return () => {
event.removeListener(listener); event.removeListener(listener);
}; };
},
}).api(), }).api(),
}, },
}; };

View File

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

View File

@@ -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,

View File

@@ -329,7 +329,10 @@ this.bookmarks = class extends ExtensionAPI {
} }
}, },
onCreated: new EventManager(context, "bookmarks.onCreated", fire => { onCreated: new EventManager({
context,
name: "bookmarks.onCreated",
register: fire => {
let listener = (event, bookmark) => { let listener = (event, bookmark) => {
fire.sync(bookmark.id, bookmark); fire.sync(bookmark.id, bookmark);
}; };
@@ -340,9 +343,13 @@ this.bookmarks = class extends ExtensionAPI {
observer.off("created", listener); observer.off("created", listener);
decrementListeners(); decrementListeners();
}; };
},
}).api(), }).api(),
onRemoved: new EventManager(context, "bookmarks.onRemoved", fire => { onRemoved: new EventManager({
context,
name: "bookmarks.onRemoved",
register: fire => {
let listener = (event, data) => { let listener = (event, data) => {
fire.sync(data.guid, data.info); fire.sync(data.guid, data.info);
}; };
@@ -353,9 +360,13 @@ this.bookmarks = class extends ExtensionAPI {
observer.off("removed", listener); observer.off("removed", listener);
decrementListeners(); decrementListeners();
}; };
},
}).api(), }).api(),
onChanged: new EventManager(context, "bookmarks.onChanged", fire => { onChanged: new EventManager({
context,
name: "bookmarks.onChanged",
register: fire => {
let listener = (event, data) => { let listener = (event, data) => {
fire.sync(data.guid, data.info); fire.sync(data.guid, data.info);
}; };
@@ -366,9 +377,13 @@ this.bookmarks = class extends ExtensionAPI {
observer.off("changed", listener); observer.off("changed", listener);
decrementListeners(); decrementListeners();
}; };
},
}).api(), }).api(),
onMoved: new EventManager(context, "bookmarks.onMoved", fire => { onMoved: new EventManager({
context,
name: "bookmarks.onMoved",
register: fire => {
let listener = (event, data) => { let listener = (event, data) => {
fire.sync(data.guid, data.info); fire.sync(data.guid, data.info);
}; };
@@ -379,6 +394,7 @@ this.bookmarks = class extends ExtensionAPI {
observer.off("moved", listener); observer.off("moved", listener);
decrementListeners(); decrementListeners();
}; };
},
}).api(), }).api(),
}, },
}; };

View File

@@ -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();

View File

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

View File

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

View File

@@ -15,7 +15,10 @@ this.devtools_network = class extends ExtensionAPI {
return { return {
devtools: { devtools: {
network: { network: {
onNavigated: new EventManager(context, "devtools.onNavigated", fire => { onNavigated: new EventManager({
context,
name: "devtools.onNavigated",
register: fire => {
let listener = data => { let listener = data => {
fire.async(data.url); fire.async(data.url);
}; };
@@ -29,13 +32,17 @@ this.devtools_network = class extends ExtensionAPI {
target.off("navigate", listener); 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({
context,
name: "devtools.network.onRequestFinished",
register: fire => {
const listener = (data) => { const listener = (data) => {
fire.async(data); fire.async(data);
}; };
@@ -46,6 +53,7 @@ this.devtools_network = class extends ExtensionAPI {
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

View File

@@ -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,6 +536,7 @@ 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())}`;

View File

@@ -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({
context,
name: "geckoProfiler.onRunning",
register: fire => {
isRunningObserver.addObserver(fire.async); isRunningObserver.addObserver(fire.async);
return () => { return () => {
isRunningObserver.removeObserver(fire.async); isRunningObserver.removeObserver(fire.async);
}; };
},
}).api(), }).api(),
}, },
}; };

View File

@@ -220,7 +220,10 @@ this.history = class extends ExtensionAPI {
return Promise.resolve(results); return Promise.resolve(results);
}, },
onVisited: new EventManager(context, "history.onVisited", fire => { onVisited: new EventManager({
context,
name: "history.onVisited",
register: fire => {
let listener = (event, data) => { let listener = (event, data) => {
fire.sync(data); fire.sync(data);
}; };
@@ -229,9 +232,13 @@ this.history = class extends ExtensionAPI {
return () => { return () => {
getHistoryObserver().off("visited", listener); getHistoryObserver().off("visited", listener);
}; };
},
}).api(), }).api(),
onVisitRemoved: new EventManager(context, "history.onVisitRemoved", fire => { onVisitRemoved: new EventManager({
context,
name: "history.onVisitRemoved",
register: fire => {
let listener = (event, data) => { let listener = (event, data) => {
fire.sync(data); fire.sync(data);
}; };
@@ -240,9 +247,13 @@ this.history = class extends ExtensionAPI {
return () => { return () => {
getHistoryObserver().off("visitRemoved", listener); getHistoryObserver().off("visitRemoved", listener);
}; };
},
}).api(), }).api(),
onTitleChanged: new EventManager(context, "history.onTitleChanged", fire => { onTitleChanged: new EventManager({
context,
name: "history.onTitleChanged",
register: fire => {
let listener = (event, data) => { let listener = (event, data) => {
fire.sync(data); fire.sync(data);
}; };
@@ -251,6 +262,7 @@ this.history = class extends ExtensionAPI {
return () => { return () => {
getHistoryObserver().off("titleChanged", listener); getHistoryObserver().off("titleChanged", listener);
}; };
},
}).api(), }).api(),
}, },
}; };

View File

@@ -813,7 +813,10 @@ this.menusInternal = class extends ExtensionAPI {
gMenuBuilder.rebuildMenu(extension); gMenuBuilder.rebuildMenu(extension);
}, },
onShown: new EventManager(context, "menus.onShown", fire => { onShown: new EventManager({
context,
name: "menus.onShown",
register: fire => {
let listener = (event, menuIds, contextData) => { let listener = (event, menuIds, contextData) => {
let info = { let info = {
menuIds, menuIds,
@@ -838,8 +841,12 @@ this.menusInternal = class extends ExtensionAPI {
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({
context,
name: "menus.onHidden",
register: fire => {
let listener = () => { let listener = () => {
fire.sync(); fire.sync();
}; };
@@ -847,6 +854,7 @@ this.menusInternal = class extends ExtensionAPI {
return () => { return () => {
extension.off("webext-menu-hidden", listener); extension.off("webext-menu-hidden", listener);
}; };
},
}).api(), }).api(),
}; };
@@ -883,7 +891,10 @@ this.menusInternal = class extends ExtensionAPI {
} }
}, },
onClicked: new EventManager(context, "menusInternal.onClicked", fire => { onClicked: new EventManager({
context,
name: "menusInternal.onClicked",
register: fire => {
let listener = (event, info, nativeTab) => { let listener = (event, info, nativeTab) => {
let {linkedBrowser} = nativeTab || tabTracker.activeTab; let {linkedBrowser} = nativeTab || tabTracker.activeTab;
let tab = nativeTab && extension.tabManager.convert(nativeTab); let tab = nativeTab && extension.tabManager.convert(nativeTab);
@@ -895,6 +906,7 @@ this.menusInternal = class extends ExtensionAPI {
return () => { return () => {
extension.off("webext-menu-menuitem-click", listener); extension.off("webext-menu-menuitem-click", listener);
}; };
},
}).api(), }).api(),
}, },
}; };

View File

@@ -37,7 +37,10 @@ this.omnibox = class extends ExtensionAPI {
} }
}, },
onInputStarted: new EventManager(context, "omnibox.onInputStarted", fire => { onInputStarted: new EventManager({
context,
name: "omnibox.onInputStarted",
register: fire => {
let listener = (eventName) => { let listener = (eventName) => {
fire.sync(); fire.sync();
}; };
@@ -45,9 +48,13 @@ this.omnibox = class extends ExtensionAPI {
return () => { return () => {
extension.off(ExtensionSearchHandler.MSG_INPUT_STARTED, listener); extension.off(ExtensionSearchHandler.MSG_INPUT_STARTED, listener);
}; };
},
}).api(), }).api(),
onInputCancelled: new EventManager(context, "omnibox.onInputCancelled", fire => { onInputCancelled: new EventManager({
context,
name: "omnibox.onInputCancelled",
register: fire => {
let listener = (eventName) => { let listener = (eventName) => {
fire.sync(); fire.sync();
}; };
@@ -55,9 +62,13 @@ this.omnibox = class extends ExtensionAPI {
return () => { return () => {
extension.off(ExtensionSearchHandler.MSG_INPUT_CANCELLED, listener); extension.off(ExtensionSearchHandler.MSG_INPUT_CANCELLED, listener);
}; };
},
}).api(), }).api(),
onInputEntered: new EventManager(context, "omnibox.onInputEntered", fire => { onInputEntered: new EventManager({
context,
name: "omnibox.onInputEntered",
register: fire => {
let listener = (eventName, text, disposition) => { let listener = (eventName, text, disposition) => {
fire.sync(text, disposition); fire.sync(text, disposition);
}; };
@@ -65,6 +76,7 @@ this.omnibox = class extends ExtensionAPI {
return () => { return () => {
extension.off(ExtensionSearchHandler.MSG_INPUT_ENTERED, listener); extension.off(ExtensionSearchHandler.MSG_INPUT_ENTERED, listener);
}; };
},
}).api(), }).api(),
// Internal APIs. // Internal APIs.
@@ -77,7 +89,10 @@ this.omnibox = class extends ExtensionAPI {
} }
}, },
onInputChanged: new EventManager(context, "omnibox.onInputChanged", fire => { onInputChanged: new EventManager({
context,
name: "omnibox.onInputChanged",
register: fire => {
let listener = (eventName, text, id) => { let listener = (eventName, text, id) => {
fire.sync(text, id); fire.sync(text, id);
}; };
@@ -85,6 +100,7 @@ this.omnibox = class extends ExtensionAPI {
return () => { return () => {
extension.off(ExtensionSearchHandler.MSG_INPUT_CHANGED, listener); extension.off(ExtensionSearchHandler.MSG_INPUT_CHANGED, listener);
}; };
},
}).api(), }).api(),
}, },
}; };

View File

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

View File

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

View File

@@ -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,7 +342,10 @@ this.tabs = class extends ExtensionAPI {
let self = { let self = {
tabs: { tabs: {
onActivated: new EventManager(context, "tabs.onActivated", fire => { onActivated: new EventManager({
context,
name: "tabs.onActivated",
register: fire => {
let listener = (eventName, event) => { let listener = (eventName, event) => {
fire.async(event); fire.async(event);
}; };
@@ -347,9 +354,13 @@ this.tabs = class extends ExtensionAPI {
return () => { return () => {
tabTracker.off("tab-activated", listener); tabTracker.off("tab-activated", listener);
}; };
},
}).api(), }).api(),
onCreated: new EventManager(context, "tabs.onCreated", fire => { onCreated: new EventManager({
context,
name: "tabs.onCreated",
register: fire => {
let listener = (eventName, event) => { let listener = (eventName, event) => {
fire.async(tabManager.convert(event.nativeTab, event.currentTab)); fire.async(tabManager.convert(event.nativeTab, event.currentTab));
}; };
@@ -358,6 +369,7 @@ this.tabs = class extends ExtensionAPI {
return () => { return () => {
tabTracker.off("tab-created", listener); tabTracker.off("tab-created", listener);
}; };
},
}).api(), }).api(),
/** /**
@@ -366,7 +378,10 @@ 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({
context,
name: "tabs.onHighlighted",
register: fire => {
let listener = (eventName, event) => { let listener = (eventName, event) => {
fire.async({tabIds: [event.tabId], windowId: event.windowId}); fire.async({tabIds: [event.tabId], windowId: event.windowId});
}; };
@@ -375,9 +390,13 @@ this.tabs = class extends ExtensionAPI {
return () => { return () => {
tabTracker.off("tab-activated", listener); tabTracker.off("tab-activated", listener);
}; };
},
}).api(), }).api(),
onAttached: new EventManager(context, "tabs.onAttached", fire => { onAttached: new EventManager({
context,
name: "tabs.onAttached",
register: fire => {
let listener = (eventName, event) => { let listener = (eventName, event) => {
fire.async(event.tabId, {newWindowId: event.newWindowId, newPosition: event.newPosition}); fire.async(event.tabId, {newWindowId: event.newWindowId, newPosition: event.newPosition});
}; };
@@ -386,9 +405,13 @@ this.tabs = class extends ExtensionAPI {
return () => { return () => {
tabTracker.off("tab-attached", listener); tabTracker.off("tab-attached", listener);
}; };
},
}).api(), }).api(),
onDetached: new EventManager(context, "tabs.onDetached", fire => { onDetached: new EventManager({
context,
name: "tabs.onDetached",
register: fire => {
let listener = (eventName, event) => { let listener = (eventName, event) => {
fire.async(event.tabId, {oldWindowId: event.oldWindowId, oldPosition: event.oldPosition}); fire.async(event.tabId, {oldWindowId: event.oldWindowId, oldPosition: event.oldPosition});
}; };
@@ -397,9 +420,13 @@ this.tabs = class extends ExtensionAPI {
return () => { return () => {
tabTracker.off("tab-detached", listener); tabTracker.off("tab-detached", listener);
}; };
},
}).api(), }).api(),
onRemoved: new EventManager(context, "tabs.onRemoved", fire => { onRemoved: new EventManager({
context,
name: "tabs.onRemoved",
register: fire => {
let listener = (eventName, event) => { let listener = (eventName, event) => {
fire.async(event.tabId, {windowId: event.windowId, isWindowClosing: event.isWindowClosing}); fire.async(event.tabId, {windowId: event.windowId, isWindowClosing: event.isWindowClosing});
}; };
@@ -408,13 +435,21 @@ this.tabs = class extends ExtensionAPI {
return () => { return () => {
tabTracker.off("tab-removed", listener); tabTracker.off("tab-removed", listener);
}; };
},
}).api(), }).api(),
onReplaced: new EventManager(context, "tabs.onReplaced", fire => { onReplaced: new EventManager({
context,
name: "tabs.onReplaced",
register: fire => {
return () => {}; return () => {};
},
}).api(), }).api(),
onMoved: new EventManager(context, "tabs.onMoved", fire => { onMoved: new EventManager({
context,
name: "tabs.onMoved",
register: fire => {
// There are certain circumstances where we need to ignore a move event. // There are certain circumstances where we need to ignore a move event.
// //
// Namely, the first time the tab is moved after it's created, we need // Namely, the first time the tab is moved after it's created, we need
@@ -455,9 +490,10 @@ this.tabs = class extends ExtensionAPI {
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,7 +932,10 @@ this.tabs = class extends ExtensionAPI {
return Promise.resolve(); return Promise.resolve();
}, },
onZoomChange: new EventManager(context, "tabs.onZoomChange", fire => { onZoomChange: new EventManager({
context,
name: "tabs.onZoomChange",
register: fire => {
let getZoomLevel = browser => { let getZoomLevel = browser => {
let {ZoomManager} = browser.ownerGlobal; let {ZoomManager} = browser.ownerGlobal;
@@ -965,6 +1004,7 @@ this.tabs = class extends ExtensionAPI {
windowTracker.removeListener("FullZoomChange", zoomListener); windowTracker.removeListener("FullZoomChange", zoomListener);
windowTracker.removeListener("TextZoomChange", zoomListener); windowTracker.removeListener("TextZoomChange", zoomListener);
}; };
},
}).api(), }).api(),
print() { print() {

View File

@@ -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,17 +54,18 @@ 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({
context,
name: "windows.onFocusChanged",
register: fire => {
// Keep track of the last windowId used to fire an onFocusChanged event // Keep track of the last windowId used to fire an onFocusChanged event
let lastOnFocusChangedWindowId; let lastOnFocusChangedWindowId;
@@ -56,6 +87,7 @@ this.windows = class extends ExtensionAPI {
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) {

View File

@@ -3,41 +3,7 @@
/* 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({
background() {
browser.tabs.query({active: true, currentWindow: true}, tabs => {
browser.pageAction.show(tabs[0].id);
});
},
manifest: {
"browser_action": {
"default_popup": "popup.html",
"browser_style": false,
},
"page_action": {
"default_popup": "popup.html",
"browser_style": false,
},
},
files: {
"popup.html": `<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body style="width: 100px; height: 100px; background-color: green;">
</body>
</html>`,
},
});
await extension.startup();
async function testPanel(browser, standAlone) {
let panel = getPanelForNode(browser); let panel = getPanelForNode(browser);
let arrowContent = document.getAnonymousElementByAttribute(panel, "class", "panel-arrowcontent"); let arrowContent = document.getAnonymousElementByAttribute(panel, "class", "panel-arrowcontent");
let arrow = document.getAnonymousElementByAttribute(panel, "anonid", "arrow"); let arrow = document.getAnonymousElementByAttribute(panel, "anonid", "arrow");
@@ -66,28 +32,70 @@ add_task(async function testPopupBackground() {
await new Promise(resolve => setTimeout(resolve, 100)); await new Promise(resolve => setTimeout(resolve, 100));
info("Test that initial background color is applied"); info("Test that initial background color is applied");
checkArrow(initial_background);
checkArrow(await getBackground(browser));
info("Test that dynamically-changed background color is applied"); info("Test that dynamically-changed background color is applied");
await alterContent(browser, setBackground, "black"); await alterContent(browser, setBackground, "black");
checkArrow(await getBackground(browser)); checkArrow(await getBackground(browser));
info("Test that non-opaque background color results in default styling"); info("Test that non-opaque background color results in default styling");
await alterContent(browser, setBackground, "rgba(1, 2, 3, .9)"); await alterContent(browser, setBackground, "rgba(1, 2, 3, .9)");
checkArrow(null); 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"); info("Test stand-alone browserAction popup");
clickBrowserAction(extension); clickBrowserAction(extension);
let browser = await awaitExtensionPanel(extension); let browser = await awaitExtensionPanel(extension);
await testPanel(browser, true); await testPanel(browser, true, testCase.initial_background);
await closeBrowserAction(extension); await closeBrowserAction(extension);
} }
@@ -99,7 +107,7 @@ add_task(async function testPopupBackground() {
clickBrowserAction(extension); clickBrowserAction(extension);
let browser = await awaitExtensionPanel(extension); let browser = await awaitExtensionPanel(extension);
await testPanel(browser, false); await testPanel(browser, false, testCase.initial_background);
await closeBrowserAction(extension); await closeBrowserAction(extension);
} }
@@ -108,9 +116,10 @@ add_task(async function testPopupBackground() {
clickPageAction(extension); clickPageAction(extension);
let browser = await awaitExtensionPanel(extension); let browser = await awaitExtensionPanel(extension);
await testPanel(browser, true); await testPanel(browser, true, testCase.initial_background);
await closePageAction(extension); await closePageAction(extension);
} }
await extension.unload(); await extension.unload();
}
}); });

View File

@@ -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,
}); });

View File

@@ -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;

View File

@@ -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();

View File

@@ -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) => {

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -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.

View File

@@ -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.

View File

@@ -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;
} }

View File

@@ -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;
} }

View File

@@ -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,

View File

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

View File

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

View File

@@ -103,13 +103,16 @@ 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({
context,
name: "tabs.onCreated",
register: fire => {
let listener = (eventName, event) => { let listener = (eventName, event) => {
fire.async(tabManager.convert(event.nativeTab)); fire.async(tabManager.convert(event.nativeTab));
}; };
@@ -118,6 +121,7 @@ this.tabs = class extends ExtensionAPI {
return () => { return () => {
tabTracker.off("tab-created", listener); tabTracker.off("tab-created", listener);
}; };
},
}).api(), }).api(),
/** /**
@@ -126,21 +130,28 @@ 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,
}).api(), name: "tabs.onRemoved",
register: fire => {
onRemoved: new EventManager(context, "tabs.onRemoved", fire => {
let listener = (eventName, event) => { let listener = (eventName, event) => {
fire.async(event.tabId, {windowId: event.windowId, isWindowClosing: event.isWindowClosing}); fire.async(event.tabId, {windowId: event.windowId, isWindowClosing: event.isWindowClosing});
}; };
@@ -149,17 +160,25 @@ this.tabs = class extends ExtensionAPI {
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({
return () => {}; context,
name: "tabs.onMoved",
register: fire => { return () => {}; },
}).api(), }).api(),
onUpdated: new EventManager(context, "tabs.onUpdated", fire => { onUpdated: new EventManager({
context,
name: "tabs.onUpdated",
register: fire => {
const restricted = ["url", "favIconUrl", "title"]; const restricted = ["url", "favIconUrl", "title"];
function sanitize(extension, changeInfo) { function sanitize(extension, changeInfo) {
@@ -234,6 +253,7 @@ this.tabs = class extends ExtensionAPI {
windowTracker.removeListener("status", statusListener); windowTracker.removeListener("status", statusListener);
windowTracker.removeListener("DOMTitleChanged", listener); windowTracker.removeListener("DOMTitleChanged", listener);
}; };
},
}).api(), }).api(),
async create(createProperties) { async create(createProperties) {

View File

@@ -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 {

View File

@@ -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()

View File

@@ -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")))
} }
}) })
} }

View File

@@ -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++

View File

@@ -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)))
}; };
}) })
} }

View File

@@ -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];
}
} }

View File

@@ -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.

View File

@@ -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");

View File

@@ -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);

View File

@@ -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)

View File

@@ -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)) {

View File

@@ -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();

View File

@@ -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):

View 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);

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -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"):

View File

@@ -88,7 +88,7 @@ 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};

View File

@@ -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>`);

View File

@@ -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

View File

@@ -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);

View File

@@ -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") &&

View File

@@ -179,19 +179,27 @@ class Port {
this.postMessage(json); this.postMessage(json);
}, },
onDisconnect: new EventManager(this.context, "Port.onDisconnect", fire => { onDisconnect: new EventManager({
context: this.context,
name: "Port.onDisconnect",
register: fire => {
return this.registerOnDisconnect(holder => { return this.registerOnDisconnect(holder => {
let error = holder && holder.deserialize(this.context.cloneScope); let error = holder && holder.deserialize(this.context.cloneScope);
portError = error && this.context.normalizeError(error); portError = error && this.context.normalizeError(error);
fire.asyncWithoutClone(portObj); fire.asyncWithoutClone(portObj);
}); });
},
}).api(), }).api(),
onMessage: new EventManager(this.context, "Port.onMessage", fire => { onMessage: new EventManager({
context: this.context,
name: "Port.onMessage",
register: fire => {
return this.registerOnMessage(holder => { return this.registerOnMessage(holder => {
let msg = holder.deserialize(this.context.cloneScope); let msg = holder.deserialize(this.context.cloneScope);
fire.asyncWithoutClone(msg, portObj); fire.asyncWithoutClone(msg, portObj);
}); });
},
}).api(), }).api(),
get error() { get error() {
@@ -403,7 +411,10 @@ class Messenger {
} }
_onMessage(name, filter) { _onMessage(name, filter) {
return new EventManager(this.context, name, fire => { return new EventManager({
context: this.context,
name,
register: fire => {
const caller = this.context.getCaller(); const caller = this.context.getCaller();
let listener = { let listener = {
@@ -460,6 +471,7 @@ class Messenger {
return () => { return () => {
MessageChannel.removeListener(this.messageManagers, "Extension:Message", listener); MessageChannel.removeListener(this.messageManagers, "Extension:Message", listener);
}; };
},
}).api(); }).api();
} }
@@ -506,7 +518,10 @@ class Messenger {
} }
_onConnect(name, filter) { _onConnect(name, filter) {
return new EventManager(this.context, name, fire => { return new EventManager({
context: this.context,
name,
register: fire => {
let listener = { let listener = {
messageFilterPermissive: this.optionalFilter, messageFilterPermissive: this.optionalFilter,
messageFilterStrict: this.filter, messageFilterStrict: this.filter,
@@ -541,6 +556,7 @@ class Messenger {
return () => { return () => {
MessageChannel.removeListener(this.messageManagers, "Extension:Connect", listener); MessageChannel.removeListener(this.messageManagers, "Extension:Connect", listener);
}; };
},
}).api(); }).api();
} }

View File

@@ -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,10 +1736,13 @@ 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({
* context,
* name: "api.subAPI",
* register: fire => {
* let listener = (...) => { * let listener = (...) => {
* // Fire any listeners registered with addListener. * // Fire any listeners registered with addListener.
* fire.async(arg1, arg2); * fire.async(arg1, arg2);
@@ -1749,32 +1753,209 @@ defineLazyGetter(LocaleData.prototype, "availableLocales", function() {
* // Return a way to unregister the listener. * // Return a way to unregister the listener.
* SomehowUnregisterListener(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 {
/*
* @param {object} params
* Parameters that control this EventManager.
* @param {BaseContext} params.context
* An object representing the extension instance using this event.
* @param {string} params.name
* A name used only for debugging.
* @param {functon} params.register
* A function called whenever a new listener is added.
* @param {boolean} [params.inputHandling=false]
* If true, the "handling user input" flag is set while handlers
* for this event are executing.
* @param {object} [params.persistent]
* Details for persistent event listeners
* @param {string} params.persistent.module
* The name of the module in which this event is defined.
* @param {string} params.persistent.event
* The name of this event.
*/
constructor(params) {
// Maintain compatibility with the old EventManager API in which
// the constructor took parameters (contest, name, register).
// Remove this in bug 1451212.
if (arguments.length > 1) {
[this.context, this.name, this.register] = arguments;
this.inputHandling = false;
this.persistent = null;
} else {
let {context, name, register, inputHandling = false, persistent = null} = params;
this.context = context; this.context = context;
this.name = name; this.name = name;
this.register = register; this.register = register;
this.unregister = new Map(); this.inputHandling = inputHandling;
this.inputHandling = false; 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) {

View File

@@ -148,7 +148,10 @@ this.storage = class extends ExtensionAPI {
}, },
}, },
onChanged: new EventManager(context, "storage.onChanged", fire => { onChanged: new EventManager({
context,
name: "storage.onChanged",
register: fire => {
let onChanged = (data, area) => { let onChanged = (data, area) => {
let changes = new context.cloneScope.Object(); let changes = new context.cloneScope.Object();
for (let [key, value] of Object.entries(data)) { for (let [key, value] of Object.entries(data)) {
@@ -162,6 +165,7 @@ this.storage = class extends ExtensionAPI {
return () => { return () => {
parent.removeListener(onChanged); parent.removeListener(onChanged);
}; };
},
}).api(), }).api(),
}, },
}; };

View File

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

View File

@@ -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;

View File

@@ -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,

View File

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

View File

@@ -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,13 +57,39 @@ 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() {
this.bgPage.shutdown(); this.bgPage.shutdown();
} }

View File

@@ -224,7 +224,10 @@ this.contextualIdentities = class extends ExtensionAPI {
return convertedIdentity; return convertedIdentity;
}, },
onCreated: new EventManager(context, "contextualIdentities.onCreated", fire => { onCreated: new EventManager({
context,
name: "contextualIdentities.onCreated",
register: fire => {
let observer = (subject, topic) => { let observer = (subject, topic) => {
let convertedIdentity = convertIdentityFromObserver(subject); let convertedIdentity = convertIdentityFromObserver(subject);
if (convertedIdentity) { if (convertedIdentity) {
@@ -236,9 +239,13 @@ this.contextualIdentities = class extends ExtensionAPI {
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({
context,
name: "contextualIdentities.onUpdated",
register: fire => {
let observer = (subject, topic) => { let observer = (subject, topic) => {
let convertedIdentity = convertIdentityFromObserver(subject); let convertedIdentity = convertIdentityFromObserver(subject);
if (convertedIdentity) { if (convertedIdentity) {
@@ -250,9 +257,13 @@ this.contextualIdentities = class extends ExtensionAPI {
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({
context,
name: "contextualIdentities.onRemoved",
register: fire => {
let observer = (subject, topic) => { let observer = (subject, topic) => {
let convertedIdentity = convertIdentityFromObserver(subject); let convertedIdentity = convertIdentityFromObserver(subject);
if (convertedIdentity) { if (convertedIdentity) {
@@ -264,6 +275,7 @@ this.contextualIdentities = class extends ExtensionAPI {
return () => { return () => {
Services.obs.removeObserver(observer, "contextual-identity-deleted"); Services.obs.removeObserver(observer, "contextual-identity-deleted");
}; };
},
}).api(), }).api(),
}, },

View File

@@ -412,7 +412,10 @@ this.cookies = class extends ExtensionAPI {
return Promise.resolve(result); return Promise.resolve(result);
}, },
onChanged: new EventManager(context, "cookies.onChanged", fire => { onChanged: new EventManager({
context,
name: "cookies.onChanged",
register: fire => {
let observer = (subject, topic, data) => { let observer = (subject, topic, data) => {
let notify = (removed, cookie, cause) => { let notify = (removed, cookie, cause) => {
cookie.QueryInterface(Ci.nsICookie2); cookie.QueryInterface(Ci.nsICookie2);
@@ -454,6 +457,7 @@ this.cookies = class extends ExtensionAPI {
Services.obs.removeObserver(observer, "cookie-changed"); Services.obs.removeObserver(observer, "cookie-changed");
Services.obs.removeObserver(observer, "private-cookie-changed"); Services.obs.removeObserver(observer, "private-cookie-changed");
}; };
},
}).api(), }).api(),
}, },
}; };

View File

@@ -765,7 +765,10 @@ this.downloads = class extends ExtensionAPI {
// ... // ...
// } // }
onChanged: new EventManager(context, "downloads.onChanged", fire => { onChanged: new EventManager({
context,
name: "downloads.onChanged",
register: fire => {
const handler = (what, item) => { const handler = (what, item) => {
let changes = {}; let changes = {};
const noundef = val => (val === undefined) ? null : val; const noundef = val => (val === undefined) ? null : val;
@@ -791,9 +794,13 @@ this.downloads = class extends ExtensionAPI {
DownloadMap.off("change", handler); DownloadMap.off("change", handler);
}); });
}; };
},
}).api(), }).api(),
onCreated: new EventManager(context, "downloads.onCreated", fire => { onCreated: new EventManager({
context,
name: "downloads.onCreated",
register: fire => {
const handler = (what, item) => { const handler = (what, item) => {
fire.async(item.serialize()); fire.async(item.serialize());
}; };
@@ -805,9 +812,13 @@ this.downloads = class extends ExtensionAPI {
DownloadMap.off("create", handler); DownloadMap.off("create", handler);
}); });
}; };
},
}).api(), }).api(),
onErased: new EventManager(context, "downloads.onErased", fire => { onErased: new EventManager({
context,
name: "downloads.onErased",
register: fire => {
const handler = (what, item) => { const handler = (what, item) => {
fire.async(item.id); fire.async(item.id);
}; };
@@ -819,6 +830,7 @@ this.downloads = class extends ExtensionAPI {
DownloadMap.off("erase", handler); DownloadMap.off("erase", handler);
}); });
}; };
},
}).api(), }).api(),
onDeterminingFilename: ignoreEvent(context, "downloads.onDeterminingFilename"), onDeterminingFilename: ignoreEvent(context, "downloads.onDeterminingFilename"),

View File

@@ -70,7 +70,10 @@ 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({
context,
name: "idle.onStateChanged",
register: fire => {
let listener = (event, data) => { let listener = (event, data) => {
fire.sync(data); fire.sync(data);
}; };
@@ -79,6 +82,7 @@ this.idle = class extends ExtensionAPI {
return () => { return () => {
getIdleObserver(extension, context).off("stateChanged", listener); getIdleObserver(extension, context).off("stateChanged", listener);
}; };
},
}).api(), }).api(),
}, },
}; };

View File

@@ -230,7 +230,10 @@ this.management = class extends ExtensionAPI {
addon.userDisabled = !enabled; addon.userDisabled = !enabled;
}, },
onDisabled: new EventManager(context, "management.onDisabled", fire => { onDisabled: new EventManager({
context,
name: "management.onDisabled",
register: fire => {
let listener = (event, data) => { let listener = (event, data) => {
fire.async(data); fire.async(data);
}; };
@@ -239,9 +242,13 @@ this.management = class extends ExtensionAPI {
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({
context,
name: "management.onEnabled",
register: fire => {
let listener = (event, data) => { let listener = (event, data) => {
fire.async(data); fire.async(data);
}; };
@@ -250,9 +257,13 @@ this.management = class extends ExtensionAPI {
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({
context,
name: "management.onInstalled",
register: fire => {
let listener = (event, data) => { let listener = (event, data) => {
fire.async(data); fire.async(data);
}; };
@@ -261,9 +272,13 @@ this.management = class extends ExtensionAPI {
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({
context,
name: "management.onUninstalled",
register: fire => {
let listener = (event, data) => { let listener = (event, data) => {
fire.async(data); fire.async(data);
}; };
@@ -272,6 +287,7 @@ this.management = class extends ExtensionAPI {
return () => { return () => {
getManagementListener(extension, context).off("onUninstalled", listener); getManagementListener(extension, context).off("onUninstalled", listener);
}; };
},
}).api(), }).api(),
}, },

View File

@@ -117,7 +117,10 @@ this.notifications = class extends ExtensionAPI {
return Promise.resolve(result); return Promise.resolve(result);
}, },
onClosed: new EventManager(context, "notifications.onClosed", fire => { onClosed: new EventManager({
context,
name: "notifications.onClosed",
register: fire => {
let listener = (event, notificationId) => { let listener = (event, notificationId) => {
// TODO Bug 1413188, Support the byUser argument. // TODO Bug 1413188, Support the byUser argument.
fire.async(notificationId, true); fire.async(notificationId, true);
@@ -127,9 +130,13 @@ this.notifications = class extends ExtensionAPI {
return () => { return () => {
notificationsMap.off("closed", listener); notificationsMap.off("closed", listener);
}; };
},
}).api(), }).api(),
onClicked: new EventManager(context, "notifications.onClicked", fire => { onClicked: new EventManager({
context,
name: "notifications.onClicked",
register: fire => {
let listener = (event, notificationId) => { let listener = (event, notificationId) => {
fire.async(notificationId, true); fire.async(notificationId, true);
}; };
@@ -138,9 +145,13 @@ this.notifications = class extends ExtensionAPI {
return () => { return () => {
notificationsMap.off("clicked", listener); notificationsMap.off("clicked", listener);
}; };
},
}).api(), }).api(),
onShown: new EventManager(context, "notifications.onShown", fire => { onShown: new EventManager({
context,
name: "notifications.onShown",
register: fire => {
let listener = (event, notificationId) => { let listener = (event, notificationId) => {
fire.async(notificationId, true); fire.async(notificationId, true);
}; };
@@ -149,6 +160,7 @@ this.notifications = class extends ExtensionAPI {
return () => { return () => {
notificationsMap.off("shown", listener); notificationsMap.off("shown", listener);
}; };
},
}).api(), }).api(),
// TODO Bug 1190681, implement button support. // TODO Bug 1190681, implement button support.

View File

@@ -43,7 +43,7 @@ class ProxyFilterEventManager extends EventManager {
}; };
}; };
super(context, name, register); super({context, name, register});
} }
} }
@@ -61,7 +61,10 @@ 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({
context,
name: "proxy.onError",
register: fire => {
let listener = (name, error) => { let listener = (name, error) => {
fire.async(error); fire.async(error);
}; };
@@ -69,6 +72,7 @@ this.proxy = class extends ExtensionAPI {
return () => { return () => {
extension.off("proxy-error", listener); extension.off("proxy-error", listener);
}; };
},
}).api(); }).api();
return { return {

View File

@@ -16,7 +16,10 @@ 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({
context,
name: "runtime.onStartup",
register: fire => {
if (context.incognito) { if (context.incognito) {
// This event should not fire if we are operating in a private profile. // This event should not fire if we are operating in a private profile.
return () => {}; return () => {};
@@ -30,9 +33,13 @@ this.runtime = class extends ExtensionAPI {
return () => { return () => {
extension.off("startup", listener); extension.off("startup", listener);
}; };
},
}).api(), }).api(),
onInstalled: new EventManager(context, "runtime.onInstalled", fire => { onInstalled: new EventManager({
context,
name: "runtime.onInstalled",
register: fire => {
let temporary = !!extension.addonData.temporarilyInstalled; let temporary = !!extension.addonData.temporarilyInstalled;
let listener = () => { let listener = () => {
@@ -58,9 +65,13 @@ this.runtime = class extends ExtensionAPI {
return () => { return () => {
extension.off("startup", listener); extension.off("startup", listener);
}; };
},
}).api(), }).api(),
onUpdateAvailable: new EventManager(context, "runtime.onUpdateAvailable", fire => { onUpdateAvailable: new EventManager({
context,
name: "runtime.onUpdateAvailable",
register: fire => {
let instanceID = extension.addonData.instanceID; let instanceID = extension.addonData.instanceID;
AddonManager.addUpgradeListener(instanceID, upgrade => { AddonManager.addUpgradeListener(instanceID, upgrade => {
extension.upgrade = upgrade; extension.upgrade = upgrade;
@@ -74,6 +85,7 @@ this.runtime = class extends ExtensionAPI {
// This can happen if we try this after shutdown is complete. // This can happen if we try this after shutdown is complete.
}); });
}; };
},
}).api(), }).api(),
reload: () => { reload: () => {

View File

@@ -89,7 +89,10 @@ this.storage = class extends ExtensionAPI {
}, },
}, },
onChanged: new EventManager(context, "storage.onChanged", fire => { onChanged: new EventManager({
context,
name: "storage.onChanged",
register: fire => {
let listenerLocal = changes => { let listenerLocal = changes => {
fire.raw(changes, "local"); fire.raw(changes, "local");
}; };
@@ -103,6 +106,7 @@ this.storage = class extends ExtensionAPI {
ExtensionStorage.removeOnChangedListener(extension.id, listenerLocal); ExtensionStorage.removeOnChangedListener(extension.id, listenerLocal);
extensionStorageSync.removeOnChangedListener(extension, listenerSync); extensionStorageSync.removeOnChangedListener(extension, listenerSync);
}; };
},
}).api(), }).api(),
}, },
}; };

View File

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

View File

@@ -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");

View File

@@ -90,7 +90,8 @@ 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 {
constructor(context, eventName) {
let name = `webNavigation.${eventName}`; let name = `webNavigation.${eventName}`;
let register = (fire, urlFilters) => { let register = (fire, urlFilters) => {
// Don't create a MatchURLFilters instance if the listener does not include any filter. // Don't create a MatchURLFilters instance if the listener does not include any filter.
@@ -140,11 +141,10 @@ function WebNavigationEventManager(context, eventName) {
}; };
}; };
return EventManager.call(this, context, name, register); super({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({
context,
name: "webNavigation.onTabReplaced",
register: fire => {
return () => {}; 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(),

View File

@@ -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.
}, },

View File

@@ -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({
context,
name: "EventManager",
register: _fire => {
fire = () => { fire = () => {
_fire.async(); _fire.async();
}; };
return () => {}; return () => {};
},
}); });
let fail = event => { let fail = event => {

View File

@@ -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();
});

View File

@@ -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();
});

View File

@@ -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]

View File

@@ -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"

View File

@@ -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>

View File

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

View File

@@ -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);
},
}; };
/** /**

View File

@@ -1297,7 +1297,9 @@ class XPIState {
this.version = aDBAddon.version; this.version = aDBAddon.version;
this.type = aDBAddon.type; this.type = aDBAddon.type;
if (aDBAddon.startupData) {
this.startupData = 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

View File

@@ -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,

View File

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