node-tutorial icon indicating copy to clipboard operation
node-tutorial copied to clipboard

Chrome插件开发

Open Wscats opened this issue 8 years ago • 1 comments

5种类型的JS对比

Chrome插件的JS主要可以分为这5类:injected scriptcontent-scriptpopup jsbackground jsdevtools js

权限对比

JS种类 可访问的API DOM访问情况 JS访问情况 直接跨域
injected script 和普通JS无任何差别,不能访问任何扩展API 可以访问 可以访问 不可以
content script 只能访问 extension、runtime等部分API 可以访问 不可以 不可以
popup js 可访问绝大部分API,除了devtools系列 不可直接访问 不可以 可以
background js 可访问绝大部分API,除了devtools系列 不可直接访问 不可以 可以
devtools js 只能访问 devtools、extension、runtime等部分API 可以 可以 不可以

调试方式对比

JS类型 调试方式 图片说明
injected script 直接普通的F12即可 懒得截图
content-script 打开Console,如图切换
popup-js popup页面右键审查元素
background 插件管理页点击背景页即可
devtools-js 暂未找到有效方法 -

互相通信概览

注:-表示不存在或者无意义,或者待验证。

injected-script content-script popup-js background-js
injected-script - window.postMessage - -
content-script window.postMessage - chrome.runtime.sendMessage chrome.runtime.connect chrome.runtime.sendMessage chrome.runtime.connect
popup-js - chrome.tabs.sendMessage chrome.tabs.connect - chrome.extension. getBackgroundPage()
background-js - chrome.tabs.sendMessage chrome.tabs.connect chrome.extension.getViews -
devtools-js chrome.devtools. inspectedWindow.eval - chrome.runtime.sendMessage chrome.runtime.sendMessage

background和content_scripts的通信

方案1

因为background是插件一直运行的主程,而content_scripts是往内容页面注入的脚本,并且它拥有最多的权限(比如跨域,读写文件等),但是与内容页面只共享DOM,不能共享数据,所以我们需要配合background和content_scripts来做通信

background.js

chrome.contextMenus.create({
  "title": "启动", "type": "normal", //菜单项类型 "checkbox", "radio","separator"
  "onclick": function (info, tab) {
    chrome.tabs.executeScript(tab.id, {
      code: `
        // 插入JS脚本
    `}, () => {
        chrome.tabs.sendMessage(tab.id, {
          greeting: "hello to content script!"
        }, function (response) {
          console.log(response);
        });
      });
  }
});

content_scripts.js

chrome.extension.onMessage.addListener(
  function(request, sender, sendResponse) {
      console.log(request);// {greeting: "hello to content script!"}
      console.log("前端/后端/Popup收到");
      sendResponse("popup返回值");
  }
);

方案2

background.js

chrome.contextMenus.create({
  "title": "通信", "type": "normal", //菜单项类型 "checkbox", "radio","separator"
  "onclick": function (info, tab) {
    chrome.tabs.executeScript(tab.id, {
      code: `
        // 在隔离环境下清楚定时器
        window.postMessage({ type: "FROM_PAGE", text: "来自网页的问候!" }, "*");
      `})
  }, //单击时的处理函数
});

content_scripts.js

// 跟隔绝区域的JS通信
window.addEventListener("message", function (event) {
  // 我们只接受来自我们自己的消息
  if (event.source != window)
    return;

  if (event.data.type && (event.data.type == "FROM_PAGE")) {
    console.log("内容脚本接收到:" + event.data.text);
  }
}, false);

Tampermonkey

我们可以通过下载Tampermonkey实现插件功能,这是一个插件的管理工具,可以通过Greasyfork下载第三方脚本配合Tampermonkey实现一些功能

inject.js

在 content_scripts.js 可以写入以下函数来往页面注入 inject.js ,从而可以借 inject.js 之手直接操作 DOM 和获取 JS 因为 content_scripts.js 只能共享 DOM 不能共享 JS

// 向页面注入JS
function injectCustomJs(jsPath) {
  jsPath = jsPath || 'static/js/inject.js';
  var temp = document.createElement('script');
  temp.setAttribute('type', 'text/javascript');
  // 获得的地址类似:chrome-extension://ihcokhadfjfchaeagdoclpnjdiokfakg/js/inject.js
  temp.src = chrome.extension.getURL(jsPath);
  temp.onload = function () {
    // 放在页面不好看,执行完后移除掉
    this.parentNode.removeChild(this);
  };
  document.body.appendChild(temp);
}
injectCustomJs()

inject.js

console.log("监听所有input输入框")
// 获取所有输入框节点
let inputs = document.querySelectorAll("input");
// 遍历所有input节点
[].forEach.call(inputs, (input) => {
    input.addEventListener("input", (e) => {
        // 打印输入的值
        console.log(e.target)
        console.log(e.data)
    })
});

需要注意的是在 manifest.json 添加这句

{
    // 普通页面能够直接访问的插件资源列表,如果不设置是无法直接访问的
    "web_accessible_resources": ["static/js/inject.js"],
}

难点

页面为https协议时候发送http请求回产生跨域,所以可以尝试升级为https请求

// 将所有http请求升级为https请求
var meta = document.createElement("meta");
meta.httpEquiv = "Content-Security-Policy";
meta.content = "upgrade-insecure-requests";
document.head.appendChild(meta)

参考文档

Wscats avatar Oct 25 '17 01:10 Wscats

推荐的方法

popup.js => content.js or background.js

这里可以实现点击图标然后触发 content.js 来获取页面的 DOM 元素或者调用 background.js 的API功能

// popup.js

document.querySelector('#btn').addEventListener('click', (e) => {
    console.log(e)
    var bg = chrome.extension.getBackgroundPage();
    bg.test(); // 访问bg的函数  意味着这里能够直接调用background.js
    sendMessageToContentScript({
        cmd: 'test',
        value: 'hello'
    }, function (response) {
        console.log('来自content的回复:' + response);
    });
})
function sendMessageToContentScript(message, callback) {
    chrome.tabs.query({
        active: true,
        currentWindow: true
    }, function (tabs) {
        chrome.tabs.sendMessage(tabs[0].id, message, function (response) {
            if (callback) callback(response);
        });
    });
}
// content.js
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
  // console.log(sender.tab ?"from a content script:" + sender.tab.url :"from the extension");
  if (request.cmd == 'test') {
    console.log(document.querySelector("#su"))
    console.log(request.value)
    // 这里是 content.js 跟 background.js 通信
    chrome.runtime.sendMessage({greeting: '你好,我是content-script呀,我主动发消息给后台!'}, function(response) {
      console.log('收到来自后台的回复:' + response);
    });
  }
  sendResponse('我收到了你的消息!');
});
// background.js
function test() {
    console.log("后台")
}

content.js => background.js or popup.js

我们可以利用 content.js 获取 DOM, 然后触发 background.js 的一些功能,因为 background.js 权限比较多(比如实现跨域)

// content.js
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
  // console.log(sender.tab ?"from a content script:" + sender.tab.url :"from the extension");
  if (request.cmd == 'test') {
    console.log(document.querySelector("#su"))
    console.log(request.value)
    // 这里是 content.js 跟 background.js 通信
    chrome.runtime.sendMessage({greeting: '你好,我是content-script呀,我主动发消息给后台!'}, function(response) {
      console.log('收到来自后台的回复:' + response);
    });
  }
  sendResponse('我收到了你的消息!');
});
// background.js
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
    console.log('收到来自content-script的消息:');
    console.log(request, sender, sendResponse);
    sendResponse('我是后台,我已收到你的消息:' + JSON.stringify(request));
});

注意事项:

  • content_scripts向popup主动发消息的前提是popup必须打开!否则需要利用background作中转;
  • 如果background和popup同时监听,那么它们都可以同时收到消息,但是只有一个可以sendResponse,一个先发送了,那么另外一个再发送就无效;

Wscats avatar Jan 06 '19 14:01 Wscats