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

#105 Add sandbox execution; move JQ to it #4701

Merged
merged 28 commits into from
Dec 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
36a70a9
Barebones sandbox loading test via browserAction
fregante Nov 25, 2022
ec232c6
Cleanup first POC, then try Messaging
fregante Nov 25, 2022
94d31de
Setup Messenger-like communicator for postMessage
fregante Nov 26, 2022
3025934
Cleanup
fregante Nov 26, 2022
3f7443c
Add `RENDER_NUNJUCKS`
fregante Dec 4, 2022
c7861c7
Add `APPLY_JQ`
fregante Dec 4, 2022
7755586
Basic error handling
fregante Dec 6, 2022
53d0a6c
/2
fregante Dec 6, 2022
85f0bbe
Types
fregante Dec 6, 2022
6a3b3ea
Live-test JQ
fregante Dec 6, 2022
9af5585
Merge remote-tracking branch 'origin/main' into F/mv3/sandbox
fregante Dec 6, 2022
081d569
Ready for production
fregante Dec 6, 2022
b2415c3
Follow review
fregante Dec 10, 2022
8cdfef7
Refactor to facilitate testing + add 1 test
fregante Dec 11, 2022
26f91f5
Use private channel for the response
fregante Dec 11, 2022
c8093f7
Cleanup
fregante Dec 11, 2022
ea1e4ce
Merge remote-tracking branch 'origin/main' into F/mv3/sandbox
fregante Dec 11, 2022
71f6f6d
Split RequestPacket from ResponsePacket
fregante Dec 11, 2022
952a5d3
Test `postMessage`
fregante Dec 11, 2022
a6727aa
Test `addPostMessageListener`
fregante Dec 11, 2022
f908216
Bypass sandbox in other tests
fregante Dec 11, 2022
0e21bd1
Mock @/utils/postMessage (unused)
fregante Dec 11, 2022
5509174
Revert "Mock @/utils/postMessage (unused)"
fregante Dec 11, 2022
b0418c9
Preload sandbox
fregante Dec 11, 2022
8902e38
Extract unrelated changes
fregante Dec 11, 2022
bfa97dc
Lint
fregante Dec 11, 2022
b6d6bd9
Fix useUndo conflict 🤷‍♂️
fregante Dec 12, 2022
8e763be
Types
fregante Dec 12, 2022
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
19 changes: 19 additions & 0 deletions src/__mocks__/@/sandbox/messenger/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright (C) 2022 PixieBrix, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

// Skip the sandbox messenger
export { renderNunjucksTemplate, applyJq } from "@/sandbox/messenger/executor";
23 changes: 23 additions & 0 deletions src/__mocks__/@/utils/shadowWrap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright (C) 2022 PixieBrix, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

// Don't shadow-wrap in tests because JSDOM can't handle it (e.g. iframes won't load even with `resources:usable`)
export default function shadowWrap(element) {
const wrapper = document.createElement("div");
wrapper.append(element);
return wrapper;
}
8 changes: 2 additions & 6 deletions src/blocks/transformers/jq.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { isNullOrBlank } from "@/utils";
import { InputValidationError } from "@/blocks/errors";
import { isErrorObject } from "@/errors/errorHelpers";
import { BusinessError } from "@/errors/businessErrors";
import { applyJq } from "@/sandbox/messenger/api";

const jqStacktraceRegexp = /jq: error \(at <stdin>:0\): (?<message>.*)/;

Expand Down Expand Up @@ -59,15 +60,10 @@ export class JQTransformer extends Transformer {
): Promise<unknown> {
const input = isNullOrBlank(data) ? ctxt : data;

const { default: jq } =
// @ts-expect-error no existing definitions exist
await import(/* webpackChunkName: "jq-web" */ "jq-web");

logger.debug("Running jq transform", { filter, data, ctxt, input });

try {
// eslint-disable-next-line @typescript-eslint/return-await -- Type is `any`, it throws the rule off
return await jq.promised.json(input, filter);
return await applyJq({ input, filter });
} catch (error) {
// The message length check is there because the JQ error message sometimes is cut and if it is we try to parse the stacktrace
// See https://github.com/pixiebrix/pixiebrix-extension/issues/3216
Expand Down
4 changes: 3 additions & 1 deletion src/contentScript/contentScriptCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ import {
isContextInvalidatedError,
notifyContextInvalidated,
} from "@/errors/contextInvalidated";
import { type UUID } from "@/core";
import { onUncaughtError } from "@/errors/errorHelpers";
import { type UUID } from "@/core";
import initSandbox from "@/sandbox/messenger/api";

// Must come before the default handler for ignoring errors. Otherwise, this handler might not be run
onUncaughtError((error) => {
Expand All @@ -57,6 +58,7 @@ export async function init(uuid: UUID): Promise<void> {
addListenerForUpdateSelectedElement();
initTelemetry();
initToaster();
void initSandbox();

await handleNavigate();

Expand Down
3 changes: 3 additions & 0 deletions src/globals.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ declare module "generate-schema" {
// In the end, the types aren't even used.
declare module "marked";

// No types available
declare module "jq-web";

// From https://github.com/mozilla/page-metadata-parser/issues/116#issuecomment-614882830
declare module "page-metadata-parser" {
export type IPageMetadata = Record<string, string | string[]>;
Expand Down
4 changes: 4 additions & 0 deletions src/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
"run_at": "document_idle"
}
],
"sandbox": {
"pages": ["sandbox.html"]
},
"optional_permissions": ["clipboardWrite", "*://*/*"],
"permissions": [
"activeTab",
Expand All @@ -47,6 +50,7 @@
"web_accessible_resources": [
"css/*",
"bundles/*",
"sandbox.html",
"frame.html",
"frame.css",
"sidebar.html",
Expand Down
68 changes: 68 additions & 0 deletions src/sandbox/messenger/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright (C) 2022 PixieBrix, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

/** @file It doesn't actually use the Messenger but this file tries to replicate the pattern */

import injectIframe, { hiddenIframeStyle } from "@/utils/injectIframe";
import postMessage from "@/utils/postMessage";
import pMemoize from "p-memoize";
import { type JsonValue, type JsonObject } from "type-fest";

// Uses pMemoize to allow retries after a failure
const loadSandbox = pMemoize(async () => {
const iframe = await injectIframe(
chrome.runtime.getURL("sandbox.html"),
hiddenIframeStyle
);
return iframe.contentWindow;
});

export default loadSandbox;

export async function ping() {
return postMessage({
recipient: await loadSandbox(),
type: "SANDBOX_PING",
});
}

export type NunjucksRenderPayload = {
template: string;
context: JsonObject;
autoescape: boolean;
};

export async function renderNunjucksTemplate(payload: NunjucksRenderPayload) {
return postMessage({
recipient: await loadSandbox(),
payload,
type: "RENDER_NUNJUCKS",
});
}

export type ApplyJqPayload = {
input: JsonValue;
filter: string;
};

export async function applyJq(payload: ApplyJqPayload) {
return postMessage({
recipient: await loadSandbox(),
payload,
type: "APPLY_JQ",
});
}
37 changes: 37 additions & 0 deletions src/sandbox/messenger/executor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright (C) 2022 PixieBrix, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import { type ApplyJqPayload, type NunjucksRenderPayload } from "./api";

export async function renderNunjucksTemplate(payload: NunjucksRenderPayload) {
const { template, context, autoescape } = payload;
const { default: nunjucks } = await import(
/* webpackChunkName: "nunjucks" */ "nunjucks"
);

nunjucks.configure({ autoescape });
return nunjucks.renderString(template, context);
}

export async function applyJq(payload: ApplyJqPayload) {
const { input, filter } = payload;
const { default: jq } = await import(
/* webpackChunkName: "jq-web" */ "jq-web"
);

return jq.promised.json(input, filter);
}
27 changes: 27 additions & 0 deletions src/sandbox/messenger/registration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright (C) 2022 PixieBrix, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

/** @file It doesn't actually use the Messenger but this file tries to replicate the pattern */

import { addPostMessageListener } from "@/utils/postMessage";
import { applyJq, renderNunjucksTemplate } from "./executor";

export default function registerMessenger(): void {
addPostMessageListener("SANDBOX_PING", async (payload) => "pong");
addPostMessageListener("RENDER_NUNJUCKS", renderNunjucksTemplate);
addPostMessageListener("APPLY_JQ", applyJq);
}
19 changes: 19 additions & 0 deletions src/sandbox/sandbox.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<!--
~ Copyright (C) 2022 PixieBrix, Inc.
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU Affero General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU Affero General Public License for more details.
~
~ You should have received a copy of the GNU Affero General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<!DOCTYPE html>
<meta charset="utf-8" />
<script src="sandbox.js"></script>
22 changes: 22 additions & 0 deletions src/sandbox/sandbox.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright (C) 2022 PixieBrix, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import registerMessenger from "./messenger/registration";

console.debug("SANDBOX: iframe loaded");

registerMessenger();
7 changes: 7 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,13 @@ export async function waitAnimationFrame(): Promise<void> {
});
}

export async function waitForBody(): Promise<void> {
while (!document.body) {
// eslint-disable-next-line no-await-in-loop -- Polling pattern
await sleep(20);
}
}

/**
* Returns a new object with all the values from the original resolved
*/
Expand Down
32 changes: 32 additions & 0 deletions src/utils/injectIframe.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* @jest-environment-options {"resources": "usable" }
*/
/*
* Copyright (C) 2022 PixieBrix, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import injectIframe from "./injectIframe";

describe("injectIframe", () => {
test("load simple iframe", async () => {
const iframe = await injectIframe("data:text/html,<html>Good soup", {});
expect(
iframe.contentDocument.documentElement.outerHTML
).toMatchInlineSnapshot(
'"<html><head></head><body>Good soup</body></html>"'
);
});
});
Loading