開發一個用於屏蔽知乎網內容的Chrome擴展

本文原發在個人博客javascript

前段時間電影《瘋狂動物城》上映了,而後個人知乎首頁就被它刷屏了。雖然我對這部電影沒有任何意見,但做爲一個還沒去電影院看過的人來講,每看到相關問題一次都是無情的劇透,因而我毅然屏蔽了「瘋狂動物園」這個話題。本覺得問題解決了,可是接下來我又被迫看到這個問題:html

2016-03-25-zhihu-filter1.png

問題上添加的五個話題無一命中,我又被劇透了一臉。算了,既然知乎的屏蔽規則靠不住,那就本身動手吧。這樣個人Chrome瀏覽器擴展——ZhihuFilter就誕生了,點擊這裏查看Github上的項目java

擴展功能

其實擴展的功能很簡單,當打開知乎首頁後,擴展會依次檢查你的屏蔽關鍵詞列表是否出如今了某一個答案中,若是出現了,就會把這個答案隱藏,取而代之的是提示信息和一個展開答案的按鈕。效果以下圖所示:jquery

2016-03-25-zhihu-filter-demo1.png

你能夠點擊圖中的按鈕來查看答案,以後能夠選擇再次隱藏或展開。git

當你安裝了擴展後,會在地址欄的右側顯示出圖標github

2016-03-25-icon.png

點擊圖標後,將會出現設置屏蔽詞的界面web

2016-03-25-popup.png

你能夠在這個頁面中設置你想屏蔽的詞語。正則表達式

關於Chrome擴展的開發

關於Chrome擴展開發的內容,能夠查看Google的官方文檔或者是這個教程chrome

一個應用(擴展)實際上是壓縮在一塊兒的一組文件,包括HTML,CSS,Javascript腳本,圖片文件,還有其它任何須要的文件。json

開發擴展的時候,必不可少的是一個manifest.json文件,這是一個配置文件,會告訴Chrome你的擴展中包含了哪些內容。manifest.json中包含擴展的名字、版本及各類資源文件(如圖標、顯示頁面等)的連接。

個人擴展的manifest.json文件

Browser_action 和 page_action

Browser_actionpage_action是擴展的兩種類型,它們很類似,主要的區別在於browser_action能夠應用於全部的頁面,而page_action只能在你設定的幾個特定網站內使用。個人擴展是專門用於知乎網站的,所以選擇了page_action來處理。

按照Google的文檔描述,二者還有一個區別:browser_action的圖標顯示在地址欄的外部,page_action的圖標顯示在地址欄內部。可是,在這裏的討論中,彷佛新版本的Chrome瀏覽器中已經將二者都顯示在了地址欄的外側,不過page_action的圖標只有在打開特定的網站時纔不會顯示爲灰色。

manifest.json文件中進行以下設置:

"page_action": {
  "default_icon": "images/icon.png",
  "default_title": "知乎屏蔽擴展"
},

background.js

在Manifest中指定background域可使擴展常駐後臺。background能夠包含三種屬性,分別是scriptspagepersistent

我在擴展中只用到了scripts

"background": {
  "scripts": ["js/jquery-2.2.1.js","js/background.js"]
},

這樣就會自動生成一個包含了列出的腳本文件的後臺頁面。

在個人background.js文件中有以下內容:

// 當網址改變的時候,判斷當前的頁面是不是知乎
    // 若是是的話,就顯示出圖標,並設置它的彈出頁面
    chrome.tabs.onUpdated.addListener(function(id, info, tab){
        if (tab.url.toLowerCase().indexOf("zhihu.com") > -1){
            chrome.pageAction.show(id);
            chrome.pageAction.setPopup({
                tabId: id,
                popup: 'popup.html'
            });
        }
    });

background.js文件中還有一個用於和content_script進行通訊的監聽器。

參考資料:
Google的Backgorund Pages文檔
另外一個教程

Content Scripts

Content scripts是在Web頁面內運行的javascript腳本。經過使用標準的DOM,它們能夠獲取瀏覽器所訪問頁面的詳細信息,並能夠修改這些信息。

manifest.json文件中進行設置:

"content_scripts": [
{
  "matches": ["*://*.zhihu.com/*"],
  "js": ["js/jquery-2.2.1.js", "js/content_script.js"]
}

在打開匹配的網站時,列出的js文件會被注入頁面,這樣就能夠得到瀏覽器所訪問的web頁面的詳細信息,並能夠對頁面作出修改。雖然content script和頁面共享了DOM結構,但不能訪問該頁面或其它content script中定義的函數和變量,這樣就避免了相同的函數或變量名稱的干擾。

個人擴展的主要功能就是在content_script.js中完成的,在該腳本中經過對頁面的DOM進行操做來實現功能。

擴展功能的實現

對知乎首頁進行分析

查看一下知乎首頁的源代碼,全部的答案內容是放在一個id=js-home-feed-list的div中的,結構大體以下:

<!--這裏省略了不少內容,只是一個大體示意-->
    <div id="js-home-feed-list">
        <div class="feed-item">
            <!--這裏省略一些題目的id,url等信息-->
            <div class="avatar"></div>  <!--頭像-->
            <div class="feed-main">  <!--除了頭像外的其它部分-->
                <div class="source"></div>
                <div class="content">
                    <!--答案內容及按鈕等部分-->
                </div>
            </div>
        </div>
        <div class="feed-item"></div>
        <div class="feed-item"></div>
    </div>

而在答案部分中,又分爲摘要和完整的答案。

<div class="zm-item-answer-detail">
        …
        <div class="zm-item-rich-text">   <!--內容部分-->
            <div class="zm-editable-content"></div>   <!--點擊顯示所有後,這一部分才顯示-->  
            <textarea class="content hidden"></div>   <!--包括了所有答案內容但不顯示-->
            <div class="zh-summary summary clearfix"></div> <!--顯示摘要-->
        </div>
    </div>

咱們能夠獲取上面的節點內容,來肯定是否須要屏蔽這個答案。最簡單的實現方法就是查找關鍵詞是否在節點的outerHTML中出現,若是出現了就給.feed-main加上一個名爲hidden的class。

用於替換的內容

原來的答案被隱藏了以後,須要在這個地方換上點新的內容,我在這裏建立了一個div,內部有一個p元素用於顯示信息,及一個button用於切換答案的狀態。

// 建立用於替換的div
    var $div = $('<div class="block-info"></div>');
    $div.append($('<p>這裏有一個被屏蔽的答案<span></span></p>'));
    $div.append($('<button class="block-btn">手賤一下</button>'));

還須要在按鈕上綁定點擊事件,p元素內顯示的信息也會根據實際進行修改。

關鍵詞的存儲

在個人擴展中,我是將須要屏蔽的關鍵詞存放在了localStorage中。關鍵詞保存在localStorage中的keywords鍵下,是一個簡單的由逗號分隔開的字符串。

要訪問同一個localStorage對象,頁面必須來自同一個域名(子域名無效),使用同一種協議,在同一個端口上。

因爲content script注入到了知乎頁面,而localStorage存放在擴展的域下,要在content script中得到關鍵詞的值,就必須用到頁面中的通訊。

Chrome提供了4個有關擴展頁面間相互通訊的接口,分別是runtime.sendMessageruntime.onMessageruntime.connectruntime.onConnect
這裏用到了前兩個。

content_script.js

// 從擴展的localStorage中得到存儲的關鍵詞
    chrome.runtime.sendMessage({method: "getKeywords"}, function (response) {
        str = response.keywords;
        keywords = str !== '' ? str.split(',') : [];
    });

對應的background.js

chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
        if (request.method == "getKeywords")
            sendResponse({keywords: localStorage['keywords']});
        else
            sendResponse({});
    });

這樣就能夠在content script中使用localStorage的值了。

參考資料:擴展頁面中的通訊

檢測頁面加載了更多內容

每當當知乎頁面加載了更多的答案時,咱們須要再進行處理。那麼如何檢測頁面中加載了更多內容,這裏須要用到MutationObserver對象。MutationObserver提供了檢測頁面中的DOM變化的方法。

MutationObserver的使用方法:

首先,建立一個mutationObserver的實例

var observer = new MutationObserver(callback)

這裏須要一個回調函數做爲構造器的參數。

callback函數接受一個參數,全部被記錄到的DOM變化將會組合成一個數組,做爲參數傳給回調函數進行處理。

mutationObserver對象有幾個方法,這裏只用到observe方法。

observe方法接收兩個參數:

void observe(
      Node target,
      MutationObserverInit options
    );

第一個參數指的是你要觀察哪個節點的DOM變化。第二個參數是一個選項參數,

var option = {
        'childList': true,    // 觀察子元素
        'subtree': true,      // 觀察目標節點的後代元素
        'attributes': false   // 不觀察目標節點屬性的變化
    };

這裏只設置了三個選項,其他屬性能夠看MDN文檔

觀察到的每個變化都是一個MutationRecord對象,它有許多屬性,好比:

  • type,記錄變化的類型

  • addedNodes,由增長的節點組成的NodeList

  • removeNodes,由刪除的節點組成的NodeList

由於addedNodes是一個NodeList,因此能夠在它上面應用jQuery的$(), $(mutation.addedNodes)

其它的屬性能夠見MDN文檔

全部的MutationRecord會被放進一個list中,做爲參數傳給上面的callback函數。經過對MutationRecord對象的屬性進行檢測,若是新增的節點裏出現了class=feed-main的元素,則表明加載了新的答案,須要再一次運行程序。

參考資料:
Getting to Know Mutation Observers
Mutation Observer

擴展的使用

在個人設想中,擴展能夠提供一些選項,好比正則表達式的支持,再好比除了首頁外,在答案頁面是否也須要提供屏蔽功能。這些選項會在以後逐步加入。

因爲Chrome的設置,不可以安裝Web Store中沒有的程序。而發佈擴展須要先付$5,我沒有信用卡,也就暫時沒有發佈擴展。若是想嘗試一下擴展的話,能夠直接下載Github中的代碼到本地。在Chrome瀏覽器的菜單中選擇 More tools -> Extensions,進入擴展頁面後,勾選右上角的Developer mode,選擇Load unpacked extension,選擇擴展文件夾便可。

歡迎使用後提出建議。

相關文章
相關標籤/搜索