Breakpoints get incorrectly hit on page refresh

17 views
Skip to first unread message

Craig Mitchell

unread,
3:03 AM (10 hours ago) 3:03 AM
to GWT Users
When debugging in Chrome, sometimes my breakpoints get hit on a page refresh, but the code is not executed (nor should it be).

I reproduced the issue with the following:

mvn archetype:generate -DarchetypeGroupId=com.github.nalukit.archetype -DarchetypeVersion=LATEST -DarchetypeArtifactId=modular-springboot-webapp

(use any values, I just entered "a" for everything)

2. Modify the a-client\src\main\java\a\App.java with the following:

2.1 Add this static method:
public static void delay(int delayMs, com.google.gwt.user.client.Command run) {
com.google.gwt.core.client.Scheduler.get().scheduleFixedDelay(() -> {
run.execute();
return false;
}, delayMs);
}

2.2 In the MyHandler class onClick, call the static method:
public void onClick(ClickEvent event) {
delay(1000, () -> {
sendNameToServer();
});
}

3. Start it up:
3.1:  mvn gwt:codeserver -pl *-client -am
3.2:  mvn spring-boot:run -pl *-server -am

4. Open it in Chrome:  http://localhost:8080/

5. Open the Chrome debugger, put a breakpoint on the line:
delay(1000, () -> {

6. Refresh the page.  The breakpoint gets hit.  It looks like this:
Screenshot 2025-12-17 185721.png

It doesn't have a call stack, and if you try to step into it, it doesn't step into the delay method.

Any idea why this occurs?

Craig Mitchell

unread,
3:07 AM (10 hours ago) 3:07 AM
to GWT Users
I also just noticed, the breakpoint doesn't get hit when you click the "send" button on the page (when it should get hit).

Jens

unread,
9:35 AM (4 hours ago) 9:35 AM
to GWT Users
In such a case you can keep the breakpoint you set in the Java file in the browser and then open dev tools settings and disable sourcemaps. Then reload the page and you see the original JS code. To better understand it you can also add <style>PRETTY</style> to your root pom.xml below the <launcherDir> configuration of the gwt-maven-plugin.

You will see something like below with the bold line being the breakpoint you set (I have added some comments above the functions):

// thats the class initializer of MyHandler. It is empty because there is no static {} block in Java
function $clinit_App$1MyHandler_0_g$(){
  $clinit_App$1MyHandler_0_g$ = Object;
  $clinit_Object_0_g$();
}

// Thats the default constructor which receives everything that the nested MyHandler class can reach in the outer class App.
function App$1MyHandler_1_g$(this$0_0_g$, val$errorLabel_0_g$, val$nameField_0_g$, val$sendButton_0_g$, val$textToServerLabel_0_g$, val$serverResponseLabel_0_g$, val$dialogBox_0_g$, val$closeButton_0_g$){
  $clinit_App$1MyHandler_0_g$();
  this.this$01_1_g$ = this$0_0_g$;
  this.val$errorLabel2_0_g$ = val$errorLabel_0_g$;
  this.val$nameField3_0_g$ = val$nameField_0_g$;
  this.val$sendButton4_0_g$ = val$sendButton_0_g$;
  this.val$textToServerLabel5_0_g$ = val$textToServerLabel_0_g$;
  this.val$serverResponseLabel6_0_g$ = val$serverResponseLabel_0_g$;
  this.val$dialogBox7_0_g$ = val$dialogBox_0_g$;
  this.val$closeButton8_0_g$ = val$closeButton_0_g$;
  Object_1_g$.call(this);
  this.$init_1443_g$();
}

// defineClass creates an underscore variable which is used to define all the functions of MyHandler.
defineClass_0_g$(5, 1, {5:1, 757:1, 838:1, 891:1, 1:1}, App$1MyHandler_1_g$);

// thats the instance initializer of MyHandler called by the constructor above. It is empty because MyHandler does not have instance variables
_.$init_1443_g$ = function $init_3_g$(){
  $clinit_App$1MyHandler_0_g$();
}
;

// thats the lambda definition that you have used in the delay function. 
_.lambda$0_72_g$ = function lambda$0_1_g$(){
  $clinit_App$1MyHandler_0_g$();
  {
    this.sendNameToServer_1_g$();
  }
}
;

// Here you see how GWT uses the lambda you have defined. 
// GWT still uses for lambdas the same logic as for anonymous classes. 
// So App$1MyHandler$lambda$0$Type_1_g$ is actually a class implementing the functional interface 
// and it receives the outer scope (MyHandler) so its execute() method (from the interface) can 
// call the lambda defined above on MyHandler.
_.onClick_5_g$ = function onClick_1_g$(event_0_g$){
  delay_0_g$(1000, new App$1MyHandler$lambda$0$Type_1_g$(this));
}
;
_.onKeyUp_5_g$ = function onKeyUp_0_g$(event_0_g$){
  if (event_0_g$.getNativeKeyCode_1_g$() == 13) {
    this.sendNameToServer_1_g$();
  }
}
;
_.sendNameToServer_1_g$ = function sendNameToServer_0_g$(){
  $clinit_App$1MyHandler_0_g$();
  var textToServer_0_g$;
  this.val$errorLabel2_0_g$.setText_15_g$('');
  textToServer_0_g$ = this.val$nameField3_0_g$.getText_14_g$();
  if (!isValidName_0_g$(textToServer_0_g$)) {
    this.val$errorLabel2_0_g$.setText_15_g$('Please enter at least four characters');
    return;
  }
  this.val$sendButton4_0_g$.setEnabled_4_g$(false);
  this.val$textToServerLabel5_0_g$.setText_15_g$(textToServer_0_g$);
  this.val$serverResponseLabel6_0_g$.setText_15_g$('');
  this.this$01_1_g$.greetingService_0_g$.greetServer_1_g$(textToServer_0_g$, new App$1MyHandler$1_1_g$(this, this.val$dialogBox7_0_g$, this.val$serverResponseLabel6_0_g$, this.val$closeButton8_0_g$));
}
;

// this creates the class literal for MyHandler
var La_App$1MyHandler_2_classLit_0_g$ = createForClass_0_g$('a', 'App/1MyHandler', 5, Ljava_lang_Object_2_classLit_0_g$);



So when you place the breakpoint at the line you have used JS sourcemaps maps that line to the definition of the lambda and not the call to delay(). I am not sure if there is a right or wrong because it has to choose between the two and generally before you can call delay() the lambda must be created. So I think it is logical that way. In such cases you better place the breakpoint inside the delay() function.

-- J.

Reply all
Reply to author
Forward
0 new messages