mirror of
https://github.com/bitwarden/browser.git
synced 2025-04-15 20:16:03 +02:00
[PM-5743] Implement eslint rule for usage of window object in background script (#7849)
* [PM-5742] Rework Usage of Extension APIs that Cannot be Called with the Background Service Worker * [PM-5742] Implementing jest tests for the updated BrowserApi methods * [PM-5742] Implementing jest tests to validate logic within added API calls * [PM-5742] Implementing jest tests to validate logic within added API calls * [PM-5742] Fixing broken Jest tests * [PM-5742] Fixing linter error * [PM-5887] Refactor WebCryptoFunction to Remove Usage of the window Object in the Background Script * [PM-5878] Rework `window` call within OverlayBackground to function within AutofillOverlayIframe service * [PM-6122] Rework `window` call within NotificationBackground to function within content script * [PM-5881] Adjust usage of the `chrome.extension.getViews` API to ensure expected behavior in manifest v3 * [PM-5881] Reworking how we handle early returns from `reloadOpenWindows` * [PM-5881] Implementing jest test to validate changes within BrowserApi.reloadOpenWindows * [PM-5743] Implement eslint rule to impeede usage of the `window` object in the background script * [PM-5743] Working through fixing eslint rule errors, and setting up ignore statements for lines that will be refactored at a later date * [PM-5743] Fixing broken jest tests * [PM-5879] Removing `backgroundWindow` reference used for determing system theme preference in Safari * [PM-5879] Removing `backgroundWindow` reference used for determing system theme preference in Safari * [PM-5743] Updating references to NodeJS.Timeout * [PM-5743] Adding notification bar and overaly content scripts to the eslint excluded files key * [PM-5743] Adding other excluded files from the eslint rule * [PM-5743] Reworking implementation to have the .eslintrc.json file present within the browser subdirectory
This commit is contained in:
parent
9d1219bda6
commit
670f33daa8
26
apps/browser/.eslintrc.json
Normal file
26
apps/browser/.eslintrc.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"webextensions": true
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["src/**/*.ts"],
|
||||
"excludedFiles": [
|
||||
"src/**/{content,popup,spec}/**/*.ts",
|
||||
"src/**/autofill/{notification,overlay}/**/*.ts",
|
||||
"src/**/autofill/**/{autofill-overlay-content,collect-autofill-content,dom-element-visibility,insert-autofill-content}.service.ts",
|
||||
"src/**/*.spec.ts"
|
||||
],
|
||||
"rules": {
|
||||
"no-restricted-globals": [
|
||||
"error",
|
||||
{
|
||||
"name": "window",
|
||||
"message": "The `window` object is not available in service workers and may not be available within the background script. Consider using `self`, `globalThis`, or another global property instead."
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@ -17,7 +17,7 @@ export default class WebRequestBackground {
|
||||
private authService: AuthService,
|
||||
) {
|
||||
if (BrowserApi.isManifestVersion(2)) {
|
||||
this.webRequest = (window as any).chrome.webRequest;
|
||||
this.webRequest = chrome.webRequest;
|
||||
}
|
||||
this.isFirefox = platformUtilsService.isFirefox();
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ function loadAutofiller() {
|
||||
let pageHref: string = null;
|
||||
let filledThisHref = false;
|
||||
let delayFillTimeout: number;
|
||||
let doFillInterval: NodeJS.Timeout;
|
||||
let doFillInterval: number | NodeJS.Timeout;
|
||||
const handleExtensionDisconnect = () => {
|
||||
clearDoFillInterval();
|
||||
clearDelayFillTimeout();
|
||||
|
@ -139,7 +139,7 @@ function initNotificationBar(message: NotificationBarWindowMessage) {
|
||||
});
|
||||
});
|
||||
|
||||
window.addEventListener("resize", adjustHeight);
|
||||
globalThis.addEventListener("resize", adjustHeight);
|
||||
adjustHeight();
|
||||
}
|
||||
|
||||
@ -384,7 +384,7 @@ function setupLogoLink(i18n: Record<string, string>) {
|
||||
function setNotificationBarTheme() {
|
||||
let theme = notificationBarIframeInitData.theme;
|
||||
if (theme === ThemeType.System) {
|
||||
theme = window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||
theme = globalThis.matchMedia("(prefers-color-scheme: dark)").matches
|
||||
? ThemeType.Dark
|
||||
: ThemeType.Light;
|
||||
}
|
||||
@ -393,5 +393,5 @@ function setNotificationBarTheme() {
|
||||
}
|
||||
|
||||
function postMessageToParent(message: NotificationBarWindowMessage) {
|
||||
window.parent.postMessage(message, windowMessageOrigin || "*");
|
||||
globalThis.parent.postMessage(message, windowMessageOrigin || "*");
|
||||
}
|
||||
|
@ -211,7 +211,7 @@ class AutofillOverlayIframeService implements AutofillOverlayIframeServiceInterf
|
||||
let borderColor: string;
|
||||
let verifiedTheme = theme;
|
||||
if (verifiedTheme === ThemeType.System) {
|
||||
verifiedTheme = window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||
verifiedTheme = globalThis.matchMedia("(prefers-color-scheme: dark)").matches
|
||||
? ThemeType.Dark
|
||||
: ThemeType.Light;
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ class AutofillOverlayList extends AutofillOverlayPageElement {
|
||||
private ciphers: OverlayCipherData[] = [];
|
||||
private ciphersList: HTMLUListElement;
|
||||
private cipherListScrollIsDebounced = false;
|
||||
private cipherListScrollDebounceTimeout: NodeJS.Timeout;
|
||||
private cipherListScrollDebounceTimeout: number | NodeJS.Timeout;
|
||||
private currentCipherIndex = 0;
|
||||
private readonly showCiphersPerPage = 6;
|
||||
private readonly overlayListWindowMessageHandlers: OverlayListWindowMessageHandlers = {
|
||||
|
@ -15,7 +15,7 @@ export interface PageDetail {
|
||||
export interface AutoFillOptions {
|
||||
cipher: CipherView;
|
||||
pageDetails: PageDetail[];
|
||||
doc?: typeof window.document;
|
||||
doc?: typeof self.document;
|
||||
tab: chrome.tabs.Tab;
|
||||
skipUsernameOnlyFill?: boolean;
|
||||
onlyEmptyFields?: boolean;
|
||||
|
@ -712,7 +712,7 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
|
||||
private async getBoundingClientRectFromIntersectionObserver(
|
||||
formFieldElement: ElementWithOpId<FormFieldElement>,
|
||||
): Promise<DOMRectReadOnly | null> {
|
||||
if (!("IntersectionObserver" in window) && !("IntersectionObserverEntry" in window)) {
|
||||
if (!("IntersectionObserver" in globalThis) && !("IntersectionObserverEntry" in globalThis)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -901,7 +901,7 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
|
||||
|
||||
if (
|
||||
this.focusedFieldData.focusedFieldRects?.top > 0 &&
|
||||
this.focusedFieldData.focusedFieldRects?.top < window.innerHeight
|
||||
this.focusedFieldData.focusedFieldRects?.top < globalThis.innerHeight
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ import {
|
||||
|
||||
export default class AutofillService implements AutofillServiceInterface {
|
||||
private openVaultItemPasswordRepromptPopout = openVaultItemPasswordRepromptPopout;
|
||||
private openPasswordRepromptPopoutDebounce: NodeJS.Timeout;
|
||||
private openPasswordRepromptPopoutDebounce: number | NodeJS.Timeout;
|
||||
private currentlyOpeningPasswordRepromptPopout = false;
|
||||
private autofillScriptPortsSet = new Set<chrome.runtime.Port>();
|
||||
static searchFieldNamesSet = new Set(AutoFillConstants.SearchFieldNames);
|
||||
|
@ -39,7 +39,7 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte
|
||||
private autofillFieldElements: AutofillFieldElements = new Map();
|
||||
private currentLocationHref = "";
|
||||
private mutationObserver: MutationObserver;
|
||||
private updateAutofillElementsAfterMutationTimeout: NodeJS.Timeout;
|
||||
private updateAutofillElementsAfterMutationTimeout: number | NodeJS.Timeout;
|
||||
private readonly updateAfterMutationTimeoutDelay = 1000;
|
||||
private readonly ignoredInputTypes = new Set([
|
||||
"hidden",
|
||||
@ -180,7 +180,7 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte
|
||||
): AutofillPageDetails {
|
||||
return {
|
||||
title: document.title,
|
||||
url: (document.defaultView || window).location.href,
|
||||
url: (document.defaultView || globalThis).location.href,
|
||||
documentUrl: document.location.href,
|
||||
forms: autofillFormsData,
|
||||
fields: autofillFieldsData,
|
||||
@ -240,7 +240,7 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte
|
||||
* @private
|
||||
*/
|
||||
private getFormActionAttribute(element: ElementWithOpId<HTMLFormElement>): string {
|
||||
return new URL(this.getPropertyOrAttribute(element, "action"), window.location.href).href;
|
||||
return new URL(this.getPropertyOrAttribute(element, "action"), globalThis.location.href).href;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -66,7 +66,7 @@ class DomElementVisibilityService implements domElementVisibilityServiceInterfac
|
||||
*/
|
||||
private getElementStyle(element: HTMLElement, styleProperty: string): string {
|
||||
if (!this.cachedComputedStyle) {
|
||||
this.cachedComputedStyle = (element.ownerDocument.defaultView || window).getComputedStyle(
|
||||
this.cachedComputedStyle = (element.ownerDocument.defaultView || globalThis).getComputedStyle(
|
||||
element,
|
||||
);
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ const initEventCount = Object.freeze(
|
||||
);
|
||||
|
||||
let confirmSpy: jest.SpyInstance<boolean, [message?: string]>;
|
||||
let windowSpy: jest.SpyInstance<any>;
|
||||
let windowLocationSpy: jest.SpyInstance<any>;
|
||||
let savedURLs: string[] | null = ["https://bitwarden.com"];
|
||||
function setMockWindowLocation({
|
||||
protocol,
|
||||
@ -56,11 +56,9 @@ function setMockWindowLocation({
|
||||
protocol: "http:" | "https:";
|
||||
hostname: string;
|
||||
}) {
|
||||
windowSpy.mockImplementation(() => ({
|
||||
location: {
|
||||
protocol,
|
||||
hostname,
|
||||
},
|
||||
windowLocationSpy.mockImplementation(() => ({
|
||||
protocol,
|
||||
hostname,
|
||||
}));
|
||||
}
|
||||
|
||||
@ -76,8 +74,8 @@ describe("InsertAutofillContentService", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
document.body.innerHTML = mockLoginForm;
|
||||
confirmSpy = jest.spyOn(window, "confirm");
|
||||
windowSpy = jest.spyOn(window, "window", "get");
|
||||
confirmSpy = jest.spyOn(globalThis, "confirm");
|
||||
windowLocationSpy = jest.spyOn(globalThis, "location", "get");
|
||||
insertAutofillContentService = new InsertAutofillContentService(
|
||||
domElementVisibilityService,
|
||||
collectAutofillContentService,
|
||||
@ -101,7 +99,7 @@ describe("InsertAutofillContentService", () => {
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
windowSpy.mockRestore();
|
||||
windowLocationSpy.mockRestore();
|
||||
confirmSpy.mockRestore();
|
||||
document.body.innerHTML = "";
|
||||
});
|
||||
@ -245,8 +243,8 @@ describe("InsertAutofillContentService", () => {
|
||||
});
|
||||
|
||||
it("returns true if the frameElement has a sandbox attribute", () => {
|
||||
Object.defineProperty(globalThis, "window", {
|
||||
value: { frameElement: { hasAttribute: jest.fn(() => true) } },
|
||||
Object.defineProperty(globalThis, "frameElement", {
|
||||
value: { hasAttribute: jest.fn(() => true) },
|
||||
writable: true,
|
||||
});
|
||||
|
||||
@ -991,11 +989,11 @@ describe("InsertAutofillContentService", () => {
|
||||
const inputElement = document.querySelector('input[type="text"]') as HTMLInputElement;
|
||||
inputElement.value = "test";
|
||||
jest.spyOn(inputElement, "focus");
|
||||
jest.spyOn(window, "String");
|
||||
jest.spyOn(globalThis, "String");
|
||||
|
||||
insertAutofillContentService["triggerFocusOnElement"](inputElement, true);
|
||||
|
||||
expect(window.String).toHaveBeenCalledWith(value);
|
||||
expect(globalThis.String).toHaveBeenCalledWith(value);
|
||||
expect(inputElement.focus).toHaveBeenCalled();
|
||||
expect(inputElement.value).toEqual(value);
|
||||
});
|
||||
@ -1005,11 +1003,11 @@ describe("InsertAutofillContentService", () => {
|
||||
const inputElement = document.querySelector('input[type="text"]') as HTMLInputElement;
|
||||
inputElement.value = "test";
|
||||
jest.spyOn(inputElement, "focus");
|
||||
jest.spyOn(window, "String");
|
||||
jest.spyOn(globalThis, "String");
|
||||
|
||||
insertAutofillContentService["triggerFocusOnElement"](inputElement, false);
|
||||
|
||||
expect(window.String).not.toHaveBeenCalledWith();
|
||||
expect(globalThis.String).not.toHaveBeenCalledWith();
|
||||
expect(inputElement.focus).toHaveBeenCalled();
|
||||
expect(inputElement.value).toEqual(value);
|
||||
});
|
||||
|
@ -65,8 +65,8 @@ class InsertAutofillContentService implements InsertAutofillContentServiceInterf
|
||||
private fillingWithinSandboxedIframe() {
|
||||
return (
|
||||
String(self.origin).toLowerCase() === "null" ||
|
||||
window.frameElement?.hasAttribute("sandbox") ||
|
||||
window.location.hostname === ""
|
||||
globalThis.frameElement?.hasAttribute("sandbox") ||
|
||||
globalThis.location.hostname === ""
|
||||
);
|
||||
}
|
||||
|
||||
@ -79,8 +79,8 @@ class InsertAutofillContentService implements InsertAutofillContentServiceInterf
|
||||
*/
|
||||
private userCancelledInsecureUrlAutofill(savedUrls?: string[] | null): boolean {
|
||||
if (
|
||||
!savedUrls?.some((url) => url.startsWith(`https://${window.location.hostname}`)) ||
|
||||
window.location.protocol !== "http:" ||
|
||||
!savedUrls?.some((url) => url.startsWith(`https://${globalThis.location.hostname}`)) ||
|
||||
globalThis.location.protocol !== "http:" ||
|
||||
!this.isPasswordFieldWithinDocument()
|
||||
) {
|
||||
return false;
|
||||
@ -88,10 +88,10 @@ class InsertAutofillContentService implements InsertAutofillContentServiceInterf
|
||||
|
||||
const confirmationWarning = [
|
||||
chrome.i18n.getMessage("insecurePageWarning"),
|
||||
chrome.i18n.getMessage("insecurePageWarningFillPrompt", [window.location.hostname]),
|
||||
chrome.i18n.getMessage("insecurePageWarningFillPrompt", [globalThis.location.hostname]),
|
||||
].join("\n\n");
|
||||
|
||||
return !confirm(confirmationWarning);
|
||||
return !globalThis.confirm(confirmationWarning);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -129,10 +129,10 @@ class InsertAutofillContentService implements InsertAutofillContentServiceInterf
|
||||
|
||||
const confirmationWarning = [
|
||||
chrome.i18n.getMessage("autofillIframeWarning"),
|
||||
chrome.i18n.getMessage("autofillIframeWarningTip", [window.location.hostname]),
|
||||
chrome.i18n.getMessage("autofillIframeWarningTip", [globalThis.location.hostname]),
|
||||
].join("\n\n");
|
||||
|
||||
return !confirm(confirmationWarning);
|
||||
return !globalThis.confirm(confirmationWarning);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -11,7 +11,7 @@ const IdleInterval = 60 * 5; // 5 minutes
|
||||
|
||||
export default class IdleBackground {
|
||||
private idle: typeof chrome.idle | typeof browser.idle | null;
|
||||
private idleTimer: number = null;
|
||||
private idleTimer: number | NodeJS.Timeout = null;
|
||||
private idleState = "active";
|
||||
|
||||
constructor(
|
||||
@ -73,7 +73,7 @@ export default class IdleBackground {
|
||||
|
||||
private pollIdle(handler: (newState: string) => void) {
|
||||
if (this.idleTimer != null) {
|
||||
window.clearTimeout(this.idleTimer);
|
||||
globalThis.clearTimeout(this.idleTimer);
|
||||
this.idleTimer = null;
|
||||
}
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
@ -83,7 +83,7 @@ export default class IdleBackground {
|
||||
this.idleState = state;
|
||||
handler(state);
|
||||
}
|
||||
this.idleTimer = window.setTimeout(() => this.pollIdle(handler), 5000);
|
||||
this.idleTimer = globalThis.setTimeout(() => this.pollIdle(handler), 5000);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -991,7 +991,7 @@ export default class MainBackground {
|
||||
}
|
||||
|
||||
async bootstrap() {
|
||||
this.containerService.attachToGlobal(window);
|
||||
this.containerService.attachToGlobal(self);
|
||||
|
||||
await this.stateService.init();
|
||||
|
||||
|
@ -89,7 +89,7 @@ export default class RuntimeBackground {
|
||||
|
||||
BrowserApi.messageListener("runtime.background", backgroundMessageListener);
|
||||
if (this.main.popupOnlyContext) {
|
||||
(window as any).bitwardenBackgroundMessageListener = backgroundMessageListener;
|
||||
(self as any).bitwardenBackgroundMessageListener = backgroundMessageListener;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,7 +33,7 @@ if (BrowserApi.isManifestVersion(3)) {
|
||||
},
|
||||
);
|
||||
} else {
|
||||
const bitwardenMain = ((window as any).bitwardenMain = new MainBackground());
|
||||
const bitwardenMain = ((self as any).bitwardenMain = new MainBackground());
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
bitwardenMain.bootstrap().then(() => {
|
||||
|
@ -351,11 +351,11 @@ export class BrowserApi {
|
||||
private static setupUnloadListeners() {
|
||||
// The MDN recommend using 'visibilitychange' but that event is fired any time the popup window is obscured as well
|
||||
// 'pagehide' works just like 'unload' but is compatible with the back/forward cache, so we prefer using that one
|
||||
window.onpagehide = () => {
|
||||
self.addEventListener("pagehide", () => {
|
||||
for (const [event, callback] of BrowserApi.trackedChromeEventListeners) {
|
||||
event.removeListener(callback);
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
static sendMessage(subscriber: string, arg: any = {}) {
|
||||
@ -423,7 +423,7 @@ export class BrowserApi {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentHref = window.location.href;
|
||||
const currentHref = self.location.href;
|
||||
views
|
||||
.filter((w) => w.location.href != null && !w.location.href.includes("background.html"))
|
||||
.filter((w) => !exemptCurrentHref || w.location.href !== currentHref)
|
||||
|
@ -29,14 +29,14 @@ class OffscreenDocument implements OffscreenDocumentInterface {
|
||||
* @param message - The extension message containing the text to copy
|
||||
*/
|
||||
private async handleOffscreenCopyToClipboard(message: OffscreenDocumentExtensionMessage) {
|
||||
await BrowserClipboardService.copy(window, message.text);
|
||||
await BrowserClipboardService.copy(self, message.text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the user's clipboard and returns the text.
|
||||
*/
|
||||
private async handleOffscreenReadFromClipboard() {
|
||||
return await BrowserClipboardService.read(window);
|
||||
return await BrowserClipboardService.read(self);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -5,8 +5,8 @@ import { FileDownloadRequest } from "@bitwarden/common/platform/abstractions/fil
|
||||
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
|
||||
import { SafariApp } from "../../browser/safariApp";
|
||||
import { BrowserApi } from "../browser/browser-api";
|
||||
import { SafariApp } from "../../../browser/safariApp";
|
||||
import { BrowserApi } from "../../browser/browser-api";
|
||||
|
||||
@Injectable()
|
||||
export class BrowserFileDownloadService implements FileDownloadService {
|
@ -3,6 +3,6 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag
|
||||
export default class BrowserMessagingPrivateModeBackgroundService implements MessagingService {
|
||||
send(subscriber: string, arg: any = {}) {
|
||||
const message = Object.assign({}, { command: subscriber }, arg);
|
||||
(window as any).bitwardenPopupMainMessageListener(message);
|
||||
(self as any).bitwardenPopupMainMessageListener(message);
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,6 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag
|
||||
export default class BrowserMessagingPrivateModePopupService implements MessagingService {
|
||||
send(subscriber: string, arg: any = {}) {
|
||||
const message = Object.assign({}, { command: subscriber }, arg);
|
||||
(window as any).bitwardenBackgroundMessageListener(message);
|
||||
(self as any).bitwardenBackgroundMessageListener(message);
|
||||
}
|
||||
}
|
||||
|
@ -140,7 +140,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
};
|
||||
|
||||
(window as any).bitwardenPopupMainMessageListener = bitwardenPopupMainMessageListener;
|
||||
(self as any).bitwardenPopupMainMessageListener = bitwardenPopupMainMessageListener;
|
||||
this.browserMessagingApi.messageListener("app.component", bitwardenPopupMainMessageListener);
|
||||
|
||||
// eslint-disable-next-line rxjs/no-async-subscribe
|
||||
|
@ -92,9 +92,9 @@ import MainBackground from "../../background/main.background";
|
||||
import { Account } from "../../models/account";
|
||||
import { BrowserApi } from "../../platform/browser/browser-api";
|
||||
import BrowserPopupUtils from "../../platform/popup/browser-popup-utils";
|
||||
import { BrowserFileDownloadService } from "../../platform/popup/services/browser-file-download.service";
|
||||
import { BrowserStateService as StateServiceAbstraction } from "../../platform/services/abstractions/browser-state.service";
|
||||
import { BrowserEnvironmentService } from "../../platform/services/browser-environment.service";
|
||||
import { BrowserFileDownloadService } from "../../platform/services/browser-file-download.service";
|
||||
import BrowserLocalStorageService from "../../platform/services/browser-local-storage.service";
|
||||
import BrowserMessagingPrivateModePopupService from "../../platform/services/browser-messaging-private-mode-popup.service";
|
||||
import BrowserMessagingService from "../../platform/services/browser-messaging.service";
|
||||
|
Loading…
Reference in New Issue
Block a user