Chrome浏览器插件开发进阶 包括: 脚本注入访问变量,插件前后端通信,跨域请求,本地存储,iframe注入,tab页控制

入门示例

网上已经有很多的示例了, 基础语法和配置都有的, 可以自行 Google/百度 一下就好

找了两篇比较详细的:

这里给出一个简单的示例: 下载

示例文件结构:

├── manifest.json       #核心配置文件
├── popup.html          #点击插件图标的弹出页面
├── popup.js
├── background.js       #后端js
├── content_scripts.js  #前端(插件沙盒环境)注入的js
├── inject.js           #向原始页面注入的本地js
├── jquery-3.4.1.min.js
└── smile.png

脚本注入访问变量

由于前端(content_scripts.js)不能访问原始页面的变量/函数, 但是DOM是共享; 利用这点可以间接的获取到原始页面变量的值

具体做法是在 前端 通过 DOM 载入外部的JS, 而新载入的JS成为了原始网页的一部分, 自然就能访问到全局的变量以及函数, 然后把想要的数据内容序列化(JSON.stringify()), 并存储到页面; 那么 前端 直接就可以从 DOM 把数据取出来, 反序列化(JSON.parse())后就可以使用了

content_scripts.js

// 注入外部js
// 假如原始页面没有jquery, 那么也可以手动注入
function loadJs(url) {
    var s = document.createElement('script');
    s.src = url;
    document.body.appendChild(s);
}
loadJs('//ajax.aspnetcdn.com/ajax/jQuery/jquery-3.4.1.min.js');
// 载入本地(插件内)的js
function loadLocalJs(files) {
    for (var i = 0; i < files.length; i++) {
        var s = document.createElement('script');
        s.src = chrome.extension.getURL(files[i]);
        document.body.appendChild(s);
    }
}
// loadLocalJs(['jquery-3.4.1.min.js', 'inject.js']);
loadLocalJs(['inject.js']);

inject.js

// 最好先检查一下变量, 防止报错
function page_var_save() {
    console.log("================ page_var_save...");
    if ("undefined" == typeof g_config) return;

    var data = g_config;
    var j = JSON.stringify(data);
    console.log(j);
    var d = document.createElement('div');
    d.id = "var-shadow";
    d.style = "display:none;";
    d.appendChild(document.createTextNode(j));
    document.body.appendChild(d);
}

// 加个延时, 方便看结果
setTimeout(function() {
    page_var_save();
}, 3000);

安装插件后, 打开淘宝首页, 在控制台就能看到效果了

插件前后端通信

content_scripts.jsbackground.js 发送消息:

function send_message(data) {
    var param = {type: 'message', data: data};
    chrome.runtime.sendMessage(param, function(response) {
        console.log("send_message log:" + response);
    });
}

background.js 接收到消息, 处理后返回结果:

// message listener ======================================================
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
    // console.log('data:' + JSON.stringify(request.data));
    if (request.type == "message") {
        var data = request.data;
        // do something...
        sendResponse('bg got it...');
    } 
    // 等待 sendResponse 把数据返回
    return true;
});

content_scripts.js / popup.jsbackground.js 后端之间消息通信的方式是一样

background.js 后端 向 content_scripts.js 发送消息时, 需要指定具体的 tabId, 可以通过 chrome.tabs.query参数选项 来过滤出目标 tabId

background.js

chrome.tabs.query({active: true, currentWindow: true}, function(tabs){
    chrome.tabs.sendMessage(tabs[0].id, {type: "open_dialog_box", msg: "hello"}, function(response) {});  
});

// 如果想通过 url 来匹配过滤的话, 可以这样写
chrome.tabs.query({url: "*://*.baidu.com/*"}, function(tabs){
    chrome.tabs.sendMessage(tabs[0].id, {type: "open_dialog_box", msg: "hello"}, function(response) {});  
});

接收消息的方式都是一样的, 参考 background.js 接收消息的方式, 更多示例可参考官方文档: Chrome Extensions - Message Passing

跨域请求

插件如果需要跟服务器进行交互, 如果在 前端 用 jquery 直接发起一个 ajax 请求, 那么可能会遇到两个问题:

  • Mixed Content: The page at 'https://xxx.xxx.com/' was loaded over HTTPS...
  • Cross-Origin Read Blocking (CORB) blocked cross-origin response http://xxx.xxx.com/ with MIME type text/html...

通常需要跟服务端交互的可以放到 background.js 里完成, 这样第一个问题就解决了

第二个是跨域的问题, 请求可以被服务端接收到, 但是浏览器会拒绝返回结果, 导致 ajax 的回调没有值. 如果是单向请求没所谓, 如果要请求数据下来就不行了; 这时需要服务端对 Response 进行设置, 把响应的请求头改一下: Access-Control-Allow-Origin: *

content_scripts.js

function send_detail_log() {
    var param = {type: 'send_detail_log', data: {}};
    chrome.runtime.sendMessage(param, function(response) {
        console.log("send_detail_log log:" + JSON.stringify(response));
    });
}

background.js

// message listener ======================================================
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
    // console.log('data:' + JSON.stringify(request.data));
    if (request.type == "message") {
        var data = request.data;
        // do something...
        sendResponse('bg got it...');
    } else if (request.type == "send_detail_log") {
        // jquery post request
        var param = request.data;
        $.post('http://host/path/detail_log', param, function(data){
            sendResponse(data);
        });
    }
    // 等待 sendResponse 把数据返回
    return true;
});

服务端如果是 flask 的话, 那么参考下面的代码:

def allow_origin(data):
    """ 允许跨域请求 """
    resp = None
    if type(data).__name__ == 'dict':
        resp = jsonify(data)
    else:
        resp = Response(data)
    resp.headers['Access-Control-Allow-Origin'] = '*'
    return resp

本地存储

要使用 chrome.storage API 需要在 manifest.jsonpermissions 里先进行设置

// 本地取值
function get_local_datas() {
    chrome.storage.local.get(["g_status", "g_cnt"], function(result) {
        var g_status = result["g_status"] || 0;
        var g_cnt = result["g_cnt"] || 0;
        console.log("load_local_datas: " + g_status + ", " + g_cnt);
        // do something...
    });
}

/**
 * 本地存值
 * dict = {key: value}
 */
function set_local_data(dict) {
    chrome.storage.local.set(dict);
}

content_scripts.jsbackground.jspopup.js 的调用方式是一样的; 但取值的时候只能在回调里处理结果

iframe注入

这个在 manifest.json 里设置就可以

    "content_scripts": [
        {
            // "matches": ["*://*.taobao.com/*"],
            "matches": ["<all_urls>"],
            "js": ["jquery-3.4.1.min.js", "content_scripts.js"],
            "run_at": "document_end"
        }, {
            "matches": ["*://*.iframe_src.com/*"],
            "js": ["jquery-3.4.1.min.js", "content_scripts.js"],
            "run_at": "document_end",
            "all_frames":true
        }
    ],

关键是需要把 all_frames 设置为 true

tab页控制

由于前端 content_scripts.js 不能访问 chrome.tabs.* 的 API, 所以需要放到 background.js(或popup.js) 来处理

content_scripts.js

function close_tab() {
    chrome.runtime.sendMessage({type: "close_tab"});
}

function open_tab(url) {
    chrome.runtime.sendMessage({type: "open_tab_url", url: url});
}

function change_tab(url) {
    chrome.runtime.sendMessage({type: "change_tab_url", url: url});
}

background.js

// message listener ======================================================
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
    if (request.type == "close_tab") {
        chrome.tabs.remove(sender.tab.id);
    } else if (request.type == "open_tab_url") {
        var url = request.url;
        chrome.tabs.create({url: url, active: false}); //打开tab后不激活tab
    } else if (request.type == "change_tab_url") {
        var tabId = sender.tab.id;
        var url = request.url;
        chrome.tabs.update(tabId, {url: url});
    }
    // 等待 sendResponse 把数据返回
    return true;
});

其他API

popup.js 可以用注入的方式调用前端 content_scripts.js 的代码

popup.js

function injectFrontScript(code) {
    chrome.tabs.executeScript(null, {code:code});
}

$(document).ready(function(){
    $("#test").click(function(){
		var msg = $("#msg").val();
        injectFrontScript("front_test('"+msg+"')");
    });
});

content_scripts.js

function front_test(msg) {
    console.log('front_test...: ' + msg);
}

更多的 API 当然是要去翻翻官方文档了


参考:



blog comments powered by Disqus

Published

03 September 2019

Tags