Bug 1409262 - Notify when openerTabId changed via tabs.update() r=robwu,dao

Differential Revision: https://phabricator.services.mozilla.com/D164982
This commit is contained in:
Stefan Richter 2024-08-05 17:49:06 +00:00
Родитель 98bcd298b3
Коммит cb5c94e122
5 изменённых файлов: 152 добавлений и 21 удалений

Просмотреть файл

@ -429,17 +429,29 @@ class TabTracker extends TabTrackerBase {
}
/**
* Sets the opener of `tab` to the ID `openerTab`. Both tabs must be in the
* same window, or this function will throw a type error.
* Sets the opener of `tab` to the ID `openerTabId`. Both tabs must be in the
* same window, or this function will throw an error. if `openerTabId` is `-1`
* the opener tab is cleared.
*
* @param {Element} tab The tab for which to set the owner.
* @param {Element} openerTab The opener of <tab>.
* @param {Element} nativeTab The tab for which to set the owner.
* @param {number} openerTabId The openerTabId of <tab>.
*/
setOpener(tab, openerTab) {
if (tab.ownerDocument !== openerTab.ownerDocument) {
throw new Error("Tab must be in the same window as its opener");
setOpener(nativeTab, openerTabId) {
let nativeOpenerTab = null;
if (openerTabId > -1) {
nativeOpenerTab = tabTracker.getTab(openerTabId);
if (nativeTab.ownerDocument !== nativeOpenerTab.ownerDocument) {
throw new ExtensionError(
"Opener tab must be in the same window as the tab being updated"
);
}
}
if (nativeTab.openerTab !== nativeOpenerTab) {
nativeTab.openerTab = nativeOpenerTab;
this.emit("tab-openerTabId", { nativeTab, openerTabId });
}
tab.openerTab = openerTab;
}
deferredForTabOpen(nativeTab) {

Просмотреть файл

@ -160,6 +160,7 @@ const allProperties = new Set([
"hidden",
"isArticle",
"mutedInfo",
"openerTabId",
"pinned",
"sharingState",
"status",
@ -503,6 +504,11 @@ this.tabs = class extends ExtensionAPIPersistent {
}
};
let openerTabIdChangeListener = (_, { nativeTab, openerTabId }) => {
let tab = tabManager.getWrapper(nativeTab);
fireForTab(tab, { openerTabId }, nativeTab);
};
let listeners = new Map();
if (filter.properties.has("status") || filter.properties.has("url")) {
listeners.set("status", statusListener);
@ -531,6 +537,10 @@ this.tabs = class extends ExtensionAPIPersistent {
tabTracker.on("tab-isarticle", isArticleChangeListener);
}
if (filter.properties.has("openerTabId")) {
tabTracker.on("tab-openerTabId", openerTabIdChangeListener);
}
return {
unregister() {
for (let [name, listener] of listeners) {
@ -540,6 +550,10 @@ this.tabs = class extends ExtensionAPIPersistent {
if (filter.properties.has("isArticle")) {
tabTracker.off("tab-isarticle", isArticleChangeListener);
}
if (filter.properties.has("openerTabId")) {
tabTracker.off("tab-openerTabId", openerTabIdChangeListener);
}
},
convert(_fire, _context) {
fire = _fire;
@ -939,14 +953,7 @@ this.tabs = class extends ExtensionAPIPersistent {
}
}
if (updateProperties.openerTabId !== null) {
let opener = tabTracker.getTab(updateProperties.openerTabId);
if (opener.ownerDocument !== nativeTab.ownerDocument) {
return Promise.reject({
message:
"Opener tab must be in the same window as the tab being updated",
});
}
tabTracker.setOpener(nativeTab, opener);
tabTracker.setOpener(nativeTab, updateProperties.openerTabId);
}
if (updateProperties.successorTabId !== null) {
let successor = null;

Просмотреть файл

@ -109,7 +109,7 @@
},
"openerTabId": {
"type": "integer",
"minimum": 0,
"minimum": -1,
"optional": true,
"description": "The ID of the tab that opened this tab, if any. This property is only present if the opener tab still exists."
},
@ -623,7 +623,7 @@
},
"openerTabId": {
"type": "integer",
"minimum": 0,
"minimum": -1,
"optional": true,
"description": "The ID of the tab that opened this tab. If specified, the opener tab must be in the same window as the newly created tab."
},
@ -828,7 +828,7 @@
},
"openerTabId": {
"type": "integer",
"minimum": 0,
"minimum": -1,
"optional": true,
"description": "The ID of the tab that opened this tab. If specified, the opener tab must be in the same window as this tab."
},
@ -967,7 +967,7 @@
},
"openerTabId": {
"type": "integer",
"minimum": 0,
"minimum": -1,
"optional": true,
"description": "The ID of the tab that opened this tab. If specified, the opener tab must be in the same window as this tab."
},

Просмотреть файл

@ -128,3 +128,114 @@ add_task(async function () {
BrowserTestUtils.removeTab(tab1);
BrowserTestUtils.removeTab(tab2);
});
add_task(async function test_tabs_onUpdated_fired_on_openerTabId_change() {
let extension = ExtensionTestUtils.loadExtension({
manifest: {
permissions: ["tabs"],
},
async background() {
let changedOpenerTabIds = [];
let promise = new Promise(resolve => {
browser.tabs.onUpdated.addListener((tabId, changeInfo, _tab) => {
const { openerTabId } = changeInfo;
if (openerTabId) {
browser.test.assertDeepEq(
{ openerTabId },
changeInfo,
`"openerTabId" is the only key in changeInfo for tab ${tabId}`
);
changedOpenerTabIds.push(openerTabId);
if (openerTabId === -1) {
// The last part of the test changes openerTabId back to -1.
resolve();
}
}
});
});
try {
let tab1 = await browser.tabs.create({});
let tab2 = await browser.tabs.create({});
browser.test.assertDeepEq(
[],
changedOpenerTabIds,
"No tabs.onUpdated fired with openerTabId at tab creation"
);
// Not changed, should not emit event:
await browser.tabs.update(tab1.id, { openerTabId: -1 });
// Should emit event:
await browser.tabs.update(tab1.id, { openerTabId: tab2.id });
// Not changed, should not emit event:
await browser.tabs.update(tab1.id, { openerTabId: tab2.id });
// Should emit event:
await browser.tabs.update(tab1.id, { openerTabId: -1 });
await promise;
browser.test.assertDeepEq(
[tab2.id, -1],
changedOpenerTabIds,
"Got expected tabs.onUpdated for openerTabId changes"
);
await browser.tabs.remove(tab1.id);
await browser.tabs.remove(tab2.id);
browser.test.notifyPass("tab-onUpdated-opener");
} catch (e) {
browser.test.fail(`${e} :: ${e.stack}`);
browser.test.notifyFail("tab-onUpdated-opener");
}
},
});
await extension.startup();
await extension.awaitFinish("tab-onUpdated-opener");
await extension.unload();
});
add_task(async function test_errors_on_openerTabId_change() {
let extension = ExtensionTestUtils.loadExtension({
manifest: {
permissions: ["tabs"],
},
async background() {
let tab = await browser.tabs.create({});
await browser.test.assertRejects(
browser.tabs.update(tab.id, { openerTabId: 123456789 }),
"Invalid tab ID: 123456789",
"Got error when openerTabId is invalid"
);
let win = await browser.windows.create({ url: "about:blank" });
await browser.test.assertRejects(
browser.tabs.update(tab.id, { openerTabId: win.tabs[0].id }),
"Opener tab must be in the same window as the tab being updated",
"Got error when openerTabId belongs to a different window"
);
tab = await browser.tabs.get(tab.id);
browser.test.assertEq(
undefined,
tab.openerTabId,
"Got initial tab.openerTabId after failing updates"
);
await browser.windows.remove(win.id);
await browser.tabs.remove(tab.id);
browser.test.notifyPass("tab-opener-with-wrong-window");
},
});
await extension.startup();
await extension.awaitFinish("tab-opener-with-wrong-window");
await extension.unload();
});

Просмотреть файл

@ -2739,7 +2739,8 @@
}
let openerTab =
(openerBrowser && this.getTabForBrowser(openerBrowser)) ||
(relatedToCurrent && this.selectedTab);
(relatedToCurrent && this.selectedTab) ||
null;
// When overflowing, new tabs are scrolled into view smoothly, which
// doesn't go well together with the width transition. So we skip the