Now browsing the SCRAPBLOG weblog archives.

nsIFeed から各種フィード情報を取得する

Firefox 2 に搭載された Feed content access API を使って、各種フィード情報を取得する。
以下、「feed」を nsIFeed 型オブジェクトとする。

フィードのタイトル

// マークアップされている場合にタグも含んだ文字列を取得する
var feedTitle = feed.title.text;
// マークアップされている場合にタグを除いた文字列として取得する
var feedTitle = feed.title.plainText();
// マークアップされている場合に node に対する DocumentFragment として取得する
var feedTitle = feed.title.createDocumentFragment(node);

フィードのサブタイトル

title, subtitle プロパティはともに nsIFeedTextConstruct 型を返すので、マークアップされている場合の取り扱いは title 同様。

var feedSubTitle = feed.subtitle.plainText();

フィードの最終更新日

nsIFeedContainer の updated プロパティは RFC822 形式の文字列を返すので、そのまま Date オブジェクトを生成することが可能。

var feedUpdated = new Date(feed.updated);

フィードの画像

RSS2.0 の image タグで記述されたフィードの画像を取得する。

var feedImage = feed.fields.getProperty("image").QueryInterface(Components.interfaces.nsIPropertyBag2);
// 画像のURL
feedImage.getPropertyAsAString("url");
// リンク先URL
feedImage.getPropertyAsAString("link");
// タイトル
feedImage.getPropertyAsAString("title");

リファレンス:
nsIFeed – MDC
nsIFeedContainer – MDC
nsIFeedTextConstruct – MDC
Interface Reference – nsIPropertyBag2

TOP

nsIFeedEntry から各種フィードエントリ情報を取得する

Firefox 2 に搭載された Feed content access API を使って、各種フィードエントリ情報を取得する。
以下、「entry」を nsIFeedEntry 型オブジェクトとする。

エントリのタイトル

フィードのタイトルと同様。

var title = entry.title.text;

エントリのパーマリンク

nsIFeedContainer の link プロパティは nsIURI 型を返す。

var parmaLink = entry.link.spec;

エントリの最終更新日

フィードの最終更新日と同様。

var lastUpdated = new Date(entry.updated);

エントリの本文

// RSS2.0の description タグで記述された内容を取得する
var summary = entry.summary;
// RSS2.0の content:encoded タグで記述された内容を取得する
var content = entry.content;
// node に対する DocumentFragment を生成する
var docFrag = (content || summary).createDocumentFragment(node);

エントリの筆者

for (var i = 0; i < entry.authors.length; i++)
{
    var author = entry.authors.queryElementAt(i, Components.interfaces.nsIFeedPerson);
    // 名前
    var authorName = author.name;
    // E-mail
    var authorMail = author.email;
    // 関連 URI(多くの場合ホームページ)
    var authorURI  = author.uri.spec;
}

エントリのカテゴリ

nsIFeedContainer の categories プロパティは使えないようだ。その代わり、以下のようにすることで成功。

var categories = entry.fields.getProperty("categories").QueryInterface(Components.interfaces.nsIArray);
for (var i = 0; i < categories.length; i++)
{
    var category = categories.queryElementAt(i, Components.interfaces.nsIPropertyBag);
    // カテゴリ名称
    var term = category.getProperty("term");
}

リファレンス:
nsIFeedPerson - MDC
Interface Reference - nsIArray

TOP

[userChrome.css] IE7 Throbber

Firefox 3 の Throbber (読み込み中にクルクル回転するアイコン)が地味であまり好きではないので、Internet Explorer7 風に変更するための userChrome.css です。アイコン画像は Ex Aequo から拝借し、少し改変して回転速度をアップさせています。

実際にアイコンの動きを見る

IE7 Throbber

userChrome.css

/* ::::: IE7 Throbber (Firefox 3) ::::: */

#navigator-throbber[busy="true"],
toolbar[iconsize="small"] #navigator-throbber[busy="true"],
toolbar[mode="text"] #navigator-throbber[busy="true"],
.tabbrowser-tab[busy] > .tab-icon-image,
.alltabs-item[busy] > .menu-iconic-left > .menu-iconic-icon,
#sidebar-throbber[loading="true"],
#checkForUpdates[loading="true"],
#extensionsManager richlistitem[loading="true"] .updateBadge,
#extensionsManager .addonThrobber,
#extensionsManager .throbber {
    list-style-image: url("data:image/gif;base64,R0lGODlhEAAQAIcAAO7y9%2BHs8anG24Kty2Wbv63J23eqx5vC1bvW4tXm7JfC0pq3xYa0x7XU3bLP3Z3F1ZnGzqrO18rm7aO3vYCktqfK2ZG9z97m7GyLlIS8wJW9wXKRl4CotL3Z222hpVqXjmensWyllE%2BMgXqXnFGcoRedoSSdkimBd46nrGeHjUGythS1ryq0o8Dp487p5Ql2YiVmWUu8xIPk4fX7%2FKjW0uvy9p3NzHiztGrL1Jnn5dzy7pvbxujy8kS4qzOPgrnp8T3Z4ur9%2FYPbxdLx83fHxjO2pmC9stPo40nZ1Jn497f3%2BKj08y3YwYDdz8Lp6ozZz0TJuzrLtGTWy1S1qZi7ty7t41fv6Cbc2hXWyRHFsk7ky3je01bMxY7GuWjh1DHl1Vf283vv66Pv6UbTp5LcwdHt5YTfulnYqjvVqovfvr%2Fb42eapXzAz6fFyme9xIfN15zV3UfFyanq7Z7L0VB%2BeyvR09309svn5iJYXMXy79zt667X1CxtcFTWypnx7%2BPz72fUu97y9Vasqavn5jPZ0Xr483HHvlO9tGOspV6LiEPZu8zq65PW0kjFqym7tFnHuCPUtZrDwTbfs1fn4Xnn4Jjj2aHYzeTy96bc5pjO3oLW3qG9x9bt8YKwsBno3j3SztPo7DvJxXrVyg9sbmO5ri7MnlnTt4TEqVuknn3LrIrv6Mf6%2BivBlZLd0FfDqrTu6X3QxXPSqdPy%2BF7H2m%2FY5bzh6ajS35rr7tr6%2BkXq5lp9hTa9yipZYTPBpghhaB5SWEq8nGTZ3SSkmhGtl2jCsqni3H%2FGu1aqwV%2BgwJ3L3JXT3EGyhB%2FFvkjEhoTTypC%2Fsc3o3ZvT0Zrc1EvN4mrw68vs8li1iSyto0upkguSjzeppEu3uGTJxGXGt5%2Fg1gvy55XP0YT7%2BeX9%2FKLV1r%2Fi4qHL0kqeilDe3ZPPyUvTxWXi5tP49pDZ2Fu2nHCyqYnVzp3a3YnFyFmmkmWZk4rRxS6ssLHd3bfn6pPEzHmzyJLY4a7R2tP8%2FCzGwyzOeG359iH%2FC05FVFNDQVBFMi4wAwEAAAAh%2BQQJBAAAACwAAAAAEAAQAAcI1wABCAQQQMAAAgQGCAgwsGEBAwcQJJiowECBhgAWMEDQwMEDCBEaSGCwYOAEChUOWHCQ4IIDCxAqUJgA4AIGjQcwAjiQQQOGCxM2cGCgU2CGDhsmePgAIkJRABFCiBhBooQJCU8TnECRQsUKFi2eungBI0UMGTNo1Chq48aJEThy6NixFiOPHj5ETPgBJEgLIUMa1iBSxMiHIwCQJFGyhEkTJ06eQIkiZQoVgTqQVEli5QqWLEy0bOHShcfAIV6%2BgAmjRIwYGWPImG7Io4yZM2PQnElTpmFAACH5BAkEAAAALAAAAAAQABAABwjTAAEIBBBAwAACBAYICDCwYQEDBxAkSKDmgYECDQEsYIAAQYQHDyp0ZLBg4AQKFQ5YcJDgggMLITlMAJBgDQQ2CjICOJChDYYLbNy8YVNDZ40MCzZMiMEFBxydAiOE2DAijpw5EqDSPEEnRR07HY5ovXMCT4orefTs4QHVxgk%2BI%2Fr4%2BQMokE4egrhOGESo0Jc%2BOhrWMHQIUaILgQAVKqRFUZNFixg1cvRoyswaib8oCcMkSxZIUpo8iiQ4jaRJlMQokVHpkaWiGcuYATRmzBkyehoGBAAh%2BQQJBAAAACwAAAAAEAAQAAcI3AABCAQQQMAAAgQGCAgwcGCNAgYOIEiQAMEDAwUaXrLABgGCCg8eVPDIYIHAQGxwYMqkwEGCCw4sHKhAYQKAH3FyaHpzqSEABRY2YbiAQw6nTD19AmDQacOEOkMuIKihFEAEDxhGeLLTIUFVAAn4wEjxaQmEO19B8cGTIlQVLKKoKrUxis8IQFX8SAmktAapE3Qm3CllhYkpO3IB1DDUA1GiCwBOaVEiSZEQUIsYNWJxCJVNgam0qFrFJEsWVoBauYrUkEeaMV5kVHpVSRSsPX3LmDkzxlWsHXoaBgQAIfkECQQAAAAsAAAAABAAEAAHCNcAAQiswanCAAIEBggIILAhAFmaZmVCkCABggMGCji0M4vWj1q2Dhyo0AABgwUNZ93CNeiNrQQXHFgYSWECgEG5ZgSwdckhAAUnMVzos4SHmho%2BBTLgsGFCqCSgGiBNGmGNrhGQPO36kVRgAj68UvTytMJJVwAJfP1KAYxQFlFnbYziM%2BIUmmBNdCStQUoEnQlHWG2RZIqHT0PCECW6AMCuKkVohCwCxQhYEVeobALgkUoGJTGmVgwrZWoQsUgOeZgZs6VJpWKDYhHb07UMmViuXBmzxLBhQAAh%2BQQJBAAAACwAAAAAEAAQAAcI1gABCATwQ9OsY8gGCAgwsCEtWrd%2BcAKF4ICBAg3tEEoy45IcOMkqOEDAYIHAQFz8dFRjS0ICBxYOVKAwAYAEQqs4IahRo%2BGBkhguGLIS5s2lhgMZcNgwAdikYMqQDoywRteIZXVWSJAqMAEfXimWMSuxlWsCX79SNCtSwllPqTZG8RnxrJejRzy4khJBZwK0ZaK2mHo70JAJRIkuAEjVqE%2BURk9cgIrmioUrVDUFpgIkxtSWYcN6uWplLFLDGjtcAapUSVorWK72SK0RYIcxI6SM0WA4MCAAIfkECQQAAAAsAAAAABAAEAAHCNcAAQgEICcYkGm7ZiULMFBgjSHBrKwKUkPWj2QGCjQM1IcaxRqcMGFCgIDBgoEZrCyppkZNAgm2LByoQGECAGhR%2FMh4c6lGQwUmMVw4ZUoGDp8NBTLgsGGCNS444CBNGmGNrhHLrpWQkHRgAj68UmArkY1rVwAJfP1KYU1bNmVnAcwZxWfEKWzXtv05K8gHnQnQsBnhYqirIROIEl0A8AwYt16NGLkAFe0Qi26obAKo0eVRpUeNWAxj8QgWsUgNa9DoZqiVN2nOYBnZczYADWOkSBmjwXBgQAAh%2BQQFBAAAACwAAAAAEAAQAAcIzwABCARQjdaVb1em0ao2sCG4KuGCzJghTtWsZA0BpKKmZMafPzxmvPrBZsHAZ17E3Noy7kKCcW%2Fg2KIwAcCFZnlkvMkIQAGDBRguUDHVhAhPgQw4bJhgjRy3cUcBRFija8QyYcISRE3Ah1cKa9myaT2awNevFCF8CLIRtRwfPiOomDMxpcZRQSfoTLhgjtQ1oxkNCUOU6AKACYK6FTkUzQWoaIeudUNUU2AkV84AsWAxjMUjZ0YiZdxjBJaQYtKcGTKy52gAGsZIGTFGI0DDgAAh%2BQQJBAAAACwAAAAADwAQAAcImwABCARQxtmnK0CunPsxUCAPdOlU4RIXZMYqdbRk1QDAw5CXdevEiKlRY0YQWjguCeQ2aBA7dk4WdVBTTQ6OBdDaFdPEcSOAGpfevAG35hSxaAD%2BNBRYgw0DACGMEVtKFcAHdyaqLv1w4oULrQ1P%2BAAHdhSACSLMgW2YCFHWtQJRGekhEFRDI0uNwHoEYJjAQ%2B%2FABvYGdyApqgEBACH5BAkEAAAALAAAAAAQABAABwjWAAEIrBHA2aNQoQjRkiOwIQAe6LjkELcOl5Ik6s45BBBLlJ1Xr3J8FDcjybRAAk8ZwpTnHRwnLeAhqDHjFo5LR1BFe%2BNMR8MaNRBwyhNDAhUNNojU2Ajg0htNbuKFkOduHFOBmdwI8jCv04lFVwGAOsEHQ6ITLxKETeALD4ARfF7YCFtuFB8AE%2Bh8ILWUqaATdABcSIRImCGmREyASCRwAqopRQ5FAwUq2qFrh1BNaBjJCKxDLIYNY3EIlpFIG%2FcYMfROmjdnhozsuRqAhjFSpIzRCOAwIAAh%2BQQJBAAAACwAAAAAEAAQAAcI1wABCATggp6bKFAeOVtUY6BAHvQeOckjS5YSVeeCDRlYIwOsFnkGOXEiR4kSK0gCCaRyw0kZZ5actICjSQ4uP0gAXJgHIR49Hg4D2Qow49yPCZ0yZLjkUGANNZfk4BgRwl2Epk4bSMgRIxGdE0ewCpRQz5GKDSFeJBALQEK2EiRG8Hlhg%2B2cUT7WTKDjg1RDrMdO6JpwIREibYawEjGBKtEFABNQHbp2KBqoBNEmH0I1YWAkUrAOsRg2jDIsUpGa7jFiyJk0ac4MGdkjNgANY6RIGaMRwGFAACH5BAkEAAAALAAAAAAQABAABwjXAAEIBKAH3aMe29w4KzOwYTk29u7ZGfJDRh90NRpqIEKOU7EhmDD9UKLOUCCBE26Asgdr3J07cN7kWGKFW6ALGDSgi8ejYSBNMpIgGTRhQ6cMGRsCCJRJ1pI%2BI1B4iKBUYI0GF2RdScTnxJGqAhN0sFNnQ6IXCcACAIVPTpwRIl7YUPtmxa4YE%2BicEJS0YY1t9VTEu5AIUQ9DVQ0JM0ICFIAJqA5dOxQNFKhoh%2BptQzVhYCRSsA6xGDZsMixSkZTuMWLImTRpzgwZ2QM2AA1jpEgZoxGgYUAAIfkECQQAAAAsAAAAABAAEAAHCNsAAQgEEGBOvmPbHtErU2PgwAL5otXiBOqePm70eDhckAEUOXJq4GC698OLsYETOHSIFo%2FGHVC23sCTI%2BUUgAsYIu3B11BgjUtvNPmJcmfChk4ZNDoEEAhHmEmARmDwEGHpQH20rIRKQedEAqsCJcxZ8ikRjBdfwSZoYMfTiBEvyoGtsa%2FDkDoTugoCe4nIvRw4LiRCpI3I0hrcmKkLdQ%2FABFRGrh2KBgpUtEOO%2BsQhEkhgJCOwDrEYNuxaDFFc4l3qCWCPEUPOpEl7Z8jIHrABaBgjRcoYjQAOAwIAIfkECQQAAAAsAAAAABAAEAAHCNcAAQgEEEBAPmTH8oErM7BhAQMQ1IACVUsZN3p%2FGi6wkKCDhHLJatW6x84YD4ETONCYE28fKBe23sDLA%2BgUgAsY2pTD1xBAjTdNBrk6MmFDpwwnewYikqPmCF1rIvQcqEzKOWAp6PABNVWgE2ZVeqWA4StBVwD3toSBNILOCxtda1gqFi7UhEQnBMXdwSNPnwuJEAkj0lOHtzxBcg0CMAGVkSKHok1kBIXSujCzBkYyAusQi2HDHPWh9CVYoIZ7jBhyBk%2FaOynpNMmaGoCGMVJGjI3jVGNgQAAh%2BQQJBAAAACwAAAAAEAAQAAcI2AABCAQQQMAAAsjyPQhQY%2BDAAgYOIEiQQE0yN%2BV4OFxgoYOaBg9sNbB3B06qgRM47CuHbx%2FFffHgFTN0CsAFDG3i4XMIoMabd4vaQZuwgUGGhjxrEHH26NkIXWsi8BRYAw63R81S8OIDaqrAFlCkLMPAx1cCrwDAqls2gs8oG2hFYckFbEIKH4K86jBFLYmhC4kQmSDisAYPb%2BuSYFkEYAKqQyoORVskgd2TV7jCSAkkMJIRWIdYrMhCaAm%2FMF84D9xjxJCzSjkohZmkzmsAGsaMPOqj6ZXDgAAh%2BQQJBAAAACwAAAAAEAAQAAcI1QABCAQQQMAAAgQGCAgwsGEBAwcQJEiAAEK%2BAg0BLGCAAEGFBw4i2OMUL0MNgRM4OHigYN%2FEffGi2YtHBcAFDAsyKMgIAB%2B4PfKgTdiwkSeAGkSIuDs1QteaCEYBRDN0yFoKXnwSRG0B6x22q760Gm0xjNWyEXxGlTNaQ1SUHNYm6DohyOifPky0nLqQCJEwIg1rXOrjh1opaAAmoDp07RA7J4vssZMBpoqxgZFIweLSj5mMV3lW5dLyp%2BEeI1Jk%2BFnFL0kuL0NONgwgTconJEAmvWoYEAAh%2BQQFBAAAACwAAAAAEAAQAAcIzQABCAQQQMAAAgQGCAgwsGEBAwcQJEiAoByDAg0BLGCAoEGFChEiqLmTYcHACRQqHLDgYOI%2BfDbIZZgA4AKGjQoyAsAXD8K8CxM2cGCgU2CGDIiojNC1JkJRANG6GAuRghefBE9duGq3LMUvX1iLuhjmatkIPr7KPaXHypu1CXROCCr6R5SpUlQuJEIljIhOLloo%2BbsAYAKqR%2F3isAMFit2nK1YkPRsYiQulc2Jy5BBj5V%2BVVBnhfLLCT5y4VWA8tSpajRYQT9%2B%2BqBvSMCAAIfkECQQAAAAsAQABAA8ADwAHCJMAAQgcKLBDg3wEEwJw8AAAuQ4WFEqc04GDRII1AGTQgOGiwhATPAq0saeTyIEmfHw4CS3bnpWj5kjMOGyZPAA%2BtmVUCOjJslMk3DDDcWlnjRrcmElZBk1DnHMy2Nlb9KNJqDrnoJwCcAnHuVWrXsnAcgVAGEWxeACosY7WpCQAViUpZEXSDrUEf0z6wldRkzIEAwIAIfkEBQQAAAAsAAAAABAAEAAHCMsAAQgcSLAgwQIGDiBIkECNAwMRCi5ggKCBgwoPInRQY2HBwAkUKhyw4CDBBQcW5kTgMEEghokKDCrIoAHDBQAbODAwKDBDmxEtda2JyJPGsw8jAPDiA4ongAQnPszDMGpUU55lXoRI5MFEiTc8a0TD5iOEBRX9uF0yyOOQK3NUJOy6NWhcjRoDL3Fz9MjckUA4KK17xU6Tkx%2FsoPTr0%2BiUwEDnCvFb8gULljrnKKWLVfBcrn%2BFkvgJM0kRGR4G5Xj5ogjNGTMB8AoMCAA7") !important;
    opacity: 1.0 !important;
}

#navigator-throbber,
toolbar[iconsize="small"] #navigator-throbber,
toolbar[mode="text"]      #navigator-throbber {
    list-style-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8%2F9hAAAABGdBTUEAAK%2FINwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAALJSURBVHjahFNdTJJRGD7f9%2FEjIcjnYkYmP02dmkJJMCAWXVnOaRvl5hq1uq2rbpqtC8fmjZdd5rzQtdwauWJd2EWNMeCidUMMYRkiytgCg8nPDEig53PVWM58t7Pz%2BzznPc%2FzHor8E%2BPj41KKoq5gqGMY5ix6fqPRSFQqlVhHR8eHxcXFZPN5qnkyNjY2otVq7xuNRpNGo2Hb2toEICOFQuHn9vZ20efzfUW%2F3NvbO%2B90OsschvkDtlgstxDOyclJK03TEoCZzc1NksvliFQqZcRisWhoaOi0QqG44Ha7Gw6H45PX660dEPT09NxEPMHt2kwmQ6%2BtrX2MxWLPotHow2QyOZ9Op7%2Ft7OywQqFQ0d3dLREIBDqPx0PjjJ%2BHVGVYnDKZTIP1ep0Eg8Hn09PTd5qfNjMz8xnd7O7u7ko%2Bn78%2BPDwsB7EDGr1gWJa9arPZ7uGt7Pr6urdard5AavVmAsy51jCbzat7e3vX5HK5AmBxKBTK0FBYWywWT2azWQLwS4izT44I7OVx1h2PxwmIBNDlEo13ddZqtdZAIMAJtkqOiVKp9C4SiRAQMSKRqJNXLperiURiH1bxufFxBNBJbLVaCcAULJXDMTqrVqvLSIfbt%2F4PDBEZmUxmVqlUpKWlpYELUzTSD2IvA38Jn8%2B3Q3HqKAJYLIQDt%2BEaUSqVdQi%2FxTGmwXweTpzDwiA8b7Xb7e855Ztjbm7uFNJ%2F3d%2Ffr5dIJMTlcpUh%2FGMGvh6kj2rTDAwMnIEr5o2NDS3GQrQcqo%2FV6XRTEG22r6%2FvMuqG%2BP3%2BEi5YXlhYeMrj2AFYgbdClO8DvV5%2FEWN7KpWyIzPC%2FQXMCQcEeW1paSkHG992dXU9OvSZRkdHR9rb2%2B8aDAYLNJGjyE5w68jyB0q5EA6Hv0C4NwC%2FQk0kDxFwMTExIUVnQxtEU%2F9eTqD%2BY3h7AOJ9B%2Fiv3b8EGADmHkdJ4SZcewAAAABJRU5ErkJggg%3D%3D") !important;
    opacity: 0.5;
}

TOP

nsIScriptSecurityManager で危険なURIを除外する

nsIScriptSecurityManager の checkLoadURI や checkLoadURIStr メソッドによって、ある URI のページからリンクされる別の URI がポリシーに沿ったものであるかどうかを判別することができる。
以下のサンプルは、現在のURI (sourceURI) に対して、リンク先のURI (targetURI) が javascript: や data: プロトコルで表された危険が潜む可能性のある URI であるかどうかをテストしている。

var sourceURI = "http://www.example.com/";
var targetURI = "javascript:alert('Blah');";

var SECMAN = Components.classes["@mozilla.org/scriptsecuritymanager;1"]
             .getService(Components.interfaces.nsIScriptSecurityManager);
// disallow javascript: and data: protocols
const flags = Components.interfaces.nsIScriptSecurityManager.DISALLOW_SCRIPT_OR_DATA;
try {
    SECMAN.checkLoadURIStr(sourceURI, targetURI, flags);
    alert("SAFE URI: " + targetURI);
} catch (ex) {
    // if the URI is unsafe, threw NS_ERROR_DOM_BAD_URI
    alert("UNSAFE URI: " + targetURI);
}

Firefox 2 で備わったフィードプレビュー機能では、見かけ上 http: プロトコルのXMLデータを表示させているように見えても実際にロードされているデータはクロム権限で動作する以下のXHTMLである。

chrome://browser/content/feeds/subscribe.xhtml

したがって、フィードのパーマリンクに javascript: プロトコルによる危険な URI が潜んでいる場合、ユーザがフィードのエントリをクリックすることでそのスクリプトが発動してしまうことになり兼ねない。
そこで、フィードプレビューを生成する際、 FeedWriter クラスの _safeSetURIAttribute メソッドが上記のように nsIScriptSecurityManager を利用し、 危険な URI を a要素等のhref属性等にセットしない(つまりリンクしない)仕組みになっている。

TOP

開発中のアドオンのソースファイルへの上書きインストール問題

以下のような手順で開発中のアドオンのソースファイルを配備したとする。

1. ファイル「myapp@example.com」を作成
2. ファイルの内容を「c:xulmyapp」とする
3. ファイルをプロファイルフォルダextensions下へ配置
4. c:xulmypp に各種ソースファイルを配置して開発

ここで、誤って同一のem:idを有するアドオンをXPIファイルからインストールすると、「c:xulmyapp」内部が一度全削除され、myapp フォルダ下にXPIが展開して配備されてしまうということが Firefox 1.5 時代にあったように思う。

しかし、この問題は Firefox 2.0 へアップグレードする過程でいつの間にか治ったようだ。今では誤って同一のem:idを有するアドオンをXPIファイルからインストールしても「c:xulmyapp」内部は削除されず、普通に extensions フォルダ下にXPIファイルが展開され、通常の手順でアドオンをインストールした状態となる。

TOP

独自のプロトコルを追加する

以下のページで解説されている通りに JavaScript 製 XPCOM を登録してやることで、簡単に実現可能。
Adding a New Protocol to Mozilla

日本語訳
Latest topics > Firefoxで独自プロトコルを定義する方法 – outsider reflex

TOP

[userChrome.js] 新しいタブを現在のタブの右隣に開く

リンク先を新しいタブを開いたときに、最後尾ではなく現在のタブの右隣(連続して2つ開く場合はさらにその右隣)に開くための userChrome.js 用ユーザスクリプトである Tabs to the Right では、Firefox 本体で定義された gBrowser.addTab 関数と gBrowser.moveTabTo 関数の改造を以下のような方法で実現させている。

  1. 元の関数(つまり Function オブジェクト)を toString() で文字列化
  2. 関数内の改造したい部分の文字列を置換
  3. evalで文字列から関数を再定義する
eval(
  "gBrowser.moveTabTo = " + 
  gBrowser.moveTabTo.toString().replace(/{/, "$& if (aTab == this.mCurrentTab) this.__uc_addedTabs = 0;")
);

なるほどこれは面白いやり方だ。拡張機能によって Firefox 本体で定義された関数の動作の一部分だけを無理やり変更したいときには苦肉の策として有効である。

同じく Firefox 本体で定義された関数の動作に変更を加えたいケースで、元の関数の前後に何らかの処理を割り込ませたい場合は、以下のような手段が有効である。

  1. 元の関数を新しい関数としてコピー
  2. 元の関数を書き換え、その関数内で割り込ませる処理とともにコピーされた関数を呼び出して実行

たとえばブックマークを削除するときに確認ダイアログを表示させるようにしたければ、 BookmarksCommand.deleteBookmark 関数の前へ確認ダイアログを表示する処理を割り込ませる。

// 関数のコピー
BookmarksCommand.deleteBookmarkOriginal = BookmarksCommand.deleteBookmark;
// 関数の書き換え
BookmarksCommand.deleteBookmark = function(aSelection)
{
    // 確認ダイアログ表示
    if ( !window.confirm("Are you sure you wish to delete bookmarks?") ) return;
    // ブックマークを削除
    this.deleteBookmarksOriginal(aSelection);
};

この方法でいくと、先ほどの、新しいタブを現在のタブの右隣に開くための userChrome.js は、以下のようにも書くことができる。どちらが良いかは好みの問題であろう。

(function()
{
    getBrowser().__uc_addedTabs = 0;
    gBrowser.addTabOriginal    = gBrowser.addTab;
    gBrowser.moveTabToOriginal = gBrowser.moveTabTo;
    gBrowser.addTab = function(aURI, aReferrerURI, aCharset, aPostData, aOwner, aAllowThirdPartyFixup)
    {
        var oldTabPos = this.mCurrentTab._tPos;
        var t = this.addTabOriginal(aURI, aReferrerURI, aCharset, aPostData, aOwner, aAllowThirdPartyFixup);
        if ( aURI != "about:blank" ) {
            this.moveTabTo(t, oldTabPos + 1 + this.__uc_addedTabs++);
        }
        return t;
    };
    gBrowser.moveTabTo = function(aTab, aIndex)
    {
        if ( aTab == this.mCurrentTab ) {
            this.__uc_addedTabs = 0;
        }
        return this.moveTabToOriginal(aTab, aIndex);
    };
    gBrowser.mTabContainer.addEventListener("select", function() { gBrowser.__uc_addedTabs = 0; }, false);
})();

TOP

[userChrome.js] 選択範囲内のすべてのURLをタブで開く、またはWeb検索する

選択範囲内にあるURLと思しき文字列をすべてタブで開くための、 userChrome.js 用スクリプトです。さらに、URLが含まれない場合はWeb検索します。URLかどうかの判定はかなり曖昧ですが、「ttp://」形式や「http://」が無い形式にも対応していますので、以下のような場合に有効です。

ttp://www.example.com/photo1.jpg
ttp://www.example.com/photo2.jpg
ttp://www.example.com/photo3.jpg

下記ではとりあえず関数として定義しています。
使い方は色々ありますが、userChrome.js マウスジェスチャからも利用可能です。

/**
 * A user script for userChrome.js extension.
 * @name    Open all URLs in selection or Web-search
 * @compatibility    Firefox 2.0
 * @author    Gomita
 * @version    2007.1.12
 * @permalink    http://www.xuldev.org/blog/?p=48
 */
function ucjsOpenOrSearchSelection()
{
    // getBrowserSelection function couldn't be apply since it removes all line breaks.
    var win = document.commandDispatcher.focusedWindow;
    var sel = win.getSelection().toString();
    if (!sel) return;
    // process each lines
    var flag = false;
    sel = sel.split("
");
    sel.forEach(function(str)
    {
        // extract strings which may be URL (not strict)
        str = str.match(/([a-zA-Z0-9+$;?.%,!#~*/:@&=_-]+)/);
        if (!str || str[1].indexOf(".") < 0 || str[1].length < 8 ) return;
        str = str[1];
        if ( str.indexOf("ttp://") == 0 ) str = "h" + str;
        // Open in a background tab
        gBrowser.loadOneTab(str, null, null, null, true, false);
        flag = true;
    });
    // If the selection doesn't contain URL, we does Web-Search it.
    if ( !flag )
        BrowserSearch.loadSearch(sel, true);
}

ついでに All-in-one Gestures の「閉じたタブを元に戻す」ジェスチャで Firefox 本体の undoCloseTab 関数を呼び出すように変更。

aioUndoCloseTab = undoCloseTab;

TOP

[userChrome.js] 「最近閉じたタブ」のエントリを中クリックしてタブを開き直す

Firefox 2.0 ではメニューバーの [履歴] → [最近閉じたタブ] から、閉じたタブを開き直すことができる。しかし、[履歴] メニュー下に最高10個表示される履歴のエントリが中クリックによって新しいタブで開くことができるのに対し、[最近閉じたタブ] のエントリは中クリックでは開くことができず、左クリックのみとなる。
そこで、 userChrome.js で以下のような簡単なスクリプトを読み込ませることで、中クリックも可能なようにする。

(function()
{
    document.getElementById("historyUndoPopup").addEventListener("click", function(event)
    {
        if (event.button == 1) event.originalTarget.doCommand();
    }, false);
})();

なお、Firefox 3.0 では本体側で修正されるもよう。

TOP

nsIStringBundleService

JavaScript内でローカライズされた文字列(平たく言えば日本語)を使用するには、properties ファイルを xul:stringbundle 要素から参照して getString や getFormattedString によって文字列を取り出す方法がある。これは XUL Tutorial にも記載されているように基本的な方法である。
しかし、ユーザーインターフェースとは切り離されたプログラム的な制御がメインの JavaScript にてローカライズされた文字列を使用する場合(特に自前のXPCOMコンポーネントを実装する場合)、いちいち xul へ stringbundle 要素を配置するのは面倒であり、規模が大きくなるとややこしくなりやすい。こういう場合には nsIStringBundleService が便利である。このXPCOMサービスは createBundle メソッドによって指定したURIから properties ファイルを読み込んで nsIStringBundle 型のXPCOMオブジェクトを生成し、 GetStringFromName や formatStringFromName メソッドによってローカライズされた文字列を取り出したりすることができる。以下のサンプルにある getLocaleString 関数は、 BookmarksUtils を参考にして文字列の置換の有無に両対応したものである。

リファレンス:
Interface Reference – nsIStringBundleService
Interface Reference – nsIStringBundle

sample.properties

HELLO=こんにちは
MY_NAME_IS=私の名前は%Sです。

sample.js

var FoxkehUtils = {

    _stringBundle : null,

    getLocaleString : function(aStringKey, aReplacements)
    {
        // 初めて呼び出された時に properties ファイルを読み込んで nsIStringBundle オブジェクト生成
        if ( !this._stringBundle ) {
            const BUNDLE_SVC = Components.classes['@mozilla.org/intl/stringbundle;1'].getService(Components.interfaces.nsIStringBundleService);
            this._stringBundle = BUNDLE_SVC.createBundle("chrome://sample/locale/sample.properties");
        }
        try {
            if ( !aReplacements )
                // 置換なし
                return this._stringBundle.GetStringFromName(aStringKey);
            else
                // 置換あり
                return this._stringBundle.formatStringFromName(aStringKey, aReplacements, aReplacements.length);
        } catch(ex) {
            // 未定義の場合 fallout
            return aStringKey;
        }
    },

    say : function()
    {
        alert(this.getLocaleString("HELLO"));    // こんにちは
        alert(this.getLocaleString("MY_NAME_IS", ["フォクすけ"]));    // 私の名前はフォクすけです。
    },
}

FoxkehUtils.say();

TOP