Skip to content

Refactor iframe communication and use MessageChannel #9945

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

Merged
merged 7 commits into from
Apr 2, 2024
Merged
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
14 changes: 9 additions & 5 deletions gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const cli = () => compileTsProject("cli", "built", true);
const webapp = () => compileTsProject("webapp", "built", true);
const reactCommon = () => compileTsProject("react-common", "built/react-common", true);
const pxtblocks = () => compileTsProject("pxtblocks", "built/pxtblocks", true);
const pxtservices = () => compileTsProject("pxtservices", "built/pxtservices", true);

const pxtapp = () => gulp.src([
"node_modules/lzma/src/lzma_worker-min.js",
Expand Down Expand Up @@ -118,7 +119,7 @@ function initWatch() {
pxtlib,
gulp.parallel(pxtcompiler, pxtsim, backendutils),
pxtpy,
gulp.parallel(pxtblocks, pxteditor),
gulp.parallel(pxtblocks, pxteditor, pxtservices),
gulp.parallel(pxtrunner, cli, pxtcommon),
gulp.parallel(updatestrings, browserifyEmbed),
gulp.parallel(pxtjs, pxtdts, pxtapp, pxtworker, pxtembed),
Expand All @@ -139,6 +140,7 @@ function initWatch() {

gulp.watch("./pxtpy/**/*", gulp.series(pxtpy, ...tasks.slice(3)));
gulp.watch("./pxtblocks/**/*", gulp.series(pxtblocks, ...tasks.slice(4)));
gulp.watch("./pxtservices/**/*", gulp.series(pxtservices, ...tasks.slice(4)));

gulp.watch("./pxteditor/**/*", gulp.series(pxteditor, ...tasks.slice(4)));

Expand Down Expand Up @@ -219,6 +221,7 @@ function updatestrings() {
return buildStrings("built/strings.json", [
"cli",
"pxtblocks",
"pxtservices",
"pxtcompiler",
"pxteditor",
"pxtlib",
Expand Down Expand Up @@ -610,8 +613,9 @@ const maybeBuildWebapps = () => {

const lintWithEslint = () => Promise.all(
["cli", "pxtblocks", "pxteditor", "pxtlib", "pxtcompiler",
"pxtpy", "pxtrunner", "pxtsim", "webapp",
"docfiles/pxtweb", "skillmap", "authcode", "multiplayer"/*, "kiosk"*/, "teachertool", "docs/static/streamer"].map(dirname =>
"pxtpy", "pxtrunner", "pxtsim", "webapp", "pxtservices",
"docfiles/pxtweb", "skillmap", "authcode",
"multiplayer"/*, "kiosk"*/, "teachertool", "docs/static/streamer"].map(dirname =>
exec(`node node_modules/eslint/bin/eslint.js -c .eslintrc.js --ext .ts,.tsx ./${dirname}/`, true)))
.then(() => console.log("linted"))
const lint = lintWithEslint
Expand Down Expand Up @@ -723,7 +727,7 @@ const buildAll = gulp.series(
gulp.parallel(pxtlib, pxtweb),
gulp.parallel(pxtcompiler, pxtsim, backendutils),
pxtpy,
gulp.parallel(pxteditor, pxtblocks),
gulp.parallel(pxteditor, pxtblocks, pxtservices),
gulp.parallel(pxtrunner, cli, pxtcommon),
browserifyEmbed,
gulp.parallel(pxtjs, pxtdts, pxtapp, pxtworker, pxtembed),
Expand All @@ -747,7 +751,7 @@ exports.clean = clean;
exports.build = buildAll;

exports.webapp = gulp.series(
gulp.parallel(reactCommon, pxtblocks, pxteditor),
gulp.parallel(reactCommon, pxtblocks, pxteditor, pxtservices),
webapp,
browserifyWebapp,
browserifyAssetEditor
Expand Down
70 changes: 70 additions & 0 deletions localtypings/pxteditor.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ declare namespace pxt.editor {
* flag to request response
*/
response?: boolean;

/**
* Frame identifier that can be passed to the iframe by adding the frameId query parameter
*/
frameId?: string;
}

export interface EditorMessageResponse extends EditorMessage {
Expand Down Expand Up @@ -1237,6 +1242,71 @@ declare namespace pxt.editor {
*/
blockId?: string;
}

interface BaseAssetEditorRequest {
id?: number;
files: pxt.Map<string>;
palette?: string[];
}

interface OpenAssetEditorRequest extends BaseAssetEditorRequest {
type: "open";
assetId: string;
assetType: pxt.AssetType;
}

interface CreateAssetEditorRequest extends BaseAssetEditorRequest {
type: "create";
assetType: pxt.AssetType;
displayName?: string;
}

interface SaveAssetEditorRequest extends BaseAssetEditorRequest {
type: "save";
}

interface DuplicateAssetEditorRequest extends BaseAssetEditorRequest {
type: "duplicate";
assetId: string;
assetType: pxt.AssetType;
}

type AssetEditorRequest = OpenAssetEditorRequest | CreateAssetEditorRequest | SaveAssetEditorRequest | DuplicateAssetEditorRequest;

interface BaseAssetEditorResponse {
id?: number;
}

interface OpenAssetEditorResponse extends BaseAssetEditorResponse {
type: "open";
}

interface CreateAssetEditorResponse extends BaseAssetEditorResponse {
type: "create";
}

interface SaveAssetEditorResponse extends BaseAssetEditorResponse {
type: "save";
files: pxt.Map<string>;
}

interface DuplicateAssetEditorResponse extends BaseAssetEditorResponse {
type: "duplicate";
}

type AssetEditorResponse = OpenAssetEditorResponse | CreateAssetEditorResponse | SaveAssetEditorResponse | DuplicateAssetEditorResponse;

interface AssetEditorRequestSaveEvent {
type: "event";
kind: "done-clicked";
}

interface AssetEditorReadyEvent {
type: "event";
kind: "ready";
}

type AssetEditorEvent = AssetEditorRequestSaveEvent | AssetEditorReadyEvent;
}

declare namespace pxt.workspace {
Expand Down
37 changes: 29 additions & 8 deletions pxteditor/editorcontroller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@
import { runValidatorPlan } from "./code-validation/runValidatorPlan";
import IProjectView = pxt.editor.IProjectView;

import { IFrameEmbeddedClient } from "../pxtservices/iframeEmbeddedClient";

const pendingRequests: pxt.Map<{
resolve: (res?: pxt.editor.EditorMessageResponse | PromiseLike<pxt.editor.EditorMessageResponse>) => void;
reject: (err: any) => void;
}> = {};

let iframeClient: IFrameEmbeddedClient;

/**
* Binds incoming window messages to the project view.
* Requires the "allowParentController" flag in the pxtarget.json/appTheme object.
Expand All @@ -24,7 +29,7 @@ export function bindEditorMessages(getEditorAsync: () => Promise<IProjectView>)

if (!allowEditorMessages && !allowExtensionMessages && !allowSimTelemetry) return;

window.addEventListener("message", (msg: MessageEvent) => {
const handleMessage = (msg: MessageEvent) => {
const data = msg.data as pxt.editor.EditorMessage;
if (!data || !/^pxt(host|editor|pkgext|sim)$/.test(data.type)) return false;

Expand Down Expand Up @@ -154,7 +159,7 @@ export function bindEditorMessages(getEditorAsync: () => Promise<IProjectView>)
})
});
}
case "renderxml": {
case "renderxml": {
const rendermsg = data as pxt.editor.EditorMessageRenderXmlRequest;
return Promise.resolve()
.then(() => {
Expand Down Expand Up @@ -186,7 +191,7 @@ export function bindEditorMessages(getEditorAsync: () => Promise<IProjectView>)
resp = { result: results };
});
}
case "gettoolboxcategories": {
case "gettoolboxcategories": {
const msg = data as pxt.editor.EditorMessageGetToolboxCategoriesRequest;
return Promise.resolve()
.then(() => {
Expand Down Expand Up @@ -289,7 +294,9 @@ export function bindEditorMessages(getEditorAsync: () => Promise<IProjectView>)
}

return true;
}, false)
};

iframeClient = new IFrameEmbeddedClient(handleMessage);
}

/**
Expand Down Expand Up @@ -343,13 +350,20 @@ export function enableControllerAnalytics() {

function sendResponse(request: pxt.editor.EditorMessage, resp: any, success: boolean, error: any) {
if (request.response) {
window.parent.postMessage({
const toSend = {
type: request.type,
id: request.id,
resp,
success,
error
}, "*");
};

if (iframeClient) {
iframeClient.postMessage(toSend)
}
else {
window.parent.postMessage(toSend, "*");
}
}
}

Expand All @@ -369,8 +383,15 @@ export function postHostMessageAsync(msg: pxt.editor.EditorMessageRequest): Prom
env.id = ts.pxtc.Util.guidGen();
if (msg.response)
pendingRequests[env.id] = { resolve, reject };
window.parent.postMessage(env, "*");

if (iframeClient) {
iframeClient.postMessage(env);
}
else {
window.parent.postMessage(env, "*");
}

if (!msg.response)
resolve(undefined)
})
}
}
5 changes: 5 additions & 0 deletions pxtservices/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
"parserOptions": {
"project": "pxtservices/tsconfig.json",
}
}
65 changes: 65 additions & 0 deletions pxtservices/assetEditorDriver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { IframeDriver } from "./iframeDriver";

export class AssetEditorDriver extends IframeDriver {
constructor(frame: HTMLIFrameElement) {
super(frame);
}

async openAsset(assetId: string, assetType: pxt.AssetType, files: pxt.Map<string>, palette?: string[]) {
await this.sendRequest(
{
type: "open",
assetId,
assetType,
files,
palette
} as pxt.editor.OpenAssetEditorRequest
);
}

async createAsset(assetType: pxt.AssetType, files: pxt.Map<string>, displayName?: string, palette?: string[]) {
await this.sendRequest({
type: "create",
assetType,
files,
displayName,
palette
} as pxt.editor.CreateAssetEditorRequest);
}

async saveAsset() {
const resp = await this.sendRequest({
type: "save"
} as pxt.editor.SaveAssetEditorRequest);

return (resp as pxt.editor.SaveAssetEditorResponse).files;
}

async duplicateAsset(assetId: string, assetType: pxt.AssetType, files: pxt.Map<string>, palette?: string[]) {
await this.sendRequest({
type: "duplicate",
assetId,
assetType,
files,
palette
} as pxt.editor.DuplicateAssetEditorRequest);
}

addEventListener(event: "ready", handler: (ev: pxt.editor.AssetEditorReadyEvent) => void): void;
addEventListener(event: "done-clicked", handler: (ev: pxt.editor.AssetEditorRequestSaveEvent) => void): void;
addEventListener(event: string, handler: (ev: any) => void): void {
super.addEventListener(event, handler);
}

protected handleMessage(event: MessageEvent<any>): void {
const data = event.data;
if (!data) return;

if (data.type === "event") {
this.fireEvent((data as pxt.editor.AssetEditorEvent).kind, data);
}
else {
this.resolvePendingMessage(event);
}
}
}
Loading