Unexpected reload of Chrome extension written with PyScript

This one is for the intrepid troubleshooter. Recently, Pete Fison (with the assistance of Ted Patrick) wrote a quick tutorial on using PyScript to write Chrome extensions (Write Chrome Extensions in Python). I’ve been able to deliver static pop-ups (for example, having the extension display a random quote) without issue. When I try to incorporate user input, however, things don’t go as expected.

Below are the html and manifest files for a mortgage calculator extension (it is adapted from a PyScript webpage I wrote earlier). The pop-up provides fields for the user to enter details about a loan (loan amount, interest rate, term, etc.) and then on clicking the “Calculate Payment” button, is supposed to provide a payment breakdown. When run as a webpage, it performs exactly as expected. When run as an extension, it accepts user input as normal. However, when the “Complete Payment” button is clicked (which triggers the main PyScript function), the correctly formatted output flashes for an instant and then the pop-up reloads. I don’t know if it’s something in my html, something in the runtime, or maybe something in the Chrome extension API that forces the reload. Setting the onsubmit event to onsubmit="return false" doesn’t seem to have any effect on the extension. Any ideas as to why this is happening and/or how to prevent reload? Is there a PyScript equivalent to something like JavaScript’s preventDefault()?

Here are the scripts. To run them, your best bet is to follow the instructions in Fison’s article above to set up the runtime and then replace the index and manifest files with what’s below. Also, ignore all the class assignments in the index file. I’ve removed the CSS for simplicity’s sake.

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <title>Mortgage Calculator</title>
    <py-config>
      [[runtimes]]
      src = "runtime/pyodide.js"
      name = "pyodide-0.21.3"
      lang = "python"
    </py-config>
    <link rel="icon" type="image/png" href="/icon-128.png" />
    <!-- <link rel="stylesheet" href="styles.css" /> -->
    <script defer src="runtime/pyscript.js"></script>
  </head>
  <body>
    <div class="container">
        <!-- <h1>Mortgage Calculator</h1> -->
        <div class="row">
            <div class="col-sm-2"></div>
            <div class="col-sm-4 content-box">
                <form onsubmit="return false">
                
                    <label for="loan_amount" class="form-label">Loan Amount:&nbsp;&nbsp;</label><br>
                    <input type="text" id="loan_amount" name="loan_amount" style="outline: solid;" class="form-control" autofocus><br>
                    
                    <label for="interest" class="form-label">Interest Rate:&nbsp;&nbsp;</label><br>
                    <input type="text" id="interest" name="interest" style="outline: solid;" class="form-control"><br>
                    
                    <label for="term" class="form-label">Loan Term (in months):&nbsp;&nbsp;</label><br>
                    <input type="text" id="term" name="term" style="outline: solid;" class="form-control"><br>
                    
                    <label for="taxes" class="form-label">Annual Property Taxes:&nbsp;&nbsp;</label><br>
                    <input type="text" id="taxes" name="taxes" style="outline: solid;" value="0" class="form-control"><br>
                    
                    <label for="insurance" class="form-label">Annual Insurance Premium:&nbsp;&nbsp;</label><br>
                    <input type="text" id="insurance" name="insurance" style="outline: solid;" value="0" class="form-control"><br>
                    <br>
                    <button py-onClick="calculate()" type="submit" id="btn-form">Calculate Payment</button>
                    <!-- <input py-onClick="calculate()" type="submit" id="btn-form" value="Calculate Payment"> -->
                </form>            
            </div>
            <div class="col-sm-4 content-box">
                <h2>Total Monthly Payment</h2>
                <p id="pandi">&nbsp;</p>
                <p id="prop_taxes">&nbsp;</p>
                <p id="hoi">&nbsp;</p>
                <p id="total_pmt">&nbsp;</p>
            </div>
        </div>
  
        
    </div>

    <py-script>
      
      def calculate(*args, **kwargs):
          
          def monthly_payment(loan_amount, interest_rate, term):
              """ A helper function to the amortization function. """
              l = loan_amount
              i = (float(interest_rate) / 12) / 100 
              t = term
              # Formula for calculating the principal and interest payment for an 
              # amortized loan
              payment = (l * (i * (1 + i) ** t) / ((1 + i) ** t - 1)) 
              return round(payment, 2)

          l = float(Element('loan_amount').value)
          i = float(Element('interest').value)
          term = int(Element('term').value)
          taxes = float(Element('taxes').value)
          insurance = float(Element('insurance').value)

          p_and_i = monthly_payment(l, i, term)
          total_payment = p_and_i + round(taxes/12, 2) + round(insurance/12, 2)
          Element('pandi').write(f"Principal and Interest: ${p_and_i:.2f}")
          Element('prop_taxes').write(f"Taxes: ${round(taxes/12, 2):.2f}")
          Element('hoi').write(f"Insurance: ${round(insurance/12, 2):.2f}")
          Element('total_pmt').write(f"Total Payment: ${total_payment:.2f}")


  </py-script>
</body>
  <!-- <script src="finished.js"></script> -->
</html>

manifest.json

{
  "name": "Mortgage Calculator",
  "description": "Mortgage Calculator Exntension",
  "version": "1.0",
  "manifest_version": 3,
  "icons": {
    "16": "images/icon-16.png",
    "32": "images/icon-32.png",
    "48": "images/icon-48.png",
    "128": "images/icon-128.png"
  },
  "action": {
    "default_popup": "index.html",
    "default_icon": "images/icon-128.png"
  },
  "content_security_policy": {
    "extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'"
  }
}