2015年12月9日水曜日

javascriptのcanvas作成でやったくだらないミス

うわ
しばらくサボってたら2015終わるやん(´・ω・`)

タイトル通りなんてことはない話
通常javascriptでcanvasを描画する際は

    var canvas = document.createElement('canvas');
    canvas.width = 100;
    canvas.height = 100;
    var context = canvas.getContext('2d');
    context.font = "30px 'MS ゴシック'";
    context.textAlign = "left";
    context.textBaseline = "top";
    context.fillText("test", 0, 0);
    document.getElementById('canvas_target').appendChild(canvas);
単純にテキストを描画するだけのcanvas作成だが、
この width,heightは「数値で指定すること」というのがすっぽり抜け落ちてた、というだけの話。

なにをやったかというとオープンソースのライブラリをいじってた時、よくあるオプション設定中で
width: "480px"
とかやってるパラメータから流用しようと一生懸命引っ張ってきて設定しようとして
「あれれ〜?なんか設定されずにwidthが0になるぞ〜?(´・ω・`)」
とかアホ晒してたというだけ。
style属性に入るならよしなにやってくれるのに、直接width属性に当て込まれるせいかね…


久しぶりにjs触った結果起きたポカを晒して、ちゃんと書く習慣を取り戻したいというのが真の狙いだったりする(´・ω・`)


2014年4月25日金曜日

mavenを使ってpomからプロジェクトを作る in Eclipse

そういえばやったこと無かったので('A`)

mavenを使ってpomからプロジェクトを作る

1. mavenのインストール

こちら からファイルをもらってくる。 とりあえず Binary Zip を取得。
展開後、任意の場所に解凍でできたフォルダを格納。パスを通すのでわかりやすいところがよろしいかと。(自分はC: 直下に mvn フォルダを作成し、その下に格納)

環境変数を開き、
変数名: Path
変数: C:\mvn\apache-maven-3.2.1\bin;%Path%
※ ver 3.2.1の自分の環境の場合。もちろん格納場所が変わればパス部も変わるが、 
・ binまで
・ 後に ;%Path%
を付ける点に注意

で設定してやる。

適当な場所でコマンドプロンプトを開き、 
mvn --version
で確認するのが王道か。

2. プロジェクトの作成

mvnの準備ができたら、pom.xmlのあるフォルダでコマンドプロンプトを開き、
mvn eclipse:eclipse
と入力。するとあら不思議。ずらずらとコマンドが走り、
BUILD SUCCESS
のテキストが表示されたら完了。
もう一度フォルダの中を見ると、.classpasthなどそれっぽいファイルが生成されているはず。

あとはeclipseを開き、 import → Existing Project into Workspace で
上記フォルダを選択 → import
すれば完了。


簡単だけど備忘録的な意味で('A`)

2014年3月31日月曜日

poiを使ってみた

気が付きゃ3月も終わりだ('A`)

poiを使ってみた

javaで走らせたり、というかSeleniumでのテスト時に一緒にデータを取って、その結果がExcelとかに出力できたらなー、とか考えたのが始まり

1. poi

ここからダウンロードできるライブラリの一種。簡単に言うと、「Microsoft Documentsにアクセスしやすくするライブラリ」 とのこと。 今回はそのうちの一つであるExcelへのアクセスを手伝ってもらう。
今回は poi-bin-3.10-FINAL-20140208.tar.gz を使用。

2. 環境設定

とりあえずよくわからないので、 
docs以下
LICENCE
NOTICE
以外は全部プロジェクトに放り込んだうえでパスを通した。 examplesとかいるのかなぁ…

3. WorkBookの作成

一つずつ順に。WorkBookはその名の通り作業する場所。ここにExcelファイルの1シートをコピーして作業する。 あくまでコピーなので、最後に書き込まないとExcelファイル自体は更新、書き込みされない点に注意。
    try {

      /* エクセルファイルはどこですか */
      File f = new File(filepath);
      InputStream is = new FileInputStream(f);

      /* ワークブックにアクセス */
      XSSFWorkbook wb = new XSSFWorkbook(is);

    } catch (FileNotFoundException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
filepathはExcelファイルの在り処。「ファイルが無ければ作る」処理を追加してもいいかも。 WorkBookの作成時には2つもExceptionが発生するので、きちんと捕まえるなど対処が必要。

4. シート、行、列からセルの指定

WorkBookにシートを読み込ませて、徐々に絞っていく感じ。
    try {

      /* エクセルファイルはどこですか */
      File f = new File(filepath);
      InputStream is = new FileInputStream(f);

      /* ワークブックにアクセス */
      XSSFWorkbook wb = new XSSFWorkbook(is);

      /* シートの指定 */
      XSSFSheet sheet = wb.getSheet(sheetname);

      /* シートの定義の最初の行 */
      int firstRow = sheet.getFirstRowNum() + 1;

      /* シートの定義の最終行 */
      int lastRow = sheet.getLastRowNum();

      /* 行を順に上から見ていく。 */
      for (int i = firstRow; i <= lastRow; i++) {

        /* 行情報を取得 */
        XSSFRow row = sheet.getRow(i);

        /* 列の読み込み。 とりあえず 1 で */
        XSSFCell cell = row.getCell(columnno);

      }

    } catch (FileNotFoundException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
sheetnameとcolumnnoは別途指定。
sheetnameはそのままシートにつける名前。デフォルトは【Sheet1】とかなってるアレ。実はintで順番指定も可能らしい(未検証)。
columnnoは列の番号。 A列が0、B列が1、といった順で対応しているらしい。

作業の順番は、
シートの指定 → 行の指定 → 列の指定
行の指定の際に、シート中にて、【何かしらが列のどこかに記入されている行】の行数が取得できる。今回は事前に項番を振っておいたExcelを読み込んでいるため、項番の分だけ回ってくれる寸法。 【最初の行】の指定の際 +1 しているのは、各列のラベルを設定した結果、その行まで含めて【最初の行】とされてしまったのか、うまくいかなかったため。

もちろん行数をピンポイントに指定してやることも可能。

5. セルの内容の確認

既に記入されていないか確認。
 /* チェック。 いろいろステータスのサポートがあるらしい */
        if (cell != null && cell.getCellType() == Cell.CELL_TYPE_STRING) {

          /* セルの内容がStringなら */
          String cellval = cell.getStringCellValue();

          /* 取得してみる */
          System.out.println(cellval);

        } else if (cell == null || cell.getCellType() == Cell.CELL_TYPE_BLANK) {

          /* ブランクなら */
          System.out.println("からっぽ(・д・`)");
        }
セルの読み込み直後から
Cell.CELL_TYPE_STRING はいわゆるセルの型。数字や日付なんかの指定もあり、読み込み時などに型のチェックができる。「数値が取り出せた場合のみ」なんて条件も可能。

6. 書き込み

実際に書き込む。

/* 指定された列番号のセルを準備する */
    XSSFCell cell = row.getCell(columnNum, Row.CREATE_NULL_AS_BLANK);

    /* 値を適当に書き込む */
    /* TODO Stringじゃなくて数字でよくないかなこれ */
    cell.setCellValue(String.format(" %1$d", 12345));
少し巻き戻って、指定の行から書き込むセルの指定のし直し。columnNumはまた別の列番号。
Row.CREATE_NULL_AS_BLANK は【空のセルとする】的な宣言...?('A`)
setCellValueで値を書き込む。TODOにある通り、なぜ数値をStringで書き込んでいるかは謎(他のとこからコピペしてきたからなんだがな…('A`))

7. Excelに書き込み

先述の通り、ここまでは WorkBook上での出来事。きちんとExcelファイルに放り込んでやらないと更新されない。

      /* 最後にワークブックをファイルに書き込み */
      OutputStream os = new FileOutputStream(f);
      wb.write(os);
これだけ。だけどこれをやらないとExcelファイルには何も起きない…('A`)

8.番外編 -色の指定-

ちょっとだけ戻ってセルの取得あたり。フォントの色やセルの色を付けてみる。
 /* style準備 */
    XSSFCellStyle style = wb.createCellStyle();
    style.setFillPattern(XSSFCellStyle.SOLID_FOREGROUND);

    /* font準備 */
    XSSFFont font = wb.createFont();

    /* 背景色設定 */
    style.setFillForegroundColor(backgroundColor);

    /* フォント設定 */
    font.setColor(fontColor);
    style.setFont(font);

    /* セルにスタイルを反映 */
    cell.setCellStyle(style);
styleがセルの色やら、fontが文字周り。 styleの準備をして、 fontの準備をして、最後にセルにその設定を反映する、という手順。
backgroundColorやfontColorは別宣言。色の指定は HSSFColorクラスが使用可能。たとえば赤であれば、HSSFColor.RED.index で使用できる。
文字の書き込みなどとは別で宣言できるが、当然 7. Excelに書き込み の処理の前にやらないと反映されない。




|ω・`) |Ю .。o0 ( 次は型やら何やらをきれいにしないとな )

2014年2月17日月曜日

AndroidでもSelenium2

|ω・`)ノ |Ю AndroidでもSelenium2が使えると聞いて

AndroidでもSelenium2

うまくいったら万々歳ですよ

1. 環境の作成

まず必要なのが、AndroidにSeleniumServerを用意すること。事前に adbコマンド は打てるようにしておくが吉。adbの入れ方は気が向いたら(PC変えた等)書くかも。

adb -versionでも打ってコマンドが使えることを確認する。

続いてこちらから、
android-server-"ver".apk
をもらう。 例によって "ver"はバージョン。(2014/02/17 時点では 2.21.0)

貰って来たら、こいつを端末にインストールする。
コマンドを開き、
adb -s 入れたい端末ID -e install -r android-server-"ver".apk
コマンドを叩く。apkは貰ったapkの名前。 入れたい端末のID は、別途
adb devices

コマンドを叩けば見られます
入れたい端末のID device
こんな感じで表示されるかと。

2. 準備

インストールが終わったら、今度は下準備。
まずは入れたWebDriverを起動する。
実機上のアイコンをタップするか、コマンドから
adb -s 入れたい端末のID shell am start -a android.intent.action.MAIN -n org.openqa.selenium.android.app/.MainActivity -e debug true
のコマンドを起動。この辺もバッチとかにしても楽かも。これでWebDriverが起動。真っ暗な感じの表示になるはず。
続いてポートフォワード設定(というらしい)。要は接続?
コマンドから、
adb -s 入れたい端末のID forward tcp:8080 tcp:8080
を入力。
ここまでやって端末側の準備が完了。

ちなみにこのWebDriver、 Android標準ブラウザ でのシミュレータになる(らしい)。 Chromeブラウザとかはどうやるんだろ…

3. テストコード用意

明らかに順番おかしい気がするけど。 JUnitが使えるので、今までのテストも使える。
ポイントは WebDriverの宣言。通常Chromeでテストする場合、
WebDriver driver = new ChromeDriver();
と宣言するところを、
WebDriver driver = new AndroidDriver();
と宣言する。

これで動きがみられるはず。
こいつをEclipseからでも実行してやると、実機上で動きが見られ、かつテスト結果はEclipseの方に出てくる。
デザインのチェックなどは厳しいけど、機能チェック、特に登録系に対してAndroidのキーボードでいちいち入力する手間が省けると、工数がぐっと減る気がする。

問題は、「バックキー」 が使えないこと。 使うと WebDriver自体が終了します。






うん。Androidでやる意味が薄いな…('A`)
PCでええやん…


4. おまけ ~WebDriverでUA偽装

正直UA偽装でもいい気がしたので。
今回はChrome用。たぶん他にもあるかと

    /* クローム用オプション。 UA偽装しないと見れない時に */
    ChromeOptions options = new ChromeOptions();

    /* UA偽装 to iPhone */
    options.addArguments("--user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A5376e Safari/8536.25");

    /* Chromeドライバのインスタンス。オプションセット版 */
    driver = new ChromeDriver(options);

要するにオプションで設定できますよ、ってお話。UAはぐーぐる先生に聞いてみるといろいろ出てくるので、お好きなものをセットしてみましょう。




正直これでいい気がした(・ω・`)

2014年1月23日木曜日

Chromeの拡張機能を試してみた.4

そろそろページの要素からいろいろしたいなぁ

Chromeの拡張機能を試してみた.4

1. ページ内の画像srcを取得する

ついでにmapの実験にしたかったので、今回はmapでもらってきた

データのやりとりは前回でできるようになったので、いよいよ派遣したContentScriptに調査をしてもらうフェーズ。
function getImgs(){

 var imgs = $("img[src]");
 var srcMap = {};

 for(var i = 0; i < imgs.length; i++){
  srcMap["img_" + i] = $(imgs[i]).attr("src");
  console.log("img_" + i + " is " + srcMap["img_" + i]);
 }

 return srcMap;
}
とりあえずsrcだけもらってくるmap。相対パスはまだ考えない。とりあえず取れればいいやなスタンス。

あとは受信機に
} else if (request.message == "getImg"){

    var m = getImgs();

    sendResponse(
   {imgMap: m}
     );
今回からmessageに変更してます。 受信したときに先ほどのメソッドを呼び出すだけ。取得したmapをレスポンスに乗せて送り返す。
後は送信側で
function(response) {
 var imgSrcs = response.imgMap;
}
てな感じでもらえば後は加工し放題。

2. 別タブに並べて表示する

ただsrcの文字列だけもらってもねぇ…せっかくsrc貰えるんだしサムネ的にして一覧化したい。
なんで別タブ作ってそこに並べていきたい。

ただ「新しいタブの作成」はいいとして、どーしたらそこにデータを渡せるか考える。

…またレシーバー立ててメッセージ投げればいいかヽ('A`)ノ

考えるのをやめた



// 新しいタブを作成
function createNewTab(){
 chrome.tabs.create(
  {
   url: "../htmls/result.html", 
   /* まだ移動はしない */
   selected: false
  },
  function(tab){
   //window.close(); // popup.htmlを閉じる
   newTabId = tab.id;
   setListener();
  }
 );
}
/* 新規タブページからのリクエストにこたえるためのリスナー */
/* 先にセットしてしまうと map の中身が更新されないっぽい… */ 
function setListener(){

 // 新規タブページからのリクエストにこたえたい
 chrome.extension.onRequest.addListener(
   function(request, sender, sendResponse) {
     if (request.message == "getResult"){

      // レスを返してからタブを移動しないと
      // ポップアップが先に消える = mapやこのレシーバ自体消えてしまう
       sendResponse(
        {
         result: map
        }
       );

     // 新規タブへ移動 = タブを選択状態にする
    chrome.tabs.update(
      newTabId,
       {
        selected: true
       }
    );
    } else{
       sendResponse({}); // snub them.
    }
   }
 );
}
こんな感じのfunctionを作っておく。一つ目のfunctionが 「新しいタブ」を作成する機能、二つ目が「新しいタブ」からのリクエストにこたえるレシーバー。
すなわち、「新しいタブ」のonloadあたりで、ContentScriptに送ったようにリクエストを投げさせ、そのリクエストに調査結果を添付して返す、という仕組み。
但し先にレシーバーを立ててしまうと、なぜか添付するものが初期値のまま送られてしまう(更新されない?)ため、調査が終わってからレシーバーを立てる。
新しいタブは、別途自分で用意したresult.htmlなるものを表示させる。さすがにGoogleトップとか表示してもいじれないし…
但し、このタブに移動するのは上記のリクエスト/レスポンスを行い調査結果を送ってから。出ないと拡張のポップアップがタブ移動の際に消えてしまうことがあり、結果、調査結果やレシーバーがなくなってしまうためである。
そのため「新しいタブ」作成時に、そのタブIDを記憶しておき、調査結果の送信が終わってからそのタブをアクティブにする、という手順を取っている。

後は野となれ山となれ。調査結果のmapは無事にresult.htmlに送信されるので、その結果をもとにimgタグを作って表示してやればいい。






長かったヽ('A`)ノ

2014年1月8日水曜日

Android標準ブラウザの不思議な挙動

Android標準ブラウザでダウンロードを行った際、なんかうまくいかなかったことがあった時の考察メモ。
8割あってる気はしてるけど確証が持てないそんなお話。

Android標準ブラウザは2度刺す

はい、Eカードネタです。

1. 事象

事象自体は単純。
【Android】の【標準ブラウザ】で【のみ】、【ダウンロードが失敗する】というお話。
Chromeであれば問題なくダウンロードできるし、iPhoneでは普通に表示される。標準ブラウザのみ、
こんな感じで、<無題>のままいつまでたってもダウンロードが終わらず、ダウンロードマネージャを見ると【失敗】となっている状態。(仮に【事象1】とする)
また、上記とは別に、
と、にべもなく瞬殺されるケースも。(こちらを【事象2】とする)

2. 前提

上記2つの事象に対して考察を行う前に、一つ前提としておきたい【仕様】がある。
それは、
【Android標準ブラウザは、表示用プロセスとダウンロードプロセスの2種類を持つ】ということである。
これにより、ダウンロードしながらブラウジングができる、というメリットはあるのだが…

3. 考察

実は【事象2】に関しては、いろいろな人がぶつかっているらしい。その原因は、【Basic認証】にあるという。
前提 の通り、【Android標準ブラウザ】は普段【表示プロセス】で表示、リクエストを行う。しかし、ダウンロードを行う段階に入ると、【ダウンロードプロセス】にそのダウンロードを委譲するが、【ダウンロードプロセス】が「ダウンロードさせてよ( ・ω・)」と投げるリクエストは、【表示プロセス】のリクエストとは異なるリクエストになるため、【Basic認証】を通ることができなくなる、らしい。

じゃあ【事象1】も同じか、と思うとそうでもない。そもそも事象が違うし、【事象1】が発生した環境では【Basic認証】もかかっていない。じゃあ何が【事象1】みたいにブロックしてるのかと考えた時浮かんだのが、

…【自己証明書】?('A`)

【事象1】の環境では、いわゆる【オレオレ証明】を持つhttps区間内での出来事。白で塗りつぶしている部分にはダウンロード対象のドメインが入っていたため、通信自体は通っているっぽい。
すると、
【オレオレ証明】を認めますか?という問いに返答しようにも、【ダウンロードプロセス】はUIを持たないため、ユーザーは答えることができないまま、【ダウンロードプロセス】は待機を続けていた
ということになるのだろうか…
過程がどうにしろ、少なくとも【オレオレ証明】でなくなった途端【事象1】は発生しなくなったので、【事象1】の犯人はこいつで間違いないと思われる。





でも確証が無いのがなぁ…('A`)

参考

Chromeの拡張機能を試してみた.3

そろそろそれっぽいものを作り始められるかな…
とか思ってたら年開けちゃったよ…(・ω・`)

Chromeの拡張機能を試してみた.3

現在の機能: アイコン押したら背景赤くなるよ!
そんだけ。
やりたいのは、「開いてるページ中の要素について調べたい」なので、これじゃ一方的に操作しに行っているだけで意味がない。
そこで、 ContentScriptなるものをページに派遣して、そいつにページの調査をさせ、その結果を拡張が受け取る、とすればいいらしい。

1. ページにスクリプトを派遣する

ContentScriptなるまんまなScriptを派遣する。但しこいつはページ自身に入り込めるわけではなく、Chrome拡張の用意する「隔離空間(Isolated World)」にて実行される。これによりDOMにアクセスはできるが、そのページの他のスクリプトに干渉したりはできない。当たり前か。逆にページのスクリプトがこちらの動きを阻害することも無いらしい。
宣言はmanifest.json
"content_scripts": [  // 表示ページ中に埋め込むなんちゃらの宣言
     {
  "matches": [   // 埋め込むページのマッチ 必須
   "http://*/*",
   "https://*/*"
   ],
  //"css": ["css/mycontentstyles.css"], // 埋め込むcss 任意
  "js": [        //埋め込むjs 任意
   "js/jquery-1.8.2.min.js",
   "js/mycontentscript.js"
   ]
     }
   ],
この項目を追加してやる。階層はpermissionsなんかの兄弟になる位置。jsのほかcssも埋め込めるらしい。どうなっちゃうのやら...
今回はおまけでjqueryも持っていく。無いといろいろめんどいし...

mycontentscript.js(終わったら名前変えよう('A`))には、とりあえず
console.log("I'm here!")
ログ出すだけ。

全部終わったらいつも通りリロードしてから新しくタブでも開いてみる。するとコンソールに先ほどのアホなログが書き込まれているはず。

2. ContentScriptと対話する

埋め込んでログ吐くだけじゃ意味ないわけで。派遣したスクリプトから報告を受けないと派遣した意味がない。対話方法としてまずメッセージングをやってみる。

メッセージングは、簡単に言えば
拡張「ちょいこれやって結果持ってきて(;´Д`) 」
埋め込み「おkわかった。これやな⊂(・ω・`)」
拡張「おk受け取った。あとはこっちでやるわ(;´ω`)」
的な通信。何かを起点にメッセージのやり取りを行うシンプルなもの。

まずはブラウザアクションのデフォルトポップアップにpopup.htmlを用意、適当にmyscript.jsなんてスクリプトを埋め込む。
"browser_action": {  // ツールバー右上に表示するのに必要な設定. Chrome4からのAPIで、それ以前のものでは使えない。
     "default_title": "titil test", // 説明. 右上にアイコンを出す場合はホバーするとみられる
     "default_popup": "htmls/popup.html" // 右上に表示されたアイコンを押した際に出るhtmlの指定. 
},

…もうやったのがだいぶ前すぎて忘れてたヨヽ('A`)ノ
適当にpopup.htmlにボタンなりなんなり置いたら、myscript.jsにてclickイベントを乗っける。ちなみにボタンに属性として onclick="" なんて乗っけるとエラー吐かれることがある。ワケワカラン( ゚д゚ )
$("#myBtn").click(function(){
 chrome.tabs.getSelected(
  null,
  function(tab) {
     chrome.tabs.sendRequest(
      tab.id,
       {
        greeting: "hello"
       },
       function(response) {
        console.log("send from myscript " + response.farewell);
      }
     );
  }
 );
});

参考まんま、だがそれがいい。
sendRequestに必要な情報を載せて送り出す。コールバックにレスポンスが返ってきたときの処理を載せる。
3行目のnullはどのタブを操作するかを考えずに、「今開いてるタブに操作する」ためのnull。idを入れれば指定したタブにのみ操作できるが…やるのかな?('A`)
これ一つで、

拡張「ちょいこれやって結果持ってきて(;´Д`) 」
埋め込み「おkわかった。これやな⊂(・ω・`)」
拡張「おk受け取った。あとはこっちでやるわ(;´ω`)」

の赤字の部分が出来上がる。シンプルにリクエストを送り、返ってきたレスポンスで何かをやる、というもの。ちなみにresponseが無いのに response.farewellとか参照しようとするとエラーを吐くっぽい。nullチェックとか必要か?

今度は受け取り側。

chrome.extension.onRequest.addListener(
  function(request, sender, sendResponse) {
    console.log(sender.tab ?
                "from a content script:" + sender.tab.url :
                "from the extension");
    if (request.greeting == "hello")
      sendResponse({farewell: "goodbye"});
    else
      sendResponse({}); // snub them.
  }
);

こちらも参考ま(ry
リクエストをもらったら処理を行い、sendResponseでお返事する。

sendResponseは無くてもいけてしまうが、無いと送信側がいつまでも返信を待って
(;´Д`)マダー?
待ちぼうけを食らうことになる。これはメモリ的によろしくないので、空のsendResponseだけでも返してやるのがよろしいらしい。

先ほど出た response.farewell は、この7行目で送り返されたもの。こんな感じでやり取りができる。

なので、これらを仕込んでから実際にボタンを押すと、コンソールには


send from myscript goodbye
…うわ すっごいfrom間違ってるorz


  参考

3. 何が送れるの?

そうすると気になるのはいったい何がメッセージとして送れるのか。
試しに2. で作った送信側に testなるキーを設定("greeting"のままなのは内緒。)
chrome.tabs.getSelected(
 null,
 function(tab) {
    chrome.tabs.sendRequest(
     tab.id,
      {
       greeting: "test"
      },
      function(response) {
       console.log("num is " + response.num);
       console.log("string is " + response.string);
       console.log("array is " + response.array);
       console.log("map is " + response.map);
     }
    );
 }
);
中身を置き換えた感じ。戻ってくるレスポンスにはとりあえず、int、string、配列、マップで実験。

返信側もいじくる
} else if (request.greeting == "test"){

   var n = 1;
   var s = "test";
   var a = [12,13,14];
   var m = {x: 100, y: "map y"};

  console.log("num is " + n);
  console.log("string is " + s);
  console.log("array is " + a);
  console.log("map is " + m);

   sendResponse(
    {num: n},
    {string: s},
    {array: a},
    {map: m}
   );

とりあえずいろいろ載せてみた。この状態だとどうなるか。
まずは返信側のページのコンソール
num is 1
string is test
array is 12,13,14
map is [object Object]
さすがにmapはそのまま出ないがきちんと出荷されてそう。では送信側に返ってきたログは…
num is 1
string is undefined
array is undefined
map is undefined
…あれ?
2. とかでstringは貰ってるし、実は書いてないけどmapとかも実験できてたはz…
まさかと思い返信側を以下に訂正
} else if (request.greeting == "test"){

   var n = 1;
   var s = "test";
   var a = [12,13,14];
   var m = {x: 100, y: "map y"};

  console.log("num is " + n);
  console.log("string is " + s);
  console.log("array is " + a);
  console.log("map is " + m);

   sendResponse(
    {string: s},
    {num: n},    
    {array: a},
    {map: m}
   );
stringを先頭に返すように変更してみる。すると…

num is undefined
string is test
array is undefined
map is undefined
マジですか Σ(・д・)

というわけで先頭の一個しか読んでくれないらしい。エラー吐いてくれよ…('A`)





ただ、mapはいけるので複数のデータ返したいときはmapで返すか、複数回リクエストするのがいいのかね('A`)