You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Chrome扩展Manifest V3下Declarative Content无法切换Action禁用状态求助

问题描述

我正在开发一款仅在特定URL生效的Chrome扩展,希望固定的扩展图标在非匹配页面显示禁用/灰度状态,在匹配页面恢复正常全彩图标。此前使用Manifest V2和declarative content实现过该功能,但在Manifest V3下无法正常工作——逻辑上应该已实现禁用/启用,但图标视觉始终无变化。额外需求:在启用/禁用扩展时修改图标标题。

现有代码

manifest.json

{
  "name": "Wheel of MatchPlay",
  "description": "Wheel of Names creation for MatchPlay tournaments",
  "version": "0.1.0",
  "manifest_version": 3,
  "permissions": [
    "storage",
    "declarativeContent",
    "activeTab",
    "scripting"
  ],
  "host_permissions": [
    "*://wheelofnames.com/*",
    "*://app.matchplay.events/*"
  ],
  "action": {
    "default_state": "disabled",
    "default_title": "not a matchplay tournament",
    "default_icon": {
      "16": "icons/icon-16.png",
      "32": "icons/icon-32.png",
      "48": "icons/icon-48.png",
      "128": "icons/icon-128.png"
    }
  },
  "background": {
    "service_worker": "background.js"
  },
  "icons": {
    "16": "icons/icon-16.png",
    "32": "icons/icon-32.png",
    "48": "icons/icon-48.png",
    "128": "icons/icon-128.png"
  }
}

background.js

// Wrap in an onInstalled callback to avoid unnecessary work
// every time the service worker is run
chrome.runtime.onInstalled.addListener((details) => {

    chrome.storage.sync.set({
        MATCHPLAY_KEY,
        WHEEL_KEY,
    });

    // if details.reason === chrome.runtime.OnInstalledReason.INSTALL
    // chrome.runtime.openOptionsPage();

    // Page actions are disabled by default and enabled on select tabs
    chrome.action.disable();

    // replace all rules...
    chrome.declarativeContent.onPageChanged.removeRules(undefined, () => {
        // with a new rule...
        chrome.declarativeContent.onPageChanged.addRules([
            {
                // That fires when a page's URL is matchplay tournaments
                conditions: [
                    new chrome.declarativeContent.PageStateMatcher({
                        pageUrl: {
                            hostEquals: 'app.matchplay.events',
                            // pathPrefix: '/tournaments'
                        },
                    })
                ],
                // And show's the extension's action
                actions: [
                    new chrome.declarativeContent.ShowAction()
                ],
            }
        ]);
    });
});

chrome.action.onClicked.addListener(async (tab) => {
    const tabId = tab.id;
    chrome.action.disable(tabId);
    chrome.action.setBadgeBackgroundColor({ tabId, color: '#f00' });
    chrome.action.setBadgeText({ tabId, text: '...' });

    // TODO -- do something about not having keys?

    // load the data and create the wheel
    const [_, tid] = tab.url.match(/tournaments\/(\d+)/);
    const tournament = await loadTournamentData(MATCHPLAY_KEY, tid);
    const wheelPath = await createWheelForTournament(WHEEL_KEY, tournament, { skipUsers: [MATCHPLAY_SELF_USER_ID]});

    // open the wheel in a new tab
    const wheelTab = await chrome.tabs.create({
        active: true,
        index: tab.index + 1,
        openerTabId: tab.id,
        url: `https://wheelofnames.com/${wheelPath}`
    });

    chrome.action.setBadgeText({ tabId, text: '' });
    chrome.action.enable(tab.id);
});
解决方案

步骤1:准备灰度图标资源

制作一套对应尺寸的灰度版本图标,命名为icon-16-gray.png、icon-32-gray.png、icon-48-gray.png、icon-128-gray.png,放入icons目录中。

步骤2:修改manifest.json

调整action的默认配置,将默认图标改为灰度版本,确保非匹配页面初始显示禁用状态:

"action": {
  "default_state": "disabled",
  "default_title": "not a matchplay tournament",
  "default_icon": {
    "16": "icons/icon-16-gray.png",
    "32": "icons/icon-32-gray.png",
    "48": "icons/icon-48-gray.png",
    "128": "icons/icon-128-gray.png"
  }
},

步骤3:重构background.js逻辑

放弃declarativeContent,改用chrome.tabs.onUpdated监听标签页URL变化,直接控制每个标签页的图标、标题和启用状态,这种方式在Manifest V3中更可靠:

// 存储初始数据
chrome.runtime.onInstalled.addListener(async (details) => {
  chrome.storage.sync.set({
    MATCHPLAY_KEY,
    WHEEL_KEY,
  });

  // 初始化所有已打开标签页的状态
  const tabs = await chrome.tabs.query({});
  tabs.forEach(tab => {
    if (tab.url) updateTabActionState(tab.id, tab.url);
  });
});

// 监听标签页URL更新,同步动作状态
chrome.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => {
  if (changeInfo.status === 'complete' && tab.url) {
    updateTabActionState(tabId, tab.url);
  }
});

// 根据URL设置标签页的动作状态(图标、标题、启用/禁用)
function updateTabActionState(tabId, url) {
  const isMatchPlayPage = url.startsWith('https://app.matchplay.events/');
  
  if (isMatchPlayPage) {
    // 匹配页面:启用动作,设置全彩图标和对应标题
    chrome.action.enable(tabId);
    chrome.action.setIcon({
      tabId,
      path: {
        "16": "icons/icon-16.png",
        "32": "icons/icon-32.png",
        "48": "icons/icon-48.png",
        "128": "icons/icon-128.png"
      }
    });
    chrome.action.setTitle({
      tabId,
      title: "Create MatchPlay Wheel"
    });
  } else {
    // 非匹配页面:禁用动作,设置灰度图标和默认标题
    chrome.action.disable(tabId);
    chrome.action.setIcon({
      tabId,
      path: {
        "16": "icons/icon-16-gray.png",
        "32": "icons/icon-32-gray.png",
        "48": "icons/icon-48-gray.png",
        "128": "icons/icon-128-gray.png"
      }
    });
    chrome.action.setTitle({
      tabId,
      title: "not a matchplay tournament"
    });
  }
}

// 保留原有的点击事件逻辑
chrome.action.onClicked.addListener(async (tab) => {
  const tabId = tab.id;
  chrome.action.disable(tabId);
  chrome.action.setBadgeBackgroundColor({ tabId, color: '#f00' });
  chrome.action.setBadgeText({ tabId, text: '...' });

  // TODO -- 处理无密钥的情况

  // 加载数据并创建轮盘
  const [_, tid] = tab.url.match(/tournaments\/(\d+)/);
  const tournament = await loadTournamentData(MATCHPLAY_KEY, tid);
  const wheelPath = await createWheelForTournament(WHEEL_KEY, tournament, { skipUsers: [MATCHPLAY_SELF_USER_ID]});

  // 在新标签页打开轮盘
  const wheelTab = await chrome.tabs.create({
    active: true,
    index: tab.index + 1,
    openerTabId: tab.id,
    url: `https://wheelofnames.com/${wheelPath}`
  });

  chrome.action.setBadgeText({ tabId, text: '' });
  chrome.action.enable(tab.id);
});

关键说明

  1. 替换declarativeContent的原因:Manifest V3中declarativeContent.ShowAction仅控制动作是否显示,无法直接关联图标灰度和标题动态修改,而通过chrome.tabs.onUpdated监听URL变化的方式更直接可控。
  2. 按标签页独立控制:使用tabId参数确保每个标签页的动作状态独立,不会互相干扰。
  3. 初始化处理:在扩展安装/更新时遍历所有已打开标签页,确保初始状态正确。

内容的提问来源于stack exchange,提问作者aron.duby

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 02:10:58