summaryrefslogtreecommitdiffstats
path: root/toolkit/components/extensions/types
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/extensions/types')
-rw-r--r--toolkit/components/extensions/types/README.md86
-rw-r--r--toolkit/components/extensions/types/XPCShellContentUtils.sys.d.mts87
-rw-r--r--toolkit/components/extensions/types/extensions.ts80
-rw-r--r--toolkit/components/extensions/types/gecko.ts163
-rw-r--r--toolkit/components/extensions/types/globals.ts33
5 files changed, 449 insertions, 0 deletions
diff --git a/toolkit/components/extensions/types/README.md b/toolkit/components/extensions/types/README.md
new file mode 100644
index 0000000000..ebd01dec60
--- /dev/null
+++ b/toolkit/components/extensions/types/README.md
@@ -0,0 +1,86 @@
+# PoC Type-Checking JavaScript Using JSDocs
+
+## Intro
+
+TypeScript can be used on plain JavaScript files documented with JSDoc comments
+to check types without a build step. This is a proof of concept to show
+viability and benefits of doing this for a "typical" component.
+
+* [Handbook: Type Checking JavaScript Files][handbook]
+* [JSDoc Reference][jsdoc]
+
+## New files
+
+ * `tsconfig.json`: at the root of a TypeScript "project" configures how to find
+ and check files.
+
+ * `types/globals.ts`: defines available globals, types and various utilities.
+
+ * `types/XPCShellContentUtils.sys.mts`: an example of type definitions file for
+ an external module which was automatically generated by `tsc`.
+
+## How to use and expectations
+
+Use [npm or yarn to install][download] TypeScript.
+Then run `tsc` in the extensions directory to check types:
+
+```
+mozilla-central/toolkit/components/extensions $ tsc
+```
+
+You can also use an editor which supports the [language server][langserv].
+VSCode should pick it up automatically, but others like Vim might need
+[some configuring][nvim].
+
+Other than continuing to use JSDocs, trying to follow guidelines below, and
+hopefully remembering to run `tsc` occasionally, for now there is explicitly
+*no expectation* that all new code must be fully typed, or pass typecheck
+on every commit.
+
+If you are in a hurry, or run into a confusing type problem, feel free to
+slap a @ts-ignore and/or ask for help from someone more familiar. Hopefully
+as workflow gets better integrations, we all learn some typescript along
+the way, and the whole process remains non-disruptive.
+
+## Guidelines for type-friendly code
+
+Using more modern and idiomatic code patterns can enable better type inference
+by the TypeScript compiler.
+
+These fall under 5 main categories:
+
+ 1) Declare and/or initialize all class fields in the constructor.
+ * (general good practice)
+
+ 2) Use real getters and redefineGetter instead of defineLazyGetter.
+ * (also keeps related code closer together)
+
+ 3) When extending, don't override class fields with getters, or vice versa.
+ * https://github.com/microsoft/TypeScript/pull/33509
+
+ 4) Declare and assign object literals at the same time, not separately.
+ * (don't use `let foo;` at the top of the file, use `var foo = {`)
+
+ 5) Don't re-use local variables unnecessarily with different types.
+ * (general good practice, local variables are "free")
+
+### @ts-ignore recommendations
+
+*Don't* use `@ts-ignore` for class fields and function or method signatures.
+
+*Feel free* to use it:
+ * locally inside methods,
+ * when the alternative is more noise than signal,
+ * when doing so isn't preventing passthrough of type-inference through other
+ parts of the codebase.
+
+
+[handbook]: https://www.typescriptlang.org/docs/handbook/type-checking-javascript-files.html
+
+[jsdoc]: https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html
+
+[download]: https://www.typescriptlang.org/download
+
+[langserv]: https://github.com/typescript-language-server/typescript-language-server
+
+[nvim]: https://www.reddit.com/r/neovim/comments/131l9cw/theres_another_typescript_lsp_that_wraps_the/
diff --git a/toolkit/components/extensions/types/XPCShellContentUtils.sys.d.mts b/toolkit/components/extensions/types/XPCShellContentUtils.sys.d.mts
new file mode 100644
index 0000000000..a2c90c4be1
--- /dev/null
+++ b/toolkit/components/extensions/types/XPCShellContentUtils.sys.d.mts
@@ -0,0 +1,87 @@
+// @ts-nocheck
+
+export namespace XPCShellContentUtils {
+ const currentScope: any;
+ const fetchScopes: Map<any, any>;
+ function initCommon(scope: any): void;
+ function init(scope: any): void;
+ function initMochitest(scope: any): void;
+ function ensureInitialized(scope: any): void;
+ /**
+ * Creates a new HttpServer for testing, and begins listening on the
+ * specified port. Automatically shuts down the server when the test
+ * unit ends.
+ *
+ * @param {object} [options = {}]
+ * The options object.
+ * @param {integer} [options.port = -1]
+ * The port to listen on. If omitted, listen on a random
+ * port. The latter is the preferred behavior.
+ * @param {sequence<string>?} [options.hosts = null]
+ * A set of hosts to accept connections to. Support for this is
+ * implemented using a proxy filter.
+ *
+ * @returns {HttpServer}
+ * The HTTP server instance.
+ */
+ function createHttpServer({ port, hosts }?: {
+ port?: number;
+ hosts?: sequence<string>;
+ }): HttpServer;
+
+ var remoteContentScripts: boolean;
+ type ContentPage = ContentPage;
+
+ function registerJSON(server: any, path: any, obj: any): void;
+ function fetch(origin: any, url: any, options: any): Promise<any>;
+ /**
+ * Loads a content page into a hidden docShell.
+ *
+ * @param {string} url
+ * The URL to load.
+ * @param {object} [options = {}]
+ * @param {ExtensionWrapper} [options.extension]
+ * If passed, load the URL as an extension page for the given
+ * extension.
+ * @param {boolean} [options.remote]
+ * If true, load the URL in a content process. If false, load
+ * it in the parent process.
+ * @param {boolean} [options.remoteSubframes]
+ * If true, load cross-origin frames in separate content processes.
+ * This is ignored if |options.remote| is false.
+ * @param {string} [options.redirectUrl]
+ * An optional URL that the initial page is expected to
+ * redirect to.
+ *
+ * @returns {ContentPage}
+ */
+ function loadContentPage(url: string, { extension, remote, remoteSubframes, redirectUrl, privateBrowsing, userContextId, }?: {
+ extension?: any;
+ remote?: boolean;
+ remoteSubframes?: boolean;
+ redirectUrl?: string;
+ }): ContentPage;
+}
+declare class ContentPage {
+ constructor(remote?: any, remoteSubframes?: any, extension?: any, privateBrowsing?: boolean, userContextId?: any);
+ remote: any;
+ remoteSubframes: any;
+ extension: any;
+ privateBrowsing: boolean;
+ userContextId: any;
+ browserReady: Promise<Element>;
+ _initBrowser(): Promise<Element>;
+ windowlessBrowser: any;
+ browser: Element;
+ get browsingContext(): any;
+ get SpecialPowers(): any;
+ loadFrameScript(func: any): void;
+ addFrameScriptHelper(func: any): void;
+ didChangeBrowserRemoteness(event: any): void;
+ loadURL(url: any, redirectUrl?: any): Promise<any>;
+ fetch(...args: any[]): Promise<any>;
+ spawn(params: any, task: any): any;
+ legacySpawn(params: any, task: any): any;
+ close(): Promise<void>;
+}
+export {};
diff --git a/toolkit/components/extensions/types/extensions.ts b/toolkit/components/extensions/types/extensions.ts
new file mode 100644
index 0000000000..8f9555421b
--- /dev/null
+++ b/toolkit/components/extensions/types/extensions.ts
@@ -0,0 +1,80 @@
+/**
+ * Type declarations for WebExtensions framework code.
+ */
+
+// This has every possible property we import from all modules, which is not
+// great, but should be manageable and easy to generate for each component.
+// ESLint warns if we use one which is not actually defined, so still safe.
+type LazyAll = {
+ BroadcastConduit: typeof import("ConduitsParent.sys.mjs").BroadcastConduit,
+ Extension: typeof import("Extension.sys.mjs").Extension,
+ ExtensionActivityLog: typeof import("ExtensionActivityLog.sys.mjs").ExtensionActivityLog,
+ ExtensionChild: typeof import("ExtensionChild.sys.mjs").ExtensionChild,
+ ExtensionCommon: typeof import("ExtensionCommon.sys.mjs").ExtensionCommon,
+ ExtensionContent: typeof import("ExtensionContent.sys.mjs").ExtensionContent,
+ ExtensionDNR: typeof import("ExtensionDNR.sys.mjs").ExtensionDNR,
+ ExtensionDNRLimits: typeof import("ExtensionDNRLimits.sys.mjs").ExtensionDNRLimits,
+ ExtensionDNRStore: typeof import("ExtensionDNRStore.sys.mjs").ExtensionDNRStore,
+ ExtensionData: typeof import("Extension.sys.mjs").ExtensionData,
+ ExtensionPageChild: typeof import("ExtensionPageChild.sys.mjs").ExtensionPageChild,
+ ExtensionParent: typeof import("ExtensionParent.sys.mjs").ExtensionParent,
+ ExtensionPermissions: typeof import("ExtensionPermissions.sys.mjs").ExtensionPermissions,
+ ExtensionStorage: typeof import("ExtensionStorage.sys.mjs").ExtensionStorage,
+ ExtensionStorageIDB: typeof import("ExtensionStorageIDB.sys.mjs").ExtensionStorageIDB,
+ ExtensionTelemetry: typeof import("ExtensionTelemetry.sys.mjs").ExtensionTelemetry,
+ ExtensionTestCommon: typeof import("resource://testing-common/ExtensionTestCommon.sys.mjs").ExtensionTestCommon,
+ ExtensionUtils: typeof import("ExtensionUtils.sys.mjs").ExtensionUtils,
+ ExtensionWorkerChild: typeof import("ExtensionWorkerChild.sys.mjs").ExtensionWorkerChild,
+ GeckoViewConnection: typeof import("resource://gre/modules/GeckoViewWebExtension.sys.mjs").GeckoViewConnection,
+ JSONFile: typeof import("resource://gre/modules/JSONFile.sys.mjs").JSONFile,
+ Management: typeof import("Extension.sys.mjs").Management,
+ MessageManagerProxy: typeof import("MessageManagerProxy.sys.mjs").MessageManagerProxy,
+ NativeApp: typeof import("NativeMessaging.sys.mjs").NativeApp,
+ NativeManifests: typeof import("NativeManifests.sys.mjs").NativeManifests,
+ PERMISSION_L10N: typeof import("ExtensionPermissionMessages.sys.mjs").PERMISSION_L10N,
+ QuarantinedDomains: typeof import("ExtensionPermissions.sys.mjs").QuarantinedDomains,
+ SchemaRoot: typeof import("Schemas.sys.mjs").SchemaRoot,
+ Schemas: typeof import("Schemas.sys.mjs").Schemas,
+ WebNavigationFrames: typeof import("WebNavigationFrames.sys.mjs").WebNavigationFrames,
+ WebRequest: typeof import("webrequest/WebRequest.sys.mjs").WebRequest,
+ extensionStorageSync: typeof import("ExtensionStorageSync.sys.mjs").extensionStorageSync,
+ getErrorNameForTelemetry: typeof import("ExtensionTelemetry.sys.mjs").getErrorNameForTelemetry,
+ getTrimmedString: typeof import("ExtensionTelemetry.sys.mjs").getTrimmedString,
+};
+
+// Utility type to extract all strings from a const array, to use as keys.
+type Items<A> = A extends ReadonlyArray<infer U extends string> ? U : never;
+
+declare global {
+ type Lazy<Keys extends keyof LazyAll = keyof LazyAll> = Pick<LazyAll, Keys> & { [k: string]: any };
+
+ // Export JSDoc types, and make other classes available globally.
+ type ConduitAddress = import("ConduitsParent.sys.mjs").ConduitAddress;
+ type ConduitID = import("ConduitsParent.sys.mjs").ConduitID;
+ type Extension = import("Extension.sys.mjs").Extension;
+
+ // Something about Class type not being exported when nested in a namespace?
+ type BaseContext = InstanceType<typeof import("ExtensionCommon.sys.mjs").ExtensionCommon.BaseContext>;
+ type BrowserExtensionContent = InstanceType<typeof import("ExtensionContent.sys.mjs").ExtensionContent.BrowserExtensionContent>;
+ type EventEmitter = InstanceType<typeof import("ExtensionCommon.sys.mjs").ExtensionCommon.EventEmitter>;
+ type ExtensionAPI = InstanceType<typeof import("ExtensionCommon.sys.mjs").ExtensionCommon.ExtensionAPI>;
+ type ExtensionError = InstanceType<typeof import("ExtensionUtils.sys.mjs").ExtensionUtils.ExtensionError>;
+ type LocaleData = InstanceType<typeof import("ExtensionCommon.sys.mjs").ExtensionCommon.LocaleData>;
+ type ProxyAPIImplementation = InstanceType<typeof import("ExtensionChild.sys.mjs").ExtensionChild.ProxyAPIImplementation>;
+ type SchemaAPIInterface = InstanceType<typeof import("ExtensionCommon.sys.mjs").ExtensionCommon.SchemaAPIInterface>;
+ type WorkerExtensionError = InstanceType<typeof import("ExtensionUtils.sys.mjs").ExtensionUtils.WorkerExtensionError>;
+
+ // Other misc types.
+ type AddonWrapper = any;
+ type Context = BaseContext;
+ type NativeTab = Element;
+ type SavedFrame = object;
+
+ // Can't define a const generic parameter in jsdocs yet.
+ // https://github.com/microsoft/TypeScript/issues/56634
+ type ConduitInit<Send> = ConduitAddress & { send: Send; };
+ type Conduit<Send> = import("../ConduitsChild.sys.mjs").PointConduit & { [s in `send${Items<Send>}`]: callback };
+ type ConduitOpen = <const Send>(subject: object, address: ConduitInit<Send>) => Conduit<Send>;
+}
+
+export {}
diff --git a/toolkit/components/extensions/types/gecko.ts b/toolkit/components/extensions/types/gecko.ts
new file mode 100644
index 0000000000..f6b5190f8d
--- /dev/null
+++ b/toolkit/components/extensions/types/gecko.ts
@@ -0,0 +1,163 @@
+/**
+ * Global Gecko type declarations.
+ */
+
+// @ts-ignore
+import type { CiClass } from "lib.gecko.xpidl"
+
+declare global {
+ // Other misc types.
+ type Browser = InstanceType<typeof XULBrowserElement>;
+ type bytestring = string;
+ type callback = (...args: any[]) => any;
+ type ColorArray = number[];
+ type integer = number;
+ type JSONValue = null | boolean | number | string | JSONValue[] | { [key: string]: JSONValue };
+
+ interface Document {
+ createXULElement(name: string): Element;
+ documentReadyForIdle: Promise<void>;
+ }
+ interface EventTarget {
+ ownerGlobal: Window;
+ }
+ interface Error {
+ code;
+ }
+ interface ErrorConstructor {
+ new (message?: string, options?: ErrorOptions, lineNo?: number): Error;
+ }
+ interface Window {
+ gBrowser;
+ }
+ // HACK to get the static isInstance for DOMException and Window?
+ interface Object {
+ isInstance(object: any): boolean;
+ }
+
+ // XPIDL additions/overrides.
+
+ interface nsISupports {
+ // OMG it works!
+ QueryInterface?<T extends CiClass<nsISupports>>(aCiClass: T): T['prototype'];
+ wrappedJSObject?: object;
+ }
+ interface nsIProperties {
+ get<T extends CiClass<nsISupports>>(prop: string, aCiClass: T): T['prototype'];
+ }
+ interface nsIPrefBranch {
+ getComplexValue<T extends CiClass<nsISupports>>(aPrefName: string, aCiClass: T): T['prototype'];
+ }
+ // TODO: incorporate above into lib.xpidl.d.ts generation, somehow?
+
+ type Sandbox = typeof globalThis;
+ interface nsIXPCComponents_utils_Sandbox {
+ (principal: nsIPrincipal | nsIPrincipal[], options: object): Sandbox;
+ }
+ interface nsIXPCComponents_Utils {
+ cloneInto<T>(obj: T, ...args: any[]): T;
+ createObjectIn<T>(Sandbox, options?: T): T;
+ exportFunction<T extends callback>(f: T, ...args: any[]): T;
+ getWeakReference<T extends object>(T): { get(): T };
+ readonly Sandbox: nsIXPCComponents_utils_Sandbox;
+ waiveXrays<T>(obj: T): T;
+ }
+ interface nsIDOMWindow extends Window {
+ docShell: nsIDocShell;
+ }
+ interface Document {
+ documentURIObject: nsIURI;
+ createXULElement(name: string): Element;
+ }
+
+ // nsDocShell is the only thing implementing nsIDocShell, but it also
+ // implements nsIWebNavigation, and a few others, so this is "ok".
+ interface nsIDocShell extends nsIWebNavigation {}
+ interface nsISimpleEnumerator extends Iterable<any> {}
+
+ namespace Components {
+ type Exception = Error;
+ }
+ namespace UrlbarUtils {
+ type RESULT_TYPE = any;
+ type RESULT_SOURCE = any;
+ }
+
+ // Various mozilla globals.
+ var Cc, Cr, ChromeUtils, Components, dump, uneval;
+
+ // [ChromeOnly] WebIDL, to be generated.
+ var BrowsingContext, ChannelWrapper, ChromeWindow, ChromeWorker,
+ ClonedErrorHolder, Glean, InspectorUtils, IOUtils, JSProcessActorChild,
+ JSProcessActorParent, JSWindowActor, JSWindowActorChild,
+ JSWindowActorParent, L10nRegistry, L10nFileSource, Localization,
+ MatchGlob, MatchPattern, MatchPatternSet, PathUtils, PreloadedScript,
+ StructuredCloneHolder, TelemetryStopwatch, WindowGlobalChild,
+ WebExtensionContentScript, WebExtensionParentActor, WebExtensionPolicy,
+ XULBrowserElement, nsIMessageListenerManager;
+
+ interface XULElement extends Element {}
+
+ // nsIServices is not a thing.
+ interface nsIServices {
+ scriptloader: mozIJSSubScriptLoader;
+ locale: mozILocaleService;
+ intl: mozIMozIntl;
+ storage: mozIStorageService;
+ appShell: nsIAppShellService;
+ startup: nsIAppStartup;
+ blocklist: nsIBlocklistService;
+ cache2: nsICacheStorageService;
+ catMan: nsICategoryManager;
+ clearData: nsIClearDataService;
+ clipboard: nsIClipboard;
+ console: nsIConsoleService;
+ cookieBanners: nsICookieBannerService;
+ cookies: nsICookieManager & nsICookieService;
+ appinfo: nsICrashReporter & nsIXULAppInfo & nsIXULRuntime;
+ DAPTelemetry: nsIDAPTelemetry;
+ DOMRequest: nsIDOMRequestService;
+ dns: nsIDNSService;
+ dirsvc: nsIDirectoryService & nsIProperties;
+ droppedLinkHandler: nsIDroppedLinkHandler;
+ eTLD: nsIEffectiveTLDService;
+ policies: nsIEnterprisePolicies;
+ env: nsIEnvironment;
+ els: nsIEventListenerService;
+ fog: nsIFOG;
+ focus: nsIFocusManager;
+ io: nsIIOService & nsINetUtil & nsISpeculativeConnect;
+ loadContextInfo: nsILoadContextInfoFactory;
+ domStorageManager: nsIDOMStorageManager & nsILocalStorageManager;
+ logins: nsILoginManager;
+ obs: nsIObserverService;
+ perms: nsIPermissionManager;
+ prefs: nsIPrefBranch & nsIPrefService;
+ profiler: nsIProfiler;
+ prompt: nsIPromptService;
+ sysinfo: nsISystemInfo & nsIPropertyBag2;
+ qms: nsIQuotaManagerService;
+ rfp: nsIRFPService;
+ scriptSecurityManager: nsIScriptSecurityManager;
+ search: nsISearchService;
+ sessionStorage: nsISessionStorageService;
+ strings: nsIStringBundleService;
+ telemetry: nsITelemetry;
+ textToSubURI: nsITextToSubURI;
+ tm: nsIThreadManager;
+ uriFixup: nsIURIFixup;
+ urlFormatter: nsIURLFormatter;
+ uuid: nsIUUIDGenerator;
+ vc: nsIVersionComparator;
+ wm: nsIWindowMediator;
+ ww: nsIWindowWatcher;
+ xulStore: nsIXULStore;
+ ppmm: any;
+ cpmm: any;
+ mm: any;
+ }
+
+ var Ci: nsIXPCComponents_Interfaces;
+ var Cu: nsIXPCComponents_Utils;
+ var Services: nsIServices;
+}
diff --git a/toolkit/components/extensions/types/globals.ts b/toolkit/components/extensions/types/globals.ts
new file mode 100644
index 0000000000..45722828e2
--- /dev/null
+++ b/toolkit/components/extensions/types/globals.ts
@@ -0,0 +1,33 @@
+/**
+ * Support types for toolkit/components/extensions code.
+ */
+
+/// <reference lib="dom" />
+/// <reference path="./gecko.ts" />
+/// <reference path="./extensions.ts" />
+
+// This now relies on types generated in bug 1872918, or get the built
+// artifact tslib directly and put it in your src/node_modules/@types:
+// https://phabricator.services.mozilla.com/D197620
+/// <reference types="lib.gecko.xpidl" />
+
+// Exports for all other external modules redirected to globals.ts.
+export var AppConstants,
+ GeckoViewConnection, GeckoViewWebExtension, IndexedDB, JSONFile, Log;
+
+/**
+ * This is a mock for the "class" from EventEmitter.sys.mjs. When we import
+ * it in extensions code using resource://gre/modules/EventEmitter.sys.mjs,
+ * the catch-all rule from tsconfig.json redirects it to this file. The export
+ * of the class below fulfills the import. The mock is needed when we subclass
+ * that EventEmitter, typescript gets confused because it's an old style
+ * function-and-prototype-based "class", and some types don't match up.
+ *
+ * TODO: Convert EventEmitter.sys.mjs into a proper class.
+ */
+export declare class EventEmitter {
+ on(event: string, listener: callback): void;
+ once(event: string, listener: callback): Promise<any>;
+ off(event: string, listener: callback): void;
+ emit(event: string, ...args: any[]): void;
+}