Compare commits
10 Commits
79f5b2572b
...
abee1a0b69
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
abee1a0b69 | ||
|
|
a135615752 | ||
|
|
8313f8f7f9 | ||
|
|
3630520885 | ||
|
|
6098550a55 | ||
|
|
ab06e84102 | ||
|
|
75b846b90f | ||
|
|
d3245b8e81 | ||
|
|
32da89cbb7 | ||
|
|
ea8e999826 |
@@ -1401,6 +1401,11 @@ BrowserGlue.prototype = {
|
|||||||
lazy.RemoteSecuritySettings.init();
|
lazy.RemoteSecuritySettings.init();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
function RemoteSettingsPollChanges() {
|
||||||
|
// Support clients that use the "sync" event or "remote-settings:changes-poll-end".
|
||||||
|
lazy.RemoteSettings.pollChanges({ trigger: "timer" });
|
||||||
|
},
|
||||||
|
|
||||||
function searchBackgroundChecks() {
|
function searchBackgroundChecks() {
|
||||||
Services.search.runBackgroundChecks();
|
Services.search.runBackgroundChecks();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -176,7 +176,7 @@
|
|||||||
<toolbarbutton type="menu" class="tabbable" wantdropmarker="true"
|
<toolbarbutton type="menu" class="tabbable" wantdropmarker="true"
|
||||||
data-l10n-id="places-organize-button-mac"
|
data-l10n-id="places-organize-button-mac"
|
||||||
#else
|
#else
|
||||||
<menubar id="placesMenu">
|
<menubar id="placesMenu" _moz-menubarkeeplocal="true">
|
||||||
<menu class="menu-iconic" data-l10n-id="places-organize-button"
|
<menu class="menu-iconic" data-l10n-id="places-organize-button"
|
||||||
#endif
|
#endif
|
||||||
id="organizeButton">
|
id="organizeButton">
|
||||||
|
|||||||
@@ -1227,7 +1227,7 @@
|
|||||||
<label><html:h2 id="dohGroupMessage" data-l10n-id="preferences-doh-group-message2"/></label>
|
<label><html:h2 id="dohGroupMessage" data-l10n-id="preferences-doh-group-message2"/></label>
|
||||||
<vbox id="dohCategories">
|
<vbox id="dohCategories">
|
||||||
<radiogroup id="dohCategoryRadioGroup"
|
<radiogroup id="dohCategoryRadioGroup"
|
||||||
preference="network.trr.mode" aria-labelledby="dohGroupMessage">
|
aria-labelledby="dohGroupMessage">
|
||||||
<vbox id="dohOptionDefault" class="privacy-detailedoption info-box-container">
|
<vbox id="dohOptionDefault" class="privacy-detailedoption info-box-container">
|
||||||
<hbox>
|
<hbox>
|
||||||
<radio id="dohDefaultRadio"
|
<radio id="dohDefaultRadio"
|
||||||
@@ -1344,6 +1344,41 @@
|
|||||||
</vbox>
|
</vbox>
|
||||||
</vbox>
|
</vbox>
|
||||||
</vbox>
|
</vbox>
|
||||||
|
<vbox id="dohOptionUltra" class="privacy-detailedoption info-box-container">
|
||||||
|
<hbox>
|
||||||
|
<radio id="dohUltraRadio"
|
||||||
|
value="ultra"
|
||||||
|
data-l10n-id="preferences-doh-setting-ultra"
|
||||||
|
flex="1"/>
|
||||||
|
<button id="dohUltraArrow"
|
||||||
|
is="highlightable-button"
|
||||||
|
class="arrowhead doh-expand-section"
|
||||||
|
data-l10n-id="preferences-doh-expand-section"
|
||||||
|
aria-expanded="false"/>
|
||||||
|
</hbox>
|
||||||
|
<vbox class="indent">
|
||||||
|
<label data-l10n-id="preferences-doh-ultra-desc"></label>
|
||||||
|
<vbox class="extra-information-label">
|
||||||
|
<label data-l10n-id="preferences-doh-ultra-fallback-mode"/>
|
||||||
|
<menulist id="dohUltraFallbackMode"
|
||||||
|
sizetopopup="none">
|
||||||
|
</menulist>
|
||||||
|
</vbox>
|
||||||
|
<vbox class="privacy-extra-information">
|
||||||
|
<vbox class="indent">
|
||||||
|
<hbox class="extra-information-label">
|
||||||
|
<label class="doh-label" data-l10n-id="preferences-doh-ultra-detailed-desc-1"/>
|
||||||
|
</hbox>
|
||||||
|
<hbox class="extra-information-label">
|
||||||
|
<label class="doh-label" data-l10n-id="preferences-doh-ultra-detailed-desc-2"/>
|
||||||
|
</hbox>
|
||||||
|
<hbox class="extra-information-label">
|
||||||
|
<label class="doh-label" data-l10n-id="preferences-doh-ultra-detailed-desc-3"/>
|
||||||
|
</hbox>
|
||||||
|
</vbox>
|
||||||
|
</vbox>
|
||||||
|
</vbox>
|
||||||
|
</vbox>
|
||||||
<vbox id="dohOptionOff" class="privacy-detailedoption info-box-container">
|
<vbox id="dohOptionOff" class="privacy-detailedoption info-box-container">
|
||||||
<hbox>
|
<hbox>
|
||||||
<radio id="dohOffRadio"
|
<radio id="dohOffRadio"
|
||||||
|
|||||||
@@ -269,6 +269,7 @@ Preferences.addAll([
|
|||||||
{ id: "network.trr.uri", type: "string" },
|
{ id: "network.trr.uri", type: "string" },
|
||||||
{ id: "network.trr.default_provider_uri", type: "string" },
|
{ id: "network.trr.default_provider_uri", type: "string" },
|
||||||
{ id: "network.trr.custom_uri", type: "string" },
|
{ id: "network.trr.custom_uri", type: "string" },
|
||||||
|
{ id: "network.trr.use_ohttp", type: "bool" },
|
||||||
{ id: "doh-rollout.disable-heuristics", type: "bool" },
|
{ id: "doh-rollout.disable-heuristics", type: "bool" },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -786,30 +787,68 @@ var gPrivacyPane = {
|
|||||||
let defaultOption = document.getElementById("dohOptionDefault");
|
let defaultOption = document.getElementById("dohOptionDefault");
|
||||||
let enabledOption = document.getElementById("dohOptionEnabled");
|
let enabledOption = document.getElementById("dohOptionEnabled");
|
||||||
let strictOption = document.getElementById("dohOptionStrict");
|
let strictOption = document.getElementById("dohOptionStrict");
|
||||||
|
let ultraOption = document.getElementById("dohOptionUltra");
|
||||||
let offOption = document.getElementById("dohOptionOff");
|
let offOption = document.getElementById("dohOptionOff");
|
||||||
|
|
||||||
|
// Clear all selections
|
||||||
defaultOption.classList.remove("selected");
|
defaultOption.classList.remove("selected");
|
||||||
enabledOption.classList.remove("selected");
|
enabledOption.classList.remove("selected");
|
||||||
strictOption.classList.remove("selected");
|
strictOption.classList.remove("selected");
|
||||||
|
ultraOption.classList.remove("selected");
|
||||||
offOption.classList.remove("selected");
|
offOption.classList.remove("selected");
|
||||||
|
|
||||||
|
// Get the radiogroup and handle selection manually
|
||||||
|
let radioGroup = document.getElementById("dohCategoryRadioGroup");
|
||||||
|
|
||||||
|
// Check if Ultra Protection mode is enabled
|
||||||
|
let ohttpEnabled = Services.prefs.getBoolPref("network.trr.use_ohttp", false);
|
||||||
|
|
||||||
switch (value) {
|
switch (value) {
|
||||||
case Ci.nsIDNSService.MODE_NATIVEONLY:
|
case Ci.nsIDNSService.MODE_NATIVEONLY:
|
||||||
defaultOption.classList.add("selected");
|
defaultOption.classList.add("selected");
|
||||||
|
if (radioGroup) radioGroup.value = "0";
|
||||||
break;
|
break;
|
||||||
case Ci.nsIDNSService.MODE_TRRFIRST:
|
case Ci.nsIDNSService.MODE_TRRFIRST:
|
||||||
enabledOption.classList.add("selected");
|
if (ohttpEnabled) {
|
||||||
|
// Ultra Protection with fallback allowed
|
||||||
|
ultraOption.classList.add("selected");
|
||||||
|
if (radioGroup) radioGroup.value = "ultra";
|
||||||
|
// Update the fallback dropdown state
|
||||||
|
let fallbackDropdown = document.getElementById("dohUltraFallbackMode");
|
||||||
|
if (fallbackDropdown) {
|
||||||
|
fallbackDropdown.disabled = false;
|
||||||
|
fallbackDropdown.value = "fallback";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
enabledOption.classList.add("selected");
|
||||||
|
if (radioGroup) radioGroup.value = "2";
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Ci.nsIDNSService.MODE_TRRONLY:
|
case Ci.nsIDNSService.MODE_TRRONLY:
|
||||||
strictOption.classList.add("selected");
|
// Ultra Protection is strict mode + OHttp enabled, or regular strict mode
|
||||||
|
if (ohttpEnabled) {
|
||||||
|
ultraOption.classList.add("selected");
|
||||||
|
if (radioGroup) radioGroup.value = "ultra";
|
||||||
|
// Update the fallback dropdown state
|
||||||
|
let fallbackDropdown = document.getElementById("dohUltraFallbackMode");
|
||||||
|
if (fallbackDropdown) {
|
||||||
|
fallbackDropdown.disabled = false;
|
||||||
|
fallbackDropdown.value = "no-fallback";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
strictOption.classList.add("selected");
|
||||||
|
if (radioGroup) radioGroup.value = "3";
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Ci.nsIDNSService.MODE_TRROFF:
|
case Ci.nsIDNSService.MODE_TRROFF:
|
||||||
offOption.classList.add("selected");
|
offOption.classList.add("selected");
|
||||||
|
if (radioGroup) radioGroup.value = "5";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// The pref is set to a random value.
|
// The pref is set to a random value.
|
||||||
// This shouldn't happen, but let's make sure off is selected.
|
// This shouldn't happen, but let's make sure off is selected.
|
||||||
offOption.classList.add("selected");
|
offOption.classList.add("selected");
|
||||||
document.getElementById("dohCategoryRadioGroup").selectedIndex = 3;
|
if (radioGroup) radioGroup.value = "5";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -836,6 +875,26 @@ var gPrivacyPane = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle Ultra Protection mode (OHttp enabled with either TRR First or TRR Only)
|
||||||
|
if (ohttpEnabled && (value == Ci.nsIDNSService.MODE_TRRFIRST || value == Ci.nsIDNSService.MODE_TRRONLY)) {
|
||||||
|
// Ultra Protection mode - ensure OHttp is enabled
|
||||||
|
Services.prefs.setBoolPref("network.trr.use_ohttp", true);
|
||||||
|
// Enable the fallback dropdown
|
||||||
|
let fallbackDropdown = document.getElementById("dohUltraFallbackMode");
|
||||||
|
if (fallbackDropdown) {
|
||||||
|
fallbackDropdown.disabled = false;
|
||||||
|
}
|
||||||
|
} else if (!ohttpEnabled) {
|
||||||
|
// Not Ultra Protection - ensure OHttp is disabled
|
||||||
|
Services.prefs.setBoolPref("network.trr.use_ohttp", false);
|
||||||
|
// Disable the fallback dropdown when not in Ultra mode
|
||||||
|
let fallbackDropdown = document.getElementById("dohUltraFallbackMode");
|
||||||
|
if (fallbackDropdown) {
|
||||||
|
fallbackDropdown.disabled = true;
|
||||||
|
fallbackDropdown.value = "fallback";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Bug 1900672
|
// Bug 1900672
|
||||||
// When the mode is set to 5, clear the pref to ensure that
|
// When the mode is set to 5, clear the pref to ensure that
|
||||||
// network.trr.uri is set to fallbackProviderURIwhen the mode is set to 2 or 3 afterwards
|
// network.trr.uri is set to fallbackProviderURIwhen the mode is set to 2 or 3 afterwards
|
||||||
@@ -853,6 +912,7 @@ var gPrivacyPane = {
|
|||||||
setEventListener("dohDefaultArrow", "command", this.toggleExpansion);
|
setEventListener("dohDefaultArrow", "command", this.toggleExpansion);
|
||||||
setEventListener("dohEnabledArrow", "command", this.toggleExpansion);
|
setEventListener("dohEnabledArrow", "command", this.toggleExpansion);
|
||||||
setEventListener("dohStrictArrow", "command", this.toggleExpansion);
|
setEventListener("dohStrictArrow", "command", this.toggleExpansion);
|
||||||
|
setEventListener("dohUltraArrow", "command", this.toggleExpansion);
|
||||||
|
|
||||||
function modeButtonPressed(e) {
|
function modeButtonPressed(e) {
|
||||||
// Clicking the active mode again should not generate another event
|
// Clicking the active mode again should not generate another event
|
||||||
@@ -866,13 +926,53 @@ var gPrivacyPane = {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setEventListener("dohDefaultRadio", "command", modeButtonPressed);
|
setEventListener("dohDefaultRadio", "command", (e) => {
|
||||||
setEventListener("dohEnabledRadio", "command", modeButtonPressed);
|
// Default Protection: Use default mode and disable OHttp
|
||||||
setEventListener("dohStrictRadio", "command", modeButtonPressed);
|
Services.prefs.setIntPref("network.trr.mode", Ci.nsIDNSService.MODE_NATIVEONLY);
|
||||||
setEventListener("dohOffRadio", "command", modeButtonPressed);
|
Services.prefs.setBoolPref("network.trr.use_ohttp", false);
|
||||||
|
modeButtonPressed(e);
|
||||||
|
});
|
||||||
|
setEventListener("dohEnabledRadio", "command", (e) => {
|
||||||
|
// Enabled Protection: Use enabled mode and disable OHttp
|
||||||
|
Services.prefs.setIntPref("network.trr.mode", Ci.nsIDNSService.MODE_TRRFIRST);
|
||||||
|
Services.prefs.setBoolPref("network.trr.use_ohttp", false);
|
||||||
|
modeButtonPressed(e);
|
||||||
|
});
|
||||||
|
setEventListener("dohStrictRadio", "command", (e) => {
|
||||||
|
// Strict Protection: Set TRR to strict mode but disable OHttp
|
||||||
|
Services.prefs.setIntPref("network.trr.mode", Ci.nsIDNSService.MODE_TRRONLY);
|
||||||
|
Services.prefs.setBoolPref("network.trr.use_ohttp", false);
|
||||||
|
modeButtonPressed(e);
|
||||||
|
});
|
||||||
|
setEventListener("dohUltraRadio", "command", (e) => {
|
||||||
|
// Ultra Protection: Enable OHttp and set default mode
|
||||||
|
Services.prefs.setBoolPref("network.trr.use_ohttp", true);
|
||||||
|
let fallbackDropdown = document.getElementById("dohUltraFallbackMode");
|
||||||
|
if (fallbackDropdown) {
|
||||||
|
// Default to fallback allowed unless user previously selected no-fallback
|
||||||
|
if (fallbackDropdown.value === "no-fallback") {
|
||||||
|
Services.prefs.setIntPref("network.trr.mode", Ci.nsIDNSService.MODE_TRRONLY);
|
||||||
|
} else {
|
||||||
|
fallbackDropdown.value = "fallback";
|
||||||
|
Services.prefs.setIntPref("network.trr.mode", Ci.nsIDNSService.MODE_TRRFIRST);
|
||||||
|
}
|
||||||
|
fallbackDropdown.disabled = false;
|
||||||
|
} else {
|
||||||
|
// Fallback if dropdown not found
|
||||||
|
Services.prefs.setIntPref("network.trr.mode", Ci.nsIDNSService.MODE_TRRFIRST);
|
||||||
|
}
|
||||||
|
modeButtonPressed(e);
|
||||||
|
});
|
||||||
|
setEventListener("dohOffRadio", "command", (e) => {
|
||||||
|
// Off Protection: Set TRR to off and disable OHttp
|
||||||
|
Services.prefs.setIntPref("network.trr.mode", Ci.nsIDNSService.MODE_TRROFF);
|
||||||
|
Services.prefs.setBoolPref("network.trr.use_ohttp", false);
|
||||||
|
modeButtonPressed(e);
|
||||||
|
});
|
||||||
|
|
||||||
this.populateDoHResolverList("dohEnabled");
|
this.populateDoHResolverList("dohEnabled");
|
||||||
this.populateDoHResolverList("dohStrict");
|
this.populateDoHResolverList("dohStrict");
|
||||||
|
this.populateUltraFallbackDropdown();
|
||||||
|
|
||||||
Preferences.get("network.trr.uri").on("change", () => {
|
Preferences.get("network.trr.uri").on("change", () => {
|
||||||
gPrivacyPane.updateDoHResolverList("dohEnabled");
|
gPrivacyPane.updateDoHResolverList("dohEnabled");
|
||||||
@@ -885,7 +985,19 @@ var gPrivacyPane = {
|
|||||||
"change",
|
"change",
|
||||||
gPrivacyPane.highlightDoHCategoryAndUpdateStatus
|
gPrivacyPane.highlightDoHCategoryAndUpdateStatus
|
||||||
);
|
);
|
||||||
|
// Also listen for OHttp pref changes
|
||||||
|
Preferences.get("network.trr.use_ohttp").on(
|
||||||
|
"change",
|
||||||
|
gPrivacyPane.highlightDoHCategoryAndUpdateStatus
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
this.highlightDoHCategoryAndUpdateStatus();
|
this.highlightDoHCategoryAndUpdateStatus();
|
||||||
|
|
||||||
|
// Initialize Ultra Protection checkbox state
|
||||||
|
this.initUltraProtectionState();
|
||||||
|
|
||||||
Services.obs.addObserver(this, "network:trr-uri-changed");
|
Services.obs.addObserver(this, "network:trr-uri-changed");
|
||||||
Services.obs.addObserver(this, "network:trr-mode-changed");
|
Services.obs.addObserver(this, "network:trr-mode-changed");
|
||||||
@@ -913,6 +1025,54 @@ var gPrivacyPane = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
populateUltraFallbackDropdown() {
|
||||||
|
let menu = document.getElementById("dohUltraFallbackMode");
|
||||||
|
if (!menu) return;
|
||||||
|
|
||||||
|
// populate the fallback options like provider dropdowns do
|
||||||
|
menu.removeAllItems();
|
||||||
|
|
||||||
|
let fallbackItem = menu.appendItem("Allow fallback to system DNS if secure DNS fails", "fallback");
|
||||||
|
let noFallbackItem = menu.appendItem("Never fall back to system DNS (sites may not load if secure DNS fails)", "no-fallback");
|
||||||
|
|
||||||
|
// Set initial selection
|
||||||
|
menu.value = "fallback";
|
||||||
|
|
||||||
|
// Add event listener
|
||||||
|
menu.addEventListener("command", (e) => {
|
||||||
|
let ohttpEnabled = Services.prefs.getBoolPref("network.trr.use_ohttp", false);
|
||||||
|
let trrMode = Services.prefs.getIntPref("network.trr.mode", 0);
|
||||||
|
if (ohttpEnabled && (trrMode == Ci.nsIDNSService.MODE_TRRFIRST || trrMode == Ci.nsIDNSService.MODE_TRRONLY)) {
|
||||||
|
if (e.target.value === "no-fallback") {
|
||||||
|
// No fallback - switch to TRR Only mode
|
||||||
|
Services.prefs.setIntPref("network.trr.mode", Ci.nsIDNSService.MODE_TRRONLY);
|
||||||
|
} else {
|
||||||
|
// Allow fallback - switch to TRR First mode
|
||||||
|
Services.prefs.setIntPref("network.trr.mode", Ci.nsIDNSService.MODE_TRRFIRST);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
initUltraProtectionState() {
|
||||||
|
// Initialize the Ultra Protection fallback dropdown state
|
||||||
|
let ohttpEnabled = Services.prefs.getBoolPref("network.trr.use_ohttp", false);
|
||||||
|
let trrMode = Services.prefs.getIntPref("network.trr.mode", 0);
|
||||||
|
let fallbackDropdown = document.getElementById("dohUltraFallbackMode");
|
||||||
|
|
||||||
|
if (fallbackDropdown) {
|
||||||
|
if (ohttpEnabled && (trrMode == Ci.nsIDNSService.MODE_TRRFIRST || trrMode == Ci.nsIDNSService.MODE_TRRONLY)) {
|
||||||
|
// Ultra Protection is active
|
||||||
|
fallbackDropdown.disabled = false;
|
||||||
|
fallbackDropdown.value = (trrMode == Ci.nsIDNSService.MODE_TRRONLY) ? "no-fallback" : "fallback";
|
||||||
|
} else {
|
||||||
|
// Ultra Protection is not active
|
||||||
|
fallbackDropdown.disabled = true;
|
||||||
|
fallbackDropdown.value = "fallback";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
initWebAuthn() {
|
initWebAuthn() {
|
||||||
document.getElementById("openWindowsPasskeySettings").hidden =
|
document.getElementById("openWindowsPasskeySettings").hidden =
|
||||||
!Services.prefs.getBoolPref(
|
!Services.prefs.getBoolPref(
|
||||||
|
|||||||
@@ -129,19 +129,22 @@ export class WallpaperFeed {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// retrieving all records in collection
|
// retrieving all records in collection
|
||||||
const records = await this.wallpaperClient.get();
|
let records = [];
|
||||||
if (!records?.length) {
|
let baseAttachmentURL = "";
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const customWallpaperEnabled = Services.prefs.getBoolPref(
|
const customWallpaperEnabled = Services.prefs.getBoolPref(
|
||||||
PREF_WALLPAPERS_CUSTOM_WALLPAPER_ENABLED
|
PREF_WALLPAPERS_CUSTOM_WALLPAPER_ENABLED
|
||||||
);
|
);
|
||||||
|
|
||||||
const baseAttachmentURL = await lazy.Utils.baseAttachmentsURL();
|
const customColorEnabled = Services.prefs.getBoolPref(
|
||||||
|
"browser.newtabpage.activity-stream.newtabWallpapers.customColor.enabled"
|
||||||
|
);
|
||||||
|
|
||||||
const wallpapers = [
|
let wallpapers = [];
|
||||||
...records.map(record => {
|
|
||||||
|
// Process records if we have any
|
||||||
|
if (records && records.length > 0 && baseAttachmentURL) {
|
||||||
|
wallpapers = records.map(record => {
|
||||||
return {
|
return {
|
||||||
...record,
|
...record,
|
||||||
...(record.attachment
|
...(record.attachment
|
||||||
@@ -151,16 +154,42 @@ export class WallpaperFeed {
|
|||||||
: {}),
|
: {}),
|
||||||
category: record.category || "",
|
category: record.category || "",
|
||||||
};
|
};
|
||||||
}),
|
});
|
||||||
];
|
}
|
||||||
|
|
||||||
const categories = [
|
// Build categories
|
||||||
|
let categories = [
|
||||||
...new Set(
|
...new Set(
|
||||||
wallpapers.map(wallpaper => wallpaper.category).filter(Boolean)
|
wallpapers.map(wallpaper => wallpaper.category).filter(Boolean)
|
||||||
),
|
),
|
||||||
...(customWallpaperEnabled ? ["custom-wallpaper"] : []), // Conditionally add custom wallpaper input
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Always add solid-colors if custom color is enabled and not already present
|
||||||
|
if (customColorEnabled && !categories.includes("solid-colors")) {
|
||||||
|
categories.push("solid-colors");
|
||||||
|
|
||||||
|
// Add default solid colors if we have no wallpapers
|
||||||
|
if (wallpapers.length === 0) {
|
||||||
|
wallpapers = [
|
||||||
|
{ title: "red-30", solid_color: "#ff7e8e", category: "solid-colors" },
|
||||||
|
{ title: "orange-30", solid_color: "#ff8f31", category: "solid-colors" },
|
||||||
|
{ title: "yellow-30", solid_color: "#f1af00", category: "solid-colors" },
|
||||||
|
{ title: "green-30", solid_color: "#61cc69", category: "solid-colors" },
|
||||||
|
{ title: "cyan-30", solid_color: "#00cadb", category: "solid-colors" },
|
||||||
|
{ title: "blue-30", solid_color: "#60adff", category: "solid-colors" },
|
||||||
|
{ title: "violet-30", solid_color: "#b295ff", category: "solid-colors"},
|
||||||
|
{ title: "purple-30", solid_color: "#d98dfa", category: "solid-colors"},
|
||||||
|
{ title: "pink-30", solid_color: "#ff7ead", category: "solid-colors"}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always add custom-wallpaper if enabled
|
||||||
|
if (customWallpaperEnabled) {
|
||||||
|
categories.push("custom-wallpaper");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dispatch even with empty wallpapers as long as we have categories
|
||||||
this.store.dispatch(
|
this.store.dispatch(
|
||||||
ac.BroadcastToContent({
|
ac.BroadcastToContent({
|
||||||
type: at.WALLPAPERS_SET,
|
type: at.WALLPAPERS_SET,
|
||||||
|
|||||||
@@ -22,9 +22,9 @@
|
|||||||
#include "nsString.h"
|
#include "nsString.h"
|
||||||
#include "nsStringFwd.h"
|
#include "nsStringFwd.h"
|
||||||
|
|
||||||
#ifndef EARLY_BETA_OR_EARLIER
|
// #ifndef EARLY_BETA_OR_EARLIER
|
||||||
# include "mozilla/dom/WorkerPrivate.h"
|
// # include "mozilla/dom/WorkerPrivate.h"
|
||||||
#endif
|
// #endif
|
||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
@@ -113,22 +113,22 @@ already_AddRefed<dom::Promise> Instance::RequestAdapter(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifndef EARLY_BETA_OR_EARLIER
|
// #ifndef EARLY_BETA_OR_EARLIER
|
||||||
rejectIf(true, "WebGPU is not yet available in Release or late Beta builds.");
|
// rejectIf(true, "WebGPU is not yet available in Release or late Beta builds.");
|
||||||
|
|
||||||
// NOTE: Deliberately left after the above check so that we only enter
|
// // NOTE: Deliberately left after the above check so that we only enter
|
||||||
// here if it's removed. Above is a more informative diagnostic, while the
|
// // here if it's removed. Above is a more informative diagnostic, while the
|
||||||
// check is still present.
|
// // check is still present.
|
||||||
//
|
// //
|
||||||
// Follow-up to remove this check:
|
// // Follow-up to remove this check:
|
||||||
// <https://bugzilla.mozilla.org/show_bug.cgi?id=1942431>
|
// // <https://bugzilla.mozilla.org/show_bug.cgi?id=1942431>
|
||||||
if (dom::WorkerPrivate* wp = dom::GetCurrentThreadWorkerPrivate()) {
|
// if (dom::WorkerPrivate* wp = dom::GetCurrentThreadWorkerPrivate()) {
|
||||||
rejectIf(wp->IsServiceWorker(),
|
// rejectIf(wp->IsServiceWorker(),
|
||||||
"WebGPU in service workers is not yet available in Release or "
|
// "WebGPU in service workers is not yet available in Release or "
|
||||||
"late Beta builds; see "
|
// "late Beta builds; see "
|
||||||
"<https://bugzilla.mozilla.org/show_bug.cgi?id=1942431>.");
|
// "<https://bugzilla.mozilla.org/show_bug.cgi?id=1942431>.");
|
||||||
}
|
// }
|
||||||
#endif
|
// #endif
|
||||||
rejectIf(!gfx::gfxVars::AllowWebGPU(), "WebGPU is disabled by blocklist.");
|
rejectIf(!gfx::gfxVars::AllowWebGPU(), "WebGPU is disabled by blocklist.");
|
||||||
rejectIf(!StaticPrefs::dom_webgpu_enabled(),
|
rejectIf(!StaticPrefs::dom_webgpu_enabled(),
|
||||||
"WebGPU is disabled because the `dom.webgpu.enabled` pref. is set "
|
"WebGPU is disabled because the `dom.webgpu.enabled` pref. is set "
|
||||||
|
|||||||
@@ -238,6 +238,10 @@ void XULPopupElement::GetState(nsString& aState) {
|
|||||||
// set this here in case there's no frame for the popup
|
// set this here in case there's no frame for the popup
|
||||||
aState.AssignLiteral("closed");
|
aState.AssignLiteral("closed");
|
||||||
|
|
||||||
|
#ifdef MOZ_WIDGET_GTK
|
||||||
|
nsAutoString nativeState;
|
||||||
|
#endif
|
||||||
|
|
||||||
if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) {
|
if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) {
|
||||||
switch (pm->GetPopupState(this)) {
|
switch (pm->GetPopupState(this)) {
|
||||||
case ePopupShown:
|
case ePopupShown:
|
||||||
@@ -260,6 +264,11 @@ void XULPopupElement::GetState(nsString& aState) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#ifdef MOZ_WIDGET_GTK
|
||||||
|
else if (GetAttr(kNameSpaceID_None, nsGkAtoms::_moz_nativemenupopupstate, nativeState)) {
|
||||||
|
aState = nativeState;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
nsINode* XULPopupElement::GetTriggerNode() const {
|
nsINode* XULPopupElement::GetTriggerNode() const {
|
||||||
|
|||||||
@@ -91,4 +91,9 @@ LOCAL_INCLUDES += [
|
|||||||
|
|
||||||
include("/ipc/chromium/chromium-config.mozbuild")
|
include("/ipc/chromium/chromium-config.mozbuild")
|
||||||
|
|
||||||
|
if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
|
||||||
|
LOCAL_INCLUDES += [
|
||||||
|
"/widget/gtk",
|
||||||
|
]
|
||||||
|
|
||||||
FINAL_LIBRARY = "xul"
|
FINAL_LIBRARY = "xul"
|
||||||
|
|||||||
@@ -3223,13 +3223,13 @@ void gfxPlatform::InitWebGPUConfig() {
|
|||||||
|
|
||||||
// When this condition changes, be sure to update the `run-if`
|
// When this condition changes, be sure to update the `run-if`
|
||||||
// conditions in `dom/webgpu/tests/mochitest/*.toml` accordingly.
|
// conditions in `dom/webgpu/tests/mochitest/*.toml` accordingly.
|
||||||
#if !(defined(NIGHTLY_BUILD) || \
|
// #if !(defined(NIGHTLY_BUILD) || \
|
||||||
(defined(XP_WIN) && defined(EARLY_BETA_OR_EARLIER)))
|
// (defined(XP_WIN) && defined(EARLY_BETA_OR_EARLIER)))
|
||||||
feature.ForceDisable(
|
// feature.ForceDisable(
|
||||||
FeatureStatus::Blocked,
|
// FeatureStatus::Blocked,
|
||||||
"WebGPU cannot be enabled unless in Nightly, or Early Beta on Windows.",
|
// "WebGPU cannot be enabled unless in Nightly, or Early Beta on Windows.",
|
||||||
"WEBGPU_DISABLE_RELEASE_OR_NON_WINDOWS_EARLY_BETA"_ns);
|
// "WEBGPU_DISABLE_RELEASE_OR_NON_WINDOWS_EARLY_BETA"_ns);
|
||||||
#endif
|
// #endif
|
||||||
|
|
||||||
gfxVars::SetAllowWebGPU(feature.IsEnabled());
|
gfxVars::SetAllowWebGPU(feature.IsEnabled());
|
||||||
|
|
||||||
|
|||||||
@@ -68,6 +68,10 @@ elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "android":
|
|||||||
"/dom/system",
|
"/dom/system",
|
||||||
"/dom/system/android",
|
"/dom/system/android",
|
||||||
]
|
]
|
||||||
|
elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
|
||||||
|
LOCAL_INCLUDES += [
|
||||||
|
"/widget/gtk",
|
||||||
|
]
|
||||||
|
|
||||||
XPCOM_MANIFESTS += [
|
XPCOM_MANIFESTS += [
|
||||||
"components.conf",
|
"components.conf",
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ ChromeUtils.defineESModuleGetters(this, {
|
|||||||
InitializationTracker: "resource://gre/modules/GeckoViewTelemetry.sys.mjs",
|
InitializationTracker: "resource://gre/modules/GeckoViewTelemetry.sys.mjs",
|
||||||
RemoteSecuritySettings:
|
RemoteSecuritySettings:
|
||||||
"resource://gre/modules/psm/RemoteSecuritySettings.sys.mjs",
|
"resource://gre/modules/psm/RemoteSecuritySettings.sys.mjs",
|
||||||
|
RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
|
||||||
SafeBrowsing: "resource://gre/modules/SafeBrowsing.sys.mjs",
|
SafeBrowsing: "resource://gre/modules/SafeBrowsing.sys.mjs",
|
||||||
CaptchaDetectionPingUtils:
|
CaptchaDetectionPingUtils:
|
||||||
"resource://gre/modules/CaptchaDetectionPingUtils.sys.mjs",
|
"resource://gre/modules/CaptchaDetectionPingUtils.sys.mjs",
|
||||||
@@ -928,6 +929,8 @@ function startup() {
|
|||||||
// submits the ping if it has data and has been about 24 hours since the
|
// submits the ping if it has data and has been about 24 hours since the
|
||||||
// last submission.
|
// last submission.
|
||||||
CaptchaDetectionPingUtils.init();
|
CaptchaDetectionPingUtils.init();
|
||||||
|
|
||||||
|
RemoteSettings.pollChanges({ trigger: "timer" });
|
||||||
});
|
});
|
||||||
|
|
||||||
// This should always go last, since the idle tasks (except for the ones with
|
// This should always go last, since the idle tasks (except for the ones with
|
||||||
|
|||||||
@@ -157,6 +157,9 @@ pref("dom.text-recognition.enabled", true);
|
|||||||
// Fastback caching - if this pref is negative, then we calculate the number
|
// Fastback caching - if this pref is negative, then we calculate the number
|
||||||
// of content viewers to cache based on the amount of available memory.
|
// of content viewers to cache based on the amount of available memory.
|
||||||
pref("browser.sessionhistory.max_total_viewers", -1);
|
pref("browser.sessionhistory.max_total_viewers", -1);
|
||||||
|
#ifdef MOZ_WIDGET_GTK
|
||||||
|
pref("ui.use_unity_menubar", true);
|
||||||
|
#endif
|
||||||
|
|
||||||
// See http://whatwg.org/specs/web-apps/current-work/#ping
|
// See http://whatwg.org/specs/web-apps/current-work/#ping
|
||||||
pref("browser.send_pings", false);
|
pref("browser.send_pings", false);
|
||||||
|
|||||||
@@ -292,14 +292,14 @@ class DecoratorVisitor(ast.NodeVisitor):
|
|||||||
kwarg_dict = {}
|
kwarg_dict = {}
|
||||||
|
|
||||||
for name, arg in zip(["command", "subcommand"], decorator.args):
|
for name, arg in zip(["command", "subcommand"], decorator.args):
|
||||||
kwarg_dict[name] = arg.s
|
kwarg_dict[name] = arg.value
|
||||||
|
|
||||||
for keyword in decorator.keywords:
|
for keyword in decorator.keywords:
|
||||||
if keyword.arg not in relevant_kwargs:
|
if keyword.arg not in relevant_kwargs:
|
||||||
# We only care about these 3 kwargs, so we can safely skip the rest
|
# We only care about these 3 kwargs, so we can safely skip the rest
|
||||||
continue
|
continue
|
||||||
|
|
||||||
kwarg_dict[keyword.arg] = getattr(keyword.value, "s", "")
|
kwarg_dict[keyword.arg] = keyword.value.value
|
||||||
|
|
||||||
command = kwarg_dict.pop("command")
|
command = kwarg_dict.pop("command")
|
||||||
self.results.setdefault(command, {})
|
self.results.setdefault(command, {})
|
||||||
|
|||||||
@@ -289,16 +289,14 @@ class CommandAction(argparse.Action):
|
|||||||
group = parser.add_argument_group(title, description)
|
group = parser.add_argument_group(title, description)
|
||||||
|
|
||||||
description = handler.description
|
description = handler.description
|
||||||
group.add_argument(command, help=description, action="store_true")
|
group.add_argument(command, help=description)
|
||||||
|
|
||||||
if disabled_commands and "disabled" in r.categories:
|
if disabled_commands and "disabled" in r.categories:
|
||||||
title, description, _priority = r.categories["disabled"]
|
title, description, _priority = r.categories["disabled"]
|
||||||
group = parser.add_argument_group(title, description)
|
group = parser.add_argument_group(title, description)
|
||||||
if verbose:
|
if verbose:
|
||||||
for c in disabled_commands:
|
for c in disabled_commands:
|
||||||
group.add_argument(
|
group.add_argument(c["command"], help=c["description"])
|
||||||
c["command"], help=c["description"], action="store_true"
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.print_help()
|
parser.print_help()
|
||||||
|
|
||||||
@@ -405,9 +403,7 @@ class CommandAction(argparse.Action):
|
|||||||
subhandlers,
|
subhandlers,
|
||||||
key=by_decl_order if handler.order == "declaration" else by_name,
|
key=by_decl_order if handler.order == "declaration" else by_name,
|
||||||
):
|
):
|
||||||
group.add_argument(
|
group.add_argument(subcommand, help=subhandler.description)
|
||||||
subcommand, help=subhandler.description, action="store_true"
|
|
||||||
)
|
|
||||||
|
|
||||||
if handler.docstring:
|
if handler.docstring:
|
||||||
parser.description = format_docstring(handler.docstring)
|
parser.description = format_docstring(handler.docstring)
|
||||||
|
|||||||
@@ -470,7 +470,7 @@ class TemplateFunction:
|
|||||||
return c(
|
return c(
|
||||||
ast.Subscript(
|
ast.Subscript(
|
||||||
value=c(ast.Name(id=self._global_name, ctx=ast.Load())),
|
value=c(ast.Name(id=self._global_name, ctx=ast.Load())),
|
||||||
slice=c(ast.Index(value=c(ast.Str(s=node.id)))),
|
slice=c(ast.Index(value=c(ast.Constant(value=node.id)))),
|
||||||
ctx=node.ctx,
|
ctx=node.ctx,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -1039,8 +1039,8 @@ class BuildReader:
|
|||||||
else:
|
else:
|
||||||
# Others
|
# Others
|
||||||
assert isinstance(target.slice, ast.Index)
|
assert isinstance(target.slice, ast.Index)
|
||||||
assert isinstance(target.slice.value, ast.Str)
|
assert isinstance(target.slice.value, ast.Constant)
|
||||||
key = target.slice.value.s
|
key = target.slice.value.value
|
||||||
elif isinstance(target, ast.Attribute):
|
elif isinstance(target, ast.Attribute):
|
||||||
assert isinstance(target.attr, str)
|
assert isinstance(target.attr, str)
|
||||||
key = target.attr
|
key = target.attr
|
||||||
@@ -1051,11 +1051,11 @@ class BuildReader:
|
|||||||
value = node.value
|
value = node.value
|
||||||
if isinstance(value, ast.List):
|
if isinstance(value, ast.List):
|
||||||
for v in value.elts:
|
for v in value.elts:
|
||||||
assert isinstance(v, ast.Str)
|
assert isinstance(v, ast.Constant)
|
||||||
yield v.s
|
yield v.value
|
||||||
else:
|
else:
|
||||||
assert isinstance(value, ast.Str)
|
assert isinstance(value, ast.Constant)
|
||||||
yield value.s
|
yield value.value
|
||||||
|
|
||||||
assignments = []
|
assignments = []
|
||||||
|
|
||||||
|
|||||||
@@ -327,15 +327,13 @@ def assignment_node_to_source_filename_list(code, node):
|
|||||||
"""
|
"""
|
||||||
if isinstance(node.value, ast.List) and "elts" in node.value._fields:
|
if isinstance(node.value, ast.List) and "elts" in node.value._fields:
|
||||||
for f in node.value.elts:
|
for f in node.value.elts:
|
||||||
if not isinstance(f, ast.Constant) and not isinstance(f, ast.Str):
|
if not isinstance(f, ast.Constant):
|
||||||
log(
|
log(
|
||||||
"Found non-constant source file name in list: ",
|
"Found non-constant source file name in list: ",
|
||||||
ast_get_source_segment(code, f),
|
ast_get_source_segment(code, f),
|
||||||
)
|
)
|
||||||
return []
|
return []
|
||||||
return [
|
return [f.value for f in node.value.elts]
|
||||||
f.value if isinstance(f, ast.Constant) else f.s for f in node.value.elts
|
|
||||||
]
|
|
||||||
elif isinstance(node.value, ast.ListComp):
|
elif isinstance(node.value, ast.ListComp):
|
||||||
# SOURCES += [f for f in foo if blah]
|
# SOURCES += [f for f in foo if blah]
|
||||||
log("Could not find the files for " + ast_get_source_segment(code, node.value))
|
log("Could not find the files for " + ast_get_source_segment(code, node.value))
|
||||||
|
|||||||
@@ -40,6 +40,10 @@ EXCLUDED_PACKAGES = {
|
|||||||
# modified 'dummy' version of it so that the dependency checks still succeed, but
|
# modified 'dummy' version of it so that the dependency checks still succeed, but
|
||||||
# if it ever is attempted to be used, it will fail gracefully.
|
# if it ever is attempted to be used, it will fail gracefully.
|
||||||
"ansicon",
|
"ansicon",
|
||||||
|
# jsonschema 4.17.3 is incompatible with Python 3.14+,
|
||||||
|
# but later versions use a dependency with Rust components, which we thus can't vendor.
|
||||||
|
# For now we apply the minimal patch to jsonschema to make it work again.
|
||||||
|
"jsonschema",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -346,7 +346,7 @@ class IntermediatePreloads {
|
|||||||
lazy.log.debug(
|
lazy.log.debug(
|
||||||
`There are ${waiting.length} intermediates awaiting download.`
|
`There are ${waiting.length} intermediates awaiting download.`
|
||||||
);
|
);
|
||||||
if (!waiting.length) {
|
if (!waiting.length || !Services.prefs.getBoolPref(INTERMEDIATES_ENABLED_PREF, true) || !this.client) {
|
||||||
// Nothing to do.
|
// Nothing to do.
|
||||||
Services.obs.notifyObservers(
|
Services.obs.notifyObservers(
|
||||||
null,
|
null,
|
||||||
@@ -441,6 +441,11 @@ class IntermediatePreloads {
|
|||||||
async maybeDownloadAttachment(record) {
|
async maybeDownloadAttachment(record) {
|
||||||
let result = { record, cert: null, subject: null };
|
let result = { record, cert: null, subject: null };
|
||||||
|
|
||||||
|
// Early return if intermediates are disabled or client doesn't exist
|
||||||
|
if (!Services.prefs.getBoolPref(INTERMEDIATES_ENABLED_PREF, true) || !this.client) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
let dataAsString = null;
|
let dataAsString = null;
|
||||||
try {
|
try {
|
||||||
let { buffer } = await this.client.attachments.download(record, {
|
let { buffer } = await this.client.attachments.download(record, {
|
||||||
@@ -642,6 +647,10 @@ class CRLiteFilters {
|
|||||||
lazy.log.debug("filtersToDownload:", filtersToDownload);
|
lazy.log.debug("filtersToDownload:", filtersToDownload);
|
||||||
let filtersDownloaded = [];
|
let filtersDownloaded = [];
|
||||||
for (let filter of filtersToDownload) {
|
for (let filter of filtersToDownload) {
|
||||||
|
// Skip download if CRLite filters are disabled or client doesn't exist
|
||||||
|
if (!Services.prefs.getBoolPref(CRLITE_FILTERS_ENABLED_PREF, true) || !this.client) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
let attachment = await this.client.attachments.downloadAsBytes(filter);
|
let attachment = await this.client.attachments.downloadAsBytes(filter);
|
||||||
let bytes = new Uint8Array(attachment);
|
let bytes = new Uint8Array(attachment);
|
||||||
|
|||||||
@@ -338,6 +338,30 @@ export class Downloader {
|
|||||||
fallbackToDump = false,
|
fallbackToDump = false,
|
||||||
avoidDownload = false,
|
avoidDownload = false,
|
||||||
} = options || {};
|
} = options || {};
|
||||||
|
|
||||||
|
// Detect translation files based on record properties (collection is often undefined)
|
||||||
|
const isTranslationFile = record && (
|
||||||
|
record.fileType === "model" ||
|
||||||
|
record.fileType === "lex" ||
|
||||||
|
record.fileType === "wasm" ||
|
||||||
|
record.fileType === "vocab" ||
|
||||||
|
(record.fromLang && record.toLang) ||
|
||||||
|
(record.name && (
|
||||||
|
record.name.includes("model.") ||
|
||||||
|
record.name.includes("lex.") ||
|
||||||
|
record.name.includes("vocab.") ||
|
||||||
|
record.name.includes("bergamot")
|
||||||
|
)) ||
|
||||||
|
(record.attachment?.filename && (
|
||||||
|
record.attachment.filename.endsWith(".wasm") ||
|
||||||
|
record.attachment.filename.includes("bergamot")
|
||||||
|
))
|
||||||
|
);
|
||||||
|
|
||||||
|
// Force avoidDownload for non-translation files
|
||||||
|
if (!isTranslationFile) {
|
||||||
|
avoidDownload = true;
|
||||||
|
}
|
||||||
if (!attachmentId) {
|
if (!attachmentId) {
|
||||||
// Check for pre-condition. This should not happen, but it is explicitly
|
// Check for pre-condition. This should not happen, but it is explicitly
|
||||||
// checked to avoid mixing up attachments, which could be dangerous.
|
// checked to avoid mixing up attachments, which could be dangerous.
|
||||||
@@ -510,7 +534,7 @@ export class Downloader {
|
|||||||
* @throws {Downloader.BadContentError} if the downloaded content integrity is not valid.
|
* @throws {Downloader.BadContentError} if the downloaded content integrity is not valid.
|
||||||
* @returns {ArrayBuffer} the file content.
|
* @returns {ArrayBuffer} the file content.
|
||||||
*/
|
*/
|
||||||
async downloadAsBytes(record, options = {}) {
|
async downloadAsBytes(record, options) {
|
||||||
const {
|
const {
|
||||||
attachment: { location, hash, size },
|
attachment: { location, hash, size },
|
||||||
} = record;
|
} = record;
|
||||||
@@ -519,7 +543,65 @@ export class Downloader {
|
|||||||
try {
|
try {
|
||||||
baseURL = await lazy.Utils.baseAttachmentsURL();
|
baseURL = await lazy.Utils.baseAttachmentsURL();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Downloader.ServerInfoError(error);
|
// For translation files only, fallback to Mozilla's server if Waterfox's is empty
|
||||||
|
const isTranslationFile = record && (
|
||||||
|
record.fileType === "model" ||
|
||||||
|
record.fileType === "lex" ||
|
||||||
|
record.fileType === "wasm" ||
|
||||||
|
record.fileType === "vocab" ||
|
||||||
|
(record.fromLang && record.toLang) ||
|
||||||
|
(record.name && (
|
||||||
|
record.name.includes("model.") ||
|
||||||
|
record.name.includes("lex.") ||
|
||||||
|
record.name.includes("vocab.") ||
|
||||||
|
record.name.includes("bergamot")
|
||||||
|
)) ||
|
||||||
|
(record.attachment?.filename && (
|
||||||
|
record.attachment.filename.endsWith(".wasm") ||
|
||||||
|
record.attachment.filename.includes("bergamot")
|
||||||
|
))
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isTranslationFile) {
|
||||||
|
// Use Mozilla's CDN for translation files when server URL is empty
|
||||||
|
baseURL = "https://firefox.settings.services.mozilla.com/v1/";
|
||||||
|
const resp = await lazy.Utils.fetch(baseURL);
|
||||||
|
const serverInfo = await resp.json();
|
||||||
|
baseURL = serverInfo.capabilities.attachments.base_url;
|
||||||
|
if (!baseURL.endsWith("/")) {
|
||||||
|
baseURL += "/";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Downloader.ServerInfoError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Additional check: if baseURL is still empty and it's a translation file
|
||||||
|
if (!baseURL || baseURL === "/") {
|
||||||
|
const isTranslationFile = record && (
|
||||||
|
record.fileType === "model" ||
|
||||||
|
record.fileType === "lex" ||
|
||||||
|
record.fileType === "wasm" ||
|
||||||
|
record.fileType === "vocab" ||
|
||||||
|
(record.fromLang && record.toLang) ||
|
||||||
|
(record.name && (
|
||||||
|
record.name.includes("model.") ||
|
||||||
|
record.name.includes("lex.") ||
|
||||||
|
record.name.includes("vocab.") ||
|
||||||
|
record.name.includes("bergamot")
|
||||||
|
)) ||
|
||||||
|
(record.attachment?.filename && (
|
||||||
|
record.attachment.filename.endsWith(".wasm") ||
|
||||||
|
record.attachment.filename.includes("bergamot")
|
||||||
|
))
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isTranslationFile) {
|
||||||
|
// Hardcode Mozilla's CDN URL for translations
|
||||||
|
baseURL = "https://cdn.settings.services.mozilla.com/";
|
||||||
|
} else {
|
||||||
|
throw new Downloader.ServerInfoError("No server URL configured");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const remoteFileUrl = baseURL + location;
|
const remoteFileUrl = baseURL + location;
|
||||||
|
|||||||
@@ -437,11 +437,19 @@ export class RemoteSettingsClient extends EventEmitter {
|
|||||||
order = "", // not sorted by default.
|
order = "", // not sorted by default.
|
||||||
dumpFallback = true,
|
dumpFallback = true,
|
||||||
emptyListFallback = true,
|
emptyListFallback = true,
|
||||||
forceSync = false,
|
|
||||||
loadDumpIfNewer = true,
|
loadDumpIfNewer = true,
|
||||||
syncIfEmpty = true,
|
|
||||||
} = options;
|
} = options;
|
||||||
let { verifySignature = false } = options;
|
|
||||||
|
const hasLocalDump = await lazy.Utils.hasLocalDump(
|
||||||
|
this.bucketName,
|
||||||
|
this.collectionName
|
||||||
|
);
|
||||||
|
if (!hasLocalDump) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const forceSync = false;
|
||||||
|
const syncIfEmpty = true;
|
||||||
|
let verifySignature = false;
|
||||||
|
|
||||||
const hasParallelCall = !!this._importingPromise;
|
const hasParallelCall = !!this._importingPromise;
|
||||||
let data;
|
let data;
|
||||||
@@ -621,6 +629,10 @@ export class RemoteSettingsClient extends EventEmitter {
|
|||||||
* @param {Object} options See #maybeSync() options.
|
* @param {Object} options See #maybeSync() options.
|
||||||
*/
|
*/
|
||||||
async sync(options) {
|
async sync(options) {
|
||||||
|
if (AppConstants.MOZ_APP_VERSION) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (lazy.Utils.shouldSkipRemoteActivityDueToTests) {
|
if (lazy.Utils.shouldSkipRemoteActivityDueToTests) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -693,7 +705,7 @@ export class RemoteSettingsClient extends EventEmitter {
|
|||||||
let thrownError = null;
|
let thrownError = null;
|
||||||
try {
|
try {
|
||||||
// If network is offline, we can't synchronize.
|
// If network is offline, we can't synchronize.
|
||||||
if (lazy.Utils.isOffline) {
|
if (!AppConstants.MOZ_APP_VERSION && lazy.Utils.isOffline) {
|
||||||
throw new RemoteSettingsClient.NetworkOfflineError();
|
throw new RemoteSettingsClient.NetworkOfflineError();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1079,14 +1091,8 @@ export class RemoteSettingsClient extends EventEmitter {
|
|||||||
options = {}
|
options = {}
|
||||||
) {
|
) {
|
||||||
const { retry = false } = options;
|
const { retry = false } = options;
|
||||||
const since = retry || !localTimestamp ? undefined : `"${localTimestamp}"`;
|
|
||||||
|
|
||||||
// Fetch collection metadata and list of changes from server.
|
let metadata, remoteTimestamp;
|
||||||
lazy.console.debug(
|
|
||||||
`${this.identifier} Fetch changes from server (expected=${expectedTimestamp}, since=${since})`
|
|
||||||
);
|
|
||||||
const { metadata, remoteTimestamp, remoteRecords } =
|
|
||||||
await this._fetchChangeset(expectedTimestamp, since);
|
|
||||||
|
|
||||||
// We build a sync result, based on remote changes.
|
// We build a sync result, based on remote changes.
|
||||||
const syncResult = {
|
const syncResult = {
|
||||||
@@ -1095,24 +1101,20 @@ export class RemoteSettingsClient extends EventEmitter {
|
|||||||
updated: [],
|
updated: [],
|
||||||
deleted: [],
|
deleted: [],
|
||||||
};
|
};
|
||||||
// If data wasn't changed, return empty sync result.
|
|
||||||
// This can happen when we update the signature but not the data.
|
try {
|
||||||
lazy.console.debug(
|
await this._importJSONDump();
|
||||||
`${this.identifier} local timestamp: ${localTimestamp}, remote: ${remoteTimestamp}`
|
} catch (e) {
|
||||||
);
|
|
||||||
if (localTimestamp && remoteTimestamp < localTimestamp) {
|
|
||||||
return syncResult;
|
return syncResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.db.importChanges(metadata, remoteTimestamp, remoteRecords, {
|
|
||||||
clear: retry,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Read the new local data, after updating.
|
// Read the new local data, after updating.
|
||||||
const newLocal = await this.db.list();
|
const newLocal = await this.db.list();
|
||||||
const newRecords = newLocal.map(r => this._cleanLocalFields(r));
|
const newRecords = newLocal.map(r => this._cleanLocalFields(r));
|
||||||
// And verify the signature on what is now stored.
|
// And verify the signature on what is now stored.
|
||||||
if (this.verifySignature) {
|
if (metadata === undefined) {
|
||||||
|
// When working only with dumps, we do not have signatures.
|
||||||
|
} else if (this.verifySignature) {
|
||||||
try {
|
try {
|
||||||
await this.validateCollectionSignature(
|
await this.validateCollectionSignature(
|
||||||
newRecords,
|
newRecords,
|
||||||
|
|||||||
@@ -63,8 +63,10 @@ def main(output):
|
|||||||
dumps_locations = []
|
dumps_locations = []
|
||||||
if buildconfig.substs["MOZ_BUILD_APP"] == "browser":
|
if buildconfig.substs["MOZ_BUILD_APP"] == "browser":
|
||||||
dumps_locations += ["services/settings/dumps/"]
|
dumps_locations += ["services/settings/dumps/"]
|
||||||
|
dumps_locations += ["services/settings/static-dumps/"]
|
||||||
elif buildconfig.substs["MOZ_BUILD_APP"] == "mobile/android":
|
elif buildconfig.substs["MOZ_BUILD_APP"] == "mobile/android":
|
||||||
dumps_locations += ["services/settings/dumps/"]
|
dumps_locations += ["services/settings/dumps/"]
|
||||||
|
dumps_locations += ["services/settings/static-dumps/"]
|
||||||
elif buildconfig.substs["MOZ_BUILD_APP"] == "mobile/ios":
|
elif buildconfig.substs["MOZ_BUILD_APP"] == "mobile/ios":
|
||||||
dumps_locations += ["services/settings/dumps/"]
|
dumps_locations += ["services/settings/dumps/"]
|
||||||
elif buildconfig.substs["MOZ_BUILD_APP"] == "comm/mail":
|
elif buildconfig.substs["MOZ_BUILD_APP"] == "comm/mail":
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ export async function jexlFilterFunc(entry, environment, collectionName) {
|
|||||||
function remoteSettingsFunction() {
|
function remoteSettingsFunction() {
|
||||||
const _clients = new Map();
|
const _clients = new Map();
|
||||||
let _invalidatePolling = false;
|
let _invalidatePolling = false;
|
||||||
|
let _initialized = false;
|
||||||
|
|
||||||
// If not explicitly specified, use the default signer.
|
// If not explicitly specified, use the default signer.
|
||||||
const defaultOptions = {
|
const defaultOptions = {
|
||||||
@@ -304,6 +305,39 @@ function remoteSettingsFunction() {
|
|||||||
trigger = "manual",
|
trigger = "manual",
|
||||||
full = false,
|
full = false,
|
||||||
} = {}) => {
|
} = {}) => {
|
||||||
|
if (AppConstants.MOZ_APP_VERSION) {
|
||||||
|
// Called multiple times on GeckoView due to bug 1730026
|
||||||
|
if (_initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_initialized = true;
|
||||||
|
let importedFromDump = false;
|
||||||
|
for (const client of _clients.values()) {
|
||||||
|
const hasLocalDump = await lazy.Utils.hasLocalDump(
|
||||||
|
client.bucketName,
|
||||||
|
client.collectionName
|
||||||
|
);
|
||||||
|
if (hasLocalDump) {
|
||||||
|
const lastModified = await client.getLastModified();
|
||||||
|
const lastModifiedDump = await lazy.Utils.getLocalDumpLastModified(
|
||||||
|
client.bucketName,
|
||||||
|
client.collectionName
|
||||||
|
);
|
||||||
|
if (lastModified < lastModifiedDump) {
|
||||||
|
await client.maybeSync(lastModifiedDump, {
|
||||||
|
loadDump: true,
|
||||||
|
trigger,
|
||||||
|
});
|
||||||
|
importedFromDump = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (importedFromDump) {
|
||||||
|
Services.obs.notifyObservers(null, "remote-settings:changes-poll-end");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (lazy.Utils.shouldSkipRemoteActivityDueToTests) {
|
if (lazy.Utils.shouldSkipRemoteActivityDueToTests) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -875,8 +875,11 @@ class RefResolver:
|
|||||||
return None
|
return None
|
||||||
uri, fragment = urldefrag(url)
|
uri, fragment = urldefrag(url)
|
||||||
for subschema in subschemas:
|
for subschema in subschemas:
|
||||||
|
id = subschema["$id"]
|
||||||
|
if not isinstance(id, str):
|
||||||
|
continue
|
||||||
target_uri = self._urljoin_cache(
|
target_uri = self._urljoin_cache(
|
||||||
self.resolution_scope, subschema["$id"],
|
self.resolution_scope, id,
|
||||||
)
|
)
|
||||||
if target_uri.rstrip("/") == uri.rstrip("/"):
|
if target_uri.rstrip("/") == uri.rstrip("/"):
|
||||||
if fragment:
|
if fragment:
|
||||||
|
|||||||
@@ -99,6 +99,9 @@ export class RemoteWebNavigation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_speculativeConnect(uri, loadURIOptions) {
|
_speculativeConnect(uri, loadURIOptions) {
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* eslint-disable-next-line no-unreachable */
|
||||||
try {
|
try {
|
||||||
// Let's start a network connection before the content process asks.
|
// Let's start a network connection before the content process asks.
|
||||||
// Note that we might have already set up the speculative connection in
|
// Note that we might have already set up the speculative connection in
|
||||||
|
|||||||
@@ -317,6 +317,13 @@ toolbox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (-moz-platform: linux) {
|
||||||
|
*|*:root[shellshowingmenubar="true"]
|
||||||
|
toolbar[type="menubar"]:not([customizing="true"]) {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
toolbarspring {
|
toolbarspring {
|
||||||
flex: 1000 1000;
|
flex: 1000 1000;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -211,11 +211,11 @@ export var AppConstants = Object.freeze({
|
|||||||
#ifdef MOZ_THUNDERBIRD
|
#ifdef MOZ_THUNDERBIRD
|
||||||
"https://thunderbird-settings.thunderbird.net/v1",
|
"https://thunderbird-settings.thunderbird.net/v1",
|
||||||
#else
|
#else
|
||||||
"https://firefox.settings.services.mozilla.com/v1",
|
"",
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
REMOTE_SETTINGS_VERIFY_SIGNATURE:
|
REMOTE_SETTINGS_VERIFY_SIGNATURE:
|
||||||
#ifdef MOZ_THUNDERBIRD
|
#if defined(MOZ_THUNDERBIRD) || defined(MOZ_APP_VERSION)
|
||||||
false,
|
false,
|
||||||
#else
|
#else
|
||||||
true,
|
true,
|
||||||
|
|||||||
@@ -82,10 +82,7 @@ const _gMainPaneOverlay = {
|
|||||||
}
|
}
|
||||||
document.initialized = true;
|
document.initialized = true;
|
||||||
}
|
}
|
||||||
this.setEventListener("enableObliviousDns", "click", () => {
|
|
||||||
const value = document.getElementById("enableObliviousDns").checked ? 2 : 0;
|
|
||||||
Services.prefs.setIntPref("network.trr.mode", value);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
tocGenerate() {
|
tocGenerate() {
|
||||||
|
|||||||
@@ -7,6 +7,19 @@
|
|||||||
<overlay id="preferences-overlay" xmlns:html="http://www.w3.org/1999/xhtml"
|
<overlay id="preferences-overlay" xmlns:html="http://www.w3.org/1999/xhtml"
|
||||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||||
|
|
||||||
|
<html:style>
|
||||||
|
/* Hide sponsored shortcuts checkbox */
|
||||||
|
checkbox[data-l10n-id="home-prefs-shortcuts-by-option-sponsored"],
|
||||||
|
checkbox[preference="browser.newtabpage.activity-stream.showSponsoredTopSites"] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide mission message about sponsors */
|
||||||
|
.mission-message {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
</html:style>
|
||||||
|
|
||||||
<!-- Home -->
|
<!-- Home -->
|
||||||
<vbox data-subcategory="topstories" delete="true" />
|
<vbox data-subcategory="topstories" delete="true" />
|
||||||
<vbox data-subcategory="snippets" delete="true" />
|
<vbox data-subcategory="snippets" delete="true" />
|
||||||
@@ -70,10 +83,6 @@
|
|||||||
|
|
||||||
<!-- DNS -->
|
<!-- DNS -->
|
||||||
<groupbox id="dohBox">
|
<groupbox id="dohBox">
|
||||||
<hbox id="obliviousDns" data-subcategory="doh" insertbefore="dohCategories">
|
|
||||||
<checkbox id="enableObliviousDns" class="tail-with-learn-more" data-l10n-id="enable-dooh"
|
|
||||||
preference="network.trr.use_ohttp" flex="1" />
|
|
||||||
</hbox>
|
|
||||||
</groupbox>
|
</groupbox>
|
||||||
</vbox>
|
</vbox>
|
||||||
</overlay>
|
</overlay>
|
||||||
|
|||||||
@@ -36,10 +36,7 @@ const _gPrivacyPaneOverlay = {
|
|||||||
javascriptPermissions.getAttribute("preference")
|
javascriptPermissions.getAttribute("preference")
|
||||||
).value;
|
).value;
|
||||||
|
|
||||||
const obliviousDns = document.getElementById("enableObliviousDns");
|
|
||||||
obliviousDns.checked = Preferences.get(
|
|
||||||
obliviousDns.getAttribute("preference")
|
|
||||||
).value;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export const TabFeatures = {
|
|||||||
PREF_ACTIVETAB: "browser.tabs.copyurl.activetab",
|
PREF_ACTIVETAB: "browser.tabs.copyurl.activetab",
|
||||||
PREF_REQUIRECONFIRM: "browser.restart_menu.requireconfirm",
|
PREF_REQUIRECONFIRM: "browser.restart_menu.requireconfirm",
|
||||||
PREF_PURGECACHE: "browser.restart_menu.purgecache",
|
PREF_PURGECACHE: "browser.restart_menu.purgecache",
|
||||||
|
PREF_COPYURL_SHORTCUT: "browser.tabs.copyurl.shortcut",
|
||||||
|
|
||||||
init(aWindow) {
|
init(aWindow) {
|
||||||
// Wait for XUL elements to be available before initializing listeners.
|
// Wait for XUL elements to be available before initializing listeners.
|
||||||
@@ -35,6 +36,7 @@ export const TabFeatures = {
|
|||||||
this.initListeners(aWindow);
|
this.initListeners(aWindow);
|
||||||
this.initNewTabConfig(); // Does not require aWindow
|
this.initNewTabConfig(); // Does not require aWindow
|
||||||
this.initNewTabFocus(aWindow);
|
this.initNewTabFocus(aWindow);
|
||||||
|
this.initShortcutCopyUrl(aWindow);
|
||||||
},
|
},
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
@@ -175,6 +177,75 @@ export const TabFeatures = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
initShortcutCopyUrl(aWindow) {
|
||||||
|
const doc = aWindow.document;
|
||||||
|
|
||||||
|
const handler = (e) => {
|
||||||
|
try {
|
||||||
|
// Allow disabling via pref; default to enabled if unset
|
||||||
|
if (!Services.prefs.getBoolPref(this.PREF_COPYURL_SHORTCUT, true)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isMac = AppConstants.platform === "macosx";
|
||||||
|
const accelPressed = isMac ? e.metaKey : e.ctrlKey;
|
||||||
|
|
||||||
|
if (!accelPressed || !e.shiftKey || e.key?.toLowerCase() !== "u") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = aWindow.gBrowser?.currentURI?.spec;
|
||||||
|
if (!url) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent default so we don't trigger other shortcuts
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
this.copyTabUrl(url, aWindow);
|
||||||
|
this._showCopyNotification(url, aWindow);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("TabFeatures: shortcut copy failed", err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Capture phase to beat page-level handlers
|
||||||
|
doc.addEventListener("keydown", handler, true);
|
||||||
|
|
||||||
|
aWindow.addEventListener(
|
||||||
|
"unload",
|
||||||
|
() => {
|
||||||
|
try {
|
||||||
|
doc.removeEventListener("keydown", handler, true);
|
||||||
|
} catch (_) {}
|
||||||
|
},
|
||||||
|
{ once: true }
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
_showCopyNotification(url, aWindow) {
|
||||||
|
try {
|
||||||
|
const alerts = Cc["@mozilla.org/alerts-service;1"].getService(
|
||||||
|
Ci.nsIAlertsService
|
||||||
|
);
|
||||||
|
const title = "🔗 URL Copied";
|
||||||
|
const text = url.length > 50 ? url.substring(0, 47) + "..." : url;
|
||||||
|
|
||||||
|
alerts.showAlertNotification(
|
||||||
|
null,
|
||||||
|
title,
|
||||||
|
text,
|
||||||
|
false,
|
||||||
|
"",
|
||||||
|
null,
|
||||||
|
"tabfeatures-copyurl"
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.log("URL copied to clipboard:", url);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
destroyNewTabConfig() {
|
destroyNewTabConfig() {
|
||||||
if (this.prefListener) {
|
if (this.prefListener) {
|
||||||
Services.prefs.removeObserver(
|
Services.prefs.removeObserver(
|
||||||
|
|||||||
4
waterfox/browser/installer/linux/debian/changelog.in
Normal file
4
waterfox/browser/installer/linux/debian/changelog.in
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
${PKG_NAME} (${PKG_VERSION}) UNRELEASED; urgency=low
|
||||||
|
* Automated repackaging for ${PKG_VERSION}
|
||||||
|
|
||||||
|
-- Alex Kontos <MrAlex94@users.noreply.github.com> ${CHANGELOG_DATE}
|
||||||
1
waterfox/browser/installer/linux/debian/compat
Normal file
1
waterfox/browser/installer/linux/debian/compat
Normal file
@@ -0,0 +1 @@
|
|||||||
|
10
|
||||||
14
waterfox/browser/installer/linux/debian/control.in
Normal file
14
waterfox/browser/installer/linux/debian/control.in
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
Source: ${PKG_NAME}
|
||||||
|
Section: web
|
||||||
|
Priority: optional
|
||||||
|
Maintainer: BrowserWorks
|
||||||
|
Standards-Version: 4.6.2
|
||||||
|
Build-Depends: debhelper (>= 9)
|
||||||
|
Homepage: https://www.waterfox.net
|
||||||
|
|
||||||
|
Package: ${PKG_NAME}
|
||||||
|
Architecture: ${ARCH_NAME}
|
||||||
|
Depends: ${DEPENDS}
|
||||||
|
Recommends: curl | wget, gnupg, ca-certificates
|
||||||
|
Provides: gnome-www-browser, www-browser
|
||||||
|
Description: ${DESCRIPTION}
|
||||||
2
waterfox/browser/installer/linux/debian/dirs
Normal file
2
waterfox/browser/installer/linux/debian/dirs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
usr/bin
|
||||||
|
usr/lib/waterfox
|
||||||
2
waterfox/browser/installer/linux/debian/install.in
Normal file
2
waterfox/browser/installer/linux/debian/install.in
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
waterfox/* ${PKG_INSTALL_PATH}
|
||||||
|
debian/${PKG_NAME}.desktop usr/share/applications
|
||||||
12
waterfox/browser/installer/linux/debian/links.in
Normal file
12
waterfox/browser/installer/linux/debian/links.in
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
${PKG_INSTALL_PATH}/waterfox usr/bin/${PKG_NAME}
|
||||||
|
${PKG_INSTALL_PATH}/waterfox usr/bin/waterfox
|
||||||
|
${PKG_INSTALL_PATH}/browser/chrome/icons/default/default16.png usr/share/icons/hicolor/16x16/apps/${PKG_NAME}.png
|
||||||
|
${PKG_INSTALL_PATH}/browser/chrome/icons/default/default32.png usr/share/icons/hicolor/32x32/apps/${PKG_NAME}.png
|
||||||
|
${PKG_INSTALL_PATH}/browser/chrome/icons/default/default48.png usr/share/icons/hicolor/48x48/apps/${PKG_NAME}.png
|
||||||
|
${PKG_INSTALL_PATH}/browser/chrome/icons/default/default64.png usr/share/icons/hicolor/64x64/apps/${PKG_NAME}.png
|
||||||
|
${PKG_INSTALL_PATH}/browser/chrome/icons/default/default128.png usr/share/icons/hicolor/128x128/apps/${PKG_NAME}.png
|
||||||
|
${PKG_INSTALL_PATH}/browser/chrome/icons/default/default16.png usr/share/icons/hicolor/16x16/apps/waterfox.png
|
||||||
|
${PKG_INSTALL_PATH}/browser/chrome/icons/default/default32.png usr/share/icons/hicolor/32x32/apps/waterfox.png
|
||||||
|
${PKG_INSTALL_PATH}/browser/chrome/icons/default/default48.png usr/share/icons/hicolor/48x48/apps/waterfox.png
|
||||||
|
${PKG_INSTALL_PATH}/browser/chrome/icons/default/default64.png usr/share/icons/hicolor/64x64/apps/waterfox.png
|
||||||
|
${PKG_INSTALL_PATH}/browser/chrome/icons/default/default128.png usr/share/icons/hicolor/128x128/apps/waterfox.png
|
||||||
90
waterfox/browser/installer/linux/debian/manpage.1.in
Normal file
90
waterfox/browser/installer/linux/debian/manpage.1.in
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
.TH ${DISPLAY_NAME} 1 "${MANPAGE_DATE}" waterfox "Linux User's Manual"
|
||||||
|
.SH NAME
|
||||||
|
waterfox \- ${DISPLAY_NAME} offers safe and easy web browsing
|
||||||
|
.SH SYNOPSIS
|
||||||
|
.B waterfox
|
||||||
|
[\fIOPTIONS\fR] [\fIURL\fR]
|
||||||
|
|
||||||
|
.B /usr/lib/waterfox/waterfox
|
||||||
|
[\fIOPTIONS\fR] [\fIURL\fR]
|
||||||
|
.SH DESCRIPTION
|
||||||
|
\fB${DISPLAY_NAME}\fR is an open-source web browser, designed for standards
|
||||||
|
compliance, performance, privacy, and portability. It supports a wide range
|
||||||
|
of modern web technologies and extensions.
|
||||||
|
.SH USAGE
|
||||||
|
If there is a \fB${DISPLAY_NAME}\fR browser already running, invoking
|
||||||
|
\fBwaterfox\fR will arrange for it to create a new browser window; otherwise
|
||||||
|
it will start a new instance.
|
||||||
|
.SH OPTIONS
|
||||||
|
A summary of commonly used options is included below. For the full list,
|
||||||
|
run \fBwaterfox \-\-help\fR.
|
||||||
|
.SS "X11 options"
|
||||||
|
.TP
|
||||||
|
.BI \-\-display= DISPLAY
|
||||||
|
Use the specified X display.
|
||||||
|
.TP
|
||||||
|
.B \-\-sync
|
||||||
|
Make X calls synchronous.
|
||||||
|
.TP
|
||||||
|
.B \-\-no\-xshm
|
||||||
|
Do not use X shared memory extension.
|
||||||
|
.SS "Browser options"
|
||||||
|
.TP
|
||||||
|
.B \-h, \-\-help
|
||||||
|
Show summary of options and exit.
|
||||||
|
.TP
|
||||||
|
.B \-v, \-\-version
|
||||||
|
Print ${DISPLAY_NAME} version and exit.
|
||||||
|
.TP
|
||||||
|
\fB\-P\fR [\fIprofile\fR]
|
||||||
|
Start with \fIprofile\fR. When no profile is given, displays the Profile Manager.
|
||||||
|
This may require \fB\-\-no\-remote\fR.
|
||||||
|
.TP
|
||||||
|
.B \-ProfileManager
|
||||||
|
Start with Profile Manager. May require \fB\-\-no\-remote\fR.
|
||||||
|
.TP
|
||||||
|
.B \-\-no\-remote
|
||||||
|
Do not connect to a running ${DISPLAY_NAME} instance. Use with options that
|
||||||
|
would otherwise be sent to an existing process.
|
||||||
|
.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
|
||||||
|
.B \-\-private\-window
|
||||||
|
Open a new private browsing window.
|
||||||
|
.TP
|
||||||
|
\fB\-\-safe\-mode\fR
|
||||||
|
Start ${DISPLAY_NAME} in Troubleshoot Mode (extensions disabled).
|
||||||
|
.TP
|
||||||
|
.B \-\-headless
|
||||||
|
Run without a visible UI (useful for testing/automation).
|
||||||
|
.TP
|
||||||
|
\fB\-\-Profile\fR \fIPATH\fR
|
||||||
|
Start with the profile located at \fIPATH\fR.
|
||||||
|
.SH ENVIRONMENT
|
||||||
|
.TP
|
||||||
|
.B MOZ_GDK_DISPLAY
|
||||||
|
When set, overrides the display passed down to GTK on X11-only builds. Normally
|
||||||
|
set by the parent process on launch with \fB\-\-display\fR.
|
||||||
|
.TP
|
||||||
|
.B MOZ_ENABLE_WAYLAND
|
||||||
|
Set to 1 to prefer Wayland backend on supported environments.
|
||||||
|
.SH FILES
|
||||||
|
.TP
|
||||||
|
\fI/usr/bin/waterfox\fR
|
||||||
|
Symbolic link to \fB/usr/lib/waterfox/waterfox\fR.
|
||||||
|
.TP
|
||||||
|
\fI/usr/lib/waterfox/waterfox\fR
|
||||||
|
${DISPLAY_NAME} executable.
|
||||||
|
.TP
|
||||||
|
\fI/usr/lib/waterfox/waterfox\-bin\fR
|
||||||
|
Legacy executable.
|
||||||
|
.SH BUGS
|
||||||
|
Report issues at \fIhttps://github.com/BrowserWorks/Waterfox/issues\fR.
|
||||||
|
.SH AUTHORS
|
||||||
|
.TP
|
||||||
|
.B BrowserWorks
|
||||||
|
.I https://www.waterfox.net
|
||||||
1
waterfox/browser/installer/linux/debian/manpages.in
Normal file
1
waterfox/browser/installer/linux/debian/manpages.in
Normal file
@@ -0,0 +1 @@
|
|||||||
|
debian/${PKG_NAME}.1
|
||||||
27
waterfox/browser/installer/linux/debian/package-prefs.js
Normal file
27
waterfox/browser/installer/linux/debian/package-prefs.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
/* 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/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Package defaults for Waterfox (Debian repackaging).
|
||||||
|
* This file is installed into defaults/pref/package-prefs.js by the repackager.
|
||||||
|
*
|
||||||
|
* These preferences are intended for distribution-level defaults, not user
|
||||||
|
* settings. They can be overridden by user prefs in profiles.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Improve startup performance on multi-process by enabling the forkserver. */
|
||||||
|
pref("dom.ipc.forkserver.enable", true);
|
||||||
|
|
||||||
|
/* Disable in-app updates when installed via system packages. */
|
||||||
|
pref("app.update.enabled", false);
|
||||||
|
pref("app.update.auto", false);
|
||||||
|
pref("app.update.autoInstallEnabled", false);
|
||||||
|
pref("app.update.staging.enabled", false);
|
||||||
|
pref("app.update.background.enabled", false);
|
||||||
|
|
||||||
|
/* Optional distribution identifiers to help diagnostics. */
|
||||||
|
pref("distribution.id", "waterfox-debian");
|
||||||
|
pref("distribution.version", "1");
|
||||||
|
pref("distribution.about", "Waterfox packaged for Debian");
|
||||||
93
waterfox/browser/installer/linux/debian/postinst.in
Normal file
93
waterfox/browser/installer/linux/debian/postinst.in
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
#!/bin/sh -e
|
||||||
|
# postinst script for Waterfox
|
||||||
|
# Sets system alternatives for x-www-browser and gnome-www-browser to Waterfox.
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
case "$$1" in
|
||||||
|
configure|abort-upgrade|abort-remove|abort-deconfigure)
|
||||||
|
# Configure Waterfox apt source and keyring for self-updates
|
||||||
|
KEYRING="/usr/share/keyrings/waterfox-archive-keyring.gpg"
|
||||||
|
LIST_FILE="/etc/apt/sources.list.d/waterfox.list"
|
||||||
|
ARCH="$$(dpkg --print-architecture)"
|
||||||
|
|
||||||
|
# Determine codename
|
||||||
|
if command -v lsb_release >/dev/null 2>&1; then
|
||||||
|
CODENAME="$$(lsb_release -sc)"
|
||||||
|
elif [ -r /etc/os-release ]; then
|
||||||
|
. /etc/os-release
|
||||||
|
CODENAME="$${VERSION_CODENAME:-}"
|
||||||
|
fi
|
||||||
|
[ -n "$$CODENAME" ] || CODENAME="bookworm"
|
||||||
|
|
||||||
|
REPO_LINE="deb [arch=$${ARCH} signed-by=$${KEYRING}] https://pkg.waterfox.net/waterfox $${CODENAME} main"
|
||||||
|
|
||||||
|
# Ensure keyring directory exists
|
||||||
|
mkdir -p "/usr/share/keyrings" || true
|
||||||
|
|
||||||
|
# Install keyring if shipped as ASCII armor (optional)
|
||||||
|
# If the keyring is already installed by the package payload, this is a no-op.
|
||||||
|
if [ ! -s "$$KEYRING" ] && [ -r /usr/share/waterfox/waterfox.asc ]; then
|
||||||
|
if command -v gpg >/dev/null 2>&1; then
|
||||||
|
gpg --dearmor </usr/share/waterfox/waterfox.asc >"$$KEYRING" || true
|
||||||
|
chmod 0644 "$$KEYRING" || true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Try to fetch keyring from pkg.waterfox.net if not bundled
|
||||||
|
if [ ! -s "$$KEYRING" ]; then
|
||||||
|
KEY_URL_GPG="https://pkg.waterfox.net/waterfox/waterfox-archive-keyring.gpg"
|
||||||
|
KEY_URL_ASC="https://pkg.waterfox.net/waterfox/waterfox.asc"
|
||||||
|
if command -v curl >/dev/null 2>&1; then
|
||||||
|
curl -fsSL "$$KEY_URL_GPG" -o "$$KEYRING" || true
|
||||||
|
if [ ! -s "$$KEYRING" ] && command -v gpg >/dev/null 2>&1; then
|
||||||
|
curl -fsSL "$$KEY_URL_ASC" | gpg --dearmor >"$$KEYRING" || true
|
||||||
|
fi
|
||||||
|
elif command -v wget >/dev/null 2>&1; then
|
||||||
|
wget -qO "$$KEYRING" "$$KEY_URL_GPG" || true
|
||||||
|
if [ ! -s "$$KEYRING" ] && command -v gpg >/dev/null 2>&1; then
|
||||||
|
wget -qO- "$$KEY_URL_ASC" | gpg --dearmor >"$$KEYRING" || true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
chmod 0644 "$$KEYRING" || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$$(dirname "$$LIST_FILE")"
|
||||||
|
if [ -f "$$LIST_FILE" ]; then
|
||||||
|
if ! grep -qF "$$REPO_LINE" "$$LIST_FILE"; then
|
||||||
|
sed -i 's|^deb .*pkg\.waterfox\.net.*$$||' "$$LIST_FILE" || true
|
||||||
|
echo "$$REPO_LINE" >> "$$LIST_FILE"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "$$REPO_LINE" > "$$LIST_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v update-alternatives >/dev/null 2>&1; then
|
||||||
|
update-alternatives --install /usr/bin/gnome-www-browser \
|
||||||
|
gnome-www-browser /usr/bin/${PKG_NAME} 100 \
|
||||||
|
--slave /usr/share/man/man1/gnome-www-browser.1.gz \
|
||||||
|
gnome-www-browser.1.gz /usr/share/man/man1/${PKG_NAME}.1.gz || true
|
||||||
|
|
||||||
|
update-alternatives --install /usr/bin/x-www-browser \
|
||||||
|
x-www-browser /usr/bin/${PKG_NAME} 100 \
|
||||||
|
--slave /usr/share/man/man1/x-www-browser.1.gz \
|
||||||
|
x-www-browser.1.gz /usr/share/man/man1/${PKG_NAME}.1.gz || true
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Refresh icon cache if available
|
||||||
|
if [ -x /usr/bin/gtk-update-icon-cache ]; then
|
||||||
|
for dir in /usr/share/icons/hicolor; do
|
||||||
|
if [ -d "$$dir" ]; then
|
||||||
|
gtk-update-icon-cache -q "$$dir" || true
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Update desktop database if available
|
||||||
|
if [ -x /usr/bin/update-desktop-database ]; then
|
||||||
|
update-desktop-database -q || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
33
waterfox/browser/installer/linux/debian/postrm.in
Normal file
33
waterfox/browser/installer/linux/debian/postrm.in
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
#!/bin/sh -e
|
||||||
|
# postrm script for Waterfox
|
||||||
|
# Refreshes desktop and icon caches after removal/purge.
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
case "$$1" in
|
||||||
|
remove|purge|disappear)
|
||||||
|
# Refresh icon cache if available
|
||||||
|
if [ -x /usr/bin/gtk-update-icon-cache ]; then
|
||||||
|
if [ -d /usr/share/icons/hicolor ]; then
|
||||||
|
/usr/bin/gtk-update-icon-cache -q /usr/share/icons/hicolor || true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Refresh desktop database if available
|
||||||
|
if [ -x /usr/bin/update-desktop-database ]; then
|
||||||
|
/usr/bin/update-desktop-database -q || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If purging, remove Waterfox apt source and keyring
|
||||||
|
if [ "$$1" = "purge" ]; then
|
||||||
|
rm -f /etc/apt/sources.list.d/waterfox.list || true
|
||||||
|
rm -f /usr/share/keyrings/waterfox-archive-keyring.gpg || true
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
upgrade|failed-upgrade|abort-install|abort-upgrade)
|
||||||
|
# Nothing to do for these actions
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
exit 0
|
||||||
23
waterfox/browser/installer/linux/debian/prerm.in
Normal file
23
waterfox/browser/installer/linux/debian/prerm.in
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#!/bin/sh -e
|
||||||
|
# prerm script for Waterfox
|
||||||
|
# Cleans up system alternatives when removing the package.
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
case "$$1" in
|
||||||
|
remove|deconfigure)
|
||||||
|
if command -v update-alternatives >/dev/null 2>&1; then
|
||||||
|
# Remove Waterfox alternatives registrations if present
|
||||||
|
update-alternatives --remove x-www-browser /usr/bin/${PKG_NAME} || true
|
||||||
|
update-alternatives --remove gnome-www-browser /usr/bin/${PKG_NAME} || true
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
upgrade)
|
||||||
|
# Nothing to do on upgrade
|
||||||
|
;;
|
||||||
|
failed-upgrade|abort-install|abort-upgrade)
|
||||||
|
# Nothing to do on failed/aborted operations
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
exit 0
|
||||||
25
waterfox/browser/installer/linux/debian/rules
Normal file
25
waterfox/browser/installer/linux/debian/rules
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#!/usr/bin/make -f
|
||||||
|
# Waterfox Debian packaging rules (debhelper, simple dh sequence)
|
||||||
|
# This package repackages prebuilt binaries; we skip build/test stages.
|
||||||
|
|
||||||
|
export DEB_BUILD_MAINT_OPTIONS = hardening=+all
|
||||||
|
|
||||||
|
%:
|
||||||
|
dh $@
|
||||||
|
|
||||||
|
# Avoid touching .xpi files for reproducibility (they are already signed/packed)
|
||||||
|
override_dh_strip_nondeterminism:
|
||||||
|
dh_strip_nondeterminism -Xxpi
|
||||||
|
|
||||||
|
# Skip auto configure/build/test — binaries are prebuilt
|
||||||
|
override_dh_auto_configure override_dh_auto_build override_dh_auto_test:
|
||||||
|
@echo "Skipping auto-configure/build/test (prebuilt binaries)."
|
||||||
|
|
||||||
|
# Do not run dh_auto_install; installation is driven by debian/install
|
||||||
|
override_dh_auto_install:
|
||||||
|
@echo "Skipping dh_auto_install; using dh_install mappings."
|
||||||
|
:
|
||||||
|
|
||||||
|
# Perform file installation via debian/install and symlinks via debian/links
|
||||||
|
override_dh_install:
|
||||||
|
dh_install
|
||||||
23
waterfox/browser/installer/linux/debian/waterfox.desktop
Normal file
23
waterfox/browser/installer/linux/debian/waterfox.desktop
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Version=1.0
|
||||||
|
Type=Application
|
||||||
|
Name=Waterfox
|
||||||
|
GenericName=Web Browser
|
||||||
|
Comment=Browse the World Wide Web
|
||||||
|
Exec=waterfox %u
|
||||||
|
Icon=waterfox
|
||||||
|
Terminal=false
|
||||||
|
Categories=Network;WebBrowser;
|
||||||
|
MimeType=text/html;text/xml;application/xhtml+xml;x-scheme-handler/http;x-scheme-handler/https;application/x-xpinstall;application/pdf;application/json;
|
||||||
|
StartupNotify=true
|
||||||
|
StartupWMClass=waterfox
|
||||||
|
Keywords=Internet;WWW;Browser;Web;Explorer;
|
||||||
|
Actions=new-window;new-private-window;
|
||||||
|
|
||||||
|
[Desktop Action new-window]
|
||||||
|
Name=New Window
|
||||||
|
Exec=waterfox --new-window %u
|
||||||
|
|
||||||
|
[Desktop Action new-private-window]
|
||||||
|
Name=New Private Window
|
||||||
|
Exec=waterfox --private-window %u
|
||||||
@@ -10,6 +10,8 @@
|
|||||||
#include "NativeMenuGtk.h"
|
#include "NativeMenuGtk.h"
|
||||||
#include "DBusMenu.h"
|
#include "DBusMenu.h"
|
||||||
#include "nsWindow.h"
|
#include "nsWindow.h"
|
||||||
|
#include "nsINativeMenuService.h"
|
||||||
|
#include "nsServiceManagerUtils.h"
|
||||||
|
|
||||||
namespace mozilla::widget {
|
namespace mozilla::widget {
|
||||||
|
|
||||||
@@ -18,6 +20,14 @@ void NativeMenuSupport::CreateNativeMenuBar(nsIWidget* aParent,
|
|||||||
MOZ_RELEASE_ASSERT(NS_IsMainThread(),
|
MOZ_RELEASE_ASSERT(NS_IsMainThread(),
|
||||||
"Attempting to create native menu bar on wrong thread!");
|
"Attempting to create native menu bar on wrong thread!");
|
||||||
|
|
||||||
|
nsCOMPtr<nsINativeMenuService> nms =
|
||||||
|
do_GetService("@mozilla.org/widget/nativemenuservice;1");
|
||||||
|
if (!nms) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
nms->CreateNativeMenuBar(aParent, aMenuBarElement);
|
||||||
|
|
||||||
#ifdef MOZ_ENABLE_DBUS
|
#ifdef MOZ_ENABLE_DBUS
|
||||||
if (aMenuBarElement && StaticPrefs::widget_gtk_global_menu_enabled() &&
|
if (aMenuBarElement && StaticPrefs::widget_gtk_global_menu_enabled() &&
|
||||||
DBusMenuFunctions::Init()) {
|
DBusMenuFunctions::Init()) {
|
||||||
|
|||||||
31
widget/gtk/NativeMenuSupport.h
Normal file
31
widget/gtk/NativeMenuSupport.h
Normal file
@@ -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
|
||||||
@@ -115,6 +115,14 @@ Classes = [
|
|||||||
'headers': ['/widget/gtk/nsUserIdleServiceGTK.h'],
|
'headers': ['/widget/gtk/nsUserIdleServiceGTK.h'],
|
||||||
'constructor': 'nsUserIdleServiceGTK::GetInstance',
|
'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'):
|
if defined('NS_PRINTING'):
|
||||||
|
|||||||
@@ -106,6 +106,15 @@ UNIFIED_SOURCES += [
|
|||||||
|
|
||||||
SOURCES += [
|
SOURCES += [
|
||||||
"MediaKeysEventSourceFactory.cpp",
|
"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
|
"nsNativeThemeGTK.cpp", # conflicts with X11 headers
|
||||||
"nsWindow.cpp", # conflicts with X11 headers
|
"nsWindow.cpp", # conflicts with X11 headers
|
||||||
"WaylandVsyncSource.cpp", # conflicts with X11 headers
|
"WaylandVsyncSource.cpp", # conflicts with X11 headers
|
||||||
@@ -157,6 +166,7 @@ LOCAL_INCLUDES += [
|
|||||||
"/layout/base",
|
"/layout/base",
|
||||||
"/layout/forms",
|
"/layout/forms",
|
||||||
"/layout/generic",
|
"/layout/generic",
|
||||||
|
"/layout/style",
|
||||||
"/layout/xul",
|
"/layout/xul",
|
||||||
"/other-licenses/atk-1.0",
|
"/other-licenses/atk-1.0",
|
||||||
"/third_party/cups/include",
|
"/third_party/cups/include",
|
||||||
|
|||||||
61
widget/gtk/nsDbusmenu.cpp
Normal file
61
widget/gtk/nsDbusmenu.cpp
Normal file
@@ -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 < std::size(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;
|
||||||
|
}
|
||||||
101
widget/gtk/nsDbusmenu.h
Normal file
101
widget/gtk/nsDbusmenu.h
Normal file
@@ -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__ */
|
||||||
795
widget/gtk/nsMenu.cpp
Normal file
795
widget/gtk/nsMenu.cpp
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
123
widget/gtk/nsMenu.h
Normal file
123
widget/gtk/nsMenu.h
Normal file
@@ -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__ */
|
||||||
548
widget/gtk/nsMenuBar.cpp
Normal file
548
widget/gtk/nsMenuBar.cpp
Normal file
@@ -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 = 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();
|
||||||
|
}
|
||||||
111
widget/gtk/nsMenuBar.h
Normal file
111
widget/gtk/nsMenuBar.h
Normal file
@@ -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__ */
|
||||||
170
widget/gtk/nsMenuContainer.cpp
Normal file
170
widget/gtk/nsMenuContainer.cpp
Normal file
@@ -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;
|
||||||
|
}
|
||||||
70
widget/gtk/nsMenuContainer.h
Normal file
70
widget/gtk/nsMenuContainer.h
Normal file
@@ -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__ */
|
||||||
766
widget/gtk/nsMenuItem.cpp
Normal file
766
widget/gtk/nsMenuItem.cpp
Normal file
@@ -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 < std::size(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 < std::size(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;
|
||||||
|
}
|
||||||
80
widget/gtk/nsMenuItem.h
Normal file
80
widget/gtk/nsMenuItem.h
Normal file
@@ -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__ */
|
||||||
654
widget/gtk/nsMenuObject.cpp
Normal file
654
widget/gtk/nsMenuObject.cpp
Normal file
@@ -0,0 +1,654 @@
|
|||||||
|
/* -*- 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mImageRequest) {
|
||||||
|
mImageRequest->Cancel(NS_BINDING_ABORTED);
|
||||||
|
mImageRequest = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
mURI = uri;
|
||||||
|
|
||||||
|
if (imageRequest) {
|
||||||
|
imageRequest->Clone(this, nullptr, getter_AddRefs(mImageRequest));
|
||||||
|
} else {
|
||||||
|
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, 0, dom::FetchPriority::Auto,
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
169
widget/gtk/nsMenuObject.h
Normal file
169
widget/gtk/nsMenuObject.h
Normal file
@@ -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__ */
|
||||||
82
widget/gtk/nsMenuSeparator.cpp
Normal file
82
widget/gtk/nsMenuSeparator.cpp
Normal file
@@ -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;
|
||||||
|
}
|
||||||
37
widget/gtk/nsMenuSeparator.h
Normal file
37
widget/gtk/nsMenuSeparator.h
Normal file
@@ -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__ */
|
||||||
346
widget/gtk/nsNativeMenuDocListener.cpp
Normal file
346
widget/gtk/nsNativeMenuDocListener.cpp
Normal file
@@ -0,0 +1,346 @@
|
|||||||
|
/* -*- 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::ContentWillBeRemoved(nsIContent* aChild, const BatchRemovalState*)
|
||||||
|
{
|
||||||
|
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(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();
|
||||||
|
}
|
||||||
152
widget/gtk/nsNativeMenuDocListener.h
Normal file
152
widget/gtk/nsNativeMenuDocListener.h
Normal file
@@ -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__ */
|
||||||
478
widget/gtk/nsNativeMenuService.cpp
Normal file
478
widget/gtk/nsNativeMenuService.cpp
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
85
widget/gtk/nsNativeMenuService.h
Normal file
85
widget/gtk/nsNativeMenuService.h
Normal file
@@ -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__ */
|
||||||
@@ -7299,6 +7299,10 @@ void nsWindow::HideWindowChrome(bool aShouldHide) {
|
|||||||
SetWindowDecoration(aShouldHide ? BorderStyle::None : mBorderStyle);
|
SetWindowDecoration(aShouldHide ? BorderStyle::None : mBorderStyle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void nsWindow::SetMenuBar(UniquePtr<nsMenuBar> aMenuBar) {
|
||||||
|
mMenuBar = std::move(aMenuBar);
|
||||||
|
}
|
||||||
|
|
||||||
bool nsWindow::CheckForRollup(gdouble aMouseX, gdouble aMouseY, bool aIsWheel,
|
bool nsWindow::CheckForRollup(gdouble aMouseX, gdouble aMouseY, bool aIsWheel,
|
||||||
bool aAlwaysRollup) {
|
bool aAlwaysRollup) {
|
||||||
LOG("nsWindow::CheckForRollup() aAlwaysRollup %d", aAlwaysRollup);
|
LOG("nsWindow::CheckForRollup() aAlwaysRollup %d", aAlwaysRollup);
|
||||||
|
|||||||
@@ -31,6 +31,8 @@
|
|||||||
#include "IMContextWrapper.h"
|
#include "IMContextWrapper.h"
|
||||||
#include "LookAndFeel.h"
|
#include "LookAndFeel.h"
|
||||||
|
|
||||||
|
#include "nsMenuBar.h"
|
||||||
|
|
||||||
#ifdef ACCESSIBILITY
|
#ifdef ACCESSIBILITY
|
||||||
# include "mozilla/a11y/LocalAccessible.h"
|
# include "mozilla/a11y/LocalAccessible.h"
|
||||||
#endif
|
#endif
|
||||||
@@ -246,6 +248,8 @@ class nsWindow final : public nsBaseWidget {
|
|||||||
nsresult MakeFullScreen(bool aFullScreen) override;
|
nsresult MakeFullScreen(bool aFullScreen) override;
|
||||||
void HideWindowChrome(bool aShouldHide) override;
|
void HideWindowChrome(bool aShouldHide) override;
|
||||||
|
|
||||||
|
void SetMenuBar(mozilla::UniquePtr<nsMenuBar> aMenuBar);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GetLastUserInputTime returns a timestamp for the most recent user input
|
* GetLastUserInputTime returns a timestamp for the most recent user input
|
||||||
* event. This is intended for pointer grab requests (including drags).
|
* event. This is intended for pointer grab requests (including drags).
|
||||||
@@ -970,6 +974,8 @@ class nsWindow final : public nsBaseWidget {
|
|||||||
|
|
||||||
static bool sTransparentMainWindow;
|
static bool sTransparentMainWindow;
|
||||||
|
|
||||||
|
mozilla::UniquePtr<nsMenuBar> mMenuBar;
|
||||||
|
|
||||||
#ifdef ACCESSIBILITY
|
#ifdef ACCESSIBILITY
|
||||||
RefPtr<mozilla::a11y::LocalAccessible> mRootAccessible;
|
RefPtr<mozilla::a11y::LocalAccessible> mRootAccessible;
|
||||||
|
|
||||||
|
|||||||
@@ -170,6 +170,11 @@ EXPORTS += [
|
|||||||
"PuppetWidget.h",
|
"PuppetWidget.h",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if toolkit == "gtk":
|
||||||
|
EXPORTS += [
|
||||||
|
"nsINativeMenuService.h",
|
||||||
|
]
|
||||||
|
|
||||||
EXPORTS.mozilla += [
|
EXPORTS.mozilla += [
|
||||||
"BasicEvents.h",
|
"BasicEvents.h",
|
||||||
"ClipboardContentAnalysisChild.h",
|
"ClipboardContentAnalysisChild.h",
|
||||||
|
|||||||
38
widget/nsINativeMenuService.h
Normal file
38
widget/nsINativeMenuService.h
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
/* -*- 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_INLINE_DECL_STATIC_IID(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;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif // nsINativeMenuService_h_
|
||||||
@@ -38,6 +38,14 @@
|
|||||||
// Menus
|
// 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}
|
// {F6CD4F21-53AF-11d2-8DC4-00609703C14E}
|
||||||
#define NS_POPUPMENU_CID \
|
#define NS_POPUPMENU_CID \
|
||||||
{0xf6cd4f21, 0x53af, 0x11d2, {0x8d, 0xc4, 0x0, 0x60, 0x97, 0x3, 0xc1, 0x4e}}
|
{0xf6cd4f21, 0x53af, 0x11d2, {0x8d, 0xc4, 0x0, 0x60, 0x97, 0x3, 0xc1, 0x4e}}
|
||||||
|
|||||||
9
xpcom/ds/NativeMenuAtoms.py
Normal file
9
xpcom/ds/NativeMenuAtoms.py
Normal file
@@ -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"),
|
||||||
|
]
|
||||||
@@ -13,6 +13,7 @@ from Atom import (
|
|||||||
PseudoElementAtom,
|
PseudoElementAtom,
|
||||||
)
|
)
|
||||||
from HTMLAtoms import HTML_PARSER_ATOMS
|
from HTMLAtoms import HTML_PARSER_ATOMS
|
||||||
|
from NativeMenuAtoms import NATIVE_MENU_ATOMS
|
||||||
|
|
||||||
# Static atom definitions, used to generate nsGkAtomList.h.
|
# Static atom definitions, used to generate nsGkAtomList.h.
|
||||||
#
|
#
|
||||||
@@ -2610,7 +2611,7 @@ STATIC_ATOMS = [
|
|||||||
InheritingAnonBoxAtom("AnonBox_mozSVGForeignContent", ":-moz-svg-foreign-content"),
|
InheritingAnonBoxAtom("AnonBox_mozSVGForeignContent", ":-moz-svg-foreign-content"),
|
||||||
InheritingAnonBoxAtom("AnonBox_mozSVGText", ":-moz-svg-text"),
|
InheritingAnonBoxAtom("AnonBox_mozSVGText", ":-moz-svg-text"),
|
||||||
# END ATOMS
|
# END ATOMS
|
||||||
] + HTML_PARSER_ATOMS
|
] + HTML_PARSER_ATOMS + NATIVE_MENU_ATOMS
|
||||||
# fmt: on
|
# fmt: on
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user