Apple Developer Connection
高度な検索
Member Login ログイン | ご入会 ADC連絡先

Technical Note TN2139
Dashboardウィジェットのデバッグ

このテクニカルノートでは、Dashboardウィジェットの開発時に遭遇する一般的な問題やエラーに加え、そのような問題を発見・防止する手法をについて取り上げます。Mac OS X TigerでDashboardに取り組む開発者を対象読者として想定しています。





はじめに

Dashboardウィジェットの作成は難しくありませんが、何か問題が発生すると、その対処は困難である場合があります。Objective-CやJavaのような言語を使い慣れている開発者は、ウィジェットの作成に使用するWebベースの技術によって提供されるものよりももっと思い通りのコントロールとより多くのフィードバックに慣れているかもしれません。このテクニカルノートでは、開発者が開発にかける時間を有効に活用できるように、そのようなコントロールとフィードバックを手に入れるための有効な手段について説明します。

このテクニカルノートの位置付けは、『Dashboardプログラミングガイド』への補足です。Dashboardウィジェットの作成方法を習得することに関心がある開発者は、まず前掲のドキュメントをお読みください。本稿では、ウィジェット開発の基礎は取り上げません。

先頭に戻る

Safariでの開発

ウィジェットを開発するときに留意すべき最も重要なことの1つは、ウィジェットはいろいろな意味でWebページにすぎないということです。DashboardウィジェットはSafariと同じWeb Kitレンダリングエンジンを使用しているため、開発のほとんどすべてをSafariで行えます。その際に、複雑さが増す原因となるバンドリング、プロパティリスト、デフォルト画像、およびアイコンを考慮する必要がありません(これらについては後述)。

すでにウィジェットをバンドルにしている場合、Finderですべての内容を表示するには、単純に.wdgtバンドルをControl-クリックして「パッケージの内容を表示」を選択します。続いて、メインのHTMLファイルをSafariで開くと、Dashboardランタイムの余分な可変要素なしでコンテンツが表示されます。

Safariでコンテンツをテストすることは、特にJavaScriptエラーの面では不可欠です。DashboardはJavaScriptエラーを「コンソール」アプリケーションに報告しますが、エラーの原因を絞り込むためにウィジェット内部から実行をブロックするのは困難です。また、Safariでの作業は、widgetオブジェクトの呼び出しの分離にも有効です。そのような呼び出しをif (window.widget)チェックでラップする良い習慣が形成されます。

Safariでデバッグを行うための最初の重要なステップは「Debug」メニューを有効にすることです。これを行うには、リスト1に示す「ターミナル」コマンドを入力します。

リスト1:Safariで「Debug」メニューを有効にする

l337_d3v$ defaults write com.apple.Safari IncludeDebugMenu 1

次回Safariを起動したときに、ヘルプメニューの右に「Debug」メニューが表示されます。できるだけ多くのフィードバックを得るために、必ず「Log JavaScript Exceptions」項目を選択します。Mac OS X v10.4の新機能の1つにJavaScript Consoleがあります。これも「Debug」メニューから有効にできます。JavaScript Consoleを有効にしたら、いつでも作業を開始できます。

先頭に戻る

デバッグ情報の出力

JavaScriptプログラミングにおいて最も基本的で一般的なデバッグ支援機能はalert文です。この文は引数(通常は文字列)を1つ受け取り、それをモーダルダイアログに表示します。実行時に特定のエラーがSafariのJavaScript Consoleにどの時点で表示されるかを正確に絞り込むために、モーダルダイアログはブロックするという特性を利用して、JavaScriptに「ブレークポイント」を設定できます。

alert関数のモーダル動作は天恵かもしれませんが、天罰にもなる可能性があります。大量のalert文が積み重なるのにさほど時間はかかりません。そして自分でも気付かないうちに、コンテンツを更新するたびに30回クリックしなければならない羽目に陥ります。これは煩わしいものであり、setIntervalタイマに基づいて実行されるコードにおいては機能に支障をきたすおそれがあります。たとえば、一定時間の経過に応じて動作するコードの場合、ダイアログを閉じるのに忙しいと、ページの読み込みに平常時よりもずっと長くかかり、タイマの反復処理が1回実行されるまでにも時間がかかります。

Dashboardで実行すると、alert文は「コンソール」に出力されるだけなので、これらの問題は回避されます。両者の長所を生かすには、Webページにデバッグ要素を作成し、Safariの中にいる場合にデバッグ情報を付加するようにします。このデバッグ要素によって、モーダルアラートよりもはるかに控えめなログ出力文を作成できます。

デバッグ要素は、ページのどこかにdivタグを挿入して、それを配置するCSSプロパティを用意するだけで宣言できます。リスト2に、Webページに挿入したデバッグ用divを示します。divの表示、非表示、そこへの出力を行うためのJavaScriptメソッドも定義しています。

リスト2:既存コンテンツにデバッグ用divを挿入

<html>
     <head>
     <style type="text/css">
          #debugDiv {
               border-style:dotted;
               border-color:black;
               background:gray;
               position:absolute;
               bottom:0px;
               left:0px;
               height:200px;
               width: 90%;
               overflow:scroll;
               display:none;
          }
     </style>
     <script language="JavaScript" type="text/javascript">
          var debugMode = false;

          // Safariの中にいる場合は、デバッグ用divに書き込む。
          // Dashboardにいる場合は、「コンソール」に簡単なアラートを送信する。
          function DEBUG(str) {
               if (debugMode) {
                    if (window.widget) {
                         alert(str);
                    } else {
                         var debugDiv = document.getElementById('debugDiv');
                         debugDiv.appendChild(document.createTextNode(str));
                         debugDiv.appendChild(document.createElement("br"));
                         debugDiv.scrollTop = debugDiv.scrollHeight;
                    }
               }
          }

          // debugModeフラグを切り替えるが、SafariではdebugDivのみを表示する
          function toggleDebug() {
               debugMode = !debugMode;
               if (debugMode == true && !window.widget) {
                    document.getElementById('debugDiv').style.display = 'block';
               } else {
                    document.getElementById('debugDiv').style.display = 'none';
               }
          }
     </script>
     </head>

     <body onload='toggleDebug();DEBUG("loaded!");'>

          <!-- 既存のすべてのコンテンツ...-->
          Hello World!
          <!-- デバッグ用divを任意の場所で宣言する配置はCSSによって処理される -->
          <div id='debugDiv'></div>

     </body>
</html>

図1に、上記のページがSafariでどのように表示されるかを示します。DEBUGに渡された文字列「loaded!」は下部のdivに表示されます。

図1:デバッグ/ログ用のdivを使った簡単なWebページ

図1, デバッグ/ログ用のdivを使った簡単なWebページ

もう1つの手法は、window.openを使って新しいウインドウを作成し、そのウインドウのdocumentオブジェクトにログ出力文を直接書き込むことです。この手法はCSSまたはHTML宣言を必要としませんが、Safariではポップアップのブロックをオフにする必要があります。

本稿では、JavaScriptデバッグの基礎については述べません。JavaScriptデバッグの基礎に関する優れた記事が、The JavaScript Sourceにあります。同記事はすでに古く、JavaScriptやDHTMLの新しい機能をいくつかカバーしていませんが、記事に記載されている情報と一般的な落とし穴の大半は今でも通用します。特に、上記のdebugDivは、同記事で使用しているdocument.writeおよびalertの各呼び出しを置き換えることができます。SafariのJavaScript Consoleは「警告ボックス」の役割を果たします。

先頭に戻る

開発モードの有効化

ウィジェットをバンドル形式にしてテストする段階になると、ウィジェットとデスクトップアプリケーション(「コンソール」や変更を加えるために使用しているエディタなど)の間で表示を切り替えるためにDashboardの表示/非表示を繰り返すのが煩わしくなってきます。Dashboardの表示/非表示を切り替える回数を最小限に抑えるには、Dashboardを開発モード(Development Mode)にします。そうすることで、Dashboardが表示されていないときでも、ウィジェットを画面に表示したままにできます。開発モードを有効にするには、次の手順を実行します。

  • 「ターミナル」でdefaults write com.apple.dashboard devmode YESと入力します。

  • 新しい設定でDashboardを読み込みなおすために、ログアウト/ログイン(または再起動)します。

  • Dashboardにウィジェットを読み込みます。

  • ウィジェットを短い距離ドラッグします。マウスボタンは放さないでください。

  • マウスボタンを押したままにして、Dashboardを非表示にします。ウィジェットはデスクトップ上に表示されているはずです。

ウィジェットが表示されたままなので、出力を確認し、変更を加え、読み込みなおす作業がずっとスムーズになります。

注:ウィジェットを読み込みなおすには、そのウィジェットにフォーカスを設定して、キーボードでCommand-Rを押します。ウィジェットのコンテンツを読み込みなおしていることを示す、「旋回」アニメーションが表示されます。

先頭に戻る

起動しないウィジェット

すべてのコンテンツを.wdgtバンドルに入れ、Dashboardでテストを行う準備が整ったとします。しかし、ウィジェットバーからウィジェットを取り出したり、Finderでバンドルをダブルクリックしても、何も起こりません。ウィジェットが表示されなかったり、ほんの一瞬だけ表示されてすぐに消えてしまいます。いくつかの原因が考えられます。付随する症状に応じて原因を以下のように分類できます。

Dashboardがまったく表示されない

作動するウィジェットをFinderでダブルクリックすると、Dashboardが表示され、そのウィジェットが読み込まれます。ウィジェットをダブルクリックしても、Dashboard自体が表示されない場合、バンドルから以下の項目のいずれか1つが欠落している可能性があります。

  • デフォルト画像。ウィジェットバンドルの最上位には、Default.pngという名前のPNG形式の画像ファイルを含める必要があります。この画像は、Dashboardが通常のコンテンツを読み込む間、表示されます。このファイルが欠落していたり、形式に誤りがあったり、名前が違っていたりすると、ウィジェットは読み込まれません。この画像が正しい形式であることを確認するには、「プレビュー」で開いて、「ツール」メニューから「情報を見る」を選択します。「書類の情報」ウインドウの「書類のタイプ」に「PNGイメージ」と表示されない場合は、有効なデフォルト画像でない可能性が高いと考えられます。

  • Info.plistファイル。ウィジェットバンドルの最上位にInfo.plistファイルを入れるのを忘れている可能性があります。『Dashboardプログラミングガイド』では必要なキーをすべてカバーしていますが、最も重要なものを以下に取り上げます。

  • CFBundleIdentifierキーInfo.plistに固有のCFBundleIdentifier値を持つことは、Mac OS X上のアプリケーションにとっては必須の条件です。Dashboardウィジェットも例外ではありません。このキーが定義されていないと、ウィジェットは読み込まれません。

  • MainHTMLキーMainHTMLキーは、ウィジェットによって読み込まれるコンテンツを定義します。このキーが存在しないと、ウィジェットは読み込まれません。このキーの値は大文字/小文字が区別されます。値が対象ファイル名と正確に一致することを確認してください。

先頭に戻る

デフォルト画像、内容なし

ウィジェットがDashboardに正常に表示されたら、メインコンテンツが読み込まれている間、画面上にはデフォルト画像が表示されているはずです。デフォルト画像が消えて、代わりに何も表示されなければ、MainHTMLキーがウィジェットバンドルの最上位にある実際のHTMLファイル名と一致していないものと考えられます。

注:このようなことが起こると、ウィジェットを閉じるのが難しい場合があります。このような場合の最も簡単な対処法はウィジェットバーを表示することです。そうすると、画面上のすべてのウィジェットにクローズボックスが表示されます。

表示されていても反応しないコンテンツは、onloadから生じるJavaScriptコードエラーをスローしたと考えられます。「コンソール」でエラーを確認してください。

先頭に戻る

(ウィジェット)バーから放り出される

ウィジェットをウィジェットバーに表示するには、/Library/Widgetsまたは~/Library/Widgetsに置きます。ウィジェットバンドルの最上位にIcon.png画像ファイルのないウィジェットでも、ウィジェットバーには表示されますが、汎用アイコンになります。

2つのインストールディレクトリの一方に追加したウィジェットは、次回Dashboardを表示したときに現れます。ウィジェットが現れない場合は、オフスクリーンになっているだけかもしれないので、ウィジェットバーのリストをすべて循環させて探ってみてください。ウィジェットバーは、各ウィジェットのローカライズされたCFBundleDisplayNameに基づいてアルファベット順に整理されています。

注:Mac OS Xの他の部分と同様に、ウィジェットバーは、ローカライズされたCFBundleDisplayNameに一致しないファイル名を優先します。Finderでウィジェットバンドルの名前を変更したり、CFBundleDisplayNameを定義していない場合、ファイル名が表示されます。

先頭に戻る

個別機能の障害

Webコンテンツ:ネットワーク、組み込み、ファイル入出力、Java

『Dashboardプログラミングガイド』の「セキュリティ」セクションで述べているように、Webコンテンツの機能によっては、Dashboardウィジェット内で機能するために特別なInfo.plistキーを必要とする場合があります。このような機能には以下のものがあります。

  • ネットワークアクセス:ドキュメント位置の変更、ハイパーテキストリンクのXMLHttpRequestオブジェクト、ネットワークを越えて何かをする他の項目

  • インターネットプラグイン:EMBEDタグによる(ウィジェットではない)Webプラグイン(たとえば、QuickTimeコンテンツを表示するもの)

  • ファイルアクセス:絶対パスまたは相対パス経由で、.wdgtバンドル外のファイルシステムにアクセスする試み

  • Javaアプレット:APPLETタグを使って、Javaコンテンツを組み込みます。

上記カテゴリのいずれかに該当するコンテンツがウィジェット内にあるものの、適切なInfo.plistキーがない場合、そのコンテンツは評価もレンダリングもされません。Dashboardウィジェットに適用される制限の詳細については、『Dashboardプログラミングガイド』の「セキュリティ」セクションを参照してください。

先頭に戻る

ウィジェットの環境設定

ウィジェットの環境設定を書くときによくやる間違いは、必要なパラメータを逆にすることです。widget.setPreferenceForKey関数は、Cocoa形式パラメータの順序で定義されています。つまり、パラメータは関数が指定する順序で渡されます。この関数を呼び出す場合は、必ず最初に環境設定値、次にキーを渡してください。JavaまたはJavaScript APIのほうが慣れている場合は、最初にキーを渡そうと思うかもしれませんが、これは情報を逆にします。環境設定が実際には正しく保管されていないため、この後でwidget.preferenceForKeyを呼び出してもヌルが返されます。

また、環境設定値とキーを文字列として永続化することも重要です。プリミティブ値またはオブジェクトは環境設定に正常に永続化できない可能性があります。永続化を試みると、通常はリスト3のようなエラーが「コンソール」に出力されます。

リスト3:一般的なウィジェット環境設定の書き込みエラー

DashboardClient[869] CFLog (15):Could not generate XML data for property list

ウィジェットの環境設定がウィジェットの状態を保存するメカニズムであることにも触れておく価値があります。ユーザはいつでもログアウトしたり、コンピュータを再起動できますが、その時点でウィジェットのプロセスは終了されます。ユーザが再ログインしてDashboardを再度表示したときに復元したい一時的な状態がある場合は、状態に変更があったときにただちにウィジェット環境設定としてその状態を書き出し、ウィジェットインスタンスの読み込み時に取り出す必要があります。

注:DashboardClientプロセスを強制終了するのは、ウィジェットを取り除く(onremoveイベントハンドラをトリガするユーザアクション)のと同じではありません。ウィジェットの取り除きは、ユーザがクローズボックスをクリックした場合にのみトリガされます。DashboardClientプロセスを強制終了されたウィジェットインスタンスは、カレントユーザが次回ログインしたときに読み込みなおされます。

先頭に戻る

ウィジェットプラグイン

ウィジェットプラグインの背後にある考え方は単純で、JavaScriptからネイティブなコードおよびAPIにアクセスできるようにすることです。このセクションでは、プラグインを開発するときによく生じる問題、それらの問題を認識する方法、問題を解決・防止する方法を取り上げます。ウィジェットプラグイン開発の基礎は『Dashboardプログラミングガイド』で説明しているため、ここでは述べません。

プラグインはどこ?

ウィジェットプラグインに関して最もよくある問題は、プラグインが表示されないことです。この問題では通常、リスト4のようなエラーも「コンソール」に表示されます。

リスト4:欠落したウィジェットプラグインによる一般的なエラー

DashboardClient[123] (com.mycompany.MyWidget) undefined:Can't find variable:MyWidgetPlugin (line: 0)

これは実際には、JavaScriptの一般的な「未定義変数」エラーです。このエラーの特徴は、問題の変数がウィジェットプラグインの名前であることです。このエラーは、JavaScriptコードがプラグインを参照しようとするたびに表示され、ウィジェットがその変数をプラグインとして認識していないことを示します。これは通常、以下のいずれかの原因が考えられます。

  • プラグインが欠落しているか、名前が間違っている。ウィジェットプラグインはウィジェットバンドルの最上位にあり、.widgetpluginというファイル名拡張子と、ウィジェットのInfo.plistファイルにあるPluginエントリと一致するファイル名が付いていなければなりません。Pluginキーの値には、.widgetplugin拡張子も含まれている必要があります。これらの要件のいずれかが満たされていないと、プラグインは読み込まれません。

    Info.plistPluginキーを追加した後で、ウィジェットを起動すると、すべてのコンテンツを読み込む前に確認ダイアログが表示されます。これは、DashboardClientが少なくともPluginキーを認識したことを確認するにはよい方法です。ただし、プラグインが存在すること、あるいは読み込まれることを保証するものではありません。

  • initWithWebViewが正しく実装されていないinitWithWebView:メソッドは、ウィジェットプラグインのコードにおいて重要な構成要素です。このメソッドが欠落していか、シグネチャが間違っているか、メソッドでエラーが発生する場合、プラグインは読み込まれず、通常は「コンソール」にselector not recognizedエラーがスローされます。

  • JavaScript名がネイティブ名と一致しない。プラグインのwindowScriptObjectAvailable:メソッドの中では、渡されたWebScriptObjectにキー/名前を登録する必要があります。この名前は、JavaScript側で使用している変数名と一致する必要があります。一致していないと、JavaScriptはこの変数をウィジェットプラグインとして扱うべきであることが分かりません。

  • バンドル内部に不一致がある。多くの開発者は、Xcodeにおいてターゲットの名前をときどき変更する傾向があります。ウィジェットプラグインのXcodeターゲットまたはそのプロダクト名を変更する場合は、ターゲットの情報ウインドウを表示し、「ビルド」タブの「プロダクト名」プロパティが「プロパティ」タブの「実行可能ファイル」フィールドと一致していることを確認してください。これにより、ビルドして得られたプロダクトにおいて、プラグインのContents/MacOSフォルダにあるコンパイル済みのバイナリが、プラグインの(ウィジェットではない)Info.plistファイルのCFBundleExecutable値に一致することが保証されます。これらの名前が一致しない場合、プラグインのバイナリが見つからず、プラグインが読み込まれません。図2にXcodeの関連する設定を示します。

図2:Xcodeターゲット設定の「プロダクト名」および「実行可能ファイル」プロパティ

図2, Xcodeターゲット設定の「プロダクト名」および「実行可能ファイル」プロパティ

Pluginキーが認識されるだけでなく)プラグインが確実に読み込まれるようにする最も簡単な方法は、リスト5に示すように、initWithWebView:メソッドにNSLog文を含めることです。

リスト5:ウィジェットプラグインの読み込みを報告

- (id) initWithWebView:(WebView*)webview {
     NSLog(@"I'm in!");
     // 初期化...
     return self;
}

注:ウィジェットを配布するときには、忘れずにプラグインからNSLog文をコメントアウトまたは削除してください。実運用の場面では、ログ文はエラーと例外用としておくべきです。

先頭に戻る

プラグインの呼び出し

ウィジェットプラグインのメソッドは、windowScriptObjectAvailable:に渡されたWebScriptObjectにバインドするとすぐに、JavaScriptに対して公開されます。ただし、webScriptNameForSelector:メソッドによって、プラグインのメソッドに対応するカスタムのJavaScriptシグネチャを用意することができます。そうすることに決めた場合は、ここで登録したシグネチャがJavaScriptからの呼び出しに一致していることを十分に注意して確認します。

また、呼び出そうとしているメソッド、または参照しようとしているメンバを、isSelectorExcludedFromWebScript:もしくはisKeyExcludedFromWebScript:メソッドの中で定義していないことを確認することも重要です。これらのメソッドは、その名前が示すように、指定したセレクタ/キーへのJavaScriptからのアクセスを禁止します。

これらのメカニズムのいずれかによってブロックされているプラグインメソッドを呼び出すと、リスト6に示すように「コンソール」エラーが生じます。

リスト6:JavaScriptからメソッドが見つからない一般的なエラー

Value undefined (result of expression MyPlugin.MyMethod) is not object.(line: 10)

先頭に戻る

プラグインのクラッシュ

プラグインの複雑さによっては、開発中や使用中にクラッシュしたりハングすることもあります。クラッシュが確かにコードの中で起こっていれば、ウィジェットを読み込んだDashboardClientプロセスのクラッシュログを見れば明らかです。ただし、このクラッシュログが伝えられることは限られています。問題のメソッドが大きい場合は、どのコードがクラッシュしたのかすぐには明白でないため、さらに絞込みを行う必要があります。このような場合にgdbが役立ちます。

このドキュメントでは、ウィジェットプラグインを対象にgdbを使用する最善の方法についてのみ述べます。gdbの一般的な利用については、「Getting Started With gdb」または「Debugging With gdb」を参照してください。

プラグインへのgdbのアタッチ

ウィジェットにアタッチするには、そのウィジェットに対応するDashboardClientのプロセスIDが必要になります。このIDを知るには、「アクティビティモニタ」を使用します。図3に示すように、ウィジェットの背後にあるプロセスは、「アクティビティモニタ」でウィジェットのCFBundleNameによって確認できます。ここから、pidを取得して、gdbからウィジェットのDashboardClientにアタッチし、ブレークポイントを設定して、クラッシュをキャッチできます。

図3:アクティブティモニタ内のDashboardウィジェット

図3, アクティビティモニタ内のDashboardウィジェット

また、NSLog文にも、ウィジェットのDashboardClientプロセスのpidが含まれています。そのため、プラグインですでにログ文を作成している場合は、「アクティビティモニタ」を見る必要はありません。繰り返しになりますが、ウィジェットを出荷する前には、忘れずに不要なログ文を削除してください。

先頭に戻る

起動時のクラッシュをキャッチ

プラグインが読み込まれるとすぐにクラッシュする場合、DashboardClientプロセスはpidを取得してアタッチできるほど長く留まっていません。gdbからはウィジェットを読み込めないため、起動からクラッシュまでの間でDashboardClientをキャッチする方法が必要になります。そのための最も簡単な方法は、プラグインのできるだけ早い段階にループを挿入することです。最適な場所はinitWithWebView:内で、プラグインが受け取る最初のメッセージです。リスト7に、例を示します。

リスト7:ループを使ってウィジェットプラグインを停止

-(id)initWithWebView: (WebView*) w {
     NSLog(@"initWithWebView");

     // 何か実のあることをする前にループを回す
     // これでgdbとアタッチする時間を稼ぐ
     int spin = 1;
     while (spin == 1) {
          usleep(1000);
     }

     self = [super init];
     return self;
}

これでウィジェットをとどまらせ、クラッシュが起こる前にgdbとアタッチできます。「コンソール」のNSLog文からpidを得ます。

pidを得たら、プロセスにアタッチしてループ条件を変更し、実行を想定どおり継続できます。図4に、この簡単な例を示します。

図4:gdbからループを脱出

図4, gdbからループを脱出

先頭に戻る

ドキュメント改訂履歴

日付メモ
2005-09-07デフォルト画像に関する説明を追加。
2005-05-02Dashboardウィジェットのトラブルシューティング手法、始めから終わりまで。

掲載日: 2005-09-07




Did this document help you?
Yes: Tell us what works for you.

It’s good, but: Report typos, inaccuracies, and so forth.

It wasn’t helpful: Tell us what would have helped.