2009年12月8日火曜日

グルージェントWebページの裏側 (2)


弊社WebサイトはGoogle Bloggerと統合し、bloggerへの記事がマスターデータとしてGAEのデータストアにレプリケーションしています。弊社サイトのメインメニューとフッターメニューは管理画面で編集ができるようになっていますし、GWTのコンポーネントとして独立できるように実装しているのですが、残念ながらbloggerに使うことができていませんでした。

クロスサイトスクリプティングになっちゃうよ問題

弊社サイトが www.gluegent.com、Bloggerが songofcloud.gluegent.com となっているため、Webブラウザのクロスサイトスクリプテイングのセキュリティ制限により、GWT側からblogger側のURLを操作することができなかったのです。
それではと、GAEのモジュールを展開してblogger側に配備することを試してみました。しかしGAEのビルド済みモジュールは複数のHTMLリソースに分割されていて、URL上の同一階層に配置されていることを前提にダイナミックにロードする仕組みになっていました。bloggerは機能として、その構造内にGAEのモジュールなどの任意のリソースを配置できる仕組みにはなっていないため、やはりGAEコンポーネントを配置したwww.gluegent.com内のHTMLページをiframeを使ってガジェットのようにblogger側に取り込まなければならないということになります(※1)。

しかし実は今日から songofcloud.gluegent.comのメインメニューとフッターメニューは www.gluegent.comと同一のGAEコンポーネントを使って構成することが出来るようになりました。これで、管理画面からメニュー構成を変えたら両サイトに反映されるようになったわけです。どのようにして問題を解決したか、苦労した点なども含めて紹介したいと思います。

同じサイトの誰かにモシモシさせる

まず、解決対象を明確にします。クロスサイトスクリプティングの制限が具体的に起こしている不具合は何かということですが、これは、iframe内から親フレーム(またはウィンドウ)のロケーションを変更できないという問題を示します。メインメニューとフッターメニューの目的はその中に並べられたアンカータグから他のページに遷移することです。iframe内に配置してしまえば、只単にiframe内で遷移することになってしまうので、親のロケーションを変更する必要があります。javascriptから以下のように操作することになります。

parent.location.href = "http://....";
しかし同一ホストではないのでブラウザに無視されて実行することはできません。長らくあきらめていたのですが、@ITで、ブラウザでのクロス・ドメイン通信のセキュリティ保護という記事を見つけました。親が別サイトなら孫を親と同じサイトにしてやりとりをさせれば良いという方法です。なるほど納得です!


IE,Firefox,Safari,Opera,Chromeで確認しましたが全て正しく確認できました。血のつながりのない義理の息子の言うことは聞いてくれないけど孫の言うことは聞いてくれました。孫というか本人同士でしたが。

基本コンセプトを理解して実装していったのですが、二つほど壁がありましたので紹介します。

onclickが効かないよ問題

メニューのアンカーはGWTから動的に作成していましたが、その際に不可解な現象に悩まされました。aタグに入れたonclickイベントが効かないという問題です。iframe化しているので、onclickからbloggerから見て孫となるiframeを動的に生成しなければならないので軽く危機です。静的に書かれているアンカーからは問題ないし、Safari,Chromeでは問題ないし、そもそもonclickイベントに入ってこないのでスクリプトの問題でもないし、大分困惑しましたが、原因は hrefの書き方でした。hrefには "#" だったり、実際のURLを見せて onclick内で return false にしたりすることは、createElementで作成したAタグではやってはいけないということでした。これもセキュリティの問題だったようです。hrefに、"javascript:void(0);" と書くことで解決できました。

iframeにURL渡してもロードされないよ問題

アンカータグからのonclickでは、動的にiframeを作り、「bloggerURL+"#" + 移動したいURL」 を渡さなければならなかったのですが、これまた Safari、Chromeでは移動できたのにIE,Firefoxでは移動しません。やっていたのは以下のようなことです。

Frame frame = new Frame(url);
Document.get().getBody().appendChild(frame.getElement());
FirefoxのFindbugプラグインや、IE8の開発者ツールから見ても、ちゃんとタグが出力されています。
<iframe src="http://..."></iframe>
にもかかわらず、読み込まれないのです。これもしばらく調査して解決に至りました。ロケーションを変更するには、document.location.href = "http://..."; と同じように、iframeのdocument.location.hrefを変更する必要があったのです。さてしかし、GWTではframeオブジェクトからdocumentオブジェクトを参照できるものの、locationメンバがありませんでしたので以下のようにJSNIで対応しました。
Anchor result = new Anchor(menu.getTitle(), url);
if (crossDomainUrl != null) {
  final String encodedUrl = encodeUriComponent(url);
  result.setHref("JavaScript:void(0);");
  result.setTitle(url);
  
  result.addClickHandler(new ClickHandler() {
    
    @Override
    public void onClick(ClickEvent event) {
      String iframeUrl = crossDomainUrl + "#go_" + encodedUrl;
      Frame frame = new Frame(iframeUrl);
      frame.setStyleName("hidden_frame");
      FrameElement frameElement = (FrameElement)frame.getElement().cast();
      frameElement.setAttribute("frameborder", "0");
      Document.get().getBody().appendChild(frame.getElement());
      openDocument(frameElement.getContentDocument(), iframeUrl);
    }
    
    private native void openDocument(Document contentDocument, String iframeUrl) /*-{
      contentDocument.location.href = iframeUrl;
    }-*/;
  });
}
もしかしたらGWTで別の方法が用意されているのに見落としているかもしれませんけどね。

とまあ、いろいろハマったりもしましたが、作成したGWT部品をサイトを越えて流用したいという要件を満たすことはできました。同じような機会が出てきたときにこの記事を思い出してくだされば幸いです。でわでわ。


  • ※1: blogger側に取り込む方法としてはレイアウトのテンプレート編集になります。bloggerのテンプレートは、Google Sitesやはてなダイヤリーなどと比べて自由にHTMLやJavaScriptが書けるようになっています。

0 件のコメント:

コメントを投稿