summaryrefslogtreecommitdiff
path: root/chrome/content
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/content')
-rw-r--r--chrome/content/ctypes-utils.js192
-rw-r--r--chrome/content/gdk-pixbuf.js28
-rw-r--r--chrome/content/gdk.js64
-rw-r--r--chrome/content/glib.js35
-rw-r--r--chrome/content/gobject.js46
-rw-r--r--chrome/content/gtk.js85
-rw-r--r--chrome/content/log4moz.js698
-rw-r--r--chrome/content/overlay.js52
-rw-r--r--chrome/content/overlay.xul4
-rw-r--r--chrome/content/topmenu-client.js27
-rw-r--r--chrome/content/topmenuservice.js792
-rw-r--r--chrome/content/vkgdkmap.js27
12 files changed, 2050 insertions, 0 deletions
diff --git a/chrome/content/ctypes-utils.js b/chrome/content/ctypes-utils.js
new file mode 100644
index 0000000..9fd24ec
--- /dev/null
+++ b/chrome/content/ctypes-utils.js
@@ -0,0 +1,192 @@
+/* -*- Mode: javascript; tab-width: 2; 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 Firetray
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Messaging, Ltd.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Chris Coulson <chris.coulson@canonical.com>
+ *
+ * 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 ***** */
+
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/ctypes.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("chrome://topmenu/content/log4moz.js");
+
+var EXPORTED_SYMBOLS = [ "ctypes_library" ];
+
+let log = Log4Moz.repository.getLogger("topmenu.ctypes-utils");
+
+/**
+ * Loads a library using ctypes and exports an object on to the specified
+ * global object. The name of the exported object will be the first name
+ * specified in the global objects EXPORTED_SYMBOLS list.
+ *
+ * It is an error to call this function more than once in a JS module. This
+ * implies that you should have one JS module per ctypes library.
+ *
+ * In addition to native types and functions, the exported object will contain
+ * some additional utility functions:
+ *
+ * close Close the library and unload the JS module
+ * available Returns true if the library is available, false otherwise
+ * ABI A property containing the library ABI loaded (or -1 if unavailable)
+ *
+ * @param aName
+ * The name of the library to load, without the "lib" prefix or any
+ * file extension.
+ *
+ * @param aABIs
+ * An array of library ABI's to search for. The first one found will
+ * be loaded and the loaded ABI will be saved on the exported object.
+ *
+ * @param aDefines
+ * A function which will be called to load library symbols and create
+ * types. The function will be called with one parameter, which contains
+ * several functions for binding symbols. The "this" object will be
+ * the exported object, on to which you can should types and symbols.
+ *
+ * @param aGlobal
+ * The global object on to which we export an object. This must be a
+ * a valid JSM global object.
+ *
+ */
+function ctypes_library(aName, aABIs, aDefines, aGlobal) {
+ try {
+ log.debug("Trying to load library: " + aName);
+
+ if (typeof(aName) != "string") {
+ throw Error("Invalid library name");
+ }
+
+ if (!aABIs || typeof(aABIs) != "object") {
+ throw Error("Invalid range of library ABI's");
+ }
+
+ if (typeof(aDefines) != "function") {
+ throw Error("Invalid defines function");
+ }
+
+ if (!aGlobal || typeof(aGlobal) != "object" || !aGlobal.EXPORTED_SYMBOLS ||
+ typeof(aGlobal.EXPORTED_SYMBOLS) != "object") {
+ throw Error("Must specify a valid global object from a loaded JS module");
+ }
+
+ if (!("__URI__" in aGlobal) || !aGlobal.__URI__) {
+ throw Error("This JS module has already been unloaded");
+ }
+
+ if (aGlobal[aGlobal.EXPORTED_SYMBOLS[0]]) {
+ throw Error("Was ctypes_library() called more than once for this module?");
+ }
+
+ var library;
+ for each (let abi in aABIs) {
+ let soname = "lib" + aName + ".so." + abi.toString();
+ log.debug("Trying " + soname);
+ try {
+ library = ctypes.open(soname);
+ this.ABI = abi;
+ log.debug("Successfully loaded " + soname);
+ break;
+ } catch(e) {
+ log.error(soname+" unfound.");
+ }
+ }
+
+ this.name = aName;
+
+ this.close = function() {
+ log.debug("Closing library " + aName);
+ library.close();
+ this.ABI = -1;
+
+ if (!("__URI__" in aGlobal) || !aGlobal.__URI__) {
+ // We could have already been unloaded by now
+ return;
+ }
+
+ log.debug("Unloading JS module " + aGlobal.__URI__);
+ Cu.unload(aGlobal.__URI__);
+ };
+
+ this.available = function() {
+ return this.ABI != -1;
+ };
+
+ if (!library) {
+ log.debug("Failed to load library: " + aName);
+ this.ABI = -1;
+ return;
+ }
+
+ var self = this;
+ let lib = {
+ declare: function() {
+ try {
+ args = [];
+ args.push(arguments[0]);
+ args.push(ctypes.default_abi);
+ for each (let arg in Array.prototype.slice.call(arguments, 1)) {
+ args.push(arg);
+ }
+
+ return library.declare.apply(library, args);
+ } catch (ex) {
+ Cu.reportError(ex);
+ log.error("Missing symbol " + arguments[0] + " in library " + aName);
+ self.ABI = -1;
+ return null;
+ }
+ },
+
+ lazy_bind: function() {
+ var args = Array.prototype.slice.call(arguments, 0);
+ XPCOMUtils.defineLazyGetter(self, arguments[0], function() {
+ return lib.declare.apply(lib, args);
+ });
+ },
+
+ bind_now: function() {
+ self[arguments[0]] = this.declare.apply(this, arguments);
+ }
+ };
+
+ aDefines.call(this, lib);
+
+ aGlobal[aGlobal.EXPORTED_SYMBOLS[0]] = this;
+ } catch(e) {
+ Cu.reportError(e);
+ log.error(aName+" definition error: "+e);
+ this.ABI = -1;
+ }
+}
diff --git a/chrome/content/gdk-pixbuf.js b/chrome/content/gdk-pixbuf.js
new file mode 100644
index 0000000..3e58a4c
--- /dev/null
+++ b/chrome/content/gdk-pixbuf.js
@@ -0,0 +1,28 @@
+var EXPORTED_SYMBOLS = [ "gdk_pixbuf" ];
+
+const LIBNAME = "gdk_pixbuf_xlib-2.0";
+const ABIS = [ 0 ];
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/ctypes.jsm");
+Cu.import("chrome://topmenu/content/ctypes-utils.js");
+Cu.import("chrome://topmenu/content/glib.js");
+Cu.import("chrome://topmenu/content/gobject.js");
+
+function defines(lib) {
+ this.GdkPixbuf = gobject.GObject;
+ this.GdkPixbufDestroyNotify = ctypes.FunctionType(ctypes.default_abi,
+ ctypes.void_t,
+ [glib.guchar.ptr, glib.gpointer]);
+ this.GdkColorspace = glib.guint;
+ this.GDK_COLORSPACE_RGB = 0;
+
+ lib.lazy_bind("gdk_pixbuf_new", this.GdkPixbuf.ptr, this.GdkColorspace, glib.gboolean, glib.gint, glib.gint, glib.gint);
+ lib.lazy_bind("gdk_pixbuf_new_from_data", this.GdkPixbuf.ptr, glib.guchar.ptr, this.GdkColorspace, glib.gboolean, glib.gint, glib.gint, glib.gint, glib.gint, this.GdkPixbufDestroyNotify.ptr, glib.gpointer);
+ lib.lazy_bind("gdk_pixbuf_copy", this.GdkPixbuf.ptr, this.GdkPixbuf.ptr);
+}
+
+new ctypes_library(LIBNAME, ABIS, defines, this);
diff --git a/chrome/content/gdk.js b/chrome/content/gdk.js
new file mode 100644
index 0000000..25458ef
--- /dev/null
+++ b/chrome/content/gdk.js
@@ -0,0 +1,64 @@
+var EXPORTED_SYMBOLS = [ "gdk" ];
+
+const LIBNAME = "gdk-x11-2.0";
+const ABIS = [ 0 ];
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/ctypes.jsm");
+Cu.import("chrome://topmenu/content/ctypes-utils.js");
+Cu.import("chrome://topmenu/content/glib.js");
+Cu.import("chrome://topmenu/content/gobject.js");
+
+function defines(lib) {
+ this.GdkDrawable = gobject.GObject;
+ this.GdkWindow = this.GdkDrawable;
+
+ this.GdkModifierType = glib.guint;
+ this.GDK_SHIFT_MASK = 1 << 0;
+ this.GDK_LOCK_MASK = 1 << 1;
+ this.GDK_CONTROL_MASK = 1 << 2;
+ this.GDK_MOD1_MASK = 1 << 3;
+ this.GDK_ALT_MASK = this.GDK_MOD1_MASK;
+ this.GDK_MOD2_MASK = 1 << 4;
+ this.GDK_MOD3_MASK = 1 << 5;
+ this.GDK_SUPER_MASK = 1 << 26;
+ this.GDK_META_MASK = 1 << 28;
+
+ this.GDK_KEY_BackSpace = 0xff08;
+ this.GDK_KEY_Tab = 0xff09;
+ this.GDK_KEY_Return = 0xff0d;
+ this.GDK_KEY_Escape = 0xff1b;
+ this.GDK_KEY_Delete = 0xffff;
+ this.GDK_KEY_F1 = 0xffbe;
+ this.GDK_KEY_F2 = 0xffbf;
+ this.GDK_KEY_F3 = 0xffc0;
+ this.GDK_KEY_F4 = 0xffc1;
+ this.GDK_KEY_F5 = 0xffc2;
+ this.GDK_KEY_F6 = 0xffc3;
+ this.GDK_KEY_F7 = 0xffc4;
+ this.GDK_KEY_F8 = 0xffc5;
+ this.GDK_KEY_F9 = 0xffc6;
+ this.GDK_KEY_F10 = 0xffc7;
+ this.GDK_KEY_F11 = 0xffc8;
+ this.GDK_KEY_L1 = 0xffc8;
+ this.GDK_KEY_F12 = 0xffc9;
+ this.GDK_KEY_Home = 0xff50;
+ this.GDK_KEY_Left = 0xff51;
+ this.GDK_KEY_Up = 0xff52;
+ this.GDK_KEY_Right = 0xff53;
+ this.GDK_KEY_Down = 0xff54;
+ this.GDK_KEY_Prior = 0xff55;
+ this.GDK_KEY_Page_Up = 0xff55;
+ this.GDK_KEY_Next = 0xff56;
+ this.GDK_KEY_Page_Down = 0xff56;
+ this.GDK_KEY_End = 0xff57;
+
+ lib.lazy_bind("gdk_window_get_toplevel", this.GdkWindow.ptr, this.GdkWindow.ptr);
+
+ lib.lazy_bind("gdk_unicode_to_keyval", glib.guint, glib.guint32);
+}
+
+new ctypes_library(LIBNAME, ABIS, defines, this);
diff --git a/chrome/content/glib.js b/chrome/content/glib.js
new file mode 100644
index 0000000..fed4caf
--- /dev/null
+++ b/chrome/content/glib.js
@@ -0,0 +1,35 @@
+var EXPORTED_SYMBOLS = [ "glib" ];
+
+const LIBNAME = "glib-2.0";
+const ABIS = [ 0 ];
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/ctypes.jsm");
+Cu.import("chrome://topmenu/content/ctypes-utils.js");
+
+function defines(lib) {
+ this.gpointer = ctypes.voidptr_t;
+ this.gulong = ctypes.unsigned_long;
+ this.guint = ctypes.unsigned_int;
+ this.guint8 = ctypes.uint8_t;
+ this.guint16 = ctypes.uint16_t;
+ this.guint32 = ctypes.uint32_t;
+ this.gint = ctypes.int;
+ this.gint8 = ctypes.int8_t;
+ this.gint16 = ctypes.int16_t;
+ this.gint32 = ctypes.int32_t;
+ this.gchar = ctypes.char;
+ this.guchar = ctypes.unsigned_char;
+ this.gboolean = this.gint;
+ this.gfloat = ctypes.float;
+ this.gdouble = ctypes.double;
+ this.gsize = ctypes.unsigned_long;
+ this.GCallback = ctypes.voidptr_t;
+ this.GClosureNotify = this.gpointer;
+ this.GFunc = ctypes.void_t.ptr;
+};
+
+new ctypes_library(LIBNAME, ABIS, defines, this);
diff --git a/chrome/content/gobject.js b/chrome/content/gobject.js
new file mode 100644
index 0000000..92bd318
--- /dev/null
+++ b/chrome/content/gobject.js
@@ -0,0 +1,46 @@
+var EXPORTED_SYMBOLS = [ "gobject" ];
+
+const LIBNAME = "gobject-2.0";
+const ABIS = [ 0 ];
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/ctypes.jsm");
+Cu.import("chrome://topmenu/content/ctypes-utils.js");
+Cu.import("chrome://topmenu/content/glib.js");
+
+function defines(lib) {
+ this.GObject = ctypes.StructType("GObject");
+ this.GCallback = glib.gpointer;
+ this.GClosureNotify = glib.gpointer;
+ this.GConnectFlags = glib.guint;
+ this.G_CONNECT_AFTER = 1 << 0;
+ this.G_CONNECT_SWAPPED = 1 << 1;
+ this.GParamSpec = ctypes.StructType("GParamSpec");
+ this.GObjectNotifyCallback = ctypes.FunctionType(ctypes.default_abi,
+ ctypes.void_t,
+ [this.GObject.ptr, this.GParamSpec.ptr, glib.gpointer]);
+
+ lib.lazy_bind("g_object_unref", ctypes.void_t, this.GObject.ptr);
+ lib.lazy_bind("g_object_ref", this.GObject.ptr, this.GObject.ptr);
+ lib.lazy_bind("g_object_ref_sink", this.GObject.ptr, this.GObject.ptr);
+
+ lib.lazy_bind("g_signal_connect_data", glib.gulong, this.GObject.ptr, glib.gchar.ptr, this.GCallback, glib.gpointer, this.GClosureNotify, this.GConnectFlags);
+ lib.lazy_bind("g_signal_handler_disconnect", ctypes.void_t, this.GObject.ptr, glib.gulong);
+
+ this.ref = function(obj) {
+ this.g_object_ref(obj);
+ return ctypes.CDataFinalizer(obj, this.g_object_unref);
+ }
+ this.ref_sink = function(obj) {
+ this.g_object_ref_sink(obj);
+ return ctypes.CDataFinalizer(obj, this.g_object_unref);
+ }
+ this.signal_connect = function(obj, sign, callback, data) {
+ return this.g_signal_connect_data(obj, sign, callback, data, null, 0);
+ }
+}
+
+new ctypes_library(LIBNAME, ABIS, defines, this);
diff --git a/chrome/content/gtk.js b/chrome/content/gtk.js
new file mode 100644
index 0000000..e24f0d5
--- /dev/null
+++ b/chrome/content/gtk.js
@@ -0,0 +1,85 @@
+var EXPORTED_SYMBOLS = [ "gtk" ];
+
+const LIBNAME = "gtk-x11-2.0";
+const ABIS = [ 0 ];
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/ctypes.jsm");
+Cu.import("chrome://topmenu/content/ctypes-utils.js");
+Cu.import("chrome://topmenu/content/glib.js");
+Cu.import("chrome://topmenu/content/gobject.js");
+Cu.import("chrome://topmenu/content/gdk.js");
+Cu.import("chrome://topmenu/content/gdk-pixbuf.js");
+
+function defines(lib) {
+ this.GtkWidget = gobject.GObject;
+ this.GtkBin = this.GtkWidget;
+ this.GtkMenuShell = this.GtkWidget;
+ this.GtkMenu = this.GtkMenuShell;
+ this.GtkItem = this.GtkWidget;
+ this.GtkMenuItem = this.GtkItem;
+ this.GtkCheckMenuItem = this.GtkMenuItem;
+ this.GtkImageMenuItem = this.GtkMenuItem;
+ this.GtkImage = this.GtkWidget;
+
+ this.GtkAccelGroup = gobject.GObject;
+
+ this.GtkIconSize = glib.guint;
+ this.GTK_ICON_SIZE_INVALID = 0;
+ this.GTK_ICON_SIZE_MENU = 1;
+ this.GTK_ICON_SIZE_SMALL_TOOLBAR = 2;
+ this.GTK_ICON_SIZE_LARGE_TOOLBAR = 3;
+ this.GTK_ICON_SIZE_BUTTON = 4;
+ this.GTK_ICON_SIZE_DND = 5;
+ this.GTK_ICON_SIZE_DIALOG = 6;
+
+ this.GtkAccelFlags = glib.guint;
+ this.GTK_ACCEL_VISIBLE = 1 << 0;
+ this.GTK_ACCEL_LOCKED = 1 << 1;
+
+ this.GtkItemSelect = ctypes.FunctionType(ctypes.default_abi,
+ ctypes.void_t,
+ [this.GtkItem.ptr, glib.gpointer]);
+ this.GtkItemDeselect = this.GtkItemSelect;
+
+ this.GtkMenuItemActivate = ctypes.FunctionType(ctypes.default_abi,
+ ctypes.void_t,
+ [this.GtkMenuItem.ptr, glib.gpointer]);
+
+ lib.lazy_bind("gtk_widget_show", ctypes.void_t, this.GtkWidget.ptr);
+ lib.lazy_bind("gtk_widget_show_all", ctypes.void_t, this.GtkWidget.ptr);
+ lib.lazy_bind("gtk_widget_destroy", ctypes.void_t, this.GtkWidget.ptr);
+ lib.lazy_bind("gtk_widget_add_accelerator", ctypes.void_t, this.GtkWidget.ptr, glib.gchar.ptr, this.GtkAccelGroup.ptr, glib.guint, gdk.GdkModifierType, this.GtkAccelFlags);
+ lib.lazy_bind("gtk_widget_mnemonic_activate", ctypes.void_t, this.GtkWidget.ptr, glib.gboolean);
+ lib.lazy_bind("gtk_widget_set_sensitive", ctypes.void_t, this.GtkWidget.ptr, glib.gboolean);
+ lib.lazy_bind("gtk_widget_set_visible", ctypes.void_t, this.GtkWidget.ptr, glib.gboolean);
+
+ lib.lazy_bind("gtk_bin_get_child", this.GtkWidget.ptr, this.GtkBin.ptr);
+
+ lib.lazy_bind("gtk_menu_shell_insert", ctypes.void_t, this.GtkMenuShell.ptr, this.GtkWidget.ptr, glib.gint);
+ lib.lazy_bind("gtk_menu_shell_append", ctypes.void_t, this.GtkMenuShell.ptr, this.GtkWidget.ptr);
+
+ lib.lazy_bind("gtk_menu_new", this.GtkWidget.ptr);
+
+ lib.lazy_bind("gtk_menu_item_new", this.GtkWidget.ptr);
+ lib.lazy_bind("gtk_menu_item_new_with_mnemonic", this.GtkWidget.ptr, glib.gchar.ptr);
+ lib.lazy_bind("gtk_menu_item_set_label", ctypes.void_t, this.GtkMenuItem.ptr, glib.gchar.ptr);
+ lib.lazy_bind("gtk_menu_item_set_submenu", ctypes.void_t, this.GtkMenuItem.ptr, this.GtkWidget.ptr);
+ lib.lazy_bind("gtk_check_menu_item_new_with_mnemonic", this.GtkWidget.ptr, glib.gchar.ptr);
+ lib.lazy_bind("gtk_check_menu_item_set_active", ctypes.void_t, this.GtkImageMenuItem.ptr, glib.gboolean);
+ lib.lazy_bind("gtk_check_menu_item_set_draw_as_radio", ctypes.void_t, this.GtkImageMenuItem.ptr, glib.gboolean);
+ lib.lazy_bind("gtk_image_menu_item_new_from_stock", this.GtkWidget.ptr, glib.gchar.ptr, this.GtkAccelGroup.ptr);
+ lib.lazy_bind("gtk_image_menu_item_new_with_mnemonic", this.GtkWidget.ptr, glib.gchar.ptr);
+ lib.lazy_bind("gtk_image_menu_item_set_image", ctypes.void_t, this.GtkImageMenuItem.ptr, this.GtkWidget.ptr);
+ lib.lazy_bind("gtk_separator_menu_item_new", this.GtkWidget.ptr);
+
+ lib.lazy_bind("gtk_image_new_from_pixbuf", this.GtkWidget.ptr, gdk_pixbuf.GdkPixbuf.ptr);
+ lib.lazy_bind("gtk_image_new_from_stock", this.GtkWidget.ptr, glib.gchar.ptr, this.GtkIconSize);
+
+ lib.lazy_bind("gtk_accel_group_new", this.GtkAccelGroup.ptr);
+}
+
+new ctypes_library(LIBNAME, ABIS, defines, this);
diff --git a/chrome/content/log4moz.js b/chrome/content/log4moz.js
new file mode 100644
index 0000000..5e12938
--- /dev/null
+++ b/chrome/content/log4moz.js
@@ -0,0 +1,698 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+this.EXPORTED_SYMBOLS = ['Log4Moz'];
+
+const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
+
+const ONE_BYTE = 1;
+const ONE_KILOBYTE = 1024 * ONE_BYTE;
+const ONE_MEGABYTE = 1024 * ONE_KILOBYTE;
+
+const STREAM_SEGMENT_SIZE = 4096;
+const PR_UINT32_MAX = 0xffffffff;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "OS",
+ "resource://gre/modules/osfile.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+
+this.Log4Moz = {
+ Level: {
+ Fatal: 70,
+ Error: 60,
+ Warn: 50,
+ Info: 40,
+ Config: 30,
+ Debug: 20,
+ Trace: 10,
+ All: 0,
+ Desc: {
+ 70: "FATAL",
+ 60: "ERROR",
+ 50: "WARN",
+ 40: "INFO",
+ 30: "CONFIG",
+ 20: "DEBUG",
+ 10: "TRACE",
+ 0: "ALL"
+ },
+ Numbers: {
+ "FATAL": 70,
+ "ERROR": 60,
+ "WARN": 50,
+ "INFO": 40,
+ "CONFIG": 30,
+ "DEBUG": 20,
+ "TRACE": 10,
+ "ALL": 0,
+ }
+ },
+
+ get repository() {
+ delete Log4Moz.repository;
+ Log4Moz.repository = new LoggerRepository();
+ return Log4Moz.repository;
+ },
+ set repository(value) {
+ delete Log4Moz.repository;
+ Log4Moz.repository = value;
+ },
+
+ LogMessage: LogMessage,
+ Logger: Logger,
+ LoggerRepository: LoggerRepository,
+
+ Formatter: Formatter,
+ BasicFormatter: BasicFormatter,
+ StructuredFormatter: StructuredFormatter,
+
+ Appender: Appender,
+ DumpAppender: DumpAppender,
+ ConsoleAppender: ConsoleAppender,
+ StorageStreamAppender: StorageStreamAppender,
+
+ FileAppender: FileAppender,
+ BoundedFileAppender: BoundedFileAppender,
+
+ // Logging helper:
+ // let logger = Log4Moz.repository.getLogger("foo");
+ // logger.info(Log4Moz.enumerateInterfaces(someObject).join(","));
+ enumerateInterfaces: function Log4Moz_enumerateInterfaces(aObject) {
+ let interfaces = [];
+
+ for (i in Ci) {
+ try {
+ aObject.QueryInterface(Ci[i]);
+ interfaces.push(i);
+ }
+ catch(ex) {}
+ }
+
+ return interfaces;
+ },
+
+ // Logging helper:
+ // let logger = Log4Moz.repository.getLogger("foo");
+ // logger.info(Log4Moz.enumerateProperties(someObject).join(","));
+ enumerateProperties: function Log4Moz_enumerateProps(aObject,
+ aExcludeComplexTypes) {
+ let properties = [];
+
+ for (p in aObject) {
+ try {
+ if (aExcludeComplexTypes &&
+ (typeof aObject[p] == "object" || typeof aObject[p] == "function"))
+ continue;
+ properties.push(p + " = " + aObject[p]);
+ }
+ catch(ex) {
+ properties.push(p + " = " + ex);
+ }
+ }
+
+ return properties;
+ }
+};
+
+
+/*
+ * LogMessage
+ * Encapsulates a single log event's data
+ */
+function LogMessage(loggerName, level, message, params) {
+ this.loggerName = loggerName;
+ this.level = level;
+ this.message = message;
+ this.params = params;
+
+ // The _structured field will correspond to whether this message is to
+ // be interpreted as a structured message.
+ this._structured = this.params && this.params.action;
+ this.time = Date.now();
+}
+LogMessage.prototype = {
+ get levelDesc() {
+ if (this.level in Log4Moz.Level.Desc)
+ return Log4Moz.Level.Desc[this.level];
+ return "UNKNOWN";
+ },
+
+ toString: function LogMsg_toString(){
+ let msg = "LogMessage [" + this.time + " " + this.level + " " +
+ this.message;
+ if (this.params) {
+ msg += " " + JSON.stringify(this.params);
+ }
+ return msg + "]"
+ }
+};
+
+/*
+ * Logger
+ * Hierarchical version. Logs to all appenders, assigned or inherited
+ */
+
+function Logger(name, repository) {
+ if (!repository)
+ repository = Log4Moz.repository;
+ this._name = name;
+ this.children = [];
+ this.ownAppenders = [];
+ this.appenders = [];
+ this._repository = repository;
+}
+Logger.prototype = {
+ get name() {
+ return this._name;
+ },
+
+ _level: null,
+ get level() {
+ if (this._level != null)
+ return this._level;
+ if (this.parent)
+ return this.parent.level;
+ dump("log4moz warning: root logger configuration error: no level defined\n");
+ return Log4Moz.Level.All;
+ },
+ set level(level) {
+ this._level = level;
+ },
+
+ _parent: null,
+ get parent() this._parent,
+ set parent(parent) {
+ if (this._parent == parent) {
+ return;
+ }
+ // Remove ourselves from parent's children
+ if (this._parent) {
+ let index = this._parent.children.indexOf(this);
+ if (index != -1) {
+ this._parent.children.splice(index, 1);
+ }
+ }
+ this._parent = parent;
+ parent.children.push(this);
+ this.updateAppenders();
+ },
+
+ updateAppenders: function updateAppenders() {
+ if (this._parent) {
+ let notOwnAppenders = this._parent.appenders.filter(function(appender) {
+ return this.ownAppenders.indexOf(appender) == -1;
+ }, this);
+ this.appenders = notOwnAppenders.concat(this.ownAppenders);
+ } else {
+ this.appenders = this.ownAppenders.slice();
+ }
+
+ // Update children's appenders.
+ for (let i = 0; i < this.children.length; i++) {
+ this.children[i].updateAppenders();
+ }
+ },
+
+ addAppender: function Logger_addAppender(appender) {
+ if (this.ownAppenders.indexOf(appender) != -1) {
+ return;
+ }
+ this.ownAppenders.push(appender);
+ this.updateAppenders();
+ },
+
+ removeAppender: function Logger_removeAppender(appender) {
+ let index = this.ownAppenders.indexOf(appender);
+ if (index == -1) {
+ return;
+ }
+ this.ownAppenders.splice(index, 1);
+ this.updateAppenders();
+ },
+
+ /**
+ * Logs a structured message object.
+ *
+ * @param action
+ * (string) A message action, one of a set of actions known to the
+ * log consumer.
+ * @param params
+ * (object) Parameters to be included in the message.
+ * If _level is included as a key and the corresponding value
+ * is a number or known level name, the message will be logged
+ * at the indicated level.
+ */
+ logStructured: function (action, params) {
+ if (!action) {
+ throw "An action is required when logging a structured message.";
+ }
+ if (!params) {
+ return this.log(this.level, undefined, {"action": action});
+ }
+ if (typeof params != "object") {
+ throw "The params argument is required to be an object.";
+ }
+
+ let level = params._level || this.level;
+ if ((typeof level == "string") && level in Log4Moz.Level.Numbers) {
+ level = Log4Moz.Level.Numbers[level];
+ }
+
+ params.action = action;
+ this.log(level, params._message, params);
+ },
+
+ log: function (level, string, params) {
+ if (this.level > level)
+ return;
+
+ // Hold off on creating the message object until we actually have
+ // an appender that's responsible.
+ let message;
+ let appenders = this.appenders;
+ for (let appender of appenders) {
+ if (appender.level > level) {
+ continue;
+ }
+ if (!message) {
+ message = new LogMessage(this._name, level, string, params);
+ }
+ appender.append(message);
+ }
+ },
+
+ fatal: function (string, params) {
+ this.log(Log4Moz.Level.Fatal, string, params);
+ },
+ error: function (string, params) {
+ this.log(Log4Moz.Level.Error, string, params);
+ },
+ warn: function (string, params) {
+ this.log(Log4Moz.Level.Warn, string, params);
+ },
+ info: function (string, params) {
+ this.log(Log4Moz.Level.Info, string, params);
+ },
+ config: function (string, params) {
+ this.log(Log4Moz.Level.Config, string, params);
+ },
+ debug: function (string, params) {
+ this.log(Log4Moz.Level.Debug, string, params);
+ },
+ trace: function (string, params) {
+ this.log(Log4Moz.Level.Trace, string, params);
+ }
+};
+
+/*
+ * LoggerRepository
+ * Implements a hierarchy of Loggers
+ */
+
+function LoggerRepository() {}
+LoggerRepository.prototype = {
+ _loggers: {},
+
+ _rootLogger: null,
+ get rootLogger() {
+ if (!this._rootLogger) {
+ this._rootLogger = new Logger("root", this);
+ this._rootLogger.level = Log4Moz.Level.All;
+ }
+ return this._rootLogger;
+ },
+ set rootLogger(logger) {
+ throw "Cannot change the root logger";
+ },
+
+ _updateParents: function LogRep__updateParents(name) {
+ let pieces = name.split('.');
+ let cur, parent;
+
+ // find the closest parent
+ // don't test for the logger name itself, as there's a chance it's already
+ // there in this._loggers
+ for (let i = 0; i < pieces.length - 1; i++) {
+ if (cur)
+ cur += '.' + pieces[i];
+ else
+ cur = pieces[i];
+ if (cur in this._loggers)
+ parent = cur;
+ }
+
+ // if we didn't assign a parent above, there is no parent
+ if (!parent)
+ this._loggers[name].parent = this.rootLogger;
+ else
+ this._loggers[name].parent = this._loggers[parent];
+
+ // trigger updates for any possible descendants of this logger
+ for (let logger in this._loggers) {
+ if (logger != name && logger.indexOf(name) == 0)
+ this._updateParents(logger);
+ }
+ },
+
+ getLogger: function LogRep_getLogger(name) {
+ if (name in this._loggers)
+ return this._loggers[name];
+ this._loggers[name] = new Logger(name, this);
+ this._updateParents(name);
+ return this._loggers[name];
+ }
+};
+
+/*
+ * Formatters
+ * These massage a LogMessage into whatever output is desired.
+ * BasicFormatter and StructuredFormatter are implemented here.
+ */
+
+// Abstract formatter
+function Formatter() {}
+Formatter.prototype = {
+ format: function Formatter_format(message) {}
+};
+
+// Basic formatter that doesn't do anything fancy.
+function BasicFormatter(dateFormat) {
+ if (dateFormat)
+ this.dateFormat = dateFormat;
+}
+BasicFormatter.prototype = {
+ __proto__: Formatter.prototype,
+
+ format: function BF_format(message) {
+ return message.time + "\t" +
+ message.loggerName + "\t" +
+ message.levelDesc + "\t" +
+ message.message + "\n";
+ }
+};
+
+// Structured formatter that outputs JSON based on message data.
+// This formatter will format unstructured messages by supplying
+// default values.
+function StructuredFormatter() { }
+StructuredFormatter.prototype = {
+ __proto__: Formatter.prototype,
+
+ format: function (logMessage) {
+ let output = {
+ _time: logMessage.time,
+ _namespace: logMessage.loggerName,
+ _level: logMessage.levelDesc
+ };
+
+ for (let key in logMessage.params) {
+ output[key] = logMessage.params[key];
+ }
+
+ if (!output.action) {
+ output.action = "UNKNOWN";
+ }
+
+ if (!output._message && logMessage.message) {
+ output._message = logMessage.message;
+ }
+
+ return JSON.stringify(output);
+ }
+}
+
+/*
+ * Appenders
+ * These can be attached to Loggers to log to different places
+ * Simply subclass and override doAppend to implement a new one
+ */
+
+function Appender(formatter) {
+ this._name = "Appender";
+ this._formatter = formatter? formatter : new BasicFormatter();
+}
+Appender.prototype = {
+ level: Log4Moz.Level.All,
+
+ append: function App_append(message) {
+ if (message) {
+ this.doAppend(this._formatter.format(message));
+ }
+ },
+ toString: function App_toString() {
+ return this._name + " [level=" + this._level +
+ ", formatter=" + this._formatter + "]";
+ },
+ doAppend: function App_doAppend(message) {}
+};
+
+/*
+ * DumpAppender
+ * Logs to standard out
+ */
+
+function DumpAppender(formatter) {
+ this._name = "DumpAppender";
+ Appender.call(this, formatter);
+}
+DumpAppender.prototype = {
+ __proto__: Appender.prototype,
+
+ doAppend: function DApp_doAppend(message) {
+ dump(message);
+ }
+};
+
+/*
+ * ConsoleAppender
+ * Logs to the javascript console
+ */
+
+function ConsoleAppender(formatter) {
+ this._name = "ConsoleAppender";
+ Appender.call(this, formatter);
+}
+ConsoleAppender.prototype = {
+ __proto__: Appender.prototype,
+
+ doAppend: function CApp_doAppend(message) {
+ if (message.level > Log4Moz.Level.Warn) {
+ Cu.reportError(message);
+ return;
+ }
+ Cc["@mozilla.org/consoleservice;1"].
+ getService(Ci.nsIConsoleService).logStringMessage(message);
+ }
+};
+
+/**
+ * Append to an nsIStorageStream
+ *
+ * This writes logging output to an in-memory stream which can later be read
+ * back as an nsIInputStream. It can be used to avoid expensive I/O operations
+ * during logging. Instead, one can periodically consume the input stream and
+ * e.g. write it to disk asynchronously.
+ */
+function StorageStreamAppender(formatter) {
+ this._name = "StorageStreamAppender";
+ Appender.call(this, formatter);
+}
+
+StorageStreamAppender.prototype = {
+ __proto__: Appender.prototype,
+
+ _converterStream: null, // holds the nsIConverterOutputStream
+ _outputStream: null, // holds the underlying nsIOutputStream
+
+ _ss: null,
+
+ get outputStream() {
+ if (!this._outputStream) {
+ // First create a raw stream. We can bail out early if that fails.
+ this._outputStream = this.newOutputStream();
+ if (!this._outputStream) {
+ return null;
+ }
+
+ // Wrap the raw stream in an nsIConverterOutputStream. We can reuse
+ // the instance if we already have one.
+ if (!this._converterStream) {
+ this._converterStream = Cc["@mozilla.org/intl/converter-output-stream;1"]
+ .createInstance(Ci.nsIConverterOutputStream);
+ }
+ this._converterStream.init(
+ this._outputStream, "UTF-8", STREAM_SEGMENT_SIZE,
+ Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
+ }
+ return this._converterStream;
+ },
+
+ newOutputStream: function newOutputStream() {
+ let ss = this._ss = Cc["@mozilla.org/storagestream;1"]
+ .createInstance(Ci.nsIStorageStream);
+ ss.init(STREAM_SEGMENT_SIZE, PR_UINT32_MAX, null);
+ return ss.getOutputStream(0);
+ },
+
+ getInputStream: function getInputStream() {
+ if (!this._ss) {
+ return null;
+ }
+ return this._ss.newInputStream(0);
+ },
+
+ reset: function reset() {
+ if (!this._outputStream) {
+ return;
+ }
+ this.outputStream.close();
+ this._outputStream = null;
+ this._ss = null;
+ },
+
+ doAppend: function (message) {
+ if (!message) {
+ return;
+ }
+ try {
+ this.outputStream.writeString(message);
+ } catch(ex) {
+ if (ex.result == Cr.NS_BASE_STREAM_CLOSED) {
+ // The underlying output stream is closed, so let's open a new one
+ // and try again.
+ this._outputStream = null;
+ } try {
+ this.outputStream.writeString(message);
+ } catch (ex) {
+ // Ah well, we tried, but something seems to be hosed permanently.
+ }
+ }
+ }
+};
+
+/**
+ * File appender
+ *
+ * Writes output to file using OS.File.
+ */
+function FileAppender(path, formatter) {
+ this._name = "FileAppender";
+ this._encoder = new TextEncoder();
+ this._path = path;
+ this._file = null;
+ this._fileReadyPromise = null;
+
+ // This is a promise exposed for testing/debugging the logger itself.
+ this._lastWritePromise = null;
+ Appender.call(this, formatter);
+}
+
+FileAppender.prototype = {
+ __proto__: Appender.prototype,
+
+ _openFile: function () {
+ return Task.spawn(function _openFile() {
+ try {
+ this._file = yield OS.File.open(this._path,
+ {truncate: true});
+ } catch (err) {
+ if (err instanceof OS.File.Error) {
+ this._file = null;
+ } else {
+ throw err;
+ }
+ }
+ }.bind(this));
+ },
+
+ _getFile: function() {
+ if (!this._fileReadyPromise) {
+ this._fileReadyPromise = this._openFile();
+ return this._fileReadyPromise;
+ }
+
+ return this._fileReadyPromise.then(_ => {
+ if (!this._file) {
+ return this._openFile();
+ }
+ });
+ },
+
+ doAppend: function (message) {
+ let array = this._encoder.encode(message);
+ if (this._file) {
+ this._lastWritePromise = this._file.write(array);
+ } else {
+ this._lastWritePromise = this._getFile().then(_ => {
+ this._fileReadyPromise = null;
+ if (this._file) {
+ return this._file.write(array);
+ }
+ });
+ }
+ },
+
+ reset: function () {
+ let fileClosePromise = this._file.close();
+ return fileClosePromise.then(_ => {
+ this._file = null;
+ return OS.File.remove(this._path);
+ });
+ }
+};
+
+/**
+ * Bounded File appender
+ *
+ * Writes output to file using OS.File. After the total message size
+ * (as defined by message.length) exceeds maxSize, existing messages
+ * will be discarded, and subsequent writes will be appended to a new log file.
+ */
+function BoundedFileAppender(path, formatter, maxSize=2*ONE_MEGABYTE) {
+ this._name = "BoundedFileAppender";
+ this._size = 0;
+ this._maxSize = maxSize;
+ this._closeFilePromise = null;
+ FileAppender.call(this, path, formatter);
+}
+
+BoundedFileAppender.prototype = {
+ __proto__: FileAppender.prototype,
+
+ doAppend: function (message) {
+ if (!this._removeFilePromise) {
+ if (this._size < this._maxSize) {
+ this._size += message.length;
+ return FileAppender.prototype.doAppend.call(this, message);
+ }
+ this._removeFilePromise = this.reset();
+ }
+ this._removeFilePromise.then(_ => {
+ this._removeFilePromise = null;
+ this.doAppend(message);
+ });
+ },
+
+ reset: function () {
+ let fileClosePromise;
+ if (this._fileReadyPromise) {
+ // An attempt to open the file may still be in progress.
+ fileClosePromise = this._fileReadyPromise.then(_ => {
+ return this._file.close();
+ });
+ } else {
+ fileClosePromise = this._file.close();
+ }
+
+ return fileClosePromise.then(_ => {
+ this._size = 0;
+ this._file = null;
+ return OS.File.remove(this._path);
+ });
+ }
+};
+
diff --git a/chrome/content/overlay.js b/chrome/content/overlay.js
new file mode 100644
index 0000000..29b9a07
--- /dev/null
+++ b/chrome/content/overlay.js
@@ -0,0 +1,52 @@
+"use strict";
+
+var topmenu = {
+ logger : null,
+ proxy : null,
+
+ setupLogging: function() {
+ Components.utils.import("chrome://topmenu/content/log4moz.js", topmenu);
+ var Log4Moz = this.Log4Moz;
+ var formatter = new Log4Moz.BasicFormatter();
+ var root = Log4Moz.repository.rootLogger;
+ root.level = Log4Moz.Level.Warn;
+
+ var capp = new Log4Moz.ConsoleAppender(formatter);
+ capp.level = Log4Moz.Level.Warn;
+ root.addAppender(capp);
+
+ /*
+ var dapp = new Log4Moz.DumpAppender(formatter);
+ dapp.level = Log4Moz.Level.Debug;
+ root.addAppender(dapp);
+ */
+
+ this.logger = Log4Moz.repository.getLogger("topmenu");
+ },
+
+ setupMenuProxy: function() {
+ Components.utils.import("chrome://topmenu/content/topmenuservice.js", topmenu);
+ this.proxy = topmenu.TopMenuService.createWindowProxy(window);
+ },
+
+ dispose: function() {
+ if (this.proxy) {
+ this.proxy.dispose();
+ this.proxy = null;
+ }
+ },
+
+ onLoad: function() {
+ window.removeEventListener('load', topmenu.onLoad);
+ window.addEventListener('unload', topmenu.onUnload);
+ topmenu.setupLogging();
+ topmenu.setupMenuProxy();
+ },
+
+ onUnload: function() {
+ window.removeEventListener('unload', topmenu.onUnload);
+ topmenu.dispose();
+ },
+}
+
+window.addEventListener('load', topmenu.onLoad);
diff --git a/chrome/content/overlay.xul b/chrome/content/overlay.xul
new file mode 100644
index 0000000..3e61323
--- /dev/null
+++ b/chrome/content/overlay.xul
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<overlay id="topmenu-overlay" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/x-javascript" src="chrome://topmenu/content/overlay.js"></script>
+</overlay>
diff --git a/chrome/content/topmenu-client.js b/chrome/content/topmenu-client.js
new file mode 100644
index 0000000..a5d7ab2
--- /dev/null
+++ b/chrome/content/topmenu-client.js
@@ -0,0 +1,27 @@
+var EXPORTED_SYMBOLS = [ "topmenu_client" ];
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/ctypes.jsm");
+Cu.import("chrome://topmenu/content/ctypes-utils.js");
+Cu.import("chrome://topmenu/content/glib.js");
+Cu.import("chrome://topmenu/content/gobject.js");
+Cu.import("chrome://topmenu/content/gdk.js");
+Cu.import("chrome://topmenu/content/gtk.js");
+
+function defines(lib) {
+ lib.lazy_bind("topmenu_client_connect_window_widget", ctypes.void_t, gdk.GdkWindow.ptr, gtk.GtkWidget.ptr);
+ lib.lazy_bind("topmenu_client_disconnect_window", ctypes.void_t, gdk.GdkWindow.ptr);
+
+ this.TopMenuAppMenuBar = gobject.GObject;
+ lib.lazy_bind("topmenu_app_menu_bar_new", this.TopMenuAppMenuBar.ptr);
+ lib.lazy_bind("topmenu_app_menu_bar_set_app_menu", ctypes.void_t, this.TopMenuAppMenuBar.ptr, gtk.GtkWidget.ptr);
+
+ this.TopMenuMonitor = gobject.GObject;
+ lib.lazy_bind("topmenu_monitor_get_instance", this.TopMenuMonitor.ptr);
+ lib.lazy_bind("topmenu_monitor_is_topmenu_available", glib.gboolean, this.TopMenuMonitor.ptr);
+}
+
+new ctypes_library("topmenu-client", [ 0 ], defines, this);
diff --git a/chrome/content/topmenuservice.js b/chrome/content/topmenuservice.js
new file mode 100644
index 0000000..ffafba8
--- /dev/null
+++ b/chrome/content/topmenuservice.js
@@ -0,0 +1,792 @@
+var EXPORTED_SYMBOLS = ["TopMenuService"];
+
+var Cu = Components.utils;
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/ctypes.jsm");
+Cu.import("chrome://topmenu/content/log4moz.js");
+Cu.import("chrome://topmenu/content/glib.js");
+Cu.import("chrome://topmenu/content/gobject.js");
+Cu.import("chrome://topmenu/content/gdk.js");
+Cu.import("chrome://topmenu/content/gdk-pixbuf.js");
+Cu.import("chrome://topmenu/content/gtk.js");
+Cu.import("chrome://topmenu/content/topmenu-client.js");
+Cu.import("chrome://topmenu/content/vkgdkmap.js");
+
+var log = Log4Moz.repository.getLogger("topmenu.TopMenuService");
+
+/* Escape a string so that it is a valid regular expression literal. */
+function escapePreg(s) {
+ if (!s) return "";
+ var regex = new RegExp('[.\\\\+*?\\[\\^\\]$(){}=!<>|:\\-]', 'g');
+ return s.replace(regex, "\\$&");
+}
+
+function getBaseWindowInterface(w) {
+ return w.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem)
+ .treeOwner
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIBaseWindow);
+}
+
+function getTopLevelGdkWindow(w) {
+ var baseWindow = getBaseWindowInterface(w);
+ var handle = baseWindow.nativeHandle;
+ var gdkWindow = new gdk.GdkWindow.ptr(ctypes.UInt64(handle));
+ return gdk.gdk_window_get_toplevel(gdkWindow);
+}
+
+function createMutationObserver(window, proxy)
+{
+ return new window.MutationObserver(function(mutations) {
+ mutations.forEach(function(mutation) {
+ proxy.handleMutation(mutation);
+ });
+ });
+}
+
+function WindowProxy(window) {
+ this.srcWindow = window;
+ this.srcMenuBar = null;
+ this.observer = null;
+ this.appMenuBar = null;
+ this.gdkWindow = getTopLevelGdkWindow(window);
+ this.accelGroup = null;
+
+ this.flags = {};
+ this.updateFlags();
+
+ var self = this;
+ this.lastCallbackId = 0;
+ this.callbackItems = {};
+ this.callbacks = {
+ activate : gtk.GtkMenuItemActivate.ptr(function (gtkItem, data) {
+ try {
+ var id = ctypes.cast(data, ctypes.uintptr_t).value;
+ var item = self.callbackItems[id];
+ self.activateItem(item);
+ } catch (ex) {
+ Cu.reportError(ex);
+ log.error("Exception in callback: " + ex.toString());
+ }
+ }),
+ select : gtk.GtkItemSelect.ptr(function (gtkItem, data) {
+ try {
+ var id = ctypes.cast(data, ctypes.uintptr_t).value;
+ var item = self.callbackItems[id];
+ self.selectItem(item);
+ } catch (ex) {
+ Cu.reportError(ex);
+ log.error("Exception in callback: " + ex.toString());
+ }
+ }),
+ deselect : gtk.GtkItemDeselect.ptr(function (gtkItem, data) {
+ try {
+ var id = ctypes.cast(data, ctypes.uintptr_t).value;
+ var item = self.callbackItems[id];
+ self.deselectItem(item);
+ } catch (ex) {
+ Cu.reportError(ex);
+ log.error("Exception in callback: " + ex.toString());
+ }
+ }),
+ monitor_available : gobject.GObjectNotifyCallback.ptr(function (obj, pspec, data) {
+ self.updateMenuBarVisibility();
+ }),
+ keypress : function(e) {
+ try {
+ self.onKeyPress(e);
+ } catch (ex) {
+ Cu.reportError(ex);
+ log.error("Exception in callback: " + ex.toString());
+ }
+ }
+ }
+
+ this.accelGroup = gobject.ref_sink(gtk.gtk_accel_group_new());
+
+ this.appMenuBar = gobject.ref_sink(topmenu_client.topmenu_app_menu_bar_new());
+ gtk.gtk_widget_show(this.appMenuBar);
+
+ topmenu_client.topmenu_client_connect_window_widget(this.gdkWindow, this.appMenuBar);
+
+ var menubars = window.document.getElementsByTagNameNS('http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul', 'menubar');
+ if (menubars.length > 0) {
+ this.srcMenuBar = menubars[0];
+ this.observer = createMutationObserver(window, this);
+ this.observer.observe(this.srcMenuBar, {
+ childList: true,
+ attributes: true,
+ subtree: true
+ });
+
+ // Let's find currently added menu items and proxy them
+ this.srcMenuBar.topmenuData = {
+ gtkMenu: this.appMenuBar
+ }
+
+ for (var item = this.srcMenuBar.firstChild; item; item = item.nextSibling) {
+ this.addItem(item, this.srcMenuBar, -1);
+ }
+
+ this.appMenu = this.buildAppMenu();
+ if (this.appMenu) {
+ topmenu_client.topmenu_app_menu_bar_set_app_menu(this.appMenuBar, this.appMenu);
+ }
+
+ window.document.addEventListener("keypress", this.callbacks.keypress);
+ }
+
+ this.monitor = topmenu_client.topmenu_monitor_get_instance();
+ this.monitorConnectionId = gobject.signal_connect(this.monitor, "notify::available",
+ this.callbacks.monitor_available, null);
+ this.updateMenuBarVisibility();
+}
+
+WindowProxy.prototype.updateMenuBarVisibility = function() {
+ var topmenuAvailable = topmenu_client.topmenu_monitor_is_topmenu_available(this.monitor);
+ if (this.srcMenuBar) {
+ this.srcMenuBar.parentNode.parentNode.hidden = topmenuAvailable;
+ }
+}
+
+WindowProxy.prototype.buildAppMenuItem = function(itemId, stockId)
+{
+ var item = this.srcWindow.document.getElementById(itemId);
+
+ if (item) {
+ var gtkItem = gtk.gtk_image_menu_item_new_from_stock(stockId, null);
+ gtk.gtk_widget_show(gtkItem);
+
+
+ var callbackId = this.lastCallbackId++;
+ this.callbackItems[callbackId] = item;
+
+ gobject.signal_connect(gtkItem, 'activate',
+ this.callbacks.activate,
+ glib.gpointer(callbackId));
+
+ return gtkItem;
+ } else {
+ return null;
+ }
+}
+
+WindowProxy.prototype.buildAppMenu = function() {
+ var menu = gobject.ref_sink(gtk.gtk_menu_new());
+ var item;
+
+ item = this.buildAppMenuItem('aboutName', 'gtk-about');
+ if (item) {
+ gtk.gtk_menu_shell_append(menu, item);
+ }
+
+ item = this.buildAppMenuItem('menu_preferences', 'gtk-preferences');
+ if (item) {
+ gtk.gtk_menu_shell_append(menu, item);
+ }
+
+ item = gtk.gtk_separator_menu_item_new();
+ gtk.gtk_widget_show(item);
+ gtk.gtk_menu_shell_append(menu, item);
+
+ item = this.buildAppMenuItem('menu_FileQuitItem', 'gtk-quit');
+ if (item) {
+ gtk.gtk_menu_shell_append(menu, item);
+ }
+
+ return menu;
+}
+
+WindowProxy.prototype.handleMutation = function(mutation) {
+ var target = mutation.target;
+
+ switch (mutation.type) {
+ case 'childList':
+ switch (target.tagName) {
+ case 'menupopup':
+ // Get the parent menu for this menupopup:
+ target = mutation.target.parentNode;
+
+ if ('topmenuData' in target) {
+ this.updateMenu(target, mutation.previousSibling, mutation.removedNodes, mutation.addedNodes);
+ } else {
+ log.warn("I do not know about this menupoup");
+ }
+ break;
+ case 'menubar':
+ // Changes in the root menu bar.
+ if ('topmenuData' in target) {
+ this.updateMenu(target, mutation.previousSibling, mutation.removedNodes, mutation.addedNodes);
+ } else {
+ log.warn("I do not know about this menubar");
+ }
+ }
+ break;
+ case 'attributes':
+ switch (target.tagName) {
+ case 'menupopup':
+ case 'menuitem':
+ case 'menu':
+ if ('topmenuData' in target) {
+ this.updateItem(mutation.target, mutation.attributeName);
+ }
+ break;
+ case 'command':
+ if (mutation.attributeName === 'disabled' && 'topmenuData' in target) {
+ target.topmenuData.menuitems.forEach(function(item) {
+ this.updateItem(item, 'disabled');
+ }, this);
+ }
+ }
+ break;
+ }
+}
+
+WindowProxy.prototype.updateFlags = function() {
+ var prefs = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefService).getBranch("");
+ this.flags.accelKey = prefs.getIntPref("ui.key.accelKey");
+ this.flags.alwaysAppendAccessKeys =
+ prefs.getComplexValue("intl.menuitems.alwaysappendaccesskeys",
+ Ci.nsIPrefLocalizedString).data == "true";
+ this.flags.insertSeparatorBeforeAccessKeys =
+ prefs.getComplexValue(
+ "intl.menuitems.insertseparatorbeforeaccesskeys",
+ Ci.nsIPrefLocalizedString).data == "true";
+ this.flags.ellipsis = prefs.getComplexValue("intl.ellipsis",
+ Ci.nsIPrefLocalizedString).data;
+}
+
+/* Convert from XUL mnemonic format to Gtk+ */
+/* https://developer.mozilla.org/En/XUL_Tutorial/Accesskey_display_rules */
+WindowProxy.prototype.createLabelWithAccessKey = function(label, accesskey) {
+ label = label.replace(/_/g, "__");
+ if (accesskey) {
+ var regex = new RegExp(escapePreg(accesskey), "i");
+ if (!this.flags.alwaysAppendAccessKeys && label.search(regex) >= 0) {
+ /* "Inline" access key (ex. "_File") */
+ return label.replace(regex, "_$&");
+ } else {
+ /* Appended access key (ex. "File (_A)") */
+ if (label.slice(-this.flags.ellipsis.length) == this.flags.ellipsis) {
+ /* Label ends with ellipsis */
+ return label.substring(0, label.length-this.flags.ellipsis.length) +
+ (this.flags.insertSeparatorBeforeAccessKeys ? " " : "") +
+ "(_" + accesskey.toUpperCase() + ")" +
+ this.flags.ellipsis;
+ } else {
+ return label +
+ (this.flags.insertSeparatorBeforeAccessKeys ? " " : "") +
+ "(_" + accesskey.toUpperCase() + ")";
+ }
+ }
+ } else {
+ /* No access key */
+ return label;
+ }
+}
+
+WindowProxy.prototype.createImageWidgetFromImage = function(image, rect) {
+ var canvas = this.srcWindow.document.createElementNS("http://www.w3.org/1999/xhtml","canvas");
+ var ctx = canvas.getContext('2d');
+
+ if (!rect[2]) rect[2] = image.width;
+ if (!rect[3]) rect[3] = image.height;
+
+ ctx.drawImage(image, rect[0], rect[1], rect[2], rect[3], 0, 0, 16, 16);
+
+ var img_data = ctx.getImageData(0,0, 16, 16);
+ var pix_data = ctypes.cast(ctypes.uint8_t.array()(img_data.data).address(), glib.guchar.ptr);
+ var pixbuf = gobject.ref_sink(gdk_pixbuf.gdk_pixbuf_new_from_data(pix_data, gdk_pixbuf.GDK_COLORSPACE_RGB, true, 8, 16, 16, 16 * 4, null, null));
+ var pixbuf_copy = gobject.ref_sink(gdk_pixbuf.gdk_pixbuf_copy(pixbuf));
+
+ var gtkImage = gtk.gtk_image_new_from_pixbuf(pixbuf_copy);
+
+ pixbuf.dispose();
+ pixbuf_copy.dispose();
+
+ return gtkImage;
+}
+
+WindowProxy.prototype.createImageWidget = function(item, style) {
+ var image_uri = item.getAttribute("image");
+ var res;
+
+ if (!image_uri) {
+ var style_regex = /url\("(.*?)"\)/;
+ var image_style = style.getPropertyValue("list-style-image");
+ res = style_regex.exec(image_style);
+ if (res) {
+ image_uri = res[1];
+ }
+ }
+
+ if (image_uri) {
+ /* Test if it is a Gtk stock image first */
+ var mozicon_regex = /moz-icon:\/\/stock\/(.*?)\?/;
+ res = mozicon_regex.exec(image_uri);
+ if (res) {
+ return gtk.gtk_image_new_from_stock(res[1], gtk.GTK_ICON_SIZE_MENU);
+ }
+
+ /* Handle image clipping */
+ var rect = [0, 0, 0, 0];
+ var region = style.getPropertyValue("-moz-image-region");
+ if (region && region !== "auto") {
+ var region_regex = /rect\((\d+)px, (\d+)px, (\d+)px, (\d+)px\)/;
+ var values = region_regex.exec(region);
+ if (values) {
+ rect[0] = parseInt(values[4]); /* Left */
+ rect[1] = parseInt(values[1]); /* Top */
+ rect[2] = parseInt(values[2]) - rect[0]; /* Right - Left*/
+ rect[3] = parseInt(values[3]) - rect[1]; /* Bottom - Top */
+ }
+ }
+
+ var img_elem = new this.srcWindow.Image();
+ img_elem.src = image_uri;
+
+ if (img_elem.complete) {
+ return this.createImageWidgetFromImage(img_elem, rect);
+ } else {
+ // Set a handler to try again when the image is loaded
+ var self = this;
+ img_elem.onload = function() {
+ self.updateItem(item, "image");
+ }
+ item.topmenuImgLoader = img_elem;
+ return "pending";
+ }
+ }
+
+ return null;
+}
+
+WindowProxy.prototype.addAccelerator = function(gtkItem, item) {
+ var keyId = item.getAttribute("key");
+ if (!keyId) return false;
+ var keyItem = this.srcWindow.document.getElementById(keyId);
+ if (!keyItem) return false;
+
+ var keyItemMods = keyItem.getAttribute("modifiers");
+ var gtkMods = 0;
+
+ if (keyItemMods) {
+ keyItemMods = keyItemMods.split(/[\s,]+/);
+ for (i in keyItemMods) {
+ var mod = keyItemMods[i];
+ if (mod === "accel") {
+ /* Read platform default accelerator key from prefs */
+ switch (this.flags.accelKey) {
+ case this.srcWindow.KeyEvent.DOM_VK_SHIFT:
+ mod = "shift";
+ break;
+ case this.srcWindow.KeyEvent.DOM_VK_CONTROL:
+ mod = "control";
+ break;
+ case this.srcWindow.KeyEvent.DOM_VK_ALT:
+ mod = "alt";
+ break;
+ case this.srcWindow.KeyEvent.DOM_VK_META:
+ mod = "meta";
+ break;
+ }
+ }
+ switch (mod) {
+ case "shift":
+ gtkMods += gdk.GDK_SHIFT_MASK;
+ break;
+ case "alt":
+ gtkMods += gdk.GDK_ALT_MASK;
+ break;
+ case "meta":
+ gtkMods += gdk.GDK_META_MASK;
+ break;
+ case "control":
+ gtkMods += gdk.GDK_CONTROL_MASK;
+ break;
+ default:
+ continue;
+ }
+ }
+ }
+
+ var keyItemKey = keyItem.getAttribute("key");
+ var keyItemCode = keyItem.getAttribute("keycode");
+ var gtkKey = 0;
+
+ if (keyItemKey) {
+ gtkKey = gdk.gdk_unicode_to_keyval(keyItemKey.charCodeAt(0));
+ } else if (keyItemCode) {
+ gtkKey = vkgdkmap[keyItemCode];
+ if (!gtkKey) {
+ log.info("Keycode " + keyItemCode + " is unmapped");
+ }
+ }
+
+ if (gtkKey) {
+ gtk.gtk_widget_add_accelerator(gtkItem, "activate", this.accelGroup,
+ gtkKey, gtkMods, gtk.GTK_ACCEL_VISIBLE);
+ }
+}
+
+WindowProxy.prototype.addItem = function(item, menu, position) {
+ var disabled = item.disabled;
+ var style = this.srcWindow.getComputedStyle(item, null);
+ var label = this.createLabelWithAccessKey(item.getAttribute('label'), item.accessKey);
+ var visible = !item.hidden && !item.collapsed && style.display !== 'none';
+ var hasSubMenu = item.tagName === "menu" && item.firstChild
+ && item.firstChild.tagName === "menupopup";
+
+ var gtkItem;
+ if (item.tagName === "menuseparator") {
+ gtkItem = gobject.ref_sink(gtk.gtk_separator_menu_item_new());
+ } else if (item.tagName === "menu") {
+ gtkItem = gobject.ref_sink(gtk.gtk_menu_item_new_with_mnemonic(label));
+ } else if (item.tagName === "menuitem") {
+ var image = this.createImageWidget(item, style);
+ if (image) {
+ gtkItem = gobject.ref_sink(gtk.gtk_image_menu_item_new_with_mnemonic(label));
+ if (image !== "pending") {
+ gtk.gtk_image_menu_item_set_image(gtkItem, image);
+ }
+ } else {
+ var type = item.getAttribute("type");
+ var checked = item.getAttribute("checked") ? true : false;
+ switch (type) {
+ case "checkbox":
+ gtkItem = gobject.ref_sink(gtk.gtk_check_menu_item_new_with_mnemonic(label));
+ gtk.gtk_check_menu_item_set_active(gtkItem, checked);
+ break;
+ case "radio":
+ gtkItem = gobject.ref_sink(gtk.gtk_check_menu_item_new_with_mnemonic(label));
+ gtk.gtk_check_menu_item_set_draw_as_radio(gtkItem, true);
+ gtk.gtk_check_menu_item_set_active(gtkItem, checked);
+ break;
+ default:
+ gtkItem = gobject.ref_sink(gtk.gtk_menu_item_new_with_mnemonic(label));
+ }
+ }
+
+ var command = item.getAttribute('command');
+ if (command) {
+ command = this.srcWindow.document.getElementById(command);
+ if (command) {
+ if (!('topmenuData' in command)) {
+ command.topmenuData = {
+ menuitems: [item]
+ };
+ } else {
+ command.topmenuData.menuitems.push(item);
+ }
+
+ this.observer.observe(command, {attributes: true});
+
+ if (command.getAttribute('disabled')) {
+ disabled = true;
+ }
+ }
+ }
+
+ if (item.hasAttribute("key")) {
+ try {
+ this.addAccelerator(gtkItem, item);
+ } catch (ex) {
+ log.warn(ex);
+ }
+ }
+ } else {
+ log.warn("Unknown tagName: " + item.tagName);
+ return;
+ }
+
+ gtk.gtk_widget_set_sensitive(gtkItem, !disabled);
+ gtk.gtk_widget_set_visible(gtkItem, visible);
+
+ item.topmenuData = {
+ gtkItem : gtkItem,
+ callbackId: this.lastCallbackId++
+ };
+ this.callbackItems[item.topmenuData.callbackId] = item;
+
+ if (hasSubMenu) {
+ var gtkSubMenu = this.addMenu(item);
+ gtk.gtk_menu_item_set_submenu(gtkItem, gtkSubMenu);
+ }
+
+ var gtkMenu = menu.topmenuData.gtkMenu;
+ if (position >= 0) {
+ gtk.gtk_menu_shell_insert(gtkMenu, gtkItem, position);
+ } else {
+ gtk.gtk_menu_shell_append(gtkMenu, gtkItem);
+ }
+
+ var conn_id;
+ if (hasSubMenu) {
+ conn_id = gobject.signal_connect(gtkItem, 'select',
+ this.callbacks.select,
+ glib.gpointer(item.topmenuData.callbackId));
+ item.topmenuData.selectConnectionId = conn_id;
+
+ conn_id = gobject.signal_connect(gtkItem, 'deselect',
+ this.callbacks.deselect,
+ glib.gpointer(item.topmenuData.callbackId));
+ item.topmenuData.deselectConnectionId = conn_id;
+ } else {
+ conn_id = gobject.signal_connect(gtkItem, 'activate',
+ this.callbacks.activate,
+ glib.gpointer(item.topmenuData.callbackId));
+ item.topmenuData.activateConnectionId = conn_id;
+ }
+
+ return gtkItem;
+}
+
+WindowProxy.prototype.addMenu = function(menu) {
+ var gtkMenu = gobject.ref_sink(gtk.gtk_menu_new());
+
+ menu.topmenuData.gtkMenu = gtkMenu;
+
+ var popup = menu.firstChild;
+ for (var item = popup.firstChild; item; item = item.nextSibling) {
+ this.addItem(item, menu, -1);
+ }
+
+ return gtkMenu;
+}
+
+WindowProxy.prototype.removeItem = function(item) {
+ var hasSubMenu = item.tagName === "menu" && item.firstChild
+ && item.firstChild.tagName === "menupopup";
+
+ if (hasSubMenu) {
+ this.removeMenu(item);
+ }
+
+ delete this.callbackItems[item.topmenuData.callbackId];
+
+ var gtkItem = item.topmenuData.gtkItem;
+
+ gtk.gtk_widget_destroy(gtkItem);
+ gtkItem.forget();
+
+ delete item.topmenuData;
+}
+
+WindowProxy.prototype.removeMenu = function(menu) {
+ var gtkMenu = menu.topmenuData.gtkMenu;
+
+ gtk.gtk_widget_destroy(gtkMenu);
+ gtkMenu.forget();
+}
+
+WindowProxy.prototype.updateItem = function(item, changedAttr) {
+ var gtkItem = item.topmenuData.gtkItem;
+ var style = this.srcWindow.getComputedStyle(item, null);
+
+ switch (changedAttr) {
+ case "accesskey":
+ case "label":
+ var label = this.createLabelWithAccessKey(item.getAttribute('label'), item.accessKey);
+ gtk.gtk_menu_item_set_label(gtkItem, label);
+ break;
+ case "disabled":
+ var disabled = item.disabled;
+ var command = item.getAttribute('command');
+ if (!disabled && command) {
+ command = this.srcWindow.document.getElementById(command);
+ if (command) {
+ if (command.getAttribute('disabled')) {
+ disabled = true;
+ }
+ }
+ }
+
+ gtk.gtk_widget_set_sensitive(gtkItem, !disabled);
+ break;
+ case "hidden":
+ case "collapsed":
+ var visible = !item.hidden && !item.collapsed && style.display !== 'none';
+ gtk.gtk_widget_set_visible(gtkItem, visible);
+ break;
+ case "image":
+ var image = this.createImageWidget(item, style);
+ if (image !== 'pending') {
+ delete item.topmenuImgLoader;
+ gtk.gtk_image_menu_item_set_image(gtkItem, image);
+ }
+ break;
+ }
+}
+
+WindowProxy.prototype.updateMenu = function(menu, prevItem, removedItems, addedItems) {
+ var position, i, node;
+ var popup = menu.firstChild;
+
+ if (removedItems.length > 0) {
+ for (i = 0; i < removedItems.length; i++) {
+ this.removeItem(removedItems[i]);
+ }
+ }
+
+ if (addedItems.length > 0) {
+ if (prevItem) {
+ position = -1;
+ i = 2;
+ for (node = popup.firstChild; node; node = node.nextSibling, i++) {
+ if (node === prevItem) {
+ position = i;
+ break;
+ }
+ }
+ if (position < 0) {
+ log.warn("prevItem node not found");
+ position = 0;
+ }
+ } else {
+ position = 0;
+ }
+
+ for (i = 0; i < addedItems.length; i++) {
+ this.addItem(addedItems[i], menu, position);
+ }
+ }
+}
+
+WindowProxy.prototype.activateItem = function(item) {
+ this.fakeCommandEvent(item);
+}
+
+WindowProxy.prototype.selectItem = function(item) {
+ var menu = item;
+ var popup = menu.firstChild;
+ if (popup.tagName !== 'menupopup') {
+ log.warn("Selected a menu without a menupopup");
+ return;
+ }
+
+ this.fakePopupEvent(popup, "popupshowing");
+ menu.open = "true";
+
+ // A hack for Firefox and friends
+ if (menu.id === 'edit-menu' && 'gEditUIVisible' in this.srcWindow) {
+ this.srcWindow.gEditUIVisible = true;
+ this.srcWindow.goUpdateGlobalEditMenuItems();
+ }
+
+ // Let's update the label of every item at least.
+ for (var i = popup.firstChild; i; i = i.nextSibling) {
+ switch (i.tagName) {
+ case 'menu':
+ case 'menuitem':
+ this.updateItem(i, 'label');
+ this.updateItem(i, 'hidden');
+ break;
+ case 'menuseparator':
+ this.updateItem(i, 'hidden');
+ break;
+ }
+ }
+
+ this.fakePopupEvent(popup, "popupshown");
+}
+
+WindowProxy.prototype.deselectItem = function(item) {
+ var menu = item;
+ var popup = menu.firstChild;
+ if (popup.tagName !== 'menupopup') {
+ log.warn("Selected a menu without a menupopup");
+ return;
+ }
+
+ this.fakePopupEvent(popup, "popuphiding");
+ menu.removeAttribute('open');
+
+ if (menu.id === 'edit-menu' && 'gEditUIVisible' in this.srcWindow) {
+ this.srcWindow.gEditUIVisible = false;
+ }
+
+ this.fakePopupEvent(popup, "popuphidden");
+}
+
+WindowProxy.prototype.onKeyPress = function(event) {
+ if (!event.altKey) return;
+ if (!event.which) return;
+
+ var char = String.fromCharCode(event.which).toLowerCase();
+ if (!char) return;
+
+ for (var item = this.srcMenuBar.firstChild; item; item = item.nextSibling) {
+ if ('topmenuData' in item &&
+ item.getAttribute('accesskey').toLowerCase() === char) {
+ event.preventDefault();
+ event.stopImmediatePropagation();
+ gtk.gtk_widget_mnemonic_activate(item.topmenuData.gtkItem, false);
+ }
+ }
+}
+
+WindowProxy.prototype.fakePopupEvent = function(popup, type) {
+ var window = this.srcWindow;
+ var popupEvent = window.document.createEvent("MouseEvent");
+ popupEvent.initMouseEvent(type, true, true, window,
+ 0, 0, 0, 0, 0, false, false, false, false, 0, null);
+ popup.dispatchEvent(popupEvent);
+}
+
+WindowProxy.prototype.fakeActiveEvent = function(item, type) {
+ var window = this.srcWindow;
+ var activeEvent = window.document.createEvent("Events");
+ activeEvent.initEvent(type, true, false);
+ item.dispatchEvent(activeEvent);
+}
+
+WindowProxy.prototype.fakeCommandEvent = function(item) {
+ var window = this.srcWindow;
+ var commandEvent = window.document.createEvent("XULCommandEvent");
+ commandEvent.initCommandEvent("command", true, true, window,
+ 0, false, false, false, false, null);
+ item.dispatchEvent(commandEvent);
+}
+
+WindowProxy.prototype.dispose = function() {
+ window.document.removeEventListener("keypress", this.callbacks.keypress);
+ if (this.monitor) {
+ if (this.monitorConnectionId) {
+ gobject.g_signal_handler_disconnect(this.monitor, this.monitorConnectionId);
+ }
+ this.monitorConnectionId = 0;
+ this.monitor = null;
+ }
+ if (this.observer) {
+ this.observer.disconnect();
+ this.observer = null;
+ }
+ if (this.appMenuBar) {
+ this.appMenuBar.dispose();
+ this.appMenuBar = null;
+ }
+ if (this.appMenu) {
+ this.appMenu.dispose();
+ this.appMenu = null;
+ }
+ if (this.accelGroup) {
+ this.accelGroup.dispose();
+ this.accelGroup = null;
+ }
+
+ this.gdkWindow = null;
+ this.srcWindow = null;
+ this.menus = null;
+ this.items = null;
+}
+
+var TopMenuService = {
+ createWindowProxy: function(window) {
+ return new WindowProxy(window);
+ }
+}
diff --git a/chrome/content/vkgdkmap.js b/chrome/content/vkgdkmap.js
new file mode 100644
index 0000000..35a8fc5
--- /dev/null
+++ b/chrome/content/vkgdkmap.js
@@ -0,0 +1,27 @@
+var EXPORTED_SYMBOLS = [ "vkgdkmap" ];
+
+const Cu = Components.utils;
+
+Cu.import("chrome://topmenu/content/gdk.js");
+
+/* Hardcoding DOM_VK_* constants, how ugly. */
+/* But how to get a window scope from here? Need to access KeyEvent. */
+
+vkgdkmap = {
+ VK_F1 : gdk.GDK_KEY_F1,
+ VK_F2 : gdk.GDK_KEY_F2,
+ VK_F3 : gdk.GDK_KEY_F3,
+ VK_F4 : gdk.GDK_KEY_F4,
+ VK_F5 : gdk.GDK_KEY_F5,
+ VK_F6 : gdk.GDK_KEY_F6,
+ VK_F7 : gdk.GDK_KEY_F7,
+ VK_F8 : gdk.GDK_KEY_F8,
+ VK_F9 : gdk.GDK_KEY_F9,
+ VK_F10 : gdk.GDK_KEY_F10,
+ VK_F11 : gdk.GDK_KEY_F11,
+ VK_F12 : gdk.GDK_KEY_F12,
+
+ VK_TAB : gdk.GDK_KEY_Tab,
+ VK_DELETE : gdk.GDK_KEY_Delete
+
+}