Proxy 切り替えアドオン @Firefox 59

Firefox 57 では、PAC を変更することしかできなかった WebExtension API
Firefox 59 で、ようやくプロクシの設定を切り替える手段ができた。

ぼくは、「プロクシなし」と「システム設定」を切り替えたいだけなので、こんな感じのアドオンで十分。

  • ファイル構成
  • manifest.json
  • background.js
  • スクリーンショット
  • 強いて言えば
  • FF 60.0b16 で動かなくなってる (2018-5-1 追記)
  • 実装が変わった (2018-5-3 追記)
    • manifest.json (FF 60.0b16 以降)
    • background.js (FF 60.0b16 以降)
続きを読む

offsetLeft の罠

こちらのさいとう先生の画像を切り取ろうとしたときのこと。

あれ、ずれてる...
offsetParent をたどって行けば、良いんじゃなかったっけ (´・ω・`)

    let e = ...
    let x = 0, y = 0;
    do {
        x += e.offsetLeft;
        y += e.offsetTop;
    } while (e = e.offsetParent);

    console.log(x, y);

(468, 1455) と計算されるけど、Firefox のものさしツールで測ってみると (514, 1454)。
46px も足りてない。


# ↓に たどり着くまで、かなりの日数が経ってます
画像の offsetParent の DIV には、border-left が指定されてる。

.NA_articleFigure {
    position: relative;
    border-left: 46px solid #fff;
}

ああ、border の分を足してあげなきゃダメなんだ(border で位置を調整するんじゃねえよ


ぴったり。

汎用的にやるためには、box-sizing も考慮してあげなきゃいけない。
こんな感じか。

    let e = ...
    let x = 0, y = 0;
    do {
        x += e.offsetLeft;
        y += e.offsetTop;
        if (e.offsetParent) {
            let {borderTopWidth, borderLeftWidth, boxSizing} =
						window.getComputedStyle(e.offsetParent);
            if (boxSizing != "border-box") {
                x += parseFloat(borderLeftWidth);
                y += parseFloat(borderTopWidth);
            }
        }
    } while (e = e.offsetParent);

    console.log(x, y);

→ (514, 1455)




今どきは、getBoundingClientRect() 使おうね、って話でした。

    let e = ...
    let {x, y} = e.getBoundingClientRect();
    x += document.documentElement.scrollLeft;
    y += document.documentElement.scrollTop;

    console.log(x, y);

→ (513.75 1455.8) : なんだ、この小数点……

Promise vs async/await

背景というか、状況というか

Firefox の addon で、コンテンツプロセスでスクリプトをロードしたり、コードを実行したりする browser.tabs.executeScript() を続けて実行したい、という場面。

  • ライブラリを使いたいんだけど、一部がエラーになる
  • ライブラリに機能を追加したい
  • でも、(何となく)ライブラリはいじらずにおきたい
  • コンテンツプロセスでは、前回の状態が残っているので、ライブラリのロードは一回にしたい

「一部がエラーになる」というのは、

    convertStringEncoding: function(str) {
        // for subscript loader
        return decodeURIComponent(escape(str || ''));
    },

この関数がこんなふうに呼ばれてて、

SketchSwitch.Buttons.RedPen = function(sketch) { this.sketch = sketch };
SketchSwitch.Buttons.RedPen.prototype = SketchSwitch.Utils.extend({
    shortcut: null,
    icon: 'data:image/png;base64,iVBOR...',
    name: SketchSwitch.Utils.convertStringEncoding('赤ペン'),

URIError: malformed URI sequence というエラーが出ちゃう。

どうやら、XPCOM で、設定ファイルで非ASCII 文字を扱うときの あるある らしく、件のコードは UTF-8UTF-16 コンバータとして機能するらしい。

そのライブラリは、XPCOM のアドオンだったのだけれど、文字列リテラルに必要な処理だったのかどうかは謎だし、WebExtension では必要ない(というか、エラーになる)。
対応としては、convertStringEncoding() を書き換えれば良いだけなんだけど、

  • ライブラリをいじらずにおきたい
  • ロードするときに呼ばれてる処理なので、後から置き換えるという手が通じない

ということがあって、ライブラリをロードする前に decodeURIComponent() を書き換えて、ロードが終わった後に戻す(ある意味、荒業
# 素直に、ライブラリをいじった方が良いんじゃないか(という気はしてる

で、こんな感じの処理の流れになる。

  1. ライブラリがロード済みかどうかを確認
  2. ライブラリがロードされてなかったら
    1. decodeURIComponent の実装を保存して、書き換える
    2. ライブラリをロードする
    3. decodeURIComponent の実装を戻す
    4. ライブラリの機能追加/変更のスクリプトをロードする
  3. ライブラリを使う

前置きが長くなりました。

で、実際のコード

Promise を使って書いたコード。

function show_sketch_menu(tab) {
    // https://github.com/mdn/webextensions-examples/blob/master/context-menu-copy-link-with-types/background.js
    let loaded_first = false;
    browser.tabs.executeScript(tab.id, {
        code: "typeof SketchSwitch === 'function'",
    }).then(result => {
        if (!result || result[0] !== true) {    // SketchSwitch is not defined
            loaded_first = true;
            // for malformed URI --- SketchSwitch.Utils.convertStringEncoding()
            return browser.tabs.executeScript(tab.id, {
                code: `
                    original__decodeURIComponent = decodeURIComponent;
                    decodeURIComponent = s => unescape(s);
                    true;   // Script '<anonymous code>' result is non-structured-clonable data
                `,
            });
        }
    }).then(result => {
        if (loaded_first) {
            return browser.tabs.executeScript(tab.id, {
                file: "/content/SketchSwitch.js",
            });
        }
    }).then(_ => {
        if (loaded_first) {
            return browser.tabs.executeScript(tab.id, {
                code: `
                    decodeURIComponent = original__decodeURIComponent;
                    true;
                `,
            });
        }
    }).then(_ => {
        if (loaded_first) {
            return browser.tabs.executeScript(tab.id, {
                file: "/content/sketch-patch.js",
            });
        }
    }).then(_ => {
        return browser.tabs.executeScript(tab.id, {
            code: `
                (_ => {
                    const canvas = document.getElementById("__sketch_switch_canvas__");
                    if (canvas) {
                        // Sketch Menu is shown.
                        return;
                    }

                    const sketch = new SketchSwitch(window, {});
                    sketch.show();
                })();
            `,
        });
    }).catch(error => {
        console.error(error.message || error);
        console.dir(error);
    });
}

こちらが async/await を使って、書き直したコード。

async function show_sketch_menu(tab) {
    try {
        // https://github.com/mdn/webextensions-examples/blob/master/context-menu-copy-link-with-types/background.js
        const result = await browser.tabs.executeScript(tab.id, {
            code: "typeof SketchSwitch === 'function'",
        });
        if (!result || result[0] !== true) {    // SketchSwitch is not defined
            // for malformed URI --- SketchSwitch.Utils.convertStringEncoding()
            await browser.tabs.executeScript(tab.id, {
                code: `
                    original__decodeURIComponent = decodeURIComponent;
                    decodeURIComponent = s => unescape(s);
                    true;   // Script '<anonymous code>' result is non-structured-clonable data
                `,
            });
            await browser.tabs.executeScript(tab.id, {
                file: "/content/SketchSwitch.js",
            });
            await browser.tabs.executeScript(tab.id, {
                code: `
                    decodeURIComponent = original__decodeURIComponent;
                    true;
                `,
            });
            await browser.tabs.executeScript(tab.id, {
                file: "/content/sketch-patch.js",
            });
        }
        await browser.tabs.executeScript(tab.id, {
            code: `
                (_ => {
                    const canvas = document.getElementById("__sketch_switch_canvas__");
                    if (canvas) {
                        // Sketch Menu is shown.
                        return;
                    }

                    const sketch = new SketchSwitch(window, {});
                    sketch.show();
                })();
            `,
        });
    } catch(error) {
        console.error(error.message || error);
        console.dir(error);
    }
}

diff を取ったものがこちら

思ったこと

こんなハイクをした後に書きました。
Promise を返してくれる API だったら、async/await は、気持ちすっきりするかなあ、という感じ。
行数もインデントも減るし。

書きながら思ったけど、Promise の方は、最初の一発目だけを、もうひとつの Promise でくくってあげれば、余計なフラグを持つ必要はなくなるのかも(試してない

    }).then(result => {
        return new Promise((resolve, reject) => {
            if (!result || result[0] !== true) {    // SketchSwitch is not defined
                // for malformed URI --- SketchSwitch.Utils.convertStringEncoding()
                browser.tabs.executeScript(tab.id, {
                    code: `
                        original__decodeURIComponent = decodeURIComponent;
                        decodeURIComponent = s => unescape(s);
                        true;   // Script '<anonymous code>' result is non-structured-clonable data
                    `,
                })
                .then(_ => {
                    return browser.tabs.executeScript(tab.id, {
                        file: "/content/SketchSwitch.js",
                    });
                }).then(_ => {
                    ...
                }).then(_ => {
                    resolve();
                }).catch(_ => {
                    reject();
                }
            }
        };
    }).then(_ => {
        ...

余計な変数は必要なくなるけど、インデントは深くなる。
自分で Promise 書いちゃうと、すっきりしないなあという感じはする。


こんなハイクのやり取りも。
http://h.hatena.ne.jp/noromanba/316607276733271244
http://h.hatena.ne.jp/noromanba/315956334695317249



おしまい。

ブックマークツールバーのアイコンを変更する @Firefox57

人呼んで "Firefox Quantum" では、アドオンが全面的に WebExtension に移行して、ブラウザの見た目をいじるようなアドオンが作れなくなってしまいました。
でも、アドオンではできなくなっちゃったけど、手作業で何とかブックマークツールバーFavicon は変えられます。
人力検索こんなのこんなので回答したやつのまとめというかリライトです


まず、ユースケースから。


次に、予備知識。

続きを読む

ようこそ、Firefox 57 @うちの事情

人呼んで、Firefox Quantum
UI が大幅に変更され、かなりスピードアップする半面、旧式のアドオンがすべて使えなくなってしまう。

9/20 だったかに 57.0b1 に更新してから二ヶ月近くもじたばたしてる。安定版を使っている人たちも 2017-11-14には直面することになる。

そんな Firefox 57(というか、もう 58.0b1 になってる)の、ぼくんとこの事情。

続きを読む

徒歩14分

これの「徒歩14分にピークがあります」の件。
ちょっと気になったので at home で調べてみた。


対象は東京23区で、全部で 204,369件(2017-11-11時点)。
こちらは、5、10、15 といったキリの良い数字にちょっと偏りがありそうな感じ。

表記の揺れはあって、徒歩\d+分 ではないものが 498件。
内訳は

  • バス\d+分 +停歩\d+分 ── 488件
  • その他交通手段 ── 8件
  • 徒歩\dm ── 2件

駅から遠いと、基本はバス+停歩の表記なのに、徒歩 111分とか。
もしかして表記ミスかしらん?

たばこ税とか

ここを見てて気になったので。

数字の推移

喫煙人口に使った総人口は、昭和25年から、「総人口」と「日本人人口」に分けてデータがとられているのだけれど、「総人口」の方を使った(ちょっと迷った)。
税収については、
  • 1988年 ─ 数字が探せてなくて、ここのグラフから読み取ってる。
  • 2016年 ─ 地方税の分が 2016年度の決算額がまだ出ていないので、ここの見込み額を使ってる。
JT設立以前は、たばこ税(当時は、たばこ消費税)ではなく専売納付金だったけど、切り替わりの辺りのデータが見つからない。

関連するイベント

日付 トピック たばこ税
(増分)
消費税 価格
1980-04-22 (昭和55) 値上げ 150 → 180
1983-05-01 (昭和58) 値上げ 180 → 200
1985-04-01 (昭和60) JT設立
1986-05-01 (昭和61) 値上げ +0.9 200 → 220
1989-04-01 (平成01) 消費税導入 0% → 3%
1997-04-01 (平成09) 消費税増税 3% → 5% 220 → 230
1998-12-01 (平成10) たばこ特別税 +0.82 230 → 250
2003-07-01 (平成15) たばこ税税率変更 +0.82 250 → 270
2006-07-01 (平成18) たばこ税税率変更 +0.852 270 → 300
2010-10-01 (平成22) たばこ税税率変更 +3.5 300 → 410
2014-04-01 (平成26) 消費税増税 5% → 8% 410 → 430
2016-04-01 (平成28) 値上げ 430 → 440

価格は、メビウス(旧、マイルドセブン)のもの。

所感

たばこの値段が上がったから喫煙人口が減っているというふうには見えない。
1996年くらいからだいたい同じくらいの割合で喫煙人口が減っていってて、税収を確保するために税率を上げているという状況に見える。
鳩山くんのときの +3.5円/本の税率アップは、健康云々を謳ってたような気もするけれど、税収確保が先にあって後付けの理由じゃないかという気もする。

ひとり当たりの消費量は、一箱/日であまり変化してない。
健康を気にしてる人は、減らすんじゃなくて止める、とかだろうか。
禁煙外来で保険が使えるようになったのが 2006年4月だから、ライトなスモーカーは止めちゃおうか、というタイミングだったのかも(喫煙人口が減ってて、ひとり当たりの消費量が増えてる)。

2016年度に販売本数が落ち込んでいるのは、IQOS の影響大だと思う。
きっと、2017年度も落ち込み幅は大きいはず。
税収には含まれてるはず(→参考)なので、税収は販売本数ほど落ち込んでない。

参考

http://www.health-net.or.jp/tobacco/product/pd090000.html
喫煙率 S40~H29
JT の調査
http://www.health-net.or.jp/tobacco/product/pd100000.html
喫煙率 H1~H26
厚生労働省の調査では、H6~8 で喫煙率が上昇している
http://www.stat.go.jp/data/jinsui/2.htm#series
人口推計資料 長期時系列データ
T9~H12、H12~H27
http://www.mof.go.jp/tax_policy/summary/consumption/d09.htm
財務省 たばこ税の内訳
税収と販売数の推移
S60~S62、 H6~H27
http://www.tioj.or.jp/data/
日本たばこ協会 販売数量
http://www.mof.go.jp/about_mof/councils/fiscal_system_council/sub-of_tabacco/proceedings/material/tabakoa270529.pdf
財務省 たばこ産業を取り巻く状況
紙巻たばこの販売数量の推移 P19
S60~H26
http://www.mof.go.jp/tax_policy/reference/account/data.htm
財務省 租税及び印紙収入決算額調べ
H9~H28
http://www.soumu.go.jp/menu_seisaku/hakusyo/index.html
総務省 地方財政白書
道府県たばこ税市町村たばこ税
平成29年度版まで
平成29年度版は、平成27年度の数字
http://www.soumu.go.jp/main_content/000397821.pdf
平成28年度地方団体の歳入歳出総額の見込額
http://www.health-net.or.jp/tobacco/product/pd070000.html
厚生労働省 販売本数と一人当たり消費本数
1920~2007
1985年(S60年)の販売本数が財務省のデータと違う。
http://www.garbagenews.net/archives/2102668.html
たばこ税収
1996~2016
単位:兆円
http://www.sangiin.go.jp/japanese/annai/chousa/keizai_prism/backnumber/h21pdf/20096623.pdf
平成元年付近の販売数量、税収(グラフだけ)
http://www.cao.go.jp/zeicho/siryou/pdf/kiso15i.pdf
H1~H14 たばこ税税収
単位:億円
http://www.tioj.or.jp/others/
H17~H26 たばこ税税収
単位:億円
http://www.esri.go.jp/jp/archive/bun/bun077/bun77e.pdf
専売納付金
S35~S53
http://kero.sq4u.jp/2012/06/22/%E3%82%BF%E3%83%90%E3%82%B3%E3%81%AE%E5%80%A4%E6%AE%B5%E6%8E%A8%E7%A7%BB/
価格の推移(マイルドセブンメビウス
http://kokkai.ndl.go.jp/SENTAKU/sangiin/193/0015/19304100015004a.html
参議院 決済委員会 H29-4-10
IQOS なんかの税率



以下、さほど参考にならないやつ
https://ja.wikipedia.org/wiki/たばこ税
「税収の推移」の数字は、ちょっと誤解を招く。
「国たばこ税税収」は「たばこ特別税」を含んだ数字。
「都たばこ税」が、地方税の「道府県たばこ税」に含まれる。
23区のたばこ税は、「市町村たばこ税」に含まれる。
http://www1.mhlw.go.jp/wp/2-3-1.html
厚生省 H9年厚生白書
喫煙率とたばこ販売量の推移
グラフだけ





(´ー`)y-~~