Bug 1336308: Part 3 - Add inline documentation for tabs API. r?aswan draft
authorKris Maglione <maglione.k@gmail.com>
Thu, 02 Feb 2017 19:30:56 -0800
changeset 470555 a334e32060e469fdfdd2fc42c88c0e45739e4956
parent 470554 b6674a9c45b36773cbf80bdb55e5d22aaab2b843
child 470556 60723adac29fe79ef3c87f087bb88734dcd494ff
push id44075
push usermaglione.k@gmail.com
push dateFri, 03 Feb 2017 23:30:42 +0000
reviewersaswan
bugs1336308
milestone54.0a1
Bug 1336308: Part 3 - Add inline documentation for tabs API. r?aswan MozReview-Commit-ID: 9ZwCkHcVv15
browser/components/extensions/ext-utils.js
toolkit/components/extensions/ExtensionTabs.jsm
--- a/browser/components/extensions/ext-utils.js
+++ b/browser/components/extensions/ext-utils.js
@@ -197,16 +197,21 @@ class TabTracker extends TabTrackerBase 
       return tab;
     }
     if (default_ !== undefined) {
       return default_;
     }
     throw new ExtensionError(`Invalid tab ID: ${tabId}`);
   }
 
+  /**
+   * @param {Event} event
+   *        The DOM Event to handle.
+   * @private
+   */
   handleEvent(event) {
     let tab = event.target;
 
     switch (event.type) {
       case "TabOpen":
         let {adoptedTab} = event.detail;
         if (adoptedTab) {
           this.adoptedTabs.set(adoptedTab, event.target);
@@ -243,16 +248,24 @@ class TabTracker extends TabTrackerBase 
           this.emitDetached(tab, adoptedBy);
         } else {
           this.emitRemoved(tab, false);
         }
         break;
     }
   }
 
+  /**
+   * A private method which is called whenever a new browser window is opened,
+   * and dispatches the necessary events for it.
+   *
+   * @param {DOMWindow} window
+   *        The window being opened.
+   * @private
+   */
   _handleWindowOpen(window) {
     if (window.arguments && window.arguments[0] instanceof window.XULElement) {
       // If the first window argument is a XUL element, it means the
       // window is about to adopt a tab from another window to replace its
       // initial tab.
       //
       // Note that this event handler depends on running before the
       // delayed startup code in browser.js, which is currently triggered
@@ -280,44 +293,86 @@ class TabTracker extends TabTrackerBase 
       this.on("tab-detached", listener);
     } else {
       for (let tab of window.gBrowser.tabs) {
         this.emitCreated(tab);
       }
     }
   }
 
+  /**
+   * A private method which is called whenever a browser window is closed,
+   * and dispatches the necessary events for it.
+   *
+   * @param {DOMWindow} window
+   *        The window being closed.
+   * @private
+   */
   _handleWindowClose(window) {
     for (let tab of window.gBrowser.tabs) {
       if (this.adoptedTabs.has(tab)) {
         this.emitDetached(tab, this.adoptedTabs.get(tab));
       } else {
         this.emitRemoved(tab, true);
       }
     }
   }
 
+  /**
+   * Emits a "tab-attached" event for the given tab element.
+   *
+   * @param {NativeTab} tab
+   *        The tab element in the window to which the tab is being attached.
+   * @private
+   */
   emitAttached(tab) {
     let newWindowId = windowTracker.getId(tab.ownerGlobal);
     let tabId = this.getId(tab);
 
     this.emit("tab-attached", {tab, tabId, newWindowId, newPosition: tab._tPos});
   }
 
+  /**
+   * Emits a "tab-detached" event for the given tab element.
+   *
+   * @param {NativeTab} tab
+   *        The tab element in the window from which the tab is being detached.
+   * @param {NativeTab} adoptedBy
+   *        The tab element in the window to which detached tab is being moved,
+   *        and will adopt this tab's contents.
+   * @private
+   */
   emitDetached(tab, adoptedBy) {
     let oldWindowId = windowTracker.getId(tab.ownerGlobal);
     let tabId = this.getId(tab);
 
     this.emit("tab-detached", {tab, adoptedBy, tabId, oldWindowId, oldPosition: tab._tPos});
   }
 
+  /**
+   * Emits a "tab-created" event for the given tab element.
+   *
+   * @param {NativeTab} tab
+   *        The tab element which is being created.
+   * @private
+   */
   emitCreated(tab) {
     this.emit("tab-created", {tab});
   }
 
+  /**
+   * Emits a "tab-removed" event for the given tab element.
+   *
+   * @param {NativeTab} tab
+   *        The tab element which is being removed.
+   * @param {boolean} isWindowClosing
+   *        True if the tab is being removed because the browser window is
+   *        closing.
+   * @private
+   */
   emitRemoved(tab, isWindowClosing) {
     let windowId = windowTracker.getId(tab.ownerGlobal);
     let tabId = this.getId(tab);
 
     // When addons run in-process, `window.close()` is synchronous. Most other
     // addon-invoked calls are asynchronous since they go through a proxy
     // context via the message manager. This includes event registrations such
     // as `tabs.onRemoved.addListener`.
@@ -394,20 +449,16 @@ class Tab extends TabBase {
   get height() {
     return this.browser.clientHeight;
   }
 
   get index() {
     return this.tab._tPos;
   }
 
-  get innerWindowID() {
-    return this.browser.innerWindowID;
-  }
-
   get mutedInfo() {
     let tab = this.tab;
 
     let mutedInfo = {muted: tab.muted};
     if (tab.muteReason === null) {
       mutedInfo.reason = "user";
     } else if (tab.muteReason) {
       mutedInfo.reason = "extension";
@@ -443,42 +494,74 @@ class Tab extends TabBase {
   get window() {
     return this.tab.ownerGlobal;
   }
 
   get windowId() {
     return windowTracker.getId(this.window);
   }
 
-  static convertFromSessionStoreClosedData(extension, tab, window = null) {
+  /**
+   * Converts session store data to an object compatible with the return value
+   * of the convert() method, representing that data.
+   *
+   * @param {Extension} extension
+   *        The extension for which to convert the data.
+   * @param {Object} tabData
+   *        Session store data for a closed tab, as returned by
+   *        `SessionStore.getClosedTabData()`.
+   * @param {DOMWindow} [window = null]
+   *        The browser window which the tab belonged to before it was closed.
+   *        May be null if the window the tab belonged to no longer exists.
+   *
+   * @returns {Object}
+   * @static
+   */
+  static convertFromSessionStoreClosedData(extension, tabData, window = null) {
     let result = {
-      sessionId: String(tab.closedId),
-      index: tab.pos ? tab.pos : 0,
+      sessionId: String(tabData.closedId),
+      index: tabData.pos ? tabData.pos : 0,
       windowId: window && windowTracker.getId(window),
       selected: false,
       highlighted: false,
       active: false,
       pinned: false,
-      incognito: Boolean(tab.state && tab.state.isPrivate),
+      incognito: Boolean(tabData.state && tabData.state.isPrivate),
     };
 
-    if (extension.tabManager.hasTabPermission(tab)) {
-      let entries = tab.state ? tab.state.entries : tab.entries;
+    if (extension.tabManager.hasTabPermission(tabData)) {
+      let entries = tabData.state ? tabData.state.entries : tabData.entries;
       result.url = entries[0].url;
       result.title = entries[0].title;
-      if (tab.image) {
-        result.favIconUrl = tab.image;
+      if (tabData.image) {
+        result.favIconUrl = tabData.image;
       }
     }
 
     return result;
   }
 }
 
 class Window extends WindowBase {
+  /**
+   * Update the geometry of the browser window.
+   *
+   * @param {Object} options
+   *        An object containing new values for the window's geometry.
+   * @param {integer} [options.left]
+   *        The new pixel distance of the left side of the browser window from
+   *        the left of the screen.
+   * @param {integer} [options.top]
+   *        The new pixel distance of the top side of the browser window from
+   *        the top of the screen.
+   * @param {integer} [options.width]
+   *        The new pixel width of the window.
+   * @param {integer} [options.height]
+   *        The new pixel height of the window.
+   */
   updateGeometry(options) {
     let {window} = this;
 
     if (options.left !== null || options.top !== null) {
       let left = options.left !== null ? options.left : window.screenX;
       let top = options.top !== null ? options.top : window.screenY;
       window.moveTo(left, top);
     }
@@ -582,29 +665,42 @@ class Window extends WindowBase {
   * getTabs() {
     let {tabManager} = this.extension;
 
     for (let tab of this.window.gBrowser.tabs) {
       yield tabManager.getWrapper(tab);
     }
   }
 
-  static convertFromSessionStoreClosedData(extension, window) {
+  /**
+   * Converts session store data to an object compatible with the return value
+   * of the convert() method, representing that data.
+   *
+   * @param {Extension} extension
+   *        The extension for which to convert the data.
+   * @param {Object} windowData
+   *        Session store data for a closed window, as returned by
+   *        `SessionStore.getClosedWindowData()`.
+   *
+   * @returns {Object}
+   * @static
+   */
+  static convertFromSessionStoreClosedData(extension, windowData) {
     let result = {
-      sessionId: String(window.closedId),
+      sessionId: String(windowData.closedId),
       focused: false,
       incognito: false,
       type: "normal", // this is always "normal" for a closed window
       // Surely this does not actually work?
-      state: this.getState(window),
+      state: this.getState(windowData),
       alwaysOnTop: false,
     };
 
-    if (window.tabs.length) {
-      result.tabs = window.tabs.map(tab => {
+    if (windowData.tabs.length) {
+      result.tabs = windowData.tabs.map(tab => {
         return Tab.convertFromSessionStoreClosedData(extension, tab);
       });
     }
 
     return result;
   }
 }
 
--- a/toolkit/components/extensions/ExtensionTabs.jsm
+++ b/toolkit/components/extensions/ExtensionTabs.jsm
@@ -22,16 +22,50 @@ Cu.import("resource://gre/modules/Extens
 
 const {
   DefaultMap,
   DefaultWeakMap,
   EventEmitter,
   ExtensionError,
 } = ExtensionUtils;
 
+/**
+ * The platform-specific type of native tab objects, which are wrapped by
+ * TabBase instances.
+ *
+ * @typedef {Object|XULElement} NativeTab
+ */
+
+/**
+ * @typedef {Object} MutedInfo
+ * @property {boolean} muted
+ *        True if the tab is currently muted, false otherwise.
+ * @property {string} [reason]
+ *        The reason the tab is muted. Either "user", if the tab was muted by a
+ *        user, or "extension", if it was muted by an extension.
+ * @property {string} [extensionId]
+ *        If the tab was muted by an extension, contains the internal ID of that
+ *        extension.
+ */
+
+/**
+ * A platform-independent base class for extension-specific wrappers around
+ * native tab objects.
+ *
+ * @param {Extension} extension
+ *        The extension object for which this wrapper is being created. Used to
+ *        determine permissions for access to certain properties and
+ *        functionality.
+ * @param {NativeTab} tab
+ *        The native tab object which is being wrapped. The type of this object
+ *        varies by platform.
+ * @param {integer} id
+ *        The numeric ID of this tab object. This ID should be the same for
+ *        every extension, and for the lifetime of the tab.
+ */
 class TabBase {
   constructor(extension, tab, id) {
     this.extension = extension;
     this.tabManager = extension.tabManager;
     this.id = id;
     this.tab = tab;
     this.activeTabWindowID = null;
   }
@@ -95,67 +129,310 @@ class TabBase {
       options,
       width: this.width,
       height: this.height,
     };
 
     return this.sendMessage(context, "Extension:Capture", message);
   }
 
+  /**
+   * @property {integer | null} innerWindowID
+   *        The last known innerWindowID loaded into this tab's docShell. This
+   *        property must remain in sync with the last known values of
+   *        properties such as `url` and `title`. Any operations on the content
+   *        of an out-of-process tab will automatically fail if the
+   *        innerWindowID of the tab when the message is received does not match
+   *        the value of this property when the message was sent.
+   *        @readonly
+   */
   get innerWindowID() {
     return this.browser.innerWindowID;
   }
 
+  /**
+   * @property {boolean} hasTabPermission
+   *        Returns true if the extension has permission to access restricted
+   *        properties of this tab, such as `url`, `title`, and `favIconUrl`.
+   *        @readonly
+   */
   get hasTabPermission() {
     return this.extension.hasPermission("tabs") || this.hasActiveTabPermission;
   }
 
+  /**
+   * @property {boolean} hasActiveTabPermission
+   *        Returns true if the extension has the "activeTab" permission, and
+   *        has been granted access to this tab due to a user executing an
+   *        extension action.
+   *
+   *        If true, the extension may load scripts and CSS into this tab, and
+   *        access restricted properties, such as its `url`.
+   *        @readonly
+   */
   get hasActiveTabPermission() {
     return (this.extension.hasPermission("activeTab") &&
             this.activeTabWindowID != null &&
             this.activeTabWindowID === this.innerWindowID);
   }
 
+  /**
+   * @property {boolean} incognito
+   *        Returns true if this is a private browsing tab, false otherwise.
+   *        @readonly
+   */
   get incognito() {
     return PrivateBrowsingUtils.isBrowserPrivate(this.browser);
   }
 
+  /**
+   * @property {string} _url
+   *        Returns the current URL of this tab. Does not do any permission
+   *        checks.
+   *        @readonly
+   */
   get _url() {
     return this.browser.currentURI.spec;
   }
 
+  /**
+   * @property {string | null} url
+   *        Returns the current URL of this tab if the extension has permission
+   *        to read it, or null otherwise.
+   *        @readonly
+   */
   get url() {
     if (this.hasTabPermission) {
       return this._url;
     }
   }
 
+  /**
+   * @property {nsIURI | null} uri
+   *        Returns the current URI of this tab if the extension has permission
+   *        to read it, or null otherwise.
+   *        @readonly
+   */
   get uri() {
     if (this.hasTabPermission) {
       return this.browser.currentURI;
     }
   }
 
+  /**
+   * @property {string} _title
+   *        Returns the current title of this tab. Does not do any permission
+   *        checks.
+   *        @readonly
+   */
   get _title() {
     return this.browser.contentTitle || this.tab.label;
   }
 
 
+  /**
+   * @property {nsIURI | null} title
+   *        Returns the current title of this tab if the extension has permission
+   *        to read it, or null otherwise.
+   *        @readonly
+   */
   get title() {
     if (this.hasTabPermission) {
       return this._title;
     }
   }
 
+  /**
+   * @property {string} _favIconUrl
+   *        Returns the current favicon URL of this tab. Does not do any permission
+   *        checks.
+   *        @readonly
+   *        @abstract
+   */
+  get _favIconUrl() {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * @property {nsIURI | null} faviconUrl
+   *        Returns the current faviron URL of this tab if the extension has permission
+   *        to read it, or null otherwise.
+   *        @readonly
+   */
   get favIconUrl() {
     if (this.hasTabPermission) {
       return this._favIconUrl;
     }
   }
 
+  /**
+   * @property {boolean} audible
+   *        Returns true if the tab is currently playing audio, false otherwise.
+   *        @readonly
+   *        @abstract
+   */
+  get audible() {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * @property {XULElement} browser
+   *        Returns the XUL browser for the given tab.
+   *        @readonly
+   *        @abstract
+   */
+  get browser() {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * @property {string} cookieStoreId
+   *        Returns the cookie store identifier for the given tab.
+   *        @readonly
+   *        @abstract
+   */
+  get cookieStoreId() {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * @property {integer} height
+   *        Returns the pixel height of the visible area of the tab.
+   *        @readonly
+   *        @abstract
+   */
+  get height() {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * @property {integer} index
+   *        Returns the index of the tab in its window's tab list.
+   *        @readonly
+   *        @abstract
+   */
+  get index() {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * @property {MutedInfo} mutedInfo
+   *        Returns information about the tab's current audio muting status.
+   *        @readonly
+   *        @abstract
+   */
+  get mutedInfo() {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * @property {boolean} pinned
+   *        Returns true if the tab is pinned, false otherwise.
+   *        @readonly
+   *        @abstract
+   */
+  get pinned() {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * @property {boolean} active
+   *        Returns true if the tab is the currently-selected tab, false
+   *        otherwise.
+   *        @readonly
+   *        @abstract
+   */
+  get active() {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * @property {boolean} selected
+   *        An alias for `active`.
+   *        @readonly
+   *        @abstract
+   */
+  get selected() {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * @property {string} status
+   *        Returns the current loading status of the tab. May be either
+   *        "loading" or "complete".
+   *        @readonly
+   *        @abstract
+   */
+  get status() {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * @property {integer} height
+   *        Returns the pixel height of the visible area of the tab.
+   *        @readonly
+   *        @abstract
+   */
+  get width() {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * @property {DOMWindow} window
+   *        Returns the browser window to which the tab belongs.
+   *        @readonly
+   *        @abstract
+   */
+  get window() {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * @property {integer} window
+   *        Returns the numeric ID of the browser window to which the tab belongs.
+   *        @readonly
+   *        @abstract
+   */
+  get windowId() {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * Returns true if this tab matches the the given query info object. Omitted
+   * or null have no effect on the match.
+   *
+   * @param {object} queryInfo
+   *        The query info against which to match.
+   * @param {boolean} [queryInfo.active]
+   *        Matches against the exact value of the tab's `active` attribute.
+   * @param {boolean} [queryInfo.audible]
+   *        Matches against the exact value of the tab's `audible` attribute.
+   * @param {string} [queryInfo.cookieStoreId]
+   *        Matches against the exact value of the tab's `cookieStoreId` attribute.
+   * @param {boolean} [queryInfo.highlighted]
+   *        Matches against the exact value of the tab's `highlighted` attribute.
+   * @param {integer} [queryInfo.index]
+   *        Matches against the exact value of the tab's `index` attribute.
+   * @param {boolean} [queryInfo.muted]
+   *        Matches against the exact value of the tab's `mutedInfo.muted` attribute.
+   * @param {boolean} [queryInfo.pinned]
+   *        Matches against the exact value of the tab's `pinned` attribute.
+   * @param {string} [queryInfo.status]
+   *        Matches against the exact value of the tab's `status` attribute.
+   * @param {string} [queryInfo.title]
+   *        Matches against the exact value of the tab's `title` attribute.
+   *
+   *        Note: Per specification, this should perform a pattern match, rather
+   *        than an exact value match, and will do so in the future.
+   * @param {MatchPattern} [queryInfo.url]
+   *        Requires the tab's URL to match the given MatchPattern object.
+   *
+   * @returns {boolean}
+   *        True if the tab matches the query.
+   */
   matches(queryInfo) {
     const PROPS = ["active", "audible", "cookieStoreId", "highlighted", "index", "pinned", "status", "title"];
 
     if (PROPS.some(prop => queryInfo[prop] !== null && queryInfo[prop] !== this[prop])) {
       return false;
     }
 
     if (queryInfo.muted !== null) {
@@ -166,16 +443,23 @@ class TabBase {
 
     if (queryInfo.url && !queryInfo.url.matches(this.uri)) {
       return false;
     }
 
     return true;
   }
 
+  /**
+   * Converts this tab object to a JSON-compatible object containing the values
+   * of its properties which the extension is permitted to access, in the format
+   * requried to be returned by WebExtension APIs.
+   *
+   * @returns {object}
+   */
   convert() {
     let result = {
       id: this.id,
       index: this.index,
       windowId: this.windowId,
       selected: this.selected,
       highlighted: this.selected,
       active: this.selected,
@@ -201,16 +485,35 @@ class TabBase {
           result[prop] = val;
         }
       }
     }
 
     return result;
   }
 
+  /**
+   * Inserts a script or stylesheet in the given tab, and returns a promise
+   * which resolves when the operation has completed.
+   *
+   * @param {BaseContext} context
+   *        The extension context for which to perform the injection.
+   * @param {InjectDetails} details
+   *        The InjectDetails object, specifying what to inject, where, and
+   *        when.
+   * @param {string} kind
+   *        The kind of data being injected. Either "script" or "css".
+   * @param {string} method
+   *        The name of the method which was called to trigger the injection.
+   *        Used to generate appropriate error messages on failure.
+   *
+   * @returns {Promise}
+   *        Resolves to the result of the execution, once it has completed.
+   * @private
+   */
   _execute(context, details, kind, method) {
     let options = {
       js: [],
       css: [],
       remove_css: method == "removeCSS",
     };
 
     // We require a `code` or a `file` property, but we can't accept both.
@@ -258,64 +561,153 @@ class TabBase {
       options.css_origin = details.cssOrigin;
     } else {
       options.css_origin = "author";
     }
 
     return this.sendMessage(context, "Extension:Execute", {options});
   }
 
+  /**
+   * Executes a script in the tab's content window, and returns a Promise which
+   * resolves to the result of the evaluation, or rejects to the value of any
+   * error the injection generates.
+   *
+   * @param {BaseContext} context
+   *        The extension context for which to inject the script.
+   * @param {InjectDetails} details
+   *        The InjectDetails object, specifying what to inject, where, and
+   *        when.
+   *
+   * @returns {Promise}
+   *        Resolves to the result of the evaluation of the given script, once
+   *        it has completed, or rejects with any error the evaluation
+   *        generates.
+   */
   executeScript(context, details) {
     return this._execute(context, details, "js", "executeScript");
   }
 
+  /**
+   * Injects CSS into the tab's content window, and returns a Promise which
+   * resolves when the injection is complete.
+   *
+   * @param {BaseContext} context
+   *        The extension context for which to inject the script.
+   * @param {InjectDetails} details
+   *        The InjectDetails object, specifying what to inject, and where.
+   *
+   * @returns {Promise}
+   *        Resolves when the injection has completed.
+   */
   insertCSS(context, details) {
     return this._execute(context, details, "css", "insertCSS").then(() => {});
   }
 
+
+  /**
+   * Removes CSS which was previously into the tab's content window via
+   * `insertCSS`, and returns a Promise which resolves when the operation is
+   * complete.
+   *
+   * @param {BaseContext} context
+   *        The extension context for which to remove the CSS.
+   * @param {InjectDetails} details
+   *        The InjectDetails object, specifying what to remove, and from where.
+   *
+   * @returns {Promise}
+   *        Resolves when the operation has completed.
+   */
   removeCSS(context, details) {
     return this._execute(context, details, "css", "removeCSS").then(() => {});
   }
 }
 
 // Note: These must match the values in windows.json.
 const WINDOW_ID_NONE = -1;
 const WINDOW_ID_CURRENT = -2;
 
+/**
+ * A platform-independent base class for extension-specific wrappers around
+ * native browser windows
+ *
+ * @param {Extension} extension
+ *        The extension object for which this wrapper is being created.
+ * @param {DOMWindow} window
+ *        The browser DOM window which is being wrapped.
+ * @param {integer} id
+ *        The numeric ID of this DOM window object. This ID should be the same for
+ *        every extension, and for the lifetime of the window.
+ */
 class WindowBase {
   constructor(extension, window, id) {
     this.extension = extension;
     this.window = window;
     this.id = id;
   }
 
+  /**
+   * @property {nsIXULWindow} xulWindow
+   *        The nsIXULWindow object for this browser window.
+   *        @readonly
+   */
   get xulWindow() {
     return this.window.QueryInterface(Ci.nsIInterfaceRequestor)
                .getInterface(Ci.nsIDocShell)
                .treeOwner.QueryInterface(Ci.nsIInterfaceRequestor)
                .getInterface(Ci.nsIXULWindow);
   }
 
+  /**
+   * Returns true if this window is the current window for the given extension
+   * context, false otherwise.
+   *
+   * @param {BaseContext} context
+   *        The extension context for which to perform the check.
+   *
+   * @returns {boolean}
+   */
   isCurrentFor(context) {
     if (context && context.currentWindow) {
       return this.window === context.currentWindow;
     }
     return this.isLastFocused;
   }
 
+  /**
+   * @property {string} type
+   *        The type of the window, as defined by the WebExtension API. May be
+   *        either "normal" or "popup".
+   *        @readonly
+   */
   get type() {
     let {chromeFlags} = this.xulWindow;
 
     if (chromeFlags & Ci.nsIWebBrowserChrome.CHROME_OPENAS_DIALOG) {
       return "popup";
     }
 
     return "normal";
   }
 
+  /**
+   * Converts this window object to a JSON-compatible object which may be
+   * returned to an extension, in the format requried to be returned by
+   * WebExtension APIs.
+   *
+   * @param {object} [getInfo]
+   *        An optional object, the properties of which determine what data is
+   *        available on the result object.
+   * @param {boolean} [getInfo.populate]
+   *        Of true, the result object will contain a `tabs` property,
+   *        containing an array of converted Tab objects, one for each tab in
+   *        the window.
+   *
+   * @returns {object}
+   */
   convert(getInfo) {
     let result = {
       id: this.id,
       focused: this.focused,
       top: this.top,
       left: this.left,
       width: this.width,
       height: this.height,
@@ -327,16 +719,39 @@ class WindowBase {
 
     if (getInfo && getInfo.populate) {
       result.tabs = Array.from(this.getTabs(), tab => tab.convert());
     }
 
     return result;
   }
 
+  /**
+   * Returns true if this window matches the the given query info object. Omitted
+   * or null have no effect on the match.
+   *
+   * @param {object} queryInfo
+   *        The query info against which to match.
+   * @param {boolean} [queryInfo.currentWindow]
+   *        Matches against against the return value of `isCurrentFor()` for the
+   *        given context.
+   * @param {boolean} [queryInfo.lastFocusedWindow]
+   *        Matches against the exact value of the window's `isLastFocused` attribute.
+   * @param {boolean} [queryInfo.windowId]
+   *        Matches against the exact value of the window's ID, taking into
+   *        account the special WINDOW_ID_CURRENT value.
+   * @param {string} [queryInfo.windowType]
+   *        Matches against the exact value of the window's `type` attribute.
+   * @param {BaseContext} context
+   *        The extension context for which the matching is being performed.
+   *        Used to determine the current window for relevant properties.
+   *
+   * @returns {boolean}
+   *        True if the window matches the query.
+   */
   matches(queryInfo, context) {
     if (queryInfo.lastFocusedWindow !== null && queryInfo.lastFocusedWindow !== this.isLastFocused) {
       return false;
     }
 
     if (queryInfo.windowType !== null && queryInfo.windowType !== this.type) {
       return false;
     }
@@ -352,30 +767,331 @@ class WindowBase {
     }
 
     if (queryInfo.currentWindow !== null && queryInfo.currentWindow !== this.isCurrentFor(context)) {
       return false;
     }
 
     return true;
   }
+
+  /**
+   * @property {boolean} focused
+   *        Returns true if the browser window is currently focused.
+   *        @readonly
+   *        @abstract
+   */
+  get focused() {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * @property {integer} top
+   *        Returns the pixel offset of the top of the window from the top of
+   *        the screen.
+   *        @readonly
+   *        @abstract
+   */
+  get top() {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * @property {integer} left
+   *        Returns the pixel offset of the left of the window from the left of
+   *        the screen.
+   *        @readonly
+   *        @abstract
+   */
+  get left() {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * @property {integer} width
+   *        Returns the pixel width of the window.
+   *        @readonly
+   *        @abstract
+   */
+  get width() {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * @property {integer} height
+   *        Returns the pixel height of the window.
+   *        @readonly
+   *        @abstract
+   */
+  get height() {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * @property {boolean} incognito
+   *        Returns true if this is a private browsing window, false otherwise.
+   *        @readonly
+   *        @abstract
+   */
+  get incognito() {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * @property {boolean} alwaysOnTop
+   *        Returns true if this window is constrained to always remain above
+   *        other windows.
+   *        @readonly
+   *        @abstract
+   */
+  get alwaysOnTop() {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * @property {boolean} isLastFocused
+   *        Returns true if this is the browser window which most recently had
+   *        focus.
+   *        @readonly
+   *        @abstract
+   */
+  get isLastFocused() {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * @property {string} state
+   *        Returns or sets the current state of this window, as determined by
+   *        `getState()`.
+   *        @abstract
+   */
+  get state() {
+    throw new Error("Not implemented");
+  }
+
+  set state(state) {
+    throw new Error("Not implemented");
+  }
+
+  // The JSDoc validator does not support @returns tags in abstract functions or
+  // star functions without return statements.
+  /* eslint-disable valid-jsdoc */
+  /**
+   * Returns the window state of the given window.
+   *
+   * @param {DOMWindow} window
+   *        The window for which to return a state.
+   *
+   * @returns {string}
+   *        The window's state. One of "normal", "minimized", "maximized",
+   *        "fullscreen", or "docked".
+   * @static
+   * @abstract
+   */
+  static getState(window) {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * Returns an iterator of TabBase objects for each tab in this window.
+   *
+   * @returns {Iterator<TabBase>}
+   */
+  * getTabs() {
+    throw new Error("Not implemented");
+  }
+  /* eslint-enable valid-jsdoc */
 }
 
 Object.assign(WindowBase, {WINDOW_ID_NONE, WINDOW_ID_CURRENT});
 
+/**
+ * The parameter type of "tab-attached" events, which are emitted when a
+ * pre-existing tab is attached to a new window.
+ *
+ * @typedef {Object} TabAttachedEvent
+ * @property {NativeTab} tab
+ *        The native tab object in the window to which the tab is being
+ *        attached. This may be a different object than was used to represent
+ *        the tab in the old window.
+ * @property {integer} tabId
+ *        The ID of the tab being attached.
+ * @property {integer} newWindowId
+ *        The ID of the window to which the tab is being attached.
+ * @property {integer} newPosition
+ *        The position of the tab in the tab list of the new window.
+ */
+
+/**
+ * The parameter type of "tab-detached" events, which are emitted when a
+ * pre-existing tab is detached from a window, in order to be attached to a new
+ * window.
+ *
+ * @typedef {Object} TabDetachedEvent
+ * @property {NativeTab} tab
+ *        The native tab object in the window from which the tab is being
+ *        detached. This may be a different object than will be used to
+ *        represent the tab in the new window.
+ * @property {NativeTab} adoptedBy
+ *        The native tab object in the window to which the tab will be attached,
+ *        and is adopting the contents of this tab. This may be a different
+ *        object than the tab in the previous window.
+ * @property {integer} tabId
+ *        The ID of the tab being detached.
+ * @property {integer} oldWindowId
+ *        The ID of the window from which the tab is being detached.
+ * @property {integer} oldPosition
+ *        The position of the tab in the tab list of the window from which it is
+ *        being detached.
+ */
+
+/**
+ * The parameter type of "tab-created" events, which are emitted when a
+ * new tab is created.
+ *
+ * @typedef {Object} TabCreatedEvent
+ * @property {NativeTab} tab
+ *        The native tab object for the tab which is being created.
+ */
+
+/**
+ * The parameter type of "tab-removed" events, which are emitted when a
+ * tab is removed and destroyed.
+ *
+ * @typedef {Object} TabRemovedEvent
+ * @property {NativeTab} tab
+ *        The native tab object for the tab which is being removed.
+ * @property {integer} tabId
+ *        The ID of the tab being removed.
+ * @property {integer} windowId
+ *        The ID of the window from which the tab is being removed.
+ * @property {boolean} isWindowClosing
+ *        True if the tab is being removed because the window is closing.
+ */
+
+/**
+ * An object containg basic, extension-independent information about the window
+ * and tab that a XUL <browser> belongs to.
+ *
+ * @typedef {Object} BrowserData
+ * @property {integer} tabId
+ *        The numeric ID of the tab that a <browser> belongs to, or -1 if it
+ *        does not belong to a tab.
+ * @property {integer} windowId
+ *        The numeric ID of the browser window that a <browser> belongs to, or -1
+ *        if it does not belong to a browser window.
+ */
+
+/**
+ * A platform-independent base class for the platform-specific TabTracker
+ * classes, which track the opening and closing of tabs, and manage the mapping
+ * of them between numeric IDs and native tab objects.
+ *
+ * Instances of this class are EventEmitters which emit the following events,
+ * each with an argument of the given type:
+ *
+ * - "tab-attached" {@link TabAttacheEvent}
+ * - "tab-detached" {@link TabDetachedEvent}
+ * - "tab-created" {@link TabCreatedEvent}
+ * - "tab-removed" {@link TabRemovedEvent}
+ */
 class TabTrackerBase extends EventEmitter {
   on(...args) {
     if (!this.initialized) {
       this.init();
     }
 
     return super.on(...args); // eslint-disable-line mozilla/balanced-listeners
   }
+
+
+  /**
+   * Called to initialize the tab tracking listeners the first time that an
+   * event listener is added.
+   *
+   * @protected
+   * @abstract
+   */
+  init() {
+    throw new Error("Not implemented");
+  }
+
+  // The JSDoc validator does not support @returns tags in abstract functions or
+  // star functions without return statements.
+  /* eslint-disable valid-jsdoc */
+  /**
+   * Returns the numeric ID for the given native tab.
+   *
+   * @param {NativeTab} tab
+   *        The native tab for which to return an ID.
+   *
+   * @returns {integer}
+   *        The tab's numeric ID.
+   * @abstract
+   */
+  getId(tab) {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * Returns the native tab with the given numeric ID.
+   *
+   * @param {integer} tabId
+   *        The numeric ID of the tab to return.
+   * @param {*} default_
+   *        The value to return if no tab exists with the given ID.
+   *
+   * @returns {NativeTab}
+   * @throws {ExtensionError}
+   *       If no tab exists with the given ID and a default return value is not
+   *       provided.
+   * @abstract
+   */
+  getTab(tabId, default_ = undefined) {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * Returns basic information about the tab and window that the given browser
+   * belongs to.
+   *
+   * @param {XULElement} browser
+   *        The XUL browser element for which to return data.
+   *
+   * @returns {BrowserData}
+   * @abstract
+   */
+  /* eslint-enable valid-jsdoc */
+  getBrowserData(browser) {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * @property {NativeTab} activeTab
+   *        Returns the native tab object for the active tab in the
+   *        most-recently focused window, or null if no live tabs currently
+   *        exist.
+   *        @abstract
+   */
+  get activeTab() {
+    throw new Error("Not implemented");
+  }
 }
 
+/**
+ * A browser progress listener instance which calls a given listener function
+ * whenever the status of the given browser changes.
+ *
+ * @param {function(Object)} listener
+ *        A function to be called whenever the status of a tab's top-level
+ *        browser. It is passed an object with a `browser` property pointing to
+ *        the XUL browser, and a `status` property with a string description of
+ *        the browser's status.
+ * @private
+ */
 class StatusListener {
   constructor(listener) {
     this.listener = listener;
   }
 
   onStateChange(browser, webProgress, request, stateFlags, statusCode) {
     if (!webProgress.isTopLevel) {
       return;
@@ -401,16 +1117,21 @@ class StatusListener {
   onLocationChange(browser, webProgress, request, locationURI, flags) {
     if (webProgress.isTopLevel) {
       let status = webProgress.isLoadingDocument ? "loading" : "complete";
       this.listener({browser, status, url: locationURI.spec});
     }
   }
 }
 
+/**
+ * A platform-independent base class for the platform-specific WindowTracker
+ * classes, which track the opening and closing of windows, and manage the
+ * mapping of them between numeric IDs and native tab objects.
+ */
 class WindowTrackerBase extends EventEmitter {
   constructor() {
     super();
 
     this._handleWindowOpened = this._handleWindowOpened.bind(this);
 
     this._openListeners = new Set();
     this._closeListeners = new Set();
@@ -429,18 +1150,29 @@ class WindowTrackerBase extends EventEmi
   }
 
   isBrowserWindow(window) {
     let {documentElement} = window.document;
 
     return documentElement.getAttribute("windowtype") === "navigator:browser";
   }
 
-  // Returns an iterator for all browser windows. Unless |includeIncomplete| is
-  // true, only fully-loaded windows are returned.
+  // The JSDoc validator does not support @returns tags in abstract functions or
+  // star functions without return statements.
+  /* eslint-disable valid-jsdoc */
+  /**
+   * Returns an iterator for all currently active browser windows.
+   *
+   * @param {boolean} [includeInomplete = false]
+   *        If true, include browser windows which are not yet fully loaded.
+   *        Otherwise, only include windows which are.
+   *
+   * @returns {Iterator<DOMWindow>}
+   */
+  /* eslint-enable valid-jsdoc */
   * browserWindows(includeIncomplete = false) {
     // The window type parameter is only available once the window's document
     // element has been created. This means that, when looking for incomplete
     // browser windows, we need to ignore the type entirely for windows which
     // haven't finished loading, since we would otherwise skip browser windows
     // in their early loading stages.
     // This is particularly important given that the "domwindowcreated" event
     // fires for browser windows when they're in that in-between state, and just
@@ -456,87 +1188,162 @@ class WindowTrackerBase extends EventEmi
       }
 
       if (ok) {
         yield window;
       }
     }
   }
 
+  /**
+   * @property {DOMWindow|null} topWindow
+   *        The currently active, or topmost, browser window, or null if no
+   *        browser window is currently open.
+   *        @readonly
+   */
   get topWindow() {
     return Services.wm.getMostRecentWindow("navigator:browser");
   }
 
+  /**
+   * Returns the numeric ID for the given browser window.
+   *
+   * @param {DOMWindow} window
+   *        The DOM window for which to return an ID.
+   *
+   * @returns {integer}
+   *        The window's numeric ID.
+   */
   getId(window) {
     return this._windowIds.get(window);
   }
 
+  /**
+   * Returns the browser window to which the given context belongs, or the top
+   * browser window if the context does not belong to a browser window.
+   *
+   * @param {BaseContext} context
+   *        The extension context for which to return the current window.
+   *
+   * @returns {DOMWindow|null}
+   */
   getCurrentWindow(context) {
-    let {xulWindow} = context;
-    if (xulWindow && context.viewType !== "background") {
-      return xulWindow;
-    }
-    return this.topWindow;
+    return context.currentWindow || this.topWindow;
   }
 
+  /**
+   * Returns the browser window with the given ID.
+   *
+   * @param {integer} id
+   *        The ID of the window to return.
+   * @param {BaseContext} context
+   *        The extension context for which the matching is being performed.
+   *        Used to determine the current window for relevant properties.
+   *
+   * @returns {DOMWindow}
+   * @throws {ExtensionError}
+   *        If no window exists with the given ID.
+   */
   getWindow(id, context) {
     if (id === WINDOW_ID_CURRENT) {
       return this.getCurrentWindow(context);
     }
 
     for (let window of this.browserWindows(true)) {
       if (this.getId(window) === id) {
         return window;
       }
     }
     throw new ExtensionError(`Invalid window ID: ${id}`);
   }
 
-  get haveListeners() {
+  /**
+   * @property {boolean} _haveListeners
+   *        Returns true if any window open or close listeners are currently
+   *        registered.
+   * @private
+   */
+  get _haveListeners() {
     return this._openListeners.size > 0 || this._closeListeners.size > 0;
   }
 
+  /**
+   * Register the given listener function to be called whenever a new browser
+   * window is opened.
+   *
+   * @param {function(DOMWindow)} listener
+   *        The listener function to register.
+   */
   addOpenListener(listener) {
-    if (!this.haveListeners) {
+    if (!this._haveListeners) {
       Services.ww.registerNotification(this);
     }
 
     this._openListeners.add(listener);
 
     for (let window of this.browserWindows(true)) {
       if (window.document.readyState !== "complete") {
         window.addEventListener("load", this);
       }
     }
   }
 
+  /**
+   * Unregister a listener function registered in a previous addOpenListener
+   * call.
+   *
+   * @param {function(DOMWindow)} listener
+   *        The listener function to unregister.
+   */
   removeOpenListener(listener) {
     this._openListeners.delete(listener);
 
-    if (!this.haveListeners) {
+    if (!this._haveListeners) {
       Services.ww.unregisterNotification(this);
     }
   }
 
+  /**
+   * Register the given listener function to be called whenever a browser
+   * window is closed.
+   *
+   * @param {function(DOMWindow)} listener
+   *        The listener function to register.
+   */
   addCloseListener(listener) {
-    if (!this.haveListeners) {
+    if (!this._haveListeners) {
       Services.ww.registerNotification(this);
     }
 
     this._closeListeners.add(listener);
   }
 
+  /**
+   * Unregister a listener function registered in a previous addCloseListener
+   * call.
+   *
+   * @param {function(DOMWindow)} listener
+   *        The listener function to unregister.
+   */
   removeCloseListener(listener) {
     this._closeListeners.delete(listener);
 
-    if (!this.haveListeners) {
+    if (!this._haveListeners) {
       Services.ww.unregisterNotification(this);
     }
   }
 
+  /**
+   * Handles load events for recently-opened windows, and adds additional
+   * listeners which may only be safely added when the window is fully loaded.
+   *
+   * @param {Event} event
+   *        A DOM event to handle.
+   * @private
+   */
   handleEvent(event) {
     if (event.type === "load") {
       event.currentTarget.removeEventListener(event.type, this);
 
       let window = event.target.defaultView;
       if (!this.isBrowserWindow(window)) {
         return;
       }
@@ -546,17 +1353,28 @@ class WindowTrackerBase extends EventEmi
           listener(window);
         } catch (e) {
           Cu.reportError(e);
         }
       }
     }
   }
 
-  observe(window, topic, data) {
+  /**
+   * Observes "domwindowopened" and "domwindowclosed" events, notifies the
+   * appropriate listeners, and adds necessary additional listeners to the new
+   * windows.
+   *
+   * @param {DOMWindow} window
+   *        A DOM window.
+   * @param {string} topic
+   *        The topic being observed.
+   * @private
+   */
+  observe(window, topic) {
     if (topic === "domwindowclosed") {
       if (!this.isBrowserWindow(window)) {
         return;
       }
 
       window.removeEventListener("load", this);
       for (let listener of this._closeListeners) {
         try {
@@ -565,19 +1383,34 @@ class WindowTrackerBase extends EventEmi
           Cu.reportError(e);
         }
       }
     } else if (topic === "domwindowopened") {
       window.addEventListener("load", this);
     }
   }
 
-  // If |type| is a normal event type, invoke |listener| each time
-  // that event fires in any open window. If |type| is "progress", add
-  // a web progress listener that covers all open windows.
+  /**
+   * Add an event listener to be called whenever the given DOM event is recieved
+   * at the top level of any browser window.
+   *
+   * @param {string} type
+   *        The type of event to listen for. May be any valid DOM event name, or
+   *        one of the following special cases:
+   *
+   *        - "progress": Adds a tab progress listener to every browser window.
+   *        - "status": Adds a StatusListener to every tab of every browser
+   *           window.
+   *        - "domwindowopened": Acts as an alias for addOpenListener.
+   *        - "domwindowclosed": Acts as an alias for addCloseListener.
+   * @param {function|object} listener
+   *        The listener to invoke in response to the given events.
+   *
+   * @returns {undefined}
+   */
   addListener(type, listener) {
     if (type === "domwindowopened") {
       return this.addOpenListener(listener);
     } else if (type === "domwindowclosed") {
       return this.addCloseListener(listener);
     }
 
     if (this._listeners.size === 0) {
@@ -592,140 +1425,384 @@ class WindowTrackerBase extends EventEmi
     this._listeners.get(type).add(listener);
 
     // Register listener on all existing windows.
     for (let window of this.browserWindows()) {
       this._addWindowListener(window, type, listener);
     }
   }
 
-  removeListener(eventType, listener) {
-    if (eventType === "domwindowopened") {
+  /**
+   * Removes an event listener previously registered via an addListener call.
+   *
+   * @param {string} type
+   *        The type of event to stop listening for.
+   * @param {function|object} listener
+   *        The listener to remove.
+   *
+   * @returns {undefined}
+   */
+  removeListener(type, listener) {
+    if (type === "domwindowopened") {
       return this.removeOpenListener(listener);
-    } else if (eventType === "domwindowclosed") {
+    } else if (type === "domwindowclosed") {
       return this.removeCloseListener(listener);
     }
 
-    if (eventType === "status") {
+    if (type === "status") {
       listener = this._statusListeners.get(listener);
-      eventType = "progress";
+      type = "progress";
     }
 
-    let listeners = this._listeners.get(eventType);
+    let listeners = this._listeners.get(type);
     listeners.delete(listener);
 
     if (listeners.size === 0) {
-      this._listeners.delete(eventType);
+      this._listeners.delete(type);
       if (this._listeners.size === 0) {
         this.removeOpenListener(this._handleWindowOpened);
       }
     }
 
     // Unregister listener from all existing windows.
-    let useCapture = eventType === "focus" || eventType === "blur";
+    let useCapture = type === "focus" || type === "blur";
     for (let window of this.browserWindows()) {
-      if (eventType === "progress") {
+      if (type === "progress") {
         this.removeProgressListener(window, listener);
       } else {
-        window.removeEventListener(eventType, listener, useCapture);
+        window.removeEventListener(type, listener, useCapture);
       }
     }
   }
 
+  /**
+   * Adds a listener for the given event to the given window.
+   *
+   * @param {DOMWindow} window
+   *        The browser window to which to add the listener.
+   * @param {string} eventType
+   *        The type of DOM event to listen for, or "progress" to add a tab
+   *        progress listener.
+   * @param {function|object} listener
+   *        The listener to add.
+   * @private
+   */
   _addWindowListener(window, eventType, listener) {
     let useCapture = eventType === "focus" || eventType === "blur";
 
     if (eventType === "progress") {
       this.addProgressListener(window, listener);
     } else {
       window.addEventListener(eventType, listener, useCapture);
     }
   }
 
+  /**
+   * A private method which is called whenever a new browser window is opened,
+   * and adds the necessary listeners to it.
+   *
+   * @param {DOMWindow} window
+   *        The window being opened.
+   * @private
+   */
   _handleWindowOpened(window) {
     for (let [eventType, listeners] of this._listeners) {
       for (let listener of listeners) {
         this._addWindowListener(window, eventType, listener);
       }
     }
   }
+
+  /**
+   * Adds a tab progress listener to the given browser window.
+   *
+   * @param {DOMWindow} window
+   *        The browser window to which to add the listener.
+   * @param {object} listener
+   *        The tab progress listener to add.
+   * @abstract
+   */
+  addProgressListener(window, listener) {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * Removes a tab progress listener from the given browser window.
+   *
+   * @param {DOMWindow} window
+   *        The browser window from which to remove the listener.
+   * @param {object} listener
+   *        The tab progress listener to remove.
+   * @abstract
+   */
+  removeProgressListener(window, listener) {
+    throw new Error("Not implemented");
+  }
 }
 
+/**
+ * Manages native tabs, their wrappers, and their dynamic permissions for a
+ * particular extension.
+ *
+ * @param {Extension} extension
+ *        The extension for which to manage tabs.
+ */
 class TabManagerBase {
   constructor(extension) {
     this.extension = extension;
 
     this._tabs = new DefaultWeakMap(tab => this.wrapTab(tab));
   }
 
+  /**
+   * If the extension has requested activeTab permission, grant it those
+   * permissions for the current inner window in the given native tab.
+   *
+   * @param {NativeTab} tab
+   *        The native tab for which to grant permissions.
+   */
   addActiveTabPermission(tab) {
     if (this.extension.hasPermission("activeTab")) {
       // Note that, unlike Chrome, we don't currently clear this permission with
       // the tab navigates. If the inner window is revived from BFCache before
       // we've granted this permission to a new inner window, the extension
       // maintains its permissions for it.
       tab = this.getWrapper(tab);
       tab.activeTabWindowID = tab.innerWindowID;
     }
   }
 
+  /**
+   * Revoke the extension's activeTab permissions for the current inner window
+   * of the given native tab.
+   *
+   * @param {NativeTab} tab
+   *        The native tab for which to revoke permissions.
+   */
   revokeActiveTabPermission(tab) {
     this.getWrapper(tab).activeTabWindowID = null;
   }
 
-  // Returns true if the extension has the "activeTab" permission for this tab.
-  // This is somewhat more permissive than the generic "tabs" permission, as
-  // checked by |hasTabPermission|, in that it also allows programmatic script
-  // injection without an explicit host permission.
+  /**
+   * Returns true if the extension has requested activeTab permission, and has
+   * been granted permissions for the current inner window if this tab.
+   *
+   * @param {NativeTab} tab
+   *        The native tab for which to check permissions.
+   * @returns {boolean}
+   *        True if the extension has activeTab permissions for this tab.
+   */
   hasActiveTabPermission(tab) {
     return this.getWrapper(tab).hasActiveTabPermission;
   }
 
+  /**
+   * Returns true if the extension has permissions to access restricted
+   * properties of the given native tab. In practice, this means that it has
+   * either requested the "tabs" permission or has activeTab permissions for the
+   * given tab.
+   *
+   * @param {NativeTab} tab
+   *        The native tab for which to check permissions.
+   * @returns {boolean}
+   *        True if the extension has permissions for this tab.
+   */
   hasTabPermission(tab) {
     return this.getWrapper(tab).hasTabPermission;
   }
 
+  /**
+   * Returns this extension's TabBase wrapper for the given native tab. This
+   * method will always return the same wrapper object for any given native tab.
+   *
+   * @param {NativeTab} tab
+   *        The tab for which to return a wrapper.
+   *
+   * @returns {TabBase}
+   *        The wrapper for this tab.
+   */
   getWrapper(tab) {
     return this._tabs.get(tab);
   }
 
+  /**
+   * Converts the given native tab to a JSON-compatible object, in the format
+   * requried to be returned by WebExtension APIs, which may be safely passed to
+   * extension code.
+   *
+   * @param {NativeTab} tab
+   *        The native tab to convert.
+   *
+   * @returns {Object}
+   */
+  convert(tab) {
+    return this.getWrapper(tab).convert();
+  }
+
+  // The JSDoc validator does not support @returns tags in abstract functions or
+  // star functions without return statements.
+  /* eslint-disable valid-jsdoc */
+  /**
+   * Returns an iterator of TabBase objects which match the given query info.
+   *
+   * @param {Object|null} [queryInfo = null]
+   *        An object containing properties on which to filter. May contain any
+   *        properties which are recognized by {@link TabBase#matches} or
+   *        {@link WindowBase#matches}. Unknown properties will be ignored.
+   * @param {BaseContext|null} [context = null]
+   *        The extension context for which the matching is being performed.
+   *        Used to determine the current window for relevant properties.
+   *
+   * @returns {Iterator<TabBase>}
+   */
   * query(queryInfo = null, context = null) {
     for (let window of this.extension.windowManager.query(queryInfo, context)) {
       for (let tab of window.getTabs()) {
         if (!queryInfo || tab.matches(queryInfo)) {
           yield tab;
         }
       }
     }
   }
 
-  convert(tab) {
-    return this.getWrapper(tab).convert();
+  /**
+   * Returns a TabBase wrapper for the tab with the given ID.
+   *
+   * @param {integer} id
+   *        The ID of the tab for which to return a wrapper.
+   *
+   * @returns {TabBase}
+   * @throws {ExtensionError}
+   *        If no tab exists with the given ID.
+   * @abstract
+   */
+  get(tabId) {
+    throw new Error("Not implemented");
   }
 
+  /**
+   * Returns a new TabBase instance wrapping the given native tab.
+   *
+   * @param {NativeTab} tab
+   *        The native tab for which to return a wrapper.
+   *
+   * @returns {TabBase}
+   * @protected
+   * @abstract
+   */
+  /* eslint-enable valid-jsdoc */
   wrapTab(tab) {
     throw new Error("Not implemented");
   }
 }
 
+/**
+ * Manages native browser windows and their wrappers for a particular extension.
+ *
+ * @param {Extension} extension
+ *        The extension for which to manage windows.
+ */
 class WindowManagerBase {
   constructor(extension) {
     this.extension = extension;
 
     this._windows = new DefaultWeakMap(window => this.wrapWindow(window));
   }
 
+  /**
+   * Converts the given browser window to a JSON-compatible object, in the
+   * format requried to be returned by WebExtension APIs, which may be safely
+   * passed to extension code.
+   *
+   * @param {DOMWindow} window
+   *        The browser window to convert.
+   * @param {*} args
+   *        Additional arguments to be passed to {@link WindowBase#convert}.
+   *
+   * @returns {Object}
+   */
   convert(window, ...args) {
     return this.getWrapper(window).convert(...args);
   }
 
-  getWrapper(tab) {
-    return this._windows.get(tab);
+  /**
+   * Returns this extension's WindowBase wrapper for the given browser window.
+   * This method will always return the same wrapper object for any given
+   * browser window.
+   *
+   * @param {DOMWindow} window
+   *        The browser window for which to return a wrapper.
+   *
+   * @returns {WindowBase}
+   *        The wrapper for this tab.
+   */
+  getWrapper(window) {
+    return this._windows.get(window);
   }
 
+  // The JSDoc validator does not support @returns tags in abstract functions or
+  // star functions without return statements.
+  /* eslint-disable valid-jsdoc */
+  /**
+   * Returns an iterator of WindowBase objects which match the given query info.
+   *
+   * @param {Object|null} [queryInfo = null]
+   *        An object containing properties on which to filter. May contain any
+   *        properties which are recognized by {@link WindowBase#matches}.
+   *        Unknown properties will be ignored.
+   * @param {BaseContext|null} [context = null]
+   *        The extension context for which the matching is being performed.
+   *        Used to determine the current window for relevant properties.
+   *
+   * @returns {Iterator<WindowBase>}
+   */
   * query(queryInfo = null, context = null) {
     for (let window of this.getAll()) {
       if (!queryInfo || window.matches(queryInfo, context)) {
         yield window;
       }
     }
   }
+
+  /**
+   * Returns a WindowBase wrapper for the browser window with the given ID.
+   *
+   * @param {integer} id
+   *        The ID of the browser window for which to return a wrapper.
+   * @param {BaseContext} context
+   *        The extension context for which the matching is being performed.
+   *        Used to determine the current window for relevant properties.
+   *
+   * @returns{WindowBase}
+   * @throws {ExtensionError}
+   *        If no window exists with the given ID.
+   * @abstract
+   */
+  get(windowId, context) {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * Returns an iterator of WindowBase wrappers for each currently existing
+   * browser window.
+   *
+   * @returns {Iterator<WindowBase>}
+   * @abstract
+   */
+  * getAll() {
+    throw new Error("Not implemented");
+  }
+
+  /**
+   * Returns a new WindowBase instance wrapping the given browser window.
+   *
+   * @param {DOMWindow} window
+   *        The browser window for which to return a wrapper.
+   *
+   * @returns {WindowBase}
+   * @protected
+   * @abstract
+   */
+  wrapWindow(window) {
+    throw new Error("Not implemented");
+  }
+  /* eslint-enable valid-jsdoc */
 }