本文將從個人經驗出發,講述為什么需要Chrome插件,如何開發,如何調試,到哪里找資料,會遇到怎樣的問題以及如何解決等,同時給出一個個人認為的比較典型的例子——獲取網頁內容,和服務器交互,再把信息反饋給用戶。OK,准備開始吧,我盡量把文章寫得好看點,以免讀者打瞌睡。
目錄
為什么需要
簡單地說,瀏覽器插件,可以大大的擴展你的瀏覽器的功能。包括但不僅限於這些功能:捕捉特定網頁的內容,捕捉HTTP報文,捕捉用戶瀏覽動作,改變瀏覽器地址欄/起始頁/書簽/Tab等界面元素的行為,與別的站點通信,修改網頁內容……給你增加許多想象空間,試想想看,你可以用它來識別一些網站上的廣告代碼,並直接把這些代碼刪掉,這樣你就不會受到廣告的困擾了,沒錯,如你所願,這樣的插件別人已經開發好了,你可以直接用。不過,也要說瀏覽器插件的弊端,那就是:會帶來一些安全隱患,也可能讓你的瀏覽器變得緩慢甚至不穩定。
為什么是Chrome
因為Chrome的插件開發起來最簡單,總體上看沒什么新的技術,開發語言就是javascript,web前端工程師能很快上手;而Firefox的插件開發則復雜許多,涉及到環境的搭建和一些WEB以外的技術;IE的插件開發就更復雜了,需要熟悉C++和COM技術,當然還要裝微軟的Visual Studio。
這里有篇老外寫的文章,對比Chrome、Opera和Firefox的插件開發的:http://blog.nparashuram.com/2011/10/writing-browser-extensions-comparing.html。
應該說Chrome和Opera的插件的開發都不難,但Firefox的則比較棘手,也許你要問,那為什么Firefox的插件是最豐富的?我想這有些歷史原因,Chrome出來畢竟比較晚,另外幾種瀏覽器提供的插件的功能也是不盡相同的,OK,我們還是言歸正傳吧。
需要准備什么
幾乎是零需求。Chrome瀏覽器和一個文本編輯器即可,文本編輯器最好是帶語法高亮的那種。谷歌對我們做技術的人來說真是太大度了。
如何開始
強烈建議看看官方的說明:https://developer.chrome.com/extensions/getstarted.html。
文章不長,照着文章去做,完成后,你就成功開發了第一個Chrome插件,這個插件會彈出一個小窗口,上面顯示些阿貓阿狗的小圖片。如圖:
這個插件一共有4個文件:
- manifest.json - 所有插件都要有這個文件,這是插件的配置文件,可看作插件的“入口”。
- icon.png - 小圖標,推薦使用19*19的半透明png圖片,更好的做法是同時提供一張38*38的半透明的png圖片作為大圖標,在我后面提供的例子中,我就是那么干的。
- popup.html - 就是你所看到的那個阿貓阿狗的彈出頁面。
- popup.js - 阿貓阿狗頁面所引用的javascript文件。
這里千萬千萬注意了,我當初沒仔細看popup.html里有一小段注釋,這一小段注釋說:出於安全考慮,javascript必須與html分開存放。而我想嘛,一個小測試程序,沒必要分開吧,直接寫一起不就行了嗎?結果javascript死活執行不了,我翻來覆去找不到原因,還以為彈出的小窗口不支持javascript,在網上搜索了半天又沒有結果,最后才發現是這個原因,浪費了許多時間,這個事情也一定程度上說明了:細節決定成敗。
manifest.json中的內容也非常顯而易見,我選擇其中幾個屬性講一下:
{ "manifest_version": 2, "name": "One-click Kittens", "description": "This extension demonstrates a browser action with kittens.", "version": "1.0", "permissions": [ "https://secure.flickr.com/" ], "browser_action": { "default_icon": "icon.png", "default_popup": "popup.html" } }
"manifest_version":現在應該總是2。
"permissions":很重要的東西,即允許插件做哪些事情,訪問哪些站點,假如一個插件的"permissions"里寫有“http://*.hacker.com/”,那么這個插件就被允許訪hacker.com上的所有內容,包括可能會把你的一些個人信息提交給hacker.com,危險性不言而喻,查看一個插件能訪問那些站點的方法是:在chrome的地址欄里輸入“chrome://extensions/”(注意:這個頁面我們之后要頻繁用到,請收藏一下),然后點對應插件的旁邊的那個“權限”,如:
"default_popup":用來指定點擊小圖標后彈出的小窗口中默認顯示的是哪個html,這個彈出的小窗口就叫做“popup”。
"browser_action":這是一個瀏覽器級的動作,也就是說,不管你現在在訪問哪個頁面,那個小按鈕總是顯示出來,而我們的插件如果僅僅是針對某些頁面的話,就不適合用這個"browser_action"了。下面我們來弄一個只有訪問博客園(www.cnblogs.com)才會出現的小按鈕。
Page Action
chrome-plugin-page-action-demo.7z
這個插件只有4個文件,其中兩個還是圖標,那就只剩下一個必須的manifest.json和一個background.js了。
mainifest.json:
{ "manifest_version": 2, "name": "cnblogs.com viewer", "version": "0.0.1", "background": { "scripts": ["background.js"] }, "permissions": ["tabs"], "page_action": { "default_icon": { "19": "cnblogs_19.png", "38": "cnblogs_38.png" }, "default_title": "cnblogs.com article information" } }
注意:這里是“page_action”而不是“browser_action”屬性了。
“permissions”屬性里的“tabs”是必須的,否則下面的js不能獲取到tab里的url,而這個url是我們判斷是否要把小圖標show出來的依據。background是什么概念?這是一個很重要的東西,可以把它認為是chrome插件的主程序,理解這個很關鍵,一旦插件被啟用(有些插件對所有頁面都啟用,有些則只對某些頁面啟用),chrome就給插件開辟了一個獨立的javascript運行環境(又稱作運行上下文),用來跑你指定的background script,在這個例子中,也就是background.js。
background.js
function getDomainFromUrl(url){ var host = "null"; if(typeof url == "undefined" || null == url) url = window.location.href; var regex = /.*\:\/\/([^\/]*).*/; var match = url.match(regex); if(typeof match != "undefined" && null != match) host = match[1]; return host; } function checkForValidUrl(tabId, changeInfo, tab) { if(getDomainFromUrl(tab.url).toLowerCase()=="www.cnblogs.com"){ chrome.pageAction.show(tabId); } };
chrome.tabs.onUpdated.addListener(checkForValidUrl);
代碼中,我們使用了一個正則表達式去匹配url,獲取出其中的domain部分,如果domain部分是“www.cnblogs.com”的話,就把小圖標show出來,效果如下:
當然了,你現在點那個小圖標的話,是沒有任何反應的,我沒有像官方提供的那個例子那樣提供了popup。OK,現在是時候描述下chrome插件的結構了。
Chrome插件結構
需要聲明的是,這個結構圖是我自己畫的,代表我對Chrome插件的理解,可能並不全面,甚至還不是十分准確,但找來找去找不到現成的,只好自己動手,如有謬誤,請不吝指出。
如圖,manifest.json作為插件的配置文件,同時可以看作程序的“入口”,因為它指定了顯示什么圖標,background script有哪些文件,content script又有哪些文件,pop up的頁面是什么,等等。
什么是popup,什么是background script,相信大家都清楚了,那什么是content script呢?content script就是我們要注入到頁面中的腳本,插件允許我們往網頁中注入腳本,這是一個多么讓人有想象力的功能,其功能之強大無需多解釋,總的來說,就是讓我們全面干預頁面的內容!也許你馬上會想到,這可能帶來很大的安全隱患,沒錯,有些惡意插件會竊取你的頁面信息,而有些有漏洞的插件則可能讓你遭受跨站腳本注入(XSS)的攻擊;另一個可能你會想到的問題是:往頁面中注入自己的腳本,難道不會跟頁面原本的腳本發生沖突嗎?能想到這點說明你真的很厲害,如果我們的注入腳本和頁面原本的腳本處於同一個運行環境中,確實會發生沖突,所以,Chrome是另外開辟了一個獨立的運行空間,供我們的Content Script使用的,Content Script能訪問DOM的內容,但卻不能訪問頁面原本的腳本(我是說直接訪問不行),反之,頁面原本的腳本也不能直接訪問Content Script。在圖中,淺紅色的背景塊代表Content Script的運行環境,而淺藍色的背景塊代表頁面運行環境,另外插件的運行環境我用淺綠色表示,注意,這是三個不同的運行環境,調試的時候你會充分體會到它們的不同。
那么,Content Script會在什么時候運行呢?默認情況下,是在網頁加載完了和頁面腳本執行完了,頁面轉入空閑的情況下(Document Idle),但這個是可以改變的,詳情可參考https://developer.chrome.com/extensions/content_scripts.html,查看其中的“run_at”。
由於處於不同的運行環境中,Content Script和Background Script不能直接互相訪問,那它們之間如何通信?通過Message!這個之后的代碼中會有。
學習資料
理解了Chrome插件結構之后,我相信你完全有能力開發一款自己的插件了,當然了,你得自己去google一些資料,這里我就分享下我的方法。
首先,官方的資料一定得看看,https://developer.chrome.com/extensions/index.html,這個上面的資料得大致瀏覽一下(不需要全部仔細看),這樣你能夠明白一些術語,知道如何去尋找你的解決方案。
再則,官方提供的例子,可以看看,https://developer.chrome.com/extensions/samples.html,我發現上面的例子有些已經不能用於新版的Chrome了,但沒關系,你只要找你想要的就行了,也不用一個個嘗試,就根據你的需要,挑選幾個你感興趣的看看即可。
遇到問題,怎么辦?當然是用google去查找問題,但這里我最最最強烈推薦stackoverflow.com,這簡直是解決問題的神器!不多解釋了,用過便知。
學習過程基本上就是:看個大概,寫點代碼,調試調試。就可以了。哦,大前提當然是你得有javascript的基礎。(你:呵呵,你在逗我吧!)
我的例子
chrome-plugin-cnblogs-article-information.zip
chrome-plugin-cnblogs-article-info-server.zip (服務器端,PHP代碼)
好,輪到我的例子登場了。它的功能是這樣的:當你瀏覽博客園的時候,它會啟動並嘗試獲取你瀏覽的文章的信息(標題、作者和日期),再通過往另一個服務器發送請求的方式,記錄和獲取你第一次訪問這篇文章的時間,把這個時間連帶文章的信息,顯示在popup上。聽起來挺無聊的功能,但關鍵是為了演示嘛,如圖:
這個插件一共有9個文件,新出現的文件有兩個(其它相信大家都很熟悉了),一個是“content_script.js”,這就是前面提到的Content Script,獲取和修改頁面的內容就靠它了;另一個是“jquery-2.0.0.min.js”,大名鼎鼎的jQuery,我很喜歡用的js庫,其理念是“write less,do more”,能幫我減少很多代碼,這是目前最新的2.0.0版,這個版本跟以前的1.x.x的最大差別就是不再支持IE6、7和8,我個人是十分贊同這種做法的,微軟的舊版瀏覽器都成了Web技術發展的絆腳石了,而且這次我們用的是Chrome瀏覽器,果斷選擇最新版了。
另外還有一個服務器端,為了讓問題簡化,這次我用了php代碼,一個php文件就是整個處理了,沒有太多繁雜的配置,簡潔,這是php最大的優勢。系統結構如圖:
抓取網頁的內容得依靠content_script.js,然后通過sendMessage/onMessage和background.js交換數據,background.js將url信息通過ajax(XMLHttpRequest)發送給localhost,獲取此頁面的第一次訪問的時間,最后,用戶點小圖標,popup.html出現,popup.html會讀取(代碼在popup.js中)background.js中的articleData的數據,把它顯示出來。這就是整個過程。
我抓取網頁數據的方式並不能確保所有的博客園的文章都能被正常獲取,這跟用戶使用的博客模板有關系,但我嘗試下來大多數文章還是可以抓取的,我不去適應所有的模板了,畢竟這只是個演示的demo。
另外還需說明的一點是我使用了jQuery做XMLHttpRequest,post的內容不是傳統的html表單形式,而是json數據,所以在服務器端這邊,就不能直接用$_REQUEST獲取,而是通過讀取“php://input”的內容獲取。順便談談個人對web api的一個看法:“統一”大於“靈活”,這是我的觀點,我確定我的接口的格式是json,使用utf-8編碼,於是就一直用下去,調用者不用考慮用XML還是html表單還是別的,開發者也不必多考慮,讓這成為一種統一的約定,在團隊協助和以后的開發中會很省事。
調試
程序開發,必定要涉及到調試,記得我剛開始做WEB開發前,問一些做了好久WEB開發的朋友,你們是怎樣做javascript調試的,我發覺大多數人竟然回答:用alert一點點試吧——不是不行,是太原始,太低效了,對吧?其實Chrome直接支持javascript的調試,擁有了Chrome,就相當於擁有了一個強大的javascript調試器了。
Chrome打開開發者工具的方法是<Ctrl>+<Shift>+<I>(Windows版),大致如下:
我們這次需要關心的有“Elements”、“Sources”和“Console”這三個標簽。Elements是用來做DOM分析的,功能有點類似Firebug,幫助我們分析頁面的內容;而Sources,是我們用來調試javascript的;Console則是我們的Log的輸出窗口,也是一個調試利器。
調試Content Script
如我提供的這個例子,可在Sources的“Copntent Scripts”下看到“content_script.js”然后設斷點,執行到斷點處時,Chrome會挺住,你可以觀察到上面的值,如圖:
太cool了,請問你還要一點點alert嗎?
調試Background
由於background和content script並不在同一個運行環境中,因此上面的方法是看不到Background的javascript的。要調試Background,還需要打開插件頁,也就是“chrome://extensions”。點對應的插件的“generated background page.html”,就出現了調試窗口,接下來的操作就跟前面的類似了。如圖:
至於你看到ID,“aajnhhjiia……”這一長串東西,這是chrome自動安排的一個ID。
調試Popup
雖然Popup和Background是處於同一運行環境中,但在剛才的Background的調試窗口中是看不到Popup的代碼的。調試Popup還需要這樣:
然后……就跟前面差不多了。
一些問題
也許有時候你會發覺調試器不是很靈,至少我用下來感覺如此,比如你可能發現斷點設不了,或者斷點不起作用,或者看不到你自己的javascript文件。我的方法是在插件頁中,把對應的插件的“已啟用”這個復選框去掉,再重新勾上,然后再點一下“重新加載(Ctrl+R)”,通常能解決問題。當然了,還有些很古怪的問題,還不好重現,總體的解決思路就是重新載入一下,實在不行的話重啟瀏覽器,或者清除瀏覽器緩存什么的,再試試看。
在做插件調試的時候我還遇到一個十分郁悶的問題,那就是我的Chrome使用了“Go Agent”,關於Go Agent是用來干嘛的,這個嘛,可以去google一下,我相信絕大多數程序員都會喜歡上它……可由於使用了這個東西,很可能會導致插件的XMLHttpRequest工作不正常,而且可能你會思索半天也找不到原因,好吧,暫時把Go Agent停用掉,甚至可能你需要重啟下Chrome——我的經驗。
總結
我還是想說,我覺得Google對我們程序員來說是個很大度的公司,在Chrome這個產品上面就可見一斑。利用Chrome插件技術,我們可以做許多有用的東西,通過本文,相信你已經知道如何去開發一款Chrome插件了,當然了,Chrome插件的功能是很強大的,我用到的僅是冰山一小角。要深入,當然還需要更加充分地利用google和stackoverflow.com了。