From b2237bccba179e5483e083edf5ebbd313d240dfd Mon Sep 17 00:00:00 2001 From: "B. Stack" Date: Sat, 19 Aug 2023 17:18:07 -0400 Subject: add waterfox-g --- .../debian/patches/fis-csd-global-menu.patch | 21 + waterfox-g/debian/patches/fix-langpack-id.patch | 67 + waterfox-g/debian/patches/fix-wayland-build.patch | 19 + waterfox-g/debian/patches/g-kde.patch | 1748 +++++++ waterfox-g/debian/patches/global_menu.patch | 5382 ++++++++++++++++++++ waterfox-g/debian/patches/libavcodec58_91.patch | 16 + waterfox-g/debian/patches/mach-depends.patch | 24 + .../debian/patches/mozilla-ntlm-full-path.patch | 28 + waterfox-g/debian/patches/nongnome-proxies.patch | 35 + waterfox-g/debian/patches/series | 9 + .../debian/patches/waterfox-branded-icons.patch | 19 + 11 files changed, 7368 insertions(+) create mode 100644 waterfox-g/debian/patches/fis-csd-global-menu.patch create mode 100644 waterfox-g/debian/patches/fix-langpack-id.patch create mode 100644 waterfox-g/debian/patches/fix-wayland-build.patch create mode 100644 waterfox-g/debian/patches/g-kde.patch create mode 100644 waterfox-g/debian/patches/global_menu.patch create mode 100644 waterfox-g/debian/patches/libavcodec58_91.patch create mode 100644 waterfox-g/debian/patches/mach-depends.patch create mode 100644 waterfox-g/debian/patches/mozilla-ntlm-full-path.patch create mode 100644 waterfox-g/debian/patches/nongnome-proxies.patch create mode 100644 waterfox-g/debian/patches/series create mode 100644 waterfox-g/debian/patches/waterfox-branded-icons.patch (limited to 'waterfox-g/debian/patches') 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: + /<>/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 + +--- a/widget/gtk/WaylandBuffer.cpp ++++ b/widget/gtk/WaylandBuffer.cpp +@@ -258,7 +258,7 @@ + + /* static */ + RefPtr WaylandBufferDMABUF::Create( +- const LayoutDeviceIntSize& aSize, GLContext* aGL) { ++ const LayoutDeviceIntSize& aSize, gl::GLContext* aGL) { + RefPtr 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 +Lubos Lunak +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 command = do_CreateInstance( NS_ARRAY_CONTRACTID ); ++ if (!command) ++ return NS_ERROR_FAILURE; ++ ++ nsCOMPtr 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 command = do_CreateInstance( NS_ARRAY_CONTRACTID ); ++ if (!command) ++ return NS_ERROR_FAILURE; ++ ++ nsCOMPtr cmdstr = do_CreateInstance( NS_SUPPORTS_CSTRING_CONTRACTID ); ++ nsCOMPtr 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 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 +@@ -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 command; ++ command.AppendElement( "GETPROXY"_ns ); ++ command.AppendElement( url ); ++ nsTArray 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(); + 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 ++ ++#include ++#include ++#include ++#include ++#include ++#include ++// 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 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* 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& command, nsTArray* output ) ++ { ++ return self()->internalCommand( command, NULL, false, output ); ++ } ++ ++bool nsKDEUtils::command( nsIArray* command, nsIArray** output) ++ { ++ nsTArray in; ++ PRUint32 length; ++ command->GetLength( &length ); ++ for ( PRUint32 i = 0; i < length; i++ ) ++ { ++ nsCOMPtr str = do_QueryElementAt( command, i ); ++ if( str ) ++ { ++ nsAutoCString s; ++ str->GetData( s ); ++ in.AppendElement( s ); ++ } ++ } ++ ++ nsTArray out; ++ bool ret = self()->internalCommand( in, NULL, false, &out ); ++ ++ if ( !output ) return ret; ++ ++ nsCOMPtr result = do_CreateInstance( NS_ARRAY_CONTRACTID ); ++ if ( !result ) return false; ++ ++ for ( PRUint32 i = 0; i < out.Length(); i++ ) ++ { ++ nsCOMPtr rstr = do_CreateInstance( NS_SUPPORTS_CSTRING_CONTRACTID ); ++ if ( !rstr ) return false; ++ ++ rstr->SetData( out[i] ); ++ result->AppendElement( rstr ); ++ } ++ ++ NS_ADDREF( *output = result); ++ return ret; ++ } ++ ++ ++bool nsKDEUtils::commandBlockUi( const nsTArray& command, GtkWindow* parent, nsTArray* output ) ++ { ++ return self()->internalCommand( command, parent, true, output ); ++ } ++ ++bool nsKDEUtils::internalCommand( const nsTArray& command, GtkWindow* parent, bool blockUi, ++ nsTArray* output ) ++ { ++ if( !startHelper()) ++ return false; ++ feedCommand( command ); ++ // do not store the data in 'this' but in extra structure, just in case there ++ // is reentrancy (can there be? the event loop is re-entered) ++ nsKDECommandData data; ++ data.file = replyFile; ++ data.output = output; ++ data.success = false; ++ if( blockUi ) ++ { ++ data.loop = g_main_loop_new( NULL, FALSE ); ++ GtkWidget* window = gtk_window_new( GTK_WINDOW_TOPLEVEL ); ++ if( parent && gtk_window_get_group(parent) ) ++ gtk_window_group_add_window( gtk_window_get_group(parent), GTK_WINDOW( window )); ++ gtk_widget_realize( window ); ++ gtk_widget_set_sensitive( window, TRUE ); ++ gtk_grab_add( window ); ++ GIOChannel* channel = g_io_channel_unix_new( fileno( data.file )); ++ g_io_add_watch( channel, static_cast< GIOCondition >( G_IO_IN | G_IO_ERR | G_IO_HUP ), kdeReadFunc, &data ); ++ g_io_channel_unref( channel ); ++ g_main_loop_run( data.loop ); ++ g_main_loop_unref( data.loop ); ++ gtk_grab_remove( window ); ++ gtk_widget_destroy( window ); ++ } ++ else ++ { ++ data.loop = NULL; ++ while( kdeReadFunc( NULL, static_cast< GIOCondition >( 0 ), &data )) ++ ; ++ } ++ return data.success; ++ } ++ ++bool nsKDEUtils::startHelper() ++ { ++ if( helperRunning ) ++ return true; ++ if( helperFailed ) ++ return false; ++ helperFailed = true; ++ int fdcommand[ 2 ]; ++ int fdreply[ 2 ]; ++ if( pipe( fdcommand ) < 0 ) ++ return false; ++ if( pipe( fdreply ) < 0 ) ++ { ++ close( fdcommand[ 0 ] ); ++ close( fdcommand[ 1 ] ); ++ return false; ++ } ++ char* args[ 2 ] = { const_cast< 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& 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 ++ ++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& command, nsTArray* output = NULL ); ++ static bool command( nsIArray* command, nsIArray** output = NULL ); ++ /* Like command(), but additionally blocks the parent widget like if there was ++ a modal dialog shown and enters the event loop (i.e. there are still paint updates, ++ this is for commands that take long). */ ++ static bool commandBlockUi( const nsTArray& command, GtkWindow* parent, nsTArray* output = NULL ); ++ ++ private: ++ nsKDEUtils(); ++ ~nsKDEUtils(); ++ static nsKDEUtils* self(); ++ bool startHelper(); ++ void closeHelper(); ++ void feedCommand( const nsTArray& command ); ++ bool internalCommand( const nsTArray& command, GtkWindow* parent, bool isParent, ++ nsTArray* output ); ++ FILE* commandFile; ++ FILE* replyFile; ++ }; ++ ++#endif // nsKDEUtils +diff --git a/uriloader/exthandler/HandlerServiceParent.cpp b/uriloader/exthandler/HandlerServiceParent.cpp +index dbcc95d95646..5f2542a95065 100644 +--- 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 ++nsCommonRegistry::GetFromExtension(const nsACString& aFileExt) ++{ ++ if( nsKDEUtils::kdeSupport()) ++ return nsKDERegistry::GetFromExtension( aFileExt ); ++ return nsGNOMERegistry::GetFromExtension( aFileExt ); ++} ++ ++/* static */ already_AddRefed ++nsCommonRegistry::GetFromType(const nsACString& aMIMEType) ++{ ++ if( nsKDEUtils::kdeSupport()) ++ return nsKDERegistry::GetFromType( aMIMEType ); ++ return nsGNOMERegistry::GetFromType( aMIMEType ); ++} +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 GetFromExtension(const nsACString& aFileExt); ++ ++ static already_AddRefed GetFromType(const nsACString& aMIMEType); ++}; ++ ++#endif +diff --git a/uriloader/exthandler/unix/nsKDERegistry.cpp b/uriloader/exthandler/unix/nsKDERegistry.cpp +new file mode 100644 +index 000000000000..f78e64c7e9a3 +--- /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 command; ++ command.AppendElement( "HANDLEREXISTS"_ns ); ++ command.AppendElement( nsAutoCString( aProtocolScheme )); ++ return nsKDEUtils::command( command ); ++} ++ ++/* static */ nsresult ++nsKDERegistry::LoadURL(nsIURI *aURL) ++{ ++ nsTArray command; ++ command.AppendElement( "OPEN"_ns ); ++ nsCString url; ++ aURL->GetSpec( url ); ++ command.AppendElement( url ); ++ bool rv = nsKDEUtils::command( command ); ++ if (!rv) ++ return NS_ERROR_FAILURE; ++ ++ return NS_OK; ++} ++ ++/* static */ void ++nsKDERegistry::GetAppDescForScheme(const nsACString& aScheme, ++ nsAString& aDesc) ++{ ++ nsTArray command; ++ command.AppendElement( "GETAPPDESCFORSCHEME"_ns ); ++ command.AppendElement( aScheme ); ++ nsTArray output; ++ if( nsKDEUtils::command( command, &output ) && output.Length() == 1 ) ++ CopyUTF8toUTF16( output[ 0 ], aDesc ); ++} ++ ++ ++/* static */ already_AddRefed ++nsKDERegistry::GetFromExtension(const nsACString& aFileExt) ++{ ++ NS_ASSERTION(aFileExt[0] != '.', "aFileExt shouldn't start with a dot"); ++ nsTArray command; ++ command.AppendElement( "GETFROMEXTENSION"_ns ); ++ command.AppendElement( aFileExt ); ++ return GetFromHelper( command ); ++} ++ ++/* static */ already_AddRefed ++nsKDERegistry::GetFromType(const nsACString& aMIMEType) ++{ ++ nsTArray command; ++ command.AppendElement( "GETFROMTYPE"_ns ); ++ command.AppendElement( aMIMEType ); ++ return GetFromHelper( command ); ++} ++ ++/* static */ already_AddRefed ++nsKDERegistry::GetFromHelper(const nsTArray& command) ++{ ++ nsTArray output; ++ if( nsKDEUtils::command( command, &output ) && output.Length() == 3 ) ++ { ++ nsCString mimetype = output[ 0 ]; ++ RefPtr mimeInfo = new nsMIMEInfoUnix( mimetype ); ++ NS_ENSURE_TRUE(mimeInfo, nullptr); ++ nsCString description = output[ 1 ]; ++ mimeInfo->SetDescription(NS_ConvertUTF8toUTF16(description)); ++ nsCString handlerAppName = output[ 2 ]; ++ mozilla::StaticPrefs::browser_download_improvements_to_download_panel() ++ ? mimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk) ++ : mimeInfo->SetPreferredAction(nsIMIMEInfo::useSystemDefault); ++ mimeInfo->SetDefaultDescription(NS_ConvertUTF8toUTF16(handlerAppName)); ++ return mimeInfo.forget(); ++ } ++ return nullptr; ++} +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 GetFromExtension(const nsACString& aFileExt); ++ ++ static already_AddRefed GetFromType(const nsACString& aMIMEType); ++ private: ++ static already_AddRefed GetFromHelper(const nsTArray& 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 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 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 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 nsOSHelperAppService::GetFromExtension( + #ifdef MOZ_WIDGET_GTK + LOG(("Looking in GNOME registry\n")); + RefPtr 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 nsOSHelperAppService::GetFromType( + + #ifdef MOZ_WIDGET_GTK + if (handler.IsEmpty()) { +- RefPtr gnomeInfo = nsGNOMERegistry::GetFromType(aMIMEType); ++ RefPtr gnomeInfo = nsCommonRegistry::GetFromType(aMIMEType); + if (gnomeInfo) { + LOG( + ("Got MIMEInfo from GNOME registry without extensions; setting them " +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 + #include ++#include + #include + #include + #include +@@ -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 command; ++ command.AppendElement( nsAutoCString( arg )); ++ command.AppendElement( startdir ); ++ if( mMode != nsIFilePicker::modeGetFolder ) ++ { ++ command.AppendElement( filters ); ++ nsAutoCString selected; ++ selected.AppendInt( mSelectedType ); ++ command.AppendElement( selected ); ++ } ++ command.AppendElement( title ); ++ if( mMode == nsIFilePicker::modeOpenMultiple ) ++ command.AppendElement( "MULTIPLE"_ns ); ++ if( PRInt32 xid = windowToXid( mParentWidget )) ++ { ++ command.AppendElement( "PARENT"_ns ); ++ nsAutoCString parent; ++ parent.AppendInt( xid ); ++ command.AppendElement( parent ); ++ } ++ ++ nsTArray output; ++ if( nsKDEUtils::commandBlockUi( command, GTK_WINDOW(mParentWidget->GetNativeData(NS_NATIVE_SHELLWIDGET)), &output )) ++ { ++ *aReturn = nsIFilePicker::returnOK; ++ mFiles.Clear(); ++ if( mMode != nsIFilePicker::modeGetFolder ) ++ { ++ mSelectedType = atoi( output[ 0 ].get()); ++ output.RemoveElementAt( 0 ); ++ } ++ if (mMode == nsIFilePicker::modeOpenMultiple) ++ { ++ mFileURL.Truncate(); ++ PRUint32 count = output.Length(); ++ for( PRUint32 i = 0; ++ i < count; ++ ++i ) ++ { ++ nsCOMPtr localfile; ++ nsresult rv = NS_NewNativeLocalFile( output[ i ], ++ PR_FALSE, ++ getter_AddRefs(localfile)); ++ if (NS_SUCCEEDED(rv)) ++ mFiles.AppendObject(localfile); ++ } ++ } ++ else ++ { ++ if( output.Length() == 0 ) ++ mFileURL = nsCString(); ++ else if( mAllowURLs ) ++ mFileURL = output[ 0 ]; ++ else // GetFile() actually requires it to be url even for local files :-/ ++ { ++ nsCOMPtr localfile; ++ nsresult rv = NS_NewNativeLocalFile( output[ 0 ], ++ PR_FALSE, ++ getter_AddRefs(localfile)); ++ if (NS_SUCCEEDED(rv)) ++ rv = net_GetURLSpecFromActualFile(localfile, mFileURL); ++ } ++ } ++ // Remember last used directory. ++ nsCOMPtr file; ++ GetFile(getter_AddRefs(file)); ++ if (file) { ++ nsCOMPtr dir; ++ file->GetParent(getter_AddRefs(dir)); ++ nsCOMPtr localDir(do_QueryInterface(dir)); ++ if (localDir) { ++ localDir.swap(mPrevDisplayDirectory); ++ } ++ } ++ if (mMode == nsIFilePicker::modeSave) ++ { ++ nsCOMPtr file; ++ GetFile(getter_AddRefs(file)); ++ if (file) ++ { ++ bool exists = false; ++ file->Exists(&exists); ++ if (exists) // TODO do overwrite check in the helper app ++ *aReturn = nsIFilePicker::returnReplace; ++ } ++ } ++ } ++ else ++ { ++ *aReturn = nsIFilePicker::returnCancel; ++ } ++ return NS_OK; ++ } ++ ++ ++NS_IMETHODIMP nsFilePicker::kdeAppsDialog(PRInt16 *aReturn) ++ { ++ NS_ENSURE_ARG_POINTER(aReturn); ++ ++ nsCString title; ++ title.Adopt(ToNewUTF8String(mTitle)); ++ ++ nsTArray command; ++ command.AppendElement( "APPSDIALOG"_ns ); ++ command.AppendElement( title ); ++ if( PRInt32 xid = windowToXid( mParentWidget )) ++ { ++ command.AppendElement( "PARENT"_ns ); ++ nsAutoCString parent; ++ parent.AppendInt( xid ); ++ command.AppendElement( parent ); ++ } ++ ++ nsTArray 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 giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID); +- if (!giovfs) { +- return NS_ERROR_FAILURE; ++ url = mPath; ++ if(nsKDEUtils::kdeSupport()) { ++ nsTArray 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 command; ++ command.AppendElement( "OPEN"_ns ); ++ command.AppendElement( mPath ); ++ return nsKDEUtils::command( command ) ? NS_OK : NS_ERROR_FAILURE; ++ } ++ + nsCOMPtr giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID); + if (!giovfs) { + return NS_ERROR_FAILURE; +-- +2.37.3 + diff --git a/waterfox-g/debian/patches/global_menu.patch b/waterfox-g/debian/patches/global_menu.patch 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 + +diff --git a/browser/base/content/browser-menubar.inc b/browser/base/content/browser-menubar.inc +index 1adea92456c7..9cb91a4befdf 100644 +--- a/browser/base/content/browser-menubar.inc ++++ b/browser/base/content/browser-menubar.inc +@@ -7,7 +7,12 @@ + # On macOS, we don't track whether activation of the native menubar happened + # with the keyboard. + #ifndef XP_MACOSX +- onpopupshowing="if (event.target.parentNode.parentNode == this) ++ onpopupshowing="if (event.target.parentNode.parentNode == this && ++#ifdef MOZ_WIDGET_GTK ++ document.documentElement.getAttribute('shellshowingmenubar') != 'true') ++#else ++ true) ++#endif + this.setAttribute('openedwithkey', + event.target.parentNode.openedWithKey);" + #endif +diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js +index d72ed6495612..eb4af8d23195 100644 +--- a/browser/base/content/browser.js ++++ b/browser/base/content/browser.js +@@ -6481,11 +6481,18 @@ function onViewToolbarsPopupShowing(aEvent, aInsertPoint) { + MozXULElement.insertFTLIfNeeded("browser/toolbarContextMenu.ftl"); + let firstMenuItem = aInsertPoint || popup.firstElementChild; + let toolbarNodes = gNavToolbox.querySelectorAll("toolbar"); ++ ++ let shellShowingMenubar = document.documentElement.getAttribute("shellshowingmenubar") == "true"; ++ + for (let toolbar of toolbarNodes) { + if (!toolbar.hasAttribute("toolbarname")) { + continue; + } + ++ if (shellShowingMenubar && toolbar.id == "toolbar-menubar") { ++ continue; ++ } ++ + if (toolbar.id == "PersonalToolbar") { + let menu = BookmarkingUI.buildBookmarksToolbarSubmenu(toolbar); + popup.insertBefore(menu, firstMenuItem); +diff --git a/browser/components/places/content/places.xhtml b/browser/components/places/content/places.xhtml +index e19a58029828..4271915765c6 100644 +--- a/browser/components/places/content/places.xhtml ++++ b/browser/components/places/content/places.xhtml +@@ -166,6 +166,7 @@ + #else + + + +diff --git a/dom/xul/XULPopupElement.cpp b/dom/xul/XULPopupElement.cpp +index b6a50a3e2408..cda8315b0ef8 100644 +--- a/dom/xul/XULPopupElement.cpp ++++ b/dom/xul/XULPopupElement.cpp +@@ -207,6 +207,10 @@ void XULPopupElement::GetState(nsString& aState) { + // set this here in case there's no frame for the popup + aState.AssignLiteral("closed"); + ++#ifdef MOZ_WIDGET_GTK ++ nsAutoString nativeState; ++#endif ++ + if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) { + switch (pm->GetPopupState(this)) { + case ePopupShown: +@@ -229,6 +233,11 @@ void XULPopupElement::GetState(nsString& aState) { + break; + } + } ++#ifdef MOZ_WIDGET_GTK ++ else if (GetAttr(kNameSpaceID_None, nsGkAtoms::_moz_nativemenupopupstate, nativeState)) { ++ aState = nativeState; ++ } ++#endif + } + + nsINode* XULPopupElement::GetTriggerNode() const { +diff --git a/dom/xul/moz.build b/dom/xul/moz.build +index bb7e4b08d924..42d4bd9b654f 100644 +--- a/dom/xul/moz.build ++++ b/dom/xul/moz.build +@@ -82,4 +82,9 @@ LOCAL_INCLUDES += [ + + include("/ipc/chromium/chromium-config.mozbuild") + ++if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk": ++ LOCAL_INCLUDES += [ ++ "/widget/gtk", ++ ] ++ + FINAL_LIBRARY = "xul" +diff --git a/layout/build/moz.build b/layout/build/moz.build +index c7869a01a373..2cbdb021b3b7 100644 +--- a/layout/build/moz.build ++++ b/layout/build/moz.build +@@ -70,6 +70,10 @@ elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "android": + "/dom/system", + "/dom/system/android", + ] ++elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk": ++ LOCAL_INCLUDES += [ ++ "/widget/gtk", ++ ] + + XPCOM_MANIFESTS += [ + "components.conf", +diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js +index 30641d9727cf..c8382f50ab28 100644 +--- a/modules/libpref/init/all.js ++++ b/modules/libpref/init/all.js +@@ -188,6 +188,9 @@ pref("dom.mouseevent.click.hack.use_legacy_non-primary_dispatch", ""); + // Fastback caching - if this pref is negative, then we calculate the number + // of content viewers to cache based on the amount of available memory. + pref("browser.sessionhistory.max_total_viewers", -1); ++#ifdef MOZ_WIDGET_GTK ++pref("ui.use_unity_menubar", true); ++#endif + + pref("browser.display.force_inline_alttext", false); // true = force ALT text for missing images to be layed out inline + // 0 = no external leading, +diff --git a/toolkit/content/xul.css b/toolkit/content/xul.css +index 70bfc2047c05..6b7938a6a7a5 100644 +--- a/toolkit/content/xul.css ++++ b/toolkit/content/xul.css +@@ -226,6 +226,13 @@ toolbox { + } + } + ++@media (-moz-platform: linux) { ++*|*:root[shellshowingmenubar="true"] ++toolbar[type="menubar"]:not([customizing="true"]) { ++ display: none !important; ++} ++} ++ + toolbarspring { + -moz-box-flex: 1000; + } +diff --git a/widget/gtk/NativeMenuSupport.cpp b/widget/gtk/NativeMenuSupport.cpp +index 4360867fff3f..c3a69f31b1d3 100644 +--- a/widget/gtk/NativeMenuSupport.cpp ++++ b/widget/gtk/NativeMenuSupport.cpp +@@ -7,6 +7,8 @@ + + #include "MainThreadUtils.h" + #include "NativeMenuGtk.h" ++#include "nsINativeMenuService.h" ++#include "nsServiceManagerUtils.h" + + namespace mozilla::widget { + +@@ -14,7 +16,14 @@ void NativeMenuSupport::CreateNativeMenuBar(nsIWidget* aParent, + dom::Element* aMenuBarElement) { + MOZ_RELEASE_ASSERT(NS_IsMainThread(), + "Attempting to create native menu bar on wrong thread!"); +- // TODO ++ ++ nsCOMPtr nms = ++ do_GetService("@mozilla.org/widget/nativemenuservice;1"); ++ if (!nms) { ++ return; ++ } ++ ++ nms->CreateNativeMenuBar(aParent, aMenuBarElement); + } + + already_AddRefed NativeMenuSupport::CreateNativeContextMenu( +diff --git a/widget/gtk/NativeMenuSupport.h b/widget/gtk/NativeMenuSupport.h +new file mode 100644 +index 000000000000..0843d45185e5 +--- /dev/null ++++ b/widget/gtk/NativeMenuSupport.h +@@ -0,0 +1,31 @@ ++/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ ++/* This Source Code Form is subject to the terms of the Mozilla Public ++ * License, v. 2.0. If a copy of the MPL was not distributed with this ++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ ++ ++#ifndef mozilla_widget_NativeMenuSupport_h ++#define mozilla_widget_NativeMenuSupport_h ++ ++class nsIWidget; ++ ++namespace mozilla { ++ ++namespace dom { ++class Element; ++} ++ ++namespace widget { ++ ++class NativeMenuSupport final { ++public: ++ // Given a top-level window widget and a menu bar DOM node, sets up native ++ // menus. Once created, native menus are controlled via the DOM, including ++ // destruction. ++ static void CreateNativeMenuBar(nsIWidget* aParent, ++ dom::Element* aMenuBarElement); ++}; ++ ++} // namespace widget ++} // namespace mozilla ++ ++#endif // mozilla_widget_NativeMenuSupport_h +diff --git a/widget/gtk/components.conf b/widget/gtk/components.conf +index 61714d8b8190..1738093137f2 100644 +--- a/widget/gtk/components.conf ++++ b/widget/gtk/components.conf +@@ -117,6 +117,14 @@ Classes = [ + 'headers': ['/widget/gtk/nsUserIdleServiceGTK.h'], + 'constructor': 'nsUserIdleServiceGTK::GetInstance', + }, ++ { ++ 'cid': '{0b3fe5aa-bc72-4303-85ae-76365df1251d}', ++ 'contract_ids': ['@mozilla.org/widget/nativemenuservice;1'], ++ 'singleton': True, ++ 'type': 'nsNativeMenuService', ++ 'constructor': 'nsNativeMenuService::GetInstanceForServiceManager', ++ 'headers': ['/widget/gtk/nsNativeMenuService.h'], ++ }, + ] + + if defined('NS_PRINTING'): +diff --git a/widget/gtk/moz.build b/widget/gtk/moz.build +index 5218c7df06aa..bf64f7ebdc65 100644 +--- a/widget/gtk/moz.build ++++ b/widget/gtk/moz.build +@@ -77,6 +77,15 @@ UNIFIED_SOURCES += [ + + SOURCES += [ + "MediaKeysEventSourceFactory.cpp", ++ "nsDbusmenu.cpp", ++ "nsMenu.cpp", # conflicts with X11 headers ++ "nsMenuBar.cpp", ++ "nsMenuContainer.cpp", ++ "nsMenuItem.cpp", ++ "nsMenuObject.cpp", ++ "nsMenuSeparator.cpp", ++ "nsNativeMenuDocListener.cpp", ++ "nsNativeMenuService.cpp", + "nsNativeThemeGTK.cpp", # conflicts with X11 headers + "nsWindow.cpp", # conflicts with X11 headers + "WaylandVsyncSource.cpp", # conflicts with X11 headers +@@ -156,6 +165,7 @@ LOCAL_INCLUDES += [ + "/layout/base", + "/layout/forms", + "/layout/generic", ++ "/layout/style", + "/layout/xul", + "/other-licenses/atk-1.0", + "/third_party/cups/include", +diff --git a/widget/gtk/nsDbusmenu.cpp b/widget/gtk/nsDbusmenu.cpp +new file mode 100644 +index 000000000000..f3a1c4400bf2 +--- /dev/null ++++ b/widget/gtk/nsDbusmenu.cpp +@@ -0,0 +1,61 @@ ++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ ++/* vim:expandtab:shiftwidth=4:tabstop=4: ++ */ ++/* This Source Code Form is subject to the terms of the Mozilla Public ++ * License, v. 2.0. If a copy of the MPL was not distributed with this ++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ ++ ++#include "nsDbusmenu.h" ++#include "prlink.h" ++#include "mozilla/ArrayUtils.h" ++ ++#define FUNC(name, type, params) \ ++nsDbusmenuFunctions::_##name##_fn nsDbusmenuFunctions::s_##name; ++DBUSMENU_GLIB_FUNCTIONS ++DBUSMENU_GTK_FUNCTIONS ++#undef FUNC ++ ++static PRLibrary *gDbusmenuGlib = nullptr; ++static PRLibrary *gDbusmenuGtk = nullptr; ++ ++typedef void (*nsDbusmenuFunc)(); ++struct nsDbusmenuDynamicFunction { ++ const char *functionName; ++ nsDbusmenuFunc *function; ++}; ++ ++/* static */ nsresult ++nsDbusmenuFunctions::Init() ++{ ++#define FUNC(name, type, params) \ ++ { #name, (nsDbusmenuFunc *)&nsDbusmenuFunctions::s_##name }, ++ static const nsDbusmenuDynamicFunction kDbusmenuGlibSymbols[] = { ++ DBUSMENU_GLIB_FUNCTIONS ++ }; ++ static const nsDbusmenuDynamicFunction kDbusmenuGtkSymbols[] = { ++ DBUSMENU_GTK_FUNCTIONS ++ }; ++ ++#define LOAD_LIBRARY(symbol, name) \ ++ if (!g##symbol) { \ ++ g##symbol = PR_LoadLibrary(name); \ ++ if (!g##symbol) { \ ++ return NS_ERROR_FAILURE; \ ++ } \ ++ } \ ++ for (uint32_t i = 0; i < mozilla::ArrayLength(k##symbol##Symbols); ++i) { \ ++ *k##symbol##Symbols[i].function = \ ++ PR_FindFunctionSymbol(g##symbol, k##symbol##Symbols[i].functionName); \ ++ if (!*k##symbol##Symbols[i].function) { \ ++ return NS_ERROR_FAILURE; \ ++ } \ ++ } ++ ++ LOAD_LIBRARY(DbusmenuGlib, "libdbusmenu-glib.so.4") ++#ifdef MOZ_WIDGET_GTK ++ LOAD_LIBRARY(DbusmenuGtk, "libdbusmenu-gtk3.so.4") ++#endif ++#undef LOAD_LIBRARY ++ ++ return NS_OK; ++} +diff --git a/widget/gtk/nsDbusmenu.h b/widget/gtk/nsDbusmenu.h +new file mode 100644 +index 000000000000..8d46a0d27bdb +--- /dev/null ++++ b/widget/gtk/nsDbusmenu.h +@@ -0,0 +1,101 @@ ++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ ++/* vim:expandtab:shiftwidth=4:tabstop=4: ++ */ ++/* This Source Code Form is subject to the terms of the Mozilla Public ++ * License, v. 2.0. If a copy of the MPL was not distributed with this ++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ ++ ++#ifndef __nsDbusmenu_h__ ++#define __nsDbusmenu_h__ ++ ++#include "nsError.h" ++ ++#include ++#include ++ ++#define DBUSMENU_GLIB_FUNCTIONS \ ++ FUNC(dbusmenu_menuitem_child_add_position, gboolean, (DbusmenuMenuitem *mi, DbusmenuMenuitem *child, guint position)) \ ++ FUNC(dbusmenu_menuitem_child_append, gboolean, (DbusmenuMenuitem *mi, DbusmenuMenuitem *child)) \ ++ FUNC(dbusmenu_menuitem_child_delete, gboolean, (DbusmenuMenuitem *mi, DbusmenuMenuitem *child)) \ ++ FUNC(dbusmenu_menuitem_get_children, GList*, (DbusmenuMenuitem *mi)) \ ++ FUNC(dbusmenu_menuitem_new, DbusmenuMenuitem*, (void)) \ ++ FUNC(dbusmenu_menuitem_property_get, const gchar*, (DbusmenuMenuitem *mi, const gchar *property)) \ ++ FUNC(dbusmenu_menuitem_property_get_bool, gboolean, (DbusmenuMenuitem *mi, const gchar *property)) \ ++ FUNC(dbusmenu_menuitem_property_remove, void, (DbusmenuMenuitem *mi, const gchar *property)) \ ++ FUNC(dbusmenu_menuitem_property_set, gboolean, (DbusmenuMenuitem *mi, const gchar *property, const gchar *value)) \ ++ FUNC(dbusmenu_menuitem_property_set_bool, gboolean, (DbusmenuMenuitem *mi, const gchar *property, const gboolean value)) \ ++ FUNC(dbusmenu_menuitem_property_set_int, gboolean, (DbusmenuMenuitem *mi, const gchar *property, const gint value)) \ ++ FUNC(dbusmenu_menuitem_show_to_user, void, (DbusmenuMenuitem *mi, guint timestamp)) \ ++ FUNC(dbusmenu_menuitem_take_children, GList*, (DbusmenuMenuitem *mi)) \ ++ FUNC(dbusmenu_server_new, DbusmenuServer*, (const gchar *object)) \ ++ FUNC(dbusmenu_server_set_root, void, (DbusmenuServer *server, DbusmenuMenuitem *root)) \ ++ FUNC(dbusmenu_server_set_status, void, (DbusmenuServer *server, DbusmenuStatus status)) ++ ++#define DBUSMENU_GTK_FUNCTIONS \ ++ FUNC(dbusmenu_menuitem_property_set_image, gboolean, (DbusmenuMenuitem *menuitem, const gchar *property, const GdkPixbuf *data)) \ ++ FUNC(dbusmenu_menuitem_property_set_shortcut, gboolean, (DbusmenuMenuitem *menuitem, guint key, GdkModifierType modifier)) ++ ++typedef struct _DbusmenuMenuitem DbusmenuMenuitem; ++typedef struct _DbusmenuServer DbusmenuServer; ++ ++enum DbusmenuStatus { ++ DBUSMENU_STATUS_NORMAL, ++ DBUSMENU_STATUS_NOTICE ++}; ++ ++#define DBUSMENU_MENUITEM_CHILD_DISPLAY_SUBMENU "submenu" ++#define DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY "children-display" ++#define DBUSMENU_MENUITEM_PROP_ENABLED "enabled" ++#define DBUSMENU_MENUITEM_PROP_ICON_DATA "icon-data" ++#define DBUSMENU_MENUITEM_PROP_LABEL "label" ++#define DBUSMENU_MENUITEM_PROP_SHORTCUT "shortcut" ++#define DBUSMENU_MENUITEM_PROP_TYPE "type" ++#define DBUSMENU_MENUITEM_PROP_TOGGLE_STATE "toggle-state" ++#define DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE "toggle-type" ++#define DBUSMENU_MENUITEM_PROP_VISIBLE "visible" ++#define DBUSMENU_MENUITEM_SIGNAL_ABOUT_TO_SHOW "about-to-show" ++#define DBUSMENU_MENUITEM_SIGNAL_EVENT "event" ++#define DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED "item-activated" ++#define DBUSMENU_MENUITEM_TOGGLE_CHECK "checkmark" ++#define DBUSMENU_MENUITEM_TOGGLE_RADIO "radio" ++#define DBUSMENU_MENUITEM_TOGGLE_STATE_CHECKED 1 ++#define DBUSMENU_MENUITEM_TOGGLE_STATE_UNCHECKED 0 ++#define DBUSMENU_SERVER_PROP_DBUS_OBJECT "dbus-object" ++ ++class nsDbusmenuFunctions ++{ ++public: ++ nsDbusmenuFunctions() = delete; ++ ++ static nsresult Init(); ++ ++#define FUNC(name, type, params) \ ++ typedef type (*_##name##_fn) params; \ ++ static _##name##_fn s_##name; ++ DBUSMENU_GLIB_FUNCTIONS ++ DBUSMENU_GTK_FUNCTIONS ++#undef FUNC ++ ++}; ++ ++#define dbusmenu_menuitem_child_add_position nsDbusmenuFunctions::s_dbusmenu_menuitem_child_add_position ++#define dbusmenu_menuitem_child_append nsDbusmenuFunctions::s_dbusmenu_menuitem_child_append ++#define dbusmenu_menuitem_child_delete nsDbusmenuFunctions::s_dbusmenu_menuitem_child_delete ++#define dbusmenu_menuitem_get_children nsDbusmenuFunctions::s_dbusmenu_menuitem_get_children ++#define dbusmenu_menuitem_new nsDbusmenuFunctions::s_dbusmenu_menuitem_new ++#define dbusmenu_menuitem_property_get nsDbusmenuFunctions::s_dbusmenu_menuitem_property_get ++#define dbusmenu_menuitem_property_get_bool nsDbusmenuFunctions::s_dbusmenu_menuitem_property_get_bool ++#define dbusmenu_menuitem_property_remove nsDbusmenuFunctions::s_dbusmenu_menuitem_property_remove ++#define dbusmenu_menuitem_property_set nsDbusmenuFunctions::s_dbusmenu_menuitem_property_set ++#define dbusmenu_menuitem_property_set_bool nsDbusmenuFunctions::s_dbusmenu_menuitem_property_set_bool ++#define dbusmenu_menuitem_property_set_int nsDbusmenuFunctions::s_dbusmenu_menuitem_property_set_int ++#define dbusmenu_menuitem_show_to_user nsDbusmenuFunctions::s_dbusmenu_menuitem_show_to_user ++#define dbusmenu_menuitem_take_children nsDbusmenuFunctions::s_dbusmenu_menuitem_take_children ++#define dbusmenu_server_new nsDbusmenuFunctions::s_dbusmenu_server_new ++#define dbusmenu_server_set_root nsDbusmenuFunctions::s_dbusmenu_server_set_root ++#define dbusmenu_server_set_status nsDbusmenuFunctions::s_dbusmenu_server_set_status ++ ++#define dbusmenu_menuitem_property_set_image nsDbusmenuFunctions::s_dbusmenu_menuitem_property_set_image ++#define dbusmenu_menuitem_property_set_shortcut nsDbusmenuFunctions::s_dbusmenu_menuitem_property_set_shortcut ++ ++#endif /* __nsDbusmenu_h__ */ +diff --git a/widget/gtk/nsMenu.cpp b/widget/gtk/nsMenu.cpp +new file mode 100644 +index 000000000000..255eb390e577 +--- /dev/null ++++ b/widget/gtk/nsMenu.cpp +@@ -0,0 +1,795 @@ ++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ ++/* vim:expandtab:shiftwidth=4:tabstop=4: ++ */ ++/* This Source Code Form is subject to the terms of the Mozilla Public ++ * License, v. 2.0. If a copy of the MPL was not distributed with this ++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ ++ ++#define _IMPL_NS_LAYOUT ++ ++#include "mozilla/dom/Document.h" ++#include "mozilla/dom/Element.h" ++#include "mozilla/Assertions.h" ++#include "mozilla/ComputedStyleInlines.h" ++#include "mozilla/EventDispatcher.h" ++#include "mozilla/MouseEvents.h" ++#include "mozilla/PresShell.h" ++#include "mozilla/PresShellInlines.h" ++#include "nsComponentManagerUtils.h" ++#include "nsContentUtils.h" ++#include "nsCSSValue.h" ++#include "nsGkAtoms.h" ++#include "nsGtkUtils.h" ++#include "nsAtom.h" ++#include "nsIContent.h" ++#include "nsIRunnable.h" ++#include "nsITimer.h" ++#include "nsString.h" ++#include "nsStyleStruct.h" ++#include "nsThreadUtils.h" ++ ++#include "nsNativeMenuDocListener.h" ++ ++#include ++ ++#include "nsMenu.h" ++ ++using namespace mozilla; ++ ++class nsMenuContentInsertedEvent : public Runnable ++{ ++public: ++ nsMenuContentInsertedEvent(nsMenu *aMenu, ++ nsIContent *aContainer, ++ nsIContent *aChild, ++ nsIContent *aPrevSibling) : ++ Runnable("nsMenuContentInsertedEvent"), ++ mWeakMenu(aMenu), ++ mContainer(aContainer), ++ mChild(aChild), ++ mPrevSibling(aPrevSibling) { } ++ ++ NS_IMETHODIMP Run() ++ { ++ if (!mWeakMenu) { ++ return NS_OK; ++ } ++ ++ static_cast(mWeakMenu.get())->HandleContentInserted(mContainer, ++ mChild, ++ mPrevSibling); ++ return NS_OK; ++ } ++ ++private: ++ nsWeakMenuObject mWeakMenu; ++ ++ nsCOMPtr mContainer; ++ nsCOMPtr mChild; ++ nsCOMPtr mPrevSibling; ++}; ++ ++class nsMenuContentRemovedEvent : public Runnable ++{ ++public: ++ nsMenuContentRemovedEvent(nsMenu *aMenu, ++ nsIContent *aContainer, ++ nsIContent *aChild) : ++ Runnable("nsMenuContentRemovedEvent"), ++ mWeakMenu(aMenu), ++ mContainer(aContainer), ++ mChild(aChild) { } ++ ++ NS_IMETHODIMP Run() ++ { ++ if (!mWeakMenu) { ++ return NS_OK; ++ } ++ ++ static_cast(mWeakMenu.get())->HandleContentRemoved(mContainer, ++ mChild); ++ return NS_OK; ++ } ++ ++private: ++ nsWeakMenuObject mWeakMenu; ++ ++ nsCOMPtr mContainer; ++ nsCOMPtr mChild; ++}; ++ ++static void ++DispatchMouseEvent(nsIContent *aTarget, mozilla::EventMessage aMsg) ++{ ++ if (!aTarget) { ++ return; ++ } ++ ++ WidgetMouseEvent event(true, aMsg, nullptr, WidgetMouseEvent::eReal); ++ EventDispatcher::Dispatch(aTarget, nullptr, &event); ++} ++ ++void ++nsMenu::SetPopupState(EPopupState aState) ++{ ++ mPopupState = aState; ++ ++ if (!mPopupContent) { ++ return; ++ } ++ ++ nsAutoString state; ++ switch (aState) { ++ case ePopupState_Showing: ++ state.Assign(u"showing"_ns); ++ break; ++ case ePopupState_Open: ++ state.Assign(u"open"_ns); ++ break; ++ case ePopupState_Hiding: ++ state.Assign(u"hiding"_ns); ++ break; ++ default: ++ break; ++ } ++ ++ if (state.IsEmpty()) { ++ mPopupContent->AsElement()->UnsetAttr( ++ kNameSpaceID_None, nsGkAtoms::_moz_nativemenupopupstate, ++ false); ++ } else { ++ mPopupContent->AsElement()->SetAttr( ++ kNameSpaceID_None, nsGkAtoms::_moz_nativemenupopupstate, ++ state, false); ++ } ++} ++ ++/* static */ void ++nsMenu::DoOpenCallback(nsITimer *aTimer, void *aClosure) ++{ ++ nsMenu* self = static_cast(aClosure); ++ ++ dbusmenu_menuitem_show_to_user(self->GetNativeData(), 0); ++ ++ self->mOpenDelayTimer = nullptr; ++} ++ ++/* static */ void ++nsMenu::menu_event_cb(DbusmenuMenuitem *menu, ++ const gchar *name, ++ GVariant *value, ++ guint timestamp, ++ gpointer user_data) ++{ ++ nsMenu *self = static_cast(user_data); ++ ++ nsAutoCString event(name); ++ ++ if (event.Equals("closed"_ns)) { ++ self->OnClose(); ++ return; ++ } ++ ++ if (event.Equals("opened"_ns)) { ++ self->OnOpen(); ++ return; ++ } ++} ++ ++void ++nsMenu::MaybeAddPlaceholderItem() ++{ ++ MOZ_ASSERT(!IsInBatchedUpdate(), ++ "Shouldn't be modifying the native menu structure now"); ++ ++ GList *children = dbusmenu_menuitem_get_children(GetNativeData()); ++ if (!children) { ++ MOZ_ASSERT(!mPlaceholderItem); ++ ++ mPlaceholderItem = dbusmenu_menuitem_new(); ++ if (!mPlaceholderItem) { ++ return; ++ } ++ ++ dbusmenu_menuitem_property_set_bool(mPlaceholderItem, ++ DBUSMENU_MENUITEM_PROP_VISIBLE, ++ false); ++ ++ MOZ_ALWAYS_TRUE( ++ dbusmenu_menuitem_child_append(GetNativeData(), mPlaceholderItem)); ++ } ++} ++ ++void ++nsMenu::EnsureNoPlaceholderItem() ++{ ++ MOZ_ASSERT(!IsInBatchedUpdate(), ++ "Shouldn't be modifying the native menu structure now"); ++ ++ if (!mPlaceholderItem) { ++ return; ++ } ++ ++ MOZ_ALWAYS_TRUE( ++ dbusmenu_menuitem_child_delete(GetNativeData(), mPlaceholderItem)); ++ MOZ_ASSERT(!dbusmenu_menuitem_get_children(GetNativeData())); ++ ++ g_object_unref(mPlaceholderItem); ++ mPlaceholderItem = nullptr; ++} ++ ++void ++nsMenu::OnOpen() ++{ ++ if (mNeedsRebuild) { ++ Build(); ++ } ++ ++ nsWeakMenuObject self(this); ++ nsCOMPtr origPopupContent(mPopupContent); ++ { ++ nsNativeMenuDocListener::BlockUpdatesScope updatesBlocker; ++ ++ SetPopupState(ePopupState_Showing); ++ DispatchMouseEvent(mPopupContent, eXULPopupShowing); ++ ++ ContentNode()->AsElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::open, ++ u"true"_ns, true); ++ } ++ ++ if (!self) { ++ // We were deleted! ++ return; ++ } ++ ++ // I guess that the popup could have changed ++ if (origPopupContent != mPopupContent) { ++ return; ++ } ++ ++ nsNativeMenuDocListener::BlockUpdatesScope updatesBlocker; ++ ++ size_t count = ChildCount(); ++ for (size_t i = 0; i < count; ++i) { ++ ChildAt(i)->ContainerIsOpening(); ++ } ++ ++ SetPopupState(ePopupState_Open); ++ DispatchMouseEvent(mPopupContent, eXULPopupShown); ++} ++ ++void ++nsMenu::Build() ++{ ++ mNeedsRebuild = false; ++ ++ while (ChildCount() > 0) { ++ RemoveChildAt(0); ++ } ++ ++ InitializePopup(); ++ ++ if (!mPopupContent) { ++ return; ++ } ++ ++ uint32_t count = mPopupContent->GetChildCount(); ++ for (uint32_t i = 0; i < count; ++i) { ++ nsIContent *childContent = mPopupContent->GetChildAt_Deprecated(i); ++ ++ UniquePtr child = CreateChild(childContent); ++ ++ if (!child) { ++ continue; ++ } ++ ++ AppendChild(std::move(child)); ++ } ++} ++ ++void ++nsMenu::InitializePopup() ++{ ++ nsCOMPtr oldPopupContent; ++ oldPopupContent.swap(mPopupContent); ++ ++ for (uint32_t i = 0; i < ContentNode()->GetChildCount(); ++i) { ++ nsIContent *child = ContentNode()->GetChildAt_Deprecated(i); ++ ++ if (child->NodeInfo()->NameAtom() == nsGkAtoms::menupopup) { ++ mPopupContent = child; ++ break; ++ } ++ } ++ ++ if (oldPopupContent == mPopupContent) { ++ return; ++ } ++ ++ // The popup has changed ++ ++ if (oldPopupContent) { ++ DocListener()->UnregisterForContentChanges(oldPopupContent); ++ } ++ ++ SetPopupState(ePopupState_Closed); ++ ++ if (!mPopupContent) { ++ return; ++ } ++ ++ DocListener()->RegisterForContentChanges(mPopupContent, this); ++} ++ ++void ++nsMenu::RemoveChildAt(size_t aIndex) ++{ ++ MOZ_ASSERT(IsInBatchedUpdate() || !mPlaceholderItem, ++ "Shouldn't have a placeholder menuitem"); ++ ++ nsMenuContainer::RemoveChildAt(aIndex, !IsInBatchedUpdate()); ++ StructureMutated(); ++ ++ if (!IsInBatchedUpdate()) { ++ MaybeAddPlaceholderItem(); ++ } ++} ++ ++void ++nsMenu::RemoveChild(nsIContent *aChild) ++{ ++ size_t index = IndexOf(aChild); ++ if (index == NoIndex) { ++ return; ++ } ++ ++ RemoveChildAt(index); ++} ++ ++void ++nsMenu::InsertChildAfter(UniquePtr aChild, ++ nsIContent *aPrevSibling) ++{ ++ if (!IsInBatchedUpdate()) { ++ EnsureNoPlaceholderItem(); ++ } ++ ++ nsMenuContainer::InsertChildAfter(std::move(aChild), aPrevSibling, ++ !IsInBatchedUpdate()); ++ StructureMutated(); ++} ++ ++void ++nsMenu::AppendChild(UniquePtr aChild) ++{ ++ if (!IsInBatchedUpdate()) { ++ EnsureNoPlaceholderItem(); ++ } ++ ++ nsMenuContainer::AppendChild(std::move(aChild), !IsInBatchedUpdate()); ++ StructureMutated(); ++} ++ ++bool ++nsMenu::IsInBatchedUpdate() const ++{ ++ return mBatchedUpdateState != eBatchedUpdateState_Inactive; ++} ++ ++void ++nsMenu::StructureMutated() ++{ ++ if (!IsInBatchedUpdate()) { ++ return; ++ } ++ ++ mBatchedUpdateState = eBatchedUpdateState_DidMutate; ++} ++ ++bool ++nsMenu::CanOpen() const ++{ ++ bool isVisible = dbusmenu_menuitem_property_get_bool(GetNativeData(), ++ DBUSMENU_MENUITEM_PROP_VISIBLE); ++ bool isDisabled = ContentNode()->AsElement()->AttrValueIs(kNameSpaceID_None, ++ nsGkAtoms::disabled, ++ nsGkAtoms::_true, ++ eCaseMatters); ++ ++ return (isVisible && !isDisabled); ++} ++ ++void ++nsMenu::HandleContentInserted(nsIContent *aContainer, ++ nsIContent *aChild, ++ nsIContent *aPrevSibling) ++{ ++ if (aContainer == mPopupContent) { ++ UniquePtr child = CreateChild(aChild); ++ ++ if (child) { ++ InsertChildAfter(std::move(child), aPrevSibling); ++ } ++ } else { ++ Build(); ++ } ++} ++ ++void ++nsMenu::HandleContentRemoved(nsIContent *aContainer, nsIContent *aChild) ++{ ++ if (aContainer == mPopupContent) { ++ RemoveChild(aChild); ++ } else { ++ Build(); ++ } ++} ++ ++void ++nsMenu::InitializeNativeData() ++{ ++ // Dbusmenu provides an "about-to-show" signal, and also "opened" and ++ // "closed" events. However, Unity is the only thing that sends ++ // both "about-to-show" and "opened" events. Unity 2D and the HUD only ++ // send "opened" events, so we ignore "about-to-show" (I don't think ++ // there's any real difference between them anyway). ++ // To complicate things, there are certain conditions where we don't ++ // get a "closed" event, so we need to be able to handle this :/ ++ g_signal_connect(G_OBJECT(GetNativeData()), "event", ++ G_CALLBACK(menu_event_cb), this); ++ ++ mNeedsRebuild = true; ++ mNeedsUpdate = true; ++ ++ MaybeAddPlaceholderItem(); ++} ++ ++void ++nsMenu::Update(const ComputedStyle *aComputedStyle) ++{ ++ if (mNeedsUpdate) { ++ mNeedsUpdate = false; ++ ++ UpdateLabel(); ++ UpdateSensitivity(); ++ } ++ ++ UpdateVisibility(aComputedStyle); ++ UpdateIcon(aComputedStyle); ++} ++ ++nsMenuObject::PropertyFlags ++nsMenu::SupportedProperties() const ++{ ++ return static_cast( ++ nsMenuObject::ePropLabel | ++ nsMenuObject::ePropEnabled | ++ nsMenuObject::ePropVisible | ++ nsMenuObject::ePropIconData | ++ nsMenuObject::ePropChildDisplay ++ ); ++} ++ ++void ++nsMenu::OnAttributeChanged(nsIContent *aContent, nsAtom *aAttribute) ++{ ++ MOZ_ASSERT(aContent == ContentNode() || aContent == mPopupContent, ++ "Received an event that wasn't meant for us!"); ++ ++ if (mNeedsUpdate) { ++ return; ++ } ++ ++ if (aContent != ContentNode()) { ++ return; ++ } ++ ++ if (!Parent()->IsBeingDisplayed()) { ++ mNeedsUpdate = true; ++ return; ++ } ++ ++ if (aAttribute == nsGkAtoms::disabled) { ++ UpdateSensitivity(); ++ } else if (aAttribute == nsGkAtoms::label || ++ aAttribute == nsGkAtoms::accesskey || ++ aAttribute == nsGkAtoms::crop) { ++ UpdateLabel(); ++ } else if (aAttribute == nsGkAtoms::hidden || ++ aAttribute == nsGkAtoms::collapsed) { ++ RefPtr style = GetComputedStyle(); ++ UpdateVisibility(style); ++ } else if (aAttribute == nsGkAtoms::image) { ++ RefPtr style = GetComputedStyle(); ++ UpdateIcon(style); ++ } ++} ++ ++void ++nsMenu::OnContentInserted(nsIContent *aContainer, nsIContent *aChild, ++ nsIContent *aPrevSibling) ++{ ++ MOZ_ASSERT(aContainer == ContentNode() || aContainer == mPopupContent, ++ "Received an event that wasn't meant for us!"); ++ ++ if (mNeedsRebuild) { ++ return; ++ } ++ ++ if (mPopupState == ePopupState_Closed) { ++ mNeedsRebuild = true; ++ return; ++ } ++ ++ nsContentUtils::AddScriptRunner( ++ new nsMenuContentInsertedEvent(this, aContainer, aChild, ++ aPrevSibling)); ++} ++ ++void ++nsMenu::OnContentRemoved(nsIContent *aContainer, nsIContent *aChild) ++{ ++ MOZ_ASSERT(aContainer == ContentNode() || aContainer == mPopupContent, ++ "Received an event that wasn't meant for us!"); ++ ++ if (mNeedsRebuild) { ++ return; ++ } ++ ++ if (mPopupState == ePopupState_Closed) { ++ mNeedsRebuild = true; ++ return; ++ } ++ ++ nsContentUtils::AddScriptRunner( ++ new nsMenuContentRemovedEvent(this, aContainer, aChild)); ++} ++ ++/* ++ * Some menus (eg, the History menu in Firefox) refresh themselves on ++ * opening by removing all children and then re-adding new ones. As this ++ * happens whilst the menu is opening in Unity, it causes some flickering ++ * as the menu popup is resized multiple times. To avoid this, we try to ++ * reuse native menu items when the menu structure changes during a ++ * batched update. If we can handle menu structure changes from Gecko ++ * just by updating properties of native menu items (rather than destroying ++ * and creating new ones), then we eliminate any flickering that occurs as ++ * the menu is opened. To do this, we don't modify any native menu items ++ * until the end of the update batch. ++ */ ++ ++void ++nsMenu::OnBeginUpdates(nsIContent *aContent) ++{ ++ MOZ_ASSERT(aContent == ContentNode() || aContent == mPopupContent, ++ "Received an event that wasn't meant for us!"); ++ MOZ_ASSERT(!IsInBatchedUpdate(), "Already in an update batch!"); ++ ++ if (aContent != mPopupContent) { ++ return; ++ } ++ ++ mBatchedUpdateState = eBatchedUpdateState_Active; ++} ++ ++void ++nsMenu::OnEndUpdates() ++{ ++ if (!IsInBatchedUpdate()) { ++ return; ++ } ++ ++ bool didMutate = mBatchedUpdateState == eBatchedUpdateState_DidMutate; ++ mBatchedUpdateState = eBatchedUpdateState_Inactive; ++ ++ /* Optimize for the case where we only had attribute changes */ ++ if (!didMutate) { ++ return; ++ } ++ ++ EnsureNoPlaceholderItem(); ++ ++ GList *nextNativeChild = dbusmenu_menuitem_get_children(GetNativeData()); ++ DbusmenuMenuitem *nextOwnedNativeChild = nullptr; ++ ++ size_t count = ChildCount(); ++ ++ // Find the first native menu item that is `owned` by a corresponding ++ // Gecko menuitem ++ for (size_t i = 0; i < count; ++i) { ++ if (ChildAt(i)->GetNativeData()) { ++ nextOwnedNativeChild = ChildAt(i)->GetNativeData(); ++ break; ++ } ++ } ++ ++ // Now iterate over all Gecko menuitems ++ for (size_t i = 0; i < count; ++i) { ++ nsMenuObject *child = ChildAt(i); ++ ++ if (child->GetNativeData()) { ++ // This child already has a corresponding native menuitem. ++ // Remove all preceding orphaned native items. At this point, we ++ // modify the native menu structure. ++ while (nextNativeChild && ++ nextNativeChild->data != nextOwnedNativeChild) { ++ ++ DbusmenuMenuitem *data = ++ static_cast(nextNativeChild->data); ++ nextNativeChild = nextNativeChild->next; ++ ++ MOZ_ALWAYS_TRUE(dbusmenu_menuitem_child_delete(GetNativeData(), ++ data)); ++ } ++ ++ if (nextNativeChild) { ++ nextNativeChild = nextNativeChild->next; ++ } ++ ++ // Now find the next native menu item that is `owned` ++ nextOwnedNativeChild = nullptr; ++ for (size_t j = i + 1; j < count; ++j) { ++ if (ChildAt(j)->GetNativeData()) { ++ nextOwnedNativeChild = ChildAt(j)->GetNativeData(); ++ break; ++ } ++ } ++ } else { ++ // This child is new, and doesn't have a native menu item. Find one! ++ if (nextNativeChild && ++ nextNativeChild->data != nextOwnedNativeChild) { ++ ++ DbusmenuMenuitem *data = ++ static_cast(nextNativeChild->data); ++ ++ if (NS_SUCCEEDED(child->AdoptNativeData(data))) { ++ nextNativeChild = nextNativeChild->next; ++ } ++ } ++ ++ // There wasn't a suitable one available, so create a new one. ++ // At this point, we modify the native menu structure. ++ if (!child->GetNativeData()) { ++ child->CreateNativeData(); ++ MOZ_ALWAYS_TRUE( ++ dbusmenu_menuitem_child_add_position(GetNativeData(), ++ child->GetNativeData(), ++ i)); ++ } ++ } ++ } ++ ++ while (nextNativeChild) { ++ DbusmenuMenuitem *data = ++ static_cast(nextNativeChild->data); ++ nextNativeChild = nextNativeChild->next; ++ ++ MOZ_ALWAYS_TRUE(dbusmenu_menuitem_child_delete(GetNativeData(), data)); ++ } ++ ++ MaybeAddPlaceholderItem(); ++} ++ ++nsMenu::nsMenu(nsMenuContainer *aParent, nsIContent *aContent) : ++ nsMenuContainer(aParent, aContent), ++ mNeedsRebuild(false), ++ mNeedsUpdate(false), ++ mPlaceholderItem(nullptr), ++ mPopupState(ePopupState_Closed), ++ mBatchedUpdateState(eBatchedUpdateState_Inactive) ++{ ++ MOZ_COUNT_CTOR(nsMenu); ++} ++ ++nsMenu::~nsMenu() ++{ ++ if (IsInBatchedUpdate()) { ++ OnEndUpdates(); ++ } ++ ++ // Although nsTArray will take care of this in its destructor, ++ // we have to manually ensure children are removed from our native menu ++ // item, just in case our parent recycles us ++ while (ChildCount() > 0) { ++ RemoveChildAt(0); ++ } ++ ++ EnsureNoPlaceholderItem(); ++ ++ if (DocListener() && mPopupContent) { ++ DocListener()->UnregisterForContentChanges(mPopupContent); ++ } ++ ++ if (GetNativeData()) { ++ g_signal_handlers_disconnect_by_func(GetNativeData(), ++ FuncToGpointer(menu_event_cb), ++ this); ++ } ++ ++ MOZ_COUNT_DTOR(nsMenu); ++} ++ ++nsMenuObject::EType ++nsMenu::Type() const ++{ ++ return eType_Menu; ++} ++ ++bool ++nsMenu::IsBeingDisplayed() const ++{ ++ return mPopupState == ePopupState_Open; ++} ++ ++bool ++nsMenu::NeedsRebuild() const ++{ ++ return mNeedsRebuild; ++} ++ ++void ++nsMenu::OpenMenu() ++{ ++ if (!CanOpen()) { ++ return; ++ } ++ ++ if (mOpenDelayTimer) { ++ return; ++ } ++ ++ // Here, we synchronously fire popupshowing and popupshown events and then ++ // open the menu after a short delay. This allows the menu to refresh before ++ // it's shown, and avoids an issue where keyboard focus is not on the first ++ // item of the history menu in Firefox when opening it with the keyboard, ++ // because extra items to appear at the top of the menu ++ ++ OnOpen(); ++ ++ mOpenDelayTimer = NS_NewTimer(); ++ if (!mOpenDelayTimer) { ++ return; ++ } ++ ++ if (NS_FAILED(mOpenDelayTimer->InitWithNamedFuncCallback(DoOpenCallback, ++ this, ++ 100, ++ nsITimer::TYPE_ONE_SHOT, ++ "nsMenu::DoOpenCallback"))) { ++ mOpenDelayTimer = nullptr; ++ } ++} ++ ++void ++nsMenu::OnClose() ++{ ++ if (mPopupState == ePopupState_Closed) { ++ return; ++ } ++ ++ MOZ_ASSERT(nsContentUtils::IsSafeToRunScript()); ++ ++ // We do this to avoid mutating our view of the menu until ++ // after we have finished ++ nsNativeMenuDocListener::BlockUpdatesScope updatesBlocker; ++ ++ SetPopupState(ePopupState_Hiding); ++ DispatchMouseEvent(mPopupContent, eXULPopupHiding); ++ ++ // Sigh, make sure all of our descendants are closed, as we don't ++ // always get closed events for submenus when scrubbing quickly through ++ // the menu ++ size_t count = ChildCount(); ++ for (size_t i = 0; i < count; ++i) { ++ if (ChildAt(i)->Type() == nsMenuObject::eType_Menu) { ++ static_cast(ChildAt(i))->OnClose(); ++ } ++ } ++ ++ SetPopupState(ePopupState_Closed); ++ DispatchMouseEvent(mPopupContent, eXULPopupHidden); ++ ++ ContentNode()->AsElement()->UnsetAttr(kNameSpaceID_None, nsGkAtoms::open, ++ true); ++} ++ +diff --git a/widget/gtk/nsMenu.h b/widget/gtk/nsMenu.h +new file mode 100644 +index 000000000000..40244d012290 +--- /dev/null ++++ b/widget/gtk/nsMenu.h +@@ -0,0 +1,123 @@ ++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ ++/* vim:expandtab:shiftwidth=4:tabstop=4: ++ */ ++/* This Source Code Form is subject to the terms of the Mozilla Public ++ * License, v. 2.0. If a copy of the MPL was not distributed with this ++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ ++ ++#ifndef __nsMenu_h__ ++#define __nsMenu_h__ ++ ++#include "mozilla/Attributes.h" ++#include "mozilla/UniquePtr.h" ++#include "nsCOMPtr.h" ++ ++#include "nsDbusmenu.h" ++#include "nsMenuContainer.h" ++#include "nsMenuObject.h" ++ ++#include ++ ++class nsAtom; ++class nsIContent; ++class nsITimer; ++ ++#define NSMENU_NUMBER_OF_POPUPSTATE_BITS 2U ++#define NSMENU_NUMBER_OF_FLAGS 4U ++ ++// This class represents a menu ++class nsMenu final : public nsMenuContainer ++{ ++public: ++ nsMenu(nsMenuContainer *aParent, nsIContent *aContent); ++ ~nsMenu(); ++ ++ nsMenuObject::EType Type() const override; ++ ++ bool IsBeingDisplayed() const override; ++ bool NeedsRebuild() const override; ++ ++ // Tell the desktop shell to display this menu ++ void OpenMenu(); ++ ++ // Normally called via the shell, but it's public so that child ++ // menuitems can do the shells work. Sigh.... ++ void OnClose(); ++ ++private: ++ friend class nsMenuContentInsertedEvent; ++ friend class nsMenuContentRemovedEvent; ++ ++ enum EPopupState { ++ ePopupState_Closed, ++ ePopupState_Showing, ++ ePopupState_Open, ++ ePopupState_Hiding ++ }; ++ ++ void SetPopupState(EPopupState aState); ++ ++ static void DoOpenCallback(nsITimer *aTimer, void *aClosure); ++ static void menu_event_cb(DbusmenuMenuitem *menu, ++ const gchar *name, ++ GVariant *value, ++ guint timestamp, ++ gpointer user_data); ++ ++ // We add a placeholder item to empty menus so that Unity actually treats ++ // us as a proper menu, rather than a menuitem without a submenu ++ void MaybeAddPlaceholderItem(); ++ ++ // Removes a placeholder item if it exists and asserts that this succeeds ++ void EnsureNoPlaceholderItem(); ++ ++ void OnOpen(); ++ void Build(); ++ void InitializePopup(); ++ void RemoveChildAt(size_t aIndex); ++ void RemoveChild(nsIContent *aChild); ++ void InsertChildAfter(mozilla::UniquePtr aChild, ++ nsIContent *aPrevSibling); ++ void AppendChild(mozilla::UniquePtr aChild); ++ bool IsInBatchedUpdate() const; ++ void StructureMutated(); ++ bool CanOpen() const; ++ ++ void HandleContentInserted(nsIContent *aContainer, ++ nsIContent *aChild, ++ nsIContent *aPrevSibling); ++ void HandleContentRemoved(nsIContent *aContainer, ++ nsIContent *aChild); ++ ++ void InitializeNativeData() override; ++ void Update(const mozilla::ComputedStyle *aComputedStyle) override; ++ nsMenuObject::PropertyFlags SupportedProperties() const override; ++ ++ void OnAttributeChanged(nsIContent *aContent, nsAtom *aAttribute) override; ++ void OnContentInserted(nsIContent *aContainer, nsIContent *aChild, ++ nsIContent *aPrevSibling) override; ++ void OnContentRemoved(nsIContent *aContainer, nsIContent *aChild) override; ++ void OnBeginUpdates(nsIContent *aContent) override; ++ void OnEndUpdates() override; ++ ++ bool mNeedsRebuild; ++ bool mNeedsUpdate; ++ ++ DbusmenuMenuitem *mPlaceholderItem; ++ ++ EPopupState mPopupState; ++ ++ enum EBatchedUpdateState { ++ eBatchedUpdateState_Inactive, ++ eBatchedUpdateState_Active, ++ eBatchedUpdateState_DidMutate ++ }; ++ ++ EBatchedUpdateState mBatchedUpdateState; ++ ++ nsCOMPtr mPopupContent; ++ ++ nsCOMPtr mOpenDelayTimer; ++}; ++ ++#endif /* __nsMenu_h__ */ +diff --git a/widget/gtk/nsMenuBar.cpp b/widget/gtk/nsMenuBar.cpp +new file mode 100644 +index 000000000000..0dcae6794329 +--- /dev/null ++++ b/widget/gtk/nsMenuBar.cpp +@@ -0,0 +1,548 @@ ++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ ++/* vim:expandtab:shiftwidth=4:tabstop=4: ++ */ ++/* This Source Code Form is subject to the terms of the Mozilla Public ++ * License, v. 2.0. If a copy of the MPL was not distributed with this ++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ ++ ++#include "mozilla/Assertions.h" ++#include "mozilla/DebugOnly.h" ++#include "mozilla/dom/Document.h" ++#include "mozilla/dom/Element.h" ++#include "mozilla/dom/Event.h" ++#include "mozilla/dom/KeyboardEvent.h" ++#include "mozilla/dom/KeyboardEventBinding.h" ++#include "mozilla/Preferences.h" ++#include "nsContentUtils.h" ++#include "nsIDOMEventListener.h" ++#include "nsIRunnable.h" ++#include "nsIWidget.h" ++#include "nsTArray.h" ++#include "nsUnicharUtils.h" ++ ++#include "nsMenu.h" ++#include "nsNativeMenuService.h" ++ ++#include ++#include ++#include ++#include ++ ++#include "nsMenuBar.h" ++ ++using namespace mozilla; ++ ++static bool ++ShouldHandleKeyEvent(dom::KeyboardEvent *aEvent) ++{ ++ return !aEvent->DefaultPrevented() && aEvent->IsTrusted(); ++} ++ ++class nsMenuBarContentInsertedEvent : public Runnable ++{ ++public: ++ nsMenuBarContentInsertedEvent(nsMenuBar *aMenuBar, ++ nsIContent *aChild, ++ nsIContent *aPrevSibling) : ++ Runnable("nsMenuBarContentInsertedEvent"), ++ mWeakMenuBar(aMenuBar), ++ mChild(aChild), ++ mPrevSibling(aPrevSibling) { } ++ ++ NS_IMETHODIMP Run() ++ { ++ if (!mWeakMenuBar) { ++ return NS_OK; ++ } ++ ++ static_cast(mWeakMenuBar.get())->HandleContentInserted(mChild, ++ mPrevSibling); ++ return NS_OK; ++ } ++ ++private: ++ nsWeakMenuObject mWeakMenuBar; ++ ++ nsCOMPtr mChild; ++ nsCOMPtr mPrevSibling; ++}; ++ ++class nsMenuBarContentRemovedEvent : public Runnable ++{ ++public: ++ nsMenuBarContentRemovedEvent(nsMenuBar *aMenuBar, ++ nsIContent *aChild) : ++ Runnable("nsMenuBarContentRemovedEvent"), ++ mWeakMenuBar(aMenuBar), ++ mChild(aChild) { } ++ ++ NS_IMETHODIMP Run() ++ { ++ if (!mWeakMenuBar) { ++ return NS_OK; ++ } ++ ++ static_cast(mWeakMenuBar.get())->HandleContentRemoved(mChild); ++ return NS_OK; ++ } ++ ++private: ++ nsWeakMenuObject mWeakMenuBar; ++ ++ nsCOMPtr mChild; ++}; ++ ++class nsMenuBar::DocEventListener final : public nsIDOMEventListener ++{ ++public: ++ NS_DECL_ISUPPORTS ++ NS_DECL_NSIDOMEVENTLISTENER ++ ++ DocEventListener(nsMenuBar *aOwner) : mOwner(aOwner) { }; ++ ++private: ++ ~DocEventListener() { }; ++ ++ nsMenuBar *mOwner; ++}; ++ ++NS_IMPL_ISUPPORTS(nsMenuBar::DocEventListener, nsIDOMEventListener) ++ ++NS_IMETHODIMP ++nsMenuBar::DocEventListener::HandleEvent(dom::Event *aEvent) ++{ ++ nsAutoString type; ++ aEvent->GetType(type); ++ ++ if (type.Equals(u"focus"_ns)) { ++ mOwner->Focus(); ++ } else if (type.Equals(u"blur"_ns)) { ++ mOwner->Blur(); ++ } ++ ++ RefPtr keyEvent = aEvent->AsKeyboardEvent(); ++ if (!keyEvent) { ++ return NS_OK; ++ } ++ ++ if (type.Equals(u"keypress"_ns)) { ++ return mOwner->Keypress(keyEvent); ++ } else if (type.Equals(u"keydown"_ns)) { ++ return mOwner->KeyDown(keyEvent); ++ } else if (type.Equals(u"keyup"_ns)) { ++ return mOwner->KeyUp(keyEvent); ++ } ++ ++ return NS_OK; ++} ++ ++nsMenuBar::nsMenuBar(nsIContent *aMenuBarNode) : ++ nsMenuContainer(new nsNativeMenuDocListener(aMenuBarNode), aMenuBarNode), ++ mTopLevel(nullptr), ++ mServer(nullptr), ++ mIsActive(false) ++{ ++ MOZ_COUNT_CTOR(nsMenuBar); ++} ++ ++nsresult ++nsMenuBar::Init(nsIWidget *aParent) ++{ ++ MOZ_ASSERT(aParent); ++ ++ GdkWindow *gdkWin = static_cast( ++ aParent->GetNativeData(NS_NATIVE_WINDOW)); ++ if (!gdkWin) { ++ return NS_ERROR_FAILURE; ++ } ++ ++ gpointer user_data = nullptr; ++ gdk_window_get_user_data(gdkWin, &user_data); ++ if (!user_data || !GTK_IS_CONTAINER(user_data)) { ++ return NS_ERROR_FAILURE; ++ } ++ ++ mTopLevel = gtk_widget_get_toplevel(GTK_WIDGET(user_data)); ++ if (!mTopLevel) { ++ return NS_ERROR_FAILURE; ++ } ++ ++ g_object_ref(mTopLevel); ++ ++ nsAutoCString path; ++ path.Append("/com/canonical/menu/"_ns); ++ char xid[10]; ++ sprintf(xid, "%X", static_cast( ++ GDK_WINDOW_XID(gtk_widget_get_window(mTopLevel)))); ++ path.Append(xid); ++ ++ mServer = dbusmenu_server_new(path.get()); ++ if (!mServer) { ++ return NS_ERROR_FAILURE; ++ } ++ ++ CreateNativeData(); ++ if (!GetNativeData()) { ++ return NS_ERROR_FAILURE; ++ } ++ ++ dbusmenu_server_set_root(mServer, GetNativeData()); ++ ++ mEventListener = new DocEventListener(this); ++ ++ mDocument = do_QueryInterface(ContentNode()->OwnerDoc()); ++ ++ mAccessKey = Preferences::GetInt("ui.key.menuAccessKey"); ++ if (mAccessKey == dom::KeyboardEvent_Binding::DOM_VK_SHIFT) { ++ mAccessKeyMask = eModifierShift; ++ } else if (mAccessKey == dom::KeyboardEvent_Binding::DOM_VK_CONTROL) { ++ mAccessKeyMask = eModifierCtrl; ++ } else if (mAccessKey == dom::KeyboardEvent_Binding::DOM_VK_ALT) { ++ mAccessKeyMask = eModifierAlt; ++ } else if (mAccessKey == dom::KeyboardEvent_Binding::DOM_VK_META) { ++ mAccessKeyMask = eModifierMeta; ++ } else { ++ mAccessKeyMask = eModifierAlt; ++ } ++ ++ return NS_OK; ++} ++ ++void ++nsMenuBar::Build() ++{ ++ uint32_t count = ContentNode()->GetChildCount(); ++ for (uint32_t i = 0; i < count; ++i) { ++ nsIContent *childContent = ContentNode()->GetChildAt_Deprecated(i); ++ ++ UniquePtr child = CreateChild(childContent); ++ ++ if (!child) { ++ continue; ++ } ++ ++ AppendChild(std::move(child)); ++ } ++} ++ ++void ++nsMenuBar::DisconnectDocumentEventListeners() ++{ ++ mDocument->RemoveEventListener(u"focus"_ns, ++ mEventListener, ++ true); ++ mDocument->RemoveEventListener(u"blur"_ns, ++ mEventListener, ++ true); ++ mDocument->RemoveEventListener(u"keypress"_ns, ++ mEventListener, ++ false); ++ mDocument->RemoveEventListener(u"keydown"_ns, ++ mEventListener, ++ false); ++ mDocument->RemoveEventListener(u"keyup"_ns, ++ mEventListener, ++ false); ++} ++ ++void ++nsMenuBar::SetShellShowingMenuBar(bool aShowing) ++{ ++ ContentNode()->OwnerDoc()->GetRootElement()->SetAttr( ++ kNameSpaceID_None, nsGkAtoms::shellshowingmenubar, ++ aShowing ? u"true"_ns : u"false"_ns, ++ true); ++} ++ ++void ++nsMenuBar::Focus() ++{ ++ ContentNode()->AsElement()->SetAttr(kNameSpaceID_None, ++ nsGkAtoms::openedwithkey, ++ u"false"_ns, true); ++} ++ ++void ++nsMenuBar::Blur() ++{ ++ // We do this here in case we lose focus before getting the ++ // keyup event, which leaves the menubar state looking like ++ // the alt key is stuck down ++ dbusmenu_server_set_status(mServer, DBUSMENU_STATUS_NORMAL); ++} ++ ++nsMenuBar::ModifierFlags ++nsMenuBar::GetModifiersFromEvent(dom::KeyboardEvent *aEvent) ++{ ++ ModifierFlags modifiers = static_cast(0); ++ ++ if (aEvent->AltKey()) { ++ modifiers = static_cast(modifiers | eModifierAlt); ++ } ++ ++ if (aEvent->ShiftKey()) { ++ modifiers = static_cast(modifiers | eModifierShift); ++ } ++ ++ if (aEvent->CtrlKey()) { ++ modifiers = static_cast(modifiers | eModifierCtrl); ++ } ++ ++ if (aEvent->MetaKey()) { ++ modifiers = static_cast(modifiers | eModifierMeta); ++ } ++ ++ return modifiers; ++} ++ ++nsresult ++nsMenuBar::Keypress(dom::KeyboardEvent *aEvent) ++{ ++ if (!ShouldHandleKeyEvent(aEvent)) { ++ return NS_OK; ++ } ++ ++ ModifierFlags modifiers = GetModifiersFromEvent(aEvent); ++ if (((modifiers & mAccessKeyMask) == 0) || ++ ((modifiers & ~mAccessKeyMask) != 0)) { ++ return NS_OK; ++ } ++ ++ uint32_t charCode = aEvent->CharCode(); ++ if (charCode == 0) { ++ return NS_OK; ++ } ++ ++ char16_t ch = char16_t(charCode); ++ char16_t chl = ToLowerCase(ch); ++ char16_t chu = ToUpperCase(ch); ++ ++ nsMenuObject *found = nullptr; ++ uint32_t count = ChildCount(); ++ for (uint32_t i = 0; i < count; ++i) { ++ nsAutoString accesskey; ++ ChildAt(i)->ContentNode()->AsElement()->GetAttr(kNameSpaceID_None, ++ nsGkAtoms::accesskey, ++ accesskey); ++ const nsAutoString::char_type *key = accesskey.BeginReading(); ++ if (*key == chu || *key == chl) { ++ found = ChildAt(i); ++ break; ++ } ++ } ++ ++ if (!found || found->Type() != nsMenuObject::eType_Menu) { ++ return NS_OK; ++ } ++ ++ ContentNode()->AsElement()->SetAttr(kNameSpaceID_None, ++ nsGkAtoms::openedwithkey, ++ u"true"_ns, true); ++ static_cast(found)->OpenMenu(); ++ ++ aEvent->StopPropagation(); ++ aEvent->PreventDefault(); ++ ++ return NS_OK; ++} ++ ++nsresult ++nsMenuBar::KeyDown(dom::KeyboardEvent *aEvent) ++{ ++ if (!ShouldHandleKeyEvent(aEvent)) { ++ return NS_OK; ++ } ++ ++ uint32_t keyCode = aEvent->KeyCode(); ++ ModifierFlags modifiers = GetModifiersFromEvent(aEvent); ++ if ((keyCode != mAccessKey) || ((modifiers & ~mAccessKeyMask) != 0)) { ++ return NS_OK; ++ } ++ ++ dbusmenu_server_set_status(mServer, DBUSMENU_STATUS_NOTICE); ++ ++ return NS_OK; ++} ++ ++nsresult ++nsMenuBar::KeyUp(dom::KeyboardEvent *aEvent) ++{ ++ if (!ShouldHandleKeyEvent(aEvent)) { ++ return NS_OK; ++ } ++ ++ uint32_t keyCode = aEvent->KeyCode(); ++ if (keyCode == mAccessKey) { ++ dbusmenu_server_set_status(mServer, DBUSMENU_STATUS_NORMAL); ++ } ++ ++ return NS_OK; ++} ++ ++void ++nsMenuBar::HandleContentInserted(nsIContent *aChild, nsIContent *aPrevSibling) ++{ ++ UniquePtr child = CreateChild(aChild); ++ ++ if (!child) { ++ return; ++ } ++ ++ InsertChildAfter(std::move(child), aPrevSibling); ++} ++ ++void ++nsMenuBar::HandleContentRemoved(nsIContent *aChild) ++{ ++ RemoveChild(aChild); ++} ++ ++void ++nsMenuBar::OnContentInserted(nsIContent *aContainer, nsIContent *aChild, ++ nsIContent *aPrevSibling) ++{ ++ MOZ_ASSERT(aContainer == ContentNode(), ++ "Received an event that wasn't meant for us"); ++ ++ nsContentUtils::AddScriptRunner( ++ new nsMenuBarContentInsertedEvent(this, aChild, aPrevSibling)); ++} ++ ++void ++nsMenuBar::OnContentRemoved(nsIContent *aContainer, nsIContent *aChild) ++{ ++ MOZ_ASSERT(aContainer == ContentNode(), ++ "Received an event that wasn't meant for us"); ++ ++ nsContentUtils::AddScriptRunner( ++ new nsMenuBarContentRemovedEvent(this, aChild)); ++} ++ ++nsMenuBar::~nsMenuBar() ++{ ++ nsNativeMenuService *service = nsNativeMenuService::GetSingleton(); ++ if (service) { ++ service->NotifyNativeMenuBarDestroyed(this); ++ } ++ ++ if (ContentNode()) { ++ SetShellShowingMenuBar(false); ++ } ++ ++ // We want to destroy all children before dropping our reference ++ // to the doc listener ++ while (ChildCount() > 0) { ++ RemoveChildAt(0); ++ } ++ ++ if (mTopLevel) { ++ g_object_unref(mTopLevel); ++ } ++ ++ if (DocListener()) { ++ DocListener()->Stop(); ++ } ++ ++ if (mDocument) { ++ DisconnectDocumentEventListeners(); ++ } ++ ++ if (mServer) { ++ g_object_unref(mServer); ++ } ++ ++ MOZ_COUNT_DTOR(nsMenuBar); ++} ++ ++/* static */ UniquePtr ++nsMenuBar::Create(nsIWidget *aParent, nsIContent *aMenuBarNode) ++{ ++ UniquePtr menubar(new nsMenuBar(aMenuBarNode)); ++ if (NS_FAILED(menubar->Init(aParent))) { ++ return nullptr; ++ } ++ ++ return menubar; ++} ++ ++nsMenuObject::EType ++nsMenuBar::Type() const ++{ ++ return eType_MenuBar; ++} ++ ++bool ++nsMenuBar::IsBeingDisplayed() const ++{ ++ return true; ++} ++ ++uint32_t ++nsMenuBar::WindowId() const ++{ ++ return static_cast(GDK_WINDOW_XID(gtk_widget_get_window(mTopLevel))); ++} ++ ++nsCString ++nsMenuBar::ObjectPath() const ++{ ++ gchar *tmp; ++ g_object_get(mServer, DBUSMENU_SERVER_PROP_DBUS_OBJECT, &tmp, NULL); ++ ++ nsCString result; ++ result.Adopt(tmp); ++ ++ return result; ++} ++ ++void ++nsMenuBar::Activate() ++{ ++ if (mIsActive) { ++ return; ++ } ++ ++ mIsActive = true; ++ ++ mDocument->AddEventListener(u"focus"_ns, ++ mEventListener, ++ true); ++ mDocument->AddEventListener(u"blur"_ns, ++ mEventListener, ++ true); ++ mDocument->AddEventListener(u"keypress"_ns, ++ mEventListener, ++ false); ++ mDocument->AddEventListener(u"keydown"_ns, ++ mEventListener, ++ false); ++ mDocument->AddEventListener(u"keyup"_ns, ++ mEventListener, ++ false); ++ ++ // Clear this. Not sure if we really need to though ++ ContentNode()->AsElement()->SetAttr(kNameSpaceID_None, ++ nsGkAtoms::openedwithkey, ++ u"false"_ns, true); ++ ++ DocListener()->Start(); ++ Build(); ++ SetShellShowingMenuBar(true); ++} ++ ++void ++nsMenuBar::Deactivate() ++{ ++ if (!mIsActive) { ++ return; ++ } ++ ++ mIsActive = false; ++ ++ SetShellShowingMenuBar(false); ++ while (ChildCount() > 0) { ++ RemoveChildAt(0); ++ } ++ DocListener()->Stop(); ++ DisconnectDocumentEventListeners(); ++} +diff --git a/widget/gtk/nsMenuBar.h b/widget/gtk/nsMenuBar.h +new file mode 100644 +index 000000000000..7a04316330c1 +--- /dev/null ++++ b/widget/gtk/nsMenuBar.h +@@ -0,0 +1,111 @@ ++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ ++/* vim:expandtab:shiftwidth=4:tabstop=4: ++ */ ++/* This Source Code Form is subject to the terms of the Mozilla Public ++ * License, v. 2.0. If a copy of the MPL was not distributed with this ++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ ++ ++#ifndef __nsMenuBar_h__ ++#define __nsMenuBar_h__ ++ ++#include "mozilla/Attributes.h" ++#include "mozilla/UniquePtr.h" ++#include "nsCOMPtr.h" ++#include "nsString.h" ++ ++#include "nsDbusmenu.h" ++#include "nsMenuContainer.h" ++#include "nsMenuObject.h" ++ ++#include ++ ++class nsIContent; ++class nsIWidget; ++class nsMenuBarDocEventListener; ++ ++namespace mozilla { ++namespace dom { ++class Document; ++class KeyboardEvent; ++} ++} ++ ++/* ++ * The menubar class. There is one of these per window (and the window ++ * owns its menubar). Each menubar has an object path, and the service is ++ * responsible for telling the desktop shell which object path corresponds ++ * to a particular window. A menubar and its hierarchy also own a ++ * nsNativeMenuDocListener. ++ */ ++class nsMenuBar final : public nsMenuContainer ++{ ++public: ++ ~nsMenuBar() override; ++ ++ static mozilla::UniquePtr Create(nsIWidget *aParent, ++ nsIContent *aMenuBarNode); ++ ++ nsMenuObject::EType Type() const override; ++ ++ bool IsBeingDisplayed() const override; ++ ++ // Get the native window ID for this menubar ++ uint32_t WindowId() const; ++ ++ // Get the object path for this menubar ++ nsCString ObjectPath() const; ++ ++ // Get the top-level GtkWindow handle ++ GtkWidget* TopLevelWindow() { return mTopLevel; } ++ ++ // Called from the menuservice when the menubar is about to be registered. ++ // Causes the native menubar to be created, and the XUL menubar to be hidden ++ void Activate(); ++ ++ // Called from the menuservice when the menubar is no longer registered ++ // with the desktop shell. Will cause the XUL menubar to be shown again ++ void Deactivate(); ++ ++private: ++ class DocEventListener; ++ friend class nsMenuBarContentInsertedEvent; ++ friend class nsMenuBarContentRemovedEvent; ++ ++ enum ModifierFlags { ++ eModifierShift = (1 << 0), ++ eModifierCtrl = (1 << 1), ++ eModifierAlt = (1 << 2), ++ eModifierMeta = (1 << 3) ++ }; ++ ++ nsMenuBar(nsIContent *aMenuBarNode); ++ nsresult Init(nsIWidget *aParent); ++ void Build(); ++ void DisconnectDocumentEventListeners(); ++ void SetShellShowingMenuBar(bool aShowing); ++ void Focus(); ++ void Blur(); ++ ModifierFlags GetModifiersFromEvent(mozilla::dom::KeyboardEvent *aEvent); ++ nsresult Keypress(mozilla::dom::KeyboardEvent *aEvent); ++ nsresult KeyDown(mozilla::dom::KeyboardEvent *aEvent); ++ nsresult KeyUp(mozilla::dom::KeyboardEvent *aEvent); ++ ++ void HandleContentInserted(nsIContent *aChild, ++ nsIContent *aPrevSibling); ++ void HandleContentRemoved(nsIContent *aChild); ++ ++ void OnContentInserted(nsIContent *aContainer, nsIContent *aChild, ++ nsIContent *aPrevSibling) override; ++ void OnContentRemoved(nsIContent *aContainer, nsIContent *aChild) override; ++ ++ GtkWidget *mTopLevel; ++ DbusmenuServer *mServer; ++ nsCOMPtr mDocument; ++ RefPtr mEventListener; ++ ++ uint32_t mAccessKey; ++ ModifierFlags mAccessKeyMask; ++ bool mIsActive; ++}; ++ ++#endif /* __nsMenuBar_h__ */ +diff --git a/widget/gtk/nsMenuContainer.cpp b/widget/gtk/nsMenuContainer.cpp +new file mode 100644 +index 000000000000..f419201f6338 +--- /dev/null ++++ b/widget/gtk/nsMenuContainer.cpp +@@ -0,0 +1,170 @@ ++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ ++/* vim:expandtab:shiftwidth=4:tabstop=4: ++ */ ++/* This Source Code Form is subject to the terms of the Mozilla Public ++ * License, v. 2.0. If a copy of the MPL was not distributed with this ++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ ++ ++#include "mozilla/DebugOnly.h" ++#include "nsGkAtoms.h" ++#include "nsIContent.h" ++ ++#include "nsDbusmenu.h" ++#include "nsMenu.h" ++#include "nsMenuItem.h" ++#include "nsMenuSeparator.h" ++ ++#include "nsMenuContainer.h" ++ ++using namespace mozilla; ++ ++const nsMenuContainer::ChildTArray::index_type nsMenuContainer::NoIndex = nsMenuContainer::ChildTArray::NoIndex; ++ ++typedef UniquePtr (*nsMenuObjectConstructor)(nsMenuContainer*, ++ nsIContent*); ++ ++template ++static UniquePtr CreateMenuObject(nsMenuContainer *aContainer, ++ nsIContent *aContent) ++{ ++ return UniquePtr(new T(aContainer, aContent)); ++} ++ ++static nsMenuObjectConstructor ++GetMenuObjectConstructor(nsIContent *aContent) ++{ ++ if (aContent->IsXULElement(nsGkAtoms::menuitem)) { ++ return CreateMenuObject; ++ } else if (aContent->IsXULElement(nsGkAtoms::menu)) { ++ return CreateMenuObject; ++ } else if (aContent->IsXULElement(nsGkAtoms::menuseparator)) { ++ return CreateMenuObject; ++ } ++ ++ return nullptr; ++} ++ ++static bool ++ContentIsSupported(nsIContent *aContent) ++{ ++ return GetMenuObjectConstructor(aContent) ? true : false; ++} ++ ++nsMenuContainer::nsMenuContainer(nsMenuContainer *aParent, ++ nsIContent *aContent) : ++ nsMenuObject(aParent, aContent) ++{ ++} ++ ++nsMenuContainer::nsMenuContainer(nsNativeMenuDocListener *aListener, ++ nsIContent *aContent) : ++ nsMenuObject(aListener, aContent) ++{ ++} ++ ++UniquePtr ++nsMenuContainer::CreateChild(nsIContent *aContent) ++{ ++ nsMenuObjectConstructor ctor = GetMenuObjectConstructor(aContent); ++ if (!ctor) { ++ // There are plenty of node types we might stumble across that ++ // aren't supported ++ return nullptr; ++ } ++ ++ UniquePtr res = ctor(this, aContent); ++ return res; ++} ++ ++size_t ++nsMenuContainer::IndexOf(nsIContent *aChild) const ++{ ++ if (!aChild) { ++ return NoIndex; ++ } ++ ++ size_t count = ChildCount(); ++ for (size_t i = 0; i < count; ++i) { ++ if (ChildAt(i)->ContentNode() == aChild) { ++ return i; ++ } ++ } ++ ++ return NoIndex; ++} ++ ++void ++nsMenuContainer::RemoveChildAt(size_t aIndex, bool aUpdateNative) ++{ ++ MOZ_ASSERT(aIndex < ChildCount()); ++ ++ if (aUpdateNative) { ++ MOZ_ALWAYS_TRUE( ++ dbusmenu_menuitem_child_delete(GetNativeData(), ++ ChildAt(aIndex)->GetNativeData())); ++ } ++ ++ mChildren.RemoveElementAt(aIndex); ++} ++ ++void ++nsMenuContainer::RemoveChild(nsIContent *aChild, bool aUpdateNative) ++{ ++ size_t index = IndexOf(aChild); ++ if (index == NoIndex) { ++ return; ++ } ++ ++ RemoveChildAt(index, aUpdateNative); ++} ++ ++void ++nsMenuContainer::InsertChildAfter(UniquePtr aChild, ++ nsIContent *aPrevSibling, ++ bool aUpdateNative) ++{ ++ size_t index = IndexOf(aPrevSibling); ++ MOZ_ASSERT(!aPrevSibling || index != NoIndex); ++ ++ ++index; ++ ++ if (aUpdateNative) { ++ aChild->CreateNativeData(); ++ MOZ_ALWAYS_TRUE( ++ dbusmenu_menuitem_child_add_position(GetNativeData(), ++ aChild->GetNativeData(), ++ index)); ++ } ++ ++ mChildren.InsertElementAt(index, std::move(aChild)); ++} ++ ++void ++nsMenuContainer::AppendChild(UniquePtr aChild, ++ bool aUpdateNative) ++{ ++ if (aUpdateNative) { ++ aChild->CreateNativeData(); ++ MOZ_ALWAYS_TRUE( ++ dbusmenu_menuitem_child_append(GetNativeData(), ++ aChild->GetNativeData())); ++ } ++ ++ mChildren.AppendElement(std::move(aChild)); ++} ++ ++bool ++nsMenuContainer::NeedsRebuild() const ++{ ++ return false; ++} ++ ++/* static */ nsIContent* ++nsMenuContainer::GetPreviousSupportedSibling(nsIContent *aContent) ++{ ++ do { ++ aContent = aContent->GetPreviousSibling(); ++ } while (aContent && !ContentIsSupported(aContent)); ++ ++ return aContent; ++} +diff --git a/widget/gtk/nsMenuContainer.h b/widget/gtk/nsMenuContainer.h +new file mode 100644 +index 000000000000..b7e8fa8db46f +--- /dev/null ++++ b/widget/gtk/nsMenuContainer.h +@@ -0,0 +1,70 @@ ++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ ++/* vim:expandtab:shiftwidth=4:tabstop=4: ++ */ ++/* This Source Code Form is subject to the terms of the Mozilla Public ++ * License, v. 2.0. If a copy of the MPL was not distributed with this ++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ ++ ++#ifndef __nsMenuContainer_h__ ++#define __nsMenuContainer_h__ ++ ++#include "mozilla/UniquePtr.h" ++#include "nsTArray.h" ++ ++#include "nsMenuObject.h" ++ ++class nsIContent; ++class nsNativeMenuDocListener; ++ ++// Base class for containers (menus and menubars) ++class nsMenuContainer : public nsMenuObject ++{ ++public: ++ typedef nsTArray > ChildTArray; ++ ++ // Determine if this container is being displayed on screen. Must be ++ // implemented by subclasses. Must return true if the container is ++ // in the fully open state, or false otherwise ++ virtual bool IsBeingDisplayed() const = 0; ++ ++ // Determine if this container will be rebuilt the next time it opens. ++ // Returns false by default but can be overridden by subclasses ++ virtual bool NeedsRebuild() const; ++ ++ // Return the first previous sibling that is of a type supported by the ++ // menu system ++ static nsIContent* GetPreviousSupportedSibling(nsIContent *aContent); ++ ++ static const ChildTArray::index_type NoIndex; ++ ++protected: ++ nsMenuContainer(nsMenuContainer *aParent, nsIContent *aContent); ++ nsMenuContainer(nsNativeMenuDocListener *aListener, nsIContent *aContent); ++ ++ // Create a new child element for the specified content node ++ mozilla::UniquePtr CreateChild(nsIContent *aContent); ++ ++ // Return the index of the child for the specified content node ++ size_t IndexOf(nsIContent *aChild) const; ++ ++ size_t ChildCount() const { return mChildren.Length(); } ++ nsMenuObject* ChildAt(size_t aIndex) const { return mChildren[aIndex].get(); } ++ ++ void RemoveChildAt(size_t aIndex, bool aUpdateNative = true); ++ ++ // Remove the child that owns the specified content node ++ void RemoveChild(nsIContent *aChild, bool aUpdateNative = true); ++ ++ // Insert a new child after the child that owns the specified content node ++ void InsertChildAfter(mozilla::UniquePtr aChild, ++ nsIContent *aPrevSibling, ++ bool aUpdateNative = true); ++ ++ void AppendChild(mozilla::UniquePtr aChild, ++ bool aUpdateNative = true); ++ ++private: ++ ChildTArray mChildren; ++}; ++ ++#endif /* __nsMenuContainer_h__ */ +diff --git a/widget/gtk/nsMenuItem.cpp b/widget/gtk/nsMenuItem.cpp +new file mode 100644 +index 000000000000..3a9915e609f5 +--- /dev/null ++++ b/widget/gtk/nsMenuItem.cpp +@@ -0,0 +1,766 @@ ++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ ++/* vim:expandtab:shiftwidth=4:tabstop=4: ++ */ ++/* This Source Code Form is subject to the terms of the Mozilla Public ++ * License, v. 2.0. If a copy of the MPL was not distributed with this ++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ ++ ++#include "mozilla/ArrayUtils.h" ++#include "mozilla/Assertions.h" ++#include "mozilla/dom/Document.h" ++#include "mozilla/dom/Element.h" ++#include "mozilla/dom/KeyboardEventBinding.h" ++#include "mozilla/dom/XULCommandEvent.h" ++#include "mozilla/Preferences.h" ++#include "mozilla/TextEvents.h" ++#include "nsContentUtils.h" ++#include "nsCRT.h" ++#include "nsGkAtoms.h" ++#include "nsGlobalWindowInner.h" ++#include "nsGtkUtils.h" ++#include "nsIContent.h" ++#include "nsIRunnable.h" ++#include "nsQueryObject.h" ++#include "nsReadableUtils.h" ++#include "nsString.h" ++#include "nsThreadUtils.h" ++ ++#include "nsMenu.h" ++#include "nsMenuBar.h" ++#include "nsMenuContainer.h" ++#include "nsNativeMenuDocListener.h" ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "nsMenuItem.h" ++ ++using namespace mozilla; ++ ++struct KeyCodeData { ++ const char* str; ++ size_t strlength; ++ uint32_t keycode; ++}; ++ ++static struct KeyCodeData gKeyCodes[] = { ++#define NS_DEFINE_VK(aDOMKeyName, aDOMKeyCode) \ ++ { #aDOMKeyName, sizeof(#aDOMKeyName) - 1, aDOMKeyCode }, ++#include "mozilla/VirtualKeyCodeList.h" ++#undef NS_DEFINE_VK ++ { nullptr, 0, 0 } ++}; ++ ++struct KeyPair { ++ uint32_t DOMKeyCode; ++ guint GDKKeyval; ++}; ++ ++// ++// Netscape keycodes are defined in widget/public/nsGUIEvent.h ++// GTK keycodes are defined in ++// ++static const KeyPair gKeyPairs[] = { ++ { NS_VK_CANCEL, GDK_Cancel }, ++ { NS_VK_BACK, GDK_BackSpace }, ++ { NS_VK_TAB, GDK_Tab }, ++ { NS_VK_TAB, GDK_ISO_Left_Tab }, ++ { NS_VK_CLEAR, GDK_Clear }, ++ { NS_VK_RETURN, GDK_Return }, ++ { NS_VK_SHIFT, GDK_Shift_L }, ++ { NS_VK_SHIFT, GDK_Shift_R }, ++ { NS_VK_SHIFT, GDK_Shift_Lock }, ++ { NS_VK_CONTROL, GDK_Control_L }, ++ { NS_VK_CONTROL, GDK_Control_R }, ++ { NS_VK_ALT, GDK_Alt_L }, ++ { NS_VK_ALT, GDK_Alt_R }, ++ { NS_VK_META, GDK_Meta_L }, ++ { NS_VK_META, GDK_Meta_R }, ++ ++ // Assume that Super or Hyper is always mapped to physical Win key. ++ { NS_VK_WIN, GDK_Super_L }, ++ { NS_VK_WIN, GDK_Super_R }, ++ { NS_VK_WIN, GDK_Hyper_L }, ++ { NS_VK_WIN, GDK_Hyper_R }, ++ ++ // GTK's AltGraph key is similar to Mac's Option (Alt) key. However, ++ // unfortunately, browsers on Mac are using NS_VK_ALT for it even though ++ // it's really different from Alt key on Windows. ++ // On the other hand, GTK's AltGrapsh keys are really different from ++ // Alt key. However, there is no AltGrapsh key on Windows. On Windows, ++ // both Ctrl and Alt keys are pressed internally when AltGr key is pressed. ++ // For some languages' users, AltGraph key is important, so, web ++ // applications on such locale may want to know AltGraph key press. ++ // Therefore, we should map AltGr keycode for them only on GTK. ++ { NS_VK_ALTGR, GDK_ISO_Level3_Shift }, ++ { NS_VK_ALTGR, GDK_ISO_Level5_Shift }, ++ // We assume that Mode_switch is always used for level3 shift. ++ { NS_VK_ALTGR, GDK_Mode_switch }, ++ ++ { NS_VK_PAUSE, GDK_Pause }, ++ { NS_VK_CAPS_LOCK, GDK_Caps_Lock }, ++ { NS_VK_KANA, GDK_Kana_Lock }, ++ { NS_VK_KANA, GDK_Kana_Shift }, ++ { NS_VK_HANGUL, GDK_Hangul }, ++ // { NS_VK_JUNJA, GDK_XXX }, ++ // { NS_VK_FINAL, GDK_XXX }, ++ { NS_VK_HANJA, GDK_Hangul_Hanja }, ++ { NS_VK_KANJI, GDK_Kanji }, ++ { NS_VK_ESCAPE, GDK_Escape }, ++ { NS_VK_CONVERT, GDK_Henkan }, ++ { NS_VK_NONCONVERT, GDK_Muhenkan }, ++ // { NS_VK_ACCEPT, GDK_XXX }, ++ // { NS_VK_MODECHANGE, GDK_XXX }, ++ { NS_VK_SPACE, GDK_space }, ++ { NS_VK_PAGE_UP, GDK_Page_Up }, ++ { NS_VK_PAGE_DOWN, GDK_Page_Down }, ++ { NS_VK_END, GDK_End }, ++ { NS_VK_HOME, GDK_Home }, ++ { NS_VK_LEFT, GDK_Left }, ++ { NS_VK_UP, GDK_Up }, ++ { NS_VK_RIGHT, GDK_Right }, ++ { NS_VK_DOWN, GDK_Down }, ++ { NS_VK_SELECT, GDK_Select }, ++ { NS_VK_PRINT, GDK_Print }, ++ { NS_VK_EXECUTE, GDK_Execute }, ++ { NS_VK_PRINTSCREEN, GDK_Print }, ++ { NS_VK_INSERT, GDK_Insert }, ++ { NS_VK_DELETE, GDK_Delete }, ++ { NS_VK_HELP, GDK_Help }, ++ ++ // keypad keys ++ { NS_VK_LEFT, GDK_KP_Left }, ++ { NS_VK_RIGHT, GDK_KP_Right }, ++ { NS_VK_UP, GDK_KP_Up }, ++ { NS_VK_DOWN, GDK_KP_Down }, ++ { NS_VK_PAGE_UP, GDK_KP_Page_Up }, ++ // Not sure what these are ++ //{ NS_VK_, GDK_KP_Prior }, ++ //{ NS_VK_, GDK_KP_Next }, ++ { NS_VK_CLEAR, GDK_KP_Begin }, // Num-unlocked 5 ++ { NS_VK_PAGE_DOWN, GDK_KP_Page_Down }, ++ { NS_VK_HOME, GDK_KP_Home }, ++ { NS_VK_END, GDK_KP_End }, ++ { NS_VK_INSERT, GDK_KP_Insert }, ++ { NS_VK_DELETE, GDK_KP_Delete }, ++ { NS_VK_RETURN, GDK_KP_Enter }, ++ ++ { NS_VK_NUM_LOCK, GDK_Num_Lock }, ++ { NS_VK_SCROLL_LOCK,GDK_Scroll_Lock }, ++ ++ // Function keys ++ { NS_VK_F1, GDK_F1 }, ++ { NS_VK_F2, GDK_F2 }, ++ { NS_VK_F3, GDK_F3 }, ++ { NS_VK_F4, GDK_F4 }, ++ { NS_VK_F5, GDK_F5 }, ++ { NS_VK_F6, GDK_F6 }, ++ { NS_VK_F7, GDK_F7 }, ++ { NS_VK_F8, GDK_F8 }, ++ { NS_VK_F9, GDK_F9 }, ++ { NS_VK_F10, GDK_F10 }, ++ { NS_VK_F11, GDK_F11 }, ++ { NS_VK_F12, GDK_F12 }, ++ { NS_VK_F13, GDK_F13 }, ++ { NS_VK_F14, GDK_F14 }, ++ { NS_VK_F15, GDK_F15 }, ++ { NS_VK_F16, GDK_F16 }, ++ { NS_VK_F17, GDK_F17 }, ++ { NS_VK_F18, GDK_F18 }, ++ { NS_VK_F19, GDK_F19 }, ++ { NS_VK_F20, GDK_F20 }, ++ { NS_VK_F21, GDK_F21 }, ++ { NS_VK_F22, GDK_F22 }, ++ { NS_VK_F23, GDK_F23 }, ++ { NS_VK_F24, GDK_F24 }, ++ ++ // context menu key, keysym 0xff67, typically keycode 117 on 105-key (Microsoft) ++ // x86 keyboards, located between right 'Windows' key and right Ctrl key ++ { NS_VK_CONTEXT_MENU, GDK_Menu }, ++ { NS_VK_SLEEP, GDK_Sleep }, ++ ++ { NS_VK_ATTN, GDK_3270_Attn }, ++ { NS_VK_CRSEL, GDK_3270_CursorSelect }, ++ { NS_VK_EXSEL, GDK_3270_ExSelect }, ++ { NS_VK_EREOF, GDK_3270_EraseEOF }, ++ { NS_VK_PLAY, GDK_3270_Play }, ++ //{ NS_VK_ZOOM, GDK_XXX }, ++ { NS_VK_PA1, GDK_3270_PA1 }, ++}; ++ ++static guint ++ConvertGeckoKeyNameToGDKKeyval(nsAString& aKeyName) ++{ ++ NS_ConvertUTF16toUTF8 keyName(aKeyName); ++ ToUpperCase(keyName); // We want case-insensitive comparison with data ++ // stored as uppercase. ++ ++ uint32_t keyCode = 0; ++ ++ uint32_t keyNameLength = keyName.Length(); ++ const char* keyNameStr = keyName.get(); ++ for (uint16_t i = 0; i < ArrayLength(gKeyCodes); ++i) { ++ if (keyNameLength == gKeyCodes[i].strlength && ++ !nsCRT::strcmp(gKeyCodes[i].str, keyNameStr)) { ++ keyCode = gKeyCodes[i].keycode; ++ break; ++ } ++ } ++ ++ // First, try to handle alphanumeric input, not listed in nsKeycodes: ++ // most likely, more letters will be getting typed in than things in ++ // the key list, so we will look through these first. ++ ++ if (keyCode >= NS_VK_A && keyCode <= NS_VK_Z) { ++ // gdk and DOM both use the ASCII codes for these keys. ++ return keyCode; ++ } ++ ++ // numbers ++ if (keyCode >= NS_VK_0 && keyCode <= NS_VK_9) { ++ // gdk and DOM both use the ASCII codes for these keys. ++ return keyCode - NS_VK_0 + GDK_0; ++ } ++ ++ switch (keyCode) { ++ // keys in numpad ++ case NS_VK_MULTIPLY: return GDK_KP_Multiply; ++ case NS_VK_ADD: return GDK_KP_Add; ++ case NS_VK_SEPARATOR: return GDK_KP_Separator; ++ case NS_VK_SUBTRACT: return GDK_KP_Subtract; ++ case NS_VK_DECIMAL: return GDK_KP_Decimal; ++ case NS_VK_DIVIDE: return GDK_KP_Divide; ++ case NS_VK_NUMPAD0: return GDK_KP_0; ++ case NS_VK_NUMPAD1: return GDK_KP_1; ++ case NS_VK_NUMPAD2: return GDK_KP_2; ++ case NS_VK_NUMPAD3: return GDK_KP_3; ++ case NS_VK_NUMPAD4: return GDK_KP_4; ++ case NS_VK_NUMPAD5: return GDK_KP_5; ++ case NS_VK_NUMPAD6: return GDK_KP_6; ++ case NS_VK_NUMPAD7: return GDK_KP_7; ++ case NS_VK_NUMPAD8: return GDK_KP_8; ++ case NS_VK_NUMPAD9: return GDK_KP_9; ++ // other prinable keys ++ case NS_VK_SPACE: return GDK_space; ++ case NS_VK_COLON: return GDK_colon; ++ case NS_VK_SEMICOLON: return GDK_semicolon; ++ case NS_VK_LESS_THAN: return GDK_less; ++ case NS_VK_EQUALS: return GDK_equal; ++ case NS_VK_GREATER_THAN: return GDK_greater; ++ case NS_VK_QUESTION_MARK: return GDK_question; ++ case NS_VK_AT: return GDK_at; ++ case NS_VK_CIRCUMFLEX: return GDK_asciicircum; ++ case NS_VK_EXCLAMATION: return GDK_exclam; ++ case NS_VK_DOUBLE_QUOTE: return GDK_quotedbl; ++ case NS_VK_HASH: return GDK_numbersign; ++ case NS_VK_DOLLAR: return GDK_dollar; ++ case NS_VK_PERCENT: return GDK_percent; ++ case NS_VK_AMPERSAND: return GDK_ampersand; ++ case NS_VK_UNDERSCORE: return GDK_underscore; ++ case NS_VK_OPEN_PAREN: return GDK_parenleft; ++ case NS_VK_CLOSE_PAREN: return GDK_parenright; ++ case NS_VK_ASTERISK: return GDK_asterisk; ++ case NS_VK_PLUS: return GDK_plus; ++ case NS_VK_PIPE: return GDK_bar; ++ case NS_VK_HYPHEN_MINUS: return GDK_minus; ++ case NS_VK_OPEN_CURLY_BRACKET: return GDK_braceleft; ++ case NS_VK_CLOSE_CURLY_BRACKET: return GDK_braceright; ++ case NS_VK_TILDE: return GDK_asciitilde; ++ case NS_VK_COMMA: return GDK_comma; ++ case NS_VK_PERIOD: return GDK_period; ++ case NS_VK_SLASH: return GDK_slash; ++ case NS_VK_BACK_QUOTE: return GDK_grave; ++ case NS_VK_OPEN_BRACKET: return GDK_bracketleft; ++ case NS_VK_BACK_SLASH: return GDK_backslash; ++ case NS_VK_CLOSE_BRACKET: return GDK_bracketright; ++ case NS_VK_QUOTE: return GDK_apostrophe; ++ } ++ ++ // misc other things ++ for (uint32_t i = 0; i < ArrayLength(gKeyPairs); ++i) { ++ if (gKeyPairs[i].DOMKeyCode == keyCode) { ++ return gKeyPairs[i].GDKKeyval; ++ } ++ } ++ ++ return 0; ++} ++ ++class nsMenuItemUncheckSiblingsRunnable final : public Runnable ++{ ++public: ++ NS_IMETHODIMP Run() ++ { ++ if (mMenuItem) { ++ static_cast(mMenuItem.get())->UncheckSiblings(); ++ } ++ return NS_OK; ++ } ++ ++ nsMenuItemUncheckSiblingsRunnable(nsMenuItem *aMenuItem) : ++ Runnable("nsMenuItemUncheckSiblingsRunnable"), ++ mMenuItem(aMenuItem) { }; ++ ++private: ++ nsWeakMenuObject mMenuItem; ++}; ++ ++bool ++nsMenuItem::IsCheckboxOrRadioItem() const ++{ ++ return mType == eMenuItemType_Radio || ++ mType == eMenuItemType_CheckBox; ++} ++ ++/* static */ void ++nsMenuItem::item_activated_cb(DbusmenuMenuitem *menuitem, ++ guint timestamp, ++ gpointer user_data) ++{ ++ nsMenuItem *item = static_cast(user_data); ++ item->Activate(timestamp); ++} ++ ++void ++nsMenuItem::Activate(uint32_t aTimestamp) ++{ ++ GdkWindow *window = gtk_widget_get_window(MenuBar()->TopLevelWindow()); ++ gdk_x11_window_set_user_time( ++ window, std::min(aTimestamp, gdk_x11_get_server_time(window))); ++ ++ // We do this to avoid mutating our view of the menu until ++ // after we have finished ++ nsNativeMenuDocListener::BlockUpdatesScope updatesBlocker; ++ ++ if (!ContentNode()->AsElement()->AttrValueIs(kNameSpaceID_None, ++ nsGkAtoms::autocheck, ++ nsGkAtoms::_false, ++ eCaseMatters) && ++ (mType == eMenuItemType_CheckBox || ++ (mType == eMenuItemType_Radio && !mIsChecked))) { ++ ContentNode()->AsElement()->SetAttr(kNameSpaceID_None, ++ nsGkAtoms::checked, ++ mIsChecked ? ++ u"false"_ns ++ : u"true"_ns, ++ true); ++ } ++ ++ dom::Document *doc = ContentNode()->OwnerDoc(); ++ ErrorResult rv; ++ RefPtr event = ++ doc->CreateEvent(u"xulcommandevent"_ns, ++ dom::CallerType::System, rv); ++ if (!rv.Failed()) { ++ RefPtr command = event->AsXULCommandEvent(); ++ if (command) { ++ command->InitCommandEvent(u"command"_ns, true, true, ++ nsGlobalWindowInner::Cast(doc->GetInnerWindow()), ++ 0, false, false, false, false, 0, nullptr, 0, rv); ++ if (!rv.Failed()) { ++ event->SetTrusted(true); ++ ContentNode()->DispatchEvent(*event, rv); ++ if (rv.Failed()) { ++ NS_WARNING("Failed to dispatch event"); ++ rv.SuppressException(); ++ } ++ } else { ++ NS_WARNING("Failed to initialize command event"); ++ rv.SuppressException(); ++ } ++ } ++ } else { ++ NS_WARNING("CreateEvent failed"); ++ rv.SuppressException(); ++ } ++ ++ // This kinda sucks, but Unity doesn't send a closed event ++ // after activating a menuitem ++ nsMenuObject *ancestor = Parent(); ++ while (ancestor && ancestor->Type() == eType_Menu) { ++ static_cast(ancestor)->OnClose(); ++ ancestor = ancestor->Parent(); ++ } ++} ++ ++void ++nsMenuItem::CopyAttrFromNodeIfExists(nsIContent *aContent, nsAtom *aAttribute) ++{ ++ nsAutoString value; ++ if (aContent->AsElement()->GetAttr(kNameSpaceID_None, aAttribute, value)) { ++ ContentNode()->AsElement()->SetAttr(kNameSpaceID_None, aAttribute, ++ value, true); ++ } ++} ++ ++void ++nsMenuItem::UpdateState() ++{ ++ if (!IsCheckboxOrRadioItem()) { ++ return; ++ } ++ ++ mIsChecked = ContentNode()->AsElement()->AttrValueIs(kNameSpaceID_None, ++ nsGkAtoms::checked, ++ nsGkAtoms::_true, ++ eCaseMatters); ++ dbusmenu_menuitem_property_set_int(GetNativeData(), ++ DBUSMENU_MENUITEM_PROP_TOGGLE_STATE, ++ mIsChecked ? ++ DBUSMENU_MENUITEM_TOGGLE_STATE_CHECKED : ++ DBUSMENU_MENUITEM_TOGGLE_STATE_UNCHECKED); ++} ++ ++void ++nsMenuItem::UpdateTypeAndState() ++{ ++ static mozilla::dom::Element::AttrValuesArray attrs[] = ++ { nsGkAtoms::checkbox, nsGkAtoms::radio, nullptr }; ++ int32_t type = ContentNode()->AsElement()->FindAttrValueIn(kNameSpaceID_None, ++ nsGkAtoms::type, ++ attrs, eCaseMatters); ++ ++ if (type >= 0 && type < 2) { ++ if (type == 0) { ++ dbusmenu_menuitem_property_set(GetNativeData(), ++ DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE, ++ DBUSMENU_MENUITEM_TOGGLE_CHECK); ++ mType = eMenuItemType_CheckBox; ++ } else if (type == 1) { ++ dbusmenu_menuitem_property_set(GetNativeData(), ++ DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE, ++ DBUSMENU_MENUITEM_TOGGLE_RADIO); ++ mType = eMenuItemType_Radio; ++ } ++ ++ UpdateState(); ++ } else { ++ dbusmenu_menuitem_property_remove(GetNativeData(), ++ DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE); ++ dbusmenu_menuitem_property_remove(GetNativeData(), ++ DBUSMENU_MENUITEM_PROP_TOGGLE_STATE); ++ mType = eMenuItemType_Normal; ++ } ++} ++ ++void ++nsMenuItem::UpdateAccel() ++{ ++ dom::Document *doc = ContentNode()->GetUncomposedDoc(); ++ if (doc) { ++ nsCOMPtr oldKeyContent; ++ oldKeyContent.swap(mKeyContent); ++ ++ nsAutoString key; ++ ContentNode()->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::key, ++ key); ++ if (!key.IsEmpty()) { ++ mKeyContent = doc->GetElementById(key); ++ } ++ ++ if (mKeyContent != oldKeyContent) { ++ if (oldKeyContent) { ++ DocListener()->UnregisterForContentChanges(oldKeyContent); ++ } ++ if (mKeyContent) { ++ DocListener()->RegisterForContentChanges(mKeyContent, this); ++ } ++ } ++ } ++ ++ if (!mKeyContent) { ++ dbusmenu_menuitem_property_remove(GetNativeData(), ++ DBUSMENU_MENUITEM_PROP_SHORTCUT); ++ return; ++ } ++ ++ nsAutoString modifiers; ++ mKeyContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::modifiers, ++ modifiers); ++ ++ uint32_t modifier = 0; ++ ++ if (!modifiers.IsEmpty()) { ++ char* str = ToNewUTF8String(modifiers); ++ char *token = strtok(str, ", \t"); ++ while(token) { ++ if (nsCRT::strcmp(token, "shift") == 0) { ++ modifier |= GDK_SHIFT_MASK; ++ } else if (nsCRT::strcmp(token, "alt") == 0) { ++ modifier |= GDK_MOD1_MASK; ++ } else if (nsCRT::strcmp(token, "meta") == 0) { ++ modifier |= GDK_META_MASK; ++ } else if (nsCRT::strcmp(token, "control") == 0) { ++ modifier |= GDK_CONTROL_MASK; ++ } else if (nsCRT::strcmp(token, "accel") == 0) { ++ int32_t accel = Preferences::GetInt("ui.key.accelKey"); ++ if (accel == dom::KeyboardEvent_Binding::DOM_VK_META) { ++ modifier |= GDK_META_MASK; ++ } else if (accel == dom::KeyboardEvent_Binding::DOM_VK_ALT) { ++ modifier |= GDK_MOD1_MASK; ++ } else { ++ modifier |= GDK_CONTROL_MASK; ++ } ++ } ++ ++ token = strtok(nullptr, ", \t"); ++ } ++ ++ free(str); ++ } ++ ++ nsAutoString keyStr; ++ mKeyContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::key, ++ keyStr); ++ ++ guint key = 0; ++ if (!keyStr.IsEmpty()) { ++ key = gdk_unicode_to_keyval(*keyStr.BeginReading()); ++ } ++ ++ if (key == 0) { ++ mKeyContent->AsElement()->GetAttr(kNameSpaceID_None, ++ nsGkAtoms::keycode, keyStr); ++ if (!keyStr.IsEmpty()) { ++ key = ConvertGeckoKeyNameToGDKKeyval(keyStr); ++ } ++ } ++ ++ if (key == 0) { ++ key = GDK_VoidSymbol; ++ } ++ ++ if (key != GDK_VoidSymbol) { ++ dbusmenu_menuitem_property_set_shortcut(GetNativeData(), key, ++ static_cast(modifier)); ++ } else { ++ dbusmenu_menuitem_property_remove(GetNativeData(), ++ DBUSMENU_MENUITEM_PROP_SHORTCUT); ++ } ++} ++ ++nsMenuBar* ++nsMenuItem::MenuBar() ++{ ++ nsMenuObject *tmp = this; ++ while (tmp->Parent()) { ++ tmp = tmp->Parent(); ++ } ++ ++ MOZ_ASSERT(tmp->Type() == eType_MenuBar, "The top-level should be a menubar"); ++ ++ return static_cast(tmp); ++} ++ ++void ++nsMenuItem::UncheckSiblings() ++{ ++ if (!ContentNode()->AsElement()->AttrValueIs(kNameSpaceID_None, ++ nsGkAtoms::type, ++ nsGkAtoms::radio, ++ eCaseMatters)) { ++ // If we're not a radio button, we don't care ++ return; ++ } ++ ++ nsAutoString name; ++ ContentNode()->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::name, ++ name); ++ ++ nsIContent *parent = ContentNode()->GetParent(); ++ if (!parent) { ++ return; ++ } ++ ++ uint32_t count = parent->GetChildCount(); ++ for (uint32_t i = 0; i < count; ++i) { ++ nsIContent *sibling = parent->GetChildAt_Deprecated(i); ++ ++ if (sibling->IsComment()) { ++ continue; ++ } ++ ++ nsAutoString otherName; ++ sibling->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::name, ++ otherName); ++ ++ if (sibling != ContentNode() && otherName == name && ++ sibling->AsElement()->AttrValueIs(kNameSpaceID_None, ++ nsGkAtoms::type, ++ nsGkAtoms::radio, ++ eCaseMatters)) { ++ sibling->AsElement()->UnsetAttr(kNameSpaceID_None, ++ nsGkAtoms::checked, true); ++ } ++ } ++} ++ ++void ++nsMenuItem::InitializeNativeData() ++{ ++ g_signal_connect(G_OBJECT(GetNativeData()), ++ DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED, ++ G_CALLBACK(item_activated_cb), this); ++ mNeedsUpdate = true; ++} ++ ++void ++nsMenuItem::UpdateContentAttributes() ++{ ++ dom::Document *doc = ContentNode()->GetUncomposedDoc(); ++ if (!doc) { ++ return; ++ } ++ ++ nsAutoString command; ++ ContentNode()->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::command, ++ command); ++ if (command.IsEmpty()) { ++ return; ++ } ++ ++ nsCOMPtr commandContent = doc->GetElementById(command); ++ if (!commandContent) { ++ return; ++ } ++ ++ if (commandContent->AsElement()->AttrValueIs(kNameSpaceID_None, ++ nsGkAtoms::disabled, ++ nsGkAtoms::_true, ++ eCaseMatters)) { ++ ContentNode()->AsElement()->SetAttr(kNameSpaceID_None, ++ nsGkAtoms::disabled, ++ u"true"_ns, true); ++ } else { ++ ContentNode()->AsElement()->UnsetAttr(kNameSpaceID_None, ++ nsGkAtoms::disabled, true); ++ } ++ ++ CopyAttrFromNodeIfExists(commandContent, nsGkAtoms::checked); ++ CopyAttrFromNodeIfExists(commandContent, nsGkAtoms::accesskey); ++ CopyAttrFromNodeIfExists(commandContent, nsGkAtoms::label); ++ CopyAttrFromNodeIfExists(commandContent, nsGkAtoms::hidden); ++} ++ ++void ++nsMenuItem::Update(const ComputedStyle *aComputedStyle) ++{ ++ if (mNeedsUpdate) { ++ mNeedsUpdate = false; ++ ++ UpdateTypeAndState(); ++ UpdateAccel(); ++ UpdateLabel(); ++ UpdateSensitivity(); ++ } ++ ++ UpdateVisibility(aComputedStyle); ++ UpdateIcon(aComputedStyle); ++} ++ ++bool ++nsMenuItem::IsCompatibleWithNativeData(DbusmenuMenuitem *aNativeData) const ++{ ++ return nsCRT::strcmp(dbusmenu_menuitem_property_get(aNativeData, ++ DBUSMENU_MENUITEM_PROP_TYPE), ++ "separator") != 0; ++} ++ ++nsMenuObject::PropertyFlags ++nsMenuItem::SupportedProperties() const ++{ ++ return static_cast( ++ nsMenuObject::ePropLabel | ++ nsMenuObject::ePropEnabled | ++ nsMenuObject::ePropVisible | ++ nsMenuObject::ePropIconData | ++ nsMenuObject::ePropShortcut | ++ nsMenuObject::ePropToggleType | ++ nsMenuObject::ePropToggleState ++ ); ++} ++ ++void ++nsMenuItem::OnAttributeChanged(nsIContent *aContent, nsAtom *aAttribute) ++{ ++ MOZ_ASSERT(aContent == ContentNode() || aContent == mKeyContent, ++ "Received an event that wasn't meant for us!"); ++ ++ if (aContent == ContentNode() && aAttribute == nsGkAtoms::checked && ++ aContent->AsElement()->AttrValueIs(kNameSpaceID_None, ++ nsGkAtoms::checked, ++ nsGkAtoms::_true, eCaseMatters)) { ++ nsContentUtils::AddScriptRunner( ++ new nsMenuItemUncheckSiblingsRunnable(this)); ++ } ++ ++ if (mNeedsUpdate) { ++ return; ++ } ++ ++ if (!Parent()->IsBeingDisplayed()) { ++ mNeedsUpdate = true; ++ return; ++ } ++ ++ if (aContent == ContentNode()) { ++ if (aAttribute == nsGkAtoms::key) { ++ UpdateAccel(); ++ } else if (aAttribute == nsGkAtoms::label || ++ aAttribute == nsGkAtoms::accesskey || ++ aAttribute == nsGkAtoms::crop) { ++ UpdateLabel(); ++ } else if (aAttribute == nsGkAtoms::disabled) { ++ UpdateSensitivity(); ++ } else if (aAttribute == nsGkAtoms::type) { ++ UpdateTypeAndState(); ++ } else if (aAttribute == nsGkAtoms::checked) { ++ UpdateState(); ++ } else if (aAttribute == nsGkAtoms::hidden || ++ aAttribute == nsGkAtoms::collapsed) { ++ RefPtr style = GetComputedStyle(); ++ UpdateVisibility(style); ++ } else if (aAttribute == nsGkAtoms::image) { ++ RefPtr style = GetComputedStyle(); ++ UpdateIcon(style); ++ } ++ } else if (aContent == mKeyContent && ++ (aAttribute == nsGkAtoms::key || ++ aAttribute == nsGkAtoms::keycode || ++ aAttribute == nsGkAtoms::modifiers)) { ++ UpdateAccel(); ++ } ++} ++ ++nsMenuItem::nsMenuItem(nsMenuContainer *aParent, nsIContent *aContent) : ++ nsMenuObject(aParent, aContent), ++ mType(eMenuItemType_Normal), ++ mIsChecked(false), ++ mNeedsUpdate(false) ++{ ++ MOZ_COUNT_CTOR(nsMenuItem); ++} ++ ++nsMenuItem::~nsMenuItem() ++{ ++ if (DocListener() && mKeyContent) { ++ DocListener()->UnregisterForContentChanges(mKeyContent); ++ } ++ ++ if (GetNativeData()) { ++ g_signal_handlers_disconnect_by_func(GetNativeData(), ++ FuncToGpointer(item_activated_cb), ++ this); ++ } ++ ++ MOZ_COUNT_DTOR(nsMenuItem); ++} ++ ++nsMenuObject::EType ++nsMenuItem::Type() const ++{ ++ return eType_MenuItem; ++} +diff --git a/widget/gtk/nsMenuItem.h b/widget/gtk/nsMenuItem.h +new file mode 100644 +index 000000000000..c621b4e223f1 +--- /dev/null ++++ b/widget/gtk/nsMenuItem.h +@@ -0,0 +1,80 @@ ++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ ++/* vim:expandtab:shiftwidth=4:tabstop=4: ++ */ ++/* This Source Code Form is subject to the terms of the Mozilla Public ++ * License, v. 2.0. If a copy of the MPL was not distributed with this ++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ ++ ++#ifndef __nsMenuItem_h__ ++#define __nsMenuItem_h__ ++ ++#include "mozilla/Attributes.h" ++#include "nsCOMPtr.h" ++ ++#include "nsDbusmenu.h" ++#include "nsMenuObject.h" ++ ++#include ++ ++class nsAtom; ++class nsIContent; ++class nsMenuBar; ++class nsMenuContainer; ++ ++/* ++ * This class represents 3 main classes of menuitems: labels, checkboxes and ++ * radio buttons (with/without an icon) ++ */ ++class nsMenuItem final : public nsMenuObject ++{ ++public: ++ nsMenuItem(nsMenuContainer *aParent, nsIContent *aContent); ++ ~nsMenuItem() override; ++ ++ nsMenuObject::EType Type() const override; ++ ++private: ++ friend class nsMenuItemUncheckSiblingsRunnable; ++ ++ enum { ++ eMenuItemFlag_ToggleState = (1 << 0) ++ }; ++ ++ enum EMenuItemType { ++ eMenuItemType_Normal, ++ eMenuItemType_Radio, ++ eMenuItemType_CheckBox ++ }; ++ ++ bool IsCheckboxOrRadioItem() const; ++ ++ static void item_activated_cb(DbusmenuMenuitem *menuitem, ++ guint timestamp, ++ gpointer user_data); ++ void Activate(uint32_t aTimestamp); ++ ++ void CopyAttrFromNodeIfExists(nsIContent *aContent, nsAtom *aAtom); ++ void UpdateState(); ++ void UpdateTypeAndState(); ++ void UpdateAccel(); ++ nsMenuBar* MenuBar(); ++ void UncheckSiblings(); ++ ++ void InitializeNativeData() override; ++ void UpdateContentAttributes() override; ++ void Update(const mozilla::ComputedStyle *aComputedStyle) override; ++ bool IsCompatibleWithNativeData(DbusmenuMenuitem *aNativeData) const override; ++ nsMenuObject::PropertyFlags SupportedProperties() const override; ++ ++ void OnAttributeChanged(nsIContent *aContent, nsAtom *aAttribute) override; ++ ++ EMenuItemType mType; ++ ++ bool mIsChecked; ++ ++ bool mNeedsUpdate; ++ ++ nsCOMPtr mKeyContent; ++}; ++ ++#endif /* __nsMenuItem_h__ */ +diff --git a/widget/gtk/nsMenuObject.cpp b/widget/gtk/nsMenuObject.cpp +new file mode 100644 +index 000000000000..fbf0fbef1368 +--- /dev/null ++++ b/widget/gtk/nsMenuObject.cpp +@@ -0,0 +1,664 @@ ++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ ++/* vim:expandtab:shiftwidth=4:tabstop=4: ++ */ ++/* This Source Code Form is subject to the terms of the Mozilla Public ++ * License, v. 2.0. If a copy of the MPL was not distributed with this ++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ ++ ++#include "ImageOps.h" ++#include "imgIContainer.h" ++#include "imgINotificationObserver.h" ++#include "imgLoader.h" ++#include "imgRequestProxy.h" ++#include "mozilla/ArrayUtils.h" ++#include "mozilla/Assertions.h" ++#include "mozilla/dom/Document.h" ++#include "mozilla/dom/Element.h" ++#include "mozilla/Preferences.h" ++#include "mozilla/PresShell.h" ++#include "mozilla/PresShellInlines.h" ++#include "mozilla/GRefPtr.h" ++#include "nsAttrValue.h" ++#include "nsComputedDOMStyle.h" ++#include "nsContentUtils.h" ++#include "nsGkAtoms.h" ++#include "nsIContent.h" ++#include "nsIContentPolicy.h" ++#include "nsILoadGroup.h" ++#include "nsImageToPixbuf.h" ++#include "nsIURI.h" ++#include "nsNetUtil.h" ++#include "nsPresContext.h" ++#include "nsRect.h" ++#include "nsServiceManagerUtils.h" ++#include "nsString.h" ++#include "nsStyleConsts.h" ++#include "nsStyleStruct.h" ++#include "nsUnicharUtils.h" ++ ++#include "nsMenuContainer.h" ++#include "nsNativeMenuDocListener.h" ++ ++#include ++#include ++#include ++ ++#include "nsMenuObject.h" ++ ++// X11's None clashes with StyleDisplay::None ++#include "X11UndefineNone.h" ++ ++#undef None ++ ++using namespace mozilla; ++using mozilla::image::ImageOps; ++ ++#define MAX_WIDTH 350000 ++ ++const char *gPropertyStrings[] = { ++#define DBUSMENU_PROPERTY(e, s, b) s, ++ DBUSMENU_PROPERTIES ++#undef DBUSMENU_PROPERTY ++ nullptr ++}; ++ ++nsWeakMenuObject* nsWeakMenuObject::sHead; ++PangoLayout* gPangoLayout = nullptr; ++ ++class nsMenuObjectIconLoader final : public imgINotificationObserver ++{ ++public: ++ NS_DECL_ISUPPORTS ++ NS_DECL_IMGINOTIFICATIONOBSERVER ++ ++ nsMenuObjectIconLoader(nsMenuObject *aOwner) : mOwner(aOwner) { }; ++ ++ void LoadIcon(const ComputedStyle *aComputedStyle); ++ void Destroy(); ++ ++private: ++ ~nsMenuObjectIconLoader() { }; ++ ++ nsMenuObject *mOwner; ++ RefPtr mImageRequest; ++ nsCOMPtr mURI; ++ nsIntRect mImageRect; ++}; ++ ++NS_IMPL_ISUPPORTS(nsMenuObjectIconLoader, imgINotificationObserver) ++ ++void ++nsMenuObjectIconLoader::Notify(imgIRequest *aProxy, ++ int32_t aType, const nsIntRect *aRect) ++{ ++ if (!mOwner) { ++ return; ++ } ++ ++ if (aProxy != mImageRequest) { ++ return; ++ } ++ ++ if (aType == imgINotificationObserver::LOAD_COMPLETE) { ++ uint32_t status = imgIRequest::STATUS_ERROR; ++ if (NS_FAILED(mImageRequest->GetImageStatus(&status)) || ++ (status & imgIRequest::STATUS_ERROR)) { ++ mImageRequest->Cancel(NS_BINDING_ABORTED); ++ mImageRequest = nullptr; ++ return; ++ } ++ ++ nsCOMPtr image; ++ mImageRequest->GetImage(getter_AddRefs(image)); ++ MOZ_ASSERT(image); ++ ++ // Ask the image to decode at its intrinsic size. ++ int32_t width = 0, height = 0; ++ image->GetWidth(&width); ++ image->GetHeight(&height); ++ image->RequestDecodeForSize(nsIntSize(width, height), imgIContainer::FLAG_NONE); ++ return; ++ } ++ ++ if (aType == imgINotificationObserver::DECODE_COMPLETE) { ++ mImageRequest->Cancel(NS_BINDING_ABORTED); ++ mImageRequest = nullptr; ++ return; ++ } ++ ++ if (aType != imgINotificationObserver::FRAME_COMPLETE) { ++ return; ++ } ++ ++ nsCOMPtr img; ++ mImageRequest->GetImage(getter_AddRefs(img)); ++ if (!img) { ++ return; ++ } ++ ++ if (!mImageRect.IsEmpty()) { ++ img = ImageOps::Clip(img, mImageRect); ++ } ++ ++ int32_t width, height; ++ img->GetWidth(&width); ++ img->GetHeight(&height); ++ ++ if (width <= 0 || height <= 0) { ++ mOwner->ClearIcon(); ++ return; ++ } ++ ++ if (width > 100 || height > 100) { ++ // The icon data needs to go across DBus. Make sure the icon ++ // data isn't too large, else our connection gets terminated and ++ // GDbus helpfully aborts the application. Thank you :) ++ NS_WARNING("Icon data too large"); ++ mOwner->ClearIcon(); ++ return; ++ } ++ ++ RefPtr pixbuf = nsImageToPixbuf::ImageToPixbuf(img); ++ if (pixbuf) { ++ dbusmenu_menuitem_property_set_image(mOwner->GetNativeData(), ++ DBUSMENU_MENUITEM_PROP_ICON_DATA, ++ pixbuf); ++ } ++ ++ return; ++} ++ ++void ++nsMenuObjectIconLoader::LoadIcon(const ComputedStyle *aComputedStyle) ++{ ++ dom::Document *doc = mOwner->ContentNode()->OwnerDoc(); ++ ++ nsCOMPtr uri; ++ nsIntRect imageRect; ++ imgRequestProxy *imageRequest = nullptr; ++ ++ nsAutoString uriString; ++ if (mOwner->ContentNode()->AsElement()->GetAttr(kNameSpaceID_None, ++ nsGkAtoms::image, ++ uriString)) { ++ NS_NewURI(getter_AddRefs(uri), uriString); ++ } else { ++ PresShell *shell = doc->GetPresShell(); ++ if (!shell) { ++ return; ++ } ++ ++ nsPresContext *pc = shell->GetPresContext(); ++ if (!pc || !aComputedStyle) { ++ return; ++ } ++ ++ const nsStyleList *list = aComputedStyle->StyleList(); ++ imageRequest = list->mListStyleImage.GetImageRequest(); ++ if (imageRequest) { ++ imageRequest->GetURI(getter_AddRefs(uri)); ++ auto& rect = list->mImageRegion.AsRect(); ++ imageRect = rect.ToLayoutRect().ToNearestPixels( ++ pc->AppUnitsPerDevPixel()); ++ } ++ } ++ ++ if (!uri) { ++ mOwner->ClearIcon(); ++ mURI = nullptr; ++ ++ if (mImageRequest) { ++ mImageRequest->Cancel(NS_BINDING_ABORTED); ++ mImageRequest = nullptr; ++ } ++ ++ return; ++ } ++ ++ bool same; ++ if (mURI && NS_SUCCEEDED(mURI->Equals(uri, &same)) && same && ++ (!imageRequest || imageRect == mImageRect)) { ++ return; ++ } ++ ++ if (mImageRequest) { ++ mImageRequest->Cancel(NS_BINDING_ABORTED); ++ mImageRequest = nullptr; ++ } ++ ++ mURI = uri; ++ ++ if (imageRequest) { ++ mImageRect = imageRect; ++ imageRequest->Clone(this, nullptr, getter_AddRefs(mImageRequest)); ++ } else { ++ mImageRect.SetEmpty(); ++ nsCOMPtr loadGroup = doc->GetDocumentLoadGroup(); ++ RefPtr loader = ++ nsContentUtils::GetImgLoaderForDocument(doc); ++ if (!loader || !loadGroup) { ++ NS_WARNING("Failed to get loader or load group for image load"); ++ return; ++ } ++ ++ loader->LoadImage(uri, nullptr, nullptr, ++ nullptr, 0, loadGroup, this, nullptr, nullptr, ++ nsIRequest::LOAD_NORMAL, nullptr, ++ nsIContentPolicy::TYPE_IMAGE, EmptyString(), ++ false, false, getter_AddRefs(mImageRequest)); ++ } ++} ++ ++void ++nsMenuObjectIconLoader::Destroy() ++{ ++ if (mImageRequest) { ++ mImageRequest->CancelAndForgetObserver(NS_BINDING_ABORTED); ++ mImageRequest = nullptr; ++ } ++ ++ mOwner = nullptr; ++} ++ ++static int ++CalculateTextWidth(const nsAString& aText) ++{ ++ if (!gPangoLayout) { ++ PangoFontMap *fontmap = pango_cairo_font_map_get_default(); ++ PangoContext *ctx = pango_font_map_create_context(fontmap); ++ gPangoLayout = pango_layout_new(ctx); ++ g_object_unref(ctx); ++ } ++ ++ pango_layout_set_text(gPangoLayout, NS_ConvertUTF16toUTF8(aText).get(), -1); ++ ++ int width, dummy; ++ pango_layout_get_size(gPangoLayout, &width, &dummy); ++ ++ return width; ++} ++ ++static const nsDependentString ++GetEllipsis() ++{ ++ static char16_t sBuf[4] = { 0, 0, 0, 0 }; ++ if (!sBuf[0]) { ++ nsString ellipsis; ++ Preferences::GetLocalizedString("intl.ellipsis", ellipsis); ++ if (!ellipsis.IsEmpty()) { ++ uint32_t l = ellipsis.Length(); ++ const nsString::char_type *c = ellipsis.BeginReading(); ++ uint32_t i = 0; ++ while (i < 3 && i < l) { ++ sBuf[i++] = *(c++); ++ } ++ } else { ++ sBuf[0] = '.'; ++ sBuf[1] = '.'; ++ sBuf[2] = '.'; ++ } ++ } ++ ++ return nsDependentString(sBuf); ++} ++ ++static int ++GetEllipsisWidth() ++{ ++ static int sEllipsisWidth = -1; ++ ++ if (sEllipsisWidth == -1) { ++ sEllipsisWidth = CalculateTextWidth(GetEllipsis()); ++ } ++ ++ return sEllipsisWidth; ++} ++ ++nsMenuObject::nsMenuObject(nsMenuContainer *aParent, nsIContent *aContent) : ++ mContent(aContent), ++ mListener(aParent->DocListener()), ++ mParent(aParent), ++ mNativeData(nullptr) ++{ ++ MOZ_ASSERT(mContent); ++ MOZ_ASSERT(mListener); ++ MOZ_ASSERT(mParent); ++} ++ ++nsMenuObject::nsMenuObject(nsNativeMenuDocListener *aListener, ++ nsIContent *aContent) : ++ mContent(aContent), ++ mListener(aListener), ++ mParent(nullptr), ++ mNativeData(nullptr) ++{ ++ MOZ_ASSERT(mContent); ++ MOZ_ASSERT(mListener); ++} ++ ++void ++nsMenuObject::UpdateLabel() ++{ ++ // Gecko stores the label and access key in separate attributes ++ // so we need to convert label="Foo_Bar"/accesskey="F" in to ++ // label="_Foo__Bar" for dbusmenu ++ ++ nsAutoString label; ++ mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::label, label); ++ ++ nsAutoString accesskey; ++ mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, ++ accesskey); ++ ++ const nsAutoString::char_type *akey = accesskey.BeginReading(); ++ char16_t keyLower = ToLowerCase(*akey); ++ char16_t keyUpper = ToUpperCase(*akey); ++ ++ const nsAutoString::char_type *iter = label.BeginReading(); ++ const nsAutoString::char_type *end = label.EndReading(); ++ uint32_t length = label.Length(); ++ uint32_t pos = 0; ++ bool foundAccessKey = false; ++ ++ while (iter != end) { ++ if (*iter != char16_t('_')) { ++ if ((*iter != keyLower && *iter != keyUpper) || foundAccessKey) { ++ ++iter; ++ ++pos; ++ continue; ++ } ++ foundAccessKey = true; ++ } ++ ++ label.SetLength(++length); ++ ++ iter = label.BeginReading() + pos; ++ end = label.EndReading(); ++ nsAutoString::char_type *cur = label.BeginWriting() + pos; ++ ++ memmove(cur + 1, cur, (length - 1 - pos) * sizeof(nsAutoString::char_type)); ++ *cur = nsAutoString::char_type('_'); ++ ++ iter += 2; ++ pos += 2; ++ } ++ ++ if (CalculateTextWidth(label) <= MAX_WIDTH) { ++ dbusmenu_menuitem_property_set(mNativeData, ++ DBUSMENU_MENUITEM_PROP_LABEL, ++ NS_ConvertUTF16toUTF8(label).get()); ++ return; ++ } ++ ++ // This sucks. ++ // This should be done at the point where the menu is drawn (hello Unity), ++ // but unfortunately it doesn't do that and will happily fill your entire ++ // screen width with a menu if you have a bookmark with a really long title. ++ // This leaves us with no other option but to ellipsize here, with no proper ++ // knowledge of Unity's render path, font size etc. This is better than nothing ++ nsAutoString truncated; ++ int target = MAX_WIDTH - GetEllipsisWidth(); ++ length = label.Length(); ++ ++ static mozilla::dom::Element::AttrValuesArray strings[] = { ++ nsGkAtoms::left, nsGkAtoms::start, ++ nsGkAtoms::center, nsGkAtoms::right, ++ nsGkAtoms::end, nullptr ++ }; ++ ++ int32_t type = mContent->AsElement()->FindAttrValueIn(kNameSpaceID_None, ++ nsGkAtoms::crop, ++ strings, eCaseMatters); ++ ++ switch (type) { ++ case 0: ++ case 1: ++ // FIXME: Implement left cropping ++ case 2: ++ // FIXME: Implement center cropping ++ case 3: ++ case 4: ++ default: ++ for (uint32_t i = 0; i < length; i++) { ++ truncated.Append(label.CharAt(i)); ++ if (CalculateTextWidth(truncated) > target) { ++ break; ++ } ++ } ++ ++ truncated.Append(GetEllipsis()); ++ } ++ ++ dbusmenu_menuitem_property_set(mNativeData, ++ DBUSMENU_MENUITEM_PROP_LABEL, ++ NS_ConvertUTF16toUTF8(truncated).get()); ++} ++ ++void ++nsMenuObject::UpdateVisibility(const ComputedStyle *aComputedStyle) ++{ ++ bool vis = true; ++ ++ if (aComputedStyle && ++ (aComputedStyle->StyleDisplay()->mDisplay == StyleDisplay::None || ++ aComputedStyle->StyleVisibility()->mVisible == ++ StyleVisibility::Collapse)) { ++ vis = false; ++ } ++ ++ dbusmenu_menuitem_property_set_bool(mNativeData, ++ DBUSMENU_MENUITEM_PROP_VISIBLE, ++ vis); ++} ++ ++void ++nsMenuObject::UpdateSensitivity() ++{ ++ bool disabled = mContent->AsElement()->AttrValueIs(kNameSpaceID_None, ++ nsGkAtoms::disabled, ++ nsGkAtoms::_true, ++ eCaseMatters); ++ ++ dbusmenu_menuitem_property_set_bool(mNativeData, ++ DBUSMENU_MENUITEM_PROP_ENABLED, ++ !disabled); ++ ++} ++ ++void ++nsMenuObject::UpdateIcon(const ComputedStyle *aComputedStyle) ++{ ++ if (ShouldShowIcon()) { ++ if (!mIconLoader) { ++ mIconLoader = new nsMenuObjectIconLoader(this); ++ } ++ ++ mIconLoader->LoadIcon(aComputedStyle); ++ } else { ++ if (mIconLoader) { ++ mIconLoader->Destroy(); ++ mIconLoader = nullptr; ++ } ++ ++ ClearIcon(); ++ } ++} ++ ++already_AddRefed ++nsMenuObject::GetComputedStyle() ++{ ++ RefPtr style = ++ nsComputedDOMStyle::GetComputedStyleNoFlush( ++ mContent->AsElement()); ++ ++ return style.forget(); ++} ++ ++void ++nsMenuObject::InitializeNativeData() ++{ ++} ++ ++nsMenuObject::PropertyFlags ++nsMenuObject::SupportedProperties() const ++{ ++ return static_cast(0); ++} ++ ++bool ++nsMenuObject::IsCompatibleWithNativeData(DbusmenuMenuitem *aNativeData) const ++{ ++ return true; ++} ++ ++void ++nsMenuObject::UpdateContentAttributes() ++{ ++} ++ ++void ++nsMenuObject::Update(const ComputedStyle *aComputedStyle) ++{ ++} ++ ++bool ++nsMenuObject::ShouldShowIcon() const ++{ ++ // Ideally we want to know the visibility of the anonymous XUL image in ++ // our menuitem, but this isn't created because we don't have a frame. ++ // The following works by default (because xul.css hides images in menuitems ++ // that don't have the "menuitem-with-favicon" class). It's possible a third ++ // party theme could override this, but, oh well... ++ const nsAttrValue *classes = mContent->AsElement()->GetClasses(); ++ if (!classes) { ++ return false; ++ } ++ ++ for (uint32_t i = 0; i < classes->GetAtomCount(); ++i) { ++ if (classes->AtomAt(i) == nsGkAtoms::menuitem_with_favicon) { ++ return true; ++ } ++ } ++ ++ return false; ++} ++ ++void ++nsMenuObject::ClearIcon() ++{ ++ dbusmenu_menuitem_property_remove(mNativeData, ++ DBUSMENU_MENUITEM_PROP_ICON_DATA); ++} ++ ++nsMenuObject::~nsMenuObject() ++{ ++ nsWeakMenuObject::NotifyDestroyed(this); ++ ++ if (mIconLoader) { ++ mIconLoader->Destroy(); ++ } ++ ++ if (mListener) { ++ mListener->UnregisterForContentChanges(mContent); ++ } ++ ++ if (mNativeData) { ++ g_object_unref(mNativeData); ++ mNativeData = nullptr; ++ } ++} ++ ++void ++nsMenuObject::CreateNativeData() ++{ ++ MOZ_ASSERT(mNativeData == nullptr, "This node already has a DbusmenuMenuitem. The old one will be leaked"); ++ ++ mNativeData = dbusmenu_menuitem_new(); ++ InitializeNativeData(); ++ if (mParent && mParent->IsBeingDisplayed()) { ++ ContainerIsOpening(); ++ } ++ ++ mListener->RegisterForContentChanges(mContent, this); ++} ++ ++nsresult ++nsMenuObject::AdoptNativeData(DbusmenuMenuitem *aNativeData) ++{ ++ MOZ_ASSERT(mNativeData == nullptr, "This node already has a DbusmenuMenuitem. The old one will be leaked"); ++ ++ if (!IsCompatibleWithNativeData(aNativeData)) { ++ return NS_ERROR_FAILURE; ++ } ++ ++ mNativeData = aNativeData; ++ g_object_ref(mNativeData); ++ ++ PropertyFlags supported = SupportedProperties(); ++ PropertyFlags mask = static_cast(1); ++ ++ for (uint32_t i = 0; gPropertyStrings[i]; ++i) { ++ if (!(mask & supported)) { ++ dbusmenu_menuitem_property_remove(mNativeData, gPropertyStrings[i]); ++ } ++ mask = static_cast(mask << 1); ++ } ++ ++ InitializeNativeData(); ++ if (mParent && mParent->IsBeingDisplayed()) { ++ ContainerIsOpening(); ++ } ++ ++ mListener->RegisterForContentChanges(mContent, this); ++ ++ return NS_OK; ++} ++ ++void ++nsMenuObject::ContainerIsOpening() ++{ ++ MOZ_ASSERT(nsContentUtils::IsSafeToRunScript()); ++ ++ UpdateContentAttributes(); ++ ++ RefPtr style = GetComputedStyle(); ++ Update(style); ++} ++ ++/* static */ void ++nsWeakMenuObject::AddWeakReference(nsWeakMenuObject *aWeak) ++{ ++ aWeak->mPrev = sHead; ++ sHead = aWeak; ++} ++ ++/* static */ void ++nsWeakMenuObject::RemoveWeakReference(nsWeakMenuObject *aWeak) ++{ ++ if (aWeak == sHead) { ++ sHead = aWeak->mPrev; ++ return; ++ } ++ ++ nsWeakMenuObject *weak = sHead; ++ while (weak && weak->mPrev != aWeak) { ++ weak = weak->mPrev; ++ } ++ ++ if (weak) { ++ weak->mPrev = aWeak->mPrev; ++ } ++} ++ ++/* static */ void ++nsWeakMenuObject::NotifyDestroyed(nsMenuObject *aMenuObject) ++{ ++ nsWeakMenuObject *weak = sHead; ++ while (weak) { ++ if (weak->mMenuObject == aMenuObject) { ++ weak->mMenuObject = nullptr; ++ } ++ ++ weak = weak->mPrev; ++ } ++} +diff --git a/widget/gtk/nsMenuObject.h b/widget/gtk/nsMenuObject.h +new file mode 100644 +index 000000000000..92b1ffd8f2fa +--- /dev/null ++++ b/widget/gtk/nsMenuObject.h +@@ -0,0 +1,169 @@ ++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ ++/* vim:expandtab:shiftwidth=4:tabstop=4: ++ */ ++/* This Source Code Form is subject to the terms of the Mozilla Public ++ * License, v. 2.0. If a copy of the MPL was not distributed with this ++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ ++ ++#ifndef __nsMenuObject_h__ ++#define __nsMenuObject_h__ ++ ++#include "mozilla/Attributes.h" ++#include "mozilla/ComputedStyleInlines.h" ++#include "nsCOMPtr.h" ++ ++#include "nsDbusmenu.h" ++#include "nsNativeMenuDocListener.h" ++ ++class nsIContent; ++class nsMenuContainer; ++class nsMenuObjectIconLoader; ++ ++#define DBUSMENU_PROPERTIES \ ++ DBUSMENU_PROPERTY(Label, DBUSMENU_MENUITEM_PROP_LABEL, 0) \ ++ DBUSMENU_PROPERTY(Enabled, DBUSMENU_MENUITEM_PROP_ENABLED, 1) \ ++ DBUSMENU_PROPERTY(Visible, DBUSMENU_MENUITEM_PROP_VISIBLE, 2) \ ++ DBUSMENU_PROPERTY(IconData, DBUSMENU_MENUITEM_PROP_ICON_DATA, 3) \ ++ DBUSMENU_PROPERTY(Type, DBUSMENU_MENUITEM_PROP_TYPE, 4) \ ++ DBUSMENU_PROPERTY(Shortcut, DBUSMENU_MENUITEM_PROP_SHORTCUT, 5) \ ++ DBUSMENU_PROPERTY(ToggleType, DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE, 6) \ ++ DBUSMENU_PROPERTY(ToggleState, DBUSMENU_MENUITEM_PROP_TOGGLE_STATE, 7) \ ++ DBUSMENU_PROPERTY(ChildDisplay, DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY, 8) ++ ++/* ++ * This is the base class for all menu nodes. Each instance represents ++ * a single node in the menu hierarchy. It wraps the corresponding DOM node and ++ * native menu node, keeps them in sync and transfers events between the two. ++ * It is not reference counted - each node is owned by its parent (the top ++ * level menubar is owned by the window) and keeps a weak pointer to its ++ * parent (which is guaranteed to always be valid because a node will never ++ * outlive its parent). It is not safe to keep a reference to nsMenuObject ++ * externally. ++ */ ++class nsMenuObject : public nsNativeMenuChangeObserver ++{ ++public: ++ enum EType { ++ eType_MenuBar, ++ eType_Menu, ++ eType_MenuItem ++ }; ++ ++ virtual ~nsMenuObject(); ++ ++ // Get the native menu item node ++ DbusmenuMenuitem* GetNativeData() const { return mNativeData; } ++ ++ // Get the parent menu object ++ nsMenuContainer* Parent() const { return mParent; } ++ ++ // Get the content node ++ nsIContent* ContentNode() const { return mContent; } ++ ++ // Get the type of this node. Must be provided by subclasses ++ virtual EType Type() const = 0; ++ ++ // Get the document listener ++ nsNativeMenuDocListener* DocListener() const { return mListener; } ++ ++ // Create the native menu item node (called by containers) ++ void CreateNativeData(); ++ ++ // Adopt the specified native menu item node (called by containers) ++ nsresult AdoptNativeData(DbusmenuMenuitem *aNativeData); ++ ++ // Called by the container to tell us that it's opening ++ void ContainerIsOpening(); ++ ++protected: ++ nsMenuObject(nsMenuContainer *aParent, nsIContent *aContent); ++ nsMenuObject(nsNativeMenuDocListener *aListener, nsIContent *aContent); ++ ++ enum PropertyFlags { ++#define DBUSMENU_PROPERTY(e, s, b) eProp##e = (1 << b), ++ DBUSMENU_PROPERTIES ++#undef DBUSMENU_PROPERTY ++ }; ++ ++ void UpdateLabel(); ++ void UpdateVisibility(const mozilla::ComputedStyle *aComputedStyle); ++ void UpdateSensitivity(); ++ void UpdateIcon(const mozilla::ComputedStyle *aComputedStyle); ++ ++ already_AddRefed GetComputedStyle(); ++ ++private: ++ friend class nsMenuObjectIconLoader; ++ ++ // Set up initial properties on the native data, connect to signals etc. ++ // This should be implemented by subclasses ++ virtual void InitializeNativeData(); ++ ++ // Return the properties that this menu object type supports ++ // This should be implemented by subclasses ++ virtual PropertyFlags SupportedProperties() const; ++ ++ // Determine whether this menu object could use the specified ++ // native item. Returns true by default but can be overridden by subclasses ++ virtual bool ++ IsCompatibleWithNativeData(DbusmenuMenuitem *aNativeData) const; ++ ++ // Update attributes on this objects content node when the container opens. ++ // This is called before style resolution, and should be implemented by ++ // subclasses who want to modify attributes that might affect style. ++ // This will not be called when there are script blockers ++ virtual void UpdateContentAttributes(); ++ ++ // Update properties that should be refreshed when the container opens. ++ // This should be implemented by subclasses that have properties which ++ // need refreshing ++ virtual void Update(const mozilla::ComputedStyle *aComputedStyle); ++ ++ bool ShouldShowIcon() const; ++ void ClearIcon(); ++ ++ nsCOMPtr mContent; ++ // mListener is a strong ref for simplicity - someone in the tree needs to ++ // own it, and this only really needs to be the top-level object (as no ++ // children outlives their parent). However, we need to keep it alive until ++ // after running the nsMenuObject destructor for the top-level menu object, ++ // hence the strong ref ++ RefPtr mListener; ++ nsMenuContainer *mParent; // [weak] ++ DbusmenuMenuitem *mNativeData; // [strong] ++ RefPtr mIconLoader; ++}; ++ ++// Keep a weak pointer to a menu object ++class nsWeakMenuObject ++{ ++public: ++ nsWeakMenuObject() : mPrev(nullptr), mMenuObject(nullptr) {} ++ ++ nsWeakMenuObject(nsMenuObject *aMenuObject) : ++ mPrev(nullptr), mMenuObject(aMenuObject) ++ { ++ AddWeakReference(this); ++ } ++ ++ ~nsWeakMenuObject() { RemoveWeakReference(this); } ++ ++ nsMenuObject* get() const { return mMenuObject; } ++ ++ nsMenuObject* operator->() const { return mMenuObject; } ++ ++ explicit operator bool() const { return !!mMenuObject; } ++ ++ static void NotifyDestroyed(nsMenuObject *aMenuObject); ++ ++private: ++ static void AddWeakReference(nsWeakMenuObject *aWeak); ++ static void RemoveWeakReference(nsWeakMenuObject *aWeak); ++ ++ nsWeakMenuObject *mPrev; ++ static nsWeakMenuObject *sHead; ++ ++ nsMenuObject *mMenuObject; ++}; ++ ++#endif /* __nsMenuObject_h__ */ +diff --git a/widget/gtk/nsMenuSeparator.cpp b/widget/gtk/nsMenuSeparator.cpp +new file mode 100644 +index 000000000000..3ab135bd8a20 +--- /dev/null ++++ b/widget/gtk/nsMenuSeparator.cpp +@@ -0,0 +1,82 @@ ++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ ++/* vim:expandtab:shiftwidth=4:tabstop=4: ++ */ ++/* This Source Code Form is subject to the terms of the Mozilla Public ++ * License, v. 2.0. If a copy of the MPL was not distributed with this ++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ ++ ++#include "mozilla/Assertions.h" ++#include "nsCRT.h" ++#include "nsGkAtoms.h" ++ ++#include "nsDbusmenu.h" ++ ++#include "nsMenuContainer.h" ++#include "nsMenuSeparator.h" ++ ++using namespace mozilla; ++ ++void ++nsMenuSeparator::InitializeNativeData() ++{ ++ dbusmenu_menuitem_property_set(GetNativeData(), ++ DBUSMENU_MENUITEM_PROP_TYPE, ++ "separator"); ++} ++ ++void ++nsMenuSeparator::Update(const ComputedStyle *aComputedStyle) ++{ ++ UpdateVisibility(aComputedStyle); ++} ++ ++bool ++nsMenuSeparator::IsCompatibleWithNativeData(DbusmenuMenuitem *aNativeData) const ++{ ++ return nsCRT::strcmp(dbusmenu_menuitem_property_get(aNativeData, ++ DBUSMENU_MENUITEM_PROP_TYPE), ++ "separator") == 0; ++} ++ ++nsMenuObject::PropertyFlags ++nsMenuSeparator::SupportedProperties() const ++{ ++ return static_cast( ++ nsMenuObject::ePropVisible | ++ nsMenuObject::ePropType ++ ); ++} ++ ++void ++nsMenuSeparator::OnAttributeChanged(nsIContent *aContent, nsAtom *aAttribute) ++{ ++ MOZ_ASSERT(aContent == ContentNode(), "Received an event that wasn't meant for us!"); ++ ++ if (!Parent()->IsBeingDisplayed()) { ++ return; ++ } ++ ++ if (aAttribute == nsGkAtoms::hidden || ++ aAttribute == nsGkAtoms::collapsed) { ++ RefPtr style = GetComputedStyle(); ++ UpdateVisibility(style); ++ } ++} ++ ++nsMenuSeparator::nsMenuSeparator(nsMenuContainer *aParent, ++ nsIContent *aContent) : ++ nsMenuObject(aParent, aContent) ++{ ++ MOZ_COUNT_CTOR(nsMenuSeparator); ++} ++ ++nsMenuSeparator::~nsMenuSeparator() ++{ ++ MOZ_COUNT_DTOR(nsMenuSeparator); ++} ++ ++nsMenuObject::EType ++nsMenuSeparator::Type() const ++{ ++ return eType_MenuItem; ++} +diff --git a/widget/gtk/nsMenuSeparator.h b/widget/gtk/nsMenuSeparator.h +new file mode 100644 +index 000000000000..dd3f5b974dd3 +--- /dev/null ++++ b/widget/gtk/nsMenuSeparator.h +@@ -0,0 +1,37 @@ ++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ ++/* vim:expandtab:shiftwidth=4:tabstop=4: ++ */ ++/* This Source Code Form is subject to the terms of the Mozilla Public ++ * License, v. 2.0. If a copy of the MPL was not distributed with this ++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ ++ ++#ifndef __nsMenuSeparator_h__ ++#define __nsMenuSeparator_h__ ++ ++#include "mozilla/Attributes.h" ++ ++#include "nsMenuObject.h" ++ ++class nsIContent; ++class nsAtom; ++class nsMenuContainer; ++ ++// Menu separator class ++class nsMenuSeparator final : public nsMenuObject ++{ ++public: ++ nsMenuSeparator(nsMenuContainer *aParent, nsIContent *aContent); ++ ~nsMenuSeparator(); ++ ++ nsMenuObject::EType Type() const override; ++ ++private: ++ void InitializeNativeData() override; ++ void Update(const mozilla::ComputedStyle *aComputedStyle) override; ++ bool IsCompatibleWithNativeData(DbusmenuMenuitem *aNativeData) const override; ++ nsMenuObject::PropertyFlags SupportedProperties() const override; ++ ++ void OnAttributeChanged(nsIContent *aContent, nsAtom *aAttribute) override; ++}; ++ ++#endif /* __nsMenuSeparator_h__ */ +diff --git a/widget/gtk/nsNativeMenuDocListener.cpp b/widget/gtk/nsNativeMenuDocListener.cpp +new file mode 100644 +index 000000000000..2336ed6e3bc5 +--- /dev/null ++++ b/widget/gtk/nsNativeMenuDocListener.cpp +@@ -0,0 +1,347 @@ ++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ ++/* vim:expandtab:shiftwidth=4:tabstop=4: ++ */ ++/* This Source Code Form is subject to the terms of the Mozilla Public ++ * License, v. 2.0. If a copy of the MPL was not distributed with this ++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ ++ ++#include "mozilla/Assertions.h" ++#include "mozilla/DebugOnly.h" ++#include "mozilla/dom/Document.h" ++#include "mozilla/dom/Element.h" ++#include "nsContentUtils.h" ++#include "nsAtom.h" ++#include "nsIContent.h" ++ ++#include "nsMenuContainer.h" ++ ++#include "nsNativeMenuDocListener.h" ++ ++using namespace mozilla; ++ ++uint32_t nsNativeMenuDocListener::sUpdateBlockersCount = 0; ++ ++nsNativeMenuDocListenerTArray *gPendingListeners; ++ ++/* ++ * Small helper which caches a single listener, so that consecutive ++ * events which go to the same node avoid multiple hash table lookups ++ */ ++class MOZ_STACK_CLASS DispatchHelper ++{ ++public: ++ DispatchHelper(nsNativeMenuDocListener *aListener, ++ nsIContent *aContent) : ++ mObserver(nullptr) ++ { ++ if (aContent == aListener->mLastSource) { ++ mObserver = aListener->mLastTarget; ++ } else { ++ mObserver = aListener->mContentToObserverTable.Get(aContent); ++ if (mObserver) { ++ aListener->mLastSource = aContent; ++ aListener->mLastTarget = mObserver; ++ } ++ } ++ } ++ ++ ~DispatchHelper() { }; ++ ++ nsNativeMenuChangeObserver* Observer() const { return mObserver; } ++ ++ bool HasObserver() const { return !!mObserver; } ++ ++private: ++ nsNativeMenuChangeObserver *mObserver; ++}; ++ ++NS_IMPL_ISUPPORTS(nsNativeMenuDocListener, nsIMutationObserver) ++ ++nsNativeMenuDocListener::~nsNativeMenuDocListener() ++{ ++ MOZ_ASSERT(mContentToObserverTable.Count() == 0, ++ "Some nodes forgot to unregister listeners. This is bad! (and we're lucky we made it this far)"); ++ MOZ_COUNT_DTOR(nsNativeMenuDocListener); ++} ++ ++void ++nsNativeMenuDocListener::AttributeChanged(mozilla::dom::Element *aElement, ++ int32_t aNameSpaceID, ++ nsAtom *aAttribute, ++ int32_t aModType, ++ const nsAttrValue* aOldValue) ++{ ++ if (sUpdateBlockersCount == 0) { ++ DoAttributeChanged(aElement, aAttribute); ++ return; ++ } ++ ++ MutationRecord *m = mPendingMutations.AppendElement(MakeUnique())->get(); ++ m->mType = MutationRecord::eAttributeChanged; ++ m->mTarget = aElement; ++ m->mAttribute = aAttribute; ++ ++ ScheduleFlush(this); ++} ++ ++void ++nsNativeMenuDocListener::ContentAppended(nsIContent *aFirstNewContent) ++{ ++ for (nsIContent *c = aFirstNewContent; c; c = c->GetNextSibling()) { ++ ContentInserted(c); ++ } ++} ++ ++void ++nsNativeMenuDocListener::ContentInserted(nsIContent *aChild) ++{ ++ nsIContent* container = aChild->GetParent(); ++ if (!container) { ++ return; ++ } ++ ++ nsIContent *prevSibling = nsMenuContainer::GetPreviousSupportedSibling(aChild); ++ ++ if (sUpdateBlockersCount == 0) { ++ DoContentInserted(container, aChild, prevSibling); ++ return; ++ } ++ ++ MutationRecord *m = mPendingMutations.AppendElement(MakeUnique())->get(); ++ m->mType = MutationRecord::eContentInserted; ++ m->mTarget = container; ++ m->mChild = aChild; ++ m->mPrevSibling = prevSibling; ++ ++ ScheduleFlush(this); ++} ++ ++void ++nsNativeMenuDocListener::ContentRemoved(nsIContent *aChild, ++ nsIContent *aPreviousSibling) ++{ ++ nsIContent* container = aChild->GetParent(); ++ if (!container) { ++ return; ++ } ++ ++ if (sUpdateBlockersCount == 0) { ++ DoContentRemoved(container, aChild); ++ return; ++ } ++ ++ MutationRecord *m = mPendingMutations.AppendElement(MakeUnique())->get(); ++ m->mType = MutationRecord::eContentRemoved; ++ m->mTarget = container; ++ m->mChild = aChild; ++ ++ ScheduleFlush(this); ++} ++ ++void ++nsNativeMenuDocListener::NodeWillBeDestroyed(const nsINode *aNode) ++{ ++ mDocument = nullptr; ++} ++ ++void ++nsNativeMenuDocListener::DoAttributeChanged(nsIContent *aContent, ++ nsAtom *aAttribute) ++{ ++ DispatchHelper h(this, aContent); ++ if (h.HasObserver()) { ++ h.Observer()->OnAttributeChanged(aContent, aAttribute); ++ } ++} ++ ++void ++nsNativeMenuDocListener::DoContentInserted(nsIContent *aContainer, ++ nsIContent *aChild, ++ nsIContent *aPrevSibling) ++{ ++ DispatchHelper h(this, aContainer); ++ if (h.HasObserver()) { ++ h.Observer()->OnContentInserted(aContainer, aChild, aPrevSibling); ++ } ++} ++ ++void ++nsNativeMenuDocListener::DoContentRemoved(nsIContent *aContainer, ++ nsIContent *aChild) ++{ ++ DispatchHelper h(this, aContainer); ++ if (h.HasObserver()) { ++ h.Observer()->OnContentRemoved(aContainer, aChild); ++ } ++} ++ ++void ++nsNativeMenuDocListener::DoBeginUpdates(nsIContent *aTarget) ++{ ++ DispatchHelper h(this, aTarget); ++ if (h.HasObserver()) { ++ h.Observer()->OnBeginUpdates(aTarget); ++ } ++} ++ ++void ++nsNativeMenuDocListener::DoEndUpdates(nsIContent *aTarget) ++{ ++ DispatchHelper h(this, aTarget); ++ if (h.HasObserver()) { ++ h.Observer()->OnEndUpdates(); ++ } ++} ++ ++void ++nsNativeMenuDocListener::FlushPendingMutations() ++{ ++ nsIContent *currentTarget = nullptr; ++ bool inUpdateSequence = false; ++ ++ while (mPendingMutations.Length() > 0) { ++ MutationRecord *m = mPendingMutations[0].get(); ++ ++ if (m->mTarget != currentTarget) { ++ if (inUpdateSequence) { ++ DoEndUpdates(currentTarget); ++ inUpdateSequence = false; ++ } ++ ++ currentTarget = m->mTarget; ++ ++ if (mPendingMutations.Length() > 1 && ++ mPendingMutations[1]->mTarget == currentTarget) { ++ DoBeginUpdates(currentTarget); ++ inUpdateSequence = true; ++ } ++ } ++ ++ switch (m->mType) { ++ case MutationRecord::eAttributeChanged: ++ DoAttributeChanged(m->mTarget, m->mAttribute); ++ break; ++ case MutationRecord::eContentInserted: ++ DoContentInserted(m->mTarget, m->mChild, m->mPrevSibling); ++ break; ++ case MutationRecord::eContentRemoved: ++ DoContentRemoved(m->mTarget, m->mChild); ++ break; ++ default: ++ MOZ_ASSERT_UNREACHABLE("Invalid type"); ++ } ++ ++ mPendingMutations.RemoveElementAt(0); ++ } ++ ++ if (inUpdateSequence) { ++ DoEndUpdates(currentTarget); ++ } ++} ++ ++/* static */ void ++nsNativeMenuDocListener::ScheduleFlush(nsNativeMenuDocListener *aListener) ++{ ++ MOZ_ASSERT(sUpdateBlockersCount > 0, "Shouldn't be doing this now"); ++ ++ if (!gPendingListeners) { ++ gPendingListeners = new nsNativeMenuDocListenerTArray; ++ } ++ ++ if (gPendingListeners->IndexOf(aListener) == ++ nsNativeMenuDocListenerTArray::NoIndex) { ++ gPendingListeners->AppendElement(aListener); ++ } ++} ++ ++/* static */ void ++nsNativeMenuDocListener::CancelFlush(nsNativeMenuDocListener *aListener) ++{ ++ if (!gPendingListeners) { ++ return; ++ } ++ ++ gPendingListeners->RemoveElement(aListener); ++} ++ ++/* static */ void ++nsNativeMenuDocListener::RemoveUpdateBlocker() ++{ ++ if (sUpdateBlockersCount == 1 && gPendingListeners) { ++ while (gPendingListeners->Length() > 0) { ++ (*gPendingListeners)[0]->FlushPendingMutations(); ++ gPendingListeners->RemoveElementAt(0); ++ } ++ } ++ ++ MOZ_ASSERT(sUpdateBlockersCount > 0, "Negative update blockers count!"); ++ sUpdateBlockersCount--; ++} ++ ++nsNativeMenuDocListener::nsNativeMenuDocListener(nsIContent *aRootNode) : ++ mRootNode(aRootNode), ++ mDocument(nullptr), ++ mLastSource(nullptr), ++ mLastTarget(nullptr) ++{ ++ MOZ_COUNT_CTOR(nsNativeMenuDocListener); ++} ++ ++void ++nsNativeMenuDocListener::RegisterForContentChanges(nsIContent *aContent, ++ nsNativeMenuChangeObserver *aObserver) ++{ ++ MOZ_ASSERT(aContent, "Need content parameter"); ++ MOZ_ASSERT(aObserver, "Need observer parameter"); ++ if (!aContent || !aObserver) { ++ return; ++ } ++ ++ DebugOnly old; ++ MOZ_ASSERT(!mContentToObserverTable.Get(aContent, &old) || old == aObserver, ++ "Multiple observers for the same content node are not supported"); ++ ++ mContentToObserverTable.InsertOrUpdate(aContent, aObserver); ++} ++ ++void ++nsNativeMenuDocListener::UnregisterForContentChanges(nsIContent *aContent) ++{ ++ MOZ_ASSERT(aContent, "Need content parameter"); ++ if (!aContent) { ++ return; ++ } ++ ++ mContentToObserverTable.Remove(aContent); ++ if (aContent == mLastSource) { ++ mLastSource = nullptr; ++ mLastTarget = nullptr; ++ } ++} ++ ++void ++nsNativeMenuDocListener::Start() ++{ ++ if (mDocument) { ++ return; ++ } ++ ++ mDocument = mRootNode->OwnerDoc(); ++ if (!mDocument) { ++ return; ++ } ++ ++ mDocument->AddMutationObserver(this); ++} ++ ++void ++nsNativeMenuDocListener::Stop() ++{ ++ if (mDocument) { ++ mDocument->RemoveMutationObserver(this); ++ mDocument = nullptr; ++ } ++ ++ CancelFlush(this); ++ mPendingMutations.Clear(); ++} +diff --git a/widget/gtk/nsNativeMenuDocListener.h b/widget/gtk/nsNativeMenuDocListener.h +new file mode 100644 +index 000000000000..5ee99cba70fd +--- /dev/null ++++ b/widget/gtk/nsNativeMenuDocListener.h +@@ -0,0 +1,152 @@ ++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ ++/* vim:expandtab:shiftwidth=4:tabstop=4: ++ */ ++/* This Source Code Form is subject to the terms of the Mozilla Public ++ * License, v. 2.0. If a copy of the MPL was not distributed with this ++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ ++ ++#ifndef __nsNativeMenuDocListener_h__ ++#define __nsNativeMenuDocListener_h__ ++ ++#include "mozilla/Attributes.h" ++#include "mozilla/RefPtr.h" ++#include "mozilla/UniquePtr.h" ++#include "nsTHashMap.h" ++#include "nsStubMutationObserver.h" ++#include "nsTArray.h" ++ ++class nsAtom; ++class nsIContent; ++class nsNativeMenuChangeObserver; ++ ++namespace mozilla { ++namespace dom { ++class Document; ++} ++} ++ ++/* ++ * This class keeps a mapping of content nodes to observers and forwards DOM ++ * mutations to these. There is exactly one of these for every menubar. ++ */ ++class nsNativeMenuDocListener final : nsStubMutationObserver ++{ ++public: ++ NS_DECL_ISUPPORTS ++ ++ nsNativeMenuDocListener(nsIContent *aRootNode); ++ ++ // Register an observer to receive mutation events for the specified ++ // content node. The caller must keep the observer alive until ++ // UnregisterForContentChanges is called. ++ void RegisterForContentChanges(nsIContent *aContent, ++ nsNativeMenuChangeObserver *aObserver); ++ ++ // Unregister the registered observer for the specified content node ++ void UnregisterForContentChanges(nsIContent *aContent); ++ ++ // Start listening to the document and forwarding DOM mutations to ++ // registered observers. ++ void Start(); ++ ++ // Stop listening to the document. No DOM mutations will be forwarded ++ // to registered observers. ++ void Stop(); ++ ++ /* ++ * This class is intended to be used inside GObject signal handlers. ++ * It allows us to queue updates until we have finished delivering ++ * events to Gecko, and then we can batch updates to our view of the ++ * menu. This allows us to do menu updates without altering the structure ++ * seen by the OS. ++ */ ++ class MOZ_STACK_CLASS BlockUpdatesScope ++ { ++ public: ++ BlockUpdatesScope() ++ { ++ nsNativeMenuDocListener::AddUpdateBlocker(); ++ } ++ ++ ~BlockUpdatesScope() ++ { ++ nsNativeMenuDocListener::RemoveUpdateBlocker(); ++ } ++ }; ++ ++private: ++ friend class DispatchHelper; ++ ++ struct MutationRecord { ++ enum RecordType { ++ eAttributeChanged, ++ eContentInserted, ++ eContentRemoved ++ } mType; ++ ++ nsCOMPtr mTarget; ++ nsCOMPtr mChild; ++ nsCOMPtr mPrevSibling; ++ RefPtr mAttribute; ++ }; ++ ++ ~nsNativeMenuDocListener(); ++ ++ NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED ++ NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED ++ NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED ++ NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED ++ NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED ++ ++ void DoAttributeChanged(nsIContent *aContent, nsAtom *aAttribute); ++ void DoContentInserted(nsIContent *aContainer, ++ nsIContent *aChild, ++ nsIContent *aPrevSibling); ++ void DoContentRemoved(nsIContent *aContainer, nsIContent *aChild); ++ void DoBeginUpdates(nsIContent *aTarget); ++ void DoEndUpdates(nsIContent *aTarget); ++ ++ void FlushPendingMutations(); ++ static void ScheduleFlush(nsNativeMenuDocListener *aListener); ++ static void CancelFlush(nsNativeMenuDocListener *aListener); ++ ++ static void AddUpdateBlocker() { ++sUpdateBlockersCount; } ++ static void RemoveUpdateBlocker(); ++ ++ nsCOMPtr mRootNode; ++ mozilla::dom::Document *mDocument; ++ nsIContent *mLastSource; ++ nsNativeMenuChangeObserver *mLastTarget; ++ nsTArray > mPendingMutations; ++ nsTHashMap, nsNativeMenuChangeObserver *> mContentToObserverTable; ++ ++ static uint32_t sUpdateBlockersCount; ++}; ++ ++typedef nsTArray > nsNativeMenuDocListenerTArray; ++ ++/* ++ * Implemented by classes that want to listen to mutation events from content ++ * nodes. ++ */ ++class nsNativeMenuChangeObserver ++{ ++public: ++ virtual void OnAttributeChanged(nsIContent *aContent, nsAtom *aAttribute) {} ++ ++ virtual void OnContentInserted(nsIContent *aContainer, ++ nsIContent *aChild, ++ nsIContent *aPrevSibling) {} ++ ++ virtual void OnContentRemoved(nsIContent *aContainer, nsIContent *aChild) {} ++ ++ // Signals the start of a sequence of more than 1 event for the specified ++ // node. This only happens when events are flushed as all BlockUpdatesScope ++ // instances go out of scope ++ virtual void OnBeginUpdates(nsIContent *aContent) {}; ++ ++ // Signals the end of a sequence of events ++ virtual void OnEndUpdates() {}; ++}; ++ ++#endif /* __nsNativeMenuDocListener_h__ */ +diff --git a/widget/gtk/nsNativeMenuService.cpp b/widget/gtk/nsNativeMenuService.cpp +new file mode 100644 +index 000000000000..5cf36a447036 +--- /dev/null ++++ b/widget/gtk/nsNativeMenuService.cpp +@@ -0,0 +1,478 @@ ++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ ++/* vim:expandtab:shiftwidth=4:tabstop=4: ++ */ ++/* This Source Code Form is subject to the terms of the Mozilla Public ++ * License, v. 2.0. If a copy of the MPL was not distributed with this ++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ ++ ++#include "mozilla/dom/Element.h" ++#include "mozilla/Assertions.h" ++#include "mozilla/Preferences.h" ++#include "mozilla/UniquePtr.h" ++#include "nsCOMPtr.h" ++#include "nsCRT.h" ++#include "nsGtkUtils.h" ++#include "nsIContent.h" ++#include "nsIWidget.h" ++#include "nsServiceManagerUtils.h" ++#include "nsWindow.h" ++#include "prlink.h" ++ ++#include "nsDbusmenu.h" ++#include "nsMenuBar.h" ++#include "nsNativeMenuDocListener.h" ++ ++#include ++#include ++#include ++ ++#include "nsNativeMenuService.h" ++ ++using namespace mozilla; ++ ++nsNativeMenuService* nsNativeMenuService::sService = nullptr; ++ ++extern PangoLayout* gPangoLayout; ++extern nsNativeMenuDocListenerTArray* gPendingListeners; ++ ++#undef g_dbus_proxy_new_for_bus ++#undef g_dbus_proxy_new_for_bus_finish ++#undef g_dbus_proxy_call ++#undef g_dbus_proxy_call_finish ++#undef g_dbus_proxy_get_name_owner ++ ++typedef void (*_g_dbus_proxy_new_for_bus_fn)(GBusType, GDBusProxyFlags, ++ GDBusInterfaceInfo*, ++ const gchar*, const gchar*, ++ const gchar*, GCancellable*, ++ GAsyncReadyCallback, gpointer); ++ ++typedef GDBusProxy* (*_g_dbus_proxy_new_for_bus_finish_fn)(GAsyncResult*, ++ GError**); ++typedef void (*_g_dbus_proxy_call_fn)(GDBusProxy*, const gchar*, GVariant*, ++ GDBusCallFlags, gint, GCancellable*, ++ GAsyncReadyCallback, gpointer); ++typedef GVariant* (*_g_dbus_proxy_call_finish_fn)(GDBusProxy*, GAsyncResult*, ++ GError**); ++typedef gchar* (*_g_dbus_proxy_get_name_owner_fn)(GDBusProxy*); ++ ++static _g_dbus_proxy_new_for_bus_fn _g_dbus_proxy_new_for_bus; ++static _g_dbus_proxy_new_for_bus_finish_fn _g_dbus_proxy_new_for_bus_finish; ++static _g_dbus_proxy_call_fn _g_dbus_proxy_call; ++static _g_dbus_proxy_call_finish_fn _g_dbus_proxy_call_finish; ++static _g_dbus_proxy_get_name_owner_fn _g_dbus_proxy_get_name_owner; ++ ++#define g_dbus_proxy_new_for_bus _g_dbus_proxy_new_for_bus ++#define g_dbus_proxy_new_for_bus_finish _g_dbus_proxy_new_for_bus_finish ++#define g_dbus_proxy_call _g_dbus_proxy_call ++#define g_dbus_proxy_call_finish _g_dbus_proxy_call_finish ++#define g_dbus_proxy_get_name_owner _g_dbus_proxy_get_name_owner ++ ++static PRLibrary *gGIOLib = nullptr; ++ ++static nsresult ++GDBusInit() ++{ ++ gGIOLib = PR_LoadLibrary("libgio-2.0.so.0"); ++ if (!gGIOLib) { ++ return NS_ERROR_FAILURE; ++ } ++ ++ g_dbus_proxy_new_for_bus = (_g_dbus_proxy_new_for_bus_fn)PR_FindFunctionSymbol(gGIOLib, "g_dbus_proxy_new_for_bus"); ++ g_dbus_proxy_new_for_bus_finish = (_g_dbus_proxy_new_for_bus_finish_fn)PR_FindFunctionSymbol(gGIOLib, "g_dbus_proxy_new_for_bus_finish"); ++ g_dbus_proxy_call = (_g_dbus_proxy_call_fn)PR_FindFunctionSymbol(gGIOLib, "g_dbus_proxy_call"); ++ g_dbus_proxy_call_finish = (_g_dbus_proxy_call_finish_fn)PR_FindFunctionSymbol(gGIOLib, "g_dbus_proxy_call_finish"); ++ g_dbus_proxy_get_name_owner = (_g_dbus_proxy_get_name_owner_fn)PR_FindFunctionSymbol(gGIOLib, "g_dbus_proxy_get_name_owner"); ++ ++ if (!g_dbus_proxy_new_for_bus || ++ !g_dbus_proxy_new_for_bus_finish || ++ !g_dbus_proxy_call || ++ !g_dbus_proxy_call_finish || ++ !g_dbus_proxy_get_name_owner) { ++ return NS_ERROR_FAILURE; ++ } ++ ++ return NS_OK; ++} ++ ++NS_IMPL_ISUPPORTS(nsNativeMenuService, nsINativeMenuService) ++ ++nsNativeMenuService::nsNativeMenuService() : ++ mCreateProxyCancellable(nullptr), mDbusProxy(nullptr), mOnline(false) ++{ ++} ++ ++nsNativeMenuService::~nsNativeMenuService() ++{ ++ SetOnline(false); ++ ++ if (mCreateProxyCancellable) { ++ g_cancellable_cancel(mCreateProxyCancellable); ++ g_object_unref(mCreateProxyCancellable); ++ mCreateProxyCancellable = nullptr; ++ } ++ ++ // Make sure we disconnect map-event handlers ++ while (mMenuBars.Length() > 0) { ++ NotifyNativeMenuBarDestroyed(mMenuBars[0]); ++ } ++ ++ Preferences::UnregisterCallback(PrefChangedCallback, ++ "ui.use_unity_menubar"); ++ ++ if (mDbusProxy) { ++ g_signal_handlers_disconnect_by_func(mDbusProxy, ++ FuncToGpointer(name_owner_changed_cb), ++ NULL); ++ g_object_unref(mDbusProxy); ++ } ++ ++ if (gPendingListeners) { ++ delete gPendingListeners; ++ gPendingListeners = nullptr; ++ } ++ if (gPangoLayout) { ++ g_object_unref(gPangoLayout); ++ gPangoLayout = nullptr; ++ } ++ ++ MOZ_ASSERT(sService == this); ++ sService = nullptr; ++} ++ ++nsresult ++nsNativeMenuService::Init() ++{ ++ nsresult rv = nsDbusmenuFunctions::Init(); ++ if (NS_FAILED(rv)) { ++ return rv; ++ } ++ ++ rv = GDBusInit(); ++ if (NS_FAILED(rv)) { ++ return rv; ++ } ++ ++ Preferences::RegisterCallback(PrefChangedCallback, ++ "ui.use_unity_menubar"); ++ ++ mCreateProxyCancellable = g_cancellable_new(); ++ ++ g_dbus_proxy_new_for_bus(G_BUS_TYPE_SESSION, ++ static_cast( ++ G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | ++ G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS | ++ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START), ++ nullptr, ++ "com.canonical.AppMenu.Registrar", ++ "/com/canonical/AppMenu/Registrar", ++ "com.canonical.AppMenu.Registrar", ++ mCreateProxyCancellable, proxy_created_cb, ++ nullptr); ++ ++ /* We don't technically know that the shell will draw the menubar until ++ * we know whether anybody owns the name of the menubar service on the ++ * session bus. However, discovering this happens asynchronously so ++ * we optimize for the common case here by assuming that the shell will ++ * draw window menubars if we are running inside Unity. This should ++ * mean that we avoid temporarily displaying the window menubar ourselves ++ */ ++ const char *desktop = getenv("XDG_CURRENT_DESKTOP"); ++ if (nsCRT::strcmp(desktop, "Unity") == 0) { ++ SetOnline(true); ++ } ++ ++ return NS_OK; ++} ++ ++/* static */ void ++nsNativeMenuService::EnsureInitialized() ++{ ++ if (sService) { ++ return; ++ } ++ nsCOMPtr service = ++ do_GetService("@mozilla.org/widget/nativemenuservice;1"); ++} ++ ++void ++nsNativeMenuService::SetOnline(bool aOnline) ++{ ++ if (!Preferences::GetBool("ui.use_unity_menubar", true)) { ++ aOnline = false; ++ } ++ ++ mOnline = aOnline; ++ if (aOnline) { ++ for (uint32_t i = 0; i < mMenuBars.Length(); ++i) { ++ RegisterNativeMenuBar(mMenuBars[i]); ++ } ++ } else { ++ for (uint32_t i = 0; i < mMenuBars.Length(); ++i) { ++ mMenuBars[i]->Deactivate(); ++ } ++ } ++} ++ ++void ++nsNativeMenuService::RegisterNativeMenuBar(nsMenuBar *aMenuBar) ++{ ++ if (!mOnline) { ++ return; ++ } ++ ++ // This will effectively create the native menubar for ++ // exporting over the session bus, and hide the XUL menubar ++ aMenuBar->Activate(); ++ ++ if (!mDbusProxy || ++ !gtk_widget_get_mapped(aMenuBar->TopLevelWindow()) || ++ mMenuBarRegistrationCancellables.Get(aMenuBar, nullptr)) { ++ // Don't go further if we don't have a proxy for the shell menu ++ // service, the window isn't mapped or there is a request in progress. ++ return; ++ } ++ ++ uint32_t xid = aMenuBar->WindowId(); ++ nsCString path = aMenuBar->ObjectPath(); ++ if (xid == 0 || path.IsEmpty()) { ++ NS_WARNING("Menubar has invalid XID or object path"); ++ return; ++ } ++ ++ GCancellable *cancellable = g_cancellable_new(); ++ mMenuBarRegistrationCancellables.InsertOrUpdate(aMenuBar, cancellable); ++ ++ // We keep a weak ref because we can't assume that GDBus cancellation ++ // is reliable (see https://launchpad.net/bugs/953562) ++ ++ g_dbus_proxy_call(mDbusProxy, "RegisterWindow", ++ g_variant_new("(uo)", xid, path.get()), ++ G_DBUS_CALL_FLAGS_NONE, -1, ++ cancellable, ++ register_native_menubar_cb, aMenuBar); ++} ++ ++/* static */ void ++nsNativeMenuService::name_owner_changed_cb(GObject *gobject, ++ GParamSpec *pspec, ++ gpointer user_data) ++{ ++ nsNativeMenuService::GetSingleton()->OnNameOwnerChanged(); ++} ++ ++/* static */ void ++nsNativeMenuService::proxy_created_cb(GObject *source_object, ++ GAsyncResult *res, ++ gpointer user_data) ++{ ++ GError *error = nullptr; ++ GDBusProxy *proxy = g_dbus_proxy_new_for_bus_finish(res, &error); ++ if (error && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { ++ g_error_free(error); ++ return; ++ } ++ ++ if (error) { ++ g_error_free(error); ++ } ++ ++ // We need this check because we can't assume that GDBus cancellation ++ // is reliable (see https://launchpad.net/bugs/953562) ++ nsNativeMenuService *self = nsNativeMenuService::GetSingleton(); ++ if (!self) { ++ if (proxy) { ++ g_object_unref(proxy); ++ } ++ return; ++ } ++ ++ self->OnProxyCreated(proxy); ++} ++ ++/* static */ void ++nsNativeMenuService::register_native_menubar_cb(GObject *source_object, ++ GAsyncResult *res, ++ gpointer user_data) ++{ ++ nsMenuBar *menuBar = static_cast(user_data); ++ ++ GError *error = nullptr; ++ GVariant *results = g_dbus_proxy_call_finish(G_DBUS_PROXY(source_object), ++ res, &error); ++ if (results) { ++ // There's nothing useful in the response ++ g_variant_unref(results); ++ } ++ ++ bool success = error ? false : true; ++ if (error && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { ++ g_error_free(error); ++ return; ++ } ++ ++ if (error) { ++ g_error_free(error); ++ } ++ ++ nsNativeMenuService *self = nsNativeMenuService::GetSingleton(); ++ if (!self) { ++ return; ++ } ++ ++ self->OnNativeMenuBarRegistered(menuBar, success); ++} ++ ++/* static */ gboolean ++nsNativeMenuService::map_event_cb(GtkWidget *widget, ++ GdkEvent *event, ++ gpointer user_data) ++{ ++ nsMenuBar *menubar = static_cast(user_data); ++ nsNativeMenuService::GetSingleton()->RegisterNativeMenuBar(menubar); ++ ++ return FALSE; ++} ++ ++void ++nsNativeMenuService::OnNameOwnerChanged() ++{ ++ char *owner = g_dbus_proxy_get_name_owner(mDbusProxy); ++ SetOnline(owner ? true : false); ++ g_free(owner); ++} ++ ++void ++nsNativeMenuService::OnProxyCreated(GDBusProxy *aProxy) ++{ ++ mDbusProxy = aProxy; ++ ++ g_object_unref(mCreateProxyCancellable); ++ mCreateProxyCancellable = nullptr; ++ ++ if (!mDbusProxy) { ++ SetOnline(false); ++ return; ++ } ++ ++ g_signal_connect(mDbusProxy, "notify::g-name-owner", ++ G_CALLBACK(name_owner_changed_cb), nullptr); ++ ++ OnNameOwnerChanged(); ++} ++ ++void ++nsNativeMenuService::OnNativeMenuBarRegistered(nsMenuBar *aMenuBar, ++ bool aSuccess) ++{ ++ // Don't assume that GDBus cancellation is reliable (ie, |aMenuBar| might ++ // have already been deleted (see https://launchpad.net/bugs/953562) ++ GCancellable *cancellable = nullptr; ++ if (!mMenuBarRegistrationCancellables.Get(aMenuBar, &cancellable)) { ++ return; ++ } ++ ++ g_object_unref(cancellable); ++ mMenuBarRegistrationCancellables.Remove(aMenuBar); ++ ++ if (!aSuccess) { ++ aMenuBar->Deactivate(); ++ } ++} ++ ++/* static */ void ++nsNativeMenuService::PrefChangedCallback(const char *aPref, ++ void *aClosure) ++{ ++ nsNativeMenuService::GetSingleton()->PrefChanged(); ++} ++ ++void ++nsNativeMenuService::PrefChanged() ++{ ++ if (!mDbusProxy) { ++ SetOnline(false); ++ return; ++ } ++ ++ OnNameOwnerChanged(); ++} ++ ++NS_IMETHODIMP ++nsNativeMenuService::CreateNativeMenuBar(nsIWidget *aParent, ++ mozilla::dom::Element *aMenuBarNode) ++{ ++ NS_ENSURE_ARG(aParent); ++ NS_ENSURE_ARG(aMenuBarNode); ++ ++ if (aMenuBarNode->AttrValueIs(kNameSpaceID_None, ++ nsGkAtoms::_moz_menubarkeeplocal, ++ nsGkAtoms::_true, ++ eCaseMatters)) { ++ return NS_OK; ++ } ++ ++ UniquePtr menubar(nsMenuBar::Create(aParent, aMenuBarNode)); ++ if (!menubar) { ++ NS_WARNING("Failed to create menubar"); ++ return NS_ERROR_FAILURE; ++ } ++ ++ // Unity forgets our window if it is unmapped by the application, which ++ // happens with some extensions that add "minimize to tray" type ++ // functionality. We hook on to the MapNotify event to re-register our menu ++ // with Unity ++ g_signal_connect(G_OBJECT(menubar->TopLevelWindow()), ++ "map-event", G_CALLBACK(map_event_cb), ++ menubar.get()); ++ ++ mMenuBars.AppendElement(menubar.get()); ++ RegisterNativeMenuBar(menubar.get()); ++ ++ static_cast(aParent)->SetMenuBar(std::move(menubar)); ++ ++ return NS_OK; ++} ++ ++/* static */ already_AddRefed ++nsNativeMenuService::GetInstanceForServiceManager() ++{ ++ RefPtr service(sService); ++ ++ if (service) { ++ return service.forget(); ++ } ++ ++ service = new nsNativeMenuService(); ++ ++ if (NS_FAILED(service->Init())) { ++ return nullptr; ++ } ++ ++ sService = service.get(); ++ return service.forget(); ++} ++ ++/* static */ nsNativeMenuService* ++nsNativeMenuService::GetSingleton() ++{ ++ EnsureInitialized(); ++ return sService; ++} ++ ++void ++nsNativeMenuService::NotifyNativeMenuBarDestroyed(nsMenuBar *aMenuBar) ++{ ++ g_signal_handlers_disconnect_by_func(aMenuBar->TopLevelWindow(), ++ FuncToGpointer(map_event_cb), ++ aMenuBar); ++ ++ mMenuBars.RemoveElement(aMenuBar); ++ ++ GCancellable *cancellable = nullptr; ++ if (mMenuBarRegistrationCancellables.Get(aMenuBar, &cancellable)) { ++ mMenuBarRegistrationCancellables.Remove(aMenuBar); ++ g_cancellable_cancel(cancellable); ++ g_object_unref(cancellable); ++ } ++} +diff --git a/widget/gtk/nsNativeMenuService.h b/widget/gtk/nsNativeMenuService.h +new file mode 100644 +index 000000000000..2e0d429eddfd +--- /dev/null ++++ b/widget/gtk/nsNativeMenuService.h +@@ -0,0 +1,85 @@ ++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ ++/* vim:expandtab:shiftwidth=4:tabstop=4: ++ */ ++/* This Source Code Form is subject to the terms of the Mozilla Public ++ * License, v. 2.0. If a copy of the MPL was not distributed with this ++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ ++ ++#ifndef __nsNativeMenuService_h__ ++#define __nsNativeMenuService_h__ ++ ++#include "mozilla/Attributes.h" ++#include "nsCOMPtr.h" ++#include "nsTHashMap.h" ++#include "nsINativeMenuService.h" ++#include "nsTArray.h" ++ ++#include ++#include ++#include ++ ++class nsMenuBar; ++ ++/* ++ * The main native menu service singleton. ++ * NativeMenuSupport::CreateNativeMenuBar calls in to this when a new top level ++ * window is created. ++ * ++ * Menubars are owned by their nsWindow. This service holds a weak reference to ++ * each menubar for the purpose of re-registering them with the shell if it ++ * needs to. The menubar is responsible for notifying the service when the last ++ * reference to it is dropped. ++ */ ++class nsNativeMenuService final : public nsINativeMenuService ++{ ++public: ++ NS_DECL_ISUPPORTS ++ ++ NS_IMETHOD CreateNativeMenuBar(nsIWidget* aParent, mozilla::dom::Element* aMenuBarNode) override; ++ ++ // Returns the singleton addref'd for the service manager ++ static already_AddRefed GetInstanceForServiceManager(); ++ ++ // Returns the singleton without increasing the reference count ++ static nsNativeMenuService* GetSingleton(); ++ ++ // Called by a menubar when it is deleted ++ void NotifyNativeMenuBarDestroyed(nsMenuBar *aMenuBar); ++ ++private: ++ nsNativeMenuService(); ++ ~nsNativeMenuService(); ++ nsresult Init(); ++ ++ static void EnsureInitialized(); ++ void SetOnline(bool aOnline); ++ void RegisterNativeMenuBar(nsMenuBar *aMenuBar); ++ static void name_owner_changed_cb(GObject *gobject, ++ GParamSpec *pspec, ++ gpointer user_data); ++ static void proxy_created_cb(GObject *source_object, ++ GAsyncResult *res, ++ gpointer user_data); ++ static void register_native_menubar_cb(GObject *source_object, ++ GAsyncResult *res, ++ gpointer user_data); ++ static gboolean map_event_cb(GtkWidget *widget, GdkEvent *event, ++ gpointer user_data); ++ void OnNameOwnerChanged(); ++ void OnProxyCreated(GDBusProxy *aProxy); ++ void OnNativeMenuBarRegistered(nsMenuBar *aMenuBar, ++ bool aSuccess); ++ static void PrefChangedCallback(const char *aPref, void *aClosure); ++ void PrefChanged(); ++ ++ GCancellable *mCreateProxyCancellable; ++ GDBusProxy *mDbusProxy; ++ bool mOnline; ++ nsTArray mMenuBars; ++ nsTHashMap, GCancellable*> mMenuBarRegistrationCancellables; ++ ++ static bool sShutdown; ++ static nsNativeMenuService *sService; ++}; ++ ++#endif /* __nsNativeMenuService_h__ */ +diff --git a/widget/gtk/nsWindow.cpp b/widget/gtk/nsWindow.cpp +index 5f227073d8cd..1ab99c38551f 100644 +--- a/widget/gtk/nsWindow.cpp ++++ b/widget/gtk/nsWindow.cpp +@@ -7103,6 +7103,10 @@ void nsWindow::HideWindowChrome(bool aShouldHide) { + SetWindowDecoration(aShouldHide ? eBorderStyle_none : mBorderStyle); + } + ++void nsWindow::SetMenuBar(UniquePtr aMenuBar) { ++ mMenuBar = std::move(aMenuBar); ++} ++ + bool nsWindow::CheckForRollup(gdouble aMouseX, gdouble aMouseY, bool aIsWheel, + bool aAlwaysRollup) { + nsIRollupListener* rollupListener = GetActiveRollupListener(); +diff --git a/widget/gtk/nsWindow.h b/widget/gtk/nsWindow.h +index d4951ec54d1c..90fbd91765b1 100644 +--- a/widget/gtk/nsWindow.h ++++ b/widget/gtk/nsWindow.h +@@ -27,6 +27,8 @@ + #include "nsRefPtrHashtable.h" + #include "IMContextWrapper.h" + ++#include "nsMenuBar.h" ++ + #ifdef ACCESSIBILITY + # include "mozilla/a11y/LocalAccessible.h" + #endif +@@ -203,6 +205,8 @@ class nsWindow final : public nsBaseWidget { + nsresult MakeFullScreen(bool aFullScreen) override; + void HideWindowChrome(bool aShouldHide) override; + ++ void SetMenuBar(mozilla::UniquePtr aMenuBar); ++ + /** + * GetLastUserInputTime returns a timestamp for the most recent user input + * event. This is intended for pointer grab requests (including drags). +@@ -874,6 +878,8 @@ class nsWindow final : public nsBaseWidget { + + static bool sTransparentMainWindow; + ++ mozilla::UniquePtr mMenuBar; ++ + #ifdef ACCESSIBILITY + RefPtr mRootAccessible; + +diff --git a/widget/moz.build b/widget/moz.build +index 9b150c592a8c..77bd0cbbe5c5 100644 +--- a/widget/moz.build ++++ b/widget/moz.build +@@ -161,6 +161,11 @@ EXPORTS += [ + "PuppetWidget.h", + ] + ++if toolkit == "gtk": ++ EXPORTS += [ ++ "nsINativeMenuService.h", ++ ] ++ + EXPORTS.mozilla += [ + "BasicEvents.h", + "ColorScheme.h", +diff --git a/widget/nsINativeMenuService.h b/widget/nsINativeMenuService.h +new file mode 100644 +index 000000000000..e92d7a74a3bc +--- /dev/null ++++ b/widget/nsINativeMenuService.h +@@ -0,0 +1,39 @@ ++/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ ++/* This Source Code Form is subject to the terms of the Mozilla Public ++ * License, v. 2.0. If a copy of the MPL was not distributed with this ++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ ++ ++#ifndef nsINativeMenuService_h_ ++#define nsINativeMenuService_h_ ++ ++#include "nsISupports.h" ++ ++class nsIWidget; ++class nsIContent; ++namespace mozilla { ++namespace dom { ++class Element; ++} ++} // namespace mozilla ++ ++// {90DF88F9-F084-4EF3-829A-49496E636DED} ++#define NS_INATIVEMENUSERVICE_IID \ ++ { \ ++ 0x90DF88F9, 0xF084, 0x4EF3, { \ ++ 0x82, 0x9A, 0x49, 0x49, 0x6E, 0x63, 0x6D, 0xED \ ++ } \ ++ } ++ ++class nsINativeMenuService : public nsISupports { ++ public: ++ NS_DECLARE_STATIC_IID_ACCESSOR(NS_INATIVEMENUSERVICE_IID) ++ // Given a top-level window widget and a menu bar DOM node, sets up native ++ // menus. Once created, native menus are controlled via the DOM, including ++ // destruction. ++ NS_IMETHOD CreateNativeMenuBar(nsIWidget* aParent, ++ mozilla::dom::Element* aMenuBarNode) = 0; ++}; ++ ++NS_DEFINE_STATIC_IID_ACCESSOR(nsINativeMenuService, NS_INATIVEMENUSERVICE_IID) ++ ++#endif // nsINativeMenuService_h_ +diff --git a/widget/nsWidgetsCID.h b/widget/nsWidgetsCID.h +index 8e0f67661414..911711e88a0e 100644 +--- a/widget/nsWidgetsCID.h ++++ b/widget/nsWidgetsCID.h +@@ -66,6 +66,14 @@ + // Menus + //----------------------------------------------------------- + ++// {0B3FE5AA-BC72-4303-85AE-76365DF1251D} ++#define NS_NATIVEMENUSERVICE_CID \ ++ { \ ++ 0x0B3FE5AA, 0xBC72, 0x4303, { \ ++ 0x85, 0xAE, 0x76, 0x36, 0x5D, 0xF1, 0x25, 0x1D \ ++ } \ ++ } ++ + // {F6CD4F21-53AF-11d2-8DC4-00609703C14E} + #define NS_POPUPMENU_CID \ + { \ +diff --git a/xpcom/ds/NativeMenuAtoms.py b/xpcom/ds/NativeMenuAtoms.py +new file mode 100644 +index 000000000000..488c8f49c021 +--- /dev/null ++++ b/xpcom/ds/NativeMenuAtoms.py +@@ -0,0 +1,9 @@ ++from Atom import Atom ++ ++NATIVE_MENU_ATOMS = [ ++ Atom("menuitem_with_favicon", "menuitem-with-favicon"), ++ Atom("_moz_menubarkeeplocal", "_moz-menubarkeeplocal"), ++ Atom("_moz_nativemenupopupstate", "_moz-nativemenupopupstate"), ++ Atom("openedwithkey", "openedwithkey"), ++ Atom("shellshowingmenubar", "shellshowingmenubar"), ++] +diff --git a/xpcom/ds/StaticAtoms.py b/xpcom/ds/StaticAtoms.py +index 740b3f6e187d..3ca7630abf25 100644 +--- a/xpcom/ds/StaticAtoms.py ++++ b/xpcom/ds/StaticAtoms.py +@@ -7,6 +7,7 @@ + from Atom import Atom, InheritingAnonBoxAtom, NonInheritingAnonBoxAtom + from Atom import PseudoElementAtom + from HTMLAtoms import HTML_PARSER_ATOMS ++from NativeMenuAtoms import NATIVE_MENU_ATOMS + import sys + + # Static atom definitions, used to generate nsGkAtomList.h. +@@ -2536,7 +2537,7 @@ STATIC_ATOMS = [ + InheritingAnonBoxAtom("AnonBox_mozSVGForeignContent", ":-moz-svg-foreign-content"), + InheritingAnonBoxAtom("AnonBox_mozSVGText", ":-moz-svg-text"), + # END ATOMS +-] + HTML_PARSER_ATOMS ++] + HTML_PARSER_ATOMS + NATIVE_MENU_ATOMS + # fmt: on + + +diff --git a/xpfe/appshell/AppWindow.cpp b/xpfe/appshell/AppWindow.cpp +index fb36837f23da..c6b6aae09ac8 100644 +--- a/xpfe/appshell/AppWindow.cpp ++++ b/xpfe/appshell/AppWindow.cpp +@@ -80,7 +80,7 @@ + + #include "mozilla/dom/DocumentL10n.h" + +-#ifdef XP_MACOSX ++#if defined(XP_MACOSX) || defined(MOZ_WIDGET_GTK) + # include "mozilla/widget/NativeMenuSupport.h" + # define USE_NATIVE_MENUS + #endif +-- +2.37.3 + diff --git a/waterfox-g/debian/patches/libavcodec58_91.patch b/waterfox-g/debian/patches/libavcodec58_91.patch 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 +# 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(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(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', + ] -- cgit