谷歌浏览器扩展 - 获取DOM内容

164

我正在尝试从我的弹出窗口访问activeTab DOM内容。这是我的清单文件:

{
  "manifest_version": 2,

  "name": "Test",
  "description": "Test script",
  "version": "0.1",

  "permissions": [
    "activeTab",
    "https://api.domain.com/"
  ],

  "background": {
    "scripts": ["background.js"],
    "persistent": false
  },
  "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",

  "browser_action": {
    "default_icon": "icon.png",
    "default_title": "Chrome Extension test",
    "default_popup": "index.html"
  }
}

我真的很困惑,是使用后台脚本(带有 persistence: false 的事件页面)还是内容脚本更好。我已经阅读了所有文档和其他 SO 帖子,但对我来说仍然毫无意义。

有人能解释一下为什么我可能会选择其中之一吗?

这是我一直在尝试的 background.js:

chrome.extension.onMessage.addListener(
  function(request, sender, sendResponse) {
    // LOG THE CONTENTS HERE
    console.log(request.content);
  }
);

我只是从弹出式控制台执行这个命令:

chrome.tabs.getSelected(null, function(tab) {
  chrome.tabs.sendMessage(tab.id, { }, function(response) {
    console.log(response);
  });
});

我得到:

Port: Could not establish connection. Receiving end does not exist. 

更新:

{
  "manifest_version": 2,

  "name": "test",
  "description": "test",
  "version": "0.1",

  "permissions": [
    "tabs",
    "activeTab",
    "https://api.domain.com/"
  ],

  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["content.js"]
    }
  ],

  "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",

  "browser_action": {
    "default_icon": "icon.png",
    "default_title": "Test",
    "default_popup": "index.html"
  }
}

内容.js

chrome.extension.onMessage.addListener(
  function(request, sender, sendResponse) {
    if (request.text && (request.text == "getDOM")) {
      sendResponse({ dom: document.body.innerHTML });
    }
  }
);

弹出窗口.html

chrome.tabs.getSelected(null, function(tab) {
  chrome.tabs.sendMessage(tab.id, { action: "getDOM" }, function(response) {
    console.log(response);
  });
});
当我运行它时,我仍然得到相同的错误:
undefined
Port: Could not establish connection. Receiving end does not exist. lastError:30
undefined
5个回答

226

如果 "background page"、"popup" 和 "content script" 这些术语仍然让您感到困惑,我强烈建议您更深入地查看 Google Chrome 扩展文档

关于您的问题,内容脚本或后台页面哪个更好:

内容脚本:绝对
内容脚本是扩展中唯一可以访问网页 DOM 的组件。

后台页面 / 弹出窗口: 或许(两者之一最多)
您可能需要将内容脚本传递给后台页面或弹出窗口以进行进一步处理。


请再次注意,我强烈建议您仔细阅读可用的文档!
话虽如此,这里有一个示例扩展,它从 StackOverflow 页面检索 DOM 内容并将其发送到后台页面,后台页面随后在控制台中打印该内容:

background.js:

// Regex-pattern to check URLs against. 
// It matches URLs like: http[s]://[...]stackoverflow.com[...]
var urlRegex = /^https?:\/\/(?:[^./?#]+\.)?stackoverflow\.com/;

// A function to use as callback
function doStuffWithDom(domContent) {
    console.log('I received the following DOM content:\n' + domContent);
}

// When the browser-action button is clicked...
chrome.browserAction.onClicked.addListener(function (tab) {
    // ...check the URL of the active tab against our pattern and...
    if (urlRegex.test(tab.url)) {
        // ...if it matches, send a message specifying a callback too
        chrome.tabs.sendMessage(tab.id, {text: 'report_back'}, doStuffWithDom);
    }
});

content.js:

// Listen for messages
chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) {
    // If the received message has the expected format...
    if (msg.text === 'report_back') {
        // Call the specified callback, passing
        // the web-page's DOM content as argument
        sendResponse(document.all[0].outerHTML);
    }
});

manifest.json:

{
  "manifest_version": 2,
  "name": "Test Extension",
  "version": "0.0",
  ...

  "background": {
    "persistent": false,
    "scripts": ["background.js"]
  },
  "content_scripts": [{
    "matches": ["*://*.stackoverflow.com/*"],
    "js": ["content.js"]
  }],
  "browser_action": {
    "default_title": "Test Extension"
  },

  "permissions": ["activeTab"]
}

9
@solvingPuzzles: chrome.runtime.sendMessage 可以向后台页(BackgroundPage)和弹出窗口(Popups)发送消息。而 chrome.tabs.sendMessage 则可以向内容脚本(ContentScripts)发送消息。 - gkalpak
51
因为这个回答没有解释如何从当前选项卡中获取实际的DOM,所以被Downvoted。 - John Paul Barbagallo
3
问题是如何获取DOM内容,而不是访问/操作实际的DOM。我认为我的答案已经解决了这个问题(其他人似乎也是这样认为)。如果你有更好的解决方案,请将其发布为答案。如果你有不同的要求,请发布一个新的问题。无论如何,感谢您的反馈 :) - gkalpak
2
@zoltar: 它被打印在背景页的控制台中。 - gkalpak
2
我已经复制粘贴了这个答案,但是无法从内容脚本中获得任何console.log。请帮忙! - ClementWalter
显示剩余17条评论

105

更新至清单v3

chrome.tabs.executeScript在清单v3中不再可用,请注意本回答评论中的说明。相反,使用chrome.scripting。您可以指定一个独立的脚本来运行,而不是一个函数,或者指定一个函数(无需将其转换为字符串!)。

请记住,您的manifest.json文件需要包括:

...
"manifest_version": 3,
"permissions": ["scripting"],
...

你不必使用消息传递来获取或修改DOM。我使用了chrome.tabs.executeScript。在我的示例中,我只使用了activeTab权限,因此脚本仅在活动选项卡上执行。

清单文件的一部分

"browser_action": {
    "default_title": "Test",
    "default_popup": "index.html"
},
"permissions": [
    "activeTab",
    "<all_urls>"
]

index.html

<!DOCTYPE html>
<html>
  <head></head>
  <body>
    <button id="test">TEST!</button>
    <script src="test.js"></script>
  </body>
</html>

test.js

document.getElementById("test").addEventListener('click', () => {
    console.log("Popup DOM fully loaded and parsed");

    function modifyDOM() {
        //You can play with your DOM here or check URL against your regex
        console.log('Tab script:');
        console.log(document.body);
        return document.body.innerHTML;
    }

    //We have permission to access the activeTab, so we can call chrome.tabs.executeScript:
    chrome.tabs.executeScript({
        code: '(' + modifyDOM + ')();' //argument here is a string but function.toString() returns function's code
    }, (results) => {
        //Here we have just the innerHTML and not DOM structure
        console.log('Popup script:')
        console.log(results[0]);
    });
});

2
你声称只使用了“activeTab”权限的说法是不准确的。除了“activeTab”之外,你显然还获取了“<all_urls>”。 - Makyen
1
test.js是您在页面HTML中包含的脚本,因此我不确定您是否需要任何权限。 - Scott Baker
在 Manifest 3 的 manifest.json 中,"browser_action" 已经改为只有 "action"。 - Gomida Matheesha Jayasinghe

15

针对那些尝试过gkalpak的答案,但没有起作用的人,

请注意,Chrome只会在启动时启用您的扩展程序并添加内容脚本到需要的页面,因此建议在修改后重新启动浏览器。


谷歌在其文档中指定,内容脚本需要刷新相关页面以及扩展程序,才能看到更改。 - Joe Moore
Google在他们的文档中指出,内容脚本需要刷新相关页面以及扩展程序,才能看到更改。 - undefined

4

我无法使上述工作。以下是我使用V3的最简单设置,可以正常工作。

manifest.json

{
  "name": "DOM Reader",
  "version": "1.0",
  "manifest_version": 3,
  "description": "Reads the content of a page.",
  "permissions": [
    "scripting",
    "activeTab"
  ],
  "action": {
    "default_popup": "index.html"
  }
}

index.html

<!DOCTYPE html>
<html>
  <head></head>
  <body>
    <button id="read-content">Read content</button>
    <script src="contentScript.js"></script>
  </body>
</html>

contentScript.js

document.getElementById('read-content').addEventListener('click', () => {
    chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
        const tab = tabs[0];

        function printTitle() {
            const title = document.title;
            console.log(title);
        };

        chrome.scripting.executeScript({
            target: { tabId: tab.id },
            func: printTitle,
            //        files: ['contentScript.js'],  // To call external file instead
        }).then(() => console.log('Injected a function!'));
    });
});

executeScript()中的输出代码将在目标选项卡的上下文中运行(即打印到控制台),而不是插件弹出窗口/操作。在这个基本示例中缺少的下一步是将executeScript()函数的"output"传递给扩展的操作,并在那里显示它。 - wesinat0r

2
这是一个 v3 版本的清单,将 DOM 内容传递给扩展的弹出上下文(动作页面),使用消息传递(https://developer.chrome.com/docs/extensions/mv3/messaging/

manifest.json

{
  "name": "DOM Reader",
  "version": "1.0",
  "manifest_version": 3,
  "description": "Reads the content of a page.",
  "permissions": [
    "scripting",
    "activeTab"
  ],
  "action": {
    "default_popup": "index.html"
  }
}

index.html

<!DOCTYPE html>
<html>
  <head></head>
  <body>
    <button id="read-content">Read content</button>
    <script src="contentScript.js"></script>
    <div id='result-div' style="width:500px">
        <code id='result'></code>
    </div>
  </body>
</html>

contentScript.js

document.getElementById('read-content').addEventListener('click', () => {
    chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
        const tab = tabs[0];
        
        function printTitle() {
            //console.log("inside printTitle func");
            const title = document.title;
            var resultStr = "doc title: " + document.title;
            console.log(resultStr);
            
            // https://developer.chrome.com/docs/extensions/mv3/messaging/
            (async () => {
                const response = await chrome.runtime.sendMessage({info: resultStr});
                // do something with response here, not outside the function
                console.log(response);
            })();
            
            //return resultStr;
        };

        chrome.scripting.executeScript({
            target: { tabId: tab.id },
            func: printTitle,
            //        files: ['contentScript.js'],  // To call external file instead
        }).then(() => console.log('Injected a function!'));
    });
});

chrome.runtime.onMessage.addListener(
  function(request, sender, sendResponse) {
    console.log(sender.tab ?
                "from a content script: " + sender.tab.url :
                "from the extension");
    var resp = request.info;
    if (resp) {
        document.getElementById("result").innerText = resp;
        sendResponse({farewell: "thanks for sending! goodbye"});
    }
  }
);

chrome.runtime.sendMessage()executeScript函数中使用,用于将内容传递给操作弹出窗口。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接