mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-27 06:43:32 +00:00
Bug 1894024 - mailto: remind users to finish the configuration of a webmailer, r=Gijs,firefox-desktop-core-reviewers
Differential Revision: https://phabricator.services.mozilla.com/D209067
This commit is contained in:
parent
cee070dc5a
commit
d19ae14789
@ -3043,9 +3043,13 @@ pref("browser.privatebrowsing.resetPBM.showConfirmationDialog", true);
|
||||
// the preferences related to the Nimbus experiment, to activate and deactivate
|
||||
// the the entire rollout (see: bug 1864216 - two prompts, 1877500 - set two in one prompt)
|
||||
pref("browser.mailto.dualPrompt", false);
|
||||
// When visiting a site which uses registerProtocolHandler: Ask the user to set Firefox as
|
||||
// default mailto handler.
|
||||
pref("browser.mailto.prompt.os", true);
|
||||
// Display a reminder prompt for known webmailers if the prompt was not
|
||||
// dismissed before the next visit of that webmailer.
|
||||
pref("browser.mailto.dualPrompt.onLocationChange", false);
|
||||
// configures after how many minutes is the prompt shown again after it was
|
||||
// dismissed. This differs from clicking the 'x' button and 'not now' (forever=0)
|
||||
pref("browser.mailto.dualPrompt.dismissNotNowMinutes", 525600); // one year
|
||||
pref("browser.mailto.dualPrompt.dismissXClickMinutes", 1440); // one day
|
||||
|
||||
// Pref to initialize the BackupService soon after startup.
|
||||
pref("browser.backup.enabled", true);
|
||||
|
@ -4348,6 +4348,8 @@ var TabsProgressListener = {
|
||||
return;
|
||||
}
|
||||
|
||||
Services.obs.notifyObservers(aBrowser, "mailto::onLocationChange", aFlags);
|
||||
|
||||
// Only need to call locationChange if the PopupNotifications object
|
||||
// for this window has already been initialized (i.e. its getter no
|
||||
// longer exists)
|
||||
|
@ -100,6 +100,8 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
||||
UIState: "resource://services-sync/UIState.sys.mjs",
|
||||
UrlbarPrefs: "resource:///modules/UrlbarPrefs.sys.mjs",
|
||||
WebChannel: "resource://gre/modules/WebChannel.sys.mjs",
|
||||
WebProtocolHandlerRegistrar:
|
||||
"resource:///modules/WebProtocolHandlerRegistrar.sys.mjs",
|
||||
WindowsLaunchOnLogin: "resource://gre/modules/WindowsLaunchOnLogin.sys.mjs",
|
||||
WindowsRegistry: "resource://gre/modules/WindowsRegistry.sys.mjs",
|
||||
WindowsGPOParser: "resource://gre/modules/policies/WindowsGPOParser.sys.mjs",
|
||||
@ -2900,6 +2902,13 @@ BrowserGlue.prototype = {
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "webProtocolHandlerService.asyncInit",
|
||||
task: () => {
|
||||
lazy.WebProtocolHandlerRegistrar.prototype.init(true);
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "RFPHelper.init",
|
||||
task: () => {
|
||||
|
@ -3,15 +3,25 @@
|
||||
* 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/. */
|
||||
|
||||
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
|
||||
|
||||
const STRING_BUNDLE_URI = "chrome://browser/locale/feeds/subscribe.properties";
|
||||
|
||||
export function WebProtocolHandlerRegistrar() {}
|
||||
|
||||
const lazy = {};
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetters(lazy, {
|
||||
ExternalProtocolService: [
|
||||
"@mozilla.org/uriloader/external-protocol-service;1",
|
||||
"nsIExternalProtocolService",
|
||||
],
|
||||
});
|
||||
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
|
||||
PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
|
||||
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
|
||||
});
|
||||
|
||||
ChromeUtils.defineLazyGetter(lazy, "log", () => {
|
||||
@ -44,14 +54,138 @@ WebProtocolHandlerRegistrar.prototype = {
|
||||
return this.stringBundle.GetStringFromName(key);
|
||||
},
|
||||
|
||||
/* Because we want to iterate over the known webmailers in the observe method
|
||||
* and with each site visited, we want to check as fast as possible if the
|
||||
* current site is already registered as a mailto handler. Using the sites
|
||||
* domain name as a key ensures that we can use Map.has(...) later to find
|
||||
* it.
|
||||
*/
|
||||
_addedObservers: 0,
|
||||
_knownWebmailerCache: new Map(),
|
||||
_ensureWebmailerCache() {
|
||||
this._knownWebmailerCache = new Map();
|
||||
|
||||
const handler =
|
||||
lazy.ExternalProtocolService.getProtocolHandlerInfo("mailto");
|
||||
|
||||
for (const h of handler.possibleApplicationHandlers.enumerate()) {
|
||||
// Services.io.newURI could fail for broken handlers in which case we
|
||||
// simply leave them out, but write a debug message (just in case)
|
||||
try {
|
||||
if (h instanceof Ci.nsIWebHandlerApp && h.uriTemplate) {
|
||||
const mailerUri = Services.io.newURI(h.uriTemplate);
|
||||
if (mailerUri.scheme == "https") {
|
||||
this._knownWebmailerCache.set(
|
||||
Services.io.newURI(h.uriTemplate).host,
|
||||
{
|
||||
uriPath: Services.io.newURI(h.uriTemplate).resolve("."),
|
||||
uriTemplate: Services.io.newURI(h.uriTemplate),
|
||||
name: h.name,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
lazy.log.debug(`Could not add ${h.uriTemplate} to cache: ${e.message}`);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/* This function can be called multiple times and (re-)initializes the cache
|
||||
* if the feature is toggled on. If called with the feature off it will also
|
||||
* unregister the observers.
|
||||
*
|
||||
* @param {boolean} firstInit
|
||||
*
|
||||
*/
|
||||
init(firstInit = false) {
|
||||
if (firstInit) {
|
||||
lazy.NimbusFeatures.mailto.onUpdate(() =>
|
||||
// make firstInit explicitly false to avoid multiple registrations.
|
||||
this.init(false)
|
||||
);
|
||||
}
|
||||
|
||||
const observers = ["mailto::onLocationChange", "mailto::onClearCache"];
|
||||
if (
|
||||
lazy.NimbusFeatures.mailto.getVariable("dualPrompt") &&
|
||||
lazy.NimbusFeatures.mailto.getVariable("dualPrompt.onLocationChange")
|
||||
) {
|
||||
this._ensureWebmailerCache();
|
||||
observers.forEach(o => {
|
||||
this._addedObservers++;
|
||||
Services.obs.addObserver(this, o);
|
||||
});
|
||||
lazy.log.debug(`mailto observers activated: [${observers}]`);
|
||||
} else {
|
||||
// With `dualPrompt` and `dualPrompt.onLocationChange` toggled on we get
|
||||
// up to two notifications when we turn the feature off again, but we
|
||||
// only want to unregister the observers once.
|
||||
//
|
||||
// Using `hasObservers` would allow us to loop over all observers as long
|
||||
// as there are more, but hasObservers is not implemented hence why we
|
||||
// use `enumerateObservers` here to create the loop and `hasMoreElements`
|
||||
// to return true or false as `hasObservers` would if it existed.
|
||||
observers.forEach(o => {
|
||||
if (
|
||||
0 < this._addedObservers &&
|
||||
Services.obs.enumerateObservers(o).hasMoreElements()
|
||||
) {
|
||||
Services.obs.removeObserver(this, o);
|
||||
this._addedObservers--;
|
||||
lazy.log.debug(`mailto observer "${o}" deactivated.`);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
async observe(browser, topic) {
|
||||
try {
|
||||
switch (topic) {
|
||||
case "mailto::onLocationChange": {
|
||||
// registerProtocolHandler only works for https
|
||||
const uri = browser.currentURI;
|
||||
if (!uri.schemeIs("https")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const host = browser.currentURI.host;
|
||||
if (this._knownWebmailerCache.has(host)) {
|
||||
// second: search the cache for an entry which starts with the path
|
||||
// of the current uri. If it exists we identified the current page as
|
||||
// webmailer (again).
|
||||
const value = this._knownWebmailerCache.get(host);
|
||||
await this._askUserToSetMailtoHandler(
|
||||
browser,
|
||||
"mailto",
|
||||
value.uriTemplate,
|
||||
value.name
|
||||
);
|
||||
}
|
||||
break; // the switch(topic) statement
|
||||
}
|
||||
case "mailto::onClearCache":
|
||||
// clear the cache for now. We could try to dynamically update the
|
||||
// cache, which is easy if a webmailer is added to the settings, but
|
||||
// becomes more complicated when webmailers are removed, because then
|
||||
// the store gets rewritten and we would require an event to deal with
|
||||
// that as well. So instead we recreate it entirely.
|
||||
this._ensureWebmailerCache();
|
||||
break;
|
||||
default:
|
||||
lazy.log.debug(`observe reached with unknown topic: ${topic}`);
|
||||
}
|
||||
} catch (e) {
|
||||
lazy.log.debug(`Problem in observer: ${e}`);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* See nsIWebProtocolHandlerRegistrar
|
||||
*/
|
||||
removeProtocolHandler(aProtocol, aURITemplate) {
|
||||
let eps = Cc[
|
||||
"@mozilla.org/uriloader/external-protocol-service;1"
|
||||
].getService(Ci.nsIExternalProtocolService);
|
||||
let handlerInfo = eps.getProtocolHandlerInfo(aProtocol);
|
||||
let handlerInfo =
|
||||
lazy.ExternalProtocolService.getProtocolHandlerInfo(aProtocol);
|
||||
let handlers = handlerInfo.possibleApplicationHandlers;
|
||||
for (let i = 0; i < handlers.length; i++) {
|
||||
try {
|
||||
@ -81,21 +215,52 @@ WebProtocolHandlerRegistrar.prototype = {
|
||||
* @returns {boolean} true if it is already registered, false otherwise.
|
||||
*/
|
||||
_protocolHandlerRegistered(aProtocol, aURITemplate) {
|
||||
let eps = Cc[
|
||||
"@mozilla.org/uriloader/external-protocol-service;1"
|
||||
].getService(Ci.nsIExternalProtocolService);
|
||||
let handlerInfo = eps.getProtocolHandlerInfo(aProtocol);
|
||||
let handlerInfo =
|
||||
lazy.ExternalProtocolService.getProtocolHandlerInfo(aProtocol);
|
||||
let handlers = handlerInfo.possibleApplicationHandlers;
|
||||
for (let i = 0; i < handlers.length; i++) {
|
||||
try {
|
||||
// We only want to test web handlers
|
||||
let handler = handlers.queryElementAt(i, Ci.nsIWebHandlerApp);
|
||||
if (handler.uriTemplate == aURITemplate) {
|
||||
return true;
|
||||
}
|
||||
} catch (e) {
|
||||
/* it wasn't a web handler */
|
||||
lazy.log.debug("no protocolhandler registered, because: " + e.message);
|
||||
for (let handler of handlers.enumerate()) {
|
||||
// We only want to test web handlers
|
||||
if (
|
||||
handler instanceof Ci.nsIWebHandlerApp &&
|
||||
handler.uriTemplate == aURITemplate
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if aURITemplate.spec points to the currently configured
|
||||
* handler for aProtocol links and the OS default is also readily configured.
|
||||
* Returns false if some of it can be made default.
|
||||
*
|
||||
* @param {string} aProtocol
|
||||
* The scheme of the web handler we are checking for.
|
||||
* @param {string} aURITemplate
|
||||
* The URI template that the handler uses to handle the protocol.
|
||||
*/
|
||||
_isProtocolHandlerDefault(aProtocol, aURITemplate) {
|
||||
const handlerInfo =
|
||||
lazy.ExternalProtocolService.getProtocolHandlerInfo(aProtocol);
|
||||
|
||||
if (
|
||||
handlerInfo.preferredAction == Ci.nsIHandlerInfo.useHelperApp &&
|
||||
handlerInfo.preferredApplicationHandler instanceof Ci.nsIWebHandlerApp
|
||||
) {
|
||||
let webHandlerApp =
|
||||
handlerInfo.preferredApplicationHandler.QueryInterface(
|
||||
Ci.nsIWebHandlerApp
|
||||
);
|
||||
|
||||
// If we are already configured as default, we cannot set a new default
|
||||
// and if the current site is already registered as default webmailer we
|
||||
// are fully set up as the default app for webmail.
|
||||
if (
|
||||
!this._canSetOSDefault(aProtocol) &&
|
||||
webHandlerApp.uriTemplate == aURITemplate.spec
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@ -191,43 +356,6 @@ WebProtocolHandlerRegistrar.prototype = {
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Private method, which returns true only if the OS default handler for
|
||||
* aProtocol is us and the configured mailto handler for us is aURI.spec.
|
||||
*
|
||||
* @param {string} aProtocol
|
||||
* @param {nsIURI} aURI
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_isOsAndLocalDefault(aProtocol, aURI, aTitle) {
|
||||
let eps = Cc[
|
||||
"@mozilla.org/uriloader/external-protocol-service;1"
|
||||
].getService(Ci.nsIExternalProtocolService);
|
||||
|
||||
let pah = eps.getProtocolHandlerInfo(aProtocol).preferredApplicationHandler;
|
||||
|
||||
// that means that always ask is configure or at least no web handler, so
|
||||
// the answer if this is site/aURI.spec a local default is no/false.
|
||||
if (!pah) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let webHandlerApp = pah.QueryInterface(Ci.nsIWebHandlerApp);
|
||||
if (
|
||||
webHandlerApp.uriTemplate != aURI.spec ||
|
||||
webHandlerApp.name != aTitle
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the protocol handler is already registered
|
||||
if (!this._protocolHandlerRegistered(aProtocol, aURI.spec)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Private method to set the default uri to handle a certain protocol. This
|
||||
* automates in a way what a user can do in settings under applications,
|
||||
@ -236,40 +364,33 @@ WebProtocolHandlerRegistrar.prototype = {
|
||||
* @param {string} protocol
|
||||
* @param {handler} handler
|
||||
*/
|
||||
_setLocalDefault(protocol, handler) {
|
||||
let eps = Cc[
|
||||
"@mozilla.org/uriloader/external-protocol-service;1"
|
||||
].getService(Ci.nsIExternalProtocolService);
|
||||
|
||||
let handlerInfo = eps.getProtocolHandlerInfo(protocol);
|
||||
handlerInfo.preferredAction = Ci.nsIHandlerInfo.useHelperApp; // this is IMPORTANT!
|
||||
_setProtocolHandlerDefault(protocol, handler) {
|
||||
let handlerInfo =
|
||||
lazy.ExternalProtocolService.getProtocolHandlerInfo(protocol);
|
||||
handlerInfo.preferredAction = Ci.nsIHandlerInfo.useHelperApp;
|
||||
handlerInfo.preferredApplicationHandler = handler;
|
||||
handlerInfo.alwaysAskBeforeHandling = false;
|
||||
let hs = Cc["@mozilla.org/uriloader/handler-service;1"].getService(
|
||||
Ci.nsIHandlerService
|
||||
);
|
||||
hs.store(handlerInfo);
|
||||
return handlerInfo;
|
||||
},
|
||||
|
||||
/**
|
||||
* Private method to set the default uri to handle a certain protocol. This
|
||||
* automates in a way what a user can do in settings under applications,
|
||||
* where different 'actions' can be chosen for different 'content types'.
|
||||
* Private method to add a ProtocolHandler of type nsIWebHandlerApp to the
|
||||
* list of possible handlers for a protocol.
|
||||
*
|
||||
* @param {string} protocol - e.g. 'mailto', so again without ://
|
||||
* @param {string} name - the protocol associated 'Action'
|
||||
* @param {string} uri - the uri (compare 'use other...' in the preferences)
|
||||
* @returns {handler} handler - either the existing one or a newly created
|
||||
*/
|
||||
_addLocal(protocol, name, uri) {
|
||||
let eps = Cc[
|
||||
"@mozilla.org/uriloader/external-protocol-service;1"
|
||||
].getService(Ci.nsIExternalProtocolService);
|
||||
|
||||
let phi = eps.getProtocolHandlerInfo(protocol);
|
||||
_addWebProtocolHandler(protocol, name, uri) {
|
||||
let phi = lazy.ExternalProtocolService.getProtocolHandlerInfo(protocol);
|
||||
// not adding duplicates and bail out with the existing entry
|
||||
for (let h of phi.possibleApplicationHandlers.enumerate()) {
|
||||
if (h.uriTemplate == uri) {
|
||||
if (h instanceof Ci.nsIWebHandlerApp && h.uriTemplate === uri) {
|
||||
return h;
|
||||
}
|
||||
}
|
||||
@ -280,15 +401,10 @@ WebProtocolHandlerRegistrar.prototype = {
|
||||
handler.name = name;
|
||||
handler.uriTemplate = uri;
|
||||
|
||||
let handlerInfo = eps.getProtocolHandlerInfo(protocol);
|
||||
let handlerInfo =
|
||||
lazy.ExternalProtocolService.getProtocolHandlerInfo(protocol);
|
||||
handlerInfo.possibleApplicationHandlers.appendElement(handler);
|
||||
|
||||
// Since the user has agreed to add a new handler, chances are good
|
||||
// that the next time they see a handler of this type, they're going
|
||||
// to want to use it. Reset the handlerInfo to ask before the next
|
||||
// use.
|
||||
handlerInfo.alwaysAskBeforeHandling = true;
|
||||
|
||||
let hs = Cc["@mozilla.org/uriloader/handler-service;1"].getService(
|
||||
Ci.nsIHandlerService
|
||||
);
|
||||
@ -356,6 +472,41 @@ WebProtocolHandlerRegistrar.prototype = {
|
||||
});
|
||||
},
|
||||
|
||||
/* function to look up for a specific stored (in site specific settings)
|
||||
* timestamp and check if it is now older as again_after_minutes.
|
||||
*
|
||||
* @param {string} group
|
||||
* site specific setting group (e.g. domain)
|
||||
* @param {string} setting_name
|
||||
* site specific settings name (e.g. `last_dimissed_ts`)
|
||||
* @param {number} again_after_minutes
|
||||
* length in minutes of time difference allowed between setting and now
|
||||
* @returns {boolean}
|
||||
* true: within again_after_minutes
|
||||
* false: older than again_after_minutes
|
||||
*/
|
||||
async _isStillDismissed(group, setting_name, again_after_minutes = 0) {
|
||||
let lastDismiss = await this._getSiteSpecificSetting(group, setting_name);
|
||||
|
||||
// first: if we find such a value, then the user has already been
|
||||
// interactive with the page
|
||||
if (lastDismiss) {
|
||||
let timeNow = new Date().getTime();
|
||||
let minutesAgo = (timeNow - lastDismiss) / 1000 / 60;
|
||||
if (minutesAgo < again_after_minutes) {
|
||||
lazy.log.debug(
|
||||
`prompt not shown- a site with setting_name '${setting_name}'` +
|
||||
` was last dismissed ${minutesAgo.toFixed(2)} minutes ago and` +
|
||||
` will only be shown again after ${again_after_minutes} minutes.`
|
||||
);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
lazy.log.debug(`no such setting: '${setting_name}'`);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* See nsIWebProtocolHandlerRegistrar
|
||||
*/
|
||||
@ -426,10 +577,8 @@ WebProtocolHandlerRegistrar.prototype = {
|
||||
handler.name = name;
|
||||
handler.uriTemplate = aButtonInfo.protocolInfo.uri;
|
||||
|
||||
let eps = Cc[
|
||||
"@mozilla.org/uriloader/external-protocol-service;1"
|
||||
].getService(Ci.nsIExternalProtocolService);
|
||||
let handlerInfo = eps.getProtocolHandlerInfo(protocol);
|
||||
let handlerInfo =
|
||||
lazy.ExternalProtocolService.getProtocolHandlerInfo(protocol);
|
||||
handlerInfo.possibleApplicationHandlers.appendElement(handler);
|
||||
|
||||
// Since the user has agreed to add a new handler, chances are good
|
||||
@ -475,68 +624,63 @@ WebProtocolHandlerRegistrar.prototype = {
|
||||
* @param {string} aTitle
|
||||
*/
|
||||
async _askUserToSetMailtoHandler(browser, aProtocol, aURI, aTitle) {
|
||||
let currentHandler = this._addLocal(aProtocol, aTitle, aURI.spec);
|
||||
let notificationId = "OS Protocol Registration: " + aProtocol;
|
||||
|
||||
// guard: if we have shown the bar before and it was dismissed: do not show
|
||||
// it again. The pathHash is used to secure the URL by limiting this user
|
||||
// input to a well-defined character set with a fixed length before it gets
|
||||
// written to the underlaying sqlite database.
|
||||
const gMailtoSiteSpecificDismiss = "protocolhandler.mailto.pathHash";
|
||||
let pathHash = lazy.PlacesUtils.md5(aURI.spec, { format: "hex" });
|
||||
let lastHash = await this._getSiteSpecificSetting(
|
||||
aURI.host,
|
||||
gMailtoSiteSpecificDismiss
|
||||
);
|
||||
if (pathHash == lastHash) {
|
||||
// guard: we do not want to reconfigure settings in private browsing mode
|
||||
if (lazy.PrivateBrowsingUtils.isWindowPrivate(browser.ownerGlobal)) {
|
||||
lazy.log.debug("prompt not shown, because this is a private window.");
|
||||
return;
|
||||
}
|
||||
|
||||
// guard: check if everything has been configured to use the current site
|
||||
// as default webmailer and bail out if so.
|
||||
if (this._isProtocolHandlerDefault(aProtocol, aURI)) {
|
||||
lazy.log.debug(
|
||||
"prompt not shown, because a site with the pathHash " +
|
||||
pathHash +
|
||||
" was dismissed before."
|
||||
`prompt not shown, because ${aTitle} is already configured to` +
|
||||
` handle ${aProtocol}-links under ${aURI.spec}.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// guard: do not show the same bar twice on a single day after dismissed
|
||||
// with 'X'
|
||||
const gMailtoSiteSpecificXClick = "protocolhandler.mailto.xclickdate";
|
||||
let lastShown = await this._getSiteSpecificSetting(
|
||||
aURI.host,
|
||||
gMailtoSiteSpecificXClick,
|
||||
null,
|
||||
0
|
||||
);
|
||||
let currentTS = new Date().getTime();
|
||||
let timeRemaining = 24 * 60 * 60 * 1000 - (currentTS - lastShown);
|
||||
if (0 < timeRemaining) {
|
||||
lazy.log.debug(
|
||||
"prompt will only be shown again in " + timeRemaining + " ms."
|
||||
);
|
||||
// guard: bail out if this site has been dismissed before (either by
|
||||
// clicking the 'x' button or the 'not now' button.
|
||||
const pathHash = lazy.PlacesUtils.md5(aURI.spec, { format: "hex" });
|
||||
const aSettingGroup = aURI.host + `/${pathHash}`;
|
||||
|
||||
const mailtoSiteSpecificNotNow = `dismissed`;
|
||||
const mailtoSiteSpecificXClick = `xclicked`;
|
||||
|
||||
if (
|
||||
await this._isStillDismissed(
|
||||
aSettingGroup,
|
||||
mailtoSiteSpecificNotNow,
|
||||
lazy.NimbusFeatures.mailto.getVariable(
|
||||
"dualPrompt.dismissNotNowMinutes"
|
||||
)
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// guard: bail out if already configured as default...
|
||||
if (this._isOsAndLocalDefault(aProtocol, aURI, aTitle)) {
|
||||
lazy.log.debug(
|
||||
"prompt not shown, because " +
|
||||
aTitle +
|
||||
" is already configured" +
|
||||
" to handle " +
|
||||
aProtocol +
|
||||
"-links under " +
|
||||
aURI.spec +
|
||||
" and we are already configured to be the OS default handler."
|
||||
);
|
||||
if (
|
||||
await this._isStillDismissed(
|
||||
aSettingGroup,
|
||||
mailtoSiteSpecificXClick,
|
||||
lazy.NimbusFeatures.mailto.getVariable(
|
||||
"dualPrompt.dismissXClickMinutes"
|
||||
)
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Now show the prompt if there is not already one...
|
||||
let osDefaultNotificationBox = browser
|
||||
.getTabBrowser()
|
||||
.getNotificationBox(browser);
|
||||
|
||||
if (!osDefaultNotificationBox.getNotificationWithValue(notificationId)) {
|
||||
let win = browser.ownerGlobal;
|
||||
win.MozXULElement.insertFTLIfNeeded("branding/brand.ftl");
|
||||
win.MozXULElement.insertFTLIfNeeded("browser/webProtocolHandler.ftl");
|
||||
|
||||
let notification = await osDefaultNotificationBox.appendNotification(
|
||||
@ -552,8 +696,8 @@ WebProtocolHandlerRegistrar.prototype = {
|
||||
// bar again tomorrow...
|
||||
if (eventType === "dismissed") {
|
||||
this._saveSiteSpecificSetting(
|
||||
aURI.host,
|
||||
gMailtoSiteSpecificXClick,
|
||||
aSettingGroup,
|
||||
mailtoSiteSpecificXClick,
|
||||
new Date().getTime()
|
||||
);
|
||||
}
|
||||
@ -564,7 +708,12 @@ WebProtocolHandlerRegistrar.prototype = {
|
||||
"l10n-id": "protocolhandler-mailto-os-handler-yes-button",
|
||||
primary: true,
|
||||
callback: newitem => {
|
||||
this._setLocalDefault(aProtocol, currentHandler);
|
||||
let currentHandler = this._addWebProtocolHandler(
|
||||
aProtocol,
|
||||
aTitle,
|
||||
aURI.spec
|
||||
);
|
||||
this._setProtocolHandlerDefault(aProtocol, currentHandler);
|
||||
Glean.protocolhandlerMailto.promptClicked.set_local_default.add();
|
||||
|
||||
if (this._canSetOSDefault(aProtocol)) {
|
||||
@ -605,9 +754,9 @@ WebProtocolHandlerRegistrar.prototype = {
|
||||
"l10n-id": "protocolhandler-mailto-os-handler-no-button",
|
||||
callback: () => {
|
||||
this._saveSiteSpecificSetting(
|
||||
aURI.host,
|
||||
gMailtoSiteSpecificDismiss,
|
||||
pathHash
|
||||
aSettingGroup,
|
||||
mailtoSiteSpecificNotNow,
|
||||
new Date().getTime()
|
||||
);
|
||||
return false;
|
||||
},
|
||||
@ -615,12 +764,11 @@ WebProtocolHandlerRegistrar.prototype = {
|
||||
]
|
||||
);
|
||||
|
||||
Glean.protocolhandlerMailto.handlerPromptShown.os_default.add();
|
||||
// remove the icon from the infobar, which is automatically assigned
|
||||
// after its priority, because the priority is also an indicator which
|
||||
// type of bar it is, e.g. a warning or error:
|
||||
notification.setAttribute("type", "system");
|
||||
|
||||
Glean.protocolhandlerMailto.handlerPromptShown.os_default.add();
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -3,12 +3,32 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const { ExperimentAPI } = ChromeUtils.importESModule(
|
||||
"resource://nimbus/ExperimentAPI.sys.mjs"
|
||||
);
|
||||
const { ExperimentFakes } = ChromeUtils.importESModule(
|
||||
"resource://testing-common/NimbusTestUtils.sys.mjs"
|
||||
);
|
||||
const WebProtocolHandlerRegistrar = ChromeUtils.importESModule(
|
||||
"resource:///modules/WebProtocolHandlerRegistrar.sys.mjs"
|
||||
).WebProtocolHandlerRegistrar.prototype;
|
||||
|
||||
add_setup(function add_setup() {
|
||||
Services.prefs.setBoolPref("browser.mailto.dualPrompt", true);
|
||||
add_setup(async () => {
|
||||
Services.prefs.setCharPref("browser.protocolhandler.loglevel", "debug");
|
||||
registerCleanupFunction(() => {
|
||||
Services.prefs.clearUserPref("browser.protocolhandler.loglevel");
|
||||
});
|
||||
|
||||
Services.prefs.clearUserPref("browser.mailto.dualPrompt");
|
||||
Services.prefs.clearUserPref("browser.mailto.dualPrompt.onLocationChange");
|
||||
|
||||
await ExperimentAPI.ready();
|
||||
|
||||
WebProtocolHandlerRegistrar._addWebProtocolHandler(
|
||||
protocol,
|
||||
"example.com",
|
||||
"example.com"
|
||||
);
|
||||
});
|
||||
|
||||
/* helper function to delete site specific settings needed to clean up
|
||||
@ -35,23 +55,18 @@ function _deleteSiteSpecificSetting(domain, setting, context = null) {
|
||||
// site specific settings
|
||||
const protocol = "mailto";
|
||||
const subdomain = (Math.random() + 1).toString(36).substring(7);
|
||||
const sss_domain = subdomain + ".example.com";
|
||||
const sss_domain = "example.com";
|
||||
const ss_setting = "system.under.test";
|
||||
|
||||
add_task(async function check_null_value() {
|
||||
Assert.ok(
|
||||
/[a-z0-9].+\.[a-z]+\.[a-z]+/.test(sss_domain),
|
||||
"test the validity of this random domain name before using it for tests: '" +
|
||||
sss_domain +
|
||||
"'"
|
||||
);
|
||||
});
|
||||
const selector_mailto_prompt =
|
||||
'notification-message[message-bar-type="infobar"]' +
|
||||
'[value="OS Protocol Registration: mailto"]';
|
||||
|
||||
add_task(async function check_null_value() {
|
||||
Assert.equal(
|
||||
null,
|
||||
await WebProtocolHandlerRegistrar._getSiteSpecificSetting(
|
||||
sss_domain,
|
||||
`${subdomain}.${sss_domain}`,
|
||||
ss_setting
|
||||
),
|
||||
"site specific setting should initially not exist and return null."
|
||||
@ -62,7 +77,7 @@ add_task(async function check_default_value() {
|
||||
Assert.equal(
|
||||
true,
|
||||
await WebProtocolHandlerRegistrar._getSiteSpecificSetting(
|
||||
sss_domain,
|
||||
`${subdomain}.${sss_domain}`,
|
||||
ss_setting,
|
||||
null,
|
||||
true
|
||||
@ -73,7 +88,7 @@ add_task(async function check_default_value() {
|
||||
|
||||
add_task(async function check_save_value() {
|
||||
WebProtocolHandlerRegistrar._saveSiteSpecificSetting(
|
||||
sss_domain,
|
||||
`${subdomain}.${sss_domain}`,
|
||||
ss_setting,
|
||||
ss_setting
|
||||
);
|
||||
@ -82,12 +97,12 @@ add_task(async function check_save_value() {
|
||||
try {
|
||||
fetchedSiteSpecificSetting =
|
||||
await WebProtocolHandlerRegistrar._getSiteSpecificSetting(
|
||||
sss_domain,
|
||||
`${subdomain}.${sss_domain}`,
|
||||
ss_setting
|
||||
);
|
||||
} finally {
|
||||
// make sure the cleanup happens, no matter what
|
||||
_deleteSiteSpecificSetting(sss_domain, ss_setting);
|
||||
_deleteSiteSpecificSetting(`${subdomain}.${sss_domain}`, ss_setting);
|
||||
}
|
||||
Assert.equal(
|
||||
ss_setting,
|
||||
@ -98,7 +113,7 @@ add_task(async function check_save_value() {
|
||||
Assert.equal(
|
||||
null,
|
||||
await WebProtocolHandlerRegistrar._getSiteSpecificSetting(
|
||||
sss_domain,
|
||||
`${subdomain}.${sss_domain}`,
|
||||
ss_setting
|
||||
),
|
||||
"site specific setting should not exist after delete."
|
||||
@ -113,11 +128,11 @@ add_task(async function check_installHash() {
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function check_addLocal() {
|
||||
let currentHandler = WebProtocolHandlerRegistrar._addLocal(
|
||||
add_task(async function check_addWebProtocolHandler() {
|
||||
let currentHandler = WebProtocolHandlerRegistrar._addWebProtocolHandler(
|
||||
protocol,
|
||||
sss_domain,
|
||||
sss_domain
|
||||
"https://" + sss_domain
|
||||
);
|
||||
|
||||
Assert.equal(
|
||||
@ -126,41 +141,175 @@ add_task(async function check_addLocal() {
|
||||
"does the handler have the right name?"
|
||||
);
|
||||
Assert.equal(
|
||||
sss_domain,
|
||||
"https://" + sss_domain,
|
||||
currentHandler.uriTemplate,
|
||||
"does the handler have the right uri?"
|
||||
);
|
||||
|
||||
WebProtocolHandlerRegistrar._setLocalDefault(currentHandler);
|
||||
|
||||
WebProtocolHandlerRegistrar._setProtocolHandlerDefault(
|
||||
protocol,
|
||||
currentHandler
|
||||
);
|
||||
WebProtocolHandlerRegistrar.removeProtocolHandler(
|
||||
protocol,
|
||||
currentHandler.uriTemplate
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function check_bar_is_shown() {
|
||||
let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
|
||||
browserWindow.windowUtils.disableNonTestMouseEvents(true);
|
||||
browserWindow.document
|
||||
.getElementById("main-window")
|
||||
.removeAttribute("remotecontrol");
|
||||
let browser = browserWindow.gBrowser.selectedBrowser;
|
||||
/* The next test is to ensure that we offer users to configure a webmailer
|
||||
* instead of a custom executable. They will not get a prompt if they have
|
||||
* configured their OS as default handler.
|
||||
*/
|
||||
add_task(async function promptShownForLocalHandler() {
|
||||
let handlerApp = Cc[
|
||||
"@mozilla.org/uriloader/local-handler-app;1"
|
||||
].createInstance(Ci.nsILocalHandlerApp);
|
||||
handlerApp.executable = Services.dirsvc.get("XREExeF", Ci.nsIFile);
|
||||
WebProtocolHandlerRegistrar._setProtocolHandlerDefault(protocol, handlerApp);
|
||||
|
||||
await WebProtocolHandlerRegistrar._askUserToSetMailtoHandler(
|
||||
browser,
|
||||
protocol,
|
||||
Services.io.newURI("https://" + sss_domain),
|
||||
sss_domain
|
||||
);
|
||||
await BrowserTestUtils.withNewTab("https://example.com/", async browser => {
|
||||
await WebProtocolHandlerRegistrar._askUserToSetMailtoHandler(
|
||||
browser,
|
||||
protocol,
|
||||
Services.io.newURI("https://example.com"),
|
||||
"https://example.com"
|
||||
);
|
||||
|
||||
let button_yes = browserWindow.document.querySelector(
|
||||
"[data-l10n-id='protocolhandler-mailto-os-handler-yes-button']"
|
||||
);
|
||||
Assert.notEqual(null, button_yes, "is the yes-button there?");
|
||||
|
||||
let button_no = browserWindow.document.querySelector(
|
||||
"[data-l10n-id='protocolhandler-mailto-os-handler-no-button']"
|
||||
);
|
||||
Assert.notEqual(null, button_no, "is the no-button there?");
|
||||
Assert.notEqual(
|
||||
null,
|
||||
document.querySelector(selector_mailto_prompt),
|
||||
"The prompt is shown when an executable is configured as handler."
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
function test_rollout(
|
||||
dualPrompt = false,
|
||||
onLocationChange = false,
|
||||
dismissNotNowMinutes = 15,
|
||||
dismissXClickMinutes = 15
|
||||
) {
|
||||
return ExperimentFakes.enrollWithFeatureConfig(
|
||||
{
|
||||
featureId: NimbusFeatures.mailto.featureId,
|
||||
value: {
|
||||
dualPrompt,
|
||||
"dualPrompt.onLocationChange": onLocationChange,
|
||||
"dualPrompt.dismissXClickMinutes": dismissXClickMinutes,
|
||||
"dualPrompt.dismissNotNowMinutes": dismissNotNowMinutes,
|
||||
},
|
||||
},
|
||||
{ isRollout: true }
|
||||
);
|
||||
}
|
||||
|
||||
add_task(async function check_no_button() {
|
||||
let cleanup = await test_rollout(true, true);
|
||||
|
||||
const url = "https://" + sss_domain;
|
||||
WebProtocolHandlerRegistrar._addWebProtocolHandler(protocol, sss_domain, url);
|
||||
|
||||
await BrowserTestUtils.withNewTab("https://example.com/", async () => {
|
||||
Assert.notEqual(
|
||||
null,
|
||||
document.querySelector(selector_mailto_prompt),
|
||||
"The prompt is shown with dualPrompt.onLocationChange toggled on."
|
||||
);
|
||||
});
|
||||
|
||||
await BrowserTestUtils.withNewTab("https://example.com/", async browser => {
|
||||
let button_no = document.querySelector(
|
||||
"[data-l10n-id='protocolhandler-mailto-os-handler-no-button']"
|
||||
);
|
||||
Assert.notEqual(null, button_no, "is the no-button there?");
|
||||
|
||||
await button_no.click();
|
||||
Assert.equal(
|
||||
null,
|
||||
document.querySelector(selector_mailto_prompt),
|
||||
"prompt hidden after button_no clicked."
|
||||
);
|
||||
|
||||
await WebProtocolHandlerRegistrar._askUserToSetMailtoHandler(
|
||||
browser,
|
||||
protocol,
|
||||
Services.io.newURI("https://example.com"),
|
||||
"https://example.com"
|
||||
);
|
||||
|
||||
Assert.equal(
|
||||
null,
|
||||
document.querySelector(selector_mailto_prompt),
|
||||
"prompt stays hidden even when called after the no button was clicked."
|
||||
);
|
||||
});
|
||||
|
||||
await cleanup();
|
||||
});
|
||||
|
||||
add_task(async function check_x_button() {
|
||||
let cleanup = await test_rollout(true, true, 0, 15);
|
||||
|
||||
await BrowserTestUtils.withNewTab("https://example.com/", async browser => {
|
||||
Assert.notEqual(
|
||||
null,
|
||||
document.querySelector(selector_mailto_prompt),
|
||||
"prompt gets shown again- the timeout for the no_button was set to zero"
|
||||
);
|
||||
|
||||
let button_x = document
|
||||
.querySelector(".infobar")
|
||||
.shadowRoot.querySelector(".close");
|
||||
Assert.notEqual(null, button_x, "is the x-button there?");
|
||||
|
||||
await button_x.click();
|
||||
Assert.equal(
|
||||
null,
|
||||
document.querySelector(selector_mailto_prompt),
|
||||
"prompt hidden after 'X' button clicked."
|
||||
);
|
||||
|
||||
await WebProtocolHandlerRegistrar._askUserToSetMailtoHandler(
|
||||
browser,
|
||||
protocol,
|
||||
Services.io.newURI("https://example.com"),
|
||||
"https://example.com"
|
||||
);
|
||||
|
||||
Assert.equal(
|
||||
null,
|
||||
document.querySelector(selector_mailto_prompt),
|
||||
"prompt stays hidden even when called after the no button was clicked."
|
||||
);
|
||||
});
|
||||
|
||||
await cleanup();
|
||||
});
|
||||
|
||||
add_task(async function check_x_button() {
|
||||
let cleanup = await test_rollout(true, true, 0, 0);
|
||||
|
||||
await BrowserTestUtils.withNewTab("https://example.com/", async () => {
|
||||
Assert.notEqual(
|
||||
null,
|
||||
document.querySelector(selector_mailto_prompt),
|
||||
"prompt gets shown again- the timeout for the no_button was set to zero"
|
||||
);
|
||||
});
|
||||
|
||||
await cleanup();
|
||||
});
|
||||
|
||||
add_task(async function check_bar_is_not_shown() {
|
||||
let cleanup = await test_rollout(true, false);
|
||||
|
||||
await BrowserTestUtils.withNewTab("https://example.com/", async () => {
|
||||
Assert.equal(
|
||||
null,
|
||||
document.querySelector(selector_mailto_prompt),
|
||||
"Prompt is not shown, because the dualPrompt.onLocationChange is off"
|
||||
);
|
||||
});
|
||||
|
||||
await cleanup();
|
||||
});
|
||||
|
@ -2620,13 +2620,24 @@ mailto:
|
||||
description: >-
|
||||
Can be used to toggle the entire feature on and off.
|
||||
fallbackPref: browser.mailto.dualPrompt
|
||||
dualPrompt.os:
|
||||
dualPrompt.onLocationChange:
|
||||
type: boolean
|
||||
description: >-
|
||||
Make webmail sites display prompts to set Firefox as default OS mailto
|
||||
application and another prompt to set the current site as default
|
||||
webmail site in Firefox.
|
||||
fallbackPref: browser.mailto.dualPrompt.os
|
||||
Display a reminder prompt for known webmailers if the prompt was not
|
||||
dismissed before the next visit of that webmailer.
|
||||
fallbackPref: browser.mailto.dualPrompt.onLocationChange
|
||||
dualPrompt.dismissXClickMinutes:
|
||||
type: int
|
||||
description: >-
|
||||
This pref controls after how many minutes the mailto prompt can be shown
|
||||
again, which has been dismissed by clicking the 'X' button before.
|
||||
fallbackPref: browser.mailto.dualPrompt.dismissXClickMinutes
|
||||
dualPrompt.dismissNotNowMinutes:
|
||||
type: int
|
||||
description: >-
|
||||
This pref controls after how many minutes the mailto prompt can be shown
|
||||
again, which has been dismissed by clicking a 'not now' button on it.
|
||||
fallbackPref: browser.mailto.dualPrompt.dismissNotNowMinutes
|
||||
|
||||
nimbusIsReady:
|
||||
description: A feature that provides the number of Nimbus is_ready events to send
|
||||
|
@ -490,6 +490,10 @@ HandlerService.prototype = {
|
||||
if (handlerInfo.type == "application/pdf") {
|
||||
Services.obs.notifyObservers(null, TOPIC_PDFJS_HANDLER_CHANGED);
|
||||
}
|
||||
|
||||
if (handlerInfo.type == "mailto") {
|
||||
Services.obs.notifyObservers(null, "mailto::onClearCache");
|
||||
}
|
||||
},
|
||||
|
||||
// nsIHandlerService
|
||||
|
Loading…
Reference in New Issue
Block a user