Shopify / shopify-app-bridge

https://shopify.dev/docs/api/app-bridge
88 stars 9 forks source link

Custom Javascript not loaded inside Modal iframes #269

Open forsbergplustwo opened 10 months ago

forsbergplustwo commented 10 months ago

Describe the bug

When using the Modal <ui-modal> component of AppBridge v4 (CDN), the javascript files referenced in the <head> of our Rails 7 application are not loaded inside the modal iframe, breaking all JS related behaviour for us.

Of interest may be that Rails applications now by default use an importmap for loading JS files.

Code used to launch Modal:

    <ui-modal id="content-modal">
      <div>
        <p>Modal content</p>
      </div>
      <ui-title-bar title="Title">
        <button variant="primary" onclick="console.log(document.getElementById('content-modal').content)">Log content</button>
        <button onclick="document.getElementById('content-modal').hide()">Close</button>
      </ui-title-bar>
    </ui-modal>

    <%= polaris_button(
      onclick: "document.getElementById('content-modal').show()"
    ) { "Open content modal" } %>

Original <head> of application

<head>
<style type="text/css">.turbo-progress-bar {
  position: fixed;
  display: block;
  top: 0;
  left: 0;
  height: 3px;
  background: #0076ff;
  z-index: 2147483647;
  transition:
    width 300ms ease-out,
    opacity 150ms 150ms ease-in;
  transform: translate3d(0, 0, 0);
}
</style>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <meta name="turbo-cache-control" content="no-preview">
    <meta name="csrf-param" content="authenticity_token">

    <title></title>

    <link rel="stylesheet" href="/assets/application-77391b69591a67e45e85ddbe061eb3a64acbf668535bcdb3f9405a4d8a6205de.css">

    <meta name="shopify-api-key" content="bb54ecc241fbd6e03ea93fd665f5e169">
    <script src="https://cdn.shopify.com/shopifycloud/app-bridge.js"></script>

    <script type="importmap" data-turbo-track="reload">{
  "imports": {
    "@rails/request.js": "/assets/requestjs-c7150df01ef8f2501194fdc0acfaa04747b98cf4295882aece676b4235edc075.js",
    "application": "/assets/application-f2cd9df22e5a3f73d140b60a28667d7d99f831b4fc300992e885dd796034e624.js",
    "@hotwired/turbo-rails": "/assets/turbo.min-dfd93b3092d1d0ff56557294538d069bdbb28977d3987cb39bc0dd892f32fc57.js",
    "polaris-view-components": "/assets/polaris_view_components-c1f4bcc399e1443ed9fa7511ffce37676969ad4ef29dd963de08e57d20b13323.js",
    "@hotwired/stimulus": "/assets/stimulus.min-dd364f16ec9504dfb72672295637a1c8838773b01c0b441bd41008124c407894.js",
    "@hotwired/stimulus-loading": "/assets/stimulus-loading-3576ce92b149ad5d6959438c6f291e2426c86df3b874c525b30faad51b0d96b3.js",
    "controllers/application": "/assets/controllers/application-368d98631bccbf2349e0d4f8269afb3fe9625118341966de054759d96ea86c7e.js"
  }
}</script>
<link rel="modulepreload" href="/assets/requestjs-c7150df01ef8f2501194fdc0acfaa04747b98cf4295882aece676b4235edc075.js">
<link rel="modulepreload" href="/assets/application-f2cd9df22e5a3f73d140b60a28667d7d99f831b4fc300992e885dd796034e624.js">
<link rel="modulepreload" href="/assets/turbo.min-dfd93b3092d1d0ff56557294538d069bdbb28977d3987cb39bc0dd892f32fc57.js">
<link rel="modulepreload" href="/assets/polaris_view_components-c1f4bcc399e1443ed9fa7511ffce37676969ad4ef29dd963de08e57d20b13323.js">
<link rel="modulepreload" href="/assets/stimulus.min-dd364f16ec9504dfb72672295637a1c8838773b01c0b441bd41008124c407894.js">
<link rel="modulepreload" href="/assets/stimulus-loading-3576ce92b149ad5d6959438c6f291e2426c86df3b874c525b30faad51b0d96b3.js">
<script type="esms-options">{"nonce":null}</script>
<script src="/assets/es-module-shims.min-4ca9b3dd5e434131e3bb4b0c1d7dff3bfd4035672a5086deec6f73979a49be73.js" async="async" data-turbo-track="reload"></script>
<script type="module">import "application"</script>
      <script src="/assets/hotwire-livereload-fc3dfc6dc0aacf7b14ec3898156c439216e32baa50b9089cdf90000e838fcc1d.js" defer="defer"></script>
    <meta name="csrf-token" content="VgtDdB4tWt9z3IHwuBxSnerOYtf2VJe6uQ6j6n8rb3LvmMjAl4QGdarhhyyOuyQ07tEsCHvuNXU8wFYzx3MmlQ">
</head>

Resulting Modal <head>, which is missing all of the JS files from original:

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <meta name="turbo-cache-control" content="no-preview">
    <meta name="csrf-param" content="authenticity_token">
<meta name="csrf-token" content="Z-V_Prv3O3EvGhxK9qb6kBCPF4F-4q-sJRi5sxPBkqTedvSKMl5n2_YnGpbAAYw5FJBZXvNYDWOg1kxqq5nbQw">

    <title></title>

    <link rel="stylesheet" href="/assets/application-77391b69591a67e45e85ddbe061eb3a64acbf668535bcdb3f9405a4d8a6205de.css">

    <meta name="shopify-api-key" content="bb54ecc241fbd6e03ea93fd665f5e169">
    <script src="https://cdn.shopify.com/shopifycloud/app-bridge.js"></script><style>
    html {
      background-color: rgba(0, 0, 0, 0) !important;
    }
    body {
      min-height: auto;
      height: auto;
      padding: 0 !important;
      margin: 0 !important;
      background-color: rgba(0, 0, 0, 0) !important;
    }
    body > * {
      display: none !important;
    }
    body > #modal-content-8885451e-38a1-4196-835b-40f3efb46c4e {
      display: flex !important;
    }
  </style>
<style type="text/css">.turbo-progress-bar {
  position: fixed;
  display: block;
  top: 0;
  left: 0;
  height: 3px;
  background: #0076ff;
  z-index: 2147483647;
  transition:
    width 300ms ease-out,
    opacity 150ms 150ms ease-in;
  transform: translate3d(0, 0, 0);
}
</style>
</head>

Issues:

  1. All referenced JS files are missing
  2. The CSS style blocks are in the wrong positions compared to the original (which could cause issues due to Cascading nature of CSS)

Note: Within the Modal iframe (just after the closing</head> tag, there is a <template> tag containing the removed javascript code, as well as the original body of the application.

CleanShot 2024-01-14 at 11 36 28

Content of <template> tag:

<template>

    <script type="importmap" data-turbo-track="reload">{
  "imports": {
    "@rails/request.js": "/assets/requestjs-c7150df01ef8f2501194fdc0acfaa04747b98cf4295882aece676b4235edc075.js",
    "application": "/assets/application-f2cd9df22e5a3f73d140b60a28667d7d99f831b4fc300992e885dd796034e624.js",
    "@hotwired/turbo-rails": "/assets/turbo.min-dfd93b3092d1d0ff56557294538d069bdbb28977d3987cb39bc0dd892f32fc57.js",
    "polaris-view-components": "/assets/polaris_view_components-c1f4bcc399e1443ed9fa7511ffce37676969ad4ef29dd963de08e57d20b13323.js",
    "@hotwired/stimulus": "/assets/stimulus.min-dd364f16ec9504dfb72672295637a1c8838773b01c0b441bd41008124c407894.js",
    "@hotwired/stimulus-loading": "/assets/stimulus-loading-3576ce92b149ad5d6959438c6f291e2426c86df3b874c525b30faad51b0d96b3.js",
    "controllers/application": "/assets/controllers/application-368d98631bccbf2349e0d4f8269afb3fe9625118341966de054759d96ea86c7e.js"
  }
}</script>
<link rel="modulepreload" href="/assets/requestjs-c7150df01ef8f2501194fdc0acfaa04747b98cf4295882aece676b4235edc075.js">
<link rel="modulepreload" href="/assets/application-f2cd9df22e5a3f73d140b60a28667d7d99f831b4fc300992e885dd796034e624.js">
<link rel="modulepreload" href="/assets/turbo.min-dfd93b3092d1d0ff56557294538d069bdbb28977d3987cb39bc0dd892f32fc57.js">
<link rel="modulepreload" href="/assets/polaris_view_components-c1f4bcc399e1443ed9fa7511ffce37676969ad4ef29dd963de08e57d20b13323.js">
<link rel="modulepreload" href="/assets/stimulus.min-dd364f16ec9504dfb72672295637a1c8838773b01c0b441bd41008124c407894.js">
<link rel="modulepreload" href="/assets/stimulus-loading-3576ce92b149ad5d6959438c6f291e2426c86df3b874c525b30faad51b0d96b3.js">
<script type="esms-options">{"nonce":null}</script>
<script src="/assets/es-module-shims.min-4ca9b3dd5e434131e3bb4b0c1d7dff3bfd4035672a5086deec6f73979a49be73.js" async="async" data-turbo-track="reload"></script>
<script type="module">import "application"</script>
      <script src="/assets/hotwire-livereload-fc3dfc6dc0aacf7b14ec3898156c439216e32baa50b9089cdf90000e838fcc1d.js" defer="defer"></script>

 <!-- REMAINDER OF OUR ORIGINAL HTML BODY AT FIRST LOAD -->

</template>

Expected behaviour

We would expect that our application is included as it was originally with all JS files included, like a normal iframe would behave.

Contextual information

Packages and versions

List the relevant packages you’re using, and their versions. For example:

Platform

Additional context

Rails 7 application using Hotwire (Turbo, Stimulus etc.)

awd commented 10 months ago

Seeing the same on my side as well. Also Turbo Rails.

henrytao-me commented 10 months ago

@forsbergplustwo @awd do you have a sample app that I can try?

We would expect that our application is included as it was originally with all JS files included, like a normal iframe would behave.

All of the missing js and template tag are expected in the modal iframe. Does this cause your <p>Modal content</p> does not show?

forsbergplustwo commented 10 months ago

Hey @henrytao-me - I'll try and create a sample app for you.

The modal content <p>Modal content</p> does display, but none of our javascript is available within the modal content.

So the js and template tag are included, but they are not being activated or loaded, which breaks trying to use any of it within the modal content. Is that expected behaviour?

forsbergplustwo commented 10 months ago

@henrytao-me - Here's an example Glitch project showing the issue: https://glitch.com/edit/#!/app-bridge-modal-js-example

Reproduction steps:

  1. Click the link: https://glitch.com/edit/#!/app-bridge-modal-js-example
  2. Click "Remix" to create your own fork of this
  3. Change the data-api-key value in index.html to your own Shopify App api key
  4. In the Shopify Partner Dashboard, update your apps URL to match your Glitch projects public URL.
  5. Login to the app through your test store
  6. Click on "Open Modal" button
  7. Notice that the script.js function iAmAvailable() is never called when modal content is shown. This is the issue, no JS from app is available inside modal.
forsbergplustwo commented 10 months ago

This would be fine if we were only building little things like confirmation modals. However, with the fullscreen "max" modal, we are building things like this:

CleanShot 2024-01-17 at 14 17 52

Which is not feasible without js libraries etc within the modal context.

henrytao-me commented 10 months ago

Hi @forsbergplustwo my team is on Shopify Partners slack if you would like to pair on this. If you are not on slack yet, you can join from this link https://join.slack.com/t/shopifypartners/shared_invite/zt-2aqba1tg5-f5bJYYINfb7W1xQ9GcrjwA

forsbergplustwo commented 10 months ago

@henrytao-me I'm on Slack 😊 How do you mean pair, is the sample not working for you?

darrynten commented 9 months ago

For us the majority of our scripts run in the modal, however we can no longer add event listeners to the resulting document, i.e. document.addEventListener no longer works

Update

The document events are being added, they're just being added to the main document, not the new "modal" document and as a result context is all broken and the event listeners inside the model no longer work as intended, and even some query selectors are not being found 😕

How can we run code in the load event of ui-modal and receive the appropriate context for the modal?

@forsbergplustwo your link does not load for me

darrynten commented 9 months ago

A potential solution is to just load another iframe inside the modal reusing all query params and load up a fresh copy of app bridge in the modal like you used to load iframe modals in v3.x but change the routes content-security model to match.

This gives back the expected document context since we're in yet another new frame.

It's not elegent, there's massive "yo dawg i heard you like iframes" energy with this "fix" and it's probably not how the 4.x team expects us to use this but it seems to be working fine for my current needs so maybe it will work for you too.

<ui-modal id="complex-modal" variant="max">
  <ui-title-bar title="Complex Max Modal"></ui-title-bar>
  <iframe src="/complex-modal?{{ request.query }}" style="min-height: 100vh;"></iframe>
</ui-modal>