Home > Latest topics

Latest topics 近況報告

たまに18歳未満の人や心臓の弱い人にはお勧めできない情報が含まれることもあるかもしれない、甘くなくて酸っぱくてしょっぱいチラシの裏。RSSによる簡単な更新情報を利用したりすると、ハッピーになるかも知れませんしそうでないかも知れません。

萌えるふぉくす子さんだば子本制作プロジェクトの動向はもえじら組ブログで。

宣伝。日経LinuxにてLinuxの基礎?を紹介する漫画「シス管系女子」を連載させていただいています。 以下の特設サイトにて、単行本まんがでわかるLinux シス管系女子の試し読みが可能! シス管系女子って何!? - 「シス管系女子」特設サイト

Page 1/241: 1 2 3 4 5 6 7 8 9 »

What's the best way to collaborate an WebExtension-based addon with others? - Jul 28, 2018

When I migrated my addon Tree Style Tab from XUL to WebExtensions, I wrote some concerns about communication between addons. Let's share my knowledge around the topic.

Case 1: Request-response type API

TST provides some APIs for other addons. Some APIs are callable via browser.runtime.sendMessage() like as:

const kTST_ID = 'treestyletab@piro.sakura.ne.jp';
browser.runtime.sendMessage(kTST_ID, {
  type: 'expand-tree',
  tab:  2 // Tab.id
});

This example expands a collapsed tree belonging to the specified tab. As you saw, such a "request-resposne" type API is very easy to implement. The receiver addon (in this case, Tree Style Tab) just need to define a listener for browser.runtime.onMessageExternal like as:

browser.runtime.onMessageExternal.addListener((message, sender) => {
  if (!message || !message.type)
    return; // Ignore invalid type API call.

  switch (message.type) {
    case 'expand-tree':
      // Here is a code to expand specified tree.
      // If needed, you can return a promise to wait until the operation finishes.
      return Promise.resolve(true);
  }
});

Then there is one problem: when a client addon sends any message to missing server addon, it will be reported as an error like "Error: Could not establish connection. Receiving end does not exist."

Solution 1: an option to enable/disable integration with other addons

The easiest solution is: providing an option to activate/deactivate such an integration. Here is an example based on storage.local:

let enableIntegration = false;
browser.storage.local.get({ enableIntegration })
  .then(values => {
    enableIntegration = values.enableIntegration;
  });

function someFeature() {
  // ...
  if (enableIntegration)
    browser.runtime.sendMessage(kSERVER_ID, { ... });
}

But it introduces a new problem: which is the best default configuration, enabled or disabled? If enabled by default, users will see mysterious errors in the debug console. If disabled by default, users will need to activate the option manually to activate integration and some people won't realize there is such an option.

Solution 2: initialization based on management API of WE

Client addons can know that the server addon is installed or not via the browser.management.getAll(). Thus you don't need to provide such an option, instead you can activate the integration only when the server addon is available:

let serverIsActive = false;
browser.management.getAll().then(items => {
  for (const item of items) {
    if (item.id == kSERVER_ID && item.enabled) {
      serverIsActive  = true;
      break;
    }
  }
});

function someFeature() {
  // ...
  if (serverIsActive)
    browser.runtime.sendMessage(kSERVER_ID, { ... });
}

However, you need to add the management permission to the list of static permissions in your manifest.json. Sadly it is impossible to be listed in the optional_permissions. The installation prompt for your addon will show a new extra line like: "Monitor extension usage and manage themes". I think most people dislike any needless permission for an addon, so this can be an disadvantage to the previous solution.

Solution 3: simply ignore the error

Both solutions 1 and 2 have concerns. So I ordinarily do nothing - I usually send messages to server addons without confirmation. Those messages will be processed if luckily the server is activated, otherwise they will be ignored. Only some developers will look errors via the debugger console, but most people don't realize that.

And, for developers I sometimes providing an option to deactivate integration. Because they intentionally deactivate the integration, I believe that I have no need to notify existence of the integration feature again.

Of course you can suppressed such errors by an error handler like:

function handleMissingReceiverError(error) {
  if (!error ||
      !error.message ||
      error.message.indexOf('Could not establish connection. Receiving end does not exist.') == -1)
    throw error;
}

browser.runtime.sendMessage(kSERVER_ID, { ... })
  .catch(handleMissingReceiverError);

Case 2: Pure broadcasting is impossible on WebExtensions!

Anyway, on the other hand, there are more other type of APIs: "broadcast" and "publish-subscribe".

Very sad news, an WE-based addon cannot "broadcast" messages to other unknown addons due to WE API's restriction. browser.runtime.sendMessage() can send any message to other addons, but it requires the exact ID of the receiver. Thus a receiver addon must notify its ID to the broadcaster addon. As you know, such an API model is called as "publish-subscribe" (aka "pub-sub"). In short, we always need to implement any API to notify something information from an server addon to other client addons, as pub-sub model.

Case 3: Pub-Sub communications on WE addons

Don't worry, you can implement such pub-sub type APIs based on the technology same to request-response type APIs. The "subscribe" (and "unsubscribe") API will be implemented a simple req-res API like as:

const subscribers = new Set();

browser.runtime.onMessageExternal.addListener((message, sender) => {
  if (!message || !message.type)
    return; // Ignore invalid type API call.

  switch (message.type) {
    case 'subscribe':
      subscribers.add(sender.id);
      return Promise.resolve(true);

    case 'unsubscribe':
      subscribers.delete(sender.id);
      return Promise.resolve(true);
  }
});

And you just need to send messages to known subscribers like as:

async function doSomething() {
  // ... do something ...
  const message = { type: 'published', ... }; // The message to be published
  await Promise.all(
    Array.from(subscribers)
      .map(id => browser.runtime.sendMessage(id, message))
  );
}

Client addons will "subscribe" and receive published messages like as:

browser.runtime.sendMessage(kSERVER_ID, {
  type: 'subscribe'
});
browser.runtime.onMessageExternal.addListener((message, sender) => {
  if (sender.id != kSERVER_ID)
    return;    
  switch (message.type) {
    case 'published':
      // Handle published message.
  }
});

But there is one big problem. Any "subscribing" API calls will fail if client addons send them to the server before the server addon start to listen messages from others.

(Sequence Graph of Two Addons Combined with Messaging) (This figure is initially published as a part of the migration story of TST from XUL to WE.)

Such a situation will happen in cases like:

  • The server addon is installed after client addons are installed.
  • Both the server and client addons are installed, and Firefox starts. Clients are initialized at first and the server is initialized later.

Solution 1: trying "subscribe" by a client periodically

This is the easiest way.

const subscribeTimer = setInterval(async () => {
  try {
    const response = await browser.runtime.sendMessage(kSERVER_ID, {
      type: 'subscribe'
    });
    if (response) // Assume that the server returns "true" for subscribing request.
      clearInterval(subscribeTimer);
  }
  catch(e) {
  }
  // If there is no response or any error, retry with delay.
}, 5000);

But this is a bad solution because the addon will try to subscribe infinitely if the server addon is not installed or enabled.

Solution 2: notifying "ready to subscribe" message from the server

This is the one TST does. TST caches subscribers' ID to its private storage, and sends ready type message to them after TST's initialization process is completed. Subscribers just need to listen the ready message, then they will "subscribe" successfully like as:

(Sequence Graph of Successful Initialization Based on Cached Information) (This figure is initially published as a part of the migration story of TST from XUL to WE.)

Here is an example at a client addon to subscribe triggered by a ready message from the server:

// Try to subscribe at onload. This will succeed if the server is already active.
browser.runtime.sendMessage(kSERVER_ID, {
  type: 'subscribe'
});
// Subscribing triggered by `ready`.
// This is required even if the subscribing at onload succeeded, because
// the server addon can be reloaded and you need to subscribe again.
browser.runtime.onMessageExternal.addListener((message, sender) => {
  if (sender.id != kSERVER_ID)
    return;    
  switch (message.type) {
    case 'ready':
      browser.runtime.sendMessage(kSERVER_ID, {
        type: 'subscribe'
      });
      break;
  }
});

This is better than the previous solution, but still there are some problems:

  • The initial try of subscribing can fail if the server addon is installed but not initialized yet.
    • You can solve this problem by providing an option to deactivate integration or an initialization mechanism based on the management WE API.
  • The client never receives ready message from the server, when the server addon is installed after the client is installed. Then you need to reload client addons manually.
  • Too much code for initialization.

Solution 3: providing information of API dependencies by a central server

If the server addon was able to know the list of possible client addons, it was possible that the server addon send ready message to all possible client addons. Who does provides such a list? - No one yet. We addon authors need to start a repository project like the npmjs.com or the cpan.org. Addon authors will register their addons with API information to the repository, then addons can collaborate aggressively with others based on information provided by the repository.

But this idea introduces a new problem: the repository will become the SPOF (single point of failure). So I think this is a worse stupid idea.

Conclusion

I think that client addons may/should send API messages to server addons without any confirmation. And an option to disable such integration will help developers who don't want to see needless errors.

On the other hand, pub-sub communication of WE addons is really complex, and not perfect. The solution 2 for the case 3 looks better than others, but I still finding more better solution.

私達はここまで来た - Jun 06, 2018

昨年10月のFirefox 57リリース直前の時期に、アドオンのWebExtensionsへの移行を主導してきたAndy McKay氏がそれまでの歩みを述懐して書かれたブログ記事の勝手訳です。


私はMozillaに在籍してきたこの7年の間に色々な事をやってきました(訳註:Mozilla Add-onsに始まり、Firefox OS用のマーケットプレイスとその支払いの仕組みに関わった後、Mozilla Add-onsとWebExtensionsに戻ってきた、とのこと)。 ほんの2年前、私はアドオンコミュニティをWebExtensionsに移行させるという、Mozillaに参加して以来で私にとって最大のプロジェクトを任されました。 あと3週間ほどのうちに、Firefox QuantumはすべてのFirefoxユーザーの元に届けられ、WebExtensionsはFirefox 57においてアドオンを実行するための唯一の方法となります(訳註:その後、2017年11月14日にリリースされた)。

これは、物事を変えると決めて一緒に取り組んできたMozillaの人々の素晴らしいチームによる、2年間の努力の成果です。 私はその最初の数週間、何でこんな事になってしまったんだ?という、とんでもない恐怖の感情に駆られた事をはっきりと覚えています。 多くの人がこのプロジェクトにおいて私を助け支えてくれましたが、特に私が思うに、Kev Needham(訳註:Mozillaでプロジェクトマネージャを務めている人)が我々を導いてくれなかったら、私達は救いようのない状態に陥っていたでしょう。 私がその最初の数週間のパニックと恐怖に陥った時に、Kevと何度か対話した結果もう少しマシな状態に戻る事ができたというのは、その好例です。

最初の数週間は私達のエンジニアリングチームにはKumar McMillan、Stuart Colville、Matthew MacPherson、そしてKris Maglioneがおり、外を出歩いて更なるエンジニアを見つける事が、その月の私の主な目標でした。 その後すぐにChristopher Grebs、Mathieu Pillard、Mark Striemer、そしてLuca Grecoが加わってくれました。 私達が働き始めた最初の週には、2016年のベルリンにMatthew Wein、Andrew Swan、そしてBob Silverbergもいました。 後にShane CaraveoとAndrew Williamsonが参加してくれたのも、私達にとってはとても幸運な事でした。 様々な分野の知見と技術力を備えた彼らエンジニアの能力は誇張するまでもなく、彼らが成し遂げられた事は驚くべき事です。 私は彼らがなした事のすべてをこの記事に列挙したかったのですが、それをすると記事がとんでもなく長くなってしまうでしょう。 私達がやった事やWebExtensionsのアドオンを書く事に関する多数のブログ記事をぜひ読んで下さい。

私にとっては、個人としてこのプロジェクトを受け入れないという選択は取り難かったのですが、この決定に反対する人達も大勢いました。 彼らは様々な異なる方法で定期的にその事を私達に知らしめようとしました。 私がMozillaに在籍するようになってから、これほどまでに激しい反対に遭った事はありませんでした。 Mozillaの他の人達が取り組まなくてはならなかった色々な事に対し、私は本当に感謝しています。

それらの懸念の声の周りを進む事は非常に大変で、時には私は舵をうまく取れず、その体験自体も助けになりませんでした(訳註:苦労したわりに得られる物が無かったという事か?)。 学習曲線がそうであるように、最初からはうまくいかない物です。

その一方で、この決定に同意してくれた他の人達による手助けは素晴らしい物でした。 ちょうど先週トロントで、複数の人々が私達の方にやってきて、私達がやり遂げた変革に対して感謝を述べてくれました。 助けてくれた皆さん、本当に、本当にありがとうございます。

私は今も、これが私達がFirefoxに対してできる最善の事の一つだったと強く確信しており、成し遂げた事を私は誇りに思っています。 それは大きな変革であり、リスクと不確実性を伴う物でした。 控えめな表現をすると、これが今後どう進んでいくかについて、やや神経質になっている部分はあります。

でも、私達はもうここまで来ました。3週間後にはFirefox Quantumがリリースされ、そこにはもうレガシーなアドオンは存在し得ません。


以上、Andy McKay氏のブログ記事の訳でした。

WebExtensionsへの完全移行に伴うレガシーアドオンの廃止については、アドオン作者や一般ユーザーから強い反発がありましたが、Mozillaの外からだけでなくMozillaの中からもそういった反発があったというのが興味深いです。

氏はその後、今年の3月にGitHubに移籍されています。そのGitHubが今度はMicrosoftに買収されて、間接的にMS資本の元におられる状態になっているという事で、不思議な運命を感じますね。

1つ前に訳したroc氏のブログ記事では、「Geckoを捨てWebKitに移行するべきだ」という破壊的な提案が述べられていましたが、そちらは実現する事はありませんでした。それと、このレガシーアドオンの大量死と大混乱をもたらしたWebExtensionsへの移行という大転換との間には、「最後までやり遂げて実現させたか、させなかったか」というだけの違いしか無いのかも知れません。

ただ、XULの中にはXPCOMと密に結合した部分も多々あり、XULの一切合切をWebKitの上に移植するのは現実的ではなかったのでしょう。仮に本当に互換性を保つ形でやるとしたら、相当に分厚い互換レイヤがWebKitの上に乗っかる事になり、そのオーバーヘッドは無視できないレベルだったのではなないでしょうか。あるいは、オーバーヘッドをなくすためにGecko上のXPCOMやXULとの互換性を犠牲にしていたら、結局それに合わせるための作業がXULアプリやアドオン側にも発生するわけで、それだったら最初からWebKitに親和性が高い形で作った方がなんぼかマシという事になっていたでしょう。いずれにしろ、roc氏の予想にあったような「WebKitの高性能さとXULの資産のいいとこ取り」は絵に描いた餅に過ぎなかったのだと僕は思っています。

Andy氏には、2016年のAll Handsにお声がけを頂き、現地でお会いした際には、グループディスカッションの場では英語のやり取りが速すぎてついていけなかったので、その後一人になられた時を見計らって捕まえて、「アドオン同士の連携を取れる事が大事なんだ」という事をつたない英語で一生懸命伝えた(同じような事をLuca氏にも言った)のを覚えています。

自分はこの記事が書かれる1ヵ月弱前にツリー型タブのWebExtensions版をリリースしましたが、「もうグダグダXULにしがみついててもしょうがない、やるしか無い」と腹を括って重い腰を上げ、他のアドオンとの連携を意識したAPIも備えた物をリリースするに至った背景には、その時Andy氏やLuca氏等と直接話して感じた「ああ、この人達は本気で物事を良くしようとしてるんだな」という心意気……みたいな? そういうのを感じて、こちらも相応の物で応えねばなるまいと思ったから、というのも心のどこかにあったのだと思っています。

1つ前の翻訳記事にも書きましたが、WebExtensionsによってFirefoxとアドオンが明確に切り離された事は、FirefoxおよびGeckoの抜本的改革を進める上でプラスに働いているという事を最近とみに実感します。 実際、Firefox 59からFirefox 60にかけての間でも、タブバー周りの実装からXBLが取り除かれるなどのかなり大きな変化がありました。 今までであれば(WebExtensions版より前のツリー型タブなどの)アドオンの互換性が損なわれたという事で大騒ぎになっていた(僕がグダグダ噛み付いていた)かもしれません。 そういった一切がWebExtensions APIの向こう側に隠蔽された事によって、アドオン作者は安心してアドオンの開発に集中できるようになっているという現実を見るに、レガシーアドオンの廃止とWebExtensionsへの移行はやはり必要だったという事を改めて感じる次第です。

(2018年6月8日追記。大袈裟だ、WebExtensions移行を正当化したいだけのこじつけだ、と思う人もいるかもしれませんが、最近実際にあったFirefox 61での後退バグとその原因の例を見るに、XULアドオンの仕組みのデメリットは色々あったという事は否定できないと思っています。)

というか、最近久しぶりに仕事でレガシーアドオンを触る事があって、「もうこんなの作りたくない……WebExtensionsで書かせてくれ……」と思ってしまいました。最初あれだけ渋ってたのに、今じゃすっかり骨抜きですよ……

古代のブラウザ戦争の歴史:MD5ハッシュ化された投稿の機密解除 - Jun 06, 2018

Robert O'Callahan氏(Mozilla内では愛称の「roc」でもっぱら通っていたようです)が1月に公開されたブログ記事をえっちらおっちら勝手に訳してみました。多分誤訳してる所があると思うので、間違いを見つけた人は指摘して頂けると幸いです。

roc氏はFirefox以前から長くMozillaに関わっておられていた重鎮中の重鎮の一人で、現在もFirefoxの日本語入力関係のコードを手がけておられる中野さん曰く、「何かGeckoで分からん事があったらとりあえずrocに聞けば答えが返ってくる」みたいな、Mozillaの低レイヤ周りの生き字引とも言えるような方です。roc氏は2016年にMozillaを退職されたのですが、最近になって、回顧録的に上記リンク先のブログ記事を公開されました。10年くらい前の、WebKitが登場してきてめちゃくちゃ持て囃されてFirefox(Gecko)から一気に人気をかっさらい始めた時期の、ライバルのキラキラした様子を横目で見ながら、クソコードの山だったGeckoにそれでも関わり続けないといけなかったやるせなさから来る、ヤケクソな感じが吐露されていて興味深いです。

「ブラウザ戦争」と題されていますが、これはIE6の牙城を崩すべくFirefoxやChromeやOperaが競っていた頃の、第二次ブラウザ戦争と言われたり言われなかったりする物の事です。MosaicとNetscapeとIEが争ってた頃の方は第一次ブラウザ戦争で、そっちはそっちでまためちゃくちゃ面白いので、「マイクロソフトへの挑戦 ~ネットスケープはいかに してマイクロソフトに挑んだのか~」や「Webの創成」あたりを読むといいと思います(どっちも絶版ですが)。


2007年から2008年はMozillaにとって興味深い時期でした。 市場において、Firefoxは健闘しており、IEの牙城を着実に崩しつつありました。 その一方で、技術面においてはそううまくいっていませんでした。 WebKitが性能面と機能開発の迅速さで私達を追い越していたのです。 Geckoが設計上のミスと技術的負債に悩まされている一方、WebKitはOSS開発者達の心を掴んでいました。 私達はGoogleがWebKitベースのブラウザを開発中だという事も、それがWebKitの市場シェアの少なさという問題を解決するだろうという事も知っていました。 私は強い懸念に囚われ、しばらくの間、MozillaはGeckoを捨ててWebKitに全面移行する事に挑戦するべきだという意見を持っていました。 私がこういう事を大声で言うとプロジェクトに深刻なダメージがあるだろうと思い、私はこの事をごく少数の人にだけ話していました。 公的には、私は不公平な攻撃からGeckoを擁護していましたが、私自身の全体的な判断と矛盾しないように気を配っていました。

私だけがGeckoについて悲観的だった訳ではありません。 Mozilla内部では、「Mozilla 2.0」というお題目を掲げ、自動リファクタリングツールによる分析のような、私達の技術的負債を減らすための近道になる方法を求めて、かなりの時間を費やしていました。 Mozilla外部においては、ライバル達はエンジン開発の面で私達を早急に追い越す事を目指していました。

蓋を開けてみると、私達はほとんどすべての事について間違っていました。 私達は銀の弾丸を見付ける事はできませんでしたが、ただただ大変な頑張りによって、Geckoはライバル達を驚かせる程度には地位を保っていました。 また、時間の経過と共にWebKitの弱みも明らかになってきました。その場凌ぎの省略によって性能は底上げされ、いくつかの互換性テストの上で優位に立ってはいたものの、それらは同時にWebKitにとって技術的負債にもなっていました。 GoogleはWebKitプロジェクトを加速させましたが、AppleとGoogleの軋轢は問題をも生み、最終的にはBlinkというフォークが生まれる結果となりました。 Firefox 57への市場の反応は、FirefoxOSに対する誤った投資という大きな迷走の後においても、Geckoが今日においてもまだ競争力を持ち続けている事を示しています。

ここでの1つの教訓は、内部にいる人は古いコードベースに基づく見通しから、過度に悲観的になり得るという事です。 献身的で有能なスタッフの長年に渡る働きは奇跡を生む事があり、その一方で、あなたのライバル達は彼ら自身で問題を生み抱え込んでしまう事もあります。

もう1つの教訓として、2007年から2008年にかけて私はIE(そしてFlash、WPFも)を倒す事に過度に集中しており、全てのオープンソースのブラウザがたった1つのエンジンの実装を共有する事になったとしても、それはWebにとってそれほど大きな問題ではないと思っていました。 でも今では私はすっかり意見を変えました。 エンジンにおいてより多くのコードを共有する事は、不具合の共通化までをも招くため、真に独立した実装を持ち続ける事は非常に重要なのだと。

私は、Brendan(訳註:Brendan Eich。Netscape時代からの古参でJavaScriptの開発者だが、同性婚反対の運動への寄付がきっかけとなって2014年にMozillaを追われた)や他の人達が、私の意見を取り入れなかった事によって、Mozillaを誤った道へと導くという過ちを私に犯させずに済ませてくれた事に、非常に感謝しています。 もしそうなっていれば、それは誰にとっても大きな災いだったでしょう。

鬱憤ばらしと、記録を後世に残す事を意図して、私は2007年から2008年にかけて、私の考えを説明する4つのブログ記事を書き、それらのMD5ハッシュだけを公開していました。 Firefox 57のリリースの成功の余波がある今は、それらのブログ記事を無害な状態で機密解除するのにちょうどいい時と言えるでしょう。 くれぐれも、私は既に意見を変えているという事を心に留め置いておいて下さい。

続きを表示する ...

Tree structure and tab icons are always lost after restart / 再起動すると必ずツリー構造やタブのアイコンが失われる - Mar 12, 2017

Q

When I start Firefox, tree of tabs and favicons are always lost. Even if I reorganize tree of tabs again, they are flattened after every restart.

Firefoxを起動すると、必ずタブのツリーとタブのアイコンが失われます。ツリーを作り直しても、再起動する度にツリーが失われてすべてのタブが同階層に表示されてしまいます。

A

Please press Ctrl(Command)-Shift-J to open the "Browser Console" - one of developer tools of Firefox. Is any error message like this reported?: NS_ERROR_ILLEGAL_VALUE: Component returned failure code: 0x80070057 (NS_ERROR_ILLEGAL_VALUE)
[nsISerializationHelper.deserializeObject] Utils.jsm:94

Screenshot: (The console with the error message.)

Then, the problem may be caused by invalid information stored in your session data. Try following steps to cleanup your session data:

  1. Close the browser console.
  2. Input "about:config" into the location bar and hit the Enter key.
  3. Skip the confirmation message by clicking the button in the page.
  4. Input "devtools.chrome.enabled" into the "Search:" field.
  5. Then you'll see just one entry. Double click it to turn the value to "true", if it is "false".
  6. Open the browser console again by Ctrl(Command)-Shift-J. Then you'll see an input field at the bottom of the console.
  7. Copy this script and paste it into the bottom field of the console:
    var TabStateCache = Components.utils.import('resource:///modules/sessionstore/SessionStore.jsm',{}).TabStateCache;
    Array.forEach(
      gBrowser.browsers,
      (browser)=>
        TabStateCache.update(browser, {
          iconLoadingPrincipal : null
        })
    );
    "OK"
  8. And hit the Enter key in the field. Then you'll see a new message "OK" in the console.
  9. Restart Firefox.

Ctrl(Command)-Shit-Jを押して、Firefoxの開発ツールの1つである「ブラウザコンソール」を開いて下さい。以下のようなエラーメッセージが出力されていませんか?:NS_ERROR_ILLEGAL_VALUE: Component returned failure code: 0x80070057 (NS_ERROR_ILLEGAL_VALUE)
[nsISerializationHelper.deserializeObject] Utils.jsm:94

スクリーンショット:(コンソールに上記のエラーが出ている様子)

もしこのエラーが出ているなら、問題はセッション情報の中に混入してしまった不正なデータが原因で引き起こされている可能性があります。以下の手順でセッション情報をクリーンアップしてみて下さい。

  1. ブラウザコンソールを閉じる。
  2. ロケーションバーに「about:config」と入力し、Enterキーを押す。
  3. 確認のメッセージが表示されるので、ページ内に表示されるボタンをクリックして先に進む。
  4. 「検索:」欄に「devtools.chrome.enabled」と入力する。
  5. 項目が1つだけ見つかるはずなので、値が「false」になっている場合は項目をダブルクリックして値を「true」に変更する。
  6. Ctrl(Command)-Shit-Jを押してブラウザコンソールを開き直す。すると、コンソール株に入力欄が表示されるようになっている。
  7. 以下のスクリプトをコピーして、その入力欄に貼り付ける。
    var TabStateCache = Components.utils.import('resource:///modules/sessionstore/SessionStore.jsm',{}).TabStateCache;
    Array.forEach(
      gBrowser.browsers,
      (browser)=>
        TabStateCache.update(browser, {
          iconLoadingPrincipal : null
        })
    );
    "OK"
  8. そのまま入力欄でEnterキーを押す。コンソールに「OK」というメッセージが出力される。
  9. Firefoxを再起動する。

未署名のアドオンが動作するノーブランド版Firefoxの入れ方・使い方(主にアドオン作者向け) - Aug 03, 2016

Firefox用アドオンは現在、Mozillaによる署名が必須となり、未署名のアドオン=Mozillaによる内容のチェックが行われていないアドオンはインストールできない、インストールしても有効化できなくなっています。

Firefox 47までのバージョンおよび法人向けの長期サポート版(ESR版)ではxpinstall.signatures.required という隠し設定を使って未署名のアドオンを強制インストールできるようになっていましたが、この設定はFirefox 48で削除されました。 そのため、リリース前に自作アドオンを自分で使って試したい(ドッグフーディングしたい)人は、正式リリースされたFirefoxでアドオンを試す方法がなくなってしまっています。

この問題に対する救済措置として、上記隠し設定が利用可能な「ノーブランド版Firefox」がアドオン作者向けとして公開されています。 このエントリでは、ノーブランド版Firefoxの使い方と注意点をまとめておきます。

ノーブランド版Firefoxと通常リリース版Firefoxの違い

ノーブランド版と通常リリース版の違いは以下の点です。

  • xpinstall.signatures.requiredfalseに設定すると、アドオンの署名チェックを無効化できる。
  • Firefoxのロゴと「Firefox」という名前が使われていない。(起動すると「Nightly」という名前が表示される)
  • 英語版しか無い。

ノーブランド版Firefoxの入手

Mozilla Wikiの、アドオンの署名に関する情報のページ内の「Unbranded Builds」という項目にインストーラのダウンロード用リンクがあります。 2016年8月3日現在ではリリース版Firefox 48.0相当の物へのリンクがありませんが、親ディレクトリから辿っていくとインストーラを入手できます。

このインストーラを実行すると、通常のFirefoxとは異なり、構成ファイル一式は「C:\Program Files (x86)\Mozilla Developer Preview」のようなフォルダにインストールされるようになっています。 インストールしたはずの物が見つからない!という時はインストール先を確認してみて下さい。 カスタムインストールすれば今までと同じ位置(「C:\Program Files (x86)\Mozilla Firefox」など)にインストールする事もできます。

インストールしたらやる事

  • 自動アップデートの無効化。 2016年8月3日現在のノーブランド版には、自動アップデートがかかると通常版Firefoxになってしまうという既知の問題があります。勝手に通常版に戻ってアドオンが無効化されてしまうと困るので、今の所は自動アップデートを無効化しておくのが無難です。当然セキュリティアップデート等は降ってこなくなるので、Mozilla Japanの公式Twitterアカウントをフォローするなり何なりしてFirefoxの最新情報をマメにチェックするようにしましょう。
  • xpinstall.signatures.requiredを再設定してアドオンを有効化する。 今まで通常版のFirefoxでxpinstall.signatures.requiredfalseにして使っていた人が、Firefox 48に自動アップデートされてアドオンが無効化されたという場合、「このアドオンは署名チェックに引っかかって無効化された」という情報がプロファイル内に保存されます。そのため、その後でノーブランド版を入れて起動しても、アドオンは無効化されたままとなります。 xpinstall.signatures.requiredを1回trueに設定して、もう1回falseに設定し直すと、この情報がリセットされてアドオンが再び有効になります。自分はこれに気付いていなくてハマりました。
  • e10s(マルチプロセス機能)の強制有効化過去のエントリで詳述していますが、Firefoxのマルチプロセス機能は、まだ十分にテストされていないという理由から、不安要素があると判定されると自動的に無効化されるようになっています。特に、アドオンがインストールされているとe10sは必ず無効化されます。以下の設定をすべて反映すると、不安要素を無視してe10sを強制的に有効化させられます。自作アドオンがe10sでちゃんと動くかどうかをテストしたい場合、これらの設定も変更しておきましょう。

Mozilla All Hands in London 2016 イベントレポート:技術編 - Jun 20, 2016

現地時刻で2016年6月13日から17日にかけて行われたMozilla All Hands in London、通称MozLondonに参加してきました。 (オープニングセッションの開始前の様子)

最近はこの種のイベントに参加する機会が減ってしまい、「えっ、Mozilla関係で何かやってる人だったの?」みたいになってそうな気がするので、ちょっと記録を残しておこうと思います。

Mozillaの活動に関わっている人はMozillaの雇用スタッフもそうでない人も世界中に散らばっていてコミュニケーションコストが高いので、半年ごとにAll Handsと称して社員と外部の開発者を一箇所に集めて色々話し合っています。 ただ、社員は全社員が招集されるのに対して、自分のような外部の人は活動のアクティブさの度合いなどに応じてボランティア枠で招待される形です。 自分は連絡を頂いた時点ではBugzilla上でそんなにアクティブではなかったのですが、今回はアドオン作者へのヒアリングということでアドオンチームの方から推薦を頂いたようです。 「ボランティア」とはいっても招待なので、交通費・食費・宿泊費は全部Mozilla持ちでした。

続きを表示する ...

Migration story of the Popup ALT Attribute from XUL/XPCOM to WebExtensions - Apr 19, 2016

Recently I wrote a blog entry for developers of addons for Firefox, who are planning to migrate his/her XUL-based addon to WebExtensions. This is the full version which includes some side topics not related to WebExtensions. I hope this helps you to migrate your ancient addon to WebExtensions.

続きを表示する ...

タブのツリーを最適な段数で段組表示する - Mar 06, 2016

ツリー型タブの最近の改善でやったことをまた地味に解説するよ。誰得な記事。

ツリー型タブには、「ブックマークからタブを複数開いた時に1つのツリーになってた方がいいけど、でもツリーにするには親になるタブが必要で、だからといって同列のブックマークの中で1つだけが親になるのってなんか違和感あるよね」という場面のための「グループ化専用のダミーのタブ」を開く機能がある。というか、about:treestyletab-groupというURIを読み込んだタブはそういう用途のタブとして扱うようになる。そのときタブの内容が空っぽだと味気ないので、そのタブ配下のタブのツリーをタブの内容として表示するようになっている。

(画像:配下のタブのツリーを表示しているタブ)

それとはまた別の話で、ツリーになっているタブの上でしばらく待つとそのタブ1つだけじゃなくて配下タブの名前も一緒にツリー表示して、さらにもう少し待つとそれらがリンクに切り替わってクリックしたらそのタブに切り替えられる、という機能もある。

(画像:ツリーを簡易表示したツールチップ) (画像:リッチな形式でツリーを表示したツールチップ)

見た目で分かるかもだけど、これらのツリーを生成する部分は共通のpseudoTreeBuilder(疑似ツリー)というモジュールになってる。

この疑似ツリーは、ちょっと前までのバージョンでは、タブの数が多くなるとそのままツリーが下に伸びていって、ウィンドウやツールチップの高さに収まらなくなった分はスクロールして見るという仕様になってた。 元々、おまけの機能だからそれほど作り込む必要性も感じてなかったんだけど、未処理のissueがたくさん溜まってたのを断捨離してたらその中に「このツリーをマルチカラム表示して欲しい」という要望があった事に気がついた。

このエントリを書くにあたって改めて調べ直してたら、別のもっと古いissueでは否定的なことを自分で言ってたみたいなんだけど、実際自分で使ってて「長いタイトルが折り返されるという事もなく巨大なツールチップが画面を覆い尽くして、そのくせちびちびスクロールしないと全項目は確認できない」という状況に意外とイライラしていたため、この機会に「じゃあやってみっか」と手を着けてみた。

で、最終的にはどうにかそれらしくマルチカラム表示できるようなった。

(画像:タブの中で疑似ツリーがマルチカラム表示されている) (画像:ツールチップの中で疑似ツリーがマルチカラム表示されている)

基本的にはCSSのマルチカラム機能を使ってるんだけど、ツールチップの方で僕の意図したとおりの結果を得るためにはちょっと工夫が必要だった。

「ツールチップの方で」と限定したのは、後で分かったんだけど、タブの中に表示する方はそこまで苦労しなくても勝手にいい感じにGeckoがレンダリングしてくれるから。 なので先にそっちの方から書いていきます。

コンテナの幅や高さが最初から決まっている場合(コンテンツ領域の中)

先に要件を整理しよう。

  • 項目をコンテンツ領域の中で2~3列くらいで段組表示したい。
  • 個々の列はコンテンツ領域の高さを越えないようにしたい。 スクロールバーを出すのは横方向だけにしたい。

まずコンテナとして、コンテンツ領域いっぱいに広がるボックスを置いておく。 コンテナには、内容が溢れたらスクロールできるようにoverflow:autoも指定しておく。

#tree {
  overflow: auto;
  box-flex: 1;
  -moz-box-flex: 1;
}

で、この中に置くツリーをマルチカラムにしたいわけなんだけど。

CSSのマルチカラム表示を有効にするだけなら、マルチカラムにしたい内容を含む上位の要素に列の数か幅を指定してやればいい。

2列なら、こう。この場合、列の幅はコンテナの半分になる(自動的に決定される)。

#tree > .treestyletab-pseudo-tree-root {
  -moz-column-count: 2;
  /* -moz-columns: 2 auto; と同等 */
}

列幅固定なら、こう。この場合、コンテナの中に1列しか収まらない時はマルチカラムにならないという、いわゆるレスポンシブデザイン的な挙動になる。

#tree > .treestyletab-pseudo-tree-root {
  -moz-column-width: 20em;
  /* -moz-columns: auto 20em; と同等 */
}

列数固定だとウィンドウの幅が広いときに余白ばかり広がってしまってあまり嬉しくないので、ツリー型タブではとりあえず主観で20emと指定してみた。 自分の使ってる環境では、これでだいたい2~3列になる。

マルチカラム表示したいツリーを構成しているXUL要素の中で、内容が複数列に泣き別れてもいい物(=「1つのタブ」に対応する「アイコンとラベル文字列のペア」以外のすべてのボックス)は明示的にdisplay:blockにしないといけない。 display:-moz-boxのままだと、要素の作るボックスは途中でぶった切られる事が無いので、マルチカラム表示にはならない。

/* required to apply CSS multi columns */
#tree > .treestyletab-pseudo-tree-root,
#tree > .treestyletab-pseudo-tree-root vbox {
  display: block;
}

当初はこれに気付いてなくて、わざわざXHTMLとXULを混ぜて実装してた。 でも、後になってからdisplayの値を変えればXUL要素でも問題ない事が分かったので、今は全部XULに戻してある。

問題1:縦に長くなる

しかし、これだけだと項目の数が多い時に「縦に長いツリーがマルチカラムで表示される」「画面に収まらなかった部分は下にスクロールしないと見えない」ということになる。

(画像:縦方向にスクロールバーが出ているのが分かる)

こういうのは一覧性が悪くて死ねる。 だって、左の列を上から順に下まで見た後、次の項目を見るにはまた一番上までスクロールし直さなきゃいけないんですよ? あり得ないでしょ……

つまり、各列がコンテンツ領域の高さより高くなられると困る。 項目の数が多いときは、「縦に長ーいリストを2列で表示する」んじゃなくて、「コンテンツ領域の高さに収まる短いリストを3列、4列と列を増やして表示させる」方がいい。 ということ。

じゃあってんでさっきの要素に最大の高さを100%と指定してみても、これは効果がなかった。相変わらず、親のボックスの大きさを超えてスクロール可能なまま。

#tree > .treestyletab-pseudo-tree-root {
  -moz-columns: auto 20em;
  height: 100%;
}

なので、resizeイベントでコンテナの高さを調べて、その都度ピクセル値で適切な高さを指定するようにしてみたら、ようやく縦のスクロールバーが消えて横方向にスクロールできるようになった。

...
  this.window.addEventListener('resize', this, false);
...
handleEvent : function GT_handleEvent(aEvent)
{
  switch (aEvent.type)
  {
    ...
    case 'resize':
      return this.onResize();
    ...
  }
},
...
onResize : function GT_onResize()
{
  ...
  var container = this.document.getElementById('tree');
  var tree = container.firstChild;
  PseudoTreeBuilder.columnizeTree(tree); // ここでcontainerの高さをtreeのheightにピクセル値で指定し直す。
},

CSSのマルチカラムでは「列の数」の指定は絶対ではなくて、コンテナの幅と高さから溢れた分の内容は自動的に列の数を増やして表示するようになってる。 コンテナがoverflow:autoになっていれば、溢れた分は横スクロールすれば見える。

問題2:下に余白ができる時がある

全体の高さを指定していても、項目の数が「全部1列で表示するには多いが、2列を埋めるには足りない」程度の時には、領域の下の方に妙に余白が出てしまう。

(画像:バランス良く配置されたせいで下の方に余白ができている)

これは-moz-column-fill:balanceという初期値のせい。この指定の時には、各列の内容の高さがなるべく揃うようにレイアウトされるため、結果として下の方に余白が産まれることになる。

まぁそれはそんなに悪くはないんだけど、今までコンテンツ領域の高さいっぱいまで並んでた物が急にその半分とかくらいの高さになるのは気持ち悪い。 違和感が無いのは、やはり、領域の高さに収まらなくなった分からちょっとずつ次のカラムに流し込まれていくという感じだろう。

そのための指定が-moz-column-fill:autoで、これを指定しておくと、1列目は下まで項目が埋まって、入りきらなかった分だけが2列目に溢れるようになる。

(画像:1列目は高さいっぱいまで項目があり、溢れた分だけが2列目に行っている)

実は、この-moz-column-fill:autoを使うのにも、ボックス全体の高さを明示的に制限しておく必要がある。 というのも、-moz-column-fill:autoの時は「高さが指定の高さに収まらなかったときに、その分を次のカラムに流す」という事になるので、高さが未指定だと「内容が溢れることもないからいつまで経ってもマルチカラムにはならず、縦にどんどんリストが伸びていく」という結果になってしまう。 ……というわけなので、やっぱり全体の高さはスクリプトで動的にピクセル値で指定してやらないといけないのでした。

コンテナの幅や高さが決まっていない場合(ツールチップ)

こちらも要件を整理する。

  • ツールチップの中で項目を2~3列くらいで段組表示したい。
  • ツールチップは画面を覆い尽くすほどの大きさにはなって欲しくない。 最大の幅と高さは制限したい。
  • 必要以上の余白は要らない。 小さく表示して問題無いなら、小さく表示したい。

ここで曲者なのが3つ目の要件。 コンテンツ領域の中であれば「与えられた領域の中を最大限使って、残りは余白扱いにすればいい」ということになるんだけど、ツールチップだとそうもいかない。 というか、必要最小限のサイズで出てくれないと困る。 「内容を表示するのに必要な最小の大きさをどうやって決めるのか?」 これがツールチップの場合の難しい所だ。

先に結論を言ってしまうと、「とりあえず許されてる最大の大きさで試しにレンダリングしてみて、その後、実際に必要な大きさで改めて表示し直す」というのが答えになる。

大きく表示してから小さく縮めるのって格好悪くない?

とはいうものの、ツールチップを1回表示した後そこから小さく縮めるというのは見た目が宜しくない(一瞬だけとはいえ目に見える状態で大きなツールチップが出るというのはイラつきますよね)。

じゃあどうすれば?という事になるんだけど、逆転の発想というかなんというか、ツリー型タブでは「そもそも最初からベストのサイズで表示する事は諦める」という解決策をとっている。 どういう事かというと、いきなり違うサイズのツールチップを出すとギャップで見当識を失うので、まずは元の普通のツールチップと同じ大きさ・位置で1回表示して、そこからアニメーションでツールチップを必要な大きさまで拡大する、という事をしてる。

(画像:ツールチップの大きさが拡大される様子のアニメーション)

ツリー型タブの高機能なツールチップはだいたい元のツールチップよりは大きくなるので、この「1回小さめのサイズで表示した」状態で必要なサイズを確定させて、後からゆっくり拡大すればいい。

元より小さなツールチップで何故「最大の大きさで試しにレンダリングしてみる」ができるのかというと、XULのarrowscrollbox要素を使ってるから。 これは元々は、ツールチップが画面いっぱいに広がっていてもまだ内容が収まりきらない時のための対策として使っていた。 arrowscrollboxは中にどんなに大きな物を置いてもそれ自体やその親(ツールチップ)の大きさが広がらないので、安心して「めいっぱい大きく表示して、最小限必要なサイズをその結果から割り出す」ということができる。

(画像:arrowscrollboxの中で大きなツリーが準備されている様子)

ツリーを収めるのに最適なサイズの求め方

「幅も高さもいい感じにする」というのは難しいので、ツリー型タブの場合はまず「画面の全体を覆い尽くさない程度」ということで画面の高さの70%(例えば画面が縦1024ピクセルなら、その7割の717ピクセル)を最大の高さとして、その中でマルチカラム表示の指定を反映させている。

arrowscrollbox > .treestyletab-pseudo-tree-root {
  -moz-columns: auto 20em;
  -moz-column-fill: auto;
  height: 717px; /* ←実際にはその都度計算する */
  max-height: 717px; /* ←実際にはその都度計算する */
}

こうすると、高さについては指定の高さまでに収まって、幅は必要な分だけ自動的に列が増えていくという形で、ツリー全体を収めるのに必要な領域を確定する事ができる。

高さは既に分かっているので、残るは幅なんだけど、これはツリー自体のboxObject.widthclientWidthからは分からない。 というのも、この時点でのマルチカラム表示されたツリーは「列の幅は指定されているが列の数は指定されていないので、要素自身の幅を超えて列が表示されている(可能性がある)」という状態で、要素自体の幅を調べても意味がないのです。 overflow:autoになってる要素の幅を計測しても中身の幅は分からない、というのと同じね。

こういう時の「本来の内容の幅」は、RangeのgetBoundingClientRect()で調べられる。 これは、DOMのRangeが含んでいる範囲の全要素が収まる矩形の位置や大きさを計測してくれるという便利なAPI。 これを使ってツリーの要素の「内容を」選択してそのサイズを測れば、ツリー全体を収めるのに本当に必要な最小のサイズが分かる。

var range = tree.ownerDocument.createRange();
range.selectNodeContents(tree);
var rect = range.getBoundingClientRect();
range.detach();

(図:ツリーの幅の計測基準)

あとは、ツールチップ全体を広げるだけ。 「ツリー全体を収めるのに必要な幅」と「画面の幅さの80%(例えば画面が横1280ピクセルなら、その8割の1024ピクセル)」のどちらか小さい方をツールチップの新しい幅にしてやる。 ツリーはツールチップの中のarrowscrollbox要素の中に置かれているので、ツールチップがツリーよりも小さければ、はみ出た部分はスクロールして見る事ができる。

まとめ

以上、マルチカラムなポップアップをいい感じに表示するためのノウハウの話でした。

実は、ここに書いた内容はmasterのHEADでの話で、解説を書くにあたって調べ直した結果の洗練された内容になってる。 今リリースしてるバージョンでは、そこまで洗練されてない状態の(でもだいたい同じような結果を得られている)コードになってるので、どこが無駄だったのかを洗い出して「こいつ技術力低いなあ!」と笑いものにしてくれていいです。

それにしても、昔なつかしNetscape Communicator 4では、フォルダの中にブックマークが大量にあるときはこんな風に段組表示してくれて一覧性が高くてものすごく便利だったんだけど、Geckoエンジンベースで作り直されたNetscape 6以降や、その流れの先にあるFirefoxには、その機能はついぞ引き継がれなかった。 業を煮やして複数のポップアップを並べることで擬似的に再現できないか?という実験をしてみたこともあったんだけど、不安定でダメだった。

今だったら、このやり方で行けるんじゃないか?という気がする。 というか、SUMOの質問に寄せられてる回答でStylish用のスタイルシートが公開されてて、2つの例のうち1つはCSSマルチカラムでやるようになってたので、間違いなくできる。

ただ、例に挙がってるStylish用のスタイルシートはカラム数が固定されていて、1列でいい時にまで常に2列3列になってしまうという問題がある。 ネスケ4の頃のそれのような使い勝手を実現するには、ここで解説したような細かい調整をやらないといけないんだと思う。 僕自身はやる元気がないので、誰か代わりにやってくれないかなあ……と、実現する見込みの低そうな事をネットの片隅でこっそり呟いてみる次第です。

スクロールバーの見た目を保ったまま細くする - Feb 18, 2016

ツリー型タブではタブバーに表示するスクロールバーについて、普通のスクロールバーよりも細く表示するようにしてるんだけど、その実装方法に少し改善があったので誰得だけど解説しておく。

ちょっと前までのバージョンでは、単純にCSSのmax-widthで以下のように実現していた。

tabs.tabbrowser-tabs
  .tabbrowser-arrowscrollbox
  > scrollbox
  > scrollbar[orient="vertical"],
tabs.tabbrowser-tabs
  .tabbrowser-arrowscrollbox
  > scrollbox
  > scrollbar[orient="vertical"] * {
  max-width: 10px;
  min-width: 10px; /* この指定がないと逆に最小サイズが大きくなってしまう */
}

tabs.tabbrowser-tabs
  .tabbrowser-arrowscrollbox
  > scrollbox
  > scrollbar[orient="vertical"] {
  font-size: 10px;
}

この方法の難点は、一部のプラットフォームで……というかWindowsでスクロールバーの端のボタンの表示がずれるということ。 (スクリーンショット) スクリーンショットを見ると、「▼」がちょっと右にずれてるのが分かる。 何故こうなるかというと、Windowsのスクロールバーの端のボタンのアイコンは最小サイズが大きめに定義されているようで、それよりも小さなサイズを指定してもWindows側の最小サイズが優先されてしまうせい。

この問題は、実用上は問題がないので長らく放置してたんだけど、最近ツリー型タブのissue trackerで閉じられてないままの古いissueを断捨離してる時にもっとこうしたらいいよという提案があったことに今頃気がついて、その方針を最近のバージョンで採用してみた。 (スクリーンショット)

これはどうやってるのかというと、スクロールバーの幅を小さくする代わりに、「スクロールバーの左右にマイナスのマージンを設定して他の要素の下に潜り込ませる」ことで、擬似的に細く見せている。 何ピクセル潜り込ませるかは環境によって変わってくるので、document.getComputedStyle()なども使ってその都度計算するという面倒なことをしている。 これで、「▼」がずれるということがなくなった。

しかし、今度は副作用としてタブバーの背景色を変えている時に表示が変になるという問題が起こってしまった。 例えばClassic Theme Restorerをインストールして且つ「Mixed」スキンを選択した状態だと、水色の背景の一部がグレーになってしまってた。 (スクリーンショット) これは、先の「マイナスのマージンを設定して他の要素の下に潜り込ませる」という手法において、スクロールバーが潜り込む先となる(スクロールバーの上に載って一部を覆い隠す)ボックスの背景色の指定が必要となってしまって、決め打ちで指定した色がユーザのカスタマイズ後の色と違うせいで見えてしまっているということ。 タブバーの背景色を変える方法はuserChrome.cssやらテーマやらClassic Theme Restorerやら色々とあるので、すべての場合にマッチする万能の対策は事実上無い。

ということで頭抱えてしまって、なんとかスクロールバーの潜り込んだ部分(はみ出た部分)を綺麗に消す方法はないかと、思いついた方法を色々試してみた。

  • スクロールバー全体にネガティブマージンを指定するのがアウトなら、ボタン部分だけネガティブマージンを指定するのはどうか?(元々、ボタン部分以外はmax-widthでちゃんと細くできてたんだし)と思って試してみたら、ボタン部分だけがスクロールバーからはみ出すという結果になってしまった。 (スクリーンショット)
  • 横方向にはみ出した部分を見えなくする、というとoverflow-x:hiddenだな!と思って、スクロールバー全体にこれを指定してはみ出したボタンだけをどうにかしようと思ったけど、実際やってみたらスクロールバー全体の動作がぶっ壊れてしまうので駄目だった。 (スクリーンショット)

万策尽きてDOMインスペクタの画面とにらめっこしてた所、clipという名前が見えた。そういえばCSSにはそういう機能もあるんだった。試したこと無かったから、じゃあそれで行けるんじゃないか? ということで実際試してみたんだけど、これもスクロールバーのボタンには効いてくれなかった。

でも、MDNのプロパティ解説を見るとdeprecatedと書いてあって、誘導先を見たらclip-pathを使えと書いてある。 clip-pathというとFirefoxのタブの見た目などを丸くするのに実際使われてるプロパティだったはずなので、こっちなら大丈夫だろうと思って再挑戦したら、ようやくうまくいった。 (スクリーンショット) Classic Theme Restorerとの併用時にも何ら問題がないことが見て取れる。

実装は以下のようになってる。

tabs.tabbrowser-tabs
  .tabbrowser-arrowscrollbox
  > scrollbox
  > scrollbar[orient="vertical"] {
  font-size: 10px;
  max-width: 10px;
  min-width: 10px;
  clip-path: url(#treestyletab-box-clip-path);
}
tabs.tabbrowser-tabs
  .tabbrowser-arrowscrollbox
  > scrollbox
  > scrollbar[orient="vertical"] * {
  font-size: 10px;
  max-width: 100%;
  min-width: 10px;
}
tabs.tabbrowser-tabs
  .tabbrowser-arrowscrollbox
  > scrollbox
  > scrollbar[orient="vertical"] scrollbarbutton {
  font-size: 10px;
  margin-left: -3px;
  margin-right: -2px;
}

参照してるSVGのクリッピングパスは以下の通り。

<svg:svg height="0">
  <svg:clipPath id="treestyletab-box-clip-path"
    clipPathUnits="objectBoundingBox">
    <svg:path d="m0,0 V1 H1 V-1 H-1 z"/>
  </svg:clipPath>
</svg:svg>

このクリッピングパスはどういう形かというと、仮想的なキャンバス全体を覆い尽くす矩形になってる。mから始まる相対指定で(0,0)から(1,1)までを覆う矩形を作っている(左上から始めて、下、右、上、左と反時計回りに進んでパスを閉じてる。最初は逆方向の時計回りに回ってしまってうまくいかなかった)ので、クリッピングパスの適用対象になるボックスがどんなサイズであってもそれと同じ大きさになる。 スクロールバーに対してこのクリッピングパスを反映すると、この矩形からはみ出る部分、つまりネガティブマージンではみ出してるボタンが切り取られて表示されるようになる、というわけ。

今回の実装自体のノウハウをそのまま流用できる場面はそうそうなさそうだけど、なんかの機会にまた使うかもしれないので、解説してみた。 要点をまとめると、「要素のスクロールの可否といった動作に影響を与えないで、見た目の表示サイズだけを小さくしたい時は、clip-pathを使うとよい」といったところでしょうか。

以上、ちっこい三角ひとつのためだけに四苦八苦させられた話なのでした。

Firefoxのマルチプロセス機能を強制的に有効化する方法まとめ - Feb 17, 2016

アドオンのe10s対応のための作業をしようと思うと当然e10s有効状態でFirefoxを動かしとかないと意味ないんだけど、まだ完成度が低いということでかe10sは事あるごとに勝手に無効化される。すぐ死ぬ。マンボウかスペランカーかってくらいに、気がついたら勝手に無効化されてる。

なので、主に法人ユーザ向けの集中管理機構を使って、アドオンのデバッグ用の環境ではe10sを有効化するための設定を強制するように設定した。 autoconfig.cfgの内容は以下のようにした。

// 1行目は必ずコメントとする。

// 基本的な有効化の設定(設定ダイアログにあるチェックボックスに対応)
lockPref("browser.tabs.remote.autostart", true);

// アクセシビリティ機能によるe10s無効化を抑止
// https://bugzilla.mozilla.org/show_bug.cgi?id=1198459
lockPref("browser.tabs.remote.force-enable", true);

// アドオンがあることによるe10s無効化を抑止
// https://bugzilla.mozilla.org/show_bug.cgi?id=1232274
lockPref("extensions.e10sBlocksEnabling", false);
lockPref("extensions.e10sBlockedByAddons", false);

これで心置きなくe10s状態でのデバッグができます。

Page 1/241: 1 2 3 4 5 6 7 8 9 »

Powered by blosxom 2.0 + starter kit
Home

カテゴリ一覧

過去の記事

1999.2~2005.8

最近のつぶやき