从零开始编写一个Chrome插件

3,089次阅读

共计 2831 个字符,预计需要花费 8 分钟才能阅读完成。

编写一个 chrome 插件是一个很简单的事只要会 javascript 就可以轻松完成这个事情,但是你要完成一个十分完美的插件还是比较困难的,使用插件我们可以做很多有趣的事情,比如我们可以做一个抢火票的插件,自动提醒,自动买票等等。本篇教程将教大家怎么完成一个 chrome 插件。

从零开始编写一个 Chrome 插件

开始工作

插件功能

提取页面所有样式表里面的背景图片显示到插件中

Manifest

manifest.json是插件的配置文件整个插件最重要的文件,配置权限、content 脚本、后台脚本、popup、插件 ICON 都是这个文件。官方文档


{
    "manifest_version": 2, // 扩展使用的 Manifest 版本, 1 是 chrome 版本低于 17 才使用. 目前主流都是用 2

    "name": "ImageSource", // 扩展名称 
    "version": "1.0", // 扩展版本
    "description": "抓取当前页面样式文件中的所有图片源地址", // 扩展描述


    // 设置扩展将在地址右侧显示扩展图标,弹出窗,提示等
    //@see https://developer.chrome.com/extensions/browserAction
    // 当你的扩展是针对某些页面操作的话,你应该使用 page_action
    /**
    "browser_action": {
        "default_icon": {                    // 设置 ICON,不同大小,浏览会选择显示那种 ICON
            "16": "images/icon16.png",           
            "24": "images/icon24.png",           
            "32": "images/icon32.png"            
        },
        "default_title": "Google Mail",      // 设置默认的标题
        "default_popup": "popup.html"        // 设置默认的弹出层页面
    },
    */

    "page_action": {
        "default_icon": "images/icon-32.png",                    // 设置 ICON,不同大小,浏览会选择显示那种 ICON
        "default_title": "图片源",      // 设置默认的标题
        "default_popup": "popup.html"        // 设置默认的弹出层页面
    },

    "icons": {
        "48": "images/icon-48.png",
        "128": "images/icon-128.png"
    },

    // 运行于后台的实例, 后台实例是一直在运行中,打开浏览器只执行一次实例
    // 官方推荐使用事件页面代替背景页面
    //@see 
    "background": {
        //"page": "background.html",
        "scripts": ["js/background.js"],
        "persistent": false 
    },
    // 页面脚本注入
    "content_scripts": [
        {"matches": ["http://*/*", "https://*/*"],
            "js": ["js/content.js"]
        }
    ],
    // 扩展权限
    //@see https://developer.chrome.com/extensions/declare_permissions 权限列表文档
    "permissions": [
        "background", 
        "tabs", 
        "activeTab", 
        "http://*/*",
        "https://*/*"
    ],
    "author": "骑驴找蚂蚁", // 开发者
    "homepage_url": "http://loocode.com" // 项目主页
}

上面是插件的 manifest.json 完整内容.

pageAction

在这个插件中我们使用的是 page_action 而不是 browser_action, 使用page_action 需要开发者自己控制插件的使用状态, 默认情况是下是非使用状态 (开启chrome.pageAction.show(tabId)) 与browser_action相反。官方认为在某些条件下使用才能使用插件应该选择page_action, 比如我是针对某个网站(githubgoogle),或者针对网页内容特定值(jsonrss)。


chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
    var url = tab.url;
    var status = changeInfo.status; //loading and complete two state
    if (url !== undefined && status === \'complete\' && url.indexOf("https://github.com") !== -1) {chrome.pageAction.show(tabId);
    }
});

比如监听 tabs 页的更新事件,当打开的页面是 github 网站的话就把插件更新为活动状态. 这里需要申请 tabs 权限, permissions属性里面填写 tabs 即可, 而 browser_action 不需要这么做。

default_popup

这个属性是设置点击插件弹出的页面层. 也是上面那张事例的显示页面。





    
    
    
    Document
    




Note:
html 中是不可以内嵌内联脚本的,所以必须文件形式引入 `


var query = {active: true, currentWindow: true};
var tab = null;
var bg = chrome.extension.getBackgroundPage();

function ajax(url) {var xhr = new XMLHttpRequest();
    xhr.open("GET", url, true);
    xhr.onreadystatechange = function() {if (xhr.readyState == 4) {
            var content = xhr.responseText,
            reg = /url\("?(.*?\.(png|gif|jpeg|jpg|svg))"?\)/gi,
            match = null, images = [];
            while((match = reg.exec(content)) !== null) {images.push(match[1]);
            }
            if (images.length> 0) {var p = document.createElement("p");
                    p.innerHTML = url,
                    offset = url.lastIndexOf("/")   1,
                    newUrl = url.substring(0, offset),
                    point = newUrl.lastIndexOf(".")   1,
                    domain = null;
                for (var j = 1; j <= 6; j) {if (url.substr(point j, 1) === "/") {domain = url.substring(0, point j);
                        break;
                    }
                }
                document.body.appendChild(p);
                for (var i in images) {var img = document.createElement("img");
                    if (images[i].substring(0,1) === \'/\') {img.setAttribute("src", domain   images[i]);
                    } else {img.setAttribute("src", newUrl   images[i]);
                    }
                    document.body.appendChild(img);
                }
            }
        }
    };
    xhr.send();}

/**
 * 查找当前窗口活动的 tab,并发送消息给 backgorund.js 取得页面的 sheet 表, 
 * 再依次请求 CSS 文件获取内容读出图片地址,显示在 popup.html。* @param {*} tabs 
 */
function callback(tabs) {tab = tabs[0]; // there will be only one in this array
    chrome.runtime.sendMessage({
        type: \'popup\',
        url: tab.url
    }, function(response) {
        var sheet = response.sheet;
        for(var key in sheet) {ajax(sheet[key]);
        }
    });
}
chrome.tabs.query(query, callback);

content_scripts

当前一个 tab 页打开时,chrome 就会执行插件注入的脚本 ("js": ["js/content.js"]), 会根据"matches": ["http://*/*", "https://*/*"] 匹配规则。

注入的脚本和页面中的脚本运行环境是相互独立的互不干扰。注入的脚本可以使用 DOM API 来控制页面的内容和操作。


/**
 * 绑定页面载入后将 sheets 信息发送给 background
 */
window.onload = function(e) {
    var content = document.body.innerHTML;
    var sheet = document.styleSheets;
    var href = [];
    for (var key in sheet) {if (sheet[key].href != null) {href.push(sheet[key].href);
        }
    }
    chrome.runtime.sendMessage({
        type: "content",
        sheet: href,
        url: location.href
    });
}

Note:
建立插件之间的通信或者 background、popup、content_script 之间通信都是通过 sendMessage 来完成。必须有绑定 chrome.runtime.onMessage.addListener 的地方,这里我们绑定在 background 脚本中。

background

插件中的后台脚本在 chrome 打开就会执行,只要有权限几乎所有 API 都可以使用。


var url = {},

/**
 * 根据 url 生成对应的 hash
 */
hashCode = function(url) {
    var hash = 0;
    for (var i = 0; i < url.length; i) {var character = url.charCodeAt(i);
        hash = ((hash<<5)-hash) character;
        hash = hash & hash; // Convert to 32bit integer
    }
    return hash;
}
/**
 * @see https://developer.chrome.com/extensions/pageAction
 */
/**
chrome.pageAction.onClicked.addListener(function(tab) {
    // No tabs or host permissions needed!
    var hash = hashCode(tab.url);
});
*/

/**
 * 删除对应的网页数据
 * @see https://developer.chrome.com/extensions/tabs#on-removed
 */
chrome.tabs.onRemoved.addListener(function(tab) {var hash = hashCode(tab.url);
    if (url.hasOwnProperty(code) === false) {delete url[hash]
    }
});


/** 
chrome.webRequest.onBeforeSendHeaders.addListener(function(request) {console.log(request);
});
*/

/**
 * 监听标签页更新事件
 * @see https://developer.chrome.com/extensions/tabs#on-updated
 */
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
    var url = tab.url;
    var status = changeInfo.status; //loading and complete two state
    if (url !== undefined && status === \'complete\' && url.substring(0, 4) === \'http\') {chrome.pageAction.show(tabId);
    } else {//chrome.pageAction.hide(tabId);
    }
});


/**
 * 监听消息事件
 * @see https://developer.chrome.com/extensions/runtime#event-onMessage
 */
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {var code = hashCode(message.url);
    console.log(message.url);
    console.log(code);
    if (message.type === \'content\') {if (url.hasOwnProperty(code) === false) {url[code] = message.sheet;
        }
    } else if (message.type === \'popup\') {sendResponse({sheet: url[code]});  
    }
    console.log(url);
});

Note:
注意 updated 的状态,是有两种 loading 和 complete。还是消息是通过消息内容来区别消息类型的

插件流程

从零开始编写一个 Chrome 插件

现在整个插件的功能完全实现了,不过还有多很不足之处没有解决。background 活动问题,展现形式都不太好,不过大伙可根据现有的代码去修改。

源码地址

https://github.com/TianLiangZhou/loocode-example/tree/master/chrome-plugin-image-source

下载地址

http://backend.loocode.com/upload/20171103/chrome-plugin-image-source.zip

推荐阅读

  1. https://developer.chrome.com/extensions/getstarted
  2. https://developer.chrome.com/extensions/api_index
  3. http://open.chrome.360.cn/extension_dev/overview.html
  4. https://developer.chrome.com/extensions/samples
  5. https://developer.chrome.com/extensions/manifest

正文完
 0
Blood.Cold
版权声明:本站原创文章,由 Blood.Cold 于2019-06-05发表,共计2831字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。