refactor: esm waterfox glue
This commit is contained in:
@@ -80,7 +80,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
|||||||
"resource:///modules/WebProtocolHandlerRegistrar.sys.mjs",
|
"resource:///modules/WebProtocolHandlerRegistrar.sys.mjs",
|
||||||
WindowsRegistry: "resource://gre/modules/WindowsRegistry.sys.mjs",
|
WindowsRegistry: "resource://gre/modules/WindowsRegistry.sys.mjs",
|
||||||
setTimeout: "resource://gre/modules/Timer.sys.mjs",
|
setTimeout: "resource://gre/modules/Timer.sys.mjs",
|
||||||
WaterfoxGlue: "resource:///modules/WaterfoxGlue.jsm",
|
WaterfoxGlue: "resource:///modules/WaterfoxGlue.sys.mjs",
|
||||||
});
|
});
|
||||||
|
|
||||||
XPCOMUtils.defineLazyServiceGetters(lazy, {
|
XPCOMUtils.defineLazyServiceGetters(lazy, {
|
||||||
@@ -234,7 +234,7 @@ BrowserGlue.prototype = {
|
|||||||
const { BootstrapLoader } = ChromeUtils.importESModule(
|
const { BootstrapLoader } = ChromeUtils.importESModule(
|
||||||
"resource:///modules/BootstrapLoader.sys.mjs"
|
"resource:///modules/BootstrapLoader.sys.mjs"
|
||||||
);
|
);
|
||||||
AddonManager.addExternalExtensionLoader(BootstrapLoader);
|
lazy.AddonManager.addExternalExtensionLoader(BootstrapLoader);
|
||||||
break;
|
break;
|
||||||
case "notifications-open-settings":
|
case "notifications-open-settings":
|
||||||
this._openPreferences("privacy-permissions");
|
this._openPreferences("privacy-permissions");
|
||||||
@@ -402,7 +402,7 @@ BrowserGlue.prototype = {
|
|||||||
|
|
||||||
lazy.DesktopActorRegistry.init();
|
lazy.DesktopActorRegistry.init();
|
||||||
|
|
||||||
WaterfoxGlue.init();
|
lazy.WaterfoxGlue.init();
|
||||||
},
|
},
|
||||||
|
|
||||||
// cleanup (called on application shutdown)
|
// cleanup (called on application shutdown)
|
||||||
|
|||||||
@@ -2,20 +2,11 @@
|
|||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
const EXPORTED_SYMBOLS = ["WaterfoxGlue"];
|
|
||||||
|
|
||||||
const { XPCOMUtils } = ChromeUtils.import(
|
|
||||||
"resource://gre/modules/XPCOMUtils.jsm"
|
|
||||||
);
|
|
||||||
|
|
||||||
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
|
||||||
|
|
||||||
const lazy = {};
|
const lazy = {};
|
||||||
|
|
||||||
XPCOMUtils.defineLazyModuleGetters(lazy, {
|
ChromeUtils.defineESModuleGetters(lazy, {
|
||||||
AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
|
AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
|
||||||
|
AttributionCode: "resource:///modules/AttributionCode.sys.mjs",
|
||||||
BrowserUtils: "resource:///modules/BrowserUtils.sys.mjs",
|
BrowserUtils: "resource:///modules/BrowserUtils.sys.mjs",
|
||||||
ChromeManifest: "resource:///modules/ChromeManifest.sys.mjs",
|
ChromeManifest: "resource:///modules/ChromeManifest.sys.mjs",
|
||||||
Overlays: "resource:///modules/Overlays.sys.mjs",
|
Overlays: "resource:///modules/Overlays.sys.mjs",
|
||||||
@@ -23,12 +14,24 @@ XPCOMUtils.defineLazyModuleGetters(lazy, {
|
|||||||
PrivateTab: "resource:///modules/PrivateTab.sys.mjs",
|
PrivateTab: "resource:///modules/PrivateTab.sys.mjs",
|
||||||
StatusBar: "resource:///modules/StatusBar.sys.mjs",
|
StatusBar: "resource:///modules/StatusBar.sys.mjs",
|
||||||
TabFeatures: "resource:///modules/TabFeatures.sys.mjs",
|
TabFeatures: "resource:///modules/TabFeatures.sys.mjs",
|
||||||
|
setTimeout: "resource://gre/modules/Timer.sys.mjs",
|
||||||
UICustomizations: "resource:///modules/UICustomizations.sys.mjs",
|
UICustomizations: "resource:///modules/UICustomizations.sys.mjs",
|
||||||
});
|
});
|
||||||
|
|
||||||
XPCOMUtils.defineLazyGlobalGetters(this, ["fetch"]);
|
const WATERFOX_CUSTOMIZATIONS_PREF =
|
||||||
|
"browser.theme.enableWaterfoxCustomizations";
|
||||||
|
|
||||||
const WaterfoxGlue = {
|
const WATERFOX_DEFAULT_THEMES = [
|
||||||
|
"default-theme@mozilla.org",
|
||||||
|
"firefox-compact-light@mozilla.org",
|
||||||
|
"firefox-compact-dark@mozilla.org",
|
||||||
|
"firefox-alpenglow@mozilla.org",
|
||||||
|
];
|
||||||
|
|
||||||
|
const WATERFOX_USERCHROME = "chrome://browser/skin/userChrome.css";
|
||||||
|
const WATERFOX_USERCONTENT = "chrome://browser/skin/userContent.css";
|
||||||
|
|
||||||
|
export const WaterfoxGlue = {
|
||||||
_addonManagersListeners: [],
|
_addonManagersListeners: [],
|
||||||
stylesEnabled: false,
|
stylesEnabled: false,
|
||||||
|
|
||||||
@@ -36,12 +39,25 @@ const WaterfoxGlue = {
|
|||||||
// Set pref observers
|
// Set pref observers
|
||||||
this._setPrefObservers();
|
this._setPrefObservers();
|
||||||
|
|
||||||
// Load always on Waterfox custom CSS.
|
// Load always-on Waterfox custom CSS.
|
||||||
// Add additional CSS here.
|
lazy.BrowserUtils.registerStylesheet(
|
||||||
BrowserUtils.registerStylesheet(
|
|
||||||
"chrome://browser/skin/waterfox/general.css"
|
"chrome://browser/skin/waterfox/general.css"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Maybe load Waterfox stylesheets
|
||||||
|
(async () => {
|
||||||
|
let amInitialized = false;
|
||||||
|
while (!amInitialized) {
|
||||||
|
try {
|
||||||
|
const activeThemeId = await this.getActiveThemeId();
|
||||||
|
this.updateCustomStylesheets({ id: activeThemeId, type: "theme" });
|
||||||
|
amInitialized = true;
|
||||||
|
} catch (ex) {
|
||||||
|
await new Promise(res => lazy.setTimeout(res, 500, {}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
// Parse chrome.manifest
|
// Parse chrome.manifest
|
||||||
this.startupManifest = await this.getChromeManifest("startup");
|
this.startupManifest = await this.getChromeManifest("startup");
|
||||||
this.privateManifest = await this.getChromeManifest("private");
|
this.privateManifest = await this.getChromeManifest("private");
|
||||||
@@ -52,37 +68,26 @@ const WaterfoxGlue = {
|
|||||||
Services.obs.addObserver(this, "main-pane-loaded");
|
Services.obs.addObserver(this, "main-pane-loaded");
|
||||||
// Observe final-ui-startup to launch browser window dependant tasks
|
// Observe final-ui-startup to launch browser window dependant tasks
|
||||||
Services.obs.addObserver(this, "final-ui-startup");
|
Services.obs.addObserver(this, "final-ui-startup");
|
||||||
|
// Observe browser shutdown
|
||||||
|
Services.obs.addObserver(this, "quit-application-granted");
|
||||||
|
// Listen for addon events
|
||||||
|
this.addAddonListener();
|
||||||
},
|
},
|
||||||
|
|
||||||
async _setPrefObservers() {
|
async _setPrefObservers() {
|
||||||
this.leptonListener = PrefUtils.addObserver(
|
this.leptonListener = lazy.PrefUtils.addObserver(
|
||||||
"userChrome.theme.enable",
|
WATERFOX_CUSTOMIZATIONS_PREF,
|
||||||
isEnabled => {
|
async _ => {
|
||||||
// Pref being false means we need to unload the sheets.
|
const activeThemeId = await this.getActiveThemeId();
|
||||||
const userChromeSheet = "chrome://browser/skin/userChrome.css";
|
this.updateCustomStylesheets({ id: activeThemeId, type: "theme" });
|
||||||
const userContentSheet = "chrome://browser/skin/userContent.css";
|
|
||||||
|
|
||||||
// Only register userContent globally
|
|
||||||
BrowserUtils.registerOrUnregisterSheet(userContentSheet, isEnabled);
|
|
||||||
|
|
||||||
// Attach or detach userChrome per-window
|
|
||||||
if (isEnabled) {
|
|
||||||
this.attachUserChromeToAllWindows(userChromeSheet);
|
|
||||||
this.stylesEnabled = true;
|
|
||||||
} else {
|
|
||||||
this.detachUserChromeFromAllWindows(userChromeSheet);
|
|
||||||
this.stylesEnabled = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
this.pinnedTabListener = lazy.PrefUtils.addObserver(
|
||||||
// Keep the pinned tabs observer
|
|
||||||
this.pinnedTabListener = PrefUtils.addObserver(
|
|
||||||
"browser.tabs.pinnedIconOnly",
|
"browser.tabs.pinnedIconOnly",
|
||||||
isEnabled => {
|
isEnabled => {
|
||||||
// Pref being true actually means we need to unload the sheet, so invert.
|
// Pref being true actually means we need to unload the sheet, so invert.
|
||||||
const uri = "chrome://browser/content/tabfeatures/pinnedtab.css";
|
const uri = "chrome://browser/content/tabfeatures/pinnedtab.css";
|
||||||
BrowserUtils.registerOrUnregisterSheet(uri, !isEnabled);
|
lazy.BrowserUtils.registerOrUnregisterSheet(uri, !isEnabled);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -105,7 +110,7 @@ const WaterfoxGlue = {
|
|||||||
uri = "resource://waterfox/overlays/preferences-other.overlay";
|
uri = "resource://waterfox/overlays/preferences-other.overlay";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
let chromeManifest = new ChromeManifest(async () => {
|
let chromeManifest = new lazy.ChromeManifest(async () => {
|
||||||
let res = await fetch(uri);
|
let res = await fetch(uri);
|
||||||
let text = await res.text();
|
let text = await res.text();
|
||||||
if (privateWindow) {
|
if (privateWindow) {
|
||||||
@@ -140,25 +145,25 @@ const WaterfoxGlue = {
|
|||||||
const window = subject.defaultView;
|
const window = subject.defaultView;
|
||||||
// Do not load non-private overlays in private window
|
// Do not load non-private overlays in private window
|
||||||
if (window.PrivateBrowsingUtils.isWindowPrivate(window)) {
|
if (window.PrivateBrowsingUtils.isWindowPrivate(window)) {
|
||||||
Overlays.load(this.privateManifest, window);
|
lazy.Overlays.load(this.privateManifest, window);
|
||||||
} else {
|
} else {
|
||||||
Overlays.load(this.startupManifest, window);
|
lazy.Overlays.load(this.startupManifest, window);
|
||||||
// Only load in non-private browser windows
|
// Only load in non-private browser windows
|
||||||
PrivateTab.init(window);
|
lazy.PrivateTab.init(window);
|
||||||
}
|
}
|
||||||
// Load in all browser windows (private and normal)
|
// Load in all browser windows (private and normal)
|
||||||
TabFeatures.init(window);
|
lazy.TabFeatures.init(window);
|
||||||
StatusBar.init(window);
|
lazy.StatusBar.init(window);
|
||||||
UICustomizations.init(window);
|
lazy.UICustomizations.init(window);
|
||||||
|
|
||||||
// Attach userChrome.css to this chrome window if styles are enabled
|
// Attach userChrome.css to this chrome window if styles are enabled
|
||||||
if (this.stylesEnabled) {
|
if (this.stylesEnabled) {
|
||||||
try {
|
try {
|
||||||
window.windowUtils.loadSheetUsingURIString(
|
window.windowUtils.loadSheetUsingURIString(
|
||||||
"chrome://browser/skin/userChrome.css",
|
WATERFOX_USERCHROME,
|
||||||
Ci.nsIStyleSheetService.USER_SHEET
|
Ci.nsIStyleSheetService.USER_SHEET
|
||||||
);
|
);
|
||||||
} catch (_) {}
|
} catch (_e) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -169,7 +174,7 @@ const WaterfoxGlue = {
|
|||||||
// exists before we attempt to load our overlay. If we are loading directly on privacy
|
// exists before we attempt to load our overlay. If we are loading directly on privacy
|
||||||
// this exists before overlaying occurs, so we have no issues. Loading overlays on
|
// this exists before overlaying occurs, so we have no issues. Loading overlays on
|
||||||
// #general is fine regardless of which pane we refresh/initially load.
|
// #general is fine regardless of which pane we refresh/initially load.
|
||||||
await Overlays.load(
|
await lazy.Overlays.load(
|
||||||
await this.getChromeManifest("preferences-general"),
|
await this.getChromeManifest("preferences-general"),
|
||||||
subject
|
subject
|
||||||
);
|
);
|
||||||
@@ -178,14 +183,14 @@ const WaterfoxGlue = {
|
|||||||
!subject.document.getElementById("homeContentsGroup")
|
!subject.document.getElementById("homeContentsGroup")
|
||||||
) {
|
) {
|
||||||
subject.setTimeout(async () => {
|
subject.setTimeout(async () => {
|
||||||
await Overlays.load(
|
await lazy.Overlays.load(
|
||||||
await this.getChromeManifest("preferences-other"),
|
await this.getChromeManifest("preferences-other"),
|
||||||
subject
|
subject
|
||||||
);
|
);
|
||||||
subject.privacyInitialized = true;
|
subject.privacyInitialized = true;
|
||||||
}, 500);
|
}, 500);
|
||||||
} else {
|
} else {
|
||||||
await Overlays.load(
|
await lazy.Overlays.load(
|
||||||
await this.getChromeManifest("preferences-other"),
|
await this.getChromeManifest("preferences-other"),
|
||||||
subject
|
subject
|
||||||
);
|
);
|
||||||
@@ -196,6 +201,7 @@ const WaterfoxGlue = {
|
|||||||
break;
|
break;
|
||||||
case "final-ui-startup":
|
case "final-ui-startup":
|
||||||
this._beforeUIStartup();
|
this._beforeUIStartup();
|
||||||
|
this._delayedTasks();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -203,7 +209,7 @@ const WaterfoxGlue = {
|
|||||||
async _beforeUIStartup() {
|
async _beforeUIStartup() {
|
||||||
this._migrateUI();
|
this._migrateUI();
|
||||||
|
|
||||||
AddonManager.maybeInstallBuiltinAddon(
|
lazy.AddonManager.maybeInstallBuiltinAddon(
|
||||||
"addonstores@waterfox.net",
|
"addonstores@waterfox.net",
|
||||||
"1.0.0",
|
"1.0.0",
|
||||||
"resource://builtin-addons/addonstores/"
|
"resource://builtin-addons/addonstores/"
|
||||||
@@ -216,8 +222,13 @@ const WaterfoxGlue = {
|
|||||||
128
|
128
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const waterfoxUIVersion = lazy.PrefUtils.get(
|
||||||
|
"browser.migration.waterfox_version",
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
async function enableTheme(id) {
|
async function enableTheme(id) {
|
||||||
const addon = await AddonManager.getAddonByID(id);
|
const addon = await lazy.AddonManager.getAddonByID(id);
|
||||||
// If we found it, enable it.
|
// If we found it, enable it.
|
||||||
addon?.enable();
|
addon?.enable();
|
||||||
}
|
}
|
||||||
@@ -239,37 +250,121 @@ const WaterfoxGlue = {
|
|||||||
case "australis-dark@waterfox.net":
|
case "australis-dark@waterfox.net":
|
||||||
enableTheme("firefox-compact-dark@mozilla.org");
|
enableTheme("firefox-compact-dark@mozilla.org");
|
||||||
break;
|
break;
|
||||||
default:
|
|
||||||
enableTheme(DEFAULT_THEME);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// If no activeTheme detected, set default.
|
// If no activeTheme detected, set default.
|
||||||
enableTheme(DEFAULT_THEME);
|
enableTheme(DEFAULT_THEME);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (waterfoxUIVersion < 1) {
|
||||||
|
const themeEnablePref = "userChrome.theme.enable";
|
||||||
|
const enabled = lazy.PrefUtils.get(themeEnablePref);
|
||||||
|
lazy.PrefUtils.set(WATERFOX_CUSTOMIZATIONS_PREF, enabled ? 1 : 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy.PrefUtils.set("browser.migration.waterfox_version", 1);
|
||||||
|
},
|
||||||
|
|
||||||
|
async _delayedTasks() {
|
||||||
|
let tasks = [
|
||||||
|
{
|
||||||
|
task: () => {
|
||||||
|
// Reset prefs
|
||||||
|
Services.prefs.clearUserPref(
|
||||||
|
"startup.homepage_welcome_url.additional"
|
||||||
|
);
|
||||||
|
Services.prefs.clearUserPref("startup.homepage_override_url");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const task of tasks) {
|
||||||
|
task.task();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async getActiveThemeId() {
|
||||||
|
// Try to get active theme from the pref
|
||||||
|
const activeThemeID = lazy.PrefUtils.get("extensions.activeThemeID", "");
|
||||||
|
if (activeThemeID) {
|
||||||
|
return activeThemeID;
|
||||||
|
}
|
||||||
|
// Otherwise just grab it from AddonManager
|
||||||
|
const themes = await lazy.AddonManager.getAddonsByTypes(["theme"]);
|
||||||
|
return themes.find(addon => addon.isActive).id;
|
||||||
|
},
|
||||||
|
|
||||||
|
addAddonListener() {
|
||||||
|
let listener = {
|
||||||
|
onInstalled: addon => this.updateCustomStylesheets(addon),
|
||||||
|
onEnabled: addon => this.updateCustomStylesheets(addon),
|
||||||
|
};
|
||||||
|
this._addonManagersListeners.push(listener);
|
||||||
|
lazy.AddonManager.addAddonListener(listener);
|
||||||
|
},
|
||||||
|
|
||||||
|
removeAddonListeners() {
|
||||||
|
for (let listener of this._addonManagersListeners) {
|
||||||
|
lazy.AddonManager.removeAddonListener(listener);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
updateCustomStylesheets(addon) {
|
||||||
|
if (addon.type === "theme") {
|
||||||
|
// If any theme and WF on any theme, reload stylesheets for every theme enable.
|
||||||
|
// If WF theme and WF customisations, then reload stylesheets.
|
||||||
|
// If no customizations, unregister sheets for every theme enable.
|
||||||
|
if (
|
||||||
|
lazy.PrefUtils.get(WATERFOX_CUSTOMIZATIONS_PREF, 0) === 0 ||
|
||||||
|
(lazy.PrefUtils.get(WATERFOX_CUSTOMIZATIONS_PREF, 0) === 1 &&
|
||||||
|
WATERFOX_DEFAULT_THEMES.includes(addon.id))
|
||||||
|
) {
|
||||||
|
this.loadWaterfoxStylesheets();
|
||||||
|
} else {
|
||||||
|
this.unloadWaterfoxStylesheets();
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Attach userChrome.css to all open browser windows (per-window).
|
// Attach userChrome.css to all open browser windows (per-window).
|
||||||
attachUserChromeToAllWindows(uri) {
|
attachUserChromeToAllWindows() {
|
||||||
BrowserUtils.executeInAllWindows((win, sheetURI) => {
|
lazy.BrowserUtils.executeInAllWindows((win, uri) => {
|
||||||
try {
|
try {
|
||||||
win.windowUtils.loadSheetUsingURIString(
|
win.windowUtils.loadSheetUsingURIString(
|
||||||
sheetURI,
|
uri,
|
||||||
Ci.nsIStyleSheetService.USER_SHEET
|
Ci.nsIStyleSheetService.USER_SHEET
|
||||||
);
|
);
|
||||||
} catch (_) {}
|
} catch (_e) {}
|
||||||
}, uri);
|
}, WATERFOX_USERCHROME);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Detach userChrome.css from all open browser windows.
|
// Detach userChrome.css from all open browser windows.
|
||||||
detachUserChromeFromAllWindows(uri) {
|
detachUserChromeFromAllWindows() {
|
||||||
BrowserUtils.executeInAllWindows((win, sheetURI) => {
|
lazy.BrowserUtils.executeInAllWindows((win, uri) => {
|
||||||
try {
|
try {
|
||||||
win.windowUtils.removeSheetUsingURIString(
|
win.windowUtils.removeSheetUsingURIString(
|
||||||
sheetURI,
|
uri,
|
||||||
Ci.nsIStyleSheetService.USER_SHEET
|
Ci.nsIStyleSheetService.USER_SHEET
|
||||||
);
|
);
|
||||||
} catch (_) {}
|
} catch (_e) {}
|
||||||
}, uri);
|
}, WATERFOX_USERCHROME);
|
||||||
|
},
|
||||||
|
|
||||||
|
loadWaterfoxStylesheets() {
|
||||||
|
// Register content sheet globally
|
||||||
|
lazy.BrowserUtils.registerStylesheet(WATERFOX_USERCONTENT);
|
||||||
|
// Attach chrome sheet to all current browser windows
|
||||||
|
this.attachUserChromeToAllWindows();
|
||||||
|
// Track enabled state so new windows can attach on open
|
||||||
|
this.stylesEnabled = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
unloadWaterfoxStylesheets() {
|
||||||
|
// Detach chrome sheet from all windows
|
||||||
|
this.detachUserChromeFromAllWindows();
|
||||||
|
// Unregister content sheet globally
|
||||||
|
lazy.BrowserUtils.unregisterStylesheet(WATERFOX_USERCONTENT);
|
||||||
|
// Track disabled state so new windows don't attach on open
|
||||||
|
this.stylesEnabled = false;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -16,7 +16,7 @@ DIRS += [
|
|||||||
]
|
]
|
||||||
|
|
||||||
EXTRA_JS_MODULES += [
|
EXTRA_JS_MODULES += [
|
||||||
"WaterfoxGlue.jsm",
|
"WaterfoxGlue.sys.mjs",
|
||||||
]
|
]
|
||||||
|
|
||||||
JAR_MANIFESTS += ["jar.mn"]
|
JAR_MANIFESTS += ["jar.mn"]
|
||||||
|
|||||||
Reference in New Issue
Block a user