Merge pull request 'Revert #277: no showmissed lock in betterbird's policy (issue 640)' (#281) from fix/640-revert-the-showmissed-lock into main
This commit was merged in pull request #281.
This commit is contained in:
@@ -97,7 +97,7 @@ start; `prefs.js` wins over a default.
|
||||
value true, false, a whole number or a string. The node-engine merges the layers key by key, so a node's layer
|
||||
overrides one preference, or takes one out with `null`. The operator's setup approved on 2026-10-12 (compact
|
||||
density, 14-point text, the vertical view with multi-line cards and threads, no new-mail pop-up, sound or tray
|
||||
icon, the calendar's day from 09:00 to 20:00, reminders missed while asleep shown (since locked off: issue 640), chat off, remote content
|
||||
icon, the calendar's day from 09:00 to 20:00, reminders missed while asleep shown, chat off, remote content
|
||||
blocked, Do Not Track; 21 preferences) moved from the catalogue's defaults file into the mesh-wide layer of
|
||||
`prefs`, set on the operator's Approve.
|
||||
|
||||
@@ -109,24 +109,10 @@ desktop node writes the map's text into the old build's user.js, and `betterbird
|
||||
refused until the push.
|
||||
|
||||
**Locked** (the policy): Betterbird's updater off (`DisableAppUpdate`); telemetry off (`DisableTelemetry`);
|
||||
add-ons change only through the mesh's pins (`ExtensionUpdate` false); and four preferences locked off:
|
||||
add-ons change only through the mesh's pins (`ExtensionUpdate` false); and three preferences locked off:
|
||||
`mail.shell.checkDefaultClient` (the ask to be the default client), `mail.inappnotifications.enabled` (the
|
||||
vendor's in-app notices), `mailnews.start_page.enabled` (the start page, which otherwise loads from the
|
||||
vendor at every start), and `calendar.alarms.showmissed` (the reminders of meetings that have passed, below).
|
||||
|
||||
**A passed meeting's reminder is not shown again** (novox/hq issue 640). With `calendar.alarms.showmissed` on,
|
||||
Betterbird's alarm service fires, at every load of a calendar (each start, each refresh, and each time Owl
|
||||
registers its calendar after signing in), every reminder of the past month whose item carries no later
|
||||
acknowledgement (`X-MOZ-LASTACK`). Off, Betterbird's own default, a past reminder fires only while its meeting
|
||||
has not ended. A dismissal is that acknowledgement, and Owl never sends it to Exchange (its changes carry the
|
||||
reminder's offset alone): it lives only in Betterbird's local calendar cache, which Owl empties when it
|
||||
resynchronises a calendar from scratch. With the pass on, Exchange meetings of the past month came back about
|
||||
every ten minutes on g14 from the start that placed the setting's `true`, Dismiss or not. So the policy locks the
|
||||
pass off, whatever the setting `prefs` or a profile says; a setting's value for it has no effect (`betterbird_config`
|
||||
shows the lock). What it costs: a reminder that fell due while the laptop slept is not shown once the meeting
|
||||
is over; one for a meeting still running is. `betterbird_check` says when the running Betterbird still has the
|
||||
pass on beside an Owl calendar, with the remedy (the module's push, or `betterbird_restart` once the policy is
|
||||
placed).
|
||||
vendor's in-app notices), and `mailnews.start_page.enabled` (the start page, which otherwise loads from the
|
||||
vendor at every start).
|
||||
|
||||
**Kept at Betterbird's default** (`// keeps Betterbird's default: <name>` in `mesh-defaults.js`): the theme,
|
||||
system colours, always-underlined links, manual spam marking, and the composer's attachment pane. A value the
|
||||
|
||||
@@ -981,9 +981,6 @@ func (m *Machine) Check() (CheckAnswer, error) {
|
||||
"take them out of install/defaults-pref/mesh-defaults.js in a reviewed catalogue change: an account is declared in the setting accounts")
|
||||
}
|
||||
}
|
||||
if what, do := m.missedReminders(profilePath); what != "" {
|
||||
add(what, do)
|
||||
}
|
||||
if why := m.defaultClientAsk(profilePath); why != "" && in.From != "not installed" {
|
||||
add("Betterbird asks at its start to be the default mail client: "+why,
|
||||
"push the module to this node: its policy locks "+defaultClientPref+" off (novox/hq issue 345)")
|
||||
|
||||
@@ -249,8 +249,8 @@ func TestTheConfigShowsTheLayersAndTheDrift(t *testing.T) {
|
||||
f.withDefaults()
|
||||
f.betterbirdsOwn()
|
||||
links(t, nil)
|
||||
f.declare(`user_pref("mail.uifontsize", 16); user_pref("mail.biff.show_alert", false); user_pref("calendar.alarms.playsound", false);`)
|
||||
f.write(profileDir+"/user.js", mark+")\n"+`user_pref("mail.uifontsize", 16);`+"\n"+`user_pref("calendar.alarms.playsound", false);`+"\n")
|
||||
f.declare(`user_pref("mail.uifontsize", 16); user_pref("mail.biff.show_alert", false); user_pref("calendar.alarms.showmissed", true);`)
|
||||
f.write(profileDir+"/user.js", mark+")\n"+`user_pref("mail.uifontsize", 16);`+"\n"+`user_pref("calendar.alarms.showmissed", true);`+"\n")
|
||||
f.write(profileDir+"/prefs.js", "// Mozilla User Preferences\n"+
|
||||
`user_pref("extensions.activeThemeID", "thunderbird-compact-dark@mozilla.org");`+"\n"+
|
||||
`user_pref("mail.uifontsize", 12);`+"\n"+
|
||||
@@ -266,7 +266,7 @@ func TestTheConfigShowsTheLayersAndTheDrift(t *testing.T) {
|
||||
for _, p := range c.Prefs {
|
||||
by[p.Name] = p
|
||||
}
|
||||
if len(c.Prefs) != len(lockedOff)+3+len(shippedKeepsWant) || !strings.HasPrefix(c.Defaults, "placed") {
|
||||
if len(c.Prefs) != 3+3+len(shippedKeepsWant) || !strings.HasPrefix(c.Defaults, "placed") {
|
||||
t.Fatalf("every lock, declared preference and kept default: %d, %s", len(c.Prefs), c.Defaults)
|
||||
}
|
||||
if !reflect.DeepEqual(c.Drifting, []string{"extensions.activeThemeID", "mail.spam.manualMark", "mail.biff.show_alert"}) {
|
||||
@@ -293,7 +293,7 @@ func TestTheConfigShowsTheLayersAndTheDrift(t *testing.T) {
|
||||
if p := by["mailnews.start_page.enabled"]; p.Layer != "locked" || p.Effective != "false" || p.Drift != "" {
|
||||
t.Errorf("a lock wins over the profile: %+v", p)
|
||||
}
|
||||
if p := by["calendar.alarms.playsound"]; p.Effective != "false" || p.Layer != "declared" {
|
||||
if p := by["calendar.alarms.showmissed"]; p.Effective != "true" || p.Layer != "declared" {
|
||||
t.Errorf("%+v", p)
|
||||
}
|
||||
// T5: the check says the drift, with both remedies.
|
||||
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -173,8 +172,8 @@ func (m *Machine) policyState() string {
|
||||
return "placed, and " + why
|
||||
}
|
||||
return "placed: Betterbird's updater, its telemetry and add-on updates off, Owl installed into every profile from " +
|
||||
"the mesh's xpi with its updates off, and the ask to be the default mail client, the vendor's in-app notices, " +
|
||||
"the start page and the reminders of meetings that have passed locked off"
|
||||
"the mesh's xpi with its updates off, and the ask to be the default mail client, the vendor's in-app notices " +
|
||||
"and the start page locked off"
|
||||
}
|
||||
|
||||
// lockedOff are the preferences the module's policy locks to false (novox/hq issue 345), each with what it is.
|
||||
@@ -182,70 +181,6 @@ var lockedOff = []struct{ Name, What string }{
|
||||
{defaultClientPref, "the ask to be the default mail client"},
|
||||
{"mail.inappnotifications.enabled", "the vendor's in-app notices"},
|
||||
{"mailnews.start_page.enabled", "the start page, loaded from the vendor at every start"},
|
||||
{missedPref, "the reminders of meetings that have passed (novox/hq issue 640)"},
|
||||
}
|
||||
|
||||
// missedPref is Betterbird's pass over missed reminders. With it on, the calendar's alarm service fires, at
|
||||
// every load of a calendar (each start, each refresh, each time Owl registers its calendar after signing in),
|
||||
// every reminder of the past month whose item carries no later acknowledgement (X-MOZ-LASTACK); with it off
|
||||
// (Betterbird's own default), a past reminder fires only while its meeting has not ended. Owl never sends a
|
||||
// dismissal to Exchange (its changes carry the reminder's offset alone) and keeps it only in Betterbird's local
|
||||
// cache, which a resynchronisation from scratch empties: a passed meeting's reminder then comes back at every
|
||||
// reload, Dismiss or not (novox/hq issue 640). The policy locks it off, so no setting and no profile can turn the
|
||||
// pass back on; betterbird_check says when the running Betterbird still has it on beside an Owl calendar.
|
||||
const missedPref = "calendar.alarms.showmissed"
|
||||
|
||||
// missedReminders says why the running Betterbird shows a passed meeting's reminder again at each reload of an
|
||||
// Owl calendar, with the remedy, or two empty strings: Betterbird's pass over missed reminders on (the profile's
|
||||
// user.js, which wins at the start, else its prefs.js; Betterbird's default is off) while the profile holds an
|
||||
// Owl calendar and the placed policy did not lock it off before Betterbird started.
|
||||
func (m *Machine) missedReminders(profile string) (string, string) {
|
||||
if profile == "" {
|
||||
return "", ""
|
||||
}
|
||||
prefs := mergedPrefs(profile)
|
||||
owl := 0
|
||||
for _, c := range profileCalendars(prefs) {
|
||||
if c.Kind == "owl" {
|
||||
owl++
|
||||
}
|
||||
}
|
||||
if owl == 0 {
|
||||
return "", ""
|
||||
}
|
||||
value, from := "false", "Betterbird's default"
|
||||
for _, f := range []string{"user.js", "prefs.js"} {
|
||||
b, err := readBounded(filepath.Join(profile, f))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if v, ok := statementsOf(b)[missedPref]; ok {
|
||||
value, from = v, "the profile's "+f
|
||||
break
|
||||
}
|
||||
}
|
||||
locked := m.locks()[missedPref] == "false"
|
||||
ps := m.mainProcs()
|
||||
if locked {
|
||||
if len(ps) == 0 {
|
||||
return "", ""
|
||||
}
|
||||
st, err := os.Stat(m.path(policyFile))
|
||||
t, terr := time.Parse(time.RFC3339, ps[0].Since)
|
||||
if err != nil || terr != nil || !st.ModTime().After(t) {
|
||||
return "", ""
|
||||
}
|
||||
}
|
||||
if value != "true" || len(ps) == 0 {
|
||||
return "", ""
|
||||
}
|
||||
what := fmt.Sprintf("Betterbird shows the reminders of meetings that have passed again at every reload of its %d Owl "+
|
||||
"calendar(s): %s is true (%s), and Owl keeps a dismissal only in Betterbird's local cache, never on the Exchange "+
|
||||
"server (novox/hq issue 640)", owl, missedPref, from)
|
||||
if locked {
|
||||
return what, "betterbird_restart: the module's policy locks " + missedPref + " off, and the running Betterbird started before it was placed"
|
||||
}
|
||||
return what, "push the module to this node: its policy locks " + missedPref + " off; then betterbird_restart"
|
||||
}
|
||||
|
||||
// policyProblem is everything the policy fails to say of what ADR 0316 and issue 345 ask, or "".
|
||||
|
||||
@@ -229,79 +229,3 @@ func mustRead(t *testing.T, f *fake, p string) []byte {
|
||||
}
|
||||
return raw
|
||||
}
|
||||
|
||||
// owlCalendar is an Owl calendar in the profile's prefs.js, as Owl registers its account's calendar.
|
||||
func (f *fake) owlCalendar(extra string) {
|
||||
f.write(profileDir+"/prefs.js", "// Mozilla User Preferences\n"+
|
||||
`user_pref("calendar.registry.45b5.type", "owl");`+"\n"+
|
||||
`user_pref("calendar.registry.45b5.uri", "owl://someone%40example%2Eorg@outlook.example.org/");`+"\n"+
|
||||
`user_pref("calendar.registry.45b5.name", "someone@example.org");`+"\n"+extra)
|
||||
}
|
||||
|
||||
// A passed meeting's reminder shows at most once: the policy locks Betterbird's pass over missed reminders off,
|
||||
// whatever the setting prefs or the profile say, because Owl keeps a dismissal only in Betterbird's local cache
|
||||
// and the pass fires every unacknowledged reminder of the past month again at each reload (novox/hq issue 640).
|
||||
func TestAPassedMeetingsReminderIsNotShownAgainAtEachReloadOfAnOwlCalendar(t *testing.T) {
|
||||
raw, err := os.ReadFile(filepath.Join("..", "..", "install", "distribution", "policies.json"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if p := policyPrefsOf(raw)[missedPref]; p.Value != "false" || !p.Locked {
|
||||
t.Fatalf("the module's policy locks %s off: %+v", missedPref, p)
|
||||
}
|
||||
before := strings.Replace(string(raw), `"calendar.alarms.showmissed"`, `"calendar.alarms.unrelated"`, 1)
|
||||
if why := policyProblem([]byte(before)); !strings.Contains(why, missedPref) {
|
||||
t.Fatalf("a policy that leaves the missed reminders on is said: %q", why)
|
||||
}
|
||||
|
||||
f := newFake(t)
|
||||
f.betterbird()
|
||||
f.placedByTheMesh()
|
||||
links(t, nil)
|
||||
nag := func() Finding {
|
||||
t.Helper()
|
||||
c, err := f.Check()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, x := range c.Findings {
|
||||
if strings.Contains(x.What, "Owl calendar") {
|
||||
return x
|
||||
}
|
||||
}
|
||||
return Finding{}
|
||||
}
|
||||
// g14 on 2026-10-13: the module's user.js turns the pass on, an Owl calendar is in the profile, and the
|
||||
// placed policy does not lock it.
|
||||
f.write(policyFile, before)
|
||||
f.owlCalendar("")
|
||||
f.write(profileDir+"/user.js", mark+")\n"+`user_pref("calendar.alarms.showmissed", true);`+"\n")
|
||||
if x := nag(); !strings.Contains(x.What, "1 Owl calendar") || !strings.Contains(x.What, "user.js") ||
|
||||
!strings.Contains(x.Do, "push the module") {
|
||||
t.Fatalf("the nag is a finding with its remedy: %+v", x)
|
||||
}
|
||||
// The policy placed after the running Betterbird started: in effect from its next start.
|
||||
f.write(policyFile, string(raw))
|
||||
later := time.Unix(1_900_000_000, 0)
|
||||
os.Chtimes(filepath.Join(f.Root, policyFile), later, later)
|
||||
if x := nag(); !strings.Contains(x.Do, "betterbird_restart") || strings.Contains(x.Do, "push") {
|
||||
t.Fatalf("a lock placed after the start waits for a restart: %+v", x)
|
||||
}
|
||||
// The lock in effect: nothing the profile says turns the pass back on.
|
||||
f.write(policyFile, string(raw))
|
||||
f.owlCalendar(`user_pref("calendar.alarms.showmissed", true);` + "\n")
|
||||
if x := nag(); x.What != "" {
|
||||
t.Fatalf("the lock holds whatever the profile says: %+v", x)
|
||||
}
|
||||
// Without the lock, the pass off is no nag, and neither is the pass on without an Owl calendar.
|
||||
f.write(policyFile, before)
|
||||
f.write(profileDir+"/user.js", mark+")\n"+`user_pref("calendar.alarms.showmissed", false);`+"\n")
|
||||
if x := nag(); x.What != "" {
|
||||
t.Fatalf("the pass off: %+v", x)
|
||||
}
|
||||
f.write(profileDir+"/user.js", mark+")\n"+`user_pref("calendar.alarms.showmissed", true);`+"\n")
|
||||
f.write(profileDir+"/prefs.js", `user_pref("calendar.registry.c1.type", "caldav");`+"\n"+`user_pref("calendar.registry.c1.uri", "https://dav.example.org/c1/");`+"\n")
|
||||
if x := nag(); x.What != "" {
|
||||
t.Fatalf("a CalDAV calendar keeps its dismissals on its server: %+v", x)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,10 +22,6 @@
|
||||
"mailnews.start_page.enabled": {
|
||||
"Value": false,
|
||||
"Status": "locked"
|
||||
},
|
||||
"calendar.alarms.showmissed": {
|
||||
"Value": false,
|
||||
"Status": "locked"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user