FirefoxアドオンとChrome拡張機能

アドオン開発について

今回はAdmatrix AnalyticsというChromeとFirefoxに関連した拡張機能・アドオンを採り上げます。SEOアナライザのような拡張機能で、サイトのSEOの内部対策を行っていくために必要な情報を抽出するアドオンです。

アドオンのスクリプトは操作すれば誰でも見られるようになっていますので、ここで一挙に公開いたします。

Google Chrome側の開発

Google Chromeで開発した拡張機能用のJavaScripyは以下のようになっています。

var page_mod = require("sdk/page-mod");
const {Cc, Ci} = require('chrome');
var request = require("sdk/request");
var widgets = require("sdk/widget");
var tabs = require("sdk/tabs");
var data = require("sdk/self").data;
var self = require("sdk/self");
var ss = require("sdk/simple-storage").storage;
ss['AdmatrixStatus'] = 'open';
var id = '0';
var mediator = Cc['@mozilla.org/appshell/window-mediator;1'].getService(Ci.nsIWindowMediator);
 
// exports.main is called when extension is installed or re-enabled
exports.main = function(options, callbacks) {
    addToolbarButton();
};
 
// exports.onUnload is called when Firefox starts and when the extension is disabled or uninstalled
exports.onUnload = function(reason) {
    removeToolbarButton();
};
 
// add our button
function addToolbarButton() {
    // this document is an XUL document
    var document = mediator.getMostRecentWindow('navigator:browser').document;      
    var navBar = document.getElementById('nav-bar');
    var btn = document.createElement('toolbarbutton');  
    if (!navBar) {
        return;
    }
    var popup = require("sdk/panel").Panel({
      width: 180,
      height: 120,
      position: {top:0,right:0},
      contentURL: data.url("panel.html"),
      contentScriptFile: [data.url("jquery.js"), data.url("popup.js")]
    });
    popup.on("show", function() {
        if(ss['AdmatrixStatus']){
            status = ss['AdmatrixStatus'];
        };
        popup.port.emit("show",status);
    });

    popup.port.on("toggle", function () {
        status = ss['AdmatrixStatus'];
        worker = tabs.activeTab.attach({
            contentScriptFile: [data.url('jquery.js'), data.url('modPage.js')]
        });
        if(status == 'close'){
            // close -> open
            worker.port.emit("inject",id);
            ss['AdmatrixStatus'] = 'open';
            popup.port.emit("btn",'close');
            btn.setAttribute('image', data.url('images/icon/19.png'));
        }else{
            // open -> close
            worker.port.emit("remove");
            ss['AdmatrixStatus'] = 'close';
            popup.port.emit("btn",'open');
            btn.setAttribute('image', data.url('images/icon/19_off.png'));
        }
    });
    btn.setAttribute('id', 'mybutton-id');
    btn.setAttribute('type', 'button');
    // the toolbarbutton-1 class makes it look like a traditional button
    btn.setAttribute('class', 'toolbarbutton-1');
    // the data.url is relative to the data folder
    btn.setAttribute('image', data.url('images/icon/19.png'));
    btn.setAttribute('orient', 'horizontal');
    // this text will be shown when the toolbar is set to text or text and iconss
    btn.setAttribute('label', 'My Button');
    btn.addEventListener('click', function() {
        popup.show();
        status = ss['AdmatrixStatus'];
        worker = tabs.activeTab.attach({
            contentScriptFile: [data.url('jquery.js'), data.url('modPage.js')]
        });
        //popup.port.emit("show",status);
        if(status == 'close'){
            worker.port.emit("inject",id);
            ss['AdmatrixStatus'] = 'open';
            btn.setAttribute('image', data.url('images/icon/19.png'));
        }else{
            worker.port.emit("remove");
            ss['AdmatrixStatus'] = 'close';
            btn.setAttribute('image', data.url('images/icon/19_off.png'));
        }
        // do stuff, for example with tabs or pageMod
    }, false)
    navBar.appendChild(btn);
}
function removeToolbarButton() { 
    var enumerator = mediator.getEnumerator("navigator:browser"); 
    while(enumerator.hasMoreElements()) { 
        var document = enumerator.getNext().document; 
        var navBar = document.getElementById('nav-bar'); 
        var btn = document.getElementById('mybutton-id'); 
        if (navBar && btn) { 
            navBar.removeChild(btn); 
        } 
    } 
}

initPageMod();

function initPageMod()
{
    myPageMod = page_mod.PageMod({
        include: ['*'],
        contentScriptWhen: "end",
        contentScriptFile: [data.url("jquery.js"), data.url("modPage.js")],
        attachTo: ["top"],
        onAttach: function(worker) {
            if(ss['AdmatrixId']){
                id = ss['AdmatrixId'];
            }else{
                
            }
            if(ss['AdmatrixStatus'] == 'open'){
                request.Request({
                    url : "https://seo-analytics.fs-site.net/1.0.0/"+ id +"/createUser.php",
                    content:{
                        loaded_url:worker.url
                    },
                    onComplete : function(response){
                        id = JSON.parse(response.text).id;
                        ss['AdmatrixId'] = id;
                        worker.port.emit("inject",id);
                    }
                }).get();
            }
        }
    });
}

Firefoxのアドオン開発

var page_mod = require("sdk/page-mod");
const {Cc, Ci} = require('chrome');
var request = require("sdk/request");
var widgets = require("sdk/widget");
var tabs = require("sdk/tabs");
var data = require("sdk/self").data;
var self = require("sdk/self");
var ss = require("sdk/simple-storage").storage;
var prefs = require('sdk/simple-prefs');
var mediator = Cc['@mozilla.org/appshell/window-mediator;1'].getService(Ci.nsIWindowMediator);
 
// exports.main is called when extension is installed or re-enabled
exports.main = function(options, callbacks) {
    addToolbarButton();
    initPageMod();
    makeOptions();
};
 
// exports.onUnload is called when Firefox starts and when the extension is disabled or uninstalled
exports.onUnload = function(reason) {
    removeToolbarButton();
};
function getExceptions(url) {
    if(!exceptions_enable) {
        return false;
    }
    var domainLabels = url.split('/')[2].split(':')[0].split('.').reverse();
    var loadedExceptions = ss['exceptions'];
    loadedExceptions = (loadedExceptions == null) ? [] : loadedExceptions.split(/\t/);
    var len = loadedExceptions.length;
    for (var i=0; i<len; i++) {
        var http = loadedExceptions[i].indexOf('http://');
        var https = loadedExceptions[i].indexOf('https://');
        var ftp = loadedExceptions[i].indexOf('ftp://');
        if(http == 0 || https == 0 || ftp == 0) {
            if(loadedExceptions[i] == url) {
                //console.log(true);
                return true;
            }
        } else {
            var same = false;
            var exceptionLabels = loadedExceptions[i].split('.').reverse();
            var len2 = exceptionLabels.length;
            var len3 = (exceptionLabels[len2 - 1] == '*') ? len2 - 1 : len2;
            for (var p=0; p<len3; p++) {
                //console.log(exceptionLabels[p]+':'+domainLabels[p]);
                if(exceptionLabels[p] == domainLabels[p]) {
                    same = true;
                } else {
                    same = false;
                    break;
                }
            }
            if(exceptionLabels[len2 - 1] == '*') {
                if(same == true) {
                    //console.log(true);
                    return true;
                }
            } else {
                if(domainLabels.length != len2) {
                    same = false;
                }
            }
            if(same) {
                //console.log(true);
                return true;
            }
        }                   
    }
    //console.log(false);
    return false;
}
function versionCheck() {
    if(ss['AdmatrixId'] != null && ss['addon-version'] != self.version) {//2回目以降の起動でアップデートが検知された場合
        ss['AdmatrixStatus'] = 'open';
        ss['addon-version'] = self.version;
        return true;
    } else {
        if(ss['AdmatrixId'] == null) {//インストール後、初回の起動の場合
            ss['AdmatrixStatus'] = 'open';
            ss['addon-version'] = self.version;
        }
        return false;
    }
}
function makeOptions(){
    page_mod.PageMod({
        include: data.url("options.html"),
        contentScriptFile:  [data.url("jquery.js"), data.url("options.js")],
        onAttach: function(worker) {
            worker.port.on("load-exceptions-enable", function(){
                if(ss['AdmatrixId']){
                    id = ss['AdmatrixId'];
                } else {
                    id = 'idIsNotStored';
                }
                request.Request({
                    url : "https://seo-analytics.fs-site.net/1.0.0/"+ id +"/createUser.php",
                    onComplete : function(response){
                        exceptions_enable = JSON.parse(response.text)['exceptions-enable'];
                        worker.port.emit("return-exceptions-enable", exceptions_enable);
                    }
                }).get();
            });
            worker.port.on("load-data", function(){
                worker.port.emit("return-data", ss['exceptions']);
            });
            worker.port.on("save-data", function(data){
                ss['exceptions'] = data;
            });
            worker.port.on("delete-data", function(){
                delete ss['exceptions'];
            });
        }
    });
    prefs.on('adMatrixAnalyticsOptions', function(name) {
        tabs.open(data.url("options.html"));
    });
}
// add our button
function addToolbarButton() {
    // this document is an XUL document
    var document = mediator.getMostRecentWindow('navigator:browser').document;      
    var navBar = document.getElementById('nav-bar');
    var btn = document.createElement('toolbarbutton');  
    if (!navBar) {
        return;
    }
    var popup = require("sdk/panel").Panel({
      width: 180,
      height: 120,
      position: {top:0,right:0},
      contentURL: data.url("panel.html"),
      contentScriptFile: [data.url("jquery.js"), data.url("popup.js")]
    });
    popup.on("show", function() {
        if(ss['AdmatrixStatus']){
            status = ss['AdmatrixStatus'];
        };
        popup.port.emit("show",status);
    });

    popup.port.on("toggle", function () {
        status = ss['AdmatrixStatus'];
        if(status == 'close'){
            // close -> open
            ss['AdmatrixStatus'] = 'open';
            popup.port.emit("btn",'close');
            btn.setAttribute('image', data.url('images/icon/19.png'));
            for each (var tab in tabs) {
                worker = tab.attach({
                    contentScriptFile: [data.url('jquery.js'), data.url('modPage.js')]
                });
                worker.port.on('save-state', function(state) {
                    ss['state' + worker.tab.id] = state;
                });
                inject(worker);
            }
        }else{
            // open -> close
            ss['AdmatrixStatus'] = 'close';
            popup.port.emit("btn",'open');
            btn.setAttribute('image', data.url('images/icon/19_off.png'));
            for each (var tab in tabs) {
                worker = tab.attach({
                    contentScriptFile: [data.url('jquery.js'), data.url('modPage.js')]
                });
                worker.port.emit("remove");
            }
        }
    });
    btn.setAttribute('id', 'mybutton-id');
    btn.setAttribute('type', 'button');
    // the toolbarbutton-1 class makes it look like a traditional button
    btn.setAttribute('class', 'toolbarbutton-1');
    // the data.url is relative to the data folder
    if(ss['AdmatrixStatus'] == 'open' || ss['AdmatrixStatus'] == null){
        btn.setAttribute('image', data.url('images/icon/19.png'));
    } else {
        btn.setAttribute('image', data.url('images/icon/19_off.png'));
    }
    //2回目以降の起動でアップデートが検知されたらアイコンをオンにする
    if(ss['AdmatrixId'] != null && ss['addon-version'] != self.version) {
        btn.setAttribute('image', data.url('images/icon/19.png'));
    }
    btn.setAttribute('orient', 'horizontal');
    // this text will be shown when the toolbar is set to text or text and iconss
    btn.setAttribute('label', 'My Button');
    btn.addEventListener('click', function() {
        popup.show();
        status = ss['AdmatrixStatus'];
        //popup.port.emit("show",status);
        if(status == 'close'){
            ss['AdmatrixStatus'] = 'open';
            btn.setAttribute('image', data.url('images/icon/19.png'));
            for each (var tab in tabs) {
                worker = tab.attach({
                    contentScriptFile: [data.url('jquery.js'), data.url('modPage.js')]
                });
                worker.port.on('save-state', function(state) {
                    ss['state' + worker.tab.id] = state;
                });
                inject(worker);
            }
        } else {
            ss['AdmatrixStatus'] = 'close';
            btn.setAttribute('image', data.url('images/icon/19_off.png'));
            for each (var tab in tabs) {
                worker = tab.attach({
                    contentScriptFile: [data.url('jquery.js'), data.url('modPage.js')]
                });
                worker.port.emit("remove");
            }
        }
        // do stuff, for example with tabs or pageMod
    }, false)
    navBar.appendChild(btn);
}
function removeToolbarButton() { 
    var enumerator = mediator.getEnumerator("navigator:browser"); 
    while(enumerator.hasMoreElements()) { 
        var document = enumerator.getNext().document; 
        var navBar = document.getElementById('nav-bar'); 
        var btn = document.getElementById('mybutton-id'); 
        if (navBar && btn) { 
            navBar.removeChild(btn); 
        } 
    } 
}
function initPageMod()
{
    myPageMod = page_mod.PageMod({
        include: ['*'],
        contentScriptWhen: "end",
        contentScriptFile: [data.url("jquery.js"), data.url("modPage.js")],
        attachTo: ["top"],
        onAttach: function(worker) {
            //各tabが閉じられたら該当するストレージを削除するイベントハンドラ
            worker.tab.on("close", function() {
                delete ss['state' + worker.tab.id];
            });
            //stateの保存用のイベントハンドラ
            worker.port.on('save-state', function(state) {
                ss['state' + worker.tab.id] = state;
            })
            //tabごとのストレージがなかったら初期値を設定する
            if(ss['state' + worker.tab.id] == null) {
                ss['state' + worker.tab.id] = {"height" : "380", "minimize" : "open", "title" : "on"};
            }
            update = versionCheck();
            //アップデートが検知されたらアップデート用の設定にする
            if(update) {
                ss['state' + worker.tab.id] = {"height" : "380", "minimize" : "close", "title" : "on"};
            }
            if(ss['AdmatrixId']){
                id = ss['AdmatrixId'];
            } else {
                id = 'idIsNotStored';
            }
            request.Request({
                url : "https://seo-analytics.fs-site.net/1.0.0/"+ id +"/createUser.php",
                onComplete : function(response){
                    ss['AdmatrixId'] = JSON.parse(response.text)['id'];
                    token = JSON.parse(response.text)['token'];
                    script_version = JSON.parse(response.text)['script-version'];
                    title_view_enable = JSON.parse(response.text)['title-view-enable'];
                    exceptions_enable = JSON.parse(response.text)['exceptions-enable'];
                    inject(worker);
                }
            }).get();
        }
    });
}
function inject(worker) {
    if(ss['AdmatrixStatus'] == 'open' && !getExceptions(worker.url)){
        worker.port.emit("inject",{
        "AdmatrixId" : ss['AdmatrixId'],
        "token" : token,
        "script_version" : script_version,
        "addon_version" : ss['addon-version'],
        "title_view_enable" : title_view_enable,
        "exceptions_enable" : exceptions_enable,
        "state" : ss['state' + worker.tab.id],
        "update" : update
        });
    }
}