我正在开发一个 Chrome 扩展程序,当在网页上选择文本时,该扩展程序会显示一个弹出窗口。它在大多数网站上运行良好,但我在使用 Google 文档时遇到问题。这是我的内容脚本的相关部分:
import React from 'react'
import ReactDOM from 'react-dom/client'
import PopupComponent from './popup'
let popupContainer = null
function showPopup(rect, selectedText) {
if (!popupContainer) {
popupContainer = document.createElement('div')
document.body.appendChild(popupContainer)
}
const popupStyle = {
position: 'absolute',
left: `${rect.left}px`,
top: `${rect.bottom + window.scrollY}px`,
zIndex: 9999999999999999999,
}
const root = ReactDOM.createRoot(popupContainer)
root.render(
<div style={popupStyle}>
<PopupComponent />
</div>
)
}
function hidePopup() {
if (popupContainer) {
const root = ReactDOM.createRoot(popupContainer)
root.unmount()
// ReactDOM.unmountComponentAtNode(popupContainer)
}
}
function handleTextSelection() {
const selectedText = window.getSelection().toString().trim()
if (selectedText) {
const range = window.getSelection().getRangeAt(0)
const rect = range.getBoundingClientRect()
showPopup(rect, selectedText)
} else {
hidePopup()
}
}
// Listen for text selection
document.addEventListener('mouseup', handleTextSelection)
// Listen for scroll events to reposition the popup
window.addEventListener('scroll', () => {
const selection = window.getSelection()
if (!selection.isCollapsed) {
const range = selection.getRangeAt(0)
const rect = range.getBoundingClientRect()
showPopup(rect, selection.toString().trim())
}
})
// Listen for clicks outside the popup to close it
document.addEventListener('mousedown', (event) => {
if (popupContainer && !popupContainer.contains(event.target)) {
hidePopup()
}
})
// Cleanup function
function cleanup() {
document.removeEventListener('mouseup', handleTextSelection)
window.removeEventListener('scroll', handleTextSelection)
if (popupContainer) {
document.body.removeChild(popupContainer)
}
}
// Call cleanup when the content script is unloaded
window.addEventListener('unload', cleanup)
问题出在这个方法上:
function handleTextSelection() {
const selectedText = window.getSelection().toString().trim()
if (selectedText) {
const range = window.getSelection().getRangeAt(0)
const rect = range.getBoundingClientRect()
showPopup(rect, selectedText)
} else {
hidePopup()
}
}
document.addEventListener('mouseup', handleTextSelection)
这在常规网页上效果很好,但在 Google Docs 中,window.getSelection() 返回空字符串。我怀疑这是因为 Google 文档使用画布或其他一些不与标准 DOM 选择 API 交互的技术。 我注意到其他扩展(例如 Grammarly)可以成功捕获 Google 文档中的文本选择。有没有办法实现这个功能?
任何见解或建议将不胜感激。谢谢!
window.getSelection()
在Google文档上返回空字符串的原因是Google对其文本使用自定义渲染方法。请尝试以下操作,它利用 document.activeElement
从 Google 文档获取内容。
import React from 'react';
import ReactDOM from 'react-dom/client';
import PopupComponent from './popup';
let popupContainer = null;
function showPopup(rect, selectedText) {
if (!popupContainer) {
popupContainer = document.createElement('div');
document.body.appendChild(popupContainer);
}
const popupStyle = {
position: 'absolute',
left: `${rect.left}px`,
top: `${rect.bottom + window.scrollY}px`,
zIndex: 9999999999999999999,
};
const root = ReactDOM.createRoot(popupContainer);
root.render(
<div style={popupStyle}>
<PopupComponent text={selectedText} />
</div>
);
}
function hidePopup() {
if (popupContainer) {
const root = ReactDOM.createRoot(popupContainer);
root.unmount();
document.body.removeChild(popupContainer);
popupContainer = null;
}
}
function getSelectedText() {
const activeElement = document.activeElement;
if (activeElement && activeElement.tagName === 'TEXTAREA') {
return activeElement.value.substring(
activeElement.selectionStart,
activeElement.selectionEnd
);
} else if (window.getSelection) {
return window.getSelection().toString();
}
return '';
}
function handleTextSelection() {
const selectedText = getSelectedText().trim();
if (selectedText) {
const range = window.getSelection().getRangeAt(0);
const rect = range.getBoundingClientRect();
showPopup(rect, selectedText);
} else {
hidePopup();
}
}
document.addEventListener('mouseup', handleTextSelection);
window.addEventListener('scroll', () => {
const selection = window.getSelection();
if (!selection.isCollapsed) {
const range = selection.getRangeAt(0);
const rect = range.getBoundingClientRect();
showPopup(rect, selection.toString().trim());
}
});
document.addEventListener('mousedown', (event) => {
if (popupContainer && !popupContainer.contains(event.target)) {
hidePopup();
}
});
document.addEventListener('copy', async () => {
try {
const clipboardData = await navigator.clipboard.readText();
if (clipboardData) {
showPopup(null, clipboardData);
}
} catch (err) {
console.error('Failed to read clipboard contents:', err);
}
});
function cleanup() {
document.removeEventListener('mouseup', handleTextSelection);
window.removeEventListener('scroll', handleTextSelection);
if (popupContainer) {
document.body.removeChild(popupContainer);
}
}
window.addEventListener('unload', cleanup);