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

移除Chrome扩展Manifest权限后,Popup无法获取Content脚本数据

Chrome扩展权限移除后Popup无法获取Content Script数据

我提交Chrome扩展首个版本时,因平台要求处理主机权限,移除了Manifest中以下权限段:

"permissions": [
    "scripting",
    "tabs"
]

移除后,popup中的代码无法获取数据,result返回null;保留权限时可正常工作。

Popup中失效的代码(pop.js)

//Fetching Data from Content Script
(async () => {
  const [tab] = await chrome.tabs.query({active: true, currentWindow: true});
  const [{result}] = await chrome.scripting.executeScript({
    target: {tabId: tab.id},
    func: () => dataPromise,
  }).catch(() => [{}]);
  
  console.log('Result Begin')
  console.log(result)
  console.log('Result Ends')
})();

Content Script核心逻辑片段(content.js)

const dataPromise = chrome.runtime.sendMessage({from:'send_url',msg:document.location.href}).then( result => {
.....
return {'status':resultType1,'missing':missingLinks,'found':foundLinks,'similar':similarLinks}
})

完整Manifest文件

{
    "name": "My Ex",
    "description": "My Ext",
    "version": "1.0.0",
    "manifest_version": 3,
    "background": {
        "service_worker": "background.js"
    },
    "content_scripts": [
        {
            "all_frames": false,
            "run_at" : "document_end",
            "matches": [
                "<all_urls>"
            ],
            "js": [
                "content_scripts/jquery.js",
                "content_scripts/functions.js",
                "content_scripts/content.js"              
            ]
        }
    ],
    "action": {
        "default_popup": "index.html",
        "default_title": "Agile SEO Super Cluster",
        "default_icon": {
            "16": "16x16.png",
            "32": "32x32.png"
        }
    },
    "web_accessible_resources": [
        {
          "resources": ["bootstrap.min.css", "index.html"],
          "matches": ["<all_urls>"]
        }
      ],
    "permissions": [
        "scripting",
        "tabs"
    ]
}

完整Content.js代码

console.log('In Content Script')
console.clear()

const dataPromise = chrome.runtime.sendMessage({from:'send_url',msg:document.location.href}).then( result => {
  var outLinksClean = [] //without http and www
  var uniqueDomains = []
  var pageLinksBeforeAdditional = [] // Links without count attribute which are before see additional..
  var filteredLinks = []
  var outLinks  = []
  var outLinksClusterKeywords  = []
  var outLinks_keywords  = []
  var absLink = ''
  var resultType1 = null
  var  missingLinks = null
  var foundLinks = null
  var foundHeadings = []
  var similarLinks = null

  // console.log(result) // HTML returned from remote API

  if (result.html) {
    var html = '<div id="asc_div"><div id="rendered">' + result.html + '</div></div>';
    var $html = $(html);
    var y = $html.add($('*', $html)) //Flatten the DOM

    outLinks_keywords = result.rules['link_destination']
    
    // Converting Keywords to lowercase
    outLinks_keywords = outLinks_keywords.map(function(a) { 
      a.content_cluster_keyword = a.content_cluster_keyword.toLowerCase();
      return a;
     });
    // console.clear()
    for(var url of outLinks_keywords) {
      outLinks.push(url['content_cluster_main_page_url'])
      outLinksClusterKeywords.push(url['content_cluster_keyword'])
    }
    
    for(var url of outLinks){
      url = url.replace('http://','').replace('https://','').replace('www.','')
      outLinksClean.push(url)
    }

    if(outLinks.length > 0) {
      uniqueDomains = getUniqueDomains(outLinks)
    }

    var new_dom = isolateAdditionalLinks($html.find('#rendered').children())
    var baseDomain = getDomainFromURL(document.location.href)

    var links = new_dom.find('a').each(function(index) {
      if (typeof ($(this).attr('count')) == 'undefined') {                
            if (typeof ($(this).attr('href')) != 'undefined') {
              // similar(outLinksClusterKeywords,$(this).text())
              absLink = relativeToAbsolute($(this).attr('href'),baseDomain)
              // console.log('ABS LINK = '+absLink+' - '+$(this).text())
              // pageLinksBeforeAdditional.push(absLink)
              pageLinksBeforeAdditional.push({'link':absLink,'text':$(this).text().trim()})
            }
        }            
    })
    // These are the links that are relevant to the pages. All Operations will be performed against them
    filteredLinks = filterLinks(uniqueDomains,pageLinksBeforeAdditional) 
    var validationResponse = validateType1(filteredLinks,outLinksClean)
    
    
    resultType1 = validationResponse[0]
    missingLinks = validationResponse[1]
    foundLinks = validationResponse[2]
    similarLinks = validationResponse[3]
    // console.log('Found Links')
    // console.log(foundLinks)
    // console.log('Missing Links')
    // console.log(missingLinks)
    
    // We will only go for optimization if Link Type1 is valid
    invalid_optimization1_links = []
    invalid_optimization2_headings = []
    if(resultType1) {
        // Optimization No1 for Link Type 1
        /**
         * Processing Optimization 1 of Type1.
         * Type1 Optimization 1 says that the anchor text of the valid found
         * links must be exact match or 80% similar
         */
          idx = 0
          var score = 0
          const OPTIMIZATION_COUNT_TYPE_1 = 1
          var optimization_1_score = 0
          var optimization_2_score = 0
          var valid_optimizations_1 = 0
          var valid_optimizations_2 = 0
          var optimization_1_result = false
          var optimization_2_result = false
          var NO_OF_LINKS = foundLinks.length          
          var TOTAL_OPTIMIZATIONS = NO_OF_LINKS * 1 // Since this is for a single opt only
          
          for(var foundLink of foundLinks) {            
            idx++            
            optimization_1_result = validateType1Optimization1(foundLink,outLinks_keywords)            
            
            if(idx%2==0) { // NOTE: TEMP CHECK
              optimization_1_result = false
              invalid_optimization1_links.push(foundLink)
            }

            if(optimization_1_result) { 
              valid_optimizations_1++             
            }
          }
          optimization_1_score = (valid_optimizations_1/TOTAL_OPTIMIZATIONS) * 100

          // Optimization No2 for Link Type 1
          //All Header Tags
          console.clear() // TEMP DO REMOVE IT AFTER DEBUGGING
          var headings = new_dom.find(':header').each(function(index) {
            // optimization_2_result = validateType1Optimization2(outLinksClusterKeywords,$(this).text().toLowerCase())  
            optimization_2_result = validateType1Optimization2($(this).text().toLowerCase(),outLinks_keywords)
            // console.log(`optimization_2_result for ${$(this).text().toLowerCase()}`)
            
            // Saving Headings not found on page
            if(!optimization_2_result) {
              invalid_optimization2_headings.push($(this).text().toLowerCase())
            } else {
              foundHeadings.push($(this).text().toLowerCase())
            }

            if(optimization_2_result) {
              valid_optimizations_2++
            }
          })
          console.clear() // TEMP
          console.log('outLinksClusterKeywords')
          console.log(outLinksClusterKeywords)
          console.log('Found Headings')
          console.log(foundHeadings)

          // This is being done to give suggestions for headings not on Page
          var b1 = new Set(foundHeadings);
          var notFoundHeadings = [...new Set(outLinksClusterKeywords.filter(x => !b1.has(x)))];
          console.log('REMAINING....')
          console.log(notFoundHeadings)
          
          console.log('valid_optimizations_2 = '+valid_optimizations_2)
          console.log('TOTAL_OPTIMIZATIONS = '+TOTAL_OPTIMIZATIONS)
          optimization_2_score = (valid_optimizations_2/TOTAL_OPTIMIZATIONS) * 100
          console.log('optimization_1_score = '+optimization_1_score)
          console.log('optimization_2_score = '+optimization_2_score)
          score = (optimization_1_score+optimization_2_score) / 2 // Because two optimization
          console.log('Final Score = '+score)
    }

    //Sending Message for Visual Status updatation.
    chrome.runtime.sendMessage({from:'set_icon',msg:resultType1}, res => {});        
  }
  return {
    'status':resultType1,
    'missing':missingLinks,
    'found':foundLinks,
    'similar':similarLinks,
    'total_score':score,
    'invalid_optimization1_links':invalid_optimization1_links,
    'invalid_optimization2_headings':notFoundHeadings
  }
})

问题原因与解决方案

原因分析

  1. tabs权限依赖:chrome.tabs.query需要tabs权限才能获取当前标签页的ID信息,移除后调用会失败,导致tab变量无有效值。
  2. scripting权限依赖:chrome.scripting.executeScript必须拥有scripting权限才能向目标标签页注入脚本并获取返回值,无此权限时调用直接进入catch分支,返回[{}],最终result为null。

解决方案

方案1:改用消息传递替代scripting.executeScript

通过Chrome扩展的消息API实现popup与content script的通信,无需依赖tabs和scripting权限:

  • 修改popup代码:
// Popup发送请求,等待content script返回数据
(async () => {
  try {
    const result = await chrome.runtime.sendMessage({from: 'popup_request_data'});
    console.log('Result Begin')
    console.log(result)
    console.log('Result Ends')
  } catch (err) {
    console.error('获取数据失败:', err);
  }
})();
  • 修改content.js代码:
    添加消息监听器,响应popup的数据请求:
// 在content.js末尾添加
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.from === 'popup_request_data') {
    // 返回已处理完成的dataPromise结果
    dataPromise.then(result => sendResponse(result));
    // 异步响应需返回true告知Chrome等待结果
    return true;
  }
});

方案2:保留必要权限并声明主机权限

若平台仅要求明确主机权限而非完全移除权限,可调整Manifest配置,保留核心权限并添加主机匹配规则:

"host_permissions": ["<all_urls>"],
"permissions": ["scripting"]

注:Manifest V3中,仅查询当前窗口活跃标签时,chrome.tabs.query可无需tabs权限(需测试验证),若仍有问题可保留tabs权限。


内容的提问来源于stack exchange,提问作者Volatil3

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 08:25:18