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 というのを使っていました。
Firebug のコンソールに出力される形式を変える
そう><
length が数字だと無条件で配列扱いなんですよね。
childNodesとコメント - ロックスターになりたい
という訳で、
domplate をちょっといじってみたら意外と楽に出来たのでメモメモ
diff
Index: reps.js =================================================================== --- reps.js (リビジョン 241) +++ reps.js (作業コピー) @@ -488,6 +488,29 @@ // ************************************************************************************************ +this.Comment = domplate(Firebug.Rep, +{ + tag: OBJECTLINK("<-- ", SPAN({class: "nodeTag"}, "$object.nodeValue|cropString"), " -->"), + + 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 なんてあるのね。知らなかった。
ふと
MDC を見ていたら
http://developer.mozilla.org/ja/docs/Core_JavaScript_1.5_Guide:Exception_Handling_Statements:try...catch_Statement#catch_.E3.83.96.E3.83.AD.E3.83.83.E3.82.AF
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 の条件を指定できる文法。
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
というディレクトリが生成されます。
6. プロジェクトの作成
ファイル → 新規プロジェクト を実行
空のプロジェクト → 次へ → プロジェクト名を入れて(今は mozdebug にしてみた) → 完了
→
そしたら、こんな感じの画面が出てくる
7. 実行可能ファイルの設定
プロジェクト → 新規カスタム実行可能ファイルを実行
以下のようなダイアログが出るので
実行可能ファイル名は適当に、実行可能ファイルのパスは
/Users/<user>/source/mozilla/dist/MinefieldDebug.app/Contents/MacOS/firefox-bin
と入れる(絶対パスで入れてね!相対だとなぜか動かない)
で、ポチっと完了を押す。
なんか設定っぽいダイアログが出る*2のでデバッグタブを開いて
ソースファイルを検索する追加のディレクトリに以下のディレクトリを追加してあげる
/Users/<user>/source/mozilla/
これも絶対パス
まとめ。ひゃっほーい!
途中から説明が雑になったのはめんどくさくなったから><っていうのは内緒です。
ブラウザ再起動ぼた〜ん
作りました。
拡張機能とか作ってるときにテラ便利ス
<?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 にオーバレイさせとくとブラウザをサクっと再起動してくれる便利ボタンが使えるようになります。