另一个答案是不正确的。第一部分(关于onSuspend
事件)实际上是不正确的。关于的部分setUninstallURL
是相关的,但不回答问题,因为它不允许您将选项卡恢复到原始状态(正如您在问题中询问的那样)。
在这个答案中,我将首先澄清关于runtime.onSuspend
,然后解释如何在禁用扩展时运行内容脚本的代码。
About runtime.onSuspend
The chrome.runtime.onSuspend and chrome.runtime.onSuspendCanceled事件与禁用/卸载的扩展无关。事件定义为活动页面,它们基本上是在一段时间不活动后挂起(卸载)的后台页面。当活动页面因暂停而即将卸载时,runtime.onSuspend
叫做。如果在此事件期间调用扩展 API(例如发送扩展消息),暂停将被取消并触发onSuspendCanceled
event.
当扩展因浏览器关闭或卸载而卸载时,扩展的生命周期无法延长。因此,您不能依赖这些事件来运行异步任务(例如从后台页面清理选项卡)。
此外,这些事件在内容脚本中不可用(仅在后台页面等扩展页面),因此不能使用这些事件来同步清理内容脚本逻辑。
从上面应该可以明显看出runtime.onSuspend
is not与禁用后的清理目标远程相关。 Chrome 中不行,更不用说 Firefox 了(Firefox 不支持事件页面,这些事件将毫无意义)。
在扩展禁用/卸载时在选项卡/内容脚本中运行代码
Chrome 扩展中的常见模式是使用port.onDisconnect事件来检测后台页面已卸载,并使用该事件来推断扩展程序可能已卸载(与该方法的选项1以达到更高的准确度)。 Chrome 的内容脚本在扩展被禁用后仍保留,因此可用于运行异步清理代码。
这在 Firefox 中是不可能的,因为在禁用 Firefox 扩展时,内容脚本的执行上下文会在执行之前被破坏。port.onDisconnect
事件有机会触发(至少,直到bugzil.la/1223425是固定的)。
尽管有这些限制,当禁用加载项时,仍然可以运行内容脚本的清理逻辑。该方法基于以下事实:在 Firefox 中,样式表插入tabs.insertCSS当加载项被禁用时将被删除。
我将讨论利用这一特性的两种方法。第一种方法允许执行任意代码。第二种方法不提供任意代码的执行,但如果您只想隐藏一些扩展插入的 DOM 元素,则它更简单且足够。
方法一:禁用扩展时在页面中运行代码
观察样式变化的方法之一是声明CSS 过渡 and 使用转换事件来检测 CSS 属性更改。
为了使它有用,您需要构造一个样式表,使其只影响您的 HTML 元素。因此,您需要生成一个唯一的选择器(类名、ID...)并将其用于您的 HTML 元素和样式表。
这是您必须放入后台脚本中的代码:
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
if (message !== 'getStyleCanary') return;
// Generate a random class name, insert a style sheet and send
// the class back to the caller if successful.
var CANARY_CLASS = '_' + crypto.getRandomValues(new Uint32Array(2)).join('');
var code = '.' + CANARY_CLASS + ' { opacity: 0 !important; }';
chrome.tabs.insertCSS(sender.tab.id, {
code,
frameId: sender.frameId,
runAt: 'document_start',
}, function() {
if (chrome.runtime.lastError) {
// Failed to inject. Frame unloaded?
sendResponse();
} else {
sendResponse(CANARY_CLASS);
}
});
return true; // We will asynchronously call sendResponse.
});
在内容脚本中:
chrome.runtime.sendMessage('getStyleCanary', function(CANARY_CLASS) {
if (!CANARY_CLASS) {
// Background was unable to insert a style sheet.
// NOTE: Consider retry sending the message in case
// the background page was not ready yet.
return;
}
var s = document.createElement('script');
s.src = chrome.runtime.getURL('canaryscript.js');
s.onload = s.remove;
s.dataset.canaryClass = CANARY_CLASS;
// This function will become available to the page and be used
// by canaryscript.js. NOTE: exportFunction is Firefox-only.
exportFunction(function() {}, s, {defineAs: 'checkCanary'});
(document.body || document.documentElement).appendChild(s);
});
我在上面使用了脚本标签,因为这是在页面中运行脚本而不被页面内容安全策略阻止的唯一方法。确保您添加canaryscript.js
to web_accessible_resources在清单.json中,否则脚本将无法加载。
如果运行清理代码并不重要(例如,因为您还使用我稍后解释的方法 2),那么您最好使用内联脚本而不是外部脚本(即使用s.textContent = '<content of canaryscript.js>'
代替s.src = ...
)。这是因为使用.src
与扩展资源介绍了Firefox 的指纹识别漏洞(错误 1372288).
这是内容canaryscript.js
:
(function() {
// Thes two properties are set in the content script.
var checkCanary = document.currentScript.checkCanary;
var CANARY_CLASS = document.currentScript.dataset.canaryClass;
var canary = document.createElement('span');
canary.className = CANARY_CLASS;
// The inserted style sheet has opacity:0. Upon removal a transition occurs.
canary.style.opacity = '1';
canary.style.transitionProperty = 'opacity';
// Wait a short while to make sure that the content script destruction
// finishes before the style sheet is removed.
canary.style.transitionDelay = '100ms';
canary.style.transitionDuration = '1ms';
canary.addEventListener('transitionstart', function() {
// To avoid inadvertently running clean-up logic when the event
// is triggered by other means, check whether the content script
// was really destroyed.
try {
// checkCanary will throw if the content script was destroyed.
checkCanary();
// If we got here, the content script is still valid.
return;
} catch (e) {
}
canary.remove();
// TODO: Put the rest of your clean up code here.
});
(document.body || document.documentElement).appendChild(canary);
})();
注意:CSS 转换事件仅在选项卡处于活动状态时才会触发。如果选项卡处于非活动状态,则在显示选项卡之前不会触发转换事件。
Note: exportFunction是仅适用于 Firefox 的扩展方法,用于在不同的执行上下文中定义函数(在上面的示例中,该函数是在页面上下文中定义的,可用于该页面中运行的脚本)。
所有其他 API 也可在其他浏览器(Chrome/Opera/Edge)中使用,但代码不能用于检测禁用的扩展,因为样式表来自tabs.insertCSS
卸载后不会被删除(我只使用 Chrome 进行测试;它可能在 Edge 中工作)。
方法二:卸载后视觉恢复
方法 1 允许您运行任意代码,例如删除在页面中插入的所有元素。作为从 DOM 中删除元素的替代方法,您还可以选择通过 CSS 隐藏元素。
下面我展示了如何修改方法 1 以隐藏元素而不运行其他代码(例如canaryscript.js
).
当您的内容脚本创建要插入到 DOM 中的元素时,您可以使用内联样式隐藏它:
var someUI = document.createElement('div');
someUI.style.display = 'none'; // <-- Hidden
// CANARY_CLASS is the random class (prefix) from the background page.
someUI.classList.add(CANARY_CLASS + 'block');
// ... other custom logic, and add to document.
在您添加的样式表中tabs.insertCSS
,然后您定义所需的display
值,与!important
标记以便覆盖内联样式:
// Put this snippet after "var code = '.' + CANARY_CLASS, above.
code += '.' + CANARY_CLASS + 'block {display: block !important;}';
上面的例子是故意通用的。如果您有多个具有不同 CSS 的 UI 元素display
值(例如block
, inline
,...),然后您可以添加多行这样的行来重新使用我提供的框架。
为了显示方法 2 相对于方法 1 的简单性:您可以使用相同的后台脚本(经过上述修改),并在内容脚本中使用以下内容:
// Example: Some UI in the content script that you want to clean up.
var someUI = document.createElement('div');
someUI.textContent = 'Example: This is a test';
document.body.appendChild(someUI);
// Clean-up is optional and a best-effort attempt.
chrome.runtime.sendMessage('getStyleCanary', function(CANARY_CLASS) {
if (!CANARY_CLASS) {
// Background was unable to insert a style sheet.
// Do not add clean-up classes.
return;
}
someUI.classList.add(CANARY_CLASS + 'block');
someUI.style.display = 'none';
});
如果您的扩展有多个元素,请考虑缓存以下值CANARY_CLASS
放在局部变量中,以便每个执行上下文仅插入一个新样式表。