Bug 1921471 - Use content process to display sidebar contents when appropriate. r=places-reviewers,sidebar-reviewers,mossop,firefox-desktop-core-reviewers ,jsudiaman

* Adds a `remoteType` attribute to the config when registering a sidebar to opt-in to using a remote content process when displaying that sidebar panel.
* Moves loading URLs into a `SidebarController._loadUrl` method, which can be passed a remoteType if the sidebar contents should be displayed in a remote content process. If needed it will replace the sidebar browser element with a remote or non-remote browser to match the remoteType passed.
* Adds browser load listeners methods, a `SidebarController._hasLoaded` promise for non-remote loading and a `SidebarController._hasProgressStopped` promise if the browser is remote.
* Adds a setter for `SidebarController.browser`.
* Adds a `shopping-sidebar` message manager group to avoid actor conflict.

Differential Revision: https://phabricator.services.mozilla.com/D223961
This commit is contained in:
Fred Chasen 2024-11-21 17:36:56 +00:00
parent 38ec4d476f
commit b0919598a5
10 changed files with 296 additions and 96 deletions

View File

@ -429,6 +429,7 @@ let JSWINDOWACTORS = {
},
matches: ["about:shoppingsidebar"],
remoteTypes: ["privilegedabout"],
messageManagerGroups: ["shopping-sidebar", "browsers"],
},
AboutWelcome: {
@ -879,6 +880,7 @@ let JSWINDOWACTORS = {
},
matches: ["about:shoppingsidebar"],
remoteTypes: ["privilegedabout"],
messageManagerGroups: ["shopping-sidebar", "browsers"],
},
SpeechDispatcher: {

View File

@ -5,7 +5,7 @@
<!-- Bookmarks and history tooltip -->
<tooltip id="bhTooltip" noautohide="true"
class="places-tooltip"
onpopupshowing="return window.top.BookmarksEventHandler.fillInBHTooltip(this, event)"
onpopupshowing="return window.browsingContext.topChromeWindow.BookmarksEventHandler.fillInBHTooltip(this, event)"
onpopuphiding="this.removeAttribute('position')">
<box class="places-tooltip-box">
<description class="tooltip-label places-tooltip-title"/>

View File

@ -29,7 +29,8 @@ XPCOMUtils.defineLazyScriptGetter(
var gCumulativeSearches = 0;
function init() {
let uidensity = window.top.document.documentElement.getAttribute("uidensity");
let top = window.browsingContext.topChromeWindow;
let uidensity = top.document.documentElement.getAttribute("uidensity");
if (uidensity) {
document.documentElement.setAttribute("uidensity", uidensity);
}

View File

@ -27,6 +27,7 @@
remote="true"
src="about:shoppingsidebar"
type="content"
messagemanagergroup="shopping-sidebar"
/>
`;
}

View File

@ -158,11 +158,12 @@ var SidebarController = {
"viewReviewCheckerSidebar",
{
elementId: "sidebar-switcher-review-checker",
url: "chrome://browser/content/shopping/shopping.html",
url: "about:shoppingsidebar",
menuId: "menu_reviewCheckerSidebar",
menuL10nId: "menu-view-review-checker",
revampL10nId: "sidebar-menu-review-checker-label",
iconUrl: "chrome://browser/content/shopping/assets/shopping.svg",
remoteType: E10SUtils.PRIVILEGEDABOUT_REMOTE_TYPE,
}
);
}
@ -217,6 +218,14 @@ var SidebarController = {
}
return (this._browser = document.getElementById("sidebar"));
},
set browser(browser) {
let currentBrowser = this.browser;
currentBrowser.destroy();
currentBrowser.replaceWith(browser);
this._browser = browser;
},
POSITION_START_PREF: "sidebar.position_start",
DEFAULT_SIDEBAR_ID: "viewBookmarksSidebar",
TOOLS_PREF: "sidebar.main.tools",
@ -687,9 +696,8 @@ var SidebarController = {
this.hideSwitcherPanel();
let content = SidebarController.browser.contentWindow;
if (content && content.updatePosition) {
content.updatePosition();
if (!this.browser.isRemoteBrowser) {
this.browser.contentWindow.updatePosition?.();
}
},
@ -868,8 +876,10 @@ var SidebarController = {
* window, only when the user opens the sidebar.
*/
_fireFocusedEvent() {
let event = new CustomEvent("SidebarFocused", { bubbles: true });
this.browser.contentWindow.dispatchEvent(event);
if (!this.browser.isRemoteBrowser) {
let event = new CustomEvent("SidebarFocused", { bubbles: true });
this.browser.contentWindow.dispatchEvent(event);
}
},
/**
@ -1485,92 +1495,218 @@ var SidebarController = {
* @param {string} commandID ID of the sidebar.
* @returns {Promise<void>}
*/
_show(commandID) {
return new Promise(resolve => {
if (this.sidebarRevampEnabled) {
this.sidebarContainer.hidden = false;
this._box.dispatchEvent(
new CustomEvent("sidebar-show", { detail: { viewId: commandID } })
);
async _show(commandID) {
if (this.sidebarRevampEnabled) {
this.sidebarContainer.hidden = false;
this._box.dispatchEvent(
new CustomEvent("sidebar-show", { detail: { viewId: commandID } })
);
// Whenever a panel is shown, the sidebar is collapsed. Upon hiding
// that panel afterwards, `expanded` reverts back to what it was prior
// to calling `show()`. Thus, we store the expanded state at this point.
this._previousExpandedState = this.sidebarMain.expanded;
// Whenever a panel is shown, the sidebar is collapsed. Upon hiding
// that panel afterwards, `expanded` reverts back to what it was prior
// to calling `show()`. Thus, we store the expanded state at this point.
this._previousExpandedState = this.sidebarMain.expanded;
this.toggleExpanded(false);
} else {
this.hideSwitcherPanel();
}
this.toggleExpanded(false);
} else {
this.hideSwitcherPanel();
}
this.selectMenuItem(commandID);
this._box.hidden = this._splitter.hidden = false;
this.selectMenuItem(commandID);
this._box.hidden = this._splitter.hidden = false;
this._box.setAttribute("checked", "true");
this._box.setAttribute("sidebarcommand", commandID);
this._box.setAttribute("checked", "true");
this._box.setAttribute("sidebarcommand", commandID);
let { icon, url, title, sourceL10nEl, contextMenuId } =
this.sidebars.get(commandID);
if (icon) {
this._switcherTarget.style.setProperty(
"--webextension-menuitem-image",
icon
);
} else {
this._switcherTarget.style.removeProperty(
"--webextension-menuitem-image"
);
}
let { icon, url, title, sourceL10nEl, contextMenuId, remoteType } =
this.sidebars.get(commandID);
if (icon) {
this._switcherTarget.style.setProperty(
"--webextension-menuitem-image",
icon
);
} else {
this._switcherTarget.style.removeProperty(
"--webextension-menuitem-image"
);
}
if (contextMenuId) {
this._box.setAttribute("context", contextMenuId);
} else {
this._box.removeAttribute("context");
}
if (contextMenuId) {
this._box.setAttribute("context", contextMenuId);
} else {
this._box.removeAttribute("context");
}
// use to live update <tree> elements if the locale changes
this.lastOpenedId = commandID;
// These title changes only apply to the old sidebar menu
if (!this.sidebarRevampEnabled) {
this.title = title;
// Keep the title element in the switcher in sync with any l10n changes.
this.observeTitleChanges(sourceL10nEl);
}
// use to live update <tree> elements if the locale changes
this.lastOpenedId = commandID;
// These title changes only apply to the old sidebar menu
if (!this.sidebarRevampEnabled) {
this.title = title;
// Keep the title element in the switcher in sync with any l10n changes.
this.observeTitleChanges(sourceL10nEl);
}
await this._loadURL(url, remoteType);
// Now that the currentId is updated, fire a show event.
this._fireShowEvent();
this._recordBrowserSize();
},
/**
* Loads the given URL into the browser.
*
* If the current sidebar browser doesn't match the remoteType
* of the new URL, the browser will be replaced with one that does.
*
* @param {string} url to load.
* @param {string} [remoteType] of the content.
* @returns {Promise<void>}
*/
async _loadURL(url, remoteType) {
let targetURI = makeURI(url);
if (
remoteType &&
(!this.browser.isRemoteBrowser || this.browser.remoteType !== remoteType)
) {
this.browser = this._createBrowser({ remoteType });
} else if (!remoteType && this.browser.hasAttribute("remote")) {
this.browser = this._createBrowser();
}
let loadPromise;
if (this.browser.isRemoteBrowser) {
this.browser.loadURI(targetURI, {
triggeringPrincipal:
Services.scriptSecurityManager.getSystemPrincipal(),
});
loadPromise = this._hasProgressStopped(targetURI);
} else {
this.browser.setAttribute("src", url); // kick off async load
loadPromise = this._hasLoaded(url);
}
if (this.browser.contentDocument.location.href != url) {
// make sure to clear the timeout if the load is aborted
this.browser.addEventListener("unload", () => {
if (this.browser.loadingTimerID) {
clearTimeout(this.browser.loadingTimerID);
await loadPromise;
},
/**
* Creates a new browser, either non-remote or remote
* with the given remoteType.
*
* @param {object} [options]
* @param {string} [options.remoteType] to set for the browser.
* @returns {Browser}
*/
_createBrowser(options = { remoteType: E10SUtils.NOT_REMOTE }) {
let { remoteType } = options;
let browser = document.createXULElement("browser");
browser.setAttribute("id", "sidebar");
browser.setAttribute("autoscroll", "false");
browser.setAttribute("disablehistory", "true");
browser.setAttribute("disablefullscreen", "true");
browser.setAttribute("tooltip", "aHTMLTooltip");
if (remoteType) {
browser.setAttribute("type", "content");
browser.setAttribute("remote", "true");
browser.setAttribute("remoteType", remoteType);
browser.setAttribute("maychangeremoteness", "true");
browser.setAttribute("messagemanagergroup", "sidebar-browsers");
}
return browser;
},
/**
* Wait for a URL to have loaded or unloaded in the sidebar browser.
*
* @param {string} url to check has loaded.
* @returns {Promise<void>}
*/
async _hasLoaded(url) {
return new Promise(resolve => {
if (
this.browser.contentDocument.location.href == url &&
this.browser.contentDocument.readyState === "complete"
) {
resolve();
return;
}
// make sure to clear the timeout if the load is aborted
this.browser.addEventListener("unload", () => {
if (this.browser.loadingTimerID) {
clearTimeout(this.browser.loadingTimerID);
delete this.browser.loadingTimerID;
resolve();
}
});
this.browser.addEventListener(
"load",
() => {
// We're handling the 'load' event before it bubbles up to the usual
// (non-capturing) event handlers. Let it bubble up before resolving.
this.browser.loadingTimerID = setTimeout(() => {
delete this.browser.loadingTimerID;
resolve();
}
});
this.browser.addEventListener(
"load",
() => {
// We're handling the 'load' event before it bubbles up to the usual
// (non-capturing) event handlers. Let it bubble up before resolving.
this.browser.loadingTimerID = setTimeout(() => {
delete this.browser.loadingTimerID;
resolve();
}, 0);
},
{ capture: true, once: true }
);
});
},
// Now that the currentId is updated, fire a show event.
this._fireShowEvent();
this._recordBrowserSize();
}, 0);
},
{ capture: true, once: true }
);
} else {
/**
* Wait for a URI's progress to have stopped after loading
* in a remote sidebar browser.
*
* @param {URI} targetURI to check has loaded.
* @returns {Promise<void>}
*/
async _hasProgressStopped(targetURI) {
return new Promise(resolve => {
let b = this.browser;
if (
b.currentURI?.equalsExceptRef(targetURI) &&
!b.webProgress.isLoadingDocument
) {
resolve();
// Now that the currentId is updated, fire a show event.
this._fireShowEvent();
this._recordBrowserSize();
return;
}
let referenceKeeper = new Set();
const { STATE_IS_WINDOW, STATE_STOP } = Ci.nsIWebProgressListener;
let progListener = {
onStateChange: (webProgress, request, flags, _status) => {
if (
flags & STATE_IS_WINDOW &&
flags & STATE_STOP &&
webProgress.isTopLevel &&
(request?.originalURI?.equalsExceptRef(targetURI) ||
b.currentURI?.equalsExceptRef(targetURI))
) {
resolve();
referenceKeeper.delete(filter);
b.removeProgressListener(filter);
}
},
QueryInterface: ChromeUtils.generateQI([
"nsIWebProgressListener",
"nsISupportsWeakReference",
]),
};
const filter = Cc[
"@mozilla.org/appshell/component/browser-status-filter;1"
].createInstance(Ci.nsIWebProgress);
filter.addProgressListener(
progListener,
Ci.nsIWebProgress.NOTIFY_STATE_WINDOW
);
referenceKeeper.add(filter);
this.browser.addProgressListener(
filter,
Ci.nsIWebProgress.NOTIFY_STATE_WINDOW
);
});
},
@ -1600,12 +1736,22 @@ var SidebarController = {
this.selectMenuItem("");
// Replace the document currently displayed in the sidebar with about:blank
// so that we can free memory by unloading the page. We need to explicitly
// create a new content viewer because the old one doesn't get destroyed
// until about:blank has loaded (which does not happen as long as the
// element is hidden).
this.browser.setAttribute("src", "about:blank");
this.browser.docShell?.createAboutBlankDocumentViewer(null, null);
// so that we can free memory by unloading the page.
if (this.browser.isRemoteBrowser) {
let nullPrincipal = Services.scriptSecurityManager.createNullPrincipal(
{}
);
this.browser.loadURI(Services.io.newURI("about:blank"), {
triggeringPrincipal: nullPrincipal,
});
this.browser.createAboutBlankDocumentViewer(nullPrincipal, nullPrincipal);
} else {
// We need to explicitly create a new content viewer because the old one
// doesn't get destroyed until about:blank has loaded (which does not happen
// as long as the element is hidden).
this.browser.setAttribute("src", "about:blank");
this.browser.docShell?.createAboutBlankDocumentViewer(null, null);
}
this._box.removeAttribute("checked");
this._box.removeAttribute("context");

View File

@ -39,7 +39,7 @@ export class SidebarPage extends MozLitElement {
}
get topWindow() {
return this.ownerGlobal.top;
return this.ownerGlobal.browsingContext.topChromeWindow;
}
get sidebarController() {

View File

@ -41,6 +41,8 @@ run-if = ["os == 'mac'"] # Mac only feature
["browser_sidebar_prefs.js"]
["browser_sidebar_remote.js"]
["browser_syncedtabs_sidebar.js"]
["browser_toolbar_sidebar_button.js"]

View File

@ -10,8 +10,8 @@ const TAB_DIRECTION_PREF = "sidebar.verticalTabs";
async function showCustomizePanel(win) {
await win.SidebarController.show("viewCustomizeSidebar");
const document = win.SidebarController.browser.contentDocument;
return TestUtils.waitForCondition(async () => {
return TestUtils.waitForCondition(() => {
const document = win.SidebarController.browser.contentDocument;
const component = document.querySelector("sidebar-customize");
if (!component?.positionInputs || !component?.visibilityInputs) {
return false;
@ -122,12 +122,13 @@ add_task(async function test_customize_not_added_in_menubar() {
add_task(async function test_manage_preferences_navigation() {
const win = await BrowserTestUtils.openNewBrowserWindow();
const { SidebarController } = win;
const { contentWindow } = SidebarController.browser;
const sidebar = document.querySelector("sidebar-main");
ok(sidebar, "Sidebar is shown.");
await sidebar.updateComplete;
await toggleSidebarPanel(win, "viewCustomizeSidebar");
await showCustomizePanel(win);
const sidebarBox = win.document.getElementById("sidebar-box");
await BrowserTestUtils.waitForCondition(
() => BrowserTestUtils.isVisible(sidebarBox),
"Sidebar panel is visible"
);
let customizeDocument = win.SidebarController.browser.contentDocument;
const customizeComponent =
customizeDocument.querySelector("sidebar-customize");
@ -138,7 +139,7 @@ add_task(async function test_manage_preferences_navigation() {
EventUtils.synthesizeMouseAtCenter(
manageSettings.querySelector("a"),
{},
contentWindow
customizeDocument.ownerGlobal
);
await BrowserTestUtils.waitForCondition(
() =>

View File

@ -0,0 +1,47 @@
/* Any copyright is dedicated to the Public Domain.
https://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
add_task(async function test_remote_sidebar_browser() {
await SpecialPowers.pushPrefEnv({
set: [
["browser.shopping.experience2023.integratedSidebar", true],
["sidebar.main.tools", "reviewchecker,syncedtabs,history"],
],
});
const sidebar = document.querySelector("sidebar-main");
ok(sidebar, "Sidebar is shown.");
// Non-remote sidebar
await SidebarController.show("viewHistorySidebar");
ok(SidebarController.browser, "Sidebar browser is shown.");
ok(
!SidebarController.browser.hasAttribute("remote"),
"Sidebar browser is not remote."
);
// Remote content sidebar
await SidebarController.show("viewReviewCheckerSidebar");
ok(SidebarController.browser, "Sidebar browser is shown.");
Assert.equal(
SidebarController.browser.getAttribute("remote"),
"true",
"Sidebar browser is remote."
);
Assert.equal(
SidebarController.browser.getAttribute("type"),
"content",
"Sidebar browser is remote."
);
// Another non-remote sidebar
await SidebarController.show("viewTabsSidebar");
ok(SidebarController.browser, "Sidebar browser is shown.");
ok(
!SidebarController.browser.hasAttribute("remote"),
"Sidebar browser is not remote."
);
await SpecialPowers.popPrefEnv();
});

View File

@ -274,7 +274,7 @@ LightweightThemeConsumer.prototype = {
// If enabled, apply the dark theme variant to private browsing windows.
if (
!Services.prefs.getBoolPref("browser.theme.dark-private-windows") ||
!lazy.PrivateBrowsingUtils.isWindowPrivate(this._win) ||
!this._win.browsingContext.usePrivateBrowsing ||
lazy.PrivateBrowsingUtils.permanentPrivateBrowsing
) {
return false;