summarylogtreecommitdiffstats
diff options
context:
space:
mode:
authornikatar2018-06-06 20:47:12 +0300
committernikatar2018-06-06 20:47:12 +0300
commitd85241d75956d20e826f714e8dcea992ad0452db (patch)
tree7f7233277f4af2488738505ace150657bedf1feb
downloadaur-d85241d75956d20e826f714e8dcea992ad0452db.tar.gz
52.8.0-1
-rw-r--r--.SRCINFO63
-rw-r--r--0001-Bug-1338655-Don-t-try-to-build-mp4parse-bindings.-r-.patch48
-rw-r--r--PKGBUILD186
-rw-r--r--fix-wifi-scanner.diff16
-rw-r--r--no-crmf.diff39
-rw-r--r--rust-i686.patch16
-rw-r--r--thunderbird-install-dir.patch12
-rw-r--r--thunderbird.desktop173
-rw-r--r--unity-menubar.patch5708
9 files changed, 6261 insertions, 0 deletions
diff --git a/.SRCINFO b/.SRCINFO
new file mode 100644
index 000000000000..a44fcd47388f
--- /dev/null
+++ b/.SRCINFO
@@ -0,0 +1,63 @@
+pkgbase = thunderbird-appmenu
+ pkgdesc = Thunderbird from extra with appmenu patch
+ pkgver = 52.8.0
+ pkgrel = 1
+ url = https://www.mozilla.org/thunderbird/
+ arch = x86_64
+ license = MPL
+ license = GPL
+ license = LGPL
+ makedepends = unzip
+ makedepends = zip
+ makedepends = diffutils
+ makedepends = python2
+ makedepends = yasm
+ makedepends = mesa
+ makedepends = imake
+ makedepends = gconf
+ makedepends = libpulse
+ makedepends = inetutils
+ makedepends = xorg-server-xvfb
+ makedepends = autoconf2.13
+ makedepends = rust
+ makedepends = clang
+ makedepends = llvm
+ depends = gtk3
+ depends = gtk2
+ depends = mozilla-common
+ depends = libxt
+ depends = startup-notification
+ depends = mime-types
+ depends = dbus-glib
+ depends = alsa-lib
+ depends = ffmpeg
+ depends = nss
+ depends = hunspell
+ depends = sqlite
+ depends = ttf-font
+ depends = icu
+ depends = libvpx
+ optdepends = libcanberra: sound support
+ provides = thunderbird=52.8.0
+ conflicts = thunderbird
+ options = !emptydirs
+ options = !makeflags
+ source = https://ftp.mozilla.org/pub/mozilla.org/thunderbird/releases/52.8.0/source/thunderbird-52.8.0.source.tar.xz
+ source = thunderbird.desktop
+ source = 0001-Bug-1338655-Don-t-try-to-build-mp4parse-bindings.-r-.patch
+ source = rust-i686.patch
+ source = fix-wifi-scanner.diff
+ source = thunderbird-install-dir.patch
+ source = no-crmf.diff
+ source = unity-menubar.patch
+ sha256sums = 35b9a687997d92f36107090c1217941e5d637760b0efa7d13819cde36894eb59
+ sha256sums = 3534ea85d8e0e35dba5f40a7a07844df19f3a480e1358fc50c2502f122dab789
+ sha256sums = 413cd6d366d78f325d80ebebccfd0afa0d266b40b2e54b66ba2fa03c15f3ea67
+ sha256sums = f61ea706ce6905f568b9bdafd1b044b58f20737426f0aa5019ddb9b64031a269
+ sha256sums = 9765bca5d63fb5525bbd0520b7ab1d27cabaed697e2fc7791400abc3fa4f13b8
+ sha256sums = 24599eab8862476744fe1619a9a53a5b8cdcab30b3fc5767512f31d3529bd05d
+ sha256sums = a7317caba56e89932bd9e3b9352d94701dd9a419685057f238b1ded8dc0adcd7
+ sha256sums = 87f194b87e2291c232a00cfa45c246e57fbdb1d02ba0a19f1f818f0fc9bf5967
+
+pkgname = thunderbird-appmenu
+
diff --git a/0001-Bug-1338655-Don-t-try-to-build-mp4parse-bindings.-r-.patch b/0001-Bug-1338655-Don-t-try-to-build-mp4parse-bindings.-r-.patch
new file mode 100644
index 000000000000..cf2e0eb9b80b
--- /dev/null
+++ b/0001-Bug-1338655-Don-t-try-to-build-mp4parse-bindings.-r-.patch
@@ -0,0 +1,48 @@
+From bbd48a5613c872883616884cfaf41665b0e4ec9b Mon Sep 17 00:00:00 2001
+From: Ralph Giles <giles@mozilla.com>
+Date: Fri, 10 Feb 2017 12:58:18 -0800
+Subject: [PATCH] Bug 1338655 - Don't try to build mp4parse bindings. r=froydnj
+
+We use the cheddar crate to generate a C header file
+for our mp4parse_capi wrapper crate. Currently we
+do this at code check-in time via update-rust.sh.
+
+Cargo 0.18 and later will try to execute a build.rs
+file in the crate source tree regardless of whether
+it's specified in Cargo.toml so patching out that
+line just results in 'crate cheddar not found'.
+
+This change restores the old behaviour by substituting
+a 'build = false' line instead.
+
+We do have syntex vendored, but we don't currently build
+it by default, so I prefer this solution to just vendoring
+cheddar and generating the header at build time. The syntex
+crate is quite large and adds significantly to our compile
+time.
+
+MozReview-Commit-ID: InJRRODWAdP
+
+--HG--
+extra : rebase_source : 29378fcbc86015ce6cc22dc66d38a43ddbac204e
+---
+ media/libstagefright/binding/mp4parse-cargo.patch | 5 +++--
+ media/libstagefright/binding/mp4parse_capi/Cargo.toml | 2 ++
+ 2 files changed, 5 insertions(+), 2 deletions(-)
+
+diff --git a/media/libstagefright/binding/mp4parse_capi/Cargo.toml b/media/libstagefright/binding/mp4parse_capi/Cargo.toml
+index aee7ee947151a27c..d7e3f55119d3f4b6 100644
+--- a/media/libstagefright/binding/mp4parse_capi/Cargo.toml
++++ b/media/libstagefright/binding/mp4parse_capi/Cargo.toml
+@@ -18,6 +18,8 @@ exclude = [
+ "*.mp4",
+ ]
+
++build = false
++
+ [dependencies]
+ byteorder = "1.0.0"
+ "mp4parse" = {version = "0.6.0", path = "../mp4parse"}
+--
+2.12.2
+
diff --git a/PKGBUILD b/PKGBUILD
new file mode 100644
index 000000000000..ef79b6d9c825
--- /dev/null
+++ b/PKGBUILD
@@ -0,0 +1,186 @@
+# $Id$
+# Contributor: Jan Alexander Steffens (heftig) <jan.steffens@gmail.com>
+# Contributor: Ionut Biru <ibiru@archlinux.org>
+# Contributor: Alexander Baldeck <alexander@archlinux.org>
+# Contributor: Dale Blount <dale@archlinux.org>
+# Contributor: Anders Bostrom <anders.bostrom@home.se>
+# Additional patching: Nikita Tarasov <nikatar@disroot.org>
+
+
+_pkgname=thunderbird
+pkgname=thunderbird-appmenu
+pkgver=52.8.0
+pkgrel=1
+pkgdesc="Thunderbird from extra with appmenu patch"
+arch=(x86_64)
+license=(MPL GPL LGPL)
+url="https://www.mozilla.org/thunderbird/"
+depends=(gtk3 gtk2 mozilla-common libxt startup-notification mime-types dbus-glib alsa-lib ffmpeg
+ nss hunspell sqlite ttf-font icu libvpx)
+makedepends=(unzip zip diffutils python2 yasm mesa imake gconf libpulse inetutils xorg-server-xvfb
+ autoconf2.13 rust clang llvm)
+optdepends=('libcanberra: sound support')
+provides=("thunderbird=$pkgver")
+conflicts=("thunderbird")
+options=(!emptydirs !makeflags)
+source=(https://ftp.mozilla.org/pub/mozilla.org/thunderbird/releases/$pkgver/source/thunderbird-$pkgver.source.tar.xz
+ $_pkgname.desktop
+ 0001-Bug-1338655-Don-t-try-to-build-mp4parse-bindings.-r-.patch
+ rust-i686.patch fix-wifi-scanner.diff
+ thunderbird-install-dir.patch no-crmf.diff
+ unity-menubar.patch)
+sha256sums=('35b9a687997d92f36107090c1217941e5d637760b0efa7d13819cde36894eb59'
+ '3534ea85d8e0e35dba5f40a7a07844df19f3a480e1358fc50c2502f122dab789'
+ '413cd6d366d78f325d80ebebccfd0afa0d266b40b2e54b66ba2fa03c15f3ea67'
+ 'f61ea706ce6905f568b9bdafd1b044b58f20737426f0aa5019ddb9b64031a269'
+ '9765bca5d63fb5525bbd0520b7ab1d27cabaed697e2fc7791400abc3fa4f13b8'
+ '24599eab8862476744fe1619a9a53a5b8cdcab30b3fc5767512f31d3529bd05d'
+ 'a7317caba56e89932bd9e3b9352d94701dd9a419685057f238b1ded8dc0adcd7'
+ '87f194b87e2291c232a00cfa45c246e57fbdb1d02ba0a19f1f818f0fc9bf5967')
+
+# Google API keys (see http://www.chromium.org/developers/how-tos/api-keys)
+# Note: These are for Arch Linux use ONLY. For your own distribution, please
+# get your own set of keys. Feel free to contact foutrelis@archlinux.org for
+# more information.
+_google_api_key=AIzaSyDwr302FpOSkGRpLlUpPThNTDPbXcIn_FM
+
+# Mozilla API keys (see https://location.services.mozilla.com/api)
+# Note: These are for Arch Linux use ONLY. For your own distribution, please
+# get your own set of keys. Feel free to contact heftig@archlinux.org for
+# more information.
+_mozilla_api_key=16674381-f021-49de-8622-3021c5942aff
+
+prepare() {
+ mkdir path
+ ln -s /usr/bin/python2 path/python
+
+ cd $_pkgname-$pkgver
+ patch -Np1 -i ../thunderbird-install-dir.patch
+
+ # https://bugzilla.mozilla.org/show_bug.cgi?id=1314968
+ patch -d mozilla -Np1 < ../fix-wifi-scanner.diff
+
+ # https://bugzilla.mozilla.org/show_bug.cgi?id=1371991
+ patch -Np1 -i ../no-crmf.diff
+
+ # Build with the rust targets we actually ship
+ patch -d mozilla -Np1 < ../rust-i686.patch
+
+ # https://bugs.archlinux.org/task/53890
+ patch -d mozilla -Np1 < ../0001-Bug-1338655-Don-t-try-to-build-mp4parse-bindings.-r-.patch
+
+ # appmenu patching
+ patch -Np1 -i ../unity-menubar.patch
+
+ echo -n "$_google_api_key" >google-api-key
+ echo -n "$_mozilla_api_key" >mozilla-api-key
+
+ cat >.mozconfig <<END
+ac_add_options --enable-application=mail
+ac_add_options --enable-calendar
+
+ac_add_options --prefix=/usr
+ac_add_options --enable-release
+ac_add_options --enable-gold
+ac_add_options --enable-pie
+ac_add_options --enable-optimize="-O2"
+ac_add_options --enable-rust
+
+# Branding
+ac_add_options --enable-official-branding
+ac_add_options --enable-update-channel=release
+ac_add_options --with-distribution-id=org.archlinux
+
+# Keys
+ac_add_options --with-google-api-keyfile=${PWD@Q}/google-api-key
+ac_add_options --with-mozilla-api-keyfile=${PWD@Q}/mozilla-api-key
+
+# System libraries
+ac_add_options --with-system-zlib
+ac_add_options --with-system-bz2
+ac_add_options --with-system-icu
+ac_add_options --with-system-jpeg
+ac_add_options --with-system-libvpx
+ac_add_options --with-system-nspr
+ac_add_options --with-system-nss
+ac_add_options --enable-system-hunspell
+ac_add_options --enable-system-sqlite
+ac_add_options --enable-system-ffi
+
+# Features
+ac_add_options --enable-alsa
+ac_add_options --disable-jack
+ac_add_options --enable-startup-notification
+ac_add_options --disable-crashreporter
+ac_add_options --disable-updater
+END
+}
+
+build() {
+ cd $_pkgname-$pkgver
+
+ # _FORTIFY_SOURCE causes configure failures
+ CPPFLAGS+=" -O2"
+
+ export PATH="$srcdir/path:$PATH"
+
+ # Do PGO
+ #xvfb-run -a -n 95 -s "-extension GLX -screen 0 1280x1024x24" \
+ # make -f client.mk build MOZ_PGO=1
+ make -f client.mk build
+}
+
+package() {
+ cd $_pkgname-$pkgver
+ make -f client.mk DESTDIR="$pkgdir" INSTALL_SDK= install
+
+ _vendorjs="$pkgdir/usr/lib/$_pkgname/defaults/preferences/vendor.js"
+ install -Dm644 /dev/stdin "$_vendorjs" <<END
+// Use LANG environment variable to choose locale
+pref("intl.locale.matchOS", true);
+
+// Disable default mailer checking.
+pref("mail.shell.checkDefaultMail", false);
+
+// Don't disable our bundled extensions in the application directory
+pref("extensions.autoDisableScopes", 11);
+pref("extensions.shownSelectionUI", true);
+END
+
+ _distini="$pkgdir/usr/lib/$_pkgname/distribution/distribution.ini"
+ install -Dm644 /dev/stdin "$_distini" <<END
+[Global]
+id=archlinux
+version=1.0
+about=Mozilla Thunderbird for Arch Linux
+
+[Preferences]
+app.distributor=archlinux
+app.distributor.channel=$_pkgname
+app.partner.archlinux=archlinux
+END
+
+ for i in 16 22 24 32 48 256; do
+ install -Dm644 other-licenses/branding/thunderbird/mailicon$i.png \
+ "$pkgdir/usr/share/icons/hicolor/${i}x${i}/apps/$_pkgname.png"
+ done
+
+ install -Dm644 ../$_pkgname.desktop \
+ "$pkgdir/usr/share/applications/$_pkgname.desktop"
+
+ # Use system-provided dictionaries
+ rm -r "$pkgdir/usr/lib/$_pkgname/dictionaries"
+ ln -Ts /usr/share/hunspell "$pkgdir/usr/lib/$_pkgname/dictionaries"
+ ln -Ts /usr/share/hyphen "$pkgdir/usr/lib/$_pkgname/hyphenation"
+
+ # Install a wrapper to avoid confusion about binary path
+ install -Dm755 /dev/stdin "$pkgdir/usr/bin/$_pkgname" <<END
+#!/bin/sh
+exec /usr/lib/$_pkgname/thunderbird "\$@"
+END
+
+ # Replace duplicate binary with wrapper
+ # https://bugzilla.mozilla.org/show_bug.cgi?id=658850
+ ln -srf "$pkgdir/usr/bin/$_pkgname" \
+ "$pkgdir/usr/lib/$_pkgname/thunderbird-bin"
+}
diff --git a/fix-wifi-scanner.diff b/fix-wifi-scanner.diff
new file mode 100644
index 000000000000..f8fdd6c42142
--- /dev/null
+++ b/fix-wifi-scanner.diff
@@ -0,0 +1,16 @@
+ netwerk/wifi/nsWifiScannerDBus.cpp | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git c/netwerk/wifi/nsWifiScannerDBus.cpp i/netwerk/wifi/nsWifiScannerDBus.cpp
+index 182553e18fa6e104..6fa0a0b023d3e45f 100644
+--- c/netwerk/wifi/nsWifiScannerDBus.cpp
++++ i/netwerk/wifi/nsWifiScannerDBus.cpp
+@@ -62,7 +62,7 @@ nsWifiScannerDBus::SendMessage(const char* aInterface,
+ return NS_ERROR_FAILURE;
+ }
+ } else if (!strcmp(aFuncCall, "GetAll")) {
+- const char* param = "";
++ const char* param = "org.freedesktop.NetworkManager.AccessPoint";
+ if (!dbus_message_iter_append_basic(&argsIter, DBUS_TYPE_STRING, &param)) {
+ return NS_ERROR_FAILURE;
+ }
diff --git a/no-crmf.diff b/no-crmf.diff
new file mode 100644
index 000000000000..73e545de0eed
--- /dev/null
+++ b/no-crmf.diff
@@ -0,0 +1,39 @@
+diff -u -rN thunderbird-52.1.1/mozilla/config/external/nss/crmf/moz.build thunderbird-52.1.1-nocrmf/mozilla/config/external/nss/crmf/moz.build
+--- thunderbird-52.1.1/mozilla/config/external/nss/crmf/moz.build 2017-05-09 23:35:13.000000000 +0200
++++ thunderbird-52.1.1-nocrmf/mozilla/config/external/nss/crmf/moz.build 2017-06-14 01:51:36.686773132 +0200
+@@ -8,7 +8,6 @@
+
+ if CONFIG['MOZ_SYSTEM_NSS']:
+ OS_LIBS += [l for l in CONFIG['NSS_LIBS'] if l.startswith('-L')]
+- OS_LIBS += ['-lcrmf']
+ else:
+ USE_LIBS += [
+ # The dependency on nss is not real, but is required to force the
+diff -u -rN thunderbird-52.1.1/mozilla/old-configure thunderbird-52.1.1-nocrmf/mozilla/old-configure
+--- thunderbird-52.1.1/mozilla/old-configure 2017-05-09 23:35:35.000000000 +0200
++++ thunderbird-52.1.1-nocrmf/mozilla/old-configure 2017-06-14 01:50:28.726873231 +0200
+@@ -10692,9 +10692,7 @@
+
+ fi
+
+-if test -n "$MOZ_SYSTEM_NSS"; then
+- NSS_LIBS="$NSS_LIBS -lcrmf"
+-else
++if test -z "$MOZ_SYSTEM_NSS"; then
+ NSS_CFLAGS="-I${DIST}/include/nss"
+ fi
+
+diff -u -rN thunderbird-52.1.1/mozilla/old-configure.in thunderbird-52.1.1-nocrmf/mozilla/old-configure.in
+--- thunderbird-52.1.1/mozilla/old-configure.in 2017-05-09 23:35:22.000000000 +0200
++++ thunderbird-52.1.1-nocrmf/mozilla/old-configure.in 2017-06-14 01:50:50.953507079 +0200
+@@ -2126,9 +2126,7 @@
+ AM_PATH_NSS(3.28.4, [MOZ_SYSTEM_NSS=1], [AC_MSG_ERROR([you don't have NSS installed or your version is too old])])
+ fi
+
+-if test -n "$MOZ_SYSTEM_NSS"; then
+- NSS_LIBS="$NSS_LIBS -lcrmf"
+-else
++if test -z "$MOZ_SYSTEM_NSS"; then
+ NSS_CFLAGS="-I${DIST}/include/nss"
+ fi
+
diff --git a/rust-i686.patch b/rust-i686.patch
new file mode 100644
index 000000000000..85512e1436b8
--- /dev/null
+++ b/rust-i686.patch
@@ -0,0 +1,16 @@
+ build/moz.configure/rust.configure | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git c/build/moz.configure/rust.configure i/build/moz.configure/rust.configure
+index cd86b24153debb1b..44911715e25d95e3 100644
+--- c/build/moz.configure/rust.configure
++++ i/build/moz.configure/rust.configure
+@@ -81,7 +81,7 @@ def rust_target(rust_compiler, rustc, target, cross_compiling):
+ # OpenBSD
+ ('x86_64', 'OpenBSD'): 'x86_64-unknown-openbsd',
+ # Linux
+- ('x86', 'Linux'): 'i586-unknown-linux-gnu',
++ ('x86', 'Linux'): 'i686-unknown-linux-gnu',
+ # Linux
+ ('x86_64', 'Linux'): 'x86_64-unknown-linux-gnu',
+ # OS X and iOS
diff --git a/thunderbird-install-dir.patch b/thunderbird-install-dir.patch
new file mode 100644
index 000000000000..0c7ffaa4a52a
--- /dev/null
+++ b/thunderbird-install-dir.patch
@@ -0,0 +1,12 @@
+diff -upr comm-esr31.orig/mozilla/config/baseconfig.mk comm-esr31/mozilla/config/baseconfig.mk
+--- comm-esr31.orig/mozilla/config/baseconfig.mk 2014-07-22 09:44:22.000000000 +0300
++++ comm-esr31/mozilla/config/baseconfig.mk 2014-07-22 09:46:45.000000000 +0300
+@@ -4,7 +4,7 @@
+ # whether a normal build is happening or whether the check is running.
+ includedir := $(includedir)/$(MOZ_APP_NAME)-$(MOZ_APP_VERSION)
+ idldir = $(datadir)/idl/$(MOZ_APP_NAME)-$(MOZ_APP_VERSION)
+-installdir = $(libdir)/$(MOZ_APP_NAME)-$(MOZ_APP_VERSION)
++installdir = $(libdir)/$(MOZ_APP_NAME)
+ sdkdir = $(libdir)/$(MOZ_APP_NAME)-devel-$(MOZ_APP_VERSION)
+ ifndef TOP_DIST
+ TOP_DIST = dist
diff --git a/thunderbird.desktop b/thunderbird.desktop
new file mode 100644
index 000000000000..ccb22b2d3a38
--- /dev/null
+++ b/thunderbird.desktop
@@ -0,0 +1,173 @@
+[Desktop Entry]
+Name=Thunderbird
+Comment=Send and receive mail with Thunderbird
+Comment[ast]=Lleer y escribir corréu electrónicu
+Comment[ca]=Llegiu i escriviu correu
+Comment[cs]=Čtení a psaní pošty
+Comment[da]=Skriv/læs e-post/nyhedsgruppe med Mozilla Thunderbird
+Comment[de]=E-Mails und Nachrichten mit Thunderbird lesen und schreiben
+Comment[el]=Διαβάστε και γράψτε γράμματα με το Mozilla Thunderbird
+Comment[es]=Lea y escriba correos y noticias con Thunderbird
+Comment[fi]=Lue ja kirjoita sähköposteja
+Comment[fr]=Lire et écrire des courriels
+Comment[gl]=Lea e escriba correo electrónico
+Comment[he]=קריאה/כתיבה של דוא״ל/חדשות באמצעות Mozilla Thunderbird
+Comment[hr]=Čitajte/šaljite e-poštu s Thunderbird
+Comment[hu]=Levelek írása és olvasása a Thunderbirddel
+Comment[it]=Per leggere e scrivere email
+Comment[ja]=メールの読み書き
+Comment[ko]=Mozilla Thunderbird 메일/뉴스 읽기 및 쓰기 클라이언트
+Comment[nl]=E-mail/nieuws lezen en schrijven met Mozilla Thunderbird
+Comment[pl]=Czytanie i wysyłanie e-maili
+Comment[pt_BR]=Leia e escreva suas mensagens
+Comment[ru]=Читайте и пишите письма
+Comment[sk]=Čítajte a píšte poštu pomocou programu Thunderbird
+Comment[sv]=Läs och skriv e-post
+Comment[ug]=ئېلخەت ۋە خەۋەرلەرنى Mozilla Thunderbird دا كۆرۈش ۋە يېزىش
+Comment[uk]=Читання та написання листів
+Comment[vi]=Đọc và soạn thư điện tử
+Comment[zh_CN]=阅读邮件或新闻
+Comment[zh_TW]=以 Mozilla Thunderbird 讀寫郵件或新聞
+GenericName=Mail Client
+GenericName[ast]=Client de correu
+GenericName[ca]=Client de correu
+GenericName[cs]=Poštovní klient
+GenericName[da]=E-postklient
+GenericName[de]=E-Mail-Anwendung
+GenericName[el]=Λογισμικό αλληλογραφίας
+GenericName[es]=Cliente de correo
+GenericName[fi]=Sähköpostiohjelma
+GenericName[fr]=Client de messagerie
+GenericName[gl]=Cliente de correo electrónico
+GenericName[he]=לקוח דוא״ל
+GenericName[hr]=Klijent e-pošte
+GenericName[hu]=Levelezőkliens
+GenericName[it]=Client email
+GenericName[ja]=電子メールクライアント
+GenericName[ko]=메일 클라이언트
+GenericName[nl]=E-mailprogramma
+GenericName[pl]=Klient poczty
+GenericName[pt_BR]=Cliente de E-mail
+GenericName[ru]=Почтовый клиент
+GenericName[sk]=Poštový klient
+GenericName[ug]=ئېلخەت دېتالى
+GenericName[uk]=Поштова програма
+GenericName[vi]=Phần mềm khách quản lý thư điện tử
+GenericName[zh_CN]=邮件新闻客户端
+GenericName[zh_TW]=郵件用戶端
+Exec=env UBUNTU_MENUPROXY=0 /usr/lib/thunderbird/thunderbird %u
+Terminal=false
+Type=Application
+Icon=thunderbird
+Categories=Network;Email;
+MimeType=message/rfc822;x-scheme-handler/mailto;application/x-xpinstall;
+StartupNotify=true
+Actions=ComposeMessage;OpenAddressBook;
+
+[Desktop Action ComposeMessage]
+Name=Write new message
+Name[ar]=اكتب رسالة جديدة
+Name[ast]=Redactar mensaxe nuevu
+Name[be]=Напісаць новы ліст
+Name[bg]=Съставяне на ново съобщение
+Name[br]=Skrivañ ur gemennadenn nevez
+Name[ca]=Escriu un missatge nou
+Name[cs]=Napsat novou zprávu
+Name[da]=Skriv en ny meddelelse
+Name[de]=Neue Nachricht verfassen
+Name[el]=Σύνταξη νέου μηνύματος
+Name[es_AR]=Escribir un nuevo mensaje
+Name[es_ES]=Redactar nuevo mensaje
+Name[et]=Kirjuta uus kiri
+Name[eu]=Idatzi mezu berria
+Name[fi]=Kirjoita uusi viesti
+Name[fr]=Rédiger un nouveau message
+Name[fy_NL]=Skriuw in nij berjocht
+Name[ga_IE]=Scríobh teachtaireacht nua
+Name[gd]=Sgrìobh teachdaireachd ùr
+Name[gl]=Escribir unha nova mensaxe
+Name[he]=כתיבת הודעה חדשה
+Name[hr]=Piši novu poruku
+Name[hu]=Új üzenet írása
+Name[hy_AM]=Գրել նոր նամակ
+Name[is]=SKrifa nýjan póst
+Name[it]=Scrivi nuovo messaggio
+Name[ja]=新しいメッセージを作成する
+Name[ko]=새 메시지 작성
+Name[lt]=Rašyti naują laišką
+Name[nb_NO]=Skriv ny melding
+Name[nl]=Nieuw bericht aanmaken
+Name[nn_NO]=Skriv ny melding
+Name[pl]=Nowa wiadomość
+Name[pt_BR]=Nova mensagem
+Name[pt_PT]=Escrever nova mensagem
+Name[rm]=Scriver in nov messadi
+Name[ro]=Scrie un mesaj nou
+Name[ru]=Создать новое сообщение
+Name[si]=නව ලිපියක් ලියන්න
+Name[sk]=Nová e-mailová správa
+Name[sl]=Sestavi novo sporočilo
+Name[sq]=Shkruani mesazh të ri
+Name[sr]=Писање нове поруке
+Name[sv_SE]=Skriv ett nytt meddelande
+Name[ta_LK]=புதிய செய்தியை எழுதுக
+Name[tr]=Yeni ileti yaz
+Name[uk]=Написати нового листа
+Name[vi]=Viết thư mới
+Name[zh_CN]=编写新消息
+Name[zh_TW]=寫一封新訊息
+Exec=env UBUNTU_MENUPROXY=0 /usr/lib/thunderbird/thunderbird -compose
+
+[Desktop Action OpenAddressBook]
+Name=Open address book
+Name[ar]=افتح دفتر العناوين
+Name[ast]=Abrir llibreta de direiciones
+Name[be]=Адкрыць адрасную кнігу
+Name[bg]=Отваряне на адресник
+Name[br]=Digeriñ ur c'harned chomlec'hioù
+Name[ca]=Obre la llibreta d'adreces
+Name[cs]=Otevřít Adresář
+Name[da]=Åbn adressebog
+Name[de]=Adressbuch öffnen
+Name[el]=Άνοιγμα ευρετηρίου διευθύνσεων
+Name[es_AR]=Abrir libreta de direcciones
+Name[es_ES]=Abrir libreta de direcciones
+Name[et]=Ava aadressiraamat
+Name[eu]=Ireki helbide-liburua
+Name[fi]=Avaa osoitekirja
+Name[fr]=Ouvrir un carnet d'adresses
+Name[fy_NL]=Iepenje adresboek
+Name[ga_IE]=Oscail leabhar seoltaí
+Name[gd]=Fosgail leabhar-sheòlaidhean
+Name[gl]=Abrir a axenda de enderezos
+Name[he]=פתיחת ספר כתובות
+Name[hr]=Otvori adresar
+Name[hu]=Címjegyzék megnyitása
+Name[hy_AM]=Բացել Հասցեագիրքը
+Name[is]=Opna nafnaskrá
+Name[it]=Apri rubrica
+Name[ja]=アドレス帳を開く
+Name[ko]=주소록 열기
+Name[lt]=Atverti adresų knygą
+Name[nb_NO]=Åpne adressebok
+Name[nl]=Adresboek openen
+Name[nn_NO]=Opne adressebok
+Name[pl]=Książka adresowa
+Name[pt_BR]=Catálogo de endereços
+Name[pt_PT]=Abrir livro de endereços
+Name[rm]=Avrir il cudeschet d'adressas
+Name[ro]=Deschide agenda de contacte
+Name[ru]=Открыть адресную книгу
+Name[si]=ලිපින පොත විවෘත කරන්න
+Name[sk]=Otvoriť adresár
+Name[sl]=Odpri adressar
+Name[sq]=Hapni libër adresash
+Name[sr]=Отвори адресар
+Name[sv_SE]=Öppna adressboken
+Name[ta_LK]=முகவரி பத்தகத்தை திறக்க
+Name[tr]=Adres defterini aç
+Name[uk]=Відкрити адресну книгу
+Name[vi]=Mở sổ địa chỉ
+Name[zh_CN]=打开通讯录
+Name[zh_TW]=開啟通訊錄
+Exec=env UBUNTU_MENUPROXY=0 /usr/lib/thunderbird/thunderbird -addressbook
diff --git a/unity-menubar.patch b/unity-menubar.patch
new file mode 100644
index 000000000000..088026c5910d
--- /dev/null
+++ b/unity-menubar.patch
@@ -0,0 +1,5708 @@
+Index: firefox-52.0~b9+build2/mozilla/browser/base/content/browser-menubar.inc
+===================================================================
+--- firefox-52.0~b9+build2.orig/mozilla/browser/base/content/browser-menubar.inc
++++ firefox-52.0~b9+build2/mozilla/browser/base/content/browser-menubar.inc
+@@ -5,7 +5,11 @@
+
+ <menubar id="main-menubar"
+ onpopupshowing="if (event.target.parentNode.parentNode == this &amp;&amp;
++#ifdef MOZ_WIDGET_GTK
++ document.documentElement.getAttribute('shellshowingmenubar') != 'true')
++#else
+ !('@mozilla.org/widget/nativemenuservice;1' in Cc))
++#endif
+ this.setAttribute('openedwithkey',
+ event.target.parentNode.openedWithKey);"
+ style="border:0px;padding:0px;margin:0px;-moz-appearance:none">
+Index: firefox-52.0~b9+build2/mozilla/browser/components/places/content/places.xul
+===================================================================
+--- firefox-52.0~b9+build2.orig/mozilla/browser/components/places/content/places.xul
++++ firefox-52.0~b9+build2/mozilla/browser/components/places/content/places.xul
+@@ -157,7 +157,7 @@
+ <toolbarbutton type="menu" class="tabbable"
+ onpopupshowing="document.getElementById('placeContent').focus()"
+ #else
+- <menubar id="placesMenu">
++ <menubar id="placesMenu" _moz-menubarkeeplocal="true">
+ <menu accesskey="&organize.accesskey;" class="menu-iconic"
+ #endif
+ id="organizeButton" label="&organize.label;"
+Index: firefox-52.0~b9+build2/mozilla/toolkit/content/widgets/popup.xml
+===================================================================
+--- firefox-52.0~b9+build2.orig/mozilla/toolkit/content/widgets/popup.xml
++++ firefox-52.0~b9+build2/mozilla/toolkit/content/widgets/popup.xml
+@@ -25,8 +25,14 @@
+ </getter>
+ </property>
+
+- <property name="state" readonly="true"
+- onget="return this.popupBoxObject.popupState"/>
++ <property name="state" readonly="true">
++ <getter><![CDATA[
++ if (this.hasAttribute('_moz-menupopupstate'))
++ return this.getAttribute('_moz-menupopupstate');
++ else
++ return this.popupBoxObject.popupState;
++ ]]></getter>
++ </property>
+
+ <property name="triggerNode" readonly="true"
+ onget="return this.popupBoxObject.triggerNode"/>
+Index: firefox-52.0~b9+build2/mozilla/toolkit/content/xul.css
+===================================================================
+--- firefox-52.0~b9+build2.orig/mozilla/toolkit/content/xul.css
++++ firefox-52.0~b9+build2/mozilla/toolkit/content/xul.css
+@@ -307,6 +307,18 @@ toolbar[type="menubar"][autohide="true"]
+ }
+ %endif
+
++%ifdef MOZ_WIDGET_GTK
++window[shellshowingmenubar="true"] menubar {
++ display: none !important;
++}
++
++window[shellshowingmenubar="true"]
++toolbar[type="menubar"]:not([customizing="true"]) {
++ min-height: 0 !important;
++ border: 0 !important;
++}
++%endif
++
+ toolbarseparator {
+ -moz-binding: url("chrome://global/content/bindings/toolbar.xml#toolbardecoration");
+ }
+Index: firefox-52.0~b9+build2/mozilla/widget/gtk/nsDbusmenu.cpp
+===================================================================
+--- /dev/null
++++ firefox-52.0~b9+build2/mozilla/widget/gtk/nsDbusmenu.cpp
+@@ -0,0 +1,63 @@
++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:expandtab:shiftwidth=4:tabstop=4:
++ */
++/* 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/. */
++
++#include "nsDbusmenu.h"
++#include "prlink.h"
++#include "mozilla/ArrayUtils.h"
++
++#define FUNC(name, type, params) \
++nsDbusmenuFunctions::_##name##_fn nsDbusmenuFunctions::s_##name;
++DBUSMENU_GLIB_FUNCTIONS
++DBUSMENU_GTK_FUNCTIONS
++#undef FUNC
++
++static PRLibrary *gDbusmenuGlib = nullptr;
++static PRLibrary *gDbusmenuGtk = nullptr;
++
++typedef void (*nsDbusmenuFunc)();
++struct nsDbusmenuDynamicFunction {
++ const char *functionName;
++ nsDbusmenuFunc *function;
++};
++
++/* static */ nsresult
++nsDbusmenuFunctions::Init()
++{
++#define FUNC(name, type, params) \
++ { #name, (nsDbusmenuFunc *)&nsDbusmenuFunctions::s_##name },
++ static const nsDbusmenuDynamicFunction kDbusmenuGlibSymbols[] = {
++ DBUSMENU_GLIB_FUNCTIONS
++ };
++ static const nsDbusmenuDynamicFunction kDbusmenuGtkSymbols[] = {
++ DBUSMENU_GTK_FUNCTIONS
++ };
++
++#define LOAD_LIBRARY(symbol, name) \
++ if (!g##symbol) { \
++ g##symbol = PR_LoadLibrary(name); \
++ if (!g##symbol) { \
++ return NS_ERROR_FAILURE; \
++ } \
++ } \
++ for (uint32_t i = 0; i < mozilla::ArrayLength(k##symbol##Symbols); ++i) { \
++ *k##symbol##Symbols[i].function = \
++ PR_FindFunctionSymbol(g##symbol, k##symbol##Symbols[i].functionName); \
++ if (!*k##symbol##Symbols[i].function) { \
++ return NS_ERROR_FAILURE; \
++ } \
++ }
++
++ LOAD_LIBRARY(DbusmenuGlib, "libdbusmenu-glib.so.4")
++#if (MOZ_WIDGET_GTK == 3)
++ LOAD_LIBRARY(DbusmenuGtk, "libdbusmenu-gtk3.so.4")
++#else
++ LOAD_LIBRARY(DbusmenuGtk, "libdbusmenu-gtk.so.4")
++#endif
++#undef LOAD_LIBRARY
++
++ return NS_OK;
++}
+Index: firefox-52.0~b9+build2/mozilla/widget/gtk/nsDbusmenu.h
+===================================================================
+--- /dev/null
++++ firefox-52.0~b9+build2/mozilla/widget/gtk/nsDbusmenu.h
+@@ -0,0 +1,99 @@
++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:expandtab:shiftwidth=4:tabstop=4:
++ */
++/* 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/. */
++
++#ifndef __nsDbusmenu_h__
++#define __nsDbusmenu_h__
++
++#include "nsError.h"
++
++#include <glib.h>
++#include <gdk/gdk.h>
++
++#define DBUSMENU_GLIB_FUNCTIONS \
++ FUNC(dbusmenu_menuitem_child_add_position, gboolean, (DbusmenuMenuitem *mi, DbusmenuMenuitem *child, guint position)) \
++ FUNC(dbusmenu_menuitem_child_append, gboolean, (DbusmenuMenuitem *mi, DbusmenuMenuitem *child)) \
++ FUNC(dbusmenu_menuitem_child_delete, gboolean, (DbusmenuMenuitem *mi, DbusmenuMenuitem *child)) \
++ FUNC(dbusmenu_menuitem_get_children, GList*, (DbusmenuMenuitem *mi)) \
++ FUNC(dbusmenu_menuitem_new, DbusmenuMenuitem*, (void)) \
++ FUNC(dbusmenu_menuitem_property_get, const gchar*, (DbusmenuMenuitem *mi, const gchar *property)) \
++ FUNC(dbusmenu_menuitem_property_get_bool, gboolean, (DbusmenuMenuitem *mi, const gchar *property)) \
++ FUNC(dbusmenu_menuitem_property_remove, void, (DbusmenuMenuitem *mi, const gchar *property)) \
++ FUNC(dbusmenu_menuitem_property_set, gboolean, (DbusmenuMenuitem *mi, const gchar *property, const gchar *value)) \
++ FUNC(dbusmenu_menuitem_property_set_bool, gboolean, (DbusmenuMenuitem *mi, const gchar *property, const gboolean value)) \
++ FUNC(dbusmenu_menuitem_property_set_int, gboolean, (DbusmenuMenuitem *mi, const gchar *property, const gint value)) \
++ FUNC(dbusmenu_menuitem_show_to_user, void, (DbusmenuMenuitem *mi, guint timestamp)) \
++ FUNC(dbusmenu_menuitem_take_children, GList*, (DbusmenuMenuitem *mi)) \
++ FUNC(dbusmenu_server_new, DbusmenuServer*, (const gchar *object)) \
++ FUNC(dbusmenu_server_set_root, void, (DbusmenuServer *server, DbusmenuMenuitem *root)) \
++ FUNC(dbusmenu_server_set_status, void, (DbusmenuServer *server, DbusmenuStatus status))
++
++#define DBUSMENU_GTK_FUNCTIONS \
++ FUNC(dbusmenu_menuitem_property_set_image, gboolean, (DbusmenuMenuitem *menuitem, const gchar *property, const GdkPixbuf *data)) \
++ FUNC(dbusmenu_menuitem_property_set_shortcut, gboolean, (DbusmenuMenuitem *menuitem, guint key, GdkModifierType modifier))
++
++typedef struct _DbusmenuMenuitem DbusmenuMenuitem;
++typedef struct _DbusmenuServer DbusmenuServer;
++
++enum DbusmenuStatus {
++ DBUSMENU_STATUS_NORMAL,
++ DBUSMENU_STATUS_NOTICE
++};
++
++#define DBUSMENU_MENUITEM_CHILD_DISPLAY_SUBMENU "submenu"
++#define DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY "children-display"
++#define DBUSMENU_MENUITEM_PROP_ENABLED "enabled"
++#define DBUSMENU_MENUITEM_PROP_ICON_DATA "icon-data"
++#define DBUSMENU_MENUITEM_PROP_LABEL "label"
++#define DBUSMENU_MENUITEM_PROP_SHORTCUT "shortcut"
++#define DBUSMENU_MENUITEM_PROP_TYPE "type"
++#define DBUSMENU_MENUITEM_PROP_TOGGLE_STATE "toggle-state"
++#define DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE "toggle-type"
++#define DBUSMENU_MENUITEM_PROP_VISIBLE "visible"
++#define DBUSMENU_MENUITEM_SIGNAL_ABOUT_TO_SHOW "about-to-show"
++#define DBUSMENU_MENUITEM_SIGNAL_EVENT "event"
++#define DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED "item-activated"
++#define DBUSMENU_MENUITEM_TOGGLE_CHECK "checkmark"
++#define DBUSMENU_MENUITEM_TOGGLE_RADIO "radio"
++#define DBUSMENU_MENUITEM_TOGGLE_STATE_CHECKED 1
++#define DBUSMENU_MENUITEM_TOGGLE_STATE_UNCHECKED 0
++#define DBUSMENU_SERVER_PROP_DBUS_OBJECT "dbus-object"
++
++class nsDbusmenuFunctions
++{
++public:
++ static nsresult Init();
++
++#define FUNC(name, type, params) \
++ typedef type (*_##name##_fn) params; \
++ static _##name##_fn s_##name;
++ DBUSMENU_GLIB_FUNCTIONS
++ DBUSMENU_GTK_FUNCTIONS
++#undef FUNC
++
++};
++
++#define dbusmenu_menuitem_child_add_position nsDbusmenuFunctions::s_dbusmenu_menuitem_child_add_position
++#define dbusmenu_menuitem_child_append nsDbusmenuFunctions::s_dbusmenu_menuitem_child_append
++#define dbusmenu_menuitem_child_delete nsDbusmenuFunctions::s_dbusmenu_menuitem_child_delete
++#define dbusmenu_menuitem_get_children nsDbusmenuFunctions::s_dbusmenu_menuitem_get_children
++#define dbusmenu_menuitem_new nsDbusmenuFunctions::s_dbusmenu_menuitem_new
++#define dbusmenu_menuitem_property_get nsDbusmenuFunctions::s_dbusmenu_menuitem_property_get
++#define dbusmenu_menuitem_property_get_bool nsDbusmenuFunctions::s_dbusmenu_menuitem_property_get_bool
++#define dbusmenu_menuitem_property_remove nsDbusmenuFunctions::s_dbusmenu_menuitem_property_remove
++#define dbusmenu_menuitem_property_set nsDbusmenuFunctions::s_dbusmenu_menuitem_property_set
++#define dbusmenu_menuitem_property_set_bool nsDbusmenuFunctions::s_dbusmenu_menuitem_property_set_bool
++#define dbusmenu_menuitem_property_set_int nsDbusmenuFunctions::s_dbusmenu_menuitem_property_set_int
++#define dbusmenu_menuitem_show_to_user nsDbusmenuFunctions::s_dbusmenu_menuitem_show_to_user
++#define dbusmenu_menuitem_take_children nsDbusmenuFunctions::s_dbusmenu_menuitem_take_children
++#define dbusmenu_server_new nsDbusmenuFunctions::s_dbusmenu_server_new
++#define dbusmenu_server_set_root nsDbusmenuFunctions::s_dbusmenu_server_set_root
++#define dbusmenu_server_set_status nsDbusmenuFunctions::s_dbusmenu_server_set_status
++
++#define dbusmenu_menuitem_property_set_image nsDbusmenuFunctions::s_dbusmenu_menuitem_property_set_image
++#define dbusmenu_menuitem_property_set_shortcut nsDbusmenuFunctions::s_dbusmenu_menuitem_property_set_shortcut
++
++#endif /* __nsDbusmenu_h__ */
+Index: firefox-52.0~b9+build2/mozilla/widget/gtk/nsMenu.cpp
+===================================================================
+--- /dev/null
++++ firefox-52.0~b9+build2/mozilla/widget/gtk/nsMenu.cpp
+@@ -0,0 +1,868 @@
++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:expandtab:shiftwidth=4:tabstop=4:
++ */
++/* 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/. */
++
++#define _IMPL_NS_LAYOUT
++
++#include "mozilla/GuardObjects.h"
++#include "mozilla/MouseEvents.h"
++#include "mozilla/StyleSetHandleInlines.h"
++#include "nsAutoPtr.h"
++#include "nsBindingManager.h"
++#include "nsComponentManagerUtils.h"
++#include "nsContentUtils.h"
++#include "nsCSSValue.h"
++#include "nsGkAtoms.h"
++#include "nsGtkUtils.h"
++#include "nsIAtom.h"
++#include "nsIContent.h"
++#include "nsIDocument.h"
++#include "nsIPresShell.h"
++#include "nsIRunnable.h"
++#include "nsITimer.h"
++#include "nsString.h"
++#include "nsStyleContext.h"
++#include "nsStyleSet.h"
++#include "nsStyleStruct.h"
++#include "nsThreadUtils.h"
++#include "nsXBLBinding.h"
++#include "nsXBLService.h"
++
++#include "nsNativeMenuAtoms.h"
++#include "nsNativeMenuDocListener.h"
++
++#include <glib-object.h>
++
++#include "nsMenu.h"
++
++using namespace mozilla;
++
++class MOZ_STACK_CLASS nsMenuUpdateBatch
++{
++public:
++ nsMenuUpdateBatch(nsMenu *aMenu MOZ_GUARD_OBJECT_NOTIFIER_PARAM) :
++ mMenu(aMenu)
++ {
++ MOZ_GUARD_OBJECT_NOTIFIER_INIT;
++ mMenu->BeginUpdateBatchInternal();
++ }
++
++ ~nsMenuUpdateBatch()
++ {
++ mMenu->EndUpdateBatch();
++ }
++
++private:
++ MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
++ nsMenu *mMenu;
++};
++
++class nsSetAttrRunnableNoNotify : public Runnable
++{
++public:
++ nsSetAttrRunnableNoNotify(nsIContent *aContent, nsIAtom *aAttribute,
++ nsAString& aValue) :
++ mContent(aContent), mAttribute(aAttribute), mValue(aValue) { };
++
++ NS_IMETHODIMP Run()
++ {
++ return mContent->SetAttr(kNameSpaceID_None, mAttribute, mValue, false);
++ }
++
++private:
++ nsCOMPtr<nsIContent> mContent;
++ nsCOMPtr<nsIAtom> mAttribute;
++ nsAutoString mValue;
++};
++
++class nsUnsetAttrRunnableNoNotify : public Runnable
++{
++public:
++ nsUnsetAttrRunnableNoNotify(nsIContent *aContent, nsIAtom *aAttribute) :
++ mContent(aContent), mAttribute(aAttribute) { };
++
++ NS_IMETHODIMP Run()
++ {
++ return mContent->UnsetAttr(kNameSpaceID_None, mAttribute, false);
++ }
++
++private:
++ nsCOMPtr<nsIContent> mContent;
++ nsCOMPtr<nsIAtom> mAttribute;
++};
++
++static void
++AttachXBLBindings(nsIContent *aContent)
++{
++ nsIDocument *doc = aContent->OwnerDoc();
++ nsIPresShell *shell = doc->GetShell();
++ if (!shell) {
++ return;
++ }
++
++ RefPtr<nsStyleContext> sc =
++ shell->StyleSet()->ResolveStyleFor(aContent->AsElement(),
++ nullptr);
++ if (!sc) {
++ return;
++ }
++
++ const nsStyleDisplay* display = sc->StyleDisplay();
++ if (!display->mBinding) {
++ return;
++ }
++
++ nsXBLService* xbl = nsXBLService::GetInstance();
++ if (!xbl) {
++ return;
++ }
++
++ RefPtr<nsXBLBinding> binding;
++ bool dummy;
++ nsresult rv = xbl->LoadBindings(aContent, display->mBinding->GetURI(),
++ display->mBinding->mOriginPrincipal,
++ getter_AddRefs(binding), &dummy);
++ if ((NS_FAILED(rv) && rv != NS_ERROR_XBL_BLOCKED) || !binding) {
++ return;
++ }
++
++ doc->BindingManager()->AddToAttachedQueue(binding);
++}
++
++void
++nsMenu::SetPopupState(EPopupState aState)
++{
++ ClearFlags(((1U << NSMENU_NUMBER_OF_POPUPSTATE_BITS) - 1U) << NSMENU_NUMBER_OF_FLAGS);
++ SetFlags(aState << NSMENU_NUMBER_OF_FLAGS);
++
++ if (!mPopupContent) {
++ return;
++ }
++
++ nsAutoString state;
++ switch (aState) {
++ case ePopupState_Showing:
++ state.Assign(NS_LITERAL_STRING("showing"));
++ break;
++ case ePopupState_Open:
++ state.Assign(NS_LITERAL_STRING("open"));
++ break;
++ case ePopupState_Hiding:
++ state.Assign(NS_LITERAL_STRING("hiding"));
++ break;
++ default:
++ break;
++ }
++
++ if (nsContentUtils::IsSafeToRunScript()) {
++ if (state.IsEmpty()) {
++ mPopupContent->UnsetAttr(kNameSpaceID_None,
++ nsNativeMenuAtoms::_moz_menupopupstate,
++ false);
++ } else {
++ mPopupContent->SetAttr(kNameSpaceID_None,
++ nsNativeMenuAtoms::_moz_menupopupstate,
++ state, false);
++ }
++ } else {
++ nsCOMPtr<nsIRunnable> r;
++ if (state.IsEmpty()) {
++ r = new nsUnsetAttrRunnableNoNotify(
++ mPopupContent, nsNativeMenuAtoms::_moz_menupopupstate);
++ } else {
++ r = new nsSetAttrRunnableNoNotify(
++ mPopupContent, nsNativeMenuAtoms::_moz_menupopupstate,
++ state);
++ }
++ nsContentUtils::AddScriptRunner(r);
++ }
++}
++
++/* static */ void
++nsMenu::menu_event_cb(DbusmenuMenuitem *menu,
++ const gchar *name,
++ GVariant *value,
++ guint timestamp,
++ gpointer user_data)
++{
++ nsMenu *self = static_cast<nsMenu *>(user_data);
++
++ nsAutoCString event(name);
++
++ if (event.Equals(NS_LITERAL_CSTRING("closed"))) {
++ self->OnClose();
++ return;
++ }
++
++ if (event.Equals(NS_LITERAL_CSTRING("opened"))) {
++ self->OnOpen();
++ return;
++ }
++}
++
++void
++nsMenu::MaybeAddPlaceholderItem()
++{
++ NS_ASSERTION(!IsInUpdateBatch(),
++ "Shouldn't be modifying the native menu structure now");
++
++ GList *children = dbusmenu_menuitem_get_children(GetNativeData());
++ if (!children) {
++ NS_ASSERTION(!HasPlaceholderItem(), "Huh?");
++
++ DbusmenuMenuitem *ph = dbusmenu_menuitem_new();
++ if (!ph) {
++ return;
++ }
++
++ dbusmenu_menuitem_property_set_bool(
++ ph, DBUSMENU_MENUITEM_PROP_VISIBLE, false);
++
++ if (!dbusmenu_menuitem_child_append(GetNativeData(), ph)) {
++ NS_WARNING("Failed to create placeholder item");
++ g_object_unref(ph);
++ return;
++ }
++
++ g_object_unref(ph);
++
++ SetHasPlaceholderItem(true);
++ }
++}
++
++bool
++nsMenu::EnsureNoPlaceholderItem()
++{
++ NS_ASSERTION(!IsInUpdateBatch(),
++ "Shouldn't be modifying the native menu structure now");
++
++ if (HasPlaceholderItem()) {
++ GList *children = dbusmenu_menuitem_get_children(GetNativeData());
++
++ NS_ASSERTION(g_list_length(children) == 1,
++ "Unexpected number of children in native menu (should be 1!)");
++
++ SetHasPlaceholderItem(false);
++
++ if (!children) {
++ return true;
++ }
++
++ if (!dbusmenu_menuitem_child_delete(
++ GetNativeData(), static_cast<DbusmenuMenuitem *>(children->data))) {
++ NS_ERROR("Failed to remove placeholder item");
++ return false;
++ }
++ }
++
++ return true;
++}
++
++static void
++DispatchMouseEvent(nsIContent *aTarget, mozilla::EventMessage aMsg)
++{
++ if (!aTarget) {
++ return;
++ }
++
++ WidgetMouseEvent event(true, aMsg, nullptr, WidgetMouseEvent::eReal);
++ aTarget->DispatchDOMEvent(&event, nullptr, nullptr, nullptr);
++}
++
++void
++nsMenu::OnOpen()
++{
++ if (NeedsRebuild()) {
++ Build();
++ }
++
++ nsWeakMenuObject<nsMenu> self(this);
++ nsCOMPtr<nsIContent> origPopupContent(mPopupContent);
++ {
++ nsNativeMenuAutoUpdateBatch batch;
++
++ SetPopupState(ePopupState_Showing);
++ DispatchMouseEvent(mPopupContent, eXULPopupShowing);
++
++ ContentNode()->SetAttr(kNameSpaceID_None, nsGkAtoms::open,
++ NS_LITERAL_STRING("true"), true);
++ }
++
++ if (!self) {
++ // We were deleted!
++ return;
++ }
++
++ // I guess that the popup could have changed
++ if (origPopupContent != mPopupContent) {
++ return;
++ }
++
++ nsNativeMenuAutoUpdateBatch batch;
++
++ size_t count = ChildCount();
++ for (size_t i = 0; i < count; ++i) {
++ ChildAt(i)->ContainerIsOpening();
++ }
++
++ SetPopupState(ePopupState_Open);
++ DispatchMouseEvent(mPopupContent, eXULPopupShown);
++}
++
++void
++nsMenu::Build()
++{
++ nsMenuUpdateBatch batch(this);
++
++ SetNeedsRebuild(false);
++
++ while (ChildCount() > 0) {
++ RemoveChildAt(0);
++ }
++
++ InitializePopup();
++
++ if (!mPopupContent) {
++ return;
++ }
++
++ uint32_t count = mPopupContent->GetChildCount();
++ for (uint32_t i = 0; i < count; ++i) {
++ nsIContent *childContent = mPopupContent->GetChildAt(i);
++
++ nsresult rv;
++ nsMenuObject *child = CreateChild(childContent, &rv);
++
++ if (child) {
++ rv = AppendChild(child);
++ }
++
++ if (NS_FAILED(rv)) {
++ NS_ERROR("Menu build failed");
++ SetNeedsRebuild(true);
++ return;
++ }
++ }
++}
++
++void
++nsMenu::InitializeNativeData()
++{
++ // Dbusmenu provides an "about-to-show" signal, and also "opened" and
++ // "closed" events. However, Unity is the only thing that sends
++ // both "about-to-show" and "opened" events. Unity 2D and the HUD only
++ // send "opened" events, so we ignore "about-to-show" (I don't think
++ // there's any real difference between them anyway).
++ // To complicate things, there are certain conditions where we don't
++ // get a "closed" event, so we need to be able to handle this :/
++ g_signal_connect(G_OBJECT(GetNativeData()), "event",
++ G_CALLBACK(menu_event_cb), this);
++
++ UpdateLabel();
++ UpdateSensitivity();
++
++ SetNeedsRebuild(true);
++ MaybeAddPlaceholderItem();
++
++ AttachXBLBindings(ContentNode());
++}
++
++void
++nsMenu::Update(nsStyleContext *aStyleContext)
++{
++ UpdateVisibility(aStyleContext);
++ UpdateIcon(aStyleContext);
++}
++
++void
++nsMenu::InitializePopup()
++{
++ nsCOMPtr<nsIContent> oldPopupContent;
++ oldPopupContent.swap(mPopupContent);
++
++ for (uint32_t i = 0; i < ContentNode()->GetChildCount(); ++i) {
++ nsIContent *child = ContentNode()->GetChildAt(i);
++
++ int32_t dummy;
++ nsCOMPtr<nsIAtom> tag = child->OwnerDoc()->BindingManager()->ResolveTag(child, &dummy);
++ if (tag == nsGkAtoms::menupopup) {
++ mPopupContent = child;
++ break;
++ }
++ }
++
++ if (oldPopupContent == mPopupContent) {
++ return;
++ }
++
++ // The popup has changed
++
++ if (oldPopupContent) {
++ DocListener()->UnregisterForContentChanges(oldPopupContent);
++ }
++
++ SetPopupState(ePopupState_Closed);
++
++ if (!mPopupContent) {
++ return;
++ }
++
++ AttachXBLBindings(mPopupContent);
++
++ DocListener()->RegisterForContentChanges(mPopupContent, this);
++}
++
++void
++nsMenu::BeginUpdateBatchInternal()
++{
++ NS_ASSERTION(!IsInUpdateBatch(), "Already in an update batch!");
++
++ SetIsInUpdateBatch(true);
++ SetDidStructureMutate(false);
++}
++
++nsresult
++nsMenu::RemoveChildAt(size_t aIndex)
++{
++ NS_ASSERTION(IsInUpdateBatch() || !HasPlaceholderItem(),
++ "Shouldn't have a placeholder menuitem");
++
++ SetDidStructureMutate(true);
++
++ nsresult rv = nsMenuContainer::RemoveChildAt(aIndex, !IsInUpdateBatch());
++
++ if (!IsInUpdateBatch()) {
++ MaybeAddPlaceholderItem();
++ }
++
++ return rv;
++}
++
++nsresult
++nsMenu::RemoveChild(nsIContent *aChild)
++{
++ size_t index = IndexOf(aChild);
++ if (index == NoIndex) {
++ return NS_ERROR_INVALID_ARG;
++ }
++
++ return RemoveChildAt(index);
++}
++
++nsresult
++nsMenu::InsertChildAfter(nsMenuObject *aChild, nsIContent *aPrevSibling)
++{
++ if (!IsInUpdateBatch() && !EnsureNoPlaceholderItem()) {
++ return NS_ERROR_FAILURE;
++ }
++
++ SetDidStructureMutate(true);
++
++ return nsMenuContainer::InsertChildAfter(aChild, aPrevSibling,
++ !IsInUpdateBatch());
++}
++
++nsresult
++nsMenu::AppendChild(nsMenuObject *aChild)
++{
++ if (!IsInUpdateBatch() && !EnsureNoPlaceholderItem()) {
++ return NS_ERROR_FAILURE;
++ }
++
++ SetDidStructureMutate(true);
++
++ return nsMenuContainer::AppendChild(aChild, !IsInUpdateBatch());
++}
++
++bool
++nsMenu::CanOpen() const
++{
++ bool isVisible = dbusmenu_menuitem_property_get_bool(GetNativeData(),
++ DBUSMENU_MENUITEM_PROP_VISIBLE);
++ bool isDisabled = ContentNode()->AttrValueIs(kNameSpaceID_None,
++ nsGkAtoms::disabled,
++ nsGkAtoms::_true,
++ eCaseMatters);
++
++ return (isVisible && !isDisabled);
++}
++
++nsMenuObject::PropertyFlags
++nsMenu::SupportedProperties() const
++{
++ return static_cast<nsMenuObject::PropertyFlags>(
++ nsMenuObject::ePropLabel |
++ nsMenuObject::ePropEnabled |
++ nsMenuObject::ePropVisible |
++ nsMenuObject::ePropIconData |
++ nsMenuObject::ePropChildDisplay
++ );
++}
++
++nsMenu::nsMenu() :
++ nsMenuContainer()
++{
++ MOZ_COUNT_CTOR(nsMenu);
++}
++
++nsMenu::~nsMenu()
++{
++ if (IsInUpdateBatch()) {
++ EndUpdateBatch();
++ }
++
++ // Although nsTArray will take care of this in its destructor,
++ // we have to manually ensure children are removed from our native menu
++ // item, just in case our parent recycles us
++ while (ChildCount() > 0) {
++ RemoveChildAt(0);
++ }
++
++ EnsureNoPlaceholderItem();
++
++ if (DocListener() && mPopupContent) {
++ DocListener()->UnregisterForContentChanges(mPopupContent);
++ }
++
++ if (GetNativeData()) {
++ g_signal_handlers_disconnect_by_func(GetNativeData(),
++ FuncToGpointer(menu_event_cb),
++ this);
++ }
++
++ MOZ_COUNT_DTOR(nsMenu);
++}
++
++/* static */ nsMenuObject*
++nsMenu::Create(nsMenuContainer *aParent, nsIContent *aContent)
++{
++ nsAutoPtr<nsMenu> menu(new nsMenu());
++ if (NS_FAILED(menu->Init(aParent, aContent))) {
++ return nullptr;
++ }
++
++ return menu.forget();
++}
++
++static void
++DoOpen(nsITimer *aTimer, void *aClosure)
++{
++ nsAutoWeakMenuObject<nsMenu> weakMenu(
++ static_cast<nsWeakMenuObject<nsMenu> *>(aClosure));
++
++ if (weakMenu) {
++ dbusmenu_menuitem_show_to_user(weakMenu->GetNativeData(), 0);
++ }
++
++ NS_RELEASE(aTimer);
++}
++
++nsMenuObject::EType
++nsMenu::Type() const
++{
++ return nsMenuObject::eType_Menu;
++}
++
++bool
++nsMenu::IsBeingDisplayed() const
++{
++ return PopupState() == ePopupState_Open;
++}
++
++bool
++nsMenu::NeedsRebuild() const
++{
++ return HasFlags(eFlag_NeedsRebuild);
++}
++
++void
++nsMenu::OpenMenu()
++{
++ if (!CanOpen()) {
++ return;
++ }
++
++ // Here, we synchronously fire popupshowing and popupshown events and then
++ // open the menu after a short delay. This allows the menu to refresh before
++ // it's shown, and avoids an issue where keyboard focus is not on the first
++ // item of the history menu in Firefox when opening it with the keyboard,
++ // because extra items to appear at the top of the menu
++
++ OnOpen();
++
++ nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID);
++ if (!timer) {
++ return;
++ }
++
++ nsAutoWeakMenuObject<nsMenu> weakMenu(this);
++
++ if (NS_FAILED(timer->InitWithFuncCallback(DoOpen, weakMenu.getWeakPtr(),
++ 100, nsITimer::TYPE_ONE_SHOT))) {
++ return;
++ }
++
++ timer.forget();
++ weakMenu.forget();
++}
++
++void
++nsMenu::OnClose()
++{
++ if (PopupState() == ePopupState_Closed) {
++ return;
++ }
++
++ // We do this to avoid mutating our view of the menu until
++ // after we have finished
++ nsNativeMenuAutoUpdateBatch batch;
++
++ SetPopupState(ePopupState_Hiding);
++ DispatchMouseEvent(mPopupContent, eXULPopupHiding);
++
++ // Sigh, make sure all of our descendants are closed, as we don't
++ // always get closed events for submenus when scrubbing quickly through
++ // the menu
++ size_t count = ChildCount();
++ for (size_t i = 0; i < count; ++i) {
++ if (ChildAt(i)->Type() == nsMenuObject::eType_Menu) {
++ static_cast<nsMenu *>(ChildAt(i))->OnClose();
++ }
++ }
++
++ SetPopupState(ePopupState_Closed);
++ DispatchMouseEvent(mPopupContent, eXULPopupHidden);
++
++ ContentNode()->UnsetAttr(kNameSpaceID_None, nsGkAtoms::open, true);
++}
++
++void
++nsMenu::OnAttributeChanged(nsIContent *aContent, nsIAtom *aAttribute)
++{
++ NS_ASSERTION(aContent == ContentNode() || aContent == mPopupContent,
++ "Received an event that wasn't meant for us!");
++
++ if (aAttribute == nsGkAtoms::open) {
++ return;
++ }
++
++ if (Parent()->NeedsRebuild()) {
++ return;
++ }
++
++ if (aContent == ContentNode()) {
++ if (aAttribute == nsGkAtoms::disabled) {
++ UpdateSensitivity();
++ } else if (aAttribute == nsGkAtoms::label ||
++ aAttribute == nsGkAtoms::accesskey ||
++ aAttribute == nsGkAtoms::crop) {
++ UpdateLabel();
++ }
++ }
++
++ if (!Parent()->IsBeingDisplayed() || aContent != ContentNode()) {
++ return;
++ }
++
++ if (aAttribute == nsGkAtoms::hidden ||
++ aAttribute == nsGkAtoms::collapsed) {
++ RefPtr<nsStyleContext> sc = GetStyleContext();
++ UpdateVisibility(sc);
++ } else if (aAttribute == nsGkAtoms::image) {
++ RefPtr<nsStyleContext> sc = GetStyleContext();
++ UpdateIcon(sc);
++ }
++}
++
++void
++nsMenu::OnContentInserted(nsIContent *aContainer, nsIContent *aChild,
++ nsIContent *aPrevSibling)
++{
++ NS_ASSERTION(aContainer == ContentNode() || aContainer == mPopupContent,
++ "Received an event that wasn't meant for us!");
++
++ if (NeedsRebuild()) {
++ return;
++ }
++
++ if (PopupState() == ePopupState_Closed) {
++ SetNeedsRebuild(true);
++ return;
++ }
++
++ if (aContainer == mPopupContent) {
++ nsresult rv;
++ nsMenuObject *child = CreateChild(aChild, &rv);
++
++ if (child) {
++ rv = InsertChildAfter(child, aPrevSibling);
++ }
++ if (NS_FAILED(rv)) {
++ NS_ERROR("OnContentInserted() failed");
++ SetNeedsRebuild(true);
++ }
++ } else {
++ Build();
++ }
++}
++
++void
++nsMenu::OnContentRemoved(nsIContent *aContainer, nsIContent *aChild)
++{
++ NS_ASSERTION(aContainer == ContentNode() || aContainer == mPopupContent,
++ "Received an event that wasn't meant for us!");
++
++ if (NeedsRebuild()) {
++ return;
++ }
++
++ if (PopupState() == ePopupState_Closed) {
++ SetNeedsRebuild(true);
++ return;
++ }
++
++ if (aContainer == mPopupContent) {
++ if (NS_FAILED(RemoveChild(aChild))) {
++ NS_ERROR("OnContentRemoved() failed");
++ SetNeedsRebuild(true);
++ }
++ } else {
++ Build();
++ }
++}
++
++/*
++ * Some menus (eg, the History menu in Firefox) refresh themselves on
++ * opening by removing all children and then re-adding new ones. As this
++ * happens whilst the menu is opening in Unity, it causes some flickering
++ * as the menu popup is resized multiple times. To avoid this, we try to
++ * reuse native menu items when the menu structure changes during a
++ * batched update. If we can handle menu structure changes from Gecko
++ * just by updating properties of native menu items (rather than destroying
++ * and creating new ones), then we eliminate any flickering that occurs as
++ * the menu is opened. To do this, we don't modify any native menu items
++ * until the end of the update batch.
++ */
++
++void
++nsMenu::BeginUpdateBatch(nsIContent *aContent)
++{
++ NS_ASSERTION(aContent == ContentNode() || aContent == mPopupContent,
++ "Received an event that wasn't meant for us!");
++
++ if (aContent == mPopupContent) {
++ BeginUpdateBatchInternal();
++ }
++}
++
++void
++nsMenu::EndUpdateBatch()
++{
++ NS_ASSERTION(IsInUpdateBatch(), "Not in an update batch");
++
++ SetIsInUpdateBatch(false);
++
++ /* Optimize for the case where we only had attribute changes */
++ if (!DidStructureMutate()) {
++ return;
++ }
++
++ if (!EnsureNoPlaceholderItem()) {
++ SetNeedsRebuild(true);
++ return;
++ }
++
++ GList *nextNativeChild = dbusmenu_menuitem_get_children(GetNativeData());
++ DbusmenuMenuitem *nextOwnedNativeChild = nullptr;
++
++ size_t count = ChildCount();
++
++ // Find the first native menu item that is `owned` by a corresponding
++ // Gecko menuitem
++ for (size_t i = 0; i < count; ++i) {
++ if (ChildAt(i)->GetNativeData()) {
++ nextOwnedNativeChild = ChildAt(i)->GetNativeData();
++ break;
++ }
++ }
++
++ // Now iterate over all Gecko menuitems
++ for (size_t i = 0; i < count; ++i) {
++ nsMenuObject *child = ChildAt(i);
++
++ if (child->GetNativeData()) {
++ // This child already has a corresponding native menuitem.
++ // Remove all preceding orphaned native items. At this point, we
++ // modify the native menu structure.
++ while (nextNativeChild &&
++ nextNativeChild->data != nextOwnedNativeChild) {
++
++ DbusmenuMenuitem *data =
++ static_cast<DbusmenuMenuitem *>(nextNativeChild->data);
++ nextNativeChild = nextNativeChild->next;
++
++ if (!dbusmenu_menuitem_child_delete(GetNativeData(), data)) {
++ NS_ERROR("Failed to remove orphaned native item from menu");
++ SetNeedsRebuild(true);
++ return;
++ }
++ }
++
++ if (nextNativeChild) {
++ nextNativeChild = nextNativeChild->next;
++ }
++
++ // Now find the next native menu item that is `owned`
++ nextOwnedNativeChild = nullptr;
++ for (size_t j = i + 1; j < count; ++j) {
++ if (ChildAt(j)->GetNativeData()) {
++ nextOwnedNativeChild = ChildAt(j)->GetNativeData();
++ break;
++ }
++ }
++ } else {
++ // This child is new, and doesn't have a native menu item. Find one!
++ if (nextNativeChild &&
++ nextNativeChild->data != nextOwnedNativeChild) {
++
++ DbusmenuMenuitem *data =
++ static_cast<DbusmenuMenuitem *>(nextNativeChild->data);
++
++ if (NS_SUCCEEDED(child->AdoptNativeData(data))) {
++ nextNativeChild = nextNativeChild->next;
++ }
++ }
++
++ // There wasn't a suitable one available, so create a new one.
++ // At this point, we modify the native menu structure.
++ if (!child->GetNativeData()) {
++ child->CreateNativeData();
++ if (!dbusmenu_menuitem_child_add_position(GetNativeData(),
++ child->GetNativeData(),
++ i)) {
++ NS_ERROR("Failed to add new native item");
++ SetNeedsRebuild(true);
++ return;
++ }
++ }
++ }
++ }
++
++ while (nextNativeChild) {
++
++ DbusmenuMenuitem *data =
++ static_cast<DbusmenuMenuitem *>(nextNativeChild->data);
++ nextNativeChild = nextNativeChild->next;
++
++ if (!dbusmenu_menuitem_child_delete(GetNativeData(), data)) {
++ NS_ERROR("Failed to remove orphaned native item from menu");
++ SetNeedsRebuild(true);
++ return;
++ }
++ }
++
++ MaybeAddPlaceholderItem();
++}
+Index: firefox-52.0~b9+build2/mozilla/widget/gtk/nsMenu.h
+===================================================================
+--- /dev/null
++++ firefox-52.0~b9+build2/mozilla/widget/gtk/nsMenu.h
+@@ -0,0 +1,166 @@
++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:expandtab:shiftwidth=4:tabstop=4:
++ */
++/* 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/. */
++
++#ifndef __nsMenu_h__
++#define __nsMenu_h__
++
++#include "mozilla/Attributes.h"
++#include "nsCOMPtr.h"
++
++#include "nsDbusmenu.h"
++#include "nsMenuContainer.h"
++#include "nsMenuObject.h"
++
++#include <glib.h>
++
++class nsIAtom;
++class nsIContent;
++class nsStyleContext;
++
++#define NSMENU_NUMBER_OF_POPUPSTATE_BITS 2U
++#define NSMENU_NUMBER_OF_FLAGS 4U
++
++// This class represents a menu
++class nsMenu final : public nsMenuContainer
++{
++public:
++ ~nsMenu();
++
++ static nsMenuObject* Create(nsMenuContainer *aParent,
++ nsIContent *aContent);
++
++ nsMenuObject::EType Type() const;
++
++ bool IsBeingDisplayed() const;
++ bool NeedsRebuild() const;
++
++ // Tell the desktop shell to display this menu
++ void OpenMenu();
++
++ // Normally called via the shell, but it's public so that child
++ // menuitems can do the shells work. Sigh....
++ void OnClose();
++
++ void OnAttributeChanged(nsIContent *aContent, nsIAtom *aAttribute);
++ void OnContentInserted(nsIContent *aContainer, nsIContent *aChild,
++ nsIContent *aPrevSibling);
++ void OnContentRemoved(nsIContent *aContainer, nsIContent *aChild);
++ void BeginUpdateBatch(nsIContent *aContent);
++ void EndUpdateBatch();
++
++private:
++ friend class nsMenuUpdateBatch;
++
++ enum {
++ // This menu needs rebuilding the next time it is opened
++ eFlag_NeedsRebuild = 1 << 0,
++
++ // This menu contains a placeholder
++ eFlag_HasPlaceholderItem = 1 << 1,
++
++ // This menu is currently receiving a batch of updates, and
++ // the native structure should not be modified
++ eFlag_InUpdateBatch = 1 << 2,
++
++ // Children were added to / removed from this menu (only valid
++ // when eFlag_InUpdateBatch is set)
++ eFlag_StructureMutated = 1 << 3
++ };
++
++ enum EPopupState {
++ ePopupState_Closed,
++ ePopupState_Showing,
++ ePopupState_Open,
++ ePopupState_Hiding
++ };
++
++ nsMenu();
++
++ void SetNeedsRebuild(bool aValue)
++ {
++ if (aValue) {
++ SetFlags(eFlag_NeedsRebuild);
++ } else {
++ ClearFlags(eFlag_NeedsRebuild);
++ }
++ }
++ bool HasPlaceholderItem() const
++ {
++ return HasFlags(eFlag_HasPlaceholderItem);
++ }
++ void SetHasPlaceholderItem(bool aValue)
++ {
++ if (aValue) {
++ SetFlags(eFlag_HasPlaceholderItem);
++ } else {
++ ClearFlags(eFlag_HasPlaceholderItem);
++ }
++ }
++
++ bool IsInUpdateBatch() const
++ {
++ return HasFlags(eFlag_InUpdateBatch);
++ }
++ void SetIsInUpdateBatch(bool aValue)
++ {
++ if (aValue) {
++ SetFlags(eFlag_InUpdateBatch);
++ } else {
++ ClearFlags(eFlag_InUpdateBatch);
++ }
++ }
++
++ bool DidStructureMutate() const
++ {
++ return HasFlags(eFlag_StructureMutated);
++ }
++ void SetDidStructureMutate(bool aValue)
++ {
++ if (aValue) {
++ SetFlags(eFlag_StructureMutated);
++ } else {
++ ClearFlags(eFlag_StructureMutated);
++ }
++ }
++
++ EPopupState PopupState() const
++ {
++ return static_cast<EPopupState>(
++ (GetFlags() &
++ (((1U << NSMENU_NUMBER_OF_POPUPSTATE_BITS) - 1U)
++ << NSMENU_NUMBER_OF_FLAGS)) >> NSMENU_NUMBER_OF_FLAGS);
++ };
++ void SetPopupState(EPopupState aState);
++
++ static void menu_event_cb(DbusmenuMenuitem *menu,
++ const gchar *name,
++ GVariant *value,
++ guint timestamp,
++ gpointer user_data);
++
++ // We add a placeholder item to empty menus so that Unity actually treats
++ // us as a proper menu, rather than a menuitem without a submenu
++ void MaybeAddPlaceholderItem();
++ bool EnsureNoPlaceholderItem();
++
++ void OnOpen();
++ void Build();
++ void InitializeNativeData();
++ void Update(nsStyleContext *aStyleContext);
++ void InitializePopup();
++ void BeginUpdateBatchInternal();
++ nsresult RemoveChildAt(size_t aIndex);
++ nsresult RemoveChild(nsIContent *aChild);
++ nsresult InsertChildAfter(nsMenuObject *aChild, nsIContent *aPrevSibling);
++ nsresult AppendChild(nsMenuObject *aChild);
++ bool CanOpen() const;
++ nsMenuObject::PropertyFlags SupportedProperties() const;
++
++ nsCOMPtr<nsIContent> mPopupContent;
++};
++
++#endif /* __nsMenu_h__ */
+Index: firefox-52.0~b9+build2/mozilla/widget/gtk/nsMenuBar.cpp
+===================================================================
+--- /dev/null
++++ firefox-52.0~b9+build2/mozilla/widget/gtk/nsMenuBar.cpp
+@@ -0,0 +1,545 @@
++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:expandtab:shiftwidth=4:tabstop=4:
++ */
++/* 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/. */
++
++#include "mozilla/DebugOnly.h"
++#include "mozilla/dom/Element.h"
++#include "mozilla/Preferences.h"
++#include "nsAutoPtr.h"
++#include "nsIDocument.h"
++#include "nsIDOMEvent.h"
++#include "nsIDOMEventListener.h"
++#include "nsIDOMEventTarget.h"
++#include "nsIDOMKeyEvent.h"
++#include "nsIWidget.h"
++#include "nsTArray.h"
++#include "nsUnicharUtils.h"
++
++#include "nsMenu.h"
++#include "nsNativeMenuAtoms.h"
++#include "nsNativeMenuService.h"
++
++#include <gdk/gdk.h>
++#include <gdk/gdkx.h>
++#include <glib.h>
++#include <glib-object.h>
++
++#include "nsMenuBar.h"
++
++using namespace mozilla;
++
++class nsMenuBarDocEventListener final : public nsIDOMEventListener
++{
++public:
++ NS_DECL_ISUPPORTS
++ NS_DECL_NSIDOMEVENTLISTENER
++
++ nsMenuBarDocEventListener(nsMenuBar *aOwner) : mOwner(aOwner) { };
++
++private:
++ ~nsMenuBarDocEventListener() { };
++
++ nsMenuBar *mOwner;
++};
++
++NS_IMPL_ISUPPORTS(nsMenuBarDocEventListener, nsIDOMEventListener)
++
++NS_IMETHODIMP
++nsMenuBarDocEventListener::HandleEvent(nsIDOMEvent *aEvent)
++{
++ nsAutoString type;
++ nsresult rv = aEvent->GetType(type);
++ if (NS_FAILED(rv)) {
++ NS_WARNING("Failed to determine event type");
++ return rv;
++ }
++
++ if (type.Equals(NS_LITERAL_STRING("focus"))) {
++ mOwner->Focus();
++ } else if (type.Equals(NS_LITERAL_STRING("blur"))) {
++ mOwner->Blur();
++ } else if (type.Equals(NS_LITERAL_STRING("keypress"))) {
++ rv = mOwner->Keypress(aEvent);
++ } else if (type.Equals(NS_LITERAL_STRING("keydown"))) {
++ rv = mOwner->KeyDown(aEvent);
++ } else if (type.Equals(NS_LITERAL_STRING("keyup"))) {
++ rv = mOwner->KeyUp(aEvent);
++ }
++
++ return rv;
++}
++
++void
++nsMenuBar::Build()
++{
++ uint32_t count = ContentNode()->GetChildCount();
++ for (uint32_t i = 0; i < count; ++i) {
++ nsIContent *childContent = ContentNode()->GetChildAt(i);
++
++ nsresult rv;
++ nsMenuObject *child = CreateChild(childContent, &rv);
++
++ if (child) {
++ rv = AppendChild(child);
++ }
++
++ if (NS_FAILED(rv)) {
++ NS_ERROR("Failed to build menubar");
++ return;
++ }
++ }
++}
++
++void
++nsMenuBar::DisconnectDocumentEventListeners()
++{
++ mDocument->RemoveEventListener(NS_LITERAL_STRING("focus"),
++ mEventListener,
++ true);
++ mDocument->RemoveEventListener(NS_LITERAL_STRING("blur"),
++ mEventListener,
++ true);
++ mDocument->RemoveEventListener(NS_LITERAL_STRING("keypress"),
++ mEventListener,
++ false);
++ mDocument->RemoveEventListener(NS_LITERAL_STRING("keydown"),
++ mEventListener,
++ false);
++ mDocument->RemoveEventListener(NS_LITERAL_STRING("keyup"),
++ mEventListener,
++ false);
++}
++
++void
++nsMenuBar::SetShellShowingMenuBar(bool aShowing)
++{
++ ContentNode()->OwnerDoc()->GetRootElement()->SetAttr(
++ kNameSpaceID_None, nsNativeMenuAtoms::shellshowingmenubar,
++ aShowing ? NS_LITERAL_STRING("true") : NS_LITERAL_STRING("false"),
++ true);
++}
++
++void
++nsMenuBar::Focus()
++{
++ ContentNode()->SetAttr(kNameSpaceID_None, nsNativeMenuAtoms::openedwithkey,
++ NS_LITERAL_STRING("false"), true);
++}
++
++void
++nsMenuBar::Blur()
++{
++ // We do this here in case we lose focus before getting the
++ // keyup event, which leaves the menubar state looking like
++ // the alt key is stuck down
++ dbusmenu_server_set_status(mServer, DBUSMENU_STATUS_NORMAL);
++}
++
++static bool
++ShouldHandleKeyEvent(nsIDOMEvent *aEvent)
++{
++ bool handled, trusted = false;
++ aEvent->GetPreventDefault(&handled);
++ aEvent->GetIsTrusted(&trusted);
++
++ if (handled || !trusted) {
++ return false;
++ }
++
++ return true;
++}
++
++nsMenuBar::ModifierFlags
++nsMenuBar::GetModifiersFromEvent(nsIDOMKeyEvent *aEvent)
++{
++ ModifierFlags modifiers = static_cast<ModifierFlags>(0);
++ bool modifier;
++
++ aEvent->GetAltKey(&modifier);
++ if (modifier) {
++ modifiers = static_cast<ModifierFlags>(modifiers | eModifierAlt);
++ }
++
++ aEvent->GetShiftKey(&modifier);
++ if (modifier) {
++ modifiers = static_cast<ModifierFlags>(modifiers | eModifierShift);
++ }
++
++ aEvent->GetCtrlKey(&modifier);
++ if (modifier) {
++ modifiers = static_cast<ModifierFlags>(modifiers | eModifierCtrl);
++ }
++
++ aEvent->GetMetaKey(&modifier);
++ if (modifier) {
++ modifiers = static_cast<ModifierFlags>(modifiers | eModifierMeta);
++ }
++
++ return modifiers;
++}
++
++nsresult
++nsMenuBar::Keypress(nsIDOMEvent *aEvent)
++{
++ if (!ShouldHandleKeyEvent(aEvent)) {
++ return NS_OK;
++ }
++
++ nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aEvent);
++ if (!keyEvent) {
++ return NS_OK;
++ }
++
++ ModifierFlags modifiers = GetModifiersFromEvent(keyEvent);
++ if (((modifiers & mAccessKeyMask) == 0) ||
++ ((modifiers & ~mAccessKeyMask) != 0)) {
++ return NS_OK;
++ }
++
++ uint32_t charCode;
++ keyEvent->GetCharCode(&charCode);
++ if (charCode == 0) {
++ return NS_OK;
++ }
++
++ char16_t ch = char16_t(charCode);
++ char16_t chl = ToLowerCase(ch);
++ char16_t chu = ToUpperCase(ch);
++
++ nsMenuObject *found = nullptr;
++ uint32_t count = ChildCount();
++ for (uint32_t i = 0; i < count; ++i) {
++ nsAutoString accesskey;
++ ChildAt(i)->ContentNode()->GetAttr(kNameSpaceID_None,
++ nsGkAtoms::accesskey,
++ accesskey);
++ const nsAutoString::char_type *key = accesskey.BeginReading();
++ if (*key == chu || *key == chl) {
++ found = ChildAt(i);
++ break;
++ }
++ }
++
++ if (!found || found->Type() != nsMenuObject::eType_Menu) {
++ return NS_OK;
++ }
++
++ ContentNode()->SetAttr(kNameSpaceID_None, nsNativeMenuAtoms::openedwithkey,
++ NS_LITERAL_STRING("true"), true);
++ static_cast<nsMenu *>(found)->OpenMenu();
++
++ aEvent->StopPropagation();
++ aEvent->PreventDefault();
++
++ return NS_OK;
++}
++
++nsresult
++nsMenuBar::KeyDown(nsIDOMEvent *aEvent)
++{
++ if (!ShouldHandleKeyEvent(aEvent)) {
++ return NS_OK;
++ }
++
++ nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aEvent);
++ if (!keyEvent) {
++ return NS_OK;
++ }
++
++ uint32_t keyCode;
++ keyEvent->GetKeyCode(&keyCode);
++ ModifierFlags modifiers = GetModifiersFromEvent(keyEvent);
++ if ((keyCode != mAccessKey) || ((modifiers & ~mAccessKeyMask) != 0)) {
++ return NS_OK;
++ }
++
++ dbusmenu_server_set_status(mServer, DBUSMENU_STATUS_NOTICE);
++
++ return NS_OK;
++}
++
++nsresult
++nsMenuBar::KeyUp(nsIDOMEvent *aEvent)
++{
++ if (!ShouldHandleKeyEvent(aEvent)) {
++ return NS_OK;
++ }
++
++ nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aEvent);
++ if (!keyEvent) {
++ return NS_OK;
++ }
++
++ uint32_t keyCode;
++ keyEvent->GetKeyCode(&keyCode);
++ if (keyCode == mAccessKey) {
++ dbusmenu_server_set_status(mServer, DBUSMENU_STATUS_NORMAL);
++ }
++
++ return NS_OK;
++}
++
++nsMenuBar::nsMenuBar() :
++ nsMenuContainer(),
++ mTopLevel(nullptr),
++ mServer(nullptr),
++ mIsActive(false)
++{
++ MOZ_COUNT_CTOR(nsMenuBar);
++}
++
++nsresult
++nsMenuBar::Init(nsIWidget *aParent, nsIContent *aMenuBarNode)
++{
++ NS_ENSURE_ARG(aParent);
++ NS_ENSURE_ARG(aMenuBarNode);
++
++ GdkWindow *gdkWin = static_cast<GdkWindow *>(
++ aParent->GetNativeData(NS_NATIVE_WINDOW));
++ if (!gdkWin) {
++ return NS_ERROR_FAILURE;
++ }
++
++ gpointer user_data = nullptr;
++ gdk_window_get_user_data(gdkWin, &user_data);
++ if (!user_data || !GTK_IS_CONTAINER(user_data)) {
++ return NS_ERROR_FAILURE;
++ }
++
++ mTopLevel = gtk_widget_get_toplevel(GTK_WIDGET(user_data));
++ if (!mTopLevel) {
++ return NS_ERROR_FAILURE;
++ }
++
++ g_object_ref(mTopLevel);
++
++ RefPtr<nsNativeMenuDocListener> listener =
++ nsNativeMenuDocListener::Create(aMenuBarNode);
++ if (!listener) {
++ return NS_ERROR_FAILURE;
++ }
++
++ nsMenuObject::Init(listener, aMenuBarNode);
++
++ nsAutoCString path;
++ path.Append(NS_LITERAL_CSTRING("/com/canonical/menu/"));
++ char xid[10];
++ sprintf(xid, "%X", static_cast<uint32_t>(
++ GDK_WINDOW_XID(gtk_widget_get_window(mTopLevel))));
++ path.Append(xid);
++
++ mServer = dbusmenu_server_new(path.get());
++ if (!mServer) {
++ return NS_ERROR_FAILURE;
++ }
++
++ CreateNativeData();
++ if (!GetNativeData()) {
++ return NS_ERROR_FAILURE;
++ }
++
++ dbusmenu_server_set_root(mServer, GetNativeData());
++
++ mEventListener = new nsMenuBarDocEventListener(this);
++
++ mDocument = do_QueryInterface(ContentNode()->OwnerDoc());
++
++ mAccessKey = Preferences::GetInt("ui.key.menuAccessKey");
++ if (mAccessKey == nsIDOMKeyEvent::DOM_VK_SHIFT) {
++ mAccessKeyMask = eModifierShift;
++ } else if (mAccessKey == nsIDOMKeyEvent::DOM_VK_CONTROL) {
++ mAccessKeyMask = eModifierCtrl;
++ } else if (mAccessKey == nsIDOMKeyEvent::DOM_VK_ALT) {
++ mAccessKeyMask = eModifierAlt;
++ } else if (mAccessKey == nsIDOMKeyEvent::DOM_VK_META) {
++ mAccessKeyMask = eModifierMeta;
++ } else {
++ mAccessKeyMask = eModifierAlt;
++ }
++
++ return NS_OK;
++}
++
++nsMenuBar::~nsMenuBar()
++{
++ nsNativeMenuService *service = nsNativeMenuService::GetSingleton();
++ if (service) {
++ service->NotifyNativeMenuBarDestroyed(this);
++ }
++
++ if (ContentNode()) {
++ SetShellShowingMenuBar(false);
++ }
++
++ // We want to destroy all children before dropping our reference
++ // to the doc listener
++ while (ChildCount() > 0) {
++ RemoveChildAt(0);
++ }
++
++ if (mTopLevel) {
++ g_object_unref(mTopLevel);
++ }
++
++ if (DocListener()) {
++ DocListener()->Stop();
++ }
++
++ if (mDocument) {
++ DisconnectDocumentEventListeners();
++ }
++
++ if (mServer) {
++ g_object_unref(mServer);
++ }
++
++ MOZ_COUNT_DTOR(nsMenuBar);
++}
++
++/* static */ nsMenuBar*
++nsMenuBar::Create(nsIWidget *aParent, nsIContent *aMenuBarNode)
++{
++ nsAutoPtr<nsMenuBar> menubar(new nsMenuBar());
++ if (NS_FAILED(menubar->Init(aParent, aMenuBarNode))) {
++ return nullptr;
++ }
++
++ return menubar.forget();
++}
++
++nsMenuObject::EType
++nsMenuBar::Type() const
++{
++ return nsMenuObject::eType_MenuBar;
++}
++
++bool
++nsMenuBar::IsBeingDisplayed() const
++{
++ return true;
++}
++
++uint32_t
++nsMenuBar::WindowId() const
++{
++ return static_cast<uint32_t>(GDK_WINDOW_XID(gtk_widget_get_window(mTopLevel)));
++}
++
++nsAdoptingCString
++nsMenuBar::ObjectPath() const
++{
++ gchar *tmp;
++ g_object_get(mServer, DBUSMENU_SERVER_PROP_DBUS_OBJECT, &tmp, NULL);
++ nsAdoptingCString result(tmp);
++
++ return result;
++}
++
++nsNativeMenuGIORequest&
++nsMenuBar::BeginRegisterRequest()
++{
++ mRegisterRequestCanceller.Start();
++ return mRegisterRequestCanceller;
++}
++
++void
++nsMenuBar::EndRegisterRequest()
++{
++ NS_ASSERTION(RegisterRequestInProgress(), "No request in progress");
++ mRegisterRequestCanceller.Finish();
++}
++
++bool
++nsMenuBar::RegisterRequestInProgress() const
++{
++ return mRegisterRequestCanceller.InProgress();
++}
++
++void
++nsMenuBar::Activate()
++{
++ if (mIsActive) {
++ return;
++ }
++
++ mIsActive = true;
++
++ mDocument->AddEventListener(NS_LITERAL_STRING("focus"),
++ mEventListener,
++ true);
++ mDocument->AddEventListener(NS_LITERAL_STRING("blur"),
++ mEventListener,
++ true);
++ mDocument->AddEventListener(NS_LITERAL_STRING("keypress"),
++ mEventListener,
++ false);
++ mDocument->AddEventListener(NS_LITERAL_STRING("keydown"),
++ mEventListener,
++ false);
++ mDocument->AddEventListener(NS_LITERAL_STRING("keyup"),
++ mEventListener,
++ false);
++
++ // Clear this. Not sure if we really need to though
++ ContentNode()->SetAttr(kNameSpaceID_None, nsNativeMenuAtoms::openedwithkey,
++ NS_LITERAL_STRING("false"), true);
++
++ DocListener()->Start();
++ Build();
++ SetShellShowingMenuBar(true);
++}
++
++void
++nsMenuBar::Deactivate()
++{
++ if (!mIsActive) {
++ return;
++ }
++
++ mIsActive = false;
++
++ mRegisterRequestCanceller.Cancel();
++
++ SetShellShowingMenuBar(false);
++ while (ChildCount() > 0) {
++ RemoveChildAt(0);
++ }
++ DocListener()->Stop();
++ DisconnectDocumentEventListeners();
++}
++
++void
++nsMenuBar::OnAttributeChanged(nsIContent *aContent, nsIAtom *aAttribute)
++{
++
++}
++
++void
++nsMenuBar::OnContentInserted(nsIContent *aContainer, nsIContent *aChild,
++ nsIContent *aPrevSibling)
++{
++ NS_ASSERTION(aContainer == ContentNode(),
++ "Received an event that wasn't meant for us");
++
++ nsresult rv;
++ nsMenuObject *child = CreateChild(aChild, &rv);
++
++ if (child) {
++ rv = InsertChildAfter(child, aPrevSibling);
++ }
++
++ NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to insert item in to menubar");
++}
++
++void
++nsMenuBar::OnContentRemoved(nsIContent *aContainer, nsIContent *aChild)
++{
++ NS_ASSERTION(aContainer == ContentNode(),
++ "Received an event that wasn't meant for us");
++
++ DebugOnly<nsresult> rv = RemoveChild(aChild);
++ NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to remove item from menubar");
++}
+Index: firefox-52.0~b9+build2/mozilla/widget/gtk/nsMenuBar.h
+===================================================================
+--- /dev/null
++++ firefox-52.0~b9+build2/mozilla/widget/gtk/nsMenuBar.h
+@@ -0,0 +1,112 @@
++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:expandtab:shiftwidth=4:tabstop=4:
++ */
++/* 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/. */
++
++#ifndef __nsMenuBar_h__
++#define __nsMenuBar_h__
++
++#include "mozilla/Attributes.h"
++#include "nsCOMPtr.h"
++#include "nsString.h"
++
++#include "nsDbusmenu.h"
++#include "nsMenuContainer.h"
++#include "nsMenuObject.h"
++#include "nsNativeMenuUtils.h"
++
++#include <gtk/gtk.h>
++
++class nsIAtom;
++class nsIContent;
++class nsIDOMEvent;
++class nsIDOMKeyEvent;
++class nsIWidget;
++class nsMenuBarDocEventListener;
++
++/*
++ * The menubar class. There is one of these per window (and the window
++ * owns its menubar). Each menubar has an object path, and the service is
++ * responsible for telling the desktop shell which object path corresponds
++ * to a particular window. A menubar and its hierarchy also own a
++ * nsNativeMenuDocListener.
++ */
++class nsMenuBar final : public nsMenuContainer
++{
++public:
++ ~nsMenuBar();
++
++ static nsMenuBar* Create(nsIWidget *aParent,
++ nsIContent *aMenuBarNode);
++
++ nsMenuObject::EType Type() const;
++
++ bool IsBeingDisplayed() const;
++
++ // Get the native window ID for this menubar
++ uint32_t WindowId() const;
++
++ // Get the object path for this menubar
++ nsAdoptingCString ObjectPath() const;
++
++ // Initializes and returns a cancellable request object, used
++ // by the menuservice when registering this menubar
++ nsNativeMenuGIORequest& BeginRegisterRequest();
++
++ // Finishes the current request to register the menubar
++ void EndRegisterRequest();
++
++ bool RegisterRequestInProgress() const;
++
++ // Get the top-level GtkWindow handle
++ GtkWidget* TopLevelWindow() { return mTopLevel; }
++
++ // Called from the menuservice when the menubar is about to be registered.
++ // Causes the native menubar to be created, and the XUL menubar to be hidden
++ void Activate();
++
++ // Called from the menuservice when the menubar is no longer registered
++ // with the desktop shell. Will cause the XUL menubar to be shown again
++ void Deactivate();
++
++ void OnAttributeChanged(nsIContent *aContent, nsIAtom *aAttribute);
++ void OnContentInserted(nsIContent *aContainer, nsIContent *aChild,
++ nsIContent *aPrevSibling);
++ void OnContentRemoved(nsIContent *aContainer, nsIContent *aChild);
++
++private:
++ friend class nsMenuBarDocEventListener;
++
++ enum ModifierFlags {
++ eModifierShift = (1 << 0),
++ eModifierCtrl = (1 << 1),
++ eModifierAlt = (1 << 2),
++ eModifierMeta = (1 << 3)
++ };
++
++ nsMenuBar();
++ nsresult Init(nsIWidget *aParent, nsIContent *aMenuBarNode);
++ void Build();
++ void DisconnectDocumentEventListeners();
++ void SetShellShowingMenuBar(bool aShowing);
++ void Focus();
++ void Blur();
++ ModifierFlags GetModifiersFromEvent(nsIDOMKeyEvent *aEvent);
++ nsresult Keypress(nsIDOMEvent *aEvent);
++ nsresult KeyDown(nsIDOMEvent *aEvent);
++ nsresult KeyUp(nsIDOMEvent *aEvent);
++
++ GtkWidget *mTopLevel;
++ DbusmenuServer *mServer;
++ nsCOMPtr<nsIDOMEventTarget> mDocument;
++ nsNativeMenuGIORequest mRegisterRequestCanceller;
++ RefPtr<nsMenuBarDocEventListener> mEventListener;
++
++ uint32_t mAccessKey;
++ ModifierFlags mAccessKeyMask;
++ bool mIsActive;
++};
++
++#endif /* __nsMenuBar_h__ */
+Index: firefox-52.0~b9+build2/mozilla/widget/gtk/nsMenuContainer.cpp
+===================================================================
+--- /dev/null
++++ firefox-52.0~b9+build2/mozilla/widget/gtk/nsMenuContainer.cpp
+@@ -0,0 +1,174 @@
++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:expandtab:shiftwidth=4:tabstop=4:
++ */
++/* 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/. */
++
++#include "nsGkAtoms.h"
++#include "nsIAtom.h"
++#include "nsIContent.h"
++
++#include "nsDbusmenu.h"
++#include "nsMenu.h"
++#include "nsMenuItem.h"
++#include "nsMenuSeparator.h"
++
++#include "nsMenuContainer.h"
++
++const nsMenuContainer::ChildTArray::index_type nsMenuContainer::NoIndex = nsMenuContainer::ChildTArray::NoIndex;
++
++typedef nsMenuObject* (*nsMenuObjectConstructor)(nsMenuContainer*,
++ nsIContent*);
++static nsMenuObjectConstructor
++GetMenuObjectConstructor(nsIContent *aContent)
++{
++ if (aContent->IsXULElement(nsGkAtoms::menuitem)) {
++ return nsMenuItem::Create;
++ } else if (aContent->IsXULElement(nsGkAtoms::menu)) {
++ return nsMenu::Create;
++ } else if (aContent->IsXULElement(nsGkAtoms::menuseparator)) {
++ return nsMenuSeparator::Create;
++ }
++
++ return nullptr;
++}
++
++static bool
++ContentIsSupported(nsIContent *aContent)
++{
++ return GetMenuObjectConstructor(aContent) ? true : false;
++}
++
++nsMenuObject*
++nsMenuContainer::CreateChild(nsIContent *aContent, nsresult *aRv)
++{
++ nsMenuObjectConstructor ctor = GetMenuObjectConstructor(aContent);
++ if (!ctor) {
++ // There are plenty of node types we might stumble across that
++ // aren't supported. This isn't an error though
++ if (aRv) {
++ *aRv = NS_OK;
++ }
++ return nullptr;
++ }
++
++ nsMenuObject *res = ctor(this, aContent);
++ if (!res) {
++ if (aRv) {
++ *aRv = NS_ERROR_FAILURE;
++ }
++ return nullptr;
++ }
++
++ if (aRv) {
++ *aRv = NS_OK;
++ }
++ return res;
++}
++
++size_t
++nsMenuContainer::IndexOf(nsIContent *aChild) const
++{
++ if (!aChild) {
++ return NoIndex;
++ }
++
++ size_t count = ChildCount();
++ for (size_t i = 0; i < count; ++i) {
++ if (ChildAt(i)->ContentNode() == aChild) {
++ return i;
++ }
++ }
++
++ return NoIndex;
++}
++
++nsresult
++nsMenuContainer::RemoveChildAt(size_t aIndex, bool aUpdateNative)
++{
++ if (aIndex >= ChildCount()) {
++ return NS_ERROR_INVALID_ARG;
++ }
++
++ if (aUpdateNative) {
++ if (!dbusmenu_menuitem_child_delete(GetNativeData(),
++ ChildAt(aIndex)->GetNativeData())) {
++ return NS_ERROR_FAILURE;
++ }
++ }
++
++ mChildren.RemoveElementAt(aIndex);
++
++ return NS_OK;
++}
++
++nsresult
++nsMenuContainer::RemoveChild(nsIContent *aChild, bool aUpdateNative)
++{
++ size_t index = IndexOf(aChild);
++ if (index == NoIndex) {
++ return NS_ERROR_INVALID_ARG;
++ }
++
++ return RemoveChildAt(index, aUpdateNative);
++}
++
++nsresult
++nsMenuContainer::InsertChildAfter(nsMenuObject *aChild,
++ nsIContent *aPrevSibling,
++ bool aUpdateNative)
++{
++ size_t index = IndexOf(aPrevSibling);
++ if (index == NoIndex && aPrevSibling) {
++ return NS_ERROR_INVALID_ARG;
++ }
++
++ ++index;
++
++ if (aUpdateNative) {
++ aChild->CreateNativeData();
++ if (!dbusmenu_menuitem_child_add_position(GetNativeData(),
++ aChild->GetNativeData(),
++ index)) {
++ return NS_ERROR_FAILURE;
++ }
++ }
++
++ return mChildren.InsertElementAt(index, aChild) ? NS_OK : NS_ERROR_FAILURE;
++}
++
++nsresult
++nsMenuContainer::AppendChild(nsMenuObject *aChild, bool aUpdateNative)
++{
++ if (aUpdateNative) {
++ aChild->CreateNativeData();
++ if (!dbusmenu_menuitem_child_append(GetNativeData(),
++ aChild->GetNativeData())) {
++ return NS_ERROR_FAILURE;
++ }
++ }
++
++ return mChildren.AppendElement(aChild) ? NS_OK : NS_ERROR_FAILURE;
++}
++
++nsMenuContainer::nsMenuContainer() :
++ nsMenuObject()
++{
++}
++
++bool
++nsMenuContainer::NeedsRebuild() const
++{
++ return false;
++}
++
++/* static */ nsIContent*
++nsMenuContainer::GetPreviousSupportedSibling(nsIContent *aContent)
++{
++ do {
++ aContent = aContent->GetPreviousSibling();
++ } while (aContent && !ContentIsSupported(aContent));
++
++ return aContent;
++}
+Index: firefox-52.0~b9+build2/mozilla/widget/gtk/nsMenuContainer.h
+===================================================================
+--- /dev/null
++++ firefox-52.0~b9+build2/mozilla/widget/gtk/nsMenuContainer.h
+@@ -0,0 +1,66 @@
++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:expandtab:shiftwidth=4:tabstop=4:
++ */
++/* 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/. */
++
++#ifndef __nsMenuContainer_h__
++#define __nsMenuContainer_h__
++
++#include "nsAutoPtr.h"
++#include "nsTArray.h"
++
++#include "nsMenuObject.h"
++
++class nsIContent;
++
++// Base class for containers (menus and menubars)
++class nsMenuContainer : public nsMenuObject
++{
++public:
++ typedef nsTArray<nsAutoPtr<nsMenuObject> > ChildTArray;
++
++ // Determine if this container is being displayed on screen. Must be
++ // implemented by subclasses. Must return true if the container is
++ // in the fully open state, or false otherwise
++ virtual bool IsBeingDisplayed() const = 0;
++
++ // Determine if this container will be rebuilt the next time it opens.
++ // Returns false by default but can be overridden by subclasses
++ virtual bool NeedsRebuild() const;
++
++ // Return the first previous sibling that is of a type supported by the
++ // menu system
++ static nsIContent* GetPreviousSupportedSibling(nsIContent *aContent);
++
++ static const ChildTArray::index_type NoIndex;
++
++protected:
++ nsMenuContainer();
++
++ // Create a new child element for the specified content node
++ nsMenuObject* CreateChild(nsIContent *aContent, nsresult *aRv);
++
++ // Return the index of the child for the specified content node
++ size_t IndexOf(nsIContent *aChild) const;
++
++ size_t ChildCount() const { return mChildren.Length(); }
++ nsMenuObject* ChildAt(size_t aIndex) const { return mChildren[aIndex]; }
++
++ nsresult RemoveChildAt(size_t aIndex, bool aUpdateNative = true);
++
++ // Remove the child that owns the specified content node
++ nsresult RemoveChild(nsIContent *aChild, bool aUpdateNative = true);
++
++ // Insert a new child after the child that owns the specified content node
++ nsresult InsertChildAfter(nsMenuObject *aChild, nsIContent *aPrevSibling,
++ bool aUpdateNative = true);
++
++ nsresult AppendChild(nsMenuObject *aChild, bool aUpdateNative = true);
++
++private:
++ ChildTArray mChildren;
++};
++
++#endif /* __nsMenuContainer_h__ */
+Index: firefox-52.0~b9+build2/mozilla/widget/gtk/nsMenuItem.cpp
+===================================================================
+--- /dev/null
++++ firefox-52.0~b9+build2/mozilla/widget/gtk/nsMenuItem.cpp
+@@ -0,0 +1,743 @@
++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:expandtab:shiftwidth=4:tabstop=4:
++ */
++/* 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/. */
++
++#include "mozilla/ArrayUtils.h"
++#include "mozilla/dom/Element.h"
++#include "mozilla/Preferences.h"
++#include "mozilla/TextEvents.h"
++#include "nsAutoPtr.h"
++#include "nsContentUtils.h"
++#include "nsCRT.h"
++#include "nsGkAtoms.h"
++#include "nsGtkUtils.h"
++#include "nsIContent.h"
++#include "nsIDocument.h"
++#include "nsIDOMDocument.h"
++#include "nsIDOMEvent.h"
++#include "nsIDOMEventTarget.h"
++#include "nsIDOMKeyEvent.h"
++#include "nsIDOMXULCommandEvent.h"
++#include "nsIRunnable.h"
++#include "nsReadableUtils.h"
++#include "nsString.h"
++#include "nsStyleContext.h"
++#include "nsThreadUtils.h"
++
++#include "nsMenu.h"
++#include "nsMenuBar.h"
++#include "nsMenuContainer.h"
++#include "nsNativeMenuDocListener.h"
++
++#include <gdk/gdk.h>
++#include <gdk/gdkkeysyms.h>
++#if (MOZ_WIDGET_GTK == 3)
++#include <gdk/gdkkeysyms-compat.h>
++#endif
++#include <gdk/gdkx.h>
++#include <gtk/gtk.h>
++
++#include "nsMenuItem.h"
++
++using namespace mozilla;
++using namespace mozilla::widget;
++
++struct KeyCodeData {
++ const char* str;
++ size_t strlength;
++ uint32_t keycode;
++};
++
++static struct KeyCodeData gKeyCodes[] = {
++#define NS_DEFINE_VK(aDOMKeyName, aDOMKeyCode) \
++ { #aDOMKeyName, sizeof(#aDOMKeyName) - 1, aDOMKeyCode },
++#include "mozilla/VirtualKeyCodeList.h"
++#undef NS_DEFINE_VK
++ { nullptr, 0, 0 }
++};
++
++struct KeyPair {
++ uint32_t DOMKeyCode;
++ guint GDKKeyval;
++};
++
++//
++// Netscape keycodes are defined in widget/public/nsGUIEvent.h
++// GTK keycodes are defined in <gdk/gdkkeysyms.h>
++//
++static const KeyPair gKeyPairs[] = {
++ { NS_VK_CANCEL, GDK_Cancel },
++ { NS_VK_BACK, GDK_BackSpace },
++ { NS_VK_TAB, GDK_Tab },
++ { NS_VK_TAB, GDK_ISO_Left_Tab },
++ { NS_VK_CLEAR, GDK_Clear },
++ { NS_VK_RETURN, GDK_Return },
++ { NS_VK_SHIFT, GDK_Shift_L },
++ { NS_VK_SHIFT, GDK_Shift_R },
++ { NS_VK_SHIFT, GDK_Shift_Lock },
++ { NS_VK_CONTROL, GDK_Control_L },
++ { NS_VK_CONTROL, GDK_Control_R },
++ { NS_VK_ALT, GDK_Alt_L },
++ { NS_VK_ALT, GDK_Alt_R },
++ { NS_VK_META, GDK_Meta_L },
++ { NS_VK_META, GDK_Meta_R },
++
++ // Assume that Super or Hyper is always mapped to physical Win key.
++ { NS_VK_WIN, GDK_Super_L },
++ { NS_VK_WIN, GDK_Super_R },
++ { NS_VK_WIN, GDK_Hyper_L },
++ { NS_VK_WIN, GDK_Hyper_R },
++
++ // GTK's AltGraph key is similar to Mac's Option (Alt) key. However,
++ // unfortunately, browsers on Mac are using NS_VK_ALT for it even though
++ // it's really different from Alt key on Windows.
++ // On the other hand, GTK's AltGrapsh keys are really different from
++ // Alt key. However, there is no AltGrapsh key on Windows. On Windows,
++ // both Ctrl and Alt keys are pressed internally when AltGr key is pressed.
++ // For some languages' users, AltGraph key is important, so, web
++ // applications on such locale may want to know AltGraph key press.
++ // Therefore, we should map AltGr keycode for them only on GTK.
++ { NS_VK_ALTGR, GDK_ISO_Level3_Shift },
++ { NS_VK_ALTGR, GDK_ISO_Level5_Shift },
++ // We assume that Mode_switch is always used for level3 shift.
++ { NS_VK_ALTGR, GDK_Mode_switch },
++
++ { NS_VK_PAUSE, GDK_Pause },
++ { NS_VK_CAPS_LOCK, GDK_Caps_Lock },
++ { NS_VK_KANA, GDK_Kana_Lock },
++ { NS_VK_KANA, GDK_Kana_Shift },
++ { NS_VK_HANGUL, GDK_Hangul },
++ // { NS_VK_JUNJA, GDK_XXX },
++ // { NS_VK_FINAL, GDK_XXX },
++ { NS_VK_HANJA, GDK_Hangul_Hanja },
++ { NS_VK_KANJI, GDK_Kanji },
++ { NS_VK_ESCAPE, GDK_Escape },
++ { NS_VK_CONVERT, GDK_Henkan },
++ { NS_VK_NONCONVERT, GDK_Muhenkan },
++ // { NS_VK_ACCEPT, GDK_XXX },
++ // { NS_VK_MODECHANGE, GDK_XXX },
++ { NS_VK_SPACE, GDK_space },
++ { NS_VK_PAGE_UP, GDK_Page_Up },
++ { NS_VK_PAGE_DOWN, GDK_Page_Down },
++ { NS_VK_END, GDK_End },
++ { NS_VK_HOME, GDK_Home },
++ { NS_VK_LEFT, GDK_Left },
++ { NS_VK_UP, GDK_Up },
++ { NS_VK_RIGHT, GDK_Right },
++ { NS_VK_DOWN, GDK_Down },
++ { NS_VK_SELECT, GDK_Select },
++ { NS_VK_PRINT, GDK_Print },
++ { NS_VK_EXECUTE, GDK_Execute },
++ { NS_VK_PRINTSCREEN, GDK_Print },
++ { NS_VK_INSERT, GDK_Insert },
++ { NS_VK_DELETE, GDK_Delete },
++ { NS_VK_HELP, GDK_Help },
++
++ // keypad keys
++ { NS_VK_LEFT, GDK_KP_Left },
++ { NS_VK_RIGHT, GDK_KP_Right },
++ { NS_VK_UP, GDK_KP_Up },
++ { NS_VK_DOWN, GDK_KP_Down },
++ { NS_VK_PAGE_UP, GDK_KP_Page_Up },
++ // Not sure what these are
++ //{ NS_VK_, GDK_KP_Prior },
++ //{ NS_VK_, GDK_KP_Next },
++ { NS_VK_CLEAR, GDK_KP_Begin }, // Num-unlocked 5
++ { NS_VK_PAGE_DOWN, GDK_KP_Page_Down },
++ { NS_VK_HOME, GDK_KP_Home },
++ { NS_VK_END, GDK_KP_End },
++ { NS_VK_INSERT, GDK_KP_Insert },
++ { NS_VK_DELETE, GDK_KP_Delete },
++ { NS_VK_RETURN, GDK_KP_Enter },
++
++ { NS_VK_NUM_LOCK, GDK_Num_Lock },
++ { NS_VK_SCROLL_LOCK,GDK_Scroll_Lock },
++
++ // Function keys
++ { NS_VK_F1, GDK_F1 },
++ { NS_VK_F2, GDK_F2 },
++ { NS_VK_F3, GDK_F3 },
++ { NS_VK_F4, GDK_F4 },
++ { NS_VK_F5, GDK_F5 },
++ { NS_VK_F6, GDK_F6 },
++ { NS_VK_F7, GDK_F7 },
++ { NS_VK_F8, GDK_F8 },
++ { NS_VK_F9, GDK_F9 },
++ { NS_VK_F10, GDK_F10 },
++ { NS_VK_F11, GDK_F11 },
++ { NS_VK_F12, GDK_F12 },
++ { NS_VK_F13, GDK_F13 },
++ { NS_VK_F14, GDK_F14 },
++ { NS_VK_F15, GDK_F15 },
++ { NS_VK_F16, GDK_F16 },
++ { NS_VK_F17, GDK_F17 },
++ { NS_VK_F18, GDK_F18 },
++ { NS_VK_F19, GDK_F19 },
++ { NS_VK_F20, GDK_F20 },
++ { NS_VK_F21, GDK_F21 },
++ { NS_VK_F22, GDK_F22 },
++ { NS_VK_F23, GDK_F23 },
++ { NS_VK_F24, GDK_F24 },
++
++ // context menu key, keysym 0xff67, typically keycode 117 on 105-key (Microsoft)
++ // x86 keyboards, located between right 'Windows' key and right Ctrl key
++ { NS_VK_CONTEXT_MENU, GDK_Menu },
++ { NS_VK_SLEEP, GDK_Sleep },
++
++ { NS_VK_ATTN, GDK_3270_Attn },
++ { NS_VK_CRSEL, GDK_3270_CursorSelect },
++ { NS_VK_EXSEL, GDK_3270_ExSelect },
++ { NS_VK_EREOF, GDK_3270_EraseEOF },
++ { NS_VK_PLAY, GDK_3270_Play },
++ //{ NS_VK_ZOOM, GDK_XXX },
++ { NS_VK_PA1, GDK_3270_PA1 },
++};
++
++static guint
++ConvertGeckoKeyNameToGDKKeyval(nsAString& aKeyName)
++{
++ NS_ConvertUTF16toUTF8 keyName(aKeyName);
++ ToUpperCase(keyName); // We want case-insensitive comparison with data
++ // stored as uppercase.
++
++ uint32_t keyCode = 0;
++
++ uint32_t keyNameLength = keyName.Length();
++ const char* keyNameStr = keyName.get();
++ for (uint16_t i = 0; i < ArrayLength(gKeyCodes); ++i) {
++ if (keyNameLength == gKeyCodes[i].strlength &&
++ !nsCRT::strcmp(gKeyCodes[i].str, keyNameStr)) {
++ keyCode = gKeyCodes[i].keycode;
++ break;
++ }
++ }
++
++ // First, try to handle alphanumeric input, not listed in nsKeycodes:
++ // most likely, more letters will be getting typed in than things in
++ // the key list, so we will look through these first.
++
++ if (keyCode >= NS_VK_A && keyCode <= NS_VK_Z) {
++ // gdk and DOM both use the ASCII codes for these keys.
++ return keyCode;
++ }
++
++ // numbers
++ if (keyCode >= NS_VK_0 && keyCode <= NS_VK_9) {
++ // gdk and DOM both use the ASCII codes for these keys.
++ return keyCode - NS_VK_0 + GDK_0;
++ }
++
++ switch (keyCode) {
++ // keys in numpad
++ case NS_VK_MULTIPLY: return GDK_KP_Multiply;
++ case NS_VK_ADD: return GDK_KP_Add;
++ case NS_VK_SEPARATOR: return GDK_KP_Separator;
++ case NS_VK_SUBTRACT: return GDK_KP_Subtract;
++ case NS_VK_DECIMAL: return GDK_KP_Decimal;
++ case NS_VK_DIVIDE: return GDK_KP_Divide;
++ case NS_VK_NUMPAD0: return GDK_KP_0;
++ case NS_VK_NUMPAD1: return GDK_KP_1;
++ case NS_VK_NUMPAD2: return GDK_KP_2;
++ case NS_VK_NUMPAD3: return GDK_KP_3;
++ case NS_VK_NUMPAD4: return GDK_KP_4;
++ case NS_VK_NUMPAD5: return GDK_KP_5;
++ case NS_VK_NUMPAD6: return GDK_KP_6;
++ case NS_VK_NUMPAD7: return GDK_KP_7;
++ case NS_VK_NUMPAD8: return GDK_KP_8;
++ case NS_VK_NUMPAD9: return GDK_KP_9;
++ // other prinable keys
++ case NS_VK_SPACE: return GDK_space;
++ case NS_VK_COLON: return GDK_colon;
++ case NS_VK_SEMICOLON: return GDK_semicolon;
++ case NS_VK_LESS_THAN: return GDK_less;
++ case NS_VK_EQUALS: return GDK_equal;
++ case NS_VK_GREATER_THAN: return GDK_greater;
++ case NS_VK_QUESTION_MARK: return GDK_question;
++ case NS_VK_AT: return GDK_at;
++ case NS_VK_CIRCUMFLEX: return GDK_asciicircum;
++ case NS_VK_EXCLAMATION: return GDK_exclam;
++ case NS_VK_DOUBLE_QUOTE: return GDK_quotedbl;
++ case NS_VK_HASH: return GDK_numbersign;
++ case NS_VK_DOLLAR: return GDK_dollar;
++ case NS_VK_PERCENT: return GDK_percent;
++ case NS_VK_AMPERSAND: return GDK_ampersand;
++ case NS_VK_UNDERSCORE: return GDK_underscore;
++ case NS_VK_OPEN_PAREN: return GDK_parenleft;
++ case NS_VK_CLOSE_PAREN: return GDK_parenright;
++ case NS_VK_ASTERISK: return GDK_asterisk;
++ case NS_VK_PLUS: return GDK_plus;
++ case NS_VK_PIPE: return GDK_bar;
++ case NS_VK_HYPHEN_MINUS: return GDK_minus;
++ case NS_VK_OPEN_CURLY_BRACKET: return GDK_braceleft;
++ case NS_VK_CLOSE_CURLY_BRACKET: return GDK_braceright;
++ case NS_VK_TILDE: return GDK_asciitilde;
++ case NS_VK_COMMA: return GDK_comma;
++ case NS_VK_PERIOD: return GDK_period;
++ case NS_VK_SLASH: return GDK_slash;
++ case NS_VK_BACK_QUOTE: return GDK_grave;
++ case NS_VK_OPEN_BRACKET: return GDK_bracketleft;
++ case NS_VK_BACK_SLASH: return GDK_backslash;
++ case NS_VK_CLOSE_BRACKET: return GDK_bracketright;
++ case NS_VK_QUOTE: return GDK_apostrophe;
++ }
++
++ // misc other things
++ for (uint32_t i = 0; i < ArrayLength(gKeyPairs); ++i) {
++ if (gKeyPairs[i].DOMKeyCode == keyCode) {
++ return gKeyPairs[i].GDKKeyval;
++ }
++ }
++
++ return 0;
++}
++
++class nsMenuItemUncheckSiblingsRunnable final : public Runnable
++{
++public:
++ NS_IMETHODIMP Run()
++ {
++ if (mMenuItem) {
++ mMenuItem->UncheckSiblings();
++ }
++ return NS_OK;
++ }
++
++ nsMenuItemUncheckSiblingsRunnable(nsMenuItem *aMenuItem) :
++ mMenuItem(aMenuItem) { };
++
++private:
++ nsWeakMenuObject<nsMenuItem> mMenuItem;
++};
++
++bool
++nsMenuItem::IsCheckboxOrRadioItem() const
++{
++ return MenuItemType() == eMenuItemType_Radio ||
++ MenuItemType() == eMenuItemType_CheckBox;
++}
++
++/* static */ void
++nsMenuItem::item_activated_cb(DbusmenuMenuitem *menuitem,
++ guint timestamp,
++ gpointer user_data)
++{
++ nsMenuItem *item = static_cast<nsMenuItem *>(user_data);
++ item->Activate(timestamp);
++}
++
++void
++nsMenuItem::Activate(uint32_t aTimestamp)
++{
++ GdkWindow *window = gtk_widget_get_window(MenuBar()->TopLevelWindow());
++ gdk_x11_window_set_user_time(
++ window, std::min(aTimestamp, gdk_x11_get_server_time(window)));
++
++ // We do this to avoid mutating our view of the menu until
++ // after we have finished
++ nsNativeMenuAutoUpdateBatch batch;
++
++ if (!ContentNode()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::autocheck,
++ nsGkAtoms::_false, eCaseMatters) &&
++ (MenuItemType() == eMenuItemType_CheckBox ||
++ (MenuItemType() == eMenuItemType_Radio && !IsChecked()))) {
++ ContentNode()->SetAttr(kNameSpaceID_None, nsGkAtoms::checked,
++ IsChecked() ?
++ NS_LITERAL_STRING("false") : NS_LITERAL_STRING("true"),
++ true);
++ }
++
++ nsIDocument *doc = ContentNode()->OwnerDoc();
++ nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(ContentNode());
++ nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(doc);
++ if (domDoc && target) {
++ nsCOMPtr<nsIDOMEvent> event;
++ domDoc->CreateEvent(NS_LITERAL_STRING("xulcommandevent"),
++ getter_AddRefs(event));
++ nsCOMPtr<nsIDOMXULCommandEvent> command = do_QueryInterface(event);
++ if (command) {
++ command->InitCommandEvent(NS_LITERAL_STRING("command"),
++ true, true, doc->GetInnerWindow(), 0,
++ false, false, false, false, nullptr);
++
++ event->SetTrusted(true);
++ bool dummy;
++ target->DispatchEvent(event, &dummy);
++ }
++ }
++
++ // This kinda sucks, but Unity doesn't send a closed event
++ // after activating a menuitem
++ nsMenuObject *ancestor = Parent();
++ while (ancestor && ancestor->Type() == eType_Menu) {
++ static_cast<nsMenu *>(ancestor)->OnClose();
++ ancestor = ancestor->Parent();
++ }
++}
++
++void
++nsMenuItem::CopyAttrFromNodeIfExists(nsIContent *aContent, nsIAtom *aAttribute)
++{
++ nsAutoString value;
++ if (aContent->GetAttr(kNameSpaceID_None, aAttribute, value)) {
++ ContentNode()->SetAttr(kNameSpaceID_None, aAttribute, value, true);
++ }
++}
++
++void
++nsMenuItem::UpdateState()
++{
++ if (!IsCheckboxOrRadioItem()) {
++ return;
++ }
++
++ SetCheckState(ContentNode()->AttrValueIs(kNameSpaceID_None,
++ nsGkAtoms::checked,
++ nsGkAtoms::_true,
++ eCaseMatters));
++ dbusmenu_menuitem_property_set_int(GetNativeData(),
++ DBUSMENU_MENUITEM_PROP_TOGGLE_STATE,
++ IsChecked() ?
++ DBUSMENU_MENUITEM_TOGGLE_STATE_CHECKED :
++ DBUSMENU_MENUITEM_TOGGLE_STATE_UNCHECKED);
++}
++
++void
++nsMenuItem::UpdateTypeAndState()
++{
++ static nsIContent::AttrValuesArray attrs[] =
++ { &nsGkAtoms::checkbox, &nsGkAtoms::radio, nullptr };
++ int32_t type = ContentNode()->FindAttrValueIn(kNameSpaceID_None,
++ nsGkAtoms::type,
++ attrs, eCaseMatters);
++
++ if (type >= 0 && type < 2) {
++ if (type == 0) {
++ dbusmenu_menuitem_property_set(GetNativeData(),
++ DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE,
++ DBUSMENU_MENUITEM_TOGGLE_CHECK);
++ SetMenuItemType(eMenuItemType_CheckBox);
++ } else if (type == 1) {
++ dbusmenu_menuitem_property_set(GetNativeData(),
++ DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE,
++ DBUSMENU_MENUITEM_TOGGLE_RADIO);
++ SetMenuItemType(eMenuItemType_Radio);
++ }
++
++ UpdateState();
++ } else {
++ dbusmenu_menuitem_property_remove(GetNativeData(),
++ DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE);
++ dbusmenu_menuitem_property_remove(GetNativeData(),
++ DBUSMENU_MENUITEM_PROP_TOGGLE_STATE);
++ SetMenuItemType(eMenuItemType_Normal);
++ }
++}
++
++void
++nsMenuItem::UpdateAccel()
++{
++ nsIDocument *doc = ContentNode()->GetUncomposedDoc();
++ if (doc) {
++ nsCOMPtr<nsIContent> oldKeyContent;
++ oldKeyContent.swap(mKeyContent);
++
++ nsAutoString key;
++ ContentNode()->GetAttr(kNameSpaceID_None, nsGkAtoms::key, key);
++ if (!key.IsEmpty()) {
++ mKeyContent = doc->GetElementById(key);
++ }
++
++ if (mKeyContent != oldKeyContent) {
++ if (oldKeyContent) {
++ DocListener()->UnregisterForContentChanges(oldKeyContent);
++ }
++ if (mKeyContent) {
++ DocListener()->RegisterForContentChanges(mKeyContent, this);
++ }
++ }
++ }
++
++ if (!mKeyContent) {
++ dbusmenu_menuitem_property_remove(GetNativeData(),
++ DBUSMENU_MENUITEM_PROP_SHORTCUT);
++ return;
++ }
++
++ nsAutoString modifiers;
++ mKeyContent->GetAttr(kNameSpaceID_None, nsGkAtoms::modifiers, modifiers);
++
++ uint32_t modifier = 0;
++
++ if (!modifiers.IsEmpty()) {
++ char* str = ToNewUTF8String(modifiers);
++ char *token = strtok(str, ", \t");
++ while(token) {
++ if (nsCRT::strcmp(token, "shift") == 0) {
++ modifier |= GDK_SHIFT_MASK;
++ } else if (nsCRT::strcmp(token, "alt") == 0) {
++ modifier |= GDK_MOD1_MASK;
++ } else if (nsCRT::strcmp(token, "meta") == 0) {
++ modifier |= GDK_META_MASK;
++ } else if (nsCRT::strcmp(token, "control") == 0) {
++ modifier |= GDK_CONTROL_MASK;
++ } else if (nsCRT::strcmp(token, "accel") == 0) {
++ int32_t accel = Preferences::GetInt("ui.key.accelKey");
++ if (accel == nsIDOMKeyEvent::DOM_VK_META) {
++ modifier |= GDK_META_MASK;
++ } else if (accel == nsIDOMKeyEvent::DOM_VK_ALT) {
++ modifier |= GDK_MOD1_MASK;
++ } else {
++ modifier |= GDK_CONTROL_MASK;
++ }
++ }
++
++ token = strtok(nullptr, ", \t");
++ }
++
++ free(str);
++ }
++
++ nsAutoString keyStr;
++ mKeyContent->GetAttr(kNameSpaceID_None, nsGkAtoms::key, keyStr);
++
++ guint key = 0;
++ if (!keyStr.IsEmpty()) {
++ key = gdk_unicode_to_keyval(*keyStr.BeginReading());
++ }
++
++ if (key == 0) {
++ mKeyContent->GetAttr(kNameSpaceID_None, nsGkAtoms::keycode, keyStr);
++ if (!keyStr.IsEmpty()) {
++ key = ConvertGeckoKeyNameToGDKKeyval(keyStr);
++ }
++ }
++
++ if (key == 0) {
++ key = GDK_VoidSymbol;
++ }
++
++ if (key != GDK_VoidSymbol) {
++ dbusmenu_menuitem_property_set_shortcut(GetNativeData(), key,
++ static_cast<GdkModifierType>(modifier));
++ } else {
++ dbusmenu_menuitem_property_remove(GetNativeData(),
++ DBUSMENU_MENUITEM_PROP_SHORTCUT);
++ }
++}
++
++void
++nsMenuItem::InitializeNativeData()
++{
++ g_signal_connect(G_OBJECT(GetNativeData()),
++ DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED,
++ G_CALLBACK(item_activated_cb), this);
++
++ UpdateTypeAndState();
++ UpdateAccel();
++ UpdateLabel();
++ UpdateSensitivity();
++}
++
++void
++nsMenuItem::UpdateContentAttributes()
++{
++ nsIDocument *doc = ContentNode()->GetUncomposedDoc();
++ if (!doc) {
++ return;
++ }
++
++ nsAutoString command;
++ ContentNode()->GetAttr(kNameSpaceID_None, nsGkAtoms::command, command);
++ if (command.IsEmpty()) {
++ return;
++ }
++
++ nsCOMPtr<nsIContent> commandContent = doc->GetElementById(command);
++ if (!commandContent) {
++ return;
++ }
++
++ if (commandContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
++ nsGkAtoms::_true, eCaseMatters)) {
++ ContentNode()->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled,
++ NS_LITERAL_STRING("true"), true);
++ } else {
++ ContentNode()->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, true);
++ }
++
++ CopyAttrFromNodeIfExists(commandContent, nsGkAtoms::checked);
++ CopyAttrFromNodeIfExists(commandContent, nsGkAtoms::accesskey);
++ CopyAttrFromNodeIfExists(commandContent, nsGkAtoms::label);
++ CopyAttrFromNodeIfExists(commandContent, nsGkAtoms::hidden);
++}
++
++void
++nsMenuItem::Update(nsStyleContext *aStyleContext)
++{
++ UpdateVisibility(aStyleContext);
++ UpdateIcon(aStyleContext);
++}
++
++void
++nsMenuItem::UncheckSiblings()
++{
++ if (!ContentNode()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
++ nsGkAtoms::radio, eCaseMatters)) {
++ // If we're not a radio button, we don't care
++ return;
++ }
++
++ nsAutoString name;
++ ContentNode()->GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
++
++ nsIContent *parent = ContentNode()->GetParent();
++ if (!parent) {
++ return;
++ }
++
++ uint32_t count = parent->GetChildCount();
++ for (uint32_t i = 0; i < count; ++i) {
++ nsIContent *sibling = parent->GetChildAt(i);
++
++ nsAutoString otherName;
++ sibling->GetAttr(kNameSpaceID_None, nsGkAtoms::name, otherName);
++
++ if (sibling != ContentNode() && otherName == name &&
++ sibling->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
++ nsGkAtoms::radio, eCaseMatters)) {
++ sibling->UnsetAttr(kNameSpaceID_None, nsGkAtoms::checked, true);
++ }
++ }
++}
++
++bool
++nsMenuItem::IsCompatibleWithNativeData(DbusmenuMenuitem *aNativeData) const
++{
++ return nsCRT::strcmp(dbusmenu_menuitem_property_get(aNativeData,
++ DBUSMENU_MENUITEM_PROP_TYPE),
++ "separator") != 0;
++}
++
++nsMenuBar*
++nsMenuItem::MenuBar()
++{
++ nsMenuObject *tmp = this;
++ while (tmp->Parent()) {
++ tmp = tmp->Parent();
++ }
++
++ MOZ_ASSERT(tmp->Type() == eType_MenuBar, "The top-level should be a menubar");
++
++ return static_cast<nsMenuBar *>(tmp);
++}
++
++nsMenuObject::PropertyFlags
++nsMenuItem::SupportedProperties() const
++{
++ return static_cast<nsMenuObject::PropertyFlags>(
++ nsMenuObject::ePropLabel |
++ nsMenuObject::ePropEnabled |
++ nsMenuObject::ePropVisible |
++ nsMenuObject::ePropIconData |
++ nsMenuObject::ePropShortcut |
++ nsMenuObject::ePropToggleType |
++ nsMenuObject::ePropToggleState
++ );
++}
++
++nsMenuItem::nsMenuItem() :
++ nsMenuObject()
++{
++ MOZ_COUNT_CTOR(nsMenuItem);
++}
++
++nsMenuItem::~nsMenuItem()
++{
++ if (DocListener() && mKeyContent) {
++ DocListener()->UnregisterForContentChanges(mKeyContent);
++ }
++
++ if (GetNativeData()) {
++ g_signal_handlers_disconnect_by_func(GetNativeData(),
++ FuncToGpointer(item_activated_cb),
++ this);
++ }
++
++ MOZ_COUNT_DTOR(nsMenuItem);
++}
++
++nsMenuObject::EType
++nsMenuItem::Type() const
++{
++ return nsMenuObject::eType_MenuItem;
++}
++
++/* static */ nsMenuObject*
++nsMenuItem::Create(nsMenuContainer *aParent, nsIContent *aContent)
++{
++ nsAutoPtr<nsMenuItem> menuitem(new nsMenuItem());
++ if (NS_FAILED(menuitem->Init(aParent, aContent))) {
++ return nullptr;
++ }
++
++ return menuitem.forget();
++}
++
++void
++nsMenuItem::OnAttributeChanged(nsIContent *aContent, nsIAtom *aAttribute)
++{
++ NS_ASSERTION(aContent == ContentNode() || aContent == mKeyContent,
++ "Received an event that wasn't meant for us!");
++
++ if (Parent()->NeedsRebuild()) {
++ return;
++ }
++
++ if (aContent == ContentNode() && aAttribute == nsGkAtoms::checked &&
++ aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::checked,
++ nsGkAtoms::_true, eCaseMatters)) {
++ if (nsContentUtils::IsSafeToRunScript()) {
++ UncheckSiblings();
++ } else {
++ nsContentUtils::AddScriptRunner(
++ new nsMenuItemUncheckSiblingsRunnable(this));
++ }
++ }
++
++ if (aContent == ContentNode()) {
++ if (aAttribute == nsGkAtoms::key) {
++ UpdateAccel();
++ } else if (aAttribute == nsGkAtoms::label ||
++ aAttribute == nsGkAtoms::accesskey ||
++ aAttribute == nsGkAtoms::crop) {
++ UpdateLabel();
++ } else if (aAttribute == nsGkAtoms::disabled) {
++ UpdateSensitivity();
++ } else if (aAttribute == nsGkAtoms::type) {
++ UpdateTypeAndState();
++ } else if (aAttribute == nsGkAtoms::checked) {
++ UpdateState();
++ }
++ } else if (aContent == mKeyContent &&
++ (aAttribute == nsGkAtoms::key ||
++ aAttribute == nsGkAtoms::keycode ||
++ aAttribute == nsGkAtoms::modifiers)) {
++ UpdateAccel();
++ }
++
++ if (!Parent()->IsBeingDisplayed() || aContent != ContentNode()) {
++ return;
++ }
++
++ if (aAttribute == nsGkAtoms::hidden ||
++ aAttribute == nsGkAtoms::collapsed) {
++ RefPtr<nsStyleContext> sc = GetStyleContext();
++ UpdateVisibility(sc);
++ } else if (aAttribute == nsGkAtoms::image) {
++ RefPtr<nsStyleContext> sc = GetStyleContext();
++ UpdateIcon(sc);
++ }
++}
+Index: firefox-52.0~b9+build2/mozilla/widget/gtk/nsMenuItem.h
+===================================================================
+--- /dev/null
++++ firefox-52.0~b9+build2/mozilla/widget/gtk/nsMenuItem.h
+@@ -0,0 +1,107 @@
++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:expandtab:shiftwidth=4:tabstop=4:
++ */
++/* 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/. */
++
++#ifndef __nsMenuItem_h__
++#define __nsMenuItem_h__
++
++#include "mozilla/Attributes.h"
++#include "nsCOMPtr.h"
++
++#include "nsDbusmenu.h"
++#include "nsMenuObject.h"
++
++#include <glib.h>
++
++#define NSMENUITEM_NUMBER_OF_TYPE_BITS 2U
++#define NSMENUITEM_NUMBER_OF_FLAGS 1U
++
++class nsIAtom;
++class nsIContent;
++class nsStyleContext;
++class nsMenuBar;
++class nsMenuContainer;
++
++/*
++ * This class represents 3 main classes of menuitems: labels, checkboxes and
++ * radio buttons (with/without an icon)
++ */
++class nsMenuItem final : public nsMenuObject
++{
++public:
++ ~nsMenuItem();
++
++ nsMenuObject::EType Type() const;
++
++ static nsMenuObject* Create(nsMenuContainer *aParent,
++ nsIContent *aContent);
++
++ void OnAttributeChanged(nsIContent *aContent, nsIAtom *aAttribute);
++
++private:
++ friend class nsMenuItemUncheckSiblingsRunnable;
++
++ enum {
++ eMenuItemFlag_ToggleState = (1 << 0)
++ };
++
++ enum EMenuItemType {
++ eMenuItemType_Normal,
++ eMenuItemType_Radio,
++ eMenuItemType_CheckBox
++ };
++
++ nsMenuItem();
++
++ EMenuItemType MenuItemType() const
++ {
++ return static_cast<EMenuItemType>(
++ (GetFlags() &
++ (((1U << NSMENUITEM_NUMBER_OF_TYPE_BITS) - 1U)
++ << NSMENUITEM_NUMBER_OF_FLAGS)) >> NSMENUITEM_NUMBER_OF_FLAGS);
++ }
++ void SetMenuItemType(EMenuItemType aType)
++ {
++ ClearFlags(((1U << NSMENUITEM_NUMBER_OF_TYPE_BITS) - 1U) << NSMENUITEM_NUMBER_OF_FLAGS);
++ SetFlags(aType << NSMENUITEM_NUMBER_OF_FLAGS);
++ }
++ bool IsCheckboxOrRadioItem() const;
++
++ bool IsChecked() const
++ {
++ return HasFlags(eMenuItemFlag_ToggleState);
++ }
++ void SetCheckState(bool aState)
++ {
++ if (aState) {
++ SetFlags(eMenuItemFlag_ToggleState);
++ } else {
++ ClearFlags(eMenuItemFlag_ToggleState);
++ }
++ }
++
++ static void item_activated_cb(DbusmenuMenuitem *menuitem,
++ guint timestamp,
++ gpointer user_data);
++ void Activate(uint32_t aTimestamp);
++
++ void CopyAttrFromNodeIfExists(nsIContent *aContent, nsIAtom *aAtom);
++ void UpdateState();
++ void UpdateTypeAndState();
++ void UpdateAccel();
++
++ void InitializeNativeData();
++ void UpdateContentAttributes();
++ void Update(nsStyleContext *aStyleContext);
++ void UncheckSiblings();
++ bool IsCompatibleWithNativeData(DbusmenuMenuitem *aNativeData) const;
++ nsMenuBar* MenuBar();
++ nsMenuObject::PropertyFlags SupportedProperties() const;
++
++ nsCOMPtr<nsIContent> mKeyContent;
++};
++
++#endif /* __nsMenuItem_h__ */
+Index: firefox-52.0~b9+build2/mozilla/widget/gtk/nsMenuObject.cpp
+===================================================================
+--- /dev/null
++++ firefox-52.0~b9+build2/mozilla/widget/gtk/nsMenuObject.cpp
+@@ -0,0 +1,694 @@
++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:expandtab:shiftwidth=4:tabstop=4:
++ */
++/* 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/. */
++
++#include "ImageOps.h"
++#include "imgIContainer.h"
++#include "imgINotificationObserver.h"
++#include "imgLoader.h"
++#include "imgRequestProxy.h"
++#include "mozilla/ArrayUtils.h"
++#include "mozilla/dom/Element.h"
++#include "mozilla/Preferences.h"
++#include "nsAttrValue.h"
++#include "nsComputedDOMStyle.h"
++#include "nsContentUtils.h"
++#include "nsGkAtoms.h"
++#include "nsIContent.h"
++#include "nsIContentPolicy.h"
++#include "nsIDocument.h"
++#include "nsILoadGroup.h"
++#include "nsImageToPixbuf.h"
++#include "nsIPresShell.h"
++#include "nsIURI.h"
++#include "nsNetUtil.h"
++#include "nsPresContext.h"
++#include "nsRect.h"
++#include "nsServiceManagerUtils.h"
++#include "nsString.h"
++#include "nsStyleConsts.h"
++#include "nsStyleContext.h"
++#include "nsStyleStruct.h"
++#include "nsThreadUtils.h"
++#include "nsUnicharUtils.h"
++
++#include "nsMenuContainer.h"
++#include "nsNativeMenuAtoms.h"
++#include "nsNativeMenuDocListener.h"
++#include "nsNativeMenuUtils.h"
++
++#include <gdk/gdk.h>
++#include <glib-object.h>
++#include <pango/pango.h>
++
++#include "nsMenuObject.h"
++
++using namespace mozilla;
++using mozilla::image::ImageOps;
++
++#define MAX_WIDTH 350000
++
++const char *gPropertyStrings[] = {
++#define DBUSMENU_PROPERTY(e, s, b) s,
++ DBUSMENU_PROPERTIES
++#undef DBUSMENU_PROPERTY
++ nullptr
++};
++
++nsWeakMenuObjectBase* nsWeakMenuObjectBase::sHead;
++PangoLayout* gPangoLayout = nullptr;
++
++class nsMenuObjectContainerOpeningRunnable : public Runnable
++{
++public:
++ NS_IMETHODIMP Run()
++ {
++ if (mMenuObject) {
++ mMenuObject->ContainerIsOpening();
++ }
++ return NS_OK;
++ }
++
++ nsMenuObjectContainerOpeningRunnable(nsMenuObject *aMenuObject) :
++ mMenuObject(aMenuObject) { };
++
++private:
++ nsWeakMenuObject<nsMenuObject> mMenuObject;
++};
++
++class nsMenuObjectIconLoader final : public imgINotificationObserver
++{
++public:
++ NS_DECL_ISUPPORTS
++ NS_DECL_IMGINOTIFICATIONOBSERVER
++
++ nsMenuObjectIconLoader(nsMenuObject *aOwner) : mOwner(aOwner) { };
++
++ void LoadIcon(nsStyleContext *aStyleContext);
++ void Destroy();
++
++private:
++ ~nsMenuObjectIconLoader() { };
++
++ nsMenuObject *mOwner;
++ RefPtr<imgRequestProxy> mImageRequest;
++ nsCOMPtr<nsIURI> mURI;
++ nsIntRect mImageRect;
++};
++
++NS_IMPL_ISUPPORTS(nsMenuObjectIconLoader, imgINotificationObserver)
++
++NS_IMETHODIMP
++nsMenuObjectIconLoader::Notify(imgIRequest *aProxy,
++ int32_t aType, const nsIntRect *aRect)
++{
++ if (!mOwner) {
++ return NS_OK;
++ }
++
++ if (aProxy != mImageRequest) {
++ return NS_ERROR_FAILURE;
++ }
++
++ if (aType == imgINotificationObserver::LOAD_COMPLETE) {
++ uint32_t status = imgIRequest::STATUS_ERROR;
++ if (NS_FAILED(mImageRequest->GetImageStatus(&status)) ||
++ (status & imgIRequest::STATUS_ERROR)) {
++ mImageRequest->Cancel(NS_BINDING_ABORTED);
++ mImageRequest = nullptr;
++ return NS_ERROR_FAILURE;
++ }
++
++ nsCOMPtr<imgIContainer> image;
++ mImageRequest->GetImage(getter_AddRefs(image));
++ MOZ_ASSERT(image);
++
++ // Ask the image to decode at its intrinsic size.
++ int32_t width = 0, height = 0;
++ image->GetWidth(&width);
++ image->GetHeight(&height);
++ image->RequestDecodeForSize(nsIntSize(width, height), imgIContainer::FLAG_NONE);
++ return NS_OK;
++ }
++
++ if (aType == imgINotificationObserver::DECODE_COMPLETE) {
++ mImageRequest->Cancel(NS_BINDING_ABORTED);
++ mImageRequest = nullptr;
++ return NS_OK;
++ }
++
++ if (aType != imgINotificationObserver::FRAME_COMPLETE) {
++ return NS_OK;
++ }
++
++ nsCOMPtr<imgIContainer> img;
++ mImageRequest->GetImage(getter_AddRefs(img));
++ if (!img) {
++ return NS_ERROR_FAILURE;
++ }
++
++ if (!mImageRect.IsEmpty()) {
++ img = ImageOps::Clip(img, mImageRect);
++ }
++
++ int32_t width, height;
++ img->GetWidth(&width);
++ img->GetHeight(&height);
++
++ if (width <= 0 || height <= 0) {
++ mOwner->ClearIcon();
++ return NS_OK;
++ }
++
++ if (width > 100 || height > 100) {
++ // The icon data needs to go across DBus. Make sure the icon
++ // data isn't too large, else our connection gets terminated and
++ // GDbus helpfully aborts the application. Thank you :)
++ NS_WARNING("Icon data too large");
++ mOwner->ClearIcon();
++ return NS_OK;
++ }
++
++ GdkPixbuf *pixbuf = nsImageToPixbuf::ImageToPixbuf(img);
++ if (pixbuf) {
++ dbusmenu_menuitem_property_set_image(mOwner->GetNativeData(),
++ DBUSMENU_MENUITEM_PROP_ICON_DATA,
++ pixbuf);
++ g_object_unref(pixbuf);
++ }
++
++ return NS_OK;
++}
++
++void
++nsMenuObjectIconLoader::LoadIcon(nsStyleContext *aStyleContext)
++{
++ nsIDocument *doc = mOwner->ContentNode()->OwnerDoc();
++
++ nsCOMPtr<nsIURI> uri;
++ nsIntRect imageRect;
++ imgRequestProxy *imageRequest = nullptr;
++
++ nsAutoString uriString;
++ if (mOwner->ContentNode()->GetAttr(kNameSpaceID_None, nsGkAtoms::image,
++ uriString)) {
++ NS_NewURI(getter_AddRefs(uri), uriString);
++ } else {
++ nsIPresShell *shell = doc->GetShell();
++ if (!shell) {
++ return;
++ }
++
++ nsPresContext *pc = shell->GetPresContext();
++ if (!pc || !aStyleContext) {
++ return;
++ }
++
++ const nsStyleList *list = aStyleContext->StyleList();
++ imageRequest = list->GetListStyleImage();
++ if (imageRequest) {
++ imageRequest->GetURI(getter_AddRefs(uri));
++ imageRect = list->mImageRegion.ToNearestPixels(
++ pc->AppUnitsPerDevPixel());
++ }
++ }
++
++ if (!uri) {
++ mOwner->ClearIcon();
++ mURI = nullptr;
++
++ if (mImageRequest) {
++ mImageRequest->Cancel(NS_BINDING_ABORTED);
++ mImageRequest = nullptr;
++ }
++
++ return;
++ }
++
++ bool same;
++ if (mURI && NS_SUCCEEDED(mURI->Equals(uri, &same)) && same &&
++ (!imageRequest || imageRect == mImageRect)) {
++ return;
++ }
++
++ if (mImageRequest) {
++ mImageRequest->Cancel(NS_BINDING_ABORTED);
++ mImageRequest = nullptr;
++ }
++
++ mURI = uri;
++
++ if (imageRequest) {
++ mImageRect = imageRect;
++ imageRequest->Clone(this, getter_AddRefs(mImageRequest));
++ } else {
++ mImageRect.SetEmpty();
++ nsCOMPtr<nsILoadGroup> loadGroup = doc->GetDocumentLoadGroup();
++ RefPtr<imgLoader> loader =
++ nsContentUtils::GetImgLoaderForDocument(doc);
++ if (!loader || !loadGroup) {
++ NS_WARNING("Failed to get loader or load group for image load");
++ return;
++ }
++
++ loader->LoadImage(uri, nullptr, nullptr, mozilla::net::RP_Default,
++ nullptr, loadGroup, this, nullptr, nullptr,
++ nsIRequest::LOAD_NORMAL, nullptr,
++ nsIContentPolicy::TYPE_IMAGE, EmptyString(),
++ getter_AddRefs(mImageRequest));
++ }
++}
++
++void
++nsMenuObjectIconLoader::Destroy()
++{
++ if (mImageRequest) {
++ mImageRequest->CancelAndForgetObserver(NS_BINDING_ABORTED);
++ mImageRequest = nullptr;
++ }
++
++ mOwner = nullptr;
++}
++
++static int
++CalculateTextWidth(const nsAString& aText)
++{
++ if (!gPangoLayout) {
++ PangoFontMap *fontmap = pango_cairo_font_map_get_default();
++ PangoContext *ctx = pango_font_map_create_context(fontmap);
++ gPangoLayout = pango_layout_new(ctx);
++ g_object_unref(ctx);
++ }
++
++ pango_layout_set_text(gPangoLayout, NS_ConvertUTF16toUTF8(aText).get(), -1);
++
++ int width, dummy;
++ pango_layout_get_size(gPangoLayout, &width, &dummy);
++
++ return width;
++}
++
++static const nsDependentString
++GetEllipsis()
++{
++ static char16_t sBuf[4] = { 0, 0, 0, 0 };
++ if (!sBuf[0]) {
++ nsAdoptingString ellipsis = Preferences::GetLocalizedString("intl.ellipsis");
++ if (!ellipsis.IsEmpty()) {
++ uint32_t l = ellipsis.Length();
++ const nsAdoptingString::char_type *c = ellipsis.BeginReading();
++ uint32_t i = 0;
++ while (i < 3 && i < l) {
++ sBuf[i++] = *(c++);
++ }
++ } else {
++ sBuf[0] = '.';
++ sBuf[1] = '.';
++ sBuf[2] = '.';
++ }
++ }
++
++ return nsDependentString(sBuf);
++}
++
++static int
++GetEllipsisWidth()
++{
++ static int sEllipsisWidth = -1;
++
++ if (sEllipsisWidth == -1) {
++ sEllipsisWidth = CalculateTextWidth(GetEllipsis());
++ }
++
++ return sEllipsisWidth;
++}
++
++void
++nsMenuObject::InitializeNativeData()
++{
++}
++
++nsMenuObject::PropertyFlags
++nsMenuObject::SupportedProperties() const
++{
++ return static_cast<nsMenuObject::PropertyFlags>(0);
++}
++
++bool
++nsMenuObject::IsCompatibleWithNativeData(DbusmenuMenuitem *aNativeData) const
++{
++ return true;
++}
++
++void
++nsMenuObject::UpdateContentAttributes()
++{
++}
++
++void
++nsMenuObject::Update(nsStyleContext *aStyleContext)
++{
++}
++
++bool
++nsMenuObject::ShouldShowIcon() const
++{
++ // Ideally we want to know the visibility of the anonymous XUL image in
++ // our menuitem, but this isn't created because we don't have a frame.
++ // The following works by default (because xul.css hides images in menuitems
++ // that don't have the "menuitem-with-favicon" class). It's possible a third
++ // party theme could override this, but, oh well...
++ const nsAttrValue *classes = mContent->GetClasses();
++ if (!classes) {
++ return false;
++ }
++
++ for (uint32_t i = 0; i < classes->GetAtomCount(); ++i) {
++ if (classes->AtomAt(i) == nsNativeMenuAtoms::menuitem_with_favicon) {
++ return true;
++ }
++ }
++
++ return false;
++}
++
++void
++nsMenuObject::ClearIcon()
++{
++ dbusmenu_menuitem_property_remove(mNativeData,
++ DBUSMENU_MENUITEM_PROP_ICON_DATA);
++}
++
++void
++nsMenuObject::UpdateLabel()
++{
++ // Gecko stores the label and access key in separate attributes
++ // so we need to convert label="Foo_Bar"/accesskey="F" in to
++ // label="_Foo__Bar" for dbusmenu
++
++ nsAutoString label;
++ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, label);
++
++ nsAutoString accesskey;
++ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, accesskey);
++
++ const nsAutoString::char_type *akey = accesskey.BeginReading();
++ char16_t keyLower = ToLowerCase(*akey);
++ char16_t keyUpper = ToUpperCase(*akey);
++
++ const nsAutoString::char_type *iter = label.BeginReading();
++ const nsAutoString::char_type *end = label.EndReading();
++ uint32_t length = label.Length();
++ uint32_t pos = 0;
++ bool foundAccessKey = false;
++
++ while (iter != end) {
++ if (*iter != char16_t('_')) {
++ if ((*iter != keyLower && *iter != keyUpper) || foundAccessKey) {
++ ++iter;
++ ++pos;
++ continue;
++ }
++ foundAccessKey = true;
++ }
++
++ label.SetLength(++length);
++
++ iter = label.BeginReading() + pos;
++ end = label.EndReading();
++ nsAutoString::char_type *cur = label.BeginWriting() + pos;
++
++ memmove(cur + 1, cur, (length - 1 - pos) * sizeof(nsAutoString::char_type));
++ *cur = nsAutoString::char_type('_');
++
++ iter += 2;
++ pos += 2;
++ }
++
++ if (CalculateTextWidth(label) <= MAX_WIDTH) {
++ dbusmenu_menuitem_property_set(mNativeData,
++ DBUSMENU_MENUITEM_PROP_LABEL,
++ NS_ConvertUTF16toUTF8(label).get());
++ return;
++ }
++
++ // This *COMPLETELY SUCKS*
++ // This should be done at the point where the menu is drawn (hello Unity),
++ // but unfortunately it doesn't do that and will happily fill your entire
++ // screen width with a menu if you have a bookmark with a really long title.
++ // This leaves us with no other option but to ellipsize here, with no proper
++ // knowledge of Unity's render path, font size etc. This is better than nothing
++ // BAH! @*&!$
++ nsAutoString truncated;
++ int target = MAX_WIDTH - GetEllipsisWidth();
++ length = label.Length();
++
++ static nsIContent::AttrValuesArray strings[] = {
++ &nsGkAtoms::left, &nsGkAtoms::start,
++ &nsGkAtoms::center, &nsGkAtoms::right,
++ &nsGkAtoms::end, nullptr
++ };
++
++ int32_t type = mContent->FindAttrValueIn(kNameSpaceID_None,
++ nsGkAtoms::crop,
++ strings, eCaseMatters);
++
++ switch (type) {
++ case 0:
++ case 1:
++ // FIXME: Implement left cropping (do we really care?)
++ case 2:
++ // FIXME: Implement center cropping (do we really care?)
++ case 3:
++ case 4:
++ default:
++ for (uint32_t i = 0; i < length; i++) {
++ truncated.Append(label.CharAt(i));
++ if (CalculateTextWidth(truncated) > target) {
++ break;
++ }
++ }
++
++ truncated.Append(GetEllipsis());
++ }
++
++ dbusmenu_menuitem_property_set(mNativeData,
++ DBUSMENU_MENUITEM_PROP_LABEL,
++ NS_ConvertUTF16toUTF8(truncated).get());
++}
++
++void
++nsMenuObject::UpdateVisibility(nsStyleContext *aStyleContext)
++{
++ bool vis = true;
++
++ if (aStyleContext &&
++ (aStyleContext->StyleDisplay()->mDisplay == StyleDisplay::None ||
++ aStyleContext->StyleVisibility()->mVisible ==
++ NS_STYLE_VISIBILITY_COLLAPSE)) {
++ vis = false;
++ }
++
++ dbusmenu_menuitem_property_set_bool(mNativeData,
++ DBUSMENU_MENUITEM_PROP_VISIBLE,
++ vis);
++}
++
++void
++nsMenuObject::UpdateSensitivity()
++{
++ bool disabled = mContent->AttrValueIs(kNameSpaceID_None,
++ nsGkAtoms::disabled,
++ nsGkAtoms::_true, eCaseMatters);
++
++ dbusmenu_menuitem_property_set_bool(mNativeData,
++ DBUSMENU_MENUITEM_PROP_ENABLED,
++ !disabled);
++
++}
++
++void
++nsMenuObject::UpdateIcon(nsStyleContext *aStyleContext)
++{
++ if (ShouldShowIcon()) {
++ if (!mIconLoader) {
++ mIconLoader = new nsMenuObjectIconLoader(this);
++ }
++
++ mIconLoader->LoadIcon(aStyleContext);
++ } else {
++ if (mIconLoader) {
++ mIconLoader->Destroy();
++ mIconLoader = nullptr;
++ }
++
++ ClearIcon();
++ }
++}
++
++already_AddRefed<nsStyleContext>
++nsMenuObject::GetStyleContext()
++{
++ nsIPresShell *shell = mContent->OwnerDoc()->GetShell();
++ if (!shell) {
++ return nullptr;
++ }
++
++ RefPtr<nsStyleContext> sc =
++ nsComputedDOMStyle::GetStyleContextForElementNoFlush(
++ mContent->AsElement(), nullptr, shell);
++
++ return sc.forget();
++}
++
++nsresult
++nsMenuObject::Init(nsMenuContainer *aParent, nsIContent *aContent)
++{
++ NS_ENSURE_ARG(aParent);
++ NS_ENSURE_ARG(aContent);
++
++ mParent = aParent;
++ mContent = aContent;
++ mListener = aParent->DocListener();
++ NS_ENSURE_ARG(mListener);
++
++ return NS_OK;
++}
++
++nsresult
++nsMenuObject::Init(nsNativeMenuDocListener *aListener, nsIContent *aContent)
++{
++ NS_ENSURE_ARG(aListener);
++ NS_ENSURE_ARG(aContent);
++
++ mParent = nullptr;
++ mContent = aContent;
++ mListener = aListener;
++
++ return NS_OK;
++}
++
++nsMenuObject::nsMenuObject() :
++ mParent(nullptr), mNativeData(nullptr), mFlags(0)
++{
++}
++
++nsMenuObject::~nsMenuObject()
++{
++ nsWeakMenuObjectBase::NotifyDestroyed(this);
++
++ if (mIconLoader) {
++ mIconLoader->Destroy();
++ }
++
++ if (mListener) {
++ mListener->UnregisterForContentChanges(mContent);
++ }
++
++ if (mNativeData) {
++ g_object_unref(mNativeData);
++ mNativeData = nullptr;
++ }
++}
++
++void
++nsMenuObject::CreateNativeData()
++{
++ NS_ASSERTION(mNativeData == nullptr, "This node already has a DbusmenuMenuitem. The old one will be leaked");
++
++ mNativeData = dbusmenu_menuitem_new();
++ InitializeNativeData();
++ if (mParent && mParent->IsBeingDisplayed()) {
++ ContainerIsOpening();
++ }
++
++ mListener->RegisterForContentChanges(mContent, this);
++}
++
++nsresult
++nsMenuObject::AdoptNativeData(DbusmenuMenuitem *aNativeData)
++{
++ NS_ASSERTION(mNativeData == nullptr, "This node already has a DbusmenuMenuitem. The old one will be leaked");
++
++ if (!IsCompatibleWithNativeData(aNativeData)) {
++ return NS_ERROR_FAILURE;
++ }
++
++ mNativeData = aNativeData;
++ g_object_ref(mNativeData);
++
++ PropertyFlags supported = SupportedProperties();
++ PropertyFlags mask = static_cast<PropertyFlags>(1);
++
++ for (uint32_t i = 0; gPropertyStrings[i]; ++i) {
++ if (!(mask & supported)) {
++ dbusmenu_menuitem_property_remove(mNativeData, gPropertyStrings[i]);
++ }
++ mask = static_cast<PropertyFlags>(mask << 1);
++ }
++
++ InitializeNativeData();
++ if (mParent && mParent->IsBeingDisplayed()) {
++ ContainerIsOpening();
++ }
++
++ mListener->RegisterForContentChanges(mContent, this);
++
++ return NS_OK;
++}
++
++void
++nsMenuObject::ContainerIsOpening()
++{
++ if (!nsContentUtils::IsSafeToRunScript()) {
++ nsContentUtils::AddScriptRunner(
++ new nsMenuObjectContainerOpeningRunnable(this));
++ return;
++ }
++
++ UpdateContentAttributes();
++
++ RefPtr<nsStyleContext> sc = GetStyleContext();
++ Update(sc);
++}
++
++/* static */ void
++nsWeakMenuObjectBase::AddWeakReference(nsWeakMenuObjectBase *aWeak)
++{
++ aWeak->SetPrevious(sHead);
++ sHead = aWeak;
++}
++
++/* static */ void
++nsWeakMenuObjectBase::RemoveWeakReference(nsWeakMenuObjectBase *aWeak)
++{
++ if (aWeak == sHead) {
++ sHead = aWeak->GetPrevious();
++ return;
++ }
++
++ nsWeakMenuObjectBase *weak = sHead;
++ while (weak && weak->GetPrevious() != aWeak) {
++ weak = weak->GetPrevious();
++ }
++
++ if (weak) {
++ weak->SetPrevious(aWeak->GetPrevious());
++ }
++}
++
++/* static */ void
++nsWeakMenuObjectBase::NotifyDestroyed(nsMenuObject *aMenuObject)
++{
++ nsWeakMenuObjectBase *weak = sHead;
++ while (weak) {
++ if (weak->getBase() == aMenuObject) {
++ weak->Clear();
++ }
++
++ weak = weak->GetPrevious();
++ }
++}
+Index: firefox-52.0~b9+build2/mozilla/widget/gtk/nsMenuObject.h
+===================================================================
+--- /dev/null
++++ firefox-52.0~b9+build2/mozilla/widget/gtk/nsMenuObject.h
+@@ -0,0 +1,242 @@
++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:expandtab:shiftwidth=4:tabstop=4:
++ */
++/* 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/. */
++
++#ifndef __nsMenuObject_h__
++#define __nsMenuObject_h__
++
++#include "mozilla/Attributes.h"
++#include "nsCOMPtr.h"
++
++#include "nsDbusmenu.h"
++#include "nsNativeMenuDocListener.h"
++
++class nsIAtom;
++class nsIContent;
++class nsStyleContext;
++class nsMenuContainer;
++class nsMenuObjectIconLoader;
++
++#define DBUSMENU_PROPERTIES \
++ DBUSMENU_PROPERTY(Label, DBUSMENU_MENUITEM_PROP_LABEL, 0) \
++ DBUSMENU_PROPERTY(Enabled, DBUSMENU_MENUITEM_PROP_ENABLED, 1) \
++ DBUSMENU_PROPERTY(Visible, DBUSMENU_MENUITEM_PROP_VISIBLE, 2) \
++ DBUSMENU_PROPERTY(IconData, DBUSMENU_MENUITEM_PROP_ICON_DATA, 3) \
++ DBUSMENU_PROPERTY(Type, DBUSMENU_MENUITEM_PROP_TYPE, 4) \
++ DBUSMENU_PROPERTY(Shortcut, DBUSMENU_MENUITEM_PROP_SHORTCUT, 5) \
++ DBUSMENU_PROPERTY(ToggleType, DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE, 6) \
++ DBUSMENU_PROPERTY(ToggleState, DBUSMENU_MENUITEM_PROP_TOGGLE_STATE, 7) \
++ DBUSMENU_PROPERTY(ChildDisplay, DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY, 8)
++
++/*
++ * This is the base class for all menu nodes. Each instance represents
++ * a single node in the menu hierarchy. It wraps the corresponding DOM node and
++ * native menu node, keeps them in sync and transfers events between the two.
++ * It is not reference counted - each node is owned by its parent (the top
++ * level menubar is owned by the window) and keeps a weak pointer to its
++ * parent (which is guaranteed to always be valid because a node will never
++ * outlive its parent). It is not safe to keep a reference to nsMenuObject
++ * externally.
++ */
++class nsMenuObject : public nsNativeMenuChangeObserver
++{
++public:
++ enum EType {
++ eType_MenuBar,
++ eType_Menu,
++ eType_MenuItem,
++ eType_MenuSeparator
++ };
++
++ enum PropertyFlags {
++#define DBUSMENU_PROPERTY(e, s, b) eProp##e = (1 << b),
++ DBUSMENU_PROPERTIES
++#undef DBUSMENU_PROPERTY
++ };
++
++ virtual ~nsMenuObject();
++
++ // Get the native menu item node
++ DbusmenuMenuitem* GetNativeData() const { return mNativeData; }
++
++ // Get the parent menu object
++ nsMenuContainer* Parent() const { return mParent; }
++
++ // Get the content node
++ nsIContent* ContentNode() const { return mContent; }
++
++ // Get the type of this node. Must be provided by subclasses
++ virtual EType Type() const = 0;
++
++ // Get the document listener
++ nsNativeMenuDocListener* DocListener() const { return mListener; }
++
++ // Create the native menu item node (called by containers)
++ void CreateNativeData();
++
++ // Adopt the specified native menu item node (called by containers)
++ nsresult AdoptNativeData(DbusmenuMenuitem *aNativeData);
++
++ // Called by the container to tell us that it's opening
++ void ContainerIsOpening();
++
++protected:
++ nsMenuObject();
++ nsresult Init(nsMenuContainer *aParent, nsIContent *aContent);
++ nsresult Init(nsNativeMenuDocListener *aListener, nsIContent *aContent);
++
++ void UpdateLabel();
++ void UpdateVisibility(nsStyleContext *aStyleContext);
++ void UpdateSensitivity();
++ void UpdateIcon(nsStyleContext *aStyleContext);
++
++ already_AddRefed<nsStyleContext> GetStyleContext();
++
++ uint8_t GetFlags() const { return mFlags; }
++ bool HasFlags(uint8_t aFlags) const
++ {
++ return (mFlags & aFlags) == aFlags;
++ }
++ void SetFlags(uint8_t aFlags)
++ {
++ mFlags |= aFlags;
++ }
++ void ClearFlags(uint8_t aFlags)
++ {
++ mFlags &= ~aFlags;
++ }
++
++private:
++ friend class nsMenuObjectIconLoader;
++
++ // Set up initial properties on the native data, connect to signals etc.
++ // This should be implemented by subclasses
++ virtual void InitializeNativeData();
++
++ // Return the properties that this menu object type supports
++ // This should be implemented by subclasses
++ virtual PropertyFlags SupportedProperties() const;
++
++ // Determine whether this menu object could use the specified
++ // native item. Returns true by default but can be overridden by subclasses
++ virtual bool
++ IsCompatibleWithNativeData(DbusmenuMenuitem *aNativeData) const;
++
++ // Update attributes on this objects content node when the container opens.
++ // This is called before style resolution, and should be implemented by
++ // subclasses who want to modify attributes that might affect style.
++ // This will not be called when there are script blockers
++ virtual void UpdateContentAttributes();
++
++ // Update properties that should be refreshed when the container opens.
++ // This should be implemented by subclasses that have properties which
++ // need refreshing
++ virtual void Update(nsStyleContext *aStyleContext);
++
++ bool ShouldShowIcon() const;
++ void ClearIcon();
++
++ nsCOMPtr<nsIContent> mContent;
++ // mListener is a strong ref for simplicity - someone in the tree needs to
++ // own it, and this only really needs to be the top-level object (as no
++ // children outlives their parent). However, we need to keep it alive until
++ // after running the nsMenuObject destructor for the top-level menu object,
++ // hence the strong ref
++ RefPtr<nsNativeMenuDocListener> mListener;
++ nsMenuContainer *mParent; // [weak]
++ DbusmenuMenuitem *mNativeData; // [strong]
++ RefPtr<nsMenuObjectIconLoader> mIconLoader;
++ uint8_t mFlags;
++};
++
++class nsWeakMenuObjectBase
++{
++public:
++ ~nsWeakMenuObjectBase()
++ {
++ RemoveWeakReference(this);
++ }
++
++ nsMenuObject* getBase() const { return mMenuObject; }
++
++ static void NotifyDestroyed(nsMenuObject *aMenuObject);
++
++protected:
++ nsWeakMenuObjectBase() : mMenuObject(nullptr) { };
++
++ void SetMenuObject(nsMenuObject *aMenuObject)
++ {
++ mMenuObject = aMenuObject;
++
++ mMenuObject ? AddWeakReference(this) : RemoveWeakReference(this);
++ }
++
++private:
++ nsWeakMenuObjectBase* GetPrevious() const { return mPrev; }
++ void SetPrevious(nsWeakMenuObjectBase *aPrev)
++ {
++ mPrev = aPrev;
++ }
++ void Clear() { mMenuObject = nullptr; }
++
++ static void AddWeakReference(nsWeakMenuObjectBase *aWeak);
++ static void RemoveWeakReference(nsWeakMenuObjectBase *aWeak);
++
++ nsWeakMenuObjectBase *mPrev;
++ static nsWeakMenuObjectBase *sHead;
++
++ nsMenuObject *mMenuObject;
++};
++
++// Keep a weak pointer to a menu object. Note, if you need to work
++// with a pointer to this class, use nsAutoWeakMenuObject instead
++template<class T>
++class nsWeakMenuObject : public nsWeakMenuObjectBase
++{
++public:
++ nsWeakMenuObject() :
++ nsWeakMenuObjectBase() { };
++
++ nsWeakMenuObject(T *aMenuObject) :
++ nsWeakMenuObjectBase()
++ {
++ SetMenuObject(aMenuObject);
++ }
++
++ T* get() const { return static_cast<T *>(getBase()); }
++
++ T* operator->() const { return get(); }
++
++ operator T*() const { return get(); }
++};
++
++template<class T>
++class nsAutoWeakMenuObject
++{
++public:
++ nsAutoWeakMenuObject() { };
++
++ nsAutoWeakMenuObject(T *aMenuObject) :
++ mPtr(new nsWeakMenuObject<T>(aMenuObject)) { };
++
++ nsAutoWeakMenuObject(nsWeakMenuObject<T> *aWeak) :
++ mPtr(aWeak) { };
++
++ T* get() const { return static_cast<T *>(*mPtr); }
++
++ T* operator->() const { return get(); }
++
++ operator T*() const { return get(); }
++
++ nsWeakMenuObject<T>* getWeakPtr() const { return mPtr; }
++
++ nsWeakMenuObject<T>* forget() { return mPtr.forget(); }
++
++private:
++ nsAutoPtr<nsWeakMenuObject<T> > mPtr;
++};
++
++#endif /* __nsMenuObject_h__ */
+Index: firefox-52.0~b9+build2/mozilla/widget/gtk/nsMenuSeparator.cpp
+===================================================================
+--- /dev/null
++++ firefox-52.0~b9+build2/mozilla/widget/gtk/nsMenuSeparator.cpp
+@@ -0,0 +1,90 @@
++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:expandtab:shiftwidth=4:tabstop=4:
++ */
++/* 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/. */
++
++#include "nsAutoPtr.h"
++#include "nsCRT.h"
++#include "nsGkAtoms.h"
++#include "nsStyleContext.h"
++
++#include "nsDbusmenu.h"
++
++#include "nsMenuContainer.h"
++#include "nsMenuSeparator.h"
++
++void
++nsMenuSeparator::InitializeNativeData()
++{
++ dbusmenu_menuitem_property_set(GetNativeData(),
++ DBUSMENU_MENUITEM_PROP_TYPE,
++ "separator");
++}
++
++void
++nsMenuSeparator::Update(nsStyleContext *aContext)
++{
++ UpdateVisibility(aContext);
++}
++
++bool
++nsMenuSeparator::IsCompatibleWithNativeData(DbusmenuMenuitem *aNativeData) const
++{
++ return nsCRT::strcmp(dbusmenu_menuitem_property_get(aNativeData,
++ DBUSMENU_MENUITEM_PROP_TYPE),
++ "separator") == 0;
++}
++
++nsMenuObject::PropertyFlags
++nsMenuSeparator::SupportedProperties() const
++{
++ return static_cast<nsMenuObject::PropertyFlags>(
++ nsMenuObject::ePropVisible |
++ nsMenuObject::ePropType
++ );
++}
++
++nsMenuSeparator::nsMenuSeparator()
++{
++ MOZ_COUNT_CTOR(nsMenuSeparator);
++}
++
++nsMenuSeparator::~nsMenuSeparator()
++{
++ MOZ_COUNT_DTOR(nsMenuSeparator);
++}
++
++nsMenuObject::EType
++nsMenuSeparator::Type() const
++{
++ return nsMenuObject::eType_MenuSeparator;
++}
++
++/* static */ nsMenuObject*
++nsMenuSeparator::Create(nsMenuContainer *aParent, nsIContent *aContent)
++{
++ nsAutoPtr<nsMenuSeparator> sep(new nsMenuSeparator());
++ if (NS_FAILED(sep->Init(aParent, aContent))) {
++ return nullptr;
++ }
++
++ return sep.forget();
++}
++
++void
++nsMenuSeparator::OnAttributeChanged(nsIContent *aContent, nsIAtom *aAttribute)
++{
++ NS_ASSERTION(aContent == ContentNode(), "Received an event that wasn't meant for us!");
++
++ if (!Parent()->IsBeingDisplayed()) {
++ return;
++ }
++
++ if (aAttribute == nsGkAtoms::hidden ||
++ aAttribute == nsGkAtoms::collapsed) {
++ RefPtr<nsStyleContext> sc = GetStyleContext();
++ UpdateVisibility(sc);
++ }
++}
+Index: firefox-52.0~b9+build2/mozilla/widget/gtk/nsMenuSeparator.h
+===================================================================
+--- /dev/null
++++ firefox-52.0~b9+build2/mozilla/widget/gtk/nsMenuSeparator.h
+@@ -0,0 +1,41 @@
++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:expandtab:shiftwidth=4:tabstop=4:
++ */
++/* 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/. */
++
++#ifndef __nsMenuSeparator_h__
++#define __nsMenuSeparator_h__
++
++#include "mozilla/Attributes.h"
++
++#include "nsMenuObject.h"
++
++class nsIContent;
++class nsIAtom;
++class nsMenuContainer;
++
++// Menu separator class
++class nsMenuSeparator final : public nsMenuObject
++{
++public:
++ ~nsMenuSeparator();
++
++ nsMenuObject::EType Type() const;
++
++ static nsMenuObject* Create(nsMenuContainer *aParent,
++ nsIContent *aContent);
++
++ void OnAttributeChanged(nsIContent *aContent, nsIAtom *aAttribute);
++
++private:
++ nsMenuSeparator();
++
++ void InitializeNativeData();
++ void Update(nsStyleContext *aStyleContext);
++ bool IsCompatibleWithNativeData(DbusmenuMenuitem *aNativeData) const;
++ nsMenuObject::PropertyFlags SupportedProperties() const;
++};
++
++#endif /* __nsMenuSeparator_h__ */
+Index: firefox-52.0~b9+build2/mozilla/widget/gtk/nsNativeMenuAtomList.h
+===================================================================
+--- /dev/null
++++ firefox-52.0~b9+build2/mozilla/widget/gtk/nsNativeMenuAtomList.h
+@@ -0,0 +1,11 @@
++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:expandtab:shiftwidth=4:tabstop=4:
++ */
++/* 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/. */
++
++WIDGET_ATOM2(menuitem_with_favicon, "menuitem-with-favicon")
++WIDGET_ATOM2(_moz_menupopupstate, "_moz-menupopupstate")
++WIDGET_ATOM(openedwithkey)
++WIDGET_ATOM(shellshowingmenubar)
+Index: firefox-52.0~b9+build2/mozilla/widget/gtk/nsNativeMenuAtoms.cpp
+===================================================================
+--- /dev/null
++++ firefox-52.0~b9+build2/mozilla/widget/gtk/nsNativeMenuAtoms.cpp
+@@ -0,0 +1,39 @@
++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:expandtab:shiftwidth=4:tabstop=4:
++ */
++/* 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/. */
++
++#include "nsIAtom.h"
++#include "nsStaticAtom.h"
++
++#include "nsNativeMenuAtoms.h"
++
++using namespace mozilla;
++
++#define WIDGET_ATOM(_name) nsIAtom* nsNativeMenuAtoms::_name;
++#define WIDGET_ATOM2(_name, _value) nsIAtom* nsNativeMenuAtoms::_name;
++#include "nsNativeMenuAtomList.h"
++#undef WIDGET_ATOM
++#undef WIDGET_ATOM2
++
++#define WIDGET_ATOM(name_) NS_STATIC_ATOM_BUFFER(name_##_buffer, #name_)
++#define WIDGET_ATOM2(name_, value_) NS_STATIC_ATOM_BUFFER(name_##_buffer, value_)
++#include "nsNativeMenuAtomList.h"
++#undef WIDGET_ATOM
++#undef WIDGET_ATOM2
++
++static const nsStaticAtom gAtoms[] = {
++#define WIDGET_ATOM(name_) NS_STATIC_ATOM(name_##_buffer, &nsNativeMenuAtoms::name_),
++#define WIDGET_ATOM2(name_, value_) NS_STATIC_ATOM(name_##_buffer, &nsNativeMenuAtoms::name_),
++#include "nsNativeMenuAtomList.h"
++#undef WIDGET_ATOM
++#undef WIDGET_ATOM2
++};
++
++/* static */ void
++nsNativeMenuAtoms::RegisterAtoms()
++{
++ NS_RegisterStaticAtoms(gAtoms);
++}
+Index: firefox-52.0~b9+build2/mozilla/widget/gtk/nsNativeMenuAtoms.h
+===================================================================
+--- /dev/null
++++ firefox-52.0~b9+build2/mozilla/widget/gtk/nsNativeMenuAtoms.h
+@@ -0,0 +1,25 @@
++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:expandtab:shiftwidth=4:tabstop=4:
++ */
++/* 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/. */
++
++#ifndef __nsNativeMenuAtoms_h__
++#define __nsNativeMenuAtoms_h__
++
++class nsIAtom;
++
++class nsNativeMenuAtoms
++{
++public:
++ static void RegisterAtoms();
++
++#define WIDGET_ATOM(_name) static nsIAtom* _name;
++#define WIDGET_ATOM2(_name, _value) static nsIAtom* _name;
++#include "nsNativeMenuAtomList.h"
++#undef WIDGET_ATOM
++#undef WIDGET_ATOM2
++};
++
++#endif /* __nsNativeMenuAtoms_h__ */
+Index: firefox-52.0~b9+build2/mozilla/widget/gtk/nsNativeMenuDocListener.cpp
+===================================================================
+--- /dev/null
++++ firefox-52.0~b9+build2/mozilla/widget/gtk/nsNativeMenuDocListener.cpp
+@@ -0,0 +1,370 @@
++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:expandtab:shiftwidth=4:tabstop=4:
++ */
++/* 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/. */
++
++#include "mozilla/Assertions.h"
++#include "mozilla/DebugOnly.h"
++#include "mozilla/dom/Element.h"
++#include "nsContentUtils.h"
++#include "nsIAtom.h"
++#include "nsIContent.h"
++#include "nsIDocument.h"
++
++#include "nsMenuContainer.h"
++
++#include "nsNativeMenuDocListener.h"
++
++using namespace mozilla;
++
++uint32_t nsNativeMenuDocListener::sUpdateDepth = 0;
++
++nsNativeMenuDocListenerTArray *gPendingListeners;
++
++/*
++ * Small helper which caches a single listener, so that consecutive
++ * events which go to the same node avoid multiple hash table lookups
++ */
++class MOZ_STACK_CLASS DispatchHelper
++{
++public:
++ DispatchHelper(nsNativeMenuDocListener *aListener,
++ nsIContent *aContent
++ MOZ_GUARD_OBJECT_NOTIFIER_PARAM) :
++ mObserver(nullptr)
++ {
++ MOZ_GUARD_OBJECT_NOTIFIER_INIT;
++ if (aContent == aListener->mLastSource) {
++ mObserver = aListener->mLastTarget;
++ } else {
++ mObserver = aListener->mContentToObserverTable.Get(aContent);
++ if (mObserver) {
++ aListener->mLastSource = aContent;
++ aListener->mLastTarget = mObserver;
++ }
++ }
++ }
++
++ ~DispatchHelper() { };
++
++ nsNativeMenuChangeObserver* Observer() const { return mObserver; }
++
++ bool HasObserver() const { return !!mObserver; }
++
++private:
++ nsNativeMenuChangeObserver *mObserver;
++ MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
++};
++
++NS_IMPL_ISUPPORTS(nsNativeMenuDocListener, nsIMutationObserver)
++
++void
++nsNativeMenuDocListener::DoAttributeChanged(nsIContent *aContent,
++ nsIAtom *aAttribute)
++{
++ DispatchHelper h(this, aContent);
++ if (h.HasObserver()) {
++ h.Observer()->OnAttributeChanged(aContent, aAttribute);
++ }
++}
++
++void
++nsNativeMenuDocListener::DoContentInserted(nsIContent *aContainer,
++ nsIContent *aChild,
++ nsIContent *aPrevSibling)
++{
++ DispatchHelper h(this, aContainer);
++ if (h.HasObserver()) {
++ h.Observer()->OnContentInserted(aContainer, aChild, aPrevSibling);
++ }
++}
++
++void
++nsNativeMenuDocListener::DoContentRemoved(nsIContent *aContainer,
++ nsIContent *aChild)
++{
++ DispatchHelper h(this, aContainer);
++ if (h.HasObserver()) {
++ h.Observer()->OnContentRemoved(aContainer, aChild);
++ }
++}
++
++void
++nsNativeMenuDocListener::DoBeginUpdateBatch(nsIContent *aTarget)
++{
++ DispatchHelper h(this, aTarget);
++ if (h.HasObserver()) {
++ h.Observer()->BeginUpdateBatch(aTarget);
++ }
++}
++
++void
++nsNativeMenuDocListener::DoEndUpdateBatch(nsIContent *aTarget)
++{
++ DispatchHelper h(this, aTarget);
++ if (h.HasObserver()) {
++ h.Observer()->EndUpdateBatch();
++ }
++}
++
++void
++nsNativeMenuDocListener::FlushPendingMutations()
++{
++ nsIContent *batchTarget = nullptr;
++ bool inUpdateBatch = false;
++
++ while (mPendingMutations.Length() > 0) {
++ MutationRecord *m = mPendingMutations[0];
++
++ if (m->mTarget != batchTarget) {
++ if (inUpdateBatch) {
++ DoEndUpdateBatch(batchTarget);
++ inUpdateBatch = false;
++ }
++
++ batchTarget = m->mTarget;
++
++ if (mPendingMutations.Length() > 1 &&
++ mPendingMutations[1]->mTarget == batchTarget) {
++ DoBeginUpdateBatch(batchTarget);
++ inUpdateBatch = true;
++ }
++ }
++
++ switch (m->mType) {
++ case MutationRecord::eAttributeChanged:
++ DoAttributeChanged(m->mTarget, m->mAttribute);
++ break;
++ case MutationRecord::eContentInserted:
++ DoContentInserted(m->mTarget, m->mChild, m->mPrevSibling);
++ break;
++ case MutationRecord::eContentRemoved:
++ DoContentRemoved(m->mTarget, m->mChild);
++ break;
++ default:
++ NS_NOTREACHED("Invalid type");
++ }
++
++ mPendingMutations.RemoveElementAt(0);
++ }
++
++ if (inUpdateBatch) {
++ DoEndUpdateBatch(batchTarget);
++ }
++}
++
++/* static */ void
++nsNativeMenuDocListener::ScheduleFlush(nsNativeMenuDocListener *aListener)
++{
++ NS_ASSERTION(sUpdateDepth > 0, "Shouldn't be doing this now");
++
++ if (!gPendingListeners) {
++ gPendingListeners = new nsNativeMenuDocListenerTArray;
++ }
++
++ if (gPendingListeners->IndexOf(aListener) ==
++ nsNativeMenuDocListenerTArray::NoIndex) {
++ gPendingListeners->AppendElement(aListener);
++ }
++}
++
++/* static */ void
++nsNativeMenuDocListener::CancelFlush(nsNativeMenuDocListener *aListener)
++{
++ if (!gPendingListeners) {
++ return;
++ }
++
++ gPendingListeners->RemoveElement(aListener);
++}
++
++/* static */ void
++nsNativeMenuDocListener::EndUpdates()
++{
++ if (sUpdateDepth == 1 && gPendingListeners) {
++ while (gPendingListeners->Length() > 0) {
++ (*gPendingListeners)[0]->FlushPendingMutations();
++ gPendingListeners->RemoveElementAt(0);
++ }
++ }
++
++ NS_ASSERTION(sUpdateDepth > 0, "Negative update depth!");
++ sUpdateDepth--;
++}
++
++nsNativeMenuDocListener::nsNativeMenuDocListener() :
++ mDocument(nullptr),
++ mLastSource(nullptr),
++ mLastTarget(nullptr)
++{
++ MOZ_COUNT_CTOR(nsNativeMenuDocListener);
++}
++
++nsNativeMenuDocListener::~nsNativeMenuDocListener()
++{
++ MOZ_ASSERT(mContentToObserverTable.Count() == 0,
++ "Some nodes forgot to unregister listeners. This is bad! (and we're lucky we made it this far)");
++ MOZ_COUNT_DTOR(nsNativeMenuDocListener);
++}
++
++nsresult
++nsNativeMenuDocListener::Init(nsIContent *aRootNode)
++{
++ NS_ENSURE_ARG(aRootNode);
++
++ mRootNode = aRootNode;
++
++ return NS_OK;
++}
++
++void
++nsNativeMenuDocListener::AttributeChanged(nsIDocument *aDocument,
++ mozilla::dom::Element *aElement,
++ int32_t aNameSpaceID,
++ nsIAtom *aAttribute,
++ int32_t aModType,
++ const nsAttrValue* aOldValue)
++{
++ if (sUpdateDepth == 0) {
++ DoAttributeChanged(aElement, aAttribute);
++ return;
++ }
++
++ MutationRecord *m = *mPendingMutations.AppendElement(new MutationRecord);
++ m->mType = MutationRecord::eAttributeChanged;
++ m->mTarget = aElement;
++ m->mAttribute = aAttribute;
++
++ ScheduleFlush(this);
++}
++
++void
++nsNativeMenuDocListener::ContentAppended(nsIDocument *aDocument,
++ nsIContent *aContainer,
++ nsIContent *aFirstNewContent,
++ int32_t aNewIndexInContainer)
++{
++ for (nsIContent *c = aFirstNewContent; c; c = c->GetNextSibling()) {
++ ContentInserted(aDocument, aContainer, c, 0);
++ }
++}
++
++void
++nsNativeMenuDocListener::ContentInserted(nsIDocument *aDocument,
++ nsIContent *aContainer,
++ nsIContent *aChild,
++ int32_t aIndexInContainer)
++{
++ nsIContent *prevSibling = nsMenuContainer::GetPreviousSupportedSibling(aChild);
++
++ if (sUpdateDepth == 0) {
++ DoContentInserted(aContainer, aChild, prevSibling);
++ return;
++ }
++
++ MutationRecord *m = *mPendingMutations.AppendElement(new MutationRecord);
++ m->mType = MutationRecord::eContentInserted;
++ m->mTarget = aContainer;
++ m->mChild = aChild;
++ m->mPrevSibling = prevSibling;
++
++ ScheduleFlush(this);
++}
++
++void
++nsNativeMenuDocListener::ContentRemoved(nsIDocument *aDocument,
++ nsIContent *aContainer,
++ nsIContent *aChild,
++ int32_t aIndexInContainer,
++ nsIContent *aPreviousSibling)
++{
++ if (sUpdateDepth == 0) {
++ DoContentRemoved(aContainer, aChild);
++ return;
++ }
++
++ MutationRecord *m = *mPendingMutations.AppendElement(new MutationRecord);
++ m->mType = MutationRecord::eContentRemoved;
++ m->mTarget = aContainer;
++ m->mChild = aChild;
++
++ ScheduleFlush(this);
++}
++
++void
++nsNativeMenuDocListener::NodeWillBeDestroyed(const nsINode *aNode)
++{
++ mDocument = nullptr;
++}
++
++/* static */ already_AddRefed<nsNativeMenuDocListener>
++nsNativeMenuDocListener::Create(nsIContent *aRootNode)
++{
++ RefPtr<nsNativeMenuDocListener> listener = new nsNativeMenuDocListener();
++ if (NS_FAILED(listener->Init(aRootNode))) {
++ return nullptr;
++ }
++
++ return listener.forget();
++}
++
++void
++nsNativeMenuDocListener::RegisterForContentChanges(nsIContent *aContent,
++ nsNativeMenuChangeObserver *aObserver)
++{
++ NS_ASSERTION(aContent, "Need content parameter");
++ NS_ASSERTION(aObserver, "Need observer parameter");
++ if (!aContent || !aObserver) {
++ return;
++ }
++
++ DebugOnly<nsNativeMenuChangeObserver *> old;
++ NS_ASSERTION(!mContentToObserverTable.Get(aContent, &old) || old == aObserver,
++ "Multiple observers for the same content node are not supported");
++
++ mContentToObserverTable.Put(aContent, aObserver);
++}
++
++void
++nsNativeMenuDocListener::UnregisterForContentChanges(nsIContent *aContent)
++{
++ NS_ASSERTION(aContent, "Need content parameter");
++ if (!aContent) {
++ return;
++ }
++
++ mContentToObserverTable.Remove(aContent);
++ if (aContent == mLastSource) {
++ mLastSource = nullptr;
++ mLastTarget = nullptr;
++ }
++}
++
++void
++nsNativeMenuDocListener::Start()
++{
++ if (mDocument) {
++ return;
++ }
++
++ mDocument = mRootNode->OwnerDoc();
++ if (!mDocument) {
++ return;
++ }
++
++ mDocument->AddMutationObserver(this);
++}
++
++void
++nsNativeMenuDocListener::Stop()
++{
++ if (mDocument) {
++ mDocument->RemoveMutationObserver(this);
++ mDocument = nullptr;
++ }
++
++ CancelFlush(this);
++ mPendingMutations.Clear();
++}
+Index: firefox-52.0~b9+build2/mozilla/widget/gtk/nsNativeMenuDocListener.h
+===================================================================
+--- /dev/null
++++ firefox-52.0~b9+build2/mozilla/widget/gtk/nsNativeMenuDocListener.h
+@@ -0,0 +1,153 @@
++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:expandtab:shiftwidth=4:tabstop=4:
++ */
++/* 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/. */
++
++#ifndef __nsNativeMenuDocListener_h__
++#define __nsNativeMenuDocListener_h__
++
++#include "mozilla/Attributes.h"
++#include "mozilla/GuardObjects.h"
++#include "mozilla/RefPtr.h"
++#include "nsAutoPtr.h"
++#include "nsDataHashtable.h"
++#include "nsStubMutationObserver.h"
++#include "nsTArray.h"
++
++class nsIAtom;
++class nsIContent;
++class nsIDocument;
++class nsNativeMenuChangeObserver;
++
++/*
++ * This class keeps a mapping of content nodes to observers and forwards DOM
++ * mutations to these. There is exactly one of these for every menubar.
++ */
++class nsNativeMenuDocListener final : nsStubMutationObserver
++{
++public:
++ NS_DECL_ISUPPORTS
++ NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
++ NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
++ NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
++ NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
++ NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED
++
++ static already_AddRefed<nsNativeMenuDocListener> Create(nsIContent *aRootNode);
++
++ // Register an observer to receive mutation events for the specified
++ // content node. The caller must keep the observer alive until
++ // UnregisterForContentChanges is called.
++ void RegisterForContentChanges(nsIContent *aContent,
++ nsNativeMenuChangeObserver *aObserver);
++
++ // Unregister the registered observer for the specified content node
++ void UnregisterForContentChanges(nsIContent *aContent);
++
++ // Start listening to the document and forwarding DOM mutations to
++ // registered observers.
++ void Start();
++
++ // Stop listening to the document. No DOM mutations will be forwarded
++ // to registered observers.
++ void Stop();
++
++private:
++ friend class nsNativeMenuAutoUpdateBatch;
++ friend class DispatchHelper;
++
++ struct MutationRecord {
++ enum RecordType {
++ eAttributeChanged,
++ eContentInserted,
++ eContentRemoved
++ } mType;
++
++ nsCOMPtr<nsIContent> mTarget;
++ nsCOMPtr<nsIContent> mChild;
++ nsCOMPtr<nsIContent> mPrevSibling;
++ nsCOMPtr<nsIAtom> mAttribute;
++ };
++
++ nsNativeMenuDocListener();
++ ~nsNativeMenuDocListener();
++ nsresult Init(nsIContent *aRootNode);
++
++ void DoAttributeChanged(nsIContent *aContent, nsIAtom *aAttribute);
++ void DoContentInserted(nsIContent *aContainer,
++ nsIContent *aChild,
++ nsIContent *aPrevSibling);
++ void DoContentRemoved(nsIContent *aContainer, nsIContent *aChild);
++ void DoBeginUpdateBatch(nsIContent *aTarget);
++ void DoEndUpdateBatch(nsIContent *aTarget);
++ void FlushPendingMutations();
++ static void ScheduleFlush(nsNativeMenuDocListener *aListener);
++ static void CancelFlush(nsNativeMenuDocListener *aListener);
++ static void BeginUpdates() { ++sUpdateDepth; }
++ static void EndUpdates();
++
++ nsCOMPtr<nsIContent> mRootNode;
++ nsIDocument *mDocument;
++ nsIContent *mLastSource;
++ nsNativeMenuChangeObserver *mLastTarget;
++ nsTArray<nsAutoPtr<MutationRecord> > mPendingMutations;
++ nsDataHashtable<nsPtrHashKey<nsIContent>, nsNativeMenuChangeObserver*> mContentToObserverTable;
++
++ static uint32_t sUpdateDepth;
++};
++
++typedef nsTArray<RefPtr<nsNativeMenuDocListener> > nsNativeMenuDocListenerTArray;
++
++class nsNativeMenuChangeObserver
++{
++public:
++ virtual void OnAttributeChanged(nsIContent *aContent, nsIAtom *aAttribute)
++ {
++ NS_ERROR("Unhandled AttributeChanged() notification");
++ }
++
++ virtual void OnContentInserted(nsIContent *aContainer,
++ nsIContent *aChild,
++ nsIContent *aPrevSibling)
++ {
++ NS_ERROR("Unhandled ContentInserted() notification");
++ }
++
++ virtual void OnContentRemoved(nsIContent *aContainer, nsIContent *aChild)
++ {
++ NS_ERROR("Unhandled ContentRemoved() notification");
++ }
++
++ virtual void BeginUpdateBatch(nsIContent *aContent) { };
++
++ virtual void EndUpdateBatch() { };
++};
++
++/*
++ * This class is intended to be used inside GObject signal handlers.
++ * It allows us to queue updates until we have finished delivering
++ * events to Gecko, and then we can batch updates to our view of the
++ * menu. This allows us to do menu updates without altering the structure
++ * seen by the OS.
++ */
++class MOZ_STACK_CLASS nsNativeMenuAutoUpdateBatch
++{
++public:
++ nsNativeMenuAutoUpdateBatch(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM)
++ {
++ MOZ_GUARD_OBJECT_NOTIFIER_INIT;
++ nsNativeMenuDocListener::BeginUpdates();
++ }
++
++ ~nsNativeMenuAutoUpdateBatch()
++ {
++ nsNativeMenuDocListener::EndUpdates();
++ }
++
++private:
++ MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
++};
++
++#endif /* __nsNativeMenuDocListener_h__ */
+Index: firefox-52.0~b9+build2/mozilla/widget/gtk/nsNativeMenuService.cpp
+===================================================================
+--- /dev/null
++++ firefox-52.0~b9+build2/mozilla/widget/gtk/nsNativeMenuService.cpp
+@@ -0,0 +1,506 @@
++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:expandtab:shiftwidth=4:tabstop=4:
++ */
++/* 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/. */
++
++#include "mozilla/Preferences.h"
++#include "mozilla/Services.h"
++#include "nsAutoPtr.h"
++#include "nsCOMPtr.h"
++#include "nsCRT.h"
++#include "nsGtkUtils.h"
++#include "nsIContent.h"
++#include "nsIObserverService.h"
++#include "nsIWidget.h"
++#include "nsServiceManagerUtils.h"
++#include "nsWindow.h"
++#include "nsXPCOM.h"
++#include "prlink.h"
++
++#include "nsDbusmenu.h"
++#include "nsMenuBar.h"
++#include "nsNativeMenuAtoms.h"
++#include "nsNativeMenuDocListener.h"
++
++#include <glib-object.h>
++#include <pango/pango.h>
++#include <stdlib.h>
++
++#include "nsNativeMenuService.h"
++
++using namespace mozilla;
++
++nsNativeMenuService* nsNativeMenuService::sService = nullptr;
++bool nsNativeMenuService::sShutdown = false;
++
++extern PangoLayout* gPangoLayout;
++extern nsNativeMenuDocListenerTArray* gPendingListeners;
++
++static const nsTArray<nsMenuBar *>::index_type NoIndex = nsTArray<nsMenuBar *>::NoIndex;
++
++#if not GLIB_CHECK_VERSION(2,26,0)
++enum GBusType {
++ G_BUS_TYPE_STARTER = -1,
++ G_BUS_TYPE_NONE = 0,
++ G_BUS_TYPE_SYSTEM = 1,
++ G_BUS_TYPE_SESSION = 2
++};
++
++enum GDBusProxyFlags {
++ G_DBUS_PROXY_FLAGS_NONE = 0,
++ G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES = 1 << 0,
++ G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS = 1 << 1,
++ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START = 1 << 2,
++ G_DBUS_PROXY_FLAGS_GET_INVALIDATED_PROPERTIES = 1 << 3
++};
++
++enum GDBusCallFlags {
++ G_DBUS_CALL_FLAGS_NONE = 0,
++ G_DBUS_CALL_FLAGS_NO_AUTO_START = 1 << 0
++};
++
++typedef _GDBusInterfaceInfo GDBusInterfaceInfo;
++typedef _GDBusProxy GDBusProxy;
++typedef _GVariant GVariant;
++#endif
++
++#undef g_dbus_proxy_new_for_bus
++#undef g_dbus_proxy_new_for_bus_finish
++#undef g_dbus_proxy_call
++#undef g_dbus_proxy_call_finish
++#undef g_dbus_proxy_get_name_owner
++
++typedef void (*_g_dbus_proxy_new_for_bus_fn)(GBusType, GDBusProxyFlags,
++ GDBusInterfaceInfo*,
++ const gchar*, const gchar*,
++ const gchar*, GCancellable*,
++ GAsyncReadyCallback, gpointer);
++
++typedef GDBusProxy* (*_g_dbus_proxy_new_for_bus_finish_fn)(GAsyncResult*,
++ GError**);
++typedef void (*_g_dbus_proxy_call_fn)(GDBusProxy*, const gchar*, GVariant*,
++ GDBusCallFlags, gint, GCancellable*,
++ GAsyncReadyCallback, gpointer);
++typedef GVariant* (*_g_dbus_proxy_call_finish_fn)(GDBusProxy*, GAsyncResult*,
++ GError**);
++typedef gchar* (*_g_dbus_proxy_get_name_owner_fn)(GDBusProxy*);
++
++static _g_dbus_proxy_new_for_bus_fn _g_dbus_proxy_new_for_bus;
++static _g_dbus_proxy_new_for_bus_finish_fn _g_dbus_proxy_new_for_bus_finish;
++static _g_dbus_proxy_call_fn _g_dbus_proxy_call;
++static _g_dbus_proxy_call_finish_fn _g_dbus_proxy_call_finish;
++static _g_dbus_proxy_get_name_owner_fn _g_dbus_proxy_get_name_owner;
++
++#define g_dbus_proxy_new_for_bus _g_dbus_proxy_new_for_bus
++#define g_dbus_proxy_new_for_bus_finish _g_dbus_proxy_new_for_bus_finish
++#define g_dbus_proxy_call _g_dbus_proxy_call
++#define g_dbus_proxy_call_finish _g_dbus_proxy_call_finish
++#define g_dbus_proxy_get_name_owner _g_dbus_proxy_get_name_owner
++
++static PRLibrary *gGIOLib = nullptr;
++
++static nsresult
++GDBusInit()
++{
++ gGIOLib = PR_LoadLibrary("libgio-2.0.so.0");
++ if (!gGIOLib) {
++ return NS_ERROR_FAILURE;
++ }
++
++ g_dbus_proxy_new_for_bus = (_g_dbus_proxy_new_for_bus_fn)PR_FindFunctionSymbol(gGIOLib, "g_dbus_proxy_new_for_bus");
++ g_dbus_proxy_new_for_bus_finish = (_g_dbus_proxy_new_for_bus_finish_fn)PR_FindFunctionSymbol(gGIOLib, "g_dbus_proxy_new_for_bus_finish");
++ g_dbus_proxy_call = (_g_dbus_proxy_call_fn)PR_FindFunctionSymbol(gGIOLib, "g_dbus_proxy_call");
++ g_dbus_proxy_call_finish = (_g_dbus_proxy_call_finish_fn)PR_FindFunctionSymbol(gGIOLib, "g_dbus_proxy_call_finish");
++ g_dbus_proxy_get_name_owner = (_g_dbus_proxy_get_name_owner_fn)PR_FindFunctionSymbol(gGIOLib, "g_dbus_proxy_get_name_owner");
++
++ if (!g_dbus_proxy_new_for_bus ||
++ !g_dbus_proxy_new_for_bus_finish ||
++ !g_dbus_proxy_call ||
++ !g_dbus_proxy_call_finish ||
++ !g_dbus_proxy_get_name_owner) {
++ return NS_ERROR_FAILURE;
++ }
++
++ return NS_OK;
++}
++
++NS_IMPL_ISUPPORTS(nsNativeMenuService, nsINativeMenuService, nsIObserver)
++
++/* static */ void
++nsNativeMenuService::EnsureInitialized()
++{
++ if (!sService) {
++ nsCOMPtr<nsINativeMenuService> service =
++ do_GetService("@mozilla.org/widget/nativemenuservice;1");
++ }
++ NS_ASSERTION(sService != nullptr || sShutdown == true,
++ "Failed to create native menu service");
++}
++
++void
++nsNativeMenuService::SetOnline(bool aOnline)
++{
++ if (!Preferences::GetBool("ui.use_unity_menubar", true)) {
++ aOnline = false;
++ }
++
++ mOnline = aOnline;
++ if (aOnline) {
++ for (uint32_t i = 0; i < mMenuBars.Length(); ++i) {
++ RegisterNativeMenuBar(mMenuBars[i]);
++ }
++ } else {
++ for (uint32_t i = 0; i < mMenuBars.Length(); ++i) {
++ mMenuBars[i]->Deactivate();
++ }
++ }
++}
++
++void
++nsNativeMenuService::RegisterNativeMenuBar(nsMenuBar *aMenuBar)
++{
++ if (!mOnline) {
++ return;
++ }
++
++ // This will effectively create the native menubar for
++ // exporting over the session bus, and hide the XUL menubar
++ aMenuBar->Activate();
++
++ if (!mDbusProxy ||
++ !gtk_widget_get_mapped(aMenuBar->TopLevelWindow()) ||
++ aMenuBar->RegisterRequestInProgress()) {
++ // Don't go further if we don't have a proxy for the shell menu
++ // service, the window isn't mapped or there is a request in progress.
++ return;
++ }
++
++ uint32_t xid = aMenuBar->WindowId();
++ nsAdoptingCString path = aMenuBar->ObjectPath();
++ if (xid == 0 || path.IsEmpty()) {
++ NS_WARNING("Menubar has invalid XID or object path");
++ return;
++ }
++
++ // We keep a weak ref because we can't assume that GDBus cancellation
++ // is reliable (see https://launchpad.net/bugs/953562)
++ nsAutoWeakMenuObject<nsMenuBar> weakMenuBar(aMenuBar);
++
++ g_dbus_proxy_call(mDbusProxy, "RegisterWindow",
++ g_variant_new("(uo)", xid, path.get()),
++ G_DBUS_CALL_FLAGS_NONE, -1,
++ aMenuBar->BeginRegisterRequest(),
++ register_native_menubar_cb, weakMenuBar.forget());
++}
++
++/* static */ void
++nsNativeMenuService::name_owner_changed_cb(GObject *gobject,
++ GParamSpec *pspec,
++ gpointer user_data)
++{
++ nsNativeMenuService::GetSingleton()->OnNameOwnerChanged();
++}
++
++/* static */ void
++nsNativeMenuService::proxy_created_cb(GObject *source_object,
++ GAsyncResult *res,
++ gpointer user_data)
++{
++ GError *error = nullptr;
++ GDBusProxy *proxy = g_dbus_proxy_new_for_bus_finish(res, &error);
++ if (error && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
++ g_error_free(error);
++ return;
++ }
++
++ if (error) {
++ g_error_free(error);
++ }
++
++ // We need this check because we can't assume that GDBus cancellation
++ // is reliable (see https://launchpad.net/bugs/953562)
++ if (sShutdown) {
++ if (proxy) {
++ g_object_unref(proxy);
++ }
++ return;
++ }
++
++ nsNativeMenuService::GetSingleton()->OnProxyCreated(proxy);
++}
++
++/* static */ void
++nsNativeMenuService::register_native_menubar_cb(GObject *source_object,
++ GAsyncResult *res,
++ gpointer user_data)
++{
++ nsAutoWeakMenuObject<nsMenuBar> weakMenuBar(
++ static_cast<nsWeakMenuObject<nsMenuBar> *>(user_data));
++
++ GError *error = nullptr;
++ GVariant *results = g_dbus_proxy_call_finish(G_DBUS_PROXY(source_object),
++ res, &error);
++ if (results) {
++ // There's nothing useful in the response
++ g_variant_unref(results);
++ }
++
++ bool success = error ? false : true;
++ if (error && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
++ g_error_free(error);
++ return;
++ }
++
++ if (error) {
++ g_error_free(error);
++ }
++
++ if (sShutdown || !weakMenuBar) {
++ return;
++ }
++
++ nsNativeMenuService::GetSingleton()->OnNativeMenuBarRegistered(weakMenuBar,
++ success);
++}
++
++/* static */ gboolean
++nsNativeMenuService::map_event_cb(GtkWidget *widget,
++ GdkEvent *event,
++ gpointer user_data)
++{
++ nsMenuBar *menubar = static_cast<nsMenuBar *>(user_data);
++ nsNativeMenuService::GetSingleton()->RegisterNativeMenuBar(menubar);
++
++ return FALSE;
++}
++
++void
++nsNativeMenuService::OnNameOwnerChanged()
++{
++ char *owner = g_dbus_proxy_get_name_owner(mDbusProxy);
++ SetOnline(owner ? true : false);
++ g_free(owner);
++}
++
++void
++nsNativeMenuService::OnProxyCreated(GDBusProxy *aProxy)
++{
++ mDbusProxy = aProxy;
++ mCreateProxyRequest.Finish();
++
++ if (!mDbusProxy) {
++ SetOnline(false);
++ return;
++ }
++
++ g_signal_connect(mDbusProxy, "notify::g-name-owner",
++ G_CALLBACK(name_owner_changed_cb), nullptr);
++
++ OnNameOwnerChanged();
++}
++
++void
++nsNativeMenuService::OnNativeMenuBarRegistered(nsMenuBar *aMenuBar,
++ bool aSuccess)
++{
++ aMenuBar->EndRegisterRequest();
++ if (!aSuccess) {
++ aMenuBar->Deactivate();
++ }
++}
++
++/* static */ void
++nsNativeMenuService::PrefChangedCallback(const char *aPref,
++ void *aClosure)
++{
++ nsNativeMenuService::GetSingleton()->PrefChanged();
++}
++
++void
++nsNativeMenuService::PrefChanged()
++{
++ if (!mDbusProxy) {
++ SetOnline(false);
++ return;
++ }
++
++ OnNameOwnerChanged();
++}
++
++nsNativeMenuService::nsNativeMenuService() :
++ mDbusProxy(nullptr), mOnline(false)
++{
++}
++
++nsNativeMenuService::~nsNativeMenuService()
++{
++ SetOnline(false);
++
++ // Make sure we disconnect map-event handlers
++ while (mMenuBars.Length() > 0) {
++ NotifyNativeMenuBarDestroyed(mMenuBars[0]);
++ }
++
++ Preferences::UnregisterCallback(PrefChangedCallback,
++ "ui.use_unity_menubar");
++
++ if (mDbusProxy) {
++ g_signal_handlers_disconnect_by_func(mDbusProxy,
++ FuncToGpointer(name_owner_changed_cb),
++ NULL);
++ g_object_unref(mDbusProxy);
++ }
++
++ if (gPendingListeners) {
++ delete gPendingListeners;
++ gPendingListeners = nullptr;
++ }
++ if (gPangoLayout) {
++ g_object_unref(gPangoLayout);
++ gPangoLayout = nullptr;
++ }
++}
++
++nsresult
++nsNativeMenuService::Init()
++{
++ nsresult rv = nsDbusmenuFunctions::Init();
++ if (NS_FAILED(rv)) {
++ return rv;
++ }
++
++ rv = GDBusInit();
++ if (NS_FAILED(rv)) {
++ return rv;
++ }
++
++ Preferences::RegisterCallback(PrefChangedCallback,
++ "ui.use_unity_menubar");
++
++ mCreateProxyRequest.Start();
++
++ g_dbus_proxy_new_for_bus(G_BUS_TYPE_SESSION,
++ static_cast<GDBusProxyFlags>(
++ G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
++ G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS |
++ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START),
++ nullptr,
++ "com.canonical.AppMenu.Registrar",
++ "/com/canonical/AppMenu/Registrar",
++ "com.canonical.AppMenu.Registrar",
++ mCreateProxyRequest, proxy_created_cb,
++ nullptr);
++
++ /* We don't technically know that the shell will draw the menubar until
++ * we know whether anybody owns the name of the menubar service on the
++ * session bus. However, discovering this happens asynchronously so
++ * we optimize for the common case here by assuming that the shell will
++ * draw window menubars if we are running inside Unity. This should
++ * mean that we avoid temporarily displaying the window menubar ourselves
++ */
++ const char *desktop = getenv("XDG_CURRENT_DESKTOP");
++ if (nsCRT::strcmp(desktop, "Unity") == 0) {
++ SetOnline(true);
++ }
++
++ nsCOMPtr<nsIObserverService> os = services::GetObserverService();
++ if (os) {
++ os->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
++ }
++
++ return NS_OK;
++}
++
++/* static */ already_AddRefed<nsNativeMenuService>
++nsNativeMenuService::GetInstance()
++{
++ RefPtr<nsNativeMenuService> service(sService);
++
++ if (service) {
++ return service.forget();
++ }
++
++ NS_ASSERTION(sShutdown == false,
++ "Attempted to access menubar service too late");
++
++ if (sShutdown) {
++ return nullptr;
++ }
++
++ sService = new nsNativeMenuService();
++ NS_ADDREF(sService);
++
++ if (NS_FAILED(sService->Init())) {
++ NS_RELEASE(sService);
++ sService = nullptr;
++ return nullptr;
++ }
++
++ service = sService;
++ return service.forget();
++}
++
++/* static */ nsNativeMenuService*
++nsNativeMenuService::GetSingleton()
++{
++ EnsureInitialized();
++ return sService;
++}
++
++NS_IMETHODIMP
++nsNativeMenuService::Observe(nsISupports *aSubject,
++ const char *aTopic,
++ const char16_t *aData)
++{
++ if (!nsCRT::strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, aTopic)) {
++ sShutdown = true;
++ nsCOMPtr<nsIObserverService> os = services::GetObserverService();
++ if (os) {
++ os->RemoveObserver(sService, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
++ }
++ NS_IF_RELEASE(sService);
++ }
++
++ return NS_OK;
++}
++
++void
++nsNativeMenuService::NotifyNativeMenuBarDestroyed(nsMenuBar *aMenuBar)
++{
++ g_signal_handlers_disconnect_by_func(aMenuBar->TopLevelWindow(),
++ FuncToGpointer(map_event_cb),
++ aMenuBar);
++
++ mMenuBars.RemoveElement(aMenuBar);
++}
++
++NS_IMETHODIMP
++nsNativeMenuService::CreateNativeMenuBar(nsIWidget *aParent,
++ nsIContent *aMenuBarNode)
++{
++ NS_ENSURE_ARG(aParent);
++ NS_ENSURE_ARG(aMenuBarNode);
++
++ nsAutoPtr<nsMenuBar> menubar(nsMenuBar::Create(aParent, aMenuBarNode));
++ if (!menubar) {
++ NS_WARNING("Failed to create menubar");
++ return NS_ERROR_FAILURE;
++ }
++
++ // Unity forgets our window if it is unmapped by the application, which
++ // happens with some extensions that add "minimize to tray" type
++ // functionality. We hook on to the MapNotify event to re-register our menu
++ // with Unity
++ g_signal_connect(G_OBJECT(menubar->TopLevelWindow()),
++ "map-event", G_CALLBACK(map_event_cb),
++ menubar);
++
++ mMenuBars.AppendElement(menubar);
++ RegisterNativeMenuBar(menubar);
++
++ static_cast<nsWindow *>(aParent)->SetMenuBar(menubar.forget());
++
++ return NS_OK;
++}
+Index: firefox-52.0~b9+build2/mozilla/widget/gtk/nsNativeMenuService.h
+===================================================================
+--- /dev/null
++++ firefox-52.0~b9+build2/mozilla/widget/gtk/nsNativeMenuService.h
+@@ -0,0 +1,88 @@
++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:expandtab:shiftwidth=4:tabstop=4:
++ */
++/* 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/. */
++
++#ifndef __nsNativeMenuService_h__
++#define __nsNativeMenuService_h__
++
++#include "mozilla/Attributes.h"
++#include "nsCOMPtr.h"
++#include "nsINativeMenuService.h"
++#include "nsIObserver.h"
++#include "nsTArray.h"
++
++#include "nsNativeMenuUtils.h"
++
++#include <gdk/gdk.h>
++#include <gio/gio.h>
++#include <gtk/gtk.h>
++
++class nsMenuBar;
++
++/*
++ * The main native menu service singleton. nsWebShellWindow calls in to this when
++ * a new top level window is created.
++ *
++ * Menubars are owned by their nsWindow. This service holds a weak reference to
++ * each menubar for the purpose of re-registering them with the shell if it
++ * needs to. The menubar is responsible for notifying the service when the last
++ * reference to it is dropped.
++ */
++class nsNativeMenuService final : public nsINativeMenuService,
++ public nsIObserver
++{
++public:
++ NS_DECL_ISUPPORTS
++ NS_DECL_NSIOBSERVER
++
++ NS_IMETHOD CreateNativeMenuBar(nsIWidget* aParent, nsIContent* aMenuBarNode);
++
++ nsresult Init();
++
++ // Returns the singleton addref'd for the service manager
++ static already_AddRefed<nsNativeMenuService> GetInstance();
++
++ // Returns the singleton without increasing the reference count
++ static nsNativeMenuService* GetSingleton();
++
++ // Called by a menubar when the last reference to it is dropped
++ void NotifyNativeMenuBarDestroyed(nsMenuBar *aMenuBar);
++
++private:
++ nsNativeMenuService();
++ ~nsNativeMenuService();
++
++ static void EnsureInitialized();
++ void SetOnline(bool aOnline);
++ void RegisterNativeMenuBar(nsMenuBar *aMenuBar);
++ static void name_owner_changed_cb(GObject *gobject,
++ GParamSpec *pspec,
++ gpointer user_data);
++ static void proxy_created_cb(GObject *source_object,
++ GAsyncResult *res,
++ gpointer user_data);
++ static void register_native_menubar_cb(GObject *source_object,
++ GAsyncResult *res,
++ gpointer user_data);
++ static gboolean map_event_cb(GtkWidget *widget, GdkEvent *event,
++ gpointer user_data);
++ void OnNameOwnerChanged();
++ void OnProxyCreated(GDBusProxy *aProxy);
++ void OnNativeMenuBarRegistered(nsMenuBar *aMenuBar,
++ bool aSuccess);
++ static void PrefChangedCallback(const char *aPref, void *aClosure);
++ void PrefChanged();
++
++ nsNativeMenuGIORequest mCreateProxyRequest;
++ GDBusProxy *mDbusProxy;
++ bool mOnline;
++ nsTArray<nsMenuBar *> mMenuBars;
++
++ static bool sShutdown;
++ static nsNativeMenuService *sService;
++};
++
++#endif /* __nsNativeMenuService_h__ */
+Index: firefox-52.0~b9+build2/mozilla/widget/gtk/nsNativeMenuUtils.h
+===================================================================
+--- /dev/null
++++ firefox-52.0~b9+build2/mozilla/widget/gtk/nsNativeMenuUtils.h
+@@ -0,0 +1,59 @@
++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:expandtab:shiftwidth=4:tabstop=4:
++ */
++/* 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/. */
++
++#ifndef __nsNativeMenuUtils_h__
++#define __nsNativeMenuUtils_h__
++
++#include <glib-object.h>
++#include <gio/gio.h>
++
++class nsNativeMenuGIORequest
++{
++public:
++ nsNativeMenuGIORequest() : mCancellable(nullptr) { };
++
++ ~nsNativeMenuGIORequest() {
++ Cancel();
++ }
++
++ void Start() {
++ Cancel();
++ mCancellable = g_cancellable_new();
++ }
++
++ void Finish() {
++ if (mCancellable) {
++ g_object_unref(mCancellable);
++ mCancellable = nullptr;
++ }
++ }
++
++ void Cancel() {
++ if (mCancellable) {
++ g_cancellable_cancel(mCancellable);
++ g_object_unref(mCancellable);
++ mCancellable = nullptr;
++ }
++ }
++
++ bool InProgress() const {
++ if (!mCancellable) {
++ return false;
++ }
++
++ return !g_cancellable_is_cancelled(mCancellable);
++ }
++
++ operator GCancellable*() const {
++ return mCancellable;
++ }
++
++private:
++ GCancellable *mCancellable;
++};
++
++#endif /* __nsNativeMenuUtils_h__ */
+Index: firefox-52.0~b9+build2/mozilla/widget/gtk/nsWidgetFactory.cpp
+===================================================================
+--- firefox-52.0~b9+build2.orig/mozilla/widget/gtk/nsWidgetFactory.cpp
++++ firefox-52.0~b9+build2/mozilla/widget/gtk/nsWidgetFactory.cpp
+@@ -49,6 +49,8 @@
+ #include "GfxInfoX11.h"
+ #endif
+
++#include "nsNativeMenuService.h"
++
+ #include "nsNativeThemeGTK.h"
+
+ #include "nsIComponentRegistrar.h"
+@@ -121,6 +123,9 @@ NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(GfxI
+ }
+ #endif
+
++NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsNativeMenuService,
++ nsNativeMenuService::GetInstance)
++
+ #ifdef NS_PRINTING
+ NS_GENERIC_FACTORY_CONSTRUCTOR(nsDeviceContextSpecGTK)
+ NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrintOptionsGTK, Init)
+@@ -223,6 +228,7 @@ NS_DEFINE_NAMED_CID(NS_IMAGE_TO_PIXBUF_C
+ NS_DEFINE_NAMED_CID(NS_IDLE_SERVICE_CID);
+ NS_DEFINE_NAMED_CID(NS_GFXINFO_CID);
+ #endif
++NS_DEFINE_NAMED_CID(NS_NATIVEMENUSERVICE_CID);
+
+
+ static const mozilla::Module::CIDEntry kWidgetCIDs[] = {
+@@ -258,6 +264,7 @@ static const mozilla::Module::CIDEntry k
+ { &kNS_IDLE_SERVICE_CID, false, nullptr, nsIdleServiceGTKConstructor },
+ { &kNS_GFXINFO_CID, false, nullptr, mozilla::widget::GfxInfoConstructor },
+ #endif
++ { &kNS_NATIVEMENUSERVICE_CID, true, NULL, nsNativeMenuServiceConstructor },
+ { nullptr }
+ };
+
+@@ -295,6 +302,7 @@ static const mozilla::Module::ContractID
+ { "@mozilla.org/widget/idleservice;1", &kNS_IDLE_SERVICE_CID },
+ { "@mozilla.org/gfx/info;1", &kNS_GFXINFO_CID },
+ #endif
++ { "@mozilla.org/widget/nativemenuservice;1", &kNS_NATIVEMENUSERVICE_CID },
+ { nullptr }
+ };
+
+Index: firefox-52.0~b9+build2/mozilla/widget/gtk/nsWindow.cpp
+===================================================================
+--- firefox-52.0~b9+build2.orig/mozilla/widget/gtk/nsWindow.cpp
++++ firefox-52.0~b9+build2/mozilla/widget/gtk/nsWindow.cpp
+@@ -5175,6 +5175,11 @@ nsWindow::HideWindowChrome(bool aShouldH
+ return NS_OK;
+ }
+
++void
++nsWindow::SetMenuBar(nsMenuBar *aMenuBar) {
++ mMenuBar.reset(aMenuBar);
++}
++
+ bool
+ nsWindow::CheckForRollup(gdouble aMouseX, gdouble aMouseY,
+ bool aIsWheel, bool aAlwaysRollup)
+Index: firefox-52.0~b9+build2/mozilla/widget/gtk/nsWindow.h
+===================================================================
+--- firefox-52.0~b9+build2.orig/mozilla/widget/gtk/nsWindow.h
++++ firefox-52.0~b9+build2/mozilla/widget/gtk/nsWindow.h
+@@ -35,6 +35,8 @@
+
+ #include "IMContextWrapper.h"
+
++#include "nsMenuBar.h"
++
+ #undef LOG
+ #ifdef MOZ_LOGGING
+
+@@ -162,6 +164,8 @@ public:
+ nsIScreen* aTargetScreen = nullptr) override;
+ NS_IMETHOD HideWindowChrome(bool aShouldHide) override;
+
++ void SetMenuBar(nsMenuBar *aMenuBar);
++
+ /**
+ * GetLastUserInputTime returns a timestamp for the most recent user input
+ * event. This is intended for pointer grab requests (including drags).
+@@ -569,6 +573,8 @@ private:
+ RefPtr<mozilla::widget::IMContextWrapper> mIMContext;
+
+ mozilla::UniquePtr<mozilla::CurrentX11TimeGetter> mCurrentTimeGetter;
++
++ mozilla::UniquePtr<nsMenuBar> mMenuBar;
+ };
+
+ class nsChildWindow : public nsWindow {
+Index: firefox-52.0~b9+build2/mozilla/xpfe/appshell/nsWebShellWindow.cpp
+===================================================================
+--- firefox-52.0~b9+build2.orig/mozilla/xpfe/appshell/nsWebShellWindow.cpp
++++ firefox-52.0~b9+build2/mozilla/xpfe/appshell/nsWebShellWindow.cpp
+@@ -58,6 +58,7 @@
+ #include "nsIScreen.h"
+
+ #include "nsIContent.h" // for menus
++#include "nsIAtom.h"
+ #include "nsIScriptSecurityManager.h"
+
+ // For calculating size
+@@ -73,7 +74,7 @@
+
+ #include "nsPIWindowRoot.h"
+
+-#ifdef XP_MACOSX
++#if defined(XP_MACOSX) || defined(MOZ_WIDGET_GTK)
+ #include "nsINativeMenuService.h"
+ #define USE_NATIVE_MENUS
+ #endif
+@@ -498,6 +499,11 @@ static void LoadNativeMenus(nsIDOMDocume
+
+ if (menubarNode) {
+ nsCOMPtr<nsIContent> menubarContent(do_QueryInterface(menubarNode));
++#ifdef MOZ_WIDGET_GTK
++ nsCOMPtr<nsIAtom> atom = NS_Atomize(NS_LITERAL_CSTRING("_moz-menubarkeeplocal"));
++ if (menubarContent->AttrValueIs(kNameSpaceID_None, atom, nsGkAtoms::_true, eCaseMatters))
++ return;
++#endif
+ nms->CreateNativeMenuBar(aParentWindow, menubarContent);
+ } else {
+ nms->CreateNativeMenuBar(aParentWindow, nullptr);
+Index: firefox-52.0~b9+build2/mozilla/widget/gtk/moz.build
+===================================================================
+--- firefox-52.0~b9+build2.orig/mozilla/widget/gtk/moz.build
++++ firefox-52.0~b9+build2/mozilla/widget/gtk/moz.build
+@@ -24,10 +24,18 @@ UNIFIED_SOURCES += [
+ 'nsAppShell.cpp',
+ 'nsBidiKeyboard.cpp',
+ 'nsColorPicker.cpp',
++ 'nsDbusmenu.cpp',
+ 'nsFilePicker.cpp',
+ 'nsGtkKeyUtils.cpp',
+ 'nsImageToPixbuf.cpp',
+ 'nsLookAndFeel.cpp',
++ 'nsMenuBar.cpp',
++ 'nsMenuContainer.cpp',
++ 'nsMenuItem.cpp',
++ 'nsMenuObject.cpp',
++ 'nsMenuSeparator.cpp',
++ 'nsNativeMenuAtoms.cpp',
++ 'nsNativeMenuDocListener.cpp',
+ 'nsNativeThemeGTK.cpp',
+ 'nsScreenGtk.cpp',
+ 'nsScreenManagerGtk.cpp',
+@@ -40,6 +48,8 @@ UNIFIED_SOURCES += [
+ ]
+
+ SOURCES += [
++ 'nsMenu.cpp', # conflicts with X11 headers
++ 'nsNativeMenuService.cpp',
+ 'nsWindow.cpp', # conflicts with X11 headers
+ ]
+
+@@ -104,6 +114,7 @@ FINAL_LIBRARY = 'xul'
+
+ LOCAL_INCLUDES += [
+ '/layout/generic',
++ '/layout/style',
+ '/layout/xul',
+ '/other-licenses/atk-1.0',
+ '/widget',
+Index: firefox-52.0~b9+build2/mozilla/browser/base/content/browser.js
+===================================================================
+--- firefox-52.0~b9+build2.orig/mozilla/browser/base/content/browser.js
++++ firefox-52.0~b9+build2/mozilla/browser/base/content/browser.js
+@@ -5079,6 +5079,8 @@ function getTogglableToolbars() {
+ let toolbarNodes = Array.slice(gNavToolbox.childNodes);
+ toolbarNodes = toolbarNodes.concat(gNavToolbox.externalToolbars);
+ toolbarNodes = toolbarNodes.filter(node => node.getAttribute("toolbarname"));
++ if (document.documentElement.getAttribute("shellshowingmenubar") == "true")
++ toolbarNodes = toolbarNodes.filter(node => node.id != "toolbar-menubar");
+ return toolbarNodes;
+ }
+
+Index: firefox-52.0~b9+build2/mozilla/widget/moz.build
+===================================================================
+--- firefox-52.0~b9+build2.orig/mozilla/widget/moz.build
++++ firefox-52.0~b9+build2/mozilla/widget/moz.build
+@@ -37,10 +37,12 @@ elif toolkit == 'cocoa':
+ 'nsITaskbarProgress.idl',
+ ]
+ EXPORTS += [
+- 'nsINativeMenuService.h',
+ 'nsIPrintDialogService.h',
+ ]
+
++if toolkit in ('cocoa', 'gtk2', 'gtk3'):
++ EXPORTS += ['nsINativeMenuService.h']
++
+ TEST_DIRS += ['tests']
+
+ # Don't build the DSO under the 'build' directory as windows does.
+Index: firefox-52.0~b9+build2/mozilla/modules/libpref/init/all.js
+===================================================================
+--- firefox-52.0~b9+build2.orig/mozilla/modules/libpref/init/all.js
++++ firefox-52.0~b9+build2/mozilla/modules/libpref/init/all.js
+@@ -229,6 +229,9 @@ pref("dom.compartment_per_addon", true);
+ pref("browser.sessionhistory.max_total_viewers", -1);
+
+ pref("ui.use_native_colors", true);
++#ifdef MOZ_WIDGET_GTK
++pref("ui.use_unity_menubar", true);
++#endif
+ pref("ui.click_hold_context_menus", false);
+ // Duration of timeout of incremental search in menus (ms). 0 means infinite.
+ pref("ui.menu.incremental_search.timeout", 1000);
+Index: firefox-52.0~b9+build2/mozilla/widget/gtk/nsScreenGtk.cpp
+===================================================================
+--- firefox-52.0~b9+build2.orig/mozilla/widget/gtk/nsScreenGtk.cpp
++++ firefox-52.0~b9+build2/mozilla/widget/gtk/nsScreenGtk.cpp
+@@ -15,6 +15,7 @@
+ #include <gtk/gtk.h>
+ #include <dlfcn.h>
+ #include "gfxPlatformGtk.h"
++#include "nsIWidget.h"
+
+ static uint32_t sScreenId = 0;
+
+Index: firefox-52.0~b9+build2/mozilla/layout/build/moz.build
+===================================================================
+--- firefox-52.0~b9+build2.orig/mozilla/layout/build/moz.build
++++ firefox-52.0~b9+build2/mozilla/layout/build/moz.build
+@@ -77,6 +77,10 @@ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'go
+ LOCAL_INCLUDES += [
+ '/dom/system/gonk',
+ ]
++elif 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
++ LOCAL_INCLUDES += [
++ '/widget/gtk',
++ ]
+
+ if CONFIG['MOZ_WEBSPEECH']:
+ LOCAL_INCLUDES += [
+Index: firefox-52.0~b9+build2/mozilla/layout/build/nsLayoutStatics.cpp
+===================================================================
+--- firefox-52.0~b9+build2.orig/mozilla/layout/build/nsLayoutStatics.cpp
++++ firefox-52.0~b9+build2/mozilla/layout/build/nsLayoutStatics.cpp
+@@ -132,6 +132,10 @@ using namespace mozilla::system;
+ #include "mozilla/StaticPresData.h"
+ #include "mozilla/dom/WebIDLGlobalNameHash.h"
+
++#ifdef MOZ_WIDGET_GTK
++#include "nsNativeMenuAtoms.h"
++#endif
++
+ using namespace mozilla;
+ using namespace mozilla::net;
+ using namespace mozilla::dom;
+@@ -166,6 +170,9 @@ nsLayoutStatics::Initialize()
+ nsTextServicesDocument::RegisterAtoms();
+ nsHTMLTags::RegisterAtoms();
+ nsRDFAtoms::RegisterAtoms();
++#ifdef MOZ_WIDGET_GTK
++ nsNativeMenuAtoms::RegisterAtoms();
++#endif
+
+ NS_SealStaticAtomTable();
+