Source code

Revision control

Copy as Markdown

Other Tools

/**
* 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/.
*/
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
export const AIWINDOW_URL = "chrome://browser/content/aiwindow/aiWindow.html";
const AIWINDOW_URI = Services.io.newURI(AIWINDOW_URL);
const FIRSTRUN_URL = "chrome://browser/content/aiwindow/firstrun.html";
const FIRSTRUN_URI = Services.io.newURI(FIRSTRUN_URL);
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
AIWindowMenu:
"moz-src:///browser/components/aiwindow/ui/modules/AIWindowMenu.sys.mjs",
SearchUIUtils: "moz-src:///browser/components/search/SearchUIUtils.sys.mjs",
ChatStore:
"moz-src:///browser/components/aiwindow/ui/modules/ChatStore.sys.mjs",
PanelMultiView:
"moz-src:///browser/components/customizableui/PanelMultiView.sys.mjs",
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
});
/**
* AI Window Service
*/
export const AIWindow = {
_initialized: false,
_windowStates: new WeakMap(),
_aiWindowMenu: null,
/**
* Handles startup tasks
*/
init(win) {
if (!this._windowStates.has(win)) {
this._windowStates.set(win, {});
this.initializeAITabsToolbar(win);
}
if (this._initialized) {
return;
}
ChromeUtils.defineLazyGetter(
AIWindow,
"chatStore",
() => new lazy.ChatStore()
);
this._initialized = true;
},
_onAIWindowEnabledPrefChange() {
ChromeUtils.nondeterministicGetWeakMapKeys(this._windowStates).forEach(
win => {
this._updateButtonVisibility(win);
}
);
},
_updateButtonVisibility(win) {
const isPrivateWindow = lazy.PrivateBrowsingUtils.isWindowPrivate(win);
const modeSwitcherButton = win.document.getElementById("ai-window-toggle");
if (modeSwitcherButton) {
modeSwitcherButton.hidden = !this.isAIWindowEnabled() || isPrivateWindow;
}
},
/**
* Sets options for new AI Window if new or inherited conditions are met
*
* @param {object} options Used in BrowserWindowTracker.openWindow
* @param {object} options.openerWindow Window making the BrowserWindowTracker.openWindow call
* @param {object} options.args Array of arguments to pass to new window
* @param {boolean} [options.aiWindow] Should new window be AI Window (true), Classic Window (false), or inherited from opener (undefined, default)
* @param {boolean} [options.private] Should new window be Private Window
* @param {boolean} [options.restoreSession] Should previous AI Window session be restored
*
* @returns {object} Modified arguments appended to the options object
*/
handleAIWindowOptions({
openerWindow,
args,
aiWindow = undefined,
private: isPrivate = false,
restoreSession = false,
} = {}) {
// Indicates whether the new window should inherit AI Window state from opener window
const canInheritAIWindow =
this.isAIWindowActiveAndEnabled(openerWindow) &&
!isPrivate &&
typeof aiWindow === "undefined";
const willOpenAIWindow =
(aiWindow && this.isAIWindowEnabled()) || canInheritAIWindow;
if (!willOpenAIWindow) {
return args;
}
args ??= Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
if (!args.length) {
const aiWindowURI = Cc["@mozilla.org/supports-string;1"].createInstance(
Ci.nsISupportsString
);
aiWindowURI.data = restoreSession ? "" : AIWINDOW_URL;
args.appendElement(aiWindowURI);
const aiOption = Cc["@mozilla.org/hash-property-bag;1"].createInstance(
Ci.nsIWritablePropertyBag2
);
aiOption.setPropertyAsBool("ai-window", aiWindow);
args.appendElement(aiOption);
}
return args;
},
/**
* Show Window Switcher button in tabs toolbar
*
* @param {object} win caller window
*/
handleAIWindowSwitcher(win) {
let view = lazy.PanelMultiView.getViewNode(
win.document,
"ai-window-toggle-view"
);
const isPrivateWindow = lazy.PrivateBrowsingUtils.isWindowPrivate(win);
if (!isPrivateWindow) {
view.querySelector("#ai-window-switch-classic").hidden = false;
view.querySelector("#ai-window-switch-ai").hidden = false;
}
let windowState = this._windowStates.get(win);
if (!windowState) {
windowState = {};
this._windowStates.set(win, windowState);
}
if (windowState.viewInitialized) {
return;
}
view.addEventListener("command", event => {
switch (event.target.id) {
case "ai-window-switch-classic":
this.toggleAIWindow(win, false);
break;
case "ai-window-switch-ai":
this.toggleAIWindow(win, true);
break;
}
});
windowState.viewInitialized = true;
},
/**
* Show Window Switcher button in tabs toolbar
*
* @param {Window} win caller window
*/
initializeAITabsToolbar(win) {
const modeSwitcherButton = win.document.getElementById("ai-window-toggle");
if (!modeSwitcherButton) {
return;
}
this._updateButtonVisibility(win);
modeSwitcherButton.addEventListener("command", event => {
if (win.PanelUI.panel.state == "open") {
win.PanelUI.hide();
} else if (win.PanelUI.panel.state == "closed") {
this.handleAIWindowSwitcher(win);
win.PanelUI.showSubView("ai-window-toggle-view", event.target, event);
}
});
},
/**
* Is current window an AI Window
*
* @param {Window} win current Window
* @returns {boolean} whether current Window is an AI Window
*/
isAIWindowActive(win) {
return !!win && win.document.documentElement.hasAttribute("ai-window");
},
/**
* Is AI Window enabled
*
* @returns {boolean} whether AI Window is enabled
*/
isAIWindowEnabled() {
return this.AIWindowEnabled;
},
isAIWindowActiveAndEnabled(win) {
return this.isAIWindowActive(win) && this.AIWindowEnabled;
},
/**
* Check if window is being opened as an AI Window.
*
* @param {Window} win - The window to check
* @returns {boolean} whether the window is being opened as an AI Window
*/
isOpeningAIWindow(win) {
const windowArgs = win?.arguments?.[1];
if (!(windowArgs instanceof Ci.nsIPropertyBag2)) {
return false;
}
return windowArgs.hasKey("ai-window");
},
/**
* Is AI Window content page active
*
* @param {nsIURI} uri current URI
* @returns {boolean} whether AI Window content page is active
*/
isAIWindowContentPage(uri) {
return (
AIWINDOW_URI.equalsExceptRef(uri) || FIRSTRUN_URI.equalsExceptRef(uri)
);
},
/**
* Adds the AI Window app menu options
*
* @param {Event} event - History menu click event
* @param {Window} win - current Window reference
*
* @returns {Promise} - Resolves when menu is done being added
*/
appMenu(event, win) {
if (!this._aiWindowMenu) {
this._aiWindowMenu = new lazy.AIWindowMenu();
}
return this._aiWindowMenu.addMenuitems(event, win);
},
get newTabURL() {
return AIWINDOW_URL;
},
/**
* Performs a search in the default search engine with
* passed query in the current tab.
*
* @param {string} query
* @param {Window} window
*/
async performSearch(query, window) {
let engine = null;
try {
engine = await Services.search.getDefault();
} catch (error) {
console.error(`Failed to get default search engine:`, error);
}
const triggeringPrincipal =
Services.scriptSecurityManager.getSystemPrincipal();
await lazy.SearchUIUtils.loadSearch({
window,
searchText: query,
where: "current",
usePrivate: false,
triggeringPrincipal,
policyContainer: null,
engine,
searchUrlType: null,
sapSource: "aiwindow_assistant",
});
},
async toggleAIWindow(win, isTogglingToAIWindow) {
let isActive = this.isAIWindowActive(win);
if (isActive != isTogglingToAIWindow) {
win.document.documentElement.toggleAttribute("ai-window");
Services.obs.notifyObservers(win, "ai-window-state-changed");
}
},
};
XPCOMUtils.defineLazyPreferenceGetter(
AIWindow,
"AIWindowEnabled",
"browser.aiwindow.enabled",
false,
AIWindow._onAIWindowEnabledPrefChange.bind(AIWindow)
);