From 5720cafc9c1de045fa4f465ca30cd986263bdcd8 Mon Sep 17 00:00:00 2001 From: "YUKI \"Piro\" Hiroshi" Date: Thu, 28 Nov 2024 18:30:48 +0900 Subject: [PATCH] Implement simulated tab preview panel (#3671) * Prepare frame contents to render tab preview tooltip #3412 * Add comments to describe the design of the module * Implement events to track mouseenter/leave on tab substances * Send show/hide preview message to the active tab #3412 * Send message to show tab preview with preview URL * Show tab preview * Remove needless parameter * Simulate behavior of the native tab preview panel #3412 * Fix indent * Apply appearance same to the native tab preview panel as possible as we can #3412 * Cleanup old subframes #3412 * Don't try to capture discarded tabs #3412 * Cleanup cached tab object to avoid mismatched relation * Make more safe for invalid tab case * Show tab preview panel with fixed width * Use internal name same to Firefox's one * Suppress error about sending message to special tabs * Clear preview when active tab is switched * Re-prepare frame to show tab preview tooltip automatically * Hide tab preview panel when the sidebar is closed * Cleanup needless iframe * Hide tab preview tooltip when the tab is detached * Destroy and reconstruct frame more safely * Show tab preview panel with expected scale in zoomed tabs * Apply platform specific appearance correctly * Don't apply opacity animation while updating * Position tab preview in better coordinates even if the sidebar header is shown. * Fix mispositioning in zoomed contents * Ignore delayed messages * Suppress regular tooltip when in-content tab previw tooltip is not available * Calculate offset more correctly * Show tab preview panel near the sidebar * Deactivate tab preview tooltip by default * Show tab preview tooltip in the sidebar if the tab contents is privileged * Calculate size and position of the tab preview tooltip more correctly * Don't try to embed tab preview tooltip in other addon's contents * Show tab preview tooltip as soon as possible * Fallback to in-sidebar tab preview tooltip if the active tab is privileged * Add an option UI for tab preview panel --- webextensions/_locales/en/messages.json | 1 + webextensions/_locales/ja/messages.json | 1 + webextensions/background/api-tabs-listener.js | 8 + .../background/browser-action-menu.js | 7 + webextensions/common/common.js | 1 + webextensions/common/constants.js | 1 + webextensions/common/permissions.js | 4 +- webextensions/common/sidebar-connection.js | 29 +- webextensions/manifest.json | 1 + webextensions/options/init.js | 9 + webextensions/options/options.html | 15 +- .../resources/tab-preview-frame.html | 11 + webextensions/resources/tab-preview-frame.js | 294 ++++++++++++++ .../sidebar/components/TabElement.js | 47 ++- webextensions/sidebar/index.js | 1 + webextensions/sidebar/mouse-event-listener.js | 1 + webextensions/sidebar/sidebar.html | 1 + webextensions/sidebar/styles/base.css | 20 +- webextensions/sidebar/tab-preview-tooltip.js | 365 ++++++++++++++++++ 19 files changed, 807 insertions(+), 10 deletions(-) create mode 100644 webextensions/resources/tab-preview-frame.html create mode 100644 webextensions/resources/tab-preview-frame.js create mode 100644 webextensions/sidebar/tab-preview-tooltip.js diff --git a/webextensions/_locales/en/messages.json b/webextensions/_locales/en/messages.json index e7e6c8b82..7e9de3edf 100644 --- a/webextensions/_locales/en/messages.json +++ b/webextensions/_locales/en/messages.json @@ -409,6 +409,7 @@ "config_maxPinnedTabsRowsAreaPercentage_label_after": { "message": "% of the sidebar" }, "config_animation_label": { "message": "Enable animation effects" }, "config_animationForce_label": { "message": "Enable animations regardless \"reduce animations\" platform settings" }, + "config_tabPreviewTooltip_label": { "message": "Show tab preview image instead of tooltip" }, "config_showCollapsedDescendantsByTooltip_label": { "message": "Show collapsed descendants in the tooltip on a tab" }, "config_shiftTabsForScrollbarDistance_label_before": { "message": "Shift tabs aside " }, "config_shiftTabsForScrollbarDistance_label_after": { "message": " to keep in-tab buttons touchable avoiding covered with the auto-shown scrollbar" }, diff --git a/webextensions/_locales/ja/messages.json b/webextensions/_locales/ja/messages.json index f7e7661ea..0a67038fb 100644 --- a/webextensions/_locales/ja/messages.json +++ b/webextensions/_locales/ja/messages.json @@ -406,6 +406,7 @@ "config_maxPinnedTabsRowsAreaPercentage_label_after": { "message": "%まで" }, "config_animation_label": { "message": "アニメーション効果を有効にする" }, "config_animationForce_label": { "message": "アニメーションを抑制するプラットフォームの設定を無視して有効にする" }, + "config_tabPreviewTooltip_label": { "message": "ツールチップの代わりにタブのプレビュー画像を表示する" }, "config_showCollapsedDescendantsByTooltip_label": { "message": "タブのツールチップに折りたたまれた子孫タブの情報を含める" }, "config_shiftTabsForScrollbarDistance_label_before": { "message": "自動的に表示されるスクロールバーに覆われてタブ内のボタンに触れなくならないよう、" }, "config_shiftTabsForScrollbarDistance_label_after": { "message": "ぶんだけタブをずらして表示する" }, diff --git a/webextensions/background/api-tabs-listener.js b/webextensions/background/api-tabs-listener.js index b7a539d52..8094874b5 100644 --- a/webextensions/background/api-tabs-listener.js +++ b/webextensions/background/api-tabs-listener.js @@ -1220,6 +1220,14 @@ async function onDetached(tabId, detachInfo) { tabId, wasPinned: oldTab.pinned }); + // We need to notify this to some conetnt scripts, to destroy themselves. + try { + browser.tabs.sendMessage(tabId, { + type: Constants.kCOMMAND_NOTIFY_TAB_DETACHED_FROM_WINDOW, + }).catch(_error => {}); + } + catch (_error) { + } TabsStore.addRemovedTab(oldTab); oldWindow.detachTab(oldTab.id, { diff --git a/webextensions/background/browser-action-menu.js b/webextensions/background/browser-action-menu.js index a457410cc..2e7320b4a 100644 --- a/webextensions/background/browser-action-menu.js +++ b/webextensions/background/browser-action-menu.js @@ -125,6 +125,12 @@ const mItems = [ type: 'checkbox', expert: true }, + { + title: browser.i18n.getMessage('config_tabPreviewTooltip_label'), + key: 'tabPreviewTooltip', + type: 'checkbox', + permissions: Permissions.ALL_URLS + }, { title: browser.i18n.getMessage('config_showCollapsedDescendantsByTooltip_label'), key: 'showCollapsedDescendantsByTooltip', @@ -1372,6 +1378,7 @@ const mItems = [ }, { title: browser.i18n.getMessage('config_requestPermissions_allUrls_ctrlTabTracking'), + key: 'skipCollapsedTabsForTabSwitchingShortcuts', type: 'checkbox', permissions: Permissions.ALL_URLS }, diff --git a/webextensions/common/common.js b/webextensions/common/common.js index e3843788b..5def3aeef 100644 --- a/webextensions/common/common.js +++ b/webextensions/common/common.js @@ -171,6 +171,7 @@ export const configs = new Configs({ showNewTabActionSelector: true, longPressOnNewTabButton: Constants.kCONTEXTUAL_IDENTITY_SELECTOR, zoomable: false, + tabPreviewTooltip: false, showOverflowTitleByTooltip: true, showCollapsedDescendantsByTooltip: true, diff --git a/webextensions/common/constants.js b/webextensions/common/constants.js index a64c103b6..70091ea4b 100644 --- a/webextensions/common/constants.js +++ b/webextensions/common/constants.js @@ -34,6 +34,7 @@ export const kCOMMAND_CONFIRM_TO_CLOSE_TABS = 'treestyletab:confirm-to- export const kCOMMAND_SHOW_DIALOG = 'treestyletab:show-dialog'; export const kCOMMAND_NOTIFY_BACKGROUND_READY = 'treestyletab:notify-background-ready'; export const kCOMMAND_NOTIFY_CONNECTION_READY = 'treestyletab:notify-connection-ready'; +export const kCOMMAND_NOTIFY_SIDEBAR_CLOSED = 'treestyletab:notify-sidebar-closed'; export const kCOMMAND_NOTIFY_TAB_CREATING = 'treestyletab:notify-tab-creating'; export const kCOMMAND_NOTIFY_TAB_CREATED = 'treestyletab:notify-tab-created'; export const kCOMMAND_NOTIFY_TAB_UPDATED = 'treestyletab:notify-tab-updated'; diff --git a/webextensions/common/permissions.js b/webextensions/common/permissions.js index c7f2396e3..9fa853ad7 100644 --- a/webextensions/common/permissions.js +++ b/webextensions/common/permissions.js @@ -117,7 +117,9 @@ export function bindToCheckbox(permissions, checkbox, options = {}) { .then(granted => { const checked = options.onInitialized ? options.onInitialized(granted) : - undefined; + checkbox.dataset.relatedConfigKey ? + configs[checkbox.dataset.relatedConfigKey] : + undefined; checkbox.checked = checked !== undefined ? !!checked : granted; }) .catch(_error => { diff --git a/webextensions/common/sidebar-connection.js b/webextensions/common/sidebar-connection.js index cb5a05fc8..80b8adfa0 100644 --- a/webextensions/common/sidebar-connection.js +++ b/webextensions/common/sidebar-connection.js @@ -10,7 +10,8 @@ import EventListenerManager from '/extlib/EventListenerManager.js'; import { log as internalLogger, mapAndFilterUniq, - configs + configs, + wait, } from './common.js'; import * as Constants from './constants.js'; import * as TabsStore from './tabs-store.js'; @@ -223,6 +224,32 @@ if (Constants.IS_BACKGROUND) { mReceivers.delete(windowId); mFocusState.delete(windowId); onDisconnected.dispatch(windowId, connections.size); + + // We need to notify this to some conetnt scripts, to destroy themselves. + /* + browser.runtime.sendMessage({ + type: Constants.kCOMMAND_NOTIFY_SIDEBAR_CLOSED, + windowId, + }); + */ + browser.windows.get(windowId, { populate: true }).then(async win => { + let count = 0; + for (const tab of win.tabs) { + count++; + if (count >= 20) { + // We should not block too long seconds on too much tabs case. + await wait(10); + count = 0; + } + try { + browser.tabs.sendMessage(tab.id, { + type: Constants.kCOMMAND_NOTIFY_SIDEBAR_CLOSED, + }); + } + catch (_error) { + } + } + }); }; const receiver = message => { if (Array.isArray(message)) diff --git a/webextensions/manifest.json b/webextensions/manifest.json index 65d6fd85e..00579ead2 100644 --- a/webextensions/manifest.json +++ b/webextensions/manifest.json @@ -261,6 +261,7 @@ "browser_style": false }, "web_accessible_resources": [ + "/resources/blank.html", "/resources/group-tab.html*", "/resources/icons/*", "/sidebar/styles/icons/*" diff --git a/webextensions/options/init.js b/webextensions/options/init.js index b666661e1..facdb96f8 100644 --- a/webextensions/options/init.js +++ b/webextensions/options/init.js @@ -781,6 +781,15 @@ function initPermissionOptions() { Permissions.isGranted(Permissions.BOOKMARKS).then(granted => updateBookmarksUI(granted)); Permissions.isGranted(Permissions.ALL_URLS).then(granted => updateCtrlTabSubItems(granted)); + Permissions.bindToCheckbox( + Permissions.ALL_URLS, + document.querySelector('#allUrlsPermissionGranted_tabPreviewTooltip'), + { + onChanged: (granted) => { + configs.tabPreviewTooltip = granted; + } + } + ); Permissions.bindToCheckbox( Permissions.ALL_URLS, document.querySelector('#allUrlsPermissionGranted_ctrlTabTracking'), diff --git a/webextensions/options/options.html b/webextensions/options/options.html index 5867210e2..15cb17dd5 100644 --- a/webextensions/options/options.html +++ b/webextensions/options/options.html @@ -115,7 +115,14 @@

__MSG_config_appearance_caption__

size="5" min="0" max="100"> - __MSG_config_maxPinnedTabsRowsAreaPercentage_label_after__

+ __MSG_config_maxPinnedTabsRowsAreaPercentage_label_after__

[ + +

+

__MSG_config_newTab_caption__

diff --git a/webextensions/sidebar/styles/base.css b/webextensions/sidebar/styles/base.css index 2328e653a..37f76a003 100644 --- a/webextensions/sidebar/styles/base.css +++ b/webextensions/sidebar/styles/base.css @@ -73,7 +73,8 @@ --over-tabs-z-index: 200; /* buttons after tabs, pinned tabs (required to make pinned tabs clickable) */ --over-buttons-z-index: 300; /* buttons on buttons after tabs */ --notification-ui-z-index: 9000; - --blocking-ui-z-index: 10000; /* frontmost of others */ + --tooltip-ui-z-index: 10000; + --blocking-ui-z-index: 20000; /* frontmost of others */ --tab-background-z-index: 10; --tab-extra-contents-behind-z-index: 20; @@ -1283,6 +1284,23 @@ iframe#subpanel { } +/* tab preview tooltip */ +#tab-preview-tooltip-frame { + background: transparent; + border: 0 none; + bottom: 0; + height: 100%; + left: 0; + overflow: hidden; + pointer-events: none; + position: fixed; + right: 0; + top: 0; + width: 100%; + z-index: var(--tooltip-ui-z-index); +} + + /* blocking UI */ #blocking-screen { diff --git a/webextensions/sidebar/tab-preview-tooltip.js b/webextensions/sidebar/tab-preview-tooltip.js new file mode 100644 index 000000000..02cdde99d --- /dev/null +++ b/webextensions/sidebar/tab-preview-tooltip.js @@ -0,0 +1,365 @@ +/* +# 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'; + +// Overview of the tab preview tooltip: +// +// Tab preview tooltips are processed by the combination of this script +// and content scripts. Players are: +// +// * This script (CONTROLLER) +// * The content script of the active tab to load tab preview frames +// (LOADER): injected by prepareFrame() +// * The content script of the tab preview frame (FRAME): loaded as +// `/resources/tab-preview-frame.js` +// * The tab A: a tab to be shown in the preview tooltip. +// * The tab B: the active tab which is used to show the preview tooltip. +// +// When we need to show a tab preview: +// +// 1. The CONTROLLER detects `tab-item-substance-enter` (`mouseenter`) event +// on a tab substance. +// 2. The CONTROLLER sends a message to the LOADER of the active tab, +// like "do you know the 'frameId' in your paeg?" +// 1. If no response, the CONTROLLER loads a content script LOADER +// into the active tab. +// 1. The LOADER generates a transparent iframe with the URL of +// `/resources/tab-preview-frame.html`. +// 2. The FRAME is loaded and it sends a message to the CONTROLLER +// like "now I'm ready!" +// 3. The CONTROLLER receives the message and gets the `sender.frameId` +// information corresponding to the message. +// 4. The CONTROLLER sends the `frameId` information to the LOADER +// of the active tab, like "hey, your iframe is loaded with the +// frameId XXX!` +// 5. The LOADER of the active tab tracks the notified `frameId`. +// 2. The LOADER of the active tab responds to the CONTROLLER, like +// "OK, I'm ready and the frameId of my iframe is XXX!" +// 3. If these operation is not finished until some seconds, the +// CONTROLLER gives up to show the preview. +// 3. The CONTROLLER receives the "I'm ready" response with `frameId` from +// the LOADER of the active tab. +// 4. The CONTROLLER generates a thumbnail image for the tab A, and sends +// a message with `frameId` to the FRAME in the active tab, like "show +// a preview with a thumbnail image 'data:image/png,...' at the position +// (x,y)" +// 5. The FRAME with the specified `frameId` shows the preview. +// +// When we need to hide a tab preview: +// +// 1. The CONTROLLER detects `tab-item-substance-leave` (`mouseleave`) event +// on a tab substance. +// 2. The CONTROLLER sends a message to the LOADER of the active tab, like +// "do you know the 'frameId' in your paeg?" +// 1. If no response, the CONTROLLER gives up to hide the preview. +// We have nothing to do. +// 3. The CONTROLLER receives the "I'm ready" response with `frameId` from +// the LOADER of the active tab. +// 4. The CONTROLLER sends a message with `frameId` to the FRAME in the +// active tab, like "hide a preview" +// 5. The FRAME with the specified `frameId` hides the preview. +// +// I think the CONTROLLER should not track `frameId` for each tab. +// Contents of tabs are frequently destroyed, so `frameId` information +// stored (cached) by the CONTROLLER will become obsolete too easily. + +import { + configs, +} from '/common/common.js'; +import * as Constants from '/common/constants.js'; +import * as TabsStore from '/common/tabs-store.js'; +import Tab from '/common/Tab.js'; + +import * as EventUtils from './event-utils.js'; +import * as Sidebar from './sidebar.js'; + +import { kEVENT_TAB_SUBSTANCE_ENTER, kEVENT_TAB_SUBSTANCE_LEAVE } from './components/TabElement.js'; + +const TAB_PREVIEW_FRAME_STYLE = ` + background: transparent; + border: 0 none; + bottom: 0; + height: 100%; + left: 0; + overflow: hidden; + /*pointer-events: none;*//* We should not keep iframe element there with unclickable state, instead we remove it on hover for safety. */ + position: fixed; + right: 0; + top: 0; + width: 100%; + z-index: 65000; +`; + +const CUSTOM_PANEL_AVAILABLE_URLS_MATCHER = new RegExp(`^((https?|data):|moz-extension://${location.host}\/)`); +const CAPTURABLE_URLS_MATCHER = /^(https?|data):/; +const PREVIEW_WITH_HOST_URLS_MATCHER = /^(https?|moz-extension):/; +const PREVIEW_WITH_TITLE_URLS_MATCHER = /^file:/; + +document.addEventListener(kEVENT_TAB_SUBSTANCE_ENTER, onTabSubstanceEnter); +document.addEventListener(kEVENT_TAB_SUBSTANCE_LEAVE, onTabSubstanceLeave); + +async function prepareFrame(tabId) { + await browser.tabs.executeScript(tabId, { + matchAboutBlank: true, + runAt: 'document_start', + code: `(() => { + const url = '${browser.runtime.getURL('/resources/tab-preview-frame.html')}'; + + // cleanup! + const oldFrames = document.querySelectorAll('iframe[src="' + url + '"]'); + for (const oldFrame of oldFrames) { + oldFrame.parentNode.removeChild(oldFrame); + } + + const frame = document.createElement('iframe'); + frame.setAttribute('src', url); + frame.setAttribute('style', ${JSON.stringify(TAB_PREVIEW_FRAME_STYLE)}); + document.documentElement.appendChild(frame); + + let lastFrameId; + let windowId; + + const onMessage = (message, _sender) => { + switch (message?.type) { + case 'treestyletab:ask-tab-preview-frame-id': + if (lastFrameId) + return Promise.resolve(lastFrameId); + break; + + case 'treestyletab:notify-tab-preview-owner-info': + lastFrameId = message.frameId; + windowId = message.windowId; + //frame.dataset.frameId = message.frameId; // Just for debugging. Do not expose this on released version! + break; + + case '${Constants.kCOMMAND_NOTIFY_TAB_DETACHED_FROM_WINDOW}': + destroy(); + break; + } + }; + browser.runtime.onMessage.addListener(onMessage); + + const destroy = () => { + lastFrameId = null; + windowId = null; + frame.parentNode.removeChild(frame); + browser.runtime.onMessage.removeListener(onMessage); + }; + frame.addEventListener('mouseenter', destroy, { once: true }); + })()`, + }); +} + +async function sendTabPreviewMessage(tabId, message, deferredReturnedValueResolver) { + if (!tabId) + return browser.runtime.sendMessage({ + ...message, + timestamp: Date.now(), + windowId: TabsStore.getCurrentWindowId(), + }); + + const retrying = !!deferredReturnedValueResolver; + + let frameId; + try { + frameId = await browser.tabs.sendMessage(tabId, { + type: 'treestyletab:ask-tab-preview-frame-id', + }).catch(_error => {}); + if (!frameId) { + if (retrying) { + deferredReturnedValueResolver(false); + return false; + } + + await prepareFrame(tabId); + let returnedValueResolver; + const promisedReturnedValue = new Promise((resolve, _reject) => { + returnedValueResolver = resolve; + }); + setTimeout(() => { + sendTabPreviewMessage(tabId, message, returnedValueResolver); + }, 100); + return promisedReturnedValue; + } + } + catch (_error) { + // We cannot show tab preview tooltip in a tab with privileged contents. + // Let's fall back to the in-sidebar tab preview tooltip. + browser.runtime.sendMessage({ + ...message, + timestamp: Date.now(), + windowId: TabsStore.getCurrentWindowId(), + }); + //console.log('Could not send tab preview message: ', tabId, message, error); + if (deferredReturnedValueResolver) + deferredReturnedValueResolver(true); + return true; + } + + let returnValue; + try { + //console.log('Sending message to the frame ', frameId); + returnValue = await browser.tabs.sendMessage(tabId, { + ...message, + timestamp: Date.now(), + }, { frameId }); + if (deferredReturnedValueResolver) + deferredReturnedValueResolver(returnValue); + } + catch (error) { + if (retrying) { + console.log(`Could not send tab preview message to the frame ${frameId}: `, tabId, message, error); + deferredReturnedValueResolver(false); + return false; + } + //console.log('Failed to send message to the frame ', frameId, ' : retry'); + + // the frame was destroyed unexpectedly, so we re-prepare it. + await prepareFrame(tabId); + let returnedValueResolver; + const promisedReturnedValue = new Promise((resolve, _reject) => { + returnedValueResolver = resolve; + }); + setTimeout(() => { + sendTabPreviewMessage(tabId, message, returnedValueResolver); + }, 100); + return promisedReturnedValue; + } + + return returnValue; +} + + +async function onTabSubstanceEnter(event) { + const activeTab = Tab.getActiveTab(TabsStore.getCurrentWindowId()); + if (!configs.tabPreviewTooltip) {; + sendTabPreviewMessage(activeTab.id, { + type: 'treestyletab:hide-tab-preview', + }); + return; + } + + if (!event.target.tab) + return; + + const targetTabId = CUSTOM_PANEL_AVAILABLE_URLS_MATCHER.test(activeTab.url) ? + activeTab.id : + null; + + const tabRect = event.target.tab.$TST.element?.getBoundingClientRect(); + const active = event.target.tab.id == activeTab.id; + const url = PREVIEW_WITH_HOST_URLS_MATCHER.test(event.target.tab.url) ? new URL(event.target.tab.url).host : + PREVIEW_WITH_TITLE_URLS_MATCHER.test(event.target.tab.url) ? null : + event.target.tab.url; + + let previewURL; + try { + if (!active && + !event.target.tab.discarded && + CAPTURABLE_URLS_MATCHER.test(event.target.tab.url)) { + previewURL = await browser.tabs.captureTab(event.target.tab.id); + } + } + catch (_error) { + } + + // This calculation logic is buggy for a window in a screen placed at + // left of the primary display and scaled. As the result, a sidebar + // placed at left can be mis-detected as placed at right. For safety + // I ignore such cases and always treat such cases as "left side placed". + // See also: https://github.com/piroor/treestyletab/issues/2984#issuecomment-901907503 + const mayBeRight = window.screenX < 0 && window.devicePixelRatio > 1 ? + false : + window.mozInnerScreenX - window.screenX > (window.outerWidth - window.innerWidth) / 2; + + //console.log(event.type, event, event.target.tab, event.target, activeTab); + const succeeded = await sendTabPreviewMessage(targetTabId, { + type: 'treestyletab:show-tab-preview', + tabId: event.target.tab.id, + tabRect: { + bottom: tabRect?.bottom || 0, + height: tabRect?.height || 0, + left: tabRect?.left || 0, + right: tabRect?.right || 0, + top: tabRect?.top || 0, + width: tabRect?.width || 0, + }, + /* These information is used to calculate offset of the sidebar header */ + offsetTop: window.mozInnerScreenY - window.screenY, + offsetLeft: window.mozInnerScreenX - window.screenX, + align: mayBeRight ? 'right' : 'left', + active, + title: event.target.tab.title, + url, + previewURL, + }).catch(_error => {}); + //console.log('tab preview for ', event.target.tab?.id, ' : success? : ', success); + if (event.target.tab.$TST.element && + succeeded) + event.target.tab.$TST.element.invalidateTooltip(); +} +onTabSubstanceEnter = EventUtils.wrapWithErrorHandler(onTabSubstanceEnter); + +function onTabSubstanceLeave(event) { + if (!event.target.tab) + return; + + const activeTab = Tab.getActiveTab(TabsStore.getCurrentWindowId()); + const targetTabId = CUSTOM_PANEL_AVAILABLE_URLS_MATCHER.test(activeTab.url) ? + activeTab.id : + null; + + //console.log(event.type, event.target.tab, event.target, activeTab); + sendTabPreviewMessage(targetTabId, { + type: 'treestyletab:hide-tab-preview', + tabId: event.target.tab.id, + }); +} +onTabSubstanceLeave = EventUtils.wrapWithErrorHandler(onTabSubstanceLeave); + + +browser.tabs.onActivated.addListener(activeInfo => { + if (activeInfo.windowId != TabsStore.getCurrentWindowId()) + return; + + sendTabPreviewMessage(null, { + type: 'treestyletab:hide-tab-preview', + }); + sendTabPreviewMessage(activeInfo.tabId, { + type: 'treestyletab:hide-tab-preview', + }); + sendTabPreviewMessage(activeInfo.previousTabId, { + type: 'treestyletab:hide-tab-preview', + }); +}); + + +browser.runtime.onMessage.addListener((message, sender) => { + if (message?.type != 'treestyletab:tab-preview-frame-loaded') + return; + + // in-sidebar preview + if (sender.envType == 'addon_child') { + return; + } + + // in-tab previews + const windowId = TabsStore.getCurrentWindowId(); + if (windowId && + sender.tab?.windowId == windowId) { + browser.tabs.sendMessage(sender.tab.id, { + type: 'treestyletab:notify-tab-preview-owner-info', + frameId: sender.frameId, + windowId, + }); + return; + } +}); + +Sidebar.onReady.addListener(() => { + const windowId = TabsStore.getCurrentWindowId(); + document.querySelector('#tab-preview-tooltip-frame').src = `/resources/tab-preview-frame.html?windowId=${windowId}`; +});