b=517133 Get crash stats for X errors r=bsmedberg
authorKarl Tomlinson <karlt+@karlt.net>
Wed, 24 Feb 2010 14:25:16 -0800
changeset 38688 a5ba2b43122bfd777ff1180fe5cba661538af2a5
parent 38687 122641d81c499523d03f147a40f8d4f5fa32a61f
child 38689 9a4b73f92f0e9a3f4bd7aa5be16b14c3551c3bab
push idunknown
push userunknown
push dateunknown
reviewersbsmedberg
bugs517133
milestone1.9.3a2pre
b=517133 Get crash stats for X errors r=bsmedberg
dom/plugins/PPluginModule.ipdl
dom/plugins/PluginModuleChild.cpp
dom/plugins/PluginModuleParent.cpp
dom/plugins/PluginModuleParent.h
dom/plugins/PluginThreadChild.cpp
dom/plugins/PluginThreadChild.h
gfx/src/thebes/nsSystemFontsGTK2.h
gfx/src/thebes/nsThebesDeviceContext.cpp
toolkit/xre/Makefile.in
toolkit/xre/nsAppRunner.cpp
toolkit/xre/nsEmbedFunctions.cpp
toolkit/xre/nsX11ErrorHandler.cpp
toolkit/xre/nsX11ErrorHandler.h
xpcom/build/nsXULAppAPI.h
--- a/dom/plugins/PPluginModule.ipdl
+++ b/dom/plugins/PPluginModule.ipdl
@@ -94,12 +94,14 @@ parent:
     returns (NPError aError,
              bool aBoolVal);
 
   // Wake up and process a few native events.  Periodically called by
   // Gtk-specific code upon detecting that the plugin process has
   // entered a nested event loop.  If the browser doesn't process
   // native events, then "livelock" and some other glitches can occur.
   rpc ProcessSomeEvents();
+
+  sync AppendNotesToCrashReport(nsCString aNotes);
 };
 
 } // namespace plugins
 } // namespace mozilla
--- a/dom/plugins/PluginModuleChild.cpp
+++ b/dom/plugins/PluginModuleChild.cpp
@@ -348,16 +348,20 @@ PluginModuleChild::InitGraphics()
     real_gtk_plug_embedded = *embedded;
     *embedded = wrap_gtk_plug_embedded;
 #elif defined(MOZ_WIDGET_QT)
     if (!qApp)
         gQApp = new QApplication(0, NULL);
 #else
     // may not be necessary on all platforms
 #endif
+#ifdef MOZ_X11
+    // Do this after initializing GDK, or GDK will install its own handler.
+    XRE_InstallX11ErrorHandler();
+#endif
 
     return true;
 }
 
 bool
 PluginModuleChild::AnswerNP_Shutdown(NPError *rv)
 {
     AssertPluginThread();
--- a/dom/plugins/PluginModuleParent.cpp
+++ b/dom/plugins/PluginModuleParent.cpp
@@ -187,19 +187,32 @@ PluginModuleParent::WriteExtraDataForMin
     else
         filePos++;
     WriteExtraDataEntry(stream, "PluginFilename",
                         pluginFile.substr(filePos).c_str());
     //TODO: add plugin name and version: bug 539841
     // (as PluginName, PluginVersion)
     WriteExtraDataEntry(stream, "PluginName", "");
     WriteExtraDataEntry(stream, "PluginVersion", "");
+
+    if (!mCrashNotes.IsEmpty()) {
+        WriteExtraDataEntry(stream, "Notes", mCrashNotes.get());
+    }
+
     stream->Close();
 }
 
+
+bool
+PluginModuleParent::RecvAppendNotesToCrashReport(const nsCString& aNotes)
+{
+    mCrashNotes.Append(aNotes);
+    return true;
+}
+
 int
 PluginModuleParent::TimeoutChanged(const char* aPref, void* aModule)
 {
     AssertPluginThread();
     NS_ABORT_IF_FALSE(!strcmp(aPref, kTimeoutPref),
                       "unexpected pref callback");
 
     PRInt32 timeoutSecs = nsContentUtils::GetIntPref(kTimeoutPref, 0);
--- a/dom/plugins/PluginModuleParent.h
+++ b/dom/plugins/PluginModuleParent.h
@@ -152,16 +152,19 @@ protected:
     virtual bool
     AnswerNPN_GetValue_WithBoolReturn(const NPNVariable& aVariable,
                                       NPError* aError,
                                       bool* aBoolVal);
 
     NS_OVERRIDE
     virtual bool AnswerProcessSomeEvents();
 
+    virtual bool
+    RecvAppendNotesToCrashReport(const nsCString& aNotes);
+
     static PluginInstanceParent* InstCast(NPP instance);
     static BrowserStreamParent* StreamCast(NPP instance, NPStream* s);
 
 private:
     void SetPluginFuncs(NPPluginFuncs* aFuncs);
 
     // Implement the module-level functions from NPAPI; these are
     // normally resolved directly from the DSO.
@@ -221,16 +224,17 @@ private:
 private:
     void WriteExtraDataForMinidump(nsIFile* dumpFile);
     void WriteExtraDataEntry(nsIFileOutputStream* stream,
                              const char* key,
                              const char* value);
     void CleanupFromTimeout();
     static int TimeoutChanged(const char* aPref, void* aModule);
 
+    nsCString mCrashNotes;
     PluginProcessParent* mSubprocess;
     bool mShutdown;
     const NPNetscapeFuncs* mNPNIface;
     nsTHashtable<nsVoidPtrHashKey> mValidIdentifiers;
     nsNPAPIPlugin* mPlugin;
     time_t mProcessStartTime;
 };
 
--- a/dom/plugins/PluginThreadChild.cpp
+++ b/dom/plugins/PluginThreadChild.cpp
@@ -99,10 +99,21 @@ PluginThreadChild::Init()
 
 void
 PluginThreadChild::CleanUp()
 {
     mPlugin.CleanUp();
     MozillaChildThread::CleanUp();
 }
 
+/* static */
+void
+PluginThreadChild::AppendNotesToCrashReport(const nsCString& aNotes)
+{
+    AssertPluginThread();
+
+    if (gInstance) {
+        gInstance->mPlugin.SendAppendNotesToCrashReport(aNotes);
+    }
+}
+
 } // namespace plugins
 } // namespace mozilla
--- a/dom/plugins/PluginThreadChild.h
+++ b/dom/plugins/PluginThreadChild.h
@@ -58,16 +58,19 @@ class PluginThreadChild : public mozilla
 public:
     PluginThreadChild(ProcessHandle aParentHandle);
     ~PluginThreadChild();
 
     static PluginThreadChild* current() {
         return gInstance;
     }
 
+    // For use on the plugin thread.
+    static void AppendNotesToCrashReport(const nsCString& aNotes);
+
 private:
     static PluginThreadChild* gInstance;
 
     // Thread implementation:
     virtual void Init();
     virtual void CleanUp();
 
     PluginModuleChild mPlugin;
--- a/gfx/src/thebes/nsSystemFontsGTK2.h
+++ b/gfx/src/thebes/nsSystemFontsGTK2.h
@@ -34,17 +34,18 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef _NS_SYSTEMFONTSGTK2_H_
 #define _NS_SYSTEMFONTSGTK2_H_
 
-#include <gfxFont.h>
+#include "gfxFont.h"
+typedef struct _GtkWidget GtkWidget;
 
 class nsSystemFontsGTK2
 {
 public:
     nsSystemFontsGTK2();
     ~nsSystemFontsGTK2();
 
     nsresult GetSystemFont(nsSystemFontID anID, nsString *aFontName,
--- a/gfx/src/thebes/nsThebesDeviceContext.cpp
+++ b/gfx/src/thebes/nsThebesDeviceContext.cpp
@@ -52,34 +52,16 @@
 #include "nsThebesRenderingContext.h"
 #include "gfxUserFontSet.h"
 #include "gfxPlatform.h"
 
 #include "nsIWidget.h"
 #include "nsIView.h"
 #include "nsILookAndFeel.h"
 
-#ifdef MOZ_ENABLE_GTK2
-// for getenv
-#include <cstdlib>
-// for round
-#include <cmath>
-
-#include <gtk/gtk.h>
-#include <gdk/gdk.h>
-
-#include "nsFont.h"
-
-#include <pango/pango.h>
-#ifdef MOZ_X11
-#include <gdk/gdkx.h>
-#endif /* MOZ_X11 */
-#include <pango/pango-fontmap.h>
-#endif /* GTK2 */
-
 #include "gfxImageSurface.h"
 
 #ifdef MOZ_ENABLE_GTK2
 #include "nsSystemFontsGTK2.h"
 #include "gfxPDFSurface.h"
 #include "gfxPSSurface.h"
 static nsSystemFontsGTK2 *gSystemFonts = nsnull;
 #elif XP_WIN
@@ -104,25 +86,16 @@ static nsSystemFontsBeOS *gSystemFonts =
 static nsSystemFontsMac *gSystemFonts = nsnull;
 #elif defined(MOZ_WIDGET_QT)
 #include "nsSystemFontsQt.h"
 static nsSystemFontsQt *gSystemFonts = nsnull;
 #else
 #error Need to declare gSystemFonts!
 #endif
 
-#if defined(MOZ_ENABLE_GTK2) && defined(MOZ_X11)
-extern "C" {
-static int x11_error_handler (Display *dpy, XErrorEvent *err) {
-    NS_ASSERTION(PR_FALSE, "X Error");
-    return 0;
-}
-}
-#endif
-
 #ifdef PR_LOGGING
 PRLogModuleInfo* gThebesGFXLog = nsnull;
 #endif
 
 class nsFontCache
 {
 public:
     nsFontCache();
@@ -735,25 +708,16 @@ nsThebesDeviceContext::Init(nsIWidget *a
 #endif
 
     // register as a memory-pressure observer to free font resources
     // in low-memory situations.
     nsCOMPtr<nsIObserverService> obs(do_GetService("@mozilla.org/observer-service;1"));
     if (obs)
         obs->AddObserver(this, "memory-pressure", PR_TRUE);
 
-#if defined(MOZ_ENABLE_GTK2) && defined(MOZ_X11)
-    if (getenv ("MOZ_X_SYNC")) {
-        PR_LOG (gThebesGFXLog, PR_LOG_DEBUG, ("+++ Enabling XSynchronize\n"));
-        XSynchronize (gdk_x11_get_default_xdisplay(), True);
-        XSetErrorHandler(x11_error_handler);
-    }
-
-#endif
-
     mScreenManager = do_GetService("@mozilla.org/gfx/screenmanager;1");
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsThebesDeviceContext::CreateRenderingContext(nsIView *aView,
                                               nsIRenderingContext *&aContext)
--- a/toolkit/xre/Makefile.in
+++ b/toolkit/xre/Makefile.in
@@ -116,16 +116,20 @@ ifeq ($(MOZ_WIDGET_TOOLKIT),cocoa)
 CMMSRCS += MacLaunchHelper.mm
 CPPSRCS += nsCommandLineServiceMac.cpp
 OS_CXXFLAGS += -fexceptions
 endif
 ifeq ($(MOZ_WIDGET_TOOLKIT),cocoa)
 CMMSRCS += MacApplicationDelegate.mm
 endif
 
+ifdef MOZ_X11
+CPPSRCS += nsX11ErrorHandler.cpp
+endif
+
 SHARED_LIBRARY_LIBS += ../profile/src/$(LIB_PREFIX)profile_s.$(LIB_SUFFIX)
 
 ifdef MOZ_ENABLE_XREMOTE
 SHARED_LIBRARY_LIBS += $(DEPTH)/widget/src/xremoteclient/$(LIB_PREFIX)xremote_client_s.$(LIB_SUFFIX)
 LOCAL_INCLUDES += -I$(topsrcdir)/widget/src/xremoteclient
 endif
 
 ifdef MOZ_CRASHREPORTER
--- a/toolkit/xre/nsAppRunner.cpp
+++ b/toolkit/xre/nsAppRunner.cpp
@@ -235,16 +235,17 @@ public:
   }
 
 protected:
   HANDLE mHandle;
 };
 #endif
 
 extern void InstallSignalHandlers(const char *ProgramName);
+#include "nsX11ErrorHandler.h"
 
 #define FILE_COMPATIBILITY_INFO NS_LITERAL_CSTRING("compatibility.ini")
 
 int    gArgc;
 char **gArgv;
 
 static char gToolkitVersion[20];
 static char gToolkitBuildID[40];
@@ -3128,16 +3129,20 @@ XRE_main(int argc, char* argv[], const n
     _gtk_window_set_auto_startup_notification_fn _gtk_window_set_auto_startup_notification =
       (_gtk_window_set_auto_startup_notification_fn)FindFunction("gtk_window_set_auto_startup_notification");
     if (_gtk_window_set_auto_startup_notification) {
       _gtk_window_set_auto_startup_notification(PR_FALSE);
     }
 
     gtk_widget_set_default_colormap(gdk_rgb_get_colormap());
 #endif /* MOZ_WIDGET_GTK2 */
+#ifdef MOZ_X11
+    // Do this after initializing GDK, or GDK will install its own handler.
+    InstallX11ErrorHandler();
+#endif
 
     // Call the code to install our handler
 #ifdef MOZ_JPROF
     setupProfilingStuff();
 #endif
 
     // Try to allocate "native app support."
     nsCOMPtr<nsINativeAppSupport> nativeApp;
--- a/toolkit/xre/nsEmbedFunctions.cpp
+++ b/toolkit/xre/nsEmbedFunctions.cpp
@@ -71,16 +71,17 @@
 #include "nsStaticComponents.h"
 #include "nsString.h"
 #include "nsThreadUtils.h"
 #include "nsWidgetsCID.h"
 #include "nsXPFEComponentsCID.h"
 #include "nsXREDirProvider.h"
 
 #ifdef MOZ_IPC
+#include "nsX11ErrorHandler.h"
 #include "base/at_exit.h"
 #include "base/command_line.h"
 #include "base/message_loop.h"
 #include "base/process_util.h"
 #include "chrome/common/child_process.h"
 
 #include "mozilla/ipc/GeckoChildProcessHost.h"
 #include "mozilla/ipc/BrowserProcessSubThread.h"
@@ -485,10 +486,18 @@ XRE_ShutdownChildProcess()
   NS_ABORT_IF_FALSE(MessageLoopForUI::current(), "Wrong thread!");
 
   MessageLoop* ioLoop = XRE_GetIOMessageLoop();
   NS_ABORT_IF_FALSE(!!ioLoop, "Bad shutdown order");
 
   ioLoop->PostTask(FROM_HERE, new MessageLoop::QuitTask());
 }
 
+#ifdef MOZ_X11
+void
+XRE_InstallX11ErrorHandler()
+{
+  InstallX11ErrorHandler();
+}
+#endif
+
 #endif // MOZ_IPC
 
new file mode 100644
--- /dev/null
+++ b/toolkit/xre/nsX11ErrorHandler.cpp
@@ -0,0 +1,198 @@
+/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation <http://www.mozilla.org/>
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Karl Tomlinson <karlt+@karlt.net>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsX11ErrorHandler.h"
+
+#ifdef MOZ_IPC
+#include "mozilla/plugins/PluginThreadChild.h"
+using mozilla::plugins::PluginThreadChild;
+#endif
+
+#include "prenv.h"
+#include "nsXULAppAPI.h"
+#include "nsExceptionHandler.h"
+#include "nsDebug.h"
+
+#include <X11/Xlib.h>
+#ifdef MOZ_WIDGET_GTK2
+#include <gdk/gdkx.h>
+#endif
+
+#define BUFSIZE 2048 // What Xlib uses with XGetErrorDatabaseText
+
+extern "C" {
+static int
+IgnoreError(Display *display, XErrorEvent *event) {
+  return 0; // This return value is ignored.
+}
+
+static int
+X11Error(Display *display, XErrorEvent *event) {
+  nsCAutoString notes;
+  char buffer[BUFSIZE];
+
+  // Get an indication of how long ago the request that caused the error was
+  // made.  Do this before querying extensions etc below.
+  unsigned long age = NextRequest(display) - event->serial;
+
+  // Ignore subsequent errors, which may get processed during the extension
+  // queries below for example.
+  XSetErrorHandler(IgnoreError);
+
+  // Get a string to represent the request that caused the error.
+  nsCAutoString message;
+  if (event->request_code < 128) {
+    // Core protocol request
+    message.AppendInt(event->request_code);
+  } else {
+    // Extension request
+    int nExts;
+    char** extNames = XListExtensions(display, &nExts);
+    if (extNames) {
+      for (int i = 0; i < nExts; ++i) {
+        int major_opcode, first_event, first_error;
+        if (XQueryExtension(display, extNames[i],
+                            &major_opcode, &first_event, &first_error)
+            && major_opcode == event->request_code) {
+          message.Append(extNames[i]);
+          message.Append('.');
+          message.AppendInt(event->minor_code);
+          break;
+        }
+      }
+
+      XFreeExtensionList(extNames);
+    }
+  }
+
+  if (message.IsEmpty()) {
+    buffer[0] = '\0';
+  } else {
+    XGetErrorDatabaseText(display, "XRequest", message.get(), "",
+                          buffer, sizeof(buffer));
+  }
+
+  if (buffer[0]) {
+    notes.Append(buffer);
+  } else {
+    notes.Append("Request ");
+    notes.AppendInt(event->request_code);
+    notes.Append('.');
+    notes.AppendInt(event->minor_code);
+  }
+
+  notes.Append(": ");
+
+  // Get a string to describe the error.
+  XGetErrorText(display, event->error_code, buffer, sizeof(buffer));
+  notes.Append(buffer);
+
+  // For requests where Xlib gets the reply synchronously, |age| will be 1
+  // and the stack will include the function making the request.  For
+  // asynchronous requests, the current stack will often be unrelated to the
+  // point of making the request, even if |age| is 1, but sometimes this may
+  // help us count back to the point of the request.  With XSynchronize on,
+  // the stack will include the function making the request, even though
+  // |age| will be 2 for asynchronous requests because XSynchronize is
+  // implemented by an empty request from an XSync, which has not yet been
+  // processed.
+  if (age > 1) {
+    // XSynchronize returns the previous "after function".  If a second
+    // XSynchronize call returns the same function after an enable call then
+    // synchronization must have already been enabled.
+    if (XSynchronize(display, True) == XSynchronize(display, False)) {
+      notes.Append("; sync");
+    } else {
+      notes.Append("; ");
+      notes.AppendInt(age);
+      notes.Append(" requests ago");
+    }
+  }
+
+#ifdef MOZ_CRASHREPORTER
+  switch (XRE_GetProcessType()) {
+  case GeckoProcessType_Default:
+    CrashReporter::AppendAppNotesToCrashReport(notes);
+    break;
+#ifdef MOZ_IPC
+  case GeckoProcessType_Plugin:
+    if (CrashReporter::GetEnabled()) {
+      // This is assuming that X operations are performed on the plugin
+      // thread.  If plugins are using X on another thread, then we'll need to
+      // handle that differently.
+      PluginThreadChild::AppendNotesToCrashReport(notes);
+    }
+    break;
+#endif
+  default: 
+    ; // crash report notes not supported.
+  }
+#endif
+
+#ifdef DEBUG
+  // The resource id is unlikely to be useful in a crash report without
+  // context of other ids, but add it to the debug console output.
+  notes.Append("; id=0x");
+  notes.AppendInt(event->resourceid, 16);
+#ifdef MOZ_WIDGET_GTK2
+  // Actually, for requests where Xlib gets the reply synchronously,
+  // MOZ_X_SYNC=1 will not be necessary, but we don't have a table to tell us
+  // which requests get a synchronous reply.
+  if (!PR_GetEnv("MOZ_X_SYNC")) {
+    notes.Append("\nRe-running with MOZ_X_SYNC=1 in the environment may give a more helpful backtrace.");
+  }
+#endif
+#endif
+
+  NS_RUNTIMEABORT(notes.get());
+  return 0; // not reached
+}
+}
+
+void
+InstallX11ErrorHandler()
+{
+  XSetErrorHandler(X11Error);
+
+#ifdef MOZ_WIDGET_GTK2
+  NS_ASSERTION(GDK_DISPLAY(), "No GDK display");
+  if (PR_GetEnv("MOZ_X_SYNC")) {
+    XSynchronize(GDK_DISPLAY(), True);
+  }
+#endif
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/xre/nsX11ErrorHandler.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation <http://www.mozilla.org/>
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Karl Tomlinson <karlt+@karlt.net>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifdef MOZ_X11
+void InstallX11ErrorHandler();
+#endif
--- a/xpcom/build/nsXULAppAPI.h
+++ b/xpcom/build/nsXULAppAPI.h
@@ -501,9 +501,11 @@ struct JSString;
 
 XRE_API(bool,
         XRE_SendTestShellCommand, (JSContext* aCx,
                                    JSString* aCommand,
                                    void* aCallback))
 XRE_API(bool,
         XRE_ShutdownTestShell, ())
 
+XRE_API(void,
+        XRE_InstallX11ErrorHandler, ())
 #endif // _nsXULAppAPI_h__