fix(extension): forwarder les messages content→sidepanel via le background
Bug réel : chrome.runtime.sendMessage depuis un content script ne va QU'au background service worker en MV3. Le side panel qui écoute via chrome.runtime.onMessage ne reçoit jamais rien directement. Résultat : la sélection, le titre de la page et le contexte n'atteignaient jamais le panneau. Fix : - background.js : ajouter un onMessage qui forward SELECTION_CHANGED et PAGE_CONTEXT_CHANGED vers toutes les surfaces (side panel). - sidepanel.js applySelectionFromMessage : normaliser les URLs (ignorer query/fragment/trailing slash), amorcer pageUrl depuis le premier message si tabs.query a échoué, accepter une sélection qui arrive avant refreshPageContext. La v0.3.1 souffrait du même bug — la sélection fonctionnait parfois par chance quand Chrome routait le message avant que le side panel ne soit complètement initialisé. Maintenant c'est garanti.
This commit is contained in:
@@ -21,3 +21,26 @@ extApi.runtime?.onStartup?.addListener?.(() => {
|
|||||||
extApi.sidePanel.setPanelBehavior({ openPanelOnActionClick: true }).catch(() => {})
|
extApi.sidePanel.setPanelBehavior({ openPanelOnActionClick: true }).catch(() => {})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// === Forwarding content script → side panel ===
|
||||||
|
// En Chrome MV3, chrome.runtime.sendMessage depuis un content script arrive
|
||||||
|
// uniquement au background service worker (PAS au side panel directement).
|
||||||
|
// On doit broadcaster manuellement vers toutes les surfaces ouvertes.
|
||||||
|
extApi.runtime?.onMessage?.addListener?.((msg, sender) => {
|
||||||
|
if (!msg || typeof msg !== 'object') return
|
||||||
|
// Messages émis par le content script qui doivent atteindre le side panel.
|
||||||
|
const FORWARD_TYPES = new Set([
|
||||||
|
'SELECTION_CHANGED',
|
||||||
|
'PAGE_CONTEXT_CHANGED',
|
||||||
|
])
|
||||||
|
if (!FORWARD_TYPES.has(msg.type)) return
|
||||||
|
// Broadcast à toutes les surfaces (side panel, popup).
|
||||||
|
// runAt permet au receiver de filtrer par URL s'il veut.
|
||||||
|
extApi.runtime.sendMessage({
|
||||||
|
...msg,
|
||||||
|
_fromContentScript: true,
|
||||||
|
_fromTabId: sender?.tab?.id,
|
||||||
|
}).catch(() => { /* no listener */ })
|
||||||
|
// Retourne true pour permettre une réponse asynchrone si besoin.
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|||||||
@@ -21,3 +21,26 @@ extApi.runtime?.onStartup?.addListener?.(() => {
|
|||||||
extApi.sidePanel.setPanelBehavior({ openPanelOnActionClick: true }).catch(() => {})
|
extApi.sidePanel.setPanelBehavior({ openPanelOnActionClick: true }).catch(() => {})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// === Forwarding content script → side panel ===
|
||||||
|
// En Chrome MV3, chrome.runtime.sendMessage depuis un content script arrive
|
||||||
|
// uniquement au background service worker (PAS au side panel directement).
|
||||||
|
// On doit broadcaster manuellement vers toutes les surfaces ouvertes.
|
||||||
|
extApi.runtime?.onMessage?.addListener?.((msg, sender) => {
|
||||||
|
if (!msg || typeof msg !== 'object') return
|
||||||
|
// Messages émis par le content script qui doivent atteindre le side panel.
|
||||||
|
const FORWARD_TYPES = new Set([
|
||||||
|
'SELECTION_CHANGED',
|
||||||
|
'PAGE_CONTEXT_CHANGED',
|
||||||
|
])
|
||||||
|
if (!FORWARD_TYPES.has(msg.type)) return
|
||||||
|
// Broadcast à toutes les surfaces (side panel, popup).
|
||||||
|
// runAt permet au receiver de filtrer par URL s'il veut.
|
||||||
|
extApi.runtime.sendMessage({
|
||||||
|
...msg,
|
||||||
|
_fromContentScript: true,
|
||||||
|
_fromTabId: sender?.tab?.id,
|
||||||
|
}).catch(() => { /* no listener */ })
|
||||||
|
// Retourne true pour permettre une réponse asynchrone si besoin.
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|||||||
@@ -293,11 +293,41 @@ function updateSelectionUI() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function applySelectionFromMessage(msg) {
|
function applySelectionFromMessage(msg) {
|
||||||
if (!msg || msg.url !== pageUrl) return
|
if (!msg) return
|
||||||
|
// Tolérance sur la comparaison d'URL : ignorer trailing slash, query,
|
||||||
|
// fragment. Si pageUrl est vide (panel ouvert avant refreshPageContext),
|
||||||
|
// on accepte la première sélection qui arrive.
|
||||||
|
const normalize = (u) => {
|
||||||
|
try {
|
||||||
|
const p = new URL(u)
|
||||||
|
return `${p.origin}${p.pathname.replace(/\/$/, '')}`
|
||||||
|
} catch { return u || '' }
|
||||||
|
}
|
||||||
|
// Si on n'a pas encore de pageUrl mais qu'un message arrive avec URL,
|
||||||
|
// on l'utilise pour amorcer la fiche page. Très utile quand chrome.tabs.query
|
||||||
|
// rate (ex. onglet sans permission, side panel non focus, etc.).
|
||||||
|
if (msg.url && (!pageUrl || normalize(msg.url) !== normalize(pageUrl))) {
|
||||||
|
if (!pageUrl) {
|
||||||
|
pageUrl = msg.url
|
||||||
|
pageTitle = msg.title || pageTitle || ''
|
||||||
|
try {
|
||||||
|
const u = new URL(msg.url)
|
||||||
|
pageDomain = u.hostname
|
||||||
|
pageFavicon = `https://www.google.com/s2/favicons?domain=${u.hostname}&sz=32`
|
||||||
|
} catch { /* ignore */ }
|
||||||
|
} else {
|
||||||
|
// URL vraiment différente — ignorer.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
selectionText = msg.text || ''
|
selectionText = msg.text || ''
|
||||||
if (msg.dir?.toLowerCase() === 'rtl') pageDir = 'rtl'
|
if (msg.dir?.toLowerCase() === 'rtl') pageDir = 'rtl'
|
||||||
if (msg.lang) pageLang = msg.lang
|
if (msg.lang) pageLang = msg.lang
|
||||||
if (state === 'idle') updateSelectionUI()
|
if (state === 'idle') {
|
||||||
|
updateSelectionUI()
|
||||||
|
} else {
|
||||||
|
console.debug('[memento] selection updated while busy, deferring UI')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function refreshPageContext() {
|
async function refreshPageContext() {
|
||||||
|
|||||||
@@ -21,3 +21,26 @@ extApi.runtime?.onStartup?.addListener?.(() => {
|
|||||||
extApi.sidePanel.setPanelBehavior({ openPanelOnActionClick: true }).catch(() => {})
|
extApi.sidePanel.setPanelBehavior({ openPanelOnActionClick: true }).catch(() => {})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// === Forwarding content script → side panel ===
|
||||||
|
// En Chrome MV3, chrome.runtime.sendMessage depuis un content script arrive
|
||||||
|
// uniquement au background service worker (PAS au side panel directement).
|
||||||
|
// On doit broadcaster manuellement vers toutes les surfaces ouvertes.
|
||||||
|
extApi.runtime?.onMessage?.addListener?.((msg, sender) => {
|
||||||
|
if (!msg || typeof msg !== 'object') return
|
||||||
|
// Messages émis par le content script qui doivent atteindre le side panel.
|
||||||
|
const FORWARD_TYPES = new Set([
|
||||||
|
'SELECTION_CHANGED',
|
||||||
|
'PAGE_CONTEXT_CHANGED',
|
||||||
|
])
|
||||||
|
if (!FORWARD_TYPES.has(msg.type)) return
|
||||||
|
// Broadcast à toutes les surfaces (side panel, popup).
|
||||||
|
// runAt permet au receiver de filtrer par URL s'il veut.
|
||||||
|
extApi.runtime.sendMessage({
|
||||||
|
...msg,
|
||||||
|
_fromContentScript: true,
|
||||||
|
_fromTabId: sender?.tab?.id,
|
||||||
|
}).catch(() => { /* no listener */ })
|
||||||
|
// Retourne true pour permettre une réponse asynchrone si besoin.
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|||||||
@@ -293,11 +293,41 @@ function updateSelectionUI() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function applySelectionFromMessage(msg) {
|
function applySelectionFromMessage(msg) {
|
||||||
if (!msg || msg.url !== pageUrl) return
|
if (!msg) return
|
||||||
|
// Tolérance sur la comparaison d'URL : ignorer trailing slash, query,
|
||||||
|
// fragment. Si pageUrl est vide (panel ouvert avant refreshPageContext),
|
||||||
|
// on accepte la première sélection qui arrive.
|
||||||
|
const normalize = (u) => {
|
||||||
|
try {
|
||||||
|
const p = new URL(u)
|
||||||
|
return `${p.origin}${p.pathname.replace(/\/$/, '')}`
|
||||||
|
} catch { return u || '' }
|
||||||
|
}
|
||||||
|
// Si on n'a pas encore de pageUrl mais qu'un message arrive avec URL,
|
||||||
|
// on l'utilise pour amorcer la fiche page. Très utile quand chrome.tabs.query
|
||||||
|
// rate (ex. onglet sans permission, side panel non focus, etc.).
|
||||||
|
if (msg.url && (!pageUrl || normalize(msg.url) !== normalize(pageUrl))) {
|
||||||
|
if (!pageUrl) {
|
||||||
|
pageUrl = msg.url
|
||||||
|
pageTitle = msg.title || pageTitle || ''
|
||||||
|
try {
|
||||||
|
const u = new URL(msg.url)
|
||||||
|
pageDomain = u.hostname
|
||||||
|
pageFavicon = `https://www.google.com/s2/favicons?domain=${u.hostname}&sz=32`
|
||||||
|
} catch { /* ignore */ }
|
||||||
|
} else {
|
||||||
|
// URL vraiment différente — ignorer.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
selectionText = msg.text || ''
|
selectionText = msg.text || ''
|
||||||
if (msg.dir?.toLowerCase() === 'rtl') pageDir = 'rtl'
|
if (msg.dir?.toLowerCase() === 'rtl') pageDir = 'rtl'
|
||||||
if (msg.lang) pageLang = msg.lang
|
if (msg.lang) pageLang = msg.lang
|
||||||
if (state === 'idle') updateSelectionUI()
|
if (state === 'idle') {
|
||||||
|
updateSelectionUI()
|
||||||
|
} else {
|
||||||
|
console.debug('[memento] selection updated while busy, deferring UI')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function refreshPageContext() {
|
async function refreshPageContext() {
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -293,11 +293,41 @@ function updateSelectionUI() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function applySelectionFromMessage(msg) {
|
function applySelectionFromMessage(msg) {
|
||||||
if (!msg || msg.url !== pageUrl) return
|
if (!msg) return
|
||||||
|
// Tolérance sur la comparaison d'URL : ignorer trailing slash, query,
|
||||||
|
// fragment. Si pageUrl est vide (panel ouvert avant refreshPageContext),
|
||||||
|
// on accepte la première sélection qui arrive.
|
||||||
|
const normalize = (u) => {
|
||||||
|
try {
|
||||||
|
const p = new URL(u)
|
||||||
|
return `${p.origin}${p.pathname.replace(/\/$/, '')}`
|
||||||
|
} catch { return u || '' }
|
||||||
|
}
|
||||||
|
// Si on n'a pas encore de pageUrl mais qu'un message arrive avec URL,
|
||||||
|
// on l'utilise pour amorcer la fiche page. Très utile quand chrome.tabs.query
|
||||||
|
// rate (ex. onglet sans permission, side panel non focus, etc.).
|
||||||
|
if (msg.url && (!pageUrl || normalize(msg.url) !== normalize(pageUrl))) {
|
||||||
|
if (!pageUrl) {
|
||||||
|
pageUrl = msg.url
|
||||||
|
pageTitle = msg.title || pageTitle || ''
|
||||||
|
try {
|
||||||
|
const u = new URL(msg.url)
|
||||||
|
pageDomain = u.hostname
|
||||||
|
pageFavicon = `https://www.google.com/s2/favicons?domain=${u.hostname}&sz=32`
|
||||||
|
} catch { /* ignore */ }
|
||||||
|
} else {
|
||||||
|
// URL vraiment différente — ignorer.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
selectionText = msg.text || ''
|
selectionText = msg.text || ''
|
||||||
if (msg.dir?.toLowerCase() === 'rtl') pageDir = 'rtl'
|
if (msg.dir?.toLowerCase() === 'rtl') pageDir = 'rtl'
|
||||||
if (msg.lang) pageLang = msg.lang
|
if (msg.lang) pageLang = msg.lang
|
||||||
if (state === 'idle') updateSelectionUI()
|
if (state === 'idle') {
|
||||||
|
updateSelectionUI()
|
||||||
|
} else {
|
||||||
|
console.debug('[memento] selection updated while busy, deferring UI')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function refreshPageContext() {
|
async function refreshPageContext() {
|
||||||
|
|||||||
Reference in New Issue
Block a user