tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

AIWindow.sys.mjs (8473B)


      1 /**
      2 * This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
      5 */
      6 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
      7 
      8 export const AIWINDOW_URL = "chrome://browser/content/aiwindow/aiWindow.html";
      9 const AIWINDOW_URI = Services.io.newURI(AIWINDOW_URL);
     10 const FIRSTRUN_URL = "chrome://browser/content/aiwindow/firstrun.html";
     11 const FIRSTRUN_URI = Services.io.newURI(FIRSTRUN_URL);
     12 
     13 const lazy = {};
     14 ChromeUtils.defineESModuleGetters(lazy, {
     15  AIWindowMenu:
     16    "moz-src:///browser/components/aiwindow/ui/modules/AIWindowMenu.sys.mjs",
     17 
     18  SearchUIUtils: "moz-src:///browser/components/search/SearchUIUtils.sys.mjs",
     19  ChatStore:
     20    "moz-src:///browser/components/aiwindow/ui/modules/ChatStore.sys.mjs",
     21  PanelMultiView:
     22    "moz-src:///browser/components/customizableui/PanelMultiView.sys.mjs",
     23  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
     24 });
     25 
     26 /**
     27 * AI Window Service
     28 */
     29 
     30 export const AIWindow = {
     31  _initialized: false,
     32  _windowStates: new WeakMap(),
     33  _aiWindowMenu: null,
     34 
     35  /**
     36   * Handles startup tasks
     37   */
     38 
     39  init(win) {
     40    if (!this._windowStates.has(win)) {
     41      this._windowStates.set(win, {});
     42      this.initializeAITabsToolbar(win);
     43    }
     44 
     45    if (this._initialized) {
     46      return;
     47    }
     48 
     49    ChromeUtils.defineLazyGetter(
     50      AIWindow,
     51      "chatStore",
     52      () => new lazy.ChatStore()
     53    );
     54    this._initialized = true;
     55  },
     56 
     57  _onAIWindowEnabledPrefChange() {
     58    ChromeUtils.nondeterministicGetWeakMapKeys(this._windowStates).forEach(
     59      win => {
     60        this._updateButtonVisibility(win);
     61      }
     62    );
     63  },
     64 
     65  _updateButtonVisibility(win) {
     66    const isPrivateWindow = lazy.PrivateBrowsingUtils.isWindowPrivate(win);
     67    const modeSwitcherButton = win.document.getElementById("ai-window-toggle");
     68    if (modeSwitcherButton) {
     69      modeSwitcherButton.hidden = !this.isAIWindowEnabled() || isPrivateWindow;
     70    }
     71  },
     72 
     73  /**
     74   * Sets options for new AI Window if new or inherited conditions are met
     75   *
     76   * @param {object} options Used in BrowserWindowTracker.openWindow
     77   * @param {object} options.openerWindow Window making the BrowserWindowTracker.openWindow call
     78   * @param {object} options.args Array of arguments to pass to new window
     79   * @param {boolean} [options.aiWindow] Should new window be AI Window (true), Classic Window (false), or inherited from opener (undefined, default)
     80   * @param {boolean} [options.private] Should new window be Private Window
     81   * @param {boolean} [options.restoreSession] Should previous AI Window session be restored
     82   *
     83   * @returns {object} Modified arguments appended to the options object
     84   */
     85  handleAIWindowOptions({
     86    openerWindow,
     87    args,
     88    aiWindow = undefined,
     89    private: isPrivate = false,
     90    restoreSession = false,
     91  } = {}) {
     92    // Indicates whether the new window should inherit AI Window state from opener window
     93    const canInheritAIWindow =
     94      this.isAIWindowActiveAndEnabled(openerWindow) &&
     95      !isPrivate &&
     96      typeof aiWindow === "undefined";
     97 
     98    const willOpenAIWindow =
     99      (aiWindow && this.isAIWindowEnabled()) || canInheritAIWindow;
    100 
    101    if (!willOpenAIWindow) {
    102      return args;
    103    }
    104 
    105    args ??= Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
    106 
    107    if (!args.length) {
    108      const aiWindowURI = Cc["@mozilla.org/supports-string;1"].createInstance(
    109        Ci.nsISupportsString
    110      );
    111      aiWindowURI.data = restoreSession ? "" : AIWINDOW_URL;
    112      args.appendElement(aiWindowURI);
    113 
    114      const aiOption = Cc["@mozilla.org/hash-property-bag;1"].createInstance(
    115        Ci.nsIWritablePropertyBag2
    116      );
    117      aiOption.setPropertyAsBool("ai-window", aiWindow);
    118      args.appendElement(aiOption);
    119    }
    120 
    121    return args;
    122  },
    123 
    124  /**
    125   * Show Window Switcher button in tabs toolbar
    126   *
    127   * @param {object} win caller window
    128   */
    129  handleAIWindowSwitcher(win) {
    130    let view = lazy.PanelMultiView.getViewNode(
    131      win.document,
    132      "ai-window-toggle-view"
    133    );
    134 
    135    const isPrivateWindow = lazy.PrivateBrowsingUtils.isWindowPrivate(win);
    136 
    137    if (!isPrivateWindow) {
    138      view.querySelector("#ai-window-switch-classic").hidden = false;
    139      view.querySelector("#ai-window-switch-ai").hidden = false;
    140    }
    141 
    142    let windowState = this._windowStates.get(win);
    143    if (!windowState) {
    144      windowState = {};
    145      this._windowStates.set(win, windowState);
    146    }
    147 
    148    if (windowState.viewInitialized) {
    149      return;
    150    }
    151 
    152    view.addEventListener("command", event => {
    153      switch (event.target.id) {
    154        case "ai-window-switch-classic":
    155          this.toggleAIWindow(win, false);
    156          break;
    157        case "ai-window-switch-ai":
    158          this.toggleAIWindow(win, true);
    159          break;
    160      }
    161    });
    162 
    163    windowState.viewInitialized = true;
    164  },
    165 
    166  /**
    167   * Show Window Switcher button in tabs toolbar
    168   *
    169   * @param {Window} win caller window
    170   */
    171  initializeAITabsToolbar(win) {
    172    const modeSwitcherButton = win.document.getElementById("ai-window-toggle");
    173    if (!modeSwitcherButton) {
    174      return;
    175    }
    176 
    177    this._updateButtonVisibility(win);
    178 
    179    modeSwitcherButton.addEventListener("command", event => {
    180      if (win.PanelUI.panel.state == "open") {
    181        win.PanelUI.hide();
    182      } else if (win.PanelUI.panel.state == "closed") {
    183        this.handleAIWindowSwitcher(win);
    184        win.PanelUI.showSubView("ai-window-toggle-view", event.target, event);
    185      }
    186    });
    187  },
    188 
    189  /**
    190   * Is current window an AI Window
    191   *
    192   * @param {Window} win current Window
    193   * @returns {boolean} whether current Window is an AI Window
    194   */
    195  isAIWindowActive(win) {
    196    return !!win && win.document.documentElement.hasAttribute("ai-window");
    197  },
    198 
    199  /**
    200   * Is AI Window enabled
    201   *
    202   * @returns {boolean} whether AI Window is enabled
    203   */
    204  isAIWindowEnabled() {
    205    return this.AIWindowEnabled;
    206  },
    207 
    208  isAIWindowActiveAndEnabled(win) {
    209    return this.isAIWindowActive(win) && this.AIWindowEnabled;
    210  },
    211 
    212  /**
    213   * Check if window is being opened as an AI Window.
    214   *
    215   * @param {Window} win - The window to check
    216   * @returns {boolean} whether the window is being opened as an AI Window
    217   */
    218  isOpeningAIWindow(win) {
    219    const windowArgs = win?.arguments?.[1];
    220    if (!(windowArgs instanceof Ci.nsIPropertyBag2)) {
    221      return false;
    222    }
    223 
    224    return windowArgs.hasKey("ai-window");
    225  },
    226 
    227  /**
    228   * Is AI Window content page active
    229   *
    230   * @param {nsIURI} uri current URI
    231   * @returns {boolean} whether AI Window content page is active
    232   */
    233  isAIWindowContentPage(uri) {
    234    return (
    235      AIWINDOW_URI.equalsExceptRef(uri) || FIRSTRUN_URI.equalsExceptRef(uri)
    236    );
    237  },
    238 
    239  /**
    240   * Adds the AI Window app menu options
    241   *
    242   * @param {Event} event - History menu click event
    243   * @param {Window} win - current Window reference
    244   *
    245   * @returns {Promise} - Resolves when menu is done being added
    246   */
    247  appMenu(event, win) {
    248    if (!this._aiWindowMenu) {
    249      this._aiWindowMenu = new lazy.AIWindowMenu();
    250    }
    251 
    252    return this._aiWindowMenu.addMenuitems(event, win);
    253  },
    254 
    255  get newTabURL() {
    256    return AIWINDOW_URL;
    257  },
    258 
    259  /**
    260   * Performs a search in the default search engine with
    261   * passed query in the current tab.
    262   *
    263   * @param {string} query
    264   * @param {Window} window
    265   */
    266  async performSearch(query, window) {
    267    let engine = null;
    268    try {
    269      engine = await Services.search.getDefault();
    270    } catch (error) {
    271      console.error(`Failed to get default search engine:`, error);
    272    }
    273 
    274    const triggeringPrincipal =
    275      Services.scriptSecurityManager.getSystemPrincipal();
    276 
    277    await lazy.SearchUIUtils.loadSearch({
    278      window,
    279      searchText: query,
    280      where: "current",
    281      usePrivate: false,
    282      triggeringPrincipal,
    283      policyContainer: null,
    284      engine,
    285      searchUrlType: null,
    286      sapSource: "aiwindow_assistant",
    287    });
    288  },
    289 
    290  async toggleAIWindow(win, isTogglingToAIWindow) {
    291    let isActive = this.isAIWindowActive(win);
    292    if (isActive != isTogglingToAIWindow) {
    293      win.document.documentElement.toggleAttribute("ai-window");
    294      Services.obs.notifyObservers(win, "ai-window-state-changed");
    295    }
    296  },
    297 };
    298 
    299 XPCOMUtils.defineLazyPreferenceGetter(
    300  AIWindow,
    301  "AIWindowEnabled",
    302  "browser.aiwindow.enabled",
    303  false,
    304  AIWindow._onAIWindowEnabledPrefChange.bind(AIWindow)
    305 );