diff --git a/browser/components/firefoxview/firefoxview-next.html b/browser/components/firefoxview/firefoxview-next.html
index e20a90de86ed..c8ddf0458ee3 100644
--- a/browser/components/firefoxview/firefoxview-next.html
+++ b/browser/components/firefoxview/firefoxview-next.html
@@ -92,7 +92,7 @@
-
+
@@ -103,10 +103,13 @@
-
-
-
-
+
+
+
+
diff --git a/browser/components/firefoxview/firefoxview-next.mjs b/browser/components/firefoxview/firefoxview-next.mjs
index f2bc9a108641..7cdb325e5c13 100644
--- a/browser/components/firefoxview/firefoxview-next.mjs
+++ b/browser/components/firefoxview/firefoxview-next.mjs
@@ -3,25 +3,37 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
let pageList = [];
+let categoryPagesDeck = null;
+let categoryNavigation = null;
+
const { topChromeWindow } = window.browsingContext;
function onHashChange() {
- changePage(document.location.hash.substring(1));
+ let page = document.location?.hash.substring(1);
+ if (!page || !pageList.includes(page)) {
+ page = "recentbrowsing";
+ }
+ changePage(page);
}
function changePage(page) {
- let navigation = document.querySelector("fxview-category-navigation");
- if (page && !pageList.includes(page)) {
- page = "recentbrowsing";
- document.location.hash = "recentbrowsing";
- }
- document.querySelector("named-deck").selectedViewName = page || pageList[0];
- navigation.currentCategory = page || pageList[0];
- if (navigation.categoryButtons.includes(document.activeElement)) {
- let currentCategoryButton = navigation.categoryButtons.find(
+ categoryPagesDeck.selectedViewName = page;
+ categoryNavigation.currentCategory = page;
+ if (categoryNavigation.categoryButtons.includes(document.activeElement)) {
+ let currentCategoryButton = categoryNavigation.categoryButtons.find(
categoryButton => categoryButton.name === page
);
- (currentCategoryButton || navigation.categoryButtons[0]).focus();
+ (currentCategoryButton || categoryNavigation.categoryButtons[0]).focus();
+ }
+}
+
+function onPagesDeckViewChange() {
+ for (const child of categoryPagesDeck.children) {
+ if (child.getAttribute("name") == categoryPagesDeck.selectedViewName) {
+ child.enter();
+ } else {
+ child.exit();
+ }
}
}
@@ -47,8 +59,11 @@ function recordNavigationTelemetry(source, eventTarget) {
window.addEventListener("DOMContentLoaded", async () => {
recordEnteredTelemetry();
- let navigation = document.querySelector("fxview-category-navigation");
- for (const item of navigation.categoryButtons) {
+
+ categoryNavigation = document.querySelector("fxview-category-navigation");
+ categoryPagesDeck = document.querySelector("named-deck");
+
+ for (const item of categoryNavigation.categoryButtons) {
pageList.push(item.getAttribute("name"));
}
window.addEventListener("hashchange", onHashChange);
@@ -60,31 +75,26 @@ window.addEventListener("DOMContentLoaded", async () => {
window.addEventListener("card-container-view-all", function (event) {
recordNavigationTelemetry("view-all", event.originalTarget);
});
- if (document.location.hash) {
- changePage(document.location.hash.substring(1));
- }
+
+ categoryPagesDeck.addEventListener("view-changed", onPagesDeckViewChange);
+
+ // set the initial state
+ onHashChange();
+ onPagesDeckViewChange();
+
if (Cu.isInAutomation) {
Services.obs.notifyObservers(null, "firefoxview-entered");
}
});
-document
- .querySelector("named-deck")
- .addEventListener("view-changed", async event => {
- for (const child of event.target.children) {
- if (child.getAttribute("name") == event.target.selectedViewName) {
- child.enter();
- } else {
- child.exit();
- }
- }
- });
-
document.addEventListener("visibilitychange", () => {
if (document.visibilityState === "visible") {
recordEnteredTelemetry();
if (Cu.isInAutomation) {
- Services.obs.notifyObservers(null, "firefoxview-entered");
+ // allow all the component visibilitychange handlers to execute before notifying
+ requestAnimationFrame(() => {
+ Services.obs.notifyObservers(null, "firefoxview-entered");
+ });
}
}
});
@@ -127,7 +137,7 @@ function onCommand(e) {
null,
{
menu_action: item.id,
- page: location.hash.substring(1) || "recentbrowsing",
+ page: location.hash?.substring(1) || "recentbrowsing",
}
);
}
diff --git a/browser/components/firefoxview/history.mjs b/browser/components/firefoxview/history.mjs
index 5589b7c2f958..e8d9fcea9ebe 100644
--- a/browser/components/firefoxview/history.mjs
+++ b/browser/components/firefoxview/history.mjs
@@ -45,6 +45,7 @@ const SEARCH_DEBOUNCE_TIMEOUT_MS = 1000;
class HistoryInView extends ViewPage {
constructor() {
super();
+ this._started = false;
this.allHistoryItems = new Map();
this.historyMapByDate = [];
this.historyMapBySite = [];
@@ -58,10 +59,25 @@ class HistoryInView extends ViewPage {
this.fullyUpdated = false;
}
+ start() {
+ if (this._started) {
+ return;
+ }
+ this._started = true;
+
+ this.#updateAllHistoryItems();
+ this.placesQuery.observeHistory(data => this.#updateAllHistoryItems(data));
+
+ this.searchTask = new lazy.DeferredTask(
+ () => this.#updateSearchResults(),
+ SEARCH_DEBOUNCE_RATE_MS,
+ SEARCH_DEBOUNCE_TIMEOUT_MS
+ );
+ }
+
async connectedCallback() {
super.connectedCallback();
await this.updateHistoryData();
- this.placesQuery.observeHistory(data => this.#updateAllHistoryItems(data));
XPCOMUtils.defineLazyPreferenceGetter(
this,
"importHistoryDismissedPref",
@@ -88,23 +104,26 @@ class HistoryInView extends ViewPage {
// Convert milliseconds to days
this.profileAge = profileAge / 1000 / 60 / 60 / 24;
}
- this.searchTask = new lazy.DeferredTask(
- () => this.#updateSearchResults(),
- SEARCH_DEBOUNCE_RATE_MS,
- SEARCH_DEBOUNCE_TIMEOUT_MS
- );
+ }
+
+ stop() {
+ if (!this._started) {
+ return;
+ }
+ this._started = false;
+ this.placesQuery.close();
+ if (!this.searchTask.isFinalized) {
+ this.searchTask.finalize();
+ }
}
disconnectedCallback() {
super.disconnectedCallback();
- this.placesQuery.close();
+ this.stop();
this.migrationWizardDialog?.removeEventListener(
"MigrationWizard:Close",
this.migrationWizardDialog
);
- if (!this.searchTask.isFinalized) {
- this.searchTask.finalize();
- }
}
async #updateAllHistoryItems(allHistoryItems) {
@@ -133,13 +152,12 @@ class HistoryInView extends ViewPage {
}
}
- viewTabVisibleCallback() {
- this.#updateAllHistoryItems();
- this.placesQuery.observeHistory(data => this.#updateAllHistoryItems(data));
+ viewVisibleCallback() {
+ this.start();
}
- viewTabHiddenCallback() {
- this.placesQuery.close();
+ viewHiddenCallback() {
+ this.stop();
}
static queries = {
diff --git a/browser/components/firefoxview/opentabs.mjs b/browser/components/firefoxview/opentabs.mjs
index c0047d3b0a40..45eff670d2c8 100644
--- a/browser/components/firefoxview/opentabs.mjs
+++ b/browser/components/firefoxview/opentabs.mjs
@@ -8,7 +8,7 @@ import {
map,
when,
} from "chrome://global/content/vendor/lit.all.mjs";
-import { ViewPage } from "./viewpage.mjs";
+import { ViewPage, ViewPageContent } from "./viewpage.mjs";
const lazy = {};
@@ -39,10 +39,15 @@ class OpenTabsInView extends ViewPage {
static properties = {
windows: { type: Map },
};
+ static queries = {
+ viewCards: { all: "view-opentabs-card" },
+ };
+
static TAB_ATTRS_TO_WATCH = Object.freeze(["image", "label"]);
constructor() {
super();
+ this._started = false;
this.everyWindowCallbackId = `firefoxview-${Services.uuid.generateUUID()}`;
this.windows = new Map();
this.currentWindow = this.getWindow();
@@ -53,13 +58,22 @@ class OpenTabsInView extends ViewPage {
this.devices = [];
}
- connectedCallback() {
- super.connectedCallback();
+ start() {
+ if (this._started) {
+ return;
+ }
+ this._started = true;
+
+ Services.obs.addObserver(this.boundObserve, lazy.UIState.ON_UPDATE);
+ Services.obs.addObserver(this.boundObserve, TOPIC_DEVICELIST_UPDATED);
+ Services.obs.addObserver(this.boundObserve, TOPIC_CURRENT_BROWSER_CHANGED);
+
lazy.EveryWindow.registerCallback(
this.everyWindowCallbackId,
win => {
if (win.gBrowser && this._shouldShowOpenTabs(win) && !win.closed) {
const { tabContainer } = win.gBrowser;
+ tabContainer.addEventListener("TabSelect", this);
tabContainer.addEventListener("TabAttrModified", this);
tabContainer.addEventListener("TabClose", this);
tabContainer.addEventListener("TabMove", this);
@@ -75,6 +89,7 @@ class OpenTabsInView extends ViewPage {
win => {
if (win.gBrowser && this._shouldShowOpenTabs(win)) {
const { tabContainer } = win.gBrowser;
+ tabContainer.removeEventListener("TabSelect", this);
tabContainer.removeEventListener("TabAttrModified", this);
tabContainer.removeEventListener("TabClose", this);
tabContainer.removeEventListener("TabMove", this);
@@ -86,41 +101,52 @@ class OpenTabsInView extends ViewPage {
}
}
);
- this._updateOpenTabsList();
- this.addObserversIfNeeded();
+ // EveryWindow will invoke the callback for existing windows - including this one
+ // So this._updateOpenTabsList will get called for the already-open window
if (this.currentWindow.gSync) {
this.devices = this.currentWindow.gSync.getSendTabTargets();
}
+
+ for (let card of this.viewCards) {
+ card.paused = false;
+ card.viewVisibleCallback?.();
+ }
}
disconnectedCallback() {
+ super.disconnectedCallback();
+ this.stop();
+ }
+
+ stop() {
+ if (!this._started) {
+ return;
+ }
+ this._started = false;
+ this.paused = true;
+
lazy.EveryWindow.unregisterCallback(this.everyWindowCallbackId);
- this.removeObserversIfNeeded();
- }
- addObserversIfNeeded() {
- if (!this.observerAdded) {
- Services.obs.addObserver(this.boundObserve, lazy.UIState.ON_UPDATE);
- Services.obs.addObserver(this.boundObserve, TOPIC_DEVICELIST_UPDATED);
- Services.obs.addObserver(
- this.boundObserve,
- TOPIC_CURRENT_BROWSER_CHANGED
- );
- this.observerAdded = true;
+ Services.obs.removeObserver(this.boundObserve, lazy.UIState.ON_UPDATE);
+ Services.obs.removeObserver(this.boundObserve, TOPIC_DEVICELIST_UPDATED);
+ Services.obs.removeObserver(
+ this.boundObserve,
+ TOPIC_CURRENT_BROWSER_CHANGED
+ );
+
+ for (let card of this.viewCards) {
+ card.paused = true;
+ card.viewHiddenCallback?.();
}
}
- removeObserversIfNeeded() {
- if (this.observerAdded) {
- Services.obs.removeObserver(this.boundObserve, lazy.UIState.ON_UPDATE);
- Services.obs.removeObserver(this.boundObserve, TOPIC_DEVICELIST_UPDATED);
- Services.obs.removeObserver(
- this.boundObserve,
- TOPIC_CURRENT_BROWSER_CHANGED
- );
- this.observerAdded = false;
- }
+ viewVisibleCallback() {
+ this.start();
+ }
+
+ viewHiddenCallback() {
+ this.stop();
}
async observe(subject, topic, data) {
@@ -144,9 +170,6 @@ class OpenTabsInView extends ViewPage {
}
render() {
- if (!this.selectedTab && !this.recentBrowsing) {
- return null;
- }
if (this.recentBrowsing) {
return this.getRecentBrowsingTemplate();
}
@@ -200,6 +223,7 @@ class OpenTabsInView extends ViewPage {
`;
}
@@ -259,6 +285,13 @@ class OpenTabsInView extends ViewPage {
const win = target.ownerGlobal;
const tabs = this.windows.get(win);
switch (type) {
+ case "TabSelect": {
+ // if we're switching away from our tab, we can halt any updates immediately
+ if (detail.previousTab == this.getBrowserTab()) {
+ this.stop();
+ }
+ return;
+ }
case "TabAttrModified":
if (
!detail.changed.some(attr =>
@@ -328,7 +361,7 @@ customElements.define("view-opentabs", OpenTabsInView);
* @property {string} title
* The window title.
*/
-class OpenTabsInViewCard extends ViewPage {
+class OpenTabsInViewCard extends ViewPageContent {
static properties = {
showMore: { type: Boolean },
tabs: { type: Array },
@@ -512,6 +545,18 @@ class OpenTabsInViewCard extends ViewPage {
);
}
+ viewVisibleCallback() {
+ if (this.tabList) {
+ this.tabList.visible = true;
+ }
+ }
+
+ viewHiddenCallback() {
+ if (this.tabList) {
+ this.tabList.visible = false;
+ }
+ }
+
render() {
return html`
this.observe(...args);
this.fullyUpdated = false;
this.maxTabsLength = this.recentBrowsing ? 5 : 25;
@@ -62,55 +63,54 @@ class RecentlyClosedTabsInView extends ViewPage {
}
}
- connectedCallback() {
- super.connectedCallback();
+ start() {
+ if (this._started) {
+ return;
+ }
+ this._started = true;
+ this.paused = false;
this.updateRecentlyClosedTabs();
- this.addObserversIfNeeded();
+
+ Services.obs.addObserver(
+ this.boundObserve,
+ SS_NOTIFY_CLOSED_OBJECTS_CHANGED
+ );
+ Services.obs.addObserver(
+ this.boundObserve,
+ SS_NOTIFY_BROWSER_SHUTDOWN_FLUSH
+ );
+ }
+
+ stop() {
+ if (!this._started) {
+ return;
+ }
+ this._started = false;
+
+ Services.obs.removeObserver(
+ this.boundObserve,
+ SS_NOTIFY_CLOSED_OBJECTS_CHANGED
+ );
+ Services.obs.removeObserver(
+ this.boundObserve,
+ SS_NOTIFY_BROWSER_SHUTDOWN_FLUSH
+ );
}
disconnectedCallback() {
super.disconnectedCallback();
- this.removeObserversIfNeeded();
+ this.stop();
}
- addObserversIfNeeded() {
- if (!this.observerAdded) {
- Services.obs.addObserver(
- this.boundObserve,
- SS_NOTIFY_CLOSED_OBJECTS_CHANGED
- );
- Services.obs.addObserver(
- this.boundObserve,
- SS_NOTIFY_BROWSER_SHUTDOWN_FLUSH
- );
- this.observerAdded = true;
- }
+ // We remove all the observers when the instance is not visible to the user
+ viewHiddenCallback() {
+ this.stop();
}
- removeObserversIfNeeded() {
- if (this.observerAdded) {
- Services.obs.removeObserver(
- this.boundObserve,
- SS_NOTIFY_CLOSED_OBJECTS_CHANGED
- );
- Services.obs.removeObserver(
- this.boundObserve,
- SS_NOTIFY_BROWSER_SHUTDOWN_FLUSH
- );
- this.observerAdded = false;
- }
- }
-
- // we observe when a tab closes but since this notification fires more frequently and on
- // all windows, we remove the observer when another tab is selected
- viewTabHiddenCallback() {
- this.removeObserversIfNeeded();
- }
-
- // we check for changes to the session store once the user return to this tab.
- viewTabVisibleCallback() {
- this.addObserversIfNeeded();
- this.updateRecentlyClosedTabs();
+ // We add observers and check for changes to the session store once the user return to this tab.
+ // or the instance becomes visible to the user
+ viewVisibleCallback() {
+ this.start();
}
getTabStateValue(tab, key) {
@@ -282,9 +282,6 @@ class RecentlyClosedTabsInView extends ViewPage {
}
render() {
- if (!this.selectedTab && !this.recentBrowsing) {
- return null;
- }
return html`
this.observe(...args);
this._currentSetupStateIndex = -1;
this.errorState = null;
@@ -56,7 +57,13 @@ class SyncedTabsInView extends ViewPage {
connectedCallback() {
super.connectedCallback();
this.addEventListener("click", this);
- this.ownerDocument.addEventListener("visibilitychange", this);
+ }
+
+ start() {
+ if (this._started) {
+ return;
+ }
+ this._started = true;
Services.obs.addObserver(this.boundObserve, TOPIC_SETUPSTATE_CHANGED);
Services.obs.addObserver(this.boundObserve, SYNCED_TABS_CHANGED);
@@ -64,15 +71,21 @@ class SyncedTabsInView extends ViewPage {
this.onVisibilityChange();
}
- cleanup() {
+ stop() {
+ if (!this._started) {
+ return;
+ }
+ this._started = false;
TabsSetupFlowManager.updateViewVisibility(this._id, "unloaded");
- this.ownerDocument?.removeEventListener("visibilitychange", this);
+ this.onVisibilityChange();
+
Services.obs.removeObserver(this.boundObserve, TOPIC_SETUPSTATE_CHANGED);
Services.obs.removeObserver(this.boundObserve, SYNCED_TABS_CHANGED);
}
disconnectedCallback() {
- this.cleanup();
+ super.disconnectedCallback();
+ this.stop();
}
handleEvent(event) {
@@ -114,15 +127,19 @@ class SyncedTabsInView extends ViewPage {
if (event.type == "change") {
TabsSetupFlowManager.syncOpenTabs(event.target);
}
-
- // Returning to fxview seems like a likely time for a device check
- if (event.type == "visibilitychange") {
- this.onVisibilityChange();
- }
}
+
+ viewVisibleCallback() {
+ this.start();
+ }
+
+ viewHiddenCallback() {
+ this.stop();
+ }
+
onVisibilityChange() {
- const isVisible = document.visibilityState == "visible";
const isOpen = this.open;
+ const isVisible = this.isVisible;
if (isVisible && isOpen) {
this.update();
TabsSetupFlowManager.updateViewVisibility(this._id, "visible");
diff --git a/browser/components/firefoxview/tests/browser/FirefoxViewTestUtils.sys.mjs b/browser/components/firefoxview/tests/browser/FirefoxViewTestUtils.sys.mjs
index 029c5133683d..0e21d97fddba 100644
--- a/browser/components/firefoxview/tests/browser/FirefoxViewTestUtils.sys.mjs
+++ b/browser/components/firefoxview/tests/browser/FirefoxViewTestUtils.sys.mjs
@@ -51,35 +51,45 @@ async function openFirefoxViewTab(win) {
);
}
await testScope.SimpleTest.promiseFocus(win);
+ let fxviewTab = win.FirefoxViewHandler.tab;
+ let alreadyLoaded =
+ fxviewTab?.linkedBrowser.currentURI.spec.includes(getFirefoxViewURL()) &&
+ fxviewTab?.linkedBrowser?.contentDocument?.readyState == "complete";
+ let enteredPromise = alreadyLoaded
+ ? Promise.resolve()
+ : TestUtils.topicObserved("firefoxview-entered");
- const fxViewTab = win.FirefoxViewHandler.tab;
- const alreadyLoaded =
- fxViewTab?.linkedBrowser?.currentURI.spec.split("#")[0] ==
- getFirefoxViewURL();
- const enteredPromise =
- alreadyLoaded &&
- fxViewTab.linkedBrowser.contentDocument.visibilityState == "visible"
- ? Promise.resolve()
- : TestUtils.topicObserved("firefoxview-entered");
- await BrowserTestUtils.synthesizeMouseAtCenter(
- "#firefox-view-button",
- { type: "mousedown" },
- win.browsingContext
- );
+ if (!fxviewTab?.selected) {
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ "#firefox-view-button",
+ { type: "mousedown" },
+ win.browsingContext
+ );
+ await TestUtils.waitForTick();
+ }
+
+ fxviewTab = win.FirefoxViewHandler.tab;
assertFirefoxViewTab(win);
Assert.ok(
win.FirefoxViewHandler.tab.selected,
"Firefox View tab is selected"
);
- if (!alreadyLoaded) {
- testScope.info("Not already loaded, waiting for browserLoaded");
- await BrowserTestUtils.browserLoaded(
- win.FirefoxViewHandler.tab.linkedBrowser
- );
- }
- await enteredPromise;
- return win.FirefoxViewHandler.tab;
+ testScope.info(
+ "openFirefoxViewTab, waiting for complete readyState, visible and firefoxview-entered"
+ );
+ await Promise.all([
+ TestUtils.waitForCondition(() => {
+ const document = fxviewTab.linkedBrowser.contentDocument;
+ return (
+ document.readyState == "complete" &&
+ document.visibilityState == "visible"
+ );
+ }),
+ enteredPromise,
+ ]);
+ testScope.info("openFirefoxViewTab, ready resolved");
+ return fxviewTab;
}
function closeFirefoxViewTab(win) {
diff --git a/browser/components/firefoxview/tests/browser/firefoxview-next/browser.toml b/browser/components/firefoxview/tests/browser/firefoxview-next/browser.toml
index 387ddb3ab071..c0b283e65f50 100644
--- a/browser/components/firefoxview/tests/browser/firefoxview-next/browser.toml
+++ b/browser/components/firefoxview/tests/browser/firefoxview-next/browser.toml
@@ -3,6 +3,8 @@ support-files = [ "../head.js"]
["browser_firefoxview_next.js"]
+["browser_firefoxview_paused.js"]
+
["browser_firefoxview_next_general_telemetry.js"]
fail-if = ["a11y_checks"] # Bug 1854625 clicked button and fxview-tab-row-secondary-button may not be focusable
diff --git a/browser/components/firefoxview/tests/browser/firefoxview-next/browser_firefoxview_paused.js b/browser/components/firefoxview/tests/browser/firefoxview-next/browser_firefoxview_paused.js
new file mode 100644
index 000000000000..8a55ade7e7f4
--- /dev/null
+++ b/browser/components/firefoxview/tests/browser/firefoxview-next/browser_firefoxview_paused.js
@@ -0,0 +1,264 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* import-globals-from ../head.js */
+
+const tabURL1 = "data:,Tab1";
+const tabURL2 = "data:,Tab2";
+const tabURL3 = "data:,Tab3";
+
+const TestTabs = {};
+
+function getTopLevelViewElements(document) {
+ return {
+ recentBrowsingView: document.querySelector(
+ "named-deck > view-recentbrowsing"
+ ),
+ recentlyClosedView: document.querySelector(
+ "named-deck > view-recentlyclosed"
+ ),
+ openTabsView: document.querySelector("named-deck > view-opentabs"),
+ };
+}
+
+async function getElements(document) {
+ let { recentBrowsingView, recentlyClosedView, openTabsView } =
+ getTopLevelViewElements(document);
+ let recentBrowsingOpenTabsView =
+ recentBrowsingView.querySelector("view-opentabs");
+ let recentBrowsingOpenTabsList =
+ recentBrowsingOpenTabsView?.viewCards[0]?.tabList;
+ let recentBrowsingRecentlyClosedTabsView = recentBrowsingView.querySelector(
+ "view-recentlyclosed"
+ );
+ let recentBrowsingRecentlyClosedTabsList =
+ recentBrowsingRecentlyClosedTabsView?.tabList;
+
+ let recentlyClosedList = recentlyClosedView.tabList;
+ let openTabsList =
+ openTabsView.shadowRoot.querySelector("view-opentabs-card")?.tabList;
+
+ return {
+ // recentbrowsing
+ recentBrowsingView,
+ recentBrowsingOpenTabsView,
+ recentBrowsingOpenTabsList,
+ recentBrowsingRecentlyClosedTabsView,
+ recentBrowsingRecentlyClosedTabsList,
+
+ // recentlyclosed
+ recentlyClosedView,
+ recentlyClosedList,
+
+ // opentabs
+ openTabsView,
+ openTabsList,
+ };
+}
+
+async function nextFrame(global = window) {
+ await new Promise(resolve => {
+ global.requestAnimationFrame(() => {
+ global.requestAnimationFrame(resolve);
+ });
+ });
+}
+
+async function setupOpenAndClosedTabs() {
+ TestTabs.tab1 = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ tabURL1
+ );
+ TestTabs.tab2 = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ tabURL2
+ );
+ TestTabs.tab3 = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ tabURL3
+ );
+ // close a tab so we have recently-closed tabs content
+ await SessionStoreTestUtils.closeTab(TestTabs.tab3);
+}
+
+async function checkFxRenderCalls(browser, elements, selectedView) {
+ const sandbox = sinon.createSandbox();
+ const topLevelViews = getTopLevelViewElements(browser.contentDocument);
+
+ // sanity-check the selectedView we were given
+ ok(
+ Object.values(topLevelViews).find(view => view == selectedView),
+ `The selected view is in the topLevelViews`
+ );
+
+ const elementSpies = new Map();
+ const viewSpies = new Map();
+
+ for (let [elemName, elem] of Object.entries(topLevelViews)) {
+ let spy;
+ if (elem.render.isSinonProxy) {
+ spy = elem.render;
+ } else {
+ info(`Creating spy for render on element: ${elemName}`);
+ spy = sandbox.spy(elem, "render");
+ }
+ viewSpies.set(elem, spy);
+ }
+ for (let [elemName, elem] of Object.entries(elements)) {
+ let spy;
+ if (elem.render.isSinonProxy) {
+ spy = elem.render;
+ } else {
+ info(`Creating spy for render on element: ${elemName}`);
+ spy = sandbox.spy(elem, "render");
+ }
+ elementSpies.set(elem, spy);
+ }
+
+ info("test switches to tab2");
+ await BrowserTestUtils.switchTab(gBrowser, TestTabs.tab2);
+
+ // check all the top-level views are paused
+ ok(
+ topLevelViews.recentBrowsingView.paused,
+ "The recent-browsing view is paused"
+ );
+ ok(
+ topLevelViews.recentlyClosedView.paused,
+ "The recently-closed tabs view is paused"
+ );
+ ok(topLevelViews.openTabsView.paused, "The open tabs view is paused");
+
+ function assertSpiesCalled(spiesMap, expectCalled) {
+ let message = expectCalled ? "to be called" : "to not be called";
+ for (let [elem, renderSpy] of spiesMap.entries()) {
+ is(
+ expectCalled,
+ renderSpy.called,
+ `Expected the render method spy on element ${elem.localName} ${message}`
+ );
+ }
+ }
+
+ await nextFrame();
+ info("test removes tab1");
+ await BrowserTestUtils.removeTab(TestTabs.tab1);
+ await nextFrame();
+
+ assertSpiesCalled(viewSpies, false);
+ assertSpiesCalled(elementSpies, false);
+
+ for (let renderSpy of [...viewSpies.values(), ...elementSpies.values()]) {
+ renderSpy.resetHistory();
+ }
+
+ info("test will re-open fxview");
+ await openFirefoxViewTab(window);
+ await nextFrame();
+
+ assertSpiesCalled(elementSpies, true);
+ ok(
+ selectedView.render.called,
+ `Render was called on the selected top-level view: ${selectedView.localName}`
+ );
+
+ // check all the other views did not render
+ viewSpies.delete(selectedView);
+ assertSpiesCalled(viewSpies, false);
+
+ sandbox.restore();
+}
+
+add_task(async function test_recentbrowsing() {
+ await setupOpenAndClosedTabs();
+
+ await withFirefoxView({}, async browser => {
+ const document = browser.contentDocument;
+ is(document.querySelector("named-deck").selectedViewName, "recentbrowsing");
+
+ const {
+ recentBrowsingView,
+ recentBrowsingOpenTabsView,
+ recentBrowsingOpenTabsList,
+ recentBrowsingRecentlyClosedTabsView,
+ recentBrowsingRecentlyClosedTabsList,
+ } = await getElements(document);
+
+ ok(recentBrowsingView, "Found the recent-browsing view");
+ ok(recentBrowsingOpenTabsView, "Found the recent-browsing open tabs view");
+ ok(recentBrowsingOpenTabsList, "Found the recent-browsing open tabs list");
+ ok(
+ recentBrowsingRecentlyClosedTabsView,
+ "Found the recent-browsing recently-closed tabs view"
+ );
+ ok(
+ recentBrowsingRecentlyClosedTabsList,
+ "Found the recent-browsing recently-closed tabs list"
+ );
+
+ await checkFxRenderCalls(
+ browser,
+ {
+ recentBrowsingView,
+ recentBrowsingOpenTabsView,
+ recentBrowsingOpenTabsList,
+ recentBrowsingRecentlyClosedTabsView,
+ recentBrowsingRecentlyClosedTabsList,
+ },
+ recentBrowsingView
+ );
+ });
+ await BrowserTestUtils.removeTab(TestTabs.tab2);
+});
+
+add_task(async function test_opentabs() {
+ await setupOpenAndClosedTabs();
+
+ await withFirefoxView({}, async browser => {
+ const document = browser.contentDocument;
+ const { openTabsView } = getTopLevelViewElements(document);
+
+ await navigateToCategoryAndWait(document, "opentabs");
+
+ const { openTabsList } = await getElements(document);
+ ok(openTabsView, "Found the open tabs view");
+ ok(openTabsList, "Found the first open tabs list");
+ ok(!openTabsView.paused, "The open tabs view is un-paused");
+ is(openTabsView.slot, "selected", "The open tabs view is selected");
+
+ await checkFxRenderCalls(
+ browser,
+ {
+ openTabsView,
+ openTabsList,
+ },
+ openTabsView
+ );
+ });
+ await BrowserTestUtils.removeTab(TestTabs.tab2);
+});
+
+add_task(async function test_recentlyclosed() {
+ await setupOpenAndClosedTabs();
+
+ await withFirefoxView({}, async browser => {
+ const document = browser.contentDocument;
+ const { recentlyClosedView } = getTopLevelViewElements(document);
+ await navigateToCategoryAndWait(document, "recentlyclosed");
+
+ const { recentlyClosedList } = await getElements(document);
+ ok(recentlyClosedView, "Found the recently-closed view");
+ ok(recentlyClosedList, "Found the recently-closed list");
+ ok(!recentlyClosedView.paused, "The recently-closed view is un-paused");
+
+ await checkFxRenderCalls(
+ browser,
+ {
+ recentlyClosedView,
+ recentlyClosedList,
+ },
+ recentlyClosedView
+ );
+ });
+ await BrowserTestUtils.removeTab(TestTabs.tab2);
+});
diff --git a/browser/components/firefoxview/tests/browser/firefoxview-next/browser_history_firefoxview_next.js b/browser/components/firefoxview/tests/browser/firefoxview-next/browser_history_firefoxview_next.js
index 255e08967299..74d78fd8b899 100644
--- a/browser/components/firefoxview/tests/browser/firefoxview-next/browser_history_firefoxview_next.js
+++ b/browser/components/firefoxview/tests/browser/firefoxview-next/browser_history_firefoxview_next.js
@@ -58,14 +58,6 @@ async function historyComponentReady(historyComponent) {
is(expected, actual, `Total number of cards should be ${expected}`);
}
-async function openFirefoxView(win) {
- await BrowserTestUtils.synthesizeMouseAtCenter(
- "#firefox-view-button",
- { type: "mousedown" },
- win.browsingContext
- );
-}
-
async function historyTelemetry() {
await TestUtils.waitForCondition(
() => {
@@ -177,7 +169,7 @@ add_task(async function test_list_ordering() {
const { document } = browser.contentWindow;
is(document.location.href, "about:firefoxview-next");
- navigateToCategory(document, "history");
+ await navigateToCategoryAndWait(document, "history");
let historyComponent = document.querySelector("view-history");
historyComponent.profileAge = 8;
@@ -201,9 +193,14 @@ add_task(async function test_list_ordering() {
return historyComponent.lists[0].rowEls.length;
});
let firstHistoryLink = historyComponent.lists[0].rowEls[0].mainEl;
+ let promiseHidden = BrowserTestUtils.waitForEvent(
+ document,
+ "visibilitychange"
+ );
await EventUtils.synthesizeMouseAtCenter(firstHistoryLink, {}, content);
await historyTelemetry();
- await switchToFxViewTab(browser.ownerGlobal);
+ await promiseHidden;
+ await openFirefoxViewTab(browser.ownerGlobal);
// Test number of cards when sorted by site/domain
await clearAllParentTelemetryEvents();
@@ -266,7 +263,7 @@ add_task(async function test_empty_states() {
const { document } = browser.contentWindow;
is(document.location.href, "about:firefoxview-next");
- navigateToCategory(document, "history");
+ await navigateToCategoryAndWait(document, "history");
let historyComponent = document.querySelector("view-history");
historyComponent.profileAge = 8;
@@ -354,7 +351,7 @@ add_task(async function test_observers_removed_when_view_is_hidden() {
);
await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow;
- navigateToCategory(document, "history");
+ await navigateToCategoryAndWait(document, "history");
const historyComponent = document.querySelector("view-history");
historyComponent.profileAge = 8;
let visitList = await TestUtils.waitForCondition(() =>
@@ -363,7 +360,12 @@ add_task(async function test_observers_removed_when_view_is_hidden() {
info("The list should show a visit from the new tab.");
await TestUtils.waitForCondition(() => visitList.rowEls.length === 1);
+ let promiseHidden = BrowserTestUtils.waitForEvent(
+ document,
+ "visibilitychange"
+ );
await BrowserTestUtils.switchTab(gBrowser, tab);
+ await promiseHidden;
const { date } = await PlacesUtils.history
.fetch(NEW_TAB_URL, {
includeVisits: true,
@@ -377,7 +379,7 @@ add_task(async function test_observers_removed_when_view_is_hidden() {
);
info("The list should update when Firefox View is visible.");
- await switchToFxViewTab(browser.ownerGlobal);
+ await openFirefoxViewTab(browser.ownerGlobal);
visitList = await TestUtils.waitForCondition(() =>
historyComponent.cards?.[0]?.querySelector("fxview-tab-list")
);
@@ -399,7 +401,7 @@ add_task(async function test_show_all_history_telemetry() {
const { document } = browser.contentWindow;
is(document.location.href, "about:firefoxview-next");
- navigateToCategory(document, "history");
+ await navigateToCategoryAndWait(document, "history");
let historyComponent = document.querySelector("view-history");
historyComponent.profileAge = 8;
@@ -424,7 +426,7 @@ add_task(async function test_show_all_history_telemetry() {
add_task(async function test_search_history() {
await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow;
- navigateToCategory(document, "history");
+ await navigateToCategoryAndWait(document, "history");
const historyComponent = document.querySelector("view-history");
historyComponent.profileAge = 8;
await historyComponentReady(historyComponent);
diff --git a/browser/components/firefoxview/tests/browser/firefoxview-next/browser_opentabs_cards.js b/browser/components/firefoxview/tests/browser/firefoxview-next/browser_opentabs_cards.js
index 289a608f6bc4..7b737a80740d 100644
--- a/browser/components/firefoxview/tests/browser/firefoxview-next/browser_opentabs_cards.js
+++ b/browser/components/firefoxview/tests/browser/firefoxview-next/browser_opentabs_cards.js
@@ -20,9 +20,8 @@ add_setup(function () {
async function navigateToOpenTabs(browser) {
const document = browser.contentDocument;
if (document.querySelector("named-deck").selectedViewName != "opentabs") {
- navigateToCategory(document, "opentabs");
+ await navigateToCategoryAndWait(browser.contentDocument, "opentabs");
}
- await TestUtils.waitForTick();
}
function getOpenTabsComponent(browser) {
@@ -79,8 +78,14 @@ add_task(async function open_tab_same_window() {
gBrowser.visibleTabs[0].linkedBrowser.currentURI.spec,
"The first item represents the first visible tab"
);
+ let promiseHidden = BrowserTestUtils.waitForEvent(
+ browser.contentDocument,
+ "visibilitychange"
+ );
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+ await promiseHidden;
});
- await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
const [originalTab, newTab] = gBrowser.visibleTabs;
await openFirefoxViewTab(window).then(async viewTab => {
@@ -92,7 +97,12 @@ add_task(async function open_tab_same_window() {
is(tabItems.length, 2, "There are two items.");
is(tabItems[1].url, TEST_URL, "The newly opened tab appears last.");
+ let promiseHidden = BrowserTestUtils.waitForEvent(
+ browser.contentDocument,
+ "visibilitychange"
+ );
tabItems[0].mainEl.click();
+ await promiseHidden;
});
await BrowserTestUtils.waitForCondition(
@@ -104,7 +114,14 @@ add_task(async function open_tab_same_window() {
const browser = viewTab.linkedBrowser;
const cards = getCards(browser);
let tabItems = await getRowsForCard(cards[0]);
+
+ let promiseHidden = BrowserTestUtils.waitForEvent(
+ browser.contentDocument,
+ "visibilitychange"
+ );
+
tabItems[1].mainEl.click();
+ await promiseHidden;
});
await BrowserTestUtils.waitForCondition(
@@ -128,11 +145,8 @@ add_task(async function open_tab_same_window() {
return tabItems[0].url === TEST_URL;
}
);
- });
+ await BrowserTestUtils.removeTab(newTab);
- await BrowserTestUtils.removeTab(newTab);
- await openFirefoxViewTab(window).then(async viewTab => {
- const browser = viewTab.linkedBrowser;
const [card] = getCards(browser);
await TestUtils.waitForCondition(
async () => (await getRowsForCard(card)).length === 1,
@@ -300,11 +314,11 @@ add_task(async function toggle_show_more_link() {
is(cards.length, NUMBER_OF_WINDOWS, "There are four windows.");
lastCard = cards[NUMBER_OF_WINDOWS - 1];
lastWindow = windows[NUMBER_OF_WINDOWS - 2];
- });
- for (let i = 0; i < NUMBER_OF_TABS - 1; i++) {
- await BrowserTestUtils.openNewForegroundTab(lastWindow.gBrowser);
- }
+ for (let i = 0; i < NUMBER_OF_TABS - 1; i++) {
+ await BrowserTestUtils.openNewForegroundTab(lastWindow.gBrowser);
+ }
+ });
await openFirefoxViewTab(window).then(async viewTab => {
const browser = viewTab.linkedBrowser;
diff --git a/browser/components/firefoxview/tests/browser/firefoxview-next/browser_opentabs_recency_next.js b/browser/components/firefoxview/tests/browser/firefoxview-next/browser_opentabs_recency_next.js
index e3a70f2a6e89..7be1a7bfc234 100644
--- a/browser/components/firefoxview/tests/browser/firefoxview-next/browser_opentabs_recency_next.js
+++ b/browser/components/firefoxview/tests/browser/firefoxview-next/browser_opentabs_recency_next.js
@@ -159,10 +159,16 @@ add_task(async function test_single_window_tabs() {
await openFirefoxViewTab(window).then(async viewTab => {
const browser = viewTab.linkedBrowser;
await checkTabList(browser, [tabURL2, tabURL1]);
+
+ // switch to the first tab
+ let promiseHidden = BrowserTestUtils.waitForEvent(
+ browser.contentDocument,
+ "visibilitychange"
+ );
+ await BrowserTestUtils.switchTab(gBrowser, gBrowser.visibleTabs[0]);
+ await promiseHidden;
});
- // switch to the first tab
- await BrowserTestUtils.switchTab(gBrowser, gBrowser.visibleTabs[0]);
// and check the results in the open tabs section of Recent Browsing
await openFirefoxViewTab(window).then(async viewTab => {
const browser = viewTab.linkedBrowser;
@@ -184,20 +190,29 @@ add_task(async function test_multiple_window_tabs() {
await openFirefoxViewTab(win2).then(async viewTab => {
const browser = viewTab.linkedBrowser;
await checkTabList(browser, [tabURL4, tabURL3, tabURL2, tabURL1]);
- });
- Assert.equal(
- tabUrl(win2.gBrowser.selectedTab),
- fxViewURL,
- `The selected tab in window 2 is ${fxViewURL}`
- );
- info("Switching to first tab (tab3) in win2");
- await BrowserTestUtils.switchTab(win2.gBrowser, win2.gBrowser.visibleTabs[0]);
- Assert.equal(
- tabUrl(win2.gBrowser.selectedTab),
- tabURL3,
- `The selected tab in window 2 is ${tabURL3}`
- );
+ Assert.equal(
+ tabUrl(win2.gBrowser.selectedTab),
+ fxViewURL,
+ `The selected tab in window 2 is ${fxViewURL}`
+ );
+
+ info("Switching to first tab (tab3) in win2");
+ let promiseHidden = BrowserTestUtils.waitForEvent(
+ browser.contentDocument,
+ "visibilitychange"
+ );
+ await BrowserTestUtils.switchTab(
+ win2.gBrowser,
+ win2.gBrowser.visibleTabs[0]
+ );
+ Assert.equal(
+ tabUrl(win2.gBrowser.selectedTab),
+ tabURL3,
+ `The selected tab in window 2 is ${tabURL3}`
+ );
+ await promiseHidden;
+ });
info("Opening fxview in win2 to confirm tab3 is most recent");
await openFirefoxViewTab(win2).then(async viewTab => {
@@ -221,10 +236,18 @@ add_task(async function test_multiple_window_tabs() {
"In fxview, check result of activating window 1, where tab 2 is selected"
);
await checkTabList(browser, [tabURL2, tabURL3, tabURL4, tabURL1]);
- });
- info("Switching to first visible tab (tab1) in win1");
- await BrowserTestUtils.switchTab(win1.gBrowser, win1.gBrowser.visibleTabs[0]);
+ let promiseHidden = BrowserTestUtils.waitForEvent(
+ browser.contentDocument,
+ "visibilitychange"
+ );
+ info("Switching to first visible tab (tab1) in win1");
+ await BrowserTestUtils.switchTab(
+ win1.gBrowser,
+ win1.gBrowser.visibleTabs[0]
+ );
+ await promiseHidden;
+ });
// check result in the fxview in the 1st window
info("Opening fxview in win1 to confirm tab1 is most recent");
@@ -272,10 +295,18 @@ add_task(async function test_minimize_restore_windows() {
await openFirefoxViewTab(win2).then(async viewTab => {
const browser = viewTab.linkedBrowser;
await checkTabList(browser, [tabURL4, tabURL3, tabURL2, tabURL1]);
+
+ let promiseHidden = BrowserTestUtils.waitForEvent(
+ browser.contentDocument,
+ "visibilitychange"
+ );
+ info("Switching to the first tab (tab3) in 2nd window");
+ await BrowserTestUtils.switchTab(
+ win2.gBrowser,
+ win2.gBrowser.visibleTabs[0]
+ );
+ await promiseHidden;
});
- //
- info("Switching to the first tab (tab3) in 2nd window");
- await BrowserTestUtils.switchTab(win2.gBrowser, win2.gBrowser.visibleTabs[0]);
// then minimize the window, focusing the 1st window
info("Minimizing win2, leaving tab 3 selected");
diff --git a/browser/components/firefoxview/tests/browser/firefoxview-next/browser_syncedtabs_errors_firefoxview_next.js b/browser/components/firefoxview/tests/browser/firefoxview-next/browser_syncedtabs_errors_firefoxview_next.js
index 59e60d727760..7561e6e03741 100644
--- a/browser/components/firefoxview/tests/browser/firefoxview-next/browser_syncedtabs_errors_firefoxview_next.js
+++ b/browser/components/firefoxview/tests/browser/firefoxview-next/browser_syncedtabs_errors_firefoxview_next.js
@@ -59,7 +59,7 @@ add_task(async function test_network_offline() {
sandbox.spy(TabsSetupFlowManager, "tryToClearError");
await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow;
- navigateToCategory(document, "syncedtabs");
+ await navigateToCategoryAndWait(document, "syncedtabs");
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
Services.obs.notifyObservers(
@@ -115,7 +115,7 @@ add_task(async function test_sync_error() {
const sandbox = await setupWithDesktopDevices();
await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow;
- navigateToCategory(document, "syncedtabs");
+ await navigateToCategoryAndWait(document, "syncedtabs");
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
Services.obs.notifyObservers(null, "weave:service:sync:error");
diff --git a/browser/components/firefoxview/tests/browser/firefoxview-next/browser_syncedtabs_firefoxview_next.js b/browser/components/firefoxview/tests/browser/firefoxview-next/browser_syncedtabs_firefoxview_next.js
index 114d991d6aab..3e8b73fcd0bc 100644
--- a/browser/components/firefoxview/tests/browser/firefoxview-next/browser_syncedtabs_firefoxview_next.js
+++ b/browser/components/firefoxview/tests/browser/firefoxview-next/browser_syncedtabs_firefoxview_next.js
@@ -27,7 +27,7 @@ add_task(async function test_unconfigured_initial_state() {
});
await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow;
- navigateToCategory(document, "syncedtabs");
+ await navigateToCategoryAndWait(document, "syncedtabs");
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
let syncedTabsComponent = document.querySelector(
@@ -46,7 +46,7 @@ add_task(async function test_unconfigured_initial_state() {
EventUtils.synthesizeMouseAtCenter(
emptyState.querySelector(`button[data-action="sign-in"]`),
{},
- content
+ browser.contentWindow
);
await TestUtils.waitForCondition(
() =>
@@ -83,7 +83,7 @@ add_task(async function test_signed_in() {
await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow;
- navigateToCategory(document, "syncedtabs");
+ await navigateToCategoryAndWait(document, "syncedtabs");
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
let syncedTabsComponent = document.querySelector(
@@ -102,7 +102,7 @@ add_task(async function test_signed_in() {
EventUtils.synthesizeMouseAtCenter(
emptyState.querySelector(`button[data-action="add-device"]`),
{},
- content
+ browser.contentWindow
);
await TestUtils.waitForCondition(
() =>
@@ -146,7 +146,7 @@ add_task(async function test_no_synced_tabs() {
await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow;
- navigateToCategory(document, "syncedtabs");
+ await navigateToCategoryAndWait(document, "syncedtabs");
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
let syncedTabsComponent = document.querySelector(
@@ -186,7 +186,7 @@ add_task(async function test_no_error_for_two_desktop() {
await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow;
- navigateToCategory(document, "syncedtabs");
+ await navigateToCategoryAndWait(document, "syncedtabs");
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
let syncedTabsComponent = document.querySelector(
@@ -230,7 +230,7 @@ add_task(async function test_empty_state() {
await withFirefoxView({ openNewWindow: true }, async browser => {
const { document } = browser.contentWindow;
- navigateToCategory(document, "syncedtabs");
+ await navigateToCategoryAndWait(document, "syncedtabs");
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
let syncedTabsComponent = document.querySelector(
@@ -275,7 +275,7 @@ add_task(async function test_tabs() {
await withFirefoxView({ openNewWindow: true }, async browser => {
const { document } = browser.contentWindow;
- navigateToCategory(document, "syncedtabs");
+ await navigateToCategoryAndWait(document, "syncedtabs");
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
let syncedTabsComponent = document.querySelector(
@@ -363,7 +363,7 @@ add_task(async function test_empty_desktop_same_name() {
await withFirefoxView({ openNewWindow: true }, async browser => {
const { document } = browser.contentWindow;
- navigateToCategory(document, "syncedtabs");
+ await navigateToCategoryAndWait(document, "syncedtabs");
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
let syncedTabsComponent = document.querySelector(
@@ -411,7 +411,7 @@ add_task(async function test_empty_desktop_same_name_three() {
await withFirefoxView({ openNewWindow: true }, async browser => {
const { document } = browser.contentWindow;
- navigateToCategory(document, "syncedtabs");
+ await navigateToCategoryAndWait(document, "syncedtabs");
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
let syncedTabsComponent = document.querySelector(
diff --git a/browser/components/firefoxview/tests/browser/head.js b/browser/components/firefoxview/tests/browser/head.js
index c03fd3818bc4..aa163a7b3ef5 100644
--- a/browser/components/firefoxview/tests/browser/head.js
+++ b/browser/components/firefoxview/tests/browser/head.js
@@ -597,6 +597,36 @@ function navigateToCategory(document, category) {
navButton.buttonEl.click();
}
+async function navigateToCategoryAndWait(document, category) {
+ info(`navigateToCategoryAndWait, for ${category}`);
+ const navigation = document.querySelector("fxview-category-navigation");
+ const win = document.ownerGlobal;
+ SimpleTest.promiseFocus(win);
+ let navButton = Array.from(navigation.categoryButtons).find(
+ categoryButton => {
+ return categoryButton.name === category;
+ }
+ );
+ const namedDeck = document.querySelector("named-deck");
+
+ await BrowserTestUtils.waitForCondition(
+ () => navButton.getBoundingClientRect().height,
+ `Waiting for ${category} button to be clickable`
+ );
+
+ EventUtils.synthesizeMouseAtCenter(navButton, {}, win);
+
+ await BrowserTestUtils.waitForCondition(() => {
+ let selectedView = Array.from(namedDeck.children).find(
+ child => child.slot == "selected"
+ );
+ return (
+ namedDeck.selectedViewName == category &&
+ selectedView?.getBoundingClientRect().height
+ );
+ }, `Waiting for ${category} to be visible`);
+}
+
/**
* Switch to the Firefox View tab.
*
diff --git a/browser/components/firefoxview/viewpage.mjs b/browser/components/firefoxview/viewpage.mjs
index 0277d5d3c268..22bed4369367 100644
--- a/browser/components/firefoxview/viewpage.mjs
+++ b/browser/components/firefoxview/viewpage.mjs
@@ -15,64 +15,58 @@ import "chrome://browser/content/firefoxview/fxview-tab-list.mjs";
import { placeLinkOnClipboard } from "./helpers.mjs";
-export class ViewPage extends MozLitElement {
+/**
+ * A base class for content container views displayed on firefox-view.
+ *
+ * @property {boolean} recentBrowsing
+ * Is part of the recentbrowsing page view
+ * @property {boolean} paused
+ * No content will be updated and rendered while paused
+ */
+export class ViewPageContent extends MozLitElement {
static get properties() {
return {
- selectedTab: { type: Boolean },
recentBrowsing: { type: Boolean },
+ paused: { type: Boolean },
};
}
-
constructor() {
super();
- this.selectedTab = false;
- this.recentBrowsing = Boolean(this.closest("VIEW-RECENTBROWSING"));
+ // don't update or render until explicitly un-paused
+ this.paused = true;
}
- connectedCallback() {
- super.connectedCallback();
- this.ownerDocument.addEventListener("visibilitychange", this);
+ get ownerViewPage() {
+ return this.closest("[type='page']") || this;
}
- disconnectedCallback() {
- super.disconnectedCallback();
- this.ownerDocument.removeEventListener("visibilitychange", this);
- }
-
- handleEvent(event) {
- switch (event.type) {
- case "visibilitychange":
- if (this.ownerDocument.visibilityState === "visible") {
- this.viewTabVisibleCallback();
- } else {
- this.viewTabHiddenCallback();
- }
- break;
+ get isVisible() {
+ if (!this.isConnected || this.ownerDocument.visibilityState != "visible") {
+ return false;
}
+ return this.ownerViewPage.selectedTab;
}
/**
- * Override this function to run a callback whenever Firefox View is visible.
+ * Override this function to run a callback whenever this content is visible.
*/
- viewTabVisibleCallback() {}
+ viewVisibleCallback() {}
/**
- * Override this function to run a callback whenever Firefox View is hidden.
+ * Override this function to run a callback whenever this content is hidden.
*/
- viewTabHiddenCallback() {}
-
- enter() {
- this.selectedTab = true;
- }
-
- exit() {
- this.selectedTab = false;
- }
+ viewHiddenCallback() {}
getWindow() {
return window.browsingContext.embedderWindowGlobal.browsingContext.window;
}
+ getBrowserTab() {
+ return this.getWindow().gBrowser.getTabForBrowser(
+ window.browsingContext.embedderElement
+ );
+ }
+
copyLink(e) {
placeLinkOnClipboard(this.triggerNode.title, this.triggerNode.url);
this.recordContextMenuTelemetry("copy-link", e);
@@ -104,4 +98,73 @@ export class ViewPage extends MozLitElement {
}
);
}
+
+ shouldUpdate(changedProperties) {
+ return !this.paused && super.shouldUpdate(changedProperties);
+ }
+}
+
+/**
+ * A "page" in firefox view, which may be hidden or shown by the named-deck container or
+ * via the owner document's visibility
+ *
+ * @property {boolean} selectedTab
+ * Is this page the selected view in the named-deck container
+ */
+export class ViewPage extends ViewPageContent {
+ static get properties() {
+ return {
+ selectedTab: { type: Boolean },
+ };
+ }
+
+ constructor() {
+ super();
+ this.selectedTab = false;
+ this.recentBrowsing = Boolean(this.closest("VIEW-RECENTBROWSING"));
+ this.onVisibilityChange = this.onVisibilityChange.bind(this);
+ }
+
+ onVisibilityChange(event) {
+ if (this.isVisible) {
+ this.paused = false;
+ this.viewVisibleCallback();
+ } else if (
+ this.ownerViewPage.selectedTab &&
+ this.ownerDocument.visibilityState == "hidden"
+ ) {
+ this.paused = true;
+ this.viewHiddenCallback();
+ }
+ }
+
+ connectedCallback() {
+ super.connectedCallback();
+ this.ownerDocument.addEventListener(
+ "visibilitychange",
+ this.onVisibilityChange
+ );
+ }
+
+ disconnectedCallback() {
+ super.disconnectedCallback();
+ this.ownerDocument.removeEventListener(
+ "visibilitychange",
+ this.onVisibilityChange
+ );
+ }
+
+ enter() {
+ this.selectedTab = true;
+ if (this.isVisible) {
+ this.paused = false;
+ this.viewVisibleCallback();
+ }
+ }
+
+ exit() {
+ this.selectedTab = false;
+ this.paused = true;
+ this.viewHiddenCallback();
+ }
}