風柳メモ

ソフトウェア・プログラミング関連の覚書が中心。

Firefox のアドオン(content_scripts)でXMLHttpRequestやfetchを使う場合の注意

先日、Firefox で Twitter メディアダウンローダが正常に動作しないという報告を受け(その後、近傍ツイート検索も異常な動作をすることが判明)、原因を突き止めるのに時間がかかったので、覚え書き。

結論としては、

ということ。





現象

Firefox 上で、Twitter 用のアドオン(Twitter メディアダウンローダ(0.1.1.2801以前)/近傍ツイート検索(0.2.6.1200以前))を動かすと、jQuery.getJSON() や jQuery.ajax() で、本来 JSON が返されるべきところが HTML が返ってきてしまい、正常に動作しない。
基本的には同一のスクリプトであるにも関わらず、Google Chrome 拡張機能版や、Firefox であっても Tampermonkey 上のユーザースクリプトとしてであれば問題なく動作する。

原因

当該関数による GET リクエストの HTTP Request Header に

  • Origin: null がセットされる
  • Referer はセットされない

という動作になっていた。

なお、この現象が発生した際には、当該通信は開発者ツールのネットワークモニタ上に表示されない、という困った不都合もある(こちらは、未だに表示させる方法が不明のまま)。

Twitter 側で(リクエストの正当性検証のため?おそらく 最近になって)Referer等をチェックするようになったことが原因で、正常な応答が返されなくなっていたものと思われる。

追記(2018/07/19)

SWSが本脅威を対策するためには、Refererなどを確認してリクエストの正当性を検証したり、CookieにSameSite属性を付与する手法などが考えられます。

ソーシャルウェブサービスにおける新たなプライバシー脅威「Silhouette」を発見:NTT持株会社ニュースリリース:NTT HOME

実は、このプライバシー脅威(Silhouette)への対策の影響だった、とか?

対策

This is accomplished by exposing more privileged XHR and fetch instances in the content script, which has the side-effect of not setting the Origin and Referer headers like a request from the page itself would, this is often preferable to prevent the request from revealing its cross-orign nature. From version 58 onwards extensions that need to perform requests that behave as if they were sent by the content itself can use content.XMLHttpRequest and content.fetch() instead. For cross-browser extensions their presence must be feature-detected.

Content scripts - Mozilla | MDN

バージョン58以上の Firefox においては、アドオンで content_scripts 内で XMLHttpRequest や fetch (およびそれらを使用している jQuery 等のライブラリ)を使用するときには、window.XMLHttpRequest や window.fetch の代わりに、content.XMLHttpRequest や content.fetch を用いればよいらしい。
これにより、普通にページ(コンテンツ)上から発行されたのと同様に、Origin や Referer がセットされるようになる。

注意事項

content_scripts 内では、window.XMLHttpRequest や window.fetch は read-only になっているため、content.* で上書きしようとしてもできない。

自作スクリプト内ならば、使用するスコープの最初で、

const XMLHttpRequest = ( typeof content != 'undefined' && typeof content.XMLHttpRequest == 'function' ) ? content.XMLHttpRequest  : window.XMLHttpRequest;
const fetch = ( typeof content != 'undefined' && typeof content.fetch == 'function' ) ? content.fetch  : window.fetch;

のようにしておけばよいかも。

問題なのは、jQuery のようなライブラリを使用する場合で、作りによってはソースコードを直接書き換えるしか無いかもしれない。
なお、Firefox のアドオンを AMO にアップする際には、既知のライブラリを使う場合、改変されたものは同梱出来ない、という制約がある。レビューでチェックされ、改変が見つかった場合は当該バージョンが無効化されてしまう(既に経験済み(汗))。

幸い、jQuery の場合には、読み込んだ後にjQuery.ajaxSettings.xhrを次のような感じにオーバーライドしてやればよさげ。
追記と訂正:直接書き換えるよりも、jQuery.ajaxSetup() を介するべきだった。

if ( ( typeof content != 'undefined' ) && ( typeof content.XMLHttpRequest == 'function' ) ) {
    /*
    //jQuery.ajaxSettings.xhr = function () {
    //    try {
    //        return new content.XMLHttpRequest();
    //    } catch ( e ) {}
    //};
    */
    jQuery.ajaxSetup( {
        xhr : function () {
            try {
                return new content.XMLHttpRequest();
            } catch ( e ) {}
        }
    } );
}

WordPressをhttps化したら、Contact Form 7 の設定が消えてしまった⁉

WordPress を設置してあるサイトを SSL/TLS(http → https)化し、記事なども含めて http://~ となっていたサイトの URL を https://~ に置換したところ、Contact Form 7 の一部の設定が消えてしまった。
今後も似たような失敗をしそうなので、覚え書きとして記事にしておく。




現象

f:id:furyu-tei:20180607212208p:plain

のような、コンタクトフォーム(Contact Form 7)のメールテンプレート設定がなされていたところに、Search Regexを使用して、Post meta value 中のサイトの URL を http://~ から https://~ に置換した。

f:id:furyu-tei:20180607212720p:plain

すると、なぜか、コンタクトフォームのメールテンプレート設定が無くなってしまっていた。

f:id:furyu-tei:20180607212804p:plain

原因

バックアップして置いたデータベースのファイルを調べてみたところ、コンタクトフォームのメールテンプレートは、

(9, 5, '_mail', 'a:9:{s:6:\"active\";b:1;s:7:\"subject\";s:35:\"WordPressてすと \"[your-subject]\"\";s:6:\"sender\";s:42:\"[your-name] \";s:9:\"recipient\";s:18:\"xxxxxxxx@xxxxxxxxx\";s:4:\"body\";s:238:\"差出人: [your-name] <[your-email]>\n題名: [your-subject]\n\nメッセージ本文:\n[your-message]\n\n-- \nこのメールは WordPressてすと (http://xxxxxxxxxxxxxxxxxxxxxxxxx) のお問い合わせフォームから送信されました\";s:18:\"additional_headers\";s:22:\"Reply-To: [your-email]\";s:11:\"attachments\";s:0:\"\";s:8:\"use_html\";b:0;s:13:\"exclude_blank\";b:0;}'),

のような形で格納されていた。

勘のいい方はお気づきだろう……そう、つまり、PHPでシリアル化(serialize)された形式になっているため、http → https へと単純に置換してしまうと、文字数が1文字増えて、シリアル化データとしては壊れてしまい、コンタクトフォームでは不正な値として設定が無効化されてしまったのである。

対策

今回たまたま当方が気付いたのがコンタクトフォームだっただけで、似たような現象は他のプラグインでも発生しうると思われる。
Search Regex による Post meta value の機械的な置換は実施せず、各プラグインの設定にサイトの URL が含まれる場合には、逐次確認して手動で変更するのが無難だろう。
コンタクトフォームに限れば、データベースファイル中の '_mail' や '_mail_2' といった行の "body" 中にサイトの URL が含まれる場合、 "body" の長さをひとつ増加させるようなスクリプトでも組んでやれば対応可能ではあろうが。

より効率の良い方法やプラグインなどをご存知の方は、ご教示願いたい。

追記

はけた氏(@)にもツイートでご指摘いただいたが、こういう場合は
interconnectit.com
を使うのが定石の模様。

Search Replace DB を使うと、
f:id:furyu-tei:20180608090145p:plain
このように、Post meta value 等のシリアル化されたデータについても、それに配慮した変換を行ってくれる。

なお、実際に不具合で発生したサイトでも最初はこれを試そうとしたのだが、WordPress のバージョンの問題でうまく実行できなかったのであった……環境はなるべく最新のものを使おうね、というお話(自分が制作・管理していない場合、いかんともしがたいが・苦笑)。

【覚書】viewport指定について

モバイルデバイス用のviewport設定についてまじめに調べようとしたら、深みにはまりそうだったので、とりあえず整理を兼ねて覚え書きを残しておく。
ついでに、viewportの簡易的な表示テストができるページも作ってみた




viewport の指定方法

viewportは、HTMLのhead内のmeta要素として指定する

<meta name="viewport" content="(viewportのパラメータ)" />

viewportのパラメータはカンマ(",")区切りで指定。

なお、これは主としてモバイルデバイス(スマートフォン・タブレットなど)用の設定であり、基本的にはデスクトップ用ブラウザの表示には影響を与えない。
ブラウザが常に全画面で表示されるために、基本、ウィンドウの幅=デバイスの表示幅となるモバイルデバイスにおいて(余白が出来たり見切れたりすることなく)適切なページを表示を行うための指定という認識。

viewportの主要なパラメータ

<meta name="viewport"> の content の値
設定可能な値 説明
width 正の整数または文字列 device-width ウェブサイトを描画したいビューポートの幅をピクセル数で定義します。
height 正の整数またはテキスト device-height ビューポートの高さを定義します。どのブラウザーでも使用されていません。
initial-scale 0.0 から 10.0 までの、正の数値 デバイスの幅 (ポートレートモードでの device-width またはランドスケープモードでの device-height) とビューポートの寸法との比率を定義します。
maximum-scale 0.0 から 10.0 までの、正の数値 ズームの最大値を定義します。この値は minimum-scale と同じまたはより大きくしなければなりません。そうではないときの動作は未定義です。ブラウザーの設定でこの規則を無視できます。また、iOS 10 以降は既定で無視します。
minimum-scale 0.0 から 10.0 までの、正の数値 ズームの最小値を定義します。この値は maximum-scale と同じまたはより小さくしなければなりません。そうではないときの動作は未定義です。ブラウザーの設定でこの規則を無視できます。また、iOS 10 以降は既定で無視します。
user-scalable yes または no no を設定すると、ユーザーはページのズームができなくなります。既定値は yes です。ブラウザーの設定でこの規則を無視できます。また、iOS 10 以降は既定で無視します。

<meta>: 文書レベルメタデータ要素 - HTML: HyperText Markup Language | MDN
viewportを指定しないとどうなる?

viewport指定を省略した場合、モバイルデバイスでは、ブラウザが暗黙的にデフォルトのwidth値(ブラウザ毎に異なる、例えば980といった値)を用いて、これをウィンドウ(≒スクリーン)内に収めるように表示される。
例えば、デスクトップ向けのページなどで、横幅がこれより大きい値を前提として設計されていたら見切れてしまう。

device-width とは?

device-widthとは、モバイルデバイスが持つ画面(スクリーン)の論理解像度(ポイント実質解像度CSSピクセル)の横幅のことで、これはカタログに載っている解像度(画素数)とは異なる。
device-heightは同じく縦幅のことだが、ほとんど使うことはないと思われる。
例えば、iPhone X のディスプレイは5.8インチ・1125x2436ピクセルの解像度を持つが、device-widthは375(device-heightは812)となる。
すなわち、ブラウザのウィンドウで375px×812pxの画像を等倍で表示した場合、画面にピッタリと収まるサイズで表示されるということになる。

initial-scale とは?

基本的には、ページを表示した際の、最初の表示倍率(拡大率)のこと。
例えば、initial-scale=1 の時は等倍で、initial-scale=0.5の時は幅が半分に縮小(矩形としては4分の1)、initial-scale=2の時には幅が倍に拡大(矩形としては4倍)となる。

これだけならばよいのだが、ややこしいのが『条件によっては viewport の大きさに影響してしまう』ということ(後述)。

viewport が影響する主な要素

JavaScript

viewportの指定によりブラウザのウィンドウの大きさが決まることになるため、window.innerWidth・innerHeightといったパラメータに影響する。
なお、window.screen.widthやwindow.screen.availWidthは、viewportの指定によらず一定で、デバイス固有の値(≒device-width)を持つ。

viewport 設定テスト用ページについて

簡易的な viewport 設定テスト用ページを作成した。

viewport setting tester

  • radioボタンに応じて、meta[name="viewport"]を書き換えるようになっている。
  • 条件によっては、値を変更しても表示が変わらない場合がある。この場合、[Reload]を押すことで反映される。
    Mobile 版 Firefox では、[Reload]を押しても変わらない場合あり。この場合、ページをいったん閉じて、開きなおす必要がある。
  • Google Chrome の開発者ツールのモバイルデバイスエミュレータ(device toolbar)で、画面の拡大縮小(ピンチアウト/イン)の操作は、[Shift]+ドラッグ(スワイプ)で行う([Ctrl]+[+]/[-]では変化しないことに注意)。リセットは[Ctrl]+[0]。

具体的な viewport 指定の例

"width=device-width, initial-scale=1" で、万全!?

viewportについて調べていると、典型的な設定例として出てくるのは、

<meta name="viewport" content="width=device-width, initial-scale=1" />

のようなもの。

  • viewport(≒ブラウザのウィンドウ)幅を device-width に
  • 最初の表示倍率を 1 に

という指定。
f:id:furyu-tei:20180520095805p:plain

適切なレスポンシブ対応(メディアクエリのブレイクポイントの設定)が行われている場合には、この設定で問題は無い。

レスポンシブ対応がなされていない場合

レスポンシブ対応がなされておらず、『とりあえず、デスクトップ用の画面を、モバイルデバイスでも横幅が綺麗に収まる形で表示したい』という場合の設定。
例は、横幅1000pxで設計されているページの例

<meta name="viewport" content="width=1000, user-scalable=no" />

f:id:furyu-tei:20180520095818p:plain
本来ならば、width さえ指定しておけば、ブラウザが拡大率を適切に設定して、スクリーン内に収まるように調整してくれそうなもの。
ではなぜ user-scalable=no を設定しているのかというと、状況によってはこの自動調整がうまくいかないケース(等倍で表示されるケース)があったため
うまく自動調整されない場合の原因は不明。
いずれにしても、user-scalable=no にするとユーザーが拡大縮小できなくなってしまい不便なので、早期にきちんとレスポンシブ対応すべきではある。

レスポンシブ対応が不完全な場合

スマートフォンには対応したのだけれど、ある範囲(例えば横幅768px~1199px)のデバイス(タブレット)だけ表示が見切れる(デスクトップ用に1200px前提でページ設計)という場合の設定。

<meta name="viewport" content="width=device-width, initial-scale=1" />
<script>
( function () {
'use strict';

var min_screen_width_for_tablet = 768, // @media (min-width: <min_screen_width_for_tablet>px) { ... }
    viewport_width_for_tablet = 1200,
    screen_width = window.screen.width;

if ( min_screen_width_for_tablet <= screen_width && screen_width < viewport_width_for_tablet ) {
    document.querySelector( 'meta[name="viewport"]' ).setAttribute( 'content', 'width=' + viewport_width_for_tablet + ', user-scalable=no' );
}
} )();
</script>

デフォルトとして、meta[name="viewport"]には"width=device-width, initial-scale=1"を指定しておき、JavaScript で window.screen.width によりデバイスのスクリーン幅(≒device-width)を調べ、条件に当てはまる場合だけ書き換えを行う。

注意点・問題点など

initial-scale について

上記のとおり、initial-scaleは初期の表示倍率を示すほかに、条件によっては viewport の大きさに影響してしまう。
具体的には、

X = device-width / initial-scale
とするとき、
width < X となる場合には、viewport の幅として X が適用される(widthの指定は無視される)。

模様。
例えばdevice-widthが360のモバイルデバイスの場合に、
"width=640, initial-scale=0.5"
のような指定をすると、実際の viewport の幅としては 720(=360/0.5)が適用されてしまう(width=640の方は無視される)

また、initial-scale に 1 未満の値を指定しても、状況によってはうまく縮小表示されないケースもある。
具体的な条件は不明。ただし、この場合でも、user-scalable=no と併用すれば正しく縮小表示される模様。

ブラウザによってウィンドウサイズ(innerWidth/Height)の決定方法が異なっている?

f:id:furyu-tei:20180520095827p:plain

Google Chromeの場合
innerWidth/innerHeightは、ユーザーがscale(ピンチインで縮小表示)して表示可能な最大領域のピクセル数を示す模様。

この最大領域の横幅は、document.body の大きさと viewport の minimum-scale により、以下のように決まっている模様。

  • document.body の横幅が viewport の以下の場合には、viewport の幅
  • document.body の横幅が viewport より大きい場合、device-width / minimum-scale と document.body の横幅のうち、小さい方

あくまで最大領域に応じて決まるため、実際のユーザーの拡大縮小操作には影響されない。

Firefoxの場合
innerWidth/innerHeightは、ドキュメントのうち、ブラウザの表示領域に実際に表示されているピクセル数を示す模様。
ユーザーの拡大縮小操作に伴って変動する。

【未確認】Safariの場合
実機が無いため、確認できていない。

【未確認】Windows Tablet / Phone について

Windows 系のモバイルデバイスについては、meta[name="viewport"] による指定は効かないため、別途、CSS で @-ms-viewport による指定が必要
実機が無いこともあり、確認が取れていない。

【覚書】ノートン 360 のせいで、pip freeze がフリーズした件

Windows 10 上の Ubuntu で

$ pip freeze

を実行したら、文字通りフリーズしてしまった。
プロンプトに返ってこない。pip install 等を実行した場合も同様。

原因は、ノートン 360 Norton 360のファイアウォールだった。
同様の事が発生したときに焦らないように、メモ書きしておく。




原因と対策

Python 3(3.6.3)の python3 (実行ファイル) がブロックされていたのが原因。
ノートン 360 の
「設定」→「ファイアウォール」→「プログラム制御」
で確認すると、python3の『信頼』項目が『未確認』になっていた。
この状態で、アクセスが『自動』になっていると、ネットワークアクセスがブロックされてしまうため、これを手動で『許可』に変更して[適用する]必要があった。
ファイアウォールのプログラム制御画面

余談…というべきかどうか

ノートン 360 のライブアップデートで、インストール失敗

pip freeze や pip install が固まってしまう状態になった際、過去の経験


もあって、「どうせまた、ノートン 360 が原因だろう」と思い、軽い気持ちで手動でライブアップデートを実施したところ……再起動後に
ノートン 360のインストールでエラーがありました。続行できません。
と言われてしまい、往生した。
もちろん、ノートン 360 そのものも動作していないため、セキュリティ的にノーガード……。

その後の復旧

上記の状態で、どうしようもないのでそのまま強制的に再起動すると*1、今度は、
ライブアップデートエラー
のようなダイアログが現れた。
最初からこっちを出してくれ、と思わないでもない。
『サポートWebページに移動する』リンク先の指示に従って、『ノートン削除/再インストールツール』(NRnR.exe) をダウンロードして実行。
削除と再インストールを行うことで、なんとか復旧することができた。

*1:普通に再起動しようとすると、再起動をキャンセルしてとノートン360に言われてしまう……キャンセルせず、強制的に再起動する必要があった

【アマゾン注文履歴フィルタ】CSVファイルとExcelを使って必要な注文だけを印刷(PDF出力)する手順

アマゾン注文履歴フィルタ
furyu.hatenablog.com
で、『領収書を必要な分だけ細かく指定して印刷(PDF出力)したい』といった場合、いったん全件表示を行い、そこからダウンロードした CSV ファイルを Excel で読み込ませて必要な注文を絞り込むのが、比較的楽な方法だと思います。
ここでは、その手順を示します。




CSV ファイルと Excel を使って、必要な注文だけを絞り込む方法

  1. まず、対象月等の最低限の条件を指定し、
    f:id:furyu-tei:20180301002736p:plain
    [領収書印刷用画面]を開きます。

     

  2. 領収書の読み込みが完了したら(印刷はせずに)[注文履歴CSV(参考用)ダウンロード]をクリックし、
    f:id:furyu-tei:20180301002803p:plain
    CSVファイルをダウンロードします。

     

  3. ダウンロードしたCSVファイルをExcelで開き、適当な位置に作業用の列を挿入します(例では「チェック」列としています)。
    f:id:furyu-tei:20180301002831p:plain
    商品名などを確認しつつ、必要なものに印(○印など)をつけて仕分けていきます。
    同一の注文番号のものは、一番上の奴だけ印を付ければ大丈夫です。
    なお、作業用列は必須ではありません。例えば、支払い方法で絞り込みたい場合、「クレカ種類」列をオートフィルタの対象にすれば同様のことが可能です。

     

  4. 仕分け終わったら、Excelの機能でフィルタをかけます。
    f:id:furyu-tei:20180301002849p:plain

     

  5. 結果から、「注文番号」列の表示されているセルだけを選択します。
    表示されているものだけを選択するには(普通に範囲選択した状態で)「[Alt]+[;](セミコロン)」を押します(これはWindowsの場合です。Macの場合、「[shift] + [command] + [Z]」だそうです)。
    f:id:furyu-tei:20180301013017p:plain
    選択したら、コピー([Ctrl]+[C])します。

     

  6. 上でコピーした注文番号を、別のシートに貼り付けます([Ctrl]+[V])。
    f:id:furyu-tei:20180301002927p:plain
    その上で、TEXTJOIN関数を使って、各注文番号を" OR "でつなぎます。
    =TEXTJOIN( " OR ", TRUE, <注文番号の入ったセルの範囲> )
    なお、TEXTJOINはExcel2016からサポートされたワークシート関数だそうです
    Excel2013以前をご使用の場合、後で示すアドインを入れてみて下さい
    もしくは、別途テキストエディタ等に注文番号列をコピー&ペーストし、改行を" OR "に変換します。

     

  7. 得られた文字列(注文番号を" OR "でつなげたもの)を、注文履歴フィルタの「絞り込み」欄に貼り付け、[適用]ボタンを押します。
    f:id:furyu-tei:20180301013309p:plain
    必要な注文のみが抽出されるので、再度[領収書印刷用画面]を開き、印刷(PDF出力)を行います。

     

TEXTJOINなどのExcel2013以前ではサポートしていない関数を、疑似的に使えるようにするアドイン

上で述べたTEXTJOINは便利な関数なのですが、残念なことにExcel2016より前のバージョンでは含まれていません。
幸い、Excel2013以前でもTEXTJOIN等のワークシート関数を、ユーザー定義関数として使う方法を紹介してくださっている一連の記事がありました。
ありがとうございます!>はけた @さん
https://www.excelspeedup.com/category/kansuu/www.excelspeedup.com


これらをまとめて、アドイン(Excel2016Func.xlam)としてインストールできるようにしてみました。
以下からダウンロードできます。

Excel2016Funcのダウンロード

GitHub - furyutei/Excel2016Func: Excel2013以前で未サポートのワークシート関数の一部を疑似的に使用可能にするアドイン

インストール方法などは、こちらをご覧ください。
なお、Mac の場合には、インストール/アンインストール用のスクリプト(VBScript)は動作しないため、"Excel2016Func.xlam" を適当なディレクトリにコピーした上で、手動でインストールする必要があります。
support.office.com
を参考にしてください。
[ツール] メニューの [アドイン] を選択し、 [アドイン] ダイアログ ボックスの [参照] をクリックして、"Excel2016Func.xlam"をコピーした場所を探して指定し、[OK]を押します。
「☑Excel2016Func」にチェックがついていることを確認してください。

なお、作者はMacを持っていないため、詳細はわかりません。あしからずご了承ください。

参考にした記事など

artfulplace.hatenablog.com

https://www.excelspeedup.com/textjoin2/www.excelspeedup.com

fnya.cocolog-nifty.com