11/08/2018, 22:31

How to create a Firefox Add-on : case study "Chatwork Emoticons Plus"

Chapter 3: The story continue Bạn có thể đọc bài blog về cách tạo CWEP cho Google Chrome extension tại đây Khác với việc tạo extension cho Google Chrome đơn giản chỉ là chỉnh sửa file javascript và reload, việc tạo một add-on cho Firefox bao gồm khá nhiều công đoạn và bao gồm cài đặt Firefox ...

Chapter 3: The story continue

Bạn có thể đọc bài blog về cách tạo CWEP cho Google Chrome extension tại đây
  • Khác với việc tạo extension cho Google Chrome đơn giản chỉ là chỉnh sửa file javascript và reload, việc tạo một add-on cho Firefox bao gồm khá nhiều công đoạn và bao gồm cài đặt Firefox Extension SDK, mỗi lần test sẽ cần chạy lại firefox, bla bla and bla... Túm lại là phức tạp hơn. Không dông dài nữa, ta sẽ đi vào công việc chính luôn và ngay.

Section 1: Install Firefox Add-on SDK

Công việc đơn giản vô cùng khi đã có hướng dẫn chi tiết và đầy đủ cho từng platform tại https://developer.mozilla.org/en-US/Add-ons/SDK/Tutorials/Installation. Ta chỉ cần lưu ý một số điểm sau:

  • Hãy cài Python 2.7, đang được dùng phổ biến hiện nay.Cần đưa Python vào PATH của OS. Mình dev add-on trên Windows nên cần thêm đường dẫn của Python (thường là C:Python2.7) vào Environment Variable trong System Properties
  • Cần active môi trường để có thể chạy được cfx tool. Cần phải chạy lại nếu đóng cửa sổ cmd

Section 2: Build a simple add-on

Việc đầu tiên cần làm tạo một add-on mẫu, trong cửa sổ terminal, hãy chạy lệnh sau:

(addon-sdk-1.17)[vigo@ubuntu ~/cwep/addon-sdk-1.17 (master)]$ cfx init CWEP
* CWEP package directory created
* lib directory created
* data directory created
* test directory created
* generated jID automatically: jid1-7jlg9axNL5A3ug
* package.json written
* test/test-main.js written
* lib/main.js written

Lúc này thư mục CWEP đã được tạo, ta cùng xem qua cấu trúc thư mục xem thế nào:

/
├── data
├── lib
│   └── main.js
├── package.json
└── test
    └── test-main.js

Ta sẽ nói qua về các thành phần của add-on:

  • Thư mục data: nơi chứa các dữ liệu của add-on, ví dụ như các file js thêm vào, các file ảnh icon, ...
  • thư mục lib: chứa file main.js là file javascript chính sẽ được chạy của add-on
  • package.json: file tương tự như file manifest của Google Chrome, chứa các thông tin về add-on ví dụ như tên tác giả, version
  • Thư mục test: chưa file js dùng để chạy các test case cho add-on Còn đây là cây thư mục của add-on của chúng ta sau khi đã hoàn thành:
.
├── data
│   ├── emo.js
│   ├── icon-16.png
│   ├── icon-32.png
│   ├── jquery-1.11.1.min.js
│   └── my-content-script.js
├── doc
│   └── main.md
├── lib
│   └── main.js
├── package.json
├── README.md
└── test
    └── test-main.js

Section 3: Build CWEP add-on, first pitfall

Để bắt tay vào việc tạo add-on, bạn có thể đọc qua hướng dẫn về các tạo add-on đơn giản tại trang tutorial: https://developer.mozilla.org/en-US/Add-ons/SDK/Tutorials/Getting_started, rất dễ hiểu: tạo add on, test thử với cfx run,...Ở đây, mình sẽ đề cập đến các vấn đề gặp phải khi tạo add-on CWEP cũng như các hoạt động của add-on này.

Vấn đề đầu tiên, khi bạn thử thêm dòng lệnh để debug javascript của mình, ví dụ như:

// file main.js
console.log("Debug string");

Tuy nhiên, khi chạy với cfx run và bật cửa sổ Console (Ctrl + Shift + K), bạn sẽ ko nhìn thấy gì cả. Dó là do cơ chế hiển thị log debug của firefox đã thay đổi, ta cần thêm setting như sau:

  1. mở about:config bằng cách gõ vào thanh địa chỉ
  2. click phải và chọn New -> String
  3. đặt tên setting là ‘extensions.sdk.console.logLevel’
  4. đặt giá trị là ‘all’
  5. khởi động lại Firefox Thông tin chi tiết xem tại https://blog.mozilla.org/addons/2013/03/27/changes-to-console-log-behaviour-in-sdk-1-14/

Tiếp theo, ta cần chạy một script trên tab mà ta đã chỉ định lúc load, ở đây là trang chatwork và chỉ chạy trên trang này để tránh trường hợp scrip của chúng ta gây lỗi cho các trang khác, để làm được điều này ta cần dùng đến page-mod. Tổng quát tương tự như bên Google Chrome, do Firefox ko cho truy cập trực tiếp vào các biến javascript ở trang web vì vấn đề bảo mật, ta cần phải inject một tag javascript vào trong trang lúc load. Tổng quát như sau:

load                         inject
main.js -----> my-content-script.js ---------> emo.js

Cụ thể:

// file main.js

var data = require("sdk/self").data;
var pageMod = require("sdk/page-mod");

pageMod.PageMod({
    include: ["https://www.chatwork.com/*", "https://kcw.kddi.ne.jp/*"],
    contentScriptFile: [data.url("jquery-1.11.1.min.js"), data.url("my-content-script.js")],
});

Ta chỉ định add-on chỉ chạy trên các trang có URL match với regex: "https://www.chatwork.com/*" và "https://kcw.kddi.ne.jp/*" .

Biến data sẽ giúp ta lấy được đường dẫn đến thư mục data để include jquery-1.11.1.min.js và file script riêng. Đọc thêm về data tại đây. OK, file my-content-script.js sẽ được chạy lúc load trang, nhiệm vụ của file này sẽ tương tự như ở bên extension của Google Chrome, tạo 1 đoạn tag javascript rồi inject vào trang, sơ qua như sau:

var s = document.createElement('script');
s.innerHTML =  'content of emo.js';
s.onload = function() {
    this.parentNode.removeChild(this);
};

Section 4: Pass it around

Tuy nhiên, ta ko có cách nào để lấy được nội dung của file emo.js, do biến data, ta chỉ có thể dùng trong file main.js, ta cũng ko thể truy cập trực tiếp vào các biến của content script. Vậy ta cần truyền nội dung của file emo.js từ file main.js vào file my-content-script.js. Firefox cho phép giao tiếp giữa file main.js và script khi được attach vào trang thông qua event. Cụ thể, bạn hãy xem qua ví dụ tại mục "Communicating With Content Scripts" ở trang page-mod. Ta áp dụng vào như sau:

// main.js
var data = require("sdk/self").data;
var pageMod = require("sdk/page-mod");
var script_content = data.load("emo.js");

pageMod.PageMod({
    include: ["https://www.chatwork.com/*", "https://kcw.kddi.ne.jp/*"],
    contentScriptFile: [data.url("jquery-1.11.1.min.js"), data.url("my-content-script.js")],
    onAttach: function(worker) {
        worker.port.emit('script-data', script_content);
    }
});

ở file main.js ta lấy nội dung của file emo.js vào biến script_content rồi truyền cho content script nhờ sự kiện script-data lúc attach script vào trang.

// file data/my-content-script.js

var s = document.createElement('script');

self.port.on("script-data", function handleMessage(payload) {
    s.innerHTML = payload;
    s.onload = function() {
        this.parentNode.removeChild(this);
    };
    (document.documentElement).appendChild(s);
});

Ở file my-content-script.js, ta lắng nghe sự kiện 'script-data', khi đó đem nội dung của message thành code js rồi đem inject vào nội dung của trang, đồng thời remove nội dung script đó, sau khi nó đã được load (đã chạy xong). File emo.js thì hoàn toàn tương tự như trước.

Section 5: Bug ? I've see some

Test thử ta có thể thấy add-on có lúc chạy, có lúc ko, nguyên nhân là do bên Google Chrome, ta có thể chỉ định cho extension chạy sau khi trang đã load xong, tức là lúc này đã có biến CW nhưng ở Firefox thì ko như vậy, do đó ta cần chỉnh sửa code của emo.js để check xem đã có biến CW chưa, lúc này mới thêm emoticon vào như sau:

var timer;
$(window).ready(function(){
    timer =setInterval(
        function(){
            if (CW.reg_cmp != undefined) {
                add_emo();
                window.clearInterval(timer);
            }
        },
        1000
    );
});

function add_emo(){
    for (var index = 0; index < emo.length; index++) {
        var encoded_text = htmlEncode(emo[index].key);
        if (emo[index].other_host) {
            img_src = emo[index].src;
        } else {
            img_src = 'http://cwep.thangtd.com/img/emoticons/' + emo[index].src;
        }
        CW.reg_cmp.push({
            key: emo[index].regex,
            rep: '<img src="' + img_src + '" title="' + encoded_text + '" alt="' +
            encoded_text + '" class="ui_emoticon"/>',
            reptxt: emo[index].key
        });
    }
}

ta tạo 1 timer đơn giản, cứ 1s check xem đã có biến CW.reg_cmp chưa, nếu có thì add_emo và clear timer. That's it             </div>
            
            <div class=

0