【问题标题】:Can't pass arguments to chrome.declarativeContent.SetIcon无法将参数传递给 chrome.declarativeContent.SetIcon
【发布时间】:2021-04-06 01:19:13
【问题描述】:

我正在尝试开发一个简单的 chrome 扩展。 pageAction 的默认图标应该出现在具有特定 URL (http://www.example.com/*) 的页面上。

有两个文件

manifest.json

{
  "manifest_version": 2,
  "name": "name",
  "description": "description",
  "version": "1.0",
  "background": {
    "scripts": [
      "background.js"
    ],
    "persistent": false
  },
  "page_action": {
    "default_icon" : "images/icons/19.png"
  },
  "permissions": [
    "declarativeContent"
  ]
}

background.js

chrome.runtime.onInstalled.addListener(function () {
    chrome.declarativeContent.onPageChanged.removeRules(undefined, function () {
        chrome.declarativeContent.onPageChanged.addRules([
            {
                // rule1
                conditions : [
                    new chrome.declarativeContent.PageStateMatcher({
                        pageUrl : {urlPrefix : 'http://www.example.com/'}
                    })
                ],
                actions    : [
                    new chrome.declarativeContent.ShowPageAction()
                ]
            },
            {
                // rule2
                conditions : [
                    new chrome.declarativeContent.PageStateMatcher({
                        pageUrl : {queryContains : 'q1=green'}
                    })
                ],
                actions    : [
                    new chrome.declarativeContent.SetIcon({
                        path : {"19" : "images/icons/green.png"}
                    })
                ]
            }
        ]);
    });
});

rule1 应该显示 pageAction 的图标,rule2 应该在 URL 看起来像 http://www.example.com/?q1=green 的页面上将图标更改为绿色版本

但是在安装扩展程序的过程中:

Error in response to events.removeRules: Error: Invalid value for argument 1. Property '.0': Value does not match any valid type choices.

【问题讨论】:

    标签: javascript google-chrome google-chrome-extension


    【解决方案1】:

    我深入研究了这个错误,似乎文档没有很好地反映未实现使用path 参数的事实。这肯定是一个错误,跟踪here

    目前,要解决此问题,您需要在调用 SetIcon 之前加载图像并将其转换为 ImageData 格式。

    // Takes a local path to intended 19x19 icon
    //   and passes a correct SetIcon action to the callback
    function createSetIconAction(path, callback) {
      var canvas = document.createElement("canvas");
      var ctx = canvas.getContext("2d");
      var image = new Image();
      image.onload = function() {
        ctx.drawImage(image,0,0,19,19);
        var imageData = ctx.getImageData(0,0,19,19);
        var action = new chrome.declarativeContent.SetIcon({imageData: imageData});
        callback(action);
      }
      image.src = chrome.runtime.getURL(path);
    }
    
    chrome.declarativeContent.onPageChanged.removeRules(undefined, function () {
      createSetIconAction("images/icons/green.png", function(setIconAction) {
        chrome.declarativeContent.onPageChanged.addRules([
          /* rule1, */
          {
            conditions : [
              new chrome.declarativeContent.PageStateMatcher({
                pageUrl : {queryContains : 'q1=green'}
              })
            ],
            actions    : [ setIconAction ]
          }
        ]);        
      });
    });
    

    如果需要,这可以推广到支持高 DPI 图标(19 + 38):

    function createSetIconAction(path19, path38, callback) {
      var canvas = document.createElement("canvas");
      var ctx = canvas.getContext("2d");
      var image19 = new Image();
      image19.onload = function() {
        ctx.drawImage(image19,0,0,19,19); // fixed
        var imageData19 = ctx.getImageData(0,0,19,19);
        var image38 = new Image();
        image38.onload = function() {
          ctx.drawImage(image38,0,0,38,38);
          var imageData38 = ctx.getImageData(0,0,38,38);      
          var action = new chrome.declarativeContent.SetIcon({
            imageData: {19: imageData19, 38: imageData38}
          });
          callback(action);
        }
        image38.src = chrome.runtime.getURL(path38);
      }
      image19.src = chrome.runtime.getURL(path19);
    }
    

    【讨论】:

    • 既然你说它可以被概括 - 你会这么好心添加那个代码吗?恐怕我无法提出解决方案,而且该错误似乎不会很快得到修复
    • @JörnBerkefeld 完成了,虽然它可能不够优雅,也没有经过测试。
    • 非常感谢 - 使用各种将参数写入 SetIcon 的方式对其进行了测试,但似乎没有实现(或者我们只是不知道如何实现)
    • 另一种方法是使用declarativeContent.RequestContentScript 注入一个内容脚本,该脚本会回调后台页面以更新图标。
    【解决方案2】:

    其实你可以用new chrome.declarativeContent.SetIcon({ path:'yourPath.png' }), 无需指定大小path: {"19": "images/icons/green.png"},其默认值为:16 使用declarativeContent.SetIcon需要注意一个问题,其实是bug。

    path的实际使用最终会自动转换为ImageData。

    看截图:

    declarativeContent.SetIcon错误的根本原因是:它是一个异步API,但同时它没有异步回调。你唯一能做的就是等待。

    const action = new chrome.declarativeContent.SetIcon({ path: 'assets/icon.png' });
    console.log(action.imageData); // =>  undefined
    

    看截图:

    // invalid
    new chrome.declarativeContent.SetIcon({ path: 'assets/icon.png' }, action => console.log(action));  
    

    需要等待一段时间:

    const action = new chrome.declarativeContent.SetIcon({ path: 'assets/icon.png' });
    setTimeout(() => {
      console.log(action.imageData); // {16: ArrayBuffer(1060)}
    }, 5);
    

    看截图:

    当你了解了SetIcon的错误原因,问题就会很好的解决。 只需要将addRules的操作放入事件中即可。

    onInstalled 事件

    const rule2 = { id: 'hideAction', conditions: [...], actions: [new chrome.declarativeContent.SetIcon({ path: 'assets/icon.png' })]};
    
    chrome.runtime.onInstalled.addListener(() => {
      chrome.declarativeContent.onPageChanged.removeRules(undefined, () => {
        chrome.declarativeContent.onPageChanged.addRules([rule2]);
      });
    });
    

    pageAction.onClicked

    const rule2 = { id: 'hideAction', conditions: [...], actions: [new chrome.declarativeContent.SetIcon({ path: 'assets/icon.png' })]};
    
    chrome.pageAction.onClicked.addListener(() => {
      if (condition) {
        chrome.declarativeContent.onPageChanged.removeRules(['hideAction']);
      } else {
        chrome.declarativeContent.onPageChanged.addRules([rule2]);
      }
    });
    

    有一些相关资料:

    SetIcon源代码

    declarativeContent.SetIcon = function (parameters) {
      // TODO(devlin): This is very, very wrong. setIcon() is potentially
      // asynchronous (in the case of a path being specified), which means this
      // becomes an "asynchronous constructor". Errors can be thrown *after* the
      // `new declarativeContent.SetIcon(...)` call, and in the async cases,
      // this wouldn't work when we immediately add the action via an API call
      // (e.g.,
      //   chrome.declarativeContent.onPageChange.addRules(
      //       [{conditions: ..., actions: [ new SetIcon(...) ]}]);
      // ). Some of this is tracked in http://crbug.com/415315.
      setIcon(
        parameters,
        $Function.bind(function (data) {
          // Fake calling the original function as a constructor.
          $Object.setPrototypeOf(this, nativeSetIcon.prototype);
          $Function.apply(nativeSetIcon, this, [data]);
        }, this)
      );
    };
    

    相关问题讨论: http://crbug.com/415315

    【讨论】:

    • 从链接中,显然这个错误自 2016 年以来的状态为 WontFix。
    【解决方案3】:

    无解

    正如我之前提到的,这是一个错误。没有解决方案,只有变通方法。

    解决方法

    #1:使用画布绘制图标

    正如his answer 中已经描述的Xan

    #2 等待图标加载(超时破解)

    感谢weiya-ouanswer 我意识到我可以等待异步图标数据转换完成。

    // Make your handler `async`
    chrome.runtime.onInstalled.addListener(async () => {
      const action = await new chrome.declarativeContent.SetIcon({
        path: {
          19: 'images/19.png',
          38: 'images/38.png',
        },
      })
    
      // THE WAIT STARTS
    
      // Wait max. 10 loops
      for (let i = 0; i < 10; i++) {
        // Create a promise
        const checkAvailability = new Promise((resolve) => {
          // Resolve promise after 100ms
          setTimeout(() => resolve(!!action.imageData), 100)
        })
    
        // Wait for the promise resolution
        const isAvailable = await checkAvailability
    
        // When image available, we are done here
        if (isAvailable) break
      }
    
      // THE WAIT ENDS
    
      const condition = new chrome.declarativeContent.PageStateMatcher({
        pageUrl: { hostEquals: 'my.page.net' },
      })
    
      chrome.declarativeContent.onPageChanged.removeRules(undefined, () => {
        chrome.declarativeContent.onPageChanged.addRules([
          {
            conditions: [condition],
            actions: [action],
          },
        ]);
      });
    });
    

    #3 使用chrome.tabs

    您需要tabs 权限(如here 所说)。

    chrome.tabs.onUpdated.addListener((tabId, { status }, { url }) => {
      // Only check when URL is resolved
      if (status !== 'complete') return
    
      // What is our target page?
      const isOurPage = url?.match(/my\.page\.net/)
    
      if (isOurPage) {
        // Show active icon
        chrome.pageAction.setIcon({
          path: {
            19: 'images/19.png',
            38: 'images/38.png',
          },
          tabId,
        })
      } else {
        // Show inactive icon
        chrome.pageAction.setIcon({
          path: {
            19: 'images/19-inactive.png',
            38: 'images/38-inactive.png',
          },
          tabId,
        })
      }
    })
    

    【讨论】:

      猜你喜欢
      • 2015-11-11
      • 2020-05-15
      • 2015-04-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多