分析chrome擴展程序-Holmes

前陣子在微博看到關於chrome書籤搜索的擴展程序Holmes,因而使用了一下,確實還不錯,尤爲書籤多不少的狀況下,有個搜索功能 ,比定時作書籤分類等等方便得多不少。看着擴展程序功能簡單,感受實現應該不難,因而我就試着查看一下擴展程序源碼,也能夠順便進一步瞭解chrome擴展程序的實現方法和操做api。javascript

本篇文章意在講解Holmes的主要實現,對於chrome擴展程序怎麼一步一步創建起來,就不作重複說明,網上搜索便可。css

首先,既然要研究擴展程序源碼,那麼最直接的方式固然就是從chrome安裝目錄來尋址該擴展程序的所在目錄了。html

這點,其實網上有資料能夠查看的,好比這裏>>java

步驟1,在chrome地址欄輸入:chrome://version 回車,將結果頁中「我的資料路徑」一欄對應的值複製(實際上是一個目錄路徑)jquery

步驟2,打開資源管理器,鍵入剛複製的路徑,進入目錄,打開Extsions目錄,裏面都是各類以字母組合的命名文件夾,對應的實際上是各個擴展程序,命名實際上是擴展程序的idgit

步驟3,在地址欄輸入:chrome://extensions,進入擴展程序管理頁,找到對應的擴展程序選項,有ID,對應的ID值就是對應的擴展程序文件夾名字。這彷佛是個惟一值,holmes的ID是:gokficnebmomagijbakglkcmhdbchbhngithub

遵循上面三個步驟,咱們就能夠找到Holmes的擴展程序目錄了 ,將其複製到方便查看的位置,就能夠開始查看源碼了。web

理解配置

作過擴展程序的都知道,項目必須有一個配置文件manifest.jsonchrome

這裏列一下主要的配置字段,不全編程

{
   "background": {
      "page": "watson.html"
   },
   "browser_action": {
      "default_icon": "images/icon_19.png",
      "default_popup": "holmes.html",
      "default_title": "Holmes"
   },
   "commands": {
      "_execute_browser_action": {
         "suggested_key": {
            "default": "Alt+Shift+H"
         }
      }
   },
   "description": "Chrome Bookmark Search Extension",
   "icons": {
      "128": "images/icon_128.png",
      "16": "images/icon_19.png",
      "48": "images/icon_48.png"
   },
   "manifest_version": 2,
   "minimum_chrome_version": "25",
   "name": "Holmes",
   "omnibox": {
      "keyword": "*"
   },
   "permissions": [ "bookmarks", "tabs", "chrome://favicon/" ],
   "update_url": "https://clients2.google.com/service/update2/crx",
   "version": "3.2.1"
}

能夠看到,這裏有個permissions字段,字面意義應該是跟權限相關,這裏配置了bookmarks、tabs、和chrome://favicon/,這爲後面要用到chrome的一些功能埋下了伏筆(這麼說好麼?= =)

另外browse_action字段定義了點擊擴展程序圖標的一些行爲,default_popup規定了點擊彈出框定位到holmes.html,也就是擴展程序的業務頁面,即搜索框和搜索結果。

深刻頁面

找到holmes.html,查看頁面會發現,頁面主要引入的js文件:

jquery 庫文件
fuse.min.js 模糊查詢工具
jquery.Core.js 基於jquery的核心方法
keymaster.min.js 按鍵事件工具
jquery.holmes.js 基於jquery的頁面業務邏輯

頁面的主要dom結構:

<div id="link-wrap" class="cf">
    <a class="icon-question" href="http://www.blackfish.fi/holmes/" target="_blank" title="Holmes website"></a>
</div>

<section>
    <header class="cf">
        <div id="search">
            <img src="images/icon_19.png">
            <input id="pattern" type="text" autofocus="autofocus" autocomplete="off" placeholder="Holmes at your service.">
        </div>
    </header>
</section>

<section>
    <div id="view">
        <ol></ol>
    </div>
</section>

主要的節點是:輸入框 #pattern 和 搜索結果 #view.

到此,咱們大概瞭解了holmes主要用到的是jquery來方便編程,利用keymaster來處理按鍵事件,利用fuse的模糊搜索來實現模糊查詢書籤。

那麼,咱們怎麼得到chrome的書籤呢?若是書籤有增刪改,又該怎麼辦呢?另外,我要怎麼打開標籤頁來承載某個書籤呢?

獲取書籤和標籤頁操做

獲取書籤,其實就呼應了前面說過的配置權限permissions.

咱們怎麼知道的? 首先定位到jquery.Core.js文件,文件內容很簡單,不過作了壓縮,這裏我優化了一下方便走讀。

(function() {
    var o, r;
    window.Holmes = {
        bookmarks: [],
        version: 3.21,
        showShortcutInfo: !1
    };
     r = function(o) {
        return function(o) {
            var e, t, n, a;
            for (a = [], t = 0, n = o.length; n > t; t++) {
                e = o[t];
                if(e.hasOwnProperty("children")){
                    a.push(r(e.children));
                }else{
                    if(e.url.match(/^javascript/)){
                        a.push(void 0);
                    }else{

                        (e.title || (e.title = e.url), a.push(Holmes.bookmarks.push({
                                title: e.title,
                                url: e.url
                            })));
                    } 
                } 
            }
            return a
        }
    }(this);
    o = function() {
        return chrome.bookmarks.getTree(function(o) {
            return Holmes.bookmarks = [], r(o)
        })
    };
     chrome.bookmarks.onCreated.addListener(function(r, e) {
        return o()
    });
     chrome.bookmarks.onRemoved.addListener(function(r, e) {
        return o()
    });
     chrome.bookmarks.onChanged.addListener(function(r, e) {
        return o()
    });
     Holmes.getPlaceholder = function() {
        var o;
        return o = ["Holmes at your service.", "How can I help you?", "It is a good day for searching.", "You know my methods. Apply them."], o[Math.floor(Math.random() * o.length)]
    };
    o()
}).call(this);

這裏主要是定義了全局對象:Holmes,包含三個屬性,其中最主要的是bookmarks,它是一個數組,存儲chrome上的書籤,怎麼存的呢?

在上述代碼上能夠看出,經過調用chrome.bookmarks.getTree, chrome.bookmarks是chrome提供的書籤對象api,用於各類書籤操做,chrome書籤操做>>, 這裏getTree就是獲取書籤樹結構,參數爲回調函數,從回調函數參數能夠得到樹節點,經過children能夠得到子節點,因而這裏是實現對節點的處理 ,當有children節點時,遞歸操做,無則當作連接獲取title跟url屬性,並push到bookmarks數組去(有可能收藏的是腳本代碼,這塊這裏作了過濾處理)。

要用chrome.bookmarks,從官方文檔可知道, 須要配置permissions權限

{
    "name": "My extension",
    ...
    "permissions": [
      "bookmarks"
    ],
    ...
}

另外,bookmarks對象還有事件處理函數,這裏用於更新書籤數據以備檢索。

chrome.bookmarks.onCreated.addListener
    chrome.bookmarks.onChangeed.addListener
    chrome.bookmarks.onRemoved.addListener

這裏還沒涉及到tabs,但由於都要配置permissions,因此提早說明,一樣,tabs也有官網文檔說明

其配置權限相似bookmarks,關於tabs的使用至關簡單:

chrome.tabs.create({url:xxx, pinned:true}) // 打開標籤頁,url爲連接, pinned是否固定標籤頁

標籤頁這裏主要是打開標籤頁,那就只須要用到這條api,其餘能夠到上面文檔說明去查看。

業務邏輯

業務邏輯在holmes.js內,主要涉及的是按鍵事件交互處理以及檢索結果展現。

對於檢索結果,實際上是從全局對象Holmes.bookmarks入手, 對其採用fuse模糊搜索。

fuse的具體操做能夠看 fuse官網

使用很簡單,以下:

// 配置對象,檢索關鍵屬性
var option = {
    keys:['id']
};
// data爲測試數據
f = new Fuse(data, options);

f.search('查找內容'); //這裏將對data的id屬性作 查找內容 的搜索

在holmes裏,使用很簡單:在輸入框監聽keyup,清空結果dom($view), 對書籤數據進行模糊查詢(鍵值:書籤標題),獲得結果調用updateView方法來給$view更新內容。(涉及到鍵入監聽,其實還能夠考慮作節流操做避免重複執行搜索)

代碼以下:

// 監聽輸入框keyup事件
$('#pattern').on('keyup', function(e) {
    var _kc, _ret, f, options;
    _kc = e.keyCode;
    if (_kc === 38 || _kc === 40 || _kc === 37 || _kc === 39) {
      $(this).css('-webkit-user-select', 'text');
      return false;
    }
    pattern = $('#pattern').val();
    // 清空搜索結果
    $view.html('');
    _ret = [];
    if (pattern.length > 0) {
      pattern.replace(' ', '');
      options = {
        keys: ['title']
      };
      // 對title進行查詢
      f = new Fuse(Holmes.bookmarks, options);
      _ret = f.search(pattern);
    } else {
      _ret = [];
      search = null;
    }
    // 調用updateView對搜索結果進行展現
    return updateView(_ret);
  });

關於updateView: 已經設定了bmarks_per_page變量用來定義結果顯示數量(並不是把所有書籤結果都輸出,而是輸出前十條數據),而且對標題進行省略裁剪,並針對關鍵字插入span標籤(可作樣式高亮,但擴展程序並無作),在拼接dom模板時對首條數據加入current類,而且將對應的dom保存到$current_mark,用於回車時直接打開以及顯示選中項,對拼接的dom添加到$view中,而且定義a標籤點擊事件(因爲每次搜索結果都是從新添加內容,因此這裏不會涉及重複綁定,但其實能夠給$view作事件代理更佳)

代碼以下:

matchString = function(_string, _char_count) {
    var _inp_val_pat;
    _string = _string.length < _char_count ? _string : _string.substr(0, _char_count) + '...';
    _inp_val_pat = new RegExp(pattern, "gi");
    return _string.replace(_inp_val_pat, '<span>' + _inp_val_pat.exec(_string) + '</span>');
  };

  updateView = function(bmarks) {
    var _classes, _match, _update, i, j, len, mark;
    if (bmarks.length > 0) {
      _update = '';
      for (i = j = 0, len = bmarks.length; j < len; i = ++j) {
        mark = bmarks[i];
        // 我認爲這裏直接break跳出循環便可,無需continue繼續循環
        if (!(i < bmarks_per_page)) {
          continue;
        }
        _classes = i === 0 ? 'current' : '';
        _match = matchString(mark.title, 50);
        _update += "<li class=\"" + _classes + " cf\"><img src=\"chrome://favicon/" + mark.url + "\"><a href=\"" + mark.url + "\" title=\"" + mark.url + "\">" + _match + "</a></li>";
      }
      $view.html(_update);
      $current_mark = $view.children(':first');
      return $view.find('a').on('click', function() {
        return chrome.tabs.create({
          url: this.href
        });
      });
    } else {
      if (search != null) {
        $view.html('<div class="no-bookmarks-found"><h1>Oh no!</h1><p><b>No single bookmark found.</b><br>Press enter to Google!</p><p>Or you could check out<br><a href="http://www.blackfish.fi/holmes/" target="_blank" title="Holmes website">Holmes new website!</a></p></div>');
      }
      return $current_mark = null;
    }
  };

ps:這裏有個小知識點,其實也涉及到permissions配置,在chrome下,chrome://favicon/+頁面地址 能夠得到該網站的favicon.

其餘按鍵交互:除了輸入框鍵入字符的監聽外,還監聽了回車打開標籤頁和切換結果選中項,主要用了keymaster

其api能夠參考這裏:keymaster官網

主要的代碼:

key.filter = function(event) {
    var tagName;
    tagName = (event.target || event.srcElement).tagName;
    return !(tagName === 'SELECT' || tagName === 'TEXTAREA');
  };

  key('down, up', function(e, h) {
    if (($current_mark != null) && $view.children().length > 1) {
      switch (h.shortcut) {
        case 'down':
          if ($current_mark.index() !== bmarks_per_page - 1 && $current_mark.index() < $view.children().length - 1) {
            return $current_mark = $current_mark.removeClass('current')
            .next('li')
            .addClass('current');
          }
          break;
        case 'up':
          if ($current_mark.index() !== 0) {
            return $current_mark = $current_mark.removeClass('current')
            .prev('li')
            .addClass('current');
          }
      }
    }
  });

  key('enter, shift + enter', function(e, h) {
    switch (h.shortcut) {
      case 'enter':
      case 'shift+enter':
        if ($current_mark != null) {
          return chrome.tabs.create({
            url: $current_mark.find('a').prop('href'),
            pinned: key.shift ? true : false
          });
        } else {
          if (pattern.length > 2) {
            return chrome.tabs.create({
              url: 'http://www.google.com/search?q=' + pattern
            });
          }
        }
    }
  });

至此,holmes的主要邏輯也就解讀完畢了, 對於樣式那塊,本文不作解讀,可看出,其實該擴展程序,還有不少優化空間,感興趣,其實能夠本身改改,來優化使用體驗,好比:代碼的一些重複邏輯、加入事件代理、搜索結果顯示體驗等等。(目前,這個擴展程序在說明上貌似最新一版已是2015年的事了,看來做者也沒有繼續更新的打算了)

相關文章
相關標籤/搜索