braintree / braintree-web

A suite of tools for integrating Braintree in the browser
https://developer.paypal.com/braintree/docs/start/hello-client/javascript/v3
MIT License
442 stars 134 forks source link

HostedFields v3 do not render inside shadow root. #226

Closed SokratisVidros closed 7 years ago

SokratisVidros commented 8 years ago

Hi there,

I am trying to setup hosted fields inside a shadow container. The iframe loads as it should, <script>braintree.hostedFields.create();</script> is injected but no label or input is rendered.

At the moment, I am tracing the issue but i'd appreciate more useful insights from the contributors.

My init code looks like:

...

braintree.hostedFields.create({
  client: clientInstance,
  styles: {},
  fields: {
    number: {
      selector: '::shadow #card-number',
      placeholder: '4111 1111 1111 1111'
    },
    cvv: {
      selector: '::shadow #cvv',
      placeholder: '123'
    },
    expirationDate: {
      selector: '::shadow #expiration-date',
      placeholder: 'MM/YYYY'
    }
  }
}, callback);

...

Thanks in advance, Sokratis

crookedneighbor commented 8 years ago

Hi @SokratisVidros

I've opened an issue on Polymer about this https://github.com/Polymer/polymer/issues/4086 (It's not a Polymer specific issue, but that's where I ran into it first)

The basic gist is that each Hosted Fields iframe requires access to window.parent.frames, but when an iframe is added to the shadow dom, it is not added to window.frames, so the Hosted Fields iframes cannot initialize.

We're looking into possible solutions for this, but at this time, Hosted Fields is incompatible with the shadow dom.

SokratisVidros commented 8 years ago

Hi @crookedneighbor.

Thanks for the quick reply. I guess a fix should be applied at src/hosted-fields/internal/assemble-iframes.js. Currently, i am planning to use it only in Chrome so the following patch might work:

...
const iframes = document.querySelectorAll('html /deep/ iframe');
const iWindows = iframes.map(f => f.contentWindow);
...

The above patch is not a cross-browser solution. Nonetheless, If we take into account the current shadow DOM support, i believe that it can be part of the official library.

Lastly, an alternate, coarse-grained approach could be the following:

...
const allNodes = document.getElementsByTagName('*');
for (let i = 0, l = allNodes.length; i < l; i++) {
  if (allNodes[i].shadowRoot) {
    // Get all the shadow iframes recursively
  }
}
...
crookedneighbor commented 8 years ago

That script for assemble-iframes occurs within the iframe itself and is hosted by braintree. So the iframe does not have access to the parent's document, so neither of those approaches will work for this. 😞

SokratisVidros commented 8 years ago

You are absolutely right.

I attempted to use the above tricks and overwrite window.frames but it didn't work.

Have you tried enclosing the shadow form in an iframe to encapsulate Braintree-web lib?

Le 25 oct. 2016 à 17:25, Blade Barringer notifications@github.com a écrit :

Hi @SokratisVidros

I've opened an issue on Polymer about this Polymer/polymer#4086 (It's not a Polymer specific issue, but that's where I ran into it first)

The basic gist is that each Hosted Fields iframe requires access to window.parent.frames, but when an iframe is added to the shadow dom, it is not added to window.frames, so the Hosted Fields iframes cannot initialize.

We're looking into possible solutions for this, but at this time, Hosted Fields is incompatible with the shadow dom.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or mute the thread.

skunkworks commented 7 years ago

Hi @SokratisVidros,

We will be considering how to best support the Shadow DOM use case moving forward, but it will take some time to refactor Hosted Fields and make it compatible. I'll leave this issue open to provide updates on our progress. Thanks for opening this!

SokratisVidros commented 7 years ago

Thanks a lot.

crookedneighbor commented 7 years ago

Here's a codepen that illustrates how to use Hosted Fields with the shadow DOM: http://codepen.io/braintree/pen/NbqPVO

The trick is that you need to set up the Hosted Fields divs in the normal DOM (so that the iframes can find each other) and then inject them into the shadow DOM.

Hope this helps!

SokratisVidros commented 7 years ago

👍

runia1 commented 7 years ago

@crookedneighbor Just in case you're wondering and for anyone else out there...

You CAN use braintree and polymer 2 together, you just have to put the form outside the shadow dom and use <slot> to tell polymer where it should end up. This way the braintree lib can find the elements to bind the iframes to, and you still get your form showing up where you want it.

Example:

In index.html add your braintree-fields inside your app container <my-app>

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">

    <!-- Load webcomponents-loader.js to check and load any polyfills your browser needs -->
    <script src="/bower_components/webcomponentsjs/webcomponents-loader.js"></script>

    <!-- Load redux library -->
    <script src="/redux/dist/redux.js"></script>

    <!-- Load the Client component -->
    <script src="https://js.braintreegateway.com/web/3.12.0/js/client.min.js"></script>

    <!-- Load the Hosted Fields component -->
    <script src="https://js.braintreegateway.com/web/3.12.0/js/hosted-fields.min.js"></script>

    <!-- Load your application shell -->
    <link rel="import" href="/src/my-app.html">

    <!-- Add any global styles for body, document, etc. -->
    <style>
      body {
        margin: 0;
        font-family: 'Roboto', 'Noto', sans-serif;
        line-height: 1.5;
        min-height: 100vh;
        background-color: #eeeeee;
      }
    </style>
  </head>
  <body>
    <my-app>
      <div id="braintree-fields">
        <div class="field" id="card-number-container">
          <label for="card-number">Card Number</label>
          <div id="card-number"></div>
        </div>
        <div id="cvv-container">
          <label for="cvv">CVV</label>
          <div id="cvv"></div>
        </div>
        <div id="expiration-date-container">
          <label for="expiration-date">Expiration Date</label>
          <div id="expiration-date"></div>
        </div>
      </div>
    </my-app>
  </body>
</html>

then in <my-app> <slot id="#braintree-fields"> where you want to put it.

          <iron-pages
            selected="[[page]]"
            attr-for-selected="name"
            fallback-selection="view404"
            role="main">
          <my-home name="home"></my-home>
          <my-give name="give"></my-give>
          <my-checkout name="checkout">
            <slot id="#braintree-fields"></slot>
          </my-checkout>
          <my-view404 name="view404"></my-view404>
        </iron-pages>

Finally, in <my-checkout> just slot in any content (which should be only the #braintree-fields)

<iron-form id="giveForm" on-iron-form-presubmit="_giveFormSubmitted">
  <slot></slot>
</iron-form>
crookedneighbor commented 4 years ago

If anyone is interested in trying a beta release that adds official support for the shadow DOM, you can try it out here:

https://js.braintreegateway.com/web/3.64.0-beta.1/js/client.min.js https://js.braintreegateway.com/web/3.64.0-beta.1/js/hosted-fields.min.js

The fix was to check if the container being passed existed in the shadow DOM, and if it does, add slot elements into the container and the iframes in divs referencing those slots on the main page. This allows the iframes to communicate with each other even when inside the shadow DOM.

paulhulatt commented 3 years ago

Is there a beta version for Dropin UI that supports shadow DOM too? Trying to get it working with Flutter Web but experiencing the perpetual spinner issue with version 1.25

crookedneighbor commented 3 years ago

@paulhulatt please jump in on the issue in the Drop-in repo: https://github.com/braintree/braintree-web-drop-in/issues/296