summaryrefslogtreecommitdiff
path: root/waterfox-g/debian/patches
diff options
context:
space:
mode:
authorB. Stack <bgstack15@gmail.com>2023-08-19 17:18:07 -0400
committerB. Stack <bgstack15@gmail.com>2023-08-19 17:18:07 -0400
commitb2237bccba179e5483e083edf5ebbd313d240dfd (patch)
tree19d822c24051e2308c32c04a102547f4e5188679 /waterfox-g/debian/patches
parentuse cgit url (diff)
downloadstackrpms-b2237bccba179e5483e083edf5ebbd313d240dfd.tar.gz
stackrpms-b2237bccba179e5483e083edf5ebbd313d240dfd.tar.bz2
stackrpms-b2237bccba179e5483e083edf5ebbd313d240dfd.zip
add waterfox-g
Diffstat (limited to 'waterfox-g/debian/patches')
-rw-r--r--waterfox-g/debian/patches/fis-csd-global-menu.patch21
-rw-r--r--waterfox-g/debian/patches/fix-langpack-id.patch67
-rw-r--r--waterfox-g/debian/patches/fix-wayland-build.patch19
-rw-r--r--waterfox-g/debian/patches/g-kde.patch1748
-rw-r--r--waterfox-g/debian/patches/global_menu.patch5382
-rw-r--r--waterfox-g/debian/patches/libavcodec58_91.patch16
-rw-r--r--waterfox-g/debian/patches/mach-depends.patch24
-rw-r--r--waterfox-g/debian/patches/mozilla-ntlm-full-path.patch28
-rw-r--r--waterfox-g/debian/patches/nongnome-proxies.patch35
-rw-r--r--waterfox-g/debian/patches/series9
-rw-r--r--waterfox-g/debian/patches/waterfox-branded-icons.patch19
11 files changed, 7368 insertions, 0 deletions
diff --git a/waterfox-g/debian/patches/fis-csd-global-menu.patch b/waterfox-g/debian/patches/fis-csd-global-menu.patch
new file mode 100644
index 0000000..6eca9e8
--- /dev/null
+++ b/waterfox-g/debian/patches/fis-csd-global-menu.patch
@@ -0,0 +1,21 @@
+diff --git a/browser/base/content/browser.css b/browser/base/content/browser.css
+index 03a778809dc8..8f0267b45328 100644
+--- a/browser/base/content/browser.css
++++ b/browser/base/content/browser.css
+@@ -334,6 +334,13 @@ toolbar[customizing] #whats-new-menu-button {
+ visibility: hidden;
+ }
+
++@media (-moz-platform: linux) {
++ *|*:root[shellshowingmenubar="true"]
++ #toolbar-menubar[autohide="true"]:not([inactive]) + #TabsToolbar > .titlebar-buttonbox-container {
++ visibility: visible !important;
++ }
++}
++
+ :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
new file mode 100644
index 0000000..c409fd8
--- /dev/null
+++ b/waterfox-g/debian/patches/fix-langpack-id.patch
@@ -0,0 +1,67 @@
+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
+--- 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";
+ const TELEMETRY_CATEGORY = "intl.ui.browserLanguage";
+
+ function langpackId(locale) {
+- return `langpack-${locale}@firefox.mozilla.org`;
++ return `langpack-${locale}@l10n.waterfox.net`;
+ }
+
+ function getManifestData(locale, version = "2.0") {
+@@ -670,7 +670,7 @@ add_task(async function testInstallFromAMO() {
+ is(getMainPaneLocales(), "en-US,pl,search", "en-US and pl now available");
+
+ // Disable the Polish langpack.
+- langpack = await AddonManager.getAddonByID("langpack-pl@firefox.mozilla.org");
++ langpack = await AddonManager.getAddonByID("langpack-pl@l10n.waterfox.net");
+ await langpack.disable();
+
+ ({ dialogDoc, available, selected } = await openDialog(doc, true));
+diff --git a/browser/locales/Makefile.in b/browser/locales/Makefile.in
+index e4b60a039956..208871cb6f75 100644
+--- a/browser/locales/Makefile.in
++++ b/browser/locales/Makefile.in
+@@ -21,9 +21,9 @@ PWD := $(CURDIR)
+ ZIP_IN ?= $(ABS_DIST)/$(PACKAGE)
+
+ ifdef MOZ_DEV_EDITION
+-MOZ_LANGPACK_EID=langpack-$(AB_CD)@devedition.mozilla.org
++MOZ_LANGPACK_EID=langpack-$(AB_CD)@l10n.waterfox.net
+ else
+-MOZ_LANGPACK_EID=langpack-$(AB_CD)@firefox.mozilla.org
++MOZ_LANGPACK_EID=langpack-$(AB_CD)@l10n.waterfox.net
+ 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() {
+ // 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) {
+ );
+ resolve(
+ availableLangpacks.map(locale => ({
+- guid: `langpack-${locale}@firefox.mozilla.org`,
++ guid: `langpack-${locale}@l10n.waterfox.net`,
+ type: "language",
+ 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
new file mode 100644
index 0000000..703b1a8
--- /dev/null
+++ b/waterfox-g/debian/patches/fix-wayland-build.patch
@@ -0,0 +1,19 @@
+Description: Fix FTBFS on bionic. Compiler errors:
+ In file included from Unified_cpp_widget_gtk1.cpp:2:
+ /<<BUILDDIR>>/firefox-92.0~b2+build1/widget/gtk/WaylandBuffer.cpp:261:39: error: unknown type name 'GLContext'; did you mean 'EGLContext'?
+ const LayoutDeviceIntSize& aSize, GLContext* aGL) {
+ ^~~~~~~~~
+
+Author: Rico Tzschichholz <ricotz@ubuntu.com>
+
+--- a/widget/gtk/WaylandBuffer.cpp
++++ b/widget/gtk/WaylandBuffer.cpp
+@@ -258,7 +258,7 @@
+
+ /* static */
+ RefPtr<WaylandBufferDMABUF> WaylandBufferDMABUF::Create(
+- const LayoutDeviceIntSize& aSize, GLContext* aGL) {
++ const LayoutDeviceIntSize& aSize, gl::GLContext* aGL) {
+ RefPtr<WaylandBufferDMABUF> buffer = new WaylandBufferDMABUF(aSize);
+
+ const auto flags =
diff --git a/waterfox-g/debian/patches/g-kde.patch b/waterfox-g/debian/patches/g-kde.patch
new file mode 100644
index 0000000..2bff3cc
--- /dev/null
+++ b/waterfox-g/debian/patches/g-kde.patch
@@ -0,0 +1,1748 @@
+Merged few patches into one patch and fixed for Waterfox
+Original authors of patch for Firefox:
+Wolfgang Rosenauer <wolfgang@rosenauer.org>
+Lubos Lunak <lunak@suse.com>
+Original patches => http://www.rosenauer.org/hg/mozilla/file/firefox91
+
+diff --git a/browser/components/preferences/main.js b/browser/components/preferences/main.js
+index 627e63440af2..6d300a8124fa 100644
+--- a/browser/components/preferences/main.js
++++ b/browser/components/preferences/main.js
+@@ -316,6 +316,13 @@ var gMainPane = {
+ }, backoffTimes[this._backoffIndex]);
+ }
+
++ var env = Components.classes["@mozilla.org/process/environment;1"]
++ .getService(Components.interfaces.nsIEnvironment);
++ var kde_session = 0;
++ if (env.get('KDE_FULL_SESSION') == "true") {
++ kde_session = 1;
++ }
++
+ this.initBrowserContainers();
+ this.buildContentProcessCountMenuList();
+
+@@ -1349,6 +1356,17 @@ var gMainPane = {
+ }
+ try {
+ shellSvc.setDefaultBrowser(true, false);
++ if (kde_session == 1) {
++ var shellObj = Components.classes["@mozilla.org/file/local;1"]
++ .createInstance(Components.interfaces.nsILocalFile);
++ shellObj.initWithPath("/usr/bin/kwriteconfig");
++ var process = Components.classes["@mozilla.org/process/util;1"]
++ .createInstance(Components.interfaces.nsIProcess);
++ process.init(shellObj);
++ var args = ["--file", "kdeglobals", "--group", "General", "--key",
++ "BrowserApplication", "waterfox-g"];
++ process.run(false, args, args.length);
++ }
+ } catch (ex) {
+ Cu.reportError(ex);
+ return;
+diff --git a/browser/components/shell/moz.build b/browser/components/shell/moz.build
+index eedbb0d938fe..9b364941b850 100644
+--- a/browser/components/shell/moz.build
++++ b/browser/components/shell/moz.build
+@@ -36,6 +36,8 @@ elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
+
+ SOURCES += [
+ "nsGNOMEShellService.cpp",
++ "nsKDEShellService.cpp",
++ "nsUnixShellService.cpp",
+ ]
+ if CONFIG["MOZ_ENABLE_DBUS"]:
+ SOURCES += [
+diff --git a/browser/components/shell/nsKDEShellService.cpp b/browser/components/shell/nsKDEShellService.cpp
+new file mode 100644
+index 000000000000..152a3aca87ea
+--- /dev/null
++++ b/browser/components/shell/nsKDEShellService.cpp
+@@ -0,0 +1,109 @@
++/* -*- 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/. */
++
++#include "mozilla/ArrayUtils.h"
++
++#include "nsCOMPtr.h"
++#include "nsKDEShellService.h"
++#include "nsShellService.h"
++#include "nsKDEUtils.h"
++#include "nsIPrefService.h"
++#include "nsIProcess.h"
++#include "nsIFile.h"
++#include "nsServiceManagerUtils.h"
++#include "nsComponentManagerUtils.h"
++#include "nsIMutableArray.h"
++#include "nsISupportsPrimitives.h"
++#include "nsArrayUtils.h"
++
++using namespace mozilla;
++
++nsresult
++nsKDEShellService::Init()
++{
++ if( !nsKDEUtils::kdeSupport())
++ return NS_ERROR_NOT_AVAILABLE;
++ return NS_OK;
++}
++
++NS_IMPL_ISUPPORTS(nsKDEShellService, nsIGNOMEShellService, nsIShellService)
++
++NS_IMETHODIMP
++nsKDEShellService::IsDefaultBrowser(bool aForAllTypes,
++ bool* aIsDefaultBrowser)
++{
++ *aIsDefaultBrowser = false;
++
++ nsCOMPtr<nsIMutableArray> command = do_CreateInstance( NS_ARRAY_CONTRACTID );
++ if (!command)
++ return NS_ERROR_FAILURE;
++
++ nsCOMPtr<nsISupportsCString> str = do_CreateInstance( NS_SUPPORTS_CSTRING_CONTRACTID );
++ if (!str)
++ return NS_ERROR_FAILURE;
++
++ str->SetData("ISDEFAULTBROWSER"_ns);
++ command->AppendElement( str );
++
++ if( nsKDEUtils::command( command ))
++ *aIsDefaultBrowser = true;
++ return NS_OK;
++}
++
++NS_IMETHODIMP
++nsKDEShellService::SetDefaultBrowser(bool aClaimAllTypes,
++ bool aForAllUsers)
++{
++ nsCOMPtr<nsIMutableArray> command = do_CreateInstance( NS_ARRAY_CONTRACTID );
++ if (!command)
++ return NS_ERROR_FAILURE;
++
++ nsCOMPtr<nsISupportsCString> cmdstr = do_CreateInstance( NS_SUPPORTS_CSTRING_CONTRACTID );
++ nsCOMPtr<nsISupportsCString> paramstr = do_CreateInstance( NS_SUPPORTS_CSTRING_CONTRACTID );
++ if (!cmdstr || !paramstr)
++ return NS_ERROR_FAILURE;
++
++ cmdstr->SetData("SETDEFAULTBROWSER"_ns);
++ command->AppendElement( cmdstr );
++
++ paramstr->SetData( aClaimAllTypes ? "ALLTYPES"_ns : "NORMAL"_ns );
++ command->AppendElement( paramstr );
++
++ return nsKDEUtils::command( command ) ? NS_OK : NS_ERROR_FAILURE;
++}
++
++NS_IMETHODIMP
++nsKDEShellService::GetCanSetDesktopBackground(bool* aResult)
++{
++ *aResult = true;
++ return NS_OK;
++}
++
++NS_IMETHODIMP
++nsKDEShellService::SetDesktopBackground(dom::Element* aElement,
++ int32_t aPosition,
++ const nsACString& aImageName)
++{
++ return NS_ERROR_NOT_IMPLEMENTED;
++}
++
++NS_IMETHODIMP
++nsKDEShellService::GetDesktopBackgroundColor(PRUint32 *aColor)
++{
++ return NS_ERROR_NOT_IMPLEMENTED;
++}
++
++NS_IMETHODIMP
++nsKDEShellService::SetDesktopBackgroundColor(PRUint32 aColor)
++{
++ return NS_ERROR_NOT_IMPLEMENTED;
++}
++
++NS_IMETHODIMP
++nsKDEShellService::IsDefaultForScheme(nsTSubstring<char> const& aScheme, bool* aIsDefaultBrowser)
++{
++ return NS_ERROR_NOT_IMPLEMENTED;
++}
++
+diff --git a/browser/components/shell/nsKDEShellService.h b/browser/components/shell/nsKDEShellService.h
+new file mode 100644
+index 000000000000..8b0bb1916435
+--- /dev/null
++++ b/browser/components/shell/nsKDEShellService.h
+@@ -0,0 +1,32 @@
++/* -*- 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 nskdeshellservice_h____
++#define nskdeshellservice_h____
++
++#include "nsIGNOMEShellService.h"
++#include "nsToolkitShellService.h"
++#include "nsString.h"
++#include "mozilla/Attributes.h"
++
++class nsKDEShellService final : public nsIGNOMEShellService,
++ public nsToolkitShellService
++{
++public:
++ nsKDEShellService() : mCheckedThisSession(false) { }
++
++ NS_DECL_ISUPPORTS
++ NS_DECL_NSISHELLSERVICE
++ NS_DECL_NSIGNOMESHELLSERVICE
++
++ nsresult Init();
++
++private:
++ ~nsKDEShellService() {}
++
++ bool mCheckedThisSession;
++};
++
++#endif // nskdeshellservice_h____
+diff --git a/browser/components/shell/nsUnixShellService.cpp b/browser/components/shell/nsUnixShellService.cpp
+new file mode 100644
+index 000000000000..abf266ebdc52
+--- /dev/null
++++ b/browser/components/shell/nsUnixShellService.cpp
+@@ -0,0 +1,22 @@
++/* -*- 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/. */
++
++
++#include "nsUnixShellService.h"
++#include "nsGNOMEShellService.h"
++#include "nsKDEShellService.h"
++#include "nsKDEUtils.h"
++#include "mozilla/ModuleUtils.h"
++
++NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsGNOMEShellService, Init)
++NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsKDEShellService, Init)
++
++NS_IMETHODIMP
++nsUnixShellServiceConstructor(REFNSIID aIID, void **aResult)
++{
++ if( nsKDEUtils::kdeSupport())
++ return nsKDEShellServiceConstructor( aIID, aResult );
++ return nsGNOMEShellServiceConstructor( aIID, aResult );
++}
+diff --git a/browser/components/shell/nsUnixShellService.h b/browser/components/shell/nsUnixShellService.h
+new file mode 100644
+index 000000000000..26b5dbac47dd
+--- /dev/null
++++ b/browser/components/shell/nsUnixShellService.h
+@@ -0,0 +1,15 @@
++/* -*- 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 nsunixshellservice_h____
++#define nsunixshellservice_h____
++
++#include "nsIGNOMEShellService.h"
++
++NS_IMETHODIMP
++nsUnixShellServiceConstructor(nsISupports *aOuter, REFNSIID aIID, void **aResult);
++
++#endif // nsunixshellservice_h____
+diff --git a/modules/libpref/Preferences.cpp b/modules/libpref/Preferences.cpp
+index 6d0bebd2ee68..c138e63b23dc 100644
+--- a/modules/libpref/Preferences.cpp
++++ b/modules/libpref/Preferences.cpp
+@@ -93,6 +93,7 @@
+ #ifdef MOZ_BACKGROUNDTASKS
+ # include "mozilla/BackgroundTasks.h"
+ #endif
++#include "nsKDEUtils.h"
+
+ #ifdef DEBUG
+ # include <map>
+@@ -4772,6 +4773,17 @@ 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";
++ break;
++ }
++ }
++ }
++
+ rv = pref_LoadPrefsInDir(defaultPrefDir, specialFiles,
+ ArrayLength(specialFiles));
+ if (NS_FAILED(rv)) {
+@@ -4846,7 +4858,7 @@ nsresult Preferences::InitInitialObjects(bool aIsStartup) {
+ }
+
+ // Do we care if a file provided by this process fails to load?
+- pref_LoadPrefsInDir(path, nullptr, 0);
++ pref_LoadPrefsInDir(path, specialFiles, ArrayLength(specialFiles));
+ }
+ }
+
+diff --git a/modules/libpref/moz.build b/modules/libpref/moz.build
+index 1f021d409c51..171d034cc03a 100644
+--- a/modules/libpref/moz.build
++++ b/modules/libpref/moz.build
+@@ -125,6 +125,10 @@ UNIFIED_SOURCES += [
+ "SharedPrefMap.cpp",
+ ]
+
++LOCAL_INCLUDES += [
++ '/toolkit/xre'
++]
++
+ gen_all_tuple = tuple(gen_h + gen_cpp + gen_rs)
+
+ GeneratedFile(
+diff --git a/python/mozbuild/mozpack/chrome/flags.py b/python/mozbuild/mozpack/chrome/flags.py
+index 0fe6ee99bd51..f7acdafe9790 100644
+--- a/python/mozbuild/mozpack/chrome/flags.py
++++ b/python/mozbuild/mozpack/chrome/flags.py
+@@ -234,6 +234,7 @@ class Flags(OrderedDict):
+ "tablet": Flag,
+ "process": StringFlag,
+ "backgroundtask": StringFlag,
++ "desktop": StringFlag,
+ }
+ RE = re.compile(r"([!<>=]+)")
+
+diff --git a/python/mozbuild/mozpack/chrome/manifest.py b/python/mozbuild/mozpack/chrome/manifest.py
+index a733685f95a3..f64b17fb7b17 100644
+--- a/python/mozbuild/mozpack/chrome/manifest.py
++++ b/python/mozbuild/mozpack/chrome/manifest.py
+@@ -44,6 +44,7 @@ class ManifestEntry(object):
+ "process",
+ "contentaccessible",
+ "backgroundtask",
++ "desktop",
+ ]
+
+ def __init__(self, base, *flags):
+diff --git a/toolkit/components/downloads/moz.build b/toolkit/components/downloads/moz.build
+index d4172e2d73ad..8bd0577bc535 100644
+--- a/toolkit/components/downloads/moz.build
++++ b/toolkit/components/downloads/moz.build
+@@ -51,5 +51,9 @@ if CONFIG["MOZ_PLACES"]:
+
+ FINAL_LIBRARY = "xul"
+
++LOCAL_INCLUDES += [
++ '/toolkit/xre'
++]
++
+ 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 = {
+ this.chosenApp = params.handlerApp;
+ }
+ } else if ("@mozilla.org/applicationchooser;1" in Cc) {
+- var nsIApplicationChooser = Ci.nsIApplicationChooser;
+- var appChooser = Cc["@mozilla.org/applicationchooser;1"].createInstance(
+- nsIApplicationChooser
+- );
+- appChooser.init(
+- this.mDialog,
+- this.dialogElement("strings").getString("chooseAppFilePickerTitle")
+- );
+- var contentTypeDialogObj = this;
+- let appChooserCallback = function appChooserCallback_done(aResult) {
+- if (aResult) {
+- contentTypeDialogObj.chosenApp = aResult.QueryInterface(
+- Ci.nsILocalHandlerApp
+- );
+- }
+- contentTypeDialogObj.finishChooseApp();
+- };
+- appChooser.open(this.mLauncher.MIMEInfo.MIMEType, appChooserCallback);
+- // The finishChooseApp is called from appChooserCallback
+- return;
++ // handle the KDE case which is implemented in the filepicker
++ // therefore falling back to Gtk2 like behaviour if KDE is running
++ // FIXME this should be better handled in the nsIApplicationChooser
++ // interface
++ var env = Components.classes["@mozilla.org/process/environment;1"]
++ .getService(Components.interfaces.nsIEnvironment);
++ if (env.get('KDE_FULL_SESSION') == "true")
++ {
++ var nsIFilePicker = Ci.nsIFilePicker;
++ var fp = Cc["@mozilla.org/filepicker;1"]
++ .createInstance(nsIFilePicker);
++ fp.init(this.mDialog,
++ this.dialogElement("strings").getString("chooseAppFilePickerTitle"),
++ nsIFilePicker.modeOpen);
++
++ fp.appendFilters(nsIFilePicker.filterApps);
++
++ fp.open(aResult => {
++ if (aResult == nsIFilePicker.returnOK && fp.file) {
++ // Remember the file they chose to run.
++ var localHandlerApp =
++ Cc["@mozilla.org/uriloader/local-handler-app;1"].
++ createInstance(Ci.nsILocalHandlerApp);
++ localHandlerApp.executable = fp.file;
++ this.chosenApp = localHandlerApp;
++ }
++ this.finishChooseApp();
++ });
++ } else {
++ var nsIApplicationChooser = Ci.nsIApplicationChooser;
++ var appChooser = Cc["@mozilla.org/applicationchooser;1"].createInstance(
++ nsIApplicationChooser
++ );
++ appChooser.init(
++ this.mDialog,
++ this.dialogElement("strings").getString("chooseAppFilePickerTitle")
++ );
++ var contentTypeDialogObj = this;
++ let appChooserCallback = function appChooserCallback_done(aResult) {
++ if (aResult) {
++ contentTypeDialogObj.chosenApp = aResult.QueryInterface(
++ Ci.nsILocalHandlerApp
++ );
++ }
++ contentTypeDialogObj.finishChooseApp();
++ };
++ appChooser.open(this.mLauncher.MIMEInfo.MIMEType, appChooserCallback);
++ // The finishChooseApp is called from appChooserCallback
++ return;
++ }
+ } else {
+ 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
+--- a/toolkit/system/unixproxy/nsUnixSystemProxySettings.cpp
++++ b/toolkit/system/unixproxy/nsUnixSystemProxySettings.cpp
+@@ -15,6 +15,8 @@
+ #include "nsNetUtil.h"
+ #include "nsISupportsPrimitives.h"
+ #include "nsIGSettingsService.h"
++#include "nsPrintfCString.h"
++#include "nsKDEUtils.h"
+
+ using namespace mozilla;
+
+@@ -38,6 +40,8 @@ class nsUnixSystemProxySettings final : public nsISystemProxySettings {
+ nsACString& aResult);
+ nsresult SetProxyResultFromGSettings(const char* aKeyBase, const char* aType,
+ nsACString& aResult);
++ nsresult GetProxyFromKDE(const nsACString& aScheme, const nsACString& aHost,
++ PRInt32 aPort, nsACString& aResult);
+ };
+
+ NS_IMPL_ISUPPORTS(nsUnixSystemProxySettings, nsISystemProxySettings)
+@@ -379,6 +383,9 @@ nsresult nsUnixSystemProxySettings::GetProxyForURI(const nsACString& aSpec,
+ const nsACString& aHost,
+ const int32_t aPort,
+ nsACString& aResult) {
++ if (nsKDEUtils::kdeSupport())
++ return GetProxyFromKDE(aScheme, aHost, aPort, aResult);
++
+ 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,
+ return GetProxyFromEnvironment(aScheme, aHost, aPort, aResult);
+ }
+
++nsresult
++nsUnixSystemProxySettings::GetProxyFromKDE(const nsACString& aScheme,
++ const nsACString& aHost,
++ PRInt32 aPort,
++ nsACString& aResult)
++{
++ nsAutoCString url;
++ url = aScheme;
++ url += "://";
++ url += aHost;
++ if( aPort >= 0 )
++ {
++ url += ":";
++ url += nsPrintfCString("%d", aPort);
++ }
++ nsTArray<nsCString> command;
++ command.AppendElement( "GETPROXY"_ns );
++ command.AppendElement( url );
++ nsTArray<nsCString> result;
++ 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<nsUnixSystemProxySettings>();
+ result->Init();
+diff --git a/toolkit/xre/moz.build b/toolkit/xre/moz.build
+index 6475c0296aac..83e0184d4938 100644
+--- a/toolkit/xre/moz.build
++++ b/toolkit/xre/moz.build
+@@ -97,7 +97,9 @@ elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "uikit":
+ "UIKitDirProvider.mm",
+ ]
+ elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
++ EXPORTS += ['nsKDEUtils.h']
+ UNIFIED_SOURCES += [
++ "nsKDEUtils.cpp",
+ "nsNativeAppSupportUnix.cpp",
+ ]
+ 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
+--- /dev/null
++++ b/toolkit/xre/nsKDEUtils.cpp
+@@ -0,0 +1,321 @@
++/* -*- 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
++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
++
++#include "nsKDEUtils.h"
++#include "nsIWidget.h"
++#include "nsISupportsPrimitives.h"
++#include "nsIMutableArray.h"
++#include "nsComponentManagerUtils.h"
++#include "nsArrayUtils.h"
++
++#include <gtk/gtk.h>
++
++#include <limits.h>
++#include <stdio.h>
++#include <sys/wait.h>
++#include <sys/resource.h>
++#include <unistd.h>
++#include <X11/Xlib.h>
++// 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 */
++#endif
++
++//#define DEBUG_KDE
++#ifdef DEBUG_KDE
++#define KWATERFOXHELPER "kwaterfoxhelper"
++#else
++// not need for lib64, it's a binary
++#define KWATERFOXHELPER "/usr/lib/waterfox/kwaterfoxhelper"
++#endif
++
++#define KWATERFOXHELPER_VERSION 6
++#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 getKdeSupport()
++ {
++ nsTArray<nsCString> 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 );
++#endif
++ return kde;
++ }
++
++nsKDEUtils::nsKDEUtils()
++ : commandFile( NULL )
++ , replyFile( NULL )
++ {
++ }
++
++nsKDEUtils::~nsKDEUtils()
++ {
++// closeHelper(); not actually useful, exiting will close the fd too
++ }
++
++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::kdeSupport()
++ {
++ static bool support = kdeSession() && getKdeSupport();
++ return support && helperRunning;
++ }
++
++struct nsKDECommandData
++ {
++ FILE* file;
++ nsTArray<nsCString>* 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;
++ }
++
++bool nsKDEUtils::command( const nsTArray<nsCString>& command, nsTArray<nsCString>* output )
++ {
++ return self()->internalCommand( command, NULL, false, output );
++ }
++
++bool nsKDEUtils::command( nsIArray* command, nsIArray** output)
++ {
++ nsTArray<nsCString> in;
++ PRUint32 length;
++ command->GetLength( &length );
++ for ( PRUint32 i = 0; i < length; i++ )
++ {
++ nsCOMPtr<nsISupportsCString> str = do_QueryElementAt( command, i );
++ if( str )
++ {
++ nsAutoCString s;
++ str->GetData( s );
++ in.AppendElement( s );
++ }
++ }
++
++ nsTArray<nsCString> out;
++ bool ret = self()->internalCommand( in, NULL, false, &out );
++
++ if ( !output ) return ret;
++
++ nsCOMPtr<nsIMutableArray> result = do_CreateInstance( NS_ARRAY_CONTRACTID );
++ if ( !result ) return false;
++
++ for ( PRUint32 i = 0; i < out.Length(); i++ )
++ {
++ nsCOMPtr<nsISupportsCString> rstr = do_CreateInstance( NS_SUPPORTS_CSTRING_CONTRACTID );
++ if ( !rstr ) return false;
++
++ rstr->SetData( out[i] );
++ result->AppendElement( rstr );
++ }
++
++ NS_ADDREF( *output = result);
++ return ret;
++ }
++
++
++bool nsKDEUtils::commandBlockUi( const nsTArray<nsCString>& command, GtkWindow* parent, nsTArray<nsCString>* output )
++ {
++ return self()->internalCommand( command, parent, true, output );
++ }
++
++bool nsKDEUtils::internalCommand( const nsTArray<nsCString>& command, GtkWindow* parent, bool blockUi,
++ nsTArray<nsCString>* 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< 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 );
++#ifdef DEBUG_KDE
++ execvp( KWATERFOXHELPER, args );
++#else
++ 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;
++ }
++
++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<nsCString>& 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());
++#endif
++ 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
+--- /dev/null
++++ b/toolkit/xre/nsKDEUtils.h
+@@ -0,0 +1,48 @@
++/* -*- 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 nsKDEUtils_h__
++#define nsKDEUtils_h__
++
++#include "nsString.h"
++#include "nsTArray.h"
++#include <stdio.h>
++
++typedef struct _GtkWindow GtkWindow;
++
++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<nsCString>& command, nsTArray<nsCString>* 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<nsCString>& command, GtkWindow* parent, nsTArray<nsCString>* output = NULL );
++
++ private:
++ nsKDEUtils();
++ ~nsKDEUtils();
++ static nsKDEUtils* self();
++ bool startHelper();
++ void closeHelper();
++ void feedCommand( const nsTArray<nsCString>& command );
++ bool internalCommand( const nsTArray<nsCString>& command, GtkWindow* parent, bool isParent,
++ nsTArray<nsCString>* output );
++ FILE* commandFile;
++ FILE* replyFile;
++ };
++
++#endif // nsKDEUtils
+diff --git a/uriloader/exthandler/HandlerServiceParent.cpp b/uriloader/exthandler/HandlerServiceParent.cpp
+index dbcc95d95646..5f2542a95065 100644
+--- a/uriloader/exthandler/HandlerServiceParent.cpp
++++ b/uriloader/exthandler/HandlerServiceParent.cpp
+@@ -12,7 +12,7 @@
+ #include "ContentHandlerService.h"
+ #include "nsStringEnumerator.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(
+ }
+ #ifdef MOZ_WIDGET_GTK
+ // Check the GNOME registry for a protocol handler
+- *aHandlerExists = nsGNOMERegistry::HandlerExists(aProtocolScheme.get());
++ *aHandlerExists = nsCommonRegistry::HandlerExists(aProtocolScheme.get());
+ #else
+ *aHandlerExists = false;
+ #endif
+diff --git a/uriloader/exthandler/moz.build b/uriloader/exthandler/moz.build
+index 92647a9b3478..fc5068cd2069 100644
+--- a/uriloader/exthandler/moz.build
++++ b/uriloader/exthandler/moz.build
+@@ -83,7 +83,9 @@ else:
+
+ if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
+ UNIFIED_SOURCES += [
++ "unix/nsCommonRegistry.cpp",
+ "unix/nsGNOMERegistry.cpp",
++ "unix/nsKDERegistry.cpp",
+ "unix/nsMIMEInfoUnix.cpp",
+ ]
+ elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "android":
+@@ -135,6 +137,7 @@ LOCAL_INCLUDES += [
+ "/dom/ipc",
+ "/netwerk/base",
+ "/netwerk/protocol/http",
++ "/toolkit/xre",
+ ]
+
+ 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
+--- /dev/null
++++ b/uriloader/exthandler/unix/nsCommonRegistry.cpp
+@@ -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
++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
++
++#include "nsCommonRegistry.h"
++
++#include "nsGNOMERegistry.h"
++#include "nsKDERegistry.h"
++#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 */ 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 */ already_AddRefed<nsMIMEInfoBase>
++nsCommonRegistry::GetFromExtension(const nsACString& aFileExt)
++{
++ if( nsKDEUtils::kdeSupport())
++ return nsKDERegistry::GetFromExtension( aFileExt );
++ return nsGNOMERegistry::GetFromExtension( aFileExt );
++}
++
++/* static */ already_AddRefed<nsMIMEInfoBase>
++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
+--- /dev/null
++++ b/uriloader/exthandler/unix/nsCommonRegistry.h
+@@ -0,0 +1,28 @@
++/* 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 nsCommonRegistry_h__
++#define nsCommonRegistry_h__
++
++#include "nsIURI.h"
++#include "nsCOMPtr.h"
++
++class nsMIMEInfoBase;
++
++class nsCommonRegistry
++{
++ public:
++ static bool HandlerExists(const char *aProtocolScheme);
++
++ static nsresult LoadURL(nsIURI *aURL);
++
++ static void GetAppDescForScheme(const nsACString& aScheme,
++ nsAString& aDesc);
++
++ static already_AddRefed<nsMIMEInfoBase> GetFromExtension(const nsACString& aFileExt);
++
++ static already_AddRefed<nsMIMEInfoBase> 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
+--- /dev/null
++++ b/uriloader/exthandler/unix/nsKDERegistry.cpp
+@@ -0,0 +1,89 @@
++/* -*- 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/. */
++
++#include "mozilla/StaticPrefs_browser.h"
++#include "nsKDERegistry.h"
++#include "prlink.h"
++#include "prmem.h"
++#include "nsString.h"
++#include "nsMIMEInfoUnix.h"
++#include "nsKDEUtils.h"
++
++/* static */ bool
++nsKDERegistry::HandlerExists(const char *aProtocolScheme)
++{
++ nsTArray<nsCString> command;
++ command.AppendElement( "HANDLEREXISTS"_ns );
++ command.AppendElement( nsAutoCString( aProtocolScheme ));
++ return nsKDEUtils::command( command );
++}
++
++/* static */ nsresult
++nsKDERegistry::LoadURL(nsIURI *aURL)
++{
++ nsTArray<nsCString> 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;
++}
++
++/* static */ void
++nsKDERegistry::GetAppDescForScheme(const nsACString& aScheme,
++ nsAString& aDesc)
++{
++ nsTArray<nsCString> command;
++ command.AppendElement( "GETAPPDESCFORSCHEME"_ns );
++ command.AppendElement( aScheme );
++ nsTArray<nsCString> output;
++ if( nsKDEUtils::command( command, &output ) && output.Length() == 1 )
++ CopyUTF8toUTF16( output[ 0 ], aDesc );
++}
++
++
++/* static */ already_AddRefed<nsMIMEInfoBase>
++nsKDERegistry::GetFromExtension(const nsACString& aFileExt)
++{
++ NS_ASSERTION(aFileExt[0] != '.', "aFileExt shouldn't start with a dot");
++ nsTArray<nsCString> command;
++ command.AppendElement( "GETFROMEXTENSION"_ns );
++ command.AppendElement( aFileExt );
++ return GetFromHelper( command );
++}
++
++/* static */ already_AddRefed<nsMIMEInfoBase>
++nsKDERegistry::GetFromType(const nsACString& aMIMEType)
++{
++ nsTArray<nsCString> command;
++ command.AppendElement( "GETFROMTYPE"_ns );
++ command.AppendElement( aMIMEType );
++ return GetFromHelper( command );
++}
++
++/* static */ already_AddRefed<nsMIMEInfoBase>
++nsKDERegistry::GetFromHelper(const nsTArray<nsCString>& command)
++{
++ nsTArray<nsCString> output;
++ if( nsKDEUtils::command( command, &output ) && output.Length() == 3 )
++ {
++ nsCString mimetype = output[ 0 ];
++ RefPtr<nsMIMEInfoUnix> 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;
++}
+diff --git a/uriloader/exthandler/unix/nsKDERegistry.h b/uriloader/exthandler/unix/nsKDERegistry.h
+new file mode 100644
+index 000000000000..5b07eebc6d62
+--- /dev/null
++++ b/uriloader/exthandler/unix/nsKDERegistry.h
+@@ -0,0 +1,34 @@
++/* 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 nsKDERegistry_h__
++#define nsKDERegistry_h__
++
++#include "nsIURI.h"
++#include "nsCOMPtr.h"
++#include "nsTArray.h"
++
++class nsMIMEInfoBase;
++//class nsAutoCString;
++//class nsCString;
++
++class nsKDERegistry
++{
++ public:
++ static bool HandlerExists(const char *aProtocolScheme);
++
++ static nsresult LoadURL(nsIURI *aURL);
++
++ static void GetAppDescForScheme(const nsACString& aScheme,
++ nsAString& aDesc);
++
++ static already_AddRefed<nsMIMEInfoBase> GetFromExtension(const nsACString& aFileExt);
++
++ static already_AddRefed<nsMIMEInfoBase> GetFromType(const nsACString& aMIMEType);
++ private:
++ static already_AddRefed<nsMIMEInfoBase> GetFromHelper(const nsTArray<nsCString>& command);
++
++};
++
++#endif //nsKDERegistry_h__
+diff --git a/uriloader/exthandler/unix/nsMIMEInfoUnix.cpp b/uriloader/exthandler/unix/nsMIMEInfoUnix.cpp
+index 7cbefcce3e94..84083348c8f1 100644
+--- a/uriloader/exthandler/unix/nsMIMEInfoUnix.cpp
++++ b/uriloader/exthandler/unix/nsMIMEInfoUnix.cpp
+@@ -5,16 +5,19 @@
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+ #include "nsMIMEInfoUnix.h"
+-#include "nsGNOMERegistry.h"
++#include "nsCommonRegistry.h"
+ #include "nsIGIOService.h"
+ #include "nsNetCID.h"
+ #include "nsIIOService.h"
+ #ifdef MOZ_ENABLE_DBUS
+ # include "nsDBusHandlerApp.h"
+ #endif
++#if defined(XP_UNIX) && !defined(XP_MACOSX)
++#include "nsKDEUtils.h"
++#endif
+
+ nsresult nsMIMEInfoUnix::LoadUriInternal(nsIURI* aURI) {
+- return nsGNOMERegistry::LoadURL(aURI);
++ return nsCommonRegistry::LoadURL(aURI);
+ }
+
+ NS_IMETHODIMP
+@@ -27,15 +30,15 @@ nsMIMEInfoUnix::GetHasDefaultHandler(bool* _retval) {
+ *_retval = false;
+
+ if (mClass == eProtocolInfo) {
+- *_retval = nsGNOMERegistry::HandlerExists(mSchemeOrType.get());
++ *_retval = nsCommonRegistry::HandlerExists(mSchemeOrType.get());
+ } else {
+ RefPtr<nsMIMEInfoBase> mimeInfo =
+- nsGNOMERegistry::GetFromType(mSchemeOrType);
++ nsCommonRegistry::GetFromType(mSchemeOrType);
+ if (!mimeInfo) {
+ nsAutoCString ext;
+ nsresult rv = GetPrimaryExtension(ext);
+ if (NS_SUCCEEDED(rv)) {
+- mimeInfo = nsGNOMERegistry::GetFromExtension(ext);
++ mimeInfo = nsCommonRegistry::GetFromExtension(ext);
+ }
+ }
+ if (mimeInfo) *_retval = true;
+@@ -55,6 +58,23 @@ nsresult nsMIMEInfoUnix::LaunchDefaultWithFile(nsIFile* aFile) {
+ nsAutoCString nativePath;
+ aFile->GetNativePath(nativePath);
+
++ if( nsKDEUtils::kdeSupport()) {
++ bool supports;
++ if( NS_SUCCEEDED( GetHasDefaultHandler( &supports )) && supports ) {
++ nsTArray<nsCString> command;
++ 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;
++
++ return LaunchWithIProcess(mDefaultApplication, nativePath);
++ }
++
+ nsCOMPtr<nsIGIOService> 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
+--- a/uriloader/exthandler/unix/nsOSHelperAppService.cpp
++++ b/uriloader/exthandler/unix/nsOSHelperAppService.cpp
+@@ -10,7 +10,7 @@
+ #include "nsOSHelperAppService.h"
+ #include "nsMIMEInfoUnix.h"
+ #ifdef MOZ_WIDGET_GTK
+-# include "nsGNOMERegistry.h"
++# include "nsCommonRegistry.h"
+ # ifdef MOZ_BUILD_APP_IS_BROWSER
+ # include "nsIToolkitShellService.h"
+ # include "nsIGNOMEShellService.h"
+@@ -1030,7 +1030,7 @@ nsresult nsOSHelperAppService::OSProtocolHandlerExists(
+ if (!XRE_IsContentProcess()) {
+ #ifdef MOZ_WIDGET_GTK
+ // Check the GNOME registry for a protocol handler
+- *aHandlerExists = nsGNOMERegistry::HandlerExists(aProtocolScheme);
++ *aHandlerExists = nsCommonRegistry::HandlerExists(aProtocolScheme);
+ #else
+ *aHandlerExists = false;
+ #endif
+@@ -1050,7 +1050,7 @@ nsresult nsOSHelperAppService::OSProtocolHandlerExists(
+ NS_IMETHODIMP nsOSHelperAppService::GetApplicationDescription(
+ const nsACString& aScheme, nsAString& _retval) {
+ #ifdef MOZ_WIDGET_GTK
+- nsGNOMERegistry::GetAppDescForScheme(aScheme, _retval);
++ nsCommonRegistry::GetAppDescForScheme(aScheme, _retval);
+ return _retval.IsEmpty() ? NS_ERROR_NOT_AVAILABLE : NS_OK;
+ #else
+ return NS_ERROR_NOT_AVAILABLE;
+@@ -1153,7 +1153,7 @@ already_AddRefed<nsMIMEInfoBase> nsOSHelperAppService::GetFromExtension(
+ #ifdef MOZ_WIDGET_GTK
+ LOG(("Looking in GNOME registry\n"));
+ RefPtr<nsMIMEInfoBase> gnomeInfo =
+- nsGNOMERegistry::GetFromExtension(aFileExt);
++ nsCommonRegistry::GetFromExtension(aFileExt);
+ if (gnomeInfo) {
+ LOG(("Got MIMEInfo from GNOME registry\n"));
+ return gnomeInfo.forget();
+@@ -1266,7 +1266,7 @@ already_AddRefed<nsMIMEInfoBase> nsOSHelperAppService::GetFromType(
+
+ #ifdef MOZ_WIDGET_GTK
+ if (handler.IsEmpty()) {
+- RefPtr<nsMIMEInfoBase> gnomeInfo = nsGNOMERegistry::GetFromType(aMIMEType);
++ RefPtr<nsMIMEInfoBase> gnomeInfo = nsCommonRegistry::GetFromType(aMIMEType);
+ if (gnomeInfo) {
+ LOG(
+ ("Got MIMEInfo from GNOME registry without extensions; setting them "
+diff --git a/widget/gtk/moz.build b/widget/gtk/moz.build
+index bf64f7ebdc65..f94b4e017e5e 100644
+--- a/widget/gtk/moz.build
++++ b/widget/gtk/moz.build
+@@ -169,6 +169,7 @@ LOCAL_INCLUDES += [
+ "/layout/xul",
+ "/other-licenses/atk-1.0",
+ "/third_party/cups/include",
++ "/toolkit/xre",
+ "/widget",
+ "/widget/headless",
+ ]
+diff --git a/widget/gtk/nsFilePicker.cpp b/widget/gtk/nsFilePicker.cpp
+index c73130496ae7..9e7b9aed56f1 100644
+--- a/widget/gtk/nsFilePicker.cpp
++++ b/widget/gtk/nsFilePicker.cpp
+@@ -5,6 +5,7 @@
+
+ #include <dlfcn.h>
+ #include <gtk/gtk.h>
++#include <gdk/gdkx.h>
+ #include <sys/types.h>
+ #include <sys/stat.h>
+ #include <unistd.h>
+@@ -27,6 +28,8 @@
+ #include "WidgetUtilsGtk.h"
+
+ #include "nsFilePicker.h"
++#include "nsKDEUtils.h"
++#include "nsURLHelper.h"
+
+ #undef LOG
+ #ifdef MOZ_LOGGING
+@@ -241,7 +244,9 @@ 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;
+ }
+
+ nsAutoCString filter, name;
+@@ -351,6 +356,29 @@ 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()) {
++ 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);
++
++ return NS_OK;
++ }
++
+ NS_ConvertUTF16toUTF8 title(mTitle);
+
+ GtkWindow* parent_widget =
+@@ -580,6 +608,234 @@ 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;
++ }
++
++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;
++ }
++ }
++
++ nsAutoCString directory;
++ if (mDisplayDirectory) {
++ mDisplayDirectory->GetNativePath(directory);
++ } else if (mPrevDisplayDirectory) {
++ mPrevDisplayDirectory->GetNativePath(directory);
++ }
++
++ nsAutoCString startdir;
++ if (!directory.IsEmpty()) {
++ startdir = directory;
++ }
++ if (mMode == nsIFilePicker::modeSave) {
++ if( !startdir.IsEmpty())
++ {
++ startdir += "/";
++ startdir += ToNewUTF8String(mDefault);
++ }
++ 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 );
++ }
++ }
++
++ nsTArray<nsCString> 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<nsCString> 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<nsIFile> 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<nsIFile> 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<nsIFile> file;
++ GetFile(getter_AddRefs(file));
++ if (file) {
++ nsCOMPtr<nsIFile> dir;
++ file->GetParent(getter_AddRefs(dir));
++ nsCOMPtr<nsIFile> localDir(do_QueryInterface(dir));
++ if (localDir) {
++ localDir.swap(mPrevDisplayDirectory);
++ }
++ }
++ if (mMode == nsIFilePicker::modeSave)
++ {
++ nsCOMPtr<nsIFile> 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(PRInt16 *aReturn)
++ {
++ NS_ENSURE_ARG_POINTER(aReturn);
++
++ nsCString title;
++ title.Adopt(ToNewUTF8String(mTitle));
++
++ nsTArray<nsCString> 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<nsCString> 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
+--- a/widget/gtk/nsFilePicker.h
++++ b/widget/gtk/nsFilePicker.h
+@@ -72,6 +72,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 );
++
+ 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
+--- a/xpcom/components/ManifestParser.cpp
++++ b/xpcom/components/ManifestParser.cpp
+@@ -43,6 +43,7 @@
+ #include "nsIScriptError.h"
+ #include "nsIXULAppInfo.h"
+ #include "nsIXULRuntime.h"
++#include "nsKDEUtils.h"
+
+ using namespace mozilla;
+
+@@ -402,6 +403,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;
++ constexpr auto kDesktop = u"desktop"_ns;
+ 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,
+ }
+
+ nsAutoString osVersion;
++ nsAutoString desktop;
+ #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,
+ nsTextFormatter::ssprintf(osVersion, u"%ld.%ld", info.dwMajorVersion,
+ info.dwMinorVersion);
+ }
++ desktop = u"win"_ns;
+ # pragma warning(pop)
+ #elif defined(MOZ_WIDGET_COCOA)
+ SInt32 majorVersion = nsCocoaFeatures::macOSVersionMajor();
+ SInt32 minorVersion = nsCocoaFeatures::macOSVersionMinor();
+ nsTextFormatter::ssprintf(osVersion, u"%ld.%ld", majorVersion, minorVersion);
++ desktop = u"macosx"_ns);
+ #elif defined(MOZ_WIDGET_GTK)
+ nsTextFormatter::ssprintf(osVersion, u"%ld.%ld", gtk_major_version,
+ gtk_minor_version);
++ 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);
+ isTablet = java::GeckoAppShell::IsTablet();
+ }
++ desktop = u"android"_ns;
+ #endif
+
+ if (XRE_IsContentProcess()) {
+@@ -588,6 +595,7 @@ void ParseManifest(NSLocationType aType, FileLocation& aFile, char* aBuf,
+ : eUnspecified;
+ #endif
+ int flags = 0;
++ TriState stDesktop = eUnspecified;
+
+ while ((token = nsCRT::strtok(whitespace, kWhitespace, &whitespace)) &&
+ ok) {
+@@ -597,6 +605,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) ||
++ CheckStringFlag(kDesktop, wtoken, desktop, stDesktop) ||
+ 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,
+ }
+
+ if (!ok || stApp == eBad || stAppVersion == eBad ||
+- stGeckoVersion == eBad || stOs == eBad || stOsVersion == 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
+--- a/xpcom/components/moz.build
++++ b/xpcom/components/moz.build
+@@ -71,6 +71,7 @@ LOCAL_INCLUDES += [
+ "/js/xpconnect/loader",
+ "/layout/build",
+ "/modules/libjar",
++ "/toolkit/xre",
+ ]
+
+ if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
+diff --git a/xpcom/io/nsLocalFileUnix.cpp b/xpcom/io/nsLocalFileUnix.cpp
+index 410fcc19e435..d7c976e0e4b2 100644
+--- a/xpcom/io/nsLocalFileUnix.cpp
++++ b/xpcom/io/nsLocalFileUnix.cpp
+@@ -59,6 +59,7 @@
+
+ #ifdef MOZ_WIDGET_GTK
+ # include "nsIGIOService.h"
++# include "nsKDEUtils.h"
+ #endif
+
+ #ifdef MOZ_WIDGET_COCOA
+@@ -2102,10 +2103,19 @@ nsLocalFile::Reveal() {
+ }
+
+ #ifdef MOZ_WIDGET_GTK
++ nsAutoCString url;
+ nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
+- if (!giovfs) {
+- return NS_ERROR_FAILURE;
++ url = mPath;
++ if(nsKDEUtils::kdeSupport()) {
++ nsTArray<nsCString> command;
++ command.AppendElement( "REVEAL"_ns );
++ command.AppendElement( mPath );
++ return nsKDEUtils::command( command ) ? NS_OK : 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() {
+ }
+
+ #ifdef MOZ_WIDGET_GTK
++ if( nsKDEUtils::kdeSupport()) {
++ nsTArray<nsCString> command;
++ command.AppendElement( "OPEN"_ns );
++ command.AppendElement( mPath );
++ return nsKDEUtils::command( command ) ? NS_OK : NS_ERROR_FAILURE;
++ }
++
+ nsCOMPtr<nsIGIOService> 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
new file mode 100644
index 0000000..9f9d487
--- /dev/null
+++ b/waterfox-g/debian/patches/global_menu.patch
@@ -0,0 +1,5382 @@
+Source: https://bazaar.launchpad.net/~mozillateam/firefox/firefox.bionic/view/1484/debian/patches/unity-menubar.patch
+Author: Olivier Tilloy <olivier.tilloy@canonical.com>
+
+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 &amp;&amp;
++#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
+ <menubar id="placesMenu">
+ <menu class="menu-iconic" data-l10n-id="places-organize-button"
++ _moz-menubarkeeplocal="true"
+ #endif
+ id="organizeButton">
+ <menupopup id="organizeButtonPopup">
+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<nsINativeMenuService> nms =
++ do_GetService("@mozilla.org/widget/nativemenuservice;1");
++ if (!nms) {
++ return;
++ }
++
++ nms->CreateNativeMenuBar(aParent, aMenuBarElement);
+ }
+
+ already_AddRefed<NativeMenu> 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 <glib.h>
++#include <gdk/gdk.h>
++
++#define DBUSMENU_GLIB_FUNCTIONS \
++ FUNC(dbusmenu_menuitem_child_add_position, gboolean, (DbusmenuMenuitem *mi, DbusmenuMenuitem *child, guint position)) \
++ FUNC(dbusmenu_menuitem_child_append, gboolean, (DbusmenuMenuitem *mi, DbusmenuMenuitem *child)) \
++ FUNC(dbusmenu_menuitem_child_delete, gboolean, (DbusmenuMenuitem *mi, DbusmenuMenuitem *child)) \
++ FUNC(dbusmenu_menuitem_get_children, GList*, (DbusmenuMenuitem *mi)) \
++ FUNC(dbusmenu_menuitem_new, DbusmenuMenuitem*, (void)) \
++ FUNC(dbusmenu_menuitem_property_get, const gchar*, (DbusmenuMenuitem *mi, const gchar *property)) \
++ FUNC(dbusmenu_menuitem_property_get_bool, gboolean, (DbusmenuMenuitem *mi, const gchar *property)) \
++ FUNC(dbusmenu_menuitem_property_remove, void, (DbusmenuMenuitem *mi, const gchar *property)) \
++ FUNC(dbusmenu_menuitem_property_set, gboolean, (DbusmenuMenuitem *mi, const gchar *property, const gchar *value)) \
++ FUNC(dbusmenu_menuitem_property_set_bool, gboolean, (DbusmenuMenuitem *mi, const gchar *property, const gboolean value)) \
++ FUNC(dbusmenu_menuitem_property_set_int, gboolean, (DbusmenuMenuitem *mi, const gchar *property, const gint value)) \
++ FUNC(dbusmenu_menuitem_show_to_user, void, (DbusmenuMenuitem *mi, guint timestamp)) \
++ FUNC(dbusmenu_menuitem_take_children, GList*, (DbusmenuMenuitem *mi)) \
++ FUNC(dbusmenu_server_new, DbusmenuServer*, (const gchar *object)) \
++ FUNC(dbusmenu_server_set_root, void, (DbusmenuServer *server, DbusmenuMenuitem *root)) \
++ FUNC(dbusmenu_server_set_status, void, (DbusmenuServer *server, DbusmenuStatus status))
++
++#define DBUSMENU_GTK_FUNCTIONS \
++ FUNC(dbusmenu_menuitem_property_set_image, gboolean, (DbusmenuMenuitem *menuitem, const gchar *property, const GdkPixbuf *data)) \
++ FUNC(dbusmenu_menuitem_property_set_shortcut, gboolean, (DbusmenuMenuitem *menuitem, guint key, GdkModifierType modifier))
++
++typedef struct _DbusmenuMenuitem DbusmenuMenuitem;
++typedef struct _DbusmenuServer DbusmenuServer;
++
++enum DbusmenuStatus {
++ DBUSMENU_STATUS_NORMAL,
++ DBUSMENU_STATUS_NOTICE
++};
++
++#define DBUSMENU_MENUITEM_CHILD_DISPLAY_SUBMENU "submenu"
++#define DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY "children-display"
++#define DBUSMENU_MENUITEM_PROP_ENABLED "enabled"
++#define DBUSMENU_MENUITEM_PROP_ICON_DATA "icon-data"
++#define DBUSMENU_MENUITEM_PROP_LABEL "label"
++#define DBUSMENU_MENUITEM_PROP_SHORTCUT "shortcut"
++#define DBUSMENU_MENUITEM_PROP_TYPE "type"
++#define DBUSMENU_MENUITEM_PROP_TOGGLE_STATE "toggle-state"
++#define DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE "toggle-type"
++#define DBUSMENU_MENUITEM_PROP_VISIBLE "visible"
++#define DBUSMENU_MENUITEM_SIGNAL_ABOUT_TO_SHOW "about-to-show"
++#define DBUSMENU_MENUITEM_SIGNAL_EVENT "event"
++#define DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED "item-activated"
++#define DBUSMENU_MENUITEM_TOGGLE_CHECK "checkmark"
++#define DBUSMENU_MENUITEM_TOGGLE_RADIO "radio"
++#define DBUSMENU_MENUITEM_TOGGLE_STATE_CHECKED 1
++#define DBUSMENU_MENUITEM_TOGGLE_STATE_UNCHECKED 0
++#define DBUSMENU_SERVER_PROP_DBUS_OBJECT "dbus-object"
++
++class nsDbusmenuFunctions
++{
++public:
++ 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 <glib-object.h>
++
++#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<nsMenu *>(mWeakMenu.get())->HandleContentInserted(mContainer,
++ mChild,
++ mPrevSibling);
++ return NS_OK;
++ }
++
++private:
++ nsWeakMenuObject mWeakMenu;
++
++ nsCOMPtr<nsIContent> mContainer;
++ nsCOMPtr<nsIContent> mChild;
++ nsCOMPtr<nsIContent> 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<nsMenu *>(mWeakMenu.get())->HandleContentRemoved(mContainer,
++ mChild);
++ return NS_OK;
++ }
++
++private:
++ nsWeakMenuObject mWeakMenu;
++
++ nsCOMPtr<nsIContent> mContainer;
++ nsCOMPtr<nsIContent> 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<nsMenu *>(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<nsMenu *>(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<nsIContent> 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<nsMenuObject> child = CreateChild(childContent);
++
++ if (!child) {
++ continue;
++ }
++
++ AppendChild(std::move(child));
++ }
++}
++
++void
++nsMenu::InitializePopup()
++{
++ nsCOMPtr<nsIContent> 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<nsMenuObject> aChild,
++ nsIContent *aPrevSibling)
++{
++ if (!IsInBatchedUpdate()) {
++ EnsureNoPlaceholderItem();
++ }
++
++ nsMenuContainer::InsertChildAfter(std::move(aChild), aPrevSibling,
++ !IsInBatchedUpdate());
++ StructureMutated();
++}
++
++void
++nsMenu::AppendChild(UniquePtr<nsMenuObject> 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<nsMenuObject> 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::PropertyFlags>(
++ 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<const ComputedStyle> style = GetComputedStyle();
++ UpdateVisibility(style);
++ } else if (aAttribute == nsGkAtoms::image) {
++ RefPtr<const ComputedStyle> 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<DbusmenuMenuitem *>(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<DbusmenuMenuitem *>(nextNativeChild->data);
++
++ if (NS_SUCCEEDED(child->AdoptNativeData(data))) {
++ nextNativeChild = nextNativeChild->next;
++ }
++ }
++
++ // There wasn't a suitable one available, so create a new one.
++ // At this point, we modify the native menu structure.
++ if (!child->GetNativeData()) {
++ child->CreateNativeData();
++ MOZ_ALWAYS_TRUE(
++ dbusmenu_menuitem_child_add_position(GetNativeData(),
++ child->GetNativeData(),
++ i));
++ }
++ }
++ }
++
++ while (nextNativeChild) {
++ DbusmenuMenuitem *data =
++ static_cast<DbusmenuMenuitem *>(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<nsMenu *>(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 <glib.h>
++
++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<nsMenuObject> aChild,
++ nsIContent *aPrevSibling);
++ void AppendChild(mozilla::UniquePtr<nsMenuObject> 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<nsIContent> mPopupContent;
++
++ nsCOMPtr<nsITimer> 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 <gdk/gdk.h>
++#include <gdk/gdkx.h>
++#include <glib.h>
++#include <glib-object.h>
++
++#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<nsMenuBar *>(mWeakMenuBar.get())->HandleContentInserted(mChild,
++ mPrevSibling);
++ return NS_OK;
++ }
++
++private:
++ nsWeakMenuObject mWeakMenuBar;
++
++ nsCOMPtr<nsIContent> mChild;
++ nsCOMPtr<nsIContent> 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<nsMenuBar *>(mWeakMenuBar.get())->HandleContentRemoved(mChild);
++ return NS_OK;
++ }
++
++private:
++ nsWeakMenuObject mWeakMenuBar;
++
++ nsCOMPtr<nsIContent> 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<dom::KeyboardEvent> 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<GdkWindow *>(
++ aParent->GetNativeData(NS_NATIVE_WINDOW));
++ if (!gdkWin) {
++ return NS_ERROR_FAILURE;
++ }
++
++ gpointer user_data = nullptr;
++ gdk_window_get_user_data(gdkWin, &user_data);
++ if (!user_data || !GTK_IS_CONTAINER(user_data)) {
++ return NS_ERROR_FAILURE;
++ }
++
++ mTopLevel = gtk_widget_get_toplevel(GTK_WIDGET(user_data));
++ if (!mTopLevel) {
++ return NS_ERROR_FAILURE;
++ }
++
++ g_object_ref(mTopLevel);
++
++ nsAutoCString path;
++ path.Append("/com/canonical/menu/"_ns);
++ char xid[10];
++ sprintf(xid, "%X", static_cast<uint32_t>(
++ GDK_WINDOW_XID(gtk_widget_get_window(mTopLevel))));
++ path.Append(xid);
++
++ mServer = dbusmenu_server_new(path.get());
++ if (!mServer) {
++ return NS_ERROR_FAILURE;
++ }
++
++ CreateNativeData();
++ if (!GetNativeData()) {
++ return NS_ERROR_FAILURE;
++ }
++
++ dbusmenu_server_set_root(mServer, GetNativeData());
++
++ mEventListener = new 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<nsMenuObject> 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<ModifierFlags>(0);
++
++ if (aEvent->AltKey()) {
++ modifiers = static_cast<ModifierFlags>(modifiers | eModifierAlt);
++ }
++
++ if (aEvent->ShiftKey()) {
++ modifiers = static_cast<ModifierFlags>(modifiers | eModifierShift);
++ }
++
++ if (aEvent->CtrlKey()) {
++ modifiers = static_cast<ModifierFlags>(modifiers | eModifierCtrl);
++ }
++
++ if (aEvent->MetaKey()) {
++ modifiers = static_cast<ModifierFlags>(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<nsMenu *>(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<nsMenuObject> 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>
++nsMenuBar::Create(nsIWidget *aParent, nsIContent *aMenuBarNode)
++{
++ UniquePtr<nsMenuBar> 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<uint32_t>(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 <gtk/gtk.h>
++
++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<nsMenuBar> 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<mozilla::dom::Document> mDocument;
++ RefPtr<DocEventListener> 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<nsMenuObject> (*nsMenuObjectConstructor)(nsMenuContainer*,
++ nsIContent*);
++
++template<class T>
++static UniquePtr<nsMenuObject> CreateMenuObject(nsMenuContainer *aContainer,
++ nsIContent *aContent)
++{
++ return UniquePtr<T>(new T(aContainer, aContent));
++}
++
++static nsMenuObjectConstructor
++GetMenuObjectConstructor(nsIContent *aContent)
++{
++ if (aContent->IsXULElement(nsGkAtoms::menuitem)) {
++ return CreateMenuObject<nsMenuItem>;
++ } else if (aContent->IsXULElement(nsGkAtoms::menu)) {
++ return CreateMenuObject<nsMenu>;
++ } else if (aContent->IsXULElement(nsGkAtoms::menuseparator)) {
++ return CreateMenuObject<nsMenuSeparator>;
++ }
++
++ 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<nsMenuObject>
++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<nsMenuObject> 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<nsMenuObject> 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<nsMenuObject> 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<mozilla::UniquePtr<nsMenuObject> > ChildTArray;
++
++ // Determine if this container is being displayed on screen. Must be
++ // implemented by subclasses. Must return true if the container is
++ // in the fully open state, or false otherwise
++ virtual bool IsBeingDisplayed() const = 0;
++
++ // Determine if this container will be rebuilt the next time it opens.
++ // Returns false by default but can be overridden by subclasses
++ virtual bool NeedsRebuild() const;
++
++ // Return the first previous sibling that is of a type supported by the
++ // menu system
++ static nsIContent* GetPreviousSupportedSibling(nsIContent *aContent);
++
++ static const ChildTArray::index_type NoIndex;
++
++protected:
++ nsMenuContainer(nsMenuContainer *aParent, nsIContent *aContent);
++ nsMenuContainer(nsNativeMenuDocListener *aListener, nsIContent *aContent);
++
++ // Create a new child element for the specified content node
++ mozilla::UniquePtr<nsMenuObject> 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<nsMenuObject> aChild,
++ nsIContent *aPrevSibling,
++ bool aUpdateNative = true);
++
++ void AppendChild(mozilla::UniquePtr<nsMenuObject> 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 <gdk/gdk.h>
++#include <gdk/gdkkeysyms.h>
++#include <gdk/gdkkeysyms-compat.h>
++#include <gdk/gdkx.h>
++#include <gtk/gtk.h>
++
++#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 <gdk/gdkkeysyms.h>
++//
++static const KeyPair gKeyPairs[] = {
++ { NS_VK_CANCEL, GDK_Cancel },
++ { NS_VK_BACK, GDK_BackSpace },
++ { NS_VK_TAB, GDK_Tab },
++ { NS_VK_TAB, GDK_ISO_Left_Tab },
++ { NS_VK_CLEAR, GDK_Clear },
++ { NS_VK_RETURN, GDK_Return },
++ { NS_VK_SHIFT, GDK_Shift_L },
++ { NS_VK_SHIFT, GDK_Shift_R },
++ { NS_VK_SHIFT, GDK_Shift_Lock },
++ { NS_VK_CONTROL, GDK_Control_L },
++ { NS_VK_CONTROL, GDK_Control_R },
++ { NS_VK_ALT, GDK_Alt_L },
++ { NS_VK_ALT, GDK_Alt_R },
++ { NS_VK_META, GDK_Meta_L },
++ { NS_VK_META, GDK_Meta_R },
++
++ // Assume that Super or Hyper is always mapped to physical Win key.
++ { NS_VK_WIN, GDK_Super_L },
++ { NS_VK_WIN, GDK_Super_R },
++ { NS_VK_WIN, GDK_Hyper_L },
++ { NS_VK_WIN, GDK_Hyper_R },
++
++ // GTK's AltGraph key is similar to Mac's Option (Alt) key. However,
++ // unfortunately, browsers on Mac are using NS_VK_ALT for it even though
++ // it's really different from Alt key on Windows.
++ // On the other hand, GTK's AltGrapsh keys are really different from
++ // Alt key. However, there is no AltGrapsh key on Windows. On Windows,
++ // both Ctrl and Alt keys are pressed internally when AltGr key is pressed.
++ // For some languages' users, AltGraph key is important, so, web
++ // applications on such locale may want to know AltGraph key press.
++ // Therefore, we should map AltGr keycode for them only on GTK.
++ { NS_VK_ALTGR, GDK_ISO_Level3_Shift },
++ { NS_VK_ALTGR, GDK_ISO_Level5_Shift },
++ // We assume that Mode_switch is always used for level3 shift.
++ { NS_VK_ALTGR, GDK_Mode_switch },
++
++ { NS_VK_PAUSE, GDK_Pause },
++ { NS_VK_CAPS_LOCK, GDK_Caps_Lock },
++ { NS_VK_KANA, GDK_Kana_Lock },
++ { NS_VK_KANA, GDK_Kana_Shift },
++ { NS_VK_HANGUL, GDK_Hangul },
++ // { NS_VK_JUNJA, GDK_XXX },
++ // { NS_VK_FINAL, GDK_XXX },
++ { NS_VK_HANJA, GDK_Hangul_Hanja },
++ { NS_VK_KANJI, GDK_Kanji },
++ { NS_VK_ESCAPE, GDK_Escape },
++ { NS_VK_CONVERT, GDK_Henkan },
++ { NS_VK_NONCONVERT, GDK_Muhenkan },
++ // { NS_VK_ACCEPT, GDK_XXX },
++ // { NS_VK_MODECHANGE, GDK_XXX },
++ { NS_VK_SPACE, GDK_space },
++ { NS_VK_PAGE_UP, GDK_Page_Up },
++ { NS_VK_PAGE_DOWN, GDK_Page_Down },
++ { NS_VK_END, GDK_End },
++ { NS_VK_HOME, GDK_Home },
++ { NS_VK_LEFT, GDK_Left },
++ { NS_VK_UP, GDK_Up },
++ { NS_VK_RIGHT, GDK_Right },
++ { NS_VK_DOWN, GDK_Down },
++ { NS_VK_SELECT, GDK_Select },
++ { NS_VK_PRINT, GDK_Print },
++ { NS_VK_EXECUTE, GDK_Execute },
++ { NS_VK_PRINTSCREEN, GDK_Print },
++ { NS_VK_INSERT, GDK_Insert },
++ { NS_VK_DELETE, GDK_Delete },
++ { NS_VK_HELP, GDK_Help },
++
++ // keypad keys
++ { NS_VK_LEFT, GDK_KP_Left },
++ { NS_VK_RIGHT, GDK_KP_Right },
++ { NS_VK_UP, GDK_KP_Up },
++ { NS_VK_DOWN, GDK_KP_Down },
++ { NS_VK_PAGE_UP, GDK_KP_Page_Up },
++ // Not sure what these are
++ //{ NS_VK_, GDK_KP_Prior },
++ //{ NS_VK_, GDK_KP_Next },
++ { NS_VK_CLEAR, GDK_KP_Begin }, // Num-unlocked 5
++ { NS_VK_PAGE_DOWN, GDK_KP_Page_Down },
++ { NS_VK_HOME, GDK_KP_Home },
++ { NS_VK_END, GDK_KP_End },
++ { NS_VK_INSERT, GDK_KP_Insert },
++ { NS_VK_DELETE, GDK_KP_Delete },
++ { NS_VK_RETURN, GDK_KP_Enter },
++
++ { NS_VK_NUM_LOCK, GDK_Num_Lock },
++ { NS_VK_SCROLL_LOCK,GDK_Scroll_Lock },
++
++ // Function keys
++ { NS_VK_F1, GDK_F1 },
++ { NS_VK_F2, GDK_F2 },
++ { NS_VK_F3, GDK_F3 },
++ { NS_VK_F4, GDK_F4 },
++ { NS_VK_F5, GDK_F5 },
++ { NS_VK_F6, GDK_F6 },
++ { NS_VK_F7, GDK_F7 },
++ { NS_VK_F8, GDK_F8 },
++ { NS_VK_F9, GDK_F9 },
++ { NS_VK_F10, GDK_F10 },
++ { NS_VK_F11, GDK_F11 },
++ { NS_VK_F12, GDK_F12 },
++ { NS_VK_F13, GDK_F13 },
++ { NS_VK_F14, GDK_F14 },
++ { NS_VK_F15, GDK_F15 },
++ { NS_VK_F16, GDK_F16 },
++ { NS_VK_F17, GDK_F17 },
++ { NS_VK_F18, GDK_F18 },
++ { NS_VK_F19, GDK_F19 },
++ { NS_VK_F20, GDK_F20 },
++ { NS_VK_F21, GDK_F21 },
++ { NS_VK_F22, GDK_F22 },
++ { NS_VK_F23, GDK_F23 },
++ { NS_VK_F24, GDK_F24 },
++
++ // context menu key, keysym 0xff67, typically keycode 117 on 105-key (Microsoft)
++ // x86 keyboards, located between right 'Windows' key and right Ctrl key
++ { NS_VK_CONTEXT_MENU, GDK_Menu },
++ { NS_VK_SLEEP, GDK_Sleep },
++
++ { NS_VK_ATTN, GDK_3270_Attn },
++ { NS_VK_CRSEL, GDK_3270_CursorSelect },
++ { NS_VK_EXSEL, GDK_3270_ExSelect },
++ { NS_VK_EREOF, GDK_3270_EraseEOF },
++ { NS_VK_PLAY, GDK_3270_Play },
++ //{ NS_VK_ZOOM, GDK_XXX },
++ { NS_VK_PA1, GDK_3270_PA1 },
++};
++
++static guint
++ConvertGeckoKeyNameToGDKKeyval(nsAString& aKeyName)
++{
++ NS_ConvertUTF16toUTF8 keyName(aKeyName);
++ ToUpperCase(keyName); // We want case-insensitive comparison with data
++ // stored as uppercase.
++
++ uint32_t keyCode = 0;
++
++ uint32_t keyNameLength = keyName.Length();
++ const char* keyNameStr = keyName.get();
++ for (uint16_t i = 0; i < ArrayLength(gKeyCodes); ++i) {
++ if (keyNameLength == gKeyCodes[i].strlength &&
++ !nsCRT::strcmp(gKeyCodes[i].str, keyNameStr)) {
++ keyCode = gKeyCodes[i].keycode;
++ break;
++ }
++ }
++
++ // First, try to handle alphanumeric input, not listed in nsKeycodes:
++ // most likely, more letters will be getting typed in than things in
++ // the key list, so we will look through these first.
++
++ if (keyCode >= NS_VK_A && keyCode <= NS_VK_Z) {
++ // gdk and DOM both use the ASCII codes for these keys.
++ return keyCode;
++ }
++
++ // numbers
++ if (keyCode >= NS_VK_0 && keyCode <= NS_VK_9) {
++ // gdk and DOM both use the ASCII codes for these keys.
++ return keyCode - NS_VK_0 + GDK_0;
++ }
++
++ switch (keyCode) {
++ // keys in numpad
++ case NS_VK_MULTIPLY: return GDK_KP_Multiply;
++ case NS_VK_ADD: return GDK_KP_Add;
++ case NS_VK_SEPARATOR: return GDK_KP_Separator;
++ case NS_VK_SUBTRACT: return GDK_KP_Subtract;
++ case NS_VK_DECIMAL: return GDK_KP_Decimal;
++ case NS_VK_DIVIDE: return GDK_KP_Divide;
++ case NS_VK_NUMPAD0: return GDK_KP_0;
++ case NS_VK_NUMPAD1: return GDK_KP_1;
++ case NS_VK_NUMPAD2: return GDK_KP_2;
++ case NS_VK_NUMPAD3: return GDK_KP_3;
++ case NS_VK_NUMPAD4: return GDK_KP_4;
++ case NS_VK_NUMPAD5: return GDK_KP_5;
++ case NS_VK_NUMPAD6: return GDK_KP_6;
++ case NS_VK_NUMPAD7: return GDK_KP_7;
++ case NS_VK_NUMPAD8: return GDK_KP_8;
++ case NS_VK_NUMPAD9: return GDK_KP_9;
++ // other prinable keys
++ case NS_VK_SPACE: return GDK_space;
++ case NS_VK_COLON: return GDK_colon;
++ case NS_VK_SEMICOLON: return GDK_semicolon;
++ case NS_VK_LESS_THAN: return GDK_less;
++ case NS_VK_EQUALS: return GDK_equal;
++ case NS_VK_GREATER_THAN: return GDK_greater;
++ case NS_VK_QUESTION_MARK: return GDK_question;
++ case NS_VK_AT: return GDK_at;
++ case NS_VK_CIRCUMFLEX: return GDK_asciicircum;
++ case NS_VK_EXCLAMATION: return GDK_exclam;
++ case NS_VK_DOUBLE_QUOTE: return GDK_quotedbl;
++ case NS_VK_HASH: return GDK_numbersign;
++ case NS_VK_DOLLAR: return GDK_dollar;
++ case NS_VK_PERCENT: return GDK_percent;
++ case NS_VK_AMPERSAND: return GDK_ampersand;
++ case NS_VK_UNDERSCORE: return GDK_underscore;
++ case NS_VK_OPEN_PAREN: return GDK_parenleft;
++ case NS_VK_CLOSE_PAREN: return GDK_parenright;
++ case NS_VK_ASTERISK: return GDK_asterisk;
++ case NS_VK_PLUS: return GDK_plus;
++ case NS_VK_PIPE: return GDK_bar;
++ case NS_VK_HYPHEN_MINUS: return GDK_minus;
++ case NS_VK_OPEN_CURLY_BRACKET: return GDK_braceleft;
++ case NS_VK_CLOSE_CURLY_BRACKET: return GDK_braceright;
++ case NS_VK_TILDE: return GDK_asciitilde;
++ case NS_VK_COMMA: return GDK_comma;
++ case NS_VK_PERIOD: return GDK_period;
++ case NS_VK_SLASH: return GDK_slash;
++ case NS_VK_BACK_QUOTE: return GDK_grave;
++ case NS_VK_OPEN_BRACKET: return GDK_bracketleft;
++ case NS_VK_BACK_SLASH: return GDK_backslash;
++ case NS_VK_CLOSE_BRACKET: return GDK_bracketright;
++ case NS_VK_QUOTE: return GDK_apostrophe;
++ }
++
++ // misc other things
++ for (uint32_t i = 0; i < ArrayLength(gKeyPairs); ++i) {
++ if (gKeyPairs[i].DOMKeyCode == keyCode) {
++ return gKeyPairs[i].GDKKeyval;
++ }
++ }
++
++ return 0;
++}
++
++class nsMenuItemUncheckSiblingsRunnable final : public Runnable
++{
++public:
++ NS_IMETHODIMP Run()
++ {
++ if (mMenuItem) {
++ static_cast<nsMenuItem *>(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<nsMenuItem *>(user_data);
++ item->Activate(timestamp);
++}
++
++void
++nsMenuItem::Activate(uint32_t aTimestamp)
++{
++ GdkWindow *window = gtk_widget_get_window(MenuBar()->TopLevelWindow());
++ gdk_x11_window_set_user_time(
++ window, std::min(aTimestamp, gdk_x11_get_server_time(window)));
++
++ // We do this to avoid mutating our view of the menu until
++ // after we have finished
++ 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<dom::Event> event =
++ doc->CreateEvent(u"xulcommandevent"_ns,
++ dom::CallerType::System, rv);
++ if (!rv.Failed()) {
++ RefPtr<dom::XULCommandEvent> 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<nsMenu *>(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<nsIContent> 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<GdkModifierType>(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<nsMenuBar *>(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<nsIContent> 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::PropertyFlags>(
++ 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<const ComputedStyle> style = GetComputedStyle();
++ UpdateVisibility(style);
++ } else if (aAttribute == nsGkAtoms::image) {
++ RefPtr<const ComputedStyle> 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 <glib.h>
++
++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<nsIContent> 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 <gdk/gdk.h>
++#include <glib-object.h>
++#include <pango/pango.h>
++
++#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<imgRequestProxy> mImageRequest;
++ nsCOMPtr<nsIURI> 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<imgIContainer> image;
++ mImageRequest->GetImage(getter_AddRefs(image));
++ MOZ_ASSERT(image);
++
++ // Ask the image to decode at its intrinsic size.
++ int32_t width = 0, height = 0;
++ image->GetWidth(&width);
++ image->GetHeight(&height);
++ image->RequestDecodeForSize(nsIntSize(width, height), imgIContainer::FLAG_NONE);
++ return;
++ }
++
++ if (aType == imgINotificationObserver::DECODE_COMPLETE) {
++ mImageRequest->Cancel(NS_BINDING_ABORTED);
++ mImageRequest = nullptr;
++ return;
++ }
++
++ if (aType != imgINotificationObserver::FRAME_COMPLETE) {
++ return;
++ }
++
++ nsCOMPtr<imgIContainer> 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<GdkPixbuf> 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<nsIURI> 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<nsILoadGroup> loadGroup = doc->GetDocumentLoadGroup();
++ RefPtr<imgLoader> loader =
++ nsContentUtils::GetImgLoaderForDocument(doc);
++ if (!loader || !loadGroup) {
++ NS_WARNING("Failed to get loader or load group for image load");
++ return;
++ }
++
++ loader->LoadImage(uri, nullptr, nullptr,
++ 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<const ComputedStyle>
++nsMenuObject::GetComputedStyle()
++{
++ RefPtr<const ComputedStyle> style =
++ nsComputedDOMStyle::GetComputedStyleNoFlush(
++ mContent->AsElement());
++
++ return style.forget();
++}
++
++void
++nsMenuObject::InitializeNativeData()
++{
++}
++
++nsMenuObject::PropertyFlags
++nsMenuObject::SupportedProperties() const
++{
++ return static_cast<nsMenuObject::PropertyFlags>(0);
++}
++
++bool
++nsMenuObject::IsCompatibleWithNativeData(DbusmenuMenuitem *aNativeData) const
++{
++ return true;
++}
++
++void
++nsMenuObject::UpdateContentAttributes()
++{
++}
++
++void
++nsMenuObject::Update(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<PropertyFlags>(1);
++
++ for (uint32_t i = 0; gPropertyStrings[i]; ++i) {
++ if (!(mask & supported)) {
++ dbusmenu_menuitem_property_remove(mNativeData, gPropertyStrings[i]);
++ }
++ mask = static_cast<PropertyFlags>(mask << 1);
++ }
++
++ InitializeNativeData();
++ if (mParent && mParent->IsBeingDisplayed()) {
++ ContainerIsOpening();
++ }
++
++ mListener->RegisterForContentChanges(mContent, this);
++
++ return NS_OK;
++}
++
++void
++nsMenuObject::ContainerIsOpening()
++{
++ MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
++
++ UpdateContentAttributes();
++
++ RefPtr<const ComputedStyle> 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<const mozilla::ComputedStyle> 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<nsIContent> mContent;
++ // mListener is a strong ref for simplicity - someone in the tree needs to
++ // own it, and this only really needs to be the top-level object (as no
++ // children outlives their parent). However, we need to keep it alive until
++ // after running the nsMenuObject destructor for the top-level menu object,
++ // hence the strong ref
++ RefPtr<nsNativeMenuDocListener> mListener;
++ nsMenuContainer *mParent; // [weak]
++ DbusmenuMenuitem *mNativeData; // [strong]
++ RefPtr<nsMenuObjectIconLoader> mIconLoader;
++};
++
++// 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::PropertyFlags>(
++ 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<const ComputedStyle> 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<MutationRecord>())->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<MutationRecord>())->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<MutationRecord>())->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<nsNativeMenuChangeObserver *> 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<nsIContent> mTarget;
++ nsCOMPtr<nsIContent> mChild;
++ nsCOMPtr<nsIContent> mPrevSibling;
++ RefPtr<nsAtom> 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<nsIContent> mRootNode;
++ mozilla::dom::Document *mDocument;
++ nsIContent *mLastSource;
++ nsNativeMenuChangeObserver *mLastTarget;
++ nsTArray<mozilla::UniquePtr<MutationRecord> > mPendingMutations;
++ nsTHashMap<nsPtrHashKey<nsIContent>, nsNativeMenuChangeObserver *> mContentToObserverTable;
++
++ static uint32_t sUpdateBlockersCount;
++};
++
++typedef nsTArray<RefPtr<nsNativeMenuDocListener> > 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 <glib-object.h>
++#include <pango/pango.h>
++#include <stdlib.h>
++
++#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<GDBusProxyFlags>(
++ G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
++ G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS |
++ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START),
++ nullptr,
++ "com.canonical.AppMenu.Registrar",
++ "/com/canonical/AppMenu/Registrar",
++ "com.canonical.AppMenu.Registrar",
++ 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<nsINativeMenuService> 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<nsMenuBar *>(user_data);
++
++ GError *error = nullptr;
++ GVariant *results = g_dbus_proxy_call_finish(G_DBUS_PROXY(source_object),
++ res, &error);
++ if (results) {
++ // There's nothing useful in the response
++ g_variant_unref(results);
++ }
++
++ bool success = error ? false : true;
++ if (error && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
++ g_error_free(error);
++ return;
++ }
++
++ if (error) {
++ g_error_free(error);
++ }
++
++ 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<nsMenuBar *>(user_data);
++ nsNativeMenuService::GetSingleton()->RegisterNativeMenuBar(menubar);
++
++ return FALSE;
++}
++
++void
++nsNativeMenuService::OnNameOwnerChanged()
++{
++ char *owner = g_dbus_proxy_get_name_owner(mDbusProxy);
++ SetOnline(owner ? true : false);
++ g_free(owner);
++}
++
++void
++nsNativeMenuService::OnProxyCreated(GDBusProxy *aProxy)
++{
++ mDbusProxy = aProxy;
++
++ 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<nsMenuBar> menubar(nsMenuBar::Create(aParent, aMenuBarNode));
++ if (!menubar) {
++ NS_WARNING("Failed to create menubar");
++ return NS_ERROR_FAILURE;
++ }
++
++ // Unity forgets our window if it is unmapped by the application, which
++ // happens with some extensions that add "minimize to tray" type
++ // functionality. We hook on to the MapNotify event to re-register our menu
++ // with Unity
++ g_signal_connect(G_OBJECT(menubar->TopLevelWindow()),
++ "map-event", G_CALLBACK(map_event_cb),
++ menubar.get());
++
++ mMenuBars.AppendElement(menubar.get());
++ RegisterNativeMenuBar(menubar.get());
++
++ static_cast<nsWindow *>(aParent)->SetMenuBar(std::move(menubar));
++
++ return NS_OK;
++}
++
++/* static */ already_AddRefed<nsNativeMenuService>
++nsNativeMenuService::GetInstanceForServiceManager()
++{
++ RefPtr<nsNativeMenuService> 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 <gdk/gdk.h>
++#include <gio/gio.h>
++#include <gtk/gtk.h>
++
++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<nsNativeMenuService> 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<nsMenuBar *> mMenuBars;
++ nsTHashMap<nsPtrHashKey<nsMenuBar>, 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<nsMenuBar> 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<nsMenuBar> 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<nsMenuBar> mMenuBar;
++
+ #ifdef ACCESSIBILITY
+ RefPtr<mozilla::a11y::LocalAccessible> 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
new file mode 100644
index 0000000..eff1d60
--- /dev/null
+++ b/waterfox-g/debian/patches/libavcodec58_91.patch
@@ -0,0 +1,16 @@
+diff --git a/dom/media/platforms/ffmpeg/FFmpegRuntimeLinker.cpp b/dom/media/platforms/ffmpeg/FFmpegRuntimeLinker.cpp
+index 91bf38df8354..e79bc068668a 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
+ #else
+ "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
new file mode 100644
index 0000000..de85066
--- /dev/null
+++ b/waterfox-g/debian/patches/mach-depends.patch
@@ -0,0 +1,24 @@
+diff --git a/python/sites/mach.txt b/python/sites/mach.txt
+index 6547ee58574a..a7d9a8b2ca70 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
+ vendored:third_party/python/taskcluster_urls
+ 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
+ # 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,<=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
new file mode 100644
index 0000000..ce5b6a2
--- /dev/null
+++ b/waterfox-g/debian/patches/mozilla-ntlm-full-path.patch
@@ -0,0 +1,28 @@
+# HG changeset patch
+# User Petr Cerny <pcerny@novell.com>
+# 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
+--- 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<uint8_t*>(PL_Base64Decode(s, length, nullptr));
+ }
+
+ nsresult nsAuthSambaNTLM::SpawnNTLMAuthHelper() {
+ const char* username = PR_GetEnv("USER");
+ if (!username) return NS_ERROR_FAILURE;
+
+- const char* const args[] = {"ntlm_auth",
++ const char* const args[] = {"/usr/bin/ntlm_auth",
+ "--helper-protocol",
+ "ntlmssp-client-1",
+ "--use-cached-creds",
+ "--username",
+ username,
+ nullptr};
+
+ bool isOK = SpawnIOChild(const_cast<char* const*>(args), &mChildPID,
diff --git a/waterfox-g/debian/patches/nongnome-proxies.patch b/waterfox-g/debian/patches/nongnome-proxies.patch
new file mode 100644
index 0000000..5eaf79d
--- /dev/null
+++ b/waterfox-g/debian/patches/nongnome-proxies.patch
@@ -0,0 +1,35 @@
+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
+--- 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;
+ }
+
+ void nsUnixSystemProxySettings::Init() {
+- mGSettings = do_GetService(NS_GSETTINGSSERVICE_CONTRACTID);
+- if (mGSettings) {
+- mGSettings->GetCollectionForSchema("org.gnome.system.proxy"_ns,
+- getter_AddRefs(mProxySettings));
++ const char* sessionType = PR_GetEnv("DESKTOP_SESSION");
++ if (sessionType && !strcmp(sessionType, "gnome")) {
++ mGSettings = do_GetService(NS_GSETTINGSSERVICE_CONTRACTID);
++ if (mGSettings) {
++ mGSettings->GetCollectionForSchema(
++ "org.gnome.system.proxy"_ns,
++ getter_AddRefs(mProxySettings));
++ }
+ }
+ }
+
+ 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
new file mode 100644
index 0000000..d6ead97
--- /dev/null
+++ b/waterfox-g/debian/patches/series
@@ -0,0 +1,9 @@
+global_menu.patch -p1
+fis-csd-global-menu.patch -p1
+#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
diff --git a/waterfox-g/debian/patches/waterfox-branded-icons.patch b/waterfox-g/debian/patches/waterfox-branded-icons.patch
new file mode 100644
index 0000000..e0778a5
--- /dev/null
+++ b/waterfox-g/debian/patches/waterfox-branded-icons.patch
@@ -0,0 +1,19 @@
+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',
+ ]
bgstack15