Chromiumを手探った#3 - URLの受け渡しを解明しよう

#2はこちら

ファイルの中でURLが受け渡される流れを追います。

printfデバッグ

前回url_constant.ccというファイル内で定義されたkChromeUINewTabURLという変数が発見されましたが、const char型であり外部から書き換えるのが困難でした。
そのため、一先ず設定画面(chrome://settings)から、デザイン「ホームボタンを表示する」にチェックを入れたときに表示されるホームページ変更画面
f:id:iuias:20161028090753p:plain
に存在するURL入力ボックスを用いてURLを取得し、それをkChromeUINewTabURLの代わりに用いるという方針を立てました。

ここからはこのボックス内に入力されたURL(文字列)がどのように受け渡され、実際にホームページを開く際にどのように活用されているのかを確認していきます。

ファイル間でのデータの受け渡しを追うのはソースコードとにらめっこしていても難しいため、ここで所謂"printfデバッグ"を活用します。
数々の.ccファイルや.jsファイルの中身を(grepなどを用いて)確認する中で、.ccファイル内での関数RegisterMessageCallback()とCallJavascriptFunctionUnsafe()を発見しました。前者はどうやら.jsファイル内のchrome.send()関数で送られた関数の中身を受け取る関数であり、後者は.jsファイル内の関数を呼び出す関数のようです。
これらによって実際に送られてきたデータを調べるために、.ccファイルでは

LOG(INFO)<<hoge;

と気になる変数hogeの直後に入力すれば、その部分が実行されたときに標準出力にhogeの中身が出力されることを利用して、その変数が本当にURLかどうか、また今回確認したい動きに関係のある部分かどうかを判断することができます。また、.jsファイルでは

alert(hoge);

でアラートダイアログを発生させ、同様の確認を行うことができます。

ホームページURLの流れ

先ほどのホームページ変更画面のURLと、"homepage(home_page)"でのgrepの結果、.../src/chrome/browser/resources/options/home_page_overlay.js(html)なるファイルが発見されました。
ここから入力されたURLを前述のprintfデバッグも活用しながら追跡すると、次のようになりました。

  • .../src/chrome/browser/resources/options/home_page_overlay.js
...

    /**
     * Sets the 'show home button' and 'home page is new tab page' preferences.
     * (The home page url preference is set automatically by the SettingsDialog
     * code.)
     */
    handleConfirm: function() {
      // Strip whitespace.
      var urlField = $('homepage-url-field');
      var homePageValue = urlField.value.replace(/\s*/g, '');
      urlField.value = homePageValue;
      // Don't save an empty URL for the home page. If the user left the field
      // empty, switch to the New Tab page.
      if (!homePageValue)
        $('homepage-use-ntp').checked = true;
      SettingsDialog.prototype.handleConfirm.call(this);
    },

...

home_page_overlay.htmlで入力されたURLはhomepage-url-fieldに格納され、home_page_overlay.jsのこの部分でpref="homepage",type="url",valueがURLのurlFieldなる変数としてSettingsDialog.prototype.handleConfirm.call(this);によって同ディレクトリ内のsettings_dialog.jsに送られます。

  • .../src/chrome/browser/resources/options/settings_dialog.js
...

  SettingsDialog.prototype = {
    __proto__: Page.prototype,

    /** @override */
    initializePage: function() {
      this.okButton.onclick = this.handleConfirm.bind(this);
      this.cancelButton.onclick = this.handleCancel.bind(this);
    },


    /**
     * Handles the confirm button by saving the dialog preferences.
     */
    handleConfirm: function() {
      PageManager.closeOverlay();

      var prefs = Preferences.getInstance();
      var els = this.pageDiv.querySelectorAll('[dialog-pref]');
      for (var i = 0; i < els.length; i++) {
        if (els[i].pref)
          prefs.commitPref(els[i].pref, els[i].metric);
      }
    },

...

ここからprefs.commitPref(els[i].pref, els[i].metric);によって同ディレクトリ内のpreferences.jsに送られています。

  • .../src/chrome/browser/resources/options/preferences.js
...

  /**
   * Sets a string preference that represents a URL and signals its new value.
   * The value will be fixed to be a valid URL when it gets committed to Chrome.
   * @param {string} name Preference name.
   * @param {string} value New preference value.
   * @param {boolean} commit Whether to commit the change to Chrome.
   * @param {string} metric User metrics identifier.
   */
  Preferences.setURLPref = function(name, value, commit, metric) {
    if (!commit) {
      Preferences.getInstance().setPrefNoCommit_(name, 'url', String(value));
      return;
    }

    var argumentList = [name, String(value)];
    if (metric != undefined) argumentList.push(metric);
    chrome.send('setURLPref', argumentList);
  };

...

ここでchrome.sendが行われ、URLが.jsファイルから.ccファイルに受け渡されます。
URLは.../src/chrome/browser/ui/webui/options/core_options_handler.ccによって受け取られています。

  • .../src/chrome/browser/ui/webui/options/core_options_handler.cc
...

void CoreOptionsHandler::RegisterMessages() {
  registrar_.Init(Profile::FromWebUI(web_ui())->GetPrefs());
  local_state_registrar_.Init(g_browser_process->local_state());

  web_ui()->RegisterMessageCallback("coreOptionsInitialize",
      base::Bind(&CoreOptionsHandler::HandleInitialize,
                 base::Unretained(this)));

...

  web_ui()->RegisterMessageCallback("setURLPref",
      base::Bind(&CoreOptionsHandler::HandleSetURLPref,
                 base::Unretained(this)));
}

...

void CoreOptionsHandler::HandleSetURLPref(const base::ListValue* args) {
  HandleSetPref(args, TYPE_URL);
}

...

void CoreOptionsHandler::HandleSetPref(const base::ListValue* args,
                                       PrefType type) {
  DCHECK_GT(static_cast<int>(args->GetSize()), 1);

  std::string pref_name;
  if (!args->GetString(0, &pref_name))
    return;

  const base::Value* value;
  if (!args->Get(1, &value))
    return;

  std::unique_ptr<base::Value> temp_value;

  switch (type) {
    case TYPE_BOOLEAN:
      if (!value->IsType(base::Value::TYPE_BOOLEAN)) {
        NOTREACHED();
        return;
      }
      break;
    case TYPE_INTEGER: {
      // In JS all numbers are doubles.
      double double_value;
      if (!value->GetAsDouble(&double_value)) {
        NOTREACHED();
        return;
      }
      int int_value = static_cast<int>(double_value);
      temp_value.reset(new base::FundamentalValue(int_value));
      value = temp_value.get();
      break;
    }
    case TYPE_DOUBLE:
      if (!value->IsType(base::Value::TYPE_DOUBLE)) {
        NOTREACHED();
        return;
      }
      break;
    case TYPE_STRING:
      if (!value->IsType(base::Value::TYPE_STRING)) {
        NOTREACHED();
        return;
      }
      break;
    case TYPE_URL: {
      std::string original;
      if (!value->GetAsString(&original)) {
        NOTREACHED();
        return;
      }
      GURL fixed = url_formatter::FixupURL(original, std::string());
      temp_value.reset(new base::StringValue(fixed.spec()));
      value = temp_value.get();
      break;
    }
    case TYPE_LIST: {
      // In case we have a List pref we got a JSON string.
      std::string json_string;
      if (!value->GetAsString(&json_string)) {
        NOTREACHED();
        return;
      }
      temp_value = base::JSONReader::Read(json_string);
      value = temp_value.get();
      if (!value || !value->IsType(base::Value::TYPE_LIST)) {
        NOTREACHED();
        return;
      }
      break;
    }
    default:
      NOTREACHED();
  }

  std::string metric;
  if (args->GetSize() > 2 && !args->GetString(2, &metric))
    LOG(WARNING) << "Invalid metric parameter: " << pref_name;
  SetPref(pref_name, value, metric);
}

...

URLはweb_ui()->RegisterMessageCallback("setURLPref",base::Bind(&CoreOptionsHandler::HandleSetURLPref,base::Unretained(this)));で関数HandleSetURLPrefの引数として渡され、
HandleSetURTPref内、HandleSetPref(args, TYPE_URL);で関数HandleSetPrefの引数として渡され、
HandleSetPref内、SetPref(pref_name, value, metric);によって登録されます。(pref_nameはsrc/chrome/common/pref_names.ccにおいて登録されているconst char kHomePage[] = “homepage”;を参照していると思われます。)

ホームページ設定画面で入力したURLがどのようにホームページとして新たに登録されるか、ここまで辿ってようやく判明しましたので、次回これを利用して「新しいタブページを設定画面から変更できるようにする」方法について具体的に見ていくことになります。

次回予告

printfデバッグによってURLの流れを理解した一行はいよいよソースコードを書き換えることに挑戦する。正しいと思われた選択もいざコンパイルするとエラーの山。容赦なく近づいてくる締切。最期の力を振り絞ったエンターキーが導くのは希望か、それとも――次回、「終わりよければ全て良し」

#4はこちら