|
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 ---").
|