在 Firefox Add-on SDK 扩展中的“onInstalled”上执行脚本

2024-05-15

我对 Mozilla 扩展开发非常陌生,即使我刚刚知道扩展和附加开发是不同的 我对我在 MDN(Mozilla 开发者网络)上看到的内容非常困惑。 我想在安装我的附加组件后立即执行脚本“content_script.js”,以便用户不需要重新启动浏览器。

我只需拖放 xpi 文件并安装它,然后单击网页上的一个按钮,该按钮将消息发送到我的附加组件,但我的附加组件仅在我重新加载网页后才会侦听此消息。

//main.js

       var chrome = require("chrome");      
    chrome.runtime.onInstalled.addListener(function(){
             executeScript (contentscript.js) in tabs});

//also I tried
    browser.runtime.onInstalled.addListener

当我知道它是用于 Mozilla 扩展而不是附加组件时,我正在尝试这样做,因为它给了我错误 browser undefined 和chrome.runtime未定义。

然后,我发现onInstalled() inside AddonManager.addAddonListener() https://developer.mozilla.org/en-US/Add-ons/Add-on_Manager/AddonListener.

但是,我仍然很困惑应该如何在我的附加组件中使用它。

无论我尝试什么方法,它都会给我带来错误。

//main.js
    const { AddonManager } = require("resource://gre/modules/AddonManager.jsm");
    Cu.import("resource://gre/modules/AddonManager.jsm");


    AddonManager.addAddonListener.onInstalled.addListener(function(){
        console.log('installed');
        tabs.executeScript(tabId, "../data/content_script.js", function(result) { console.log('result ='+result); });           
    });

下面的代码消除了错误,但也不起作用:

    const { AddonManager } = require("resource://gre/modules/AddonManager.jsm");
    Cu.import("resource://gre/modules/AddonManager.jsm");


    var listener = {
  onInstalled: function(addon) {
      console.log('installed');
      console.log(addon); 
        tabs.executeScript(tabId, "../data/content_script.js", function(result) { console.log('result ='+result); });           
  }
 };

     AddonManager.addAddonListener(listener);

我的 package.json 看起来像这样

    {
  "title": "test",
  "name": "test",
  "version": "1.0.0",
  "description": "test addon corp",
  "main": "lib/main.js",
  "author": "test",
  "engines": {
    "firefox": ">=38.0a1",
    "fennec": ">=38.0a1"
  },
  "license": "MIT",
  "keywords": [
    "jetpack"
  ]
}

main包括 main.js,但它仍然没有执行,因为当我重新加载页面时,我的附加组件才起作用。

我认为我做的完全错误,我在 Chrome 扩展中做到了,这很简单,但我对 Mozilla 插件没有任何想法。我什至无法达到“已安装”。执行 content_script 还很遥远。


您似乎遇到了一些问题:

  • You are still mixing WebExtensions and Add-on SDK
    • Add-on SDK没有这样的方法tabs.executeScript() https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/tabs/executeScript你正在寻找tab.attach() https://developer.mozilla.org/en-US/Add-ons/SDK/High-Level_APIs/tabs#attach(options)。对于活动选项卡来说,它是tabs.activeTab.attach() https://developer.mozilla.org/en-US/Add-ons/SDK/High-Level_APIs/tabs#activeTab.
  • The AddonManager https://developer.mozilla.org/en-US/Add-ons/Add-on_Manager/AddonManager onInstalled https://developer.mozilla.org/en-US/Add-ons/Add-on_Manager/AddonListener#onInstalled() event for the installation of the add-on currently being installed happens prior to any code of the add-on being run. Thus, you will not receive this event for the add-on that is listening.
    • The AddonManager主要是为了让您的附加组件能够监控涉及的活动other附加组件。它在监视执行监视的附加组件的活动方面的用处是有限的。
    • 你可以获得你的AddonManager onDisabled https://developer.mozilla.org/en-US/Add-ons/Add-on_Manager/AddonListener#onDisabled()如果用户在删除插件之前禁用该插件,则会发生该事件。如果用户直接删除它,你只会得到一个onUninstalling https://developer.mozilla.org/en-US/Add-ons/Add-on_Manager/AddonListener#onUninstalling() event.
  • 您可能会遇到这样的问题console.log()当内容脚本由作为正在测试的主要加载项加载的加载项附加时,来自内容脚本的内容不会显示在浏览器控制台中jpm run https://developer.mozilla.org/en-US/Add-ons/SDK/Tools/jpm#jpm_run。输出可在执行以下命令的控制台窗口中找到jpm run。虽然按照设计,输出会转到控制台窗口,但我认为输出不会转到浏览器控制台(除了控制台窗口之外),这要么是错误,要么是错误。
  • 将内容脚本加载到about:*页面导致的某些行为与将其加载到普通网页中不同。这种行为可以包括使console.log() https://developer.mozilla.org/en-US/docs/Web/API/Console/log输出仅显示为好像是使用输出的dump() https://developer.mozilla.org/en-US/docs/Web/API/Window/dump(即它不会显示在浏览器控制台中,但会显示在执行 Firefox/jpm run)。如果您尝试这样做,您将需要尝试您能做的事情。
  • You have stated, "I think my main.js is not running right after installation." This belief may be as a result of not looking in the right place for console.log() output, the Browser Console https://developer.mozilla.org/en-US/docs/Tools/Browser_Console. As such, I have the add-ons below automatically open the Browser Console.
    • 中指定的 JavaScript 文件包.json https://developer.mozilla.org/en-US/Add-ons/SDK/Tools/package_json key "main" https://developer.mozilla.org/en-US/Add-ons/SDK/Tools/package_json#main安装附加组件时运行。它也在 Firefox 启动时运行。
    • 你可以有一个exports.main https://developer.mozilla.org/en-US/Add-ons/SDK/Tutorials/Listening_for_load_and_unload#exports.main()在您的代码之后自动调用的函数"main"JavaScript 文件被评估并执行。此功能可用于查找您的附加组件被执行的原因。可能的原因是install, enable, startup, upgrade, and downgrade。下面的附加组件演示了何时执行此代码以及reason传递给它。

为了演示各种内容,我编写了一个 Firefox Add-on SDK 扩展,它可以加载几个内容脚本并监听AddonManager事件。我制作了此附加组件的两个版本,它们的不同之处仅在于分配给各自的名称和 ID包.json文件。这两个附加组件是installinfo@ex1 and installinfo@ex2.

The first add-on, installinfo@ex1, is loaded by running jpm run in its directory. The second add-on, installinfo@ex2, is installed by dragging and dropping the .xpi created for it by jpm xpi. Using the Firefox UI, I immediately navigate to about:addons (Ctrl-Shift-A, Cmd-Shift-A on OSX) and proceed to first disable installinfo@ex2; then "remove" installinfo@ex2; then refresh the about:addons page to make it not possible to "undo" the removal. I then exit the Firefox main browser window.

这些附加组件有大量的输出到控制台,因此您可以查看事件发生的顺序以及哪个附加组件能够接收哪些内容AddonManager事件。控制台输出是[括号中指出的是我对用户界面所做的操作,以及一些注释]:

[User action: Start Firefox in installinfo@ex1 directory]
    installinfo@ex1: In index.js
    installinfo@ex1: In installAddonListener: Adding add-on listener
    installinfo@ex1: Attaching content script A
    installinfo@ex1: In exports.main: This add-on is being loaded for reason= install Object { loadReason: "install", staticArgs: undefined }
    installinfo@ex1: Attaching content script B
    installinfo@ex1: In exports.main: was passed callbacks= Object { print: print(), quit: function () }
[Note: no console.log output from within conentScriptA loading]
    installinfo@ex1: received message from contentScriptA: Is Loaded
[Note: no console.log output from within conentScriptB loading]
    installinfo@ex1: received message from contentScriptB: Is Loaded
[User action: Drag and drop .xpi for installinfo@ex2 onto Firefox]
    installinfo@ex1: AddonManager Event: Installing addon ID: installinfo@ex2 ::needsRestart= false ::addon object: Object {  }
    installinfo@ex1: AddonManager Event: Installed addon ID: installinfo@ex2 ::addon object: Object {  }
            installinfo@ex2: In index.js
            installinfo@ex2: In installAddonListener: Adding add-on listener
            installinfo@ex2: Attaching content script A
            installinfo@ex2: In exports.main: This add-on is being loaded for reason= install Object { loadReason: "install", staticArgs: undefined }
            installinfo@ex2: Attaching content script B
            installinfo@ex2: In exports.main: was passed callbacks= Object { print: print(_), quit: function () }
                installinfo@ex2: In contentScriptA: Loaded
            installinfo@ex2: received message from contentScriptA: Is Loaded
                installinfo@ex2: In contentScriptB: Loaded
            installinfo@ex2: received message from contentScriptB: Is Loaded
[User action: Navigate to about:addons]
[User action: Disable installinfo@ex2]
    installinfo@ex1: AddonManager Event: Disabling addon ID: installinfo@ex2 ::needsRestart= false ::addon object: Object {  }
            installinfo@ex2: AddonManager Event: Disabling addon ID: installinfo@ex2 ::needsRestart= false ::addon object: Object {  }
            installinfo@ex2: In exports.onUnload: This add-on is being unloaded for reason= disable
            installinfo@ex2: In removeAddonListener: Removing add-on listener
    installinfo@ex1: AddonManager Event: Disabled addon ID: installinfo@ex2 ::addon object: Object {  }
            installinfo@ex2: AddonManager Event: Disabled addon ID: installinfo@ex2 ::addon object: Object {  }
    installinfo@ex1: AddonManager Event: Uninstalling addon ID: installinfo@ex2 ::needsRestart= true ::addon object: Object {  }
[Get a warning in Browser Console because installinfo@ex2 did not remove its AddonManager listeners, and AddonManager is still trying to call them.]
            1472563865661   addons.manager   WARN   AddonListener threw exception when calling onUninstalling: TypeError: can't access dead object (resource://gre/modules/AddonManager.jsm:1756:1) JS Stack trace: [email protected] /cdn-cgi/l/email-protection:1756:1 < [email protected] /cdn-cgi/l/email-protection:3075:5 < [email protected] /cdn-cgi/l/email-protection:5041:7 < Ad[email protected] /cdn-cgi/l/email-protection:7484:5 < [email protected] /cdn-cgi/l/email-protection:1548:13 < oncommand@about:addons:1:1
[User action: Refresh about:addons page to remove "undo" posibility for installinfo@ex2]
    installinfo@ex1: Uninstalled addon ID: installinfo@ex2 ::addon object: Object {  }
[Get a warning in Browser Console because installinfo@ex2 did not remove its AddonManager listeners, and AddonManager is still trying to call them.]
            1472563873408   addons.manager   WARN   AddonListener threw exception when calling onUninstalled: TypeError: can't access dead object (resource://gre/modules/AddonManager.jsm:1756:1) JS Stack trace: [email protected] /cdn-cgi/l/email-protection:1756:1 < [email protected] /cdn-cgi/l/email-protection:3075:5 < [email protected] /cdn-cgi/l/email-protection:5096:7 < [email protected] /cdn-cgi/l/email-protection:7484:5 < [email protected] /cdn-cgi/l/email-protection:1740:5 < [email protected] /cdn-cgi/l/email-protection:2733:5 < gViewControlle[email protected] /cdn-cgi/l/email-protection:651:7 < [email protected] /cdn-cgi/l/email-protection:184:3 < EventListener.handleEvent*@extensions.js:84:1
[User action: Close main Firefox browser window]
[User action: Close Firefox Browser Console window]
    (via dump):installinfo@ex1: In exports.onUnload: This add-on is being unloaded for reason= shutdown

我建议您尝试下面的附加组件,以了解 AddonManager 事件的可能性以及代码何时在附加组件中执行。

附加文件:

index.js:

/* Firefox Add-on SDK test when code is executed upon install*/

//For testing:
var doNotRemoveAddonManagerListenersUponUninstall = true

var self = require("sdk/self");
var tabs = require("sdk/tabs");
var myId = self.id;
let myIdText = myId;
if(myId.indexOf('2') > -1){
    //Indent console logs for version 2
    myIdText = '\t\t' + myIdText ;
}

var utils = require('sdk/window/utils');
activeWin = utils.getMostRecentBrowserWindow();
//This will execute every time the add-on is loaded (e.g. install, startup, enable, etc).
myLog('In index.js');

//Open the Browser Console
activeWin.document.getElementById('menu_browserConsole').doCommand();

function myLog(){
    // Using activeWin.console.log() is needed to have output to the
    // Browser Console when installed as an .xpi file.  In that case,
    // console is mapped to dump(), which does not output to the console. 
    // This is done, I assume, to not polute the console from SDK add-ons
    // that are logging information when they should not.  Using jpm run,
    // console.log outputs to the Browser Console.
    let activeWin = require('sdk/window/utils').getMostRecentBrowserWindow();
    // If Firefox is exiting (and some other conditions), there is
    // no active window.  At such times, we must use the version
    // of console.log() aliases to dump().
    let useConsole = console;
    if(activeWin ){
        //useConsole = activeWin.console;
    }
    useConsole.log(myIdText +':',...arguments);
}

function attachScript(script){
    let tabWorker = tabs.activeTab.attach({
        contentScriptFile: script,
        contentScriptOptions: {
            //extra \t because content script console.log (from .xpi) doesn't prefix with
            //  add-on name.
            idText: '\t\t\t' + myIdText
        }
    });
    tabWorker.port.on('consoleLog',logMessage);
    return tabWorker;
}

function logMessage(message){
    myLog(message);
}

exports.main = function (options,callbacks) {
    myLog('In exports.main: This add-on is being loaded for reason=', options.loadReason
          , options);
    myLog('Attaching content script B');
    attachScript('./contentScriptB.js');
    if(typeof callbacks !== 'undefined'){
        myLog('In exports.main: was passed callbacks=', callbacks);
    }
    switch (options.loadReason) {
        case 'install':
            //Do actions upon install
            break;
        case 'enable':
            //Do actions upon enable
            break;
        case 'startup':
            //Do actions upon startup
            break;
        case 'upgrade':
            //Do actions upon upgrade
            break;
        case 'downgrade':
            //Do actions upon downgrade
            break;
        default:
            throw 'Unknown load reason:' + options.loadReason;
            break; //Not needed, throw will exit
    }
};

exports.onUnload = function (reason) {
    myLog('In exports.onUnload: This add-on is being unloaded for reason=',reason);
    //Your add-on listeners are NOT automatically removed when
    //  your add-on is disabled/uninstalled. 
    //You MUST remove them in exports.onUnload if the reason is
    //  not 'shutdown'.  If you don't, errors will be shown in the
    //  console for all events for which you registered a listener.
    if(reason !== 'shutdown') {
        uninstallAddonListener();
    }
};


const { AddonManager } = require("resource://gre/modules/AddonManager.jsm");

let addonListener = {
    onEnabling: function(addon, needsRestart){
        myLog('AddonManager Event: Enabliling addon ID: ' + addon.id 
              + ' ::needsRestart= ' + needsRestart + ' ::addon object:', addon);
    },
    onEnabled: function(addon){
        myLog('AddonManager Event: Enabled addon ID: ' + addon.id 
              + ' ::addon object:', addon);
    },
    onDisabling: function(addon, needsRestart){
        myLog('AddonManager Event: Disabling addon ID: ' + addon.id 
              + ' ::needsRestart= ' + needsRestart + ' ::addon object:', addon);
    },
    onDisabled: function(addon){
        myLog('AddonManager Event: Disabled addon ID: ' + addon.id 
              + ' ::addon object:', addon);
    },
    onInstalling: function(addon, needsRestart){
        myLog('AddonManager Event: Installing addon ID: ' + addon.id 
              + ' ::needsRestart= ' + needsRestart + ' ::addon object:', addon);
    },
    onInstalled: function(addon){
        myLog('AddonManager Event: Installed addon ID: ' + addon.id 
              + ' ::addon object:', addon);
    },
    onUninstalling: function(addon, needsRestart){
        myLog('AddonManager Event: Uninstalling addon ID: ' + addon.id 
              + ' ::needsRestart= ' + needsRestart + ' ::addon object:', addon);
    },
    onUninstalled: function(addon){
        myLog('AddonManager Event: Uninstalled addon ID: ' + addon.id 
              + ' ::addon object:', addon);
    },
    onOperationCancelled: function(addon){
        myLog('AddonManager Event: Add-on Operation Canceled addon ID: ' 
              + addon.id + ' ::addon object:', addon);
    },
    onPropertyChanged: function(addon, properties){
        myLog('AddonManager Event: Add-on Property Changed addon ID: ' + addon.id 
              + ' ::properties= ', properties, ' ::addon object:', addon);
    }
}



function installAddonListener(){
    //Using an AddonManager listener is not effective to listen for your own add-on's
    //  install event.  The event happens prior to you adding the listener.
    myLog('In installAddonListener: Adding add-on listener');
    AddonManager.addAddonListener(addonListener);
}

function uninstallAddonListener(){
    if(doNotRemoveAddonManagerListenersUponUninstall === true){
        //Do the WRONG thing to demonstrate what happens when you don't
        //  remove AddonManager listeners upon your add-on being disabled.
        return;
    }
    //Using an AddonManager listener is not effective to listen for your own add-on's
    //  install event.  The event happens prior to you adding the listener.
    myLog('In removeAddonListener: Removing add-on listener');
    AddonManager.removeAddonListener(addonListener);
}

installAddonListener();

myLog('Attaching content script A');
attachScript('./contentScriptA.js');

数据/内容ScriptA:

//console.log is ailiased to dump() when running under jpm run. Thus,
//  you will not see the output from this line in the Browser Console
//  when run under jpm run.  It will appear in the console window from 
//  which you executed 'jpm run'
//  From an .xpi it outputs to the Browser Console, as expected.
console.log(self.options.idText + ': In contentScriptA: Loaded');

//Send a message to the background script that this content script is loaded.
self.port.emit('consoleLog', 'received message from contentScriptA: Is Loaded');

数据/内容ScriptB:

//console.log is ailiased to dump() when running under jpm run. Thus,
//  you will not see the output from this line in the Browser Console
//  when run under jpm run.  It will appear in the console window from 
//  which you executed 'jpm run'
//  From an .xpi it outputs to the Browser Console, as expected.
console.log(self.options.idText + ': In contentScriptB: Loaded');

//Send a message to the background script that this content script is loaded.
self.port.emit('consoleLog', 'received message from contentScriptB: Is Loaded');

包.json(对于 installinfo@ex1):

{
    "title": "Demo when code executes re install 1",
    "name": "installinfo1",
    "id": "installinfo@ex1",
    "version": "0.0.1",
    "description": "Demo when execute various code with respect to install",
    "main": "index.js",
    "author": "Makyen",
    "engines": {
        "firefox": ">=38.0a1",
        "fennec": ">=38.0a1"
    },
    "license": "MIT",
    "keywords": [
        "jetpack"
    ]
}

包.json(对于 installinfo@ex2):

{
    "title": "Demo when code executes re install 2",
    "name": "installinfo2",
    "id": "installinfo@ex2",
    "version": "0.0.1",
    "description": "Demo when execute various code with respect to install",
    "main": "index.js",
    "author": "Makyen",
    "engines": {
        "firefox": ">=38.0a1",
        "fennec": ">=38.0a1"
    },
    "license": "MIT",
    "keywords": [
        "jetpack"
    ]
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在 Firefox Add-on SDK 扩展中的“onInstalled”上执行脚本 的相关文章

随机推荐

  • CSS 中的边框图像开头

    MDN 是这样定义的边框图像开始 https developer mozilla org en docs Web CSS border image outset border image outset 属性描述了边框的数量 图像区域超出边框
  • presentRenderbuffer :GL_RENDERBUFFER_OES 需要很长时间

    我在游戏中添加了一个分析器并隔离了此功能 有时 它会导致 FPS 下降 这是我的结果 Present buffer time 22 Present buffer time 1 Present buffer time 9 Present bu
  • 使用 Angularjs 中的指令创建 ajax 加载微调器

    我正在尝试创建一个简单的加载程序 以下是我到目前为止所做的事情 有人可以看一下并让我知道我哪里出错了吗 出现CSS样式loading style 2没有被添加 我的 DOM 只显示 span class span 我的指令 angular
  • Java 8:并行 FOR 循环

    我听说 Java 8 提供了很多关于并发计算的实用程序 因此我想知道并行给定 for 循环的最简单方法是什么 public static void main String args Set
  • gitlab 中的自动发行说明[关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 任何人都可以帮助我在 git lab 中自动生成发行说明 有什么方法可以执行此操作 请告诉我 谢谢 您可以使用 API 列出里程碑中的
  • ActionBarActivity 和 ActivityUnitTest - NameNotFoundException

    好吧 这更有可能是我的错 当我做 public class XmlLifecycleTests extends ActivityUnitTestCase
  • 这个奇怪的泽西警告是什么意思?

    这个警告在 Jersey 1 4 中意味着什么 WARNING A sub resource method public final java lang String com XXX render with URI template is
  • 将数据集导出到一个 Excel 文件的多个 Excel 工作表中

    我需要在同一工作簿的两个 Excel 工作表中导出两个数据集的值 我的查询是这样的 数据集一 DataSet ds1 new DataSet SqlCommand commandOpen new SqlCommand storedproc1
  • 为什么不使用均方误差来解决分类问题?

    我正在尝试使用 LSTM 解决一个简单的二元分类问题 我正在尝试找出网络的正确损失函数 问题是 当我使用二元交叉熵作为损失函数时 与使用均方误差 MSE 函数相比 训练和测试的损失值相对较高 经过研究 我发现二元交叉熵应该用于分类问题 MS
  • inno setup bmp图像出现在单个页面上

    我希望 bmp 图像出现在单个页面 selectadditionaltasks 上 但它出现在所有页面上 我究竟做错了什么 procedure LogoOnClick Sender TObject var ResCode Integer b
  • Linux 桌面快捷方式和安装图标

    我需要添加什么到我的 spec文件来创建桌面快捷方式并在安装过程中为快捷方式分配一个图标 rpm 如果需要脚本 一个示例将非常有帮助 您在 Linux 下使用 desktop 文件作为图标 图标放置的位置取决于您使用的发行版和桌面环境 由于
  • 在 Angular 中获取当前路由路径名称的最简单方法是什么?

    我正在寻找一种获取当前路线的路径名称的好方法 这是我能找到的最简单的 this route snapshot firstChild url 0 path 有没有更好的办法 谢谢 谢谢大家的回答 这是我发现我必须做的 router event
  • 无法删除数据库 mysql:错误 3664 (HY000)

    我的应用程序中有一个名为X Files 我想要drop它 但每当我运行命令时drop database X Files我收到以下错误 mysql gt drop database X Files ERROR 3664 HY000 Faile
  • 由于继承抽象类而禁用设计器?

    我有一个项目的解决方案 那个项目中有 40 或 50 种形式 我制作了 4 个基本形式 所有其他形式都可以继承 所有 4 个基本表单都继承 System Windows Forms Form 几乎 90 的形式继承了前 2 个基本形式之一
  • 具有继承类型的 Aux 模式推理失败

    我有一个复杂的玩具算法 我希望纯粹在类型级别上表示 根据饮食要求选择当天菜肴的修改 对卷积表示歉意 但我认为我们需要每一层才能达到我想要使用的最终界面 我的代码有一个问题 如果我们表达一个类型约束Aux 模式生成的类型基于另一个泛型类型 它
  • 为什么我的 Pygame 窗口在为对象设置动画时会闪烁?

    所以我的 pygame 窗口不会停止闪烁 我知道只要one项目在snake snakearray 不会闪烁 class for the array class snake snakearray ScreenConfigs width 2 S
  • 如何通过start-stop-daemon正常关闭Spring Boot应用程序[重复]

    这个问题在这里已经有答案了 我们有一个多线程 Spring Boot 应用程序 它作为守护进程在 Linux 机器上运行 当我尝试像这样通过启动停止守护进程停止应用程序时 start stop daemon stop quiet retry
  • py2neo 引发完成(自我)错误

    使用 py2neo 时 我在尝试附加事务时收到以下错误 statement MERGE a Person name actorName n MERGE b Series title actorsFields 3 year actorsFie
  • 可以在 pystan 或 pymc3 中使用样本权重吗?

    如果我观察到的数据集具有权重 例如跟踪多重性 是否可以将其提供给 pystan 或 pymc3 类似于函数签名 http mc stan org rstanarm reference stan glm html http mc stan o
  • 在 Firefox Add-on SDK 扩展中的“onInstalled”上执行脚本

    我对 Mozilla 扩展开发非常陌生 即使我刚刚知道扩展和附加开发是不同的 我对我在 MDN Mozilla 开发者网络 上看到的内容非常困惑 我想在安装我的附加组件后立即执行脚本 content script js 以便用户不需要重新启