人力検索と Hatena::Let とアドオンと
まずは、
Hatena::Let のコードがさみしいので、Syntax Highlight しようと思ったの。
よく見るところだから、Bookmarklet じゃなくてユーザスクリプト(アドオン)なの。
スクラッチパッドで確認したよ
(() => {
document.body.appendChild(Object.assign(document.createElement("link"), {
rel: "stylesheet",
href: "https://highlightjs.org/static/demo/styles/zenburn.css",
}));
document.body.appendChild(Object.assign(document.createElement("script"), {
src: "//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js",
onload: () => {
document.querySelectorAll('pre.code-raw > code').forEach(e => {
hljs.highlightBlock(e);
});
},
}));
})();
そんなに難しいことしてない。
アドオンだと動かないんだ
ブラウザツールボックスのコンソールには、こんなエラーが出てる。
ReferenceError: hljs is not defined
すぐに思い出したよ(うそ。ちょっと「あれ?」ってなった)。
chrome extension について。 ページ内から、contentscripts.js … - 人力検索はてな
Although content scripts don't by default get access to objects created by page scripts,
Content scripts - Mozilla | MDN
コンテンツスクリプトの window と、ページスクリプトの window は、同じじゃない。
だから、ページスクリプトにある window.hljs は、コンテンツスクリプトでは使えない。
window.postMessage と window.addEventListener を使うんだった
(() => {
const d_ = document;
d_.body.appendChild(Object.assign(d_.createElement("link"), {
rel: "stylesheet",
href: "//highlightjs.org/static/demo/styles/zenburn.css",
}));
d_.body.appendChild(Object.assign(d_.createElement("script"), {
src: "//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js",
onload: () => {
window.postMessage({
command: "do_highlight",
}, "*");
},
}));
d_.body.appendChild(Object.assign(d_.createElement("script"), {
innerHTML: `
window.addEventListener("message", ev => {
if (ev.source == window && ev.data && ev.data.command == "do_highlight") {
document.querySelectorAll('pre.code-raw > code').forEach(e => {
hljs.highlightBlock(e);
});
}
});
`,
}));
})();
きっと普通のやり方は、
- アドオンのリソースに、外部CSS と外部JS を取り込んで、tabs.insertCSS() と tabs.executeScripts() を使う
- CDN に頼るなら、コードを Bookmarklet で書いて、tabs.executeScripts() で実行する
- ヤバい感じのやつ → Sharing objects with page scripts - Mozilla | MDN
Google Maps API → OpenLayers API (V3以降)
Google Maps API を OpenLayers API に書き換えるとこうなる、という話です。
以前、こんな投稿をしたのですが、OpenLayers のバージョンは V2 です。
そのときは既に V3 が出ていましたし、この記事を書いている時点では、OpenLayers は V5 が出ようか、という状況です。
というわけで、そのときの記事を OpenLayers V3 以降に対応するコードでリライトしました。
# コードは、記事を書いている時点の最新版 V4.6.5 で確認しました
では、ここから本題です。
id:ykhpno1 さんの、この質問。
その前までの一連の質問で、Google Maps API を使ってある程度の形になっていたのだけれど、アクセス数の制限があったりするから OpenStreet Map に変更したいという お話。
Google Maps API のここは、OpenLayers API ではこう書く、みたいな感じページが意外と見つからなかったので、備忘を兼ねて。
# 長いよ
- まずは、ソース
- Google Map
- OpenStreet Map (OpenLayers API V3 以降)
- Google Maps API vs. OpenLayers API (V3 以降)
- よく見かけるサンプルとの違い
- 以前の OpenLayers V2 との比較の記事のコードから変更したところ
- OpenLayers 2 と OpenLayers 3 以降
- その他
- 公式
- リンク
- 元ネタとか
- Fiddle
Proxy 切り替えアドオン @Firefox 59
Firefox 57 では、PAC を変更することしかできなかった WebExtension API 。
Firefox 59 で、ようやくプロクシの設定を切り替える手段ができた。
- https://developer.mozilla.org/en-US/Firefox/Releases/59#WebExtensions
- https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/browserSettings/proxyConfig
ぼくは、「プロクシなし」と「システム設定」を切り替えたいだけなので、こんな感じのアドオンで十分。
- ファイル構成
- 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-8 → UTF-16 コンバータとして機能するらしい。
- Firefox 拡張機能の設定項目の保存時に日本語が化ける - vivid memo
- 4章:XPCOM活用術 - Firefox拡張機能開発チュートリアル (XHTML)
- javascript - decodeURIComponent vs unescape, what is wrong with unescape? - Stack Overflow
そのライブラリは、XPCOM のアドオンだったのだけれど、文字列リテラルに必要な処理だったのかどうかは謎だし、WebExtension では必要ない(というか、エラーになる)。
対応としては、convertStringEncoding()
を書き換えれば良いだけなんだけど、
- ライブラリをいじらずにおきたい
- ロードするときに呼ばれてる処理なので、後から置き換えるという手が通じない
ということがあって、ライブラリをロードする前に decodeURIComponent() を書き換えて、ロードが終わった後に戻す(ある意味、荒業
# 素直に、ライブラリをいじった方が良いんじゃないか(という気はしてる
で、こんな感じの処理の流れになる。
- ライブラリがロード済みかどうかを確認
- ライブラリがロードされてなかったら
- decodeURIComponent の実装を保存して、書き換える
- ライブラリをロードする
- decodeURIComponent の実装を戻す
- ライブラリの機能追加/変更のスクリプトをロードする
- ライブラリを使う
前置きが長くなりました。
で、実際のコード
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); } }
思ったこと
こんなハイクをした後に書きました。
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
おしまい。
ようこそ、Firefox 57 @うちの事情
人呼んで、Firefox Quantum 。
UI が大幅に変更され、かなりスピードアップする半面、旧式のアドオンがすべて使えなくなってしまう。
9/20 だったかに 57.0b1 に更新してから二ヶ月近くもじたばたしてる。安定版を使っている人たちも 2017-11-14には直面することになる。
そんな Firefox 57(というか、もう 58.0b1 になってる)の、ぼくんとこの事情。
- 使っているアドオンの状況
- Stylish → Stylus
- Toggle Proxy → ?
- はてなスクリーンショット拡張 → ?
- Gyazo のアドオンのソースを追っかけてみる
- さて...
- Bookmark Favicon Changer → ?
- アドオン以外の話
- パンがなければ、自分で焼けば良いじゃない