Popups are blocked in the iframe within the macro, with the message: "because the request was made in a sandboxed frame whose 'allow-popups' permission is not set."

I used Forge’s Custom UI to develop a macro with a configuration page. The macro renders an iframe, and the code for the rendering page is as follows:

import React, { useEffect, useState } from 'react';
import { invoke,view,requestConfluence } from '@forge/bridge';
import { useConfig } from '@forge/react';
function App() {
    const [data, setData] = useState(null);
    const config = useConfig();
    const [iframeUrl, setIframeUrl] = useState('');

    useEffect(() => {
        invoke('getText', { example: 'my-invoke-variable' }).then(setData);
    }, []);

    useEffect( () => {
        const context = view.getContext();
        requestConfluence(
            `/wiki/rest/api/user/current`,
            {
                headers: {
                    Accept: "application/json",
                },
            }
        ).then(async response => {
            const userInfo = await response.json()
            if (config && config.myUrl) {
                let url = config.myUrl;

                // 添加用户属性参数到URL
                const appendParam = (paramName, paramValue) => {
                    if (paramName && paramValue) {
                        const connector = url.includes('?') ? '&' : '?';
                        const encodedName = encodeURIComponent(paramName);
                        const encodedValue = encodeURIComponent(paramValue);
                        url += `${connector}${encodedName}=${encodedValue}`;
                    }
                };
                if (config.user_attr_name) {
                    appendParam(config.user_attr_name, userInfo.displayName);
                }

                if (config.display_name_attr_name) {
                    appendParam(config.display_name_attr_name, userInfo.publicName);
                }

                if (config.email_attr_name) {
                    appendParam(config.email_attr_name, userInfo.email);
                }

                setIframeUrl(url);
                console.log('=======url:' + url)
            }
        })


    }, [config]);

    return (
        <div style={{overflow:'auto',
            width:'100%'
        }}>
            {iframeUrl && (
                <iframe
                    src={iframeUrl}
                    sandbox = "allow-downloads allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-presentation allow-same-origin allow-scripts allow-top-navigation-by-user-activation"
                    style={{
                        width: config.if_width || '800px',
                        height: config.if_height || '500px'
                    }}
                    {...(config.customAttr ? { [config.customAttr.split('=')[0]]: config.customAttr.split('=')[1] } : {})}
                />
            )}
        </div>
    );
}

export default App;

The embedded page loads correctly, but when the page inside the iframe attempts to open a new window, it still reports an error:
“Blocked opening ‘XXX’ in a new window because the request was made in a sandboxed frame whose ‘allow-popups’ permission is not set.”
How should I modify my code to resolve this issue?

Hi @huangzhihui , we have recently added support for this. https://developer.atlassian.com/platform/forge/changelog/#CHANGE-2794

Your manifest will need to include the following to allow for popups:

permissions:
    external:
        fetch:
          client:
            - address: '*'
1 Like

Thanks for your help in solving my problem. Have a great day!

1 Like

Hi @QuocLieu ,

Tried to play with permissions, but in case we have declared some other URLs, manifest validation is failing:

Manifest sample:
permissions:
external:
fetch:
client:
- address: "*"
- address: https://my.forge.remote.example.com
- address: my.analytics.com
category: analytics
inScopeEUD: true

Error:
Error: Manifest validation failed: Conflicting configurations detected for overlapping egress domains
Additional information: {
“addresses”: [
“*”,
“my.analytics.com”
],
“conflictingFields”: [
“category”,
“inScopeEUD”
]
}

Does this mean that if we enable allow-url using a wildcard (*), we can’t fine-tune access for specific URLs anymore — essentially making it an all-or-nothing setting ? Or is it only an issue with the manifest validation ?

3 Likes

Hi @julien , yes different categories cannot have overlapping egress domains. Addresses with the analytics category will allow an admin to optionally deny requests from that domain. But in this case, since you’re already allowing wildcard client addresses, having anything with the analytics scope won’t matter as your app is already allowing fetch requests from any domain.

Hi @QuocLieu ,

Thanks you for your reply.

Could you please clarify why an application that needs to open a pop-up from within an iframe is required to declare all domains (using a wildcard *) as potential egress, effectively preventing application users from disabling analytics?

Shouldn’t allow-popups be defined as a dedicated permission in the manifest, rather than being an indirect side effect of using a wildcard domain?

1 Like

Sure, the challenge we faced with offering a separate control for the allow-popups directive is that it effectively enables users to open links or navigate to domains outside those specified in the manifest, introducing potential data egress without their consent. We no longer retain any control over this when allow-popups is applied. A wildcard domain captures this side effect and the risks that comes with the directive.

@QuocLieu ,

Thanks you for your reply.

So, if we want to continue allowing users to disable analytics while still being able to open links in a new tab (for example, to access documentation without losing context), we have no choice but to avoid using wildcards and instead open links via router.open.

However, I see that we can whitelist domains for same-tab navigation using router.navigate (see https://developer.atlassian.com/platform/forge/manifest-reference/permissions/#fetch

). That said, declaring a domain does not seem to prevent the warning from appearing when using router.open.

Why is there a distinction between router.navigate and router.open and why is it not possible to avoid this warning ? From my point of view, same egress rules should apply.

1 Like

Hi @julien , yes allow-popup should generally be used if you want to allow for navigations or opening popups from within an iframe that your app has embedded and you have no control over that content. Otherwise, we recommend listing out domains in the manifest and using the router bridge methods.

Adding domains into external.fetch.client will ensure navigation will not trigger a warning modal. This applies to both router.navigate and router.open.

Here is an example that should work

// Manifest

permissions:
  external:
    fetch:
      client:
        - address: www.youtube.com
// App code

      <Button
        onClick={() => {
          router.open("www.youtube.com");
        }}
      >
        Open
      </Button>
      <Button onClick={() => router.navigate("www.youtube.com")}>
        Navigate
      </Button>
1 Like

@QuocLieu ,

Great, thanks for your reply!
I confirm that adding the manifest declaration removes the warning on router.navigate and router.open.

1 Like