谷歌浏览器插件带来了很大的方便,于是就想着是不是也可以开发一个来用用。几经折腾下,开发了个CSDN快速 登陆的插件。下面简述一下开发的步骤。
1、开发工具:谷歌浏览器(我开发时用的是chrome 30.0.1599.101 )、HTML/JS/CSS/JSON开发工具(UE、VS等,当然也可以用记事本)
2、开发过程
(1)新建清单文件manifest.json:该名字不可以改。下面是代码
{ "manifest_version": 2, "background": { "page": "main.html"//后台运行页,page和scrptis只能选择一个 }, "browser_action":{ "default_icon":"assets/images/Icon.png",//插件默认图标 "default_title":"__MSG_manifest_iconTitle__",//插件标题 "default_popup":"popup.html"//单击插件图标时弹出的页面 }, "default_locale":"en",//默认本地化语言 "description":"__MSG_manifest_appDescription__",//插件描述 "icons":{//显示的图标 "16":"assets/images/Icon.png", "128":"assets/images/Icon.png" }, "name":"__MSG_manifest_appName__",//插件名字 "options_page":"options.html",//插件选项页 "permissions":[ "proxy", "tabs", "<all_urls>","notifications"],//申请需要的权限 "version":"1.1.1",//插件版本 "minimum_chrome_version":"18.0.0"//浏览器最低版本 }
其中__MSG_manifest_iconTitle__、__MSG_manifest_appDescription__、__MSG_manifest_appName__是依据系统语言的本地化配置,比如在简体中文系统和英文系统时会本地化成相应的语言,见下图
(2)新建弹出页(popup.html)
写HTML和CSS代码,排布出如上图所示的界面
<html xmlns="http://www.w3.org/1999/xhtml"> <head> <link rel="stylesheet" href="assets/styles/popup.css" type="text/css"> <script src="assets/libs/jquery-1.7.2.min.js" type="text/javascript"></script> <script src="assets/scripts/Popup.js" type="text/javascript"></script> </head> <body> <div id="set_config"> <div class="header title" data-i18n-content="popup_setting">Setting</div> <div class="header light"> <table> <tr> <th> <span data-i18n-content="popup_account_id">User</span> </th> <td> <input type="text" id="setting_account_id" value="" /> </td> </tr> <tr> <th> <span data-i18n-content="popup_password">Password</span> </th> <td> <input type="password" id="setting_password" value="" /> </td> </tr> </table> </div> <div class="header light"> <table class="control"> <tr> <td> <button class="button bold" id="setting_login" data-i18n-content="popup_setting_login">Save</button> </td> <td> </td> <td> <button class="button bold" id="setting_cancel" data-i18n-content="popup_setting_cancel">Cancel</button> </td> </tr> </table> </div> </div> </body> </html>
注意:这里面也采用了本地化配置,data-i18n-content="popup_setting"一类的写法,就是进行本地化配置,不过需要通过i18n.js来配套实现。i18n表示的是国际化。人们常把I18N作为“国际化”的简称,其来源是英文单词 internationalization的首末字符i和n。18为中间的字符数。
这里面还用到了popup.js和jquery。jquery是JS库,网上可以下载到。popup.js代码如下
/// <reference path="Config.js"/> /// <reference path="Settings.js"/> /// <reference path="Notify.js"/> var Popup = {}; Popup.CONTROL_IDS = { SET_CANCEL: "setting_cancel", SET_LOGIN: "setting_login", SETTING_ACCOUNT_ID: "setting_account_id", SETTING_PASSWORD: "setting_password" }; $(document).ready(function () { Popup.init(); $("#" + Popup.CONTROL_IDS.SET_LOGIN).click(Popup.setLogin); $("#" + Popup.CONTROL_IDS.SET_CANCEL).click(Popup.setCancel); }); Popup.init = function () { Popup.extension = chrome.extension.getBackgroundPage(); Settings = Popup.extension.Settings;//Settings in Settings.js Config = Popup.extension.Config;//Config in Config.js Notify = Popup.extension.Notify; I18n = Popup.extension.I18n;//I18n in I18n.js I18n.process(document); document.body.style.visibility = "visible"; Popup.initUI(); } Popup.initUI = function () { $("#" + Popup.CONTROL_IDS.SETTING_ACCOUNT_ID).attr("value", Settings.getValue(Config.KEYS.ACCOUNT_ID, '')); $("#" + Popup.CONTROL_IDS.SETTING_PASSWORD).attr("value", Settings.getValue(Config.KEYS.PASSOWRD, '')); } Popup.openPage = function () { Popup.closePopup(); chrome.tabs.create({//新建标签页 url: Config.URLS.LOGIN }); } Popup.openOptions = function () { Popup.closePopup(); extension.openOptions(); } Popup.closePopup = function () { window.close(); } Popup.setCancel = function () { Popup.closePopup(); } Popup.setLogin = function () { Settings.setValue(Config.KEYS.ACCOUNT_ID, $("#" + Popup.CONTROL_IDS.SETTING_ACCOUNT_ID).val()); Settings.setValue(Config.KEYS.PASSOWRD, $("#" + Popup.CONTROL_IDS.SETTING_PASSWORD).val()); Notify.notifyText(Config.FILES.NOTIFY_IMAGE, '', 'OK'); Popup.openPage(); }
说明:
1) /// <reference path="Config.js"/>是VS中为了可以像类一样点出相应的字段和方法的引用方法。
2)$(document).ready是popup.html加载完成后进行相应的操作,比如初始化按钮的点击事件等。chrome是不可以在页面中直接写内连的JS的,那样有安全隐患。
<scritp type="text/javascript"> //内连脚本 function test(){ alert("test");} </script>
如果非要进行内连,那必须将其转换成64码,然后连到src中(直接放到scrpit中的scr中不可以,需要放到iframe中。不知道具体是什么原因。),如下面代码。
<iframe id="sandbox-frame" sandbox="allow-scripts" src="data:text/html;base64,JTNDc2NyaXB0JTIwdHlwZSUzRCUyMnRleHQvamF2YXNjcmlwdCUyMiUzRSUwQWFsZXJ0JTI4JTIyQUFBQSUyMiUyOSUzQiUwQSUzQy9zY3JpcHQlM0U="></iframe>
3)最重要的是Popup.init函数,这个函数将后台的js引用到前台(popup.js是从前台界面popup.html中引用的,所以为前台JS),以便于更好的调用。
Popup.extension = chrome.extension.getBackgroundPage()先把后台引用到前台。
Config = Popup.extension.Config是从后台将Config引用到前台来,其中Config不是Config.js的脚本名字,而是Config.js中的var Config={}。其他的引用同理。
I18n.process(document)这是进行本地化配置。
(3)后台JS
后台JS需要放到main.html中,代码如下
<html> <head> <meta charset="UTF-8" /> <title></title> <script src="assets/libs/jquery-1.7.2.min.js" type="text/javascript"></script> <script src="assets/scripts/CSDN.js" type="text/javascript"></script> <script src="assets/scripts/Settings.js" type="text/javascript"></script> <script src="assets/scripts/Config.js" type="text/javascript"></script> <script src="assets/scripts/I18n.js" type="text/javascript"></script> <script src="assets/scripts/Notify.js" type="text/javascript"></script> </head> <body> </body> </html>
1)CSDN.js
/// <reference path="Config.js"/> var CSDN = {}; chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) {//添加监听事件 var url = tab.url; var config = null; if (tab.status !== "complete") {//需要增加这一判断,不然会在页面加载和加载完成后分别触发 return; } CSDN.login(url); }); CSDN.CONTROL_IDS = { ACCOUNT_ID: "u", PASSWORD: "p", LOGIN: "aLogin", LOGIN_IFRAME: "logfrm" }; CSDN.isLoginUrl = function (url) { if (url === Config.URLS.LOGIN) { return true; } else { return false; } } CSDN.login = function (url) { if (!CSDN.isLoginUrl(url)) { return; } var accountID = Settings.getValue(Config.KEYS.ACCOUNT_ID, ''); var password = Settings.getValue(Config.KEYS.PASSOWRD, ''); var scripts = ""; if (accountID === undefined || accountID === null || accountID.replace(/(^s*)|(s*$)/g, "").length <= 0) { return; } scripts += "var loginIFrame = document.getElementById('" + CSDN.CONTROL_IDS.LOGIN_IFRAME + "');" + " var loginDocument=loginIFrame.contentWindow.document;" + "loginDocument.getElementById('" + CSDN.CONTROL_IDS.ACCOUNT_ID + "').value='" + accountID + "';" + "loginDocument.getElementById('" + CSDN.CONTROL_IDS.PASSWORD + "').value='" + password + "';" + "loginDocument.getElementById('" + CSDN.CONTROL_IDS.LOGIN + "').click();" chrome.tabs.executeScript(null, {//嵌入JS脚本到所打开的页面中。 code: scripts }); }
2)Notify.js
从浏览器中发出桌面通知,代码如下
var Notify = {}; Notify.notifyText = function (iconUrl, title, content) {//这个要触发,必须在manifest.json中添加notifications,以申请桌面通知权限 var notification = window.webkitNotifications.createNotification(iconUrl, title, content); notification.show(); }
3)其他JS代码如下
Config.js
var Config = {}; Config.KEYS = { ACCOUNT_ID: "accountID", PASSOWRD: "password" }; Config.FILES = { NOTIFY_IMAGE: "assets/images/Notify.png" }; Config.URLS = { LOGIN: "https://passport.csdn.net/account/login" };
Setting.js
var Settings = {}; Settings.configCache = {}; Settings.setValue = function setValue(key, value) { Settings.configCache[key] = value; var config = {}; if (localStorage.config) config = JSON.parse(localStorage.config); config[key] = value; localStorage.config = JSON.stringify(config); return value; }; Settings.getValue = function getValue(key, defaultValue) { if (typeof Settings.configCache[key] != "undefined") return Settings.configCache[key]; if (!localStorage.config) return defaultValue; var config = JSON.parse(localStorage.config); if (typeof config[key] == "undefined") return defaultValue; Settings.configCache[key] = config[key]; return config[key]; }; Settings.keyExists = function keyExists(key) { if (!localStorage.config) return false; var config = JSON.parse(localStorage.config); return (config[key] != undefined); }; Settings.setObject = function setObject(key, object) { localStorage[key] = JSON.stringify(object); return object; }; Settings.getObject = function getObject(key) { if (localStorage[key] == undefined) return undefined; return JSON.parse(localStorage[key]); }; Settings.refreshCache = function refreshCache() { Settings.configCache = {}; };
I18n.js
var I18n = {}; I18n.messages = null; I18n.init = function init() { I18n.messages = I18n.readMessages(); // I18n.readMessages(function(messages) { // I18n.messages = messages; // }); }; I18n.buildMessages = function buildMessages() { var result = " "; $("*[data-i18n-content]").each(function (i, item) { result += '"' + item.getAttribute("i18n-content") + '"' + ': { "message": "' + item.innerHTML.replace(/[ ]+/g, " ") + '" }, '; }); $("*[data-i18n-values]").each(function (i, item) { $(item.getAttribute("i18n-values").split(";")).each(function (i, subItem) { var subItemParts = subItem.split(":"); if (subItemParts.length == 2 && subItemParts[0].charAt(0) != ".") { result += '"' + subItemParts[1] + '"' + ': { "message": "' + item.getAttribute(subItemParts[0]).replace(/[ ]/g, "\n") + '" }, '; } }); }); return result; }; I18n.readMessages = function readMessages(callback) { var async = (callback != undefined); var data = null; var request = new XMLHttpRequest(); request.open("GET", chrome.extension.getURL("_locales/en/messages.json"), async); request.onreadystatechange = function () { if (this.readyState == XMLHttpRequest.DONE) { data = this.responseText; data = JSON.parse(data.replace(/[ ]+/g, " ")); if (async) callback(data); } }; request.send(""); return data; }; I18n.getMessage = function getMessage(messageName, substitution) { var result = chrome.i18n.getMessage(messageName, substitution); if (result == undefined || result.length == 0) { var messageObject = I18n.messages[messageName]; if (messageObject != undefined) { result = messageObject.message; if (result != undefined) result = result.replace("$1", substitution); } } return result; }; I18n.process = function process(node) { return I18nTemplate.process(node); }; I18n.init(); //------------------------------------------------------- /** * i18nTemplate: http://src.chromium.org/viewvc/chrome/trunk/src/chrome/browser/resources/i18n_template.js */ var I18nTemplate = (function () { var handlers = { /** * This handler sets the textContent of the element. */ 'data-i18n-content': function (element, attributeValue) { element.innerHTML/*textContent*/ = I18n.getMessage(attributeValue); }, /** * This is used to set HTML attributes and DOM properties,. The syntax * is: attributename:key; .domProperty:key; .nested.dom.property:key */ 'data-i18n-values': function (element, attributeValue) { var parts = attributeValue.replace(/s/g, '').split(/;/); for (var j = 0; j < parts.length; j++) { var a = parts[j].match(/^([^:]+):(.+)$/); if (a) { var propName = a[1]; var propExpr = a[2]; var value = I18n.getMessage(propExpr); if (propName.charAt(0) == '.') { var path = propName.slice(1).split('.'); var object = element; while (object && path.length > 1) { object = object[path.shift()]; } if (object) { object[path] = value; // In case we set innerHTML (ignoring others) we need to // recursively check the content if (path.toString() === 'innerHTML') { process(element); } } } else { element.setAttribute(propName, value); } } } } }; var attributeNames = []; for (var key in handlers) { if (handlers.hasOwnProperty(key)) { attributeNames.push(key); } } var selector = '[' + attributeNames.join('],[') + ']'; function process(node) { var elements = node.querySelectorAll(selector); for (var element, i = 0; element = elements[i]; i++) { for (var j = 0; j < attributeNames.length; j++) { var name = attributeNames[j]; var att = element.getAttribute(name); if (att != null) { handlers[name](element, att); } } } } return { process: process }; })();
开发时参考了SwitchProxy的源码https://code.google.com/p/switchysharp/source/。
转载请注明出处http://blog.csdn.net/xxdddail/article/details/13504259