IT戦記

プログラミング、起業などについて書いているプログラマーのブログです😚

Firebug 1.2 のバグ

ブラウザを再起動したら Firebug 1.2 が起動しなくなったので

調べてみた><

原因はファイルのパーミッション

Index: branches/firebug1.2/components/firebug-service.js
===================================================================
--- branches/firebug1.2/components/firebug-service.js   (リビジョン 265)
+++ branches/firebug1.2/components/firebug-service.js   (作業コピー)
@@ -2383,7 +2383,7 @@
         file.append("firebug-service-dump.txt");
         //file.createUnique(CI("nsIFile").NORMAL_FILE_TYPE, 0666);
         var stream = CC("@mozilla.org/network/file-output-stream;1").createInstance(CI("nsIFileOutputStream"));
-        stream.init(file, 0x04 | 0x08 | 0x20, 664, 0); // write, create, truncate
+        stream.init(file, 0x04 | 0x08 | 0x20, 0664, 0); // write, create, truncate
         return stream;
     }
     catch (exc)

664 が十進数で 0644 が八進数というオチ。

Firebug 1.2 を使ってみた。

GranParadaiso で作業してて

  • おいおい。 Firebug 対応してねーよー。
  • ちょっとソース見てみるかあ

chrome からコンテンツにプロパティを設定できなくなっちゃった見たい。。。

win.hoge = hoge; // <- win はコンテンツの window
win.location = "javascript: eval(hoge)"; // <- hoge がないって怒られる

みたいなところが動かなくなってるみたい><

で、リポジトリを除いてみたら

branches/firebug1.2 ってのがあった

さっそくビルド

$ svn co http://fbug.googlecode.com/svn/branches/firebug1.2
$ cd firebug1.2
$ ant

firebug1.2/dist に xpi が出来てるので、それを Firefox で開く

動いたー!


ちょっと気になったのでソースを見てみたら

var sandbox = new Components.utils.Sandbox(win);
var sandbox.hoge = hoge;
Components.utils.evalInSandbox('alert(hoge)', sandbox);

こんな感じで Components.utils.Sandbox というのを使っていました。

ただ

これを使うと、var 宣言された変数は sandbox オブジェクトプロパティになってしまう(グローバルオブジェクトは win だけど)ので、そこだけ注意が必要。
今まで、コンソール上で var したらそのままコンテンツでも変数になったけど、 Firebug 1.2 では window.hoge = 'hoge'; のようにして変数を追加しない限りコンテンツは汚れない。

Firebug のコンソールに出力される形式を変える

そう><

length が数字だと無条件で配列扱いなんですよね。
childNodesとコメント - ロックスターになりたい

という訳で、

domplate をちょっといじってみたら意外と楽に出来たのでメモメモ

  • domplate は Firebug のテンプレートエンジン
  • コンソールの形式を定義したオブジェクトを作る
    • 自分が担当するオブジェクトが supportsObject に渡ってきたら true を返す
    • tag はコンソールに出るときのテンプレート(domplate)
    • shortTag は配列の中に入ってるときのテンプレート(domplate)
    • getTitle はプロパティの中に入ってるときの文字列(たぶん?)
  • 作ったオブジェクトは registerRep に入れる
image


diff
Index: reps.js
===================================================================
--- reps.js     (リビジョン 241)
+++ reps.js     (作業コピー)
@@ -488,6 +488,29 @@
 
 // ************************************************************************************************
 
+this.Comment = domplate(Firebug.Rep,
+{
+    tag: OBJECTLINK("&lt;-- ", SPAN({class: "nodeTag"}, "$object.nodeValue|cropString"), " --&gt;"),
+
+    shortTag: OBJECTLINK(SPAN({class: "selectorHidden"}, SPAN({class: "selectorTag"}, "!comment") ) ),
+    
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
+    
+    className: "element",
+
+    supportsObject: function(object)
+    {
+        return object instanceof Comment;
+    },
+
+    getTitle: function(object, context)
+    {
+        return "<!>";
+    }
+});
+
+// ************************************************************************************************
+
 this.Element = domplate(Firebug.Rep,
 {
     tag:
@@ -1411,6 +1434,7 @@
     this.NetFile,
     this.Property,
     this.Except,
+    this.Comment,
     this.Arr
 );

XPathResult 用のを作りたい!Firebug 最強。

ロケーションバーに直入力するとブクマを見に行って補完してくれるコンポーネント作った

今日徹夜ぎみで作ってみた



でも

使ってみたら逆に不便だった><
勉強になったからいいや。
破棄!

もったいないので、今手元にあるソースを貼っときます

このソースを Firefox インストールディレクトリ以下の components に入れると textbox 要素で autocomplete="delicious" が使えるようになります。
開発中のものなのでバグありまくりです。
ちなみに、僕が一日に書けるコードはちょうどこのくらいです↓

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const CLASS_ID = Components.ID('{aa892eb4-ffbf-477d-9f9a-06c995ae9f85}');
const CONSTRACT_ID = '@mozilla.org/autocomplete/search;1?name=delicious';

var SocialBookmark = function(href, description, hash, tags, date) {
    this.href = href;
    this.description = description;
    this.hash = hash;
    this.tags = tags.split(/\s+/);
    this.date = date;
};

var SocialBookmarkAPI = {
    username: null,
    password: null,
    _parseBookmarkXML: function(data) {
        var posts = data.getElementsByTagName('post');
        var bookmarks = [];
        for (var i = 0, l = posts.length; i < l; i ++) {
            var post = posts[i];
            bookmarks.push(
                new SocialBookmark(
                    post.getAttribute('href'),
                    post.getAttribute('description'),
                    post.getAttribute('hash'),
                    post.getAttribute('tag'),
                    post.getAttribute('time')));
                
        }
        return bookmarks;
    },
    _parseCountXML: function(date, data) {
        var dates = data.getElementsByTagName('date');
        if (!date) {
            date = dates[dates.length - 1].getAttribute('date');
        }
        else {
            date = date.split(/T/)[0];
        }
        var count = 0;
        for (var i = 0, l = dates.length; i < l; i ++) {
            count += +dates[i].getAttribute('count');
            if (date == dates[i].getAttribute('date')) {
                break;
            }
        }
        return count;
    },
    getBookmarks: function(callback) {
        var self = this;
        this._get('https://api.del.icio.us/v1/posts/all', function(data) {
        //this._get('https://api.del.icio.us/v1/posts/recent?count=100', function(data) {
            if (data) {
                callback(self._parseBookmarkXML(data));
            }
            else {
                callback(null);
            }
        });
    },
    getBookmarksFrom: function(date, callback) {
        var self = this;
        this._get('https://api.del.icio.us/v1/posts/dates', function(data) {
            if (data) {
                var count = self._parseCountXML(date, data);
                self._get('https://api.del.icio.us/v1/posts/recent?count=' + count, function(data) {
                    if (data) {
                        callback(self._parseBookmarkXML(data));
                    }
                    else {
                        callback(null);
                    }
                });
            }
            else {
                callback(null);
            }
        });
    },
    getRecentHash: function(callback) {
        var self = this;
        this._get('https://api.del.icio.us/v1/posts/recent?count=1', function(data) {
            if (data) {
                callback(self._parseBookmarkXML(data)[0].hash);
            }
            else {
                callback(null);
            }
        });
    },
    abort: function() {
        if (this._request) {
            this._request.abort();
            this._request = null;
        }
    },
    _get: function(uri, callback) {
        dump('_get(' + uri + ')\n');
        var self = this;
        var request = this._request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest);
        request.open('GET',uri,true,this.username,this.password);
        request.send(null);
        request.onreadystatechange = function() {
            if (!self._request || self._request.readyState != 4) {
                return;
            }
            try {
                var request = self._request;
                self._request = null;
                switch (request.status) {
                case 503: // Del.icio.us のサーバから蹴られた場合
                    dump(request.status + '\n');
                    callback(request.responseXML);
                    break;
                case 200: // OK の場合
                    callback(request.responseXML);
                    break;
                default:
                    dump(request.status + '\n');
                    callback(request.responseXML);
                    break;
                }
            }
            catch(e) {
                callback(null);
            }
        };
    },
    _setBasicData: function(request, username, password) {
        request.setRequestHeader('Authorization', 'Basic ' + this._toBase64(username + ':' + password));
    },
    _setWsseData: function(request, username, password) {
        var created = this._iso8601Date(new Date());
        var nonce = (Math.random() + "").substr(2, 32);
        var data = nonce + created + (password || '');

        var converter = Cc['@mozilla.org/intl/scriptableunicodeconverter'].createInstance(Ci.nsIScriptableUnicodeConverter);
        converter.charset = 'UTF-8';
        data = converter.convertToByteArray(data, {});
        var ch = Cc['@mozilla.org/security/hash;1'].createInstance(Ci.nsICryptoHash);
        ch.init(ch.SHA1);
        ch.update(data, data.length);
        var data = ch.finish(false);

        var passwordDigest = this._toBase64(data);
        nonce = this._toBase64(nonce);

        var wsse = 'UsernameToken Username="' + username + '", PasswordDigest="' + passwordDigest + '", Created="' + created + '", Nonce="' + nonce + '"';

        request.setRequestHeader('Authorization', 'WSSE profile="UsernameToken"');
        request.setRequestHeader('X-WSSE', wsse);
    },
    _iso8601Date: function(date) {
        var datetime = date.getUTCFullYear();
        var month = String(date.getUTCMonth() + 1);
        datetime += (month.length == 1 ?  '0' + month : month);
        var day = date.getUTCDate();
        datetime += (day < 10 ? '0' + day : day);
        datetime += 'T';
        var hour = date.getUTCHours();
        datetime += (hour < 10 ? '0' + hour : hour) + ':';
        var minutes = date.getUTCMinutes();
        datetime += (minutes < 10 ? '0' + minutes : minutes) + ':';
        var seconds = date.getUTCSeconds();
        datetime += (seconds < 10 ? '0' + seconds : seconds);
        return datetime;
    },
    _toBase64: function(data) {
        const toBase64Table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
        const base64Pad = '=';

        var result = '';
        var length = data.length;

        for (var i = 0; i < (length - 2); i += 3) {
            result += toBase64Table[data.charCodeAt(i) >> 2];
            result += toBase64Table[((data.charCodeAt(i) & 0x03) << 4) + (data.charCodeAt(i+1) >> 4)];
            result += toBase64Table[((data.charCodeAt(i+1) & 0x0f) << 2) + (data.charCodeAt(i+2) >> 6)];
            result += toBase64Table[data.charCodeAt(i+2) & 0x3f];
        }

        if (length%3) {
            i = length - (length%3);
            result += toBase64Table[data.charCodeAt(i) >> 2];
            if ((length%3) == 2) {
                result += toBase64Table[((data.charCodeAt(i) & 0x03) << 4) + (data.charCodeAt(i+1) >> 4)];
                result += toBase64Table[(data.charCodeAt(i+1) & 0x0f) << 2];
                result += base64Pad;
            }
            else {
                result += toBase64Table[(data.charCodeAt(i) & 0x03) << 4];
                result += base64Pad + base64Pad;
            }
        }
        return result;
    }

};

var SocialBookmarkUrlCache = function(username, password, callback) {
    SocialBookmarkAPI.username = username;
    SocialBookmarkAPI.password = password;
    var self = this;
    this.bookmarks = [];
    this.load();
    this.hashes = {};
    this.update(function(result) {
        callback(result);
    });
};
SocialBookmarkUrlCache.prototype = {
    updateAll: function(callback) {
        var self = this;
        SocialBookmarkAPI.abort();
        SocialBookmarkAPI.getBookmarks(function(bookmarks) {
            if (bookmarks) {
                self._appendBookmarks(bookmarks);
                callback(true);
            }
            else {
                callback(false);
            }
        });
    },
    update: function(callback) {
        if (!this.loaded) {
            return this.updateAll(callback)
        }
        var self = this;
        SocialBookmarkAPI.abort();
        SocialBookmarkAPI.getRecentHash(function(hash) {
            if (hash && (!self.recentHash || self.recentHash != hash)) { // 更新されていたら
                SocialBookmarkAPI.getBookmarksFrom(self.recentDate, function(bookmarks) {
                    if (bookmarks) {
                        self._appendBookmarks(bookmarks);
                        callback(true);
                    }
                    else {
                        callback(false);
                    }
                });
            }
            else {
                callback(true);
            }
        });
    },
    _file: function() {
        var file = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties).get("ProfD", Ci.nsILocalFile);
        file.append("social_bookmark_autocomplete_cache.js");

        return file; 
    },
    load: function() {
        var file = this._file();

        if (!file.exists()) {
            return;
        }

        var data = "";
        var fstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(Ci.nsIFileInputStream);
        var sstream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(Ci.nsIScriptableInputStream);

        fstream.init(file, -1, 0, 0);
        sstream.init(fstream); 
        
        var str = sstream.read(4096);
        while (str.length > 0) {
            data += str;
            str = sstream.read(4096);
        }
        
        sstream.close();
        fstream.close();

        this.bookmarks = eval(data);
        if (this.bookmarks[0]) {
            this.recentHash = this.bookmarks[0].hash;
            this.recentDate = this.bookmarks[0].date;
        }
        this.loaded = true;
    },
    save: function() {
        var file = this._file();
        if (file.exists()) {
            file.remove(false);
        }
        var stream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(Ci.nsIFileOutputStream);
        stream.init(file, 0x02 | 0x08 | 0x20, 420, -1);
        var data = this.bookmarks.toSource();
        stream.write(data, data.length);
        stream.close();
        this.loaded = true;
    },
    createSuggestAutoCompleteResult: function(searchString) {
        var self = this;
        var results = [];
        var comments = [];
        this.bookmarks.forEach(function(bm) {
            if (bm.href.indexOf(searchString, 0) != -1) {
                results.push(bm.href);
                comments.push(bm.tags.join(' '));
            }
            else if (bm.tags.some(function(tag) { return tag.indexOf(searchString) != -1 })) {
                results.push(bm.href);
                comments.push(bm.tags.join(' '));
            }
        });
        return new SocialBookmarkSuggestAutoCompleteResult(
                searchString,
                Ci.nsIAutoCompleteResult.RESULT_SUCCESS,
                0,
                '',
                results,
                comments);
    },
    _appendBookmarks: function(bookmarks) {
        bookmarks = bookmarks.reverse();
        for (var i = 0, l = bookmarks.length; i < l; i ++) {
            this._appendBookmark(bookmarks[i]);
        }
        this.save();
        this.recentHash = this.bookmarks[0].hash;
        this.recentDate = this.bookmarks[0].date;
    },
    _appendBookmark: function(bookmark) {
        if (!this.hashes[bookmark.hash]) {
            this.hashes[bookmark.hash] = true;
            this.bookmarks.unshift(bookmark);
        }
    }
};

var SocialBookmarkSuggestAutoCompleteResult = function(
                                                searchString,
                                                searchResult,
                                                defaultIndex,
                                                errorDescription,
                                                results,
                                                comments) {
    this._searchString = searchString;
    this._searchResult = searchResult;
    this._defaultIndex = defaultIndex;
    this._errorDescription = errorDescription;
    this._matchCount = results.length;
    this._results = results;
    this._comments = comments;
};
SocialBookmarkSuggestAutoCompleteResult.prototype = {
    _className: 'SocialBookmarkSuggestAutoCompleteResult',
    get searchString() {
        return this._searchString;
    },
    get searchResult() {
        return this._searchResult;
    },
    get defaultIndex() {
        return this._defaultIndex;
    },
    get errorDescription() {
        return this._errorDescription;
    },
    get matchCount() {
        return this._matchCount;
    },
    getValueAt: function(index) {
        return this._results[index];
    },
    getCommentAt: function(index) {
        return this._comments[index];
    },
    getStyleAt: function(index) {
        return 'hoge';
    },
    removeValueAt: function(rowIndex, removeFrom) {
    },
    QueryInterface: function(iid) {
        if (!iid.equals(Ci.nsIAutoCompleteResult) &&
            !iid.equals(Ci.nsISupports)) {
            throw Cr.NS_ERROR_NO_INTERFACE;                        
        }
        return this;
    }
}

var SocialBookmarkSuggestAutoComplete = function() {
    this._busy = true;
    this._available = true;
    self = this;
    this._cache = new SocialBookmarkUrlCache('amachang', '*********', function(result) {
        if (!result) {
            self._available = false;
        }
        self._busy = false;
    });
};
SocialBookmarkSuggestAutoComplete.prototype = {
    _className: 'SocialBookmarkSuggestAutoComplete',
    startSearch: function(searchString, searchParam, previousResult, listener) {
        if (!this._busy && this._available) {
            var cache = this._cache;
            var self = this;
            cache.update(function(result) {
                if (!result) {
                    self._available = false;
                }
                else {
                    listener.onSearchResult(self, cache.createSuggestAutoCompleteResult(searchString));
                }
            });
        }
    },
    stopSearch: function() {
    },
    QueryInterface: function(iid) {
        if (!iid.equals(Ci.nsIAutoCompleteSearch) &&
            !iid.equals(Ci.nsIAutoCompleteObserver) &&
            !iid.equals(Ci.nsISupports)) {
            throw Cr.NS_ERROR_NO_INTERFACE;                        
        }
        return this;
    }
};

function NSGetModule(compMgr, fileSpec) {
    dump('HA: Call NSGetModule\n');
    return {
        registerSelf: function(compMgr, fileSpec, location, type) {
            dump('HA: Call registerSelf\n');
            compMgr = compMgr.QueryInterface(Ci.nsIComponentRegistrar);
            compMgr.registerFactoryLocation(
                CLASS_ID,
                'Social Bookmark Suggestions',
                CONSTRACT_ID,
                fileSpec,
                location,
                type);
        },
        getClassObject: function(compMgr, cid, iid) {
            return {
                createInstance: function(outer, iid) {
                    if (outer != null) {
                        throw Cr.NS_ERROR_NO_AGGREGATION;
                    }
                    return new SocialBookmarkSuggestAutoComplete().QueryInterface(iid);
                }
            };
        }
    };
}

dump('HA: Load\n');

Mozilla の JS には Catch Guard なんてあるのね。知らなかった。

Catch Guard とは

try {
  throw Math.random();
}
catch (e if e < 0.3) {
  alert('ちっちゃい');
}
catch (e if e < 0.7) {
  alert('ふつー');
}
catch (e) {
  alert('でかい');
}

こんな感じで catch の条件を指定できる文法。

へー!!

知らなかった!
でも、 Mozilla 限定だから、拡張機能の開発とかにしか使えない><

Mac 2 日目の僕でも出来た! XCode を使った Firefox デバッギング

ちょっと釣りっぽいタイトルでごめんなさいw

でも、本当に簡単に出来るので皆様もお試しあれ。あと、 Mac まだよくわかってないのですごい回りくどいことやってるかもしれないので、そのときは指摘してくだしあ><

じゃあ、いってみよう!

1. Firefox のソース持ってくる!
$ mkdir ~/source
$ cd ~/source
$ export CVSROOT=:pserver:anonymous@cvs-mirror.mozilla.org:/cvsroot
$ cvs login
pass:anonymous
$ cvs co mozilla/client.mk mozilla/browser/config
$ cd ~/source/mozilla
$ make -f client.mk pull_all MOZ_CO_PROJECT=browser

とやるとソースがダーっとダウンロードされる。
今は ~/source ってところで作業する前提でやってみる

2. ビルドに必要なものはそろってる?
$ sudo port install libidl
$ sudo port install autoconf213

ほかにも何か必要なものとかあるのかなあ?多分これくらいしかいれてないと思うんだけど

3. .mozconfig というファイルを作っちゃう!

以下の場所*1にファイルを作ります。

~/source/mozilla/.mozconfig

内容は以下の通り。ここに書いてある通りやったら行けました!感謝><

mk_add_options MOZ_CO_PROJECT=browser
mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/obj-@CONFIG_GUESS@
ac_add_options --enable-application=browser
ac_add_options --disable-optimize
ac_add_options --disable-tests
ac_add_options --enable-debug
ac_add_options --enable-svg

(追記)takenspc 様の指摘で修正いたしました。ありがとうございます。

4. configure して make !

めっちゃ時間がかかるので寝る準備をしてからやったほうがいいかもしれません><

$ ./configure --disable-installer ; make

成功すると

~/source/mozilla/dist/MinefieldDebug.app

というディレクトリが生成されます。

5. XCode を起動

XCode は以下の場所にあります。

/Developer/Applications/Xcode.app
6. プロジェクトの作成

ファイル → 新規プロジェクト を実行

空のプロジェクト → 次へ → プロジェクト名を入れて(今は mozdebug にしてみた) → 完了

そしたら、こんな感じの画面が出てくる

7. 実行可能ファイルの設定

プロジェクト → 新規カスタム実行可能ファイルを実行

以下のようなダイアログが出るので

実行可能ファイル名は適当に、実行可能ファイルのパスは

/Users/<user>/source/mozilla/dist/MinefieldDebug.app/Contents/MacOS/firefox-bin

と入れる(絶対パスで入れてね!相対だとなぜか動かない)
で、ポチっと完了を押す。
なんか設定っぽいダイアログが出る*2のでデバッグタブを開いて

ソースファイルを検索する追加のディレクトリに以下のディレクトリを追加してあげる

/Users/<user>/source/mozilla/

これも絶対パス

8. デバッグ開始

まず、二重起動にならないように Firefox をすべて終了させる。
デバッグ → 実行可能ファイルをデバッグ を選択する。

するとブラウザが起動し、ブラウザと一緒にこんなウィンドウが起動する

で、この右ペインにはソースをドラッグできるので、あとは、ブレークポイントとか張り放題!
ステップ実行とかコールスタックのチェックとか変数のモニタリングとかも完璧!

完成!Xcode 万歳!ddd とかより全然使いやすいわー

まとめ。ひゃっほーい!

途中から説明が雑になったのはめんどくさくなったから><っていうのは内緒です。

*1:~/source は各自の作業場所に脳内変換してください

*2:出ない人は プロジェクト → アクティブな実行可能ファイル'MinefieldDebug'の編集で出ます。

ブラウザ再起動ぼた〜ん

作りました。

拡張機能とか作ってるときにテラ便利ス

<?xml version="1.0"?>

<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

  <toolbarpalette id="BrowserToolbarPalette">
    <toolbarbutton id="tmpRestartApp"
                   style="list-style-image: url(data:image/gif;base64,R0lGODlhEAAQAMQfAOt0dP94eOFjY/a0tP/JyfFfX/yVlf6mppNtbf5qanknJ9dVVeZqat5eXpiMjGo4OIUvL3pGRthWVuhvb1kaGv39/f1lZdg7O/7Y2F8/P+13d4tcXNRTU2dCQv///////yH5BAEAAB8ALAAAAAAQABAAAAV/4Cd+Xml6Y0pGTosgEap6G0YQh6FDskhjGg0AMJkwAjxfBygkGhmCAAXl6QyGnuLFI4g+qNbixLMNdBNfkpXBLncbial6AC17Gvg4eND1BPB3cHJVBguGhwsSHHo+GRqKHJGRCQo9JI4WBZoFFpUVMw8QCqMQU58qJCclqKytIQA7)"
                   class="toolbarbutton-1 chromeclass-toolbar-additional"
                   oncommand="Components.classes['@mozilla.org/toolkit/app-startup;1'].getService(Components.interfaces.nsIAppStartup).quit(Components.interfaces.nsIAppStartup.eRestart|Components.interfaces.nsIAppStartup.eAttemptQuit)"
                   label="Restart"
                   tooltiptext="Restart" />
  </toolbarpalette>

</overlay>

これを browser.xul と customizeToolbar.xul にオーバレイさせとくとブラウザをサクっと再起動してくれる便利ボタンが使えるようになります。