about summary refs log tree commit diff stats
path: root/src/html
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2022-11-28 19:52:10 +0100
committerbptato <nincsnevem662@gmail.com>2022-11-28 23:00:06 +0100
commiteb2e57c97eb67eec19f068e294a8f6d1375c82f5 (patch)
tree87156c515f6ee9a63f58dc080184bd3127ce6836 /src/html
parent8af10b8b74fd29fe4c9debcd5cbecfaddf53a7b5 (diff)
downloadchawan-eb2e57c97eb67eec19f068e294a8f6d1375c82f5.tar.gz
Add textarea
Editing is implemented using an external editor (like vi).
Diffstat (limited to 'src/html')
-rw-r--r--src/html/dom.nim157
-rw-r--r--src/html/htmlparser.nim74
-rw-r--r--src/html/tags.nim2
3 files changed, 154 insertions, 79 deletions
diff --git a/src/html/dom.nim b/src/html/dom.nim
index be66dc51..ab3b95be 100644
--- a/src/html/dom.nim
+++ b/src/html/dom.nim
@@ -1,3 +1,4 @@
+import macros
 import options
 import streams
 import strutils
@@ -91,10 +92,10 @@ type
   HTMLElement* = ref object of Element
 
   FormAssociatedElement* = ref object of HTMLElement
-    form*: HTMLFormElement
     parserInserted*: bool
 
   HTMLInputElement* = ref object of FormAssociatedElement
+    form* {.jsget.}: HTMLFormElement
     inputType*: InputType
     autofocus*: bool
     required*: bool
@@ -108,6 +109,7 @@ type
   HTMLAnchorElement* = ref object of HTMLElement
 
   HTMLSelectElement* = ref object of FormAssociatedElement
+    form* {.jsget.}: HTMLFormElement
     size*: int
 
   HTMLSpanElement* = ref object of HTMLElement
@@ -169,8 +171,15 @@ type
   HTMLAreaElement* = ref object of HTMLElement
 
   HTMLButtonElement* = ref object of FormAssociatedElement
+    form* {.jsget.}: HTMLFormElement
     ctype*: ButtonType
-    value*: string
+    value* {.jsget, jsset.}: string
+
+  HTMLTextAreaElement* = ref object of FormAssociatedElement
+    form* {.jsget.}: HTMLFormElement
+    rows*: int
+    cols*: int
+    value* {.jsget.}: string
 
 proc tostr(ftype: enum): string =
   return ($ftype).split('_')[1..^1].join("-").tolower()
@@ -312,6 +321,39 @@ iterator options*(select: HTMLSelectElement): HTMLOptionElement {.inline.} =
         if opt.tagType == TAG_OPTION:
           yield HTMLOptionElement(child)
 
+func form*(element: FormAssociatedElement): HTMLFormElement =
+  case element.tagType
+  of TAG_INPUT: return HTMLInputElement(element).form
+  of TAG_SELECT: return HTMLSelectElement(element).form
+  of TAG_BUTTON: return HTMLButtonElement(element).form
+  of TAG_TEXTAREA: return HTMLTextAreaElement(element).form
+  else: assert false
+
+func `form=`*(element: FormAssociatedElement, form: HTMLFormElement) =
+  case element.tagType
+  of TAG_INPUT: HTMLInputElement(element).form = form
+  of TAG_SELECT:  HTMLSelectElement(element).form = form
+  of TAG_BUTTON: HTMLButtonElement(element).form = form
+  of TAG_TEXTAREA: HTMLTextAreaElement(element).form = form
+  else: assert false
+
+func canSubmitImplicitly*(form: HTMLFormElement): bool =
+  const BlocksImplicitSubmission = {
+    INPUT_TEXT, INPUT_SEARCH, INPUT_URL, INPUT_TEL, INPUT_EMAIL, INPUT_PASSWORD,
+    INPUT_DATE, INPUT_MONTH, INPUT_WEEK, INPUT_TIME, INPUT_DATETIME_LOCAL,
+    INPUT_NUMBER
+  }
+  var found = false
+  for control in form.controls:
+    if control.tagType == TAG_INPUT:
+      let input = HTMLInputElement(control)
+      if input.inputType in BlocksImplicitSubmission:
+        if found:
+          return false
+        else:
+          found = true
+  return true
+
 func qualifiedName*(element: Element): string =
   if element.namespacePrefix.issome: element.namespacePrefix.get & ':' & element.localName
   else: element.localName
@@ -441,7 +483,7 @@ func nextElementSibling*(elem: Element): Element =
     inc i
   return nil
 
-func attr*(element: Element, s: string): string =
+func attr*(element: Element, s: string): string {.inline.} =
   return element.attributes.getOrDefault(s, "")
 
 func attri*(element: Element, s: string): Option[int] =
@@ -492,7 +534,7 @@ proc sheets*(element: Element): seq[CSSStylesheet] =
         result.add(child.sheet)
 
 func inputString*(input: HTMLInputElement): string =
-  var text = case input.inputType
+  case input.inputType
   of INPUT_CHECKBOX, INPUT_RADIO:
     if input.checked: "*"
     else: " "
@@ -510,7 +552,17 @@ func inputString*(input: HTMLInputElement): string =
     if input.file.isnone: "".padToWidth(input.size)
     else: input.file.get.path.serialize_unicode().padToWidth(input.size)
   else: input.value
-  return text
+
+func textAreaString*(textarea: HTMLTextAreaElement): string =
+  let split = textarea.value.split('\n')
+  for i in 0 ..< textarea.rows:
+    if textarea.cols > 2:
+      if i < split.len:
+        result &= '[' & split[i].padToWidth(textarea.cols - 2) & "]\n"
+      else:
+        result &= '[' & ' '.repeat(textarea.cols - 2) & "]\n"
+    else:
+      result &= "[]\n"
 
 func isButton*(element: Element): bool =
   if element.tagType == TAG_BUTTON:
@@ -537,6 +589,8 @@ func action*(element: Element): string =
     if element.form != nil:
       if element.form.attrb("action"):
         return element.form.attr("action")
+  if element.tagType == TAG_FORM:
+    return element.attr("action")
   return ""
 
 func enctype*(element: Element): FormEncodingType =
@@ -655,6 +709,8 @@ func newHTMLElement*(document: Document, tagType: TagType, namespace = Namespace
     result = new(HTMLBaseElement)
   of TAG_BUTTON:
     result = new(HTMLButtonElement)
+  of TAG_TEXTAREA:
+    result = new(HTMLTextAreaElement)
   else:
     result = new(HTMLElement)
 
@@ -886,6 +942,10 @@ proc resetElement*(element: Element) =
             if option.selected:
               option.selected = false
               inc j
+  of TAG_TEXTAREA:
+    let textarea = HTMLTextAreaElement(element)
+    textarea.value = textarea.childTextContent()
+    textarea.invalid = true
   else: discard
 
 proc setForm*(element: FormAssociatedElement, form: HTMLFormElement) =
@@ -902,7 +962,11 @@ proc setForm*(element: FormAssociatedElement, form: HTMLFormElement) =
     let button = HTMLButtonElement(element)
     button.form = form
     form.controls.add(button)
-  of TAG_FIELDSET, TAG_OBJECT, TAG_OUTPUT, TAG_TEXTAREA, TAG_IMG:
+  of TAG_TEXTAREA:
+    let textarea = HTMLTextAreaElement(element)
+    textarea.form = form
+    form.controls.add(textarea)
+  of TAG_FIELDSET, TAG_OBJECT, TAG_OUTPUT, TAG_IMG:
     discard #TODO
   else: assert false
 
@@ -935,9 +999,7 @@ proc insertionSteps(insertedNode: Node) =
         if select != nil:
           select.resetElement()
     else: discard
-    if tagType in FormAssociatedElements:
-      if tagType notin SupportedFormAssociatedElements:
-        return #TODO TODO TODO implement others too
+    if tagType in SupportedFormAssociatedElements:
       let element = FormAssociatedElement(element)
       if element.parserInserted:
         return
@@ -987,52 +1049,57 @@ proc reset*(form: HTMLFormElement) =
     control.resetElement()
     control.invalid = true
 
-proc appendAttribute*(element: Element, k, v: string) =
-  case k
-  of "id": element.id = v
-  of "class":
-    let classes = v.split(' ')
-    for class in classes:
-      if class != "" and class notin element.classList:
-        element.classList.add(class)
+proc appendAttributes*(element: Element, attrs: Table[string, string]) =
+  for k, v in attrs:
+    element.attributes[k] = v
+  template reflect_str(element: Element, name: static string, val: untyped) =
+    element.attributes.withValue(name, val):
+      element.val = val[]
+  template reflect_str(element: Element, name: static string, val, fun: untyped) =
+    element.attributes.withValue(name, val):
+      element.val = fun(val[])
+  template reflect_nonzero_int(element: Element, name: static string, val: untyped, default: int) =
+    element.attributes.withValue(name, val):
+      if val[].isValidNonZeroInt():
+        element.val = parseInt(val[])
+      else:
+        element.val = default
+    do:
+      element.val = default
+  template reflect_bool(element: Element, name: static string, val: untyped) =
+    if name in element.attributes:
+      element.val = true
+  element.reflect_str "id", id
+  element.attributes.withValue("class", val):
+    let classList = val[].split(' ')
+    for x in classList:
+      if x != "" and x notin element.classList:
+        element.classList.add(x)
   case element.tagType
   of TAG_INPUT:
     let input = HTMLInputElement(element)
-    case k
-    of "value": input.value = v
-    of "type": input.inputType = inputType(v)
-    of "size":
-      if v.isValidNonZeroInt():
-        input.size = parseInt(v)
-      else:
-        input.size = 20
-    of "checked": input.checked = true
+    input.reflect_str "value", value
+    input.reflect_str "type", inputType, inputType
+    input.reflect_nonzero_int "size", size, 20
+    input.reflect_bool "checked", checked
   of TAG_OPTION:
     let option = HTMLOptionElement(element)
-    if k == "selected":
-      option.selected = true
+    option.reflect_bool "selected", selected
   of TAG_SELECT:
     let select = HTMLSelectElement(element)
-    case k
-    of "multiple":
-      if not select.attributes["size"].isValidNonZeroInt():
-        select.size = 4
-    of "size":
-      if v.isValidNonZeroInt():
-        select.size = parseInt(v)
-      elif "multiple" in select.attributes:
-        select.size = 4
+    select.reflect_nonzero_int "size", size, (if "multiple" in element.attributes: 4 else: 1)
   of TAG_BUTTON:
     let button = HTMLButtonElement(element)
-    if k == "type":
-      case v
-      of "submit": button.ctype = BUTTON_SUBMIT
-      of "reset": button.ctype = BUTTON_RESET
-      of "button": button.ctype = BUTTON_BUTTON
-    elif k == "value":
-      button.value = v
+    button.reflect_str "type", ctype, (func(s: string): ButtonType =
+      case s
+      of "submit": return BUTTON_SUBMIT
+      of "reset": return BUTTON_RESET
+      of "button": return BUTTON_BUTTON)
+  of TAG_TEXTAREA:
+    let textarea = HTMLTextAreaElement(element)
+    textarea.reflect_nonzero_int "cols", cols, 20
+    textarea.reflect_nonzero_int "rows", rows, 1
   else: discard
-  element.attributes[k] = v
 
 # Forward definition hack (these are set in selectors.nim)
 var doqsa*: proc (node: Node, q: string): seq[Element]
diff --git a/src/html/htmlparser.nim b/src/html/htmlparser.nim
index 2b48c59d..e1359201 100644
--- a/src/html/htmlparser.nim
+++ b/src/html/htmlparser.nim
@@ -16,11 +16,13 @@ import utils/twtstr
 type
   DOMParser = ref object # JS interface
 
+  OpenElements = seq[Element]
+
   HTML5Parser = object
     case fragment: bool
     of true: ctx: Element
     else: discard
-    openElements: seq[Element]
+    openElements: OpenElements
     insertionMode: InsertionMode
     oldInsertionMode: InsertionMode
     templateModes: seq[InsertionMode]
@@ -196,8 +198,9 @@ func createElement(parser: HTML5Parser, token: Token, namespace: Namespace, inte
   let document = intendedParent.document
   let localName = token.tagname
   let element = document.newHTMLElement(localName, namespace, tagType = token.tagtype)
-  for k, v in token.attrs:
-    element.appendAttribute(k, v)
+  element.appendAttributes(token.attrs)
+  #for k, v in token.attrs:
+  #  element.appendAttribute(k, v)
   if element.isResettable():
     element.resetElement()
 
@@ -450,18 +453,23 @@ proc genericRCDATAElementParsingAlgorithm(parser: var HTML5Parser, token: Token)
   parser.oldInsertionMode = parser.insertionMode
   parser.insertionMode = TEXT
 
+proc popElement(parser: var HTML5Parser): Element =
+  result = parser.openElements.pop()
+  if result.tagType == TAG_TEXTAREA:
+    result.resetElement()
+
 # 13.2.6.3
 proc generateImpliedEndTags(parser: var HTML5Parser) =
   const tags = {TAG_DD, TAG_DT, TAG_LI, TAG_OPTGROUP, TAG_OPTION, TAG_P,
                 TAG_RB, TAG_RP, TAG_RT, TAG_RTC}
   while parser.currentNode.tagType in tags:
-    discard parser.openElements.pop()
+    discard parser.popElement()
 
 proc generateImpliedEndTags(parser: var HTML5Parser, exclude: TagType) =
   let tags = {TAG_DD, TAG_DT, TAG_LI, TAG_OPTGROUP, TAG_OPTION, TAG_P,
                 TAG_RB, TAG_RP, TAG_RT, TAG_RTC} - {exclude}
   while parser.currentNode.tagType in tags:
-    discard parser.openElements.pop()
+    discard parser.popElement()
 
 proc generateImpliedEndTagsThoroughly(parser: var HTML5Parser) =
   const tags = {TAG_CAPTION, TAG_COLGROUP, TAG_DD, TAG_DT, TAG_LI,
@@ -469,7 +477,7 @@ proc generateImpliedEndTagsThoroughly(parser: var HTML5Parser) =
                 TAG_RTC, TAG_TBODY, TAG_TD, TAG_TFOOT, TAG_TH, TAG_THEAD,
                 TAG_TR}
   while parser.currentNode.tagType in tags:
-    discard parser.openElements.pop()
+    discard parser.popElement()
 
 # 13.2.4.3
 proc pushOntoActiveFormatting(parser: var HTML5Parser, element: Element, token: Token) =
@@ -535,7 +543,7 @@ proc reconstructActiveFormatting(parser: var HTML5Parser) =
 proc clearActiveFormattingTillMarker(parser: var HTML5Parser) =
   while parser.activeFormatting.len > 0 and parser.activeFormatting.pop()[0] != nil: discard
 
-template pop_current_node = discard parser.openElements.pop()
+template pop_current_node = discard parser.popElement()
 
 func isHTMLIntegrationPoint(node: Element): bool =
   return false #TODO SVG (NOTE MathML not implemented)
@@ -849,7 +857,7 @@ proc processInHTMLContent(parser: var HTML5Parser, token: Token, insertionMode =
           parser.generateImpliedEndTagsThoroughly()
           if parser.currentNode.tagType != TAG_TEMPLATE:
             parse_error
-          while parser.openElements.pop().tagType != TAG_TEMPLATE: discard
+          while parser.popElement().tagType != TAG_TEMPLATE: discard
           parser.clearActiveFormattingTillMarker()
           discard parser.templateModes.pop()
           parser.resetInsertionMode()
@@ -918,7 +926,7 @@ proc processInHTMLContent(parser: var HTML5Parser, token: Token, insertionMode =
     proc closeP(parser: var HTML5Parser) =
       parser.generateImpliedEndTags(TAG_P)
       if parser.currentNode.tagType != TAG_P: parse_error
-      while parser.openElements.pop().tagType != TAG_P: discard
+      while parser.popElement().tagType != TAG_P: discard
 
     proc adoptionAgencyAlgorithm(parser: var HTML5Parser, token: Token): bool =
       if parser.currentNode.tagType != TAG_UNKNOWN and parser.currentNode.tagtype == token.tagtype or parser.currentNode.localName == token.tagname:
@@ -965,7 +973,7 @@ proc processInHTMLContent(parser: var HTML5Parser, token: Token, insertionMode =
             furthestBlockIndex = j
             break
         if furthestBlock == nil:
-          while parser.openElements.pop() != formatting: discard
+          while parser.popElement() != formatting: discard
           parser.activeFormatting.delete(formattingIndex)
           return false
         let commonAncestor = parser.openElements[stackIndex - 1]
@@ -1031,7 +1039,7 @@ proc processInHTMLContent(parser: var HTML5Parser, token: Token, insertionMode =
         if node.tagType != TAG_UNKNOWN and node.tagType == token.tagtype or node.localName == token.tagname:
           parser.generateImpliedEndTags(token.tagtype)
           if node != parser.currentNode: parse_error
-          while parser.openElements.pop() != node: discard
+          while parser.popElement() != node: discard
           break
         elif node.tagType in SpecialElements:
           parse_error
@@ -1149,7 +1157,7 @@ proc processInHTMLContent(parser: var HTML5Parser, token: Token, insertionMode =
           of TAG_LI:
             parser.generateImpliedEndTags(TAG_LI)
             if parser.currentNode.tagType != TAG_LI: parse_error
-            while parser.openElements.pop().tagType != TAG_LI: discard
+            while parser.popElement().tagType != TAG_LI: discard
             break
           of SpecialElements - {TAG_ADDRESS, TAG_DIV, TAG_P, TAG_LI}:
             break
@@ -1166,12 +1174,12 @@ proc processInHTMLContent(parser: var HTML5Parser, token: Token, insertionMode =
           of TAG_DD:
             parser.generateImpliedEndTags(TAG_DD)
             if parser.currentNode.tagType != TAG_DD: parse_error
-            while parser.openElements.pop().tagType != TAG_DD: discard
+            while parser.popElement().tagType != TAG_DD: discard
             break
           of TAG_DT:
             parser.generateImpliedEndTags(TAG_DT)
             if parser.currentNode.tagType != TAG_DT: parse_error
-            while parser.openElements.pop().tagType != TAG_DT: discard
+            while parser.popElement().tagType != TAG_DT: discard
             break
           of SpecialElements - {TAG_ADDRESS, TAG_DIV, TAG_P, TAG_DD, TAG_DT}:
             break
@@ -1190,7 +1198,7 @@ proc processInHTMLContent(parser: var HTML5Parser, token: Token, insertionMode =
         if parser.openElements.hasElementInScope(TAG_BUTTON):
           parse_error
           parser.generateImpliedEndTags()
-          while parser.openElements.pop().tagType != TAG_BUTTON: discard
+          while parser.popElement().tagType != TAG_BUTTON: discard
         parser.reconstructActiveFormatting()
         discard parser.insertHTMLElement(token)
         parser.framesetOk = false
@@ -1205,7 +1213,7 @@ proc processInHTMLContent(parser: var HTML5Parser, token: Token, insertionMode =
         else:
           parser.generateImpliedEndTags()
           if parser.currentNode.tagType != token.tagtype: parse_error
-          while parser.openElements.pop().tagType != token.tagtype: discard
+          while parser.popElement().tagType != token.tagtype: discard
       )
       "</form>" => (block:
         if not parser.openElements.hasElement(TAG_TEMPLATE):
@@ -1223,7 +1231,7 @@ proc processInHTMLContent(parser: var HTML5Parser, token: Token, insertionMode =
             return
           parser.generateImpliedEndTags()
           if parser.currentNode.tagType != TAG_FORM: parse_error
-          while parser.openElements.pop().tagType != TAG_FORM: discard
+          while parser.popElement().tagType != TAG_FORM: discard
       )
       "</p>" => (block:
         if not parser.openElements.hasElementInButtonScope(TAG_P):
@@ -1237,7 +1245,7 @@ proc processInHTMLContent(parser: var HTML5Parser, token: Token, insertionMode =
         else:
           parser.generateImpliedEndTags(TAG_LI)
           if parser.currentNode.tagType != TAG_LI: parse_error
-          while parser.openElements.pop().tagType != TAG_LI: discard
+          while parser.popElement().tagType != TAG_LI: discard
       )
       ("</dd>", "</dt>") => (block:
         if not parser.openElements.hasElementInScope(token.tagtype):
@@ -1245,7 +1253,7 @@ proc processInHTMLContent(parser: var HTML5Parser, token: Token, insertionMode =
         else:
           parser.generateImpliedEndTags(token.tagtype)
           if parser.currentNode.tagType != token.tagtype: parse_error
-          while parser.openElements.pop().tagType != token.tagtype: discard
+          while parser.popElement().tagType != token.tagtype: discard
       )
       ("</h1>", "</h2>", "</h3>", "</h4>", "</h5>", "</h6>") => (block:
         if not parser.openElements.hasElementInScope(HTagTypes):
@@ -1253,7 +1261,7 @@ proc processInHTMLContent(parser: var HTML5Parser, token: Token, insertionMode =
         else:
           parser.generateImpliedEndTags()
           if parser.currentNode.tagType != token.tagtype: parse_error
-          while parser.openElements.pop().tagType notin HTagTypes: discard
+          while parser.popElement().tagType notin HTagTypes: discard
       )
       "</sarcasm>" => (block:
         #*deep breath*
@@ -1321,7 +1329,7 @@ proc processInHTMLContent(parser: var HTML5Parser, token: Token, insertionMode =
         else:
           parser.generateImpliedEndTags()
           if parser.currentNode.tagType != token.tagtype: parse_error
-          while parser.openElements.pop().tagType != token.tagtype: discard
+          while parser.popElement().tagType != token.tagtype: discard
           parser.clearActiveFormattingTillMarker()
       )
       "<table>" => (block:
@@ -1504,7 +1512,7 @@ proc processInHTMLContent(parser: var HTML5Parser, token: Token, insertionMode =
         if not parser.openElements.hasElementInScope(TAG_TABLE):
           discard
         else:
-          while parser.openElements.pop().tagType != TAG_TABLE: discard
+          while parser.popElement().tagType != TAG_TABLE: discard
           parser.resetInsertionMode()
           reprocess token
       )
@@ -1512,7 +1520,7 @@ proc processInHTMLContent(parser: var HTML5Parser, token: Token, insertionMode =
         if not parser.openElements.hasElementInScope(TAG_TABLE):
           parse_error
         else:
-          while parser.openElements.pop().tagType != TAG_TABLE: discard
+          while parser.popElement().tagType != TAG_TABLE: discard
           parser.resetInsertionMode()
       )
       ("</body>", "</caption>", "</col>", "</colgroup>", "</html>", "</tbody>",
@@ -1587,7 +1595,7 @@ proc processInHTMLContent(parser: var HTML5Parser, token: Token, insertionMode =
         else:
           parser.generateImpliedEndTags()
           if parser.currentNode.tagType != TAG_CAPTION: parse_error
-          while parser.openElements.pop().tagType != TAG_CAPTION: discard
+          while parser.popElement().tagType != TAG_CAPTION: discard
           parser.clearActiveFormattingTillMarker()
           parser.insertionMode = IN_TABLE
       )
@@ -1728,7 +1736,7 @@ proc processInHTMLContent(parser: var HTML5Parser, token: Token, insertionMode =
     template close_cell() =
       parser.generateImpliedEndTags()
       if parser.currentNode.tagType notin {TAG_TD, TAG_TH}: parse_error
-      while parser.openElements.pop().tagType notin {TAG_TD, TAG_TH}: discard
+      while parser.popElement().tagType notin {TAG_TD, TAG_TH}: discard
       parser.clearActiveFormattingTillMarker()
       parser.insertionMode = IN_ROW
 
@@ -1739,7 +1747,7 @@ proc processInHTMLContent(parser: var HTML5Parser, token: Token, insertionMode =
         else:
           parser.generateImpliedEndTags()
           if parser.currentNode.tagType != token.tagtype: parse_error
-          while parser.openElements.pop().tagType != token.tagtype: discard
+          while parser.popElement().tagType != token.tagtype: discard
           parser.clearActiveFormattingTillMarker()
           parser.insertionMode = IN_ROW
       )
@@ -1799,13 +1807,13 @@ proc processInHTMLContent(parser: var HTML5Parser, token: Token, insertionMode =
         if not parser.openElements.hasElementInSelectScope(TAG_SELECT):
           parse_error
         else:
-          while parser.openElements.pop().tagType != TAG_SELECT: discard
+          while parser.popElement().tagType != TAG_SELECT: discard
           parser.resetInsertionMode()
       )
       "<select>" => (block:
         parse_error
         if parser.openElements.hasElementInSelectScope(TAG_SELECT):
-          while parser.openElements.pop().tagType != TAG_SELECT: discard
+          while parser.popElement().tagType != TAG_SELECT: discard
           parser.resetInsertionMode()
       )
       ("<input>", "<keygen>", "<textarea>") => (block:
@@ -1813,7 +1821,7 @@ proc processInHTMLContent(parser: var HTML5Parser, token: Token, insertionMode =
         if not parser.openElements.hasElementInSelectScope(TAG_SELECT):
           discard
         else:
-          while parser.openElements.pop().tagType != TAG_SELECT: discard
+          while parser.popElement().tagType != TAG_SELECT: discard
           parser.resetInsertionMode()
           reprocess token
       )
@@ -1826,7 +1834,7 @@ proc processInHTMLContent(parser: var HTML5Parser, token: Token, insertionMode =
       ("<caption>", "<table>", "<tbody>", "<tfoot>", "<thead>", "<tr>", "<td>",
        "<th>") => (block:
         parse_error
-        while parser.openElements.pop().tagType != TAG_SELECT: discard
+        while parser.popElement().tagType != TAG_SELECT: discard
         parser.resetInsertionMode()
         reprocess token
       )
@@ -1836,7 +1844,7 @@ proc processInHTMLContent(parser: var HTML5Parser, token: Token, insertionMode =
         if not parser.openElements.hasElementInTableScope(token.tagtype):
           discard
         else:
-          while parser.openElements.pop().tagType != TAG_SELECT: discard
+          while parser.popElement().tagType != TAG_SELECT: discard
           parser.resetInsertionMode()
           reprocess token
       )
@@ -1887,7 +1895,7 @@ proc processInHTMLContent(parser: var HTML5Parser, token: Token, insertionMode =
           discard # stop
         else:
           parse_error
-          while parser.openElements.pop().tagType != TAG_TEMPLATE: discard
+          while parser.popElement().tagType != TAG_TEMPLATE: discard
           parser.clearActiveFormattingTillMarker()
           discard parser.templateModes.pop()
           parser.resetInsertionMode()
@@ -1982,7 +1990,7 @@ proc processInForeignContent(parser: var HTML5Parser, token: Token) =
     for i in countdown(parser.openElements.high, 1):
       let node = parser.openElements[i]
       if node.localName == token.tagname:
-        while parser.openElements.pop() != node: discard
+        while parser.popElement() != node: discard
         break
       if node.namespace == Namespace.HTML: break
       parser.processInHTMLContent(token)
diff --git a/src/html/tags.nim b/src/html/tags.nim
index fbec9164..ff6a3a30 100644
--- a/src/html/tags.nim
+++ b/src/html/tags.nim
@@ -118,7 +118,7 @@ const FormAssociatedElements* = {
 
 #TODO support all the other ones
 const SupportedFormAssociatedElements* = {
-  TAG_SELECT, TAG_INPUT, TAG_BUTTON
+  TAG_BUTTON, TAG_INPUT, TAG_SELECT, TAG_TEXTAREA
 }
 
 const ListedElements* = {