Project

General

Profile

test-11180-g-teardown-mouse-decoy.p

Sergey Ivanovskiy, 08/17/2026 05:51 AM

Download (14.8 KB)

 
1
/*------------------------------------------------------------------------------
2
  test-11180-g-teardown-mouse-decoy.p                    (mouse/keyboard path)
3

    
4
  ONE-VARIABLE VARIANT of test-11180-g-teardown-mouse.p.  Identical topology -
5
  secondary WINDOW W2 holds frmO + fO, focus is parked in fO, fO's frame LEAVE
6
  DELETEs W2 with NO APPLY, and the transfer is driven by a physical CLICK on
7
  fT in the default window - EXCEPT that frmT now contains a DECOY enabled
8
  fill-in (fDecoy) placed AHEAD of fT, both in the frame definition and on
9
  screen.
10

    
11
  WHY THE DECOY EXISTS (root cause measured with jdb against FWD 16725 on
12
  2026-08-17, client PID 47876):
13

    
14
  The parent probe's missing frmT/fT ENTRY is NOT a FocusManager defect -
15
  FocusManager.handleFocusChange is never called at all on that path.  The
16
  actual chain, taken from a live breakpoint stack, is:
17

    
18
      WindowManager.processWindowEvent:2320   sendLeave(frmO, null)
19
        ThinClient.sendLeave                  posts the LEAVE event
20
          ThinClient.invokeTriggers -> RPC    the 4GL oLeave runs on the server
21
            ThinClient.removeWindow:11927     <- DELETE WIDGET hWin2 calls back
22
              WindowManager.removeWindow -> remove -> WindowGuiImpl.destroy:1417
23
                WindowManager.setFocusWindow:1179 -> :1206  window.requestFocus()
24
                  ... -> AbstractContainer.setFocusInt(fT)
25

    
26
  WindowGuiImpl.destroy ends with setFocusWindow(getActiveWindow()), whose
27
  one-arg overload passes focusFirstWidget = TRUE (verified live), so :1206
28
  calls window.requestFocus() and the focus is driven down to the FIRST
29
  FOCUSABLE WIDGET of the newly active window.  requestFocus is a raw
30
  assignment: it emits NO frame ENTRY and NO widget ENTRY.
31

    
32
  In the parent probe the default window's first focusable widget IS fT - the
33
  very widget being clicked.  So by the time the mouse PRESSED is processed,
34
  oldFocus == newFocus == fT, the guard at ThinClient.java:22677 fails on
35
  "newFocus != oldFocus", handleFocusChange is skipped, and the click's ENTRYs
36
  are never dispatched.  FWD gets the right answer for the wrong reason only
37
  when the teardown fallback happens to land somewhere else.
38

    
39
  THE VARIABLE THIS PROBE CHANGES: make the first focusable widget NOT be the
40
  click target.  The teardown fallback should then land on fDecoy, leaving
41
  oldFocus = fDecoy != newFocus = fT at the press, so handleFocusChange DOES
42
  run and the click is honoured.
43

    
44
  HOW TO READ THE FWD LOG (the discrimination this probe exists for):
45

    
46
    * fDecoy LEAVE then fT ENTRY, and NO fDecoy ENTRY
47
        -> mechanism CONFIRMED.  The fallback landed on fDecoy silently (no
48
           ENTRY, exactly as the debugger showed the raw requestFocus does),
49
           and the click then drove a real focus change.  Note frmT ENTRY is
50
           NOT expected here: fDecoy and fT share frmT, so oldFrame ==
51
           newFrame and handleFocusChange skips both frame-level branches.
52
    * nothing after "frmO LEAVE (end...)", exactly like the parent probe
53
        -> mechanism REFUTED.  The fallback did not land on fDecoy, so
54
           "first focusable widget of the newly active window" is the wrong
55
           description of what requestFocus resolves to.
56
    * fDecoy ENTRY appears
57
        -> the teardown fallback is NOT silent after all, which contradicts the
58
           breakpoint evidence and would need re-measuring.
59

    
60
  NATIVE OPENEDGE BASELINE - MEASURED 2026-08-17 (verbatim):
61

    
62
      === test-11180-g-teardown-mouse-decoy start ===
63
      01 --- APPLY ENTRY to fO (park focus in secondary window W2) ---
64
      02 --- now CLICK fT - the SECOND field - in the default window ---
65
      03 frmT   ENTRY
66
      04 fDecoy ENTRY
67
      05 frmO   LEAVE  (begin - delete secondary WINDOW W2, NO APPLY)
68
      06 frmO   LEAVE  (end - no APPLY performed)
69
      07 fDecoy LEAVE
70
      08 fT     ENTRY
71
      09 --- F5: settled FOCUS is now: FILL-IN/fT      (2nd run; F5 pressed)
72

    
73
  Reproduced twice on 2026-08-17; lines 01-08 identical both times.  Line 09
74
  appears only when F5 is pressed after the click and before closing.
75

    
76
  SETTLED FOCUS AGREES ACROSS ENGINES: OE reports FILL-IN/fT, and the FWD trace
77
  reports settledFocus=fT.  So this is a TRIGGER-VISIBILITY divergence (two
78
  events never emitted), NOT a focus-state divergence - application code that
79
  only reads FOCUS sees the same thing on both engines.
80

    
81
  F5 ALSO ACTS AS A CONTROL: it produces no ENTRY/LEAVE of its own, only its
82
  own log line.  That matters for the PARENT probe, whose trailing fT ENTRY was
83
  once attributed to the ESC keypress that closes the window.  Line 08 here
84
  arrives BEFORE the F5 line, so on this shape the trailing ENTRY is
85
  click-driven; the parent's line 07 is the same step, which on its
86
  single-field frame shows up as a second fT ENTRY with no matching LEAVE
87
  (focus never left fT).  Do not re-attribute it to ESC.
88

    
89
  THIS FALSIFIED THE HYPOTHESIS THIS PROBE WAS WRITTEN WITH.  The guess was
90
  that OE would ignore fDecoy and enter the clicked widget directly, as the
91
  parent probe's log appears to show.  It does not.  OE enters the target
92
  FRAME at its FIRST ENABLED FIELD (03, 04), runs the source frame's LEAVE
93
  (05, 06), and only then moves WITHIN the frame to the widget actually
94
  clicked (07, 08).  The parent probe cannot show this: frmT has exactly one
95
  field there, so "first field" and "clicked field" are the same widget and
96
  the two steps collapse onto it.
97

    
98
  Consequence for the FWD reading: the teardown fallback landing on the first
99
  focusable widget is NOT a wrong destination - OE goes there too.  The defect
100
  is that WindowGuiImpl.destroy -> requestFocus performs that step SILENTLY,
101
  losing OE's lines 03 and 04, while the second move (07, 08) does go through
102
  handleFocusChange and should be emitted correctly.  In the single-field
103
  parent probe the silent step IS the whole transfer, which is why every event
104
  disappeared there.
105

    
106
  FWD 16725 RESULT - MEASURED 2026-08-17 (verbatim):
107

    
108
      === test-11180-g-teardown-mouse-decoy start ===
109
      01 --- APPLY ENTRY to fO (park focus in secondary window W2) ---
110
      02 --- now CLICK fT - the SECOND field - in the default window ---
111
      03 frmO   LEAVE  (begin - delete secondary WINDOW W2, NO APPLY)
112
      04 frmO   LEAVE  (end - no APPLY performed)
113
      05 fDecoy LEAVE
114
      06 fT     ENTRY
115
      07 --- F9: settled FOCUS is now: FILL-IN/fT
116

    
117
  Line 07 is the F9 settled-focus probe (F5 is unusable here - it reloads the
118
  browser page; see the trigger comment below).  It confirms 4GL-side, not just
119
  from the Java trace, that FWD ends on the SAME widget as OE.
120

    
121
  TRUNK CONTROL - MEASURED 2026-08-17: an FWD *trunk* web client produces this
122
  log byte-identically (verified with cmp: deploy/client/... vs
123
  docs/11180b/logs/...).  Two consequences:
124
    * the divergence is PRE-EXISTING IN TRUNK, not a regression introduced by
125
      the #11180 work, so it can be filed and scheduled independently;
126
    * the temporary [11180m] instrumentation carried by the 11180b build is
127
      side-effect free - the uninstrumented trunk jar behaves the same.
128
  The parent probe already showed the same trunk parity (docs/p2j/logs/ and
129
  docs/11180c/logs/ test-11180-g-mouse.log).
130

    
131
  WATCH THE DIRECTORY WHEN COMPARING LOGS: under docs/11180b/, the "4gl/"
132
  subdirectory holds the NATIVE OPENEDGE baseline (9 numbered lines here) while
133
  "logs/" holds the FWD run (7 numbered lines).  Both paths are branch-scoped by
134
  name, so a 9-vs-7 line difference between them is the OE-vs-FWD divergence -
135
  NOT a trunk-vs-branch difference.
136

    
137
  MECHANISM CONFIRMED - this is the "fDecoy LEAVE then fT ENTRY, and NO fDecoy
138
  ENTRY" row of the table above.  FWD is missing EXACTLY OE's lines 03 and 04
139
  (frmT ENTRY + fDecoy ENTRY) and matches OE's 05-08 in order.  Client trace
140
  (tag [11180m]) shows the whole chain: the teardown fallback set globalFocus =
141
  fDecoy silently; the ACTIVATE arm sent only the window ENTRY; the mouse
142
  PRESSED then saw old=fDecoy != new=fT so handleFocusChange DID run, emitted
143
  the widget LEAVE, correctly SKIPPED the frame ENTRY (oldFrame == newFrame)
144
  and emitted fT's ENTRY.
145

    
146
  Why the parent probe loses THREE events and this one only TWO: same cause.
147
  There the silent fallback lands on fT itself, so the press sees
148
  old == new, handleFocusChange is skipped, and the within-frame move that
149
  produces OE's line 07 never happens either.
150

    
151
  Note the ORDERING difference this probe exposes, which no minimal fix closes:
152
  OE enters the target frame at click time, BEFORE the source frame's LEAVE
153
  runs.  FWD re-homes focus only as a side effect of the window destroy, which
154
  happens INSIDE that LEAVE.  So setFocusWindow(..., false) at
155
  WindowGuiImpl.java:1417 would make the events exist but in the wrong order,
156
  and would skip the first-field step entirely.
157

    
158
  NB the "<==" annotations on the log lines below were written FWD-first, from
159
  the pre-measurement hypothesis.  On OE, line 04 "fDecoy ENTRY ... (fallback
160
  NOT silent)" is NORMAL frame-entry behaviour, not an anomaly - read it as
161
  "OE announces the first-field entry".  The strings are deliberately left
162
  unchanged so this baseline stays byte-comparable with future runs.
163

    
164
  Steps to drive (GUI - NOT head-less, needs a real GUI driver):
165
    1. Focus starts in fO (secondary window W2).
166
    2. On FWD, drag WINDOW-2 aside first (it opens IN FRONT of the default
167
       window and occludes frmT; on OE it opens behind and no drag is needed).
168
       See auto/gmouse_drive.cjs.  NB the click coordinate is NOT the parent
169
       probe's [40,137] - frmT now has two rows and fT sits one row LOWER.
170
       Take the b_dragged screenshot and read the target off it.
171
    3. CLICK fT - the SECOND field.  Do NOT click fDecoy.
172
    4. Log the settled focus (the parent probe could never capture it: ESC
173
       aborts the procedure before the post-WAIT-FOR statements run, so the
174
       trailing "done" line never appears).  Press the key AFTER the click and
175
       BEFORE closing.
176
         * on FWD (web client): press F9.  DO NOT press F5 - the browser
177
           reloads the page; FWD only suppresses the browser default for TAB
178
           and F3.
179
         * on OE: either works.  The banked line 09 below was taken with F5.
180
    5. Close the window to end.
181

    
182
  Log to test-11180-g-mouse-decoy.log.
183
------------------------------------------------------------------------------*/
184

    
185
DEFINE VARIABLE seq AS INTEGER NO-UNDO.
186

    
187
DEFINE VARIABLE hWin2 AS HANDLE NO-UNDO.
188
DEFINE VARIABLE hFrmO AS HANDLE NO-UNDO.
189
DEFINE VARIABLE hFO   AS HANDLE NO-UNDO.
190

    
191
DEFINE VARIABLE fDecoy AS CHARACTER NO-UNDO FORMAT "x(8)" INITIAL "ddd".
192
DEFINE VARIABLE fT     AS CHARACTER NO-UNDO FORMAT "x(8)" INITIAL "ttt".
193

    
194
/* fDecoy is FIRST in the frame definition AND first on screen, so it is the
195
   first focusable widget of the default window - the slot the teardown
196
   fallback resolves to. */
197
DEFINE FRAME frmT
198
   fDecoy AT ROW 1 COLUMN 2
199
   fT     AT ROW 2 COLUMN 2
200
   WITH TITLE "FRAME-T (decoy first, click target second)"
201
   AT ROW 3 COLUMN 2 SIZE 44 BY 6 SIDE-LABELS.
202

    
203
PROCEDURE logit:
204
   DEFINE INPUT PARAMETER m AS CHARACTER NO-UNDO.
205
   seq = seq + 1.
206
   OUTPUT TO "test-11180-g-mouse-decoy.log" APPEND.
207
   PUT UNFORMATTED STRING(seq, "99") " " m SKIP.
208
   OUTPUT CLOSE.
209
END PROCEDURE.
210

    
211
/* NB FOCUS:NAME is the UNKNOWN value for a dynamic widget that was never given
212
   a NAME, and "..." + ? is unknown in 4GL, so the whole line would vanish. */
213
PROCEDURE logFocus:
214
   DEFINE INPUT PARAMETER m AS CHARACTER NO-UNDO.
215
   RUN logit(m + (IF VALID-HANDLE(FOCUS)
216
                  THEN STRING(FOCUS:TYPE)
217
                       + "/"
218
                       + (IF FOCUS:NAME = ? THEN "<noname>" ELSE FOCUS:NAME)
219
                  ELSE "<none/invalid>")).
220
END PROCEDURE.
221

    
222
/* fO's frame LEAVE (driven by the click): tear down window W2, NO APPLY */
223
PROCEDURE oLeave:
224
   RUN logit("frmO   LEAVE  (begin - delete secondary WINDOW W2, NO APPLY)").
225
   IF VALID-HANDLE(hWin2) THEN DELETE WIDGET hWin2.
226
   RUN logit("frmO   LEAVE  (end - no APPLY performed)").
227
END PROCEDURE.
228

    
229
ON ENTRY OF FRAME frmT
230
   RUN logit("frmT   ENTRY  <== target FRAME ENTRY (not expected on FWD: same frame)").
231
ON LEAVE OF FRAME frmT
232
   RUN logit("frmT   LEAVE").
233

    
234
/* The decoy: ENTRY here would mean the teardown fallback is NOT silent.
235
   LEAVE here is the signature that the fallback DID land on it and the click
236
   then drove a real focus change. */
237
ON ENTRY OF fDecoy IN FRAME frmT
238
   RUN logit("fDecoy ENTRY  <== fallback NOT silent (contradicts jdb evidence)").
239
ON LEAVE OF fDecoy IN FRAME frmT
240
   RUN logit("fDecoy LEAVE  <== fallback landed here, click drove a real change").
241

    
242
ON ENTRY OF fT IN FRAME frmT
243
   RUN logit("fT     ENTRY  <== clicked target ENTRY (dropped by the parent probe)").
244
ON LEAVE OF fT IN FRAME frmT
245
   RUN logit("fT     LEAVE").
246

    
247
/* Settled-focus probe: press the key AFTER the click and BEFORE closing.  Adds
248
   no event to the measured click window (verified on OE: the keypress emits
249
   only its own log line, no ENTRY/LEAVE).
250
   TWO keys are bound on purpose:
251
     F5 - what the banked OE baseline (line 09) was recorded with.  UNUSABLE on
252
          the FWD web client: p2j.keyboard.js forwards F5 to the server but its
253
          permittedKeyStrokes() only suppresses the browser default for TAB and
254
          F3, so Chrome reloads the page instead.
255
     F9 - use this one on FWD.  No Chrome shortcut, and 4GL reserves a default
256
          function only for F1 (HELP) and F2 (GO) - see Keyboard.java:1704 - so
257
          F3-F12 reach the application untouched.
258
   Each logs its own key name, so a log line is unambiguous about which was
259
   pressed and the F5 baseline text stays byte-identical. */
260
ON "F5":U ANYWHERE
261
   RUN logFocus("--- F5: settled FOCUS is now: ").
262
ON "F9":U ANYWHERE
263
   RUN logFocus("--- F9: settled FOCUS is now: ").
264

    
265
/* ---- main ---- */
266
OUTPUT TO "test-11180-g-mouse-decoy.log".
267
PUT UNFORMATTED "=== test-11180-g-teardown-mouse-decoy start ===" SKIP.
268
OUTPUT CLOSE.
269

    
270
ENABLE fDecoy fT WITH FRAME frmT.
271
VIEW FRAME frmT.
272

    
273
CREATE WINDOW hWin2
274
   ASSIGN TITLE = "WINDOW-2 (torn down in its own LEAVE)"
275
          WIDTH = 40 HEIGHT = 6 VISIBLE = FALSE.
276
CREATE FRAME hFrmO
277
   ASSIGN TITLE = "FRAME-O (source, in window 2)"
278
          PARENT = hWin2
279
          ROW = 2 COLUMN = 2 WIDTH = 34 HEIGHT = 4
280
          BOX = TRUE VISIBLE = FALSE.
281
CREATE FILL-IN hFO
282
   ASSIGN FRAME = hFrmO
283
          NAME = "fO" FORMAT = "x(8)" SCREEN-VALUE = "ooo"
284
          X = 4 Y = 4 WIDTH = 20
285
          SENSITIVE = TRUE VISIBLE = TRUE.
286
ON "LEAVE":U OF hFrmO PERSISTENT RUN oLeave.
287

    
288
hWin2:VISIBLE = TRUE.
289
hFrmO:VISIBLE = TRUE.
290

    
291
CURRENT-WINDOW = hWin2.
292

    
293
RUN logit("--- APPLY ENTRY to fO (park focus in secondary window W2) ---").
294
APPLY "ENTRY" TO hFO.
295
RUN logit("--- now CLICK fT - the SECOND field - in the default window ---").
296
PAUSE 0.
297
/* Keep the session alive so the tester can click fT; the LEAVE fires from the
298
   click, not from an APPLY.  Close the window to end. */
299
WAIT-FOR CLOSE OF THIS-PROCEDURE.
300

    
301
RUN logFocus("--- done; FOCUS is now: ").
302
RUN logit("--- OE baseline for this shape was UNMEASURED when written ---").