summaryrefslogtreecommitdiff
path: root/waterfox-g
diff options
context:
space:
mode:
authorB. Stack <bgstack15@gmail.com>2023-08-19 17:18:07 -0400
committerB. Stack <bgstack15@gmail.com>2023-08-19 17:18:07 -0400
commitb2237bccba179e5483e083edf5ebbd313d240dfd (patch)
tree19d822c24051e2308c32c04a102547f4e5188679 /waterfox-g
parentuse cgit url (diff)
downloadstackrpms-b2237bccba179e5483e083edf5ebbd313d240dfd.tar.gz
stackrpms-b2237bccba179e5483e083edf5ebbd313d240dfd.tar.bz2
stackrpms-b2237bccba179e5483e083edf5ebbd313d240dfd.zip
add waterfox-g
Diffstat (limited to 'waterfox-g')
-rw-r--r--waterfox-g/README.md32
-rw-r--r--waterfox-g/debian/_constraints14
-rw-r--r--waterfox-g/debian/_service34
-rwxr-xr-xwaterfox-g/debian/add_new_i18n_packages.sh32
-rw-r--r--waterfox-g/debian/bgstack15-librewolf-prefs.js126
-rw-r--r--waterfox-g/debian/changelog241
-rw-r--r--waterfox-g/debian/compat1
-rw-r--r--waterfox-g/debian/control242
-rw-r--r--waterfox-g/debian/copyright8
-rw-r--r--waterfox-g/debian/distribution.ini12
-rw-r--r--waterfox-g/debian/locales.shipped26
-rw-r--r--waterfox-g/debian/mozconfig69
-rw-r--r--waterfox-g/debian/mozconfig_LANG31
-rw-r--r--waterfox-g/debian/patches/fis-csd-global-menu.patch21
-rw-r--r--waterfox-g/debian/patches/fix-langpack-id.patch67
-rw-r--r--waterfox-g/debian/patches/fix-wayland-build.patch19
-rw-r--r--waterfox-g/debian/patches/g-kde.patch1748
-rw-r--r--waterfox-g/debian/patches/global_menu.patch5382
-rw-r--r--waterfox-g/debian/patches/libavcodec58_91.patch16
-rw-r--r--waterfox-g/debian/patches/mach-depends.patch24
-rw-r--r--waterfox-g/debian/patches/mozilla-ntlm-full-path.patch28
-rw-r--r--waterfox-g/debian/patches/nongnome-proxies.patch35
-rw-r--r--waterfox-g/debian/patches/series9
-rw-r--r--waterfox-g/debian/patches/waterfox-branded-icons.patch19
-rwxr-xr-xwaterfox-g/debian/rules69
-rw-r--r--waterfox-g/debian/source/format1
-rw-r--r--waterfox-g/debian/spellcheck.js2
-rw-r--r--waterfox-g/debian/syspref.js3
-rw-r--r--waterfox-g/debian/usr.bin.waterfox-g232
-rw-r--r--waterfox-g/debian/vendor.js29
-rwxr-xr-xwaterfox-g/debian/waterfox-g-bin.sh2
-rw-r--r--waterfox-g/debian/waterfox-g-i18n-ar.install1
-rw-r--r--waterfox-g/debian/waterfox-g-i18n-cs.install1
-rw-r--r--waterfox-g/debian/waterfox-g-i18n-da.install1
-rw-r--r--waterfox-g/debian/waterfox-g-i18n-de.install1
-rw-r--r--waterfox-g/debian/waterfox-g-i18n-el.install1
-rw-r--r--waterfox-g/debian/waterfox-g-i18n-en-gb.install1
-rw-r--r--waterfox-g/debian/waterfox-g-i18n-es-es.install1
-rw-r--r--waterfox-g/debian/waterfox-g-i18n-es-mx.install1
-rw-r--r--waterfox-g/debian/waterfox-g-i18n-fr.install1
-rw-r--r--waterfox-g/debian/waterfox-g-i18n-hu.install1
-rw-r--r--waterfox-g/debian/waterfox-g-i18n-id.install1
-rw-r--r--waterfox-g/debian/waterfox-g-i18n-it.install1
-rw-r--r--waterfox-g/debian/waterfox-g-i18n-ja.install1
-rw-r--r--waterfox-g/debian/waterfox-g-i18n-ko.install1
-rw-r--r--waterfox-g/debian/waterfox-g-i18n-lt.install1
-rw-r--r--waterfox-g/debian/waterfox-g-i18n-nl.install1
-rw-r--r--waterfox-g/debian/waterfox-g-i18n-nn-no.install1
-rw-r--r--waterfox-g/debian/waterfox-g-i18n-pl.install1
-rw-r--r--waterfox-g/debian/waterfox-g-i18n-pt-br.install1
-rw-r--r--waterfox-g/debian/waterfox-g-i18n-pt-pt.install1
-rw-r--r--waterfox-g/debian/waterfox-g-i18n-ru.install1
-rw-r--r--waterfox-g/debian/waterfox-g-i18n-sv-se.install1
-rw-r--r--waterfox-g/debian/waterfox-g-i18n-th.install1
-rw-r--r--waterfox-g/debian/waterfox-g-i18n-vi.install1
-rw-r--r--waterfox-g/debian/waterfox-g-i18n-zh-cn.install1
-rw-r--r--waterfox-g/debian/waterfox-g-i18n-zh-tw.install1
-rwxr-xr-xwaterfox-g/debian/waterfox-g-wayland-bin.sh6
-rw-r--r--waterfox-g/debian/waterfox-g-wayland.desktop347
-rw-r--r--waterfox-g/debian/waterfox-g-wayland.dirs1
-rw-r--r--waterfox-g/debian/waterfox-g-wayland.install1
-rw-r--r--waterfox-g/debian/waterfox-g.1119
-rw-r--r--waterfox-g/debian/waterfox-g.appdata.xml.in36
-rw-r--r--waterfox-g/debian/waterfox-g.desktop347
-rw-r--r--waterfox-g/debian/waterfox-g.dirs10
-rw-r--r--waterfox-g/debian/waterfox-g.dsc16
-rw-r--r--waterfox-g/debian/waterfox-g.install8
-rw-r--r--waterfox-g/debian/waterfox-g.links17
-rw-r--r--waterfox-g/debian/waterfox-g.lintian-overrides3
-rw-r--r--waterfox-g/debian/waterfox-g.manpages1
-rwxr-xr-xwaterfox-g/debian/waterfox-g.postinst113
-rwxr-xr-xwaterfox-g/debian/waterfox-g.postrm62
-rwxr-xr-xwaterfox-g/debian/waterfox-g.preinst88
-rwxr-xr-xwaterfox-g/debian/waterfox-g.prerm12
74 files changed, 9787 insertions, 0 deletions
diff --git a/waterfox-g/README.md b/waterfox-g/README.md
new file mode 100644
index 0000000..5d16cab
--- /dev/null
+++ b/waterfox-g/README.md
@@ -0,0 +1,32 @@
+# Readme for Waterfox G
+Waterfox G is a customizable privacy-conscious web browser with primary support for webextensions.
+
+## Upstream
+<https://www.waterfox.net/>
+Forked from <https://build.opensuse.org/package/show/home:hawkeye116477:waterfox/waterfox-g-kpe>
+
+## Reason for being in stackrpms
+I want a non-KDE focused Waterfox G package for myself. Hawkeye builds his with KDE integrations that are unnecessary for my use case.
+
+## Alternatives
+* Hawkeye116477's package
+
+## Dependencies
+See the build recipe.
+
+## Using
+It is very similar to Firefox or Librewolf.
+
+## Building
+This package is designed to be built on the [Open Build Service](https://build.opensuse.org).
+
+### Credits
+This package, is made possible by the concerted efforts of many people and groups.
+* [Mozilla Firefox](https://www.mozilla.org)
+* [Hawkeye116477](https://build.opensuse.org/project/subprojects/home:hawkeye116477)
+* My work on [waterfox-classic package](https://bgstack15.ddns.net/cgit/stackrpms/tree/waterfox) which was also based on Hawkeye116477's work.
+
+### References
+
+## Differences from upstream
+I strip out KDE things and add my prefs.js file.
diff --git a/waterfox-g/debian/_constraints b/waterfox-g/debian/_constraints
new file mode 100644
index 0000000..32e33ee
--- /dev/null
+++ b/waterfox-g/debian/_constraints
@@ -0,0 +1,14 @@
+<?xml version="1.0"?>
+<constraints>
+ <hardware>
+ <disk>
+ <size unit="G">38</size>
+ </disk>
+ <memory>
+ <size unit="M">8000</size>
+ </memory>
+ <cpu>
+ <flag>sse2</flag>
+ </cpu>
+ </hardware>
+</constraints>
diff --git a/waterfox-g/debian/_service b/waterfox-g/debian/_service
new file mode 100644
index 0000000..3bcea87
--- /dev/null
+++ b/waterfox-g/debian/_service
@@ -0,0 +1,34 @@
+<services>
+ <service name="tar_scm">
+ <param name="scm">git</param>
+ <param name="url">https://bgstack15.ddns.net/cgit/stackrpms</param>
+ <param name="subdir">waterfox-g/debian</param>
+ <param name="filename">debian</param>
+ <param name="revision">waterfox-g-bump</param>
+ <param name="version">_none_</param>
+ </service>
+ <service name="recompress">
+ <param name="file">*.tar</param>
+ <param name="compression">xz</param>
+ </service>
+ <service name="extract_file">
+ <param name="archive">*.tar.xz</param>
+ <param name="files">*/*.dsc</param>
+ </service>
+ <service name="obs_scm">
+ <param name="scm">git</param>
+ <param name="url">https://github.com/WaterfoxCo/Waterfox.git</param>
+ <param name="filename">waterfox-g</param>
+ <param name="versionformat">@PARENT_TAG@</param>
+ <param name="versionrewrite-pattern">G(.*)</param>
+ <param name="revision">G5.1.10</param>
+ </service>
+ <service mode="buildtime" name="tar">
+ <param name="obsinfo">waterfox-g.obsinfo</param>
+ </service>
+ <service mode="buildtime" name="recompress">
+ <param name="file">waterfox-g*.tar</param>
+ <param name="compression">xz</param>
+ </service>
+ <!--<service name="set_version" mode="buildtime" />-->
+</services>
diff --git a/waterfox-g/debian/add_new_i18n_packages.sh b/waterfox-g/debian/add_new_i18n_packages.sh
new file mode 100755
index 0000000..ee7631a
--- /dev/null
+++ b/waterfox-g/debian/add_new_i18n_packages.sh
@@ -0,0 +1,32 @@
+#!/bin/bash
+
+SCRIPT_PATH=$(dirname "$0")
+
+sed -i '/Package: waterfox-g-i18n-/Q' $SCRIPT_PATH/control
+mapfile -t _languages < <(cat $SCRIPT_PATH/locales.shipped)
+
+for _lang in "${_languages[@]}"; do
+ _locale_file=$(echo $_lang | awk -F: '{print $1}')
+ _locale_pkg=$(echo $_lang | awk -F: '{print tolower($1)}')
+ _locale_desc=$(echo $_lang | awk -F: '{print $2}')
+ _pkgname=waterfox-g-i18n-$_locale_pkg
+ _pkgname_trans3=waterfox-g3-i18n-$_locale_pkg
+ _pkgname_trans4=waterfox-g4-i18n-$_locale_pkg
+ cat <<EOT >> $SCRIPT_PATH/control
+Package: $_pkgname
+Architecture: all
+Depends: \${misc:Depends}, waterfox-g (>= \${source:Version})
+Replaces: $_pkgname_trans4 (<< 4.1.1-0~), $_pkgname_trans3 (<< 4.1.1-0~)
+Breaks: $_pkgname_trans4 (<< 4.1.1-0~), $_pkgname_trans3 (<< 4.1.1-0~)
+Description: $_locale_desc language pack for Waterfox G
+ This package contains $_locale_desc translations for Waterfox G
+
+EOT
+touch $SCRIPT_PATH/${_pkgname}.install
+
+ cat <<EOT > $SCRIPT_PATH/${_pkgname}.install
+extensions/langpack-${_locale_file}@l10n.waterfox.net.xpi /usr/lib/waterfox-g/browser/extensions
+EOT
+done
+
+sed -i '$ d' $SCRIPT_PATH/control
diff --git a/waterfox-g/debian/bgstack15-librewolf-prefs.js b/waterfox-g/debian/bgstack15-librewolf-prefs.js
new file mode 100644
index 0000000..53fee23
--- /dev/null
+++ b/waterfox-g/debian/bgstack15-librewolf-prefs.js
@@ -0,0 +1,126 @@
+// file: /usr/lib/librewolf/browser/defaults/preferences/bgstack15-librewolf-prefs.js
+// deployed with librewolf (stackrpms) package (rpm or dpkg) built by bgstack15
+// last modified 2022-07-03
+// reference:
+// https://support.mozilla.org/en-US/kb/customizing-firefox-using-autoconfig
+// vm2 librewolf user profile prefs.js
+pref("browser.allTabs.previews", false);
+pref("browser.backspace_action", 0);
+pref("browser.ctrlTab.previews", false);
+pref("browser.ctrlTab.recentlyUsedOrder", false);
+pref("browser.ctrlTab.migrated", true);
+pref("browser.engagement.ctrlTab.has-used", true);
+pref("browser.uidensity", 1);
+pref("browser.download.useDownloadDir", true);
+pref("browser.newtabpage.activity-stream.migrationExpired", true);
+pref("browser.newtabpage.activity-stream.prerender", false);
+pref("browser.newtabpage.activity-stream.showSearch", false);
+pref("browser.newtabpage.activity-stream.showTopSites", false);
+pref("browser.newtabpage.directory.ping", "http://127.0.0.1:9999/");
+pref("browser.newtabpage.directory.source", "http://127.0.0.1:9999/");
+pref("browser.newtabpage.enabled", false);
+pref("browser.newtabpage.enhanced", false);
+pref("browser.newtab.choice", 1);
+pref("browser.newtabpage.storageVersion", 1);
+pref("browser.newtabpage.directory.ping", "http://127.0.0.1:9999/");
+pref("browser.newtabpage.directory.source", "http://127.0.0.1:9999/");
+pref("browser.search.update", false);
+pref("browser.sessionstore.restore_on_demand", false);
+pref("browser.startup.page", 3);
+pref("browser.tabs.closeWindowWithLastTab", false);
+// These two have to stay undefined in Firefox 77+ in order for the drop-down for autocompletion to still work.
+//pref("browser.urlbar.disableExtendForTests", true);
+//pref("browser.urlbar.maxRichResults", 0);
+pref("browser.urlbar.trimURLs", false);
+pref("browser.urlbar.update1", false);
+pref("browser.xul.error_pages.enabled", false);
+pref("camera.control.face_detection.enabled", false);
+pref("canvas.filters.enabled", false);
+pref("canvas.focusring.enabled", false);
+pref("canvas.path.enabled", false);
+pref("captivedetect.canonicalURL", "http://127.0.0.1:9980");
+pref("devtools.devedition.promo.url", "http://127.0.0.1:9999/");
+pref("dom.event.clipboardevents.enabled", false);
+pref("experiments.manifest.uri", "http://127.0.0.1:9999/");
+pref("extensions.enabledAddons", "%7B972ce4c6-7e08-4474-a285-3208198ce6fd%7D:28.3.0");
+pref("extensions.blocklist.detailsURL", "http://127.0.0.1:9999/");
+pref("extensions.blocklist.itemURL", "http://127.0.0.1:9999/");
+pref("extensions.pocket.api", "localhost:9999");
+pref("extensions.pocket.site", "localhost:9999");
+pref("extensions.shownSelectionUI", true);
+pref("extensions.update.autoUpdateDefault", false);
+pref("general.warnOnAboutConfig", false);
+pref("media.videocontrols.picture-in-picture.allow-multiple", false);
+pref("media.videocontrols.picture-in-picture.enabled", false);
+pref("media.videocontrols.picture-in-picture.video-toggle.has-used", true);
+pref("network.automatic-ntlm-auth.trusted-uris", ".ipa.smith122.com");
+pref("network.cookie.prefsMigrated", true);
+pref("network.http.spdy.enabled", false);
+pref("network.negotiate-auth.trusted-uris", ".ipa.smith122.com");
+pref("network.stricttransportsecurity.preloadlist", false);
+pref("privacy.sanitize.migrateFx3Prefs", true);
+pref("pref.privacy.disable_button.cookie_exceptions", false);
+pref("pref.privacy.disable_button.view_passwords", false);
+pref("privacy.annotate_channels.strict_list.enabled", true);
+pref("privacy.donottrackheader.enabled", true);
+pref("privacy.partition.network_state.ocsp_cache", true);
+pref("privacy.purge_trackers.date_in_cookie_database", "0");
+pref("privacy.purge_trackers.last_purge", "1641399136538");
+pref("privacy.resistFingerprinting", false);
+pref("privacy.resistFingerprinting.autoDeclineNoUserInputCanvasPrompts", false);
+pref("privacy.sanitize.pending", "[{\"id\":\"newtab-container\",\"itemsToClear\":[],\"options\":{}}]");
+pref("privacy.sanitize.sanitizeOnShutdown", false);
+pref("privacy.trackingprotection.enabled", true);
+pref("privacy.trackingprotection.socialtracking.enabled", true);
+pref("reader.parse-on-load.enabled", false);
+pref("security.cert_pinning.enforcement_level", 0);
+pref("services.sync.declinedEngines", "");
+pref("services.sync.serverURL", "http://127.0.0.1:9999/");
+pref("services.sync.tabs.lastSync", "0");
+pref("services.sync.tabs.lastSyncLocal", "0");
+pref("signon.autofillForms", true);
+pref("signon.importedFromSqlite", true);
+pref("signon.rememberSignons", true);
+pref("startup.homepage_welcome_url", "http://127.0.0.1:9999/");
+pref("browser.startup.homepage", "https://start.duckduckgo.com/");
+pref("startup.homepage_override_url", "");
+pref("toolkit.telemetry.reportingpolicy.firstRun", false);
+pref("xpinstall.whitelist.add", "");
+// Control DNS over HTTPS (DoH) and Trusted Recursive Resolver (TRR).
+// More about DoH: https://github.com/bambenek/block-doh
+// https://blog.nightly.mozilla.org/2018/06/01/improving-dns-privacy-in-firefox/
+// https://support.mozilla.org/en-US/kb/configuring-networks-disable-dns-over-https
+// https://wiki.mozilla.org/Trusted_Recursive_Resolver
+// 0: Off by default, 1: Firefox chooses faster, 2: TRR default w/DNS fallback,
+// 3: TRR only mode, 4: Use DNS and shadow TRR for timings, 5: Disabled.
+pref("network.trr.mode", 0);
+pref("extensions.pocket.enabled", false);
+pref("extensions.pocket.api", "http://localhost:9980");
+pref("extensions.pocket.site", "http://localhost:9980");
+// show menu
+pref("ui.key.menuAccessKeyFocuses", false);
+pref("browser.uiCustomization.state", "{\"placements\":{\"widget-overflow-fixed-list\":[],\"nav-bar\":[\"back-button\",\"forward-button\",\"stop-reload-button\",\"urlbar-container\",\"save-to-pocket-button\",\"downloads-button\",\"fxa-toolbar-menu-button\",\"ublock0_raymondhill_net-browser-action\"],\"toolbar-menubar\":[\"menubar-items\"],\"TabsToolbar\":[\"tabbrowser-tabs\",\"new-tab-button\",\"alltabs-button\"],\"PersonalToolbar\":[\"personal-bookmarks\"]},\"seen\":[\"developer-button\",\"ublock0_raymondhill_net-browser-action\"],\"dirtyAreaCache\":[\"nav-bar\",\"PersonalToolbar\",\"toolbar-menubar\",\"TabsToolbar\"],\"currentVersion\":17,\"newElementCount\":3}");
+pref("bgstack15-librewolf-prefs.js.version", "20220108.140650");
+pref("browser.startup.page", 3);
+pref("privacy.userContext.enabled", false);
+pref("browser.download.useDownloadDir", true);
+pref("browser.download.improvements_to_download_panel", false);
+pref("media.videocontrols.picture-in-picture.video-toggle.enabled", false);
+pref("browser.urlbar.placeholderName", "DuckDuckGo");
+pref("browser.urlbar.placeholderName.private", "DuckDuckGo");
+pref("browser.formfill.enable", true);
+pref("dom.security.https_only_mode", false);
+pref("network.cookie.lifetimePolicy", 0);
+pref("places.history.enabled", true);
+pref("privacy.sanitize.pending", "[{\"id\":\"newtab-container\",\"itemsToClear\":[],\"options\":{}},{\"id\":\"newtab-container\",\"itemsToClear\":[],\"options\":{}}]");
+pref("privacy.sanitize.sanitizeOnShutdown", false);
+pref("signon.autofillForms", true);
+pref("signon.rememberSignons", true);
+pref("browser.toolbars.bookmarks.visibility", "always");
+pref("browser.compactmode.show", true);
+librewolf("extensions.webextensions.ExtensionStorageIDB.migrated.uBlock0@raymondhill.net", true);
+pref("extensions.webextensions.uuids", "{\"formautofill@mozilla.org\":\"932d9ca1-4ab4-4176-908a-775a8c5c232b\",\"pictureinpicture@mozilla.org\":\"d20931db-dda1-4902-85fd-c324857cc611\",\"proxy-failover@mozilla.com\":\"9511d4e1-5bce-4214-90ba-cf9004a1896a\",\"screenshots@mozilla.org\":\"74a504ec-3b68-40db-b8de-213e87ec1f0d\",\"default-theme@mozilla.org\":\"e2607c67-dc48-4ce0-99c9-9422500c5397\",\"addons-search-detection@mozilla.com\":\"00ce34b6-f943-4060-970e-7ab093b1263f\",\"wikipedia@search.mozilla.org\":\"fd8e14ab-a33d-4393-8023-6a8a34382417\",\"ddg@search.mozilla.org\":\"e810f6e1-24e9-4e11-9a36-3be0691aef77\",\"uBlock0@raymondhill.net\":\"44aaf03a-52f1-402c-9300-d41904ab3746\"}");
+pref("widget.gtk.overlay-scrollbars.enabled", false);
+pref("widget.non-native-theme.gtk.scrollbar.allow-buttons", true);
+pref("widget.non-native-theme.scrollbar.size.override", 18);
+pref("widget.non-native-theme.scrollbar.style", 2);
diff --git a/waterfox-g/debian/changelog b/waterfox-g/debian/changelog
new file mode 100644
index 0000000..192982b
--- /dev/null
+++ b/waterfox-g/debian/changelog
@@ -0,0 +1,241 @@
+waterfox-g (5.1.10-1+stackrpms) obs; urgency=medium
+
+ * Fork to remove KDE bits
+
+ -- B. Stack <bgstack15@gmail.com> Sun, 06 Aug 2023 11:41:53 -0400
+
+waterfox-g-kpe (5.1.10-0) obs; urgency=medium
+
+ * Added various security fixes.
+
+ -- hawkeye116477 <hawkeye116477@gmail.com> Tue, 01 Aug 2023 21:05:03 +0200
+
+waterfox-g-kpe (5.1.9-0) obs; urgency=medium
+
+ * Added various security fixes.
+
+ -- hawkeye116477 <hawkeye116477@gmail.com> Thu, 06 Jul 2023 21:28:18 +0200
+
+waterfox-g-kpe (5.1.8-0) obs; urgency=medium
+
+ * Added various security fixes.
+
+ -- hawkeye116477 <hawkeye116477@gmail.com> Wed, 07 Jun 2023 18:32:46 +0200
+
+waterfox-g-kpe (5.1.6-0) obs; urgency=medium
+
+ * Added various security fixes.
+
+ -- hawkeye116477 <hawkeye116477@gmail.com> Thu, 11 May 2023 12:46:00 +0200
+
+waterfox-g-kpe (5.1.5-0) obs; urgency=medium
+
+ * Added various security fixes.
+
+ -- hawkeye116477 <hawkeye116477@gmail.com> Sat, 15 Apr 2023 23:46:00 +0200
+
+waterfox-g-kpe (5.1.4-0) obs; urgency=medium
+
+ * Added various security fixes.
+
+ -- hawkeye116477 <hawkeye116477@gmail.com> Tue, 28 Mar 2023 14:07:17 +0200
+
+waterfox-g-kpe (5.1.3-0) obs; urgency=medium
+
+ * Added various security fixes.
+
+ -- hawkeye116477 <hawkeye116477@gmail.com> Sat, 25 Feb 2023 13:56:05 +0100
+
+waterfox-g-kpe (5.1.2-0) obs; urgency=medium
+
+ * JPEG-XL is now enabled by default.
+ * Fixed various theme issues.
+ * Added various security fixes.
+
+ -- hawkeye116477 <hawkeye116477@gmail.com> Sun, 22 Jan 2023 18:28:26 +0100
+
+waterfox-g-kpe (5.1.1-0) obs; urgency=medium
+
+ * Issue playing Netflix and similar DRM protected websites have now resolved. If it's not working straight away, you can update the DRM plugin by going to the Preferences page → General → Files and Applications and un-check "Play DRM-controlled content". Wait for a few seconds, then re-check the box. The DRM plugin should update.
+ * Drag space is now disabled by default (that extra space above your tabs, so you can drag the window around).
+ * The status bar colour now matches the navigation bar.
+ * Various security fixes
+
+ -- hawkeye116477 <hawkeye116477@gmail.com> Mon, 19 Dec 2022 18:40:41 +0100
+
+waterfox-g-kpe (5.1-0) obs; urgency=medium
+
+ * You can modify Waterfox's look and feel by heading to about:preferences → Look & Feel. You can set various default presets, change the way tabs behave and much more!
+ * Desktop counterparts to your WebExtensions will now be able to communicate with Waterfox once again!
+ * Various security fixes.
+
+ -- hawkeye116477 <hawkeye116477@gmail.com> Tue, 15 Nov 2022 16:54:44 +0100
+
+waterfox-g-kpe (5.0.2-0) obs; urgency=medium
+
+ * The option to "Open All Bookmarks" once again works.
+ * DNS-over-HTTPS being forced on users will no longer happen by default.
+ * Forms will no longer save data in Private Tab.
+ * Disabling images now works properly.
+ * Added various security fixes.
+
+ -- hawkeye116477 <hawkeye116477@gmail.com> Fri, 21 Oct 2022 13:37:16 +0200
+
+waterfox-g-kpe (5.0.1-0) obs; urgency=medium
+
+ * There was an issue when upgrading from G3 onward where your theme would get reset to default. This is no longer the case.
+ * Various preferences were incorrectly configured and these have now been changed.
+
+ -- hawkeye116477 <hawkeye116477@gmail.com> Wed, 05 Oct 2022 15:58:46 +0200
+
+waterfox-g-kpe (5.0-0) obs; urgency=medium
+
+ * You can now toggle to view password you are typing in.
+ * You can now Sync your settings.
+ * The search input type is now enabled.
+ * Updated Gecko to v102 ESR.
+
+ -- hawkeye116477 <hawkeye116477@gmail.com> Sun, 02 Oct 2022 21:36:28 +0200
+
+waterfox-g-kpe (4.1.5-0) obs; urgency=medium
+
+ * Various stability, functionality, and security fixes.
+
+ -- hawkeye116477 <hawkeye116477@gmail.com> Fri, 26 Aug 2022 23:24:31 +0200
+
+waterfox-g-kpe (4.1.4-0) obs; urgency=medium
+
+ * Various stability, functionality, and security fixes.
+
+ -- hawkeye116477 <hawkeye116477@gmail.com> Sat, 13 Aug 2022 18:32:09 +0200
+
+waterfox-g-kpe (4.1.3.2-0) obs; urgency=medium
+
+ * Waterfox now identifies as Firefox for better website compatibility. This also fixes issue such as being unable to log in to Sync.
+
+ -- hawkeye116477 <hawkeye116477@gmail.com> Thu, 14 Jul 2022 15:04:50 +0200
+
+waterfox-g-kpe (4.1.3.1-0) obs; urgency=medium
+
+ * Various stability, functionality, and security fixes.
+
+ -- hawkeye116477 <hawkeye116477@gmail.com> Fri, 24 Jun 2022 22:47:00 +0200
+
+waterfox-g-kpe (4.1.2.1-0) obs; urgency=medium
+
+ * Various stability, functionality, and security fixes
+
+ -- hawkeye116477 <hawkeye116477@gmail.com> Wed, 01 Jun 2022 18:52:17 +0200
+
+waterfox-g-kpe (4.1.2-0) obs; urgency=medium
+
+ * Various stability, functionality, and security fixes.
+
+ -- hawkeye116477 <hawkeye116477@gmail.com> Tue, 03 May 2022 17:06:37 +0200
+
+waterfox-g-kpe (4.1.1.1-0) obs; urgency=medium
+
+ * Fixed an issue where container tabs were not being restored properly.
+
+ -- hawkeye116477 <hawkeye116477@gmail.com> Wed, 27 Apr 2022 13:12:51 +0200
+
+waterfox-g4-kpe (1.1-0) obs; urgency=medium
+
+ * Users who have DuckDuckGo as their default search should now have it as their default Private search as well.
+ * Various links in the About Waterfox dialogue have now been updated.
+ * A small cosmetic UI fix.
+
+ -- hawkeye116477 <hawkeye116477@gmail.com> Tue, 12 Apr 2022 18:53:17 +0200
+
+waterfox-g4-kpe (1.0-0) obs; urgency=medium
+
+ * Addon version numbers once more show next to their names in about:addons
+ * Startpage is now the default search when performance searches in Private Tab and Private Window.
+ * Tabs will now unload automatically when your system is running low on memory.
+ * Tabs can now be manually unloaded after enabling the preference in about:preferences → General → Tabs. You can then right click on a tab and unload it.
+ * Chrome Web Store extensions should now install properly once again.
+ * Back and forward buttons on various mice now work properly in Waterfox, instead of acting as middle-click.
+ * Sometimes non-private tabs would be restored as private tabs. This will no longer occur.
+ * Various fixes to the default Lepton theme.
+ * Various Japanese translation improvements by @surapunoyousei
+ * Unprefixed -moz-fit-content. -fit-content show now work without the -moz prefix.
+ * The <dialog> HTML element is now enabled.
+ * References to his and her have been replaced with their where relevant.
+
+ -- hawkeye116477 <hawkeye116477@gmail.com> Sat, 09 Apr 2022 16:14:14 +0200
+
+waterfox-g4-kpe (0.8-0) obs; urgency=medium
+
+ * Added a toggle on about:preferences to force light/dark mode with Lepton theme.
+ * Private tab will now correctly not store search suggestions and recently closed tabs.
+ * Private tab icon will now only appear in Lepton theme, so should no longer appear on themes that do not have context menu icons.
+ * Forcing dark mode in Lepton with ui.systemUsesDarkTheme on Linux distros should now work.
+
+ -- hawkeye116477 <hawkeye116477@gmail.com> Mon, 07 Mar 2022 13:42:22 +0100
+
+waterfox-g4-kpe (0.7-0) obs; urgency=medium
+
+ * The default theme Lepton has been updated, fixing various issues.
+ * The status bar is now visible when in Customise Mode.
+ * Updated Gecko to 91.6.
+
+ -- hawkeye116477 <hawkeye116477@gmail.com> Fri, 11 Feb 2022 13:53:09 +0100
+
+waterfox-g4-kpe (0.6-0) obs; urgency=medium
+
+ * Added default items to the status bar when enabled.
+ * You can now toggle text contrast on the status bar in about:preferences → General → Language and Appearance → Status Bar.
+ * Importing data from other browsers now works once again. (`File → Import From Another Browser)
+
+ -- hawkeye116477 <hawkeye116477@gmail.com> Wed, 12 Jan 2022 15:43:30 +0100
+
+waterfox-g4-kpe (0.5.1-0) obs; urgency=medium
+
+ * Tabs toolbar and status bar will now be appropriately coloured when tabs toolbar set to bottom display with a dark theme.
+ * Tabs toolbar set to bottom display will now collapse correctly when entering DOM fullscreen mode (e.g. fullscreen video).
+ * Widget text in the status bar will be coloured correctly with dark themes.
+
+ -- hawkeye116477 <hawkeye116477@gmail.com> Tue, 21 Dec 2021 17:23:26 +0100
+
+waterfox-g4-kpe (0.5-0) obs; urgency=medium
+
+ * Added an option to about:preferences to prevent pinned tabs shrinking to icon size.
+ * Added options to about:preferences to allow new tabs to be opened after all existing tabs.
+ * Google search suggestions should now display again.
+ * bloomberg.com should no longer display an out of date browser warning.
+ * White bar no longer visible at the bottom of the browser window when using light themes.
+ * More AVIF image types are now supported, which fixes issues of images not rendering on websites such as The Daily Mail.
+ * The default Lepton theme has been upgraded to its latest version, with improved visual changes.
+ * The Firefox themes are now more identifiable by name.
+ * The Firefox Modern Dynamic theme has now also been added.
+
+ -- hawkeye116477 <hawkeye116477@gmail.com> Fri, 17 Dec 2021 18:15:07 +0100
+
+waterfox-g4-kpe (0.4-0) obs; urgency=medium
+
+ * Widevine has been updated to 4.10.2391.0 which should fix issues playing DRM protected content.
+ * Contrast has increased on Australis Light theme, to better differentiate tabs.
+ * The way tab positioning and various other Waterfox features have been implemented has been improved.
+ * Updated to Gecko 91.4.0, which includes various security patches.
+
+ -- hawkeye116477 <hawkeye116477@gmail.com> Wed, 08 Dec 2021 17:40:01 +0100
+
+waterfox-g4-kpe (0.3.1-0) obs; urgency=medium
+
+ * Plugins such as Flash can now be enabled, but require you to interact to have web pages run it for security reasons.
+ * The address bar now makes it more obvious when a website is secure or insecure.
+ * Bootstraped extensions will now load on restart.
+ * Fixed various bookmarks bar and status bar issues.
+ * Issues installing bootstrapped extensions have now been resolved.
+ * Copy tab URL should now no longer throw an error.
+
+ -- hawkeye116477 <hawkeye116477@gmail.com> Mon, 29 Nov 2021 15:08:10 +0100
+
+waterfox-g4-kpe (0.2.1-0) obs; urgency=medium
+
+ * Based on Gecko 91
+ * "Lepton" theme based on an improved Photon theme.
+ * You can now import specific Gecko-based profiles (Firefox, Waterfox etc.) from File > Import From Another Browswer
+ * Limited as many outgoing connections as possible to keep the browser operational
+
+ -- hawkeye116477 <hawkeye116477@gmail.com> Fri, 12 Nov 2021 22:15:00 +0100
diff --git a/waterfox-g/debian/compat b/waterfox-g/debian/compat
new file mode 100644
index 0000000..ec63514
--- /dev/null
+++ b/waterfox-g/debian/compat
@@ -0,0 +1 @@
+9
diff --git a/waterfox-g/debian/control b/waterfox-g/debian/control
new file mode 100644
index 0000000..819860e
--- /dev/null
+++ b/waterfox-g/debian/control
@@ -0,0 +1,242 @@
+Source: waterfox-g
+Section: web
+Priority: optional
+Maintainer: B. Stack <bgstack15@gmail.com>
+XSBC-Original-Maintainer: hawkeye116477 <hawkeye116477@gmail.com>
+Build-Depends: debhelper (>= 9), autoconf2.13, libgtk-3-dev (>= 3.14), libdbus-glib-1-dev, libpulse-dev, libasound2-dev, yasm (>= 1.1), build-essential, libxt-dev, python3 (>= 3.5), zip, unzip, cargo (>= 0.59), libgl1-mesa-dev, libnotify-dev (>= 0.4), binutils-avr, libfreetype6-dev (>= 2.0.1), libfontconfig1-dev, pkg-config, libtinfo-dev | libncurses-dev, clang (>= 5.0) | clang-10 | clang-11 | clang-12 | clang-13, llvm-dev (>= 5.0) | llvm-10-dev | llvm-11-dev | llvm-12-dev | llvm-13-dev, lld (>= 5.0) | lld-10 | lld-11 | lld-12 | lld-13, rustc (>= 1.59.0~), libxext-dev, libglib2.0-dev (>= 2.18), libpango1.0-dev (>= 1.14.0), libstartup-notification0-dev, libcurl4-openssl-dev, libiw-dev, mesa-common-dev, libxrender-dev, dbus-x11, xvfb, libx11-dev, libx11-xcb-dev, libfile-fcntllock-perl, apt-utils, locales, autotools-dev, libjpeg-dev, zlib1g-dev, libreadline-dev, dpkg-dev (>= 1.16.1.1~), libevent-dev (>= 1.4.1), libjsoncpp-dev, xfonts-base, xauth, lsb-release, cbindgen (>= 0.24.2), nodejs (>= 10.24.1) | nodejs-mozilla (>= 10.24.1), libjack-dev, nasm (>= 2.14) | nasm-mozilla (>= 2.14), libclang-dev (>= 5.0) | libclang-10-dev | libclang-11-dev | libclang-12-dev | libclang-13-dev, libstdc++6 (>= 7.0) | gcc-mozilla (>= 7), bc
+Build-Conflicts: graphicsmagick-imagemagick-compat,
+ liboss4-salsa-dev,
+ libhildonmime-dev,
+ libosso-dev
+Standards-Version: 3.9.7
+Homepage: https://www.waterfox.net/
+
+Package: waterfox-g
+Architecture: any
+Depends: ${shlibs:Depends}, ${misc:Depends}
+Replaces: waterfox-g4 (<< 4.1.1-0~), waterfox-g3 (<< 4.1.1-0~)
+Breaks: waterfox-g4 (<< 4.1.1-0~), waterfox-g3 (<< 4.1.1-0~)
+Suggests: fonts-lyx, pulseaudio, waterfox-g-wayland, ffmpeg
+Provides: www-browser, gnome-www-browser, waterfox-g
+Description: Customizable privacy-conscious web browser with primary support for webextensions
+ Waterfox allows you to make the choices within your browser, while providing sane defaults to make sure things run smoothly for the average user. Waterfox supports extensions from the Chrome Web Store, Firefox add-on store and the Opera Extensions store.
+ .
+ Waterfox is powered by Mozilla Firefox source code.
+ .
+ NOTE 1: Language packs are available as separate packages to install from apt repository!
+
+Package: waterfox-g-wayland
+Architecture: all
+Depends: ${shlibs:Depends}, ${misc:Depends}, waterfox-g
+Replaces: waterfox-g4-wayland (<< 4.1.1-0~), waterfox-g3-wayland (<< 4.1.1-0~)
+Breaks: waterfox-g4-wayland (<< 4.1.1-0~), waterfox-g3-wayland (<< 4.1.1-0~)
+Description: Waterfox Third Generation Wayland launcher
+ This package contains launcher and desktop file to run Waterfox Third Generation natively on Wayland.
+
+Package: waterfox-g-i18n-ar
+Architecture: all
+Depends: ${misc:Depends}, waterfox-g (>= ${source:Version})
+Replaces: waterfox-g4-i18n-ar (<< 4.1.1-0~), waterfox-g3-i18n-ar (<< 4.1.1-0~)
+Breaks: waterfox-g4-i18n-ar (<< 4.1.1-0~), waterfox-g3-i18n-ar (<< 4.1.1-0~)
+Description: Arabic language pack for Waterfox G
+ This package contains Arabic translations for Waterfox G
+
+Package: waterfox-g-i18n-cs
+Architecture: all
+Depends: ${misc:Depends}, waterfox-g (>= ${source:Version})
+Replaces: waterfox-g4-i18n-cs (<< 4.1.1-0~), waterfox-g3-i18n-cs (<< 4.1.1-0~)
+Breaks: waterfox-g4-i18n-cs (<< 4.1.1-0~), waterfox-g3-i18n-cs (<< 4.1.1-0~)
+Description: Czech language pack for Waterfox G
+ This package contains Czech translations for Waterfox G
+
+Package: waterfox-g-i18n-da
+Architecture: all
+Depends: ${misc:Depends}, waterfox-g (>= ${source:Version})
+Replaces: waterfox-g4-i18n-da (<< 4.1.1-0~), waterfox-g3-i18n-da (<< 4.1.1-0~)
+Breaks: waterfox-g4-i18n-da (<< 4.1.1-0~), waterfox-g3-i18n-da (<< 4.1.1-0~)
+Description: Danish language pack for Waterfox G
+ This package contains Danish translations for Waterfox G
+
+Package: waterfox-g-i18n-de
+Architecture: all
+Depends: ${misc:Depends}, waterfox-g (>= ${source:Version})
+Replaces: waterfox-g4-i18n-de (<< 4.1.1-0~), waterfox-g3-i18n-de (<< 4.1.1-0~)
+Breaks: waterfox-g4-i18n-de (<< 4.1.1-0~), waterfox-g3-i18n-de (<< 4.1.1-0~)
+Description: German language pack for Waterfox G
+ This package contains German translations for Waterfox G
+
+Package: waterfox-g-i18n-el
+Architecture: all
+Depends: ${misc:Depends}, waterfox-g (>= ${source:Version})
+Replaces: waterfox-g4-i18n-el (<< 4.1.1-0~), waterfox-g3-i18n-el (<< 4.1.1-0~)
+Breaks: waterfox-g4-i18n-el (<< 4.1.1-0~), waterfox-g3-i18n-el (<< 4.1.1-0~)
+Description: Greek language pack for Waterfox G
+ This package contains Greek translations for Waterfox G
+
+Package: waterfox-g-i18n-en-gb
+Architecture: all
+Depends: ${misc:Depends}, waterfox-g (>= ${source:Version})
+Replaces: waterfox-g4-i18n-en-gb (<< 4.1.1-0~), waterfox-g3-i18n-en-gb (<< 4.1.1-0~)
+Breaks: waterfox-g4-i18n-en-gb (<< 4.1.1-0~), waterfox-g3-i18n-en-gb (<< 4.1.1-0~)
+Description: English (British) language pack for Waterfox G
+ This package contains English (British) translations for Waterfox G
+
+Package: waterfox-g-i18n-es-es
+Architecture: all
+Depends: ${misc:Depends}, waterfox-g (>= ${source:Version})
+Replaces: waterfox-g4-i18n-es-es (<< 4.1.1-0~), waterfox-g3-i18n-es-es (<< 4.1.1-0~)
+Breaks: waterfox-g4-i18n-es-es (<< 4.1.1-0~), waterfox-g3-i18n-es-es (<< 4.1.1-0~)
+Description: Spanish (Spain) language pack for Waterfox G
+ This package contains Spanish (Spain) translations for Waterfox G
+
+Package: waterfox-g-i18n-es-mx
+Architecture: all
+Depends: ${misc:Depends}, waterfox-g (>= ${source:Version})
+Replaces: waterfox-g4-i18n-es-mx (<< 4.1.1-0~), waterfox-g3-i18n-es-mx (<< 4.1.1-0~)
+Breaks: waterfox-g4-i18n-es-mx (<< 4.1.1-0~), waterfox-g3-i18n-es-mx (<< 4.1.1-0~)
+Description: Spanish (Mexico) language pack for Waterfox G
+ This package contains Spanish (Mexico) translations for Waterfox G
+
+Package: waterfox-g-i18n-fr
+Architecture: all
+Depends: ${misc:Depends}, waterfox-g (>= ${source:Version})
+Replaces: waterfox-g4-i18n-fr (<< 4.1.1-0~), waterfox-g3-i18n-fr (<< 4.1.1-0~)
+Breaks: waterfox-g4-i18n-fr (<< 4.1.1-0~), waterfox-g3-i18n-fr (<< 4.1.1-0~)
+Description: French language pack for Waterfox G
+ This package contains French translations for Waterfox G
+
+Package: waterfox-g-i18n-hu
+Architecture: all
+Depends: ${misc:Depends}, waterfox-g (>= ${source:Version})
+Replaces: waterfox-g4-i18n-hu (<< 4.1.1-0~), waterfox-g3-i18n-hu (<< 4.1.1-0~)
+Breaks: waterfox-g4-i18n-hu (<< 4.1.1-0~), waterfox-g3-i18n-hu (<< 4.1.1-0~)
+Description: Hungarian language pack for Waterfox G
+ This package contains Hungarian translations for Waterfox G
+
+Package: waterfox-g-i18n-id
+Architecture: all
+Depends: ${misc:Depends}, waterfox-g (>= ${source:Version})
+Replaces: waterfox-g4-i18n-id (<< 4.1.1-0~), waterfox-g3-i18n-id (<< 4.1.1-0~)
+Breaks: waterfox-g4-i18n-id (<< 4.1.1-0~), waterfox-g3-i18n-id (<< 4.1.1-0~)
+Description: Indonesian language pack for Waterfox G
+ This package contains Indonesian translations for Waterfox G
+
+Package: waterfox-g-i18n-it
+Architecture: all
+Depends: ${misc:Depends}, waterfox-g (>= ${source:Version})
+Replaces: waterfox-g4-i18n-it (<< 4.1.1-0~), waterfox-g3-i18n-it (<< 4.1.1-0~)
+Breaks: waterfox-g4-i18n-it (<< 4.1.1-0~), waterfox-g3-i18n-it (<< 4.1.1-0~)
+Description: Italian language pack for Waterfox G
+ This package contains Italian translations for Waterfox G
+
+Package: waterfox-g-i18n-ja
+Architecture: all
+Depends: ${misc:Depends}, waterfox-g (>= ${source:Version})
+Replaces: waterfox-g4-i18n-ja (<< 4.1.1-0~), waterfox-g3-i18n-ja (<< 4.1.1-0~)
+Breaks: waterfox-g4-i18n-ja (<< 4.1.1-0~), waterfox-g3-i18n-ja (<< 4.1.1-0~)
+Description: Japanese language pack for Waterfox G
+ This package contains Japanese translations for Waterfox G
+
+Package: waterfox-g-i18n-ko
+Architecture: all
+Depends: ${misc:Depends}, waterfox-g (>= ${source:Version})
+Replaces: waterfox-g4-i18n-ko (<< 4.1.1-0~), waterfox-g3-i18n-ko (<< 4.1.1-0~)
+Breaks: waterfox-g4-i18n-ko (<< 4.1.1-0~), waterfox-g3-i18n-ko (<< 4.1.1-0~)
+Description: Korean language pack for Waterfox G
+ This package contains Korean translations for Waterfox G
+
+Package: waterfox-g-i18n-lt
+Architecture: all
+Depends: ${misc:Depends}, waterfox-g (>= ${source:Version})
+Replaces: waterfox-g4-i18n-lt (<< 4.1.1-0~), waterfox-g3-i18n-lt (<< 4.1.1-0~)
+Breaks: waterfox-g4-i18n-lt (<< 4.1.1-0~), waterfox-g3-i18n-lt (<< 4.1.1-0~)
+Description: Lithuanian language pack for Waterfox G
+ This package contains Lithuanian translations for Waterfox G
+
+Package: waterfox-g-i18n-nl
+Architecture: all
+Depends: ${misc:Depends}, waterfox-g (>= ${source:Version})
+Replaces: waterfox-g4-i18n-nl (<< 4.1.1-0~), waterfox-g3-i18n-nl (<< 4.1.1-0~)
+Breaks: waterfox-g4-i18n-nl (<< 4.1.1-0~), waterfox-g3-i18n-nl (<< 4.1.1-0~)
+Description: Dutch; Flemish language pack for Waterfox G
+ This package contains Dutch; Flemish translations for Waterfox G
+
+Package: waterfox-g-i18n-nn-no
+Architecture: all
+Depends: ${misc:Depends}, waterfox-g (>= ${source:Version})
+Replaces: waterfox-g4-i18n-nn-no (<< 4.1.1-0~), waterfox-g3-i18n-nn-no (<< 4.1.1-0~)
+Breaks: waterfox-g4-i18n-nn-no (<< 4.1.1-0~), waterfox-g3-i18n-nn-no (<< 4.1.1-0~)
+Description: Norwegian (Nynorsk) language pack for Waterfox G
+ This package contains Norwegian (Nynorsk) translations for Waterfox G
+
+Package: waterfox-g-i18n-pl
+Architecture: all
+Depends: ${misc:Depends}, waterfox-g (>= ${source:Version})
+Replaces: waterfox-g4-i18n-pl (<< 4.1.1-0~), waterfox-g3-i18n-pl (<< 4.1.1-0~)
+Breaks: waterfox-g4-i18n-pl (<< 4.1.1-0~), waterfox-g3-i18n-pl (<< 4.1.1-0~)
+Description: Polish language pack for Waterfox G
+ This package contains Polish translations for Waterfox G
+
+Package: waterfox-g-i18n-pt-br
+Architecture: all
+Depends: ${misc:Depends}, waterfox-g (>= ${source:Version})
+Replaces: waterfox-g4-i18n-pt-br (<< 4.1.1-0~), waterfox-g3-i18n-pt-br (<< 4.1.1-0~)
+Breaks: waterfox-g4-i18n-pt-br (<< 4.1.1-0~), waterfox-g3-i18n-pt-br (<< 4.1.1-0~)
+Description: Portuguese (Brazilian) language pack for Waterfox G
+ This package contains Portuguese (Brazilian) translations for Waterfox G
+
+Package: waterfox-g-i18n-pt-pt
+Architecture: all
+Depends: ${misc:Depends}, waterfox-g (>= ${source:Version})
+Replaces: waterfox-g4-i18n-pt-pt (<< 4.1.1-0~), waterfox-g3-i18n-pt-pt (<< 4.1.1-0~)
+Breaks: waterfox-g4-i18n-pt-pt (<< 4.1.1-0~), waterfox-g3-i18n-pt-pt (<< 4.1.1-0~)
+Description: Portuguese (Portugal) language pack for Waterfox G
+ This package contains Portuguese (Portugal) translations for Waterfox G
+
+Package: waterfox-g-i18n-ru
+Architecture: all
+Depends: ${misc:Depends}, waterfox-g (>= ${source:Version})
+Replaces: waterfox-g4-i18n-ru (<< 4.1.1-0~), waterfox-g3-i18n-ru (<< 4.1.1-0~)
+Breaks: waterfox-g4-i18n-ru (<< 4.1.1-0~), waterfox-g3-i18n-ru (<< 4.1.1-0~)
+Description: Russian language pack for Waterfox G
+ This package contains Russian translations for Waterfox G
+
+Package: waterfox-g-i18n-sv-se
+Architecture: all
+Depends: ${misc:Depends}, waterfox-g (>= ${source:Version})
+Replaces: waterfox-g4-i18n-sv-se (<< 4.1.1-0~), waterfox-g3-i18n-sv-se (<< 4.1.1-0~)
+Breaks: waterfox-g4-i18n-sv-se (<< 4.1.1-0~), waterfox-g3-i18n-sv-se (<< 4.1.1-0~)
+Description: Swedish (Sweden) language pack for Waterfox G
+ This package contains Swedish (Sweden) translations for Waterfox G
+
+Package: waterfox-g-i18n-th
+Architecture: all
+Depends: ${misc:Depends}, waterfox-g (>= ${source:Version})
+Replaces: waterfox-g4-i18n-th (<< 4.1.1-0~), waterfox-g3-i18n-th (<< 4.1.1-0~)
+Breaks: waterfox-g4-i18n-th (<< 4.1.1-0~), waterfox-g3-i18n-th (<< 4.1.1-0~)
+Description: Thai language pack for Waterfox G
+ This package contains Thai translations for Waterfox G
+
+Package: waterfox-g-i18n-vi
+Architecture: all
+Depends: ${misc:Depends}, waterfox-g (>= ${source:Version})
+Replaces: waterfox-g4-i18n-vi (<< 4.1.1-0~), waterfox-g3-i18n-vi (<< 4.1.1-0~)
+Breaks: waterfox-g4-i18n-vi (<< 4.1.1-0~), waterfox-g3-i18n-vi (<< 4.1.1-0~)
+Description: Vietnamese language pack for Waterfox G
+ This package contains Vietnamese translations for Waterfox G
+
+Package: waterfox-g-i18n-zh-cn
+Architecture: all
+Depends: ${misc:Depends}, waterfox-g (>= ${source:Version})
+Replaces: waterfox-g4-i18n-zh-cn (<< 4.1.1-0~), waterfox-g3-i18n-zh-cn (<< 4.1.1-0~)
+Breaks: waterfox-g4-i18n-zh-cn (<< 4.1.1-0~), waterfox-g3-i18n-zh-cn (<< 4.1.1-0~)
+Description: Simplified Chinese language pack for Waterfox G
+ This package contains Simplified Chinese translations for Waterfox G
+
+Package: waterfox-g-i18n-zh-tw
+Architecture: all
+Depends: ${misc:Depends}, waterfox-g (>= ${source:Version})
+Replaces: waterfox-g4-i18n-zh-tw (<< 4.1.1-0~), waterfox-g3-i18n-zh-tw (<< 4.1.1-0~)
+Breaks: waterfox-g4-i18n-zh-tw (<< 4.1.1-0~), waterfox-g3-i18n-zh-tw (<< 4.1.1-0~)
+Description: Traditional Chinese language pack for Waterfox G
+ This package contains Traditional Chinese translations for Waterfox G
diff --git a/waterfox-g/debian/copyright b/waterfox-g/debian/copyright
new file mode 100644
index 0000000..bfa8743
--- /dev/null
+++ b/waterfox-g/debian/copyright
@@ -0,0 +1,8 @@
+Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: Waterfox
+Source: https://github.com/WaterfoxCo/Waterfox.git
+Comment: This package was debianized by hawkeye116477 <hawkeye116477@gmail.com>.
+
+Files: *
+Copyright: Copyright 2021, all rights belongs to Waterfox Ltd and other respective owners.
+License: MPL-2.0
diff --git a/waterfox-g/debian/distribution.ini b/waterfox-g/debian/distribution.ini
new file mode 100644
index 0000000..948bd7e
--- /dev/null
+++ b/waterfox-g/debian/distribution.ini
@@ -0,0 +1,12 @@
+[Global]
+id=stackrpms
+version=rolling
+about=Waterfox Modern for Devuan
+
+[Preferences]
+app.distributor=bgstack15
+
+[BookmarksToolbar]
+item.1.title=Waterfox Support
+item.1.link=https://www.reddit.com/r/waterfox/
+item.1.description=Waterfox Community Support/Forum
diff --git a/waterfox-g/debian/locales.shipped b/waterfox-g/debian/locales.shipped
new file mode 100644
index 0000000..42e9d92
--- /dev/null
+++ b/waterfox-g/debian/locales.shipped
@@ -0,0 +1,26 @@
+ar:Arabic
+cs:Czech
+da:Danish
+de:German
+el:Greek
+en-GB:English (British)
+es-ES:Spanish (Spain)
+es-MX:Spanish (Mexico)
+fr:French
+hu:Hungarian
+id:Indonesian
+it:Italian
+ja:Japanese
+ko:Korean
+lt:Lithuanian
+nl:Dutch; Flemish
+nn-NO:Norwegian (Nynorsk)
+pl:Polish
+pt-BR:Portuguese (Brazilian)
+pt-PT:Portuguese (Portugal)
+ru:Russian
+sv-SE:Swedish (Sweden)
+th:Thai
+vi:Vietnamese
+zh-CN:Simplified Chinese
+zh-TW:Traditional Chinese
diff --git a/waterfox-g/debian/mozconfig b/waterfox-g/debian/mozconfig
new file mode 100644
index 0000000..da1d8d4
--- /dev/null
+++ b/waterfox-g/debian/mozconfig
@@ -0,0 +1,69 @@
+if test `lsb_release -sc` = "bionic" || test `lsb_release -sc` = "focal" || test `lsb_release -sc` = "stretch" || test `lsb_release -sc` = "buster"; then
+export NODEJS=/usr/lib/nodejs-mozilla/bin/node
+fi
+
+if test `lsb_release -sc` = "bionic" || test `lsb_release -sc` = "stretch"; then
+export NASM=/usr/lib/nasm-mozilla/bin/nasm
+fi
+
+# For successfull LTO build, we need to use matching LLVM version
+if test `lsb_release -sc` = "bionic" || test `lsb_release -sc` = "focal" || test `lsb_release -sc` = "impish" || test `lsb_release -sc` = "stretch" || test `lsb_release -sc` = "buster" || test `lsb_release -sc` = "bullseye"; then
+export PATH=/usr/lib/llvm-12/bin/:$PATH
+fi
+
+if test `lsb_release -sc` = "kinetic"; then
+export PATH=/usr/lib/llvm-14/bin/:$PATH
+fi
+
+export CC=clang
+export CXX=clang++
+export AR=llvm-ar
+export NM=llvm-nm
+export RANLIB=llvm-ranlib
+export MACH_BUILD_PYTHON_NATIVE_PACKAGE_SOURCE=system
+
+ac_add_options --prefix=/usr
+
+export MOZ_PGO=1
+
+mk_add_options AUTOCLOBBER=1
+
+ac_add_options --disable-debug
+ac_add_options --disable-debug-symbols
+ac_add_options --disable-crashreporter
+ac_add_options --disable-profiling
+ac_add_options --disable-verify-mar
+ac_add_options --disable-dmd
+ac_add_options --disable-geckodriver
+ac_add_options --disable-bootstrap
+ac_add_options --disable-updater
+ac_add_options --disable-elf-hack
+
+ac_add_options --enable-pulseaudio
+ac_add_options --enable-alsa
+ac_add_options --enable-jack
+ac_add_options --enable-eme=widevine
+ac_add_options --enable-application=browser
+ac_add_options --enable-default-toolkit=cairo-gtk3-wayland
+ac_add_options --enable-hardening
+ac_add_options --enable-optimize
+ac_add_options --enable-rust-simd
+ac_add_options --enable-lto
+ac_add_options --enable-linker=lld
+ac_add_options --enable-jxl
+
+ac_add_options --with-app-name=waterfox-g
+ac_add_options --with-app-basename=Waterfox
+ac_add_options --with-branding=waterfox/browser/branding
+ac_add_options --with-unsigned-addon-scopes=app,system
+ac_add_options --without-wasm-sandboxed-libraries
+ac_add_options --allow-addon-sideload
+ac_add_options --with-version-file-path=$topsrcdir/debian/app_version
+
+export MOZ_REQUIRE_SIGNING=
+export MOZ_INCLUDE_SOURCE_INFO=1
+export MOZ_APP_REMOTINGNAME=waterfox-g
+ac_add_options "MOZ_ALLOW_LEGACY_EXTENSIONS=1"
+
+X=$(($(nproc --all)/2))
+mk_add_options MOZ_MAKE_FLAGS="-j${X%.*}"
diff --git a/waterfox-g/debian/mozconfig_LANG b/waterfox-g/debian/mozconfig_LANG
new file mode 100644
index 0000000..1af22f3
--- /dev/null
+++ b/waterfox-g/debian/mozconfig_LANG
@@ -0,0 +1,31 @@
+if test `lsb_release -sc` = "bionic" || test `lsb_release -sc` = "focal" || test `lsb_release -sc` = "stretch" || test `lsb_release -sc` = "buster"; then
+export NODEJS=/usr/lib/nodejs-mozilla/bin/node
+fi
+
+if test `lsb_release -sc` = "bionic" || test `lsb_release -sc` = "stretch"; then
+export NASM=/usr/lib/nasm-mozilla/bin/nasm
+fi
+
+# For successfull LTO build, we need to use matching LLVM version
+if test `lsb_release -sc` = "bionic" || test `lsb_release -sc` = "focal" || test `lsb_release -sc` = "impish" || test `lsb_release -sc` = "stretch" || test `lsb_release -sc` = "buster" || test `lsb_release -sc` = "bullseye"; then
+export PATH=/usr/lib/llvm-12/bin/:$PATH
+fi
+
+if test `lsb_release -sc` = "kinetic"; then
+export PATH=/usr/lib/llvm-14/bin/:$PATH
+fi
+
+export MACH_BUILD_PYTHON_NATIVE_PACKAGE_SOURCE=system
+
+ac_add_options --enable-linker=lld
+
+ac_add_options --with-app-name=waterfox-g
+ac_add_options --with-app-basename=Waterfox
+ac_add_options --with-branding=waterfox/browser/branding
+mk_add_options MOZ_OBJDIR=$topsrcdir/../obj_LANG
+ac_add_options --prefix=/usr
+ac_add_options --with-l10n-base=$topsrcdir/waterfox/browser/locales
+ac_add_options --disable-updater
+ac_add_options --disable-bootstrap
+ac_add_options --without-wasm-sandboxed-libraries
+ac_add_options --with-version-file-path=$topsrcdir/debian/app_version
diff --git a/waterfox-g/debian/patches/fis-csd-global-menu.patch b/waterfox-g/debian/patches/fis-csd-global-menu.patch
new file mode 100644
index 0000000..6eca9e8
--- /dev/null
+++ b/waterfox-g/debian/patches/fis-csd-global-menu.patch
@@ -0,0 +1,21 @@
+diff --git a/browser/base/content/browser.css b/browser/base/content/browser.css
+index 03a778809dc8..8f0267b45328 100644
+--- a/browser/base/content/browser.css
++++ b/browser/base/content/browser.css
+@@ -334,6 +334,13 @@ toolbar[customizing] #whats-new-menu-button {
+ visibility: hidden;
+ }
+
++@media (-moz-platform: linux) {
++ *|*:root[shellshowingmenubar="true"]
++ #toolbar-menubar[autohide="true"]:not([inactive]) + #TabsToolbar > .titlebar-buttonbox-container {
++ visibility: visible !important;
++ }
++}
++
+ :root[tabsintitlebar] .titlebar-buttonbox {
+ position: relative;
+ }
+--
+2.37.3
+
diff --git a/waterfox-g/debian/patches/fix-langpack-id.patch b/waterfox-g/debian/patches/fix-langpack-id.patch
new file mode 100644
index 0000000..c409fd8
--- /dev/null
+++ b/waterfox-g/debian/patches/fix-langpack-id.patch
@@ -0,0 +1,67 @@
+diff --git a/browser/components/preferences/tests/browser_browser_languages_subdialog.js b/browser/components/preferences/tests/browser_browser_languages_subdialog.js
+index a204f697f904..4b87ab85156a 100644
+--- a/browser/components/preferences/tests/browser_browser_languages_subdialog.js
++++ b/browser/components/preferences/tests/browser_browser_languages_subdialog.js
+@@ -14,7 +14,7 @@ const DICTIONARY_ID_PL = "pl@dictionaries.addons.mozilla.org";
+ const TELEMETRY_CATEGORY = "intl.ui.browserLanguage";
+
+ function langpackId(locale) {
+- return `langpack-${locale}@firefox.mozilla.org`;
++ return `langpack-${locale}@l10n.waterfox.net`;
+ }
+
+ function getManifestData(locale, version = "2.0") {
+@@ -670,7 +670,7 @@ add_task(async function testInstallFromAMO() {
+ is(getMainPaneLocales(), "en-US,pl,search", "en-US and pl now available");
+
+ // Disable the Polish langpack.
+- langpack = await AddonManager.getAddonByID("langpack-pl@firefox.mozilla.org");
++ langpack = await AddonManager.getAddonByID("langpack-pl@l10n.waterfox.net");
+ await langpack.disable();
+
+ ({ dialogDoc, available, selected } = await openDialog(doc, true));
+diff --git a/browser/locales/Makefile.in b/browser/locales/Makefile.in
+index e4b60a039956..208871cb6f75 100644
+--- a/browser/locales/Makefile.in
++++ b/browser/locales/Makefile.in
+@@ -21,9 +21,9 @@ PWD := $(CURDIR)
+ ZIP_IN ?= $(ABS_DIST)/$(PACKAGE)
+
+ ifdef MOZ_DEV_EDITION
+-MOZ_LANGPACK_EID=langpack-$(AB_CD)@devedition.mozilla.org
++MOZ_LANGPACK_EID=langpack-$(AB_CD)@l10n.waterfox.net
+ else
+-MOZ_LANGPACK_EID=langpack-$(AB_CD)@firefox.mozilla.org
++MOZ_LANGPACK_EID=langpack-$(AB_CD)@l10n.waterfox.net
+ endif
+ # For Nightly, we know where to get the builds from to do local repacks
+ ifdef NIGHTLY_BUILD
+diff --git a/intl/locale/LangPackMatcher.jsm b/intl/locale/LangPackMatcher.jsm
+index 0913ef9fda38..2b923b59df6d 100644
+--- a/intl/locale/LangPackMatcher.jsm
++++ b/intl/locale/LangPackMatcher.jsm
+@@ -370,7 +370,7 @@ async function getAvailableLocales() {
+ // If defaultLocale isn't lastFallbackLocale, then we still need the langpack
+ // for lastFallbackLocale for it to be useful.
+ if (defaultLocale != lastFallbackLocale) {
+- let lastFallbackId = `langpack-${lastFallbackLocale}@firefox.mozilla.org`;
++ let lastFallbackId = `langpack-${lastFallbackLocale}@l10n.waterfox.net`;
+ let lastFallbackInstalled = await AddonManager.getAddonByID(lastFallbackId);
+ if (!lastFallbackInstalled) {
+ return availableLocales.filter(locale => locale != lastFallbackLocale);
+diff --git a/intl/locale/tests/LangPackMatcherTestUtils.jsm b/intl/locale/tests/LangPackMatcherTestUtils.jsm
+index 38345e1ec2d6..9898c733747c 100644
+--- a/intl/locale/tests/LangPackMatcherTestUtils.jsm
++++ b/intl/locale/tests/LangPackMatcherTestUtils.jsm
+@@ -43,7 +43,7 @@ function getAddonAndLocalAPIsMocker(testScope, sandbox) {
+ );
+ resolve(
+ availableLangpacks.map(locale => ({
+- guid: `langpack-${locale}@firefox.mozilla.org`,
++ guid: `langpack-${locale}@l10n.waterfox.net`,
+ type: "language",
+ target_locale: locale,
+ current_compatible_version: {
+--
+2.37.3
+
diff --git a/waterfox-g/debian/patches/fix-wayland-build.patch b/waterfox-g/debian/patches/fix-wayland-build.patch
new file mode 100644
index 0000000..703b1a8
--- /dev/null
+++ b/waterfox-g/debian/patches/fix-wayland-build.patch
@@ -0,0 +1,19 @@
+Description: Fix FTBFS on bionic. Compiler errors:
+ In file included from Unified_cpp_widget_gtk1.cpp:2:
+ /<<BUILDDIR>>/firefox-92.0~b2+build1/widget/gtk/WaylandBuffer.cpp:261:39: error: unknown type name 'GLContext'; did you mean 'EGLContext'?
+ const LayoutDeviceIntSize& aSize, GLContext* aGL) {
+ ^~~~~~~~~
+
+Author: Rico Tzschichholz <ricotz@ubuntu.com>
+
+--- a/widget/gtk/WaylandBuffer.cpp
++++ b/widget/gtk/WaylandBuffer.cpp
+@@ -258,7 +258,7 @@
+
+ /* static */
+ RefPtr<WaylandBufferDMABUF> WaylandBufferDMABUF::Create(
+- const LayoutDeviceIntSize& aSize, GLContext* aGL) {
++ const LayoutDeviceIntSize& aSize, gl::GLContext* aGL) {
+ RefPtr<WaylandBufferDMABUF> buffer = new WaylandBufferDMABUF(aSize);
+
+ const auto flags =
diff --git a/waterfox-g/debian/patches/g-kde.patch b/waterfox-g/debian/patches/g-kde.patch
new file mode 100644
index 0000000..2bff3cc
--- /dev/null
+++ b/waterfox-g/debian/patches/g-kde.patch
@@ -0,0 +1,1748 @@
+Merged few patches into one patch and fixed for Waterfox
+Original authors of patch for Firefox:
+Wolfgang Rosenauer <wolfgang@rosenauer.org>
+Lubos Lunak <lunak@suse.com>
+Original patches => http://www.rosenauer.org/hg/mozilla/file/firefox91
+
+diff --git a/browser/components/preferences/main.js b/browser/components/preferences/main.js
+index 627e63440af2..6d300a8124fa 100644
+--- a/browser/components/preferences/main.js
++++ b/browser/components/preferences/main.js
+@@ -316,6 +316,13 @@ var gMainPane = {
+ }, backoffTimes[this._backoffIndex]);
+ }
+
++ var env = Components.classes["@mozilla.org/process/environment;1"]
++ .getService(Components.interfaces.nsIEnvironment);
++ var kde_session = 0;
++ if (env.get('KDE_FULL_SESSION') == "true") {
++ kde_session = 1;
++ }
++
+ this.initBrowserContainers();
+ this.buildContentProcessCountMenuList();
+
+@@ -1349,6 +1356,17 @@ var gMainPane = {
+ }
+ try {
+ shellSvc.setDefaultBrowser(true, false);
++ if (kde_session == 1) {
++ var shellObj = Components.classes["@mozilla.org/file/local;1"]
++ .createInstance(Components.interfaces.nsILocalFile);
++ shellObj.initWithPath("/usr/bin/kwriteconfig");
++ var process = Components.classes["@mozilla.org/process/util;1"]
++ .createInstance(Components.interfaces.nsIProcess);
++ process.init(shellObj);
++ var args = ["--file", "kdeglobals", "--group", "General", "--key",
++ "BrowserApplication", "waterfox-g"];
++ process.run(false, args, args.length);
++ }
+ } catch (ex) {
+ Cu.reportError(ex);
+ return;
+diff --git a/browser/components/shell/moz.build b/browser/components/shell/moz.build
+index eedbb0d938fe..9b364941b850 100644
+--- a/browser/components/shell/moz.build
++++ b/browser/components/shell/moz.build
+@@ -36,6 +36,8 @@ elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
+
+ SOURCES += [
+ "nsGNOMEShellService.cpp",
++ "nsKDEShellService.cpp",
++ "nsUnixShellService.cpp",
+ ]
+ if CONFIG["MOZ_ENABLE_DBUS"]:
+ SOURCES += [
+diff --git a/browser/components/shell/nsKDEShellService.cpp b/browser/components/shell/nsKDEShellService.cpp
+new file mode 100644
+index 000000000000..152a3aca87ea
+--- /dev/null
++++ b/browser/components/shell/nsKDEShellService.cpp
+@@ -0,0 +1,109 @@
++/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
++/* This Source Code Form is subject to the terms of the Mozilla Public
++ * License, v. 2.0. If a copy of the MPL was not distributed with this
++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
++
++#include "mozilla/ArrayUtils.h"
++
++#include "nsCOMPtr.h"
++#include "nsKDEShellService.h"
++#include "nsShellService.h"
++#include "nsKDEUtils.h"
++#include "nsIPrefService.h"
++#include "nsIProcess.h"
++#include "nsIFile.h"
++#include "nsServiceManagerUtils.h"
++#include "nsComponentManagerUtils.h"
++#include "nsIMutableArray.h"
++#include "nsISupportsPrimitives.h"
++#include "nsArrayUtils.h"
++
++using namespace mozilla;
++
++nsresult
++nsKDEShellService::Init()
++{
++ if( !nsKDEUtils::kdeSupport())
++ return NS_ERROR_NOT_AVAILABLE;
++ return NS_OK;
++}
++
++NS_IMPL_ISUPPORTS(nsKDEShellService, nsIGNOMEShellService, nsIShellService)
++
++NS_IMETHODIMP
++nsKDEShellService::IsDefaultBrowser(bool aForAllTypes,
++ bool* aIsDefaultBrowser)
++{
++ *aIsDefaultBrowser = false;
++
++ nsCOMPtr<nsIMutableArray> command = do_CreateInstance( NS_ARRAY_CONTRACTID );
++ if (!command)
++ return NS_ERROR_FAILURE;
++
++ nsCOMPtr<nsISupportsCString> str = do_CreateInstance( NS_SUPPORTS_CSTRING_CONTRACTID );
++ if (!str)
++ return NS_ERROR_FAILURE;
++
++ str->SetData("ISDEFAULTBROWSER"_ns);
++ command->AppendElement( str );
++
++ if( nsKDEUtils::command( command ))
++ *aIsDefaultBrowser = true;
++ return NS_OK;
++}
++
++NS_IMETHODIMP
++nsKDEShellService::SetDefaultBrowser(bool aClaimAllTypes,
++ bool aForAllUsers)
++{
++ nsCOMPtr<nsIMutableArray> command = do_CreateInstance( NS_ARRAY_CONTRACTID );
++ if (!command)
++ return NS_ERROR_FAILURE;
++
++ nsCOMPtr<nsISupportsCString> cmdstr = do_CreateInstance( NS_SUPPORTS_CSTRING_CONTRACTID );
++ nsCOMPtr<nsISupportsCString> paramstr = do_CreateInstance( NS_SUPPORTS_CSTRING_CONTRACTID );
++ if (!cmdstr || !paramstr)
++ return NS_ERROR_FAILURE;
++
++ cmdstr->SetData("SETDEFAULTBROWSER"_ns);
++ command->AppendElement( cmdstr );
++
++ paramstr->SetData( aClaimAllTypes ? "ALLTYPES"_ns : "NORMAL"_ns );
++ command->AppendElement( paramstr );
++
++ return nsKDEUtils::command( command ) ? NS_OK : NS_ERROR_FAILURE;
++}
++
++NS_IMETHODIMP
++nsKDEShellService::GetCanSetDesktopBackground(bool* aResult)
++{
++ *aResult = true;
++ return NS_OK;
++}
++
++NS_IMETHODIMP
++nsKDEShellService::SetDesktopBackground(dom::Element* aElement,
++ int32_t aPosition,
++ const nsACString& aImageName)
++{
++ return NS_ERROR_NOT_IMPLEMENTED;
++}
++
++NS_IMETHODIMP
++nsKDEShellService::GetDesktopBackgroundColor(PRUint32 *aColor)
++{
++ return NS_ERROR_NOT_IMPLEMENTED;
++}
++
++NS_IMETHODIMP
++nsKDEShellService::SetDesktopBackgroundColor(PRUint32 aColor)
++{
++ return NS_ERROR_NOT_IMPLEMENTED;
++}
++
++NS_IMETHODIMP
++nsKDEShellService::IsDefaultForScheme(nsTSubstring<char> const& aScheme, bool* aIsDefaultBrowser)
++{
++ return NS_ERROR_NOT_IMPLEMENTED;
++}
++
+diff --git a/browser/components/shell/nsKDEShellService.h b/browser/components/shell/nsKDEShellService.h
+new file mode 100644
+index 000000000000..8b0bb1916435
+--- /dev/null
++++ b/browser/components/shell/nsKDEShellService.h
+@@ -0,0 +1,32 @@
++/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
++/* This Source Code Form is subject to the terms of the Mozilla Public
++ * License, v. 2.0. If a copy of the MPL was not distributed with this
++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
++
++#ifndef nskdeshellservice_h____
++#define nskdeshellservice_h____
++
++#include "nsIGNOMEShellService.h"
++#include "nsToolkitShellService.h"
++#include "nsString.h"
++#include "mozilla/Attributes.h"
++
++class nsKDEShellService final : public nsIGNOMEShellService,
++ public nsToolkitShellService
++{
++public:
++ nsKDEShellService() : mCheckedThisSession(false) { }
++
++ NS_DECL_ISUPPORTS
++ NS_DECL_NSISHELLSERVICE
++ NS_DECL_NSIGNOMESHELLSERVICE
++
++ nsresult Init();
++
++private:
++ ~nsKDEShellService() {}
++
++ bool mCheckedThisSession;
++};
++
++#endif // nskdeshellservice_h____
+diff --git a/browser/components/shell/nsUnixShellService.cpp b/browser/components/shell/nsUnixShellService.cpp
+new file mode 100644
+index 000000000000..abf266ebdc52
+--- /dev/null
++++ b/browser/components/shell/nsUnixShellService.cpp
+@@ -0,0 +1,22 @@
++/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
++/* This Source Code Form is subject to the terms of the Mozilla Public
++ * License, v. 2.0. If a copy of the MPL was not distributed with this
++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
++
++
++#include "nsUnixShellService.h"
++#include "nsGNOMEShellService.h"
++#include "nsKDEShellService.h"
++#include "nsKDEUtils.h"
++#include "mozilla/ModuleUtils.h"
++
++NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsGNOMEShellService, Init)
++NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsKDEShellService, Init)
++
++NS_IMETHODIMP
++nsUnixShellServiceConstructor(REFNSIID aIID, void **aResult)
++{
++ if( nsKDEUtils::kdeSupport())
++ return nsKDEShellServiceConstructor( aIID, aResult );
++ return nsGNOMEShellServiceConstructor( aIID, aResult );
++}
+diff --git a/browser/components/shell/nsUnixShellService.h b/browser/components/shell/nsUnixShellService.h
+new file mode 100644
+index 000000000000..26b5dbac47dd
+--- /dev/null
++++ b/browser/components/shell/nsUnixShellService.h
+@@ -0,0 +1,15 @@
++/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
++/* This Source Code Form is subject to the terms of the Mozilla Public
++ * License, v. 2.0. If a copy of the MPL was not distributed with this
++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
++
++
++#ifndef nsunixshellservice_h____
++#define nsunixshellservice_h____
++
++#include "nsIGNOMEShellService.h"
++
++NS_IMETHODIMP
++nsUnixShellServiceConstructor(nsISupports *aOuter, REFNSIID aIID, void **aResult);
++
++#endif // nsunixshellservice_h____
+diff --git a/modules/libpref/Preferences.cpp b/modules/libpref/Preferences.cpp
+index 6d0bebd2ee68..c138e63b23dc 100644
+--- a/modules/libpref/Preferences.cpp
++++ b/modules/libpref/Preferences.cpp
+@@ -93,6 +93,7 @@
+ #ifdef MOZ_BACKGROUNDTASKS
+ # include "mozilla/BackgroundTasks.h"
+ #endif
++#include "nsKDEUtils.h"
+
+ #ifdef DEBUG
+ # include <map>
+@@ -4772,6 +4773,17 @@ nsresult Preferences::InitInitialObjects(bool aIsStartup) {
+ #endif
+ };
+
++ if(nsKDEUtils::kdeSession()) { // TODO what if some setup actually requires the helper?
++ for(int i = 0;
++ i < MOZ_ARRAY_LENGTH(specialFiles);
++ ++i ) {
++ if( *specialFiles[ i ] == '\0' ) {
++ specialFiles[ i ] = "kde.js";
++ break;
++ }
++ }
++ }
++
+ rv = pref_LoadPrefsInDir(defaultPrefDir, specialFiles,
+ ArrayLength(specialFiles));
+ if (NS_FAILED(rv)) {
+@@ -4846,7 +4858,7 @@ nsresult Preferences::InitInitialObjects(bool aIsStartup) {
+ }
+
+ // Do we care if a file provided by this process fails to load?
+- pref_LoadPrefsInDir(path, nullptr, 0);
++ pref_LoadPrefsInDir(path, specialFiles, ArrayLength(specialFiles));
+ }
+ }
+
+diff --git a/modules/libpref/moz.build b/modules/libpref/moz.build
+index 1f021d409c51..171d034cc03a 100644
+--- a/modules/libpref/moz.build
++++ b/modules/libpref/moz.build
+@@ -125,6 +125,10 @@ UNIFIED_SOURCES += [
+ "SharedPrefMap.cpp",
+ ]
+
++LOCAL_INCLUDES += [
++ '/toolkit/xre'
++]
++
+ gen_all_tuple = tuple(gen_h + gen_cpp + gen_rs)
+
+ GeneratedFile(
+diff --git a/python/mozbuild/mozpack/chrome/flags.py b/python/mozbuild/mozpack/chrome/flags.py
+index 0fe6ee99bd51..f7acdafe9790 100644
+--- a/python/mozbuild/mozpack/chrome/flags.py
++++ b/python/mozbuild/mozpack/chrome/flags.py
+@@ -234,6 +234,7 @@ class Flags(OrderedDict):
+ "tablet": Flag,
+ "process": StringFlag,
+ "backgroundtask": StringFlag,
++ "desktop": StringFlag,
+ }
+ RE = re.compile(r"([!<>=]+)")
+
+diff --git a/python/mozbuild/mozpack/chrome/manifest.py b/python/mozbuild/mozpack/chrome/manifest.py
+index a733685f95a3..f64b17fb7b17 100644
+--- a/python/mozbuild/mozpack/chrome/manifest.py
++++ b/python/mozbuild/mozpack/chrome/manifest.py
+@@ -44,6 +44,7 @@ class ManifestEntry(object):
+ "process",
+ "contentaccessible",
+ "backgroundtask",
++ "desktop",
+ ]
+
+ def __init__(self, base, *flags):
+diff --git a/toolkit/components/downloads/moz.build b/toolkit/components/downloads/moz.build
+index d4172e2d73ad..8bd0577bc535 100644
+--- a/toolkit/components/downloads/moz.build
++++ b/toolkit/components/downloads/moz.build
+@@ -51,5 +51,9 @@ if CONFIG["MOZ_PLACES"]:
+
+ FINAL_LIBRARY = "xul"
+
++LOCAL_INCLUDES += [
++ '/toolkit/xre'
++]
++
+ with Files("**"):
+ BUG_COMPONENT = ("Toolkit", "Downloads API")
+diff --git a/toolkit/mozapps/downloads/HelperAppDlg.jsm b/toolkit/mozapps/downloads/HelperAppDlg.jsm
+index 0a5a4a460bf3..5381240f52c6 100644
+--- a/toolkit/mozapps/downloads/HelperAppDlg.jsm
++++ b/toolkit/mozapps/downloads/HelperAppDlg.jsm
+@@ -1260,26 +1260,56 @@ nsUnknownContentTypeDialog.prototype = {
+ this.chosenApp = params.handlerApp;
+ }
+ } else if ("@mozilla.org/applicationchooser;1" in Cc) {
+- var nsIApplicationChooser = Ci.nsIApplicationChooser;
+- var appChooser = Cc["@mozilla.org/applicationchooser;1"].createInstance(
+- nsIApplicationChooser
+- );
+- appChooser.init(
+- this.mDialog,
+- this.dialogElement("strings").getString("chooseAppFilePickerTitle")
+- );
+- var contentTypeDialogObj = this;
+- let appChooserCallback = function appChooserCallback_done(aResult) {
+- if (aResult) {
+- contentTypeDialogObj.chosenApp = aResult.QueryInterface(
+- Ci.nsILocalHandlerApp
+- );
+- }
+- contentTypeDialogObj.finishChooseApp();
+- };
+- appChooser.open(this.mLauncher.MIMEInfo.MIMEType, appChooserCallback);
+- // The finishChooseApp is called from appChooserCallback
+- return;
++ // handle the KDE case which is implemented in the filepicker
++ // therefore falling back to Gtk2 like behaviour if KDE is running
++ // FIXME this should be better handled in the nsIApplicationChooser
++ // interface
++ var env = Components.classes["@mozilla.org/process/environment;1"]
++ .getService(Components.interfaces.nsIEnvironment);
++ if (env.get('KDE_FULL_SESSION') == "true")
++ {
++ var nsIFilePicker = Ci.nsIFilePicker;
++ var fp = Cc["@mozilla.org/filepicker;1"]
++ .createInstance(nsIFilePicker);
++ fp.init(this.mDialog,
++ this.dialogElement("strings").getString("chooseAppFilePickerTitle"),
++ nsIFilePicker.modeOpen);
++
++ fp.appendFilters(nsIFilePicker.filterApps);
++
++ fp.open(aResult => {
++ if (aResult == nsIFilePicker.returnOK && fp.file) {
++ // Remember the file they chose to run.
++ var localHandlerApp =
++ Cc["@mozilla.org/uriloader/local-handler-app;1"].
++ createInstance(Ci.nsILocalHandlerApp);
++ localHandlerApp.executable = fp.file;
++ this.chosenApp = localHandlerApp;
++ }
++ this.finishChooseApp();
++ });
++ } else {
++ var nsIApplicationChooser = Ci.nsIApplicationChooser;
++ var appChooser = Cc["@mozilla.org/applicationchooser;1"].createInstance(
++ nsIApplicationChooser
++ );
++ appChooser.init(
++ this.mDialog,
++ this.dialogElement("strings").getString("chooseAppFilePickerTitle")
++ );
++ var contentTypeDialogObj = this;
++ let appChooserCallback = function appChooserCallback_done(aResult) {
++ if (aResult) {
++ contentTypeDialogObj.chosenApp = aResult.QueryInterface(
++ Ci.nsILocalHandlerApp
++ );
++ }
++ contentTypeDialogObj.finishChooseApp();
++ };
++ appChooser.open(this.mLauncher.MIMEInfo.MIMEType, appChooserCallback);
++ // The finishChooseApp is called from appChooserCallback
++ return;
++ }
+ } else {
+ var nsIFilePicker = Ci.nsIFilePicker;
+ var fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
+diff --git a/toolkit/system/unixproxy/nsUnixSystemProxySettings.cpp b/toolkit/system/unixproxy/nsUnixSystemProxySettings.cpp
+index ef110b1287bf..e29e8a5b62de 100644
+--- a/toolkit/system/unixproxy/nsUnixSystemProxySettings.cpp
++++ b/toolkit/system/unixproxy/nsUnixSystemProxySettings.cpp
+@@ -15,6 +15,8 @@
+ #include "nsNetUtil.h"
+ #include "nsISupportsPrimitives.h"
+ #include "nsIGSettingsService.h"
++#include "nsPrintfCString.h"
++#include "nsKDEUtils.h"
+
+ using namespace mozilla;
+
+@@ -38,6 +40,8 @@ class nsUnixSystemProxySettings final : public nsISystemProxySettings {
+ nsACString& aResult);
+ nsresult SetProxyResultFromGSettings(const char* aKeyBase, const char* aType,
+ nsACString& aResult);
++ nsresult GetProxyFromKDE(const nsACString& aScheme, const nsACString& aHost,
++ PRInt32 aPort, nsACString& aResult);
+ };
+
+ NS_IMPL_ISUPPORTS(nsUnixSystemProxySettings, nsISystemProxySettings)
+@@ -379,6 +383,9 @@ nsresult nsUnixSystemProxySettings::GetProxyForURI(const nsACString& aSpec,
+ const nsACString& aHost,
+ const int32_t aPort,
+ nsACString& aResult) {
++ if (nsKDEUtils::kdeSupport())
++ return GetProxyFromKDE(aScheme, aHost, aPort, aResult);
++
+ if (mProxySettings) {
+ nsresult rv = GetProxyFromGSettings(aScheme, aHost, aPort, aResult);
+ if (NS_SUCCEEDED(rv)) return rv;
+@@ -387,6 +394,32 @@ nsresult nsUnixSystemProxySettings::GetProxyForURI(const nsACString& aSpec,
+ return GetProxyFromEnvironment(aScheme, aHost, aPort, aResult);
+ }
+
++nsresult
++nsUnixSystemProxySettings::GetProxyFromKDE(const nsACString& aScheme,
++ const nsACString& aHost,
++ PRInt32 aPort,
++ nsACString& aResult)
++{
++ nsAutoCString url;
++ url = aScheme;
++ url += "://";
++ url += aHost;
++ if( aPort >= 0 )
++ {
++ url += ":";
++ url += nsPrintfCString("%d", aPort);
++ }
++ nsTArray<nsCString> command;
++ command.AppendElement( "GETPROXY"_ns );
++ command.AppendElement( url );
++ nsTArray<nsCString> result;
++ if( !nsKDEUtils::command( command, &result ) || result.Length() != 1 )
++ return NS_ERROR_FAILURE;
++ aResult = result[0];
++ return NS_OK;
++}
++
++
+ NS_IMPL_COMPONENT_FACTORY(nsUnixSystemProxySettings) {
+ auto result = MakeRefPtr<nsUnixSystemProxySettings>();
+ result->Init();
+diff --git a/toolkit/xre/moz.build b/toolkit/xre/moz.build
+index 6475c0296aac..83e0184d4938 100644
+--- a/toolkit/xre/moz.build
++++ b/toolkit/xre/moz.build
+@@ -97,7 +97,9 @@ elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "uikit":
+ "UIKitDirProvider.mm",
+ ]
+ elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
++ EXPORTS += ['nsKDEUtils.h']
+ UNIFIED_SOURCES += [
++ "nsKDEUtils.cpp",
+ "nsNativeAppSupportUnix.cpp",
+ ]
+ CXXFLAGS += CONFIG["MOZ_X11_SM_CFLAGS"]
+diff --git a/toolkit/xre/nsKDEUtils.cpp b/toolkit/xre/nsKDEUtils.cpp
+new file mode 100644
+index 000000000000..a5242b6c6699
+--- /dev/null
++++ b/toolkit/xre/nsKDEUtils.cpp
+@@ -0,0 +1,321 @@
++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* This Source Code Form is subject to the terms of the Mozilla Public
++ * License, v. 2.0. If a copy of the MPL was not distributed with this
++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
++
++#include "nsKDEUtils.h"
++#include "nsIWidget.h"
++#include "nsISupportsPrimitives.h"
++#include "nsIMutableArray.h"
++#include "nsComponentManagerUtils.h"
++#include "nsArrayUtils.h"
++
++#include <gtk/gtk.h>
++
++#include <limits.h>
++#include <stdio.h>
++#include <sys/wait.h>
++#include <sys/resource.h>
++#include <unistd.h>
++#include <X11/Xlib.h>
++// copied from X11/X.h as a hack since for an unknown
++// reason it's not picked up from X11/X.h
++#ifndef None
++#define None 0L /* universal null resource or null atom */
++#endif
++
++//#define DEBUG_KDE
++#ifdef DEBUG_KDE
++#define KWATERFOXHELPER "kwaterfoxhelper"
++#else
++// not need for lib64, it's a binary
++#define KWATERFOXHELPER "/usr/lib/waterfox/kwaterfoxhelper"
++#endif
++
++#define KWATERFOXHELPER_VERSION 6
++#define MAKE_STR2( n ) #n
++#define MAKE_STR( n ) MAKE_STR2( n )
++
++static bool getKdeSession()
++{
++ if (PR_GetEnv("KDE_FULL_SESSION"))
++ {
++ return true;
++ }
++ return false;
++}
++
++static bool getKdeSupport()
++ {
++ nsTArray<nsCString> command;
++ command.AppendElement( "CHECK"_ns );
++ command.AppendElement( "KWATERFOXHELPER_VERSION"_ns );
++ bool kde = nsKDEUtils::command( command );
++#ifdef DEBUG_KDE
++ fprintf( stderr, "KDE RUNNING %d\n", kde );
++#endif
++ return kde;
++ }
++
++nsKDEUtils::nsKDEUtils()
++ : commandFile( NULL )
++ , replyFile( NULL )
++ {
++ }
++
++nsKDEUtils::~nsKDEUtils()
++ {
++// closeHelper(); not actually useful, exiting will close the fd too
++ }
++
++nsKDEUtils* nsKDEUtils::self()
++ {
++ static nsKDEUtils s;
++ return &s;
++ }
++
++static bool helperRunning = false;
++static bool helperFailed = false;
++
++bool nsKDEUtils::kdeSession()
++ {
++ static bool session = getKdeSession();
++ return session;
++ }
++
++bool nsKDEUtils::kdeSupport()
++ {
++ static bool support = kdeSession() && getKdeSupport();
++ return support && helperRunning;
++ }
++
++struct nsKDECommandData
++ {
++ FILE* file;
++ nsTArray<nsCString>* output;
++ GMainLoop* loop;
++ bool success;
++ };
++
++static gboolean kdeReadFunc( GIOChannel*, GIOCondition, gpointer data )
++ {
++ nsKDECommandData* p = static_cast< nsKDECommandData* >( data );
++ char buf[ 8192 ]; // TODO big enough
++ bool command_done = false;
++ bool command_failed = false;
++ while( !command_done && !command_failed && fgets( buf, 8192, p->file ) != NULL )
++ { // TODO what if the kernel splits a line into two chunks?
++//#ifdef DEBUG_KDE
++// fprintf( stderr, "READ: %s %d\n", buf, feof( p->file ));
++//#endif
++ if( char* eol = strchr( buf, '\n' ))
++ *eol = '\0';
++ command_done = ( strcmp( buf, "\\1" ) == 0 );
++ command_failed = ( strcmp( buf, "\\0" ) == 0 );
++ nsAutoCString line( buf );
++ line.ReplaceSubstring( "\\n", "\n" );
++ line.ReplaceSubstring( "\\" "\\", "\\" ); // \\ -> \ , i.e. unescape
++ if( p->output && !( command_done || command_failed ))
++ p->output->AppendElement( nsCString( buf )); // TODO utf8?
++ }
++ bool quit = false;
++ if( feof( p->file ) || command_failed )
++ {
++ quit = true;
++ p->success = false;
++ }
++ if( command_done )
++ { // reading one reply finished
++ quit = true;
++ p->success = true;
++ }
++ if( quit )
++ {
++ if( p->loop )
++ g_main_loop_quit( p->loop );
++ return FALSE;
++ }
++ return TRUE;
++ }
++
++bool nsKDEUtils::command( const nsTArray<nsCString>& command, nsTArray<nsCString>* output )
++ {
++ return self()->internalCommand( command, NULL, false, output );
++ }
++
++bool nsKDEUtils::command( nsIArray* command, nsIArray** output)
++ {
++ nsTArray<nsCString> in;
++ PRUint32 length;
++ command->GetLength( &length );
++ for ( PRUint32 i = 0; i < length; i++ )
++ {
++ nsCOMPtr<nsISupportsCString> str = do_QueryElementAt( command, i );
++ if( str )
++ {
++ nsAutoCString s;
++ str->GetData( s );
++ in.AppendElement( s );
++ }
++ }
++
++ nsTArray<nsCString> out;
++ bool ret = self()->internalCommand( in, NULL, false, &out );
++
++ if ( !output ) return ret;
++
++ nsCOMPtr<nsIMutableArray> result = do_CreateInstance( NS_ARRAY_CONTRACTID );
++ if ( !result ) return false;
++
++ for ( PRUint32 i = 0; i < out.Length(); i++ )
++ {
++ nsCOMPtr<nsISupportsCString> rstr = do_CreateInstance( NS_SUPPORTS_CSTRING_CONTRACTID );
++ if ( !rstr ) return false;
++
++ rstr->SetData( out[i] );
++ result->AppendElement( rstr );
++ }
++
++ NS_ADDREF( *output = result);
++ return ret;
++ }
++
++
++bool nsKDEUtils::commandBlockUi( const nsTArray<nsCString>& command, GtkWindow* parent, nsTArray<nsCString>* output )
++ {
++ return self()->internalCommand( command, parent, true, output );
++ }
++
++bool nsKDEUtils::internalCommand( const nsTArray<nsCString>& command, GtkWindow* parent, bool blockUi,
++ nsTArray<nsCString>* output )
++ {
++ if( !startHelper())
++ return false;
++ feedCommand( command );
++ // do not store the data in 'this' but in extra structure, just in case there
++ // is reentrancy (can there be? the event loop is re-entered)
++ nsKDECommandData data;
++ data.file = replyFile;
++ data.output = output;
++ data.success = false;
++ if( blockUi )
++ {
++ data.loop = g_main_loop_new( NULL, FALSE );
++ GtkWidget* window = gtk_window_new( GTK_WINDOW_TOPLEVEL );
++ if( parent && gtk_window_get_group(parent) )
++ gtk_window_group_add_window( gtk_window_get_group(parent), GTK_WINDOW( window ));
++ gtk_widget_realize( window );
++ gtk_widget_set_sensitive( window, TRUE );
++ gtk_grab_add( window );
++ GIOChannel* channel = g_io_channel_unix_new( fileno( data.file ));
++ g_io_add_watch( channel, static_cast< GIOCondition >( G_IO_IN | G_IO_ERR | G_IO_HUP ), kdeReadFunc, &data );
++ g_io_channel_unref( channel );
++ g_main_loop_run( data.loop );
++ g_main_loop_unref( data.loop );
++ gtk_grab_remove( window );
++ gtk_widget_destroy( window );
++ }
++ else
++ {
++ data.loop = NULL;
++ while( kdeReadFunc( NULL, static_cast< GIOCondition >( 0 ), &data ))
++ ;
++ }
++ return data.success;
++ }
++
++bool nsKDEUtils::startHelper()
++ {
++ if( helperRunning )
++ return true;
++ if( helperFailed )
++ return false;
++ helperFailed = true;
++ int fdcommand[ 2 ];
++ int fdreply[ 2 ];
++ if( pipe( fdcommand ) < 0 )
++ return false;
++ if( pipe( fdreply ) < 0 )
++ {
++ close( fdcommand[ 0 ] );
++ close( fdcommand[ 1 ] );
++ return false;
++ }
++ char* args[ 2 ] = { const_cast< char* >( KWATERFOXHELPER ), NULL };
++ switch( fork())
++ {
++ case -1:
++ {
++ close( fdcommand[ 0 ] );
++ close( fdcommand[ 1 ] );
++ close( fdreply[ 0 ] );
++ close( fdreply[ 1 ] );
++ return false;
++ }
++ case 0: // child
++ {
++ if( dup2( fdcommand[ 0 ], STDIN_FILENO ) < 0 )
++ _exit( 1 );
++ if( dup2( fdreply[ 1 ], STDOUT_FILENO ) < 0 )
++ _exit( 1 );
++ int maxfd = 1024; // close all other fds
++ struct rlimit rl;
++ if( getrlimit( RLIMIT_NOFILE, &rl ) == 0 )
++ maxfd = rl.rlim_max;
++ for( int i = 3;
++ i < maxfd;
++ ++i )
++ close( i );
++#ifdef DEBUG_KDE
++ execvp( KWATERFOXHELPER, args );
++#else
++ execv( KWATERFOXHELPER, args );
++#endif
++ _exit( 1 ); // failed
++ }
++ default: // parent
++ {
++ commandFile = fdopen( fdcommand[ 1 ], "w" );
++ replyFile = fdopen( fdreply[ 0 ], "r" );
++ close( fdcommand[ 0 ] );
++ close( fdreply[ 1 ] );
++ if( commandFile == NULL || replyFile == NULL )
++ {
++ closeHelper();
++ return false;
++ }
++ // ok, helper ready, getKdeRunning() will check if it works
++ }
++ }
++ helperFailed = false;
++ helperRunning = true;
++ return true;
++ }
++
++void nsKDEUtils::closeHelper()
++ {
++ if( commandFile != NULL )
++ fclose( commandFile ); // this will also make the helper quit
++ if( replyFile != NULL )
++ fclose( replyFile );
++ helperRunning = false;
++ }
++
++void nsKDEUtils::feedCommand( const nsTArray<nsCString>& command )
++ {
++ for( int i = 0;
++ i < command.Length();
++ ++i )
++ {
++ nsCString line = command[ i ];
++ line.ReplaceSubstring( "\\", "\\" "\\" ); // \ -> \\ , i.e. escape
++ line.ReplaceSubstring( "\n", "\\n" );
++#ifdef DEBUG_KDE
++ fprintf( stderr, "COMM: %s\n", line.get());
++#endif
++ fputs( line.get(), commandFile );
++ fputs( "\n", commandFile );
++ }
++ fputs( "\\E\n", commandFile ); // done as \E, so it cannot happen in normal data
++ fflush( commandFile );
++ }
+diff --git a/toolkit/xre/nsKDEUtils.h b/toolkit/xre/nsKDEUtils.h
+new file mode 100644
+index 000000000000..8729aebcaa1c
+--- /dev/null
++++ b/toolkit/xre/nsKDEUtils.h
+@@ -0,0 +1,48 @@
++/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
++/* This Source Code Form is subject to the terms of the Mozilla Public
++ * License, v. 2.0. If a copy of the MPL was not distributed with this
++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
++
++#ifndef nsKDEUtils_h__
++#define nsKDEUtils_h__
++
++#include "nsString.h"
++#include "nsTArray.h"
++#include <stdio.h>
++
++typedef struct _GtkWindow GtkWindow;
++
++class nsIArray;
++
++class NS_EXPORT nsKDEUtils
++ {
++ public:
++ /* Returns true if running inside a KDE session (regardless of whether there is KDE
++ support available for Waterfox). This should be used e.g. when determining
++ dialog button order but not for code that requires the KDE support. */
++ static bool kdeSession();
++ /* Returns true if running inside a KDE session and KDE support is available
++ for Waterfox. This should be used everywhere where the external helper is needed. */
++ static bool kdeSupport();
++ /* Executes the given helper command, returns true if helper returned success. */
++ static bool command( const nsTArray<nsCString>& command, nsTArray<nsCString>* output = NULL );
++ static bool command( nsIArray* command, nsIArray** output = NULL );
++ /* Like command(), but additionally blocks the parent widget like if there was
++ a modal dialog shown and enters the event loop (i.e. there are still paint updates,
++ this is for commands that take long). */
++ static bool commandBlockUi( const nsTArray<nsCString>& command, GtkWindow* parent, nsTArray<nsCString>* output = NULL );
++
++ private:
++ nsKDEUtils();
++ ~nsKDEUtils();
++ static nsKDEUtils* self();
++ bool startHelper();
++ void closeHelper();
++ void feedCommand( const nsTArray<nsCString>& command );
++ bool internalCommand( const nsTArray<nsCString>& command, GtkWindow* parent, bool isParent,
++ nsTArray<nsCString>* output );
++ FILE* commandFile;
++ FILE* replyFile;
++ };
++
++#endif // nsKDEUtils
+diff --git a/uriloader/exthandler/HandlerServiceParent.cpp b/uriloader/exthandler/HandlerServiceParent.cpp
+index dbcc95d95646..5f2542a95065 100644
+--- a/uriloader/exthandler/HandlerServiceParent.cpp
++++ b/uriloader/exthandler/HandlerServiceParent.cpp
+@@ -12,7 +12,7 @@
+ #include "ContentHandlerService.h"
+ #include "nsStringEnumerator.h"
+ #ifdef MOZ_WIDGET_GTK
+-# include "unix/nsGNOMERegistry.h"
++# include "unix/nsCommonRegistry.h"
+ #endif
+
+ using mozilla::dom::ContentHandlerService;
+@@ -304,7 +304,7 @@ mozilla::ipc::IPCResult HandlerServiceParent::RecvExistsForProtocolOS(
+ }
+ #ifdef MOZ_WIDGET_GTK
+ // Check the GNOME registry for a protocol handler
+- *aHandlerExists = nsGNOMERegistry::HandlerExists(aProtocolScheme.get());
++ *aHandlerExists = nsCommonRegistry::HandlerExists(aProtocolScheme.get());
+ #else
+ *aHandlerExists = false;
+ #endif
+diff --git a/uriloader/exthandler/moz.build b/uriloader/exthandler/moz.build
+index 92647a9b3478..fc5068cd2069 100644
+--- a/uriloader/exthandler/moz.build
++++ b/uriloader/exthandler/moz.build
+@@ -83,7 +83,9 @@ else:
+
+ if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
+ UNIFIED_SOURCES += [
++ "unix/nsCommonRegistry.cpp",
+ "unix/nsGNOMERegistry.cpp",
++ "unix/nsKDERegistry.cpp",
+ "unix/nsMIMEInfoUnix.cpp",
+ ]
+ elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "android":
+@@ -135,6 +137,7 @@ LOCAL_INCLUDES += [
+ "/dom/ipc",
+ "/netwerk/base",
+ "/netwerk/protocol/http",
++ "/toolkit/xre",
+ ]
+
+ if CONFIG["MOZ_ENABLE_DBUS"]:
+diff --git a/uriloader/exthandler/unix/nsCommonRegistry.cpp b/uriloader/exthandler/unix/nsCommonRegistry.cpp
+new file mode 100644
+index 000000000000..630ab6147db3
+--- /dev/null
++++ b/uriloader/exthandler/unix/nsCommonRegistry.cpp
+@@ -0,0 +1,53 @@
++/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
++/* This Source Code Form is subject to the terms of the Mozilla Public
++ * License, v. 2.0. If a copy of the MPL was not distributed with this
++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
++
++#include "nsCommonRegistry.h"
++
++#include "nsGNOMERegistry.h"
++#include "nsKDERegistry.h"
++#include "nsString.h"
++#include "nsKDEUtils.h"
++
++/* static */ bool
++nsCommonRegistry::HandlerExists(const char *aProtocolScheme)
++{
++ if( nsKDEUtils::kdeSupport())
++ return nsKDERegistry::HandlerExists( aProtocolScheme );
++ return nsGNOMERegistry::HandlerExists( aProtocolScheme );
++}
++
++/* static */ nsresult
++nsCommonRegistry::LoadURL(nsIURI *aURL)
++{
++ if( nsKDEUtils::kdeSupport())
++ return nsKDERegistry::LoadURL( aURL );
++ return nsGNOMERegistry::LoadURL( aURL );
++}
++
++/* static */ void
++nsCommonRegistry::GetAppDescForScheme(const nsACString& aScheme,
++ nsAString& aDesc)
++{
++ if( nsKDEUtils::kdeSupport())
++ return nsKDERegistry::GetAppDescForScheme( aScheme, aDesc );
++ return nsGNOMERegistry::GetAppDescForScheme( aScheme, aDesc );
++}
++
++
++/* static */ already_AddRefed<nsMIMEInfoBase>
++nsCommonRegistry::GetFromExtension(const nsACString& aFileExt)
++{
++ if( nsKDEUtils::kdeSupport())
++ return nsKDERegistry::GetFromExtension( aFileExt );
++ return nsGNOMERegistry::GetFromExtension( aFileExt );
++}
++
++/* static */ already_AddRefed<nsMIMEInfoBase>
++nsCommonRegistry::GetFromType(const nsACString& aMIMEType)
++{
++ if( nsKDEUtils::kdeSupport())
++ return nsKDERegistry::GetFromType( aMIMEType );
++ return nsGNOMERegistry::GetFromType( aMIMEType );
++}
+diff --git a/uriloader/exthandler/unix/nsCommonRegistry.h b/uriloader/exthandler/unix/nsCommonRegistry.h
+new file mode 100644
+index 000000000000..85b3d9cee25e
+--- /dev/null
++++ b/uriloader/exthandler/unix/nsCommonRegistry.h
+@@ -0,0 +1,28 @@
++/* This Source Code Form is subject to the terms of the Mozilla Public
++ * License, v. 2.0. If a copy of the MPL was not distributed with this
++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
++
++#ifndef nsCommonRegistry_h__
++#define nsCommonRegistry_h__
++
++#include "nsIURI.h"
++#include "nsCOMPtr.h"
++
++class nsMIMEInfoBase;
++
++class nsCommonRegistry
++{
++ public:
++ static bool HandlerExists(const char *aProtocolScheme);
++
++ static nsresult LoadURL(nsIURI *aURL);
++
++ static void GetAppDescForScheme(const nsACString& aScheme,
++ nsAString& aDesc);
++
++ static already_AddRefed<nsMIMEInfoBase> GetFromExtension(const nsACString& aFileExt);
++
++ static already_AddRefed<nsMIMEInfoBase> GetFromType(const nsACString& aMIMEType);
++};
++
++#endif
+diff --git a/uriloader/exthandler/unix/nsKDERegistry.cpp b/uriloader/exthandler/unix/nsKDERegistry.cpp
+new file mode 100644
+index 000000000000..f78e64c7e9a3
+--- /dev/null
++++ b/uriloader/exthandler/unix/nsKDERegistry.cpp
+@@ -0,0 +1,89 @@
++/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
++/* This Source Code Form is subject to the terms of the Mozilla Public
++ * License, v. 2.0. If a copy of the MPL was not distributed with this
++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
++
++#include "mozilla/StaticPrefs_browser.h"
++#include "nsKDERegistry.h"
++#include "prlink.h"
++#include "prmem.h"
++#include "nsString.h"
++#include "nsMIMEInfoUnix.h"
++#include "nsKDEUtils.h"
++
++/* static */ bool
++nsKDERegistry::HandlerExists(const char *aProtocolScheme)
++{
++ nsTArray<nsCString> command;
++ command.AppendElement( "HANDLEREXISTS"_ns );
++ command.AppendElement( nsAutoCString( aProtocolScheme ));
++ return nsKDEUtils::command( command );
++}
++
++/* static */ nsresult
++nsKDERegistry::LoadURL(nsIURI *aURL)
++{
++ nsTArray<nsCString> command;
++ command.AppendElement( "OPEN"_ns );
++ nsCString url;
++ aURL->GetSpec( url );
++ command.AppendElement( url );
++ bool rv = nsKDEUtils::command( command );
++ if (!rv)
++ return NS_ERROR_FAILURE;
++
++ return NS_OK;
++}
++
++/* static */ void
++nsKDERegistry::GetAppDescForScheme(const nsACString& aScheme,
++ nsAString& aDesc)
++{
++ nsTArray<nsCString> command;
++ command.AppendElement( "GETAPPDESCFORSCHEME"_ns );
++ command.AppendElement( aScheme );
++ nsTArray<nsCString> output;
++ if( nsKDEUtils::command( command, &output ) && output.Length() == 1 )
++ CopyUTF8toUTF16( output[ 0 ], aDesc );
++}
++
++
++/* static */ already_AddRefed<nsMIMEInfoBase>
++nsKDERegistry::GetFromExtension(const nsACString& aFileExt)
++{
++ NS_ASSERTION(aFileExt[0] != '.', "aFileExt shouldn't start with a dot");
++ nsTArray<nsCString> command;
++ command.AppendElement( "GETFROMEXTENSION"_ns );
++ command.AppendElement( aFileExt );
++ return GetFromHelper( command );
++}
++
++/* static */ already_AddRefed<nsMIMEInfoBase>
++nsKDERegistry::GetFromType(const nsACString& aMIMEType)
++{
++ nsTArray<nsCString> command;
++ command.AppendElement( "GETFROMTYPE"_ns );
++ command.AppendElement( aMIMEType );
++ return GetFromHelper( command );
++}
++
++/* static */ already_AddRefed<nsMIMEInfoBase>
++nsKDERegistry::GetFromHelper(const nsTArray<nsCString>& command)
++{
++ nsTArray<nsCString> output;
++ if( nsKDEUtils::command( command, &output ) && output.Length() == 3 )
++ {
++ nsCString mimetype = output[ 0 ];
++ RefPtr<nsMIMEInfoUnix> mimeInfo = new nsMIMEInfoUnix( mimetype );
++ NS_ENSURE_TRUE(mimeInfo, nullptr);
++ nsCString description = output[ 1 ];
++ mimeInfo->SetDescription(NS_ConvertUTF8toUTF16(description));
++ nsCString handlerAppName = output[ 2 ];
++ mozilla::StaticPrefs::browser_download_improvements_to_download_panel()
++ ? mimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk)
++ : mimeInfo->SetPreferredAction(nsIMIMEInfo::useSystemDefault);
++ mimeInfo->SetDefaultDescription(NS_ConvertUTF8toUTF16(handlerAppName));
++ return mimeInfo.forget();
++ }
++ return nullptr;
++}
+diff --git a/uriloader/exthandler/unix/nsKDERegistry.h b/uriloader/exthandler/unix/nsKDERegistry.h
+new file mode 100644
+index 000000000000..5b07eebc6d62
+--- /dev/null
++++ b/uriloader/exthandler/unix/nsKDERegistry.h
+@@ -0,0 +1,34 @@
++/* This Source Code Form is subject to the terms of the Mozilla Public
++ * License, v. 2.0. If a copy of the MPL was not distributed with this
++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
++
++#ifndef nsKDERegistry_h__
++#define nsKDERegistry_h__
++
++#include "nsIURI.h"
++#include "nsCOMPtr.h"
++#include "nsTArray.h"
++
++class nsMIMEInfoBase;
++//class nsAutoCString;
++//class nsCString;
++
++class nsKDERegistry
++{
++ public:
++ static bool HandlerExists(const char *aProtocolScheme);
++
++ static nsresult LoadURL(nsIURI *aURL);
++
++ static void GetAppDescForScheme(const nsACString& aScheme,
++ nsAString& aDesc);
++
++ static already_AddRefed<nsMIMEInfoBase> GetFromExtension(const nsACString& aFileExt);
++
++ static already_AddRefed<nsMIMEInfoBase> GetFromType(const nsACString& aMIMEType);
++ private:
++ static already_AddRefed<nsMIMEInfoBase> GetFromHelper(const nsTArray<nsCString>& command);
++
++};
++
++#endif //nsKDERegistry_h__
+diff --git a/uriloader/exthandler/unix/nsMIMEInfoUnix.cpp b/uriloader/exthandler/unix/nsMIMEInfoUnix.cpp
+index 7cbefcce3e94..84083348c8f1 100644
+--- a/uriloader/exthandler/unix/nsMIMEInfoUnix.cpp
++++ b/uriloader/exthandler/unix/nsMIMEInfoUnix.cpp
+@@ -5,16 +5,19 @@
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+ #include "nsMIMEInfoUnix.h"
+-#include "nsGNOMERegistry.h"
++#include "nsCommonRegistry.h"
+ #include "nsIGIOService.h"
+ #include "nsNetCID.h"
+ #include "nsIIOService.h"
+ #ifdef MOZ_ENABLE_DBUS
+ # include "nsDBusHandlerApp.h"
+ #endif
++#if defined(XP_UNIX) && !defined(XP_MACOSX)
++#include "nsKDEUtils.h"
++#endif
+
+ nsresult nsMIMEInfoUnix::LoadUriInternal(nsIURI* aURI) {
+- return nsGNOMERegistry::LoadURL(aURI);
++ return nsCommonRegistry::LoadURL(aURI);
+ }
+
+ NS_IMETHODIMP
+@@ -27,15 +30,15 @@ nsMIMEInfoUnix::GetHasDefaultHandler(bool* _retval) {
+ *_retval = false;
+
+ if (mClass == eProtocolInfo) {
+- *_retval = nsGNOMERegistry::HandlerExists(mSchemeOrType.get());
++ *_retval = nsCommonRegistry::HandlerExists(mSchemeOrType.get());
+ } else {
+ RefPtr<nsMIMEInfoBase> mimeInfo =
+- nsGNOMERegistry::GetFromType(mSchemeOrType);
++ nsCommonRegistry::GetFromType(mSchemeOrType);
+ if (!mimeInfo) {
+ nsAutoCString ext;
+ nsresult rv = GetPrimaryExtension(ext);
+ if (NS_SUCCEEDED(rv)) {
+- mimeInfo = nsGNOMERegistry::GetFromExtension(ext);
++ mimeInfo = nsCommonRegistry::GetFromExtension(ext);
+ }
+ }
+ if (mimeInfo) *_retval = true;
+@@ -55,6 +58,23 @@ nsresult nsMIMEInfoUnix::LaunchDefaultWithFile(nsIFile* aFile) {
+ nsAutoCString nativePath;
+ aFile->GetNativePath(nativePath);
+
++ if( nsKDEUtils::kdeSupport()) {
++ bool supports;
++ if( NS_SUCCEEDED( GetHasDefaultHandler( &supports )) && supports ) {
++ nsTArray<nsCString> command;
++ command.AppendElement( "OPEN"_ns );
++ command.AppendElement( nativePath );
++ command.AppendElement( "MIMETYPE"_ns );
++ command.AppendElement( mSchemeOrType );
++ if( nsKDEUtils::command( command ))
++ return NS_OK;
++ }
++ if (!mDefaultApplication)
++ return NS_ERROR_FILE_NOT_FOUND;
++
++ return LaunchWithIProcess(mDefaultApplication, nativePath);
++ }
++
+ nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
+ if (!giovfs) {
+ return NS_ERROR_FAILURE;
+diff --git a/uriloader/exthandler/unix/nsOSHelperAppService.cpp b/uriloader/exthandler/unix/nsOSHelperAppService.cpp
+index b9e7aed3cb5c..367ad9ee2421 100644
+--- a/uriloader/exthandler/unix/nsOSHelperAppService.cpp
++++ b/uriloader/exthandler/unix/nsOSHelperAppService.cpp
+@@ -10,7 +10,7 @@
+ #include "nsOSHelperAppService.h"
+ #include "nsMIMEInfoUnix.h"
+ #ifdef MOZ_WIDGET_GTK
+-# include "nsGNOMERegistry.h"
++# include "nsCommonRegistry.h"
+ # ifdef MOZ_BUILD_APP_IS_BROWSER
+ # include "nsIToolkitShellService.h"
+ # include "nsIGNOMEShellService.h"
+@@ -1030,7 +1030,7 @@ nsresult nsOSHelperAppService::OSProtocolHandlerExists(
+ if (!XRE_IsContentProcess()) {
+ #ifdef MOZ_WIDGET_GTK
+ // Check the GNOME registry for a protocol handler
+- *aHandlerExists = nsGNOMERegistry::HandlerExists(aProtocolScheme);
++ *aHandlerExists = nsCommonRegistry::HandlerExists(aProtocolScheme);
+ #else
+ *aHandlerExists = false;
+ #endif
+@@ -1050,7 +1050,7 @@ nsresult nsOSHelperAppService::OSProtocolHandlerExists(
+ NS_IMETHODIMP nsOSHelperAppService::GetApplicationDescription(
+ const nsACString& aScheme, nsAString& _retval) {
+ #ifdef MOZ_WIDGET_GTK
+- nsGNOMERegistry::GetAppDescForScheme(aScheme, _retval);
++ nsCommonRegistry::GetAppDescForScheme(aScheme, _retval);
+ return _retval.IsEmpty() ? NS_ERROR_NOT_AVAILABLE : NS_OK;
+ #else
+ return NS_ERROR_NOT_AVAILABLE;
+@@ -1153,7 +1153,7 @@ already_AddRefed<nsMIMEInfoBase> nsOSHelperAppService::GetFromExtension(
+ #ifdef MOZ_WIDGET_GTK
+ LOG(("Looking in GNOME registry\n"));
+ RefPtr<nsMIMEInfoBase> gnomeInfo =
+- nsGNOMERegistry::GetFromExtension(aFileExt);
++ nsCommonRegistry::GetFromExtension(aFileExt);
+ if (gnomeInfo) {
+ LOG(("Got MIMEInfo from GNOME registry\n"));
+ return gnomeInfo.forget();
+@@ -1266,7 +1266,7 @@ already_AddRefed<nsMIMEInfoBase> nsOSHelperAppService::GetFromType(
+
+ #ifdef MOZ_WIDGET_GTK
+ if (handler.IsEmpty()) {
+- RefPtr<nsMIMEInfoBase> gnomeInfo = nsGNOMERegistry::GetFromType(aMIMEType);
++ RefPtr<nsMIMEInfoBase> gnomeInfo = nsCommonRegistry::GetFromType(aMIMEType);
+ if (gnomeInfo) {
+ LOG(
+ ("Got MIMEInfo from GNOME registry without extensions; setting them "
+diff --git a/widget/gtk/moz.build b/widget/gtk/moz.build
+index bf64f7ebdc65..f94b4e017e5e 100644
+--- a/widget/gtk/moz.build
++++ b/widget/gtk/moz.build
+@@ -169,6 +169,7 @@ LOCAL_INCLUDES += [
+ "/layout/xul",
+ "/other-licenses/atk-1.0",
+ "/third_party/cups/include",
++ "/toolkit/xre",
+ "/widget",
+ "/widget/headless",
+ ]
+diff --git a/widget/gtk/nsFilePicker.cpp b/widget/gtk/nsFilePicker.cpp
+index c73130496ae7..9e7b9aed56f1 100644
+--- a/widget/gtk/nsFilePicker.cpp
++++ b/widget/gtk/nsFilePicker.cpp
+@@ -5,6 +5,7 @@
+
+ #include <dlfcn.h>
+ #include <gtk/gtk.h>
++#include <gdk/gdkx.h>
+ #include <sys/types.h>
+ #include <sys/stat.h>
+ #include <unistd.h>
+@@ -27,6 +28,8 @@
+ #include "WidgetUtilsGtk.h"
+
+ #include "nsFilePicker.h"
++#include "nsKDEUtils.h"
++#include "nsURLHelper.h"
+
+ #undef LOG
+ #ifdef MOZ_LOGGING
+@@ -241,7 +244,9 @@ NS_IMETHODIMP
+ nsFilePicker::AppendFilter(const nsAString& aTitle, const nsAString& aFilter) {
+ if (aFilter.EqualsLiteral("..apps")) {
+ // No platform specific thing we can do here, really....
+- return NS_OK;
++ // Unless it's KDE.
++ if( mMode != modeOpen || !nsKDEUtils::kdeSupport())
++ return NS_OK;
+ }
+
+ nsAutoCString filter, name;
+@@ -351,6 +356,29 @@ nsFilePicker::Open(nsIFilePickerShownCallback* aCallback) {
+ // Can't show two dialogs concurrently with the same filepicker
+ if (mRunning) return NS_ERROR_NOT_AVAILABLE;
+
++ // KDE file picker is not handled via callback
++ if( nsKDEUtils::kdeSupport()) {
++ mCallback = aCallback;
++ mRunning = true;
++ NS_ADDREF_THIS();
++ g_idle_add([](gpointer data) -> gboolean {
++ nsFilePicker* queuedPicker = (nsFilePicker*) data;
++ int16_t result;
++ queuedPicker->kdeFileDialog(&result);
++ if (queuedPicker->mCallback) {
++ queuedPicker->mCallback->Done(result);
++ queuedPicker->mCallback = nullptr;
++ } else {
++ queuedPicker->mResult = result;
++ }
++ queuedPicker->mRunning = false;
++ NS_RELEASE(queuedPicker);
++ return G_SOURCE_REMOVE;
++ }, this);
++
++ return NS_OK;
++ }
++
+ NS_ConvertUTF16toUTF8 title(mTitle);
+
+ GtkWindow* parent_widget =
+@@ -580,6 +608,234 @@ void nsFilePicker::Done(void* file_chooser, gint response) {
+ NS_RELEASE_THIS();
+ }
+
++nsCString nsFilePicker::kdeMakeFilter( int index )
++ {
++ nsCString buf = mFilters[ index ];
++ for( PRUint32 i = 0;
++ i < buf.Length();
++ ++i )
++ if( buf[ i ] == ';' ) // KDE separates just using spaces
++ buf.SetCharAt( ' ', i );
++ if (!mFilterNames[index].IsEmpty())
++ {
++ buf += "|";
++ buf += mFilterNames[index].get();
++ }
++ return buf;
++ }
++
++static PRInt32 windowToXid( nsIWidget* widget )
++ {
++ GtkWindow *parent_widget = GTK_WINDOW(widget->GetNativeData(NS_NATIVE_SHELLWIDGET));
++ GdkWindow* gdk_window = gtk_widget_get_window( gtk_widget_get_toplevel( GTK_WIDGET( parent_widget )));
++ return GDK_WINDOW_XID( gdk_window );
++ }
++
++NS_IMETHODIMP nsFilePicker::kdeFileDialog(PRInt16 *aReturn)
++ {
++ NS_ENSURE_ARG_POINTER(aReturn);
++
++ if( mMode == modeOpen && mFilters.Length() == 1 && mFilters[ 0 ].EqualsLiteral( "..apps" ))
++ return kdeAppsDialog( aReturn );
++
++ nsCString title;
++ title.Adopt(ToNewUTF8String(mTitle));
++
++ const char* arg = NULL;
++ if( mAllowURLs )
++ {
++ switch( mMode )
++ {
++ case nsIFilePicker::modeOpen:
++ case nsIFilePicker::modeOpenMultiple:
++ arg = "GETOPENURL";
++ break;
++ case nsIFilePicker::modeSave:
++ arg = "GETSAVEURL";
++ break;
++ case nsIFilePicker::modeGetFolder:
++ arg = "GETDIRECTORYURL";
++ break;
++ }
++ }
++ else
++ {
++ switch( mMode )
++ {
++ case nsIFilePicker::modeOpen:
++ case nsIFilePicker::modeOpenMultiple:
++ arg = "GETOPENFILENAME";
++ break;
++ case nsIFilePicker::modeSave:
++ arg = "GETSAVEFILENAME";
++ break;
++ case nsIFilePicker::modeGetFolder:
++ arg = "GETDIRECTORYFILENAME";
++ break;
++ }
++ }
++
++ nsAutoCString directory;
++ if (mDisplayDirectory) {
++ mDisplayDirectory->GetNativePath(directory);
++ } else if (mPrevDisplayDirectory) {
++ mPrevDisplayDirectory->GetNativePath(directory);
++ }
++
++ nsAutoCString startdir;
++ if (!directory.IsEmpty()) {
++ startdir = directory;
++ }
++ if (mMode == nsIFilePicker::modeSave) {
++ if( !startdir.IsEmpty())
++ {
++ startdir += "/";
++ startdir += ToNewUTF8String(mDefault);
++ }
++ else
++ startdir = ToNewUTF8String(mDefault);
++ }
++
++ nsAutoCString filters;
++ PRInt32 count = mFilters.Length();
++ if( count == 0 ) //just in case
++ filters = "*";
++ else
++ {
++ filters = kdeMakeFilter( 0 );
++ for (PRInt32 i = 1; i < count; ++i)
++ {
++ filters += "\n";
++ filters += kdeMakeFilter( i );
++ }
++ }
++
++ nsTArray<nsCString> command;
++ command.AppendElement( nsAutoCString( arg ));
++ command.AppendElement( startdir );
++ if( mMode != nsIFilePicker::modeGetFolder )
++ {
++ command.AppendElement( filters );
++ nsAutoCString selected;
++ selected.AppendInt( mSelectedType );
++ command.AppendElement( selected );
++ }
++ command.AppendElement( title );
++ if( mMode == nsIFilePicker::modeOpenMultiple )
++ command.AppendElement( "MULTIPLE"_ns );
++ if( PRInt32 xid = windowToXid( mParentWidget ))
++ {
++ command.AppendElement( "PARENT"_ns );
++ nsAutoCString parent;
++ parent.AppendInt( xid );
++ command.AppendElement( parent );
++ }
++
++ nsTArray<nsCString> output;
++ if( nsKDEUtils::commandBlockUi( command, GTK_WINDOW(mParentWidget->GetNativeData(NS_NATIVE_SHELLWIDGET)), &output ))
++ {
++ *aReturn = nsIFilePicker::returnOK;
++ mFiles.Clear();
++ if( mMode != nsIFilePicker::modeGetFolder )
++ {
++ mSelectedType = atoi( output[ 0 ].get());
++ output.RemoveElementAt( 0 );
++ }
++ if (mMode == nsIFilePicker::modeOpenMultiple)
++ {
++ mFileURL.Truncate();
++ PRUint32 count = output.Length();
++ for( PRUint32 i = 0;
++ i < count;
++ ++i )
++ {
++ nsCOMPtr<nsIFile> localfile;
++ nsresult rv = NS_NewNativeLocalFile( output[ i ],
++ PR_FALSE,
++ getter_AddRefs(localfile));
++ if (NS_SUCCEEDED(rv))
++ mFiles.AppendObject(localfile);
++ }
++ }
++ else
++ {
++ if( output.Length() == 0 )
++ mFileURL = nsCString();
++ else if( mAllowURLs )
++ mFileURL = output[ 0 ];
++ else // GetFile() actually requires it to be url even for local files :-/
++ {
++ nsCOMPtr<nsIFile> localfile;
++ nsresult rv = NS_NewNativeLocalFile( output[ 0 ],
++ PR_FALSE,
++ getter_AddRefs(localfile));
++ if (NS_SUCCEEDED(rv))
++ rv = net_GetURLSpecFromActualFile(localfile, mFileURL);
++ }
++ }
++ // Remember last used directory.
++ nsCOMPtr<nsIFile> file;
++ GetFile(getter_AddRefs(file));
++ if (file) {
++ nsCOMPtr<nsIFile> dir;
++ file->GetParent(getter_AddRefs(dir));
++ nsCOMPtr<nsIFile> localDir(do_QueryInterface(dir));
++ if (localDir) {
++ localDir.swap(mPrevDisplayDirectory);
++ }
++ }
++ if (mMode == nsIFilePicker::modeSave)
++ {
++ nsCOMPtr<nsIFile> file;
++ GetFile(getter_AddRefs(file));
++ if (file)
++ {
++ bool exists = false;
++ file->Exists(&exists);
++ if (exists) // TODO do overwrite check in the helper app
++ *aReturn = nsIFilePicker::returnReplace;
++ }
++ }
++ }
++ else
++ {
++ *aReturn = nsIFilePicker::returnCancel;
++ }
++ return NS_OK;
++ }
++
++
++NS_IMETHODIMP nsFilePicker::kdeAppsDialog(PRInt16 *aReturn)
++ {
++ NS_ENSURE_ARG_POINTER(aReturn);
++
++ nsCString title;
++ title.Adopt(ToNewUTF8String(mTitle));
++
++ nsTArray<nsCString> command;
++ command.AppendElement( "APPSDIALOG"_ns );
++ command.AppendElement( title );
++ if( PRInt32 xid = windowToXid( mParentWidget ))
++ {
++ command.AppendElement( "PARENT"_ns );
++ nsAutoCString parent;
++ parent.AppendInt( xid );
++ command.AppendElement( parent );
++ }
++
++ nsTArray<nsCString> output;
++ if( nsKDEUtils::commandBlockUi( command, GTK_WINDOW(mParentWidget->GetNativeData(NS_NATIVE_SHELLWIDGET)), &output ))
++ {
++ *aReturn = nsIFilePicker::returnOK;
++ mFileURL = output.Length() > 0 ? output[ 0 ] : nsCString();
++ }
++ else
++ {
++ *aReturn = nsIFilePicker::returnCancel;
++ }
++ return NS_OK;
++ }
++
+ // All below functions available as of GTK 3.20+
+ void* nsFilePicker::GtkFileChooserNew(const gchar* title, GtkWindow* parent,
+ GtkFileChooserAction action,
+diff --git a/widget/gtk/nsFilePicker.h b/widget/gtk/nsFilePicker.h
+index 9b3110aa0048..be9d559c7bf4 100644
+--- a/widget/gtk/nsFilePicker.h
++++ b/widget/gtk/nsFilePicker.h
+@@ -72,6 +72,12 @@ class nsFilePicker : public nsBaseFilePicker {
+ private:
+ static nsIFile* mPrevDisplayDirectory;
+
++ bool kdeRunning();
++ bool getKdeRunning();
++ NS_IMETHODIMP kdeFileDialog(PRInt16 *aReturn);
++ NS_IMETHODIMP kdeAppsDialog(PRInt16 *aReturn);
++ nsCString kdeMakeFilter( int index );
++
+ void* GtkFileChooserNew(const gchar* title, GtkWindow* parent,
+ GtkFileChooserAction action,
+ const gchar* accept_label);
+diff --git a/xpcom/components/ManifestParser.cpp b/xpcom/components/ManifestParser.cpp
+index f3d0055f2cc6..d13543ab5221 100644
+--- a/xpcom/components/ManifestParser.cpp
++++ b/xpcom/components/ManifestParser.cpp
+@@ -43,6 +43,7 @@
+ #include "nsIScriptError.h"
+ #include "nsIXULAppInfo.h"
+ #include "nsIXULRuntime.h"
++#include "nsKDEUtils.h"
+
+ using namespace mozilla;
+
+@@ -402,6 +403,7 @@ void ParseManifest(NSLocationType aType, FileLocation& aFile, char* aBuf,
+ constexpr auto kOs = u"os"_ns;
+ constexpr auto kOsVersion = u"osversion"_ns;
+ constexpr auto kABI = u"abi"_ns;
++ constexpr auto kDesktop = u"desktop"_ns;
+ constexpr auto kProcess = u"process"_ns;
+ #if defined(MOZ_WIDGET_ANDROID)
+ constexpr auto kTablet = u"tablet"_ns;
+@@ -461,6 +463,7 @@ void ParseManifest(NSLocationType aType, FileLocation& aFile, char* aBuf,
+ }
+
+ nsAutoString osVersion;
++ nsAutoString desktop;
+ #if defined(XP_WIN)
+ # pragma warning(push)
+ # pragma warning(disable : 4996) // VC12+ deprecates GetVersionEx
+@@ -469,14 +472,17 @@ void ParseManifest(NSLocationType aType, FileLocation& aFile, char* aBuf,
+ nsTextFormatter::ssprintf(osVersion, u"%ld.%ld", info.dwMajorVersion,
+ info.dwMinorVersion);
+ }
++ desktop = u"win"_ns;
+ # pragma warning(pop)
+ #elif defined(MOZ_WIDGET_COCOA)
+ SInt32 majorVersion = nsCocoaFeatures::macOSVersionMajor();
+ SInt32 minorVersion = nsCocoaFeatures::macOSVersionMinor();
+ nsTextFormatter::ssprintf(osVersion, u"%ld.%ld", majorVersion, minorVersion);
++ desktop = u"macosx"_ns);
+ #elif defined(MOZ_WIDGET_GTK)
+ nsTextFormatter::ssprintf(osVersion, u"%ld.%ld", gtk_major_version,
+ gtk_minor_version);
++ desktop = nsKDEUtils::kdeSession() ? u"kde"_ns : u"gnome"_ns;
+ #elif defined(MOZ_WIDGET_ANDROID)
+ bool isTablet = false;
+ if (mozilla::AndroidBridge::Bridge()) {
+@@ -484,6 +490,7 @@ void ParseManifest(NSLocationType aType, FileLocation& aFile, char* aBuf,
+ "android/os/Build$VERSION", "RELEASE", osVersion);
+ isTablet = java::GeckoAppShell::IsTablet();
+ }
++ desktop = u"android"_ns;
+ #endif
+
+ if (XRE_IsContentProcess()) {
+@@ -588,6 +595,7 @@ void ParseManifest(NSLocationType aType, FileLocation& aFile, char* aBuf,
+ : eUnspecified;
+ #endif
+ int flags = 0;
++ TriState stDesktop = eUnspecified;
+
+ while ((token = nsCRT::strtok(whitespace, kWhitespace, &whitespace)) &&
+ ok) {
+@@ -597,6 +605,7 @@ void ParseManifest(NSLocationType aType, FileLocation& aFile, char* aBuf,
+ if (CheckStringFlag(kApplication, wtoken, appID, stApp) ||
+ CheckOsFlag(kOs, wtoken, osTarget, stOs) ||
+ CheckStringFlag(kABI, wtoken, abi, stABI) ||
++ CheckStringFlag(kDesktop, wtoken, desktop, stDesktop) ||
+ CheckStringFlag(kProcess, wtoken, process, stProcess) ||
+ CheckVersionFlag(kOsVersion, wtoken, osVersion, stOsVersion) ||
+ CheckVersionFlag(kAppVersion, wtoken, appVersion, stAppVersion) ||
+@@ -655,7 +664,7 @@ void ParseManifest(NSLocationType aType, FileLocation& aFile, char* aBuf,
+ }
+
+ if (!ok || stApp == eBad || stAppVersion == eBad ||
+- stGeckoVersion == eBad || stOs == eBad || stOsVersion == eBad ||
++ stGeckoVersion == eBad || stOs == eBad || stOsVersion == eBad || stDesktop == eBad ||
+ #ifdef MOZ_WIDGET_ANDROID
+ stTablet == eBad ||
+ #endif
+diff --git a/xpcom/components/moz.build b/xpcom/components/moz.build
+index 6cf78aa9bec5..dfcf9c7697af 100644
+--- a/xpcom/components/moz.build
++++ b/xpcom/components/moz.build
+@@ -71,6 +71,7 @@ LOCAL_INCLUDES += [
+ "/js/xpconnect/loader",
+ "/layout/build",
+ "/modules/libjar",
++ "/toolkit/xre",
+ ]
+
+ if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
+diff --git a/xpcom/io/nsLocalFileUnix.cpp b/xpcom/io/nsLocalFileUnix.cpp
+index 410fcc19e435..d7c976e0e4b2 100644
+--- a/xpcom/io/nsLocalFileUnix.cpp
++++ b/xpcom/io/nsLocalFileUnix.cpp
+@@ -59,6 +59,7 @@
+
+ #ifdef MOZ_WIDGET_GTK
+ # include "nsIGIOService.h"
++# include "nsKDEUtils.h"
+ #endif
+
+ #ifdef MOZ_WIDGET_COCOA
+@@ -2102,10 +2103,19 @@ nsLocalFile::Reveal() {
+ }
+
+ #ifdef MOZ_WIDGET_GTK
++ nsAutoCString url;
+ nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
+- if (!giovfs) {
+- return NS_ERROR_FAILURE;
++ url = mPath;
++ if(nsKDEUtils::kdeSupport()) {
++ nsTArray<nsCString> command;
++ command.AppendElement( "REVEAL"_ns );
++ command.AppendElement( mPath );
++ return nsKDEUtils::command( command ) ? NS_OK : NS_ERROR_FAILURE;
+ }
++
++ if (!giovfs)
++ return NS_ERROR_FAILURE;
++
+ return giovfs->RevealFile(this);
+ #elif defined(MOZ_WIDGET_COCOA)
+ CFURLRef url;
+@@ -2127,6 +2137,13 @@ nsLocalFile::Launch() {
+ }
+
+ #ifdef MOZ_WIDGET_GTK
++ if( nsKDEUtils::kdeSupport()) {
++ nsTArray<nsCString> command;
++ command.AppendElement( "OPEN"_ns );
++ command.AppendElement( mPath );
++ return nsKDEUtils::command( command ) ? NS_OK : NS_ERROR_FAILURE;
++ }
++
+ nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
+ if (!giovfs) {
+ return NS_ERROR_FAILURE;
+--
+2.37.3
+
diff --git a/waterfox-g/debian/patches/global_menu.patch b/waterfox-g/debian/patches/global_menu.patch
new file mode 100644
index 0000000..9f9d487
--- /dev/null
+++ b/waterfox-g/debian/patches/global_menu.patch
@@ -0,0 +1,5382 @@
+Source: https://bazaar.launchpad.net/~mozillateam/firefox/firefox.bionic/view/1484/debian/patches/unity-menubar.patch
+Author: Olivier Tilloy <olivier.tilloy@canonical.com>
+
+diff --git a/browser/base/content/browser-menubar.inc b/browser/base/content/browser-menubar.inc
+index 1adea92456c7..9cb91a4befdf 100644
+--- a/browser/base/content/browser-menubar.inc
++++ b/browser/base/content/browser-menubar.inc
+@@ -7,7 +7,12 @@
+ # On macOS, we don't track whether activation of the native menubar happened
+ # with the keyboard.
+ #ifndef XP_MACOSX
+- onpopupshowing="if (event.target.parentNode.parentNode == this)
++ onpopupshowing="if (event.target.parentNode.parentNode == this &amp;&amp;
++#ifdef MOZ_WIDGET_GTK
++ document.documentElement.getAttribute('shellshowingmenubar') != 'true')
++#else
++ true)
++#endif
+ this.setAttribute('openedwithkey',
+ event.target.parentNode.openedWithKey);"
+ #endif
+diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js
+index d72ed6495612..eb4af8d23195 100644
+--- a/browser/base/content/browser.js
++++ b/browser/base/content/browser.js
+@@ -6481,11 +6481,18 @@ function onViewToolbarsPopupShowing(aEvent, aInsertPoint) {
+ MozXULElement.insertFTLIfNeeded("browser/toolbarContextMenu.ftl");
+ let firstMenuItem = aInsertPoint || popup.firstElementChild;
+ let toolbarNodes = gNavToolbox.querySelectorAll("toolbar");
++
++ let shellShowingMenubar = document.documentElement.getAttribute("shellshowingmenubar") == "true";
++
+ for (let toolbar of toolbarNodes) {
+ if (!toolbar.hasAttribute("toolbarname")) {
+ continue;
+ }
+
++ if (shellShowingMenubar && toolbar.id == "toolbar-menubar") {
++ continue;
++ }
++
+ if (toolbar.id == "PersonalToolbar") {
+ let menu = BookmarkingUI.buildBookmarksToolbarSubmenu(toolbar);
+ popup.insertBefore(menu, firstMenuItem);
+diff --git a/browser/components/places/content/places.xhtml b/browser/components/places/content/places.xhtml
+index e19a58029828..4271915765c6 100644
+--- a/browser/components/places/content/places.xhtml
++++ b/browser/components/places/content/places.xhtml
+@@ -166,6 +166,7 @@
+ #else
+ <menubar id="placesMenu">
+ <menu class="menu-iconic" data-l10n-id="places-organize-button"
++ _moz-menubarkeeplocal="true"
+ #endif
+ id="organizeButton">
+ <menupopup id="organizeButtonPopup">
+diff --git a/dom/xul/XULPopupElement.cpp b/dom/xul/XULPopupElement.cpp
+index b6a50a3e2408..cda8315b0ef8 100644
+--- a/dom/xul/XULPopupElement.cpp
++++ b/dom/xul/XULPopupElement.cpp
+@@ -207,6 +207,10 @@ void XULPopupElement::GetState(nsString& aState) {
+ // set this here in case there's no frame for the popup
+ aState.AssignLiteral("closed");
+
++#ifdef MOZ_WIDGET_GTK
++ nsAutoString nativeState;
++#endif
++
+ if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) {
+ switch (pm->GetPopupState(this)) {
+ case ePopupShown:
+@@ -229,6 +233,11 @@ void XULPopupElement::GetState(nsString& aState) {
+ break;
+ }
+ }
++#ifdef MOZ_WIDGET_GTK
++ else if (GetAttr(kNameSpaceID_None, nsGkAtoms::_moz_nativemenupopupstate, nativeState)) {
++ aState = nativeState;
++ }
++#endif
+ }
+
+ nsINode* XULPopupElement::GetTriggerNode() const {
+diff --git a/dom/xul/moz.build b/dom/xul/moz.build
+index bb7e4b08d924..42d4bd9b654f 100644
+--- a/dom/xul/moz.build
++++ b/dom/xul/moz.build
+@@ -82,4 +82,9 @@ LOCAL_INCLUDES += [
+
+ include("/ipc/chromium/chromium-config.mozbuild")
+
++if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
++ LOCAL_INCLUDES += [
++ "/widget/gtk",
++ ]
++
+ FINAL_LIBRARY = "xul"
+diff --git a/layout/build/moz.build b/layout/build/moz.build
+index c7869a01a373..2cbdb021b3b7 100644
+--- a/layout/build/moz.build
++++ b/layout/build/moz.build
+@@ -70,6 +70,10 @@ elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "android":
+ "/dom/system",
+ "/dom/system/android",
+ ]
++elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
++ LOCAL_INCLUDES += [
++ "/widget/gtk",
++ ]
+
+ XPCOM_MANIFESTS += [
+ "components.conf",
+diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js
+index 30641d9727cf..c8382f50ab28 100644
+--- a/modules/libpref/init/all.js
++++ b/modules/libpref/init/all.js
+@@ -188,6 +188,9 @@ pref("dom.mouseevent.click.hack.use_legacy_non-primary_dispatch", "");
+ // Fastback caching - if this pref is negative, then we calculate the number
+ // of content viewers to cache based on the amount of available memory.
+ pref("browser.sessionhistory.max_total_viewers", -1);
++#ifdef MOZ_WIDGET_GTK
++pref("ui.use_unity_menubar", true);
++#endif
+
+ pref("browser.display.force_inline_alttext", false); // true = force ALT text for missing images to be layed out inline
+ // 0 = no external leading,
+diff --git a/toolkit/content/xul.css b/toolkit/content/xul.css
+index 70bfc2047c05..6b7938a6a7a5 100644
+--- a/toolkit/content/xul.css
++++ b/toolkit/content/xul.css
+@@ -226,6 +226,13 @@ toolbox {
+ }
+ }
+
++@media (-moz-platform: linux) {
++*|*:root[shellshowingmenubar="true"]
++toolbar[type="menubar"]:not([customizing="true"]) {
++ display: none !important;
++}
++}
++
+ toolbarspring {
+ -moz-box-flex: 1000;
+ }
+diff --git a/widget/gtk/NativeMenuSupport.cpp b/widget/gtk/NativeMenuSupport.cpp
+index 4360867fff3f..c3a69f31b1d3 100644
+--- a/widget/gtk/NativeMenuSupport.cpp
++++ b/widget/gtk/NativeMenuSupport.cpp
+@@ -7,6 +7,8 @@
+
+ #include "MainThreadUtils.h"
+ #include "NativeMenuGtk.h"
++#include "nsINativeMenuService.h"
++#include "nsServiceManagerUtils.h"
+
+ namespace mozilla::widget {
+
+@@ -14,7 +16,14 @@ void NativeMenuSupport::CreateNativeMenuBar(nsIWidget* aParent,
+ dom::Element* aMenuBarElement) {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread(),
+ "Attempting to create native menu bar on wrong thread!");
+- // TODO
++
++ nsCOMPtr<nsINativeMenuService> nms =
++ do_GetService("@mozilla.org/widget/nativemenuservice;1");
++ if (!nms) {
++ return;
++ }
++
++ nms->CreateNativeMenuBar(aParent, aMenuBarElement);
+ }
+
+ already_AddRefed<NativeMenu> NativeMenuSupport::CreateNativeContextMenu(
+diff --git a/widget/gtk/NativeMenuSupport.h b/widget/gtk/NativeMenuSupport.h
+new file mode 100644
+index 000000000000..0843d45185e5
+--- /dev/null
++++ b/widget/gtk/NativeMenuSupport.h
+@@ -0,0 +1,31 @@
++/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
++/* This Source Code Form is subject to the terms of the Mozilla Public
++ * License, v. 2.0. If a copy of the MPL was not distributed with this
++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
++
++#ifndef mozilla_widget_NativeMenuSupport_h
++#define mozilla_widget_NativeMenuSupport_h
++
++class nsIWidget;
++
++namespace mozilla {
++
++namespace dom {
++class Element;
++}
++
++namespace widget {
++
++class NativeMenuSupport final {
++public:
++ // Given a top-level window widget and a menu bar DOM node, sets up native
++ // menus. Once created, native menus are controlled via the DOM, including
++ // destruction.
++ static void CreateNativeMenuBar(nsIWidget* aParent,
++ dom::Element* aMenuBarElement);
++};
++
++} // namespace widget
++} // namespace mozilla
++
++#endif // mozilla_widget_NativeMenuSupport_h
+diff --git a/widget/gtk/components.conf b/widget/gtk/components.conf
+index 61714d8b8190..1738093137f2 100644
+--- a/widget/gtk/components.conf
++++ b/widget/gtk/components.conf
+@@ -117,6 +117,14 @@ Classes = [
+ 'headers': ['/widget/gtk/nsUserIdleServiceGTK.h'],
+ 'constructor': 'nsUserIdleServiceGTK::GetInstance',
+ },
++ {
++ 'cid': '{0b3fe5aa-bc72-4303-85ae-76365df1251d}',
++ 'contract_ids': ['@mozilla.org/widget/nativemenuservice;1'],
++ 'singleton': True,
++ 'type': 'nsNativeMenuService',
++ 'constructor': 'nsNativeMenuService::GetInstanceForServiceManager',
++ 'headers': ['/widget/gtk/nsNativeMenuService.h'],
++ },
+ ]
+
+ if defined('NS_PRINTING'):
+diff --git a/widget/gtk/moz.build b/widget/gtk/moz.build
+index 5218c7df06aa..bf64f7ebdc65 100644
+--- a/widget/gtk/moz.build
++++ b/widget/gtk/moz.build
+@@ -77,6 +77,15 @@ UNIFIED_SOURCES += [
+
+ SOURCES += [
+ "MediaKeysEventSourceFactory.cpp",
++ "nsDbusmenu.cpp",
++ "nsMenu.cpp", # conflicts with X11 headers
++ "nsMenuBar.cpp",
++ "nsMenuContainer.cpp",
++ "nsMenuItem.cpp",
++ "nsMenuObject.cpp",
++ "nsMenuSeparator.cpp",
++ "nsNativeMenuDocListener.cpp",
++ "nsNativeMenuService.cpp",
+ "nsNativeThemeGTK.cpp", # conflicts with X11 headers
+ "nsWindow.cpp", # conflicts with X11 headers
+ "WaylandVsyncSource.cpp", # conflicts with X11 headers
+@@ -156,6 +165,7 @@ LOCAL_INCLUDES += [
+ "/layout/base",
+ "/layout/forms",
+ "/layout/generic",
++ "/layout/style",
+ "/layout/xul",
+ "/other-licenses/atk-1.0",
+ "/third_party/cups/include",
+diff --git a/widget/gtk/nsDbusmenu.cpp b/widget/gtk/nsDbusmenu.cpp
+new file mode 100644
+index 000000000000..f3a1c4400bf2
+--- /dev/null
++++ b/widget/gtk/nsDbusmenu.cpp
+@@ -0,0 +1,61 @@
++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:expandtab:shiftwidth=4:tabstop=4:
++ */
++/* This Source Code Form is subject to the terms of the Mozilla Public
++ * License, v. 2.0. If a copy of the MPL was not distributed with this
++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
++
++#include "nsDbusmenu.h"
++#include "prlink.h"
++#include "mozilla/ArrayUtils.h"
++
++#define FUNC(name, type, params) \
++nsDbusmenuFunctions::_##name##_fn nsDbusmenuFunctions::s_##name;
++DBUSMENU_GLIB_FUNCTIONS
++DBUSMENU_GTK_FUNCTIONS
++#undef FUNC
++
++static PRLibrary *gDbusmenuGlib = nullptr;
++static PRLibrary *gDbusmenuGtk = nullptr;
++
++typedef void (*nsDbusmenuFunc)();
++struct nsDbusmenuDynamicFunction {
++ const char *functionName;
++ nsDbusmenuFunc *function;
++};
++
++/* static */ nsresult
++nsDbusmenuFunctions::Init()
++{
++#define FUNC(name, type, params) \
++ { #name, (nsDbusmenuFunc *)&nsDbusmenuFunctions::s_##name },
++ static const nsDbusmenuDynamicFunction kDbusmenuGlibSymbols[] = {
++ DBUSMENU_GLIB_FUNCTIONS
++ };
++ static const nsDbusmenuDynamicFunction kDbusmenuGtkSymbols[] = {
++ DBUSMENU_GTK_FUNCTIONS
++ };
++
++#define LOAD_LIBRARY(symbol, name) \
++ if (!g##symbol) { \
++ g##symbol = PR_LoadLibrary(name); \
++ if (!g##symbol) { \
++ return NS_ERROR_FAILURE; \
++ } \
++ } \
++ for (uint32_t i = 0; i < mozilla::ArrayLength(k##symbol##Symbols); ++i) { \
++ *k##symbol##Symbols[i].function = \
++ PR_FindFunctionSymbol(g##symbol, k##symbol##Symbols[i].functionName); \
++ if (!*k##symbol##Symbols[i].function) { \
++ return NS_ERROR_FAILURE; \
++ } \
++ }
++
++ LOAD_LIBRARY(DbusmenuGlib, "libdbusmenu-glib.so.4")
++#ifdef MOZ_WIDGET_GTK
++ LOAD_LIBRARY(DbusmenuGtk, "libdbusmenu-gtk3.so.4")
++#endif
++#undef LOAD_LIBRARY
++
++ return NS_OK;
++}
+diff --git a/widget/gtk/nsDbusmenu.h b/widget/gtk/nsDbusmenu.h
+new file mode 100644
+index 000000000000..8d46a0d27bdb
+--- /dev/null
++++ b/widget/gtk/nsDbusmenu.h
+@@ -0,0 +1,101 @@
++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:expandtab:shiftwidth=4:tabstop=4:
++ */
++/* This Source Code Form is subject to the terms of the Mozilla Public
++ * License, v. 2.0. If a copy of the MPL was not distributed with this
++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
++
++#ifndef __nsDbusmenu_h__
++#define __nsDbusmenu_h__
++
++#include "nsError.h"
++
++#include <glib.h>
++#include <gdk/gdk.h>
++
++#define DBUSMENU_GLIB_FUNCTIONS \
++ FUNC(dbusmenu_menuitem_child_add_position, gboolean, (DbusmenuMenuitem *mi, DbusmenuMenuitem *child, guint position)) \
++ FUNC(dbusmenu_menuitem_child_append, gboolean, (DbusmenuMenuitem *mi, DbusmenuMenuitem *child)) \
++ FUNC(dbusmenu_menuitem_child_delete, gboolean, (DbusmenuMenuitem *mi, DbusmenuMenuitem *child)) \
++ FUNC(dbusmenu_menuitem_get_children, GList*, (DbusmenuMenuitem *mi)) \
++ FUNC(dbusmenu_menuitem_new, DbusmenuMenuitem*, (void)) \
++ FUNC(dbusmenu_menuitem_property_get, const gchar*, (DbusmenuMenuitem *mi, const gchar *property)) \
++ FUNC(dbusmenu_menuitem_property_get_bool, gboolean, (DbusmenuMenuitem *mi, const gchar *property)) \
++ FUNC(dbusmenu_menuitem_property_remove, void, (DbusmenuMenuitem *mi, const gchar *property)) \
++ FUNC(dbusmenu_menuitem_property_set, gboolean, (DbusmenuMenuitem *mi, const gchar *property, const gchar *value)) \
++ FUNC(dbusmenu_menuitem_property_set_bool, gboolean, (DbusmenuMenuitem *mi, const gchar *property, const gboolean value)) \
++ FUNC(dbusmenu_menuitem_property_set_int, gboolean, (DbusmenuMenuitem *mi, const gchar *property, const gint value)) \
++ FUNC(dbusmenu_menuitem_show_to_user, void, (DbusmenuMenuitem *mi, guint timestamp)) \
++ FUNC(dbusmenu_menuitem_take_children, GList*, (DbusmenuMenuitem *mi)) \
++ FUNC(dbusmenu_server_new, DbusmenuServer*, (const gchar *object)) \
++ FUNC(dbusmenu_server_set_root, void, (DbusmenuServer *server, DbusmenuMenuitem *root)) \
++ FUNC(dbusmenu_server_set_status, void, (DbusmenuServer *server, DbusmenuStatus status))
++
++#define DBUSMENU_GTK_FUNCTIONS \
++ FUNC(dbusmenu_menuitem_property_set_image, gboolean, (DbusmenuMenuitem *menuitem, const gchar *property, const GdkPixbuf *data)) \
++ FUNC(dbusmenu_menuitem_property_set_shortcut, gboolean, (DbusmenuMenuitem *menuitem, guint key, GdkModifierType modifier))
++
++typedef struct _DbusmenuMenuitem DbusmenuMenuitem;
++typedef struct _DbusmenuServer DbusmenuServer;
++
++enum DbusmenuStatus {
++ DBUSMENU_STATUS_NORMAL,
++ DBUSMENU_STATUS_NOTICE
++};
++
++#define DBUSMENU_MENUITEM_CHILD_DISPLAY_SUBMENU "submenu"
++#define DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY "children-display"
++#define DBUSMENU_MENUITEM_PROP_ENABLED "enabled"
++#define DBUSMENU_MENUITEM_PROP_ICON_DATA "icon-data"
++#define DBUSMENU_MENUITEM_PROP_LABEL "label"
++#define DBUSMENU_MENUITEM_PROP_SHORTCUT "shortcut"
++#define DBUSMENU_MENUITEM_PROP_TYPE "type"
++#define DBUSMENU_MENUITEM_PROP_TOGGLE_STATE "toggle-state"
++#define DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE "toggle-type"
++#define DBUSMENU_MENUITEM_PROP_VISIBLE "visible"
++#define DBUSMENU_MENUITEM_SIGNAL_ABOUT_TO_SHOW "about-to-show"
++#define DBUSMENU_MENUITEM_SIGNAL_EVENT "event"
++#define DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED "item-activated"
++#define DBUSMENU_MENUITEM_TOGGLE_CHECK "checkmark"
++#define DBUSMENU_MENUITEM_TOGGLE_RADIO "radio"
++#define DBUSMENU_MENUITEM_TOGGLE_STATE_CHECKED 1
++#define DBUSMENU_MENUITEM_TOGGLE_STATE_UNCHECKED 0
++#define DBUSMENU_SERVER_PROP_DBUS_OBJECT "dbus-object"
++
++class nsDbusmenuFunctions
++{
++public:
++ nsDbusmenuFunctions() = delete;
++
++ static nsresult Init();
++
++#define FUNC(name, type, params) \
++ typedef type (*_##name##_fn) params; \
++ static _##name##_fn s_##name;
++ DBUSMENU_GLIB_FUNCTIONS
++ DBUSMENU_GTK_FUNCTIONS
++#undef FUNC
++
++};
++
++#define dbusmenu_menuitem_child_add_position nsDbusmenuFunctions::s_dbusmenu_menuitem_child_add_position
++#define dbusmenu_menuitem_child_append nsDbusmenuFunctions::s_dbusmenu_menuitem_child_append
++#define dbusmenu_menuitem_child_delete nsDbusmenuFunctions::s_dbusmenu_menuitem_child_delete
++#define dbusmenu_menuitem_get_children nsDbusmenuFunctions::s_dbusmenu_menuitem_get_children
++#define dbusmenu_menuitem_new nsDbusmenuFunctions::s_dbusmenu_menuitem_new
++#define dbusmenu_menuitem_property_get nsDbusmenuFunctions::s_dbusmenu_menuitem_property_get
++#define dbusmenu_menuitem_property_get_bool nsDbusmenuFunctions::s_dbusmenu_menuitem_property_get_bool
++#define dbusmenu_menuitem_property_remove nsDbusmenuFunctions::s_dbusmenu_menuitem_property_remove
++#define dbusmenu_menuitem_property_set nsDbusmenuFunctions::s_dbusmenu_menuitem_property_set
++#define dbusmenu_menuitem_property_set_bool nsDbusmenuFunctions::s_dbusmenu_menuitem_property_set_bool
++#define dbusmenu_menuitem_property_set_int nsDbusmenuFunctions::s_dbusmenu_menuitem_property_set_int
++#define dbusmenu_menuitem_show_to_user nsDbusmenuFunctions::s_dbusmenu_menuitem_show_to_user
++#define dbusmenu_menuitem_take_children nsDbusmenuFunctions::s_dbusmenu_menuitem_take_children
++#define dbusmenu_server_new nsDbusmenuFunctions::s_dbusmenu_server_new
++#define dbusmenu_server_set_root nsDbusmenuFunctions::s_dbusmenu_server_set_root
++#define dbusmenu_server_set_status nsDbusmenuFunctions::s_dbusmenu_server_set_status
++
++#define dbusmenu_menuitem_property_set_image nsDbusmenuFunctions::s_dbusmenu_menuitem_property_set_image
++#define dbusmenu_menuitem_property_set_shortcut nsDbusmenuFunctions::s_dbusmenu_menuitem_property_set_shortcut
++
++#endif /* __nsDbusmenu_h__ */
+diff --git a/widget/gtk/nsMenu.cpp b/widget/gtk/nsMenu.cpp
+new file mode 100644
+index 000000000000..255eb390e577
+--- /dev/null
++++ b/widget/gtk/nsMenu.cpp
+@@ -0,0 +1,795 @@
++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:expandtab:shiftwidth=4:tabstop=4:
++ */
++/* This Source Code Form is subject to the terms of the Mozilla Public
++ * License, v. 2.0. If a copy of the MPL was not distributed with this
++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
++
++#define _IMPL_NS_LAYOUT
++
++#include "mozilla/dom/Document.h"
++#include "mozilla/dom/Element.h"
++#include "mozilla/Assertions.h"
++#include "mozilla/ComputedStyleInlines.h"
++#include "mozilla/EventDispatcher.h"
++#include "mozilla/MouseEvents.h"
++#include "mozilla/PresShell.h"
++#include "mozilla/PresShellInlines.h"
++#include "nsComponentManagerUtils.h"
++#include "nsContentUtils.h"
++#include "nsCSSValue.h"
++#include "nsGkAtoms.h"
++#include "nsGtkUtils.h"
++#include "nsAtom.h"
++#include "nsIContent.h"
++#include "nsIRunnable.h"
++#include "nsITimer.h"
++#include "nsString.h"
++#include "nsStyleStruct.h"
++#include "nsThreadUtils.h"
++
++#include "nsNativeMenuDocListener.h"
++
++#include <glib-object.h>
++
++#include "nsMenu.h"
++
++using namespace mozilla;
++
++class nsMenuContentInsertedEvent : public Runnable
++{
++public:
++ nsMenuContentInsertedEvent(nsMenu *aMenu,
++ nsIContent *aContainer,
++ nsIContent *aChild,
++ nsIContent *aPrevSibling) :
++ Runnable("nsMenuContentInsertedEvent"),
++ mWeakMenu(aMenu),
++ mContainer(aContainer),
++ mChild(aChild),
++ mPrevSibling(aPrevSibling) { }
++
++ NS_IMETHODIMP Run()
++ {
++ if (!mWeakMenu) {
++ return NS_OK;
++ }
++
++ static_cast<nsMenu *>(mWeakMenu.get())->HandleContentInserted(mContainer,
++ mChild,
++ mPrevSibling);
++ return NS_OK;
++ }
++
++private:
++ nsWeakMenuObject mWeakMenu;
++
++ nsCOMPtr<nsIContent> mContainer;
++ nsCOMPtr<nsIContent> mChild;
++ nsCOMPtr<nsIContent> mPrevSibling;
++};
++
++class nsMenuContentRemovedEvent : public Runnable
++{
++public:
++ nsMenuContentRemovedEvent(nsMenu *aMenu,
++ nsIContent *aContainer,
++ nsIContent *aChild) :
++ Runnable("nsMenuContentRemovedEvent"),
++ mWeakMenu(aMenu),
++ mContainer(aContainer),
++ mChild(aChild) { }
++
++ NS_IMETHODIMP Run()
++ {
++ if (!mWeakMenu) {
++ return NS_OK;
++ }
++
++ static_cast<nsMenu *>(mWeakMenu.get())->HandleContentRemoved(mContainer,
++ mChild);
++ return NS_OK;
++ }
++
++private:
++ nsWeakMenuObject mWeakMenu;
++
++ nsCOMPtr<nsIContent> mContainer;
++ nsCOMPtr<nsIContent> mChild;
++};
++
++static void
++DispatchMouseEvent(nsIContent *aTarget, mozilla::EventMessage aMsg)
++{
++ if (!aTarget) {
++ return;
++ }
++
++ WidgetMouseEvent event(true, aMsg, nullptr, WidgetMouseEvent::eReal);
++ EventDispatcher::Dispatch(aTarget, nullptr, &event);
++}
++
++void
++nsMenu::SetPopupState(EPopupState aState)
++{
++ mPopupState = aState;
++
++ if (!mPopupContent) {
++ return;
++ }
++
++ nsAutoString state;
++ switch (aState) {
++ case ePopupState_Showing:
++ state.Assign(u"showing"_ns);
++ break;
++ case ePopupState_Open:
++ state.Assign(u"open"_ns);
++ break;
++ case ePopupState_Hiding:
++ state.Assign(u"hiding"_ns);
++ break;
++ default:
++ break;
++ }
++
++ if (state.IsEmpty()) {
++ mPopupContent->AsElement()->UnsetAttr(
++ kNameSpaceID_None, nsGkAtoms::_moz_nativemenupopupstate,
++ false);
++ } else {
++ mPopupContent->AsElement()->SetAttr(
++ kNameSpaceID_None, nsGkAtoms::_moz_nativemenupopupstate,
++ state, false);
++ }
++}
++
++/* static */ void
++nsMenu::DoOpenCallback(nsITimer *aTimer, void *aClosure)
++{
++ nsMenu* self = static_cast<nsMenu *>(aClosure);
++
++ dbusmenu_menuitem_show_to_user(self->GetNativeData(), 0);
++
++ self->mOpenDelayTimer = nullptr;
++}
++
++/* static */ void
++nsMenu::menu_event_cb(DbusmenuMenuitem *menu,
++ const gchar *name,
++ GVariant *value,
++ guint timestamp,
++ gpointer user_data)
++{
++ nsMenu *self = static_cast<nsMenu *>(user_data);
++
++ nsAutoCString event(name);
++
++ if (event.Equals("closed"_ns)) {
++ self->OnClose();
++ return;
++ }
++
++ if (event.Equals("opened"_ns)) {
++ self->OnOpen();
++ return;
++ }
++}
++
++void
++nsMenu::MaybeAddPlaceholderItem()
++{
++ MOZ_ASSERT(!IsInBatchedUpdate(),
++ "Shouldn't be modifying the native menu structure now");
++
++ GList *children = dbusmenu_menuitem_get_children(GetNativeData());
++ if (!children) {
++ MOZ_ASSERT(!mPlaceholderItem);
++
++ mPlaceholderItem = dbusmenu_menuitem_new();
++ if (!mPlaceholderItem) {
++ return;
++ }
++
++ dbusmenu_menuitem_property_set_bool(mPlaceholderItem,
++ DBUSMENU_MENUITEM_PROP_VISIBLE,
++ false);
++
++ MOZ_ALWAYS_TRUE(
++ dbusmenu_menuitem_child_append(GetNativeData(), mPlaceholderItem));
++ }
++}
++
++void
++nsMenu::EnsureNoPlaceholderItem()
++{
++ MOZ_ASSERT(!IsInBatchedUpdate(),
++ "Shouldn't be modifying the native menu structure now");
++
++ if (!mPlaceholderItem) {
++ return;
++ }
++
++ MOZ_ALWAYS_TRUE(
++ dbusmenu_menuitem_child_delete(GetNativeData(), mPlaceholderItem));
++ MOZ_ASSERT(!dbusmenu_menuitem_get_children(GetNativeData()));
++
++ g_object_unref(mPlaceholderItem);
++ mPlaceholderItem = nullptr;
++}
++
++void
++nsMenu::OnOpen()
++{
++ if (mNeedsRebuild) {
++ Build();
++ }
++
++ nsWeakMenuObject self(this);
++ nsCOMPtr<nsIContent> origPopupContent(mPopupContent);
++ {
++ nsNativeMenuDocListener::BlockUpdatesScope updatesBlocker;
++
++ SetPopupState(ePopupState_Showing);
++ DispatchMouseEvent(mPopupContent, eXULPopupShowing);
++
++ ContentNode()->AsElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::open,
++ u"true"_ns, true);
++ }
++
++ if (!self) {
++ // We were deleted!
++ return;
++ }
++
++ // I guess that the popup could have changed
++ if (origPopupContent != mPopupContent) {
++ return;
++ }
++
++ nsNativeMenuDocListener::BlockUpdatesScope updatesBlocker;
++
++ size_t count = ChildCount();
++ for (size_t i = 0; i < count; ++i) {
++ ChildAt(i)->ContainerIsOpening();
++ }
++
++ SetPopupState(ePopupState_Open);
++ DispatchMouseEvent(mPopupContent, eXULPopupShown);
++}
++
++void
++nsMenu::Build()
++{
++ mNeedsRebuild = false;
++
++ while (ChildCount() > 0) {
++ RemoveChildAt(0);
++ }
++
++ InitializePopup();
++
++ if (!mPopupContent) {
++ return;
++ }
++
++ uint32_t count = mPopupContent->GetChildCount();
++ for (uint32_t i = 0; i < count; ++i) {
++ nsIContent *childContent = mPopupContent->GetChildAt_Deprecated(i);
++
++ UniquePtr<nsMenuObject> child = CreateChild(childContent);
++
++ if (!child) {
++ continue;
++ }
++
++ AppendChild(std::move(child));
++ }
++}
++
++void
++nsMenu::InitializePopup()
++{
++ nsCOMPtr<nsIContent> oldPopupContent;
++ oldPopupContent.swap(mPopupContent);
++
++ for (uint32_t i = 0; i < ContentNode()->GetChildCount(); ++i) {
++ nsIContent *child = ContentNode()->GetChildAt_Deprecated(i);
++
++ if (child->NodeInfo()->NameAtom() == nsGkAtoms::menupopup) {
++ mPopupContent = child;
++ break;
++ }
++ }
++
++ if (oldPopupContent == mPopupContent) {
++ return;
++ }
++
++ // The popup has changed
++
++ if (oldPopupContent) {
++ DocListener()->UnregisterForContentChanges(oldPopupContent);
++ }
++
++ SetPopupState(ePopupState_Closed);
++
++ if (!mPopupContent) {
++ return;
++ }
++
++ DocListener()->RegisterForContentChanges(mPopupContent, this);
++}
++
++void
++nsMenu::RemoveChildAt(size_t aIndex)
++{
++ MOZ_ASSERT(IsInBatchedUpdate() || !mPlaceholderItem,
++ "Shouldn't have a placeholder menuitem");
++
++ nsMenuContainer::RemoveChildAt(aIndex, !IsInBatchedUpdate());
++ StructureMutated();
++
++ if (!IsInBatchedUpdate()) {
++ MaybeAddPlaceholderItem();
++ }
++}
++
++void
++nsMenu::RemoveChild(nsIContent *aChild)
++{
++ size_t index = IndexOf(aChild);
++ if (index == NoIndex) {
++ return;
++ }
++
++ RemoveChildAt(index);
++}
++
++void
++nsMenu::InsertChildAfter(UniquePtr<nsMenuObject> aChild,
++ nsIContent *aPrevSibling)
++{
++ if (!IsInBatchedUpdate()) {
++ EnsureNoPlaceholderItem();
++ }
++
++ nsMenuContainer::InsertChildAfter(std::move(aChild), aPrevSibling,
++ !IsInBatchedUpdate());
++ StructureMutated();
++}
++
++void
++nsMenu::AppendChild(UniquePtr<nsMenuObject> aChild)
++{
++ if (!IsInBatchedUpdate()) {
++ EnsureNoPlaceholderItem();
++ }
++
++ nsMenuContainer::AppendChild(std::move(aChild), !IsInBatchedUpdate());
++ StructureMutated();
++}
++
++bool
++nsMenu::IsInBatchedUpdate() const
++{
++ return mBatchedUpdateState != eBatchedUpdateState_Inactive;
++}
++
++void
++nsMenu::StructureMutated()
++{
++ if (!IsInBatchedUpdate()) {
++ return;
++ }
++
++ mBatchedUpdateState = eBatchedUpdateState_DidMutate;
++}
++
++bool
++nsMenu::CanOpen() const
++{
++ bool isVisible = dbusmenu_menuitem_property_get_bool(GetNativeData(),
++ DBUSMENU_MENUITEM_PROP_VISIBLE);
++ bool isDisabled = ContentNode()->AsElement()->AttrValueIs(kNameSpaceID_None,
++ nsGkAtoms::disabled,
++ nsGkAtoms::_true,
++ eCaseMatters);
++
++ return (isVisible && !isDisabled);
++}
++
++void
++nsMenu::HandleContentInserted(nsIContent *aContainer,
++ nsIContent *aChild,
++ nsIContent *aPrevSibling)
++{
++ if (aContainer == mPopupContent) {
++ UniquePtr<nsMenuObject> child = CreateChild(aChild);
++
++ if (child) {
++ InsertChildAfter(std::move(child), aPrevSibling);
++ }
++ } else {
++ Build();
++ }
++}
++
++void
++nsMenu::HandleContentRemoved(nsIContent *aContainer, nsIContent *aChild)
++{
++ if (aContainer == mPopupContent) {
++ RemoveChild(aChild);
++ } else {
++ Build();
++ }
++}
++
++void
++nsMenu::InitializeNativeData()
++{
++ // Dbusmenu provides an "about-to-show" signal, and also "opened" and
++ // "closed" events. However, Unity is the only thing that sends
++ // both "about-to-show" and "opened" events. Unity 2D and the HUD only
++ // send "opened" events, so we ignore "about-to-show" (I don't think
++ // there's any real difference between them anyway).
++ // To complicate things, there are certain conditions where we don't
++ // get a "closed" event, so we need to be able to handle this :/
++ g_signal_connect(G_OBJECT(GetNativeData()), "event",
++ G_CALLBACK(menu_event_cb), this);
++
++ mNeedsRebuild = true;
++ mNeedsUpdate = true;
++
++ MaybeAddPlaceholderItem();
++}
++
++void
++nsMenu::Update(const ComputedStyle *aComputedStyle)
++{
++ if (mNeedsUpdate) {
++ mNeedsUpdate = false;
++
++ UpdateLabel();
++ UpdateSensitivity();
++ }
++
++ UpdateVisibility(aComputedStyle);
++ UpdateIcon(aComputedStyle);
++}
++
++nsMenuObject::PropertyFlags
++nsMenu::SupportedProperties() const
++{
++ return static_cast<nsMenuObject::PropertyFlags>(
++ nsMenuObject::ePropLabel |
++ nsMenuObject::ePropEnabled |
++ nsMenuObject::ePropVisible |
++ nsMenuObject::ePropIconData |
++ nsMenuObject::ePropChildDisplay
++ );
++}
++
++void
++nsMenu::OnAttributeChanged(nsIContent *aContent, nsAtom *aAttribute)
++{
++ MOZ_ASSERT(aContent == ContentNode() || aContent == mPopupContent,
++ "Received an event that wasn't meant for us!");
++
++ if (mNeedsUpdate) {
++ return;
++ }
++
++ if (aContent != ContentNode()) {
++ return;
++ }
++
++ if (!Parent()->IsBeingDisplayed()) {
++ mNeedsUpdate = true;
++ return;
++ }
++
++ if (aAttribute == nsGkAtoms::disabled) {
++ UpdateSensitivity();
++ } else if (aAttribute == nsGkAtoms::label ||
++ aAttribute == nsGkAtoms::accesskey ||
++ aAttribute == nsGkAtoms::crop) {
++ UpdateLabel();
++ } else if (aAttribute == nsGkAtoms::hidden ||
++ aAttribute == nsGkAtoms::collapsed) {
++ RefPtr<const ComputedStyle> style = GetComputedStyle();
++ UpdateVisibility(style);
++ } else if (aAttribute == nsGkAtoms::image) {
++ RefPtr<const ComputedStyle> style = GetComputedStyle();
++ UpdateIcon(style);
++ }
++}
++
++void
++nsMenu::OnContentInserted(nsIContent *aContainer, nsIContent *aChild,
++ nsIContent *aPrevSibling)
++{
++ MOZ_ASSERT(aContainer == ContentNode() || aContainer == mPopupContent,
++ "Received an event that wasn't meant for us!");
++
++ if (mNeedsRebuild) {
++ return;
++ }
++
++ if (mPopupState == ePopupState_Closed) {
++ mNeedsRebuild = true;
++ return;
++ }
++
++ nsContentUtils::AddScriptRunner(
++ new nsMenuContentInsertedEvent(this, aContainer, aChild,
++ aPrevSibling));
++}
++
++void
++nsMenu::OnContentRemoved(nsIContent *aContainer, nsIContent *aChild)
++{
++ MOZ_ASSERT(aContainer == ContentNode() || aContainer == mPopupContent,
++ "Received an event that wasn't meant for us!");
++
++ if (mNeedsRebuild) {
++ return;
++ }
++
++ if (mPopupState == ePopupState_Closed) {
++ mNeedsRebuild = true;
++ return;
++ }
++
++ nsContentUtils::AddScriptRunner(
++ new nsMenuContentRemovedEvent(this, aContainer, aChild));
++}
++
++/*
++ * Some menus (eg, the History menu in Firefox) refresh themselves on
++ * opening by removing all children and then re-adding new ones. As this
++ * happens whilst the menu is opening in Unity, it causes some flickering
++ * as the menu popup is resized multiple times. To avoid this, we try to
++ * reuse native menu items when the menu structure changes during a
++ * batched update. If we can handle menu structure changes from Gecko
++ * just by updating properties of native menu items (rather than destroying
++ * and creating new ones), then we eliminate any flickering that occurs as
++ * the menu is opened. To do this, we don't modify any native menu items
++ * until the end of the update batch.
++ */
++
++void
++nsMenu::OnBeginUpdates(nsIContent *aContent)
++{
++ MOZ_ASSERT(aContent == ContentNode() || aContent == mPopupContent,
++ "Received an event that wasn't meant for us!");
++ MOZ_ASSERT(!IsInBatchedUpdate(), "Already in an update batch!");
++
++ if (aContent != mPopupContent) {
++ return;
++ }
++
++ mBatchedUpdateState = eBatchedUpdateState_Active;
++}
++
++void
++nsMenu::OnEndUpdates()
++{
++ if (!IsInBatchedUpdate()) {
++ return;
++ }
++
++ bool didMutate = mBatchedUpdateState == eBatchedUpdateState_DidMutate;
++ mBatchedUpdateState = eBatchedUpdateState_Inactive;
++
++ /* Optimize for the case where we only had attribute changes */
++ if (!didMutate) {
++ return;
++ }
++
++ EnsureNoPlaceholderItem();
++
++ GList *nextNativeChild = dbusmenu_menuitem_get_children(GetNativeData());
++ DbusmenuMenuitem *nextOwnedNativeChild = nullptr;
++
++ size_t count = ChildCount();
++
++ // Find the first native menu item that is `owned` by a corresponding
++ // Gecko menuitem
++ for (size_t i = 0; i < count; ++i) {
++ if (ChildAt(i)->GetNativeData()) {
++ nextOwnedNativeChild = ChildAt(i)->GetNativeData();
++ break;
++ }
++ }
++
++ // Now iterate over all Gecko menuitems
++ for (size_t i = 0; i < count; ++i) {
++ nsMenuObject *child = ChildAt(i);
++
++ if (child->GetNativeData()) {
++ // This child already has a corresponding native menuitem.
++ // Remove all preceding orphaned native items. At this point, we
++ // modify the native menu structure.
++ while (nextNativeChild &&
++ nextNativeChild->data != nextOwnedNativeChild) {
++
++ DbusmenuMenuitem *data =
++ static_cast<DbusmenuMenuitem *>(nextNativeChild->data);
++ nextNativeChild = nextNativeChild->next;
++
++ MOZ_ALWAYS_TRUE(dbusmenu_menuitem_child_delete(GetNativeData(),
++ data));
++ }
++
++ if (nextNativeChild) {
++ nextNativeChild = nextNativeChild->next;
++ }
++
++ // Now find the next native menu item that is `owned`
++ nextOwnedNativeChild = nullptr;
++ for (size_t j = i + 1; j < count; ++j) {
++ if (ChildAt(j)->GetNativeData()) {
++ nextOwnedNativeChild = ChildAt(j)->GetNativeData();
++ break;
++ }
++ }
++ } else {
++ // This child is new, and doesn't have a native menu item. Find one!
++ if (nextNativeChild &&
++ nextNativeChild->data != nextOwnedNativeChild) {
++
++ DbusmenuMenuitem *data =
++ static_cast<DbusmenuMenuitem *>(nextNativeChild->data);
++
++ if (NS_SUCCEEDED(child->AdoptNativeData(data))) {
++ nextNativeChild = nextNativeChild->next;
++ }
++ }
++
++ // There wasn't a suitable one available, so create a new one.
++ // At this point, we modify the native menu structure.
++ if (!child->GetNativeData()) {
++ child->CreateNativeData();
++ MOZ_ALWAYS_TRUE(
++ dbusmenu_menuitem_child_add_position(GetNativeData(),
++ child->GetNativeData(),
++ i));
++ }
++ }
++ }
++
++ while (nextNativeChild) {
++ DbusmenuMenuitem *data =
++ static_cast<DbusmenuMenuitem *>(nextNativeChild->data);
++ nextNativeChild = nextNativeChild->next;
++
++ MOZ_ALWAYS_TRUE(dbusmenu_menuitem_child_delete(GetNativeData(), data));
++ }
++
++ MaybeAddPlaceholderItem();
++}
++
++nsMenu::nsMenu(nsMenuContainer *aParent, nsIContent *aContent) :
++ nsMenuContainer(aParent, aContent),
++ mNeedsRebuild(false),
++ mNeedsUpdate(false),
++ mPlaceholderItem(nullptr),
++ mPopupState(ePopupState_Closed),
++ mBatchedUpdateState(eBatchedUpdateState_Inactive)
++{
++ MOZ_COUNT_CTOR(nsMenu);
++}
++
++nsMenu::~nsMenu()
++{
++ if (IsInBatchedUpdate()) {
++ OnEndUpdates();
++ }
++
++ // Although nsTArray will take care of this in its destructor,
++ // we have to manually ensure children are removed from our native menu
++ // item, just in case our parent recycles us
++ while (ChildCount() > 0) {
++ RemoveChildAt(0);
++ }
++
++ EnsureNoPlaceholderItem();
++
++ if (DocListener() && mPopupContent) {
++ DocListener()->UnregisterForContentChanges(mPopupContent);
++ }
++
++ if (GetNativeData()) {
++ g_signal_handlers_disconnect_by_func(GetNativeData(),
++ FuncToGpointer(menu_event_cb),
++ this);
++ }
++
++ MOZ_COUNT_DTOR(nsMenu);
++}
++
++nsMenuObject::EType
++nsMenu::Type() const
++{
++ return eType_Menu;
++}
++
++bool
++nsMenu::IsBeingDisplayed() const
++{
++ return mPopupState == ePopupState_Open;
++}
++
++bool
++nsMenu::NeedsRebuild() const
++{
++ return mNeedsRebuild;
++}
++
++void
++nsMenu::OpenMenu()
++{
++ if (!CanOpen()) {
++ return;
++ }
++
++ if (mOpenDelayTimer) {
++ return;
++ }
++
++ // Here, we synchronously fire popupshowing and popupshown events and then
++ // open the menu after a short delay. This allows the menu to refresh before
++ // it's shown, and avoids an issue where keyboard focus is not on the first
++ // item of the history menu in Firefox when opening it with the keyboard,
++ // because extra items to appear at the top of the menu
++
++ OnOpen();
++
++ mOpenDelayTimer = NS_NewTimer();
++ if (!mOpenDelayTimer) {
++ return;
++ }
++
++ if (NS_FAILED(mOpenDelayTimer->InitWithNamedFuncCallback(DoOpenCallback,
++ this,
++ 100,
++ nsITimer::TYPE_ONE_SHOT,
++ "nsMenu::DoOpenCallback"))) {
++ mOpenDelayTimer = nullptr;
++ }
++}
++
++void
++nsMenu::OnClose()
++{
++ if (mPopupState == ePopupState_Closed) {
++ return;
++ }
++
++ MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
++
++ // We do this to avoid mutating our view of the menu until
++ // after we have finished
++ nsNativeMenuDocListener::BlockUpdatesScope updatesBlocker;
++
++ SetPopupState(ePopupState_Hiding);
++ DispatchMouseEvent(mPopupContent, eXULPopupHiding);
++
++ // Sigh, make sure all of our descendants are closed, as we don't
++ // always get closed events for submenus when scrubbing quickly through
++ // the menu
++ size_t count = ChildCount();
++ for (size_t i = 0; i < count; ++i) {
++ if (ChildAt(i)->Type() == nsMenuObject::eType_Menu) {
++ static_cast<nsMenu *>(ChildAt(i))->OnClose();
++ }
++ }
++
++ SetPopupState(ePopupState_Closed);
++ DispatchMouseEvent(mPopupContent, eXULPopupHidden);
++
++ ContentNode()->AsElement()->UnsetAttr(kNameSpaceID_None, nsGkAtoms::open,
++ true);
++}
++
+diff --git a/widget/gtk/nsMenu.h b/widget/gtk/nsMenu.h
+new file mode 100644
+index 000000000000..40244d012290
+--- /dev/null
++++ b/widget/gtk/nsMenu.h
+@@ -0,0 +1,123 @@
++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:expandtab:shiftwidth=4:tabstop=4:
++ */
++/* This Source Code Form is subject to the terms of the Mozilla Public
++ * License, v. 2.0. If a copy of the MPL was not distributed with this
++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
++
++#ifndef __nsMenu_h__
++#define __nsMenu_h__
++
++#include "mozilla/Attributes.h"
++#include "mozilla/UniquePtr.h"
++#include "nsCOMPtr.h"
++
++#include "nsDbusmenu.h"
++#include "nsMenuContainer.h"
++#include "nsMenuObject.h"
++
++#include <glib.h>
++
++class nsAtom;
++class nsIContent;
++class nsITimer;
++
++#define NSMENU_NUMBER_OF_POPUPSTATE_BITS 2U
++#define NSMENU_NUMBER_OF_FLAGS 4U
++
++// This class represents a menu
++class nsMenu final : public nsMenuContainer
++{
++public:
++ nsMenu(nsMenuContainer *aParent, nsIContent *aContent);
++ ~nsMenu();
++
++ nsMenuObject::EType Type() const override;
++
++ bool IsBeingDisplayed() const override;
++ bool NeedsRebuild() const override;
++
++ // Tell the desktop shell to display this menu
++ void OpenMenu();
++
++ // Normally called via the shell, but it's public so that child
++ // menuitems can do the shells work. Sigh....
++ void OnClose();
++
++private:
++ friend class nsMenuContentInsertedEvent;
++ friend class nsMenuContentRemovedEvent;
++
++ enum EPopupState {
++ ePopupState_Closed,
++ ePopupState_Showing,
++ ePopupState_Open,
++ ePopupState_Hiding
++ };
++
++ void SetPopupState(EPopupState aState);
++
++ static void DoOpenCallback(nsITimer *aTimer, void *aClosure);
++ static void menu_event_cb(DbusmenuMenuitem *menu,
++ const gchar *name,
++ GVariant *value,
++ guint timestamp,
++ gpointer user_data);
++
++ // We add a placeholder item to empty menus so that Unity actually treats
++ // us as a proper menu, rather than a menuitem without a submenu
++ void MaybeAddPlaceholderItem();
++
++ // Removes a placeholder item if it exists and asserts that this succeeds
++ void EnsureNoPlaceholderItem();
++
++ void OnOpen();
++ void Build();
++ void InitializePopup();
++ void RemoveChildAt(size_t aIndex);
++ void RemoveChild(nsIContent *aChild);
++ void InsertChildAfter(mozilla::UniquePtr<nsMenuObject> aChild,
++ nsIContent *aPrevSibling);
++ void AppendChild(mozilla::UniquePtr<nsMenuObject> aChild);
++ bool IsInBatchedUpdate() const;
++ void StructureMutated();
++ bool CanOpen() const;
++
++ void HandleContentInserted(nsIContent *aContainer,
++ nsIContent *aChild,
++ nsIContent *aPrevSibling);
++ void HandleContentRemoved(nsIContent *aContainer,
++ nsIContent *aChild);
++
++ void InitializeNativeData() override;
++ void Update(const mozilla::ComputedStyle *aComputedStyle) override;
++ nsMenuObject::PropertyFlags SupportedProperties() const override;
++
++ void OnAttributeChanged(nsIContent *aContent, nsAtom *aAttribute) override;
++ void OnContentInserted(nsIContent *aContainer, nsIContent *aChild,
++ nsIContent *aPrevSibling) override;
++ void OnContentRemoved(nsIContent *aContainer, nsIContent *aChild) override;
++ void OnBeginUpdates(nsIContent *aContent) override;
++ void OnEndUpdates() override;
++
++ bool mNeedsRebuild;
++ bool mNeedsUpdate;
++
++ DbusmenuMenuitem *mPlaceholderItem;
++
++ EPopupState mPopupState;
++
++ enum EBatchedUpdateState {
++ eBatchedUpdateState_Inactive,
++ eBatchedUpdateState_Active,
++ eBatchedUpdateState_DidMutate
++ };
++
++ EBatchedUpdateState mBatchedUpdateState;
++
++ nsCOMPtr<nsIContent> mPopupContent;
++
++ nsCOMPtr<nsITimer> mOpenDelayTimer;
++};
++
++#endif /* __nsMenu_h__ */
+diff --git a/widget/gtk/nsMenuBar.cpp b/widget/gtk/nsMenuBar.cpp
+new file mode 100644
+index 000000000000..0dcae6794329
+--- /dev/null
++++ b/widget/gtk/nsMenuBar.cpp
+@@ -0,0 +1,548 @@
++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:expandtab:shiftwidth=4:tabstop=4:
++ */
++/* This Source Code Form is subject to the terms of the Mozilla Public
++ * License, v. 2.0. If a copy of the MPL was not distributed with this
++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
++
++#include "mozilla/Assertions.h"
++#include "mozilla/DebugOnly.h"
++#include "mozilla/dom/Document.h"
++#include "mozilla/dom/Element.h"
++#include "mozilla/dom/Event.h"
++#include "mozilla/dom/KeyboardEvent.h"
++#include "mozilla/dom/KeyboardEventBinding.h"
++#include "mozilla/Preferences.h"
++#include "nsContentUtils.h"
++#include "nsIDOMEventListener.h"
++#include "nsIRunnable.h"
++#include "nsIWidget.h"
++#include "nsTArray.h"
++#include "nsUnicharUtils.h"
++
++#include "nsMenu.h"
++#include "nsNativeMenuService.h"
++
++#include <gdk/gdk.h>
++#include <gdk/gdkx.h>
++#include <glib.h>
++#include <glib-object.h>
++
++#include "nsMenuBar.h"
++
++using namespace mozilla;
++
++static bool
++ShouldHandleKeyEvent(dom::KeyboardEvent *aEvent)
++{
++ return !aEvent->DefaultPrevented() && aEvent->IsTrusted();
++}
++
++class nsMenuBarContentInsertedEvent : public Runnable
++{
++public:
++ nsMenuBarContentInsertedEvent(nsMenuBar *aMenuBar,
++ nsIContent *aChild,
++ nsIContent *aPrevSibling) :
++ Runnable("nsMenuBarContentInsertedEvent"),
++ mWeakMenuBar(aMenuBar),
++ mChild(aChild),
++ mPrevSibling(aPrevSibling) { }
++
++ NS_IMETHODIMP Run()
++ {
++ if (!mWeakMenuBar) {
++ return NS_OK;
++ }
++
++ static_cast<nsMenuBar *>(mWeakMenuBar.get())->HandleContentInserted(mChild,
++ mPrevSibling);
++ return NS_OK;
++ }
++
++private:
++ nsWeakMenuObject mWeakMenuBar;
++
++ nsCOMPtr<nsIContent> mChild;
++ nsCOMPtr<nsIContent> mPrevSibling;
++};
++
++class nsMenuBarContentRemovedEvent : public Runnable
++{
++public:
++ nsMenuBarContentRemovedEvent(nsMenuBar *aMenuBar,
++ nsIContent *aChild) :
++ Runnable("nsMenuBarContentRemovedEvent"),
++ mWeakMenuBar(aMenuBar),
++ mChild(aChild) { }
++
++ NS_IMETHODIMP Run()
++ {
++ if (!mWeakMenuBar) {
++ return NS_OK;
++ }
++
++ static_cast<nsMenuBar *>(mWeakMenuBar.get())->HandleContentRemoved(mChild);
++ return NS_OK;
++ }
++
++private:
++ nsWeakMenuObject mWeakMenuBar;
++
++ nsCOMPtr<nsIContent> mChild;
++};
++
++class nsMenuBar::DocEventListener final : public nsIDOMEventListener
++{
++public:
++ NS_DECL_ISUPPORTS
++ NS_DECL_NSIDOMEVENTLISTENER
++
++ DocEventListener(nsMenuBar *aOwner) : mOwner(aOwner) { };
++
++private:
++ ~DocEventListener() { };
++
++ nsMenuBar *mOwner;
++};
++
++NS_IMPL_ISUPPORTS(nsMenuBar::DocEventListener, nsIDOMEventListener)
++
++NS_IMETHODIMP
++nsMenuBar::DocEventListener::HandleEvent(dom::Event *aEvent)
++{
++ nsAutoString type;
++ aEvent->GetType(type);
++
++ if (type.Equals(u"focus"_ns)) {
++ mOwner->Focus();
++ } else if (type.Equals(u"blur"_ns)) {
++ mOwner->Blur();
++ }
++
++ RefPtr<dom::KeyboardEvent> keyEvent = aEvent->AsKeyboardEvent();
++ if (!keyEvent) {
++ return NS_OK;
++ }
++
++ if (type.Equals(u"keypress"_ns)) {
++ return mOwner->Keypress(keyEvent);
++ } else if (type.Equals(u"keydown"_ns)) {
++ return mOwner->KeyDown(keyEvent);
++ } else if (type.Equals(u"keyup"_ns)) {
++ return mOwner->KeyUp(keyEvent);
++ }
++
++ return NS_OK;
++}
++
++nsMenuBar::nsMenuBar(nsIContent *aMenuBarNode) :
++ nsMenuContainer(new nsNativeMenuDocListener(aMenuBarNode), aMenuBarNode),
++ mTopLevel(nullptr),
++ mServer(nullptr),
++ mIsActive(false)
++{
++ MOZ_COUNT_CTOR(nsMenuBar);
++}
++
++nsresult
++nsMenuBar::Init(nsIWidget *aParent)
++{
++ MOZ_ASSERT(aParent);
++
++ GdkWindow *gdkWin = static_cast<GdkWindow *>(
++ aParent->GetNativeData(NS_NATIVE_WINDOW));
++ if (!gdkWin) {
++ return NS_ERROR_FAILURE;
++ }
++
++ gpointer user_data = nullptr;
++ gdk_window_get_user_data(gdkWin, &user_data);
++ if (!user_data || !GTK_IS_CONTAINER(user_data)) {
++ return NS_ERROR_FAILURE;
++ }
++
++ mTopLevel = gtk_widget_get_toplevel(GTK_WIDGET(user_data));
++ if (!mTopLevel) {
++ return NS_ERROR_FAILURE;
++ }
++
++ g_object_ref(mTopLevel);
++
++ nsAutoCString path;
++ path.Append("/com/canonical/menu/"_ns);
++ char xid[10];
++ sprintf(xid, "%X", static_cast<uint32_t>(
++ GDK_WINDOW_XID(gtk_widget_get_window(mTopLevel))));
++ path.Append(xid);
++
++ mServer = dbusmenu_server_new(path.get());
++ if (!mServer) {
++ return NS_ERROR_FAILURE;
++ }
++
++ CreateNativeData();
++ if (!GetNativeData()) {
++ return NS_ERROR_FAILURE;
++ }
++
++ dbusmenu_server_set_root(mServer, GetNativeData());
++
++ mEventListener = new DocEventListener(this);
++
++ mDocument = do_QueryInterface(ContentNode()->OwnerDoc());
++
++ mAccessKey = Preferences::GetInt("ui.key.menuAccessKey");
++ if (mAccessKey == dom::KeyboardEvent_Binding::DOM_VK_SHIFT) {
++ mAccessKeyMask = eModifierShift;
++ } else if (mAccessKey == dom::KeyboardEvent_Binding::DOM_VK_CONTROL) {
++ mAccessKeyMask = eModifierCtrl;
++ } else if (mAccessKey == dom::KeyboardEvent_Binding::DOM_VK_ALT) {
++ mAccessKeyMask = eModifierAlt;
++ } else if (mAccessKey == dom::KeyboardEvent_Binding::DOM_VK_META) {
++ mAccessKeyMask = eModifierMeta;
++ } else {
++ mAccessKeyMask = eModifierAlt;
++ }
++
++ return NS_OK;
++}
++
++void
++nsMenuBar::Build()
++{
++ uint32_t count = ContentNode()->GetChildCount();
++ for (uint32_t i = 0; i < count; ++i) {
++ nsIContent *childContent = ContentNode()->GetChildAt_Deprecated(i);
++
++ UniquePtr<nsMenuObject> child = CreateChild(childContent);
++
++ if (!child) {
++ continue;
++ }
++
++ AppendChild(std::move(child));
++ }
++}
++
++void
++nsMenuBar::DisconnectDocumentEventListeners()
++{
++ mDocument->RemoveEventListener(u"focus"_ns,
++ mEventListener,
++ true);
++ mDocument->RemoveEventListener(u"blur"_ns,
++ mEventListener,
++ true);
++ mDocument->RemoveEventListener(u"keypress"_ns,
++ mEventListener,
++ false);
++ mDocument->RemoveEventListener(u"keydown"_ns,
++ mEventListener,
++ false);
++ mDocument->RemoveEventListener(u"keyup"_ns,
++ mEventListener,
++ false);
++}
++
++void
++nsMenuBar::SetShellShowingMenuBar(bool aShowing)
++{
++ ContentNode()->OwnerDoc()->GetRootElement()->SetAttr(
++ kNameSpaceID_None, nsGkAtoms::shellshowingmenubar,
++ aShowing ? u"true"_ns : u"false"_ns,
++ true);
++}
++
++void
++nsMenuBar::Focus()
++{
++ ContentNode()->AsElement()->SetAttr(kNameSpaceID_None,
++ nsGkAtoms::openedwithkey,
++ u"false"_ns, true);
++}
++
++void
++nsMenuBar::Blur()
++{
++ // We do this here in case we lose focus before getting the
++ // keyup event, which leaves the menubar state looking like
++ // the alt key is stuck down
++ dbusmenu_server_set_status(mServer, DBUSMENU_STATUS_NORMAL);
++}
++
++nsMenuBar::ModifierFlags
++nsMenuBar::GetModifiersFromEvent(dom::KeyboardEvent *aEvent)
++{
++ ModifierFlags modifiers = static_cast<ModifierFlags>(0);
++
++ if (aEvent->AltKey()) {
++ modifiers = static_cast<ModifierFlags>(modifiers | eModifierAlt);
++ }
++
++ if (aEvent->ShiftKey()) {
++ modifiers = static_cast<ModifierFlags>(modifiers | eModifierShift);
++ }
++
++ if (aEvent->CtrlKey()) {
++ modifiers = static_cast<ModifierFlags>(modifiers | eModifierCtrl);
++ }
++
++ if (aEvent->MetaKey()) {
++ modifiers = static_cast<ModifierFlags>(modifiers | eModifierMeta);
++ }
++
++ return modifiers;
++}
++
++nsresult
++nsMenuBar::Keypress(dom::KeyboardEvent *aEvent)
++{
++ if (!ShouldHandleKeyEvent(aEvent)) {
++ return NS_OK;
++ }
++
++ ModifierFlags modifiers = GetModifiersFromEvent(aEvent);
++ if (((modifiers & mAccessKeyMask) == 0) ||
++ ((modifiers & ~mAccessKeyMask) != 0)) {
++ return NS_OK;
++ }
++
++ uint32_t charCode = aEvent->CharCode();
++ if (charCode == 0) {
++ return NS_OK;
++ }
++
++ char16_t ch = char16_t(charCode);
++ char16_t chl = ToLowerCase(ch);
++ char16_t chu = ToUpperCase(ch);
++
++ nsMenuObject *found = nullptr;
++ uint32_t count = ChildCount();
++ for (uint32_t i = 0; i < count; ++i) {
++ nsAutoString accesskey;
++ ChildAt(i)->ContentNode()->AsElement()->GetAttr(kNameSpaceID_None,
++ nsGkAtoms::accesskey,
++ accesskey);
++ const nsAutoString::char_type *key = accesskey.BeginReading();
++ if (*key == chu || *key == chl) {
++ found = ChildAt(i);
++ break;
++ }
++ }
++
++ if (!found || found->Type() != nsMenuObject::eType_Menu) {
++ return NS_OK;
++ }
++
++ ContentNode()->AsElement()->SetAttr(kNameSpaceID_None,
++ nsGkAtoms::openedwithkey,
++ u"true"_ns, true);
++ static_cast<nsMenu *>(found)->OpenMenu();
++
++ aEvent->StopPropagation();
++ aEvent->PreventDefault();
++
++ return NS_OK;
++}
++
++nsresult
++nsMenuBar::KeyDown(dom::KeyboardEvent *aEvent)
++{
++ if (!ShouldHandleKeyEvent(aEvent)) {
++ return NS_OK;
++ }
++
++ uint32_t keyCode = aEvent->KeyCode();
++ ModifierFlags modifiers = GetModifiersFromEvent(aEvent);
++ if ((keyCode != mAccessKey) || ((modifiers & ~mAccessKeyMask) != 0)) {
++ return NS_OK;
++ }
++
++ dbusmenu_server_set_status(mServer, DBUSMENU_STATUS_NOTICE);
++
++ return NS_OK;
++}
++
++nsresult
++nsMenuBar::KeyUp(dom::KeyboardEvent *aEvent)
++{
++ if (!ShouldHandleKeyEvent(aEvent)) {
++ return NS_OK;
++ }
++
++ uint32_t keyCode = aEvent->KeyCode();
++ if (keyCode == mAccessKey) {
++ dbusmenu_server_set_status(mServer, DBUSMENU_STATUS_NORMAL);
++ }
++
++ return NS_OK;
++}
++
++void
++nsMenuBar::HandleContentInserted(nsIContent *aChild, nsIContent *aPrevSibling)
++{
++ UniquePtr<nsMenuObject> child = CreateChild(aChild);
++
++ if (!child) {
++ return;
++ }
++
++ InsertChildAfter(std::move(child), aPrevSibling);
++}
++
++void
++nsMenuBar::HandleContentRemoved(nsIContent *aChild)
++{
++ RemoveChild(aChild);
++}
++
++void
++nsMenuBar::OnContentInserted(nsIContent *aContainer, nsIContent *aChild,
++ nsIContent *aPrevSibling)
++{
++ MOZ_ASSERT(aContainer == ContentNode(),
++ "Received an event that wasn't meant for us");
++
++ nsContentUtils::AddScriptRunner(
++ new nsMenuBarContentInsertedEvent(this, aChild, aPrevSibling));
++}
++
++void
++nsMenuBar::OnContentRemoved(nsIContent *aContainer, nsIContent *aChild)
++{
++ MOZ_ASSERT(aContainer == ContentNode(),
++ "Received an event that wasn't meant for us");
++
++ nsContentUtils::AddScriptRunner(
++ new nsMenuBarContentRemovedEvent(this, aChild));
++}
++
++nsMenuBar::~nsMenuBar()
++{
++ nsNativeMenuService *service = nsNativeMenuService::GetSingleton();
++ if (service) {
++ service->NotifyNativeMenuBarDestroyed(this);
++ }
++
++ if (ContentNode()) {
++ SetShellShowingMenuBar(false);
++ }
++
++ // We want to destroy all children before dropping our reference
++ // to the doc listener
++ while (ChildCount() > 0) {
++ RemoveChildAt(0);
++ }
++
++ if (mTopLevel) {
++ g_object_unref(mTopLevel);
++ }
++
++ if (DocListener()) {
++ DocListener()->Stop();
++ }
++
++ if (mDocument) {
++ DisconnectDocumentEventListeners();
++ }
++
++ if (mServer) {
++ g_object_unref(mServer);
++ }
++
++ MOZ_COUNT_DTOR(nsMenuBar);
++}
++
++/* static */ UniquePtr<nsMenuBar>
++nsMenuBar::Create(nsIWidget *aParent, nsIContent *aMenuBarNode)
++{
++ UniquePtr<nsMenuBar> menubar(new nsMenuBar(aMenuBarNode));
++ if (NS_FAILED(menubar->Init(aParent))) {
++ return nullptr;
++ }
++
++ return menubar;
++}
++
++nsMenuObject::EType
++nsMenuBar::Type() const
++{
++ return eType_MenuBar;
++}
++
++bool
++nsMenuBar::IsBeingDisplayed() const
++{
++ return true;
++}
++
++uint32_t
++nsMenuBar::WindowId() const
++{
++ return static_cast<uint32_t>(GDK_WINDOW_XID(gtk_widget_get_window(mTopLevel)));
++}
++
++nsCString
++nsMenuBar::ObjectPath() const
++{
++ gchar *tmp;
++ g_object_get(mServer, DBUSMENU_SERVER_PROP_DBUS_OBJECT, &tmp, NULL);
++
++ nsCString result;
++ result.Adopt(tmp);
++
++ return result;
++}
++
++void
++nsMenuBar::Activate()
++{
++ if (mIsActive) {
++ return;
++ }
++
++ mIsActive = true;
++
++ mDocument->AddEventListener(u"focus"_ns,
++ mEventListener,
++ true);
++ mDocument->AddEventListener(u"blur"_ns,
++ mEventListener,
++ true);
++ mDocument->AddEventListener(u"keypress"_ns,
++ mEventListener,
++ false);
++ mDocument->AddEventListener(u"keydown"_ns,
++ mEventListener,
++ false);
++ mDocument->AddEventListener(u"keyup"_ns,
++ mEventListener,
++ false);
++
++ // Clear this. Not sure if we really need to though
++ ContentNode()->AsElement()->SetAttr(kNameSpaceID_None,
++ nsGkAtoms::openedwithkey,
++ u"false"_ns, true);
++
++ DocListener()->Start();
++ Build();
++ SetShellShowingMenuBar(true);
++}
++
++void
++nsMenuBar::Deactivate()
++{
++ if (!mIsActive) {
++ return;
++ }
++
++ mIsActive = false;
++
++ SetShellShowingMenuBar(false);
++ while (ChildCount() > 0) {
++ RemoveChildAt(0);
++ }
++ DocListener()->Stop();
++ DisconnectDocumentEventListeners();
++}
+diff --git a/widget/gtk/nsMenuBar.h b/widget/gtk/nsMenuBar.h
+new file mode 100644
+index 000000000000..7a04316330c1
+--- /dev/null
++++ b/widget/gtk/nsMenuBar.h
+@@ -0,0 +1,111 @@
++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:expandtab:shiftwidth=4:tabstop=4:
++ */
++/* This Source Code Form is subject to the terms of the Mozilla Public
++ * License, v. 2.0. If a copy of the MPL was not distributed with this
++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
++
++#ifndef __nsMenuBar_h__
++#define __nsMenuBar_h__
++
++#include "mozilla/Attributes.h"
++#include "mozilla/UniquePtr.h"
++#include "nsCOMPtr.h"
++#include "nsString.h"
++
++#include "nsDbusmenu.h"
++#include "nsMenuContainer.h"
++#include "nsMenuObject.h"
++
++#include <gtk/gtk.h>
++
++class nsIContent;
++class nsIWidget;
++class nsMenuBarDocEventListener;
++
++namespace mozilla {
++namespace dom {
++class Document;
++class KeyboardEvent;
++}
++}
++
++/*
++ * The menubar class. There is one of these per window (and the window
++ * owns its menubar). Each menubar has an object path, and the service is
++ * responsible for telling the desktop shell which object path corresponds
++ * to a particular window. A menubar and its hierarchy also own a
++ * nsNativeMenuDocListener.
++ */
++class nsMenuBar final : public nsMenuContainer
++{
++public:
++ ~nsMenuBar() override;
++
++ static mozilla::UniquePtr<nsMenuBar> Create(nsIWidget *aParent,
++ nsIContent *aMenuBarNode);
++
++ nsMenuObject::EType Type() const override;
++
++ bool IsBeingDisplayed() const override;
++
++ // Get the native window ID for this menubar
++ uint32_t WindowId() const;
++
++ // Get the object path for this menubar
++ nsCString ObjectPath() const;
++
++ // Get the top-level GtkWindow handle
++ GtkWidget* TopLevelWindow() { return mTopLevel; }
++
++ // Called from the menuservice when the menubar is about to be registered.
++ // Causes the native menubar to be created, and the XUL menubar to be hidden
++ void Activate();
++
++ // Called from the menuservice when the menubar is no longer registered
++ // with the desktop shell. Will cause the XUL menubar to be shown again
++ void Deactivate();
++
++private:
++ class DocEventListener;
++ friend class nsMenuBarContentInsertedEvent;
++ friend class nsMenuBarContentRemovedEvent;
++
++ enum ModifierFlags {
++ eModifierShift = (1 << 0),
++ eModifierCtrl = (1 << 1),
++ eModifierAlt = (1 << 2),
++ eModifierMeta = (1 << 3)
++ };
++
++ nsMenuBar(nsIContent *aMenuBarNode);
++ nsresult Init(nsIWidget *aParent);
++ void Build();
++ void DisconnectDocumentEventListeners();
++ void SetShellShowingMenuBar(bool aShowing);
++ void Focus();
++ void Blur();
++ ModifierFlags GetModifiersFromEvent(mozilla::dom::KeyboardEvent *aEvent);
++ nsresult Keypress(mozilla::dom::KeyboardEvent *aEvent);
++ nsresult KeyDown(mozilla::dom::KeyboardEvent *aEvent);
++ nsresult KeyUp(mozilla::dom::KeyboardEvent *aEvent);
++
++ void HandleContentInserted(nsIContent *aChild,
++ nsIContent *aPrevSibling);
++ void HandleContentRemoved(nsIContent *aChild);
++
++ void OnContentInserted(nsIContent *aContainer, nsIContent *aChild,
++ nsIContent *aPrevSibling) override;
++ void OnContentRemoved(nsIContent *aContainer, nsIContent *aChild) override;
++
++ GtkWidget *mTopLevel;
++ DbusmenuServer *mServer;
++ nsCOMPtr<mozilla::dom::Document> mDocument;
++ RefPtr<DocEventListener> mEventListener;
++
++ uint32_t mAccessKey;
++ ModifierFlags mAccessKeyMask;
++ bool mIsActive;
++};
++
++#endif /* __nsMenuBar_h__ */
+diff --git a/widget/gtk/nsMenuContainer.cpp b/widget/gtk/nsMenuContainer.cpp
+new file mode 100644
+index 000000000000..f419201f6338
+--- /dev/null
++++ b/widget/gtk/nsMenuContainer.cpp
+@@ -0,0 +1,170 @@
++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:expandtab:shiftwidth=4:tabstop=4:
++ */
++/* This Source Code Form is subject to the terms of the Mozilla Public
++ * License, v. 2.0. If a copy of the MPL was not distributed with this
++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
++
++#include "mozilla/DebugOnly.h"
++#include "nsGkAtoms.h"
++#include "nsIContent.h"
++
++#include "nsDbusmenu.h"
++#include "nsMenu.h"
++#include "nsMenuItem.h"
++#include "nsMenuSeparator.h"
++
++#include "nsMenuContainer.h"
++
++using namespace mozilla;
++
++const nsMenuContainer::ChildTArray::index_type nsMenuContainer::NoIndex = nsMenuContainer::ChildTArray::NoIndex;
++
++typedef UniquePtr<nsMenuObject> (*nsMenuObjectConstructor)(nsMenuContainer*,
++ nsIContent*);
++
++template<class T>
++static UniquePtr<nsMenuObject> CreateMenuObject(nsMenuContainer *aContainer,
++ nsIContent *aContent)
++{
++ return UniquePtr<T>(new T(aContainer, aContent));
++}
++
++static nsMenuObjectConstructor
++GetMenuObjectConstructor(nsIContent *aContent)
++{
++ if (aContent->IsXULElement(nsGkAtoms::menuitem)) {
++ return CreateMenuObject<nsMenuItem>;
++ } else if (aContent->IsXULElement(nsGkAtoms::menu)) {
++ return CreateMenuObject<nsMenu>;
++ } else if (aContent->IsXULElement(nsGkAtoms::menuseparator)) {
++ return CreateMenuObject<nsMenuSeparator>;
++ }
++
++ return nullptr;
++}
++
++static bool
++ContentIsSupported(nsIContent *aContent)
++{
++ return GetMenuObjectConstructor(aContent) ? true : false;
++}
++
++nsMenuContainer::nsMenuContainer(nsMenuContainer *aParent,
++ nsIContent *aContent) :
++ nsMenuObject(aParent, aContent)
++{
++}
++
++nsMenuContainer::nsMenuContainer(nsNativeMenuDocListener *aListener,
++ nsIContent *aContent) :
++ nsMenuObject(aListener, aContent)
++{
++}
++
++UniquePtr<nsMenuObject>
++nsMenuContainer::CreateChild(nsIContent *aContent)
++{
++ nsMenuObjectConstructor ctor = GetMenuObjectConstructor(aContent);
++ if (!ctor) {
++ // There are plenty of node types we might stumble across that
++ // aren't supported
++ return nullptr;
++ }
++
++ UniquePtr<nsMenuObject> res = ctor(this, aContent);
++ return res;
++}
++
++size_t
++nsMenuContainer::IndexOf(nsIContent *aChild) const
++{
++ if (!aChild) {
++ return NoIndex;
++ }
++
++ size_t count = ChildCount();
++ for (size_t i = 0; i < count; ++i) {
++ if (ChildAt(i)->ContentNode() == aChild) {
++ return i;
++ }
++ }
++
++ return NoIndex;
++}
++
++void
++nsMenuContainer::RemoveChildAt(size_t aIndex, bool aUpdateNative)
++{
++ MOZ_ASSERT(aIndex < ChildCount());
++
++ if (aUpdateNative) {
++ MOZ_ALWAYS_TRUE(
++ dbusmenu_menuitem_child_delete(GetNativeData(),
++ ChildAt(aIndex)->GetNativeData()));
++ }
++
++ mChildren.RemoveElementAt(aIndex);
++}
++
++void
++nsMenuContainer::RemoveChild(nsIContent *aChild, bool aUpdateNative)
++{
++ size_t index = IndexOf(aChild);
++ if (index == NoIndex) {
++ return;
++ }
++
++ RemoveChildAt(index, aUpdateNative);
++}
++
++void
++nsMenuContainer::InsertChildAfter(UniquePtr<nsMenuObject> aChild,
++ nsIContent *aPrevSibling,
++ bool aUpdateNative)
++{
++ size_t index = IndexOf(aPrevSibling);
++ MOZ_ASSERT(!aPrevSibling || index != NoIndex);
++
++ ++index;
++
++ if (aUpdateNative) {
++ aChild->CreateNativeData();
++ MOZ_ALWAYS_TRUE(
++ dbusmenu_menuitem_child_add_position(GetNativeData(),
++ aChild->GetNativeData(),
++ index));
++ }
++
++ mChildren.InsertElementAt(index, std::move(aChild));
++}
++
++void
++nsMenuContainer::AppendChild(UniquePtr<nsMenuObject> aChild,
++ bool aUpdateNative)
++{
++ if (aUpdateNative) {
++ aChild->CreateNativeData();
++ MOZ_ALWAYS_TRUE(
++ dbusmenu_menuitem_child_append(GetNativeData(),
++ aChild->GetNativeData()));
++ }
++
++ mChildren.AppendElement(std::move(aChild));
++}
++
++bool
++nsMenuContainer::NeedsRebuild() const
++{
++ return false;
++}
++
++/* static */ nsIContent*
++nsMenuContainer::GetPreviousSupportedSibling(nsIContent *aContent)
++{
++ do {
++ aContent = aContent->GetPreviousSibling();
++ } while (aContent && !ContentIsSupported(aContent));
++
++ return aContent;
++}
+diff --git a/widget/gtk/nsMenuContainer.h b/widget/gtk/nsMenuContainer.h
+new file mode 100644
+index 000000000000..b7e8fa8db46f
+--- /dev/null
++++ b/widget/gtk/nsMenuContainer.h
+@@ -0,0 +1,70 @@
++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:expandtab:shiftwidth=4:tabstop=4:
++ */
++/* This Source Code Form is subject to the terms of the Mozilla Public
++ * License, v. 2.0. If a copy of the MPL was not distributed with this
++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
++
++#ifndef __nsMenuContainer_h__
++#define __nsMenuContainer_h__
++
++#include "mozilla/UniquePtr.h"
++#include "nsTArray.h"
++
++#include "nsMenuObject.h"
++
++class nsIContent;
++class nsNativeMenuDocListener;
++
++// Base class for containers (menus and menubars)
++class nsMenuContainer : public nsMenuObject
++{
++public:
++ typedef nsTArray<mozilla::UniquePtr<nsMenuObject> > ChildTArray;
++
++ // Determine if this container is being displayed on screen. Must be
++ // implemented by subclasses. Must return true if the container is
++ // in the fully open state, or false otherwise
++ virtual bool IsBeingDisplayed() const = 0;
++
++ // Determine if this container will be rebuilt the next time it opens.
++ // Returns false by default but can be overridden by subclasses
++ virtual bool NeedsRebuild() const;
++
++ // Return the first previous sibling that is of a type supported by the
++ // menu system
++ static nsIContent* GetPreviousSupportedSibling(nsIContent *aContent);
++
++ static const ChildTArray::index_type NoIndex;
++
++protected:
++ nsMenuContainer(nsMenuContainer *aParent, nsIContent *aContent);
++ nsMenuContainer(nsNativeMenuDocListener *aListener, nsIContent *aContent);
++
++ // Create a new child element for the specified content node
++ mozilla::UniquePtr<nsMenuObject> CreateChild(nsIContent *aContent);
++
++ // Return the index of the child for the specified content node
++ size_t IndexOf(nsIContent *aChild) const;
++
++ size_t ChildCount() const { return mChildren.Length(); }
++ nsMenuObject* ChildAt(size_t aIndex) const { return mChildren[aIndex].get(); }
++
++ void RemoveChildAt(size_t aIndex, bool aUpdateNative = true);
++
++ // Remove the child that owns the specified content node
++ void RemoveChild(nsIContent *aChild, bool aUpdateNative = true);
++
++ // Insert a new child after the child that owns the specified content node
++ void InsertChildAfter(mozilla::UniquePtr<nsMenuObject> aChild,
++ nsIContent *aPrevSibling,
++ bool aUpdateNative = true);
++
++ void AppendChild(mozilla::UniquePtr<nsMenuObject> aChild,
++ bool aUpdateNative = true);
++
++private:
++ ChildTArray mChildren;
++};
++
++#endif /* __nsMenuContainer_h__ */
+diff --git a/widget/gtk/nsMenuItem.cpp b/widget/gtk/nsMenuItem.cpp
+new file mode 100644
+index 000000000000..3a9915e609f5
+--- /dev/null
++++ b/widget/gtk/nsMenuItem.cpp
+@@ -0,0 +1,766 @@
++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:expandtab:shiftwidth=4:tabstop=4:
++ */
++/* This Source Code Form is subject to the terms of the Mozilla Public
++ * License, v. 2.0. If a copy of the MPL was not distributed with this
++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
++
++#include "mozilla/ArrayUtils.h"
++#include "mozilla/Assertions.h"
++#include "mozilla/dom/Document.h"
++#include "mozilla/dom/Element.h"
++#include "mozilla/dom/KeyboardEventBinding.h"
++#include "mozilla/dom/XULCommandEvent.h"
++#include "mozilla/Preferences.h"
++#include "mozilla/TextEvents.h"
++#include "nsContentUtils.h"
++#include "nsCRT.h"
++#include "nsGkAtoms.h"
++#include "nsGlobalWindowInner.h"
++#include "nsGtkUtils.h"
++#include "nsIContent.h"
++#include "nsIRunnable.h"
++#include "nsQueryObject.h"
++#include "nsReadableUtils.h"
++#include "nsString.h"
++#include "nsThreadUtils.h"
++
++#include "nsMenu.h"
++#include "nsMenuBar.h"
++#include "nsMenuContainer.h"
++#include "nsNativeMenuDocListener.h"
++
++#include <gdk/gdk.h>
++#include <gdk/gdkkeysyms.h>
++#include <gdk/gdkkeysyms-compat.h>
++#include <gdk/gdkx.h>
++#include <gtk/gtk.h>
++
++#include "nsMenuItem.h"
++
++using namespace mozilla;
++
++struct KeyCodeData {
++ const char* str;
++ size_t strlength;
++ uint32_t keycode;
++};
++
++static struct KeyCodeData gKeyCodes[] = {
++#define NS_DEFINE_VK(aDOMKeyName, aDOMKeyCode) \
++ { #aDOMKeyName, sizeof(#aDOMKeyName) - 1, aDOMKeyCode },
++#include "mozilla/VirtualKeyCodeList.h"
++#undef NS_DEFINE_VK
++ { nullptr, 0, 0 }
++};
++
++struct KeyPair {
++ uint32_t DOMKeyCode;
++ guint GDKKeyval;
++};
++
++//
++// Netscape keycodes are defined in widget/public/nsGUIEvent.h
++// GTK keycodes are defined in <gdk/gdkkeysyms.h>
++//
++static const KeyPair gKeyPairs[] = {
++ { NS_VK_CANCEL, GDK_Cancel },
++ { NS_VK_BACK, GDK_BackSpace },
++ { NS_VK_TAB, GDK_Tab },
++ { NS_VK_TAB, GDK_ISO_Left_Tab },
++ { NS_VK_CLEAR, GDK_Clear },
++ { NS_VK_RETURN, GDK_Return },
++ { NS_VK_SHIFT, GDK_Shift_L },
++ { NS_VK_SHIFT, GDK_Shift_R },
++ { NS_VK_SHIFT, GDK_Shift_Lock },
++ { NS_VK_CONTROL, GDK_Control_L },
++ { NS_VK_CONTROL, GDK_Control_R },
++ { NS_VK_ALT, GDK_Alt_L },
++ { NS_VK_ALT, GDK_Alt_R },
++ { NS_VK_META, GDK_Meta_L },
++ { NS_VK_META, GDK_Meta_R },
++
++ // Assume that Super or Hyper is always mapped to physical Win key.
++ { NS_VK_WIN, GDK_Super_L },
++ { NS_VK_WIN, GDK_Super_R },
++ { NS_VK_WIN, GDK_Hyper_L },
++ { NS_VK_WIN, GDK_Hyper_R },
++
++ // GTK's AltGraph key is similar to Mac's Option (Alt) key. However,
++ // unfortunately, browsers on Mac are using NS_VK_ALT for it even though
++ // it's really different from Alt key on Windows.
++ // On the other hand, GTK's AltGrapsh keys are really different from
++ // Alt key. However, there is no AltGrapsh key on Windows. On Windows,
++ // both Ctrl and Alt keys are pressed internally when AltGr key is pressed.
++ // For some languages' users, AltGraph key is important, so, web
++ // applications on such locale may want to know AltGraph key press.
++ // Therefore, we should map AltGr keycode for them only on GTK.
++ { NS_VK_ALTGR, GDK_ISO_Level3_Shift },
++ { NS_VK_ALTGR, GDK_ISO_Level5_Shift },
++ // We assume that Mode_switch is always used for level3 shift.
++ { NS_VK_ALTGR, GDK_Mode_switch },
++
++ { NS_VK_PAUSE, GDK_Pause },
++ { NS_VK_CAPS_LOCK, GDK_Caps_Lock },
++ { NS_VK_KANA, GDK_Kana_Lock },
++ { NS_VK_KANA, GDK_Kana_Shift },
++ { NS_VK_HANGUL, GDK_Hangul },
++ // { NS_VK_JUNJA, GDK_XXX },
++ // { NS_VK_FINAL, GDK_XXX },
++ { NS_VK_HANJA, GDK_Hangul_Hanja },
++ { NS_VK_KANJI, GDK_Kanji },
++ { NS_VK_ESCAPE, GDK_Escape },
++ { NS_VK_CONVERT, GDK_Henkan },
++ { NS_VK_NONCONVERT, GDK_Muhenkan },
++ // { NS_VK_ACCEPT, GDK_XXX },
++ // { NS_VK_MODECHANGE, GDK_XXX },
++ { NS_VK_SPACE, GDK_space },
++ { NS_VK_PAGE_UP, GDK_Page_Up },
++ { NS_VK_PAGE_DOWN, GDK_Page_Down },
++ { NS_VK_END, GDK_End },
++ { NS_VK_HOME, GDK_Home },
++ { NS_VK_LEFT, GDK_Left },
++ { NS_VK_UP, GDK_Up },
++ { NS_VK_RIGHT, GDK_Right },
++ { NS_VK_DOWN, GDK_Down },
++ { NS_VK_SELECT, GDK_Select },
++ { NS_VK_PRINT, GDK_Print },
++ { NS_VK_EXECUTE, GDK_Execute },
++ { NS_VK_PRINTSCREEN, GDK_Print },
++ { NS_VK_INSERT, GDK_Insert },
++ { NS_VK_DELETE, GDK_Delete },
++ { NS_VK_HELP, GDK_Help },
++
++ // keypad keys
++ { NS_VK_LEFT, GDK_KP_Left },
++ { NS_VK_RIGHT, GDK_KP_Right },
++ { NS_VK_UP, GDK_KP_Up },
++ { NS_VK_DOWN, GDK_KP_Down },
++ { NS_VK_PAGE_UP, GDK_KP_Page_Up },
++ // Not sure what these are
++ //{ NS_VK_, GDK_KP_Prior },
++ //{ NS_VK_, GDK_KP_Next },
++ { NS_VK_CLEAR, GDK_KP_Begin }, // Num-unlocked 5
++ { NS_VK_PAGE_DOWN, GDK_KP_Page_Down },
++ { NS_VK_HOME, GDK_KP_Home },
++ { NS_VK_END, GDK_KP_End },
++ { NS_VK_INSERT, GDK_KP_Insert },
++ { NS_VK_DELETE, GDK_KP_Delete },
++ { NS_VK_RETURN, GDK_KP_Enter },
++
++ { NS_VK_NUM_LOCK, GDK_Num_Lock },
++ { NS_VK_SCROLL_LOCK,GDK_Scroll_Lock },
++
++ // Function keys
++ { NS_VK_F1, GDK_F1 },
++ { NS_VK_F2, GDK_F2 },
++ { NS_VK_F3, GDK_F3 },
++ { NS_VK_F4, GDK_F4 },
++ { NS_VK_F5, GDK_F5 },
++ { NS_VK_F6, GDK_F6 },
++ { NS_VK_F7, GDK_F7 },
++ { NS_VK_F8, GDK_F8 },
++ { NS_VK_F9, GDK_F9 },
++ { NS_VK_F10, GDK_F10 },
++ { NS_VK_F11, GDK_F11 },
++ { NS_VK_F12, GDK_F12 },
++ { NS_VK_F13, GDK_F13 },
++ { NS_VK_F14, GDK_F14 },
++ { NS_VK_F15, GDK_F15 },
++ { NS_VK_F16, GDK_F16 },
++ { NS_VK_F17, GDK_F17 },
++ { NS_VK_F18, GDK_F18 },
++ { NS_VK_F19, GDK_F19 },
++ { NS_VK_F20, GDK_F20 },
++ { NS_VK_F21, GDK_F21 },
++ { NS_VK_F22, GDK_F22 },
++ { NS_VK_F23, GDK_F23 },
++ { NS_VK_F24, GDK_F24 },
++
++ // context menu key, keysym 0xff67, typically keycode 117 on 105-key (Microsoft)
++ // x86 keyboards, located between right 'Windows' key and right Ctrl key
++ { NS_VK_CONTEXT_MENU, GDK_Menu },
++ { NS_VK_SLEEP, GDK_Sleep },
++
++ { NS_VK_ATTN, GDK_3270_Attn },
++ { NS_VK_CRSEL, GDK_3270_CursorSelect },
++ { NS_VK_EXSEL, GDK_3270_ExSelect },
++ { NS_VK_EREOF, GDK_3270_EraseEOF },
++ { NS_VK_PLAY, GDK_3270_Play },
++ //{ NS_VK_ZOOM, GDK_XXX },
++ { NS_VK_PA1, GDK_3270_PA1 },
++};
++
++static guint
++ConvertGeckoKeyNameToGDKKeyval(nsAString& aKeyName)
++{
++ NS_ConvertUTF16toUTF8 keyName(aKeyName);
++ ToUpperCase(keyName); // We want case-insensitive comparison with data
++ // stored as uppercase.
++
++ uint32_t keyCode = 0;
++
++ uint32_t keyNameLength = keyName.Length();
++ const char* keyNameStr = keyName.get();
++ for (uint16_t i = 0; i < ArrayLength(gKeyCodes); ++i) {
++ if (keyNameLength == gKeyCodes[i].strlength &&
++ !nsCRT::strcmp(gKeyCodes[i].str, keyNameStr)) {
++ keyCode = gKeyCodes[i].keycode;
++ break;
++ }
++ }
++
++ // First, try to handle alphanumeric input, not listed in nsKeycodes:
++ // most likely, more letters will be getting typed in than things in
++ // the key list, so we will look through these first.
++
++ if (keyCode >= NS_VK_A && keyCode <= NS_VK_Z) {
++ // gdk and DOM both use the ASCII codes for these keys.
++ return keyCode;
++ }
++
++ // numbers
++ if (keyCode >= NS_VK_0 && keyCode <= NS_VK_9) {
++ // gdk and DOM both use the ASCII codes for these keys.
++ return keyCode - NS_VK_0 + GDK_0;
++ }
++
++ switch (keyCode) {
++ // keys in numpad
++ case NS_VK_MULTIPLY: return GDK_KP_Multiply;
++ case NS_VK_ADD: return GDK_KP_Add;
++ case NS_VK_SEPARATOR: return GDK_KP_Separator;
++ case NS_VK_SUBTRACT: return GDK_KP_Subtract;
++ case NS_VK_DECIMAL: return GDK_KP_Decimal;
++ case NS_VK_DIVIDE: return GDK_KP_Divide;
++ case NS_VK_NUMPAD0: return GDK_KP_0;
++ case NS_VK_NUMPAD1: return GDK_KP_1;
++ case NS_VK_NUMPAD2: return GDK_KP_2;
++ case NS_VK_NUMPAD3: return GDK_KP_3;
++ case NS_VK_NUMPAD4: return GDK_KP_4;
++ case NS_VK_NUMPAD5: return GDK_KP_5;
++ case NS_VK_NUMPAD6: return GDK_KP_6;
++ case NS_VK_NUMPAD7: return GDK_KP_7;
++ case NS_VK_NUMPAD8: return GDK_KP_8;
++ case NS_VK_NUMPAD9: return GDK_KP_9;
++ // other prinable keys
++ case NS_VK_SPACE: return GDK_space;
++ case NS_VK_COLON: return GDK_colon;
++ case NS_VK_SEMICOLON: return GDK_semicolon;
++ case NS_VK_LESS_THAN: return GDK_less;
++ case NS_VK_EQUALS: return GDK_equal;
++ case NS_VK_GREATER_THAN: return GDK_greater;
++ case NS_VK_QUESTION_MARK: return GDK_question;
++ case NS_VK_AT: return GDK_at;
++ case NS_VK_CIRCUMFLEX: return GDK_asciicircum;
++ case NS_VK_EXCLAMATION: return GDK_exclam;
++ case NS_VK_DOUBLE_QUOTE: return GDK_quotedbl;
++ case NS_VK_HASH: return GDK_numbersign;
++ case NS_VK_DOLLAR: return GDK_dollar;
++ case NS_VK_PERCENT: return GDK_percent;
++ case NS_VK_AMPERSAND: return GDK_ampersand;
++ case NS_VK_UNDERSCORE: return GDK_underscore;
++ case NS_VK_OPEN_PAREN: return GDK_parenleft;
++ case NS_VK_CLOSE_PAREN: return GDK_parenright;
++ case NS_VK_ASTERISK: return GDK_asterisk;
++ case NS_VK_PLUS: return GDK_plus;
++ case NS_VK_PIPE: return GDK_bar;
++ case NS_VK_HYPHEN_MINUS: return GDK_minus;
++ case NS_VK_OPEN_CURLY_BRACKET: return GDK_braceleft;
++ case NS_VK_CLOSE_CURLY_BRACKET: return GDK_braceright;
++ case NS_VK_TILDE: return GDK_asciitilde;
++ case NS_VK_COMMA: return GDK_comma;
++ case NS_VK_PERIOD: return GDK_period;
++ case NS_VK_SLASH: return GDK_slash;
++ case NS_VK_BACK_QUOTE: return GDK_grave;
++ case NS_VK_OPEN_BRACKET: return GDK_bracketleft;
++ case NS_VK_BACK_SLASH: return GDK_backslash;
++ case NS_VK_CLOSE_BRACKET: return GDK_bracketright;
++ case NS_VK_QUOTE: return GDK_apostrophe;
++ }
++
++ // misc other things
++ for (uint32_t i = 0; i < ArrayLength(gKeyPairs); ++i) {
++ if (gKeyPairs[i].DOMKeyCode == keyCode) {
++ return gKeyPairs[i].GDKKeyval;
++ }
++ }
++
++ return 0;
++}
++
++class nsMenuItemUncheckSiblingsRunnable final : public Runnable
++{
++public:
++ NS_IMETHODIMP Run()
++ {
++ if (mMenuItem) {
++ static_cast<nsMenuItem *>(mMenuItem.get())->UncheckSiblings();
++ }
++ return NS_OK;
++ }
++
++ nsMenuItemUncheckSiblingsRunnable(nsMenuItem *aMenuItem) :
++ Runnable("nsMenuItemUncheckSiblingsRunnable"),
++ mMenuItem(aMenuItem) { };
++
++private:
++ nsWeakMenuObject mMenuItem;
++};
++
++bool
++nsMenuItem::IsCheckboxOrRadioItem() const
++{
++ return mType == eMenuItemType_Radio ||
++ mType == eMenuItemType_CheckBox;
++}
++
++/* static */ void
++nsMenuItem::item_activated_cb(DbusmenuMenuitem *menuitem,
++ guint timestamp,
++ gpointer user_data)
++{
++ nsMenuItem *item = static_cast<nsMenuItem *>(user_data);
++ item->Activate(timestamp);
++}
++
++void
++nsMenuItem::Activate(uint32_t aTimestamp)
++{
++ GdkWindow *window = gtk_widget_get_window(MenuBar()->TopLevelWindow());
++ gdk_x11_window_set_user_time(
++ window, std::min(aTimestamp, gdk_x11_get_server_time(window)));
++
++ // We do this to avoid mutating our view of the menu until
++ // after we have finished
++ nsNativeMenuDocListener::BlockUpdatesScope updatesBlocker;
++
++ if (!ContentNode()->AsElement()->AttrValueIs(kNameSpaceID_None,
++ nsGkAtoms::autocheck,
++ nsGkAtoms::_false,
++ eCaseMatters) &&
++ (mType == eMenuItemType_CheckBox ||
++ (mType == eMenuItemType_Radio && !mIsChecked))) {
++ ContentNode()->AsElement()->SetAttr(kNameSpaceID_None,
++ nsGkAtoms::checked,
++ mIsChecked ?
++ u"false"_ns
++ : u"true"_ns,
++ true);
++ }
++
++ dom::Document *doc = ContentNode()->OwnerDoc();
++ ErrorResult rv;
++ RefPtr<dom::Event> event =
++ doc->CreateEvent(u"xulcommandevent"_ns,
++ dom::CallerType::System, rv);
++ if (!rv.Failed()) {
++ RefPtr<dom::XULCommandEvent> command = event->AsXULCommandEvent();
++ if (command) {
++ command->InitCommandEvent(u"command"_ns, true, true,
++ nsGlobalWindowInner::Cast(doc->GetInnerWindow()),
++ 0, false, false, false, false, 0, nullptr, 0, rv);
++ if (!rv.Failed()) {
++ event->SetTrusted(true);
++ ContentNode()->DispatchEvent(*event, rv);
++ if (rv.Failed()) {
++ NS_WARNING("Failed to dispatch event");
++ rv.SuppressException();
++ }
++ } else {
++ NS_WARNING("Failed to initialize command event");
++ rv.SuppressException();
++ }
++ }
++ } else {
++ NS_WARNING("CreateEvent failed");
++ rv.SuppressException();
++ }
++
++ // This kinda sucks, but Unity doesn't send a closed event
++ // after activating a menuitem
++ nsMenuObject *ancestor = Parent();
++ while (ancestor && ancestor->Type() == eType_Menu) {
++ static_cast<nsMenu *>(ancestor)->OnClose();
++ ancestor = ancestor->Parent();
++ }
++}
++
++void
++nsMenuItem::CopyAttrFromNodeIfExists(nsIContent *aContent, nsAtom *aAttribute)
++{
++ nsAutoString value;
++ if (aContent->AsElement()->GetAttr(kNameSpaceID_None, aAttribute, value)) {
++ ContentNode()->AsElement()->SetAttr(kNameSpaceID_None, aAttribute,
++ value, true);
++ }
++}
++
++void
++nsMenuItem::UpdateState()
++{
++ if (!IsCheckboxOrRadioItem()) {
++ return;
++ }
++
++ mIsChecked = ContentNode()->AsElement()->AttrValueIs(kNameSpaceID_None,
++ nsGkAtoms::checked,
++ nsGkAtoms::_true,
++ eCaseMatters);
++ dbusmenu_menuitem_property_set_int(GetNativeData(),
++ DBUSMENU_MENUITEM_PROP_TOGGLE_STATE,
++ mIsChecked ?
++ DBUSMENU_MENUITEM_TOGGLE_STATE_CHECKED :
++ DBUSMENU_MENUITEM_TOGGLE_STATE_UNCHECKED);
++}
++
++void
++nsMenuItem::UpdateTypeAndState()
++{
++ static mozilla::dom::Element::AttrValuesArray attrs[] =
++ { nsGkAtoms::checkbox, nsGkAtoms::radio, nullptr };
++ int32_t type = ContentNode()->AsElement()->FindAttrValueIn(kNameSpaceID_None,
++ nsGkAtoms::type,
++ attrs, eCaseMatters);
++
++ if (type >= 0 && type < 2) {
++ if (type == 0) {
++ dbusmenu_menuitem_property_set(GetNativeData(),
++ DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE,
++ DBUSMENU_MENUITEM_TOGGLE_CHECK);
++ mType = eMenuItemType_CheckBox;
++ } else if (type == 1) {
++ dbusmenu_menuitem_property_set(GetNativeData(),
++ DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE,
++ DBUSMENU_MENUITEM_TOGGLE_RADIO);
++ mType = eMenuItemType_Radio;
++ }
++
++ UpdateState();
++ } else {
++ dbusmenu_menuitem_property_remove(GetNativeData(),
++ DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE);
++ dbusmenu_menuitem_property_remove(GetNativeData(),
++ DBUSMENU_MENUITEM_PROP_TOGGLE_STATE);
++ mType = eMenuItemType_Normal;
++ }
++}
++
++void
++nsMenuItem::UpdateAccel()
++{
++ dom::Document *doc = ContentNode()->GetUncomposedDoc();
++ if (doc) {
++ nsCOMPtr<nsIContent> oldKeyContent;
++ oldKeyContent.swap(mKeyContent);
++
++ nsAutoString key;
++ ContentNode()->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::key,
++ key);
++ if (!key.IsEmpty()) {
++ mKeyContent = doc->GetElementById(key);
++ }
++
++ if (mKeyContent != oldKeyContent) {
++ if (oldKeyContent) {
++ DocListener()->UnregisterForContentChanges(oldKeyContent);
++ }
++ if (mKeyContent) {
++ DocListener()->RegisterForContentChanges(mKeyContent, this);
++ }
++ }
++ }
++
++ if (!mKeyContent) {
++ dbusmenu_menuitem_property_remove(GetNativeData(),
++ DBUSMENU_MENUITEM_PROP_SHORTCUT);
++ return;
++ }
++
++ nsAutoString modifiers;
++ mKeyContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::modifiers,
++ modifiers);
++
++ uint32_t modifier = 0;
++
++ if (!modifiers.IsEmpty()) {
++ char* str = ToNewUTF8String(modifiers);
++ char *token = strtok(str, ", \t");
++ while(token) {
++ if (nsCRT::strcmp(token, "shift") == 0) {
++ modifier |= GDK_SHIFT_MASK;
++ } else if (nsCRT::strcmp(token, "alt") == 0) {
++ modifier |= GDK_MOD1_MASK;
++ } else if (nsCRT::strcmp(token, "meta") == 0) {
++ modifier |= GDK_META_MASK;
++ } else if (nsCRT::strcmp(token, "control") == 0) {
++ modifier |= GDK_CONTROL_MASK;
++ } else if (nsCRT::strcmp(token, "accel") == 0) {
++ int32_t accel = Preferences::GetInt("ui.key.accelKey");
++ if (accel == dom::KeyboardEvent_Binding::DOM_VK_META) {
++ modifier |= GDK_META_MASK;
++ } else if (accel == dom::KeyboardEvent_Binding::DOM_VK_ALT) {
++ modifier |= GDK_MOD1_MASK;
++ } else {
++ modifier |= GDK_CONTROL_MASK;
++ }
++ }
++
++ token = strtok(nullptr, ", \t");
++ }
++
++ free(str);
++ }
++
++ nsAutoString keyStr;
++ mKeyContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::key,
++ keyStr);
++
++ guint key = 0;
++ if (!keyStr.IsEmpty()) {
++ key = gdk_unicode_to_keyval(*keyStr.BeginReading());
++ }
++
++ if (key == 0) {
++ mKeyContent->AsElement()->GetAttr(kNameSpaceID_None,
++ nsGkAtoms::keycode, keyStr);
++ if (!keyStr.IsEmpty()) {
++ key = ConvertGeckoKeyNameToGDKKeyval(keyStr);
++ }
++ }
++
++ if (key == 0) {
++ key = GDK_VoidSymbol;
++ }
++
++ if (key != GDK_VoidSymbol) {
++ dbusmenu_menuitem_property_set_shortcut(GetNativeData(), key,
++ static_cast<GdkModifierType>(modifier));
++ } else {
++ dbusmenu_menuitem_property_remove(GetNativeData(),
++ DBUSMENU_MENUITEM_PROP_SHORTCUT);
++ }
++}
++
++nsMenuBar*
++nsMenuItem::MenuBar()
++{
++ nsMenuObject *tmp = this;
++ while (tmp->Parent()) {
++ tmp = tmp->Parent();
++ }
++
++ MOZ_ASSERT(tmp->Type() == eType_MenuBar, "The top-level should be a menubar");
++
++ return static_cast<nsMenuBar *>(tmp);
++}
++
++void
++nsMenuItem::UncheckSiblings()
++{
++ if (!ContentNode()->AsElement()->AttrValueIs(kNameSpaceID_None,
++ nsGkAtoms::type,
++ nsGkAtoms::radio,
++ eCaseMatters)) {
++ // If we're not a radio button, we don't care
++ return;
++ }
++
++ nsAutoString name;
++ ContentNode()->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::name,
++ name);
++
++ nsIContent *parent = ContentNode()->GetParent();
++ if (!parent) {
++ return;
++ }
++
++ uint32_t count = parent->GetChildCount();
++ for (uint32_t i = 0; i < count; ++i) {
++ nsIContent *sibling = parent->GetChildAt_Deprecated(i);
++
++ if (sibling->IsComment()) {
++ continue;
++ }
++
++ nsAutoString otherName;
++ sibling->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::name,
++ otherName);
++
++ if (sibling != ContentNode() && otherName == name &&
++ sibling->AsElement()->AttrValueIs(kNameSpaceID_None,
++ nsGkAtoms::type,
++ nsGkAtoms::radio,
++ eCaseMatters)) {
++ sibling->AsElement()->UnsetAttr(kNameSpaceID_None,
++ nsGkAtoms::checked, true);
++ }
++ }
++}
++
++void
++nsMenuItem::InitializeNativeData()
++{
++ g_signal_connect(G_OBJECT(GetNativeData()),
++ DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED,
++ G_CALLBACK(item_activated_cb), this);
++ mNeedsUpdate = true;
++}
++
++void
++nsMenuItem::UpdateContentAttributes()
++{
++ dom::Document *doc = ContentNode()->GetUncomposedDoc();
++ if (!doc) {
++ return;
++ }
++
++ nsAutoString command;
++ ContentNode()->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::command,
++ command);
++ if (command.IsEmpty()) {
++ return;
++ }
++
++ nsCOMPtr<nsIContent> commandContent = doc->GetElementById(command);
++ if (!commandContent) {
++ return;
++ }
++
++ if (commandContent->AsElement()->AttrValueIs(kNameSpaceID_None,
++ nsGkAtoms::disabled,
++ nsGkAtoms::_true,
++ eCaseMatters)) {
++ ContentNode()->AsElement()->SetAttr(kNameSpaceID_None,
++ nsGkAtoms::disabled,
++ u"true"_ns, true);
++ } else {
++ ContentNode()->AsElement()->UnsetAttr(kNameSpaceID_None,
++ nsGkAtoms::disabled, true);
++ }
++
++ CopyAttrFromNodeIfExists(commandContent, nsGkAtoms::checked);
++ CopyAttrFromNodeIfExists(commandContent, nsGkAtoms::accesskey);
++ CopyAttrFromNodeIfExists(commandContent, nsGkAtoms::label);
++ CopyAttrFromNodeIfExists(commandContent, nsGkAtoms::hidden);
++}
++
++void
++nsMenuItem::Update(const ComputedStyle *aComputedStyle)
++{
++ if (mNeedsUpdate) {
++ mNeedsUpdate = false;
++
++ UpdateTypeAndState();
++ UpdateAccel();
++ UpdateLabel();
++ UpdateSensitivity();
++ }
++
++ UpdateVisibility(aComputedStyle);
++ UpdateIcon(aComputedStyle);
++}
++
++bool
++nsMenuItem::IsCompatibleWithNativeData(DbusmenuMenuitem *aNativeData) const
++{
++ return nsCRT::strcmp(dbusmenu_menuitem_property_get(aNativeData,
++ DBUSMENU_MENUITEM_PROP_TYPE),
++ "separator") != 0;
++}
++
++nsMenuObject::PropertyFlags
++nsMenuItem::SupportedProperties() const
++{
++ return static_cast<nsMenuObject::PropertyFlags>(
++ nsMenuObject::ePropLabel |
++ nsMenuObject::ePropEnabled |
++ nsMenuObject::ePropVisible |
++ nsMenuObject::ePropIconData |
++ nsMenuObject::ePropShortcut |
++ nsMenuObject::ePropToggleType |
++ nsMenuObject::ePropToggleState
++ );
++}
++
++void
++nsMenuItem::OnAttributeChanged(nsIContent *aContent, nsAtom *aAttribute)
++{
++ MOZ_ASSERT(aContent == ContentNode() || aContent == mKeyContent,
++ "Received an event that wasn't meant for us!");
++
++ if (aContent == ContentNode() && aAttribute == nsGkAtoms::checked &&
++ aContent->AsElement()->AttrValueIs(kNameSpaceID_None,
++ nsGkAtoms::checked,
++ nsGkAtoms::_true, eCaseMatters)) {
++ nsContentUtils::AddScriptRunner(
++ new nsMenuItemUncheckSiblingsRunnable(this));
++ }
++
++ if (mNeedsUpdate) {
++ return;
++ }
++
++ if (!Parent()->IsBeingDisplayed()) {
++ mNeedsUpdate = true;
++ return;
++ }
++
++ if (aContent == ContentNode()) {
++ if (aAttribute == nsGkAtoms::key) {
++ UpdateAccel();
++ } else if (aAttribute == nsGkAtoms::label ||
++ aAttribute == nsGkAtoms::accesskey ||
++ aAttribute == nsGkAtoms::crop) {
++ UpdateLabel();
++ } else if (aAttribute == nsGkAtoms::disabled) {
++ UpdateSensitivity();
++ } else if (aAttribute == nsGkAtoms::type) {
++ UpdateTypeAndState();
++ } else if (aAttribute == nsGkAtoms::checked) {
++ UpdateState();
++ } else if (aAttribute == nsGkAtoms::hidden ||
++ aAttribute == nsGkAtoms::collapsed) {
++ RefPtr<const ComputedStyle> style = GetComputedStyle();
++ UpdateVisibility(style);
++ } else if (aAttribute == nsGkAtoms::image) {
++ RefPtr<const ComputedStyle> style = GetComputedStyle();
++ UpdateIcon(style);
++ }
++ } else if (aContent == mKeyContent &&
++ (aAttribute == nsGkAtoms::key ||
++ aAttribute == nsGkAtoms::keycode ||
++ aAttribute == nsGkAtoms::modifiers)) {
++ UpdateAccel();
++ }
++}
++
++nsMenuItem::nsMenuItem(nsMenuContainer *aParent, nsIContent *aContent) :
++ nsMenuObject(aParent, aContent),
++ mType(eMenuItemType_Normal),
++ mIsChecked(false),
++ mNeedsUpdate(false)
++{
++ MOZ_COUNT_CTOR(nsMenuItem);
++}
++
++nsMenuItem::~nsMenuItem()
++{
++ if (DocListener() && mKeyContent) {
++ DocListener()->UnregisterForContentChanges(mKeyContent);
++ }
++
++ if (GetNativeData()) {
++ g_signal_handlers_disconnect_by_func(GetNativeData(),
++ FuncToGpointer(item_activated_cb),
++ this);
++ }
++
++ MOZ_COUNT_DTOR(nsMenuItem);
++}
++
++nsMenuObject::EType
++nsMenuItem::Type() const
++{
++ return eType_MenuItem;
++}
+diff --git a/widget/gtk/nsMenuItem.h b/widget/gtk/nsMenuItem.h
+new file mode 100644
+index 000000000000..c621b4e223f1
+--- /dev/null
++++ b/widget/gtk/nsMenuItem.h
+@@ -0,0 +1,80 @@
++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:expandtab:shiftwidth=4:tabstop=4:
++ */
++/* This Source Code Form is subject to the terms of the Mozilla Public
++ * License, v. 2.0. If a copy of the MPL was not distributed with this
++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
++
++#ifndef __nsMenuItem_h__
++#define __nsMenuItem_h__
++
++#include "mozilla/Attributes.h"
++#include "nsCOMPtr.h"
++
++#include "nsDbusmenu.h"
++#include "nsMenuObject.h"
++
++#include <glib.h>
++
++class nsAtom;
++class nsIContent;
++class nsMenuBar;
++class nsMenuContainer;
++
++/*
++ * This class represents 3 main classes of menuitems: labels, checkboxes and
++ * radio buttons (with/without an icon)
++ */
++class nsMenuItem final : public nsMenuObject
++{
++public:
++ nsMenuItem(nsMenuContainer *aParent, nsIContent *aContent);
++ ~nsMenuItem() override;
++
++ nsMenuObject::EType Type() const override;
++
++private:
++ friend class nsMenuItemUncheckSiblingsRunnable;
++
++ enum {
++ eMenuItemFlag_ToggleState = (1 << 0)
++ };
++
++ enum EMenuItemType {
++ eMenuItemType_Normal,
++ eMenuItemType_Radio,
++ eMenuItemType_CheckBox
++ };
++
++ bool IsCheckboxOrRadioItem() const;
++
++ static void item_activated_cb(DbusmenuMenuitem *menuitem,
++ guint timestamp,
++ gpointer user_data);
++ void Activate(uint32_t aTimestamp);
++
++ void CopyAttrFromNodeIfExists(nsIContent *aContent, nsAtom *aAtom);
++ void UpdateState();
++ void UpdateTypeAndState();
++ void UpdateAccel();
++ nsMenuBar* MenuBar();
++ void UncheckSiblings();
++
++ void InitializeNativeData() override;
++ void UpdateContentAttributes() override;
++ void Update(const mozilla::ComputedStyle *aComputedStyle) override;
++ bool IsCompatibleWithNativeData(DbusmenuMenuitem *aNativeData) const override;
++ nsMenuObject::PropertyFlags SupportedProperties() const override;
++
++ void OnAttributeChanged(nsIContent *aContent, nsAtom *aAttribute) override;
++
++ EMenuItemType mType;
++
++ bool mIsChecked;
++
++ bool mNeedsUpdate;
++
++ nsCOMPtr<nsIContent> mKeyContent;
++};
++
++#endif /* __nsMenuItem_h__ */
+diff --git a/widget/gtk/nsMenuObject.cpp b/widget/gtk/nsMenuObject.cpp
+new file mode 100644
+index 000000000000..fbf0fbef1368
+--- /dev/null
++++ b/widget/gtk/nsMenuObject.cpp
+@@ -0,0 +1,664 @@
++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:expandtab:shiftwidth=4:tabstop=4:
++ */
++/* This Source Code Form is subject to the terms of the Mozilla Public
++ * License, v. 2.0. If a copy of the MPL was not distributed with this
++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
++
++#include "ImageOps.h"
++#include "imgIContainer.h"
++#include "imgINotificationObserver.h"
++#include "imgLoader.h"
++#include "imgRequestProxy.h"
++#include "mozilla/ArrayUtils.h"
++#include "mozilla/Assertions.h"
++#include "mozilla/dom/Document.h"
++#include "mozilla/dom/Element.h"
++#include "mozilla/Preferences.h"
++#include "mozilla/PresShell.h"
++#include "mozilla/PresShellInlines.h"
++#include "mozilla/GRefPtr.h"
++#include "nsAttrValue.h"
++#include "nsComputedDOMStyle.h"
++#include "nsContentUtils.h"
++#include "nsGkAtoms.h"
++#include "nsIContent.h"
++#include "nsIContentPolicy.h"
++#include "nsILoadGroup.h"
++#include "nsImageToPixbuf.h"
++#include "nsIURI.h"
++#include "nsNetUtil.h"
++#include "nsPresContext.h"
++#include "nsRect.h"
++#include "nsServiceManagerUtils.h"
++#include "nsString.h"
++#include "nsStyleConsts.h"
++#include "nsStyleStruct.h"
++#include "nsUnicharUtils.h"
++
++#include "nsMenuContainer.h"
++#include "nsNativeMenuDocListener.h"
++
++#include <gdk/gdk.h>
++#include <glib-object.h>
++#include <pango/pango.h>
++
++#include "nsMenuObject.h"
++
++// X11's None clashes with StyleDisplay::None
++#include "X11UndefineNone.h"
++
++#undef None
++
++using namespace mozilla;
++using mozilla::image::ImageOps;
++
++#define MAX_WIDTH 350000
++
++const char *gPropertyStrings[] = {
++#define DBUSMENU_PROPERTY(e, s, b) s,
++ DBUSMENU_PROPERTIES
++#undef DBUSMENU_PROPERTY
++ nullptr
++};
++
++nsWeakMenuObject* nsWeakMenuObject::sHead;
++PangoLayout* gPangoLayout = nullptr;
++
++class nsMenuObjectIconLoader final : public imgINotificationObserver
++{
++public:
++ NS_DECL_ISUPPORTS
++ NS_DECL_IMGINOTIFICATIONOBSERVER
++
++ nsMenuObjectIconLoader(nsMenuObject *aOwner) : mOwner(aOwner) { };
++
++ void LoadIcon(const ComputedStyle *aComputedStyle);
++ void Destroy();
++
++private:
++ ~nsMenuObjectIconLoader() { };
++
++ nsMenuObject *mOwner;
++ RefPtr<imgRequestProxy> mImageRequest;
++ nsCOMPtr<nsIURI> mURI;
++ nsIntRect mImageRect;
++};
++
++NS_IMPL_ISUPPORTS(nsMenuObjectIconLoader, imgINotificationObserver)
++
++void
++nsMenuObjectIconLoader::Notify(imgIRequest *aProxy,
++ int32_t aType, const nsIntRect *aRect)
++{
++ if (!mOwner) {
++ return;
++ }
++
++ if (aProxy != mImageRequest) {
++ return;
++ }
++
++ if (aType == imgINotificationObserver::LOAD_COMPLETE) {
++ uint32_t status = imgIRequest::STATUS_ERROR;
++ if (NS_FAILED(mImageRequest->GetImageStatus(&status)) ||
++ (status & imgIRequest::STATUS_ERROR)) {
++ mImageRequest->Cancel(NS_BINDING_ABORTED);
++ mImageRequest = nullptr;
++ return;
++ }
++
++ nsCOMPtr<imgIContainer> image;
++ mImageRequest->GetImage(getter_AddRefs(image));
++ MOZ_ASSERT(image);
++
++ // Ask the image to decode at its intrinsic size.
++ int32_t width = 0, height = 0;
++ image->GetWidth(&width);
++ image->GetHeight(&height);
++ image->RequestDecodeForSize(nsIntSize(width, height), imgIContainer::FLAG_NONE);
++ return;
++ }
++
++ if (aType == imgINotificationObserver::DECODE_COMPLETE) {
++ mImageRequest->Cancel(NS_BINDING_ABORTED);
++ mImageRequest = nullptr;
++ return;
++ }
++
++ if (aType != imgINotificationObserver::FRAME_COMPLETE) {
++ return;
++ }
++
++ nsCOMPtr<imgIContainer> img;
++ mImageRequest->GetImage(getter_AddRefs(img));
++ if (!img) {
++ return;
++ }
++
++ if (!mImageRect.IsEmpty()) {
++ img = ImageOps::Clip(img, mImageRect);
++ }
++
++ int32_t width, height;
++ img->GetWidth(&width);
++ img->GetHeight(&height);
++
++ if (width <= 0 || height <= 0) {
++ mOwner->ClearIcon();
++ return;
++ }
++
++ if (width > 100 || height > 100) {
++ // The icon data needs to go across DBus. Make sure the icon
++ // data isn't too large, else our connection gets terminated and
++ // GDbus helpfully aborts the application. Thank you :)
++ NS_WARNING("Icon data too large");
++ mOwner->ClearIcon();
++ return;
++ }
++
++ RefPtr<GdkPixbuf> pixbuf = nsImageToPixbuf::ImageToPixbuf(img);
++ if (pixbuf) {
++ dbusmenu_menuitem_property_set_image(mOwner->GetNativeData(),
++ DBUSMENU_MENUITEM_PROP_ICON_DATA,
++ pixbuf);
++ }
++
++ return;
++}
++
++void
++nsMenuObjectIconLoader::LoadIcon(const ComputedStyle *aComputedStyle)
++{
++ dom::Document *doc = mOwner->ContentNode()->OwnerDoc();
++
++ nsCOMPtr<nsIURI> uri;
++ nsIntRect imageRect;
++ imgRequestProxy *imageRequest = nullptr;
++
++ nsAutoString uriString;
++ if (mOwner->ContentNode()->AsElement()->GetAttr(kNameSpaceID_None,
++ nsGkAtoms::image,
++ uriString)) {
++ NS_NewURI(getter_AddRefs(uri), uriString);
++ } else {
++ PresShell *shell = doc->GetPresShell();
++ if (!shell) {
++ return;
++ }
++
++ nsPresContext *pc = shell->GetPresContext();
++ if (!pc || !aComputedStyle) {
++ return;
++ }
++
++ const nsStyleList *list = aComputedStyle->StyleList();
++ imageRequest = list->mListStyleImage.GetImageRequest();
++ if (imageRequest) {
++ imageRequest->GetURI(getter_AddRefs(uri));
++ auto& rect = list->mImageRegion.AsRect();
++ imageRect = rect.ToLayoutRect().ToNearestPixels(
++ pc->AppUnitsPerDevPixel());
++ }
++ }
++
++ if (!uri) {
++ mOwner->ClearIcon();
++ mURI = nullptr;
++
++ if (mImageRequest) {
++ mImageRequest->Cancel(NS_BINDING_ABORTED);
++ mImageRequest = nullptr;
++ }
++
++ return;
++ }
++
++ bool same;
++ if (mURI && NS_SUCCEEDED(mURI->Equals(uri, &same)) && same &&
++ (!imageRequest || imageRect == mImageRect)) {
++ return;
++ }
++
++ if (mImageRequest) {
++ mImageRequest->Cancel(NS_BINDING_ABORTED);
++ mImageRequest = nullptr;
++ }
++
++ mURI = uri;
++
++ if (imageRequest) {
++ mImageRect = imageRect;
++ imageRequest->Clone(this, nullptr, getter_AddRefs(mImageRequest));
++ } else {
++ mImageRect.SetEmpty();
++ nsCOMPtr<nsILoadGroup> loadGroup = doc->GetDocumentLoadGroup();
++ RefPtr<imgLoader> loader =
++ nsContentUtils::GetImgLoaderForDocument(doc);
++ if (!loader || !loadGroup) {
++ NS_WARNING("Failed to get loader or load group for image load");
++ return;
++ }
++
++ loader->LoadImage(uri, nullptr, nullptr,
++ nullptr, 0, loadGroup, this, nullptr, nullptr,
++ nsIRequest::LOAD_NORMAL, nullptr,
++ nsIContentPolicy::TYPE_IMAGE, EmptyString(),
++ false, false, getter_AddRefs(mImageRequest));
++ }
++}
++
++void
++nsMenuObjectIconLoader::Destroy()
++{
++ if (mImageRequest) {
++ mImageRequest->CancelAndForgetObserver(NS_BINDING_ABORTED);
++ mImageRequest = nullptr;
++ }
++
++ mOwner = nullptr;
++}
++
++static int
++CalculateTextWidth(const nsAString& aText)
++{
++ if (!gPangoLayout) {
++ PangoFontMap *fontmap = pango_cairo_font_map_get_default();
++ PangoContext *ctx = pango_font_map_create_context(fontmap);
++ gPangoLayout = pango_layout_new(ctx);
++ g_object_unref(ctx);
++ }
++
++ pango_layout_set_text(gPangoLayout, NS_ConvertUTF16toUTF8(aText).get(), -1);
++
++ int width, dummy;
++ pango_layout_get_size(gPangoLayout, &width, &dummy);
++
++ return width;
++}
++
++static const nsDependentString
++GetEllipsis()
++{
++ static char16_t sBuf[4] = { 0, 0, 0, 0 };
++ if (!sBuf[0]) {
++ nsString ellipsis;
++ Preferences::GetLocalizedString("intl.ellipsis", ellipsis);
++ if (!ellipsis.IsEmpty()) {
++ uint32_t l = ellipsis.Length();
++ const nsString::char_type *c = ellipsis.BeginReading();
++ uint32_t i = 0;
++ while (i < 3 && i < l) {
++ sBuf[i++] = *(c++);
++ }
++ } else {
++ sBuf[0] = '.';
++ sBuf[1] = '.';
++ sBuf[2] = '.';
++ }
++ }
++
++ return nsDependentString(sBuf);
++}
++
++static int
++GetEllipsisWidth()
++{
++ static int sEllipsisWidth = -1;
++
++ if (sEllipsisWidth == -1) {
++ sEllipsisWidth = CalculateTextWidth(GetEllipsis());
++ }
++
++ return sEllipsisWidth;
++}
++
++nsMenuObject::nsMenuObject(nsMenuContainer *aParent, nsIContent *aContent) :
++ mContent(aContent),
++ mListener(aParent->DocListener()),
++ mParent(aParent),
++ mNativeData(nullptr)
++{
++ MOZ_ASSERT(mContent);
++ MOZ_ASSERT(mListener);
++ MOZ_ASSERT(mParent);
++}
++
++nsMenuObject::nsMenuObject(nsNativeMenuDocListener *aListener,
++ nsIContent *aContent) :
++ mContent(aContent),
++ mListener(aListener),
++ mParent(nullptr),
++ mNativeData(nullptr)
++{
++ MOZ_ASSERT(mContent);
++ MOZ_ASSERT(mListener);
++}
++
++void
++nsMenuObject::UpdateLabel()
++{
++ // Gecko stores the label and access key in separate attributes
++ // so we need to convert label="Foo_Bar"/accesskey="F" in to
++ // label="_Foo__Bar" for dbusmenu
++
++ nsAutoString label;
++ mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::label, label);
++
++ nsAutoString accesskey;
++ mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey,
++ accesskey);
++
++ const nsAutoString::char_type *akey = accesskey.BeginReading();
++ char16_t keyLower = ToLowerCase(*akey);
++ char16_t keyUpper = ToUpperCase(*akey);
++
++ const nsAutoString::char_type *iter = label.BeginReading();
++ const nsAutoString::char_type *end = label.EndReading();
++ uint32_t length = label.Length();
++ uint32_t pos = 0;
++ bool foundAccessKey = false;
++
++ while (iter != end) {
++ if (*iter != char16_t('_')) {
++ if ((*iter != keyLower && *iter != keyUpper) || foundAccessKey) {
++ ++iter;
++ ++pos;
++ continue;
++ }
++ foundAccessKey = true;
++ }
++
++ label.SetLength(++length);
++
++ iter = label.BeginReading() + pos;
++ end = label.EndReading();
++ nsAutoString::char_type *cur = label.BeginWriting() + pos;
++
++ memmove(cur + 1, cur, (length - 1 - pos) * sizeof(nsAutoString::char_type));
++ *cur = nsAutoString::char_type('_');
++
++ iter += 2;
++ pos += 2;
++ }
++
++ if (CalculateTextWidth(label) <= MAX_WIDTH) {
++ dbusmenu_menuitem_property_set(mNativeData,
++ DBUSMENU_MENUITEM_PROP_LABEL,
++ NS_ConvertUTF16toUTF8(label).get());
++ return;
++ }
++
++ // This sucks.
++ // This should be done at the point where the menu is drawn (hello Unity),
++ // but unfortunately it doesn't do that and will happily fill your entire
++ // screen width with a menu if you have a bookmark with a really long title.
++ // This leaves us with no other option but to ellipsize here, with no proper
++ // knowledge of Unity's render path, font size etc. This is better than nothing
++ nsAutoString truncated;
++ int target = MAX_WIDTH - GetEllipsisWidth();
++ length = label.Length();
++
++ static mozilla::dom::Element::AttrValuesArray strings[] = {
++ nsGkAtoms::left, nsGkAtoms::start,
++ nsGkAtoms::center, nsGkAtoms::right,
++ nsGkAtoms::end, nullptr
++ };
++
++ int32_t type = mContent->AsElement()->FindAttrValueIn(kNameSpaceID_None,
++ nsGkAtoms::crop,
++ strings, eCaseMatters);
++
++ switch (type) {
++ case 0:
++ case 1:
++ // FIXME: Implement left cropping
++ case 2:
++ // FIXME: Implement center cropping
++ case 3:
++ case 4:
++ default:
++ for (uint32_t i = 0; i < length; i++) {
++ truncated.Append(label.CharAt(i));
++ if (CalculateTextWidth(truncated) > target) {
++ break;
++ }
++ }
++
++ truncated.Append(GetEllipsis());
++ }
++
++ dbusmenu_menuitem_property_set(mNativeData,
++ DBUSMENU_MENUITEM_PROP_LABEL,
++ NS_ConvertUTF16toUTF8(truncated).get());
++}
++
++void
++nsMenuObject::UpdateVisibility(const ComputedStyle *aComputedStyle)
++{
++ bool vis = true;
++
++ if (aComputedStyle &&
++ (aComputedStyle->StyleDisplay()->mDisplay == StyleDisplay::None ||
++ aComputedStyle->StyleVisibility()->mVisible ==
++ StyleVisibility::Collapse)) {
++ vis = false;
++ }
++
++ dbusmenu_menuitem_property_set_bool(mNativeData,
++ DBUSMENU_MENUITEM_PROP_VISIBLE,
++ vis);
++}
++
++void
++nsMenuObject::UpdateSensitivity()
++{
++ bool disabled = mContent->AsElement()->AttrValueIs(kNameSpaceID_None,
++ nsGkAtoms::disabled,
++ nsGkAtoms::_true,
++ eCaseMatters);
++
++ dbusmenu_menuitem_property_set_bool(mNativeData,
++ DBUSMENU_MENUITEM_PROP_ENABLED,
++ !disabled);
++
++}
++
++void
++nsMenuObject::UpdateIcon(const ComputedStyle *aComputedStyle)
++{
++ if (ShouldShowIcon()) {
++ if (!mIconLoader) {
++ mIconLoader = new nsMenuObjectIconLoader(this);
++ }
++
++ mIconLoader->LoadIcon(aComputedStyle);
++ } else {
++ if (mIconLoader) {
++ mIconLoader->Destroy();
++ mIconLoader = nullptr;
++ }
++
++ ClearIcon();
++ }
++}
++
++already_AddRefed<const ComputedStyle>
++nsMenuObject::GetComputedStyle()
++{
++ RefPtr<const ComputedStyle> style =
++ nsComputedDOMStyle::GetComputedStyleNoFlush(
++ mContent->AsElement());
++
++ return style.forget();
++}
++
++void
++nsMenuObject::InitializeNativeData()
++{
++}
++
++nsMenuObject::PropertyFlags
++nsMenuObject::SupportedProperties() const
++{
++ return static_cast<nsMenuObject::PropertyFlags>(0);
++}
++
++bool
++nsMenuObject::IsCompatibleWithNativeData(DbusmenuMenuitem *aNativeData) const
++{
++ return true;
++}
++
++void
++nsMenuObject::UpdateContentAttributes()
++{
++}
++
++void
++nsMenuObject::Update(const ComputedStyle *aComputedStyle)
++{
++}
++
++bool
++nsMenuObject::ShouldShowIcon() const
++{
++ // Ideally we want to know the visibility of the anonymous XUL image in
++ // our menuitem, but this isn't created because we don't have a frame.
++ // The following works by default (because xul.css hides images in menuitems
++ // that don't have the "menuitem-with-favicon" class). It's possible a third
++ // party theme could override this, but, oh well...
++ const nsAttrValue *classes = mContent->AsElement()->GetClasses();
++ if (!classes) {
++ return false;
++ }
++
++ for (uint32_t i = 0; i < classes->GetAtomCount(); ++i) {
++ if (classes->AtomAt(i) == nsGkAtoms::menuitem_with_favicon) {
++ return true;
++ }
++ }
++
++ return false;
++}
++
++void
++nsMenuObject::ClearIcon()
++{
++ dbusmenu_menuitem_property_remove(mNativeData,
++ DBUSMENU_MENUITEM_PROP_ICON_DATA);
++}
++
++nsMenuObject::~nsMenuObject()
++{
++ nsWeakMenuObject::NotifyDestroyed(this);
++
++ if (mIconLoader) {
++ mIconLoader->Destroy();
++ }
++
++ if (mListener) {
++ mListener->UnregisterForContentChanges(mContent);
++ }
++
++ if (mNativeData) {
++ g_object_unref(mNativeData);
++ mNativeData = nullptr;
++ }
++}
++
++void
++nsMenuObject::CreateNativeData()
++{
++ MOZ_ASSERT(mNativeData == nullptr, "This node already has a DbusmenuMenuitem. The old one will be leaked");
++
++ mNativeData = dbusmenu_menuitem_new();
++ InitializeNativeData();
++ if (mParent && mParent->IsBeingDisplayed()) {
++ ContainerIsOpening();
++ }
++
++ mListener->RegisterForContentChanges(mContent, this);
++}
++
++nsresult
++nsMenuObject::AdoptNativeData(DbusmenuMenuitem *aNativeData)
++{
++ MOZ_ASSERT(mNativeData == nullptr, "This node already has a DbusmenuMenuitem. The old one will be leaked");
++
++ if (!IsCompatibleWithNativeData(aNativeData)) {
++ return NS_ERROR_FAILURE;
++ }
++
++ mNativeData = aNativeData;
++ g_object_ref(mNativeData);
++
++ PropertyFlags supported = SupportedProperties();
++ PropertyFlags mask = static_cast<PropertyFlags>(1);
++
++ for (uint32_t i = 0; gPropertyStrings[i]; ++i) {
++ if (!(mask & supported)) {
++ dbusmenu_menuitem_property_remove(mNativeData, gPropertyStrings[i]);
++ }
++ mask = static_cast<PropertyFlags>(mask << 1);
++ }
++
++ InitializeNativeData();
++ if (mParent && mParent->IsBeingDisplayed()) {
++ ContainerIsOpening();
++ }
++
++ mListener->RegisterForContentChanges(mContent, this);
++
++ return NS_OK;
++}
++
++void
++nsMenuObject::ContainerIsOpening()
++{
++ MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
++
++ UpdateContentAttributes();
++
++ RefPtr<const ComputedStyle> style = GetComputedStyle();
++ Update(style);
++}
++
++/* static */ void
++nsWeakMenuObject::AddWeakReference(nsWeakMenuObject *aWeak)
++{
++ aWeak->mPrev = sHead;
++ sHead = aWeak;
++}
++
++/* static */ void
++nsWeakMenuObject::RemoveWeakReference(nsWeakMenuObject *aWeak)
++{
++ if (aWeak == sHead) {
++ sHead = aWeak->mPrev;
++ return;
++ }
++
++ nsWeakMenuObject *weak = sHead;
++ while (weak && weak->mPrev != aWeak) {
++ weak = weak->mPrev;
++ }
++
++ if (weak) {
++ weak->mPrev = aWeak->mPrev;
++ }
++}
++
++/* static */ void
++nsWeakMenuObject::NotifyDestroyed(nsMenuObject *aMenuObject)
++{
++ nsWeakMenuObject *weak = sHead;
++ while (weak) {
++ if (weak->mMenuObject == aMenuObject) {
++ weak->mMenuObject = nullptr;
++ }
++
++ weak = weak->mPrev;
++ }
++}
+diff --git a/widget/gtk/nsMenuObject.h b/widget/gtk/nsMenuObject.h
+new file mode 100644
+index 000000000000..92b1ffd8f2fa
+--- /dev/null
++++ b/widget/gtk/nsMenuObject.h
+@@ -0,0 +1,169 @@
++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:expandtab:shiftwidth=4:tabstop=4:
++ */
++/* This Source Code Form is subject to the terms of the Mozilla Public
++ * License, v. 2.0. If a copy of the MPL was not distributed with this
++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
++
++#ifndef __nsMenuObject_h__
++#define __nsMenuObject_h__
++
++#include "mozilla/Attributes.h"
++#include "mozilla/ComputedStyleInlines.h"
++#include "nsCOMPtr.h"
++
++#include "nsDbusmenu.h"
++#include "nsNativeMenuDocListener.h"
++
++class nsIContent;
++class nsMenuContainer;
++class nsMenuObjectIconLoader;
++
++#define DBUSMENU_PROPERTIES \
++ DBUSMENU_PROPERTY(Label, DBUSMENU_MENUITEM_PROP_LABEL, 0) \
++ DBUSMENU_PROPERTY(Enabled, DBUSMENU_MENUITEM_PROP_ENABLED, 1) \
++ DBUSMENU_PROPERTY(Visible, DBUSMENU_MENUITEM_PROP_VISIBLE, 2) \
++ DBUSMENU_PROPERTY(IconData, DBUSMENU_MENUITEM_PROP_ICON_DATA, 3) \
++ DBUSMENU_PROPERTY(Type, DBUSMENU_MENUITEM_PROP_TYPE, 4) \
++ DBUSMENU_PROPERTY(Shortcut, DBUSMENU_MENUITEM_PROP_SHORTCUT, 5) \
++ DBUSMENU_PROPERTY(ToggleType, DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE, 6) \
++ DBUSMENU_PROPERTY(ToggleState, DBUSMENU_MENUITEM_PROP_TOGGLE_STATE, 7) \
++ DBUSMENU_PROPERTY(ChildDisplay, DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY, 8)
++
++/*
++ * This is the base class for all menu nodes. Each instance represents
++ * a single node in the menu hierarchy. It wraps the corresponding DOM node and
++ * native menu node, keeps them in sync and transfers events between the two.
++ * It is not reference counted - each node is owned by its parent (the top
++ * level menubar is owned by the window) and keeps a weak pointer to its
++ * parent (which is guaranteed to always be valid because a node will never
++ * outlive its parent). It is not safe to keep a reference to nsMenuObject
++ * externally.
++ */
++class nsMenuObject : public nsNativeMenuChangeObserver
++{
++public:
++ enum EType {
++ eType_MenuBar,
++ eType_Menu,
++ eType_MenuItem
++ };
++
++ virtual ~nsMenuObject();
++
++ // Get the native menu item node
++ DbusmenuMenuitem* GetNativeData() const { return mNativeData; }
++
++ // Get the parent menu object
++ nsMenuContainer* Parent() const { return mParent; }
++
++ // Get the content node
++ nsIContent* ContentNode() const { return mContent; }
++
++ // Get the type of this node. Must be provided by subclasses
++ virtual EType Type() const = 0;
++
++ // Get the document listener
++ nsNativeMenuDocListener* DocListener() const { return mListener; }
++
++ // Create the native menu item node (called by containers)
++ void CreateNativeData();
++
++ // Adopt the specified native menu item node (called by containers)
++ nsresult AdoptNativeData(DbusmenuMenuitem *aNativeData);
++
++ // Called by the container to tell us that it's opening
++ void ContainerIsOpening();
++
++protected:
++ nsMenuObject(nsMenuContainer *aParent, nsIContent *aContent);
++ nsMenuObject(nsNativeMenuDocListener *aListener, nsIContent *aContent);
++
++ enum PropertyFlags {
++#define DBUSMENU_PROPERTY(e, s, b) eProp##e = (1 << b),
++ DBUSMENU_PROPERTIES
++#undef DBUSMENU_PROPERTY
++ };
++
++ void UpdateLabel();
++ void UpdateVisibility(const mozilla::ComputedStyle *aComputedStyle);
++ void UpdateSensitivity();
++ void UpdateIcon(const mozilla::ComputedStyle *aComputedStyle);
++
++ already_AddRefed<const mozilla::ComputedStyle> GetComputedStyle();
++
++private:
++ friend class nsMenuObjectIconLoader;
++
++ // Set up initial properties on the native data, connect to signals etc.
++ // This should be implemented by subclasses
++ virtual void InitializeNativeData();
++
++ // Return the properties that this menu object type supports
++ // This should be implemented by subclasses
++ virtual PropertyFlags SupportedProperties() const;
++
++ // Determine whether this menu object could use the specified
++ // native item. Returns true by default but can be overridden by subclasses
++ virtual bool
++ IsCompatibleWithNativeData(DbusmenuMenuitem *aNativeData) const;
++
++ // Update attributes on this objects content node when the container opens.
++ // This is called before style resolution, and should be implemented by
++ // subclasses who want to modify attributes that might affect style.
++ // This will not be called when there are script blockers
++ virtual void UpdateContentAttributes();
++
++ // Update properties that should be refreshed when the container opens.
++ // This should be implemented by subclasses that have properties which
++ // need refreshing
++ virtual void Update(const mozilla::ComputedStyle *aComputedStyle);
++
++ bool ShouldShowIcon() const;
++ void ClearIcon();
++
++ nsCOMPtr<nsIContent> mContent;
++ // mListener is a strong ref for simplicity - someone in the tree needs to
++ // own it, and this only really needs to be the top-level object (as no
++ // children outlives their parent). However, we need to keep it alive until
++ // after running the nsMenuObject destructor for the top-level menu object,
++ // hence the strong ref
++ RefPtr<nsNativeMenuDocListener> mListener;
++ nsMenuContainer *mParent; // [weak]
++ DbusmenuMenuitem *mNativeData; // [strong]
++ RefPtr<nsMenuObjectIconLoader> mIconLoader;
++};
++
++// Keep a weak pointer to a menu object
++class nsWeakMenuObject
++{
++public:
++ nsWeakMenuObject() : mPrev(nullptr), mMenuObject(nullptr) {}
++
++ nsWeakMenuObject(nsMenuObject *aMenuObject) :
++ mPrev(nullptr), mMenuObject(aMenuObject)
++ {
++ AddWeakReference(this);
++ }
++
++ ~nsWeakMenuObject() { RemoveWeakReference(this); }
++
++ nsMenuObject* get() const { return mMenuObject; }
++
++ nsMenuObject* operator->() const { return mMenuObject; }
++
++ explicit operator bool() const { return !!mMenuObject; }
++
++ static void NotifyDestroyed(nsMenuObject *aMenuObject);
++
++private:
++ static void AddWeakReference(nsWeakMenuObject *aWeak);
++ static void RemoveWeakReference(nsWeakMenuObject *aWeak);
++
++ nsWeakMenuObject *mPrev;
++ static nsWeakMenuObject *sHead;
++
++ nsMenuObject *mMenuObject;
++};
++
++#endif /* __nsMenuObject_h__ */
+diff --git a/widget/gtk/nsMenuSeparator.cpp b/widget/gtk/nsMenuSeparator.cpp
+new file mode 100644
+index 000000000000..3ab135bd8a20
+--- /dev/null
++++ b/widget/gtk/nsMenuSeparator.cpp
+@@ -0,0 +1,82 @@
++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:expandtab:shiftwidth=4:tabstop=4:
++ */
++/* This Source Code Form is subject to the terms of the Mozilla Public
++ * License, v. 2.0. If a copy of the MPL was not distributed with this
++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
++
++#include "mozilla/Assertions.h"
++#include "nsCRT.h"
++#include "nsGkAtoms.h"
++
++#include "nsDbusmenu.h"
++
++#include "nsMenuContainer.h"
++#include "nsMenuSeparator.h"
++
++using namespace mozilla;
++
++void
++nsMenuSeparator::InitializeNativeData()
++{
++ dbusmenu_menuitem_property_set(GetNativeData(),
++ DBUSMENU_MENUITEM_PROP_TYPE,
++ "separator");
++}
++
++void
++nsMenuSeparator::Update(const ComputedStyle *aComputedStyle)
++{
++ UpdateVisibility(aComputedStyle);
++}
++
++bool
++nsMenuSeparator::IsCompatibleWithNativeData(DbusmenuMenuitem *aNativeData) const
++{
++ return nsCRT::strcmp(dbusmenu_menuitem_property_get(aNativeData,
++ DBUSMENU_MENUITEM_PROP_TYPE),
++ "separator") == 0;
++}
++
++nsMenuObject::PropertyFlags
++nsMenuSeparator::SupportedProperties() const
++{
++ return static_cast<nsMenuObject::PropertyFlags>(
++ nsMenuObject::ePropVisible |
++ nsMenuObject::ePropType
++ );
++}
++
++void
++nsMenuSeparator::OnAttributeChanged(nsIContent *aContent, nsAtom *aAttribute)
++{
++ MOZ_ASSERT(aContent == ContentNode(), "Received an event that wasn't meant for us!");
++
++ if (!Parent()->IsBeingDisplayed()) {
++ return;
++ }
++
++ if (aAttribute == nsGkAtoms::hidden ||
++ aAttribute == nsGkAtoms::collapsed) {
++ RefPtr<const ComputedStyle> style = GetComputedStyle();
++ UpdateVisibility(style);
++ }
++}
++
++nsMenuSeparator::nsMenuSeparator(nsMenuContainer *aParent,
++ nsIContent *aContent) :
++ nsMenuObject(aParent, aContent)
++{
++ MOZ_COUNT_CTOR(nsMenuSeparator);
++}
++
++nsMenuSeparator::~nsMenuSeparator()
++{
++ MOZ_COUNT_DTOR(nsMenuSeparator);
++}
++
++nsMenuObject::EType
++nsMenuSeparator::Type() const
++{
++ return eType_MenuItem;
++}
+diff --git a/widget/gtk/nsMenuSeparator.h b/widget/gtk/nsMenuSeparator.h
+new file mode 100644
+index 000000000000..dd3f5b974dd3
+--- /dev/null
++++ b/widget/gtk/nsMenuSeparator.h
+@@ -0,0 +1,37 @@
++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:expandtab:shiftwidth=4:tabstop=4:
++ */
++/* This Source Code Form is subject to the terms of the Mozilla Public
++ * License, v. 2.0. If a copy of the MPL was not distributed with this
++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
++
++#ifndef __nsMenuSeparator_h__
++#define __nsMenuSeparator_h__
++
++#include "mozilla/Attributes.h"
++
++#include "nsMenuObject.h"
++
++class nsIContent;
++class nsAtom;
++class nsMenuContainer;
++
++// Menu separator class
++class nsMenuSeparator final : public nsMenuObject
++{
++public:
++ nsMenuSeparator(nsMenuContainer *aParent, nsIContent *aContent);
++ ~nsMenuSeparator();
++
++ nsMenuObject::EType Type() const override;
++
++private:
++ void InitializeNativeData() override;
++ void Update(const mozilla::ComputedStyle *aComputedStyle) override;
++ bool IsCompatibleWithNativeData(DbusmenuMenuitem *aNativeData) const override;
++ nsMenuObject::PropertyFlags SupportedProperties() const override;
++
++ void OnAttributeChanged(nsIContent *aContent, nsAtom *aAttribute) override;
++};
++
++#endif /* __nsMenuSeparator_h__ */
+diff --git a/widget/gtk/nsNativeMenuDocListener.cpp b/widget/gtk/nsNativeMenuDocListener.cpp
+new file mode 100644
+index 000000000000..2336ed6e3bc5
+--- /dev/null
++++ b/widget/gtk/nsNativeMenuDocListener.cpp
+@@ -0,0 +1,347 @@
++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:expandtab:shiftwidth=4:tabstop=4:
++ */
++/* This Source Code Form is subject to the terms of the Mozilla Public
++ * License, v. 2.0. If a copy of the MPL was not distributed with this
++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
++
++#include "mozilla/Assertions.h"
++#include "mozilla/DebugOnly.h"
++#include "mozilla/dom/Document.h"
++#include "mozilla/dom/Element.h"
++#include "nsContentUtils.h"
++#include "nsAtom.h"
++#include "nsIContent.h"
++
++#include "nsMenuContainer.h"
++
++#include "nsNativeMenuDocListener.h"
++
++using namespace mozilla;
++
++uint32_t nsNativeMenuDocListener::sUpdateBlockersCount = 0;
++
++nsNativeMenuDocListenerTArray *gPendingListeners;
++
++/*
++ * Small helper which caches a single listener, so that consecutive
++ * events which go to the same node avoid multiple hash table lookups
++ */
++class MOZ_STACK_CLASS DispatchHelper
++{
++public:
++ DispatchHelper(nsNativeMenuDocListener *aListener,
++ nsIContent *aContent) :
++ mObserver(nullptr)
++ {
++ if (aContent == aListener->mLastSource) {
++ mObserver = aListener->mLastTarget;
++ } else {
++ mObserver = aListener->mContentToObserverTable.Get(aContent);
++ if (mObserver) {
++ aListener->mLastSource = aContent;
++ aListener->mLastTarget = mObserver;
++ }
++ }
++ }
++
++ ~DispatchHelper() { };
++
++ nsNativeMenuChangeObserver* Observer() const { return mObserver; }
++
++ bool HasObserver() const { return !!mObserver; }
++
++private:
++ nsNativeMenuChangeObserver *mObserver;
++};
++
++NS_IMPL_ISUPPORTS(nsNativeMenuDocListener, nsIMutationObserver)
++
++nsNativeMenuDocListener::~nsNativeMenuDocListener()
++{
++ MOZ_ASSERT(mContentToObserverTable.Count() == 0,
++ "Some nodes forgot to unregister listeners. This is bad! (and we're lucky we made it this far)");
++ MOZ_COUNT_DTOR(nsNativeMenuDocListener);
++}
++
++void
++nsNativeMenuDocListener::AttributeChanged(mozilla::dom::Element *aElement,
++ int32_t aNameSpaceID,
++ nsAtom *aAttribute,
++ int32_t aModType,
++ const nsAttrValue* aOldValue)
++{
++ if (sUpdateBlockersCount == 0) {
++ DoAttributeChanged(aElement, aAttribute);
++ return;
++ }
++
++ MutationRecord *m = mPendingMutations.AppendElement(MakeUnique<MutationRecord>())->get();
++ m->mType = MutationRecord::eAttributeChanged;
++ m->mTarget = aElement;
++ m->mAttribute = aAttribute;
++
++ ScheduleFlush(this);
++}
++
++void
++nsNativeMenuDocListener::ContentAppended(nsIContent *aFirstNewContent)
++{
++ for (nsIContent *c = aFirstNewContent; c; c = c->GetNextSibling()) {
++ ContentInserted(c);
++ }
++}
++
++void
++nsNativeMenuDocListener::ContentInserted(nsIContent *aChild)
++{
++ nsIContent* container = aChild->GetParent();
++ if (!container) {
++ return;
++ }
++
++ nsIContent *prevSibling = nsMenuContainer::GetPreviousSupportedSibling(aChild);
++
++ if (sUpdateBlockersCount == 0) {
++ DoContentInserted(container, aChild, prevSibling);
++ return;
++ }
++
++ MutationRecord *m = mPendingMutations.AppendElement(MakeUnique<MutationRecord>())->get();
++ m->mType = MutationRecord::eContentInserted;
++ m->mTarget = container;
++ m->mChild = aChild;
++ m->mPrevSibling = prevSibling;
++
++ ScheduleFlush(this);
++}
++
++void
++nsNativeMenuDocListener::ContentRemoved(nsIContent *aChild,
++ nsIContent *aPreviousSibling)
++{
++ nsIContent* container = aChild->GetParent();
++ if (!container) {
++ return;
++ }
++
++ if (sUpdateBlockersCount == 0) {
++ DoContentRemoved(container, aChild);
++ return;
++ }
++
++ MutationRecord *m = mPendingMutations.AppendElement(MakeUnique<MutationRecord>())->get();
++ m->mType = MutationRecord::eContentRemoved;
++ m->mTarget = container;
++ m->mChild = aChild;
++
++ ScheduleFlush(this);
++}
++
++void
++nsNativeMenuDocListener::NodeWillBeDestroyed(const nsINode *aNode)
++{
++ mDocument = nullptr;
++}
++
++void
++nsNativeMenuDocListener::DoAttributeChanged(nsIContent *aContent,
++ nsAtom *aAttribute)
++{
++ DispatchHelper h(this, aContent);
++ if (h.HasObserver()) {
++ h.Observer()->OnAttributeChanged(aContent, aAttribute);
++ }
++}
++
++void
++nsNativeMenuDocListener::DoContentInserted(nsIContent *aContainer,
++ nsIContent *aChild,
++ nsIContent *aPrevSibling)
++{
++ DispatchHelper h(this, aContainer);
++ if (h.HasObserver()) {
++ h.Observer()->OnContentInserted(aContainer, aChild, aPrevSibling);
++ }
++}
++
++void
++nsNativeMenuDocListener::DoContentRemoved(nsIContent *aContainer,
++ nsIContent *aChild)
++{
++ DispatchHelper h(this, aContainer);
++ if (h.HasObserver()) {
++ h.Observer()->OnContentRemoved(aContainer, aChild);
++ }
++}
++
++void
++nsNativeMenuDocListener::DoBeginUpdates(nsIContent *aTarget)
++{
++ DispatchHelper h(this, aTarget);
++ if (h.HasObserver()) {
++ h.Observer()->OnBeginUpdates(aTarget);
++ }
++}
++
++void
++nsNativeMenuDocListener::DoEndUpdates(nsIContent *aTarget)
++{
++ DispatchHelper h(this, aTarget);
++ if (h.HasObserver()) {
++ h.Observer()->OnEndUpdates();
++ }
++}
++
++void
++nsNativeMenuDocListener::FlushPendingMutations()
++{
++ nsIContent *currentTarget = nullptr;
++ bool inUpdateSequence = false;
++
++ while (mPendingMutations.Length() > 0) {
++ MutationRecord *m = mPendingMutations[0].get();
++
++ if (m->mTarget != currentTarget) {
++ if (inUpdateSequence) {
++ DoEndUpdates(currentTarget);
++ inUpdateSequence = false;
++ }
++
++ currentTarget = m->mTarget;
++
++ if (mPendingMutations.Length() > 1 &&
++ mPendingMutations[1]->mTarget == currentTarget) {
++ DoBeginUpdates(currentTarget);
++ inUpdateSequence = true;
++ }
++ }
++
++ switch (m->mType) {
++ case MutationRecord::eAttributeChanged:
++ DoAttributeChanged(m->mTarget, m->mAttribute);
++ break;
++ case MutationRecord::eContentInserted:
++ DoContentInserted(m->mTarget, m->mChild, m->mPrevSibling);
++ break;
++ case MutationRecord::eContentRemoved:
++ DoContentRemoved(m->mTarget, m->mChild);
++ break;
++ default:
++ MOZ_ASSERT_UNREACHABLE("Invalid type");
++ }
++
++ mPendingMutations.RemoveElementAt(0);
++ }
++
++ if (inUpdateSequence) {
++ DoEndUpdates(currentTarget);
++ }
++}
++
++/* static */ void
++nsNativeMenuDocListener::ScheduleFlush(nsNativeMenuDocListener *aListener)
++{
++ MOZ_ASSERT(sUpdateBlockersCount > 0, "Shouldn't be doing this now");
++
++ if (!gPendingListeners) {
++ gPendingListeners = new nsNativeMenuDocListenerTArray;
++ }
++
++ if (gPendingListeners->IndexOf(aListener) ==
++ nsNativeMenuDocListenerTArray::NoIndex) {
++ gPendingListeners->AppendElement(aListener);
++ }
++}
++
++/* static */ void
++nsNativeMenuDocListener::CancelFlush(nsNativeMenuDocListener *aListener)
++{
++ if (!gPendingListeners) {
++ return;
++ }
++
++ gPendingListeners->RemoveElement(aListener);
++}
++
++/* static */ void
++nsNativeMenuDocListener::RemoveUpdateBlocker()
++{
++ if (sUpdateBlockersCount == 1 && gPendingListeners) {
++ while (gPendingListeners->Length() > 0) {
++ (*gPendingListeners)[0]->FlushPendingMutations();
++ gPendingListeners->RemoveElementAt(0);
++ }
++ }
++
++ MOZ_ASSERT(sUpdateBlockersCount > 0, "Negative update blockers count!");
++ sUpdateBlockersCount--;
++}
++
++nsNativeMenuDocListener::nsNativeMenuDocListener(nsIContent *aRootNode) :
++ mRootNode(aRootNode),
++ mDocument(nullptr),
++ mLastSource(nullptr),
++ mLastTarget(nullptr)
++{
++ MOZ_COUNT_CTOR(nsNativeMenuDocListener);
++}
++
++void
++nsNativeMenuDocListener::RegisterForContentChanges(nsIContent *aContent,
++ nsNativeMenuChangeObserver *aObserver)
++{
++ MOZ_ASSERT(aContent, "Need content parameter");
++ MOZ_ASSERT(aObserver, "Need observer parameter");
++ if (!aContent || !aObserver) {
++ return;
++ }
++
++ DebugOnly<nsNativeMenuChangeObserver *> old;
++ MOZ_ASSERT(!mContentToObserverTable.Get(aContent, &old) || old == aObserver,
++ "Multiple observers for the same content node are not supported");
++
++ mContentToObserverTable.InsertOrUpdate(aContent, aObserver);
++}
++
++void
++nsNativeMenuDocListener::UnregisterForContentChanges(nsIContent *aContent)
++{
++ MOZ_ASSERT(aContent, "Need content parameter");
++ if (!aContent) {
++ return;
++ }
++
++ mContentToObserverTable.Remove(aContent);
++ if (aContent == mLastSource) {
++ mLastSource = nullptr;
++ mLastTarget = nullptr;
++ }
++}
++
++void
++nsNativeMenuDocListener::Start()
++{
++ if (mDocument) {
++ return;
++ }
++
++ mDocument = mRootNode->OwnerDoc();
++ if (!mDocument) {
++ return;
++ }
++
++ mDocument->AddMutationObserver(this);
++}
++
++void
++nsNativeMenuDocListener::Stop()
++{
++ if (mDocument) {
++ mDocument->RemoveMutationObserver(this);
++ mDocument = nullptr;
++ }
++
++ CancelFlush(this);
++ mPendingMutations.Clear();
++}
+diff --git a/widget/gtk/nsNativeMenuDocListener.h b/widget/gtk/nsNativeMenuDocListener.h
+new file mode 100644
+index 000000000000..5ee99cba70fd
+--- /dev/null
++++ b/widget/gtk/nsNativeMenuDocListener.h
+@@ -0,0 +1,152 @@
++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:expandtab:shiftwidth=4:tabstop=4:
++ */
++/* This Source Code Form is subject to the terms of the Mozilla Public
++ * License, v. 2.0. If a copy of the MPL was not distributed with this
++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
++
++#ifndef __nsNativeMenuDocListener_h__
++#define __nsNativeMenuDocListener_h__
++
++#include "mozilla/Attributes.h"
++#include "mozilla/RefPtr.h"
++#include "mozilla/UniquePtr.h"
++#include "nsTHashMap.h"
++#include "nsStubMutationObserver.h"
++#include "nsTArray.h"
++
++class nsAtom;
++class nsIContent;
++class nsNativeMenuChangeObserver;
++
++namespace mozilla {
++namespace dom {
++class Document;
++}
++}
++
++/*
++ * This class keeps a mapping of content nodes to observers and forwards DOM
++ * mutations to these. There is exactly one of these for every menubar.
++ */
++class nsNativeMenuDocListener final : nsStubMutationObserver
++{
++public:
++ NS_DECL_ISUPPORTS
++
++ nsNativeMenuDocListener(nsIContent *aRootNode);
++
++ // Register an observer to receive mutation events for the specified
++ // content node. The caller must keep the observer alive until
++ // UnregisterForContentChanges is called.
++ void RegisterForContentChanges(nsIContent *aContent,
++ nsNativeMenuChangeObserver *aObserver);
++
++ // Unregister the registered observer for the specified content node
++ void UnregisterForContentChanges(nsIContent *aContent);
++
++ // Start listening to the document and forwarding DOM mutations to
++ // registered observers.
++ void Start();
++
++ // Stop listening to the document. No DOM mutations will be forwarded
++ // to registered observers.
++ void Stop();
++
++ /*
++ * This class is intended to be used inside GObject signal handlers.
++ * It allows us to queue updates until we have finished delivering
++ * events to Gecko, and then we can batch updates to our view of the
++ * menu. This allows us to do menu updates without altering the structure
++ * seen by the OS.
++ */
++ class MOZ_STACK_CLASS BlockUpdatesScope
++ {
++ public:
++ BlockUpdatesScope()
++ {
++ nsNativeMenuDocListener::AddUpdateBlocker();
++ }
++
++ ~BlockUpdatesScope()
++ {
++ nsNativeMenuDocListener::RemoveUpdateBlocker();
++ }
++ };
++
++private:
++ friend class DispatchHelper;
++
++ struct MutationRecord {
++ enum RecordType {
++ eAttributeChanged,
++ eContentInserted,
++ eContentRemoved
++ } mType;
++
++ nsCOMPtr<nsIContent> mTarget;
++ nsCOMPtr<nsIContent> mChild;
++ nsCOMPtr<nsIContent> mPrevSibling;
++ RefPtr<nsAtom> mAttribute;
++ };
++
++ ~nsNativeMenuDocListener();
++
++ NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
++ NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
++ NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
++ NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
++ NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED
++
++ void DoAttributeChanged(nsIContent *aContent, nsAtom *aAttribute);
++ void DoContentInserted(nsIContent *aContainer,
++ nsIContent *aChild,
++ nsIContent *aPrevSibling);
++ void DoContentRemoved(nsIContent *aContainer, nsIContent *aChild);
++ void DoBeginUpdates(nsIContent *aTarget);
++ void DoEndUpdates(nsIContent *aTarget);
++
++ void FlushPendingMutations();
++ static void ScheduleFlush(nsNativeMenuDocListener *aListener);
++ static void CancelFlush(nsNativeMenuDocListener *aListener);
++
++ static void AddUpdateBlocker() { ++sUpdateBlockersCount; }
++ static void RemoveUpdateBlocker();
++
++ nsCOMPtr<nsIContent> mRootNode;
++ mozilla::dom::Document *mDocument;
++ nsIContent *mLastSource;
++ nsNativeMenuChangeObserver *mLastTarget;
++ nsTArray<mozilla::UniquePtr<MutationRecord> > mPendingMutations;
++ nsTHashMap<nsPtrHashKey<nsIContent>, nsNativeMenuChangeObserver *> mContentToObserverTable;
++
++ static uint32_t sUpdateBlockersCount;
++};
++
++typedef nsTArray<RefPtr<nsNativeMenuDocListener> > nsNativeMenuDocListenerTArray;
++
++/*
++ * Implemented by classes that want to listen to mutation events from content
++ * nodes.
++ */
++class nsNativeMenuChangeObserver
++{
++public:
++ virtual void OnAttributeChanged(nsIContent *aContent, nsAtom *aAttribute) {}
++
++ virtual void OnContentInserted(nsIContent *aContainer,
++ nsIContent *aChild,
++ nsIContent *aPrevSibling) {}
++
++ virtual void OnContentRemoved(nsIContent *aContainer, nsIContent *aChild) {}
++
++ // Signals the start of a sequence of more than 1 event for the specified
++ // node. This only happens when events are flushed as all BlockUpdatesScope
++ // instances go out of scope
++ virtual void OnBeginUpdates(nsIContent *aContent) {};
++
++ // Signals the end of a sequence of events
++ virtual void OnEndUpdates() {};
++};
++
++#endif /* __nsNativeMenuDocListener_h__ */
+diff --git a/widget/gtk/nsNativeMenuService.cpp b/widget/gtk/nsNativeMenuService.cpp
+new file mode 100644
+index 000000000000..5cf36a447036
+--- /dev/null
++++ b/widget/gtk/nsNativeMenuService.cpp
+@@ -0,0 +1,478 @@
++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:expandtab:shiftwidth=4:tabstop=4:
++ */
++/* This Source Code Form is subject to the terms of the Mozilla Public
++ * License, v. 2.0. If a copy of the MPL was not distributed with this
++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
++
++#include "mozilla/dom/Element.h"
++#include "mozilla/Assertions.h"
++#include "mozilla/Preferences.h"
++#include "mozilla/UniquePtr.h"
++#include "nsCOMPtr.h"
++#include "nsCRT.h"
++#include "nsGtkUtils.h"
++#include "nsIContent.h"
++#include "nsIWidget.h"
++#include "nsServiceManagerUtils.h"
++#include "nsWindow.h"
++#include "prlink.h"
++
++#include "nsDbusmenu.h"
++#include "nsMenuBar.h"
++#include "nsNativeMenuDocListener.h"
++
++#include <glib-object.h>
++#include <pango/pango.h>
++#include <stdlib.h>
++
++#include "nsNativeMenuService.h"
++
++using namespace mozilla;
++
++nsNativeMenuService* nsNativeMenuService::sService = nullptr;
++
++extern PangoLayout* gPangoLayout;
++extern nsNativeMenuDocListenerTArray* gPendingListeners;
++
++#undef g_dbus_proxy_new_for_bus
++#undef g_dbus_proxy_new_for_bus_finish
++#undef g_dbus_proxy_call
++#undef g_dbus_proxy_call_finish
++#undef g_dbus_proxy_get_name_owner
++
++typedef void (*_g_dbus_proxy_new_for_bus_fn)(GBusType, GDBusProxyFlags,
++ GDBusInterfaceInfo*,
++ const gchar*, const gchar*,
++ const gchar*, GCancellable*,
++ GAsyncReadyCallback, gpointer);
++
++typedef GDBusProxy* (*_g_dbus_proxy_new_for_bus_finish_fn)(GAsyncResult*,
++ GError**);
++typedef void (*_g_dbus_proxy_call_fn)(GDBusProxy*, const gchar*, GVariant*,
++ GDBusCallFlags, gint, GCancellable*,
++ GAsyncReadyCallback, gpointer);
++typedef GVariant* (*_g_dbus_proxy_call_finish_fn)(GDBusProxy*, GAsyncResult*,
++ GError**);
++typedef gchar* (*_g_dbus_proxy_get_name_owner_fn)(GDBusProxy*);
++
++static _g_dbus_proxy_new_for_bus_fn _g_dbus_proxy_new_for_bus;
++static _g_dbus_proxy_new_for_bus_finish_fn _g_dbus_proxy_new_for_bus_finish;
++static _g_dbus_proxy_call_fn _g_dbus_proxy_call;
++static _g_dbus_proxy_call_finish_fn _g_dbus_proxy_call_finish;
++static _g_dbus_proxy_get_name_owner_fn _g_dbus_proxy_get_name_owner;
++
++#define g_dbus_proxy_new_for_bus _g_dbus_proxy_new_for_bus
++#define g_dbus_proxy_new_for_bus_finish _g_dbus_proxy_new_for_bus_finish
++#define g_dbus_proxy_call _g_dbus_proxy_call
++#define g_dbus_proxy_call_finish _g_dbus_proxy_call_finish
++#define g_dbus_proxy_get_name_owner _g_dbus_proxy_get_name_owner
++
++static PRLibrary *gGIOLib = nullptr;
++
++static nsresult
++GDBusInit()
++{
++ gGIOLib = PR_LoadLibrary("libgio-2.0.so.0");
++ if (!gGIOLib) {
++ return NS_ERROR_FAILURE;
++ }
++
++ g_dbus_proxy_new_for_bus = (_g_dbus_proxy_new_for_bus_fn)PR_FindFunctionSymbol(gGIOLib, "g_dbus_proxy_new_for_bus");
++ g_dbus_proxy_new_for_bus_finish = (_g_dbus_proxy_new_for_bus_finish_fn)PR_FindFunctionSymbol(gGIOLib, "g_dbus_proxy_new_for_bus_finish");
++ g_dbus_proxy_call = (_g_dbus_proxy_call_fn)PR_FindFunctionSymbol(gGIOLib, "g_dbus_proxy_call");
++ g_dbus_proxy_call_finish = (_g_dbus_proxy_call_finish_fn)PR_FindFunctionSymbol(gGIOLib, "g_dbus_proxy_call_finish");
++ g_dbus_proxy_get_name_owner = (_g_dbus_proxy_get_name_owner_fn)PR_FindFunctionSymbol(gGIOLib, "g_dbus_proxy_get_name_owner");
++
++ if (!g_dbus_proxy_new_for_bus ||
++ !g_dbus_proxy_new_for_bus_finish ||
++ !g_dbus_proxy_call ||
++ !g_dbus_proxy_call_finish ||
++ !g_dbus_proxy_get_name_owner) {
++ return NS_ERROR_FAILURE;
++ }
++
++ return NS_OK;
++}
++
++NS_IMPL_ISUPPORTS(nsNativeMenuService, nsINativeMenuService)
++
++nsNativeMenuService::nsNativeMenuService() :
++ mCreateProxyCancellable(nullptr), mDbusProxy(nullptr), mOnline(false)
++{
++}
++
++nsNativeMenuService::~nsNativeMenuService()
++{
++ SetOnline(false);
++
++ if (mCreateProxyCancellable) {
++ g_cancellable_cancel(mCreateProxyCancellable);
++ g_object_unref(mCreateProxyCancellable);
++ mCreateProxyCancellable = nullptr;
++ }
++
++ // Make sure we disconnect map-event handlers
++ while (mMenuBars.Length() > 0) {
++ NotifyNativeMenuBarDestroyed(mMenuBars[0]);
++ }
++
++ Preferences::UnregisterCallback(PrefChangedCallback,
++ "ui.use_unity_menubar");
++
++ if (mDbusProxy) {
++ g_signal_handlers_disconnect_by_func(mDbusProxy,
++ FuncToGpointer(name_owner_changed_cb),
++ NULL);
++ g_object_unref(mDbusProxy);
++ }
++
++ if (gPendingListeners) {
++ delete gPendingListeners;
++ gPendingListeners = nullptr;
++ }
++ if (gPangoLayout) {
++ g_object_unref(gPangoLayout);
++ gPangoLayout = nullptr;
++ }
++
++ MOZ_ASSERT(sService == this);
++ sService = nullptr;
++}
++
++nsresult
++nsNativeMenuService::Init()
++{
++ nsresult rv = nsDbusmenuFunctions::Init();
++ if (NS_FAILED(rv)) {
++ return rv;
++ }
++
++ rv = GDBusInit();
++ if (NS_FAILED(rv)) {
++ return rv;
++ }
++
++ Preferences::RegisterCallback(PrefChangedCallback,
++ "ui.use_unity_menubar");
++
++ mCreateProxyCancellable = g_cancellable_new();
++
++ g_dbus_proxy_new_for_bus(G_BUS_TYPE_SESSION,
++ static_cast<GDBusProxyFlags>(
++ G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
++ G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS |
++ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START),
++ nullptr,
++ "com.canonical.AppMenu.Registrar",
++ "/com/canonical/AppMenu/Registrar",
++ "com.canonical.AppMenu.Registrar",
++ mCreateProxyCancellable, proxy_created_cb,
++ nullptr);
++
++ /* We don't technically know that the shell will draw the menubar until
++ * we know whether anybody owns the name of the menubar service on the
++ * session bus. However, discovering this happens asynchronously so
++ * we optimize for the common case here by assuming that the shell will
++ * draw window menubars if we are running inside Unity. This should
++ * mean that we avoid temporarily displaying the window menubar ourselves
++ */
++ const char *desktop = getenv("XDG_CURRENT_DESKTOP");
++ if (nsCRT::strcmp(desktop, "Unity") == 0) {
++ SetOnline(true);
++ }
++
++ return NS_OK;
++}
++
++/* static */ void
++nsNativeMenuService::EnsureInitialized()
++{
++ if (sService) {
++ return;
++ }
++ nsCOMPtr<nsINativeMenuService> service =
++ do_GetService("@mozilla.org/widget/nativemenuservice;1");
++}
++
++void
++nsNativeMenuService::SetOnline(bool aOnline)
++{
++ if (!Preferences::GetBool("ui.use_unity_menubar", true)) {
++ aOnline = false;
++ }
++
++ mOnline = aOnline;
++ if (aOnline) {
++ for (uint32_t i = 0; i < mMenuBars.Length(); ++i) {
++ RegisterNativeMenuBar(mMenuBars[i]);
++ }
++ } else {
++ for (uint32_t i = 0; i < mMenuBars.Length(); ++i) {
++ mMenuBars[i]->Deactivate();
++ }
++ }
++}
++
++void
++nsNativeMenuService::RegisterNativeMenuBar(nsMenuBar *aMenuBar)
++{
++ if (!mOnline) {
++ return;
++ }
++
++ // This will effectively create the native menubar for
++ // exporting over the session bus, and hide the XUL menubar
++ aMenuBar->Activate();
++
++ if (!mDbusProxy ||
++ !gtk_widget_get_mapped(aMenuBar->TopLevelWindow()) ||
++ mMenuBarRegistrationCancellables.Get(aMenuBar, nullptr)) {
++ // Don't go further if we don't have a proxy for the shell menu
++ // service, the window isn't mapped or there is a request in progress.
++ return;
++ }
++
++ uint32_t xid = aMenuBar->WindowId();
++ nsCString path = aMenuBar->ObjectPath();
++ if (xid == 0 || path.IsEmpty()) {
++ NS_WARNING("Menubar has invalid XID or object path");
++ return;
++ }
++
++ GCancellable *cancellable = g_cancellable_new();
++ mMenuBarRegistrationCancellables.InsertOrUpdate(aMenuBar, cancellable);
++
++ // We keep a weak ref because we can't assume that GDBus cancellation
++ // is reliable (see https://launchpad.net/bugs/953562)
++
++ g_dbus_proxy_call(mDbusProxy, "RegisterWindow",
++ g_variant_new("(uo)", xid, path.get()),
++ G_DBUS_CALL_FLAGS_NONE, -1,
++ cancellable,
++ register_native_menubar_cb, aMenuBar);
++}
++
++/* static */ void
++nsNativeMenuService::name_owner_changed_cb(GObject *gobject,
++ GParamSpec *pspec,
++ gpointer user_data)
++{
++ nsNativeMenuService::GetSingleton()->OnNameOwnerChanged();
++}
++
++/* static */ void
++nsNativeMenuService::proxy_created_cb(GObject *source_object,
++ GAsyncResult *res,
++ gpointer user_data)
++{
++ GError *error = nullptr;
++ GDBusProxy *proxy = g_dbus_proxy_new_for_bus_finish(res, &error);
++ if (error && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
++ g_error_free(error);
++ return;
++ }
++
++ if (error) {
++ g_error_free(error);
++ }
++
++ // We need this check because we can't assume that GDBus cancellation
++ // is reliable (see https://launchpad.net/bugs/953562)
++ nsNativeMenuService *self = nsNativeMenuService::GetSingleton();
++ if (!self) {
++ if (proxy) {
++ g_object_unref(proxy);
++ }
++ return;
++ }
++
++ self->OnProxyCreated(proxy);
++}
++
++/* static */ void
++nsNativeMenuService::register_native_menubar_cb(GObject *source_object,
++ GAsyncResult *res,
++ gpointer user_data)
++{
++ nsMenuBar *menuBar = static_cast<nsMenuBar *>(user_data);
++
++ GError *error = nullptr;
++ GVariant *results = g_dbus_proxy_call_finish(G_DBUS_PROXY(source_object),
++ res, &error);
++ if (results) {
++ // There's nothing useful in the response
++ g_variant_unref(results);
++ }
++
++ bool success = error ? false : true;
++ if (error && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
++ g_error_free(error);
++ return;
++ }
++
++ if (error) {
++ g_error_free(error);
++ }
++
++ nsNativeMenuService *self = nsNativeMenuService::GetSingleton();
++ if (!self) {
++ return;
++ }
++
++ self->OnNativeMenuBarRegistered(menuBar, success);
++}
++
++/* static */ gboolean
++nsNativeMenuService::map_event_cb(GtkWidget *widget,
++ GdkEvent *event,
++ gpointer user_data)
++{
++ nsMenuBar *menubar = static_cast<nsMenuBar *>(user_data);
++ nsNativeMenuService::GetSingleton()->RegisterNativeMenuBar(menubar);
++
++ return FALSE;
++}
++
++void
++nsNativeMenuService::OnNameOwnerChanged()
++{
++ char *owner = g_dbus_proxy_get_name_owner(mDbusProxy);
++ SetOnline(owner ? true : false);
++ g_free(owner);
++}
++
++void
++nsNativeMenuService::OnProxyCreated(GDBusProxy *aProxy)
++{
++ mDbusProxy = aProxy;
++
++ g_object_unref(mCreateProxyCancellable);
++ mCreateProxyCancellable = nullptr;
++
++ if (!mDbusProxy) {
++ SetOnline(false);
++ return;
++ }
++
++ g_signal_connect(mDbusProxy, "notify::g-name-owner",
++ G_CALLBACK(name_owner_changed_cb), nullptr);
++
++ OnNameOwnerChanged();
++}
++
++void
++nsNativeMenuService::OnNativeMenuBarRegistered(nsMenuBar *aMenuBar,
++ bool aSuccess)
++{
++ // Don't assume that GDBus cancellation is reliable (ie, |aMenuBar| might
++ // have already been deleted (see https://launchpad.net/bugs/953562)
++ GCancellable *cancellable = nullptr;
++ if (!mMenuBarRegistrationCancellables.Get(aMenuBar, &cancellable)) {
++ return;
++ }
++
++ g_object_unref(cancellable);
++ mMenuBarRegistrationCancellables.Remove(aMenuBar);
++
++ if (!aSuccess) {
++ aMenuBar->Deactivate();
++ }
++}
++
++/* static */ void
++nsNativeMenuService::PrefChangedCallback(const char *aPref,
++ void *aClosure)
++{
++ nsNativeMenuService::GetSingleton()->PrefChanged();
++}
++
++void
++nsNativeMenuService::PrefChanged()
++{
++ if (!mDbusProxy) {
++ SetOnline(false);
++ return;
++ }
++
++ OnNameOwnerChanged();
++}
++
++NS_IMETHODIMP
++nsNativeMenuService::CreateNativeMenuBar(nsIWidget *aParent,
++ mozilla::dom::Element *aMenuBarNode)
++{
++ NS_ENSURE_ARG(aParent);
++ NS_ENSURE_ARG(aMenuBarNode);
++
++ if (aMenuBarNode->AttrValueIs(kNameSpaceID_None,
++ nsGkAtoms::_moz_menubarkeeplocal,
++ nsGkAtoms::_true,
++ eCaseMatters)) {
++ return NS_OK;
++ }
++
++ UniquePtr<nsMenuBar> menubar(nsMenuBar::Create(aParent, aMenuBarNode));
++ if (!menubar) {
++ NS_WARNING("Failed to create menubar");
++ return NS_ERROR_FAILURE;
++ }
++
++ // Unity forgets our window if it is unmapped by the application, which
++ // happens with some extensions that add "minimize to tray" type
++ // functionality. We hook on to the MapNotify event to re-register our menu
++ // with Unity
++ g_signal_connect(G_OBJECT(menubar->TopLevelWindow()),
++ "map-event", G_CALLBACK(map_event_cb),
++ menubar.get());
++
++ mMenuBars.AppendElement(menubar.get());
++ RegisterNativeMenuBar(menubar.get());
++
++ static_cast<nsWindow *>(aParent)->SetMenuBar(std::move(menubar));
++
++ return NS_OK;
++}
++
++/* static */ already_AddRefed<nsNativeMenuService>
++nsNativeMenuService::GetInstanceForServiceManager()
++{
++ RefPtr<nsNativeMenuService> service(sService);
++
++ if (service) {
++ return service.forget();
++ }
++
++ service = new nsNativeMenuService();
++
++ if (NS_FAILED(service->Init())) {
++ return nullptr;
++ }
++
++ sService = service.get();
++ return service.forget();
++}
++
++/* static */ nsNativeMenuService*
++nsNativeMenuService::GetSingleton()
++{
++ EnsureInitialized();
++ return sService;
++}
++
++void
++nsNativeMenuService::NotifyNativeMenuBarDestroyed(nsMenuBar *aMenuBar)
++{
++ g_signal_handlers_disconnect_by_func(aMenuBar->TopLevelWindow(),
++ FuncToGpointer(map_event_cb),
++ aMenuBar);
++
++ mMenuBars.RemoveElement(aMenuBar);
++
++ GCancellable *cancellable = nullptr;
++ if (mMenuBarRegistrationCancellables.Get(aMenuBar, &cancellable)) {
++ mMenuBarRegistrationCancellables.Remove(aMenuBar);
++ g_cancellable_cancel(cancellable);
++ g_object_unref(cancellable);
++ }
++}
+diff --git a/widget/gtk/nsNativeMenuService.h b/widget/gtk/nsNativeMenuService.h
+new file mode 100644
+index 000000000000..2e0d429eddfd
+--- /dev/null
++++ b/widget/gtk/nsNativeMenuService.h
+@@ -0,0 +1,85 @@
++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:expandtab:shiftwidth=4:tabstop=4:
++ */
++/* This Source Code Form is subject to the terms of the Mozilla Public
++ * License, v. 2.0. If a copy of the MPL was not distributed with this
++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
++
++#ifndef __nsNativeMenuService_h__
++#define __nsNativeMenuService_h__
++
++#include "mozilla/Attributes.h"
++#include "nsCOMPtr.h"
++#include "nsTHashMap.h"
++#include "nsINativeMenuService.h"
++#include "nsTArray.h"
++
++#include <gdk/gdk.h>
++#include <gio/gio.h>
++#include <gtk/gtk.h>
++
++class nsMenuBar;
++
++/*
++ * The main native menu service singleton.
++ * NativeMenuSupport::CreateNativeMenuBar calls in to this when a new top level
++ * window is created.
++ *
++ * Menubars are owned by their nsWindow. This service holds a weak reference to
++ * each menubar for the purpose of re-registering them with the shell if it
++ * needs to. The menubar is responsible for notifying the service when the last
++ * reference to it is dropped.
++ */
++class nsNativeMenuService final : public nsINativeMenuService
++{
++public:
++ NS_DECL_ISUPPORTS
++
++ NS_IMETHOD CreateNativeMenuBar(nsIWidget* aParent, mozilla::dom::Element* aMenuBarNode) override;
++
++ // Returns the singleton addref'd for the service manager
++ static already_AddRefed<nsNativeMenuService> GetInstanceForServiceManager();
++
++ // Returns the singleton without increasing the reference count
++ static nsNativeMenuService* GetSingleton();
++
++ // Called by a menubar when it is deleted
++ void NotifyNativeMenuBarDestroyed(nsMenuBar *aMenuBar);
++
++private:
++ nsNativeMenuService();
++ ~nsNativeMenuService();
++ nsresult Init();
++
++ static void EnsureInitialized();
++ void SetOnline(bool aOnline);
++ void RegisterNativeMenuBar(nsMenuBar *aMenuBar);
++ static void name_owner_changed_cb(GObject *gobject,
++ GParamSpec *pspec,
++ gpointer user_data);
++ static void proxy_created_cb(GObject *source_object,
++ GAsyncResult *res,
++ gpointer user_data);
++ static void register_native_menubar_cb(GObject *source_object,
++ GAsyncResult *res,
++ gpointer user_data);
++ static gboolean map_event_cb(GtkWidget *widget, GdkEvent *event,
++ gpointer user_data);
++ void OnNameOwnerChanged();
++ void OnProxyCreated(GDBusProxy *aProxy);
++ void OnNativeMenuBarRegistered(nsMenuBar *aMenuBar,
++ bool aSuccess);
++ static void PrefChangedCallback(const char *aPref, void *aClosure);
++ void PrefChanged();
++
++ GCancellable *mCreateProxyCancellable;
++ GDBusProxy *mDbusProxy;
++ bool mOnline;
++ nsTArray<nsMenuBar *> mMenuBars;
++ nsTHashMap<nsPtrHashKey<nsMenuBar>, GCancellable*> mMenuBarRegistrationCancellables;
++
++ static bool sShutdown;
++ static nsNativeMenuService *sService;
++};
++
++#endif /* __nsNativeMenuService_h__ */
+diff --git a/widget/gtk/nsWindow.cpp b/widget/gtk/nsWindow.cpp
+index 5f227073d8cd..1ab99c38551f 100644
+--- a/widget/gtk/nsWindow.cpp
++++ b/widget/gtk/nsWindow.cpp
+@@ -7103,6 +7103,10 @@ void nsWindow::HideWindowChrome(bool aShouldHide) {
+ SetWindowDecoration(aShouldHide ? eBorderStyle_none : mBorderStyle);
+ }
+
++void nsWindow::SetMenuBar(UniquePtr<nsMenuBar> aMenuBar) {
++ mMenuBar = std::move(aMenuBar);
++}
++
+ bool nsWindow::CheckForRollup(gdouble aMouseX, gdouble aMouseY, bool aIsWheel,
+ bool aAlwaysRollup) {
+ nsIRollupListener* rollupListener = GetActiveRollupListener();
+diff --git a/widget/gtk/nsWindow.h b/widget/gtk/nsWindow.h
+index d4951ec54d1c..90fbd91765b1 100644
+--- a/widget/gtk/nsWindow.h
++++ b/widget/gtk/nsWindow.h
+@@ -27,6 +27,8 @@
+ #include "nsRefPtrHashtable.h"
+ #include "IMContextWrapper.h"
+
++#include "nsMenuBar.h"
++
+ #ifdef ACCESSIBILITY
+ # include "mozilla/a11y/LocalAccessible.h"
+ #endif
+@@ -203,6 +205,8 @@ class nsWindow final : public nsBaseWidget {
+ nsresult MakeFullScreen(bool aFullScreen) override;
+ void HideWindowChrome(bool aShouldHide) override;
+
++ void SetMenuBar(mozilla::UniquePtr<nsMenuBar> aMenuBar);
++
+ /**
+ * GetLastUserInputTime returns a timestamp for the most recent user input
+ * event. This is intended for pointer grab requests (including drags).
+@@ -874,6 +878,8 @@ class nsWindow final : public nsBaseWidget {
+
+ static bool sTransparentMainWindow;
+
++ mozilla::UniquePtr<nsMenuBar> mMenuBar;
++
+ #ifdef ACCESSIBILITY
+ RefPtr<mozilla::a11y::LocalAccessible> mRootAccessible;
+
+diff --git a/widget/moz.build b/widget/moz.build
+index 9b150c592a8c..77bd0cbbe5c5 100644
+--- a/widget/moz.build
++++ b/widget/moz.build
+@@ -161,6 +161,11 @@ EXPORTS += [
+ "PuppetWidget.h",
+ ]
+
++if toolkit == "gtk":
++ EXPORTS += [
++ "nsINativeMenuService.h",
++ ]
++
+ EXPORTS.mozilla += [
+ "BasicEvents.h",
+ "ColorScheme.h",
+diff --git a/widget/nsINativeMenuService.h b/widget/nsINativeMenuService.h
+new file mode 100644
+index 000000000000..e92d7a74a3bc
+--- /dev/null
++++ b/widget/nsINativeMenuService.h
+@@ -0,0 +1,39 @@
++/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
++/* This Source Code Form is subject to the terms of the Mozilla Public
++ * License, v. 2.0. If a copy of the MPL was not distributed with this
++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
++
++#ifndef nsINativeMenuService_h_
++#define nsINativeMenuService_h_
++
++#include "nsISupports.h"
++
++class nsIWidget;
++class nsIContent;
++namespace mozilla {
++namespace dom {
++class Element;
++}
++} // namespace mozilla
++
++// {90DF88F9-F084-4EF3-829A-49496E636DED}
++#define NS_INATIVEMENUSERVICE_IID \
++ { \
++ 0x90DF88F9, 0xF084, 0x4EF3, { \
++ 0x82, 0x9A, 0x49, 0x49, 0x6E, 0x63, 0x6D, 0xED \
++ } \
++ }
++
++class nsINativeMenuService : public nsISupports {
++ public:
++ NS_DECLARE_STATIC_IID_ACCESSOR(NS_INATIVEMENUSERVICE_IID)
++ // Given a top-level window widget and a menu bar DOM node, sets up native
++ // menus. Once created, native menus are controlled via the DOM, including
++ // destruction.
++ NS_IMETHOD CreateNativeMenuBar(nsIWidget* aParent,
++ mozilla::dom::Element* aMenuBarNode) = 0;
++};
++
++NS_DEFINE_STATIC_IID_ACCESSOR(nsINativeMenuService, NS_INATIVEMENUSERVICE_IID)
++
++#endif // nsINativeMenuService_h_
+diff --git a/widget/nsWidgetsCID.h b/widget/nsWidgetsCID.h
+index 8e0f67661414..911711e88a0e 100644
+--- a/widget/nsWidgetsCID.h
++++ b/widget/nsWidgetsCID.h
+@@ -66,6 +66,14 @@
+ // Menus
+ //-----------------------------------------------------------
+
++// {0B3FE5AA-BC72-4303-85AE-76365DF1251D}
++#define NS_NATIVEMENUSERVICE_CID \
++ { \
++ 0x0B3FE5AA, 0xBC72, 0x4303, { \
++ 0x85, 0xAE, 0x76, 0x36, 0x5D, 0xF1, 0x25, 0x1D \
++ } \
++ }
++
+ // {F6CD4F21-53AF-11d2-8DC4-00609703C14E}
+ #define NS_POPUPMENU_CID \
+ { \
+diff --git a/xpcom/ds/NativeMenuAtoms.py b/xpcom/ds/NativeMenuAtoms.py
+new file mode 100644
+index 000000000000..488c8f49c021
+--- /dev/null
++++ b/xpcom/ds/NativeMenuAtoms.py
+@@ -0,0 +1,9 @@
++from Atom import Atom
++
++NATIVE_MENU_ATOMS = [
++ Atom("menuitem_with_favicon", "menuitem-with-favicon"),
++ Atom("_moz_menubarkeeplocal", "_moz-menubarkeeplocal"),
++ Atom("_moz_nativemenupopupstate", "_moz-nativemenupopupstate"),
++ Atom("openedwithkey", "openedwithkey"),
++ Atom("shellshowingmenubar", "shellshowingmenubar"),
++]
+diff --git a/xpcom/ds/StaticAtoms.py b/xpcom/ds/StaticAtoms.py
+index 740b3f6e187d..3ca7630abf25 100644
+--- a/xpcom/ds/StaticAtoms.py
++++ b/xpcom/ds/StaticAtoms.py
+@@ -7,6 +7,7 @@
+ from Atom import Atom, InheritingAnonBoxAtom, NonInheritingAnonBoxAtom
+ from Atom import PseudoElementAtom
+ from HTMLAtoms import HTML_PARSER_ATOMS
++from NativeMenuAtoms import NATIVE_MENU_ATOMS
+ import sys
+
+ # Static atom definitions, used to generate nsGkAtomList.h.
+@@ -2536,7 +2537,7 @@ STATIC_ATOMS = [
+ InheritingAnonBoxAtom("AnonBox_mozSVGForeignContent", ":-moz-svg-foreign-content"),
+ InheritingAnonBoxAtom("AnonBox_mozSVGText", ":-moz-svg-text"),
+ # END ATOMS
+-] + HTML_PARSER_ATOMS
++] + HTML_PARSER_ATOMS + NATIVE_MENU_ATOMS
+ # fmt: on
+
+
+diff --git a/xpfe/appshell/AppWindow.cpp b/xpfe/appshell/AppWindow.cpp
+index fb36837f23da..c6b6aae09ac8 100644
+--- a/xpfe/appshell/AppWindow.cpp
++++ b/xpfe/appshell/AppWindow.cpp
+@@ -80,7 +80,7 @@
+
+ #include "mozilla/dom/DocumentL10n.h"
+
+-#ifdef XP_MACOSX
++#if defined(XP_MACOSX) || defined(MOZ_WIDGET_GTK)
+ # include "mozilla/widget/NativeMenuSupport.h"
+ # define USE_NATIVE_MENUS
+ #endif
+--
+2.37.3
+
diff --git a/waterfox-g/debian/patches/libavcodec58_91.patch b/waterfox-g/debian/patches/libavcodec58_91.patch
new file mode 100644
index 0000000..eff1d60
--- /dev/null
+++ b/waterfox-g/debian/patches/libavcodec58_91.patch
@@ -0,0 +1,16 @@
+diff --git a/dom/media/platforms/ffmpeg/FFmpegRuntimeLinker.cpp b/dom/media/platforms/ffmpeg/FFmpegRuntimeLinker.cpp
+index 91bf38df8354..e79bc068668a 100644
+--- a/dom/media/platforms/ffmpeg/FFmpegRuntimeLinker.cpp
++++ b/dom/media/platforms/ffmpeg/FFmpegRuntimeLinker.cpp
+@@ -39,6 +39,8 @@ static const char* sLibs[] = {
+ // of ffmpeg and update it regulary on ABI/API changes
+ #else
+ "libavcodec.so.59",
++ "libavcodec.so.58.134",
++ "libavcodec.so.58.91",
+ "libavcodec.so.58",
+ "libavcodec-ffmpeg.so.58",
+ "libavcodec-ffmpeg.so.57",
+--
+2.37.3
+
diff --git a/waterfox-g/debian/patches/mach-depends.patch b/waterfox-g/debian/patches/mach-depends.patch
new file mode 100644
index 0000000..de85066
--- /dev/null
+++ b/waterfox-g/debian/patches/mach-depends.patch
@@ -0,0 +1,24 @@
+diff --git a/python/sites/mach.txt b/python/sites/mach.txt
+index 6547ee58574a..a7d9a8b2ca70 100644
+--- a/python/sites/mach.txt
++++ b/python/sites/mach.txt
+@@ -114,7 +114,7 @@ vendored:third_party/python/taskcluster
+ vendored:third_party/python/taskcluster_taskgraph
+ vendored:third_party/python/taskcluster_urls
+ vendored:third_party/python/tqdm
+-vendored:third_party/python/typing_extensions
++pypi-optional:typing_extensions>=3.10.0:something will break
+ vendored:third_party/python/urllib3
+ vendored:third_party/python/voluptuous
+ vendored:third_party/python/wheel
+@@ -133,5 +133,5 @@ pypi-optional:glean-sdk==44.1.1:telemetry will not be collected
+ # Mach gracefully handles the case where `psutil` is unavailable.
+ # We aren't (yet) able to pin packages in automation, so we have to
+ # support down to the oldest locally-installed version (5.4.2).
+-pypi-optional:psutil>=5.4.2,<=5.8.0:telemetry will be missing some data
+-pypi-optional:zstandard>=0.11.1,<=0.17.0:zstd archives will not be possible to extract
++pypi-optional:psutil>=5.4.2,<=6.0.0:telemetry will be missing some data
++pypi-optional:zstandard>=0.11.1,<=1.0.0:zstd archives will not be possible to extract
+--
+2.37.3
+
diff --git a/waterfox-g/debian/patches/mozilla-ntlm-full-path.patch b/waterfox-g/debian/patches/mozilla-ntlm-full-path.patch
new file mode 100644
index 0000000..ce5b6a2
--- /dev/null
+++ b/waterfox-g/debian/patches/mozilla-ntlm-full-path.patch
@@ -0,0 +1,28 @@
+# HG changeset patch
+# User Petr Cerny <pcerny@novell.com>
+# Parent 7308e4a7c1f769f4bbbc90870b849cadd99495a6
+# Parent 2361c5db1e70e358b2158325e07fa15bb4569c2c
+Bug 634334 - call to the ntlm_auth helper fails
+
+diff --git a/extensions/auth/nsAuthSambaNTLM.cpp b/extensions/auth/nsAuthSambaNTLM.cpp
+--- a/extensions/auth/nsAuthSambaNTLM.cpp
++++ b/extensions/auth/nsAuthSambaNTLM.cpp
+@@ -156,17 +156,17 @@ static uint8_t* ExtractMessage(const nsA
+ *aLen = (length / 4) * 3 - numEquals;
+ return reinterpret_cast<uint8_t*>(PL_Base64Decode(s, length, nullptr));
+ }
+
+ nsresult nsAuthSambaNTLM::SpawnNTLMAuthHelper() {
+ const char* username = PR_GetEnv("USER");
+ if (!username) return NS_ERROR_FAILURE;
+
+- const char* const args[] = {"ntlm_auth",
++ const char* const args[] = {"/usr/bin/ntlm_auth",
+ "--helper-protocol",
+ "ntlmssp-client-1",
+ "--use-cached-creds",
+ "--username",
+ username,
+ nullptr};
+
+ bool isOK = SpawnIOChild(const_cast<char* const*>(args), &mChildPID,
diff --git a/waterfox-g/debian/patches/nongnome-proxies.patch b/waterfox-g/debian/patches/nongnome-proxies.patch
new file mode 100644
index 0000000..5eaf79d
--- /dev/null
+++ b/waterfox-g/debian/patches/nongnome-proxies.patch
@@ -0,0 +1,35 @@
+From: Wolfgang Rosenauer
+Subject: Do not use gconf for proxy settings if not running within Gnome
+
+diff --git a/toolkit/system/unixproxy/nsUnixSystemProxySettings.cpp b/toolkit/system/unixproxy/nsUnixSystemProxySettings.cpp
+--- a/toolkit/system/unixproxy/nsUnixSystemProxySettings.cpp
++++ b/toolkit/system/unixproxy/nsUnixSystemProxySettings.cpp
+@@ -49,20 +49,24 @@ NS_IMETHODIMP
+ nsUnixSystemProxySettings::GetMainThreadOnly(bool* aMainThreadOnly) {
+ // dbus prevents us from being threadsafe, but this routine should not block
+ // anyhow
+ *aMainThreadOnly = true;
+ return NS_OK;
+ }
+
+ void nsUnixSystemProxySettings::Init() {
+- mGSettings = do_GetService(NS_GSETTINGSSERVICE_CONTRACTID);
+- if (mGSettings) {
+- mGSettings->GetCollectionForSchema("org.gnome.system.proxy"_ns,
+- getter_AddRefs(mProxySettings));
++ const char* sessionType = PR_GetEnv("DESKTOP_SESSION");
++ if (sessionType && !strcmp(sessionType, "gnome")) {
++ mGSettings = do_GetService(NS_GSETTINGSSERVICE_CONTRACTID);
++ if (mGSettings) {
++ mGSettings->GetCollectionForSchema(
++ "org.gnome.system.proxy"_ns,
++ getter_AddRefs(mProxySettings));
++ }
+ }
+ }
+
+ nsresult nsUnixSystemProxySettings::GetPACURI(nsACString& aResult) {
+ if (mProxySettings) {
+ nsCString proxyMode;
+ // Check if mode is auto
+ nsresult rv = mProxySettings->GetString("mode"_ns, proxyMode);
diff --git a/waterfox-g/debian/patches/series b/waterfox-g/debian/patches/series
new file mode 100644
index 0000000..d6ead97
--- /dev/null
+++ b/waterfox-g/debian/patches/series
@@ -0,0 +1,9 @@
+global_menu.patch -p1
+fis-csd-global-menu.patch -p1
+#g-kde.patch -p1
+nongnome-proxies.patch -p1
+mozilla-ntlm-full-path.patch -p1
+libavcodec58_91.patch -p1
+fix-langpack-id.patch -p1
+waterfox-branded-icons.patch -p1
+fix-wayland-build.patch -p1
diff --git a/waterfox-g/debian/patches/waterfox-branded-icons.patch b/waterfox-g/debian/patches/waterfox-branded-icons.patch
new file mode 100644
index 0000000..e0778a5
--- /dev/null
+++ b/waterfox-g/debian/patches/waterfox-branded-icons.patch
@@ -0,0 +1,19 @@
+diff --git a/browser/branding/branding-common.mozbuild b/browser/branding/branding-common.mozbuild
+--- a/browser/branding/branding-common.mozbuild
++++ b/browser/branding/branding-common.mozbuild
+@@ -22,12 +22,15 @@ def FirefoxBranding():
+ FINAL_TARGET_FILES.VisualElements += [
+ 'VisualElements_150.png',
+ 'VisualElements_70.png',
+ ]
+ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gtk':
+ FINAL_TARGET_FILES.chrome.icons.default += [
+ 'default128.png',
+ 'default16.png',
++ 'default22.png',
++ 'default24.png',
++ 'default256.png',
+ 'default32.png',
+ 'default48.png',
+ 'default64.png',
+ ]
diff --git a/waterfox-g/debian/rules b/waterfox-g/debian/rules
new file mode 100755
index 0000000..462dd96
--- /dev/null
+++ b/waterfox-g/debian/rules
@@ -0,0 +1,69 @@
+#!/usr/bin/make -f
+# -*- makefile -*-
+
+export SHELL=/bin/bash
+export MOZCONFIG=$(shell pwd)/debian/mozconfig
+export MOZ_NOSPAM:=1
+export MOZ_SOURCE_REPO=https://github.com/WaterfoxCo/Waterfox
+export MOZ_SOURCE_CHANGESET=$(shell awk -F ': ' '/^commit:/ {print $$2; exit}' ../SOURCES/waterfox-g.obsinfo)
+export WF_VERSION=$(shell awk -F ': ' '/^version:/ {print "G"$$2; exit}' ../SOURCES/waterfox-g.obsinfo)
+export TODAY_DATE=$(shell date +%Y-%m-%d)
+export LC_ALL=C.UTF-8
+
+LDFLAGS += -Wl,--no-keep-memory -Wl,--no-mmap-output-file
+
+distrelease := $(shell lsb_release -sc)
+ifeq ($(distrelease),$(filter $(distrelease),stretch xenial))
+LDFLAGS += -static-libstdc++
+endif
+
+export LDFLAGS
+
+export JOBS=$(shell echo $(shell grep -c ^processor /proc/cpuinfo)\/2 | bc)
+
+export MACH_BUILD_PYTHON_NATIVE_PACKAGE_SOURCE=system
+
+export DEB_BUILD_MAINT_OPTIONS = optimize=-lto
+
+%:
+ dh $@
+
+override_dh_auto_clean:
+ dh_auto_clean
+ find . -name '*.pyc' -delete
+
+override_dh_auto_build:
+ # Upstream modifies version_display.txt using script included in GitHub Actions (version=git_tag),
+ # without patching. Debhelper won't allow us to do that by same way and
+ # patching won't be much convenient, so we need to create new files.
+ mkdir -p $$(pwd)/debian/app_version
+ cp $$(pwd)/browser/config/version.txt $$(pwd)/debian/app_version/version.txt
+ echo $$WF_VERSION > $$(pwd)/debian/app_version/version_display.txt
+ # Build browser
+ # LTO needs more open files
+ ulimit -n 4096; xvfb-run -a -n 97 -s "-screen 0 1920x1080x24" ./mach build
+
+ # Build langpacks
+ mkdir -p $$(pwd)/extensions
+ # langpack-build can not be done in parallel easily (see https://bugzilla.mozilla.org/show_bug.cgi?id=1660943)
+ # Therefore, we have to have a separate obj-dir for each language
+ # We do this, by creating a mozconfig-template with the necessary switches
+ # and a placeholder obj-dir, which gets copied and modified for each language
+ sed -r '/^(ja-JP-mac|en-US|)$$/d;s/ .*$$//' debian/locales.shipped | cut -f1 -d":" \
+ | xargs -n 1 -P $$JOBS -I {} /bin/sh -c 'locale=$$1; cp debian/mozconfig_LANG mozconfig_$$locale; sed -i "s|obj_LANG|obj_$$locale|" mozconfig_$$locale; export MOZCONFIG=mozconfig_$$locale; ./mach build config/nsinstall langpack-$$locale; cp -L ../obj_$$locale/dist/linux-*/xpi/waterfox-g-$$WF_VERSION.$$locale.langpack.xpi \
+ $$(pwd)/extensions/langpack-$$locale@l10n.waterfox.net.xpi' -- {}
+
+override_dh_auto_install:
+ chmod +x $$(pwd)/debian/waterfox-g-bin.sh
+ DESTDIR=$$(pwd)/debian/waterfox-g ./mach install
+ rm -rf $$(pwd)/debian/waterfox-g/usr/lib/waterfox-g/dictionaries
+ rm -rf $$(pwd)/debian/waterfox-g/usr/lib/waterfox-g/waterfox-g-bin
+ sed -i "s/__DATE__/$$TODAY_DATE/g" $$(pwd)/debian/waterfox-g.appdata.xml.in
+ sed -e "s/__VERSION__/$$WF_VERSION/g" $$(pwd)/debian/waterfox-g.appdata.xml.in > $$(pwd)/debian/waterfox-g/usr/share/metainfo/waterfox-g.appdata.xml
+ mv $$(pwd)/debian/waterfox-g-wayland-bin.sh $$(pwd)/debian/waterfox-g-wayland/usr/bin/waterfox-g-wayland
+
+override_dh_shlibdeps:
+ dh_shlibdeps -l /usr/lib/waterfox-g/waterfox-g
+
+override_dh_strip_nondeterminism:
+ dh_strip_nondeterminism -Xdebian/waterfox-g/usr/lib/waterfox-g/browser/features/formautofill@mozilla.org.xpi
diff --git a/waterfox-g/debian/source/format b/waterfox-g/debian/source/format
new file mode 100644
index 0000000..163aaf8
--- /dev/null
+++ b/waterfox-g/debian/source/format
@@ -0,0 +1 @@
+3.0 (quilt)
diff --git a/waterfox-g/debian/spellcheck.js b/waterfox-g/debian/spellcheck.js
new file mode 100644
index 0000000..ddd9a6e
--- /dev/null
+++ b/waterfox-g/debian/spellcheck.js
@@ -0,0 +1,2 @@
+// Use system's dictionaries
+pref("spellchecker.dictionary_path", "/usr/share/hunspell");
diff --git a/waterfox-g/debian/syspref.js b/waterfox-g/debian/syspref.js
new file mode 100644
index 0000000..403ba2e
--- /dev/null
+++ b/waterfox-g/debian/syspref.js
@@ -0,0 +1,3 @@
+// This file can be used to configure global preferences for Waterfox Current
+// Example: Homepage
+//pref("browser.startup.homepage", "http://www.weebls-stuff.com/wab/");
diff --git a/waterfox-g/debian/usr.bin.waterfox-g b/waterfox-g/debian/usr.bin.waterfox-g
new file mode 100644
index 0000000..e588573
--- /dev/null
+++ b/waterfox-g/debian/usr.bin.waterfox-g
@@ -0,0 +1,232 @@
+# vim:syntax=apparmor
+# Modified from firefox definition file
+# Original Author: Jamie Strandboge <jamie@canonical.com>
+
+# Declare an apparmor variable to help with overrides
+@{MOZ_LIBDIR}=/usr/lib/waterfox-g4
+
+#include <tunables/global>
+
+# We want to confine the binaries that match:
+# /usr/lib/waterfox-g4/waterfox-g4
+# /usr/lib/waterfox-g4/waterfox-g4
+# but not:
+# /usr/lib/waterfox-g4/waterfox-g4.sh
+/usr/lib/waterfox-g4/waterfox-g4{,*[^s][^h]} {
+ #include <abstractions/audio>
+ #include <abstractions/cups-client>
+ #include <abstractions/dbus-strict>
+ #include <abstractions/dbus-session-strict>
+ #include <abstractions/dconf>
+ #include <abstractions/gnome>
+ #include <abstractions/ibus>
+ #include <abstractions/nameservice>
+ #include <abstractions/openssl>
+ #include <abstractions/p11-kit>
+ #include <abstractions/ubuntu-unity7-base>
+ #include <abstractions/ubuntu-unity7-launcher>
+
+ #include <abstractions/dbus-accessibility-strict>
+ dbus (send)
+ bus=session
+ peer=(name=org.a11y.Bus),
+ dbus (receive)
+ bus=session
+ interface=org.a11y.atspi**,
+ dbus (receive, send)
+ bus=accessibility,
+
+ # for networking
+ network inet stream,
+ network inet6 stream,
+ @{PROC}/[0-9]*/net/if_inet6 r,
+ @{PROC}/[0-9]*/net/ipv6_route r,
+ @{PROC}/[0-9]*/net/dev r,
+ @{PROC}/[0-9]*/net/wireless r,
+ dbus (send)
+ bus=system
+ path=/org/freedesktop/NetworkManager
+ member=state,
+ dbus (receive)
+ bus=system
+ path=/org/freedesktop/NetworkManager,
+
+ # should maybe be in abstractions
+ /etc/ r,
+ /etc/mime.types r,
+ /etc/mailcap r,
+ /etc/xdg/*buntu/applications/defaults.list r, # for all derivatives
+ /etc/xfce4/defaults.list r,
+ /usr/share/xubuntu/applications/defaults.list r,
+ owner @{HOME}/.local/share/applications/defaults.list r,
+ owner @{HOME}/.local/share/applications/mimeapps.list r,
+ owner @{HOME}/.local/share/applications/mimeinfo.cache r,
+ owner /tmp/** m,
+ owner /var/tmp/** m,
+ owner /{,var/}run/shm/shmfd-* rw,
+ owner /{dev,run}/shm/org.chromium.* rwk,
+ /tmp/.X[0-9]*-lock r,
+ /etc/udev/udev.conf r,
+ # Doesn't seem to be required, but noisy. Maybe allow 'r' for 'b*' if needed.
+ # Possibly move to an abstraction if anything else needs it.
+ deny /run/udev/data/** r,
+ # let the shell know we launched something
+ dbus (send)
+ bus=session
+ interface=org.gtk.gio.DesktopAppInfo
+ member=Launched,
+
+ /etc/timezone r,
+ /etc/wildmidi/wildmidi.cfg r,
+
+ # waterfox specific
+ /etc/waterfox-g4*/ r,
+ /etc/waterfox-g4*/** r,
+
+ # firefox specific
+ #/etc/xul-ext/** r,
+ #/etc/xulrunner-2.0*/ r,
+ #/etc/xulrunner-2.0*/** r,
+ #/etc/gre.d/ r,
+ #/etc/gre.d/* r,
+
+ # noisy
+ #deny @{MOZ_LIBDIR}/** w,
+ #deny /usr/lib/firefox-addons/** w,
+ #deny /usr/lib/xulrunner-addons/** w,
+ #deny /usr/lib/xulrunner-*/components/*.tmp w,
+ deny /.suspended r,
+ deny /boot/initrd.img* r,
+ deny /boot/vmlinuz* r,
+ deny /var/cache/fontconfig/ w,
+ deny @{HOME}/.local/share/recently-used.xbel r,
+
+ # TODO: investigate
+ deny /usr/bin/gconftool-2 x,
+
+ # These are needed when a new user starts waterfox and waterfox.sh is used
+ @{MOZ_LIBDIR}/** ixr,
+ /usr/bin/basename ixr,
+ /usr/bin/dirname ixr,
+ /usr/bin/pwd ixr,
+ /sbin/killall5 ixr,
+ /bin/which ixr,
+ /usr/bin/tr ixr,
+ @{PROC}/ r,
+ @{PROC}/[0-9]*/cmdline r,
+ @{PROC}/[0-9]*/mountinfo r,
+ @{PROC}/[0-9]*/stat r,
+ owner @{PROC}/[0-9]*/task/[0-9]*/stat r,
+ @{PROC}/[0-9]*/status r,
+ @{PROC}/filesystems r,
+ @{PROC}/sys/vm/overcommit_memory r,
+ /sys/devices/pci[0-9]*/**/uevent r,
+ /sys/devices/platform/**/uevent r,
+ /sys/devices/pci*/**/{busnum,idVendor,idProduct} r,
+ owner @{HOME}/.cache/thumbnails/** rw,
+
+ /etc/mtab r,
+ /etc/fstab r,
+
+ # Needed for the crash reporter
+ owner @{PROC}/[0-9]*/environ r,
+ owner @{PROC}/[0-9]*/auxv r,
+ /etc/lsb-release r,
+ /usr/bin/expr ix,
+ /sys/devices/system/cpu/ r,
+ /sys/devices/system/cpu/** r,
+
+ # about:memory
+ owner @{PROC}/[0-9]*/statm r,
+ owner @{PROC}/[0-9]*/smaps r,
+
+ # Needed for container to work in xul builds
+ #/usr/lib/xulrunner-*/plugin-container ixr,
+
+ # allow access to documentation and other files the user may want to look
+ # at in /usr and @{MOZ_LIBDIR}
+ /usr/ r,
+ /usr/** r,
+ @{MOZ_LIBDIR}/ r,
+ @{MOZ_LIBDIR}/** r,
+
+ # so browsing directories works
+ / r,
+ /**/ r,
+
+ # Default profile allows downloads to ~/Downloads and uploads from ~/Public
+ owner @{HOME}/ r,
+ owner @{HOME}/Public/ r,
+ owner @{HOME}/Public/* r,
+ owner @{HOME}/Downloads/ r,
+ owner @{HOME}/Downloads/* rw,
+
+ # per-user waterfox configuration
+ owner @{HOME}/.waterfox/ rw,
+ owner @{HOME}/.waterfox/** rw,
+ owner @{HOME}/.waterfox/**/*.{db,parentlock,sqlite}* k,
+ owner @{HOME}/.waterfox/plugins/** rm,
+ owner @{HOME}/.waterfox/**/plugins/** rm,
+ owner @{HOME}/.gnome2/waterfox* rwk,
+ owner @{HOME}/.cache/waterfox/ rw,
+ owner @{HOME}/.cache/waterfox/** rw,
+ owner @{HOME}/.cache/waterfox/**/*.sqlite k,
+ owner @{HOME}/.config/gtk-3.0/bookmarks r,
+ owner @{HOME}/.config/dconf/user w,
+ owner /{,var/}run/user/*/dconf/user w,
+ dbus (send)
+ bus=session
+ path=/org/gnome/GConf/Server
+ member=GetDefaultDatabase,
+ dbus (send)
+ bus=session
+ path=/org/gnome/GConf/Database/*
+ member={AddMatch,AddNotify,AllEntries,LookupExtended,RemoveNotify},
+
+ #
+ # Extensions
+ # /usr/share/.../extensions/... is already covered by '/usr/** r', above.
+ # Allow 'x' for downloaded extensions, but inherit policy for safety
+ owner @{HOME}/.waterfox/**/extensions/** mixr,
+
+ #deny @{MOZ_LIBDIR}/update.test w,
+ #deny /usr/lib/mozilla/extensions/**/ w,
+ #deny /usr/lib/xulrunner-addons/extensions/**/ w,
+ #deny /usr/share/mozilla/extensions/**/ w,
+ #deny /usr/share/mozilla/ w,
+
+ # Miscellaneous (to be abstracted)
+ # Ideally these would use a child profile. They are all ELF executables
+ # so running with 'Ux', while not ideal, is ok because we will at least
+ # benefit from glibc's secure execute.
+ /usr/bin/mkfifo Uxr, # investigate
+ /bin/ps Uxr,
+ /bin/uname Uxr,
+
+ /usr/bin/lsb_release Cxr -> lsb_release,
+ profile lsb_release {
+ #include <abstractions/base>
+ #include <abstractions/python>
+ /usr/bin/lsb_release r,
+ /bin/dash ixr,
+ /usr/bin/dpkg-query ixr,
+ /usr/include/python2.[4567]/pyconfig.h r,
+ /etc/lsb-release r,
+ /etc/debian_version r,
+ /var/lib/dpkg/** r,
+
+ /usr/local/lib/python3.[0-4]/dist-packages/ r,
+ /usr/bin/ r,
+ /usr/bin/python3.[0-4] r,
+
+ # file_inherit
+ deny /tmp/gtalkplugin.log w,
+ }
+
+ # Addons
+ #include <abstractions/ubuntu-browsers.d/waterfox-g4>
+ /usr/lib/waterfox/kwaterfoxhelper Cxr -> sanitized_helper,
+
+ # Site-specific additions and overrides. See local/README for details.
+ #include <local/usr.bin.waterfox-g4>
+}
diff --git a/waterfox-g/debian/vendor.js b/waterfox-g/debian/vendor.js
new file mode 100644
index 0000000..a7c9948
--- /dev/null
+++ b/waterfox-g/debian/vendor.js
@@ -0,0 +1,29 @@
+// Disable default browser checking
+pref("browser.shell.checkDefaultBrowser", false);
+pref("browser.defaultbrowser.notificationbar", false);
+
+// Don't disable extensions dropped in to a system
+// location, or those owned by the application
+pref("extensions.autoDisableScopes", 3);
+
+// Don't display the one-off addon selection dialog when
+// upgrading from a version of Waterfox older than 8.0
+pref("extensions.shownSelectionUI", true);
+
+// Fall back to en-US search plugins if none exist for the current locale
+pref("distribution.searchplugins.defaultLocale", "en-US");
+
+// Use OS regional settings for date and time
+pref("intl.regional_prefs.use_os_locales", true);
+
+// Use LANG environment variable to choose locale
+pref("intl.locale.requested", "");
+
+// Enable Network Manager integration
+pref("network.manage-offline-status", true);
+
+// Disable downloading language packs, cuz Waterfox uses own and they are already included in subpackages
+pref("extensions.getAddons.langpacks.url", "", locked);
+
+// Disable requiring signatures for language packs
+pref("extensions.langpacks.signatures.required", false, locked);
diff --git a/waterfox-g/debian/waterfox-g-bin.sh b/waterfox-g/debian/waterfox-g-bin.sh
new file mode 100755
index 0000000..af78404
--- /dev/null
+++ b/waterfox-g/debian/waterfox-g-bin.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+exec /usr/lib/waterfox-g/waterfox-g "$@"
diff --git a/waterfox-g/debian/waterfox-g-i18n-ar.install b/waterfox-g/debian/waterfox-g-i18n-ar.install
new file mode 100644
index 0000000..cccfebd
--- /dev/null
+++ b/waterfox-g/debian/waterfox-g-i18n-ar.install
@@ -0,0 +1 @@
+extensions/langpack-ar@l10n.waterfox.net.xpi /usr/lib/waterfox-g/browser/extensions
diff --git a/waterfox-g/debian/waterfox-g-i18n-cs.install b/waterfox-g/debian/waterfox-g-i18n-cs.install
new file mode 100644
index 0000000..aa3857a
--- /dev/null
+++ b/waterfox-g/debian/waterfox-g-i18n-cs.install
@@ -0,0 +1 @@
+extensions/langpack-cs@l10n.waterfox.net.xpi /usr/lib/waterfox-g/browser/extensions
diff --git a/waterfox-g/debian/waterfox-g-i18n-da.install b/waterfox-g/debian/waterfox-g-i18n-da.install
new file mode 100644
index 0000000..5907ad7
--- /dev/null
+++ b/waterfox-g/debian/waterfox-g-i18n-da.install
@@ -0,0 +1 @@
+extensions/langpack-da@l10n.waterfox.net.xpi /usr/lib/waterfox-g/browser/extensions
diff --git a/waterfox-g/debian/waterfox-g-i18n-de.install b/waterfox-g/debian/waterfox-g-i18n-de.install
new file mode 100644
index 0000000..77116eb
--- /dev/null
+++ b/waterfox-g/debian/waterfox-g-i18n-de.install
@@ -0,0 +1 @@
+extensions/langpack-de@l10n.waterfox.net.xpi /usr/lib/waterfox-g/browser/extensions
diff --git a/waterfox-g/debian/waterfox-g-i18n-el.install b/waterfox-g/debian/waterfox-g-i18n-el.install
new file mode 100644
index 0000000..18967d4
--- /dev/null
+++ b/waterfox-g/debian/waterfox-g-i18n-el.install
@@ -0,0 +1 @@
+extensions/langpack-el@l10n.waterfox.net.xpi /usr/lib/waterfox-g/browser/extensions
diff --git a/waterfox-g/debian/waterfox-g-i18n-en-gb.install b/waterfox-g/debian/waterfox-g-i18n-en-gb.install
new file mode 100644
index 0000000..ebc80bd
--- /dev/null
+++ b/waterfox-g/debian/waterfox-g-i18n-en-gb.install
@@ -0,0 +1 @@
+extensions/langpack-en-GB@l10n.waterfox.net.xpi /usr/lib/waterfox-g/browser/extensions
diff --git a/waterfox-g/debian/waterfox-g-i18n-es-es.install b/waterfox-g/debian/waterfox-g-i18n-es-es.install
new file mode 100644
index 0000000..d1a4932
--- /dev/null
+++ b/waterfox-g/debian/waterfox-g-i18n-es-es.install
@@ -0,0 +1 @@
+extensions/langpack-es-ES@l10n.waterfox.net.xpi /usr/lib/waterfox-g/browser/extensions
diff --git a/waterfox-g/debian/waterfox-g-i18n-es-mx.install b/waterfox-g/debian/waterfox-g-i18n-es-mx.install
new file mode 100644
index 0000000..ee9d84e
--- /dev/null
+++ b/waterfox-g/debian/waterfox-g-i18n-es-mx.install
@@ -0,0 +1 @@
+extensions/langpack-es-MX@l10n.waterfox.net.xpi /usr/lib/waterfox-g/browser/extensions
diff --git a/waterfox-g/debian/waterfox-g-i18n-fr.install b/waterfox-g/debian/waterfox-g-i18n-fr.install
new file mode 100644
index 0000000..257d6bd
--- /dev/null
+++ b/waterfox-g/debian/waterfox-g-i18n-fr.install
@@ -0,0 +1 @@
+extensions/langpack-fr@l10n.waterfox.net.xpi /usr/lib/waterfox-g/browser/extensions
diff --git a/waterfox-g/debian/waterfox-g-i18n-hu.install b/waterfox-g/debian/waterfox-g-i18n-hu.install
new file mode 100644
index 0000000..7567626
--- /dev/null
+++ b/waterfox-g/debian/waterfox-g-i18n-hu.install
@@ -0,0 +1 @@
+extensions/langpack-hu@l10n.waterfox.net.xpi /usr/lib/waterfox-g/browser/extensions
diff --git a/waterfox-g/debian/waterfox-g-i18n-id.install b/waterfox-g/debian/waterfox-g-i18n-id.install
new file mode 100644
index 0000000..96cd5ab
--- /dev/null
+++ b/waterfox-g/debian/waterfox-g-i18n-id.install
@@ -0,0 +1 @@
+extensions/langpack-id@l10n.waterfox.net.xpi /usr/lib/waterfox-g/browser/extensions
diff --git a/waterfox-g/debian/waterfox-g-i18n-it.install b/waterfox-g/debian/waterfox-g-i18n-it.install
new file mode 100644
index 0000000..663bcf5
--- /dev/null
+++ b/waterfox-g/debian/waterfox-g-i18n-it.install
@@ -0,0 +1 @@
+extensions/langpack-it@l10n.waterfox.net.xpi /usr/lib/waterfox-g/browser/extensions
diff --git a/waterfox-g/debian/waterfox-g-i18n-ja.install b/waterfox-g/debian/waterfox-g-i18n-ja.install
new file mode 100644
index 0000000..553c8d4
--- /dev/null
+++ b/waterfox-g/debian/waterfox-g-i18n-ja.install
@@ -0,0 +1 @@
+extensions/langpack-ja@l10n.waterfox.net.xpi /usr/lib/waterfox-g/browser/extensions
diff --git a/waterfox-g/debian/waterfox-g-i18n-ko.install b/waterfox-g/debian/waterfox-g-i18n-ko.install
new file mode 100644
index 0000000..2bb19d2
--- /dev/null
+++ b/waterfox-g/debian/waterfox-g-i18n-ko.install
@@ -0,0 +1 @@
+extensions/langpack-ko@l10n.waterfox.net.xpi /usr/lib/waterfox-g/browser/extensions
diff --git a/waterfox-g/debian/waterfox-g-i18n-lt.install b/waterfox-g/debian/waterfox-g-i18n-lt.install
new file mode 100644
index 0000000..bcff3c5
--- /dev/null
+++ b/waterfox-g/debian/waterfox-g-i18n-lt.install
@@ -0,0 +1 @@
+extensions/langpack-lt@l10n.waterfox.net.xpi /usr/lib/waterfox-g/browser/extensions
diff --git a/waterfox-g/debian/waterfox-g-i18n-nl.install b/waterfox-g/debian/waterfox-g-i18n-nl.install
new file mode 100644
index 0000000..d0a1bfb
--- /dev/null
+++ b/waterfox-g/debian/waterfox-g-i18n-nl.install
@@ -0,0 +1 @@
+extensions/langpack-nl@l10n.waterfox.net.xpi /usr/lib/waterfox-g/browser/extensions
diff --git a/waterfox-g/debian/waterfox-g-i18n-nn-no.install b/waterfox-g/debian/waterfox-g-i18n-nn-no.install
new file mode 100644
index 0000000..0a5f477
--- /dev/null
+++ b/waterfox-g/debian/waterfox-g-i18n-nn-no.install
@@ -0,0 +1 @@
+extensions/langpack-nn-NO@l10n.waterfox.net.xpi /usr/lib/waterfox-g/browser/extensions
diff --git a/waterfox-g/debian/waterfox-g-i18n-pl.install b/waterfox-g/debian/waterfox-g-i18n-pl.install
new file mode 100644
index 0000000..7400132
--- /dev/null
+++ b/waterfox-g/debian/waterfox-g-i18n-pl.install
@@ -0,0 +1 @@
+extensions/langpack-pl@l10n.waterfox.net.xpi /usr/lib/waterfox-g/browser/extensions
diff --git a/waterfox-g/debian/waterfox-g-i18n-pt-br.install b/waterfox-g/debian/waterfox-g-i18n-pt-br.install
new file mode 100644
index 0000000..0f7344e
--- /dev/null
+++ b/waterfox-g/debian/waterfox-g-i18n-pt-br.install
@@ -0,0 +1 @@
+extensions/langpack-pt-BR@l10n.waterfox.net.xpi /usr/lib/waterfox-g/browser/extensions
diff --git a/waterfox-g/debian/waterfox-g-i18n-pt-pt.install b/waterfox-g/debian/waterfox-g-i18n-pt-pt.install
new file mode 100644
index 0000000..e63d557
--- /dev/null
+++ b/waterfox-g/debian/waterfox-g-i18n-pt-pt.install
@@ -0,0 +1 @@
+extensions/langpack-pt-PT@l10n.waterfox.net.xpi /usr/lib/waterfox-g/browser/extensions
diff --git a/waterfox-g/debian/waterfox-g-i18n-ru.install b/waterfox-g/debian/waterfox-g-i18n-ru.install
new file mode 100644
index 0000000..c9211ed
--- /dev/null
+++ b/waterfox-g/debian/waterfox-g-i18n-ru.install
@@ -0,0 +1 @@
+extensions/langpack-ru@l10n.waterfox.net.xpi /usr/lib/waterfox-g/browser/extensions
diff --git a/waterfox-g/debian/waterfox-g-i18n-sv-se.install b/waterfox-g/debian/waterfox-g-i18n-sv-se.install
new file mode 100644
index 0000000..d364eff
--- /dev/null
+++ b/waterfox-g/debian/waterfox-g-i18n-sv-se.install
@@ -0,0 +1 @@
+extensions/langpack-sv-SE@l10n.waterfox.net.xpi /usr/lib/waterfox-g/browser/extensions
diff --git a/waterfox-g/debian/waterfox-g-i18n-th.install b/waterfox-g/debian/waterfox-g-i18n-th.install
new file mode 100644
index 0000000..27fb620
--- /dev/null
+++ b/waterfox-g/debian/waterfox-g-i18n-th.install
@@ -0,0 +1 @@
+extensions/langpack-th@l10n.waterfox.net.xpi /usr/lib/waterfox-g/browser/extensions
diff --git a/waterfox-g/debian/waterfox-g-i18n-vi.install b/waterfox-g/debian/waterfox-g-i18n-vi.install
new file mode 100644
index 0000000..99238e8
--- /dev/null
+++ b/waterfox-g/debian/waterfox-g-i18n-vi.install
@@ -0,0 +1 @@
+extensions/langpack-vi@l10n.waterfox.net.xpi /usr/lib/waterfox-g/browser/extensions
diff --git a/waterfox-g/debian/waterfox-g-i18n-zh-cn.install b/waterfox-g/debian/waterfox-g-i18n-zh-cn.install
new file mode 100644
index 0000000..03d5a7e
--- /dev/null
+++ b/waterfox-g/debian/waterfox-g-i18n-zh-cn.install
@@ -0,0 +1 @@
+extensions/langpack-zh-CN@l10n.waterfox.net.xpi /usr/lib/waterfox-g/browser/extensions
diff --git a/waterfox-g/debian/waterfox-g-i18n-zh-tw.install b/waterfox-g/debian/waterfox-g-i18n-zh-tw.install
new file mode 100644
index 0000000..eb23fef
--- /dev/null
+++ b/waterfox-g/debian/waterfox-g-i18n-zh-tw.install
@@ -0,0 +1 @@
+extensions/langpack-zh-TW@l10n.waterfox.net.xpi /usr/lib/waterfox-g/browser/extensions
diff --git a/waterfox-g/debian/waterfox-g-wayland-bin.sh b/waterfox-g/debian/waterfox-g-wayland-bin.sh
new file mode 100755
index 0000000..5b227ba
--- /dev/null
+++ b/waterfox-g/debian/waterfox-g-wayland-bin.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+#
+# Run Waterfox G under Wayland
+#
+export MOZ_ENABLE_WAYLAND=1
+exec /usr/lib/waterfox-g/waterfox-g "$@"
diff --git a/waterfox-g/debian/waterfox-g-wayland.desktop b/waterfox-g/debian/waterfox-g-wayland.desktop
new file mode 100644
index 0000000..7dc6f47
--- /dev/null
+++ b/waterfox-g/debian/waterfox-g-wayland.desktop
@@ -0,0 +1,347 @@
+[Desktop Entry]
+Version=1.0
+Name=Waterfox G (Wayland)
+Comment=Browse the World Wide Web
+Comment[ar]=تصفح الشبكة العنكبوتية العالمية
+Comment[ast]=Restola pela Rede
+Comment[bn]=ইন্টারনেট ব্রাউজ করুন
+Comment[ca]=Navegueu per la web
+Comment[cs]=Prohlížení stránek World Wide Webu
+Comment[da]=Surf på internettet
+Comment[de]=Im Internet surfen
+Comment[el]=Μπορείτε να περιηγηθείτε στο διαδίκτυο (Web)
+Comment[es]=Navegue por la web
+Comment[et]=Lehitse veebi
+Comment[fa]=صفحات شبکه جهانی اینترنت را مرور نمایید
+Comment[fi]=Selaa Internetin WWW-sivuja
+Comment[fr]=Naviguer sur le Web
+Comment[gl]=Navegar pola rede
+Comment[he]=גלישה ברחבי האינטרנט
+Comment[hr]=Pretražite web
+Comment[hu]=A világháló böngészése
+Comment[it]=Esplora il web
+Comment[ja]=ウェブを閲覧します
+Comment[ko]=웹을 돌아 다닙니다
+Comment[ku]=Li torê bigere
+Comment[lt]=Naršykite internete
+Comment[nb]=Surf på nettet
+Comment[nl]=Verken het internet
+Comment[nn]=Surf på nettet
+Comment[no]=Surf på nettet
+Comment[pl]=Przeglądaj strony WWW
+Comment[pt]=Explorar a Internet com o Waterfox
+Comment[pt_BR]=Navegue na Internet
+Comment[ro]=Navigați pe Internet
+Comment[ru]=Доступ в Интернет
+Comment[sk]=Prehliadanie internetu
+Comment[sl]=Brskajte po spletu
+Comment[sv]=Surfa på webben
+Comment[tr]=İnternet'te Gezinin
+Comment[ug]=دۇنيادىكى توربەتلەرنى كۆرگىلى بولىدۇ
+Comment[uk]=Перегляд сторінок Інтернету
+Comment[vi]=Để duyệt các trang web
+Comment[zh_CN]=浏览互联网
+Comment[zh_TW]=瀏覽網際網路
+GenericName=Web Browser
+GenericName[ar]=متصفح ويب
+GenericName[ast]=Restolador Web
+GenericName[bn]=ওয়েব ব্রাউজার
+GenericName[ca]=Navegador web
+GenericName[cs]=Webový prohlížeč
+GenericName[da]=Webbrowser
+GenericName[el]=Περιηγητής διαδικτύου
+GenericName[es]=Navegador web
+GenericName[et]=Veebibrauser
+GenericName[fa]=مرورگر اینترنتی
+GenericName[fi]=WWW-selain
+GenericName[fr]=Navigateur Web
+GenericName[gl]=Navegador Web
+GenericName[he]=דפדפן אינטרנט
+GenericName[hr]=Web preglednik
+GenericName[hu]=Webböngésző
+GenericName[it]=Browser web
+GenericName[ja]=ウェブ・ブラウザ
+GenericName[ko]=웹 브라우저
+GenericName[ku]=Geroka torê
+GenericName[lt]=Interneto naršyklė
+GenericName[nb]=Nettleser
+GenericName[nl]=Webbrowser
+GenericName[nn]=Nettlesar
+GenericName[no]=Nettleser
+GenericName[pl]=Przeglądarka WWW
+GenericName[pt]=Navegador web
+GenericName[pt_BR]=Navegador Web
+GenericName[ro]=Navigator Internet
+GenericName[ru]=Веб-браузер
+GenericName[sk]=Internetový prehliadač
+GenericName[sl]=Spletni brskalnik
+GenericName[sv]=Webbläsare
+GenericName[tr]=Web Tarayıcı
+GenericName[ug]=توركۆرگۈ
+GenericName[uk]=Веб-браузер
+GenericName[vi]=Trình duyệt Web
+GenericName[zh_CN]=网络浏览器
+GenericName[zh_TW]=網路瀏覽器
+Keywords=Internet;WWW;Browser;Web;Explorer;
+Keywords[ar]=انترنت;إنترنت;متصفح;ويب;وب;
+Keywords[ast]=Internet;WWW;Restolador;Web;Esplorador;
+Keywords[ca]=Internet;WWW;Navegador;Web;Explorador;Explorer;
+Keywords[cs]=Internet;WWW;Prohlížeč;Web;Explorer;
+Keywords[da]=Internet;Internettet;WWW;Browser;Browse;Web;Surf;Nettet;
+Keywords[de]=Internet;WWW;Browser;Web;Explorer;Webseite;Site;surfen;online;browsen;
+Keywords[el]=Internet;WWW;Browser;Web;Explorer;Διαδίκτυο;Περιηγητής;Waterfox;Φιρεφοχ;Ιντερνετ;
+Keywords[es]=Explorador;Internet;WWW;
+Keywords[fi]=Internet;WWW;Browser;Web;Explorer;selain;Internet-selain;internetselain;verkkoselain;netti;surffaa;
+Keywords[fr]=Internet;WWW;Browser;Web;Explorer;Fureteur;Surfer;Navigateur;
+Keywords[he]=דפדפן;אינטרנט;רשת;אתרים;אתר;פיירפוקס;מוזילה;
+Keywords[hr]=Internet;WWW;preglednik;Web;
+Keywords[hu]=Internet;WWW;Böngésző;Web;Háló;Net;Explorer;
+Keywords[it]=Internet;WWW;Browser;Web;Navigatore;
+Keywords[is]=Internet;WWW;Vafri;Vefur;Netvafri;Flakk;
+Keywords[ja]=Internet;WWW;Web;インターネット;ブラウザ;ウェブ;エクスプローラ;
+Keywords[nb]=Internett;WWW;Nettleser;Explorer;Web;Browser;Nettside;
+Keywords[nl]=Internet;WWW;Browser;Web;Explorer;Verkenner;Website;Surfen;Online;
+Keywords[pl]=Internet;WWW;Przeglądarka;Sieć;Surfowanie;Strona internetowa;Strona;Przeglądanie;
+Keywords[pt]=Internet;WWW;Browser;Web;Explorador;Navegador;
+Keywords[pt_BR]=Internet;WWW;Browser;Web;Explorador;Navegador;
+Keywords[ru]=Internet;WWW;Browser;Web;Explorer;интернет;браузер;веб;файрфокс;огнелис;
+Keywords[sk]=Internet;WWW;Prehliadač;Web;Explorer;
+Keywords[sl]=Internet;WWW;Browser;Web;Explorer;Brskalnik;Splet;
+Keywords[tr]=İnternet;WWW;Tarayıcı;Web;Gezgin;Web sitesi;Site;sörf;çevrimiçi;tara;
+Keywords[uk]=Internet;WWW;Browser;Web;Explorer;Інтернет;мережа;переглядач;оглядач;браузер;веб;файрфокс;вогнелис;перегляд;
+Keywords[vi]=Internet;WWW;Browser;Web;Explorer;Trình duyệt;Trang web;
+Keywords[zh_CN]=Internet;WWW;Browser;Web;Explorer;网页;浏览;上网;水狐;Waterfox;wf;互联网;网站;
+Keywords[zh_TW]=Internet;WWW;Browser;Web;Explorer;網際網路;網路;瀏覽器;上網;網頁;水狐;
+Exec=waterfox-g-wayland %u
+Terminal=false
+X-MuiltpleArgs=false
+Type=Application
+Icon=waterfox-g
+Categories=Network;WebBrowser;
+MimeType=text/html;text/xml;application/xhtml+xml;application/xml;application/rss+xml;application/rdf+xml;image/gif;image/jpeg;image/png;x-scheme-handler/http;x-scheme-handler/https;x-scheme-handler/ftp;x-scheme-handler/chrome;video/webm;application/x-xpinstall;
+StartupNotify=true
+Actions=NewWindow;NewPrivateWindow;ProfileManagerWindow;
+
+[Desktop Action NewWindow]
+Name=Open a New Window
+Name[ach]=Dirica manyen
+Name[af]=Nuwe venster
+Name[an]=Nueva finestra
+Name[ar]=نافذة جديدة
+Name[as]=নতুন উইন্ডো
+Name[ast]=Ventana nueva
+Name[az]=Yeni Pəncərə
+Name[be]=Новае акно
+Name[bg]=Нов прозорец
+Name[bn_BD]=নতুন উইন্ডো (N)
+Name[bn_IN]=নতুন উইন্ডো
+Name[br]=Prenestr nevez
+Name[brx]=गोदान उइन्ड'(N)
+Name[bs]=Novi prozor
+Name[ca]=Finestra nova
+Name[cak]=K'ak'a' tzuwäch
+Name[cs]=Nové okno
+Name[cy]=Ffenestr Newydd
+Name[da]=Nyt vindue
+Name[de]=Neues Fenster
+Name[dsb]=Nowe wokno
+Name[el]=Νέο παράθυρο
+Name[en_GB]=New Window
+Name[en_US]=New Window
+Name[en_ZA]=New Window
+Name[eo]=Nova fenestro
+Name[es_AR]=Nueva ventana
+Name[es_CL]=Nueva ventana
+Name[es_ES]=Nueva ventana
+Name[es_MX]=Nueva ventana
+Name[et]=Uus aken
+Name[eu]=Leiho berria
+Name[fa]=پنجره جدید‌
+Name[ff]=Henorde Hesere
+Name[fi]=Uusi ikkuna
+Name[fr]=Nouvelle fenêtre
+Name[fy_NL]=Nij finster
+Name[ga_IE]=Fuinneog Nua
+Name[gd]=Uinneag ùr
+Name[gl]=Nova xanela
+Name[gn]=Ovetã pyahu
+Name[gu_IN]=નવી વિન્ડો
+Name[he]=חלון חדש
+Name[hi_IN]=नया विंडो
+Name[hr]=Novi prozor
+Name[hsb]=Nowe wokno
+Name[hu]=Új ablak
+Name[hy_AM]=Նոր Պատուհան
+Name[id]=Jendela Baru
+Name[is]=Nýr gluggi
+Name[it]=Nuova finestra
+Name[ja]=新しいウィンドウ
+Name[ja_JP-mac]=新規ウインドウ
+Name[ka]=ახალი ფანჯარა
+Name[kk]=Жаңа терезе
+Name[km]=បង្អួច​​​ថ្មី
+Name[kn]=ಹೊಸ ಕಿಟಕಿ
+Name[ko]=새 창
+Name[kok]=नवें जनेल
+Name[ks]=نئئ وِنڈو
+Name[lij]=Neuvo barcon
+Name[lo]=ຫນ້າຕ່າງໃຫມ່
+Name[lt]=Naujas langas
+Name[ltg]=Jauns lūgs
+Name[lv]=Jauns logs
+Name[mai]=नव विंडो
+Name[mk]=Нов прозорец
+Name[ml]=പുതിയ ജാലകം
+Name[mr]=नवीन पटल
+Name[ms]=Tetingkap Baru
+Name[my]=ဝင်းဒိုးအသစ်
+Name[nb_NO]=Nytt vindu
+Name[ne_NP]=नयाँ सञ्झ्याल
+Name[nl]=Nieuw venster
+Name[nn_NO]=Nytt vindauge
+Name[or]=ନୂତନ ୱିଣ୍ଡୋ
+Name[pa_IN]=ਨਵੀਂ ਵਿੰਡੋ
+Name[pl]=Nowe okno
+Name[pt_BR]=Nova janela
+Name[pt_PT]=Nova janela
+Name[rm]=Nova fanestra
+Name[ro]=Fereastră nouă
+Name[ru]=Новое окно
+Name[sat]=नावा विंडो (N)
+Name[si]=නව කවුළුවක්
+Name[sk]=Nové okno
+Name[sl]=Novo okno
+Name[son]=Zanfun taaga
+Name[sq]=Dritare e Re
+Name[sr]=Нови прозор
+Name[sv_SE]=Nytt fönster
+Name[ta]=புதிய சாளரம்
+Name[te]=కొత్త విండో
+Name[th]=หน้าต่างใหม่
+Name[tr]=Yeni pencere
+Name[tsz]=Eraatarakua jimpani
+Name[uk]=Нове вікно
+Name[ur]=نیا دریچہ
+Name[uz]=Yangi oyna
+Name[vi]=Cửa sổ mới
+Name[wo]=Palanteer bu bees
+Name[xh]=Ifestile entsha
+Name[zh_CN]=新建窗口
+Name[zh_TW]=開新視窗
+Exec=waterfox-g-wayland --new-window
+
+[Desktop Action NewPrivateWindow]
+Name=Open a New Private Window
+Name[ach]=Dirica manyen me mung
+Name[af]=Nuwe privaatvenster
+Name[an]=Nueva finestra privada
+Name[ar]=نافذة خاصة جديدة
+Name[as]=নতুন ব্যক্তিগত উইন্ডো
+Name[ast]=Ventana privada nueva
+Name[az]=Yeni Məxfi Pəncərə
+Name[be]=Новае акно адасаблення
+Name[bg]=Нов прозорец за поверително сърфиране
+Name[bn_BD]=নতুন ব্যক্তিগত উইন্ডো
+Name[bn_IN]=নতুন ব্যক্তিগত উইন্ডো
+Name[br]=Prenestr merdeiñ prevez nevez
+Name[brx]=गोदान प्राइभेट उइन्ड'
+Name[bs]=Novi privatni prozor
+Name[ca]=Finestra privada nova
+Name[cak]=K'ak'a' ichinan tzuwäch
+Name[cs]=Nové anonymní okno
+Name[cy]=Ffenestr Breifat Newydd
+Name[da]=Nyt privat vindue
+Name[de]=Neues privates Fenster
+Name[dsb]=Nowe priwatne wokno
+Name[el]=Νέο παράθυρο ιδιωτικής περιήγησης
+Name[en_GB]=New Private Window
+Name[en_US]=New Private Window
+Name[en_ZA]=New Private Window
+Name[eo]=Nova privata fenestro
+Name[es_AR]=Nueva ventana privada
+Name[es_CL]=Nueva ventana privada
+Name[es_ES]=Nueva ventana privada
+Name[es_MX]=Nueva ventana privada
+Name[et]=Uus privaatne aken
+Name[eu]=Leiho pribatu berria
+Name[fa]=پنجره ناشناس جدید
+Name[ff]=Henorde Suturo Hesere
+Name[fi]=Uusi yksityinen ikkuna
+Name[fr]=Nouvelle fenêtre de navigation privée
+Name[fy_NL]=Nij priveefinster
+Name[ga_IE]=Fuinneog Nua Phríobháideach
+Name[gd]=Uinneag phrìobhaideach ùr
+Name[gl]=Nova xanela privada
+Name[gn]=Ovetã ñemi pyahu
+Name[gu_IN]=નવી ખાનગી વિન્ડો
+Name[he]=חלון פרטי חדש
+Name[hi_IN]=नयी निजी विंडो
+Name[hr]=Novi privatni prozor
+Name[hsb]=Nowe priwatne wokno
+Name[hu]=Új privát ablak
+Name[hy_AM]=Սկսել Գաղտնի դիտարկում
+Name[id]=Jendela Mode Pribadi Baru
+Name[is]=Nýr huliðsgluggi
+Name[it]=Nuova finestra anonima
+Name[ja]=新しいプライベートウィンドウ
+Name[ja_JP-mac]=新規プライベートウインドウ
+Name[ka]=ახალი პირადი ფანჯარა
+Name[kk]=Жаңа жекелік терезе
+Name[km]=បង្អួច​ឯកជន​ថ្មី
+Name[kn]=ಹೊಸ ಖಾಸಗಿ ಕಿಟಕಿ
+Name[ko]=새 사생활 보호 모드
+Name[kok]=नवो खाजगी विंडो
+Name[ks]=نْو پرایوٹ وینڈو&amp;
+Name[lij]=Neuvo barcon privou
+Name[lo]=ເປີດຫນ້າຕ່າງສວນຕົວຂື້ນມາໃຫມ່
+Name[lt]=Naujas privataus naršymo langas
+Name[ltg]=Jauns privatais lūgs
+Name[lv]=Jauns privātais logs
+Name[mai]=नया निज विंडो (W)
+Name[mk]=Нов приватен прозорец
+Name[ml]=പുതിയ സ്വകാര്യ ജാലകം
+Name[mr]=नवीन वैयक्तिक पटल
+Name[ms]=Tetingkap Persendirian Baharu
+Name[my]=New Private Window
+Name[nb_NO]=Nytt privat vindu
+Name[ne_NP]=नयाँ निजी सञ्झ्याल
+Name[nl]=Nieuw privévenster
+Name[nn_NO]=Nytt privat vindauge
+Name[or]=ନୂତନ ବ୍ୟକ୍ତିଗତ ୱିଣ୍ଡୋ
+Name[pa_IN]=ਨਵੀਂ ਪ੍ਰਾਈਵੇਟ ਵਿੰਡੋ
+Name[pl]=Nowe okno prywatne
+Name[pt_BR]=Nova janela privativa
+Name[pt_PT]=Nova janela privada
+Name[rm]=Nova fanestra privata
+Name[ro]=Fereastră privată nouă
+Name[ru]=Новое приватное окно
+Name[sat]=नावा निजेराक् विंडो (W )
+Name[si]=නව පුද්ගලික කවුළුව (W)
+Name[sk]=Nové okno v režime Súkromné prehliadanie
+Name[sl]=Novo zasebno okno
+Name[son]=Sutura zanfun taaga
+Name[sq]=Dritare e Re Private
+Name[sr]=Нови приватан прозор
+Name[sv_SE]=Nytt privat fönster
+Name[ta]=புதிய தனிப்பட்ட சாளரம்
+Name[te]=కొత్త ఆంతరంగిక విండో
+Name[th]=หน้าต่างส่วนตัวใหม่
+Name[tr]=Yeni gizli pencere
+Name[tsz]=Juchiiti eraatarakua jimpani
+Name[uk]=Приватне вікно
+Name[ur]=نیا نجی دریچہ
+Name[uz]=Yangi maxfiy oyna
+Name[vi]=Cửa sổ riêng tư mới
+Name[wo]=Panlanteeru biir bu bees
+Name[xh]=Ifestile yangasese entsha
+Name[zh_CN]=新建隐私浏览窗口
+Name[zh_TW]=新增隱私視窗
+Exec=waterfox-g-wayland --private-window
+
+[Desktop Action ProfileManagerWindow]
+Name=Open the Profile Manager
+Name[cs]=Správa profilů
+Name[en_GB]=Profile Manager
+Name[en_US]=Profile Manager
+Name[pl]=Menedżer Profili
+Exec=waterfox-g-wayland --ProfileManager
diff --git a/waterfox-g/debian/waterfox-g-wayland.dirs b/waterfox-g/debian/waterfox-g-wayland.dirs
new file mode 100644
index 0000000..e772481
--- /dev/null
+++ b/waterfox-g/debian/waterfox-g-wayland.dirs
@@ -0,0 +1 @@
+usr/bin
diff --git a/waterfox-g/debian/waterfox-g-wayland.install b/waterfox-g/debian/waterfox-g-wayland.install
new file mode 100644
index 0000000..3ea8171
--- /dev/null
+++ b/waterfox-g/debian/waterfox-g-wayland.install
@@ -0,0 +1 @@
+debian/waterfox-g-wayland.desktop usr/share/applications
diff --git a/waterfox-g/debian/waterfox-g.1 b/waterfox-g/debian/waterfox-g.1
new file mode 100644
index 0000000..0f3f117
--- /dev/null
+++ b/waterfox-g/debian/waterfox-g.1
@@ -0,0 +1,119 @@
+.TH waterfox-g 1 "Apr 24, 2022" waterfox-g "Linux User's Manual"
+.SH NAME
+waterfox-g \- customizable privacy-conscious web browser with primary support for webextensions and secondary support for legacy extensions
+
+.SH SYNOPSIS
+.B waterfox-g
+[\fIOPTIONS\fR] [\fIURL\fR]
+
+.SH DESCRIPTION
+Waterfox focuses on giving users choice. The browser is focused on power users, which lets you make the important decisions. There is no plugin whitelist (meaning you can run Java Applets and Silverlight apps), you can run whichever extensions you like (including modified bootstrapped add-ons that can completely change functionality of the browser) and absolutely no data or telemetry is sent back to Mozilla or the Waterfox project.
+
+.SH OPTIONS
+A summary of the options supported by \fBwaterfox-g\fR is included below.
+
+.SS "X11 options"
+.TP
+.BI \-\-display= DISPLAY
+X display to use
+.TP
+.B \--sync
+Make X calls synchronous
+.TP
+.B \-\-g-fatal-warnings
+Make all warnings fatal
+
+.SS "Waterfox options"
+.TP
+.B \-h, \-help
+Show summary of options.
+.TP
+.B \-v, \-version
+Print Waterfox version.
+.TP
+\fB\-P\fR \fIprofile\fR
+Start with \fIprofile\fR.
+.TP
+\fB\-\-profile\fR \fIpath\fR
+Start with profile at \fIpath\fR.
+.TP
+\fB\-\-migration\fR
+Start with migration wizard.
+.TP
+.B \-\-ProfileManager
+Start with ProfileManager.
+.TP
+\fB\-\-no\-remote\fR
+Do not accept or send remote commands; implies \fB--new-instance\fR.
+.TP
+\fB\-\-new\-instance\fR
+Open new instance, not a new window in running instance.
+.TP
+\fB\-\-UILocale\fR \fIlocale\fR
+Start with \fIlocale\fR resources as UI Locale.
+.TP
+\fB\-\-safe\-mode\fR
+Disables extensions and themes for this session.
+.TP
+\fB\-\-headless\fR
+Run without a GUI.
+.TP
+\fB\-\-marionette\fR
+Enable remote control server.
+.TP
+\fB\-\-browser\fR
+Open a browser window.
+.TP
+\fB\-\-new-window\fR \fIurl\fR
+Open \fIurl\fR in a new window.
+.TP
+\fB\-\-new-tab\fR \fIurl\fR
+Open \fIurl\fR in a new tab.
+.TP
+\fB\-\-private-window\fR \fIurl\fR
+Open \fIurl\fR in a new private window.
+.TP
+\fB\-\-preferences\fR
+Open Preferences dialog.
+.TP
+\fB\-\-search\fR \fIterm\fR
+Search \fIterm\fR with your default search engine.
+.TP
+
+
+\fB\-\-jsconsole\fR
+Open the Browser Console.
+.TP
+\fB\-\-jsdebugger\fR
+Open the Browser Toolbox.
+.TP
+\fB\-\-wait-for-jsdebugger\fR
+Spin event loop until JS debugger connects. Enables debugging (some) application startup code paths. Only has an effect when \fI--jsdebugger\fR is also supplied.
+.TP
+\fB\-\-devtools\fR
+Open DevTools on initial load.
+.TP
+\fB\-\-start-debugger-server\fR [ws:][\fIport\fR|\fIpath\fR]
+Start the debugger server on a TCP port or Unix domain socket path. Defaults to TCP port 6000. Use WebSocket protocol if ws: prefix is specified.
+.TP
+\fB\-\-recording\fR \fIfile\fR
+Record drawing for a given URL.
+.TP
+\fB\-\-recording-output\fR \fIfile\fR
+Specify destination file for a drawing recording.
+.TP
+\fB\-\-setDefaultBrowser\fR
+Set this app as the default browser.
+
+.SH FILES
+\fI/usr/bin/waterfox-g\fR - shell script wrapping
+\fBwaterfox-g\fR
+.br
+\fI/usr/lib/waterfox-g/waterfox-g\fR - \fBWaterfox G\fR
+executable
+
+.SH BUGS
+To report a bug, please visit \fIhttps://www.reddit.com/r/waterfox/\fR or \fIhttps://github.com/WaterfoxCo/Waterfox/issues\fR
+
+.SH AUTHOR
+This manual page was written by hawkeye116477, based on Mozilla Firefox's manpage.
diff --git a/waterfox-g/debian/waterfox-g.appdata.xml.in b/waterfox-g/debian/waterfox-g.appdata.xml.in
new file mode 100644
index 0000000..68bf7cc
--- /dev/null
+++ b/waterfox-g/debian/waterfox-g.appdata.xml.in
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<component type="desktop-application">
+ <id>waterfox-g</id>
+ <metadata_license>CC-BY-SA-3.0</metadata_license>
+ <project_license>MPL-2.0</project_license>
+ <project_group>Waterfox</project_group>
+ <developer_name>Waterfox Ltd</developer_name>
+ <name>Waterfox G Edition</name>
+ <summary>Customizable privacy-conscious web browser with primary support for webextensions</summary>
+ <summary xml:lang="pl">Konfigurowalna dbająca o prywatność przeglądarka internetowa z obsługą webextensions i lepszą integracją z KDE</summary>
+ <description>
+ <p>Waterfox focuses on giving users choice. The browser is focused on power users, which lets you make the important decisions. There is no plugin whitelist (meaning you can run Java Applets and Silverlight apps), you can run whichever extensions you like (including bootstrapped add-ons that can completely change functionality of the browser) and absolutely no data or telemetry is sent back to Mozilla or the Waterfox project.</p>
+ <p xml:lang="pl">Waterfox skupia się na zapewnianiu użytkownikom wyboru. Przeglądarka ta koncentruje się na zaawansowanych użytkownikach, co pozwala podejmować ważne decyzje. Nie ma białych list wtyczek (co oznacza, że można uruchomić applety Java i aplikacje Silverlight), można uruchomić dowolne rozszerzenie (w tym dodatki bootstrapowane, które mogą całkowicie zmienić funkcjonalność przeglądarki) i absolutnie żadne dane ani telemetria nie są wysyłane z powrotem do Mozilli ani do projektu Waterfox.</p>
+ <p>Waterfox is powered by Mozilla Firefox source code.</p>
+ <p xml:lang="pl">Waterfox bazuje na kodzie źródłowym programu Mozilla Firefox.</p>
+ </description>
+ <launchable type="desktop-id">waterfox-g.desktop</launchable>
+ <url type="homepage">https://www.waterfox.net/</url>
+ <url type="bugtracker">https://github.com/WaterfoxCo/Waterfox/issues</url>
+ <translation type="gettext">waterfox-g</translation>
+ <screenshots>
+ <screenshot type="default">
+ <image>https://i.imgur.com/8MMsziq.png</image>
+ </screenshot>
+ </screenshots>
+ <provides>
+ <id>waterfox-g.desktop</id>
+ </provides>
+ <content_rating type="oars-1.1" />
+ <releases>
+ <release version="__VERSION__" date="__DATE__"/>
+ </releases>
+ <kudos>
+ <kudo>ModernToolkit</kudo>
+ </kudos>
+</component>
diff --git a/waterfox-g/debian/waterfox-g.desktop b/waterfox-g/debian/waterfox-g.desktop
new file mode 100644
index 0000000..e9ace15
--- /dev/null
+++ b/waterfox-g/debian/waterfox-g.desktop
@@ -0,0 +1,347 @@
+[Desktop Entry]
+Version=1.0
+Name=Waterfox G
+Comment=Browse the World Wide Web
+Comment[ar]=تصفح الشبكة العنكبوتية العالمية
+Comment[ast]=Restola pela Rede
+Comment[bn]=ইন্টারনেট ব্রাউজ করুন
+Comment[ca]=Navegueu per la web
+Comment[cs]=Prohlížení stránek World Wide Webu
+Comment[da]=Surf på internettet
+Comment[de]=Im Internet surfen
+Comment[el]=Μπορείτε να περιηγηθείτε στο διαδίκτυο (Web)
+Comment[es]=Navegue por la web
+Comment[et]=Lehitse veebi
+Comment[fa]=صفحات شبکه جهانی اینترنت را مرور نمایید
+Comment[fi]=Selaa Internetin WWW-sivuja
+Comment[fr]=Naviguer sur le Web
+Comment[gl]=Navegar pola rede
+Comment[he]=גלישה ברחבי האינטרנט
+Comment[hr]=Pretražite web
+Comment[hu]=A világháló böngészése
+Comment[it]=Esplora il web
+Comment[ja]=ウェブを閲覧します
+Comment[ko]=웹을 돌아 다닙니다
+Comment[ku]=Li torê bigere
+Comment[lt]=Naršykite internete
+Comment[nb]=Surf på nettet
+Comment[nl]=Verken het internet
+Comment[nn]=Surf på nettet
+Comment[no]=Surf på nettet
+Comment[pl]=Przeglądaj strony WWW
+Comment[pt]=Explorar a Internet com o Waterfox
+Comment[pt_BR]=Navegue na Internet
+Comment[ro]=Navigați pe Internet
+Comment[ru]=Доступ в Интернет
+Comment[sk]=Prehliadanie internetu
+Comment[sl]=Brskajte po spletu
+Comment[sv]=Surfa på webben
+Comment[tr]=İnternet'te Gezinin
+Comment[ug]=دۇنيادىكى توربەتلەرنى كۆرگىلى بولىدۇ
+Comment[uk]=Перегляд сторінок Інтернету
+Comment[vi]=Để duyệt các trang web
+Comment[zh_CN]=浏览互联网
+Comment[zh_TW]=瀏覽網際網路
+GenericName=Web Browser
+GenericName[ar]=متصفح ويب
+GenericName[ast]=Restolador Web
+GenericName[bn]=ওয়েব ব্রাউজার
+GenericName[ca]=Navegador web
+GenericName[cs]=Webový prohlížeč
+GenericName[da]=Webbrowser
+GenericName[el]=Περιηγητής διαδικτύου
+GenericName[es]=Navegador web
+GenericName[et]=Veebibrauser
+GenericName[fa]=مرورگر اینترنتی
+GenericName[fi]=WWW-selain
+GenericName[fr]=Navigateur Web
+GenericName[gl]=Navegador Web
+GenericName[he]=דפדפן אינטרנט
+GenericName[hr]=Web preglednik
+GenericName[hu]=Webböngésző
+GenericName[it]=Browser web
+GenericName[ja]=ウェブ・ブラウザ
+GenericName[ko]=웹 브라우저
+GenericName[ku]=Geroka torê
+GenericName[lt]=Interneto naršyklė
+GenericName[nb]=Nettleser
+GenericName[nl]=Webbrowser
+GenericName[nn]=Nettlesar
+GenericName[no]=Nettleser
+GenericName[pl]=Przeglądarka WWW
+GenericName[pt]=Navegador web
+GenericName[pt_BR]=Navegador Web
+GenericName[ro]=Navigator Internet
+GenericName[ru]=Веб-браузер
+GenericName[sk]=Internetový prehliadač
+GenericName[sl]=Spletni brskalnik
+GenericName[sv]=Webbläsare
+GenericName[tr]=Web Tarayıcı
+GenericName[ug]=توركۆرگۈ
+GenericName[uk]=Веб-браузер
+GenericName[vi]=Trình duyệt Web
+GenericName[zh_CN]=网络浏览器
+GenericName[zh_TW]=網路瀏覽器
+Keywords=Internet;WWW;Browser;Web;Explorer;
+Keywords[ar]=انترنت;إنترنت;متصفح;ويب;وب;
+Keywords[ast]=Internet;WWW;Restolador;Web;Esplorador;
+Keywords[ca]=Internet;WWW;Navegador;Web;Explorador;Explorer;
+Keywords[cs]=Internet;WWW;Prohlížeč;Web;Explorer;
+Keywords[da]=Internet;Internettet;WWW;Browser;Browse;Web;Surf;Nettet;
+Keywords[de]=Internet;WWW;Browser;Web;Explorer;Webseite;Site;surfen;online;browsen;
+Keywords[el]=Internet;WWW;Browser;Web;Explorer;Διαδίκτυο;Περιηγητής;Waterfox;Φιρεφοχ;Ιντερνετ;
+Keywords[es]=Explorador;Internet;WWW;
+Keywords[fi]=Internet;WWW;Browser;Web;Explorer;selain;Internet-selain;internetselain;verkkoselain;netti;surffaa;
+Keywords[fr]=Internet;WWW;Browser;Web;Explorer;Fureteur;Surfer;Navigateur;
+Keywords[he]=דפדפן;אינטרנט;רשת;אתרים;אתר;פיירפוקס;מוזילה;
+Keywords[hr]=Internet;WWW;preglednik;Web;
+Keywords[hu]=Internet;WWW;Böngésző;Web;Háló;Net;Explorer;
+Keywords[it]=Internet;WWW;Browser;Web;Navigatore;
+Keywords[is]=Internet;WWW;Vafri;Vefur;Netvafri;Flakk;
+Keywords[ja]=Internet;WWW;Web;インターネット;ブラウザ;ウェブ;エクスプローラ;
+Keywords[nb]=Internett;WWW;Nettleser;Explorer;Web;Browser;Nettside;
+Keywords[nl]=Internet;WWW;Browser;Web;Explorer;Verkenner;Website;Surfen;Online;
+Keywords[pl]=Internet;WWW;Przeglądarka;Sieć;Surfowanie;Strona internetowa;Strona;Przeglądanie;
+Keywords[pt]=Internet;WWW;Browser;Web;Explorador;Navegador;
+Keywords[pt_BR]=Internet;WWW;Browser;Web;Explorador;Navegador;
+Keywords[ru]=Internet;WWW;Browser;Web;Explorer;интернет;браузер;веб;файрфокс;огнелис;
+Keywords[sk]=Internet;WWW;Prehliadač;Web;Explorer;
+Keywords[sl]=Internet;WWW;Browser;Web;Explorer;Brskalnik;Splet;
+Keywords[tr]=İnternet;WWW;Tarayıcı;Web;Gezgin;Web sitesi;Site;sörf;çevrimiçi;tara;
+Keywords[uk]=Internet;WWW;Browser;Web;Explorer;Інтернет;мережа;переглядач;оглядач;браузер;веб;файрфокс;вогнелис;перегляд;
+Keywords[vi]=Internet;WWW;Browser;Web;Explorer;Trình duyệt;Trang web;
+Keywords[zh_CN]=Internet;WWW;Browser;Web;Explorer;网页;浏览;上网;水狐;Waterfox;wf;互联网;网站;
+Keywords[zh_TW]=Internet;WWW;Browser;Web;Explorer;網際網路;網路;瀏覽器;上網;網頁;水狐;
+Exec=waterfox-g %u
+Terminal=false
+X-MuiltpleArgs=false
+Type=Application
+Icon=waterfox-g
+Categories=Network;WebBrowser;
+MimeType=text/html;text/xml;application/xhtml+xml;application/xml;application/rss+xml;application/rdf+xml;image/gif;image/jpeg;image/png;x-scheme-handler/http;x-scheme-handler/https;x-scheme-handler/ftp;x-scheme-handler/chrome;video/webm;application/x-xpinstall;
+StartupNotify=true
+Actions=NewWindow;NewPrivateWindow;ProfileManagerWindow;
+
+[Desktop Action NewWindow]
+Name=Open a New Window
+Name[ach]=Dirica manyen
+Name[af]=Nuwe venster
+Name[an]=Nueva finestra
+Name[ar]=نافذة جديدة
+Name[as]=নতুন উইন্ডো
+Name[ast]=Ventana nueva
+Name[az]=Yeni Pəncərə
+Name[be]=Новае акно
+Name[bg]=Нов прозорец
+Name[bn_BD]=নতুন উইন্ডো (N)
+Name[bn_IN]=নতুন উইন্ডো
+Name[br]=Prenestr nevez
+Name[brx]=गोदान उइन्ड'(N)
+Name[bs]=Novi prozor
+Name[ca]=Finestra nova
+Name[cak]=K'ak'a' tzuwäch
+Name[cs]=Nové okno
+Name[cy]=Ffenestr Newydd
+Name[da]=Nyt vindue
+Name[de]=Neues Fenster
+Name[dsb]=Nowe wokno
+Name[el]=Νέο παράθυρο
+Name[en_GB]=New Window
+Name[en_US]=New Window
+Name[en_ZA]=New Window
+Name[eo]=Nova fenestro
+Name[es_AR]=Nueva ventana
+Name[es_CL]=Nueva ventana
+Name[es_ES]=Nueva ventana
+Name[es_MX]=Nueva ventana
+Name[et]=Uus aken
+Name[eu]=Leiho berria
+Name[fa]=پنجره جدید‌
+Name[ff]=Henorde Hesere
+Name[fi]=Uusi ikkuna
+Name[fr]=Nouvelle fenêtre
+Name[fy_NL]=Nij finster
+Name[ga_IE]=Fuinneog Nua
+Name[gd]=Uinneag ùr
+Name[gl]=Nova xanela
+Name[gn]=Ovetã pyahu
+Name[gu_IN]=નવી વિન્ડો
+Name[he]=חלון חדש
+Name[hi_IN]=नया विंडो
+Name[hr]=Novi prozor
+Name[hsb]=Nowe wokno
+Name[hu]=Új ablak
+Name[hy_AM]=Նոր Պատուհան
+Name[id]=Jendela Baru
+Name[is]=Nýr gluggi
+Name[it]=Nuova finestra
+Name[ja]=新しいウィンドウ
+Name[ja_JP-mac]=新規ウインドウ
+Name[ka]=ახალი ფანჯარა
+Name[kk]=Жаңа терезе
+Name[km]=បង្អួច​​​ថ្មី
+Name[kn]=ಹೊಸ ಕಿಟಕಿ
+Name[ko]=새 창
+Name[kok]=नवें जनेल
+Name[ks]=نئئ وِنڈو
+Name[lij]=Neuvo barcon
+Name[lo]=ຫນ້າຕ່າງໃຫມ່
+Name[lt]=Naujas langas
+Name[ltg]=Jauns lūgs
+Name[lv]=Jauns logs
+Name[mai]=नव विंडो
+Name[mk]=Нов прозорец
+Name[ml]=പുതിയ ജാലകം
+Name[mr]=नवीन पटल
+Name[ms]=Tetingkap Baru
+Name[my]=ဝင်းဒိုးအသစ်
+Name[nb_NO]=Nytt vindu
+Name[ne_NP]=नयाँ सञ्झ्याल
+Name[nl]=Nieuw venster
+Name[nn_NO]=Nytt vindauge
+Name[or]=ନୂତନ ୱିଣ୍ଡୋ
+Name[pa_IN]=ਨਵੀਂ ਵਿੰਡੋ
+Name[pl]=Nowe okno
+Name[pt_BR]=Nova janela
+Name[pt_PT]=Nova janela
+Name[rm]=Nova fanestra
+Name[ro]=Fereastră nouă
+Name[ru]=Новое окно
+Name[sat]=नावा विंडो (N)
+Name[si]=නව කවුළුවක්
+Name[sk]=Nové okno
+Name[sl]=Novo okno
+Name[son]=Zanfun taaga
+Name[sq]=Dritare e Re
+Name[sr]=Нови прозор
+Name[sv_SE]=Nytt fönster
+Name[ta]=புதிய சாளரம்
+Name[te]=కొత్త విండో
+Name[th]=หน้าต่างใหม่
+Name[tr]=Yeni pencere
+Name[tsz]=Eraatarakua jimpani
+Name[uk]=Нове вікно
+Name[ur]=نیا دریچہ
+Name[uz]=Yangi oyna
+Name[vi]=Cửa sổ mới
+Name[wo]=Palanteer bu bees
+Name[xh]=Ifestile entsha
+Name[zh_CN]=新建窗口
+Name[zh_TW]=開新視窗
+Exec=waterfox-g --new-window
+
+[Desktop Action NewPrivateWindow]
+Name=Open a New Private Window
+Name[ach]=Dirica manyen me mung
+Name[af]=Nuwe privaatvenster
+Name[an]=Nueva finestra privada
+Name[ar]=نافذة خاصة جديدة
+Name[as]=নতুন ব্যক্তিগত উইন্ডো
+Name[ast]=Ventana privada nueva
+Name[az]=Yeni Məxfi Pəncərə
+Name[be]=Новае акно адасаблення
+Name[bg]=Нов прозорец за поверително сърфиране
+Name[bn_BD]=নতুন ব্যক্তিগত উইন্ডো
+Name[bn_IN]=নতুন ব্যক্তিগত উইন্ডো
+Name[br]=Prenestr merdeiñ prevez nevez
+Name[brx]=गोदान प्राइभेट उइन्ड'
+Name[bs]=Novi privatni prozor
+Name[ca]=Finestra privada nova
+Name[cak]=K'ak'a' ichinan tzuwäch
+Name[cs]=Nové anonymní okno
+Name[cy]=Ffenestr Breifat Newydd
+Name[da]=Nyt privat vindue
+Name[de]=Neues privates Fenster
+Name[dsb]=Nowe priwatne wokno
+Name[el]=Νέο παράθυρο ιδιωτικής περιήγησης
+Name[en_GB]=New Private Window
+Name[en_US]=New Private Window
+Name[en_ZA]=New Private Window
+Name[eo]=Nova privata fenestro
+Name[es_AR]=Nueva ventana privada
+Name[es_CL]=Nueva ventana privada
+Name[es_ES]=Nueva ventana privada
+Name[es_MX]=Nueva ventana privada
+Name[et]=Uus privaatne aken
+Name[eu]=Leiho pribatu berria
+Name[fa]=پنجره ناشناس جدید
+Name[ff]=Henorde Suturo Hesere
+Name[fi]=Uusi yksityinen ikkuna
+Name[fr]=Nouvelle fenêtre de navigation privée
+Name[fy_NL]=Nij priveefinster
+Name[ga_IE]=Fuinneog Nua Phríobháideach
+Name[gd]=Uinneag phrìobhaideach ùr
+Name[gl]=Nova xanela privada
+Name[gn]=Ovetã ñemi pyahu
+Name[gu_IN]=નવી ખાનગી વિન્ડો
+Name[he]=חלון פרטי חדש
+Name[hi_IN]=नयी निजी विंडो
+Name[hr]=Novi privatni prozor
+Name[hsb]=Nowe priwatne wokno
+Name[hu]=Új privát ablak
+Name[hy_AM]=Սկսել Գաղտնի դիտարկում
+Name[id]=Jendela Mode Pribadi Baru
+Name[is]=Nýr huliðsgluggi
+Name[it]=Nuova finestra anonima
+Name[ja]=新しいプライベートウィンドウ
+Name[ja_JP-mac]=新規プライベートウインドウ
+Name[ka]=ახალი პირადი ფანჯარა
+Name[kk]=Жаңа жекелік терезе
+Name[km]=បង្អួច​ឯកជន​ថ្មី
+Name[kn]=ಹೊಸ ಖಾಸಗಿ ಕಿಟಕಿ
+Name[ko]=새 사생활 보호 모드
+Name[kok]=नवो खाजगी विंडो
+Name[ks]=نْو پرایوٹ وینڈو&amp;
+Name[lij]=Neuvo barcon privou
+Name[lo]=ເປີດຫນ້າຕ່າງສວນຕົວຂື້ນມາໃຫມ່
+Name[lt]=Naujas privataus naršymo langas
+Name[ltg]=Jauns privatais lūgs
+Name[lv]=Jauns privātais logs
+Name[mai]=नया निज विंडो (W)
+Name[mk]=Нов приватен прозорец
+Name[ml]=പുതിയ സ്വകാര്യ ജാലകം
+Name[mr]=नवीन वैयक्तिक पटल
+Name[ms]=Tetingkap Persendirian Baharu
+Name[my]=New Private Window
+Name[nb_NO]=Nytt privat vindu
+Name[ne_NP]=नयाँ निजी सञ्झ्याल
+Name[nl]=Nieuw privévenster
+Name[nn_NO]=Nytt privat vindauge
+Name[or]=ନୂତନ ବ୍ୟକ୍ତିଗତ ୱିଣ୍ଡୋ
+Name[pa_IN]=ਨਵੀਂ ਪ੍ਰਾਈਵੇਟ ਵਿੰਡੋ
+Name[pl]=Nowe okno prywatne
+Name[pt_BR]=Nova janela privativa
+Name[pt_PT]=Nova janela privada
+Name[rm]=Nova fanestra privata
+Name[ro]=Fereastră privată nouă
+Name[ru]=Новое приватное окно
+Name[sat]=नावा निजेराक् विंडो (W )
+Name[si]=නව පුද්ගලික කවුළුව (W)
+Name[sk]=Nové okno v režime Súkromné prehliadanie
+Name[sl]=Novo zasebno okno
+Name[son]=Sutura zanfun taaga
+Name[sq]=Dritare e Re Private
+Name[sr]=Нови приватан прозор
+Name[sv_SE]=Nytt privat fönster
+Name[ta]=புதிய தனிப்பட்ட சாளரம்
+Name[te]=కొత్త ఆంతరంగిక విండో
+Name[th]=หน้าต่างส่วนตัวใหม่
+Name[tr]=Yeni gizli pencere
+Name[tsz]=Juchiiti eraatarakua jimpani
+Name[uk]=Приватне вікно
+Name[ur]=نیا نجی دریچہ
+Name[uz]=Yangi maxfiy oyna
+Name[vi]=Cửa sổ riêng tư mới
+Name[wo]=Panlanteeru biir bu bees
+Name[xh]=Ifestile yangasese entsha
+Name[zh_CN]=新建隐私浏览窗口
+Name[zh_TW]=新增隱私視窗
+Exec=waterfox-g --private-window
+
+[Desktop Action ProfileManagerWindow]
+Name=Open the Profile Manager
+Name[cs]=Správa profilů
+Name[en_GB]=Profile Manager
+Name[en_US]=Profile Manager
+Name[pl]=Menedżer Profili
+Exec=waterfox-g --ProfileManager
diff --git a/waterfox-g/debian/waterfox-g.dirs b/waterfox-g/debian/waterfox-g.dirs
new file mode 100644
index 0000000..4e660d0
--- /dev/null
+++ b/waterfox-g/debian/waterfox-g.dirs
@@ -0,0 +1,10 @@
+etc/apparmor.d/disable
+usr/share/icons/hicolor/16x16/apps
+usr/share/icons/hicolor/22x22/apps
+usr/share/icons/hicolor/24x24/apps
+usr/share/icons/hicolor/32x32/apps
+usr/share/icons/hicolor/48x48/apps
+usr/share/icons/hicolor/64x64/apps
+usr/share/icons/hicolor/128x128/apps
+usr/share/icons/hicolor/256x256/apps
+usr/share/metainfo
diff --git a/waterfox-g/debian/waterfox-g.dsc b/waterfox-g/debian/waterfox-g.dsc
new file mode 100644
index 0000000..11b4ec8
--- /dev/null
+++ b/waterfox-g/debian/waterfox-g.dsc
@@ -0,0 +1,16 @@
+Format: 3.0 (quilt)
+Source: waterfox-g
+Binary: waterfox-g
+Architecture: any
+Version: 5.1.10-1+stackrpms
+Maintainer: hawkeye116477 <hawkeye116477@gmail.com>
+Homepage: https://www.waterfox.net
+Standards-Version: 3.9.7
+Build-Depends: debhelper (>= 9), autoconf2.13, libgtk-3-dev (>= 3.14), libdbus-glib-1-dev, libpulse-dev, libasound2-dev, yasm (>= 1.1), build-essential, libxt-dev, python3 (>= 3.5), zip, unzip, cargo (>= 0.59), libgl1-mesa-dev, libnotify-dev (>= 0.4), binutils-avr, libfreetype6-dev (>= 2.0.1), libfontconfig1-dev, pkg-config, libtinfo-dev | libncurses-dev, clang (>= 5.0) | clang-10 | clang-11 | clang-12 | clang-13, llvm-dev (>= 5.0) | llvm-10-dev | llvm-11-dev | llvm-12-dev | llvm-13-dev, lld (>= 5.0) | lld-10 | lld-11 | lld-12 | lld-13, rustc (>= 1.59.0~), libxext-dev, libglib2.0-dev (>= 2.18), libpango1.0-dev (>= 1.14.0), libstartup-notification0-dev, libcurl4-openssl-dev, libiw-dev, mesa-common-dev, libxrender-dev, dbus-x11, xvfb, libx11-dev, libx11-xcb-dev, libfile-fcntllock-perl, apt-utils, locales, autotools-dev, libjpeg-dev, zlib1g-dev, libreadline-dev, dpkg-dev (>= 1.16.1.1~), libevent-dev (>= 1.4.1), libjsoncpp-dev, xfonts-base, xauth, lsb-release, cbindgen (>= 0.24.2), nodejs (>= 10.21) | nodejs-mozilla (>= 10.21), libjack-dev, nasm (>= 2.14) | nasm-mozilla (>= 2.14), libclang-dev (>= 5.0) | libclang-10-dev | libclang-11-dev | libclang-12-dev | libclang-13-dev, libstdc++6 (>= 7.0) | gcc-mozilla (>= 7), bc
+Package-List:
+ waterfox-g deb web optional arch=any
+Files:
+ 0000000000000000000000000000000 1 waterfox-g.orig.tar.xz
+ 0000000000000000000000000000000 1 waterfox-g.debian.tar.xz
+# https://github.com/openSUSE/obs-build/pull/147
+#DEBTRANSFORM-RELEASE: 1
diff --git a/waterfox-g/debian/waterfox-g.install b/waterfox-g/debian/waterfox-g.install
new file mode 100644
index 0000000..1e986bb
--- /dev/null
+++ b/waterfox-g/debian/waterfox-g.install
@@ -0,0 +1,8 @@
+debian/vendor.js usr/lib/waterfox-g/browser/defaults/preferences
+debian/spellcheck.js usr/lib/waterfox-g/browser/defaults/preferences
+debian/waterfox-g.desktop usr/share/applications
+debian/distribution.ini usr/lib/waterfox-g/distribution
+debian/waterfox-g-bin.sh usr/lib/waterfox-g
+debian/usr.bin.waterfox-g etc/apparmor.d
+debian/syspref.js etc/waterfox-g
+debian/bgstack15-librewolf-prefs.js usr/lib/waterfox-g/browser/defaults/preferences
diff --git a/waterfox-g/debian/waterfox-g.links b/waterfox-g/debian/waterfox-g.links
new file mode 100644
index 0000000..b9f562f
--- /dev/null
+++ b/waterfox-g/debian/waterfox-g.links
@@ -0,0 +1,17 @@
+# Icons
+usr/lib/waterfox-g/browser/chrome/icons/default/default16.png usr/share/icons/hicolor/16x16/apps/waterfox-g.png
+usr/lib/waterfox-g/browser/chrome/icons/default/default32.png usr/share/icons/hicolor/32x32/apps/waterfox-g.png
+usr/lib/waterfox-g/browser/chrome/icons/default/default48.png usr/share/icons/hicolor/48x48/apps/waterfox-g.png
+usr/lib/waterfox-g/browser/chrome/icons/default/default64.png usr/share/icons/hicolor/64x64/apps/waterfox-g.png
+usr/lib/waterfox-g/browser/chrome/icons/default/default128.png usr/share/icons/hicolor/128x128/apps/waterfox-g.png
+
+# Wrapper
+usr/lib/waterfox-g/waterfox-g-bin.sh usr/bin/waterfox-g
+
+# Backwards compatibility symlinks
+# TODO: Remove them in November
+usr/bin/waterfox-g usr/bin/waterfox-g3
+usr/bin/waterfox-g usr/bin/waterfox-g4
+
+# System Wide Settings
+etc/waterfox-g/syspref.js usr/lib/waterfox-g/browser/defaults/preferences/syspref.js
diff --git a/waterfox-g/debian/waterfox-g.lintian-overrides b/waterfox-g/debian/waterfox-g.lintian-overrides
new file mode 100644
index 0000000..c4dcb7d
--- /dev/null
+++ b/waterfox-g/debian/waterfox-g.lintian-overrides
@@ -0,0 +1,3 @@
+waterfox-g binary: embedded-library usr/lib/waterfox-g/libxul.so: libjpeg
+waterfox-g binary: embedded-library usr/lib/waterfox-g/libmozsqlite3.so: sqlite
+waterfox-g binary: image-file-in-usr-lib
diff --git a/waterfox-g/debian/waterfox-g.manpages b/waterfox-g/debian/waterfox-g.manpages
new file mode 100644
index 0000000..f34938e
--- /dev/null
+++ b/waterfox-g/debian/waterfox-g.manpages
@@ -0,0 +1 @@
+debian/waterfox-g.1
diff --git a/waterfox-g/debian/waterfox-g.postinst b/waterfox-g/debian/waterfox-g.postinst
new file mode 100755
index 0000000..8c8717e
--- /dev/null
+++ b/waterfox-g/debian/waterfox-g.postinst
@@ -0,0 +1,113 @@
+#!/bin/sh
+
+set -e
+
+MOZ_LIBDIR=/usr/lib/waterfox-g
+MOZ_APP_NAME=waterfox-g
+MOZ_PKG_NAME=waterfox-g
+
+# Move a conffile without triggering a dpkg question
+finish_mv_conffile() {
+ local OLDCONFFILE="$1"
+ local NEWCONFFILE="$2"
+
+ rm -f $OLDCONFFILE.dpkg-remove
+
+ [ -e "$OLDCONFFILE" ] || return 0
+
+ echo "Preserving user changes to $NEWCONFFILE (renamed from $OLDCONFFILE)..."
+ mv -f "$NEWCONFFILE" "$NEWCONFFILE.dpkg-new"
+ mv -f "$OLDCONFFILE" "$NEWCONFFILE"
+}
+
+finish_rm_conffile() {
+ local CONFFILE="$1"
+
+ if [ -e "$CONFFILE.dpkg-backup" ]; then
+ mv -f "$CONFFILE.dpkg-backup" "$CONFFILE.dpkg-bak"
+ fi
+ if [ -e "$CONFFILE.dpkg-remove" ]; then
+ echo "Removing obsolete conffile $CONFFILE ..."
+ rm -f "$CONFFILE.dpkg-remove"
+ fi
+}
+
+if [ "$1" = "configure" ] || [ "$1" = "abort-upgrade" ] || [ "$1" = "abort-remove" ] ; then
+ update-alternatives --install /usr/bin/gnome-www-browser \
+ gnome-www-browser /usr/bin/$MOZ_APP_NAME 40
+
+ update-alternatives --install /usr/bin/x-www-browser \
+ x-www-browser /usr/bin/$MOZ_APP_NAME 40
+fi
+
+if [ "$1" = "configure" ] ; then
+ #
+ # AppArmor
+ #
+ APP_PROFILE="/etc/apparmor.d/usr.bin.$MOZ_PKG_NAME"
+ if [ -f "$APP_PROFILE" ]; then
+ # Setup the extra include files
+ # Add the local/ include
+ LOCAL_APP_PROFILE="/etc/apparmor.d/local/usr.bin.$MOZ_PKG_NAME"
+ test -e "$LOCAL_APP_PROFILE" || {
+ tmp=`mktemp`
+ cat <<EOM > "$tmp"
+# Site-specific additions and overrides for usr.bin.waterfox-g
+# For more details, please see /etc/apparmor.d/local/README.
+EOM
+ mkdir `dirname $LOCAL_APP_PROFILE` 2>/dev/null || true
+ mv -f "$tmp" "$LOCAL_APP_PROFILE"
+ chmod 644 "$LOCAL_APP_PROFILE"
+ }
+
+ # Add the addons include
+ ADDONS_APP_PROFILE="/etc/apparmor.d/abstractions/ubuntu-browsers.d/$MOZ_PKG_NAME"
+ test -e "$ADDONS_APP_PROFILE" || {
+ tmp=`mktemp`
+ cat <<EOM > "$tmp"
+# This file is updated by 'aa-update-browser' and may be overwritten on
+# upgrades.
+#
+# For site-specific adjustments, please see /etc/apparmor.d/local/<binary>
+
+#include <abstractions/ubuntu-browsers.d/plugins-common>
+#include <abstractions/ubuntu-browsers.d/mailto>
+#include <abstractions/ubuntu-browsers.d/multimedia>
+#include <abstractions/ubuntu-browsers.d/productivity>
+#include <abstractions/ubuntu-browsers.d/java>
+#include <abstractions/ubuntu-browsers.d/kde>
+#include <abstractions/ubuntu-browsers.d/text-editors>
+#include <abstractions/ubuntu-browsers.d/ubuntu-integration>
+#include <abstractions/ubuntu-browsers.d/user-files>
+EOM
+ mkdir -p `dirname $ADDONS_APP_PROFILE` 2>/dev/null || true
+ mv -f "$tmp" "$ADDONS_APP_PROFILE"
+ chmod 644 "$ADDONS_APP_PROFILE"
+ }
+
+ # Reload AppArmor profile
+ DISABLE_APP_PROFILE="/etc/apparmor.d/disable/usr.bin.$MOZ_PKG_NAME"
+ if [ ! -f "$DISABLE_APP_PROFILE" ] && aa-status --enabled 2>/dev/null; then
+ apparmor_parser -r -T -W "$APP_PROFILE" || true
+ fi
+ fi
+ #
+ # End AppArmor
+ #
+
+ finish_rm_conffile "/etc/${MOZ_PKG_NAME}/profile/bookmarks.html"
+ finish_rm_conffile "/etc/${MOZ_PKG_NAME}/profile/localstore.rdf"
+ finish_rm_conffile "/etc/${MOZ_PKG_NAME}/profile/mimeTypes.rdf"
+ finish_rm_conffile "/etc/${MOZ_PKG_NAME}/profile/prefs.js"
+ finish_rm_conffile "/etc/${MOZ_PKG_NAME}/profile/chrome/userChrome-example.css"
+ finish_rm_conffile "/etc/${MOZ_PKG_NAME}/profile/chrome/userContent-example.css"
+
+ finish_mv_conffile "/etc/${MOZ_PKG_NAME}/pref/waterfox-g.js" "/etc/${MOZ_PKG_NAME}/syspref.js"
+fi
+
+echo "Please restart all running instances of $MOZ_APP_NAME, or you will experience problems."
+
+if [ -d /var/run ] ; then
+ touch /var/run/$MOZ_APP_NAME-restart-required
+fi
+#DEBHELPER#
diff --git a/waterfox-g/debian/waterfox-g.postrm b/waterfox-g/debian/waterfox-g.postrm
new file mode 100755
index 0000000..c1dce5b
--- /dev/null
+++ b/waterfox-g/debian/waterfox-g.postrm
@@ -0,0 +1,62 @@
+#!/bin/sh
+
+set -e
+
+MOZ_PKG_NAME=waterfox-g
+
+abort_mv_conffile() {
+ local CONFFILE="$1"
+
+ if [ -e "$CONFFILE.dpkg-remove" ]; then
+ echo "Reinstalling $CONFFILE that was moved away"
+ mv "$CONFFILE.dpkg-remove" "$CONFFILE"
+ fi
+}
+
+abort_rm_conffile() {
+ local CONFFILE="$1"
+
+ if [ -e "$CONFFILE.dpkg-remove" ]; then
+ echo "Reinstalling $CONFFILE that was moved away"
+ mv "$CONFFILE.dpkg-remove" "$CONFFILE"
+ fi
+ if [ -e "$CONFFILE.dpkg-backup" ]; then
+ echo "Reinstalling $CONFFILE that was backupped"
+ mv "$CONFFILE.dpkg-backup" "$CONFFILE"
+ fi
+}
+
+purge_conffile() {
+ local CONFFILE="$1"
+
+ rm -f "$CONFFILE.dpkg-bak" "$CONFFILE.dpkg-remove" "$CONFFILE.dpkg-backup" || true
+}
+
+if [ "$1" = "purge" ]; then
+ APP_PROFILE="usr.bin.waterfox-g"
+ rm -f /etc/apparmor.d/force-complain/$APP_PROFILE || true
+ rm -f /etc/apparmor.d/disable/$APP_PROFILE || true
+ rm -f /etc/apparmor.d/local/$APP_PROFILE || true
+ rm -f "/etc/apparmor.d/abstractions/ubuntu-browsers.d/waterfox-g" || true
+ rmdir /etc/apparmor.d/local 2>/dev/null || true
+
+ purge_conffile "/etc/${MOZ_PKG_NAME}/profile/bookmarks.html"
+ purge_conffile "/etc/${MOZ_PKG_NAME}/profile/localstore.rdf"
+ purge_conffile "/etc/${MOZ_PKG_NAME}/profile/mimeTypes.rdf"
+ purge_conffile "/etc/${MOZ_PKG_NAME}/profile/prefs.js"
+ purge_conffile "/etc/${MOZ_PKG_NAME}/profile/chrome/userChrome-example.css"
+ purge_conffile "/etc/${MOZ_PKG_NAME}/profile/chrome/userContent-example.css"
+fi
+
+if [ "$1" = "abort-install" -o "$1" = "abort-upgrade" ] ; then
+ abort_rm_conffile "/etc/${MOZ_PKG_NAME}/profile/bookmarks.html"
+ abort_rm_conffile "/etc/${MOZ_PKG_NAME}/profile/localstore.rdf"
+ abort_rm_conffile "/etc/${MOZ_PKG_NAME}/profile/mimeTypes.rdf"
+ abort_rm_conffile "/etc/${MOZ_PKG_NAME}/profile/prefs.js"
+ abort_rm_conffile "/etc/${MOZ_PKG_NAME}/profile/chrome/userChrome-example.css"
+ abort_rm_conffile "/etc/${MOZ_PKG_NAME}/profile/chrome/userContent-example.css"
+
+ abort_mv_conffile "/etc/${MOZ_PKG_NAME}/pref/waterfox-g.js"
+fi
+
+#DEBHELPER#
diff --git a/waterfox-g/debian/waterfox-g.preinst b/waterfox-g/debian/waterfox-g.preinst
new file mode 100755
index 0000000..77ef682
--- /dev/null
+++ b/waterfox-g/debian/waterfox-g.preinst
@@ -0,0 +1,88 @@
+#!/bin/sh
+
+set -e
+
+APP_DIR="/etc/apparmor.d"
+APP_PROFILE="usr.bin.waterfox-g"
+APP_CONFFILE="$APP_DIR/$APP_PROFILE"
+APP_DISABLE="$APP_DIR/disable/$APP_PROFILE"
+MOZ_PKG_NAME=waterfox-g
+MOZ_LIBDIR=/usr/lib/waterfox-g
+
+# Prepare to move a conffile without triggering a dpkg question
+prepare_mv_conffile() {
+ local PKGNAME="$1"
+ local CONFFILE="$2"
+
+ [ -e "$CONFFILE" ] || return 0
+
+ local md5sum="$(md5sum $CONFFILE | sed -e 's/ .*//')"
+ local old_md5sum="$(dpkg-query -W -f='${Conffiles}' $PKGNAME | \
+ sed -n -e "\' $CONFFILE ' { s/ obsolete$//; s/.* //; p }")"
+ #'
+ if [ "$md5sum" = "$old_md5sum" ]; then
+ mv -f "$CONFFILE" "$CONFFILE.dpkg-remove"
+ fi
+}
+
+prepare_rm_conffile() {
+ local PACKAGE="$1"
+ local CONFFILE="$2"
+
+ [ -e "$CONFFILE" ] || return 0
+
+ local md5sum="$(md5sum $CONFFILE | sed -e 's/ .*//')"
+ local old_md5sum="$(dpkg-query -W -f='${Conffiles}' $PACKAGE | \
+ sed -n -e "\' $CONFFILE ' { s/ obsolete$//; s/.* //; p }")"
+ if [ "$md5sum" != "$old_md5sum" ]; then
+ echo "Obsolete conffile $CONFFILE has been modified by you."
+ echo "Saving as $CONFFILE.dpkg-bak ..."
+ mv -f "$CONFFILE" "$CONFFILE.dpkg-backup"
+ else
+ echo "Moving obsolete conffile $CONFFILE out of the way..."
+ mv -f "$CONFFILE" "$CONFFILE.dpkg-remove"
+ fi
+}
+
+disable_profile() {
+ # Create a symlink to the yet-to-be-unpacked profile
+ if [ ! -e "$APP_CONFFILE" ]; then
+ mkdir -p `dirname $APP_DISABLE` 2>/dev/null || true
+ ln -sf $APP_CONFFILE $APP_DISABLE
+ fi
+}
+
+if [ "$1" = "install" ] || [ "$1" = "upgrade" ] ; then
+ if [ "$1" = "install" ]; then
+ # Disable AppArmor profile on install, unless the last profile they
+ # modified is enabled.
+ base=`echo $APP_PROFILE | cut -d '-' -f 1`
+ last_modified=`ls -rt $APP_DIR/$base* 2>/dev/null | grep -v '\.dpkg' | tail -n1`
+ if [ -s "$last_modified" ]; then
+ if [ -e "$APP_DIR/disable/`basename $last_modified`" ]; then
+ disable_profile
+ fi
+ else
+ # Fresh install and no other waterfox profiles exist, so disable.
+ disable_profile
+ fi
+ elif [ "$1" = "upgrade" ]; then
+ # Disable AppArmor on upgrade from earlier than when we first shipped
+ # the profile if the user does not already have a profile defined.
+ if dpkg --compare-versions "$2" lt "56.2.5-0" ; then
+ disable_profile
+ fi
+ fi
+
+ prepare_rm_conffile "${MOZ_PKG_NAME}" "/etc/${MOZ_PKG_NAME}/profile/bookmarks.html"
+ prepare_rm_conffile "${MOZ_PKG_NAME}" "/etc/${MOZ_PKG_NAME}/profile/localstore.rdf"
+ prepare_rm_conffile "${MOZ_PKG_NAME}" "/etc/${MOZ_PKG_NAME}/profile/mimeTypes.rdf"
+ prepare_rm_conffile "${MOZ_PKG_NAME}" "/etc/${MOZ_PKG_NAME}/profile/prefs.js"
+ prepare_rm_conffile "${MOZ_PKG_NAME}" "/etc/${MOZ_PKG_NAME}/profile/chrome/userChrome-example.css"
+ prepare_rm_conffile "${MOZ_PKG_NAME}" "/etc/${MOZ_PKG_NAME}/profile/chrome/userContent-example.css"
+
+ prepare_mv_conffile "${MOZ_PKG_NAME}" "/etc/${MOZ_PKG_NAME}/pref/waterfox-g.js"
+
+fi
+
+#DEBHELPER#
diff --git a/waterfox-g/debian/waterfox-g.prerm b/waterfox-g/debian/waterfox-g.prerm
new file mode 100755
index 0000000..5358048
--- /dev/null
+++ b/waterfox-g/debian/waterfox-g.prerm
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+set -e
+
+APP_NAME=waterfox-g
+
+if [ "$1" = "remove" ] ; then
+ update-alternatives --remove gnome-www-browser /usr/bin/$APP_NAME
+ update-alternatives --remove x-www-browser /usr/bin/$APP_NAME
+fi
+
+#DEBHELPER#
bgstack15