Skip to content

Commit f4e9aad

Browse files
riknolleanders-ms
andauthored
Refactor iframe communication and use MessageChannel (#9945)
* Refactor iframe communication and use MessageChannel * remove unused typing * Update pxtservices/iframeEmbeddedClient.ts Co-authored-by: Eric Anderson <[email protected]> * Update pxtservices/iframeEmbeddedClient.ts Co-authored-by: Eric Anderson <[email protected]> * pr feedback * lint --------- Co-authored-by: Eric Anderson <[email protected]>
1 parent a344259 commit f4e9aad

File tree

10 files changed

+865
-581
lines changed

10 files changed

+865
-581
lines changed

gulpfile.js

+9-5
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ const cli = () => compileTsProject("cli", "built", true);
4848
const webapp = () => compileTsProject("webapp", "built", true);
4949
const reactCommon = () => compileTsProject("react-common", "built/react-common", true);
5050
const pxtblocks = () => compileTsProject("pxtblocks", "built/pxtblocks", true);
51+
const pxtservices = () => compileTsProject("pxtservices", "built/pxtservices", true);
5152

5253
const pxtapp = () => gulp.src([
5354
"node_modules/lzma/src/lzma_worker-min.js",
@@ -118,7 +119,7 @@ function initWatch() {
118119
pxtlib,
119120
gulp.parallel(pxtcompiler, pxtsim, backendutils),
120121
pxtpy,
121-
gulp.parallel(pxtblocks, pxteditor),
122+
gulp.parallel(pxtblocks, pxteditor, pxtservices),
122123
gulp.parallel(pxtrunner, cli, pxtcommon),
123124
gulp.parallel(updatestrings, browserifyEmbed),
124125
gulp.parallel(pxtjs, pxtdts, pxtapp, pxtworker, pxtembed),
@@ -139,6 +140,7 @@ function initWatch() {
139140

140141
gulp.watch("./pxtpy/**/*", gulp.series(pxtpy, ...tasks.slice(3)));
141142
gulp.watch("./pxtblocks/**/*", gulp.series(pxtblocks, ...tasks.slice(4)));
143+
gulp.watch("./pxtservices/**/*", gulp.series(pxtservices, ...tasks.slice(4)));
142144

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

@@ -219,6 +221,7 @@ function updatestrings() {
219221
return buildStrings("built/strings.json", [
220222
"cli",
221223
"pxtblocks",
224+
"pxtservices",
222225
"pxtcompiler",
223226
"pxteditor",
224227
"pxtlib",
@@ -610,8 +613,9 @@ const maybeBuildWebapps = () => {
610613

611614
const lintWithEslint = () => Promise.all(
612615
["cli", "pxtblocks", "pxteditor", "pxtlib", "pxtcompiler",
613-
"pxtpy", "pxtrunner", "pxtsim", "webapp",
614-
"docfiles/pxtweb", "skillmap", "authcode", "multiplayer"/*, "kiosk"*/, "teachertool", "docs/static/streamer"].map(dirname =>
616+
"pxtpy", "pxtrunner", "pxtsim", "webapp", "pxtservices",
617+
"docfiles/pxtweb", "skillmap", "authcode",
618+
"multiplayer"/*, "kiosk"*/, "teachertool", "docs/static/streamer"].map(dirname =>
615619
exec(`node node_modules/eslint/bin/eslint.js -c .eslintrc.js --ext .ts,.tsx ./${dirname}/`, true)))
616620
.then(() => console.log("linted"))
617621
const lint = lintWithEslint
@@ -723,7 +727,7 @@ const buildAll = gulp.series(
723727
gulp.parallel(pxtlib, pxtweb),
724728
gulp.parallel(pxtcompiler, pxtsim, backendutils),
725729
pxtpy,
726-
gulp.parallel(pxteditor, pxtblocks),
730+
gulp.parallel(pxteditor, pxtblocks, pxtservices),
727731
gulp.parallel(pxtrunner, cli, pxtcommon),
728732
browserifyEmbed,
729733
gulp.parallel(pxtjs, pxtdts, pxtapp, pxtworker, pxtembed),
@@ -747,7 +751,7 @@ exports.clean = clean;
747751
exports.build = buildAll;
748752

749753
exports.webapp = gulp.series(
750-
gulp.parallel(reactCommon, pxtblocks, pxteditor),
754+
gulp.parallel(reactCommon, pxtblocks, pxteditor, pxtservices),
751755
webapp,
752756
browserifyWebapp,
753757
browserifyAssetEditor

localtypings/pxteditor.d.ts

+70
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ declare namespace pxt.editor {
1616
* flag to request response
1717
*/
1818
response?: boolean;
19+
20+
/**
21+
* Frame identifier that can be passed to the iframe by adding the frameId query parameter
22+
*/
23+
frameId?: string;
1924
}
2025

2126
export interface EditorMessageResponse extends EditorMessage {
@@ -1237,6 +1242,71 @@ declare namespace pxt.editor {
12371242
*/
12381243
blockId?: string;
12391244
}
1245+
1246+
interface BaseAssetEditorRequest {
1247+
id?: number;
1248+
files: pxt.Map<string>;
1249+
palette?: string[];
1250+
}
1251+
1252+
interface OpenAssetEditorRequest extends BaseAssetEditorRequest {
1253+
type: "open";
1254+
assetId: string;
1255+
assetType: pxt.AssetType;
1256+
}
1257+
1258+
interface CreateAssetEditorRequest extends BaseAssetEditorRequest {
1259+
type: "create";
1260+
assetType: pxt.AssetType;
1261+
displayName?: string;
1262+
}
1263+
1264+
interface SaveAssetEditorRequest extends BaseAssetEditorRequest {
1265+
type: "save";
1266+
}
1267+
1268+
interface DuplicateAssetEditorRequest extends BaseAssetEditorRequest {
1269+
type: "duplicate";
1270+
assetId: string;
1271+
assetType: pxt.AssetType;
1272+
}
1273+
1274+
type AssetEditorRequest = OpenAssetEditorRequest | CreateAssetEditorRequest | SaveAssetEditorRequest | DuplicateAssetEditorRequest;
1275+
1276+
interface BaseAssetEditorResponse {
1277+
id?: number;
1278+
}
1279+
1280+
interface OpenAssetEditorResponse extends BaseAssetEditorResponse {
1281+
type: "open";
1282+
}
1283+
1284+
interface CreateAssetEditorResponse extends BaseAssetEditorResponse {
1285+
type: "create";
1286+
}
1287+
1288+
interface SaveAssetEditorResponse extends BaseAssetEditorResponse {
1289+
type: "save";
1290+
files: pxt.Map<string>;
1291+
}
1292+
1293+
interface DuplicateAssetEditorResponse extends BaseAssetEditorResponse {
1294+
type: "duplicate";
1295+
}
1296+
1297+
type AssetEditorResponse = OpenAssetEditorResponse | CreateAssetEditorResponse | SaveAssetEditorResponse | DuplicateAssetEditorResponse;
1298+
1299+
interface AssetEditorRequestSaveEvent {
1300+
type: "event";
1301+
kind: "done-clicked";
1302+
}
1303+
1304+
interface AssetEditorReadyEvent {
1305+
type: "event";
1306+
kind: "ready";
1307+
}
1308+
1309+
type AssetEditorEvent = AssetEditorRequestSaveEvent | AssetEditorReadyEvent;
12401310
}
12411311

12421312
declare namespace pxt.workspace {

pxteditor/editorcontroller.ts

+29-8
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,15 @@
33
import { runValidatorPlan } from "./code-validation/runValidatorPlan";
44
import IProjectView = pxt.editor.IProjectView;
55

6+
import { IFrameEmbeddedClient } from "../pxtservices/iframeEmbeddedClient";
7+
68
const pendingRequests: pxt.Map<{
79
resolve: (res?: pxt.editor.EditorMessageResponse | PromiseLike<pxt.editor.EditorMessageResponse>) => void;
810
reject: (err: any) => void;
911
}> = {};
12+
13+
let iframeClient: IFrameEmbeddedClient;
14+
1015
/**
1116
* Binds incoming window messages to the project view.
1217
* Requires the "allowParentController" flag in the pxtarget.json/appTheme object.
@@ -24,7 +29,7 @@ export function bindEditorMessages(getEditorAsync: () => Promise<IProjectView>)
2429

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

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

@@ -154,7 +159,7 @@ export function bindEditorMessages(getEditorAsync: () => Promise<IProjectView>)
154159
})
155160
});
156161
}
157-
case "renderxml": {
162+
case "renderxml": {
158163
const rendermsg = data as pxt.editor.EditorMessageRenderXmlRequest;
159164
return Promise.resolve()
160165
.then(() => {
@@ -186,7 +191,7 @@ export function bindEditorMessages(getEditorAsync: () => Promise<IProjectView>)
186191
resp = { result: results };
187192
});
188193
}
189-
case "gettoolboxcategories": {
194+
case "gettoolboxcategories": {
190195
const msg = data as pxt.editor.EditorMessageGetToolboxCategoriesRequest;
191196
return Promise.resolve()
192197
.then(() => {
@@ -289,7 +294,9 @@ export function bindEditorMessages(getEditorAsync: () => Promise<IProjectView>)
289294
}
290295

291296
return true;
292-
}, false)
297+
};
298+
299+
iframeClient = new IFrameEmbeddedClient(handleMessage);
293300
}
294301

295302
/**
@@ -343,13 +350,20 @@ export function enableControllerAnalytics() {
343350

344351
function sendResponse(request: pxt.editor.EditorMessage, resp: any, success: boolean, error: any) {
345352
if (request.response) {
346-
window.parent.postMessage({
353+
const toSend = {
347354
type: request.type,
348355
id: request.id,
349356
resp,
350357
success,
351358
error
352-
}, "*");
359+
};
360+
361+
if (iframeClient) {
362+
iframeClient.postMessage(toSend)
363+
}
364+
else {
365+
window.parent.postMessage(toSend, "*");
366+
}
353367
}
354368
}
355369

@@ -369,8 +383,15 @@ export function postHostMessageAsync(msg: pxt.editor.EditorMessageRequest): Prom
369383
env.id = ts.pxtc.Util.guidGen();
370384
if (msg.response)
371385
pendingRequests[env.id] = { resolve, reject };
372-
window.parent.postMessage(env, "*");
386+
387+
if (iframeClient) {
388+
iframeClient.postMessage(env);
389+
}
390+
else {
391+
window.parent.postMessage(env, "*");
392+
}
393+
373394
if (!msg.response)
374395
resolve(undefined)
375396
})
376-
}
397+
}

pxtservices/.eslintrc.js

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module.exports = {
2+
"parserOptions": {
3+
"project": "pxtservices/tsconfig.json",
4+
}
5+
}

pxtservices/assetEditorDriver.ts

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { IframeDriver } from "./iframeDriver";
2+
3+
export class AssetEditorDriver extends IframeDriver {
4+
constructor(frame: HTMLIFrameElement) {
5+
super(frame);
6+
}
7+
8+
async openAsset(assetId: string, assetType: pxt.AssetType, files: pxt.Map<string>, palette?: string[]) {
9+
await this.sendRequest(
10+
{
11+
type: "open",
12+
assetId,
13+
assetType,
14+
files,
15+
palette
16+
} as pxt.editor.OpenAssetEditorRequest
17+
);
18+
}
19+
20+
async createAsset(assetType: pxt.AssetType, files: pxt.Map<string>, displayName?: string, palette?: string[]) {
21+
await this.sendRequest({
22+
type: "create",
23+
assetType,
24+
files,
25+
displayName,
26+
palette
27+
} as pxt.editor.CreateAssetEditorRequest);
28+
}
29+
30+
async saveAsset() {
31+
const resp = await this.sendRequest({
32+
type: "save"
33+
} as pxt.editor.SaveAssetEditorRequest);
34+
35+
return (resp as pxt.editor.SaveAssetEditorResponse).files;
36+
}
37+
38+
async duplicateAsset(assetId: string, assetType: pxt.AssetType, files: pxt.Map<string>, palette?: string[]) {
39+
await this.sendRequest({
40+
type: "duplicate",
41+
assetId,
42+
assetType,
43+
files,
44+
palette
45+
} as pxt.editor.DuplicateAssetEditorRequest);
46+
}
47+
48+
addEventListener(event: "ready", handler: (ev: pxt.editor.AssetEditorReadyEvent) => void): void;
49+
addEventListener(event: "done-clicked", handler: (ev: pxt.editor.AssetEditorRequestSaveEvent) => void): void;
50+
addEventListener(event: string, handler: (ev: any) => void): void {
51+
super.addEventListener(event, handler);
52+
}
53+
54+
protected handleMessage(event: MessageEvent<any>): void {
55+
const data = event.data;
56+
if (!data) return;
57+
58+
if (data.type === "event") {
59+
this.fireEvent((data as pxt.editor.AssetEditorEvent).kind, data);
60+
}
61+
else {
62+
this.resolvePendingMessage(event);
63+
}
64+
}
65+
}

0 commit comments

Comments
 (0)