ククログ

株式会社クリアコード > ククログ > dbus-sendを利用して既存のFirefoxプロセスでサイトを開く方法

dbus-sendを利用して既存のFirefoxプロセスでサイトを開く方法

Firefoxのプロセスが既に起動している場合、新たにFirefoxを起動しようとすると、既に起動している方のプロセスにてコンテンツが表示されます。

その一方で、同一のプロファイルを指定してFirefoxを追加で起動しようと試みた場合など、そのままでは既に起動しているプロセスにてコンテンツを開かせることができない場合もあります。 すでにFirefoxが起動中だが、応答しない旨のエラーメッセージが表示され、Firefoxを終了し別プロファイルを利用するようにうながされます。 このような挙動になるのは、プロファイルを保護するためにロックがかけられている状態になっているためです。

今回は、GNU/Linux環境下においてそのような場合でも既存のプロセスでタブを開けるように、dbus-sendをどのように利用するとよいかを説明します。

FirefoxのDBusインターフェースについて

Firefoxでは、DBus経由でアクセスできるインターフェースとしてOpenURLが公開されています。

static const char* introspect_template =
    "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection "
    "1.0//EN\"\n"
    "\"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n"
    "<node>\n"
    " <interface name=\"org.mozilla.%s\">\n"
    "   <method name=\"OpenURL\">\n"
    "     <arg name=\"url\" direction=\"in\" type=\"ay\"/>\n"
    "   </method>\n"
    " </interface>\n"
    "</node>\n";

上記はインターフェースに関するIntrospection結果を返すテンプレートの定義ですが、OpenURLは引数にtype=ay - つまりバイト列を受け取る仕様であることがわかります。

では、バイト列はどのように渡せばよいのでしょうか。 それについては、渡されたバイト列をコマンドライン引数として処理するnsUnixRemoteServer::HandleCommandLineにて次のように言及されています。

  // the commandline property is constructed as an array of int32_t
  // followed by a series of null-terminated strings:
  //
  // [argc][offsetargv0][offsetargv1...]<workingdir>\0<argv[0]>\0argv[1]...\0
  // (offset is from the beginning of the buffer)

ざっくり説明すると次のとおりです。

  • argc 引数の個数を指定する (4byte)
  • offsetargv0 argv0へのオフセットを指定する (4byte)
  • offsetargvN argvNへのオフセットを指定する (4byte)
  • workingdir 作業ディレクトリのパスを指定する
  • argv[0] 0番目の引数を指定する
  • argv[N] N番目の引数を指定する

argcで指定した個数だけオフセットが並び、NULL終端の作業ディレクトリと後続の引数が続く仕様であることがわかります。

なお、argv[0]は無視されるので、与えるURLはargv[1]以降に指定しなければなりません。

URLを1つだけ渡す次のような例を考えてみましょう。

この場合のargcとそのオフセット値は次のとおりです。

  • argc: 2
  • offsetargv0: 13 (argcとoffsetargv0,offsetargv1で4*3バイト、workingdirの\0を加味するのでargv0へのオフセットは先頭から13バイト)
  • offsetargv1: 14 (offsetargv0に加えて、argv0の\0を加味するのでargv1へのオフセットは14バイト)

最終的なバイト列は次のようになります。(途中で改行していますが実際には1行)

0x02,0x00,0x00,0x00,
0x0d,0x00,0x00,0x00,
0x0e,0x00,0x00,0x00,
0x00,
0x00,
0x68,0x74,0x74,0x70,0x73,0x3a,0x2f,0x2f,0x77,0x77,0x77,0x2e,0x63,0x6c,0x65,0x61,0x72,0x2d,0x63,0x6f,0x64,0x65,0x2e,0x63,0x6f,0x6d,0x00

FirefoxのDBusインターフェースの宛先を知るには

どのようなバイト列を送れば受け付けてもらえるかがわかりましたが、宛先がわかっていません。 この宛先は、次のようにして調べることができます。

Firefoxのセッションバスを指定したうえで、次のコマンドを実行します。

dbus-send --session --dest=org.freedesktop.DBus --type=method_call \
		  --print-reply /org/freedesktop/DBus org.freedesktop.DBus.ListNames

すると、次のような応答が含まれていることがわかります。

...
string ":1.8"
string "org.freedesktop.portal.Desktop"
string ":1.9"
string "org.mozilla.firefox.ZGVmYXVsdC1yZWxlYXNl"
string "org.gtk.vfs.UDisks2VolumeMonitor"
string "org.gtk.vfs.mountpoint_4104"
string "org.a11y.Bus"
...

したがって、dbus-sendを利用してOpenURLを実行するには次のようなコマンドを実行1します。

dbus-send --session \
           --dest=org.mozilla.firefox.cHJvZmlsZS1lc3IxMjI_ \
           --type=method_call \
           --print-reply \
           /org/mozilla/firefox/Remote \
           org.mozilla.firefox.OpenURL array:byte:(バイト列)

これで、既存のFirefoxプロセスでタブを開かせることができるようになります。

ここまでの内容をスクリプトとしてまとめると次のようになります。 引数に開きたいURLを指定して実行することを想定しています。

#!/bin/bash

# 古いPID(親プロセスIDを取得する)
PID=$(pidof firefox | rev | cut -d' ' -f1 | rev)
# PIDをもとにしてBUSアドレスを取得する
eval `strings /proc/$PID/environ | grep DBUS_SESSION_BUS_ADDRESS`
export DBUS_SESSION_BUS_ADDRESS=$DBUS_SESSION_BUS_ADDRESS
env | grep DBUS

# string "org.mozilla.firefox.xxx"からBUSアドレス部分を抜き出す
DEST=$(dbus-send --session --dest=org.freedesktop.DBus --type=method_call --print-reply /org/freedesktop/DBus org.freedesktop.DBus.ListNames | grep firefox | sed -e 's/^ *\| *$//' | cut -d' ' -f2 | sed -e 's/"//g')
echo $DEST

# 引数として与えられたURLをバイト列に変換する
BYTES="0x02,0x00,0x00,0x00" # argc
BYTES="$BYTES,0x0d,0x00,0x00,0x00" # offset to argv[0] (4 + 4 * 2 + 1) = 13 (0x0d)
BYTES="$BYTES,0x0e,0x00,0x00,0x00" # offset to argv[1] (4 + 4 * 2 + 1 + 1) = 14 (0x0e)
BYTES="$BYTES,0x00" # empty workingdir = \0
BYTES="$BYTES,0x00" # empty argv[0] = \0
# 例) odで1バイトずつ1行で表示できる。
#   echo -n https://www.clear-code.com | od --format x1 --width=2048
#   0000000 68 74 74 70 73 3a 2f 2f 77 77 77 2e 63 6c 65 61 72 2d 63 6f 64 65 2e 63 6f 6d
#   0000032
# あとはこの出力から左端のオフセット表示を除去する
for b in `echo -n $1 | od --format x1 --width=2048 | head -n 1 | cut -d' ' -f2-`; do
    BYTES="$BYTES,0x$b"
done
BYTES="$BYTES,0x00" # \0

echo $BYTES
dbus-send --session \
           --dest=$DEST \
           --type=method_call \
           --print-reply \
           /org/mozilla/firefox/Remote \
           org.mozilla.firefox.OpenURL array:byte:$BYTES

dbus-sendではarray:byte:と型指定をすることでバイト列を送信できます。 これをシェルスクリプトとして保存しておけば、任意のサイトを指定して実行することで、dbus-sendを利用してコンテンツを既存のFirefoxプロセスで開けます。

おわりに

今回は、dbus-sendを利用して起動済みのFirefoxのプロセスで特定のサイトを開く方法を紹介しました。

クリアコードでは、お客さまからの技術的なご質問・ご依頼に有償にて対応するFirefoxサポートサービスを提 供しています。企業内でのFirefoxの運用でお困りの情シスご担当者さまやベンダーさまは、お問い合わせフォームよりお 問い合わせください。

  1. --destオプションに渡す値はFirefoxを起動するごとに変化します。