mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 02:09:05 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			291 lines
		
	
	
	
		
			9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			291 lines
		
	
	
	
		
			9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* This Source Code Form is subject to the terms of the Mozilla Public
 | 
						|
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 | 
						|
 * You can obtain one at http://mozilla.org/MPL/2.0/. */
 | 
						|
"use strict";
 | 
						|
 | 
						|
ChromeUtils.defineESModuleGetters(this, {
 | 
						|
  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
 | 
						|
});
 | 
						|
 | 
						|
var { ExtensionError } = ExtensionUtils;
 | 
						|
 | 
						|
const spellColour = color => (color === "grey" ? "gray" : color);
 | 
						|
 | 
						|
/**
 | 
						|
 * @param {MozTabbrowserTabGroup} group Group to move.
 | 
						|
 * @param {DOMWindow} window Browser window to move to.
 | 
						|
 * @param {integer} index The desired position of the group within the window
 | 
						|
 * @returns {integer} The tab index that the group should move to, such that
 | 
						|
 *   after the move operation, the group's position is at the given index.
 | 
						|
 */
 | 
						|
function adjustIndexForMove(group, window, index) {
 | 
						|
  let tabIndex = index < 0 ? window.gBrowser.tabs.length : index;
 | 
						|
  if (group.ownerGlobal === window) {
 | 
						|
    let group_tabs = group.tabs;
 | 
						|
    if (tabIndex > group_tabs[0]._tPos) {
 | 
						|
      // When group is moving to a higher index, we need to increase the
 | 
						|
      // index to account for the fact that the act of moving tab groups
 | 
						|
      // causes all following tabs to have a decreased index.
 | 
						|
      tabIndex += group_tabs.length;
 | 
						|
    }
 | 
						|
  }
 | 
						|
  tabIndex = Math.min(tabIndex, window.gBrowser.tabs.length);
 | 
						|
 | 
						|
  let prevTab = tabIndex > 0 ? window.gBrowser.tabs.at(tabIndex - 1) : null;
 | 
						|
  let nextTab = window.gBrowser.tabs.at(tabIndex);
 | 
						|
  if (nextTab?.pinned) {
 | 
						|
    throw new ExtensionError(
 | 
						|
      "Cannot move the group to an index that is in the middle of pinned tabs."
 | 
						|
    );
 | 
						|
  }
 | 
						|
  if (prevTab && nextTab?.group && prevTab.group === nextTab.group) {
 | 
						|
    throw new ExtensionError(
 | 
						|
      "Cannot move the group to an index that is in the middle of another group."
 | 
						|
    );
 | 
						|
  }
 | 
						|
 | 
						|
  return tabIndex;
 | 
						|
}
 | 
						|
 | 
						|
this.tabGroups = class extends ExtensionAPIPersistent {
 | 
						|
  queryGroups({ collapsed, color, title, windowId } = {}) {
 | 
						|
    color = spellColour(color);
 | 
						|
    let glob = title != null && new MatchGlob(title);
 | 
						|
    let window =
 | 
						|
      windowId != null && windowTracker.getWindow(windowId, null, false);
 | 
						|
    return windowTracker
 | 
						|
      .browserWindows()
 | 
						|
      .filter(
 | 
						|
        win =>
 | 
						|
          this.extension.canAccessWindow(win) &&
 | 
						|
          (windowId == null || win === window)
 | 
						|
      )
 | 
						|
      .flatMap(win => win.gBrowser.tabGroups)
 | 
						|
      .filter(
 | 
						|
        group =>
 | 
						|
          (collapsed == null || group.collapsed === collapsed) &&
 | 
						|
          (color == null || group.color === color) &&
 | 
						|
          (title == null || glob.matches(group.name))
 | 
						|
      );
 | 
						|
  }
 | 
						|
 | 
						|
  get(groupId) {
 | 
						|
    let gid = getInternalTabGroupIdForExtTabGroupId(groupId);
 | 
						|
    if (!gid) {
 | 
						|
      throw new ExtensionError(`No group with id: ${groupId}`);
 | 
						|
    }
 | 
						|
    for (let group of this.queryGroups()) {
 | 
						|
      if (group.id === gid) {
 | 
						|
        return group;
 | 
						|
      }
 | 
						|
    }
 | 
						|
    throw new ExtensionError(`No group with id: ${groupId}`);
 | 
						|
  }
 | 
						|
 | 
						|
  convert(group) {
 | 
						|
    return {
 | 
						|
      collapsed: !!group.collapsed,
 | 
						|
      /** Internally we use "gray", but Chrome uses "grey" @see spellColour. */
 | 
						|
      color: group.color === "gray" ? "grey" : group.color,
 | 
						|
      id: getExtTabGroupIdForInternalTabGroupId(group.id),
 | 
						|
      title: group.name,
 | 
						|
      windowId: windowTracker.getId(group.ownerGlobal),
 | 
						|
    };
 | 
						|
  }
 | 
						|
 | 
						|
  PERSISTENT_EVENTS = {
 | 
						|
    onCreated({ fire }) {
 | 
						|
      let onCreate = event => {
 | 
						|
        if (event.detail.isAdoptingGroup) {
 | 
						|
          // Tab group moved from a different window.
 | 
						|
          return;
 | 
						|
        }
 | 
						|
        if (!this.extension.canAccessWindow(event.originalTarget.ownerGlobal)) {
 | 
						|
          return;
 | 
						|
        }
 | 
						|
        fire.async(this.convert(event.originalTarget));
 | 
						|
      };
 | 
						|
      windowTracker.addListener("TabGroupCreate", onCreate);
 | 
						|
      return {
 | 
						|
        unregister() {
 | 
						|
          windowTracker.removeListener("TabGroupCreate", onCreate);
 | 
						|
        },
 | 
						|
        convert(_fire) {
 | 
						|
          fire = _fire;
 | 
						|
        },
 | 
						|
      };
 | 
						|
    },
 | 
						|
    onMoved({ fire }) {
 | 
						|
      let onMove = event => {
 | 
						|
        if (!this.extension.canAccessWindow(event.originalTarget.ownerGlobal)) {
 | 
						|
          return;
 | 
						|
        }
 | 
						|
        fire.async(this.convert(event.originalTarget));
 | 
						|
      };
 | 
						|
      let onCreate = event => {
 | 
						|
        if (!event.detail.isAdoptingGroup) {
 | 
						|
          // We are only interested in tab groups moved from a different window.
 | 
						|
          return;
 | 
						|
        }
 | 
						|
        if (!this.extension.canAccessWindow(event.originalTarget.ownerGlobal)) {
 | 
						|
          return;
 | 
						|
        }
 | 
						|
        fire.async(this.convert(event.originalTarget));
 | 
						|
      };
 | 
						|
      windowTracker.addListener("TabGroupMoved", onMove);
 | 
						|
      windowTracker.addListener("TabGroupCreate", onCreate);
 | 
						|
      return {
 | 
						|
        unregister() {
 | 
						|
          windowTracker.removeListener("TabGroupMoved", onMove);
 | 
						|
          windowTracker.removeListener("TabGroupCreate", onCreate);
 | 
						|
        },
 | 
						|
        convert(_fire) {
 | 
						|
          fire = _fire;
 | 
						|
        },
 | 
						|
      };
 | 
						|
    },
 | 
						|
    onRemoved({ fire }) {
 | 
						|
      let onRemove = event => {
 | 
						|
        if (event.originalTarget.removedByAdoption) {
 | 
						|
          // Tab group moved to a different window.
 | 
						|
          return;
 | 
						|
        }
 | 
						|
        if (!this.extension.canAccessWindow(event.originalTarget.ownerGlobal)) {
 | 
						|
          return;
 | 
						|
        }
 | 
						|
        fire.async(this.convert(event.originalTarget), {
 | 
						|
          isWindowClosing: false,
 | 
						|
        });
 | 
						|
      };
 | 
						|
      let onClosed = window => {
 | 
						|
        if (!this.extension.canAccessWindow(window)) {
 | 
						|
          return;
 | 
						|
        }
 | 
						|
        for (const group of window.gBrowser.tabGroups) {
 | 
						|
          fire.async(this.convert(group), { isWindowClosing: true });
 | 
						|
        }
 | 
						|
      };
 | 
						|
      windowTracker.addListener("TabGroupRemoved", onRemove);
 | 
						|
      windowTracker.addListener("domwindowclosed", onClosed);
 | 
						|
      return {
 | 
						|
        unregister() {
 | 
						|
          windowTracker.removeListener("TabGroupRemoved", onRemove);
 | 
						|
          windowTracker.removeListener("domwindowclosed", onClosed);
 | 
						|
        },
 | 
						|
        convert(_fire) {
 | 
						|
          fire = _fire;
 | 
						|
        },
 | 
						|
      };
 | 
						|
    },
 | 
						|
    onUpdated({ fire }) {
 | 
						|
      let onUpdate = event => {
 | 
						|
        if (!this.extension.canAccessWindow(event.originalTarget.ownerGlobal)) {
 | 
						|
          return;
 | 
						|
        }
 | 
						|
        fire.async(this.convert(event.originalTarget));
 | 
						|
      };
 | 
						|
      windowTracker.addListener("TabGroupCollapse", onUpdate);
 | 
						|
      windowTracker.addListener("TabGroupExpand", onUpdate);
 | 
						|
      windowTracker.addListener("TabGroupUpdate", onUpdate);
 | 
						|
      return {
 | 
						|
        unregister() {
 | 
						|
          windowTracker.removeListener("TabGroupCollapse", onUpdate);
 | 
						|
          windowTracker.removeListener("TabGroupExpand", onUpdate);
 | 
						|
          windowTracker.removeListener("TabGroupUpdate", onUpdate);
 | 
						|
        },
 | 
						|
        convert(_fire) {
 | 
						|
          fire = _fire;
 | 
						|
        },
 | 
						|
      };
 | 
						|
    },
 | 
						|
  };
 | 
						|
 | 
						|
  getAPI(context) {
 | 
						|
    const { windowManager } = this.extension;
 | 
						|
    return {
 | 
						|
      tabGroups: {
 | 
						|
        get: groupId => {
 | 
						|
          return this.convert(this.get(groupId));
 | 
						|
        },
 | 
						|
 | 
						|
        move: (groupId, { index, windowId }) => {
 | 
						|
          let group = this.get(groupId);
 | 
						|
          let win = group.ownerGlobal;
 | 
						|
 | 
						|
          if (windowId != null) {
 | 
						|
            win = windowTracker.getWindow(windowId, context);
 | 
						|
            if (
 | 
						|
              PrivateBrowsingUtils.isWindowPrivate(group.ownerGlobal) !==
 | 
						|
              PrivateBrowsingUtils.isWindowPrivate(win)
 | 
						|
            ) {
 | 
						|
              throw new ExtensionError(
 | 
						|
                "Can't move groups between private and non-private windows"
 | 
						|
              );
 | 
						|
            }
 | 
						|
            if (windowManager.getWrapper(win).type !== "normal") {
 | 
						|
              throw new ExtensionError(
 | 
						|
                "Groups can only be moved to normal windows."
 | 
						|
              );
 | 
						|
            }
 | 
						|
          }
 | 
						|
 | 
						|
          let tabIndex = adjustIndexForMove(group, win, index);
 | 
						|
          if (win !== group.ownerGlobal) {
 | 
						|
            group = win.gBrowser.adoptTabGroup(group, { tabIndex });
 | 
						|
          } else {
 | 
						|
            win.gBrowser.moveTabTo(group, { tabIndex });
 | 
						|
          }
 | 
						|
          return this.convert(group);
 | 
						|
        },
 | 
						|
 | 
						|
        query: query => {
 | 
						|
          return Array.from(this.queryGroups(query), group =>
 | 
						|
            this.convert(group)
 | 
						|
          );
 | 
						|
        },
 | 
						|
 | 
						|
        update: (groupId, { collapsed, color, title }) => {
 | 
						|
          let group = this.get(groupId);
 | 
						|
          if (collapsed != null) {
 | 
						|
            group.collapsed = collapsed;
 | 
						|
          }
 | 
						|
          if (color != null) {
 | 
						|
            group.color = spellColour(color);
 | 
						|
          }
 | 
						|
          if (title != null) {
 | 
						|
            group.name = title;
 | 
						|
          }
 | 
						|
          return this.convert(group);
 | 
						|
        },
 | 
						|
 | 
						|
        onCreated: new EventManager({
 | 
						|
          context,
 | 
						|
          module: "tabGroups",
 | 
						|
          event: "onCreated",
 | 
						|
          extensionApi: this,
 | 
						|
        }).api(),
 | 
						|
 | 
						|
        onMoved: new EventManager({
 | 
						|
          context,
 | 
						|
          module: "tabGroups",
 | 
						|
          event: "onMoved",
 | 
						|
          extensionApi: this,
 | 
						|
        }).api(),
 | 
						|
 | 
						|
        onRemoved: new EventManager({
 | 
						|
          context,
 | 
						|
          module: "tabGroups",
 | 
						|
          event: "onRemoved",
 | 
						|
          extensionApi: this,
 | 
						|
        }).api(),
 | 
						|
 | 
						|
        onUpdated: new EventManager({
 | 
						|
          context,
 | 
						|
          module: "tabGroups",
 | 
						|
          event: "onUpdated",
 | 
						|
          extensionApi: this,
 | 
						|
        }).api(),
 | 
						|
      },
 | 
						|
    };
 | 
						|
  }
 | 
						|
};
 |