summaryrefslogtreecommitdiff
path: root/chrome/content/topmenuservice.js
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/content/topmenuservice.js')
-rw-r--r--chrome/content/topmenuservice.js792
1 files changed, 792 insertions, 0 deletions
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);
+ }
+}