Files
tubestation/waterfox/browser/components/sidebar/background/context-menu.js
2025-11-06 14:13:52 +00:00

765 lines
24 KiB
JavaScript

/*
# 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/.
*/
'use strict';
import {
log as internalLogger,
configs,
} from '/common/common.js';
import * as ApiTabs from '/common/api-tabs.js';
import * as Bookmark from '/common/bookmark.js';
import * as Sync from '/common/sync.js';
import * as TSTAPI from '/common/tst-api.js';
import { Tab } from '/common/TreeItem.js';
import * as Commands from './commands.js';
import * as TabContextMenu from './tab-context-menu.js';
function log(...args) {
internalLogger('background/context-menu', ...args);
}
export async function init() {
return Promise.all([
addTabItems(),
addBookmarkItems(),
]);
}
const SAFE_CREATE_PROPERTIES = [
'checked',
'contexts',
'documentUrlPatterns',
'enabled',
'icons',
'id',
'parentId',
'title',
'type',
'viewTypes',
'visible'
];
function getSafeCreateParams(params) {
const safeParams = {};
for (const property of SAFE_CREATE_PROPERTIES) {
if (property in params)
safeParams[property] = params[property];
}
return safeParams;
}
const manifest = browser.runtime.getManifest();
const kROOT_TAB_ITEM = 'ws';
const kROOT_BOOKMARK_ITEM = 'ws-bookmark';
const mTabItemsById = {
'reloadTree': {
title: browser.i18n.getMessage('context_reloadTree_label'),
titleMultiselected: browser.i18n.getMessage('context_reloadTree_label_multiselected')
},
'reloadDescendants': {
title: browser.i18n.getMessage('context_reloadDescendants_label'),
titleMultiselected: browser.i18n.getMessage('context_reloadDescendants_label_multiselected')
},
// This item won't be handled by the onClicked handler, so you may need to handle it with something experiments API.
'unblockAutoplayTree': {
title: browser.i18n.getMessage('context_unblockAutoplayTree_label'),
titleMultiselected: browser.i18n.getMessage('context_unblockAutoplayTree_label_multiselected'),
requireAutoplayBlockedTab: true,
},
// This item won't be handled by the onClicked handler, so you may need to handle it with something experiments API.
'unblockAutoplayDescendants': {
title: browser.i18n.getMessage('context_unblockAutoplayDescendants_label'),
titleMultiselected: browser.i18n.getMessage('context_unblockAutoplayDescendants_label_multiselected'),
requireAutoplayBlockedDescendant: true,
},
'toggleMuteTree': {
titleMuteTree: browser.i18n.getMessage('context_toggleMuteTree_label_mute'),
titleMultiselectedMuteTree: browser.i18n.getMessage('context_toggleMuteTree_label_multiselected_mute'),
titleUnmuteTree: browser.i18n.getMessage('context_toggleMuteTree_label_unmute'),
titleMultiselectedUnmuteTree: browser.i18n.getMessage('context_toggleMuteTree_label_multiselected_unmute')
},
'toggleMuteDescendants': {
titleMuteDescendant: browser.i18n.getMessage('context_toggleMuteDescendants_label_mute'),
titleMultiselectedMuteDescendant: browser.i18n.getMessage('context_toggleMuteDescendants_label_multiselected_mute'),
titleUnmuteDescendant: browser.i18n.getMessage('context_toggleMuteDescendants_label_unmute'),
titleMultiselectedUnmuteDescendant: browser.i18n.getMessage('context_toggleMuteDescendants_label_multiselected_unmute')
},
'separatorAfterReload': {
type: 'separator'
},
'closeTree': {
title: browser.i18n.getMessage('context_closeTree_label'),
titleMultiselected: browser.i18n.getMessage('context_closeTree_label_multiselected')
},
'closeDescendants': {
title: browser.i18n.getMessage('context_closeDescendants_label'),
titleMultiselected: browser.i18n.getMessage('context_closeDescendants_label_multiselected'),
requireTree: true,
},
'closeOthers': {
title: browser.i18n.getMessage('context_closeOthers_label'),
titleMultiselected: browser.i18n.getMessage('context_closeOthers_label_multiselected')
},
'separatorAfterClose': {
type: 'separator'
},
'toggleSticky': {
titleStick: browser.i18n.getMessage('context_toggleSticky_label_stick'),
titleMultiselectedStick: browser.i18n.getMessage('context_toggleSticky_label_multiselected_stick'),
titleUnstick: browser.i18n.getMessage('context_toggleSticky_label_unstick'),
titleMultiselectedUnstick: browser.i18n.getMessage('context_toggleSticky_label_multiselected_unstick'),
requireNormal: true,
},
'separatorAfterToggleSticky': {
type: 'separator'
},
'collapseTree': {
title: browser.i18n.getMessage('context_collapseTree_label'),
titleMultiselected: browser.i18n.getMessage('context_collapseTree_label_multiselected'),
requireTree: true,
},
'collapseTreeRecursively': {
title: browser.i18n.getMessage('context_collapseTreeRecursively_label'),
titleMultiselected: browser.i18n.getMessage('context_collapseTreeRecursively_label_multiselected'),
requireTree: true,
},
'collapseAll': {
title: browser.i18n.getMessage('context_collapseAll_label'),
hideOnMultiselected: true
},
'expandTree': {
title: browser.i18n.getMessage('context_expandTree_label'),
titleMultiselected: browser.i18n.getMessage('context_expandTree_label_multiselected'),
requireTree: true,
},
'expandTreeRecursively': {
title: browser.i18n.getMessage('context_expandTreeRecursively_label'),
titleMultiselected: browser.i18n.getMessage('context_expandTreeRecursively_label_multiselected'),
requireTree: true,
},
'expandAll': {
title: browser.i18n.getMessage('context_expandAll_label'),
hideOnMultiselected: true
},
'separatorAfterCollapseExpand': {
type: 'separator'
},
'bookmarkTree': {
title: browser.i18n.getMessage('context_bookmarkTree_label'),
titleMultiselected: browser.i18n.getMessage('context_bookmarkTree_label_multiselected')
},
'sendTreeToDevice': {
title: browser.i18n.getMessage('context_sendTreeToDevice_label'),
titleMultiselected: browser.i18n.getMessage('context_sendTreeToDevice_label_multiselected')
},
'separatorAfterBookmark': {
type: 'separator'
},
'collapsed': {
title: browser.i18n.getMessage('context_collapsed_label'),
requireTree: true,
type: 'checkbox'
},
'pinnedTab': {
title: browser.i18n.getMessage('context_pinnedTab_label'),
type: 'radio'
},
'unpinnedTab': {
title: browser.i18n.getMessage('context_unpinnedTab_label'),
type: 'radio'
}
};
const mTabItems = [];
const mGroupedTabItems = [];
const mGroupedTabItemsById = {};
for (const id of Object.keys(mTabItemsById)) {
const item = mTabItemsById[id];
item.id = id;
item.configKey = `context_${id}`;
item.checked = false; // initialize as unchecked
item.enabled = true;
// Access key is not supported by WE API.
// See also: https://bugzilla.mozilla.org/show_bug.cgi?id=1320462
item.titleWithoutAccesskey = item.title?.replace(/\(&[a-z]\)|&([a-z])/i, '$1');
item.titleMultiselectedWithoutAccesskey = item.titleMultiselected?.replace(/\(&[a-z]\)|&([a-z])/i, '$1');
item.type = item.type || 'normal';
item.contexts = ['tab'];
item.lastVisible = item.visible = false;
item.lastTitle = item.title;
mTabItems.push(item);
const groupedItem = {
...item,
id: `grouped:${id}`,
parentId: kROOT_TAB_ITEM
};
mGroupedTabItems.push(groupedItem);
mGroupedTabItemsById[groupedItem.id] = groupedItem;
}
const mTabSeparator = {
id: `separatprBefore${kROOT_TAB_ITEM}`,
type: 'separator',
contexts: ['tab'],
viewTypes: ['sidebar'],
documentUrlPatterns: [`moz-extension://${location.host}/*`],
visible: false,
lastVisible: false
};
const mTabRootItem = {
id: kROOT_TAB_ITEM,
type: 'normal',
contexts: ['tab'],
title: browser.i18n.getMessage('context_menu_label'),
icons: manifest.icons,
visible: false,
lastVisible: false
};
const mAllTabItems = [
mTabSeparator,
mTabRootItem,
...mTabItems,
...mGroupedTabItems
];
function addTabItems() {
const promises = [];
if (addTabItems.done) {
for (const item of mAllTabItems) {
promises.push(browser.menus.remove(item.id));
}
}
for (const item of mAllTabItems) {
const params = getSafeCreateParams(item);
promises.push(browser.menus.create(params));
if (item.id == mTabSeparator.id ||
addTabItems.done)
continue;
promises.push(TabContextMenu.onMessageExternal({
type: TSTAPI.kCONTEXT_MENU_CREATE,
params
}, browser.runtime));
}
addTabItems.done = true;
return Promise.all(promises);
}
addTabItems.done = false;
const mBookmarkItemsById = {
openAllBookmarksWithStructure: {
title: browser.i18n.getMessage('context_openAllBookmarksWithStructure_label')
},
openAllBookmarksWithStructureRecursively: {
title: browser.i18n.getMessage('context_openAllBookmarksWithStructureRecursively_label')
}
};
const mBookmarkItems = [];
const mGroupedBookmarkItems = []
for (const id of Object.keys(mBookmarkItemsById)) {
const item = mBookmarkItemsById[id];
item.id = id;
item.contexts = ['bookmark'];
item.configKey = `context_${id}`;
const groupedItem = {
...item,
id: `grouped:${id}`,
parentId: kROOT_BOOKMARK_ITEM,
ungroupedItem: item
};
item.icons = manifest.icons;
mBookmarkItems.push(item);
mBookmarkItemsById[groupedItem.id] = groupedItem;
mGroupedBookmarkItems.push(groupedItem);
}
const mBookmarkSeparator = {
id: `separatprBefore${kROOT_BOOKMARK_ITEM}`,
type: 'separator',
contexts: ['bookmark'],
viewTypes: ['sidebar'],
documentUrlPatterns: [`moz-extension://${location.host}/*`],
visible: false,
lastVisible: false
};
const mBookmarkRootItem = {
id: kROOT_BOOKMARK_ITEM,
type: 'normal',
contexts: ['bookmark'],
title: manifest.name,
icons: manifest.icons,
visible: false,
lastVisible: false
};
const mAllBookmarkItems = [
mBookmarkSeparator,
mBookmarkRootItem,
...mBookmarkItems,
...mGroupedBookmarkItems
];
function addBookmarkItems() {
const promises = [];
if (addBookmarkItems.done) {
for (const item of mAllBookmarkItems) {
promises.push(browser.menus.remove(item.id));
}
}
for (const item of mAllBookmarkItems) {
promises.push(browser.menus.create(getSafeCreateParams(item)));
}
addBookmarkItems.done = true;
return Promise.all(promises);
}
addBookmarkItems.done = false;
// Re-register items to put them after
// top level items added by other addons.
TabContextMenu.onTopLevelItemAdded.addListener(reserveToRefreshItems);
function reserveToRefreshItems() {
if (reserveToRefreshItems.invoked)
return;
reserveToRefreshItems.invoked = true;
setTimeout(() => { // because window.requestAnimationFrame is decelerate for an invisible document.
reserveToRefreshItems.invoked = false;
addTabItems();
addBookmarkItems();
}, 0);
}
function updateItem(id, params) {
log('updateItem ', id, params);
browser.menus.update(id, params).catch(ApiTabs.createErrorSuppressor());
TabContextMenu.onMessageExternal({
type: TSTAPI.kCONTEXT_MENU_UPDATE,
params: [id, params]
}, browser.runtime);
}
function updateItemsVisibility(items, { forceVisible = null, multiselected = false, hasUnmutedTab = false, hasUnmutedDescendant = false, hasAutoplayBlockedTab = false, hasAutoplayBlockedDescendant = false, sticky = false, hidden = false } = {}) {
log('updateItemsVisibility ', items, { forceVisible, multiselected, hasUnmutedTab, hasUnmutedDescendant, hasAutoplayBlockedTab, hasAutoplayBlockedDescendant, sticky });
let updated = false;
let visibleItemsCount = 0;
let visibleNormalItemsCount = 0;
let lastSeparator;
for (const item of items) {
if (item.type == 'separator') {
if (lastSeparator) {
if (lastSeparator.lastVisible) {
updateItem(lastSeparator.id, { visible: false });
lastSeparator.lastVisible = false;
updated = true;
}
}
lastSeparator = item;
}
else {
const title = Commands.getMenuItemTitle(item, { multiselected, hasUnmutedTab, hasUnmutedDescendant, sticky });
let visible = !hidden && (!(item.configKey in configs) || configs[item.configKey] || false);
log('checking ', item.id, {
config: visible,
multiselected: item.hideOnMultiselected && multiselected,
lastVisible: item.lastVisible,
forceVisible,
});
if (forceVisible !== null && forceVisible !== undefined)
visible = forceVisible;
if ((item.hideOnMultiselected && multiselected) ||
(item.requireAutoplayBlockedTab && !hasAutoplayBlockedTab) ||
(item.requireAutoplayBlockedDescendant && !hasAutoplayBlockedDescendant))
visible = false;
if (visible) {
if (lastSeparator) {
updateItem(lastSeparator.id, { visible: visibleNormalItemsCount > 0 });
lastSeparator.lastVisible = true;
lastSeparator = null;
updated = true;
visibleNormalItemsCount = 0;
}
visibleNormalItemsCount++;
visibleItemsCount++;
}
const updatedParams = {};
if (visible !== item.lastVisible) {
updatedParams.visible = visible;
item.lastVisible = visible;
}
if (title !== item.lastTitle) {
updatedParams.title = title;
item.lastTitle = title;
}
if (Object.keys(updatedParams).length == 0)
continue;
updateItem(item.id, updatedParams);
updated = true;
}
}
if (lastSeparator?.lastVisible) {
updateItem(lastSeparator.id, { visible: false });
lastSeparator.lastVisible = false;
updated = true;
}
return { updated, visibleItemsCount };
}
async function updateItems({ multiselected, hasUnmutedTab, hasUnmutedDescendant, hasAutoplayBlockedTab, hasAutoplayBlockedDescendant, sticky, hidden } = {}) {
let updated = false;
const groupedItems = updateItemsVisibility(mGroupedTabItems, { multiselected, hasUnmutedTab, hasUnmutedDescendant, hasAutoplayBlockedTab, hasAutoplayBlockedDescendant, sticky, hidden });
if (groupedItems.updated)
updated = true;
const separatorVisible = !hidden && configs.emulateDefaultContextMenu && groupedItems.visibleItemsCount > 0;
if (separatorVisible != mTabSeparator.lastVisible) {
updateItem(mTabSeparator.id, { visible: separatorVisible });
mTabSeparator.lastVisible = separatorVisible;
updated = true;
}
const grouped = !hidden && configs.emulateDefaultContextMenu && groupedItems.visibleItemsCount > 1;
if (grouped != mTabRootItem.lastVisible) {
updateItem(mTabRootItem.id, { visible: grouped });
mTabRootItem.lastVisible = grouped;
updated = true;
}
const topLevelItems = updateItemsVisibility(mTabItems, { forceVisible: grouped ? false : null, multiselected, hasUnmutedTab, hasUnmutedDescendant, hasAutoplayBlockedTab, hasAutoplayBlockedDescendant, sticky, hidden });
if (topLevelItems.updated)
updated = true;
if (mGroupedTabItemsById['grouped:sendTreeToDevice'].lastVisible &&
await TabContextMenu.updateSendToDeviceItems('grouped:sendTreeToDevice', {
manage: navigator.userAgent.includes('Fennec'), // see also https://github.com/piroor/treestyletab/issues/3174
}))
updated = true;
return updated;
}
export function onClick(info, tab) {
if (info.bookmarkId)
return onBookmarkItemClick(info);
else
return onTabItemClick(info, tab);
}
browser.menus.onClicked.addListener(onClick);
function onTabItemClick(info, tab) {
// Extra context menu commands won't be available on the blank area of the tab bar.
if (!tab)
return;
log('context menu item clicked: ', info, tab);
const contextTab = Tab.get(tab.id);
const contextTabs = contextTab.$TST.multiselected ? Tab.getSelectedTabs(contextTab.windowId) : [contextTab];
const itemId = info.menuItemId.replace(/^(?:grouped:|context_topLevel_)/, '');
if (mTabItemsById[itemId] &&
mTabItemsById[itemId].type == 'checkbox')
mTabItemsById[itemId].checked = !mTabItemsById[itemId].checked;
const inverted = info.button == 1;
switch (itemId) {
case 'reloadTree':
if (inverted)
Commands.reloadDescendants(contextTabs);
else
Commands.reloadTree(contextTabs);
break;
case 'reloadDescendants':
if (inverted)
Commands.reloadTree(contextTabs);
else
Commands.reloadDescendants(contextTabs);
break;
case 'toggleMuteTree':
if (inverted)
Commands.toggleMuteDescendants(contextTabs);
else
Commands.toggleMuteTree(contextTabs);
break;
case 'toggleMuteDescendants':
if (inverted)
Commands.toggleMuteTree(contextTabs);
else
Commands.toggleMuteDescendants(contextTabs);
break;
case 'closeTree':
if (inverted)
Commands.closeDescendants(contextTabs);
else
Commands.closeTree(contextTabs);
break;
case 'closeDescendants':
if (inverted)
Commands.closeTree(contextTabs);
else
Commands.closeDescendants(contextTabs);
break;
case 'closeOthers':
Commands.closeOthers(contextTabs);
break;
case 'toggleSticky':
Commands.toggleSticky(contextTabs, !contextTab.$TST.sticky);
break;
case 'collapseTree':
Commands.collapseTree(contextTabs, { recursively: inverted });
break;
case 'collapseTreeRecursively':
Commands.collapseTree(contextTabs, { recursively: !inverted });
break;
case 'collapseAll':
Commands.collapseAll(contextTab.windowId);
break;
case 'expandTree':
Commands.expandTree(contextTabs, { recursively: inverted });
break;
case 'expandTreeRecursively':
Commands.expandTree(contextTabs, { recursively: !inverted });
break;
case 'expandAll':
Commands.expandAll(contextTab.windowId);
break;
case 'bookmarkTree':
Commands.bookmarkTree(contextTabs);
break;
case 'sendTreeToDevice:all':
Sync.sendTabsToAllDevices(contextTabs, { recursively: true });
break;
case 'collapsed':
if (info.wasChecked)
Commands.expandTree(contextTab);
else
Commands.collapseTree(contextTab);
break;
case 'pinnedTab': {
const tabs = Tab.getPinnedTabs(contextTab.windowId);
if (tabs.length > 0)
browser.tabs.update(tabs[0].id, { active: true })
.catch(ApiTabs.createErrorHandler(ApiTabs.handleMissingTabError));
}; break;
case 'unpinnedTab': {
const tabs = Tab.getUnpinnedTabs(tab.windowId);
if (tabs.length > 0)
browser.tabs.update(tabs[0].id, { active: true })
.catch(ApiTabs.createErrorHandler(ApiTabs.handleMissingTabError));
}; break;
default: {
const sendToDeviceMatch = info.menuItemId.match(/^sendTreeToDevice:device:(.+)$/);
if (contextTab &&
sendToDeviceMatch)
Sync.sendTabsToDevice(
contextTabs,
{ to: sendToDeviceMatch[1],
recursively: true }
);
}; break;
}
}
TabContextMenu.onTSTItemClick.addListener(onTabItemClick);
async function onBookmarkItemClick(info) {
switch (info.menuItemId.replace(/^grouped:/, '')) {
case 'openAllBookmarksWithStructure':
Commands.openAllBookmarksWithStructure(info.bookmarkId, { recursively: false });
break;
case 'openAllBookmarksWithStructureRecursively':
Commands.openAllBookmarksWithStructure(info.bookmarkId, { recursively: true });
break;
}
}
async function onShown(info, tab) {
if (info.contexts.includes('tab'))
await onTabContextMenuShown(info, tab);
else if (info.contexts.includes('bookmark'))
onBookmarkContextMenuShown(info);
}
browser.menus.onShown.addListener(onShown);
let mLastContextTabId = null;
async function onTabContextMenuShown(info, tab) {
const contextTabId = tab?.id;
mLastContextTabId = contextTabId;
tab = tab && Tab.get(contextTabId);
const multiselected = tab?.$TST.multiselected;
const contextTabs = multiselected ? Tab.getSelectedTabs(tab.windowId) : tab ? [tab] : [];
const hasChild = contextTabs.length > 0 && contextTabs.some(tab => tab.$TST.hasChild);
const subtreeCollapsed = contextTabs.length > 0 && contextTabs.some(tab => tab.$TST.subtreeCollapsed);
const grouped = contextTabs.length > 0 && contextTabs.some(tab => tab.$TST.isGroupTab);
const { hasUnmutedTab, hasUnmutedDescendant } = Commands.getUnmutedState(contextTabs);
const { hasAutoplayBlockedTab, hasAutoplayBlockedDescendant } = Commands.getAutoplayBlockedState(contextTabs);
let updated = await updateItems({
multiselected,
hasUnmutedTab,
hasUnmutedDescendant,
hasAutoplayBlockedTab,
hasAutoplayBlockedDescendant,
sticky: tab?.$TST.sticky,
hidden: !configs.showTreeCommandsInTabsContextMenuGlobally && info.viewType != 'sidebar',
});
if (mLastContextTabId != contextTabId)
return; // Skip further operations if the menu was already reopened on a different context tab.
for (const item of mTabItems) {
let newVisible;
let newEnabled;
if (item.id == 'sendTreeToDevice' &&
item.visible) {
newVisible = contextTabs.filter(Sync.isSendableTab).length > 0;
newEnabled = (
hasChild &&
Sync.getOtherDevices().length > 0
);
}
else if (item.requireTree) {
newEnabled = hasChild;
switch (item.id) {
case 'collapseTree':
if (subtreeCollapsed)
newEnabled = false;
break;
case 'expandTree':
if (!subtreeCollapsed)
newEnabled = false;
break;
}
}
else if (item.requireMultiselected) {
newEnabled = multiselected;
}
else if (item.requireGrouped) {
newEnabled = grouped;
}
else if (item.requireNormal) {
newEnabled = tab?.pinned;
}
else {
continue;
}
if ((newVisible === undefined ||
newVisible == !!item.visible) &&
(newEnabled === undefined ||
newEnabled == !!item.enabled))
continue;
const params = {};
if (newVisible !== undefined &&
newVisible != !!item.visible)
params.visible = item.visible = !!newVisible;
if (newEnabled !== undefined &&
newEnabled != !!item.enabled)
params.enabled = item.enabled = !!newEnabled;
updateItem(item.id, params);
updateItem(`grouped:${item.id}`, params);
updated = true;
}
{
const canExpand = hasChild && subtreeCollapsed;
mTabItemsById.collapsed.checked = canExpand;
const params = {
checked: canExpand
};
updateItem('collapsed', params);
updateItem(`grouped:collapsed`, params);
updated = true;
}
if (updated)
browser.menus.refresh().catch(ApiTabs.createErrorSuppressor());
}
TabContextMenu.onTSTTabContextMenuShown.addListener(onTabContextMenuShown);
let mLastContextItemId = null;
async function onBookmarkContextMenuShown(info) {
const contextItemId = info.bookmarkId;
mLastContextItemId = contextItemId;
let isFolder = true;
if (info.bookmarkId) {
const item = await Bookmark.getItemById(info.bookmarkId);
if (mLastContextItemId != contextItemId)
return; // Skip further operations if the menu was already reopened on a different context item.
isFolder = (
item.type == 'folder' ||
(item.type == 'bookmark' &&
/^place:parent=([^&]+)$/.test(item.url))
);
}
let visibleItemCount = 0;
mBookmarkItemsById.openAllBookmarksWithStructure.visible = !!(
isFolder &&
configs[mBookmarkItemsById.openAllBookmarksWithStructure.configKey] &&
++visibleItemCount
);
mBookmarkItemsById.openAllBookmarksWithStructureRecursively.visible = !!(
isFolder &&
configs[mBookmarkItemsById.openAllBookmarksWithStructureRecursively.configKey] &&
++visibleItemCount
);
for (const item of mGroupedBookmarkItems) {
item.visible = !!(
visibleItemCount > 1 &&
item.ungroupedItem.visible
);
if (item.visible)
item.ungroupedItem.visible = false;
}
for (const item of [...mBookmarkItems, ...mGroupedBookmarkItems]) {
browser.menus.update(item.id, {
visible: !!item.visible,
});
}
browser.menus.update(mBookmarkSeparator.id, {
visible: visibleItemCount > 0
});
browser.menus.update(mBookmarkRootItem.id, {
visible: visibleItemCount > 1
});
browser.menus.refresh().catch(ApiTabs.createErrorSuppressor());
}
export function getItemIdsWithIcon() {
return [
kROOT_TAB_ITEM,
kROOT_BOOKMARK_ITEM,
...Object.keys(mBookmarkItemsById),
];
}