From 64f94edaaafa871bd320fa2515ae8c42a3ede300 Mon Sep 17 00:00:00 2001 From: "B. Stack" Date: Mon, 9 Oct 2023 13:25:50 -0400 Subject: waterfox-g 6.0.2 rc1 --- waterfox-g/debian/_service | 4 +- waterfox-g/debian/build.sh | 81 + waterfox-g/debian/changelog | 39 +- waterfox-g/debian/control | 6 +- waterfox-g/debian/mozconfig | 42 +- waterfox-g/debian/mozconfig_LANG | 19 - .../Relax-cargo-version-requirement.patch | 21 + .../Relax-nodejs-minimum-version.patch | 21 + .../debian/patches/fis-csd-global-menu.patch | 7 +- waterfox-g/debian/patches/fix-langpack-id.patch | 39 +- waterfox-g/debian/patches/fix-wayland-build.patch | 4 +- waterfox-g/debian/patches/g-kde.patch | 1414 +++-- waterfox-g/debian/patches/global_menu.patch | 5382 -------------------- waterfox-g/debian/patches/libavcodec58_91.patch | 9 +- waterfox-g/debian/patches/mach-depends.patch | 17 +- .../debian/patches/mozilla-ntlm-full-path.patch | 17 +- waterfox-g/debian/patches/nongnome-proxies.patch | 13 +- waterfox-g/debian/patches/series | 5 +- .../debian/patches/waterfox-branded-icons.patch | 19 - waterfox-g/debian/rules | 39 +- 20 files changed, 885 insertions(+), 6313 deletions(-) create mode 100755 waterfox-g/debian/build.sh create mode 100644 waterfox-g/debian/patches/debian-hacks/Relax-cargo-version-requirement.patch create mode 100644 waterfox-g/debian/patches/debian-hacks/Relax-nodejs-minimum-version.patch delete mode 100644 waterfox-g/debian/patches/global_menu.patch delete mode 100644 waterfox-g/debian/patches/waterfox-branded-icons.patch diff --git a/waterfox-g/debian/_service b/waterfox-g/debian/_service index 3bcea87..d03384b 100644 --- a/waterfox-g/debian/_service +++ b/waterfox-g/debian/_service @@ -1,7 +1,7 @@ git - https://bgstack15.ddns.net/cgit/stackrpms + https://gitlab.com/bgstack15/stackrpms.git waterfox-g/debian debian waterfox-g-bump @@ -21,7 +21,7 @@ waterfox-g @PARENT_TAG@ G(.*) - G5.1.10 + G6.0.2 waterfox-g.obsinfo diff --git a/waterfox-g/debian/build.sh b/waterfox-g/debian/build.sh new file mode 100755 index 0000000..b7a1f9e --- /dev/null +++ b/waterfox-g/debian/build.sh @@ -0,0 +1,81 @@ +#!/bin/bash + +# Upstream modifies version_display.txt using script included in GitHub Actions (version=git_tag), +# without patching. Debhelper won't allow us to do that by same way and +# patching won't be much convenient, so we need to create new files. +mkdir -p "$(pwd)"/debian/app_version + +cp "$(pwd)"/browser/config/version.txt "$(pwd)"/debian/app_version/version.txt +echo "$WF_VERSION" >"$(pwd)"/debian/app_version/version_display.txt + +# Tune flags +OPT_EXTRA_FLAGS="-march=x86-64 -mtune=k8" +CFLAGS+=" $OPT_EXTRA_FLAGS" +CXXFLAGS+=" $OPT_EXTRA_FLAGS" + +# LTO needs more open files +ulimit -n 4096 +# Do 3-tier PGO +LDFLAGS+=" -Wl,--no-keep-memory -Wl,--no-mmap-output-file" +export LDFLAGS +if test `lsb_release -sc` = "bionic" || test `lsb_release -sc` = "focal" || test `lsb_release -sc` = "buster"; then +export NODEJS=/usr/lib/nodejs-mozilla/bin/node +fi + +if test `lsb_release -sc` = "bionic"; then +export NASM=/usr/lib/nasm-mozilla/bin/nasm +fi + +# For successfull LTO build, we need to use matching LLVM version +if test `lsb_release -sc` = "bionic" || test `lsb_release -sc` = "focal" || test `lsb_release -sc` = "buster" || test `lsb_release -sc` = "bullseye"; then +export PATH=/usr/lib/llvm-13/bin/:$PATH +fi + +if test `lsb_release -sc` = "jammy"; then +export PATH=/usr/lib/llvm-15/bin/:$PATH +fi + +export CC=clang +export CXX=clang++ +export AR=llvm-ar +export NM=llvm-nm +export RANLIB=llvm-ranlib +export LLVM_PROFDATA=llvm-profdata +export MACH_BUILD_PYTHON_NATIVE_PACKAGE_SOURCE=system +export GEN_PGO=1 +./mach build + +echo "Profiling instrumented browser..." +./mach package +JARLOG_FILE="$(pwd)/jarlog" xvfb-run -s "-screen 0 1920x1080x24 -nolisten local" ./mach python build/pgo/profileserver.py + +stat -c "Profile data found (%s bytes)" merged.profdata +test -s merged.profdata + +stat -c "Jar log found (%s bytes)" jarlog +test -s jarlog + +echo "Removing instrumented browser..." +./mach clobber + +echo "Building optimized browser..." +unset GEN_PGO +export USE_PGO=1 +./mach build + +# Build langpacks +mkdir -p "$(pwd)"/extensions +# langpack-build can not be done in parallel easily (see https://bugzilla.mozilla.org/show_bug.cgi?id=1660943) +# Therefore, we have to have a separate obj-dir for each language +# We do this, by creating a mozconfig-template with the necessary switches +# and a placeholder obj-dir, which gets copied and modified for each language +sed -r '/^(ja-JP-mac|en-US|)$/d;s/ .*$//' debian/locales.shipped | cut -f1 -d":" | + xargs -n 1 -P $JOBS -I {} /bin/sh -c ' + locale=$1 + cp debian/mozconfig_LANG ${PWD}/mozconfig_$locale + sed -i "s|obj_LANG|obj_$locale|" ${PWD}/mozconfig_$locale + export MOZCONFIG=${PWD}/mozconfig_$locale + ./mach build config/nsinstall langpack-$locale + cp -L ../obj_$locale/dist/linux-*/xpi/waterfox-g-$WF_VERSION.$locale.langpack.xpi \ + "$(pwd)"/extensions/langpack-$locale@l10n.waterfox.net.xpi +' -- {} diff --git a/waterfox-g/debian/changelog b/waterfox-g/debian/changelog index 192982b..d85c707 100644 --- a/waterfox-g/debian/changelog +++ b/waterfox-g/debian/changelog @@ -1,8 +1,43 @@ -waterfox-g (5.1.10-1+stackrpms) obs; urgency=medium +waterfox-g (6.0.2-1+stackrpms) obs; urgency=medium * Fork to remove KDE bits - -- B. Stack Sun, 06 Aug 2023 11:41:53 -0400 + -- B. Stack Mon, 09 Oct 2023 12:43:32 -0400 + +waterfox-g-kpe (6.0.2-0) obs; urgency=medium + + * Critical security fix + + -- hawkeye116477 Fri, 29 Sep 2023 17:43:11 +0200 + +waterfox-g-kpe (6.0.1-0) obs; urgency=medium + + * Update privacy preferences. + * Prevent new searches and opening bookmarks from opening in a new tab by default. + + -- hawkeye116477 Tue, 26 Sep 2023 22:38:10 +0200 + +waterfox-g-kpe (6.0-0) obs; urgency=medium + + * Waterfox uses DNS over Oblivious HTTP1, a privacy preserving method for DNS queries. + * You can now move and remove the extension button from the navigation bar. + * Linux, alongside Windows, now supports DRM. That means you can watch your favourite shows on the streaming platform of your choice. + * Waterfox preferences have been gone through with a fine tooth comb, with a lot of assistance from yokoffing, who maintains Betterfox.js. + + -- hawkeye116477 Wed, 20 Sep 2023 17:47:57 +0200 + +waterfox-g-kpe (5.1.13-0) obs; urgency=medium + + * High severity security fix + + -- hawkeye116477 Sat, 16 Sep 2023 23:48:21 +0200 + +waterfox-g-kpe (5.1.12-0) obs; urgency=medium + + * Various security fixes + + -- hawkeye116477 Sat, 02 Sep 2023 14:07:45 +0200 + waterfox-g-kpe (5.1.10-0) obs; urgency=medium diff --git a/waterfox-g/debian/control b/waterfox-g/debian/control index 819860e..15d7853 100644 --- a/waterfox-g/debian/control +++ b/waterfox-g/debian/control @@ -3,11 +3,7 @@ Section: web Priority: optional Maintainer: B. Stack XSBC-Original-Maintainer: hawkeye116477 -Build-Depends: debhelper (>= 9), autoconf2.13, libgtk-3-dev (>= 3.14), libdbus-glib-1-dev, libpulse-dev, libasound2-dev, yasm (>= 1.1), build-essential, libxt-dev, python3 (>= 3.5), zip, unzip, cargo (>= 0.59), libgl1-mesa-dev, libnotify-dev (>= 0.4), binutils-avr, libfreetype6-dev (>= 2.0.1), libfontconfig1-dev, pkg-config, libtinfo-dev | libncurses-dev, clang (>= 5.0) | clang-10 | clang-11 | clang-12 | clang-13, llvm-dev (>= 5.0) | llvm-10-dev | llvm-11-dev | llvm-12-dev | llvm-13-dev, lld (>= 5.0) | lld-10 | lld-11 | lld-12 | lld-13, rustc (>= 1.59.0~), libxext-dev, libglib2.0-dev (>= 2.18), libpango1.0-dev (>= 1.14.0), libstartup-notification0-dev, libcurl4-openssl-dev, libiw-dev, mesa-common-dev, libxrender-dev, dbus-x11, xvfb, libx11-dev, libx11-xcb-dev, libfile-fcntllock-perl, apt-utils, locales, autotools-dev, libjpeg-dev, zlib1g-dev, libreadline-dev, dpkg-dev (>= 1.16.1.1~), libevent-dev (>= 1.4.1), libjsoncpp-dev, xfonts-base, xauth, lsb-release, cbindgen (>= 0.24.2), nodejs (>= 10.24.1) | nodejs-mozilla (>= 10.24.1), libjack-dev, nasm (>= 2.14) | nasm-mozilla (>= 2.14), libclang-dev (>= 5.0) | libclang-10-dev | libclang-11-dev | libclang-12-dev | libclang-13-dev, libstdc++6 (>= 7.0) | gcc-mozilla (>= 7), bc -Build-Conflicts: graphicsmagick-imagemagick-compat, - liboss4-salsa-dev, - libhildonmime-dev, - libosso-dev +Build-Depends: debhelper (>= 9), libgtk-3-dev, libdbus-glib-1-dev, libpulse-dev, libasound2-dev, yasm, build-essential, libxt-dev, python3 (>= 3.7) | python3.7, zip, unzip, cargo (>= 0.66), libgl1-mesa-dev, binutils-avr, clang (>= 5.0) | clang-13 | clang-14 | clang-15, llvm-dev (>= 5.0) | llvm-13-dev | llvm-14-dev | llvm-15-dev, lld (>= 5.0) | lld-13 | lld-14 | lld-15, rustc (>= 1.66.0~), libxext-dev, libglib2.0-dev, libstartup-notification0-dev, libcurl4-openssl-dev, libiw-dev, mesa-common-dev, libxrender-dev, dbus-x11, xvfb, libx11-dev, libx11-xcb-dev, apt-utils, locales, autotools-dev, libjpeg-dev, zlib1g-dev, libreadline-dev, dpkg-dev, libevent-dev, libjsoncpp-dev, xfonts-base, xauth, lsb-release, cbindgen (>= 0.24.3~), nodejs (>= 12.22.1) | nodejs-mozilla (>= 12.22.1), libjack-dev, nasm (>= 2.14) | nasm-mozilla (>= 2.14), libclang-dev (>= 5.0) | libclang-13-dev | libclang-14-dev | libclang-15-dev, libstdc++6 (>= 7.0) | gcc-mozilla (>= 7), bc, libpci-dev Standards-Version: 3.9.7 Homepage: https://www.waterfox.net/ diff --git a/waterfox-g/debian/mozconfig b/waterfox-g/debian/mozconfig index da1d8d4..850281e 100644 --- a/waterfox-g/debian/mozconfig +++ b/waterfox-g/debian/mozconfig @@ -1,44 +1,19 @@ -if test `lsb_release -sc` = "bionic" || test `lsb_release -sc` = "focal" || test `lsb_release -sc` = "stretch" || test `lsb_release -sc` = "buster"; then -export NODEJS=/usr/lib/nodejs-mozilla/bin/node -fi - -if test `lsb_release -sc` = "bionic" || test `lsb_release -sc` = "stretch"; then -export NASM=/usr/lib/nasm-mozilla/bin/nasm -fi - -# For successfull LTO build, we need to use matching LLVM version -if test `lsb_release -sc` = "bionic" || test `lsb_release -sc` = "focal" || test `lsb_release -sc` = "impish" || test `lsb_release -sc` = "stretch" || test `lsb_release -sc` = "buster" || test `lsb_release -sc` = "bullseye"; then -export PATH=/usr/lib/llvm-12/bin/:$PATH -fi - -if test `lsb_release -sc` = "kinetic"; then -export PATH=/usr/lib/llvm-14/bin/:$PATH -fi - -export CC=clang -export CXX=clang++ -export AR=llvm-ar -export NM=llvm-nm -export RANLIB=llvm-ranlib -export MACH_BUILD_PYTHON_NATIVE_PACKAGE_SOURCE=system - ac_add_options --prefix=/usr -export MOZ_PGO=1 - mk_add_options AUTOCLOBBER=1 ac_add_options --disable-debug ac_add_options --disable-debug-symbols ac_add_options --disable-crashreporter ac_add_options --disable-profiling -ac_add_options --disable-verify-mar ac_add_options --disable-dmd ac_add_options --disable-geckodriver ac_add_options --disable-bootstrap ac_add_options --disable-updater ac_add_options --disable-elf-hack +ac_add_options --disable-tests +ac_add_options --enable-unverified-updates ac_add_options --enable-pulseaudio ac_add_options --enable-alsa ac_add_options --enable-jack @@ -48,9 +23,16 @@ ac_add_options --enable-default-toolkit=cairo-gtk3-wayland ac_add_options --enable-hardening ac_add_options --enable-optimize ac_add_options --enable-rust-simd -ac_add_options --enable-lto ac_add_options --enable-linker=lld ac_add_options --enable-jxl +if test "$GEN_PGO"; then + ac_add_options --enable-profile-generate=cross +elif test "$USE_PGO"; then + ac_add_options --enable-lto=cross + ac_add_options --enable-profile-use=cross + ac_add_options --with-pgo-profile-path="$PWD"/merged.profdata + ac_add_options --with-pgo-jarlog="$PWD"/jarlog +fi ac_add_options --with-app-name=waterfox-g ac_add_options --with-app-basename=Waterfox @@ -58,12 +40,14 @@ ac_add_options --with-branding=waterfox/browser/branding ac_add_options --with-unsigned-addon-scopes=app,system ac_add_options --without-wasm-sandboxed-libraries ac_add_options --allow-addon-sideload -ac_add_options --with-version-file-path=$topsrcdir/debian/app_version +ac_add_options --with-version-file-path="$topsrcdir"/debian/app_version export MOZ_REQUIRE_SIGNING= export MOZ_INCLUDE_SOURCE_INFO=1 export MOZ_APP_REMOTINGNAME=waterfox-g +# stackrpms,2 ac_add_options "MOZ_ALLOW_LEGACY_EXTENSIONS=1" X=$(($(nproc --all)/2)) mk_add_options MOZ_MAKE_FLAGS="-j${X%.*}" +mk_add_options MOZ_OBJDIR="${PWD}"/obj diff --git a/waterfox-g/debian/mozconfig_LANG b/waterfox-g/debian/mozconfig_LANG index 1af22f3..f10957e 100644 --- a/waterfox-g/debian/mozconfig_LANG +++ b/waterfox-g/debian/mozconfig_LANG @@ -1,22 +1,3 @@ -if test `lsb_release -sc` = "bionic" || test `lsb_release -sc` = "focal" || test `lsb_release -sc` = "stretch" || test `lsb_release -sc` = "buster"; then -export NODEJS=/usr/lib/nodejs-mozilla/bin/node -fi - -if test `lsb_release -sc` = "bionic" || test `lsb_release -sc` = "stretch"; then -export NASM=/usr/lib/nasm-mozilla/bin/nasm -fi - -# For successfull LTO build, we need to use matching LLVM version -if test `lsb_release -sc` = "bionic" || test `lsb_release -sc` = "focal" || test `lsb_release -sc` = "impish" || test `lsb_release -sc` = "stretch" || test `lsb_release -sc` = "buster" || test `lsb_release -sc` = "bullseye"; then -export PATH=/usr/lib/llvm-12/bin/:$PATH -fi - -if test `lsb_release -sc` = "kinetic"; then -export PATH=/usr/lib/llvm-14/bin/:$PATH -fi - -export MACH_BUILD_PYTHON_NATIVE_PACKAGE_SOURCE=system - ac_add_options --enable-linker=lld ac_add_options --with-app-name=waterfox-g diff --git a/waterfox-g/debian/patches/debian-hacks/Relax-cargo-version-requirement.patch b/waterfox-g/debian/patches/debian-hacks/Relax-cargo-version-requirement.patch new file mode 100644 index 0000000..313f1d4 --- /dev/null +++ b/waterfox-g/debian/patches/debian-hacks/Relax-cargo-version-requirement.patch @@ -0,0 +1,21 @@ +From: Mike Hommey +Date: Wed, 2 Aug 2023 08:46:33 +0900 +Subject: Relax cargo version requirement + +--- + build/moz.configure/rust.configure | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/build/moz.configure/rust.configure b/build/moz.configure/rust.configure +index 7a2fd1a..9801f82 100644 +--- a/build/moz.configure/rust.configure ++++ b/build/moz.configure/rust.configure +@@ -180,7 +180,7 @@ def rust_compiler(rustc_info, cargo_info, target): + ) + ) + rustc_min_version = Version(MINIMUM_RUST_VERSION) +- cargo_min_version = rustc_min_version ++ cargo_min_version = Version("1.65.0") + + version = rustc_info.version + is_nightly = "nightly" in version.version diff --git a/waterfox-g/debian/patches/debian-hacks/Relax-nodejs-minimum-version.patch b/waterfox-g/debian/patches/debian-hacks/Relax-nodejs-minimum-version.patch new file mode 100644 index 0000000..8839a39 --- /dev/null +++ b/waterfox-g/debian/patches/debian-hacks/Relax-nodejs-minimum-version.patch @@ -0,0 +1,21 @@ +From: Mike Hommey +Date: Wed, 21 Sep 2022 08:48:27 +0900 +Subject: Relax nodejs minimum version + +--- + python/mozbuild/mozbuild/nodeutil.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/python/mozbuild/mozbuild/nodeutil.py b/python/mozbuild/mozbuild/nodeutil.py +index 8ec724a..efc7b7f 100644 +--- a/python/mozbuild/mozbuild/nodeutil.py ++++ b/python/mozbuild/mozbuild/nodeutil.py +@@ -11,7 +11,7 @@ from mozboot.util import get_tools_dir + from mozfile import which + from six import PY3 + +-NODE_MIN_VERSION = StrictVersion("12.22.12") ++NODE_MIN_VERSION = StrictVersion("12.22") + NPM_MIN_VERSION = StrictVersion("6.14.16") + + diff --git a/waterfox-g/debian/patches/fis-csd-global-menu.patch b/waterfox-g/debian/patches/fis-csd-global-menu.patch index 6eca9e8..6f2d2dc 100644 --- a/waterfox-g/debian/patches/fis-csd-global-menu.patch +++ b/waterfox-g/debian/patches/fis-csd-global-menu.patch @@ -1,8 +1,8 @@ diff --git a/browser/base/content/browser.css b/browser/base/content/browser.css -index 03a778809dc8..8f0267b45328 100644 +index 4279c9ccfc83..8573feb7cdfb 100644 --- a/browser/base/content/browser.css +++ b/browser/base/content/browser.css -@@ -334,6 +334,13 @@ toolbar[customizing] #whats-new-menu-button { +@@ -359,6 +359,13 @@ toolbar[customizing] #whats-new-menu-button { visibility: hidden; } @@ -16,6 +16,3 @@ index 03a778809dc8..8f0267b45328 100644 :root[tabsintitlebar] .titlebar-buttonbox { position: relative; } --- -2.37.3 - diff --git a/waterfox-g/debian/patches/fix-langpack-id.patch b/waterfox-g/debian/patches/fix-langpack-id.patch index c409fd8..4159530 100644 --- a/waterfox-g/debian/patches/fix-langpack-id.patch +++ b/waterfox-g/debian/patches/fix-langpack-id.patch @@ -1,8 +1,8 @@ diff --git a/browser/components/preferences/tests/browser_browser_languages_subdialog.js b/browser/components/preferences/tests/browser_browser_languages_subdialog.js -index a204f697f904..4b87ab85156a 100644 +index 8b57bf08a83b..dd6f22094a93 100644 --- a/browser/components/preferences/tests/browser_browser_languages_subdialog.js +++ b/browser/components/preferences/tests/browser_browser_languages_subdialog.js -@@ -14,7 +14,7 @@ const DICTIONARY_ID_PL = "pl@dictionaries.addons.mozilla.org"; +@@ -13,7 +13,7 @@ const DICTIONARY_ID_PL = "pl@dictionaries.addons.mozilla.org"; const TELEMETRY_CATEGORY = "intl.ui.browserLanguage"; function langpackId(locale) { @@ -11,7 +11,7 @@ index a204f697f904..4b87ab85156a 100644 } function getManifestData(locale, version = "2.0") { -@@ -670,7 +670,7 @@ add_task(async function testInstallFromAMO() { +@@ -669,7 +669,7 @@ add_task(async function testInstallFromAMO() { is(getMainPaneLocales(), "en-US,pl,search", "en-US and pl now available"); // Disable the Polish langpack. @@ -21,7 +21,7 @@ index a204f697f904..4b87ab85156a 100644 ({ dialogDoc, available, selected } = await openDialog(doc, true)); diff --git a/browser/locales/Makefile.in b/browser/locales/Makefile.in -index e4b60a039956..208871cb6f75 100644 +index 1b8f21be1ca6..7ca5d391219a 100644 --- a/browser/locales/Makefile.in +++ b/browser/locales/Makefile.in @@ -21,9 +21,9 @@ PWD := $(CURDIR) @@ -36,32 +36,29 @@ index e4b60a039956..208871cb6f75 100644 endif # For Nightly, we know where to get the builds from to do local repacks ifdef NIGHTLY_BUILD -diff --git a/intl/locale/LangPackMatcher.jsm b/intl/locale/LangPackMatcher.jsm -index 0913ef9fda38..2b923b59df6d 100644 ---- a/intl/locale/LangPackMatcher.jsm -+++ b/intl/locale/LangPackMatcher.jsm -@@ -370,7 +370,7 @@ async function getAvailableLocales() { +diff --git a/intl/locale/LangPackMatcher.sys.mjs b/intl/locale/LangPackMatcher.sys.mjs +index 977398b082e8..fd7c72939369 100644 +--- a/intl/locale/LangPackMatcher.sys.mjs ++++ b/intl/locale/LangPackMatcher.sys.mjs +@@ -374,7 +374,7 @@ async function getAvailableLocales() { // If defaultLocale isn't lastFallbackLocale, then we still need the langpack // for lastFallbackLocale for it to be useful. if (defaultLocale != lastFallbackLocale) { - let lastFallbackId = `langpack-${lastFallbackLocale}@firefox.mozilla.org`; + let lastFallbackId = `langpack-${lastFallbackLocale}@l10n.waterfox.net`; - let lastFallbackInstalled = await AddonManager.getAddonByID(lastFallbackId); - if (!lastFallbackInstalled) { - return availableLocales.filter(locale => locale != lastFallbackLocale); -diff --git a/intl/locale/tests/LangPackMatcherTestUtils.jsm b/intl/locale/tests/LangPackMatcherTestUtils.jsm -index 38345e1ec2d6..9898c733747c 100644 ---- a/intl/locale/tests/LangPackMatcherTestUtils.jsm -+++ b/intl/locale/tests/LangPackMatcherTestUtils.jsm -@@ -43,7 +43,7 @@ function getAddonAndLocalAPIsMocker(testScope, sandbox) { + let lastFallbackInstalled = await lazy.AddonManager.getAddonByID( + lastFallbackId + ); +diff --git a/intl/locale/tests/LangPackMatcherTestUtils.sys.mjs b/intl/locale/tests/LangPackMatcherTestUtils.sys.mjs +index 4b18f1be134e..1e1008b55edd 100644 +--- a/intl/locale/tests/LangPackMatcherTestUtils.sys.mjs ++++ b/intl/locale/tests/LangPackMatcherTestUtils.sys.mjs +@@ -38,7 +38,7 @@ export function getAddonAndLocalAPIsMocker(testScope, sandbox) { ); resolve( availableLangpacks.map(locale => ({ - guid: `langpack-${locale}@firefox.mozilla.org`, + guid: `langpack-${locale}@l10n.waterfox.net`, type: "language", + hash: locale, target_locale: locale, - current_compatible_version: { --- -2.37.3 - diff --git a/waterfox-g/debian/patches/fix-wayland-build.patch b/waterfox-g/debian/patches/fix-wayland-build.patch index 703b1a8..f0309fa 100644 --- a/waterfox-g/debian/patches/fix-wayland-build.patch +++ b/waterfox-g/debian/patches/fix-wayland-build.patch @@ -6,9 +6,11 @@ Description: Fix FTBFS on bionic. Compiler errors: Author: Rico Tzschichholz +diff --git a/widget/gtk/WaylandBuffer.cpp b/widget/gtk/WaylandBuffer.cpp +index f3fc4093623f..78d213d5d302 100644 --- a/widget/gtk/WaylandBuffer.cpp +++ b/widget/gtk/WaylandBuffer.cpp -@@ -258,7 +258,7 @@ +@@ -197,7 +197,7 @@ void WaylandBufferSHM::DumpToFile(const char* aHint) { /* static */ RefPtr WaylandBufferDMABUF::Create( diff --git a/waterfox-g/debian/patches/g-kde.patch b/waterfox-g/debian/patches/g-kde.patch index 2bff3cc..6fb67eb 100644 --- a/waterfox-g/debian/patches/g-kde.patch +++ b/waterfox-g/debian/patches/g-kde.patch @@ -2,13 +2,13 @@ Merged few patches into one patch and fixed for Waterfox Original authors of patch for Firefox: Wolfgang Rosenauer Lubos Lunak -Original patches => http://www.rosenauer.org/hg/mozilla/file/firefox91 +Original patches => http://www.rosenauer.org/hg/mozilla/file/firefox115 diff --git a/browser/components/preferences/main.js b/browser/components/preferences/main.js -index 627e63440af2..6d300a8124fa 100644 +index 95ed194f4bdd..90460d8bab88 100644 --- a/browser/components/preferences/main.js +++ b/browser/components/preferences/main.js -@@ -316,6 +316,13 @@ var gMainPane = { +@@ -300,6 +300,13 @@ var gMainPane = { }, backoffTimes[this._backoffIndex]); } @@ -22,7 +22,7 @@ index 627e63440af2..6d300a8124fa 100644 this.initBrowserContainers(); this.buildContentProcessCountMenuList(); -@@ -1349,6 +1356,17 @@ var gMainPane = { +@@ -1771,6 +1778,17 @@ var gMainPane = { } try { shellSvc.setDefaultBrowser(true, false); @@ -38,10 +38,10 @@ index 627e63440af2..6d300a8124fa 100644 + process.run(false, args, args.length); + } } catch (ex) { - Cu.reportError(ex); + console.error(ex); return; diff --git a/browser/components/shell/moz.build b/browser/components/shell/moz.build -index eedbb0d938fe..9b364941b850 100644 +index eb88cb287dc3..95277533781a 100644 --- a/browser/components/shell/moz.build +++ b/browser/components/shell/moz.build @@ -36,6 +36,8 @@ elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk": @@ -256,10 +256,10 @@ index 000000000000..26b5dbac47dd + +#endif // nsunixshellservice_h____ diff --git a/modules/libpref/Preferences.cpp b/modules/libpref/Preferences.cpp -index 6d0bebd2ee68..c138e63b23dc 100644 +index c04744a8b936..c2f1cd5a1e1a 100644 --- a/modules/libpref/Preferences.cpp +++ b/modules/libpref/Preferences.cpp -@@ -93,6 +93,7 @@ +@@ -95,6 +95,7 @@ #ifdef MOZ_BACKGROUNDTASKS # include "mozilla/BackgroundTasks.h" #endif @@ -267,16 +267,15 @@ index 6d0bebd2ee68..c138e63b23dc 100644 #ifdef DEBUG # include -@@ -4772,6 +4773,17 @@ nsresult Preferences::InitInitialObjects(bool aIsStartup) { +@@ -4911,6 +4912,16 @@ nsresult Preferences::InitInitialObjects(bool aIsStartup) { #endif }; -+ if(nsKDEUtils::kdeSession()) { // TODO what if some setup actually requires the helper? -+ for(int i = 0; -+ i < MOZ_ARRAY_LENGTH(specialFiles); -+ ++i ) { -+ if( *specialFiles[ i ] == '\0' ) { -+ specialFiles[ i ] = "kde.js"; ++ if (nsKDEUtils::kdeSession()) { // TODO what if some setup actually requires ++ // the helper? ++ for (int i = 0; i < MOZ_ARRAY_LENGTH(specialFiles); ++i) { ++ if (*specialFiles[i] == '\0') { ++ specialFiles[i] = "kde.js"; + break; + } + } @@ -285,7 +284,7 @@ index 6d0bebd2ee68..c138e63b23dc 100644 rv = pref_LoadPrefsInDir(defaultPrefDir, specialFiles, ArrayLength(specialFiles)); if (NS_FAILED(rv)) { -@@ -4846,7 +4858,7 @@ nsresult Preferences::InitInitialObjects(bool aIsStartup) { +@@ -4985,7 +4996,7 @@ nsresult Preferences::InitInitialObjects(bool aIsStartup) { } // Do we care if a file provided by this process fails to load? @@ -295,10 +294,10 @@ index 6d0bebd2ee68..c138e63b23dc 100644 } diff --git a/modules/libpref/moz.build b/modules/libpref/moz.build -index 1f021d409c51..171d034cc03a 100644 +index e8f8b97170d3..831001cee4b1 100644 --- a/modules/libpref/moz.build +++ b/modules/libpref/moz.build -@@ -125,6 +125,10 @@ UNIFIED_SOURCES += [ +@@ -126,6 +126,10 @@ UNIFIED_SOURCES += [ "SharedPrefMap.cpp", ] @@ -310,7 +309,7 @@ index 1f021d409c51..171d034cc03a 100644 GeneratedFile( diff --git a/python/mozbuild/mozpack/chrome/flags.py b/python/mozbuild/mozpack/chrome/flags.py -index 0fe6ee99bd51..f7acdafe9790 100644 +index 6b096c862aaa..2b46d9294b93 100644 --- a/python/mozbuild/mozpack/chrome/flags.py +++ b/python/mozbuild/mozpack/chrome/flags.py @@ -234,6 +234,7 @@ class Flags(OrderedDict): @@ -322,10 +321,10 @@ index 0fe6ee99bd51..f7acdafe9790 100644 RE = re.compile(r"([!<>=]+)") diff --git a/python/mozbuild/mozpack/chrome/manifest.py b/python/mozbuild/mozpack/chrome/manifest.py -index a733685f95a3..f64b17fb7b17 100644 +index 14c11d4c1daa..41b9969e7277 100644 --- a/python/mozbuild/mozpack/chrome/manifest.py +++ b/python/mozbuild/mozpack/chrome/manifest.py -@@ -44,6 +44,7 @@ class ManifestEntry(object): +@@ -43,6 +43,7 @@ class ManifestEntry(object): "process", "contentaccessible", "backgroundtask", @@ -334,7 +333,7 @@ index a733685f95a3..f64b17fb7b17 100644 def __init__(self, base, *flags): diff --git a/toolkit/components/downloads/moz.build b/toolkit/components/downloads/moz.build -index d4172e2d73ad..8bd0577bc535 100644 +index 3818e8c0db1e..b70986db8111 100644 --- a/toolkit/components/downloads/moz.build +++ b/toolkit/components/downloads/moz.build @@ -51,5 +51,9 @@ if CONFIG["MOZ_PLACES"]: @@ -347,11 +346,11 @@ index d4172e2d73ad..8bd0577bc535 100644 + with Files("**"): BUG_COMPONENT = ("Toolkit", "Downloads API") -diff --git a/toolkit/mozapps/downloads/HelperAppDlg.jsm b/toolkit/mozapps/downloads/HelperAppDlg.jsm -index 0a5a4a460bf3..5381240f52c6 100644 ---- a/toolkit/mozapps/downloads/HelperAppDlg.jsm -+++ b/toolkit/mozapps/downloads/HelperAppDlg.jsm -@@ -1260,26 +1260,56 @@ nsUnknownContentTypeDialog.prototype = { +diff --git a/toolkit/mozapps/downloads/HelperAppDlg.sys.mjs b/toolkit/mozapps/downloads/HelperAppDlg.sys.mjs +index af6993416e1a..39f1f7b6dfec 100644 +--- a/toolkit/mozapps/downloads/HelperAppDlg.sys.mjs ++++ b/toolkit/mozapps/downloads/HelperAppDlg.sys.mjs +@@ -1246,26 +1246,56 @@ nsUnknownContentTypeDialog.prototype = { this.chosenApp = params.handlerApp; } } else if ("@mozilla.org/applicationchooser;1" in Cc) { @@ -429,19 +428,19 @@ index 0a5a4a460bf3..5381240f52c6 100644 var nsIFilePicker = Ci.nsIFilePicker; var fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker); diff --git a/toolkit/system/unixproxy/nsUnixSystemProxySettings.cpp b/toolkit/system/unixproxy/nsUnixSystemProxySettings.cpp -index ef110b1287bf..e29e8a5b62de 100644 +index 982faf7d7968..d5417c058050 100644 --- a/toolkit/system/unixproxy/nsUnixSystemProxySettings.cpp +++ b/toolkit/system/unixproxy/nsUnixSystemProxySettings.cpp -@@ -15,6 +15,8 @@ - #include "nsNetUtil.h" +@@ -16,6 +16,8 @@ #include "nsISupportsPrimitives.h" #include "nsIGSettingsService.h" + #include "nsReadableUtils.h" +#include "nsPrintfCString.h" +#include "nsKDEUtils.h" using namespace mozilla; -@@ -38,6 +40,8 @@ class nsUnixSystemProxySettings final : public nsISystemProxySettings { +@@ -39,6 +41,8 @@ class nsUnixSystemProxySettings final : public nsISystemProxySettings { nsACString& aResult); nsresult SetProxyResultFromGSettings(const char* aKeyBase, const char* aType, nsACString& aResult); @@ -450,7 +449,7 @@ index ef110b1287bf..e29e8a5b62de 100644 }; NS_IMPL_ISUPPORTS(nsUnixSystemProxySettings, nsISystemProxySettings) -@@ -379,6 +383,9 @@ nsresult nsUnixSystemProxySettings::GetProxyForURI(const nsACString& aSpec, +@@ -393,6 +397,9 @@ nsresult nsUnixSystemProxySettings::GetProxyForURI(const nsACString& aSpec, const nsACString& aHost, const int32_t aPort, nsACString& aResult) { @@ -460,44 +459,40 @@ index ef110b1287bf..e29e8a5b62de 100644 if (mProxySettings) { nsresult rv = GetProxyFromGSettings(aScheme, aHost, aPort, aResult); if (NS_SUCCEEDED(rv)) return rv; -@@ -387,6 +394,32 @@ nsresult nsUnixSystemProxySettings::GetProxyForURI(const nsACString& aSpec, +@@ -401,6 +408,28 @@ nsresult nsUnixSystemProxySettings::GetProxyForURI(const nsACString& aSpec, return GetProxyFromEnvironment(aScheme, aHost, aPort, aResult); } -+nsresult -+nsUnixSystemProxySettings::GetProxyFromKDE(const nsACString& aScheme, -+ const nsACString& aHost, -+ PRInt32 aPort, -+ nsACString& aResult) -+{ ++nsresult nsUnixSystemProxySettings::GetProxyFromKDE(const nsACString& aScheme, ++ const nsACString& aHost, ++ PRInt32 aPort, ++ nsACString& aResult) { + nsAutoCString url; + url = aScheme; + url += "://"; + url += aHost; -+ if( aPort >= 0 ) -+ { ++ if (aPort >= 0) { + url += ":"; + url += nsPrintfCString("%d", aPort); + } + nsTArray command; -+ command.AppendElement( "GETPROXY"_ns ); -+ command.AppendElement( url ); ++ command.AppendElement("GETPROXY"_ns); ++ command.AppendElement(url); + nsTArray result; -+ if( !nsKDEUtils::command( command, &result ) || result.Length() != 1 ) ++ if (!nsKDEUtils::command(command, &result) || result.Length() != 1) + return NS_ERROR_FAILURE; + aResult = result[0]; + return NS_OK; +} -+ + NS_IMPL_COMPONENT_FACTORY(nsUnixSystemProxySettings) { auto result = MakeRefPtr(); result->Init(); diff --git a/toolkit/xre/moz.build b/toolkit/xre/moz.build -index 6475c0296aac..83e0184d4938 100644 +index dc7695e7acc5..3d7656515967 100644 --- a/toolkit/xre/moz.build +++ b/toolkit/xre/moz.build -@@ -97,7 +97,9 @@ elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "uikit": +@@ -96,7 +96,9 @@ elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "uikit": "UIKitDirProvider.mm", ] elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk": @@ -509,10 +504,10 @@ index 6475c0296aac..83e0184d4938 100644 CXXFLAGS += CONFIG["MOZ_X11_SM_CFLAGS"] diff --git a/toolkit/xre/nsKDEUtils.cpp b/toolkit/xre/nsKDEUtils.cpp new file mode 100644 -index 000000000000..a5242b6c6699 +index 000000000000..693280deefff --- /dev/null +++ b/toolkit/xre/nsKDEUtils.cpp -@@ -0,0 +1,321 @@ +@@ -0,0 +1,286 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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 @@ -536,310 +531,275 @@ index 000000000000..a5242b6c6699 +// copied from X11/X.h as a hack since for an unknown +// reason it's not picked up from X11/X.h +#ifndef None -+#define None 0L /* universal null resource or null atom */ ++# define None 0L /* universal null resource or null atom */ +#endif + -+//#define DEBUG_KDE ++// #define DEBUG_KDE +#ifdef DEBUG_KDE -+#define KWATERFOXHELPER "kwaterfoxhelper" ++# define KWATERFOXHELPER "kwaterfoxhelper" +#else +// not need for lib64, it's a binary -+#define KWATERFOXHELPER "/usr/lib/waterfox/kwaterfoxhelper" ++# define KWATERFOXHELPER "/usr/lib/waterfox/kwaterfoxhelper" +#endif + +#define KWATERFOXHELPER_VERSION 6 -+#define MAKE_STR2( n ) #n -+#define MAKE_STR( n ) MAKE_STR2( n ) ++#define MAKE_STR2(n) #n ++#define MAKE_STR(n) MAKE_STR2(n) + -+static bool getKdeSession() -+{ -+ if (PR_GetEnv("KDE_FULL_SESSION")) -+ { -+ return true; -+ } -+ return false; ++static bool getKdeSession() { ++ if (PR_GetEnv("KDE_FULL_SESSION")) { ++ return true; ++ } ++ return false; +} + -+static bool getKdeSupport() -+ { -+ nsTArray command; -+ command.AppendElement( "CHECK"_ns ); -+ command.AppendElement( "KWATERFOXHELPER_VERSION"_ns ); -+ bool kde = nsKDEUtils::command( command ); ++static bool getKdeSupport() { ++ nsTArray command; ++ command.AppendElement("CHECK"_ns); ++ command.AppendElement("KWATERFOXHELPER_VERSION"_ns); ++ bool kde = nsKDEUtils::command(command); +#ifdef DEBUG_KDE -+ fprintf( stderr, "KDE RUNNING %d\n", kde ); ++ fprintf(stderr, "KDE RUNNING %d\n", kde); +#endif -+ return kde; -+ } ++ return kde; ++} + -+nsKDEUtils::nsKDEUtils() -+ : commandFile( NULL ) -+ , replyFile( NULL ) -+ { -+ } ++nsKDEUtils::nsKDEUtils() : commandFile(NULL), replyFile(NULL) {} + -+nsKDEUtils::~nsKDEUtils() -+ { -+// closeHelper(); not actually useful, exiting will close the fd too -+ } ++nsKDEUtils::~nsKDEUtils() { ++ // closeHelper(); not actually useful, exiting will close the fd too ++} + -+nsKDEUtils* nsKDEUtils::self() -+ { -+ static nsKDEUtils s; -+ return &s; -+ } ++nsKDEUtils* nsKDEUtils::self() { ++ static nsKDEUtils s; ++ return &s; ++} + +static bool helperRunning = false; +static bool helperFailed = false; + -+bool nsKDEUtils::kdeSession() -+ { -+ static bool session = getKdeSession(); -+ return session; -+ } ++bool nsKDEUtils::kdeSession() { ++ static bool session = getKdeSession(); ++ return session; ++} + -+bool nsKDEUtils::kdeSupport() -+ { -+ static bool support = kdeSession() && getKdeSupport(); -+ return support && helperRunning; -+ } ++bool nsKDEUtils::kdeSupport() { ++ static bool support = kdeSession() && getKdeSupport(); ++ return support && helperRunning; ++} + -+struct nsKDECommandData -+ { -+ FILE* file; -+ nsTArray* output; -+ GMainLoop* loop; -+ bool success; -+ }; ++struct nsKDECommandData { ++ FILE* file; ++ nsTArray* output; ++ GMainLoop* loop; ++ bool success; ++}; + -+static gboolean kdeReadFunc( GIOChannel*, GIOCondition, gpointer data ) -+ { -+ nsKDECommandData* p = static_cast< nsKDECommandData* >( data ); -+ char buf[ 8192 ]; // TODO big enough -+ bool command_done = false; -+ bool command_failed = false; -+ while( !command_done && !command_failed && fgets( buf, 8192, p->file ) != NULL ) -+ { // TODO what if the kernel splits a line into two chunks? -+//#ifdef DEBUG_KDE -+// fprintf( stderr, "READ: %s %d\n", buf, feof( p->file )); -+//#endif -+ if( char* eol = strchr( buf, '\n' )) -+ *eol = '\0'; -+ command_done = ( strcmp( buf, "\\1" ) == 0 ); -+ command_failed = ( strcmp( buf, "\\0" ) == 0 ); -+ nsAutoCString line( buf ); -+ line.ReplaceSubstring( "\\n", "\n" ); -+ line.ReplaceSubstring( "\\" "\\", "\\" ); // \\ -> \ , i.e. unescape -+ if( p->output && !( command_done || command_failed )) -+ p->output->AppendElement( nsCString( buf )); // TODO utf8? -+ } -+ bool quit = false; -+ if( feof( p->file ) || command_failed ) -+ { -+ quit = true; -+ p->success = false; -+ } -+ if( command_done ) -+ { // reading one reply finished -+ quit = true; -+ p->success = true; -+ } -+ if( quit ) -+ { -+ if( p->loop ) -+ g_main_loop_quit( p->loop ); -+ return FALSE; -+ } -+ return TRUE; -+ } ++static gboolean kdeReadFunc(GIOChannel*, GIOCondition, gpointer data) { ++ nsKDECommandData* p = static_cast(data); ++ char buf[8192]; // TODO big enough ++ bool command_done = false; ++ bool command_failed = false; ++ while (!command_done && !command_failed && ++ fgets(buf, 8192, p->file) != ++ NULL) { // TODO what if the kernel splits a line into two chunks? ++ // #ifdef DEBUG_KDE ++ // fprintf( stderr, "READ: %s %d\n", buf, feof( p->file )); ++ // #endif ++ if (char* eol = strchr(buf, '\n')) *eol = '\0'; ++ command_done = (strcmp(buf, "\\1") == 0); ++ command_failed = (strcmp(buf, "\\0") == 0); ++ nsAutoCString line(buf); ++ line.ReplaceSubstring("\\n", "\n"); ++ line.ReplaceSubstring( ++ "\\" ++ "\\", ++ "\\"); // \\ -> \ , i.e. unescape ++ if (p->output && !(command_done || command_failed)) ++ p->output->AppendElement(nsCString(buf)); // TODO utf8? ++ } ++ bool quit = false; ++ if (feof(p->file) || command_failed) { ++ quit = true; ++ p->success = false; ++ } ++ if (command_done) { // reading one reply finished ++ quit = true; ++ p->success = true; ++ } ++ if (quit) { ++ if (p->loop) g_main_loop_quit(p->loop); ++ return FALSE; ++ } ++ return TRUE; ++} + -+bool nsKDEUtils::command( const nsTArray& command, nsTArray* output ) -+ { -+ return self()->internalCommand( command, NULL, false, output ); -+ } ++bool nsKDEUtils::command(const nsTArray& command, ++ nsTArray* output) { ++ return self()->internalCommand(command, NULL, false, output); ++} + -+bool nsKDEUtils::command( nsIArray* command, nsIArray** output) -+ { -+ nsTArray in; -+ PRUint32 length; -+ command->GetLength( &length ); -+ for ( PRUint32 i = 0; i < length; i++ ) -+ { -+ nsCOMPtr str = do_QueryElementAt( command, i ); -+ if( str ) -+ { -+ nsAutoCString s; -+ str->GetData( s ); -+ in.AppendElement( s ); -+ } -+ } ++bool nsKDEUtils::command(nsIArray* command, nsIArray** output) { ++ nsTArray in; ++ PRUint32 length; ++ command->GetLength(&length); ++ for (PRUint32 i = 0; i < length; i++) { ++ nsCOMPtr str = do_QueryElementAt(command, i); ++ if (str) { ++ nsAutoCString s; ++ str->GetData(s); ++ in.AppendElement(s); ++ } ++ } + -+ nsTArray out; -+ bool ret = self()->internalCommand( in, NULL, false, &out ); ++ nsTArray out; ++ bool ret = self()->internalCommand(in, NULL, false, &out); + -+ if ( !output ) return ret; ++ if (!output) return ret; + -+ nsCOMPtr result = do_CreateInstance( NS_ARRAY_CONTRACTID ); -+ if ( !result ) return false; ++ nsCOMPtr result = do_CreateInstance(NS_ARRAY_CONTRACTID); ++ if (!result) return false; + -+ for ( PRUint32 i = 0; i < out.Length(); i++ ) -+ { -+ nsCOMPtr rstr = do_CreateInstance( NS_SUPPORTS_CSTRING_CONTRACTID ); -+ if ( !rstr ) return false; ++ for (PRUint32 i = 0; i < out.Length(); i++) { ++ nsCOMPtr rstr = ++ do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID); ++ if (!rstr) return false; + -+ rstr->SetData( out[i] ); -+ result->AppendElement( rstr ); -+ } ++ rstr->SetData(out[i]); ++ result->AppendElement(rstr); ++ } + -+ NS_ADDREF( *output = result); -+ return ret; -+ } ++ NS_ADDREF(*output = result); ++ return ret; ++} + ++bool nsKDEUtils::commandBlockUi(const nsTArray& command, ++ GtkWindow* parent, ++ nsTArray* output) { ++ return self()->internalCommand(command, parent, true, output); ++} + -+bool nsKDEUtils::commandBlockUi( const nsTArray& command, GtkWindow* parent, nsTArray* output ) -+ { -+ return self()->internalCommand( command, parent, true, output ); -+ } ++bool nsKDEUtils::internalCommand(const nsTArray& command, ++ GtkWindow* parent, bool blockUi, ++ nsTArray* output) { ++ if (!startHelper()) return false; ++ feedCommand(command); ++ // do not store the data in 'this' but in extra structure, just in case there ++ // is reentrancy (can there be? the event loop is re-entered) ++ nsKDECommandData data; ++ data.file = replyFile; ++ data.output = output; ++ data.success = false; ++ if (blockUi) { ++ data.loop = g_main_loop_new(NULL, FALSE); ++ GtkWidget* window = gtk_window_new(GTK_WINDOW_TOPLEVEL); ++ if (parent && gtk_window_get_group(parent)) ++ gtk_window_group_add_window(gtk_window_get_group(parent), ++ GTK_WINDOW(window)); ++ gtk_widget_realize(window); ++ gtk_widget_set_sensitive(window, TRUE); ++ gtk_grab_add(window); ++ GIOChannel* channel = g_io_channel_unix_new(fileno(data.file)); ++ g_io_add_watch(channel, ++ static_cast(G_IO_IN | G_IO_ERR | G_IO_HUP), ++ kdeReadFunc, &data); ++ g_io_channel_unref(channel); ++ g_main_loop_run(data.loop); ++ g_main_loop_unref(data.loop); ++ gtk_grab_remove(window); ++ gtk_widget_destroy(window); ++ } else { ++ data.loop = NULL; ++ while (kdeReadFunc(NULL, static_cast(0), &data)) ++ ; ++ } ++ return data.success; ++} + -+bool nsKDEUtils::internalCommand( const nsTArray& command, GtkWindow* parent, bool blockUi, -+ nsTArray* output ) -+ { -+ if( !startHelper()) -+ return false; -+ feedCommand( command ); -+ // do not store the data in 'this' but in extra structure, just in case there -+ // is reentrancy (can there be? the event loop is re-entered) -+ nsKDECommandData data; -+ data.file = replyFile; -+ data.output = output; -+ data.success = false; -+ if( blockUi ) -+ { -+ data.loop = g_main_loop_new( NULL, FALSE ); -+ GtkWidget* window = gtk_window_new( GTK_WINDOW_TOPLEVEL ); -+ if( parent && gtk_window_get_group(parent) ) -+ gtk_window_group_add_window( gtk_window_get_group(parent), GTK_WINDOW( window )); -+ gtk_widget_realize( window ); -+ gtk_widget_set_sensitive( window, TRUE ); -+ gtk_grab_add( window ); -+ GIOChannel* channel = g_io_channel_unix_new( fileno( data.file )); -+ g_io_add_watch( channel, static_cast< GIOCondition >( G_IO_IN | G_IO_ERR | G_IO_HUP ), kdeReadFunc, &data ); -+ g_io_channel_unref( channel ); -+ g_main_loop_run( data.loop ); -+ g_main_loop_unref( data.loop ); -+ gtk_grab_remove( window ); -+ gtk_widget_destroy( window ); -+ } -+ else -+ { -+ data.loop = NULL; -+ while( kdeReadFunc( NULL, static_cast< GIOCondition >( 0 ), &data )) -+ ; -+ } -+ return data.success; ++bool nsKDEUtils::startHelper() { ++ if (helperRunning) return true; ++ if (helperFailed) return false; ++ helperFailed = true; ++ int fdcommand[2]; ++ int fdreply[2]; ++ if (pipe(fdcommand) < 0) return false; ++ if (pipe(fdreply) < 0) { ++ close(fdcommand[0]); ++ close(fdcommand[1]); ++ return false; ++ } ++ char* args[2] = {const_cast(KWATERFOXHELPER), NULL}; ++ switch (fork()) { ++ case -1: { ++ close(fdcommand[0]); ++ close(fdcommand[1]); ++ close(fdreply[0]); ++ close(fdreply[1]); ++ return false; + } -+ -+bool nsKDEUtils::startHelper() ++ case 0: // child + { -+ if( helperRunning ) -+ return true; -+ if( helperFailed ) -+ return false; -+ helperFailed = true; -+ int fdcommand[ 2 ]; -+ int fdreply[ 2 ]; -+ if( pipe( fdcommand ) < 0 ) -+ return false; -+ if( pipe( fdreply ) < 0 ) -+ { -+ close( fdcommand[ 0 ] ); -+ close( fdcommand[ 1 ] ); -+ return false; -+ } -+ char* args[ 2 ] = { const_cast< char* >( KWATERFOXHELPER ), NULL }; -+ switch( fork()) -+ { -+ case -1: -+ { -+ close( fdcommand[ 0 ] ); -+ close( fdcommand[ 1 ] ); -+ close( fdreply[ 0 ] ); -+ close( fdreply[ 1 ] ); -+ return false; -+ } -+ case 0: // child -+ { -+ if( dup2( fdcommand[ 0 ], STDIN_FILENO ) < 0 ) -+ _exit( 1 ); -+ if( dup2( fdreply[ 1 ], STDOUT_FILENO ) < 0 ) -+ _exit( 1 ); -+ int maxfd = 1024; // close all other fds -+ struct rlimit rl; -+ if( getrlimit( RLIMIT_NOFILE, &rl ) == 0 ) -+ maxfd = rl.rlim_max; -+ for( int i = 3; -+ i < maxfd; -+ ++i ) -+ close( i ); ++ if (dup2(fdcommand[0], STDIN_FILENO) < 0) _exit(1); ++ if (dup2(fdreply[1], STDOUT_FILENO) < 0) _exit(1); ++ int maxfd = 1024; // close all other fds ++ struct rlimit rl; ++ if (getrlimit(RLIMIT_NOFILE, &rl) == 0) maxfd = rl.rlim_max; ++ for (int i = 3; i < maxfd; ++i) close(i); +#ifdef DEBUG_KDE -+ execvp( KWATERFOXHELPER, args ); ++ execvp(KWATERFOXHELPER, args); +#else -+ execv( KWATERFOXHELPER, args ); ++ execv(KWATERFOXHELPER, args); +#endif -+ _exit( 1 ); // failed -+ } -+ default: // parent -+ { -+ commandFile = fdopen( fdcommand[ 1 ], "w" ); -+ replyFile = fdopen( fdreply[ 0 ], "r" ); -+ close( fdcommand[ 0 ] ); -+ close( fdreply[ 1 ] ); -+ if( commandFile == NULL || replyFile == NULL ) -+ { -+ closeHelper(); -+ return false; -+ } -+ // ok, helper ready, getKdeRunning() will check if it works -+ } -+ } -+ helperFailed = false; -+ helperRunning = true; -+ return true; ++ _exit(1); // failed + } -+ -+void nsKDEUtils::closeHelper() ++ default: // parent + { -+ if( commandFile != NULL ) -+ fclose( commandFile ); // this will also make the helper quit -+ if( replyFile != NULL ) -+ fclose( replyFile ); -+ helperRunning = false; ++ commandFile = fdopen(fdcommand[1], "w"); ++ replyFile = fdopen(fdreply[0], "r"); ++ close(fdcommand[0]); ++ close(fdreply[1]); ++ if (commandFile == NULL || replyFile == NULL) { ++ closeHelper(); ++ return false; ++ } ++ // ok, helper ready, getKdeRunning() will check if it works + } ++ } ++ helperFailed = false; ++ helperRunning = true; ++ return true; ++} + -+void nsKDEUtils::feedCommand( const nsTArray& command ) -+ { -+ for( int i = 0; -+ i < command.Length(); -+ ++i ) -+ { -+ nsCString line = command[ i ]; -+ line.ReplaceSubstring( "\\", "\\" "\\" ); // \ -> \\ , i.e. escape -+ line.ReplaceSubstring( "\n", "\\n" ); ++void nsKDEUtils::closeHelper() { ++ if (commandFile != NULL) ++ fclose(commandFile); // this will also make the helper quit ++ if (replyFile != NULL) fclose(replyFile); ++ helperRunning = false; ++} ++ ++void nsKDEUtils::feedCommand(const nsTArray& command) { ++ for (int i = 0; i < command.Length(); ++i) { ++ nsCString line = command[i]; ++ line.ReplaceSubstring("\\", ++ "\\" ++ "\\"); // \ -> \\ , i.e. escape ++ line.ReplaceSubstring("\n", "\\n"); +#ifdef DEBUG_KDE -+ fprintf( stderr, "COMM: %s\n", line.get()); ++ fprintf(stderr, "COMM: %s\n", line.get()); +#endif -+ fputs( line.get(), commandFile ); -+ fputs( "\n", commandFile ); -+ } -+ fputs( "\\E\n", commandFile ); // done as \E, so it cannot happen in normal data -+ fflush( commandFile ); -+ } ++ fputs(line.get(), commandFile); ++ fputs("\n", commandFile); ++ } ++ fputs("\\E\n", ++ commandFile); // done as \E, so it cannot happen in normal data ++ fflush(commandFile); ++} diff --git a/toolkit/xre/nsKDEUtils.h b/toolkit/xre/nsKDEUtils.h new file mode 100644 -index 000000000000..8729aebcaa1c +index 000000000000..ed607357c1fe --- /dev/null +++ b/toolkit/xre/nsKDEUtils.h -@@ -0,0 +1,48 @@ +@@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 @@ -856,65 +816,72 @@ index 000000000000..8729aebcaa1c + +class nsIArray; + -+class NS_EXPORT nsKDEUtils -+ { -+ public: -+ /* Returns true if running inside a KDE session (regardless of whether there is KDE -+ support available for Waterfox). This should be used e.g. when determining -+ dialog button order but not for code that requires the KDE support. */ -+ static bool kdeSession(); -+ /* Returns true if running inside a KDE session and KDE support is available -+ for Waterfox. This should be used everywhere where the external helper is needed. */ -+ static bool kdeSupport(); -+ /* Executes the given helper command, returns true if helper returned success. */ -+ static bool command( const nsTArray& command, nsTArray* output = NULL ); -+ static bool command( nsIArray* command, nsIArray** output = NULL ); -+ /* Like command(), but additionally blocks the parent widget like if there was -+ a modal dialog shown and enters the event loop (i.e. there are still paint updates, -+ this is for commands that take long). */ -+ static bool commandBlockUi( const nsTArray& command, GtkWindow* parent, nsTArray* output = NULL ); -+ -+ private: -+ nsKDEUtils(); -+ ~nsKDEUtils(); -+ static nsKDEUtils* self(); -+ bool startHelper(); -+ void closeHelper(); -+ void feedCommand( const nsTArray& command ); -+ bool internalCommand( const nsTArray& command, GtkWindow* parent, bool isParent, -+ nsTArray* output ); -+ FILE* commandFile; -+ FILE* replyFile; -+ }; -+ -+#endif // nsKDEUtils ++class NS_EXPORT nsKDEUtils { ++ public: ++ /* Returns true if running inside a KDE session (regardless of whether there ++ is KDE support available for Waterfox). This should be used e.g. when ++ determining dialog button order but not for code that requires the KDE ++ support. */ ++ static bool kdeSession(); ++ /* Returns true if running inside a KDE session and KDE support is available ++ for Waterfox. This should be used everywhere where the external helper is ++ needed. */ ++ static bool kdeSupport(); ++ /* Executes the given helper command, returns true if helper returned success. ++ */ ++ static bool command(const nsTArray& command, ++ nsTArray* output = NULL); ++ static bool command(nsIArray* command, nsIArray** output = NULL); ++ /* Like command(), but additionally blocks the parent widget like if there was ++ a modal dialog shown and enters the event loop (i.e. there are still paint ++ updates, this is for commands that take long). */ ++ static bool commandBlockUi(const nsTArray& command, ++ GtkWindow* parent, ++ nsTArray* output = NULL); ++ ++ private: ++ nsKDEUtils(); ++ ~nsKDEUtils(); ++ static nsKDEUtils* self(); ++ bool startHelper(); ++ void closeHelper(); ++ void feedCommand(const nsTArray& command); ++ bool internalCommand(const nsTArray& command, GtkWindow* parent, ++ bool isParent, nsTArray* output); ++ FILE* commandFile; ++ FILE* replyFile; ++}; ++ ++#endif // nsKDEUtils diff --git a/uriloader/exthandler/HandlerServiceParent.cpp b/uriloader/exthandler/HandlerServiceParent.cpp -index dbcc95d95646..5f2542a95065 100644 +index ab77657dd5f3..18b4d8556069 100644 --- a/uriloader/exthandler/HandlerServiceParent.cpp +++ b/uriloader/exthandler/HandlerServiceParent.cpp -@@ -12,7 +12,7 @@ - #include "ContentHandlerService.h" - #include "nsStringEnumerator.h" +@@ -18,7 +18,7 @@ + #include "nsComponentManagerUtils.h" + #include "nsServiceManagerUtils.h" #ifdef MOZ_WIDGET_GTK -# include "unix/nsGNOMERegistry.h" +# include "unix/nsCommonRegistry.h" #endif using mozilla::dom::ContentHandlerService; -@@ -304,7 +304,7 @@ mozilla::ipc::IPCResult HandlerServiceParent::RecvExistsForProtocolOS( +@@ -310,8 +310,8 @@ mozilla::ipc::IPCResult HandlerServiceParent::RecvExistsForProtocolOS( } #ifdef MOZ_WIDGET_GTK // Check the GNOME registry for a protocol handler -- *aHandlerExists = nsGNOMERegistry::HandlerExists(aProtocolScheme.get()); -+ *aHandlerExists = nsCommonRegistry::HandlerExists(aProtocolScheme.get()); +- *aHandlerExists = +- nsGNOMERegistry::HandlerExists(PromiseFlatCString(aProtocolScheme).get()); ++ *aHandlerExists = nsCommonRegistry::HandlerExists( ++ PromiseFlatCString(aProtocolScheme).get()); #else *aHandlerExists = false; #endif diff --git a/uriloader/exthandler/moz.build b/uriloader/exthandler/moz.build -index 92647a9b3478..fc5068cd2069 100644 +index 0fb126a7f3f7..8cc0006f3045 100644 --- a/uriloader/exthandler/moz.build +++ b/uriloader/exthandler/moz.build -@@ -83,7 +83,9 @@ else: +@@ -86,7 +86,9 @@ else: if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk": UNIFIED_SOURCES += [ @@ -924,7 +891,7 @@ index 92647a9b3478..fc5068cd2069 100644 "unix/nsMIMEInfoUnix.cpp", ] elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "android": -@@ -135,6 +137,7 @@ LOCAL_INCLUDES += [ +@@ -134,6 +136,7 @@ LOCAL_INCLUDES += [ "/dom/ipc", "/netwerk/base", "/netwerk/protocol/http", @@ -934,10 +901,10 @@ index 92647a9b3478..fc5068cd2069 100644 if CONFIG["MOZ_ENABLE_DBUS"]: diff --git a/uriloader/exthandler/unix/nsCommonRegistry.cpp b/uriloader/exthandler/unix/nsCommonRegistry.cpp new file mode 100644 -index 000000000000..630ab6147db3 +index 000000000000..3371a756e2c2 --- /dev/null +++ b/uriloader/exthandler/unix/nsCommonRegistry.cpp -@@ -0,0 +1,53 @@ +@@ -0,0 +1,42 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 @@ -950,50 +917,39 @@ index 000000000000..630ab6147db3 +#include "nsString.h" +#include "nsKDEUtils.h" + -+/* static */ bool -+nsCommonRegistry::HandlerExists(const char *aProtocolScheme) -+{ -+ if( nsKDEUtils::kdeSupport()) -+ return nsKDERegistry::HandlerExists( aProtocolScheme ); -+ return nsGNOMERegistry::HandlerExists( aProtocolScheme ); ++/* static */ bool nsCommonRegistry::HandlerExists(const char* aProtocolScheme) { ++ if (nsKDEUtils::kdeSupport()) ++ return nsKDERegistry::HandlerExists(aProtocolScheme); ++ return nsGNOMERegistry::HandlerExists(aProtocolScheme); +} + -+/* static */ nsresult -+nsCommonRegistry::LoadURL(nsIURI *aURL) -+{ -+ if( nsKDEUtils::kdeSupport()) -+ return nsKDERegistry::LoadURL( aURL ); -+ return nsGNOMERegistry::LoadURL( aURL ); ++/* static */ nsresult nsCommonRegistry::LoadURL(nsIURI* aURL) { ++ if (nsKDEUtils::kdeSupport()) return nsKDERegistry::LoadURL(aURL); ++ return nsGNOMERegistry::LoadURL(aURL); +} + -+/* static */ void -+nsCommonRegistry::GetAppDescForScheme(const nsACString& aScheme, -+ nsAString& aDesc) -+{ -+ if( nsKDEUtils::kdeSupport()) -+ return nsKDERegistry::GetAppDescForScheme( aScheme, aDesc ); -+ return nsGNOMERegistry::GetAppDescForScheme( aScheme, aDesc ); ++/* static */ void nsCommonRegistry::GetAppDescForScheme( ++ const nsACString& aScheme, nsAString& aDesc) { ++ if (nsKDEUtils::kdeSupport()) ++ return nsKDERegistry::GetAppDescForScheme(aScheme, aDesc); ++ return nsGNOMERegistry::GetAppDescForScheme(aScheme, aDesc); +} + -+ +/* static */ already_AddRefed -+nsCommonRegistry::GetFromExtension(const nsACString& aFileExt) -+{ -+ if( nsKDEUtils::kdeSupport()) -+ return nsKDERegistry::GetFromExtension( aFileExt ); -+ return nsGNOMERegistry::GetFromExtension( aFileExt ); ++nsCommonRegistry::GetFromExtension(const nsACString& aFileExt) { ++ if (nsKDEUtils::kdeSupport()) ++ return nsKDERegistry::GetFromExtension(aFileExt); ++ return nsGNOMERegistry::GetFromExtension(aFileExt); +} + -+/* static */ already_AddRefed -+nsCommonRegistry::GetFromType(const nsACString& aMIMEType) -+{ -+ if( nsKDEUtils::kdeSupport()) -+ return nsKDERegistry::GetFromType( aMIMEType ); -+ return nsGNOMERegistry::GetFromType( aMIMEType ); ++/* static */ already_AddRefed nsCommonRegistry::GetFromType( ++ const nsACString& aMIMEType) { ++ if (nsKDEUtils::kdeSupport()) return nsKDERegistry::GetFromType(aMIMEType); ++ return nsGNOMERegistry::GetFromType(aMIMEType); +} diff --git a/uriloader/exthandler/unix/nsCommonRegistry.h b/uriloader/exthandler/unix/nsCommonRegistry.h new file mode 100644 -index 000000000000..85b3d9cee25e +index 000000000000..075413e2fbb1 --- /dev/null +++ b/uriloader/exthandler/unix/nsCommonRegistry.h @@ -0,0 +1,28 @@ @@ -1009,28 +965,28 @@ index 000000000000..85b3d9cee25e + +class nsMIMEInfoBase; + -+class nsCommonRegistry -+{ ++class nsCommonRegistry { + public: -+ static bool HandlerExists(const char *aProtocolScheme); ++ static bool HandlerExists(const char* aProtocolScheme); + -+ static nsresult LoadURL(nsIURI *aURL); ++ static nsresult LoadURL(nsIURI* aURL); + -+ static void GetAppDescForScheme(const nsACString& aScheme, -+ nsAString& aDesc); ++ static void GetAppDescForScheme(const nsACString& aScheme, nsAString& aDesc); + -+ static already_AddRefed GetFromExtension(const nsACString& aFileExt); ++ static already_AddRefed GetFromExtension( ++ const nsACString& aFileExt); + -+ static already_AddRefed GetFromType(const nsACString& aMIMEType); ++ static already_AddRefed GetFromType( ++ const nsACString& aMIMEType); +}; + +#endif diff --git a/uriloader/exthandler/unix/nsKDERegistry.cpp b/uriloader/exthandler/unix/nsKDERegistry.cpp new file mode 100644 -index 000000000000..f78e64c7e9a3 +index 000000000000..082035566f0b --- /dev/null +++ b/uriloader/exthandler/unix/nsKDERegistry.cpp -@@ -0,0 +1,89 @@ +@@ -0,0 +1,75 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 @@ -1044,88 +1000,74 @@ index 000000000000..f78e64c7e9a3 +#include "nsMIMEInfoUnix.h" +#include "nsKDEUtils.h" + -+/* static */ bool -+nsKDERegistry::HandlerExists(const char *aProtocolScheme) -+{ -+ nsTArray command; -+ command.AppendElement( "HANDLEREXISTS"_ns ); -+ command.AppendElement( nsAutoCString( aProtocolScheme )); -+ return nsKDEUtils::command( command ); ++/* static */ bool nsKDERegistry::HandlerExists(const char* aProtocolScheme) { ++ nsTArray command; ++ command.AppendElement("HANDLEREXISTS"_ns); ++ command.AppendElement(nsAutoCString(aProtocolScheme)); ++ return nsKDEUtils::command(command); +} + -+/* static */ nsresult -+nsKDERegistry::LoadURL(nsIURI *aURL) -+{ -+ nsTArray command; -+ command.AppendElement( "OPEN"_ns ); -+ nsCString url; -+ aURL->GetSpec( url ); -+ command.AppendElement( url ); -+ bool rv = nsKDEUtils::command( command ); -+ if (!rv) -+ return NS_ERROR_FAILURE; ++/* static */ nsresult nsKDERegistry::LoadURL(nsIURI* aURL) { ++ nsTArray command; ++ command.AppendElement("OPEN"_ns); ++ nsCString url; ++ aURL->GetSpec(url); ++ command.AppendElement(url); ++ bool rv = nsKDEUtils::command(command); ++ if (!rv) return NS_ERROR_FAILURE; + -+ return NS_OK; ++ return NS_OK; +} + -+/* static */ void -+nsKDERegistry::GetAppDescForScheme(const nsACString& aScheme, -+ nsAString& aDesc) -+{ -+ nsTArray command; -+ command.AppendElement( "GETAPPDESCFORSCHEME"_ns ); -+ command.AppendElement( aScheme ); -+ nsTArray output; -+ if( nsKDEUtils::command( command, &output ) && output.Length() == 1 ) -+ CopyUTF8toUTF16( output[ 0 ], aDesc ); ++/* static */ void nsKDERegistry::GetAppDescForScheme(const nsACString& aScheme, ++ nsAString& aDesc) { ++ nsTArray command; ++ command.AppendElement("GETAPPDESCFORSCHEME"_ns); ++ command.AppendElement(aScheme); ++ nsTArray output; ++ if (nsKDEUtils::command(command, &output) && output.Length() == 1) ++ CopyUTF8toUTF16(output[0], aDesc); +} + -+ -+/* static */ already_AddRefed -+nsKDERegistry::GetFromExtension(const nsACString& aFileExt) -+{ -+ NS_ASSERTION(aFileExt[0] != '.', "aFileExt shouldn't start with a dot"); -+ nsTArray command; -+ command.AppendElement( "GETFROMEXTENSION"_ns ); -+ command.AppendElement( aFileExt ); -+ return GetFromHelper( command ); ++/* static */ already_AddRefed nsKDERegistry::GetFromExtension( ++ const nsACString& aFileExt) { ++ NS_ASSERTION(aFileExt[0] != '.', "aFileExt shouldn't start with a dot"); ++ nsTArray command; ++ command.AppendElement("GETFROMEXTENSION"_ns); ++ command.AppendElement(aFileExt); ++ return GetFromHelper(command); +} + -+/* static */ already_AddRefed -+nsKDERegistry::GetFromType(const nsACString& aMIMEType) -+{ -+ nsTArray command; -+ command.AppendElement( "GETFROMTYPE"_ns ); -+ command.AppendElement( aMIMEType ); -+ return GetFromHelper( command ); ++/* static */ already_AddRefed nsKDERegistry::GetFromType( ++ const nsACString& aMIMEType) { ++ nsTArray command; ++ command.AppendElement("GETFROMTYPE"_ns); ++ command.AppendElement(aMIMEType); ++ return GetFromHelper(command); +} + -+/* static */ already_AddRefed -+nsKDERegistry::GetFromHelper(const nsTArray& command) -+{ -+ nsTArray output; -+ if( nsKDEUtils::command( command, &output ) && output.Length() == 3 ) -+ { -+ nsCString mimetype = output[ 0 ]; -+ RefPtr mimeInfo = new nsMIMEInfoUnix( mimetype ); -+ NS_ENSURE_TRUE(mimeInfo, nullptr); -+ nsCString description = output[ 1 ]; -+ mimeInfo->SetDescription(NS_ConvertUTF8toUTF16(description)); -+ nsCString handlerAppName = output[ 2 ]; -+ mozilla::StaticPrefs::browser_download_improvements_to_download_panel() -+ ? mimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk) -+ : mimeInfo->SetPreferredAction(nsIMIMEInfo::useSystemDefault); -+ mimeInfo->SetDefaultDescription(NS_ConvertUTF8toUTF16(handlerAppName)); -+ return mimeInfo.forget(); -+ } -+ return nullptr; ++/* static */ already_AddRefed nsKDERegistry::GetFromHelper( ++ const nsTArray& command) { ++ nsTArray output; ++ if (nsKDEUtils::command(command, &output) && output.Length() == 3) { ++ nsCString mimetype = output[0]; ++ RefPtr mimeInfo = new nsMIMEInfoUnix(mimetype); ++ NS_ENSURE_TRUE(mimeInfo, nullptr); ++ nsCString description = output[1]; ++ mimeInfo->SetDescription(NS_ConvertUTF8toUTF16(description)); ++ nsCString handlerAppName = output[2]; ++ mimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk); ++ mimeInfo->SetDefaultDescription(NS_ConvertUTF8toUTF16(handlerAppName)); ++ return mimeInfo.forget(); ++ } ++ return nullptr; +} diff --git a/uriloader/exthandler/unix/nsKDERegistry.h b/uriloader/exthandler/unix/nsKDERegistry.h new file mode 100644 -index 000000000000..5b07eebc6d62 +index 000000000000..c6a41b331b2b --- /dev/null +++ b/uriloader/exthandler/unix/nsKDERegistry.h -@@ -0,0 +1,34 @@ +@@ -0,0 +1,35 @@ +/* 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/. */ @@ -1138,30 +1080,31 @@ index 000000000000..5b07eebc6d62 +#include "nsTArray.h" + +class nsMIMEInfoBase; -+//class nsAutoCString; -+//class nsCString; ++// class nsAutoCString; ++// class nsCString; + -+class nsKDERegistry -+{ ++class nsKDERegistry { + public: -+ static bool HandlerExists(const char *aProtocolScheme); ++ static bool HandlerExists(const char* aProtocolScheme); + -+ static nsresult LoadURL(nsIURI *aURL); ++ static nsresult LoadURL(nsIURI* aURL); + -+ static void GetAppDescForScheme(const nsACString& aScheme, -+ nsAString& aDesc); ++ static void GetAppDescForScheme(const nsACString& aScheme, nsAString& aDesc); + -+ static already_AddRefed GetFromExtension(const nsACString& aFileExt); ++ static already_AddRefed GetFromExtension( ++ const nsACString& aFileExt); + -+ static already_AddRefed GetFromType(const nsACString& aMIMEType); -+ private: -+ static already_AddRefed GetFromHelper(const nsTArray& command); ++ static already_AddRefed GetFromType( ++ const nsACString& aMIMEType); + ++ private: ++ static already_AddRefed GetFromHelper( ++ const nsTArray& command); +}; + -+#endif //nsKDERegistry_h__ ++#endif // nsKDERegistry_h__ diff --git a/uriloader/exthandler/unix/nsMIMEInfoUnix.cpp b/uriloader/exthandler/unix/nsMIMEInfoUnix.cpp -index 7cbefcce3e94..84083348c8f1 100644 +index 330c4411597f..c96c1f3ca5a0 100644 --- a/uriloader/exthandler/unix/nsMIMEInfoUnix.cpp +++ b/uriloader/exthandler/unix/nsMIMEInfoUnix.cpp @@ -5,16 +5,19 @@ @@ -1177,7 +1120,7 @@ index 7cbefcce3e94..84083348c8f1 100644 # include "nsDBusHandlerApp.h" #endif +#if defined(XP_UNIX) && !defined(XP_MACOSX) -+#include "nsKDEUtils.h" ++# include "nsKDEUtils.h" +#endif nsresult nsMIMEInfoUnix::LoadUriInternal(nsIURI* aURI) { @@ -1186,7 +1129,7 @@ index 7cbefcce3e94..84083348c8f1 100644 } NS_IMETHODIMP -@@ -27,15 +30,15 @@ nsMIMEInfoUnix::GetHasDefaultHandler(bool* _retval) { +@@ -29,15 +32,15 @@ nsMIMEInfoUnix::GetHasDefaultHandler(bool* _retval) { *_retval = false; if (mClass == eProtocolInfo) { @@ -1205,32 +1148,30 @@ index 7cbefcce3e94..84083348c8f1 100644 } } if (mimeInfo) *_retval = true; -@@ -55,6 +58,23 @@ nsresult nsMIMEInfoUnix::LaunchDefaultWithFile(nsIFile* aFile) { +@@ -59,6 +62,21 @@ nsresult nsMIMEInfoUnix::LaunchDefaultWithFile(nsIFile* aFile) { nsAutoCString nativePath; aFile->GetNativePath(nativePath); -+ if( nsKDEUtils::kdeSupport()) { ++ if (nsKDEUtils::kdeSupport()) { + bool supports; -+ if( NS_SUCCEEDED( GetHasDefaultHandler( &supports )) && supports ) { ++ if (NS_SUCCEEDED(GetHasDefaultHandler(&supports)) && supports) { + nsTArray command; -+ command.AppendElement( "OPEN"_ns ); -+ command.AppendElement( nativePath ); -+ command.AppendElement( "MIMETYPE"_ns ); -+ command.AppendElement( mSchemeOrType ); -+ if( nsKDEUtils::command( command )) -+ return NS_OK; ++ command.AppendElement("OPEN"_ns); ++ command.AppendElement(nativePath); ++ command.AppendElement("MIMETYPE"_ns); ++ command.AppendElement(mSchemeOrType); ++ if (nsKDEUtils::command(command)) return NS_OK; + } -+ if (!mDefaultApplication) -+ return NS_ERROR_FILE_NOT_FOUND; ++ if (!GetDefaultApplication()) return NS_ERROR_FILE_NOT_FOUND; + -+ return LaunchWithIProcess(mDefaultApplication, nativePath); ++ return LaunchWithIProcess(GetDefaultApplication(), nativePath); + } + nsCOMPtr giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID); if (!giovfs) { return NS_ERROR_FAILURE; diff --git a/uriloader/exthandler/unix/nsOSHelperAppService.cpp b/uriloader/exthandler/unix/nsOSHelperAppService.cpp -index b9e7aed3cb5c..367ad9ee2421 100644 +index 7f6eaa46f2ee..f7627e790c47 100644 --- a/uriloader/exthandler/unix/nsOSHelperAppService.cpp +++ b/uriloader/exthandler/unix/nsOSHelperAppService.cpp @@ -10,7 +10,7 @@ @@ -1242,7 +1183,7 @@ index b9e7aed3cb5c..367ad9ee2421 100644 # ifdef MOZ_BUILD_APP_IS_BROWSER # include "nsIToolkitShellService.h" # include "nsIGNOMEShellService.h" -@@ -1030,7 +1030,7 @@ nsresult nsOSHelperAppService::OSProtocolHandlerExists( +@@ -1106,7 +1106,7 @@ nsresult nsOSHelperAppService::OSProtocolHandlerExists( if (!XRE_IsContentProcess()) { #ifdef MOZ_WIDGET_GTK // Check the GNOME registry for a protocol handler @@ -1251,7 +1192,7 @@ index b9e7aed3cb5c..367ad9ee2421 100644 #else *aHandlerExists = false; #endif -@@ -1050,7 +1050,7 @@ nsresult nsOSHelperAppService::OSProtocolHandlerExists( +@@ -1126,7 +1126,7 @@ nsresult nsOSHelperAppService::OSProtocolHandlerExists( NS_IMETHODIMP nsOSHelperAppService::GetApplicationDescription( const nsACString& aScheme, nsAString& _retval) { #ifdef MOZ_WIDGET_GTK @@ -1260,29 +1201,29 @@ index b9e7aed3cb5c..367ad9ee2421 100644 return _retval.IsEmpty() ? NS_ERROR_NOT_AVAILABLE : NS_OK; #else return NS_ERROR_NOT_AVAILABLE; -@@ -1153,7 +1153,7 @@ already_AddRefed nsOSHelperAppService::GetFromExtension( +@@ -1231,7 +1231,7 @@ already_AddRefed nsOSHelperAppService::GetFromExtension( #ifdef MOZ_WIDGET_GTK - LOG(("Looking in GNOME registry\n")); + LOG("Looking in GNOME registry\n"); RefPtr gnomeInfo = - nsGNOMERegistry::GetFromExtension(aFileExt); + nsCommonRegistry::GetFromExtension(aFileExt); if (gnomeInfo) { - LOG(("Got MIMEInfo from GNOME registry\n")); + LOG("Got MIMEInfo from GNOME registry\n"); return gnomeInfo.forget(); -@@ -1266,7 +1266,7 @@ already_AddRefed nsOSHelperAppService::GetFromType( +@@ -1344,7 +1344,7 @@ already_AddRefed nsOSHelperAppService::GetFromType( #ifdef MOZ_WIDGET_GTK if (handler.IsEmpty()) { - RefPtr gnomeInfo = nsGNOMERegistry::GetFromType(aMIMEType); + RefPtr gnomeInfo = nsCommonRegistry::GetFromType(aMIMEType); if (gnomeInfo) { - LOG( - ("Got MIMEInfo from GNOME registry without extensions; setting them " + LOG("Got MIMEInfo from GNOME registry without extensions; setting them " + "to %s\n", diff --git a/widget/gtk/moz.build b/widget/gtk/moz.build -index bf64f7ebdc65..f94b4e017e5e 100644 +index 90574c130f59..ef530396aa0b 100644 --- a/widget/gtk/moz.build +++ b/widget/gtk/moz.build -@@ -169,6 +169,7 @@ LOCAL_INCLUDES += [ +@@ -159,6 +159,7 @@ LOCAL_INCLUDES += [ "/layout/xul", "/other-licenses/atk-1.0", "/third_party/cups/include", @@ -1291,7 +1232,7 @@ index bf64f7ebdc65..f94b4e017e5e 100644 "/widget/headless", ] diff --git a/widget/gtk/nsFilePicker.cpp b/widget/gtk/nsFilePicker.cpp -index c73130496ae7..9e7b9aed56f1 100644 +index 22d0f46b9563..b2a68711eb34 100644 --- a/widget/gtk/nsFilePicker.cpp +++ b/widget/gtk/nsFilePicker.cpp @@ -5,6 +5,7 @@ @@ -1302,7 +1243,7 @@ index c73130496ae7..9e7b9aed56f1 100644 #include #include #include -@@ -27,6 +28,8 @@ +@@ -28,6 +29,8 @@ #include "WidgetUtilsGtk.h" #include "nsFilePicker.h" @@ -1311,40 +1252,41 @@ index c73130496ae7..9e7b9aed56f1 100644 #undef LOG #ifdef MOZ_LOGGING -@@ -241,7 +244,9 @@ NS_IMETHODIMP +@@ -242,7 +245,8 @@ NS_IMETHODIMP nsFilePicker::AppendFilter(const nsAString& aTitle, const nsAString& aFilter) { if (aFilter.EqualsLiteral("..apps")) { // No platform specific thing we can do here, really.... - return NS_OK; + // Unless it's KDE. -+ if( mMode != modeOpen || !nsKDEUtils::kdeSupport()) -+ return NS_OK; ++ if (mMode != modeOpen || !nsKDEUtils::kdeSupport()) return NS_OK; } nsAutoCString filter, name; -@@ -351,6 +356,29 @@ nsFilePicker::Open(nsIFilePickerShownCallback* aCallback) { +@@ -352,6 +356,31 @@ nsFilePicker::Open(nsIFilePickerShownCallback* aCallback) { // Can't show two dialogs concurrently with the same filepicker if (mRunning) return NS_ERROR_NOT_AVAILABLE; + // KDE file picker is not handled via callback -+ if( nsKDEUtils::kdeSupport()) { ++ if (nsKDEUtils::kdeSupport()) { + mCallback = aCallback; + mRunning = true; + NS_ADDREF_THIS(); -+ g_idle_add([](gpointer data) -> gboolean { -+ nsFilePicker* queuedPicker = (nsFilePicker*) data; -+ int16_t result; -+ queuedPicker->kdeFileDialog(&result); -+ if (queuedPicker->mCallback) { -+ queuedPicker->mCallback->Done(result); -+ queuedPicker->mCallback = nullptr; -+ } else { -+ queuedPicker->mResult = result; -+ } -+ queuedPicker->mRunning = false; -+ NS_RELEASE(queuedPicker); -+ return G_SOURCE_REMOVE; -+ }, this); ++ g_idle_add( ++ [](gpointer data) -> gboolean { ++ nsFilePicker* queuedPicker = (nsFilePicker*)data; ++ nsIFilePicker::ResultCode result; ++ queuedPicker->kdeFileDialog(&result); ++ if (queuedPicker->mCallback) { ++ queuedPicker->mCallback->Done(result); ++ queuedPicker->mCallback = nullptr; ++ } else { ++ queuedPicker->mResult = result; ++ } ++ queuedPicker->mRunning = false; ++ NS_RELEASE(queuedPicker); ++ return G_SOURCE_REMOVE; ++ }, ++ this); + + return NS_OK; + } @@ -1352,76 +1294,68 @@ index c73130496ae7..9e7b9aed56f1 100644 NS_ConvertUTF16toUTF8 title(mTitle); GtkWindow* parent_widget = -@@ -580,6 +608,234 @@ void nsFilePicker::Done(void* file_chooser, gint response) { +@@ -633,6 +662,205 @@ void nsFilePicker::Done(void* file_chooser, gint response) { NS_RELEASE_THIS(); } -+nsCString nsFilePicker::kdeMakeFilter( int index ) -+ { -+ nsCString buf = mFilters[ index ]; -+ for( PRUint32 i = 0; -+ i < buf.Length(); -+ ++i ) -+ if( buf[ i ] == ';' ) // KDE separates just using spaces -+ buf.SetCharAt( ' ', i ); -+ if (!mFilterNames[index].IsEmpty()) -+ { -+ buf += "|"; -+ buf += mFilterNames[index].get(); -+ } -+ return buf; -+ } ++nsCString nsFilePicker::kdeMakeFilter(int index) { ++ nsCString buf = mFilters[index]; ++ for (PRUint32 i = 0; i < buf.Length(); ++i) ++ if (buf[i] == ';') // KDE separates just using spaces ++ buf.SetCharAt(' ', i); ++ if (!mFilterNames[index].IsEmpty()) { ++ buf += "|"; ++ buf += mFilterNames[index].get(); ++ } ++ return buf; ++} + -+static PRInt32 windowToXid( nsIWidget* widget ) -+ { -+ GtkWindow *parent_widget = GTK_WINDOW(widget->GetNativeData(NS_NATIVE_SHELLWIDGET)); -+ GdkWindow* gdk_window = gtk_widget_get_window( gtk_widget_get_toplevel( GTK_WIDGET( parent_widget ))); -+ return GDK_WINDOW_XID( gdk_window ); -+ } ++static PRInt32 windowToXid(nsIWidget* widget) { ++ GtkWindow* parent_widget = ++ GTK_WINDOW(widget->GetNativeData(NS_NATIVE_SHELLWIDGET)); ++ GdkWindow* gdk_window = ++ gtk_widget_get_window(gtk_widget_get_toplevel(GTK_WIDGET(parent_widget))); ++ return GDK_WINDOW_XID(gdk_window); ++} + -+NS_IMETHODIMP nsFilePicker::kdeFileDialog(PRInt16 *aReturn) -+ { -+ NS_ENSURE_ARG_POINTER(aReturn); -+ -+ if( mMode == modeOpen && mFilters.Length() == 1 && mFilters[ 0 ].EqualsLiteral( "..apps" )) -+ return kdeAppsDialog( aReturn ); -+ -+ nsCString title; -+ title.Adopt(ToNewUTF8String(mTitle)); -+ -+ const char* arg = NULL; -+ if( mAllowURLs ) -+ { -+ switch( mMode ) -+ { -+ case nsIFilePicker::modeOpen: -+ case nsIFilePicker::modeOpenMultiple: -+ arg = "GETOPENURL"; -+ break; -+ case nsIFilePicker::modeSave: -+ arg = "GETSAVEURL"; -+ break; -+ case nsIFilePicker::modeGetFolder: -+ arg = "GETDIRECTORYURL"; -+ break; -+ } -+ } -+ else -+ { -+ switch( mMode ) -+ { -+ case nsIFilePicker::modeOpen: -+ case nsIFilePicker::modeOpenMultiple: -+ arg = "GETOPENFILENAME"; -+ break; -+ case nsIFilePicker::modeSave: -+ arg = "GETSAVEFILENAME"; -+ break; -+ case nsIFilePicker::modeGetFolder: -+ arg = "GETDIRECTORYFILENAME"; -+ break; -+ } -+ } ++NS_IMETHODIMP nsFilePicker::kdeFileDialog(nsIFilePicker::ResultCode* aReturn) { ++ NS_ENSURE_ARG_POINTER(aReturn); ++ ++ if (mMode == modeOpen && mFilters.Length() == 1 && ++ mFilters[0].EqualsLiteral("..apps")) ++ return kdeAppsDialog(aReturn); ++ ++ nsCString title; ++ title.Adopt(ToNewUTF8String(mTitle)); ++ ++ const char* arg = NULL; ++ if (mAllowURLs) { ++ switch (mMode) { ++ case nsIFilePicker::modeOpen: ++ case nsIFilePicker::modeOpenMultiple: ++ arg = "GETOPENURL"; ++ break; ++ case nsIFilePicker::modeSave: ++ arg = "GETSAVEURL"; ++ break; ++ case nsIFilePicker::modeGetFolder: ++ arg = "GETDIRECTORYURL"; ++ break; ++ } ++ } else { ++ switch (mMode) { ++ case nsIFilePicker::modeOpen: ++ case nsIFilePicker::modeOpenMultiple: ++ arg = "GETOPENFILENAME"; ++ break; ++ case nsIFilePicker::modeSave: ++ arg = "GETSAVEFILENAME"; ++ break; ++ case nsIFilePicker::modeGetFolder: ++ arg = "GETDIRECTORYFILENAME"; ++ break; ++ } ++ } + + nsAutoCString directory; + if (mDisplayDirectory) { @@ -1430,182 +1364,161 @@ index c73130496ae7..9e7b9aed56f1 100644 + mPrevDisplayDirectory->GetNativePath(directory); + } + -+ nsAutoCString startdir; ++ nsAutoCString startdir; + if (!directory.IsEmpty()) { + startdir = directory; + } + if (mMode == nsIFilePicker::modeSave) { -+ if( !startdir.IsEmpty()) -+ { ++ if (!startdir.IsEmpty()) { + startdir += "/"; + startdir += ToNewUTF8String(mDefault); -+ } -+ else ++ } else + startdir = ToNewUTF8String(mDefault); + } + -+ nsAutoCString filters; -+ PRInt32 count = mFilters.Length(); -+ if( count == 0 ) //just in case -+ filters = "*"; -+ else -+ { -+ filters = kdeMakeFilter( 0 ); -+ for (PRInt32 i = 1; i < count; ++i) -+ { -+ filters += "\n"; -+ filters += kdeMakeFilter( i ); -+ } -+ } ++ nsAutoCString filters; ++ PRInt32 count = mFilters.Length(); ++ if (count == 0) // just in case ++ filters = "*"; ++ else { ++ filters = kdeMakeFilter(0); ++ for (PRInt32 i = 1; i < count; ++i) { ++ filters += "\n"; ++ filters += kdeMakeFilter(i); ++ } ++ } + -+ nsTArray command; -+ command.AppendElement( nsAutoCString( arg )); -+ command.AppendElement( startdir ); -+ if( mMode != nsIFilePicker::modeGetFolder ) -+ { -+ command.AppendElement( filters ); -+ nsAutoCString selected; -+ selected.AppendInt( mSelectedType ); -+ command.AppendElement( selected ); -+ } -+ command.AppendElement( title ); -+ if( mMode == nsIFilePicker::modeOpenMultiple ) -+ command.AppendElement( "MULTIPLE"_ns ); -+ if( PRInt32 xid = windowToXid( mParentWidget )) -+ { -+ command.AppendElement( "PARENT"_ns ); -+ nsAutoCString parent; -+ parent.AppendInt( xid ); -+ command.AppendElement( parent ); -+ } ++ nsTArray command; ++ command.AppendElement(nsAutoCString(arg)); ++ command.AppendElement(startdir); ++ if (mMode != nsIFilePicker::modeGetFolder) { ++ command.AppendElement(filters); ++ nsAutoCString selected; ++ selected.AppendInt(mSelectedType); ++ command.AppendElement(selected); ++ } ++ command.AppendElement(title); ++ if (mMode == nsIFilePicker::modeOpenMultiple) ++ command.AppendElement("MULTIPLE"_ns); ++ if (PRInt32 xid = windowToXid(mParentWidget)) { ++ command.AppendElement("PARENT"_ns); ++ nsAutoCString parent; ++ parent.AppendInt(xid); ++ command.AppendElement(parent); ++ } + -+ nsTArray output; -+ if( nsKDEUtils::commandBlockUi( command, GTK_WINDOW(mParentWidget->GetNativeData(NS_NATIVE_SHELLWIDGET)), &output )) -+ { -+ *aReturn = nsIFilePicker::returnOK; -+ mFiles.Clear(); -+ if( mMode != nsIFilePicker::modeGetFolder ) -+ { -+ mSelectedType = atoi( output[ 0 ].get()); -+ output.RemoveElementAt( 0 ); -+ } -+ if (mMode == nsIFilePicker::modeOpenMultiple) -+ { -+ mFileURL.Truncate(); -+ PRUint32 count = output.Length(); -+ for( PRUint32 i = 0; -+ i < count; -+ ++i ) -+ { -+ nsCOMPtr localfile; -+ nsresult rv = NS_NewNativeLocalFile( output[ i ], -+ PR_FALSE, -+ getter_AddRefs(localfile)); -+ if (NS_SUCCEEDED(rv)) -+ mFiles.AppendObject(localfile); -+ } -+ } -+ else -+ { -+ if( output.Length() == 0 ) -+ mFileURL = nsCString(); -+ else if( mAllowURLs ) -+ mFileURL = output[ 0 ]; -+ else // GetFile() actually requires it to be url even for local files :-/ -+ { -+ nsCOMPtr localfile; -+ nsresult rv = NS_NewNativeLocalFile( output[ 0 ], -+ PR_FALSE, -+ getter_AddRefs(localfile)); -+ if (NS_SUCCEEDED(rv)) -+ rv = net_GetURLSpecFromActualFile(localfile, mFileURL); -+ } -+ } -+ // Remember last used directory. -+ nsCOMPtr file; -+ GetFile(getter_AddRefs(file)); -+ if (file) { -+ nsCOMPtr dir; -+ file->GetParent(getter_AddRefs(dir)); -+ nsCOMPtr localDir(do_QueryInterface(dir)); -+ if (localDir) { -+ localDir.swap(mPrevDisplayDirectory); ++ nsTArray output; ++ if (nsKDEUtils::commandBlockUi( ++ command, ++ GTK_WINDOW(mParentWidget->GetNativeData(NS_NATIVE_SHELLWIDGET)), ++ &output)) { ++ *aReturn = nsIFilePicker::returnOK; ++ mFiles.Clear(); ++ if (mMode != nsIFilePicker::modeGetFolder) { ++ mSelectedType = atoi(output[0].get()); ++ output.RemoveElementAt(0); + } -+ } -+ if (mMode == nsIFilePicker::modeSave) -+ { -+ nsCOMPtr file; -+ GetFile(getter_AddRefs(file)); -+ if (file) -+ { -+ bool exists = false; -+ file->Exists(&exists); -+ if (exists) // TODO do overwrite check in the helper app -+ *aReturn = nsIFilePicker::returnReplace; -+ } -+ } -+ } -+ else -+ { -+ *aReturn = nsIFilePicker::returnCancel; -+ } -+ return NS_OK; ++ if (mMode == nsIFilePicker::modeOpenMultiple) { ++ mFileURL.Truncate(); ++ PRUint32 count = output.Length(); ++ for (PRUint32 i = 0; i < count; ++i) { ++ nsCOMPtr localfile; ++ nsresult rv = NS_NewNativeLocalFile(output[i], PR_FALSE, ++ getter_AddRefs(localfile)); ++ if (NS_SUCCEEDED(rv)) mFiles.AppendObject(localfile); ++ } ++ } else { ++ if (output.Length() == 0) ++ mFileURL = nsCString(); ++ else if (mAllowURLs) ++ mFileURL = output[0]; ++ else // GetFile() actually requires it to be url even for local files :-/ ++ { ++ nsCOMPtr localfile; ++ nsresult rv = NS_NewNativeLocalFile(output[0], PR_FALSE, ++ getter_AddRefs(localfile)); ++ if (NS_SUCCEEDED(rv)) ++ rv = net_GetURLSpecFromActualFile(localfile, mFileURL); ++ } + } ++ // Remember last used directory. ++ nsCOMPtr file; ++ GetFile(getter_AddRefs(file)); ++ if (file) { ++ nsCOMPtr dir; ++ file->GetParent(getter_AddRefs(dir)); ++ nsCOMPtr localDir(dir); ++ if (localDir) { ++ localDir.swap(mPrevDisplayDirectory); ++ } ++ } ++ if (mMode == nsIFilePicker::modeSave) { ++ nsCOMPtr file; ++ GetFile(getter_AddRefs(file)); ++ if (file) { ++ bool exists = false; ++ file->Exists(&exists); ++ if (exists) // TODO do overwrite check in the helper app ++ *aReturn = nsIFilePicker::returnReplace; ++ } ++ } ++ } else { ++ *aReturn = nsIFilePicker::returnCancel; ++ } ++ return NS_OK; ++} + ++NS_IMETHODIMP nsFilePicker::kdeAppsDialog(nsIFilePicker::ResultCode* aReturn) { ++ NS_ENSURE_ARG_POINTER(aReturn); + -+NS_IMETHODIMP nsFilePicker::kdeAppsDialog(PRInt16 *aReturn) -+ { -+ NS_ENSURE_ARG_POINTER(aReturn); -+ -+ nsCString title; -+ title.Adopt(ToNewUTF8String(mTitle)); ++ nsCString title; ++ title.Adopt(ToNewUTF8String(mTitle)); + -+ nsTArray command; -+ command.AppendElement( "APPSDIALOG"_ns ); -+ command.AppendElement( title ); -+ if( PRInt32 xid = windowToXid( mParentWidget )) -+ { -+ command.AppendElement( "PARENT"_ns ); -+ nsAutoCString parent; -+ parent.AppendInt( xid ); -+ command.AppendElement( parent ); -+ } ++ nsTArray command; ++ command.AppendElement("APPSDIALOG"_ns); ++ command.AppendElement(title); ++ if (PRInt32 xid = windowToXid(mParentWidget)) { ++ command.AppendElement("PARENT"_ns); ++ nsAutoCString parent; ++ parent.AppendInt(xid); ++ command.AppendElement(parent); ++ } + -+ nsTArray output; -+ if( nsKDEUtils::commandBlockUi( command, GTK_WINDOW(mParentWidget->GetNativeData(NS_NATIVE_SHELLWIDGET)), &output )) -+ { -+ *aReturn = nsIFilePicker::returnOK; -+ mFileURL = output.Length() > 0 ? output[ 0 ] : nsCString(); -+ } -+ else -+ { -+ *aReturn = nsIFilePicker::returnCancel; -+ } -+ return NS_OK; -+ } ++ nsTArray output; ++ if (nsKDEUtils::commandBlockUi( ++ command, ++ GTK_WINDOW(mParentWidget->GetNativeData(NS_NATIVE_SHELLWIDGET)), ++ &output)) { ++ *aReturn = nsIFilePicker::returnOK; ++ mFileURL = output.Length() > 0 ? output[0] : nsCString(); ++ } else { ++ *aReturn = nsIFilePicker::returnCancel; ++ } ++ return NS_OK; ++} + // All below functions available as of GTK 3.20+ void* nsFilePicker::GtkFileChooserNew(const gchar* title, GtkWindow* parent, GtkFileChooserAction action, diff --git a/widget/gtk/nsFilePicker.h b/widget/gtk/nsFilePicker.h -index 9b3110aa0048..be9d559c7bf4 100644 +index 496df4937277..a4c1862ec042 100644 --- a/widget/gtk/nsFilePicker.h +++ b/widget/gtk/nsFilePicker.h -@@ -72,6 +72,12 @@ class nsFilePicker : public nsBaseFilePicker { +@@ -74,6 +74,12 @@ class nsFilePicker : public nsBaseFilePicker { private: static nsIFile* mPrevDisplayDirectory; + bool kdeRunning(); + bool getKdeRunning(); -+ NS_IMETHODIMP kdeFileDialog(PRInt16 *aReturn); -+ NS_IMETHODIMP kdeAppsDialog(PRInt16 *aReturn); -+ nsCString kdeMakeFilter( int index ); ++ NS_IMETHODIMP kdeFileDialog(nsIFilePicker::ResultCode* aReturn); ++ NS_IMETHODIMP kdeAppsDialog(nsIFilePicker::ResultCode* aReturn); ++ nsCString kdeMakeFilter(int index); + void* GtkFileChooserNew(const gchar* title, GtkWindow* parent, GtkFileChooserAction action, const gchar* accept_label); diff --git a/xpcom/components/ManifestParser.cpp b/xpcom/components/ManifestParser.cpp -index f3d0055f2cc6..d13543ab5221 100644 +index 88ee06d78db6..834d6a2d353c 100644 --- a/xpcom/components/ManifestParser.cpp +++ b/xpcom/components/ManifestParser.cpp @@ -43,6 +43,7 @@ @@ -1616,7 +1529,7 @@ index f3d0055f2cc6..d13543ab5221 100644 using namespace mozilla; -@@ -402,6 +403,7 @@ void ParseManifest(NSLocationType aType, FileLocation& aFile, char* aBuf, +@@ -394,6 +395,7 @@ void ParseManifest(NSLocationType aType, FileLocation& aFile, char* aBuf, constexpr auto kOs = u"os"_ns; constexpr auto kOsVersion = u"osversion"_ns; constexpr auto kABI = u"abi"_ns; @@ -1624,7 +1537,7 @@ index f3d0055f2cc6..d13543ab5221 100644 constexpr auto kProcess = u"process"_ns; #if defined(MOZ_WIDGET_ANDROID) constexpr auto kTablet = u"tablet"_ns; -@@ -461,6 +463,7 @@ void ParseManifest(NSLocationType aType, FileLocation& aFile, char* aBuf, +@@ -453,6 +455,7 @@ void ParseManifest(NSLocationType aType, FileLocation& aFile, char* aBuf, } nsAutoString osVersion; @@ -1632,7 +1545,7 @@ index f3d0055f2cc6..d13543ab5221 100644 #if defined(XP_WIN) # pragma warning(push) # pragma warning(disable : 4996) // VC12+ deprecates GetVersionEx -@@ -469,14 +472,17 @@ void ParseManifest(NSLocationType aType, FileLocation& aFile, char* aBuf, +@@ -461,14 +464,17 @@ void ParseManifest(NSLocationType aType, FileLocation& aFile, char* aBuf, nsTextFormatter::ssprintf(osVersion, u"%ld.%ld", info.dwMajorVersion, info.dwMinorVersion); } @@ -1649,16 +1562,16 @@ index f3d0055f2cc6..d13543ab5221 100644 + desktop = nsKDEUtils::kdeSession() ? u"kde"_ns : u"gnome"_ns; #elif defined(MOZ_WIDGET_ANDROID) bool isTablet = false; - if (mozilla::AndroidBridge::Bridge()) { -@@ -484,6 +490,7 @@ void ParseManifest(NSLocationType aType, FileLocation& aFile, char* aBuf, - "android/os/Build$VERSION", "RELEASE", osVersion); + if (jni::IsAvailable()) { +@@ -476,6 +482,7 @@ void ParseManifest(NSLocationType aType, FileLocation& aFile, char* aBuf, + osVersion.Assign(release->ToString()); isTablet = java::GeckoAppShell::IsTablet(); } + desktop = u"android"_ns; #endif if (XRE_IsContentProcess()) { -@@ -588,6 +595,7 @@ void ParseManifest(NSLocationType aType, FileLocation& aFile, char* aBuf, +@@ -576,6 +583,7 @@ void ParseManifest(NSLocationType aType, FileLocation& aFile, char* aBuf, : eUnspecified; #endif int flags = 0; @@ -1666,7 +1579,7 @@ index f3d0055f2cc6..d13543ab5221 100644 while ((token = nsCRT::strtok(whitespace, kWhitespace, &whitespace)) && ok) { -@@ -597,6 +605,7 @@ void ParseManifest(NSLocationType aType, FileLocation& aFile, char* aBuf, +@@ -585,6 +593,7 @@ void ParseManifest(NSLocationType aType, FileLocation& aFile, char* aBuf, if (CheckStringFlag(kApplication, wtoken, appID, stApp) || CheckOsFlag(kOs, wtoken, osTarget, stOs) || CheckStringFlag(kABI, wtoken, abi, stABI) || @@ -1674,17 +1587,16 @@ index f3d0055f2cc6..d13543ab5221 100644 CheckStringFlag(kProcess, wtoken, process, stProcess) || CheckVersionFlag(kOsVersion, wtoken, osVersion, stOsVersion) || CheckVersionFlag(kAppVersion, wtoken, appVersion, stAppVersion) || -@@ -655,7 +664,7 @@ void ParseManifest(NSLocationType aType, FileLocation& aFile, char* aBuf, - } +@@ -644,6 +653,7 @@ void ParseManifest(NSLocationType aType, FileLocation& aFile, char* aBuf, if (!ok || stApp == eBad || stAppVersion == eBad || -- stGeckoVersion == eBad || stOs == eBad || stOsVersion == eBad || -+ stGeckoVersion == eBad || stOs == eBad || stOsVersion == eBad || stDesktop == eBad || + stGeckoVersion == eBad || stOs == eBad || stOsVersion == eBad || ++ stDesktop == eBad || #ifdef MOZ_WIDGET_ANDROID stTablet == eBad || #endif diff --git a/xpcom/components/moz.build b/xpcom/components/moz.build -index 6cf78aa9bec5..dfcf9c7697af 100644 +index 95ee64e985ac..9af8f80497b7 100644 --- a/xpcom/components/moz.build +++ b/xpcom/components/moz.build @@ -71,6 +71,7 @@ LOCAL_INCLUDES += [ @@ -1696,10 +1608,10 @@ index 6cf78aa9bec5..dfcf9c7697af 100644 if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk": diff --git a/xpcom/io/nsLocalFileUnix.cpp b/xpcom/io/nsLocalFileUnix.cpp -index 410fcc19e435..d7c976e0e4b2 100644 +index 08c77360de6f..eedd5bcf86bd 100644 --- a/xpcom/io/nsLocalFileUnix.cpp +++ b/xpcom/io/nsLocalFileUnix.cpp -@@ -59,6 +59,7 @@ +@@ -51,6 +51,7 @@ #ifdef MOZ_WIDGET_GTK # include "nsIGIOService.h" @@ -1707,7 +1619,7 @@ index 410fcc19e435..d7c976e0e4b2 100644 #endif #ifdef MOZ_WIDGET_COCOA -@@ -2102,10 +2103,19 @@ nsLocalFile::Reveal() { +@@ -2172,10 +2173,18 @@ nsLocalFile::Reveal() { } #ifdef MOZ_WIDGET_GTK @@ -1716,33 +1628,29 @@ index 410fcc19e435..d7c976e0e4b2 100644 - if (!giovfs) { - return NS_ERROR_FAILURE; + url = mPath; -+ if(nsKDEUtils::kdeSupport()) { ++ if (nsKDEUtils::kdeSupport()) { + nsTArray command; -+ command.AppendElement( "REVEAL"_ns ); -+ command.AppendElement( mPath ); -+ return nsKDEUtils::command( command ) ? NS_OK : NS_ERROR_FAILURE; ++ command.AppendElement("REVEAL"_ns); ++ command.AppendElement(mPath); ++ return nsKDEUtils::command(command) ? NS_OK : NS_ERROR_FAILURE; } + -+ if (!giovfs) -+ return NS_ERROR_FAILURE; ++ if (!giovfs) return NS_ERROR_FAILURE; + return giovfs->RevealFile(this); #elif defined(MOZ_WIDGET_COCOA) CFURLRef url; -@@ -2127,6 +2137,13 @@ nsLocalFile::Launch() { +@@ -2197,6 +2206,13 @@ nsLocalFile::Launch() { } #ifdef MOZ_WIDGET_GTK -+ if( nsKDEUtils::kdeSupport()) { ++ if (nsKDEUtils::kdeSupport()) { + nsTArray command; -+ command.AppendElement( "OPEN"_ns ); -+ command.AppendElement( mPath ); -+ return nsKDEUtils::command( command ) ? NS_OK : NS_ERROR_FAILURE; ++ command.AppendElement("OPEN"_ns); ++ command.AppendElement(mPath); ++ return nsKDEUtils::command(command) ? NS_OK : NS_ERROR_FAILURE; + } + nsCOMPtr giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID); if (!giovfs) { return NS_ERROR_FAILURE; --- -2.37.3 - diff --git a/waterfox-g/debian/patches/global_menu.patch b/waterfox-g/debian/patches/global_menu.patch deleted file mode 100644 index 9f9d487..0000000 --- a/waterfox-g/debian/patches/global_menu.patch +++ /dev/null @@ -1,5382 +0,0 @@ -Source: https://bazaar.launchpad.net/~mozillateam/firefox/firefox.bionic/view/1484/debian/patches/unity-menubar.patch -Author: Olivier Tilloy - -diff --git a/browser/base/content/browser-menubar.inc b/browser/base/content/browser-menubar.inc -index 1adea92456c7..9cb91a4befdf 100644 ---- a/browser/base/content/browser-menubar.inc -+++ b/browser/base/content/browser-menubar.inc -@@ -7,7 +7,12 @@ - # On macOS, we don't track whether activation of the native menubar happened - # with the keyboard. - #ifndef XP_MACOSX -- onpopupshowing="if (event.target.parentNode.parentNode == this) -+ onpopupshowing="if (event.target.parentNode.parentNode == this && -+#ifdef MOZ_WIDGET_GTK -+ document.documentElement.getAttribute('shellshowingmenubar') != 'true') -+#else -+ true) -+#endif - this.setAttribute('openedwithkey', - event.target.parentNode.openedWithKey);" - #endif -diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js -index d72ed6495612..eb4af8d23195 100644 ---- a/browser/base/content/browser.js -+++ b/browser/base/content/browser.js -@@ -6481,11 +6481,18 @@ function onViewToolbarsPopupShowing(aEvent, aInsertPoint) { - MozXULElement.insertFTLIfNeeded("browser/toolbarContextMenu.ftl"); - let firstMenuItem = aInsertPoint || popup.firstElementChild; - let toolbarNodes = gNavToolbox.querySelectorAll("toolbar"); -+ -+ let shellShowingMenubar = document.documentElement.getAttribute("shellshowingmenubar") == "true"; -+ - for (let toolbar of toolbarNodes) { - if (!toolbar.hasAttribute("toolbarname")) { - continue; - } - -+ if (shellShowingMenubar && toolbar.id == "toolbar-menubar") { -+ continue; -+ } -+ - if (toolbar.id == "PersonalToolbar") { - let menu = BookmarkingUI.buildBookmarksToolbarSubmenu(toolbar); - popup.insertBefore(menu, firstMenuItem); -diff --git a/browser/components/places/content/places.xhtml b/browser/components/places/content/places.xhtml -index e19a58029828..4271915765c6 100644 ---- a/browser/components/places/content/places.xhtml -+++ b/browser/components/places/content/places.xhtml -@@ -166,6 +166,7 @@ - #else - - - -diff --git a/dom/xul/XULPopupElement.cpp b/dom/xul/XULPopupElement.cpp -index b6a50a3e2408..cda8315b0ef8 100644 ---- a/dom/xul/XULPopupElement.cpp -+++ b/dom/xul/XULPopupElement.cpp -@@ -207,6 +207,10 @@ void XULPopupElement::GetState(nsString& aState) { - // set this here in case there's no frame for the popup - aState.AssignLiteral("closed"); - -+#ifdef MOZ_WIDGET_GTK -+ nsAutoString nativeState; -+#endif -+ - if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) { - switch (pm->GetPopupState(this)) { - case ePopupShown: -@@ -229,6 +233,11 @@ void XULPopupElement::GetState(nsString& aState) { - break; - } - } -+#ifdef MOZ_WIDGET_GTK -+ else if (GetAttr(kNameSpaceID_None, nsGkAtoms::_moz_nativemenupopupstate, nativeState)) { -+ aState = nativeState; -+ } -+#endif - } - - nsINode* XULPopupElement::GetTriggerNode() const { -diff --git a/dom/xul/moz.build b/dom/xul/moz.build -index bb7e4b08d924..42d4bd9b654f 100644 ---- a/dom/xul/moz.build -+++ b/dom/xul/moz.build -@@ -82,4 +82,9 @@ LOCAL_INCLUDES += [ - - include("/ipc/chromium/chromium-config.mozbuild") - -+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk": -+ LOCAL_INCLUDES += [ -+ "/widget/gtk", -+ ] -+ - FINAL_LIBRARY = "xul" -diff --git a/layout/build/moz.build b/layout/build/moz.build -index c7869a01a373..2cbdb021b3b7 100644 ---- a/layout/build/moz.build -+++ b/layout/build/moz.build -@@ -70,6 +70,10 @@ elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "android": - "/dom/system", - "/dom/system/android", - ] -+elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk": -+ LOCAL_INCLUDES += [ -+ "/widget/gtk", -+ ] - - XPCOM_MANIFESTS += [ - "components.conf", -diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js -index 30641d9727cf..c8382f50ab28 100644 ---- a/modules/libpref/init/all.js -+++ b/modules/libpref/init/all.js -@@ -188,6 +188,9 @@ pref("dom.mouseevent.click.hack.use_legacy_non-primary_dispatch", ""); - // Fastback caching - if this pref is negative, then we calculate the number - // of content viewers to cache based on the amount of available memory. - pref("browser.sessionhistory.max_total_viewers", -1); -+#ifdef MOZ_WIDGET_GTK -+pref("ui.use_unity_menubar", true); -+#endif - - pref("browser.display.force_inline_alttext", false); // true = force ALT text for missing images to be layed out inline - // 0 = no external leading, -diff --git a/toolkit/content/xul.css b/toolkit/content/xul.css -index 70bfc2047c05..6b7938a6a7a5 100644 ---- a/toolkit/content/xul.css -+++ b/toolkit/content/xul.css -@@ -226,6 +226,13 @@ toolbox { - } - } - -+@media (-moz-platform: linux) { -+*|*:root[shellshowingmenubar="true"] -+toolbar[type="menubar"]:not([customizing="true"]) { -+ display: none !important; -+} -+} -+ - toolbarspring { - -moz-box-flex: 1000; - } -diff --git a/widget/gtk/NativeMenuSupport.cpp b/widget/gtk/NativeMenuSupport.cpp -index 4360867fff3f..c3a69f31b1d3 100644 ---- a/widget/gtk/NativeMenuSupport.cpp -+++ b/widget/gtk/NativeMenuSupport.cpp -@@ -7,6 +7,8 @@ - - #include "MainThreadUtils.h" - #include "NativeMenuGtk.h" -+#include "nsINativeMenuService.h" -+#include "nsServiceManagerUtils.h" - - namespace mozilla::widget { - -@@ -14,7 +16,14 @@ void NativeMenuSupport::CreateNativeMenuBar(nsIWidget* aParent, - dom::Element* aMenuBarElement) { - MOZ_RELEASE_ASSERT(NS_IsMainThread(), - "Attempting to create native menu bar on wrong thread!"); -- // TODO -+ -+ nsCOMPtr nms = -+ do_GetService("@mozilla.org/widget/nativemenuservice;1"); -+ if (!nms) { -+ return; -+ } -+ -+ nms->CreateNativeMenuBar(aParent, aMenuBarElement); - } - - already_AddRefed NativeMenuSupport::CreateNativeContextMenu( -diff --git a/widget/gtk/NativeMenuSupport.h b/widget/gtk/NativeMenuSupport.h -new file mode 100644 -index 000000000000..0843d45185e5 ---- /dev/null -+++ b/widget/gtk/NativeMenuSupport.h -@@ -0,0 +1,31 @@ -+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -+/* 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 mozilla_widget_NativeMenuSupport_h -+#define mozilla_widget_NativeMenuSupport_h -+ -+class nsIWidget; -+ -+namespace mozilla { -+ -+namespace dom { -+class Element; -+} -+ -+namespace widget { -+ -+class NativeMenuSupport final { -+public: -+ // Given a top-level window widget and a menu bar DOM node, sets up native -+ // menus. Once created, native menus are controlled via the DOM, including -+ // destruction. -+ static void CreateNativeMenuBar(nsIWidget* aParent, -+ dom::Element* aMenuBarElement); -+}; -+ -+} // namespace widget -+} // namespace mozilla -+ -+#endif // mozilla_widget_NativeMenuSupport_h -diff --git a/widget/gtk/components.conf b/widget/gtk/components.conf -index 61714d8b8190..1738093137f2 100644 ---- a/widget/gtk/components.conf -+++ b/widget/gtk/components.conf -@@ -117,6 +117,14 @@ Classes = [ - 'headers': ['/widget/gtk/nsUserIdleServiceGTK.h'], - 'constructor': 'nsUserIdleServiceGTK::GetInstance', - }, -+ { -+ 'cid': '{0b3fe5aa-bc72-4303-85ae-76365df1251d}', -+ 'contract_ids': ['@mozilla.org/widget/nativemenuservice;1'], -+ 'singleton': True, -+ 'type': 'nsNativeMenuService', -+ 'constructor': 'nsNativeMenuService::GetInstanceForServiceManager', -+ 'headers': ['/widget/gtk/nsNativeMenuService.h'], -+ }, - ] - - if defined('NS_PRINTING'): -diff --git a/widget/gtk/moz.build b/widget/gtk/moz.build -index 5218c7df06aa..bf64f7ebdc65 100644 ---- a/widget/gtk/moz.build -+++ b/widget/gtk/moz.build -@@ -77,6 +77,15 @@ UNIFIED_SOURCES += [ - - SOURCES += [ - "MediaKeysEventSourceFactory.cpp", -+ "nsDbusmenu.cpp", -+ "nsMenu.cpp", # conflicts with X11 headers -+ "nsMenuBar.cpp", -+ "nsMenuContainer.cpp", -+ "nsMenuItem.cpp", -+ "nsMenuObject.cpp", -+ "nsMenuSeparator.cpp", -+ "nsNativeMenuDocListener.cpp", -+ "nsNativeMenuService.cpp", - "nsNativeThemeGTK.cpp", # conflicts with X11 headers - "nsWindow.cpp", # conflicts with X11 headers - "WaylandVsyncSource.cpp", # conflicts with X11 headers -@@ -156,6 +165,7 @@ LOCAL_INCLUDES += [ - "/layout/base", - "/layout/forms", - "/layout/generic", -+ "/layout/style", - "/layout/xul", - "/other-licenses/atk-1.0", - "/third_party/cups/include", -diff --git a/widget/gtk/nsDbusmenu.cpp b/widget/gtk/nsDbusmenu.cpp -new file mode 100644 -index 000000000000..f3a1c4400bf2 ---- /dev/null -+++ b/widget/gtk/nsDbusmenu.cpp -@@ -0,0 +1,61 @@ -+/* -*- 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") -+#ifdef MOZ_WIDGET_GTK -+ LOAD_LIBRARY(DbusmenuGtk, "libdbusmenu-gtk3.so.4") -+#endif -+#undef LOAD_LIBRARY -+ -+ return NS_OK; -+} -diff --git a/widget/gtk/nsDbusmenu.h b/widget/gtk/nsDbusmenu.h -new file mode 100644 -index 000000000000..8d46a0d27bdb ---- /dev/null -+++ b/widget/gtk/nsDbusmenu.h -@@ -0,0 +1,101 @@ -+/* -*- 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 -+#include -+ -+#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: -+ nsDbusmenuFunctions() = delete; -+ -+ 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__ */ -diff --git a/widget/gtk/nsMenu.cpp b/widget/gtk/nsMenu.cpp -new file mode 100644 -index 000000000000..255eb390e577 ---- /dev/null -+++ b/widget/gtk/nsMenu.cpp -@@ -0,0 +1,795 @@ -+/* -*- 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/dom/Document.h" -+#include "mozilla/dom/Element.h" -+#include "mozilla/Assertions.h" -+#include "mozilla/ComputedStyleInlines.h" -+#include "mozilla/EventDispatcher.h" -+#include "mozilla/MouseEvents.h" -+#include "mozilla/PresShell.h" -+#include "mozilla/PresShellInlines.h" -+#include "nsComponentManagerUtils.h" -+#include "nsContentUtils.h" -+#include "nsCSSValue.h" -+#include "nsGkAtoms.h" -+#include "nsGtkUtils.h" -+#include "nsAtom.h" -+#include "nsIContent.h" -+#include "nsIRunnable.h" -+#include "nsITimer.h" -+#include "nsString.h" -+#include "nsStyleStruct.h" -+#include "nsThreadUtils.h" -+ -+#include "nsNativeMenuDocListener.h" -+ -+#include -+ -+#include "nsMenu.h" -+ -+using namespace mozilla; -+ -+class nsMenuContentInsertedEvent : public Runnable -+{ -+public: -+ nsMenuContentInsertedEvent(nsMenu *aMenu, -+ nsIContent *aContainer, -+ nsIContent *aChild, -+ nsIContent *aPrevSibling) : -+ Runnable("nsMenuContentInsertedEvent"), -+ mWeakMenu(aMenu), -+ mContainer(aContainer), -+ mChild(aChild), -+ mPrevSibling(aPrevSibling) { } -+ -+ NS_IMETHODIMP Run() -+ { -+ if (!mWeakMenu) { -+ return NS_OK; -+ } -+ -+ static_cast(mWeakMenu.get())->HandleContentInserted(mContainer, -+ mChild, -+ mPrevSibling); -+ return NS_OK; -+ } -+ -+private: -+ nsWeakMenuObject mWeakMenu; -+ -+ nsCOMPtr mContainer; -+ nsCOMPtr mChild; -+ nsCOMPtr mPrevSibling; -+}; -+ -+class nsMenuContentRemovedEvent : public Runnable -+{ -+public: -+ nsMenuContentRemovedEvent(nsMenu *aMenu, -+ nsIContent *aContainer, -+ nsIContent *aChild) : -+ Runnable("nsMenuContentRemovedEvent"), -+ mWeakMenu(aMenu), -+ mContainer(aContainer), -+ mChild(aChild) { } -+ -+ NS_IMETHODIMP Run() -+ { -+ if (!mWeakMenu) { -+ return NS_OK; -+ } -+ -+ static_cast(mWeakMenu.get())->HandleContentRemoved(mContainer, -+ mChild); -+ return NS_OK; -+ } -+ -+private: -+ nsWeakMenuObject mWeakMenu; -+ -+ nsCOMPtr mContainer; -+ nsCOMPtr mChild; -+}; -+ -+static void -+DispatchMouseEvent(nsIContent *aTarget, mozilla::EventMessage aMsg) -+{ -+ if (!aTarget) { -+ return; -+ } -+ -+ WidgetMouseEvent event(true, aMsg, nullptr, WidgetMouseEvent::eReal); -+ EventDispatcher::Dispatch(aTarget, nullptr, &event); -+} -+ -+void -+nsMenu::SetPopupState(EPopupState aState) -+{ -+ mPopupState = aState; -+ -+ if (!mPopupContent) { -+ return; -+ } -+ -+ nsAutoString state; -+ switch (aState) { -+ case ePopupState_Showing: -+ state.Assign(u"showing"_ns); -+ break; -+ case ePopupState_Open: -+ state.Assign(u"open"_ns); -+ break; -+ case ePopupState_Hiding: -+ state.Assign(u"hiding"_ns); -+ break; -+ default: -+ break; -+ } -+ -+ if (state.IsEmpty()) { -+ mPopupContent->AsElement()->UnsetAttr( -+ kNameSpaceID_None, nsGkAtoms::_moz_nativemenupopupstate, -+ false); -+ } else { -+ mPopupContent->AsElement()->SetAttr( -+ kNameSpaceID_None, nsGkAtoms::_moz_nativemenupopupstate, -+ state, false); -+ } -+} -+ -+/* static */ void -+nsMenu::DoOpenCallback(nsITimer *aTimer, void *aClosure) -+{ -+ nsMenu* self = static_cast(aClosure); -+ -+ dbusmenu_menuitem_show_to_user(self->GetNativeData(), 0); -+ -+ self->mOpenDelayTimer = nullptr; -+} -+ -+/* static */ void -+nsMenu::menu_event_cb(DbusmenuMenuitem *menu, -+ const gchar *name, -+ GVariant *value, -+ guint timestamp, -+ gpointer user_data) -+{ -+ nsMenu *self = static_cast(user_data); -+ -+ nsAutoCString event(name); -+ -+ if (event.Equals("closed"_ns)) { -+ self->OnClose(); -+ return; -+ } -+ -+ if (event.Equals("opened"_ns)) { -+ self->OnOpen(); -+ return; -+ } -+} -+ -+void -+nsMenu::MaybeAddPlaceholderItem() -+{ -+ MOZ_ASSERT(!IsInBatchedUpdate(), -+ "Shouldn't be modifying the native menu structure now"); -+ -+ GList *children = dbusmenu_menuitem_get_children(GetNativeData()); -+ if (!children) { -+ MOZ_ASSERT(!mPlaceholderItem); -+ -+ mPlaceholderItem = dbusmenu_menuitem_new(); -+ if (!mPlaceholderItem) { -+ return; -+ } -+ -+ dbusmenu_menuitem_property_set_bool(mPlaceholderItem, -+ DBUSMENU_MENUITEM_PROP_VISIBLE, -+ false); -+ -+ MOZ_ALWAYS_TRUE( -+ dbusmenu_menuitem_child_append(GetNativeData(), mPlaceholderItem)); -+ } -+} -+ -+void -+nsMenu::EnsureNoPlaceholderItem() -+{ -+ MOZ_ASSERT(!IsInBatchedUpdate(), -+ "Shouldn't be modifying the native menu structure now"); -+ -+ if (!mPlaceholderItem) { -+ return; -+ } -+ -+ MOZ_ALWAYS_TRUE( -+ dbusmenu_menuitem_child_delete(GetNativeData(), mPlaceholderItem)); -+ MOZ_ASSERT(!dbusmenu_menuitem_get_children(GetNativeData())); -+ -+ g_object_unref(mPlaceholderItem); -+ mPlaceholderItem = nullptr; -+} -+ -+void -+nsMenu::OnOpen() -+{ -+ if (mNeedsRebuild) { -+ Build(); -+ } -+ -+ nsWeakMenuObject self(this); -+ nsCOMPtr origPopupContent(mPopupContent); -+ { -+ nsNativeMenuDocListener::BlockUpdatesScope updatesBlocker; -+ -+ SetPopupState(ePopupState_Showing); -+ DispatchMouseEvent(mPopupContent, eXULPopupShowing); -+ -+ ContentNode()->AsElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::open, -+ u"true"_ns, true); -+ } -+ -+ if (!self) { -+ // We were deleted! -+ return; -+ } -+ -+ // I guess that the popup could have changed -+ if (origPopupContent != mPopupContent) { -+ return; -+ } -+ -+ nsNativeMenuDocListener::BlockUpdatesScope updatesBlocker; -+ -+ size_t count = ChildCount(); -+ for (size_t i = 0; i < count; ++i) { -+ ChildAt(i)->ContainerIsOpening(); -+ } -+ -+ SetPopupState(ePopupState_Open); -+ DispatchMouseEvent(mPopupContent, eXULPopupShown); -+} -+ -+void -+nsMenu::Build() -+{ -+ mNeedsRebuild = 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_Deprecated(i); -+ -+ UniquePtr child = CreateChild(childContent); -+ -+ if (!child) { -+ continue; -+ } -+ -+ AppendChild(std::move(child)); -+ } -+} -+ -+void -+nsMenu::InitializePopup() -+{ -+ nsCOMPtr oldPopupContent; -+ oldPopupContent.swap(mPopupContent); -+ -+ for (uint32_t i = 0; i < ContentNode()->GetChildCount(); ++i) { -+ nsIContent *child = ContentNode()->GetChildAt_Deprecated(i); -+ -+ if (child->NodeInfo()->NameAtom() == nsGkAtoms::menupopup) { -+ mPopupContent = child; -+ break; -+ } -+ } -+ -+ if (oldPopupContent == mPopupContent) { -+ return; -+ } -+ -+ // The popup has changed -+ -+ if (oldPopupContent) { -+ DocListener()->UnregisterForContentChanges(oldPopupContent); -+ } -+ -+ SetPopupState(ePopupState_Closed); -+ -+ if (!mPopupContent) { -+ return; -+ } -+ -+ DocListener()->RegisterForContentChanges(mPopupContent, this); -+} -+ -+void -+nsMenu::RemoveChildAt(size_t aIndex) -+{ -+ MOZ_ASSERT(IsInBatchedUpdate() || !mPlaceholderItem, -+ "Shouldn't have a placeholder menuitem"); -+ -+ nsMenuContainer::RemoveChildAt(aIndex, !IsInBatchedUpdate()); -+ StructureMutated(); -+ -+ if (!IsInBatchedUpdate()) { -+ MaybeAddPlaceholderItem(); -+ } -+} -+ -+void -+nsMenu::RemoveChild(nsIContent *aChild) -+{ -+ size_t index = IndexOf(aChild); -+ if (index == NoIndex) { -+ return; -+ } -+ -+ RemoveChildAt(index); -+} -+ -+void -+nsMenu::InsertChildAfter(UniquePtr aChild, -+ nsIContent *aPrevSibling) -+{ -+ if (!IsInBatchedUpdate()) { -+ EnsureNoPlaceholderItem(); -+ } -+ -+ nsMenuContainer::InsertChildAfter(std::move(aChild), aPrevSibling, -+ !IsInBatchedUpdate()); -+ StructureMutated(); -+} -+ -+void -+nsMenu::AppendChild(UniquePtr aChild) -+{ -+ if (!IsInBatchedUpdate()) { -+ EnsureNoPlaceholderItem(); -+ } -+ -+ nsMenuContainer::AppendChild(std::move(aChild), !IsInBatchedUpdate()); -+ StructureMutated(); -+} -+ -+bool -+nsMenu::IsInBatchedUpdate() const -+{ -+ return mBatchedUpdateState != eBatchedUpdateState_Inactive; -+} -+ -+void -+nsMenu::StructureMutated() -+{ -+ if (!IsInBatchedUpdate()) { -+ return; -+ } -+ -+ mBatchedUpdateState = eBatchedUpdateState_DidMutate; -+} -+ -+bool -+nsMenu::CanOpen() const -+{ -+ bool isVisible = dbusmenu_menuitem_property_get_bool(GetNativeData(), -+ DBUSMENU_MENUITEM_PROP_VISIBLE); -+ bool isDisabled = ContentNode()->AsElement()->AttrValueIs(kNameSpaceID_None, -+ nsGkAtoms::disabled, -+ nsGkAtoms::_true, -+ eCaseMatters); -+ -+ return (isVisible && !isDisabled); -+} -+ -+void -+nsMenu::HandleContentInserted(nsIContent *aContainer, -+ nsIContent *aChild, -+ nsIContent *aPrevSibling) -+{ -+ if (aContainer == mPopupContent) { -+ UniquePtr child = CreateChild(aChild); -+ -+ if (child) { -+ InsertChildAfter(std::move(child), aPrevSibling); -+ } -+ } else { -+ Build(); -+ } -+} -+ -+void -+nsMenu::HandleContentRemoved(nsIContent *aContainer, nsIContent *aChild) -+{ -+ if (aContainer == mPopupContent) { -+ RemoveChild(aChild); -+ } else { -+ Build(); -+ } -+} -+ -+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); -+ -+ mNeedsRebuild = true; -+ mNeedsUpdate = true; -+ -+ MaybeAddPlaceholderItem(); -+} -+ -+void -+nsMenu::Update(const ComputedStyle *aComputedStyle) -+{ -+ if (mNeedsUpdate) { -+ mNeedsUpdate = false; -+ -+ UpdateLabel(); -+ UpdateSensitivity(); -+ } -+ -+ UpdateVisibility(aComputedStyle); -+ UpdateIcon(aComputedStyle); -+} -+ -+nsMenuObject::PropertyFlags -+nsMenu::SupportedProperties() const -+{ -+ return static_cast( -+ nsMenuObject::ePropLabel | -+ nsMenuObject::ePropEnabled | -+ nsMenuObject::ePropVisible | -+ nsMenuObject::ePropIconData | -+ nsMenuObject::ePropChildDisplay -+ ); -+} -+ -+void -+nsMenu::OnAttributeChanged(nsIContent *aContent, nsAtom *aAttribute) -+{ -+ MOZ_ASSERT(aContent == ContentNode() || aContent == mPopupContent, -+ "Received an event that wasn't meant for us!"); -+ -+ if (mNeedsUpdate) { -+ return; -+ } -+ -+ if (aContent != ContentNode()) { -+ return; -+ } -+ -+ if (!Parent()->IsBeingDisplayed()) { -+ mNeedsUpdate = true; -+ return; -+ } -+ -+ if (aAttribute == nsGkAtoms::disabled) { -+ UpdateSensitivity(); -+ } else if (aAttribute == nsGkAtoms::label || -+ aAttribute == nsGkAtoms::accesskey || -+ aAttribute == nsGkAtoms::crop) { -+ UpdateLabel(); -+ } else if (aAttribute == nsGkAtoms::hidden || -+ aAttribute == nsGkAtoms::collapsed) { -+ RefPtr style = GetComputedStyle(); -+ UpdateVisibility(style); -+ } else if (aAttribute == nsGkAtoms::image) { -+ RefPtr style = GetComputedStyle(); -+ UpdateIcon(style); -+ } -+} -+ -+void -+nsMenu::OnContentInserted(nsIContent *aContainer, nsIContent *aChild, -+ nsIContent *aPrevSibling) -+{ -+ MOZ_ASSERT(aContainer == ContentNode() || aContainer == mPopupContent, -+ "Received an event that wasn't meant for us!"); -+ -+ if (mNeedsRebuild) { -+ return; -+ } -+ -+ if (mPopupState == ePopupState_Closed) { -+ mNeedsRebuild = true; -+ return; -+ } -+ -+ nsContentUtils::AddScriptRunner( -+ new nsMenuContentInsertedEvent(this, aContainer, aChild, -+ aPrevSibling)); -+} -+ -+void -+nsMenu::OnContentRemoved(nsIContent *aContainer, nsIContent *aChild) -+{ -+ MOZ_ASSERT(aContainer == ContentNode() || aContainer == mPopupContent, -+ "Received an event that wasn't meant for us!"); -+ -+ if (mNeedsRebuild) { -+ return; -+ } -+ -+ if (mPopupState == ePopupState_Closed) { -+ mNeedsRebuild = true; -+ return; -+ } -+ -+ nsContentUtils::AddScriptRunner( -+ new nsMenuContentRemovedEvent(this, aContainer, aChild)); -+} -+ -+/* -+ * 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::OnBeginUpdates(nsIContent *aContent) -+{ -+ MOZ_ASSERT(aContent == ContentNode() || aContent == mPopupContent, -+ "Received an event that wasn't meant for us!"); -+ MOZ_ASSERT(!IsInBatchedUpdate(), "Already in an update batch!"); -+ -+ if (aContent != mPopupContent) { -+ return; -+ } -+ -+ mBatchedUpdateState = eBatchedUpdateState_Active; -+} -+ -+void -+nsMenu::OnEndUpdates() -+{ -+ if (!IsInBatchedUpdate()) { -+ return; -+ } -+ -+ bool didMutate = mBatchedUpdateState == eBatchedUpdateState_DidMutate; -+ mBatchedUpdateState = eBatchedUpdateState_Inactive; -+ -+ /* Optimize for the case where we only had attribute changes */ -+ if (!didMutate) { -+ return; -+ } -+ -+ EnsureNoPlaceholderItem(); -+ -+ 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(nextNativeChild->data); -+ nextNativeChild = nextNativeChild->next; -+ -+ MOZ_ALWAYS_TRUE(dbusmenu_menuitem_child_delete(GetNativeData(), -+ data)); -+ } -+ -+ 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(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(); -+ MOZ_ALWAYS_TRUE( -+ dbusmenu_menuitem_child_add_position(GetNativeData(), -+ child->GetNativeData(), -+ i)); -+ } -+ } -+ } -+ -+ while (nextNativeChild) { -+ DbusmenuMenuitem *data = -+ static_cast(nextNativeChild->data); -+ nextNativeChild = nextNativeChild->next; -+ -+ MOZ_ALWAYS_TRUE(dbusmenu_menuitem_child_delete(GetNativeData(), data)); -+ } -+ -+ MaybeAddPlaceholderItem(); -+} -+ -+nsMenu::nsMenu(nsMenuContainer *aParent, nsIContent *aContent) : -+ nsMenuContainer(aParent, aContent), -+ mNeedsRebuild(false), -+ mNeedsUpdate(false), -+ mPlaceholderItem(nullptr), -+ mPopupState(ePopupState_Closed), -+ mBatchedUpdateState(eBatchedUpdateState_Inactive) -+{ -+ MOZ_COUNT_CTOR(nsMenu); -+} -+ -+nsMenu::~nsMenu() -+{ -+ if (IsInBatchedUpdate()) { -+ OnEndUpdates(); -+ } -+ -+ // 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); -+} -+ -+nsMenuObject::EType -+nsMenu::Type() const -+{ -+ return eType_Menu; -+} -+ -+bool -+nsMenu::IsBeingDisplayed() const -+{ -+ return mPopupState == ePopupState_Open; -+} -+ -+bool -+nsMenu::NeedsRebuild() const -+{ -+ return mNeedsRebuild; -+} -+ -+void -+nsMenu::OpenMenu() -+{ -+ if (!CanOpen()) { -+ return; -+ } -+ -+ if (mOpenDelayTimer) { -+ 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(); -+ -+ mOpenDelayTimer = NS_NewTimer(); -+ if (!mOpenDelayTimer) { -+ return; -+ } -+ -+ if (NS_FAILED(mOpenDelayTimer->InitWithNamedFuncCallback(DoOpenCallback, -+ this, -+ 100, -+ nsITimer::TYPE_ONE_SHOT, -+ "nsMenu::DoOpenCallback"))) { -+ mOpenDelayTimer = nullptr; -+ } -+} -+ -+void -+nsMenu::OnClose() -+{ -+ if (mPopupState == ePopupState_Closed) { -+ return; -+ } -+ -+ MOZ_ASSERT(nsContentUtils::IsSafeToRunScript()); -+ -+ // We do this to avoid mutating our view of the menu until -+ // after we have finished -+ nsNativeMenuDocListener::BlockUpdatesScope updatesBlocker; -+ -+ 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(ChildAt(i))->OnClose(); -+ } -+ } -+ -+ SetPopupState(ePopupState_Closed); -+ DispatchMouseEvent(mPopupContent, eXULPopupHidden); -+ -+ ContentNode()->AsElement()->UnsetAttr(kNameSpaceID_None, nsGkAtoms::open, -+ true); -+} -+ -diff --git a/widget/gtk/nsMenu.h b/widget/gtk/nsMenu.h -new file mode 100644 -index 000000000000..40244d012290 ---- /dev/null -+++ b/widget/gtk/nsMenu.h -@@ -0,0 +1,123 @@ -+/* -*- 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 "mozilla/UniquePtr.h" -+#include "nsCOMPtr.h" -+ -+#include "nsDbusmenu.h" -+#include "nsMenuContainer.h" -+#include "nsMenuObject.h" -+ -+#include -+ -+class nsAtom; -+class nsIContent; -+class nsITimer; -+ -+#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(nsMenuContainer *aParent, nsIContent *aContent); -+ ~nsMenu(); -+ -+ nsMenuObject::EType Type() const override; -+ -+ bool IsBeingDisplayed() const override; -+ bool NeedsRebuild() const override; -+ -+ // 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(); -+ -+private: -+ friend class nsMenuContentInsertedEvent; -+ friend class nsMenuContentRemovedEvent; -+ -+ enum EPopupState { -+ ePopupState_Closed, -+ ePopupState_Showing, -+ ePopupState_Open, -+ ePopupState_Hiding -+ }; -+ -+ void SetPopupState(EPopupState aState); -+ -+ static void DoOpenCallback(nsITimer *aTimer, void *aClosure); -+ 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(); -+ -+ // Removes a placeholder item if it exists and asserts that this succeeds -+ void EnsureNoPlaceholderItem(); -+ -+ void OnOpen(); -+ void Build(); -+ void InitializePopup(); -+ void RemoveChildAt(size_t aIndex); -+ void RemoveChild(nsIContent *aChild); -+ void InsertChildAfter(mozilla::UniquePtr aChild, -+ nsIContent *aPrevSibling); -+ void AppendChild(mozilla::UniquePtr aChild); -+ bool IsInBatchedUpdate() const; -+ void StructureMutated(); -+ bool CanOpen() const; -+ -+ void HandleContentInserted(nsIContent *aContainer, -+ nsIContent *aChild, -+ nsIContent *aPrevSibling); -+ void HandleContentRemoved(nsIContent *aContainer, -+ nsIContent *aChild); -+ -+ void InitializeNativeData() override; -+ void Update(const mozilla::ComputedStyle *aComputedStyle) override; -+ nsMenuObject::PropertyFlags SupportedProperties() const override; -+ -+ void OnAttributeChanged(nsIContent *aContent, nsAtom *aAttribute) override; -+ void OnContentInserted(nsIContent *aContainer, nsIContent *aChild, -+ nsIContent *aPrevSibling) override; -+ void OnContentRemoved(nsIContent *aContainer, nsIContent *aChild) override; -+ void OnBeginUpdates(nsIContent *aContent) override; -+ void OnEndUpdates() override; -+ -+ bool mNeedsRebuild; -+ bool mNeedsUpdate; -+ -+ DbusmenuMenuitem *mPlaceholderItem; -+ -+ EPopupState mPopupState; -+ -+ enum EBatchedUpdateState { -+ eBatchedUpdateState_Inactive, -+ eBatchedUpdateState_Active, -+ eBatchedUpdateState_DidMutate -+ }; -+ -+ EBatchedUpdateState mBatchedUpdateState; -+ -+ nsCOMPtr mPopupContent; -+ -+ nsCOMPtr mOpenDelayTimer; -+}; -+ -+#endif /* __nsMenu_h__ */ -diff --git a/widget/gtk/nsMenuBar.cpp b/widget/gtk/nsMenuBar.cpp -new file mode 100644 -index 000000000000..0dcae6794329 ---- /dev/null -+++ b/widget/gtk/nsMenuBar.cpp -@@ -0,0 +1,548 @@ -+/* -*- 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/Document.h" -+#include "mozilla/dom/Element.h" -+#include "mozilla/dom/Event.h" -+#include "mozilla/dom/KeyboardEvent.h" -+#include "mozilla/dom/KeyboardEventBinding.h" -+#include "mozilla/Preferences.h" -+#include "nsContentUtils.h" -+#include "nsIDOMEventListener.h" -+#include "nsIRunnable.h" -+#include "nsIWidget.h" -+#include "nsTArray.h" -+#include "nsUnicharUtils.h" -+ -+#include "nsMenu.h" -+#include "nsNativeMenuService.h" -+ -+#include -+#include -+#include -+#include -+ -+#include "nsMenuBar.h" -+ -+using namespace mozilla; -+ -+static bool -+ShouldHandleKeyEvent(dom::KeyboardEvent *aEvent) -+{ -+ return !aEvent->DefaultPrevented() && aEvent->IsTrusted(); -+} -+ -+class nsMenuBarContentInsertedEvent : public Runnable -+{ -+public: -+ nsMenuBarContentInsertedEvent(nsMenuBar *aMenuBar, -+ nsIContent *aChild, -+ nsIContent *aPrevSibling) : -+ Runnable("nsMenuBarContentInsertedEvent"), -+ mWeakMenuBar(aMenuBar), -+ mChild(aChild), -+ mPrevSibling(aPrevSibling) { } -+ -+ NS_IMETHODIMP Run() -+ { -+ if (!mWeakMenuBar) { -+ return NS_OK; -+ } -+ -+ static_cast(mWeakMenuBar.get())->HandleContentInserted(mChild, -+ mPrevSibling); -+ return NS_OK; -+ } -+ -+private: -+ nsWeakMenuObject mWeakMenuBar; -+ -+ nsCOMPtr mChild; -+ nsCOMPtr mPrevSibling; -+}; -+ -+class nsMenuBarContentRemovedEvent : public Runnable -+{ -+public: -+ nsMenuBarContentRemovedEvent(nsMenuBar *aMenuBar, -+ nsIContent *aChild) : -+ Runnable("nsMenuBarContentRemovedEvent"), -+ mWeakMenuBar(aMenuBar), -+ mChild(aChild) { } -+ -+ NS_IMETHODIMP Run() -+ { -+ if (!mWeakMenuBar) { -+ return NS_OK; -+ } -+ -+ static_cast(mWeakMenuBar.get())->HandleContentRemoved(mChild); -+ return NS_OK; -+ } -+ -+private: -+ nsWeakMenuObject mWeakMenuBar; -+ -+ nsCOMPtr mChild; -+}; -+ -+class nsMenuBar::DocEventListener final : public nsIDOMEventListener -+{ -+public: -+ NS_DECL_ISUPPORTS -+ NS_DECL_NSIDOMEVENTLISTENER -+ -+ DocEventListener(nsMenuBar *aOwner) : mOwner(aOwner) { }; -+ -+private: -+ ~DocEventListener() { }; -+ -+ nsMenuBar *mOwner; -+}; -+ -+NS_IMPL_ISUPPORTS(nsMenuBar::DocEventListener, nsIDOMEventListener) -+ -+NS_IMETHODIMP -+nsMenuBar::DocEventListener::HandleEvent(dom::Event *aEvent) -+{ -+ nsAutoString type; -+ aEvent->GetType(type); -+ -+ if (type.Equals(u"focus"_ns)) { -+ mOwner->Focus(); -+ } else if (type.Equals(u"blur"_ns)) { -+ mOwner->Blur(); -+ } -+ -+ RefPtr keyEvent = aEvent->AsKeyboardEvent(); -+ if (!keyEvent) { -+ return NS_OK; -+ } -+ -+ if (type.Equals(u"keypress"_ns)) { -+ return mOwner->Keypress(keyEvent); -+ } else if (type.Equals(u"keydown"_ns)) { -+ return mOwner->KeyDown(keyEvent); -+ } else if (type.Equals(u"keyup"_ns)) { -+ return mOwner->KeyUp(keyEvent); -+ } -+ -+ return NS_OK; -+} -+ -+nsMenuBar::nsMenuBar(nsIContent *aMenuBarNode) : -+ nsMenuContainer(new nsNativeMenuDocListener(aMenuBarNode), aMenuBarNode), -+ mTopLevel(nullptr), -+ mServer(nullptr), -+ mIsActive(false) -+{ -+ MOZ_COUNT_CTOR(nsMenuBar); -+} -+ -+nsresult -+nsMenuBar::Init(nsIWidget *aParent) -+{ -+ MOZ_ASSERT(aParent); -+ -+ GdkWindow *gdkWin = static_cast( -+ 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); -+ -+ nsAutoCString path; -+ path.Append("/com/canonical/menu/"_ns); -+ char xid[10]; -+ sprintf(xid, "%X", static_cast( -+ 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 DocEventListener(this); -+ -+ mDocument = do_QueryInterface(ContentNode()->OwnerDoc()); -+ -+ mAccessKey = Preferences::GetInt("ui.key.menuAccessKey"); -+ if (mAccessKey == dom::KeyboardEvent_Binding::DOM_VK_SHIFT) { -+ mAccessKeyMask = eModifierShift; -+ } else if (mAccessKey == dom::KeyboardEvent_Binding::DOM_VK_CONTROL) { -+ mAccessKeyMask = eModifierCtrl; -+ } else if (mAccessKey == dom::KeyboardEvent_Binding::DOM_VK_ALT) { -+ mAccessKeyMask = eModifierAlt; -+ } else if (mAccessKey == dom::KeyboardEvent_Binding::DOM_VK_META) { -+ mAccessKeyMask = eModifierMeta; -+ } else { -+ mAccessKeyMask = eModifierAlt; -+ } -+ -+ return NS_OK; -+} -+ -+void -+nsMenuBar::Build() -+{ -+ uint32_t count = ContentNode()->GetChildCount(); -+ for (uint32_t i = 0; i < count; ++i) { -+ nsIContent *childContent = ContentNode()->GetChildAt_Deprecated(i); -+ -+ UniquePtr child = CreateChild(childContent); -+ -+ if (!child) { -+ continue; -+ } -+ -+ AppendChild(std::move(child)); -+ } -+} -+ -+void -+nsMenuBar::DisconnectDocumentEventListeners() -+{ -+ mDocument->RemoveEventListener(u"focus"_ns, -+ mEventListener, -+ true); -+ mDocument->RemoveEventListener(u"blur"_ns, -+ mEventListener, -+ true); -+ mDocument->RemoveEventListener(u"keypress"_ns, -+ mEventListener, -+ false); -+ mDocument->RemoveEventListener(u"keydown"_ns, -+ mEventListener, -+ false); -+ mDocument->RemoveEventListener(u"keyup"_ns, -+ mEventListener, -+ false); -+} -+ -+void -+nsMenuBar::SetShellShowingMenuBar(bool aShowing) -+{ -+ ContentNode()->OwnerDoc()->GetRootElement()->SetAttr( -+ kNameSpaceID_None, nsGkAtoms::shellshowingmenubar, -+ aShowing ? u"true"_ns : u"false"_ns, -+ true); -+} -+ -+void -+nsMenuBar::Focus() -+{ -+ ContentNode()->AsElement()->SetAttr(kNameSpaceID_None, -+ nsGkAtoms::openedwithkey, -+ u"false"_ns, 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); -+} -+ -+nsMenuBar::ModifierFlags -+nsMenuBar::GetModifiersFromEvent(dom::KeyboardEvent *aEvent) -+{ -+ ModifierFlags modifiers = static_cast(0); -+ -+ if (aEvent->AltKey()) { -+ modifiers = static_cast(modifiers | eModifierAlt); -+ } -+ -+ if (aEvent->ShiftKey()) { -+ modifiers = static_cast(modifiers | eModifierShift); -+ } -+ -+ if (aEvent->CtrlKey()) { -+ modifiers = static_cast(modifiers | eModifierCtrl); -+ } -+ -+ if (aEvent->MetaKey()) { -+ modifiers = static_cast(modifiers | eModifierMeta); -+ } -+ -+ return modifiers; -+} -+ -+nsresult -+nsMenuBar::Keypress(dom::KeyboardEvent *aEvent) -+{ -+ if (!ShouldHandleKeyEvent(aEvent)) { -+ return NS_OK; -+ } -+ -+ ModifierFlags modifiers = GetModifiersFromEvent(aEvent); -+ if (((modifiers & mAccessKeyMask) == 0) || -+ ((modifiers & ~mAccessKeyMask) != 0)) { -+ return NS_OK; -+ } -+ -+ uint32_t charCode = aEvent->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()->AsElement()->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()->AsElement()->SetAttr(kNameSpaceID_None, -+ nsGkAtoms::openedwithkey, -+ u"true"_ns, true); -+ static_cast(found)->OpenMenu(); -+ -+ aEvent->StopPropagation(); -+ aEvent->PreventDefault(); -+ -+ return NS_OK; -+} -+ -+nsresult -+nsMenuBar::KeyDown(dom::KeyboardEvent *aEvent) -+{ -+ if (!ShouldHandleKeyEvent(aEvent)) { -+ return NS_OK; -+ } -+ -+ uint32_t keyCode = aEvent->KeyCode(); -+ ModifierFlags modifiers = GetModifiersFromEvent(aEvent); -+ if ((keyCode != mAccessKey) || ((modifiers & ~mAccessKeyMask) != 0)) { -+ return NS_OK; -+ } -+ -+ dbusmenu_server_set_status(mServer, DBUSMENU_STATUS_NOTICE); -+ -+ return NS_OK; -+} -+ -+nsresult -+nsMenuBar::KeyUp(dom::KeyboardEvent *aEvent) -+{ -+ if (!ShouldHandleKeyEvent(aEvent)) { -+ return NS_OK; -+ } -+ -+ uint32_t keyCode = aEvent->KeyCode(); -+ if (keyCode == mAccessKey) { -+ dbusmenu_server_set_status(mServer, DBUSMENU_STATUS_NORMAL); -+ } -+ -+ return NS_OK; -+} -+ -+void -+nsMenuBar::HandleContentInserted(nsIContent *aChild, nsIContent *aPrevSibling) -+{ -+ UniquePtr child = CreateChild(aChild); -+ -+ if (!child) { -+ return; -+ } -+ -+ InsertChildAfter(std::move(child), aPrevSibling); -+} -+ -+void -+nsMenuBar::HandleContentRemoved(nsIContent *aChild) -+{ -+ RemoveChild(aChild); -+} -+ -+void -+nsMenuBar::OnContentInserted(nsIContent *aContainer, nsIContent *aChild, -+ nsIContent *aPrevSibling) -+{ -+ MOZ_ASSERT(aContainer == ContentNode(), -+ "Received an event that wasn't meant for us"); -+ -+ nsContentUtils::AddScriptRunner( -+ new nsMenuBarContentInsertedEvent(this, aChild, aPrevSibling)); -+} -+ -+void -+nsMenuBar::OnContentRemoved(nsIContent *aContainer, nsIContent *aChild) -+{ -+ MOZ_ASSERT(aContainer == ContentNode(), -+ "Received an event that wasn't meant for us"); -+ -+ nsContentUtils::AddScriptRunner( -+ new nsMenuBarContentRemovedEvent(this, aChild)); -+} -+ -+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 */ UniquePtr -+nsMenuBar::Create(nsIWidget *aParent, nsIContent *aMenuBarNode) -+{ -+ UniquePtr menubar(new nsMenuBar(aMenuBarNode)); -+ if (NS_FAILED(menubar->Init(aParent))) { -+ return nullptr; -+ } -+ -+ return menubar; -+} -+ -+nsMenuObject::EType -+nsMenuBar::Type() const -+{ -+ return eType_MenuBar; -+} -+ -+bool -+nsMenuBar::IsBeingDisplayed() const -+{ -+ return true; -+} -+ -+uint32_t -+nsMenuBar::WindowId() const -+{ -+ return static_cast(GDK_WINDOW_XID(gtk_widget_get_window(mTopLevel))); -+} -+ -+nsCString -+nsMenuBar::ObjectPath() const -+{ -+ gchar *tmp; -+ g_object_get(mServer, DBUSMENU_SERVER_PROP_DBUS_OBJECT, &tmp, NULL); -+ -+ nsCString result; -+ result.Adopt(tmp); -+ -+ return result; -+} -+ -+void -+nsMenuBar::Activate() -+{ -+ if (mIsActive) { -+ return; -+ } -+ -+ mIsActive = true; -+ -+ mDocument->AddEventListener(u"focus"_ns, -+ mEventListener, -+ true); -+ mDocument->AddEventListener(u"blur"_ns, -+ mEventListener, -+ true); -+ mDocument->AddEventListener(u"keypress"_ns, -+ mEventListener, -+ false); -+ mDocument->AddEventListener(u"keydown"_ns, -+ mEventListener, -+ false); -+ mDocument->AddEventListener(u"keyup"_ns, -+ mEventListener, -+ false); -+ -+ // Clear this. Not sure if we really need to though -+ ContentNode()->AsElement()->SetAttr(kNameSpaceID_None, -+ nsGkAtoms::openedwithkey, -+ u"false"_ns, true); -+ -+ DocListener()->Start(); -+ Build(); -+ SetShellShowingMenuBar(true); -+} -+ -+void -+nsMenuBar::Deactivate() -+{ -+ if (!mIsActive) { -+ return; -+ } -+ -+ mIsActive = false; -+ -+ SetShellShowingMenuBar(false); -+ while (ChildCount() > 0) { -+ RemoveChildAt(0); -+ } -+ DocListener()->Stop(); -+ DisconnectDocumentEventListeners(); -+} -diff --git a/widget/gtk/nsMenuBar.h b/widget/gtk/nsMenuBar.h -new file mode 100644 -index 000000000000..7a04316330c1 ---- /dev/null -+++ b/widget/gtk/nsMenuBar.h -@@ -0,0 +1,111 @@ -+/* -*- 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 "mozilla/UniquePtr.h" -+#include "nsCOMPtr.h" -+#include "nsString.h" -+ -+#include "nsDbusmenu.h" -+#include "nsMenuContainer.h" -+#include "nsMenuObject.h" -+ -+#include -+ -+class nsIContent; -+class nsIWidget; -+class nsMenuBarDocEventListener; -+ -+namespace mozilla { -+namespace dom { -+class Document; -+class KeyboardEvent; -+} -+} -+ -+/* -+ * 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() override; -+ -+ static mozilla::UniquePtr Create(nsIWidget *aParent, -+ nsIContent *aMenuBarNode); -+ -+ nsMenuObject::EType Type() const override; -+ -+ bool IsBeingDisplayed() const override; -+ -+ // Get the native window ID for this menubar -+ uint32_t WindowId() const; -+ -+ // Get the object path for this menubar -+ nsCString ObjectPath() 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(); -+ -+private: -+ class DocEventListener; -+ friend class nsMenuBarContentInsertedEvent; -+ friend class nsMenuBarContentRemovedEvent; -+ -+ enum ModifierFlags { -+ eModifierShift = (1 << 0), -+ eModifierCtrl = (1 << 1), -+ eModifierAlt = (1 << 2), -+ eModifierMeta = (1 << 3) -+ }; -+ -+ nsMenuBar(nsIContent *aMenuBarNode); -+ nsresult Init(nsIWidget *aParent); -+ void Build(); -+ void DisconnectDocumentEventListeners(); -+ void SetShellShowingMenuBar(bool aShowing); -+ void Focus(); -+ void Blur(); -+ ModifierFlags GetModifiersFromEvent(mozilla::dom::KeyboardEvent *aEvent); -+ nsresult Keypress(mozilla::dom::KeyboardEvent *aEvent); -+ nsresult KeyDown(mozilla::dom::KeyboardEvent *aEvent); -+ nsresult KeyUp(mozilla::dom::KeyboardEvent *aEvent); -+ -+ void HandleContentInserted(nsIContent *aChild, -+ nsIContent *aPrevSibling); -+ void HandleContentRemoved(nsIContent *aChild); -+ -+ void OnContentInserted(nsIContent *aContainer, nsIContent *aChild, -+ nsIContent *aPrevSibling) override; -+ void OnContentRemoved(nsIContent *aContainer, nsIContent *aChild) override; -+ -+ GtkWidget *mTopLevel; -+ DbusmenuServer *mServer; -+ nsCOMPtr mDocument; -+ RefPtr mEventListener; -+ -+ uint32_t mAccessKey; -+ ModifierFlags mAccessKeyMask; -+ bool mIsActive; -+}; -+ -+#endif /* __nsMenuBar_h__ */ -diff --git a/widget/gtk/nsMenuContainer.cpp b/widget/gtk/nsMenuContainer.cpp -new file mode 100644 -index 000000000000..f419201f6338 ---- /dev/null -+++ b/widget/gtk/nsMenuContainer.cpp -@@ -0,0 +1,170 @@ -+/* -*- 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 "nsGkAtoms.h" -+#include "nsIContent.h" -+ -+#include "nsDbusmenu.h" -+#include "nsMenu.h" -+#include "nsMenuItem.h" -+#include "nsMenuSeparator.h" -+ -+#include "nsMenuContainer.h" -+ -+using namespace mozilla; -+ -+const nsMenuContainer::ChildTArray::index_type nsMenuContainer::NoIndex = nsMenuContainer::ChildTArray::NoIndex; -+ -+typedef UniquePtr (*nsMenuObjectConstructor)(nsMenuContainer*, -+ nsIContent*); -+ -+template -+static UniquePtr CreateMenuObject(nsMenuContainer *aContainer, -+ nsIContent *aContent) -+{ -+ return UniquePtr(new T(aContainer, aContent)); -+} -+ -+static nsMenuObjectConstructor -+GetMenuObjectConstructor(nsIContent *aContent) -+{ -+ if (aContent->IsXULElement(nsGkAtoms::menuitem)) { -+ return CreateMenuObject; -+ } else if (aContent->IsXULElement(nsGkAtoms::menu)) { -+ return CreateMenuObject; -+ } else if (aContent->IsXULElement(nsGkAtoms::menuseparator)) { -+ return CreateMenuObject; -+ } -+ -+ return nullptr; -+} -+ -+static bool -+ContentIsSupported(nsIContent *aContent) -+{ -+ return GetMenuObjectConstructor(aContent) ? true : false; -+} -+ -+nsMenuContainer::nsMenuContainer(nsMenuContainer *aParent, -+ nsIContent *aContent) : -+ nsMenuObject(aParent, aContent) -+{ -+} -+ -+nsMenuContainer::nsMenuContainer(nsNativeMenuDocListener *aListener, -+ nsIContent *aContent) : -+ nsMenuObject(aListener, aContent) -+{ -+} -+ -+UniquePtr -+nsMenuContainer::CreateChild(nsIContent *aContent) -+{ -+ nsMenuObjectConstructor ctor = GetMenuObjectConstructor(aContent); -+ if (!ctor) { -+ // There are plenty of node types we might stumble across that -+ // aren't supported -+ return nullptr; -+ } -+ -+ UniquePtr res = ctor(this, aContent); -+ 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; -+} -+ -+void -+nsMenuContainer::RemoveChildAt(size_t aIndex, bool aUpdateNative) -+{ -+ MOZ_ASSERT(aIndex < ChildCount()); -+ -+ if (aUpdateNative) { -+ MOZ_ALWAYS_TRUE( -+ dbusmenu_menuitem_child_delete(GetNativeData(), -+ ChildAt(aIndex)->GetNativeData())); -+ } -+ -+ mChildren.RemoveElementAt(aIndex); -+} -+ -+void -+nsMenuContainer::RemoveChild(nsIContent *aChild, bool aUpdateNative) -+{ -+ size_t index = IndexOf(aChild); -+ if (index == NoIndex) { -+ return; -+ } -+ -+ RemoveChildAt(index, aUpdateNative); -+} -+ -+void -+nsMenuContainer::InsertChildAfter(UniquePtr aChild, -+ nsIContent *aPrevSibling, -+ bool aUpdateNative) -+{ -+ size_t index = IndexOf(aPrevSibling); -+ MOZ_ASSERT(!aPrevSibling || index != NoIndex); -+ -+ ++index; -+ -+ if (aUpdateNative) { -+ aChild->CreateNativeData(); -+ MOZ_ALWAYS_TRUE( -+ dbusmenu_menuitem_child_add_position(GetNativeData(), -+ aChild->GetNativeData(), -+ index)); -+ } -+ -+ mChildren.InsertElementAt(index, std::move(aChild)); -+} -+ -+void -+nsMenuContainer::AppendChild(UniquePtr aChild, -+ bool aUpdateNative) -+{ -+ if (aUpdateNative) { -+ aChild->CreateNativeData(); -+ MOZ_ALWAYS_TRUE( -+ dbusmenu_menuitem_child_append(GetNativeData(), -+ aChild->GetNativeData())); -+ } -+ -+ mChildren.AppendElement(std::move(aChild)); -+} -+ -+bool -+nsMenuContainer::NeedsRebuild() const -+{ -+ return false; -+} -+ -+/* static */ nsIContent* -+nsMenuContainer::GetPreviousSupportedSibling(nsIContent *aContent) -+{ -+ do { -+ aContent = aContent->GetPreviousSibling(); -+ } while (aContent && !ContentIsSupported(aContent)); -+ -+ return aContent; -+} -diff --git a/widget/gtk/nsMenuContainer.h b/widget/gtk/nsMenuContainer.h -new file mode 100644 -index 000000000000..b7e8fa8db46f ---- /dev/null -+++ b/widget/gtk/nsMenuContainer.h -@@ -0,0 +1,70 @@ -+/* -*- 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 "mozilla/UniquePtr.h" -+#include "nsTArray.h" -+ -+#include "nsMenuObject.h" -+ -+class nsIContent; -+class nsNativeMenuDocListener; -+ -+// Base class for containers (menus and menubars) -+class nsMenuContainer : public nsMenuObject -+{ -+public: -+ typedef nsTArray > 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(nsMenuContainer *aParent, nsIContent *aContent); -+ nsMenuContainer(nsNativeMenuDocListener *aListener, nsIContent *aContent); -+ -+ // Create a new child element for the specified content node -+ mozilla::UniquePtr CreateChild(nsIContent *aContent); -+ -+ // 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].get(); } -+ -+ void RemoveChildAt(size_t aIndex, bool aUpdateNative = true); -+ -+ // Remove the child that owns the specified content node -+ void RemoveChild(nsIContent *aChild, bool aUpdateNative = true); -+ -+ // Insert a new child after the child that owns the specified content node -+ void InsertChildAfter(mozilla::UniquePtr aChild, -+ nsIContent *aPrevSibling, -+ bool aUpdateNative = true); -+ -+ void AppendChild(mozilla::UniquePtr aChild, -+ bool aUpdateNative = true); -+ -+private: -+ ChildTArray mChildren; -+}; -+ -+#endif /* __nsMenuContainer_h__ */ -diff --git a/widget/gtk/nsMenuItem.cpp b/widget/gtk/nsMenuItem.cpp -new file mode 100644 -index 000000000000..3a9915e609f5 ---- /dev/null -+++ b/widget/gtk/nsMenuItem.cpp -@@ -0,0 +1,766 @@ -+/* -*- 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/Assertions.h" -+#include "mozilla/dom/Document.h" -+#include "mozilla/dom/Element.h" -+#include "mozilla/dom/KeyboardEventBinding.h" -+#include "mozilla/dom/XULCommandEvent.h" -+#include "mozilla/Preferences.h" -+#include "mozilla/TextEvents.h" -+#include "nsContentUtils.h" -+#include "nsCRT.h" -+#include "nsGkAtoms.h" -+#include "nsGlobalWindowInner.h" -+#include "nsGtkUtils.h" -+#include "nsIContent.h" -+#include "nsIRunnable.h" -+#include "nsQueryObject.h" -+#include "nsReadableUtils.h" -+#include "nsString.h" -+#include "nsThreadUtils.h" -+ -+#include "nsMenu.h" -+#include "nsMenuBar.h" -+#include "nsMenuContainer.h" -+#include "nsNativeMenuDocListener.h" -+ -+#include -+#include -+#include -+#include -+#include -+ -+#include "nsMenuItem.h" -+ -+using namespace mozilla; -+ -+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 -+// -+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) { -+ static_cast(mMenuItem.get())->UncheckSiblings(); -+ } -+ return NS_OK; -+ } -+ -+ nsMenuItemUncheckSiblingsRunnable(nsMenuItem *aMenuItem) : -+ Runnable("nsMenuItemUncheckSiblingsRunnable"), -+ mMenuItem(aMenuItem) { }; -+ -+private: -+ nsWeakMenuObject mMenuItem; -+}; -+ -+bool -+nsMenuItem::IsCheckboxOrRadioItem() const -+{ -+ return mType == eMenuItemType_Radio || -+ mType == eMenuItemType_CheckBox; -+} -+ -+/* static */ void -+nsMenuItem::item_activated_cb(DbusmenuMenuitem *menuitem, -+ guint timestamp, -+ gpointer user_data) -+{ -+ nsMenuItem *item = static_cast(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 -+ nsNativeMenuDocListener::BlockUpdatesScope updatesBlocker; -+ -+ if (!ContentNode()->AsElement()->AttrValueIs(kNameSpaceID_None, -+ nsGkAtoms::autocheck, -+ nsGkAtoms::_false, -+ eCaseMatters) && -+ (mType == eMenuItemType_CheckBox || -+ (mType == eMenuItemType_Radio && !mIsChecked))) { -+ ContentNode()->AsElement()->SetAttr(kNameSpaceID_None, -+ nsGkAtoms::checked, -+ mIsChecked ? -+ u"false"_ns -+ : u"true"_ns, -+ true); -+ } -+ -+ dom::Document *doc = ContentNode()->OwnerDoc(); -+ ErrorResult rv; -+ RefPtr event = -+ doc->CreateEvent(u"xulcommandevent"_ns, -+ dom::CallerType::System, rv); -+ if (!rv.Failed()) { -+ RefPtr command = event->AsXULCommandEvent(); -+ if (command) { -+ command->InitCommandEvent(u"command"_ns, true, true, -+ nsGlobalWindowInner::Cast(doc->GetInnerWindow()), -+ 0, false, false, false, false, 0, nullptr, 0, rv); -+ if (!rv.Failed()) { -+ event->SetTrusted(true); -+ ContentNode()->DispatchEvent(*event, rv); -+ if (rv.Failed()) { -+ NS_WARNING("Failed to dispatch event"); -+ rv.SuppressException(); -+ } -+ } else { -+ NS_WARNING("Failed to initialize command event"); -+ rv.SuppressException(); -+ } -+ } -+ } else { -+ NS_WARNING("CreateEvent failed"); -+ rv.SuppressException(); -+ } -+ -+ // 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(ancestor)->OnClose(); -+ ancestor = ancestor->Parent(); -+ } -+} -+ -+void -+nsMenuItem::CopyAttrFromNodeIfExists(nsIContent *aContent, nsAtom *aAttribute) -+{ -+ nsAutoString value; -+ if (aContent->AsElement()->GetAttr(kNameSpaceID_None, aAttribute, value)) { -+ ContentNode()->AsElement()->SetAttr(kNameSpaceID_None, aAttribute, -+ value, true); -+ } -+} -+ -+void -+nsMenuItem::UpdateState() -+{ -+ if (!IsCheckboxOrRadioItem()) { -+ return; -+ } -+ -+ mIsChecked = ContentNode()->AsElement()->AttrValueIs(kNameSpaceID_None, -+ nsGkAtoms::checked, -+ nsGkAtoms::_true, -+ eCaseMatters); -+ dbusmenu_menuitem_property_set_int(GetNativeData(), -+ DBUSMENU_MENUITEM_PROP_TOGGLE_STATE, -+ mIsChecked ? -+ DBUSMENU_MENUITEM_TOGGLE_STATE_CHECKED : -+ DBUSMENU_MENUITEM_TOGGLE_STATE_UNCHECKED); -+} -+ -+void -+nsMenuItem::UpdateTypeAndState() -+{ -+ static mozilla::dom::Element::AttrValuesArray attrs[] = -+ { nsGkAtoms::checkbox, nsGkAtoms::radio, nullptr }; -+ int32_t type = ContentNode()->AsElement()->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); -+ mType = eMenuItemType_CheckBox; -+ } else if (type == 1) { -+ dbusmenu_menuitem_property_set(GetNativeData(), -+ DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE, -+ DBUSMENU_MENUITEM_TOGGLE_RADIO); -+ mType = eMenuItemType_Radio; -+ } -+ -+ UpdateState(); -+ } else { -+ dbusmenu_menuitem_property_remove(GetNativeData(), -+ DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE); -+ dbusmenu_menuitem_property_remove(GetNativeData(), -+ DBUSMENU_MENUITEM_PROP_TOGGLE_STATE); -+ mType = eMenuItemType_Normal; -+ } -+} -+ -+void -+nsMenuItem::UpdateAccel() -+{ -+ dom::Document *doc = ContentNode()->GetUncomposedDoc(); -+ if (doc) { -+ nsCOMPtr oldKeyContent; -+ oldKeyContent.swap(mKeyContent); -+ -+ nsAutoString key; -+ ContentNode()->AsElement()->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->AsElement()->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 == dom::KeyboardEvent_Binding::DOM_VK_META) { -+ modifier |= GDK_META_MASK; -+ } else if (accel == dom::KeyboardEvent_Binding::DOM_VK_ALT) { -+ modifier |= GDK_MOD1_MASK; -+ } else { -+ modifier |= GDK_CONTROL_MASK; -+ } -+ } -+ -+ token = strtok(nullptr, ", \t"); -+ } -+ -+ free(str); -+ } -+ -+ nsAutoString keyStr; -+ mKeyContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::key, -+ keyStr); -+ -+ guint key = 0; -+ if (!keyStr.IsEmpty()) { -+ key = gdk_unicode_to_keyval(*keyStr.BeginReading()); -+ } -+ -+ if (key == 0) { -+ mKeyContent->AsElement()->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(modifier)); -+ } else { -+ dbusmenu_menuitem_property_remove(GetNativeData(), -+ DBUSMENU_MENUITEM_PROP_SHORTCUT); -+ } -+} -+ -+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(tmp); -+} -+ -+void -+nsMenuItem::UncheckSiblings() -+{ -+ if (!ContentNode()->AsElement()->AttrValueIs(kNameSpaceID_None, -+ nsGkAtoms::type, -+ nsGkAtoms::radio, -+ eCaseMatters)) { -+ // If we're not a radio button, we don't care -+ return; -+ } -+ -+ nsAutoString name; -+ ContentNode()->AsElement()->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_Deprecated(i); -+ -+ if (sibling->IsComment()) { -+ continue; -+ } -+ -+ nsAutoString otherName; -+ sibling->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::name, -+ otherName); -+ -+ if (sibling != ContentNode() && otherName == name && -+ sibling->AsElement()->AttrValueIs(kNameSpaceID_None, -+ nsGkAtoms::type, -+ nsGkAtoms::radio, -+ eCaseMatters)) { -+ sibling->AsElement()->UnsetAttr(kNameSpaceID_None, -+ nsGkAtoms::checked, true); -+ } -+ } -+} -+ -+void -+nsMenuItem::InitializeNativeData() -+{ -+ g_signal_connect(G_OBJECT(GetNativeData()), -+ DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED, -+ G_CALLBACK(item_activated_cb), this); -+ mNeedsUpdate = true; -+} -+ -+void -+nsMenuItem::UpdateContentAttributes() -+{ -+ dom::Document *doc = ContentNode()->GetUncomposedDoc(); -+ if (!doc) { -+ return; -+ } -+ -+ nsAutoString command; -+ ContentNode()->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::command, -+ command); -+ if (command.IsEmpty()) { -+ return; -+ } -+ -+ nsCOMPtr commandContent = doc->GetElementById(command); -+ if (!commandContent) { -+ return; -+ } -+ -+ if (commandContent->AsElement()->AttrValueIs(kNameSpaceID_None, -+ nsGkAtoms::disabled, -+ nsGkAtoms::_true, -+ eCaseMatters)) { -+ ContentNode()->AsElement()->SetAttr(kNameSpaceID_None, -+ nsGkAtoms::disabled, -+ u"true"_ns, true); -+ } else { -+ ContentNode()->AsElement()->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(const ComputedStyle *aComputedStyle) -+{ -+ if (mNeedsUpdate) { -+ mNeedsUpdate = false; -+ -+ UpdateTypeAndState(); -+ UpdateAccel(); -+ UpdateLabel(); -+ UpdateSensitivity(); -+ } -+ -+ UpdateVisibility(aComputedStyle); -+ UpdateIcon(aComputedStyle); -+} -+ -+bool -+nsMenuItem::IsCompatibleWithNativeData(DbusmenuMenuitem *aNativeData) const -+{ -+ return nsCRT::strcmp(dbusmenu_menuitem_property_get(aNativeData, -+ DBUSMENU_MENUITEM_PROP_TYPE), -+ "separator") != 0; -+} -+ -+nsMenuObject::PropertyFlags -+nsMenuItem::SupportedProperties() const -+{ -+ return static_cast( -+ nsMenuObject::ePropLabel | -+ nsMenuObject::ePropEnabled | -+ nsMenuObject::ePropVisible | -+ nsMenuObject::ePropIconData | -+ nsMenuObject::ePropShortcut | -+ nsMenuObject::ePropToggleType | -+ nsMenuObject::ePropToggleState -+ ); -+} -+ -+void -+nsMenuItem::OnAttributeChanged(nsIContent *aContent, nsAtom *aAttribute) -+{ -+ MOZ_ASSERT(aContent == ContentNode() || aContent == mKeyContent, -+ "Received an event that wasn't meant for us!"); -+ -+ if (aContent == ContentNode() && aAttribute == nsGkAtoms::checked && -+ aContent->AsElement()->AttrValueIs(kNameSpaceID_None, -+ nsGkAtoms::checked, -+ nsGkAtoms::_true, eCaseMatters)) { -+ nsContentUtils::AddScriptRunner( -+ new nsMenuItemUncheckSiblingsRunnable(this)); -+ } -+ -+ if (mNeedsUpdate) { -+ return; -+ } -+ -+ if (!Parent()->IsBeingDisplayed()) { -+ mNeedsUpdate = true; -+ return; -+ } -+ -+ 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 (aAttribute == nsGkAtoms::hidden || -+ aAttribute == nsGkAtoms::collapsed) { -+ RefPtr style = GetComputedStyle(); -+ UpdateVisibility(style); -+ } else if (aAttribute == nsGkAtoms::image) { -+ RefPtr style = GetComputedStyle(); -+ UpdateIcon(style); -+ } -+ } else if (aContent == mKeyContent && -+ (aAttribute == nsGkAtoms::key || -+ aAttribute == nsGkAtoms::keycode || -+ aAttribute == nsGkAtoms::modifiers)) { -+ UpdateAccel(); -+ } -+} -+ -+nsMenuItem::nsMenuItem(nsMenuContainer *aParent, nsIContent *aContent) : -+ nsMenuObject(aParent, aContent), -+ mType(eMenuItemType_Normal), -+ mIsChecked(false), -+ mNeedsUpdate(false) -+{ -+ 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 eType_MenuItem; -+} -diff --git a/widget/gtk/nsMenuItem.h b/widget/gtk/nsMenuItem.h -new file mode 100644 -index 000000000000..c621b4e223f1 ---- /dev/null -+++ b/widget/gtk/nsMenuItem.h -@@ -0,0 +1,80 @@ -+/* -*- 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 -+ -+class nsAtom; -+class nsIContent; -+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(nsMenuContainer *aParent, nsIContent *aContent); -+ ~nsMenuItem() override; -+ -+ nsMenuObject::EType Type() const override; -+ -+private: -+ friend class nsMenuItemUncheckSiblingsRunnable; -+ -+ enum { -+ eMenuItemFlag_ToggleState = (1 << 0) -+ }; -+ -+ enum EMenuItemType { -+ eMenuItemType_Normal, -+ eMenuItemType_Radio, -+ eMenuItemType_CheckBox -+ }; -+ -+ bool IsCheckboxOrRadioItem() const; -+ -+ static void item_activated_cb(DbusmenuMenuitem *menuitem, -+ guint timestamp, -+ gpointer user_data); -+ void Activate(uint32_t aTimestamp); -+ -+ void CopyAttrFromNodeIfExists(nsIContent *aContent, nsAtom *aAtom); -+ void UpdateState(); -+ void UpdateTypeAndState(); -+ void UpdateAccel(); -+ nsMenuBar* MenuBar(); -+ void UncheckSiblings(); -+ -+ void InitializeNativeData() override; -+ void UpdateContentAttributes() override; -+ void Update(const mozilla::ComputedStyle *aComputedStyle) override; -+ bool IsCompatibleWithNativeData(DbusmenuMenuitem *aNativeData) const override; -+ nsMenuObject::PropertyFlags SupportedProperties() const override; -+ -+ void OnAttributeChanged(nsIContent *aContent, nsAtom *aAttribute) override; -+ -+ EMenuItemType mType; -+ -+ bool mIsChecked; -+ -+ bool mNeedsUpdate; -+ -+ nsCOMPtr mKeyContent; -+}; -+ -+#endif /* __nsMenuItem_h__ */ -diff --git a/widget/gtk/nsMenuObject.cpp b/widget/gtk/nsMenuObject.cpp -new file mode 100644 -index 000000000000..fbf0fbef1368 ---- /dev/null -+++ b/widget/gtk/nsMenuObject.cpp -@@ -0,0 +1,664 @@ -+/* -*- 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/Assertions.h" -+#include "mozilla/dom/Document.h" -+#include "mozilla/dom/Element.h" -+#include "mozilla/Preferences.h" -+#include "mozilla/PresShell.h" -+#include "mozilla/PresShellInlines.h" -+#include "mozilla/GRefPtr.h" -+#include "nsAttrValue.h" -+#include "nsComputedDOMStyle.h" -+#include "nsContentUtils.h" -+#include "nsGkAtoms.h" -+#include "nsIContent.h" -+#include "nsIContentPolicy.h" -+#include "nsILoadGroup.h" -+#include "nsImageToPixbuf.h" -+#include "nsIURI.h" -+#include "nsNetUtil.h" -+#include "nsPresContext.h" -+#include "nsRect.h" -+#include "nsServiceManagerUtils.h" -+#include "nsString.h" -+#include "nsStyleConsts.h" -+#include "nsStyleStruct.h" -+#include "nsUnicharUtils.h" -+ -+#include "nsMenuContainer.h" -+#include "nsNativeMenuDocListener.h" -+ -+#include -+#include -+#include -+ -+#include "nsMenuObject.h" -+ -+// X11's None clashes with StyleDisplay::None -+#include "X11UndefineNone.h" -+ -+#undef None -+ -+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 -+}; -+ -+nsWeakMenuObject* nsWeakMenuObject::sHead; -+PangoLayout* gPangoLayout = nullptr; -+ -+class nsMenuObjectIconLoader final : public imgINotificationObserver -+{ -+public: -+ NS_DECL_ISUPPORTS -+ NS_DECL_IMGINOTIFICATIONOBSERVER -+ -+ nsMenuObjectIconLoader(nsMenuObject *aOwner) : mOwner(aOwner) { }; -+ -+ void LoadIcon(const ComputedStyle *aComputedStyle); -+ void Destroy(); -+ -+private: -+ ~nsMenuObjectIconLoader() { }; -+ -+ nsMenuObject *mOwner; -+ RefPtr mImageRequest; -+ nsCOMPtr mURI; -+ nsIntRect mImageRect; -+}; -+ -+NS_IMPL_ISUPPORTS(nsMenuObjectIconLoader, imgINotificationObserver) -+ -+void -+nsMenuObjectIconLoader::Notify(imgIRequest *aProxy, -+ int32_t aType, const nsIntRect *aRect) -+{ -+ if (!mOwner) { -+ return; -+ } -+ -+ if (aProxy != mImageRequest) { -+ return; -+ } -+ -+ 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; -+ } -+ -+ nsCOMPtr 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; -+ } -+ -+ if (aType == imgINotificationObserver::DECODE_COMPLETE) { -+ mImageRequest->Cancel(NS_BINDING_ABORTED); -+ mImageRequest = nullptr; -+ return; -+ } -+ -+ if (aType != imgINotificationObserver::FRAME_COMPLETE) { -+ return; -+ } -+ -+ nsCOMPtr img; -+ mImageRequest->GetImage(getter_AddRefs(img)); -+ if (!img) { -+ return; -+ } -+ -+ 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; -+ } -+ -+ 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; -+ } -+ -+ RefPtr pixbuf = nsImageToPixbuf::ImageToPixbuf(img); -+ if (pixbuf) { -+ dbusmenu_menuitem_property_set_image(mOwner->GetNativeData(), -+ DBUSMENU_MENUITEM_PROP_ICON_DATA, -+ pixbuf); -+ } -+ -+ return; -+} -+ -+void -+nsMenuObjectIconLoader::LoadIcon(const ComputedStyle *aComputedStyle) -+{ -+ dom::Document *doc = mOwner->ContentNode()->OwnerDoc(); -+ -+ nsCOMPtr uri; -+ nsIntRect imageRect; -+ imgRequestProxy *imageRequest = nullptr; -+ -+ nsAutoString uriString; -+ if (mOwner->ContentNode()->AsElement()->GetAttr(kNameSpaceID_None, -+ nsGkAtoms::image, -+ uriString)) { -+ NS_NewURI(getter_AddRefs(uri), uriString); -+ } else { -+ PresShell *shell = doc->GetPresShell(); -+ if (!shell) { -+ return; -+ } -+ -+ nsPresContext *pc = shell->GetPresContext(); -+ if (!pc || !aComputedStyle) { -+ return; -+ } -+ -+ const nsStyleList *list = aComputedStyle->StyleList(); -+ imageRequest = list->mListStyleImage.GetImageRequest(); -+ if (imageRequest) { -+ imageRequest->GetURI(getter_AddRefs(uri)); -+ auto& rect = list->mImageRegion.AsRect(); -+ imageRect = rect.ToLayoutRect().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, nullptr, getter_AddRefs(mImageRequest)); -+ } else { -+ mImageRect.SetEmpty(); -+ nsCOMPtr loadGroup = doc->GetDocumentLoadGroup(); -+ RefPtr 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, -+ nullptr, 0, loadGroup, this, nullptr, nullptr, -+ nsIRequest::LOAD_NORMAL, nullptr, -+ nsIContentPolicy::TYPE_IMAGE, EmptyString(), -+ false, false, 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]) { -+ nsString ellipsis; -+ Preferences::GetLocalizedString("intl.ellipsis", ellipsis); -+ if (!ellipsis.IsEmpty()) { -+ uint32_t l = ellipsis.Length(); -+ const nsString::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; -+} -+ -+nsMenuObject::nsMenuObject(nsMenuContainer *aParent, nsIContent *aContent) : -+ mContent(aContent), -+ mListener(aParent->DocListener()), -+ mParent(aParent), -+ mNativeData(nullptr) -+{ -+ MOZ_ASSERT(mContent); -+ MOZ_ASSERT(mListener); -+ MOZ_ASSERT(mParent); -+} -+ -+nsMenuObject::nsMenuObject(nsNativeMenuDocListener *aListener, -+ nsIContent *aContent) : -+ mContent(aContent), -+ mListener(aListener), -+ mParent(nullptr), -+ mNativeData(nullptr) -+{ -+ MOZ_ASSERT(mContent); -+ MOZ_ASSERT(mListener); -+} -+ -+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->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::label, label); -+ -+ nsAutoString accesskey; -+ mContent->AsElement()->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 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 -+ nsAutoString truncated; -+ int target = MAX_WIDTH - GetEllipsisWidth(); -+ length = label.Length(); -+ -+ static mozilla::dom::Element::AttrValuesArray strings[] = { -+ nsGkAtoms::left, nsGkAtoms::start, -+ nsGkAtoms::center, nsGkAtoms::right, -+ nsGkAtoms::end, nullptr -+ }; -+ -+ int32_t type = mContent->AsElement()->FindAttrValueIn(kNameSpaceID_None, -+ nsGkAtoms::crop, -+ strings, eCaseMatters); -+ -+ switch (type) { -+ case 0: -+ case 1: -+ // FIXME: Implement left cropping -+ case 2: -+ // FIXME: Implement center cropping -+ 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(const ComputedStyle *aComputedStyle) -+{ -+ bool vis = true; -+ -+ if (aComputedStyle && -+ (aComputedStyle->StyleDisplay()->mDisplay == StyleDisplay::None || -+ aComputedStyle->StyleVisibility()->mVisible == -+ StyleVisibility::Collapse)) { -+ vis = false; -+ } -+ -+ dbusmenu_menuitem_property_set_bool(mNativeData, -+ DBUSMENU_MENUITEM_PROP_VISIBLE, -+ vis); -+} -+ -+void -+nsMenuObject::UpdateSensitivity() -+{ -+ bool disabled = mContent->AsElement()->AttrValueIs(kNameSpaceID_None, -+ nsGkAtoms::disabled, -+ nsGkAtoms::_true, -+ eCaseMatters); -+ -+ dbusmenu_menuitem_property_set_bool(mNativeData, -+ DBUSMENU_MENUITEM_PROP_ENABLED, -+ !disabled); -+ -+} -+ -+void -+nsMenuObject::UpdateIcon(const ComputedStyle *aComputedStyle) -+{ -+ if (ShouldShowIcon()) { -+ if (!mIconLoader) { -+ mIconLoader = new nsMenuObjectIconLoader(this); -+ } -+ -+ mIconLoader->LoadIcon(aComputedStyle); -+ } else { -+ if (mIconLoader) { -+ mIconLoader->Destroy(); -+ mIconLoader = nullptr; -+ } -+ -+ ClearIcon(); -+ } -+} -+ -+already_AddRefed -+nsMenuObject::GetComputedStyle() -+{ -+ RefPtr style = -+ nsComputedDOMStyle::GetComputedStyleNoFlush( -+ mContent->AsElement()); -+ -+ return style.forget(); -+} -+ -+void -+nsMenuObject::InitializeNativeData() -+{ -+} -+ -+nsMenuObject::PropertyFlags -+nsMenuObject::SupportedProperties() const -+{ -+ return static_cast(0); -+} -+ -+bool -+nsMenuObject::IsCompatibleWithNativeData(DbusmenuMenuitem *aNativeData) const -+{ -+ return true; -+} -+ -+void -+nsMenuObject::UpdateContentAttributes() -+{ -+} -+ -+void -+nsMenuObject::Update(const ComputedStyle *aComputedStyle) -+{ -+} -+ -+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->AsElement()->GetClasses(); -+ if (!classes) { -+ return false; -+ } -+ -+ for (uint32_t i = 0; i < classes->GetAtomCount(); ++i) { -+ if (classes->AtomAt(i) == nsGkAtoms::menuitem_with_favicon) { -+ return true; -+ } -+ } -+ -+ return false; -+} -+ -+void -+nsMenuObject::ClearIcon() -+{ -+ dbusmenu_menuitem_property_remove(mNativeData, -+ DBUSMENU_MENUITEM_PROP_ICON_DATA); -+} -+ -+nsMenuObject::~nsMenuObject() -+{ -+ nsWeakMenuObject::NotifyDestroyed(this); -+ -+ if (mIconLoader) { -+ mIconLoader->Destroy(); -+ } -+ -+ if (mListener) { -+ mListener->UnregisterForContentChanges(mContent); -+ } -+ -+ if (mNativeData) { -+ g_object_unref(mNativeData); -+ mNativeData = nullptr; -+ } -+} -+ -+void -+nsMenuObject::CreateNativeData() -+{ -+ MOZ_ASSERT(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) -+{ -+ MOZ_ASSERT(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(1); -+ -+ for (uint32_t i = 0; gPropertyStrings[i]; ++i) { -+ if (!(mask & supported)) { -+ dbusmenu_menuitem_property_remove(mNativeData, gPropertyStrings[i]); -+ } -+ mask = static_cast(mask << 1); -+ } -+ -+ InitializeNativeData(); -+ if (mParent && mParent->IsBeingDisplayed()) { -+ ContainerIsOpening(); -+ } -+ -+ mListener->RegisterForContentChanges(mContent, this); -+ -+ return NS_OK; -+} -+ -+void -+nsMenuObject::ContainerIsOpening() -+{ -+ MOZ_ASSERT(nsContentUtils::IsSafeToRunScript()); -+ -+ UpdateContentAttributes(); -+ -+ RefPtr style = GetComputedStyle(); -+ Update(style); -+} -+ -+/* static */ void -+nsWeakMenuObject::AddWeakReference(nsWeakMenuObject *aWeak) -+{ -+ aWeak->mPrev = sHead; -+ sHead = aWeak; -+} -+ -+/* static */ void -+nsWeakMenuObject::RemoveWeakReference(nsWeakMenuObject *aWeak) -+{ -+ if (aWeak == sHead) { -+ sHead = aWeak->mPrev; -+ return; -+ } -+ -+ nsWeakMenuObject *weak = sHead; -+ while (weak && weak->mPrev != aWeak) { -+ weak = weak->mPrev; -+ } -+ -+ if (weak) { -+ weak->mPrev = aWeak->mPrev; -+ } -+} -+ -+/* static */ void -+nsWeakMenuObject::NotifyDestroyed(nsMenuObject *aMenuObject) -+{ -+ nsWeakMenuObject *weak = sHead; -+ while (weak) { -+ if (weak->mMenuObject == aMenuObject) { -+ weak->mMenuObject = nullptr; -+ } -+ -+ weak = weak->mPrev; -+ } -+} -diff --git a/widget/gtk/nsMenuObject.h b/widget/gtk/nsMenuObject.h -new file mode 100644 -index 000000000000..92b1ffd8f2fa ---- /dev/null -+++ b/widget/gtk/nsMenuObject.h -@@ -0,0 +1,169 @@ -+/* -*- 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 "mozilla/ComputedStyleInlines.h" -+#include "nsCOMPtr.h" -+ -+#include "nsDbusmenu.h" -+#include "nsNativeMenuDocListener.h" -+ -+class nsIContent; -+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 -+ }; -+ -+ 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(nsMenuContainer *aParent, nsIContent *aContent); -+ nsMenuObject(nsNativeMenuDocListener *aListener, nsIContent *aContent); -+ -+ enum PropertyFlags { -+#define DBUSMENU_PROPERTY(e, s, b) eProp##e = (1 << b), -+ DBUSMENU_PROPERTIES -+#undef DBUSMENU_PROPERTY -+ }; -+ -+ void UpdateLabel(); -+ void UpdateVisibility(const mozilla::ComputedStyle *aComputedStyle); -+ void UpdateSensitivity(); -+ void UpdateIcon(const mozilla::ComputedStyle *aComputedStyle); -+ -+ already_AddRefed GetComputedStyle(); -+ -+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(const mozilla::ComputedStyle *aComputedStyle); -+ -+ bool ShouldShowIcon() const; -+ void ClearIcon(); -+ -+ nsCOMPtr 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 mListener; -+ nsMenuContainer *mParent; // [weak] -+ DbusmenuMenuitem *mNativeData; // [strong] -+ RefPtr mIconLoader; -+}; -+ -+// Keep a weak pointer to a menu object -+class nsWeakMenuObject -+{ -+public: -+ nsWeakMenuObject() : mPrev(nullptr), mMenuObject(nullptr) {} -+ -+ nsWeakMenuObject(nsMenuObject *aMenuObject) : -+ mPrev(nullptr), mMenuObject(aMenuObject) -+ { -+ AddWeakReference(this); -+ } -+ -+ ~nsWeakMenuObject() { RemoveWeakReference(this); } -+ -+ nsMenuObject* get() const { return mMenuObject; } -+ -+ nsMenuObject* operator->() const { return mMenuObject; } -+ -+ explicit operator bool() const { return !!mMenuObject; } -+ -+ static void NotifyDestroyed(nsMenuObject *aMenuObject); -+ -+private: -+ static void AddWeakReference(nsWeakMenuObject *aWeak); -+ static void RemoveWeakReference(nsWeakMenuObject *aWeak); -+ -+ nsWeakMenuObject *mPrev; -+ static nsWeakMenuObject *sHead; -+ -+ nsMenuObject *mMenuObject; -+}; -+ -+#endif /* __nsMenuObject_h__ */ -diff --git a/widget/gtk/nsMenuSeparator.cpp b/widget/gtk/nsMenuSeparator.cpp -new file mode 100644 -index 000000000000..3ab135bd8a20 ---- /dev/null -+++ b/widget/gtk/nsMenuSeparator.cpp -@@ -0,0 +1,82 @@ -+/* -*- 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 "nsCRT.h" -+#include "nsGkAtoms.h" -+ -+#include "nsDbusmenu.h" -+ -+#include "nsMenuContainer.h" -+#include "nsMenuSeparator.h" -+ -+using namespace mozilla; -+ -+void -+nsMenuSeparator::InitializeNativeData() -+{ -+ dbusmenu_menuitem_property_set(GetNativeData(), -+ DBUSMENU_MENUITEM_PROP_TYPE, -+ "separator"); -+} -+ -+void -+nsMenuSeparator::Update(const ComputedStyle *aComputedStyle) -+{ -+ UpdateVisibility(aComputedStyle); -+} -+ -+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::ePropVisible | -+ nsMenuObject::ePropType -+ ); -+} -+ -+void -+nsMenuSeparator::OnAttributeChanged(nsIContent *aContent, nsAtom *aAttribute) -+{ -+ MOZ_ASSERT(aContent == ContentNode(), "Received an event that wasn't meant for us!"); -+ -+ if (!Parent()->IsBeingDisplayed()) { -+ return; -+ } -+ -+ if (aAttribute == nsGkAtoms::hidden || -+ aAttribute == nsGkAtoms::collapsed) { -+ RefPtr style = GetComputedStyle(); -+ UpdateVisibility(style); -+ } -+} -+ -+nsMenuSeparator::nsMenuSeparator(nsMenuContainer *aParent, -+ nsIContent *aContent) : -+ nsMenuObject(aParent, aContent) -+{ -+ MOZ_COUNT_CTOR(nsMenuSeparator); -+} -+ -+nsMenuSeparator::~nsMenuSeparator() -+{ -+ MOZ_COUNT_DTOR(nsMenuSeparator); -+} -+ -+nsMenuObject::EType -+nsMenuSeparator::Type() const -+{ -+ return eType_MenuItem; -+} -diff --git a/widget/gtk/nsMenuSeparator.h b/widget/gtk/nsMenuSeparator.h -new file mode 100644 -index 000000000000..dd3f5b974dd3 ---- /dev/null -+++ b/widget/gtk/nsMenuSeparator.h -@@ -0,0 +1,37 @@ -+/* -*- 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 nsAtom; -+class nsMenuContainer; -+ -+// Menu separator class -+class nsMenuSeparator final : public nsMenuObject -+{ -+public: -+ nsMenuSeparator(nsMenuContainer *aParent, nsIContent *aContent); -+ ~nsMenuSeparator(); -+ -+ nsMenuObject::EType Type() const override; -+ -+private: -+ void InitializeNativeData() override; -+ void Update(const mozilla::ComputedStyle *aComputedStyle) override; -+ bool IsCompatibleWithNativeData(DbusmenuMenuitem *aNativeData) const override; -+ nsMenuObject::PropertyFlags SupportedProperties() const override; -+ -+ void OnAttributeChanged(nsIContent *aContent, nsAtom *aAttribute) override; -+}; -+ -+#endif /* __nsMenuSeparator_h__ */ -diff --git a/widget/gtk/nsNativeMenuDocListener.cpp b/widget/gtk/nsNativeMenuDocListener.cpp -new file mode 100644 -index 000000000000..2336ed6e3bc5 ---- /dev/null -+++ b/widget/gtk/nsNativeMenuDocListener.cpp -@@ -0,0 +1,347 @@ -+/* -*- 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/Document.h" -+#include "mozilla/dom/Element.h" -+#include "nsContentUtils.h" -+#include "nsAtom.h" -+#include "nsIContent.h" -+ -+#include "nsMenuContainer.h" -+ -+#include "nsNativeMenuDocListener.h" -+ -+using namespace mozilla; -+ -+uint32_t nsNativeMenuDocListener::sUpdateBlockersCount = 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) : -+ mObserver(nullptr) -+ { -+ 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; -+}; -+ -+NS_IMPL_ISUPPORTS(nsNativeMenuDocListener, nsIMutationObserver) -+ -+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); -+} -+ -+void -+nsNativeMenuDocListener::AttributeChanged(mozilla::dom::Element *aElement, -+ int32_t aNameSpaceID, -+ nsAtom *aAttribute, -+ int32_t aModType, -+ const nsAttrValue* aOldValue) -+{ -+ if (sUpdateBlockersCount == 0) { -+ DoAttributeChanged(aElement, aAttribute); -+ return; -+ } -+ -+ MutationRecord *m = mPendingMutations.AppendElement(MakeUnique())->get(); -+ m->mType = MutationRecord::eAttributeChanged; -+ m->mTarget = aElement; -+ m->mAttribute = aAttribute; -+ -+ ScheduleFlush(this); -+} -+ -+void -+nsNativeMenuDocListener::ContentAppended(nsIContent *aFirstNewContent) -+{ -+ for (nsIContent *c = aFirstNewContent; c; c = c->GetNextSibling()) { -+ ContentInserted(c); -+ } -+} -+ -+void -+nsNativeMenuDocListener::ContentInserted(nsIContent *aChild) -+{ -+ nsIContent* container = aChild->GetParent(); -+ if (!container) { -+ return; -+ } -+ -+ nsIContent *prevSibling = nsMenuContainer::GetPreviousSupportedSibling(aChild); -+ -+ if (sUpdateBlockersCount == 0) { -+ DoContentInserted(container, aChild, prevSibling); -+ return; -+ } -+ -+ MutationRecord *m = mPendingMutations.AppendElement(MakeUnique())->get(); -+ m->mType = MutationRecord::eContentInserted; -+ m->mTarget = container; -+ m->mChild = aChild; -+ m->mPrevSibling = prevSibling; -+ -+ ScheduleFlush(this); -+} -+ -+void -+nsNativeMenuDocListener::ContentRemoved(nsIContent *aChild, -+ nsIContent *aPreviousSibling) -+{ -+ nsIContent* container = aChild->GetParent(); -+ if (!container) { -+ return; -+ } -+ -+ if (sUpdateBlockersCount == 0) { -+ DoContentRemoved(container, aChild); -+ return; -+ } -+ -+ MutationRecord *m = mPendingMutations.AppendElement(MakeUnique())->get(); -+ m->mType = MutationRecord::eContentRemoved; -+ m->mTarget = container; -+ m->mChild = aChild; -+ -+ ScheduleFlush(this); -+} -+ -+void -+nsNativeMenuDocListener::NodeWillBeDestroyed(const nsINode *aNode) -+{ -+ mDocument = nullptr; -+} -+ -+void -+nsNativeMenuDocListener::DoAttributeChanged(nsIContent *aContent, -+ nsAtom *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::DoBeginUpdates(nsIContent *aTarget) -+{ -+ DispatchHelper h(this, aTarget); -+ if (h.HasObserver()) { -+ h.Observer()->OnBeginUpdates(aTarget); -+ } -+} -+ -+void -+nsNativeMenuDocListener::DoEndUpdates(nsIContent *aTarget) -+{ -+ DispatchHelper h(this, aTarget); -+ if (h.HasObserver()) { -+ h.Observer()->OnEndUpdates(); -+ } -+} -+ -+void -+nsNativeMenuDocListener::FlushPendingMutations() -+{ -+ nsIContent *currentTarget = nullptr; -+ bool inUpdateSequence = false; -+ -+ while (mPendingMutations.Length() > 0) { -+ MutationRecord *m = mPendingMutations[0].get(); -+ -+ if (m->mTarget != currentTarget) { -+ if (inUpdateSequence) { -+ DoEndUpdates(currentTarget); -+ inUpdateSequence = false; -+ } -+ -+ currentTarget = m->mTarget; -+ -+ if (mPendingMutations.Length() > 1 && -+ mPendingMutations[1]->mTarget == currentTarget) { -+ DoBeginUpdates(currentTarget); -+ inUpdateSequence = 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: -+ MOZ_ASSERT_UNREACHABLE("Invalid type"); -+ } -+ -+ mPendingMutations.RemoveElementAt(0); -+ } -+ -+ if (inUpdateSequence) { -+ DoEndUpdates(currentTarget); -+ } -+} -+ -+/* static */ void -+nsNativeMenuDocListener::ScheduleFlush(nsNativeMenuDocListener *aListener) -+{ -+ MOZ_ASSERT(sUpdateBlockersCount > 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::RemoveUpdateBlocker() -+{ -+ if (sUpdateBlockersCount == 1 && gPendingListeners) { -+ while (gPendingListeners->Length() > 0) { -+ (*gPendingListeners)[0]->FlushPendingMutations(); -+ gPendingListeners->RemoveElementAt(0); -+ } -+ } -+ -+ MOZ_ASSERT(sUpdateBlockersCount > 0, "Negative update blockers count!"); -+ sUpdateBlockersCount--; -+} -+ -+nsNativeMenuDocListener::nsNativeMenuDocListener(nsIContent *aRootNode) : -+ mRootNode(aRootNode), -+ mDocument(nullptr), -+ mLastSource(nullptr), -+ mLastTarget(nullptr) -+{ -+ MOZ_COUNT_CTOR(nsNativeMenuDocListener); -+} -+ -+void -+nsNativeMenuDocListener::RegisterForContentChanges(nsIContent *aContent, -+ nsNativeMenuChangeObserver *aObserver) -+{ -+ MOZ_ASSERT(aContent, "Need content parameter"); -+ MOZ_ASSERT(aObserver, "Need observer parameter"); -+ if (!aContent || !aObserver) { -+ return; -+ } -+ -+ DebugOnly old; -+ MOZ_ASSERT(!mContentToObserverTable.Get(aContent, &old) || old == aObserver, -+ "Multiple observers for the same content node are not supported"); -+ -+ mContentToObserverTable.InsertOrUpdate(aContent, aObserver); -+} -+ -+void -+nsNativeMenuDocListener::UnregisterForContentChanges(nsIContent *aContent) -+{ -+ MOZ_ASSERT(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(); -+} -diff --git a/widget/gtk/nsNativeMenuDocListener.h b/widget/gtk/nsNativeMenuDocListener.h -new file mode 100644 -index 000000000000..5ee99cba70fd ---- /dev/null -+++ b/widget/gtk/nsNativeMenuDocListener.h -@@ -0,0 +1,152 @@ -+/* -*- 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/RefPtr.h" -+#include "mozilla/UniquePtr.h" -+#include "nsTHashMap.h" -+#include "nsStubMutationObserver.h" -+#include "nsTArray.h" -+ -+class nsAtom; -+class nsIContent; -+class nsNativeMenuChangeObserver; -+ -+namespace mozilla { -+namespace dom { -+class Document; -+} -+} -+ -+/* -+ * 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 -+ -+ nsNativeMenuDocListener(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(); -+ -+ /* -+ * 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 BlockUpdatesScope -+ { -+ public: -+ BlockUpdatesScope() -+ { -+ nsNativeMenuDocListener::AddUpdateBlocker(); -+ } -+ -+ ~BlockUpdatesScope() -+ { -+ nsNativeMenuDocListener::RemoveUpdateBlocker(); -+ } -+ }; -+ -+private: -+ friend class DispatchHelper; -+ -+ struct MutationRecord { -+ enum RecordType { -+ eAttributeChanged, -+ eContentInserted, -+ eContentRemoved -+ } mType; -+ -+ nsCOMPtr mTarget; -+ nsCOMPtr mChild; -+ nsCOMPtr mPrevSibling; -+ RefPtr mAttribute; -+ }; -+ -+ ~nsNativeMenuDocListener(); -+ -+ NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED -+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED -+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED -+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED -+ NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED -+ -+ void DoAttributeChanged(nsIContent *aContent, nsAtom *aAttribute); -+ void DoContentInserted(nsIContent *aContainer, -+ nsIContent *aChild, -+ nsIContent *aPrevSibling); -+ void DoContentRemoved(nsIContent *aContainer, nsIContent *aChild); -+ void DoBeginUpdates(nsIContent *aTarget); -+ void DoEndUpdates(nsIContent *aTarget); -+ -+ void FlushPendingMutations(); -+ static void ScheduleFlush(nsNativeMenuDocListener *aListener); -+ static void CancelFlush(nsNativeMenuDocListener *aListener); -+ -+ static void AddUpdateBlocker() { ++sUpdateBlockersCount; } -+ static void RemoveUpdateBlocker(); -+ -+ nsCOMPtr mRootNode; -+ mozilla::dom::Document *mDocument; -+ nsIContent *mLastSource; -+ nsNativeMenuChangeObserver *mLastTarget; -+ nsTArray > mPendingMutations; -+ nsTHashMap, nsNativeMenuChangeObserver *> mContentToObserverTable; -+ -+ static uint32_t sUpdateBlockersCount; -+}; -+ -+typedef nsTArray > nsNativeMenuDocListenerTArray; -+ -+/* -+ * Implemented by classes that want to listen to mutation events from content -+ * nodes. -+ */ -+class nsNativeMenuChangeObserver -+{ -+public: -+ virtual void OnAttributeChanged(nsIContent *aContent, nsAtom *aAttribute) {} -+ -+ virtual void OnContentInserted(nsIContent *aContainer, -+ nsIContent *aChild, -+ nsIContent *aPrevSibling) {} -+ -+ virtual void OnContentRemoved(nsIContent *aContainer, nsIContent *aChild) {} -+ -+ // Signals the start of a sequence of more than 1 event for the specified -+ // node. This only happens when events are flushed as all BlockUpdatesScope -+ // instances go out of scope -+ virtual void OnBeginUpdates(nsIContent *aContent) {}; -+ -+ // Signals the end of a sequence of events -+ virtual void OnEndUpdates() {}; -+}; -+ -+#endif /* __nsNativeMenuDocListener_h__ */ -diff --git a/widget/gtk/nsNativeMenuService.cpp b/widget/gtk/nsNativeMenuService.cpp -new file mode 100644 -index 000000000000..5cf36a447036 ---- /dev/null -+++ b/widget/gtk/nsNativeMenuService.cpp -@@ -0,0 +1,478 @@ -+/* -*- 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/dom/Element.h" -+#include "mozilla/Assertions.h" -+#include "mozilla/Preferences.h" -+#include "mozilla/UniquePtr.h" -+#include "nsCOMPtr.h" -+#include "nsCRT.h" -+#include "nsGtkUtils.h" -+#include "nsIContent.h" -+#include "nsIWidget.h" -+#include "nsServiceManagerUtils.h" -+#include "nsWindow.h" -+#include "prlink.h" -+ -+#include "nsDbusmenu.h" -+#include "nsMenuBar.h" -+#include "nsNativeMenuDocListener.h" -+ -+#include -+#include -+#include -+ -+#include "nsNativeMenuService.h" -+ -+using namespace mozilla; -+ -+nsNativeMenuService* nsNativeMenuService::sService = nullptr; -+ -+extern PangoLayout* gPangoLayout; -+extern nsNativeMenuDocListenerTArray* gPendingListeners; -+ -+#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) -+ -+nsNativeMenuService::nsNativeMenuService() : -+ mCreateProxyCancellable(nullptr), mDbusProxy(nullptr), mOnline(false) -+{ -+} -+ -+nsNativeMenuService::~nsNativeMenuService() -+{ -+ SetOnline(false); -+ -+ if (mCreateProxyCancellable) { -+ g_cancellable_cancel(mCreateProxyCancellable); -+ g_object_unref(mCreateProxyCancellable); -+ mCreateProxyCancellable = nullptr; -+ } -+ -+ // 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; -+ } -+ -+ MOZ_ASSERT(sService == this); -+ sService = 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"); -+ -+ mCreateProxyCancellable = g_cancellable_new(); -+ -+ g_dbus_proxy_new_for_bus(G_BUS_TYPE_SESSION, -+ static_cast( -+ 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", -+ mCreateProxyCancellable, 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); -+ } -+ -+ return NS_OK; -+} -+ -+/* static */ void -+nsNativeMenuService::EnsureInitialized() -+{ -+ if (sService) { -+ return; -+ } -+ nsCOMPtr service = -+ do_GetService("@mozilla.org/widget/nativemenuservice;1"); -+} -+ -+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()) || -+ mMenuBarRegistrationCancellables.Get(aMenuBar, nullptr)) { -+ // 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(); -+ nsCString path = aMenuBar->ObjectPath(); -+ if (xid == 0 || path.IsEmpty()) { -+ NS_WARNING("Menubar has invalid XID or object path"); -+ return; -+ } -+ -+ GCancellable *cancellable = g_cancellable_new(); -+ mMenuBarRegistrationCancellables.InsertOrUpdate(aMenuBar, cancellable); -+ -+ // We keep a weak ref because we can't assume that GDBus cancellation -+ // is reliable (see https://launchpad.net/bugs/953562) -+ -+ g_dbus_proxy_call(mDbusProxy, "RegisterWindow", -+ g_variant_new("(uo)", xid, path.get()), -+ G_DBUS_CALL_FLAGS_NONE, -1, -+ cancellable, -+ register_native_menubar_cb, aMenuBar); -+} -+ -+/* 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) -+ nsNativeMenuService *self = nsNativeMenuService::GetSingleton(); -+ if (!self) { -+ if (proxy) { -+ g_object_unref(proxy); -+ } -+ return; -+ } -+ -+ self->OnProxyCreated(proxy); -+} -+ -+/* static */ void -+nsNativeMenuService::register_native_menubar_cb(GObject *source_object, -+ GAsyncResult *res, -+ gpointer user_data) -+{ -+ nsMenuBar *menuBar = static_cast(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); -+ } -+ -+ nsNativeMenuService *self = nsNativeMenuService::GetSingleton(); -+ if (!self) { -+ return; -+ } -+ -+ self->OnNativeMenuBarRegistered(menuBar, success); -+} -+ -+/* static */ gboolean -+nsNativeMenuService::map_event_cb(GtkWidget *widget, -+ GdkEvent *event, -+ gpointer user_data) -+{ -+ nsMenuBar *menubar = static_cast(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; -+ -+ g_object_unref(mCreateProxyCancellable); -+ mCreateProxyCancellable = nullptr; -+ -+ 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) -+{ -+ // Don't assume that GDBus cancellation is reliable (ie, |aMenuBar| might -+ // have already been deleted (see https://launchpad.net/bugs/953562) -+ GCancellable *cancellable = nullptr; -+ if (!mMenuBarRegistrationCancellables.Get(aMenuBar, &cancellable)) { -+ return; -+ } -+ -+ g_object_unref(cancellable); -+ mMenuBarRegistrationCancellables.Remove(aMenuBar); -+ -+ 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(); -+} -+ -+NS_IMETHODIMP -+nsNativeMenuService::CreateNativeMenuBar(nsIWidget *aParent, -+ mozilla::dom::Element *aMenuBarNode) -+{ -+ NS_ENSURE_ARG(aParent); -+ NS_ENSURE_ARG(aMenuBarNode); -+ -+ if (aMenuBarNode->AttrValueIs(kNameSpaceID_None, -+ nsGkAtoms::_moz_menubarkeeplocal, -+ nsGkAtoms::_true, -+ eCaseMatters)) { -+ return NS_OK; -+ } -+ -+ UniquePtr 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.get()); -+ -+ mMenuBars.AppendElement(menubar.get()); -+ RegisterNativeMenuBar(menubar.get()); -+ -+ static_cast(aParent)->SetMenuBar(std::move(menubar)); -+ -+ return NS_OK; -+} -+ -+/* static */ already_AddRefed -+nsNativeMenuService::GetInstanceForServiceManager() -+{ -+ RefPtr service(sService); -+ -+ if (service) { -+ return service.forget(); -+ } -+ -+ service = new nsNativeMenuService(); -+ -+ if (NS_FAILED(service->Init())) { -+ return nullptr; -+ } -+ -+ sService = service.get(); -+ return service.forget(); -+} -+ -+/* static */ nsNativeMenuService* -+nsNativeMenuService::GetSingleton() -+{ -+ EnsureInitialized(); -+ return sService; -+} -+ -+void -+nsNativeMenuService::NotifyNativeMenuBarDestroyed(nsMenuBar *aMenuBar) -+{ -+ g_signal_handlers_disconnect_by_func(aMenuBar->TopLevelWindow(), -+ FuncToGpointer(map_event_cb), -+ aMenuBar); -+ -+ mMenuBars.RemoveElement(aMenuBar); -+ -+ GCancellable *cancellable = nullptr; -+ if (mMenuBarRegistrationCancellables.Get(aMenuBar, &cancellable)) { -+ mMenuBarRegistrationCancellables.Remove(aMenuBar); -+ g_cancellable_cancel(cancellable); -+ g_object_unref(cancellable); -+ } -+} -diff --git a/widget/gtk/nsNativeMenuService.h b/widget/gtk/nsNativeMenuService.h -new file mode 100644 -index 000000000000..2e0d429eddfd ---- /dev/null -+++ b/widget/gtk/nsNativeMenuService.h -@@ -0,0 +1,85 @@ -+/* -*- 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 "nsTHashMap.h" -+#include "nsINativeMenuService.h" -+#include "nsTArray.h" -+ -+#include -+#include -+#include -+ -+class nsMenuBar; -+ -+/* -+ * The main native menu service singleton. -+ * NativeMenuSupport::CreateNativeMenuBar 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: -+ NS_DECL_ISUPPORTS -+ -+ NS_IMETHOD CreateNativeMenuBar(nsIWidget* aParent, mozilla::dom::Element* aMenuBarNode) override; -+ -+ // Returns the singleton addref'd for the service manager -+ static already_AddRefed GetInstanceForServiceManager(); -+ -+ // Returns the singleton without increasing the reference count -+ static nsNativeMenuService* GetSingleton(); -+ -+ // Called by a menubar when it is deleted -+ void NotifyNativeMenuBarDestroyed(nsMenuBar *aMenuBar); -+ -+private: -+ nsNativeMenuService(); -+ ~nsNativeMenuService(); -+ nsresult Init(); -+ -+ 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(); -+ -+ GCancellable *mCreateProxyCancellable; -+ GDBusProxy *mDbusProxy; -+ bool mOnline; -+ nsTArray mMenuBars; -+ nsTHashMap, GCancellable*> mMenuBarRegistrationCancellables; -+ -+ static bool sShutdown; -+ static nsNativeMenuService *sService; -+}; -+ -+#endif /* __nsNativeMenuService_h__ */ -diff --git a/widget/gtk/nsWindow.cpp b/widget/gtk/nsWindow.cpp -index 5f227073d8cd..1ab99c38551f 100644 ---- a/widget/gtk/nsWindow.cpp -+++ b/widget/gtk/nsWindow.cpp -@@ -7103,6 +7103,10 @@ void nsWindow::HideWindowChrome(bool aShouldHide) { - SetWindowDecoration(aShouldHide ? eBorderStyle_none : mBorderStyle); - } - -+void nsWindow::SetMenuBar(UniquePtr aMenuBar) { -+ mMenuBar = std::move(aMenuBar); -+} -+ - bool nsWindow::CheckForRollup(gdouble aMouseX, gdouble aMouseY, bool aIsWheel, - bool aAlwaysRollup) { - nsIRollupListener* rollupListener = GetActiveRollupListener(); -diff --git a/widget/gtk/nsWindow.h b/widget/gtk/nsWindow.h -index d4951ec54d1c..90fbd91765b1 100644 ---- a/widget/gtk/nsWindow.h -+++ b/widget/gtk/nsWindow.h -@@ -27,6 +27,8 @@ - #include "nsRefPtrHashtable.h" - #include "IMContextWrapper.h" - -+#include "nsMenuBar.h" -+ - #ifdef ACCESSIBILITY - # include "mozilla/a11y/LocalAccessible.h" - #endif -@@ -203,6 +205,8 @@ class nsWindow final : public nsBaseWidget { - nsresult MakeFullScreen(bool aFullScreen) override; - void HideWindowChrome(bool aShouldHide) override; - -+ void SetMenuBar(mozilla::UniquePtr aMenuBar); -+ - /** - * GetLastUserInputTime returns a timestamp for the most recent user input - * event. This is intended for pointer grab requests (including drags). -@@ -874,6 +878,8 @@ class nsWindow final : public nsBaseWidget { - - static bool sTransparentMainWindow; - -+ mozilla::UniquePtr mMenuBar; -+ - #ifdef ACCESSIBILITY - RefPtr mRootAccessible; - -diff --git a/widget/moz.build b/widget/moz.build -index 9b150c592a8c..77bd0cbbe5c5 100644 ---- a/widget/moz.build -+++ b/widget/moz.build -@@ -161,6 +161,11 @@ EXPORTS += [ - "PuppetWidget.h", - ] - -+if toolkit == "gtk": -+ EXPORTS += [ -+ "nsINativeMenuService.h", -+ ] -+ - EXPORTS.mozilla += [ - "BasicEvents.h", - "ColorScheme.h", -diff --git a/widget/nsINativeMenuService.h b/widget/nsINativeMenuService.h -new file mode 100644 -index 000000000000..e92d7a74a3bc ---- /dev/null -+++ b/widget/nsINativeMenuService.h -@@ -0,0 +1,39 @@ -+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -+/* 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 nsINativeMenuService_h_ -+#define nsINativeMenuService_h_ -+ -+#include "nsISupports.h" -+ -+class nsIWidget; -+class nsIContent; -+namespace mozilla { -+namespace dom { -+class Element; -+} -+} // namespace mozilla -+ -+// {90DF88F9-F084-4EF3-829A-49496E636DED} -+#define NS_INATIVEMENUSERVICE_IID \ -+ { \ -+ 0x90DF88F9, 0xF084, 0x4EF3, { \ -+ 0x82, 0x9A, 0x49, 0x49, 0x6E, 0x63, 0x6D, 0xED \ -+ } \ -+ } -+ -+class nsINativeMenuService : public nsISupports { -+ public: -+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_INATIVEMENUSERVICE_IID) -+ // Given a top-level window widget and a menu bar DOM node, sets up native -+ // menus. Once created, native menus are controlled via the DOM, including -+ // destruction. -+ NS_IMETHOD CreateNativeMenuBar(nsIWidget* aParent, -+ mozilla::dom::Element* aMenuBarNode) = 0; -+}; -+ -+NS_DEFINE_STATIC_IID_ACCESSOR(nsINativeMenuService, NS_INATIVEMENUSERVICE_IID) -+ -+#endif // nsINativeMenuService_h_ -diff --git a/widget/nsWidgetsCID.h b/widget/nsWidgetsCID.h -index 8e0f67661414..911711e88a0e 100644 ---- a/widget/nsWidgetsCID.h -+++ b/widget/nsWidgetsCID.h -@@ -66,6 +66,14 @@ - // Menus - //----------------------------------------------------------- - -+// {0B3FE5AA-BC72-4303-85AE-76365DF1251D} -+#define NS_NATIVEMENUSERVICE_CID \ -+ { \ -+ 0x0B3FE5AA, 0xBC72, 0x4303, { \ -+ 0x85, 0xAE, 0x76, 0x36, 0x5D, 0xF1, 0x25, 0x1D \ -+ } \ -+ } -+ - // {F6CD4F21-53AF-11d2-8DC4-00609703C14E} - #define NS_POPUPMENU_CID \ - { \ -diff --git a/xpcom/ds/NativeMenuAtoms.py b/xpcom/ds/NativeMenuAtoms.py -new file mode 100644 -index 000000000000..488c8f49c021 ---- /dev/null -+++ b/xpcom/ds/NativeMenuAtoms.py -@@ -0,0 +1,9 @@ -+from Atom import Atom -+ -+NATIVE_MENU_ATOMS = [ -+ Atom("menuitem_with_favicon", "menuitem-with-favicon"), -+ Atom("_moz_menubarkeeplocal", "_moz-menubarkeeplocal"), -+ Atom("_moz_nativemenupopupstate", "_moz-nativemenupopupstate"), -+ Atom("openedwithkey", "openedwithkey"), -+ Atom("shellshowingmenubar", "shellshowingmenubar"), -+] -diff --git a/xpcom/ds/StaticAtoms.py b/xpcom/ds/StaticAtoms.py -index 740b3f6e187d..3ca7630abf25 100644 ---- a/xpcom/ds/StaticAtoms.py -+++ b/xpcom/ds/StaticAtoms.py -@@ -7,6 +7,7 @@ - from Atom import Atom, InheritingAnonBoxAtom, NonInheritingAnonBoxAtom - from Atom import PseudoElementAtom - from HTMLAtoms import HTML_PARSER_ATOMS -+from NativeMenuAtoms import NATIVE_MENU_ATOMS - import sys - - # Static atom definitions, used to generate nsGkAtomList.h. -@@ -2536,7 +2537,7 @@ STATIC_ATOMS = [ - InheritingAnonBoxAtom("AnonBox_mozSVGForeignContent", ":-moz-svg-foreign-content"), - InheritingAnonBoxAtom("AnonBox_mozSVGText", ":-moz-svg-text"), - # END ATOMS --] + HTML_PARSER_ATOMS -+] + HTML_PARSER_ATOMS + NATIVE_MENU_ATOMS - # fmt: on - - -diff --git a/xpfe/appshell/AppWindow.cpp b/xpfe/appshell/AppWindow.cpp -index fb36837f23da..c6b6aae09ac8 100644 ---- a/xpfe/appshell/AppWindow.cpp -+++ b/xpfe/appshell/AppWindow.cpp -@@ -80,7 +80,7 @@ - - #include "mozilla/dom/DocumentL10n.h" - --#ifdef XP_MACOSX -+#if defined(XP_MACOSX) || defined(MOZ_WIDGET_GTK) - # include "mozilla/widget/NativeMenuSupport.h" - # define USE_NATIVE_MENUS - #endif --- -2.37.3 - diff --git a/waterfox-g/debian/patches/libavcodec58_91.patch b/waterfox-g/debian/patches/libavcodec58_91.patch index eff1d60..48aa2af 100644 --- a/waterfox-g/debian/patches/libavcodec58_91.patch +++ b/waterfox-g/debian/patches/libavcodec58_91.patch @@ -1,16 +1,13 @@ diff --git a/dom/media/platforms/ffmpeg/FFmpegRuntimeLinker.cpp b/dom/media/platforms/ffmpeg/FFmpegRuntimeLinker.cpp -index 91bf38df8354..e79bc068668a 100644 +index 7716162e0707..a9bed062905d 100644 --- a/dom/media/platforms/ffmpeg/FFmpegRuntimeLinker.cpp +++ b/dom/media/platforms/ffmpeg/FFmpegRuntimeLinker.cpp -@@ -39,6 +39,8 @@ static const char* sLibs[] = { - // of ffmpeg and update it regulary on ABI/API changes +@@ -41,6 +41,8 @@ static const char* sLibs[] = { #else + "libavcodec.so.60", "libavcodec.so.59", + "libavcodec.so.58.134", + "libavcodec.so.58.91", "libavcodec.so.58", "libavcodec-ffmpeg.so.58", "libavcodec-ffmpeg.so.57", --- -2.37.3 - diff --git a/waterfox-g/debian/patches/mach-depends.patch b/waterfox-g/debian/patches/mach-depends.patch index de85066..a499d45 100644 --- a/waterfox-g/debian/patches/mach-depends.patch +++ b/waterfox-g/debian/patches/mach-depends.patch @@ -1,24 +1,21 @@ diff --git a/python/sites/mach.txt b/python/sites/mach.txt -index 6547ee58574a..a7d9a8b2ca70 100644 +index 83dec4a14237..effb59fc457f 100644 --- a/python/sites/mach.txt +++ b/python/sites/mach.txt -@@ -114,7 +114,7 @@ vendored:third_party/python/taskcluster - vendored:third_party/python/taskcluster_taskgraph +@@ -121,7 +121,7 @@ vendored:third_party/python/taskcluster_taskgraph vendored:third_party/python/taskcluster_urls + vendored:third_party/python/toml vendored:third_party/python/tqdm -vendored:third_party/python/typing_extensions +pypi-optional:typing_extensions>=3.10.0:something will break vendored:third_party/python/urllib3 vendored:third_party/python/voluptuous - vendored:third_party/python/wheel -@@ -133,5 +133,5 @@ pypi-optional:glean-sdk==44.1.1:telemetry will not be collected + vendored:third_party/python/wcwidth +@@ -141,5 +141,5 @@ pypi-optional:glean-sdk==52.7.0:telemetry will not be collected # Mach gracefully handles the case where `psutil` is unavailable. # We aren't (yet) able to pin packages in automation, so we have to # support down to the oldest locally-installed version (5.4.2). --pypi-optional:psutil>=5.4.2,<=5.8.0:telemetry will be missing some data --pypi-optional:zstandard>=0.11.1,<=0.17.0:zstd archives will not be possible to extract +-pypi-optional:psutil>=5.4.2,<=5.9.4:telemetry will be missing some data +-pypi-optional:zstandard>=0.11.1,<=0.19.0:zstd archives will not be possible to extract +pypi-optional:psutil>=5.4.2,<=6.0.0:telemetry will be missing some data +pypi-optional:zstandard>=0.11.1,<=1.0.0:zstd archives will not be possible to extract --- -2.37.3 - diff --git a/waterfox-g/debian/patches/mozilla-ntlm-full-path.patch b/waterfox-g/debian/patches/mozilla-ntlm-full-path.patch index ce5b6a2..9c7a8db 100644 --- a/waterfox-g/debian/patches/mozilla-ntlm-full-path.patch +++ b/waterfox-g/debian/patches/mozilla-ntlm-full-path.patch @@ -1,18 +1,10 @@ -# HG changeset patch # User Petr Cerny -# Parent 7308e4a7c1f769f4bbbc90870b849cadd99495a6 -# Parent 2361c5db1e70e358b2158325e07fa15bb4569c2c -Bug 634334 - call to the ntlm_auth helper fails diff --git a/extensions/auth/nsAuthSambaNTLM.cpp b/extensions/auth/nsAuthSambaNTLM.cpp +index 5b701f237978..4004a5cfc79a 100644 --- a/extensions/auth/nsAuthSambaNTLM.cpp +++ b/extensions/auth/nsAuthSambaNTLM.cpp -@@ -156,17 +156,17 @@ static uint8_t* ExtractMessage(const nsA - *aLen = (length / 4) * 3 - numEquals; - return reinterpret_cast(PL_Base64Decode(s, length, nullptr)); - } - - nsresult nsAuthSambaNTLM::SpawnNTLMAuthHelper() { +@@ -160,7 +160,7 @@ nsresult nsAuthSambaNTLM::SpawnNTLMAuthHelper() { const char* username = PR_GetEnv("USER"); if (!username) return NS_ERROR_FAILURE; @@ -21,8 +13,3 @@ diff --git a/extensions/auth/nsAuthSambaNTLM.cpp b/extensions/auth/nsAuthSambaNT "--helper-protocol", "ntlmssp-client-1", "--use-cached-creds", - "--username", - username, - nullptr}; - - bool isOK = SpawnIOChild(const_cast(args), &mChildPID, diff --git a/waterfox-g/debian/patches/nongnome-proxies.patch b/waterfox-g/debian/patches/nongnome-proxies.patch index 5eaf79d..ff5e4b9 100644 --- a/waterfox-g/debian/patches/nongnome-proxies.patch +++ b/waterfox-g/debian/patches/nongnome-proxies.patch @@ -2,14 +2,10 @@ From: Wolfgang Rosenauer Subject: Do not use gconf for proxy settings if not running within Gnome diff --git a/toolkit/system/unixproxy/nsUnixSystemProxySettings.cpp b/toolkit/system/unixproxy/nsUnixSystemProxySettings.cpp +index 982faf7d7968..185dc1e22a90 100644 --- a/toolkit/system/unixproxy/nsUnixSystemProxySettings.cpp +++ b/toolkit/system/unixproxy/nsUnixSystemProxySettings.cpp -@@ -49,20 +49,24 @@ NS_IMETHODIMP - nsUnixSystemProxySettings::GetMainThreadOnly(bool* aMainThreadOnly) { - // dbus prevents us from being threadsafe, but this routine should not block - // anyhow - *aMainThreadOnly = true; - return NS_OK; +@@ -52,10 +52,14 @@ nsUnixSystemProxySettings::GetMainThreadOnly(bool* aMainThreadOnly) { } void nsUnixSystemProxySettings::Init() { @@ -28,8 +24,3 @@ diff --git a/toolkit/system/unixproxy/nsUnixSystemProxySettings.cpp b/toolkit/sy } } - nsresult nsUnixSystemProxySettings::GetPACURI(nsACString& aResult) { - if (mProxySettings) { - nsCString proxyMode; - // Check if mode is auto - nsresult rv = mProxySettings->GetString("mode"_ns, proxyMode); diff --git a/waterfox-g/debian/patches/series b/waterfox-g/debian/patches/series index d6ead97..0b9bea1 100644 --- a/waterfox-g/debian/patches/series +++ b/waterfox-g/debian/patches/series @@ -1,9 +1,10 @@ -global_menu.patch -p1 fis-csd-global-menu.patch -p1 +# stackrpms, 2 #g-kde.patch -p1 nongnome-proxies.patch -p1 mozilla-ntlm-full-path.patch -p1 libavcodec58_91.patch -p1 fix-langpack-id.patch -p1 -waterfox-branded-icons.patch -p1 fix-wayland-build.patch -p1 +debian-hacks/Relax-nodejs-minimum-version.patch -p1 +debian-hacks/Relax-cargo-version-requirement.patch -p1 diff --git a/waterfox-g/debian/patches/waterfox-branded-icons.patch b/waterfox-g/debian/patches/waterfox-branded-icons.patch deleted file mode 100644 index e0778a5..0000000 --- a/waterfox-g/debian/patches/waterfox-branded-icons.patch +++ /dev/null @@ -1,19 +0,0 @@ -diff --git a/browser/branding/branding-common.mozbuild b/browser/branding/branding-common.mozbuild ---- a/browser/branding/branding-common.mozbuild -+++ b/browser/branding/branding-common.mozbuild -@@ -22,12 +22,15 @@ def FirefoxBranding(): - FINAL_TARGET_FILES.VisualElements += [ - 'VisualElements_150.png', - 'VisualElements_70.png', - ] - elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gtk': - FINAL_TARGET_FILES.chrome.icons.default += [ - 'default128.png', - 'default16.png', -+ 'default22.png', -+ 'default24.png', -+ 'default256.png', - 'default32.png', - 'default48.png', - 'default64.png', - ] diff --git a/waterfox-g/debian/rules b/waterfox-g/debian/rules index 462dd96..21db460 100755 --- a/waterfox-g/debian/rules +++ b/waterfox-g/debian/rules @@ -5,60 +5,38 @@ export SHELL=/bin/bash export MOZCONFIG=$(shell pwd)/debian/mozconfig export MOZ_NOSPAM:=1 export MOZ_SOURCE_REPO=https://github.com/WaterfoxCo/Waterfox +# stackrpms,3 export MOZ_SOURCE_CHANGESET=$(shell awk -F ': ' '/^commit:/ {print $$2; exit}' ../SOURCES/waterfox-g.obsinfo) export WF_VERSION=$(shell awk -F ': ' '/^version:/ {print "G"$$2; exit}' ../SOURCES/waterfox-g.obsinfo) export TODAY_DATE=$(shell date +%Y-%m-%d) export LC_ALL=C.UTF-8 - -LDFLAGS += -Wl,--no-keep-memory -Wl,--no-mmap-output-file - -distrelease := $(shell lsb_release -sc) -ifeq ($(distrelease),$(filter $(distrelease),stretch xenial)) -LDFLAGS += -static-libstdc++ -endif - -export LDFLAGS - export JOBS=$(shell echo $(shell grep -c ^processor /proc/cpuinfo)\/2 | bc) - export MACH_BUILD_PYTHON_NATIVE_PACKAGE_SOURCE=system export DEB_BUILD_MAINT_OPTIONS = optimize=-lto +include /usr/share/dpkg/buildflags.mk +export CXXFLAGS CFLAGS LDFLAGS %: dh $@ +override_dh_auto_configure: + override_dh_auto_clean: dh_auto_clean find . -name '*.pyc' -delete override_dh_auto_build: - # Upstream modifies version_display.txt using script included in GitHub Actions (version=git_tag), - # without patching. Debhelper won't allow us to do that by same way and - # patching won't be much convenient, so we need to create new files. - mkdir -p $$(pwd)/debian/app_version - cp $$(pwd)/browser/config/version.txt $$(pwd)/debian/app_version/version.txt - echo $$WF_VERSION > $$(pwd)/debian/app_version/version_display.txt - # Build browser - # LTO needs more open files - ulimit -n 4096; xvfb-run -a -n 97 -s "-screen 0 1920x1080x24" ./mach build - - # Build langpacks - mkdir -p $$(pwd)/extensions - # langpack-build can not be done in parallel easily (see https://bugzilla.mozilla.org/show_bug.cgi?id=1660943) - # Therefore, we have to have a separate obj-dir for each language - # We do this, by creating a mozconfig-template with the necessary switches - # and a placeholder obj-dir, which gets copied and modified for each language - sed -r '/^(ja-JP-mac|en-US|)$$/d;s/ .*$$//' debian/locales.shipped | cut -f1 -d":" \ - | xargs -n 1 -P $$JOBS -I {} /bin/sh -c 'locale=$$1; cp debian/mozconfig_LANG mozconfig_$$locale; sed -i "s|obj_LANG|obj_$$locale|" mozconfig_$$locale; export MOZCONFIG=mozconfig_$$locale; ./mach build config/nsinstall langpack-$$locale; cp -L ../obj_$$locale/dist/linux-*/xpi/waterfox-g-$$WF_VERSION.$$locale.langpack.xpi \ - $$(pwd)/extensions/langpack-$$locale@l10n.waterfox.net.xpi' -- {} + $$(pwd)/debian/build.sh override_dh_auto_install: chmod +x $$(pwd)/debian/waterfox-g-bin.sh + # stackrpms,4 DESTDIR=$$(pwd)/debian/waterfox-g ./mach install rm -rf $$(pwd)/debian/waterfox-g/usr/lib/waterfox-g/dictionaries rm -rf $$(pwd)/debian/waterfox-g/usr/lib/waterfox-g/waterfox-g-bin sed -i "s/__DATE__/$$TODAY_DATE/g" $$(pwd)/debian/waterfox-g.appdata.xml.in + # stackrpms,2 sed -e "s/__VERSION__/$$WF_VERSION/g" $$(pwd)/debian/waterfox-g.appdata.xml.in > $$(pwd)/debian/waterfox-g/usr/share/metainfo/waterfox-g.appdata.xml mv $$(pwd)/debian/waterfox-g-wayland-bin.sh $$(pwd)/debian/waterfox-g-wayland/usr/bin/waterfox-g-wayland @@ -66,4 +44,3 @@ override_dh_shlibdeps: dh_shlibdeps -l /usr/lib/waterfox-g/waterfox-g override_dh_strip_nondeterminism: - dh_strip_nondeterminism -Xdebian/waterfox-g/usr/lib/waterfox-g/browser/features/formautofill@mozilla.org.xpi -- cgit