diff options
author | nikatar | 2018-06-06 20:47:12 +0300 |
---|---|---|
committer | nikatar | 2018-06-06 20:47:12 +0300 |
commit | d85241d75956d20e826f714e8dcea992ad0452db (patch) | |
tree | 7f7233277f4af2488738505ace150657bedf1feb | |
download | aur-d85241d75956d20e826f714e8dcea992ad0452db.tar.gz |
52.8.0-1
-rw-r--r-- | .SRCINFO | 63 | ||||
-rw-r--r-- | 0001-Bug-1338655-Don-t-try-to-build-mp4parse-bindings.-r-.patch | 48 | ||||
-rw-r--r-- | PKGBUILD | 186 | ||||
-rw-r--r-- | fix-wifi-scanner.diff | 16 | ||||
-rw-r--r-- | no-crmf.diff | 39 | ||||
-rw-r--r-- | rust-i686.patch | 16 | ||||
-rw-r--r-- | thunderbird-install-dir.patch | 12 | ||||
-rw-r--r-- | thunderbird.desktop | 173 | ||||
-rw-r--r-- | unity-menubar.patch | 5708 |
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, ¶m)) { + 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 && ++#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(); + |