Search code examples
reactjsformsgoogle-chrome-extensionformik

How to get current form state values when user clicks injected button?


I'm building a browser extension that's intended to extend the capabilities of a web form (this is for a site I do not own). From inspecting the site, the forms are built and rendered using React and Formik.

Currently I'm injecting new elements on the page that will allow users to:

  1. Store the active form.
  2. Save the form as a draft.
  3. Edit existing drafts.

I would like to capture all values from the form when users click the injected "Save as Draft" button, but am struggling to find the best approach. Ideally, I could access the React components tree as the state values are structured exactly as I'd like them to be, but I'm open to any method that would reliably capture the values for any field type.

Here's an example of the HTML from an example form that includes one of each potential field type:

<form>
  <div class="css-1o5h39n e1tmc1mx0">
    <div data-client-id="container_Text" data-client-type="text" id="vXja2L09" class="css-1e3khfm ef83ajd0"><label for="text_box_Text" data-client-id="label_Text" class="css-1xl1v40 ekxsfat0">Text</label>
      <div style="display: flex;"><input title="" aria-invalid="false" tabindex="0" id="text_box_Text" data-client-id="text_box_Text" data-client-type="" name="vXja2L09" maxlength="4000" class="css-1p0590h e1407lhe0" value=""></div>
    </div>
  </div>
  <div class="css-1o5h39n e1tmc1mx0">
    <div role="application" title="" data-client-id="container_Single Select" data-client-type="dropdown" id="nqLe2Glw" class="css-u3yn36 ef83ajd0"><label for="select_input_Single Select" id="be240b0b-ffe5-4f77-bbd1-89863eaa9c1c" data-client-id="label_Single Select" class="css-1xl1v40 ekxsfat0">Single Select</label>
      <div class="css-t8xanh-container">
        <div class="css-kf2egt-control react-select__control">
          <div class="css-11ksah1 react-select__value-container">
            <div class="css-1i5h0xy-placeholder react-select__placeholder">Select</div>
            <div class="css-1g6gooi">
              <div class="react-select__input" style="display: inline-block;"><input autocapitalize="none" autocomplete="off" autocorrect="off" id="select_input_Single Select" spellcheck="false" tabindex="0" type="text" aria-label="Select" aria-labelledby="be240b0b-ffe5-4f77-bbd1-89863eaa9c1c" value="" style="box-sizing: content-box; width: 2px; background: 0px center; border: 0px; font-size: inherit; opacity: 1; outline: 0px; padding: 0px; color: inherit;">
                <div style="position: absolute; top: 0px; left: 0px; visibility: hidden; height: 0px; overflow: scroll; white-space: pre; font-size: 13px; font-family: Arial; font-weight: 400; font-style: normal; letter-spacing: normal; text-transform: none;">
                </div>
              </div>
            </div>
          </div>
          <div class="css-1wy0on6 react-select__indicators"><span class="css-9lpq22-indicatorSeparator react-select__indicator-separator"></span><span style="margin-right: 6px;"><svg data-client-id="caret_icon" width="16px" height="16px" viewBox="0 0 16 16" version="1.1" role="img" xmlns="http://www.w3.org/2000/svg">
                <title>Caret Icon</title>
                <desc>Caret symbol</desc>
                <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
                  <g fill="#161616">
                    <path d="M8.38951287,9.81740951 C8.3743818,9.83527284 8.35811596,9.85207547 8.34082332,9.86770589 C8.12569934,10.0621513 7.79871352,10.0396329 7.61048004,9.81740951 L5.12806062,6.88673235 C5.04550447,6.78926879 5,6.66416424 5,6.53465776 C5,6.23937443 5.23172713,6 5.51757703,6 L10.4824159,6 C10.607785,6 10.7288928,6.04700617 10.8232427,6.13228679 C11.0383667,6.32673222 11.0601658,6.664509 10.8719323,6.88673235 L8.38951287,9.81740951 Z">
                    </path>
                  </g>
                </g>
              </svg></span></div>
        </div>
      </div>
    </div>
  </div>
  <div class="css-1o5h39n e1tmc1mx0">
    <div role="application" title="" data-client-id="container_Multi-Select" data-client-type="dropdown" id="g6G8KeOD" class="css-u3yn36 ef83ajd0"><label for="select_input_Multi-Select" id="5a2fe23a-5bae-4b8f-adbc-2ab0d4dbc1f6" data-client-id="label_Multi-Select" class="css-1xl1v40 ekxsfat0">Multi-Select</label>
      <div class="css-t8xanh-container">
        <div class="css-kf2egt-control react-select__control">
          <div class="css-11ksah1 react-select__value-container react-select__value-container--is-multi">
            <div class="css-1i5h0xy-placeholder react-select__placeholder">Select or enter value</div>
            <div class="css-1g6gooi">
              <div class="react-select__input" style="display: inline-block;"><input autocapitalize="none" autocomplete="off" autocorrect="off" id="select_input_Multi-Select" spellcheck="false" tabindex="0" type="text" aria-label="Select or enter value" aria-labelledby="5a2fe23a-5bae-4b8f-adbc-2ab0d4dbc1f6" value="" style="box-sizing: content-box; width: 2px; background: 0px center; border: 0px; font-size: inherit; opacity: 1; outline: 0px; padding: 0px; color: inherit;">
                <div style="position: absolute; top: 0px; left: 0px; visibility: hidden; height: 0px; overflow: scroll; white-space: pre; font-size: 13px; font-family: Arial; font-weight: 400; font-style: normal; letter-spacing: normal; text-transform: none;">
                </div>
              </div>
            </div>
          </div>
          <div class="css-1wy0on6 react-select__indicators"><span class="css-9lpq22-indicatorSeparator react-select__indicator-separator"></span><span style="margin-right: 6px;"><svg data-client-id="caret_icon" width="16px" height="16px" viewBox="0 0 16 16" version="1.1" role="img" xmlns="http://www.w3.org/2000/svg">
                <title>Caret Icon</title>
                <desc>Caret symbol</desc>
                <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
                  <g fill="#161616">
                    <path d="M8.38951287,9.81740951 C8.3743818,9.83527284 8.35811596,9.85207547 8.34082332,9.86770589 C8.12569934,10.0621513 7.79871352,10.0396329 7.61048004,9.81740951 L5.12806062,6.88673235 C5.04550447,6.78926879 5,6.66416424 5,6.53465776 C5,6.23937443 5.23172713,6 5.51757703,6 L10.4824159,6 C10.607785,6 10.7288928,6.04700617 10.8232427,6.13228679 C11.0383667,6.32673222 11.0601658,6.664509 10.8719323,6.88673235 L8.38951287,9.81740951 Z">
                    </path>
                  </g>
                </g>
              </svg></span></div>
        </div>
      </div>
    </div>
  </div>
  <div class="css-1o5h39n e1tmc1mx0">
    <div data-client-id="container_Date" data-client-type="date" id="W062w1gM" class="css-gbq6lm ef83ajd0"><label for="date_Date" data-client-id="label_Date" class="css-1xl1v40 ekxsfat0">Date</label>
      <div class="css-1f92r3b"><input data-client-id="date_Date" id="date_Date" aria-label="Date" tabindex="0" name="Date" maxlength="100" class="css-1dpiipj epidubd0" value=""><button tabindex="0" type="button" aria-label="Choose" class="css-17q24xo"><span title="Choose a date" class="css-14atay6 e1yrjtds0"><svg xmlns="http://www.w3.org/2000/svg" role="img" width="16" height="16" viewBox="0 0 16 16">
              <title>Calendar Icon</title>
              <desc>Calendar</desc>
              <g fill="none" fill-rule="evenodd">
                <rect width="13" height="13" x="2" y="2" fill="#005EE0" rx="1"></rect>
                <path fill="#FFF" d="M11.648 12h-.96V8.38c-.351.328-.765.571-1.241.728v-.871c.25-.082.523-.238.817-.467.294-.229.495-.496.605-.801h.779V12zm-6.385-1.33l.93-.112c.03.237.11.418.24.543.129.125.286.188.47.188a.648.648 0 0 0 .502-.226c.135-.15.203-.353.203-.608 0-.241-.065-.433-.195-.574a.621.621 0 0 0-.475-.212c-.123 0-.27.024-.44.072l.105-.783c.26.007.458-.05.595-.17a.602.602 0 0 0 .205-.476.545.545 0 0 0-.15-.403.54.54 0 0 0-.4-.15.579.579 0 0 0-.42.17c-.117.114-.188.28-.213.5l-.885-.151c.061-.303.154-.545.279-.726.124-.182.297-.324.52-.428.221-.103.47-.155.746-.155.472 0 .85.15 1.135.45.234.247.352.525.352.835 0 .44-.24.79-.721 1.053.287.061.516.199.688.413a1.2 1.2 0 0 1 .258.776c0 .44-.16.815-.482 1.125-.32.31-.72.464-1.2.464-.453 0-.829-.13-1.127-.39a1.538 1.538 0 0 1-.52-1.025zM11 5a1 1 0 1 1 0-2 1 1 0 0 1 0 2zM6 5a1 1 0 1 1 0-2 1 1 0 0 1 0 2z">
                </path>
              </g>
            </svg></span></button></div>
    </div>
  </div>
  <div class="css-1o5h39n e1tmc1mx0">
    <div data-client-id="container_Single Contact" data-client-type="text" id="AJ3vWnL2" class="css-1e3khfm ef83ajd0"><label for="text_box_Single Contact" data-client-id="label_Single Contact" class="css-1xl1v40 ekxsfat0">Single Contact</label>
      <div style="display: flex;"><input title="" aria-invalid="false" tabindex="0" id="text_box_Single Contact" data-client-id="text_box_Single Contact" data-client-type="" name="AJ3vWnL2" maxlength="4000" class="css-1p0590h e1407lhe0" value=""></div>
    </div>
  </div>
  <div class="css-1o5h39n e1tmc1mx0">
    <div role="application" title="" data-client-id="container_Multi-Contact" data-client-type="dropdown" id="50kWJj5y" class="css-u3yn36 ef83ajd0"><label for="select_input_Multi-Contact" id="d3090522-e525-4e09-8f55-a461287a1dc5" data-client-id="label_Multi-Contact" class="css-1xl1v40 ekxsfat0">Multi-Contact</label>
      <div class="css-t8xanh-container">
        <div class="css-kf2egt-control react-select__control">
          <div class="css-11ksah1 react-select__value-container react-select__value-container--is-multi">
            <div class="css-1i5h0xy-placeholder react-select__placeholder">Select or enter value</div>
            <div class="css-1g6gooi">
              <div class="react-select__input" style="display: inline-block;"><input autocapitalize="none" autocomplete="off" autocorrect="off" id="select_input_Multi-Contact" spellcheck="false" tabindex="0" type="text" aria-label="Select or enter value" aria-labelledby="d3090522-e525-4e09-8f55-a461287a1dc5" value="" style="box-sizing: content-box; width: 2px; background: 0px center; border: 0px; font-size: inherit; opacity: 1; outline: 0px; padding: 0px; color: inherit;">
                <div style="position: absolute; top: 0px; left: 0px; visibility: hidden; height: 0px; overflow: scroll; white-space: pre; font-size: 13px; font-family: Arial; font-weight: 400; font-style: normal; letter-spacing: normal; text-transform: none;">
                </div>
              </div>
            </div>
          </div>
          <div class="css-1wy0on6 react-select__indicators"><span class="css-9lpq22-indicatorSeparator react-select__indicator-separator"></span><span style="margin-right: 6px;"><svg data-client-id="caret_icon" width="16px" height="16px" viewBox="0 0 16 16" version="1.1" role="img" xmlns="http://www.w3.org/2000/svg">
                <title>Caret Icon</title>
                <desc>Caret symbol</desc>
                <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
                  <g fill="#161616">
                    <path d="M8.38951287,9.81740951 C8.3743818,9.83527284 8.35811596,9.85207547 8.34082332,9.86770589 C8.12569934,10.0621513 7.79871352,10.0396329 7.61048004,9.81740951 L5.12806062,6.88673235 C5.04550447,6.78926879 5,6.66416424 5,6.53465776 C5,6.23937443 5.23172713,6 5.51757703,6 L10.4824159,6 C10.607785,6 10.7288928,6.04700617 10.8232427,6.13228679 C11.0383667,6.32673222 11.0601658,6.664509 10.8719323,6.88673235 L8.38951287,9.81740951 Z">
                    </path>
                  </g>
                </g>
              </svg></span></div>
        </div>
      </div>
    </div>
  </div>
  <div class="css-1o5h39n e1tmc1mx0">
    <div data-client-id="container_Number" data-client-type="text" id="mdLN2PKn" class="css-1e3khfm ef83ajd0"><label for="text_box_Number" data-client-id="label_Number" class="css-1xl1v40 ekxsfat0">Number</label>
      <div style="display: flex; width: 193px;"><input title="" aria-invalid="false" tabindex="0" id="text_box_Number" data-client-id="text_box_Number" data-client-type="NUMERIC" type="number" name="mdLN2PKn" maxlength="4000" class="css-1p0590h e1407lhe0" value="" style="width: 300px;"></div>
    </div>
  </div>
  <div class="css-1o5h39n e1tmc1mx0">
    <div data-client-id="container_Checkbox" data-client-type="checkbox" data-client-label-position="ABOVE" data-client-symbol-type="CHECKBOX" id="36kqqqOP" class="css-1x55rpa ef83ajd0"><label for="checkbox_id_36kqqqOP" data-client-id="label_Checkbox" class="css-1xl1v40 ekxsfat0">Checkbox</label>
      <div class="css-b811ne e1t681ye0"><input tabindex="0" id="checkbox_id_36kqqqOP" data-client-id="checkbox_Checkbox" type="checkbox" name="36kqqqOP" aria-label="Checkbox" title="Checkbox" class="css-1f2no93 e1t681ye1"></div><span class="css-1s209s0 e1t681ye5"></span>
    </div>
  </div>
  <div class="css-1o5h39n e1tmc1mx0">
    <div role="application" title="" data-client-id="container_Symbol" data-client-type="dropdown" data-client-symbol-set="RYG" id="ae862Lml" class="css-u3yn36 ef83ajd0"><label for="select_input_Symbol" id="6127db59-2cd2-44c6-8440-b0be5780d876" data-client-id="label_Symbol" class="css-1xl1v40 ekxsfat0">Symbol</label>
      <div class="css-t8xanh-container">
        <div class="css-kf2egt-control react-select__control">
          <div class="css-11ksah1 react-select__value-container">
            <div class="css-1i5h0xy-placeholder react-select__placeholder">Select or enter value</div>
            <div class="css-1g6gooi">
              <div class="react-select__input" style="display: inline-block;"><input autocapitalize="none" autocomplete="off" autocorrect="off" id="select_input_Symbol" spellcheck="false" tabindex="0" type="text" aria-label="Select or enter value" aria-labelledby="6127db59-2cd2-44c6-8440-b0be5780d876" value="" style="box-sizing: content-box; width: 2px; background: 0px center; border: 0px; font-size: inherit; opacity: 1; outline: 0px; padding: 0px; color: inherit;">
                <div style="position: absolute; top: 0px; left: 0px; visibility: hidden; height: 0px; overflow: scroll; white-space: pre; font-size: 13px; font-family: Arial; font-weight: 400; font-style: normal; letter-spacing: normal; text-transform: none;">
                </div>
              </div>
            </div>
          </div>
          <div class="css-1wy0on6 react-select__indicators"><span class="css-9lpq22-indicatorSeparator react-select__indicator-separator"></span><span style="margin-right: 6px;"><svg data-client-id="caret_icon" width="16px" height="16px" viewBox="0 0 16 16" version="1.1" role="img" xmlns="http://www.w3.org/2000/svg">
                <title>Caret Icon</title>
                <desc>Caret symbol</desc>
                <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
                  <g fill="#161616">
                    <path d="M8.38951287,9.81740951 C8.3743818,9.83527284 8.35811596,9.85207547 8.34082332,9.86770589 C8.12569934,10.0621513 7.79871352,10.0396329 7.61048004,9.81740951 L5.12806062,6.88673235 C5.04550447,6.78926879 5,6.66416424 5,6.53465776 C5,6.23937443 5.23172713,6 5.51757703,6 L10.4824159,6 C10.607785,6 10.7288928,6.04700617 10.8232427,6.13228679 C11.0383667,6.32673222 11.0601658,6.664509 10.8719323,6.88673235 L8.38951287,9.81740951 Z">
                    </path>
                  </g>
                </g>
              </svg></span></div>
        </div>
      </div>
    </div>
  </div>
  <div class="css-1o5h39n e1tmc1mx0">
    <div data-client-id="container_File Upload" data-client-type="file_upload" id="ATTACHMENT" class="css-1ylo82b ef83ajd0"><label for="file_upload_File Upload" data-client-id="label_File Upload" class="css-1xl1v40 ekxsfat0">File Upload</label>
      <div class="css-vqlje4" tabindex="0" data-client-id="file_upload_dropzone" aria-disabled="false" style="position: relative;">
        <div class="css-hvudvc eo18oon8"><span class="css-1btvv3a eo18oon1"><span>Drag and drop files here or
            </span> <button tabindex="0" type="button" class="css-uro7iq eo18oon0"><span>browse
                files</span></button></span></div><input id="file_upload_File Upload" tabindex="0" type="file" multiple="" autocomplete="off" style="position: absolute; inset: 0px; opacity: 1e-05; pointer-events: none;">
      </div>
    </div>
  </div>
  <div class="css-1o5h39n e1tmc1mx0">
    <div data-client-id="email_receipt_section">
      <div data-client-id="container_email_receipt" data-client-type="divider" class="css-1e2fy0l e14sboee0">
        <hr data-client-id="email_receipt" class="css-1xs1ymt e14sboee2">
      </div>
      <div class="css-1gl0c9l excyp8g0"><label class="css-1y93uaa excyp8g1"><input data-client-id="email_receipt_checkbox" name="EMAIL_RECEIPT_CHECKBOX" type="checkbox" class="css-1czgm8r excyp8g2" value="false"><span>Send me a copy of my responses</span></label>
      </div>
    </div>
  </div>
  <div class="css-1ock8o4 e1xpzprc2"><button data-client-id="form_submit_btn" disabled="" type="submit" value="submit" class="css-oltpmt e1xpzprc0"><span>Submit</span></button></div>
</form>

I've tried using the following approaches so far:

  • Including React in my extension to try and access the React components tree (unsuccessful)
  • Capturing FormData entries (only works for certain field types)
  • Manually parsing the Form HTML (far to cumbersome and complicated to be reliable for any given form configuration)

Solution

  • The answer ended up being extremely simple and simply involved embedding the code I needed in a script that wasn't the Chrome Extensions content script.

    This thread in particular was extremely helpful: Access variables and functions defined in page context using a content script

    In the end, what I did was inject a separate file (script.js) into my content script like this:

    let s = document.createElement('script');
    s.src = chrome.runtime.getURL('script.bundle.js');
    s.onload = function () {
        this.remove();
    };
    (document.head || document.documentElement).appendChild(s);
    

    The code in the script to get the form props I was looking for was this:

    Object.values(document.querySelector('.[insert class name]'))[1].children.props.form
    

    Most of the difficultly I had with this stemmed from my being a novice with Chrome Extensions and React, but hopefully this is somehow helpful to others!