Đối với người dùng Internet, không thể phủ nhận Google Chrome là một trong những trình duyệt phổ biến nhất hiện nay. Chính vì vậy việc trải nghiệm Internet được tiện lợi như hiện nay không thể thiếu những đóng góp không ngừng của cộng đồng các lập trình viên trên toàn thế giới. Một trong những đóng góp đó chính là các Extension, trong bài viết này tôi trình bày phương pháp để tạo ra một Extension trong Chrome.

I. Phát triển & Kiểm thử Extension của bạn

Rất may là có một cách để kiểm tra extension của bạn mà không phải tải nó lên cửa hàng của Chrome. Trong thanh địa chỉ của trình duyệt, chỉ cần nhập vào:
hrome://extensions
Hãy đảm bảo rằng bạn đã tích vào Developer mode và nhấp vào nút Load unpacked extension.... Sau đó, chỉ cần chọn thư mục từ ổ cứng chứa các tập tin của extension.
uc?id=1uKIrzprobp42xbgSdMPWoq11KOhZc0nC&export=download

II. Kiến trúc

Dưới đây là sơ đồ kiến trúc cho một extension của Chrome:
uc?id=1mj--rMw54Y4fvhYaMRgJUOkbelZBgUk0&export=download
Và bây giờ hãy xem kỹ hơn về mỗi thành phần bên trong kiến trúc.

Manifest

Khởi điểm của extension là tập tin manifest.json. Nó phải chứa một đối tượng JSON hợp lệ. Ví dụ:

{
    "name": "BrowserActionExtension",
    "version": "0.0.1",
    "manifest_version": 2,
    "browser_action": {
        "default_title": "That's the tool tip",
        "default_popup": "popup.html"
    }
}

Các thuộc tính cần thiết là name, versionmanifest_version. version có thể là bất cứ số nào từ một đến bốn, số nguyên phân cách nhau bởi dấu chấm. Đó là thứ được sử dụng bởi hệ thống tự động cập nhật của Google. Đó là cách để nó biết khi nào cập nhật extension của bạn. Giá trị của manifest_version phải là số nguyên 2.

Manifest có thể chứa các thuộc tính khác tùy thuộc vào loại extension mà bạn cần, nhưng tôi sẽ chỉ mô tả những cái mà tôi thấy thú vị.

Trang nền

Mỗi extension có một trang nền (background page) không nhìn thấy được chạy bởi trình duyệt. Có hai loại - trang nền cố định và trang sự kiện. Cái đầu tiên hoạt động, mọi lúc. Cái thứ hai chỉ hoạt động khi cần. Google khuyến khích các nhà phát triển sử dụng các trang sự kiện, vì cái này giúp tiết kiệm bộ nhớ và cải thiện hiệu suất tổng thể của trình duyệt. Tuy nhiên, cũng cần phải biết rằng đây cũng là nơi bạn nên đặt logic chính và những khởi tạo của bạn. Thông thường, trang nền/script đóng vai trò là cầu nối giữa các phần khác nhau của extension.

Đây là cách bạn nên mô tả nó trong tập tin manifest:

"background": {
    "scripts": ["background.js"],
    "persistent": false/true
}

Như bạn có thể đoán được, nếu thuộc tính persistentfalse thì bạn đang sử dụng các trang sự kiện. Nếu không, bạn đang làm việc với một trang nền cố định.

Script nội dung

Nếu bạn cần truy cập vào DOM của trang hiện tại, thì bạn cần phải sử dụng một script nội dung (content script). Code được chạy bên trong ngữ cảnh của trang web hiện tại, có nghĩa là nó sẽ được thực thi với mỗi lần làm mới. Để thêm một script như vậy, hãy sử dụng cú pháp sau đây.

"content_scripts": [
    {
        "matches": ["http://*/*", "https://*/*"],
        "js": ["content.js"]
    }
]

Hãy nhớ rằng giá trị của matches xác định những trang mà script của bạn sẽ được sử dụng. Đọc thêm về các mẫu matches ở đây.

Giao diện người dùng

Có một số cách để xây dựng UI (giao diện người dùng) cho extension của bạn. Dưới đây là bốn cách phổ biến nhất.

Browser Action
Hầu hết các nhà phát triển sử dụng thuộc tính browser_action để xây dựng các plugin của họ. Một khi bạn đã thiết lập nó, một biểu tượng đại diện cho extension của bạn sẽ được đặt ở bên phải của thanh địa chỉ. Sau đó người dùng có thể nhấp vào biểu tượng và mở một cửa sổ mà chính là nội dung HTML do bạn kiểm soát.
uc?id=1e4endAIQsfb7DKz_OkPInqlrXpZWojfE&export=download

Các tập tin manifest nên chứa các dữ liệu sau đây:

"browser_action": {
    "default_icon": {
        "19": "icons/19x19.png",
        "38": "icons/38x38.png"
    },
    "default_title": "That's the tool tip",
    "default_popup": "popup.html"
}

default_title là một tool tip nhỏ được hiển thị khi người sử dụng rê chuột lên trên biểu tượng của bạn. default_popup là tập tin HTML thật sự được nạp bên trong cửa sổ pop-up. Ngoài ra còn có một biểu trưng mà bạn có thể đặt lên trên biểu tượng của bạn. Bạn có thể làm điều đó bên trong script nền của bạn. Ví dụ:

chrome.browserAction.setBadgeText({text: "yeah"});

Đây là code mà tôi sử dụng để tạo ra hình ảnh ở trên.

Page Action
Thuộc tính page_action tương tự như Browser Action, nhưng biểu tượng được hiển thị bên trong thanh địa chỉ:

uc?id=1Vzo3TcFZA2yAFQbU96r6tjrtw5LbRtK1&export=download

Điều thú vị ở đây là biểu tượng của bạn ban đầu bị ẩn, vì vậy bạn có thể quyết định khi nào hiển thị nó. Ví dụ, trong hình trên, biểu tượng RSS sẽ chỉ được hiển thị khi trang hiện tại chứa liên kết đến một RSS feed. Nếu bạn cần lúc nào cũng hiển thị biểu tượng của bạn, thì tốt nhất là sử dụng trực tiếp browser_action.

Để thêm Page Action, gõ code sau bên trong tập tin manifest của bạn:

"page_action": {
    "default_icon": {
        "19": "images/icon19.png",
        "38": "images/icon38.png"
    },
    "default_title": "Google Mail",
    "default_popup": "popup.html"
}

Không giống như biểu tượng của Browser Action, biểu tượng Page Action không có biểu trưng.

DeveloperTools
Tôi sử dụng DeveloperTools rất nhiều và thật tuyệt khi Chrome cung cấp một cách để thêm các tab mới vào công cụ này. Điều đầu tiên bạn cần làm là thêm một trang HTML sẽ được nạp khi bảng điều khiển được mở ra:

"devtools_page": "devtools.html"

Không cần đặt bất kỳ HTML nào bên trong trang, ngoại trừ liên kết đến một tập tin JavaScript, cái mà sẽ tạo ra tab:

<script src="devtools.js"></script>;

Và sau đó bao gồm code sau đây bên trong tập tin devtools.js:

chrome.devtools.panels.create(
    "TheNameOfYourExtension", 
    "img/icon16.png", 
    "index.html",
    function() {
 
    }
);

Bây giờ code trên sẽ thêm một tab mới với tên là TheNameOfYourExtension và một khi bạn nhấp vào nó thì trình duyệt sẽ nạp index.html bên trong DeveloperTools.

Omnibox
omnibox là từ khoá được hiển thị bên trong thanh địa chỉ của Chrome. Ví dụ, nếu bạn thêm thuộc tính sau vào tập tin manifest của bạn:

"omnibox": { "keyword" : "yeah" }

Và sau đó thêm code dưới đây, vào bên trong script nền của bạn:

chrome.omnibox.onInputChanged.addListener(function(text, suggest) {
    suggest([
      {content: text + " one", description: "the first one"},
      {content: text + " number two", description: "the second entry"}
    ]);
});
chrome.omnibox.onInputEntered.addListener(function(text) {
    alert('You just typed "' + text + '"');
});

Bạn sẽ có thể gõ yeah bên trong thanh địa chỉ. Sau đó bạn sẽ thấy một cái gì đó đại loại như sau:

uc?id=1odrbJKz0YX19j5wsYaCB23g8RRujX4dk&export=download

Nhấn vào tab sẽ cho ra màn hình sau:

uc?id=1eEZ2hJmBkSbi9TIJCJm-iL_rPQTeNGIx&export=download

Tất nhiên việc sử dụng chrome.omnibox API, bạn có thể lấy đầu vào của người dùng và tương tác với đầu vào đó.

API

Có rất nhiều thứ khác nhau để bạn có thể thực hiện trong extension của bạn. Ví dụ, bạn có thể truy cập vào bookmark hoặc lịch sử của người dùng. Bạn có thể di chuyển, tạo các tab hoặc thậm chí thay đổi kích thước cửa sổ chính. Tôi khuyên bạn nên tham khảo tài liệu hướng dẫn để có được một ý tưởng tốt hơn về cách để thực hiện các công việc này.

Những gì bạn cần biết là không phải tất cả các API đều có sẵn trong mọi phần của extension của bạn. Ví dụ, script nội dung của bạn không thể truy cập vào chrome.devtools.panels hoặc script bên trong tab DeveloperTools của bạn không thể đọc DOM của trang web. Vì vậy, nếu bạn đang thắc mắc tại sao điều gì đó không hoạt động, thì đây có thể là lý do.

Thông điệp

Như tôi đã đề cập ở trên, bạn không phải lúc nào cũng có thể truy cập vào API mà bạn muốn sử dụng. Nếu như vậy, thì bạn nên sử dụng việc truyền thông điệp. Có hai loại thông điệp - các yêu cầu một lần và kết nối lâu dài.

Yêu cầu một lần
Kiểu giao tiếp này chỉ xảy ra một lần. Ví dụ, bạn gửi một tin nhắn và chờ một câu trả lời. Ví dụ, bạn có thể đặt code sau đây bên trong script nền của bạn:

chrome.extension.onMessage.addListener(function(request, sender, sendResponse) {
    switch(request.type) {
        case "dom-loaded":
            alert(request.data.myProperty);
        break;
    }
    return true;
});

Sau đó sử dụng code từ bên dưới trong script nội dung của bạn:

window.addEventListener("load", function() {
    chrome.extension.sendMessage({
        type: "dom-loaded", 
        data: {
            myProperty: "value"
        }
    });
}, true);

Và đây là cách bạn có thể lấy thông tin về DOM của trang hiện tại và sử dụng nó bên trong script nền của bạn, cái mà thông thường không có quyền truy cập vào dữ liệu này.

Kết nối lâu dài
Sử dụng kiểu thông điệp này nếu bạn cần một kênh giao tiếp cố định. Bên trong script nội dung của bạn, hãy đặt code sau đây:

var port = chrome.runtime.connect({name: "my-channel"});
port.postMessage({myProperty: "value"});
port.onMessage.addListener(function(msg) {
    // do some stuff here
});

Và sau đó bên trong script nền, sử dụng code này:

chrome.runtime.onConnect.addListener(function(port) {
    if(port.name == "my-channel"){
        port.onMessage.addListener(function(msg) {
            // do some stuff here
        });
    }
});

Thay thế trang
Thay thế trang là một cách hay để tùy biến trình duyệt của bạn. Bạn cũng có thể thay thế một số trang mặc định trong Chrome. Ví dụ bạn có thể tạo trang lịch sử của riêng mình. Để làm điều đó, hãy thêm đoạn code sau đây:

"chrome_url_overrides" : {
    "<page to override>;": "custom.html"
}

Các giá trị có thể có của <page to override>bookmarks, historynewtab. Thật tuyệt khi có một trang new tab mới tinh.

III. Một Extension mẫu

Để tổng hợp bài này, tôi đã quyết định bao gồm một ví dụ đơn giản, để bạn có thể hiểu rõ hơn về bức tranh tổng thể. Extension ví dụ này sử dụng hầu hết những gì mà tôi đã mô tả ở trên để thiết lập một màu nền #F00 cho tất cả các div ở trong trang hiện tại. Bạn có thể tải về mã nguồn bằng cách sử dụng nút Download ở đầu bài viết này.

Tập tin Manifest

{
   "name": "BrowserExtension",
   "version": "0.0.1",
   "manifest_version": 2,
   "description" : "Description ...",
   "icons": { "16": "icons/16x16.png", "48": "icons/48x48.png", "128": "icons/128x128.png" },
   "omnibox": { "keyword" : "yeah" },
   "browser_action": {
       "default_icon": { "19": "icons/19x19.png", "38": "icons/38x38.png" },
       "default_title": "That's the tool tip",
       "default_popup": "browseraction/popup.html"
   },
   "background": {
       "scripts": ["background.js"],
       "persistent": false
   },
   "chrome_url_overrides" : {
       "newtab": "newtab/newtab.html"
   },
   "content_scripts": [{
       "matches": ["http://*/*", "https://*/*"],
       "js": ["content.js"]
   }],
   "devtools_page": "devtools/devtools.html"
}

Lưu ý rằng, bạn có thể sắp xếp các tập tin của bạn thành các thư mục. Ngoài ra, hãy chú ý đến thuộc tính version. Bạn nên cập nhật thuộc tính này mỗi khi bạn muốn tải extension của bạn lên cửa hàng.

Script nền

// omnibox
chrome.omnibox.onInputChanged.addListener(function(text, suggest) {
    suggest([
      {content: "color-divs", description: "Make everything red"}
    ]);
});
chrome.omnibox.onInputEntered.addListener(function(text) {
    if(text == "color-divs") colorDivs();
});
 
// listening for an event / one-time requests
// coming from the popup
chrome.extension.onMessage.addListener(function(request, sender, sendResponse) {
    switch(request.type) {
        case "color-divs":
            colorDivs();
        break;
    }
    return true;
});
 
// listening for an event / long-lived connections
// coming from devtools
chrome.extension.onConnect.addListener(function (port) {
    port.onMessage.addListener(function (message) {
        switch(port.name) {
            case "color-divs-port":
                colorDivs();
            break;
        }
    });
});
 
// send a message to the content script
var colorDivs = function() {
    chrome.tabs.getSelected(null, function(tab){
        chrome.tabs.sendMessage(tab.id, {type: "colors-div", color: "#F00"});
        // setting a badge
        chrome.browserAction.setBadgeText({text: "red!"});
    });
}

Một số dòng đầu tiên lấy hành động của người dùng từ omnibox. Sau đó, tôi thiết lập một listener cho yêu cầu một lần, sẽ chấp nhận thông điệp từ biểu tượng hành động của trình duyệt.

Đoạn code tiếp theo là một kết nối lâu dài với tab devtools (hoàn toàn không cần thiết phải sử dụng một kết nối lâu dài cho việc này, tôi thực hiện nó chỉ cho mục đích nghiên cứu). Sử dụng các listener này, tôi có thể lấy được đầu vào từ người dùng và gửi nó đến script nội dung, cái mà có quyền truy cập vào các phần tử DOM. Điểm mấu chốt ở đây là trước tiên chọn tab mà tôi muốn thao tác và sau đó gửi một thông điệp đến nó. Sau cùng, tôi đặt một biểu trưng vào biểu tượng tiện của extension.

Browser action
Chúng ta bắt đầu bằng tập tin popup.html:

// popup.html
<script type="text/javascript" src="popup.js"></script>
<div style="width:200px">
    <button id="button">Color all the divs</button>
</div>

Sau đó chúng ta tạo tập tin popup.js:

// popup.js
window.onload = function() {
    document.getElementById("button").onclick = function() {
        chrome.extension.sendMessage({
            type: "color-divs"
        });
    }
}

Cửa sổ pop-up có chứa một nút đơn và một khi người dùng nhấp chuột vào nó, nó sẽ gửi một thông điệp đến script nền.

Script nội dung

chrome.extension.onMessage.addListener(function(message, sender, sendResponse) {
    switch(message.type) {
        case "colors-div":
            var divs = document.querySelectorAll("div");
            if(divs.length === 0) {
                alert("There are no any divs in the page.");
            } else {
                for(var i=0; i&lt;divs.length; i++) {
                    divs[i].style.backgroundColor = message.color;
                }
            }
        break;
    }
});

Script nội dung lắng nghe một thông điệp, chọn tất cả các thẻ div trên trang hiện tại, và thay đổi màu nền của chúng. Hãy chú ý đến đối tượng mà tôi đã gắn vào listener. Trong script nội dung đó là chrome.extension.onMessage.

IV. Tóm tắt

Trong bài này chỉ cung cấp khái quát về cách tạo ra plugin trong Chrome. Theo tôi, Chrome là một trong những trình duyệt tốt nhất hiện nay. Các nhà phát triển tại Google giúp cho việc tạo ra các extension tương đối dễ dàng bằng cách cung cấp cho chúng ta sức mạnh để tạo ra chúng bằng HTML, CSS và JavaScript.

V. Tham khảo

Google Chrome Extension