Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

POC #5944: Just use shadow DOM instead of iframes #7616

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
"autocompleter": "^9.1.2",
"axios": "^0.27.2",
"batched-function": "^2.0.1",
"bootstrap": "^4.6.0",
"bootstrap": "^4.6.2",
"bootstrap-icons": "^1.11.3",
"bootstrap-switch-button-react": "^1.2.0",
"canvas-confetti": "^1.9.2",
Expand Down
9 changes: 6 additions & 3 deletions src/bricks/transformers/ephemeralForm/EphemeralForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,13 @@ const EphemeralFormContent: React.FC<{
/**
* @see FormTransformer
*/
const EphemeralForm: React.FC = () => {
const EphemeralForm: React.FC<{ nonce?: UUID; opener?: Target }> = ({
nonce,
opener,
}) => {
const params = new URLSearchParams(location.search);
const nonce = validateUUID(params.get("nonce"));
const opener = JSON.parse(params.get("opener")) as Target;
nonce ??= validateUUID(params.get("nonce"));
opener ??= JSON.parse(params.get("opener")) as Target;
const mode = params.get("mode") ?? "modal";

const isModal = mode === "modal";
Expand Down
4 changes: 2 additions & 2 deletions src/bricks/transformers/ephemeralForm/formTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,9 +201,9 @@ export class FormTransformer extends TransformerABC {
void cancelForm(formNonce);
});
} else {
const frameSource = await createFrameSource(formNonce, location);
const target = await getConnectedTarget();

showModal({ url: frameSource, controller });
showModal({ nonce: formNonce, opener: target, controller });
}

try {
Expand Down
91 changes: 53 additions & 38 deletions src/bricks/transformers/ephemeralForm/modalUtils.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
import { scrollbarWidth } from "@xobotyi/scrollbar-width";
import { render, unmountComponentAtNode } from "react-dom";
import React from "react";
import EphemeralForm from "@/bricks/transformers/ephemeralForm/EphemeralForm";
import { type UUID } from "@/types/stringTypes";
import { type Target } from "@/types/messengerTypes";
import { Stylesheets } from "@/components/Stylesheets";
import bootstrap from "@/vendors/bootstrapWithoutRem.css?loadAsUrl";

export function showModal({
url,
controller,
onOutsideClick,
opener,
nonce,
}: {
url: URL;
url?: URL;
opener?: Target;
nonce?: UUID;
controller: AbortController;
onOutsideClick?: () => void;
}): void {
Expand All @@ -27,47 +36,53 @@ export function showModal({
const shadowRoot = container.attachShadow({ mode: "closed" });
document.body.append(container, style);
render(
<dialog
ref={(dialog) => {
if (!dialog) {
return;
}
<Stylesheets href={bootstrap}>
<dialog
ref={(dialog) => {
if (!dialog) {
return;
}

dialog.showModal();
// No types support for "onClose" attribute
dialog.addEventListener(
"close",
() => {
controller.abort();
},
{ once: true },
);
dialog.showModal();
// No types support for "onClose" attribute
dialog.addEventListener(
"close",
() => {
controller.abort();
},
{ once: true },
);

// This doesn't work below the modal, because the Shadow Root extends
dialog.addEventListener("click", () => {
// Normally you'd check for event.target = dialog. But given the shadow root, the target ends up being
// somewhere on the page, not the dialog itself
onOutsideClick?.();
});
}}
style={{
border: 0,
width: "500px",
height: "100vh", // TODO: Replace with frame auto-sizer via messaging
display: "flex", // Fit iframe inside
background: "none",
}}
>
<iframe
src={url.href}
title="Modal content"
// This doesn't work below the modal, because the Shadow Root extends
dialog.addEventListener("click", () => {
// Normally you'd check for event.target = dialog. But given the shadow root, the target ends up being
// somewhere on the page, not the dialog itself
onOutsideClick?.();
});
}}
style={{
border: "0",
flexGrow: 1, // Fit dialog
colorScheme: "normal", // Match parent color scheme #1650
border: 0,
width: "500px",
height: "100vh", // TODO: Replace with frame auto-sizer via messaging
display: "flex", // Fit iframe inside
background: "none",
}}
/>
</dialog>,
>
{nonce && opener ? (
<EphemeralForm nonce={nonce} opener={opener} />
) : (
<iframe
src={url.href}
title="Modal content"
style={{
border: "0",
flexGrow: 1, // Fit dialog
colorScheme: "normal", // Match parent color scheme #1650
}}
/>
)}
</dialog>
</Stylesheets>,
shadowRoot,
);

Expand Down
2 changes: 1 addition & 1 deletion src/components/StylesheetsContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
*/

import React, { useContext } from "react";
import bootstrap from "bootstrap/dist/css/bootstrap.min.css?loadAsUrl";
import bootstrap from "@/vendors/bootstrapWithoutRem.css?loadAsUrl";
import bootstrapOverrides from "@/pageEditor/sidebar/sidebarBootstrapOverrides.scss?loadAsUrl";
import custom from "@/bricks/renderers/customForm.css?loadAsUrl";

Expand Down
2 changes: 1 addition & 1 deletion src/components/floatingActions/FloatingActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

import EmotionShadowRoot from "react-shadow/emotion";
import { Stylesheets } from "@/components/Stylesheets";
import bootstrap from "bootstrap/dist/css/bootstrap.min.css?loadAsUrl";
import bootstrap from "@/vendors/bootstrapWithoutRem.css?loadAsUrl";
import React from "react";
import styles from "./FloatingActions.scss?loadAsUrl";
import ReactDOM from "react-dom";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

import React, { type ChangeEvent, useEffect, useState } from "react";
import ReactDOM from "react-dom";
import bootstrap from "bootstrap/dist/css/bootstrap.min.css?loadAsUrl";
import bootstrap from "@/vendors/bootstrapWithoutRem.css?loadAsUrl";
import Draggable from "react-draggable";
import EmotionShadowRoot from "react-shadow/emotion";
import SwitchButtonWidget, {
Expand Down
2 changes: 1 addition & 1 deletion src/hooks/useAsyncState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {
} from "@/utils/asyncStateUtils";
import { type UUID } from "@/types/stringTypes";

type ValueFactory<T> = Promise<T> | (() => Promise<T>);
type ValueFactory<T> = Promise<T> | (() => Promise<T>) | (() => T);

const warnUndefinedValueOnce = once(() => {
// This will warn once per module -- not once per instance of useAsyncState. We might want to track in the slice
Expand Down
2 changes: 1 addition & 1 deletion src/pageEditor/pageEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import "bootstrap/dist/css/bootstrap.min.css";
import "@/vendors/bootstrapWithoutRem.css";
import "@/vendors/overrides.scss";
import "@/utils/layout.scss";

Expand Down
35 changes: 5 additions & 30 deletions src/sidebar/FormBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ import React from "react";
import { type FormPanelEntry } from "@/types/sidebarTypes";
import useAsyncState from "@/hooks/useAsyncState";
import Loader from "@/components/Loader";
import { getErrorMessage } from "@/errors/errorHelpers";
import { createFrameSource } from "@/bricks/transformers/ephemeralForm/formTransformer";
import EphemeralForm from "@/bricks/transformers/ephemeralForm/EphemeralForm";
import { getConnectedTarget } from "./connectedTarget";

type FormBodyProps = {
form: FormPanelEntry;
Expand All @@ -32,41 +32,16 @@ type FormBodyProps = {
* @constructor
*/
const FormBody: React.FunctionComponent<FormBodyProps> = ({ form }) => {
const {
data: sourceURL,
isLoading,
error,
} = useAsyncState(
async () => createFrameSource(form.nonce, "panel"),
[form.nonce],
);

if (isLoading) {
const { data: target } = useAsyncState(getConnectedTarget, []);
if (!form.nonce || !target) {
return (
<div>
<Loader />
</div>
);
}

if (error) {
return (
<div className="text-danger">
Error getting information for form: {getErrorMessage(error)}
</div>
);
}

return (
<iframe
title={form.nonce}
height="100%"
width="100%"
src={sourceURL.toString()}
style={{ border: "none" }}
allowFullScreen={false}
/>
);
return <EphemeralForm nonce={form.nonce} opener={target} />;
};

export default FormBody;
2 changes: 1 addition & 1 deletion src/sidebar/sidebar.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<head>
<!--Uncomment this script tag to enable the React Dev Tools app-->
<!--See https://github.com/pixiebrix/pixiebrix-extension/wiki/Development-commands#react-dev-tools -->
<!-- <script src="http://localhost:8097"></script> -->
<script src="http://localhost:8097"></script>

Check warning

Code scanning / CodeQL

Inclusion of functionality from an untrusted source Medium

Script loaded using unencrypted connection.
<meta charset="utf-8" />
<!-- Open all links in new tab, never in the sidebar #851 -->
<base target="_blank" />
Expand Down
2 changes: 1 addition & 1 deletion src/tinyPages/ephemeralForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import "bootstrap/dist/css/bootstrap.min.css";
import "@/vendors/bootstrapWithoutRem.css";
import "./ephemeralModal.scss";

import "@/extensionContext";
Expand Down
2 changes: 1 addition & 1 deletion src/tinyPages/ephemeralPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import "bootstrap/dist/css/bootstrap.min.css";
import "@/vendors/bootstrapWithoutRem.css";
import "./ephemeralModal.scss";

import "@/extensionContext";
Expand Down
2 changes: 1 addition & 1 deletion src/tinyPages/restrictedUrlPopup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import "bootstrap/dist/css/bootstrap.min.css";
import "@/vendors/bootstrapWithoutRem.css";
import "@/extensionContext";
import RestrictedUrlPopupApp from "@/tinyPages/RestrictedUrlPopupApp";
import ReactDOM from "react-dom";
Expand Down
Loading
Loading