@raydiant/playlist-window-manager 0.29.1 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/IFrameRenderer.d.ts +1 -0
- package/build/IFrameRenderer.js +14 -0
- package/build/IFrameRenderer.test.d.ts +1 -0
- package/build/IFrameRenderer.test.js +70 -0
- package/build/PlaylistWindowManager.d.ts +7 -0
- package/build/PlaylistWindowManager.js +186 -65
- package/build/PlaylistWindowManager.test.d.ts +1 -0
- package/build/PlaylistWindowManager.test.js +1763 -0
- package/build/PresentationWindow.d.ts +37 -1
- package/build/PresentationWindow.js +601 -31
- package/build/PresentationWindow.test.js +509 -0
- package/build/SurfaceBroker.d.ts +30 -0
- package/build/SurfaceBroker.js +311 -0
- package/build/SurfaceBroker.test.d.ts +1 -0
- package/build/SurfaceBroker.test.js +432 -0
- package/build/index.d.ts +1 -0
- package/build/index.js +1 -0
- package/build/surfaces.d.ts +52 -0
- package/build/surfaces.js +15 -0
- package/build/testUtils.d.ts +56 -0
- package/build/testUtils.js +327 -5
- package/build/types.d.ts +1 -0
- package/package.json +2 -1
- package/src/IFrameRenderer.test.ts +88 -0
- package/src/IFrameRenderer.ts +21 -0
- package/src/PlaylistWindowManager.test.ts +1466 -0
- package/src/PlaylistWindowManager.ts +164 -20
- package/src/PresentationWindow.test.ts +371 -0
- package/src/PresentationWindow.ts +712 -4
- package/src/SurfaceBroker.test.ts +278 -0
- package/src/SurfaceBroker.ts +281 -0
- package/src/__snapshots__/PlaylistWindowManager.test.ts.snap +26 -0
- package/src/index.ts +1 -0
- package/src/surfaces.ts +82 -0
- package/src/testUtils.ts +335 -1
- package/src/types.ts +5 -0
package/build/testUtils.js
CHANGED
|
@@ -62,12 +62,19 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
62
62
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
63
63
|
};
|
|
64
64
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
65
|
-
exports.createRenderer = exports.createLogger = exports.createRuleEngineState = exports.createPlaylistWindowManagerState = exports.createApplicationVersion = exports.createPresentationRelationships = exports.createPresentation = exports.createPresentationWindow = void 0;
|
|
65
|
+
exports.flush = exports.createFakeSurfaceHost = exports.createRecordingRenderer = exports.createRenderer = exports.createLogger = exports.createRuleEngineState = exports.createPlaylistWindowManagerState = exports.createApplicationVersion = exports.createPresentationRelationships = exports.createPresentation = exports.createPresentationWindow = void 0;
|
|
66
66
|
var re = __importStar(require("@raydiant/playlist-rule-engine"));
|
|
67
67
|
var PresentationWindow_1 = __importDefault(require("./PresentationWindow"));
|
|
68
68
|
var PlaylistWindowManagerState_1 = __importDefault(require("./PlaylistWindowManagerState"));
|
|
69
|
+
// Orders calls recorded by the renderer and the fake surface host against
|
|
70
|
+
// each other.
|
|
71
|
+
var sequence = 0;
|
|
72
|
+
var nextSequence = function () {
|
|
73
|
+
sequence += 1;
|
|
74
|
+
return sequence;
|
|
75
|
+
};
|
|
69
76
|
function createPresentationWindow(overrides) {
|
|
70
|
-
var _a, _b, _c, _d, _e, _f;
|
|
77
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
71
78
|
if (overrides === void 0) { overrides = {}; }
|
|
72
79
|
return new PresentationWindow_1.default({
|
|
73
80
|
windowId: (_a = overrides.windowId) !== null && _a !== void 0 ? _a : 'test',
|
|
@@ -76,9 +83,9 @@ function createPresentationWindow(overrides) {
|
|
|
76
83
|
publishedAt: (_b = overrides.publishedAt) !== null && _b !== void 0 ? _b : 'publishedAt',
|
|
77
84
|
playlistWindowManagerState: (_c = overrides.playlistWindowManagerState) !== null && _c !== void 0 ? _c : createPlaylistWindowManagerState(),
|
|
78
85
|
ruleEngineState: (_d = overrides.ruleEngineState) !== null && _d !== void 0 ? _d : createRuleEngineState(),
|
|
79
|
-
logger: createLogger(),
|
|
80
|
-
workerState: (
|
|
81
|
-
renderer: (
|
|
86
|
+
logger: (_e = overrides.logger) !== null && _e !== void 0 ? _e : createLogger(),
|
|
87
|
+
workerState: (_f = overrides.workerState) !== null && _f !== void 0 ? _f : {},
|
|
88
|
+
renderer: (_g = overrides.renderer) !== null && _g !== void 0 ? _g : {
|
|
82
89
|
createWindow: function () { },
|
|
83
90
|
loadWindow: function () {
|
|
84
91
|
return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
@@ -101,6 +108,7 @@ function createPresentationWindow(overrides) {
|
|
|
101
108
|
getTerminal: overrides.getTerminal,
|
|
102
109
|
isPreview: overrides.isPreview,
|
|
103
110
|
device: overrides.device,
|
|
111
|
+
surfaceBroker: overrides.surfaceBroker,
|
|
104
112
|
});
|
|
105
113
|
}
|
|
106
114
|
exports.createPresentationWindow = createPresentationWindow;
|
|
@@ -210,3 +218,317 @@ function createRenderer() {
|
|
|
210
218
|
};
|
|
211
219
|
}
|
|
212
220
|
exports.createRenderer = createRenderer;
|
|
221
|
+
// A renderer that records every call in order, across every window, and lets
|
|
222
|
+
// a test play the app's side of the message channel through its hooks.
|
|
223
|
+
function createRecordingRenderer() {
|
|
224
|
+
var calls = [];
|
|
225
|
+
// Messages the apps sent the window manager, in the same order as calls.
|
|
226
|
+
var received = [];
|
|
227
|
+
var callbacks = {};
|
|
228
|
+
var errorCallbacks = {};
|
|
229
|
+
var hooks = {};
|
|
230
|
+
var record = function (method, windowId) {
|
|
231
|
+
var args = [];
|
|
232
|
+
for (var _i = 2; _i < arguments.length; _i++) {
|
|
233
|
+
args[_i - 2] = arguments[_i];
|
|
234
|
+
}
|
|
235
|
+
calls.push({ method: method, windowId: windowId, args: args, seq: nextSequence() });
|
|
236
|
+
};
|
|
237
|
+
var renderer = {
|
|
238
|
+
createWindow: function (opts) {
|
|
239
|
+
callbacks[opts.windowId] = opts.receiveMessageCallback || (function () { return null; });
|
|
240
|
+
errorCallbacks[opts.windowId] = opts.errorCallback;
|
|
241
|
+
record('createWindow', opts.windowId, opts.backgroundColor);
|
|
242
|
+
},
|
|
243
|
+
loadWindow: function (windowId) {
|
|
244
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
245
|
+
return __generator(this, function (_a) {
|
|
246
|
+
record('loadWindow', windowId);
|
|
247
|
+
if (hooks.onLoad)
|
|
248
|
+
hooks.onLoad(windowId);
|
|
249
|
+
return [2 /*return*/];
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
},
|
|
253
|
+
updateWindowBounds: function (windowId, bounds) {
|
|
254
|
+
record('updateWindowBounds', windowId, bounds);
|
|
255
|
+
},
|
|
256
|
+
showWindow: function (windowId, isFocused) {
|
|
257
|
+
record('showWindow', windowId, isFocused);
|
|
258
|
+
},
|
|
259
|
+
hideWindow: function (windowId) {
|
|
260
|
+
record('hideWindow', windowId);
|
|
261
|
+
},
|
|
262
|
+
removeWindow: function (windowId) {
|
|
263
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
264
|
+
return __generator(this, function (_a) {
|
|
265
|
+
record('removeWindow', windowId);
|
|
266
|
+
delete callbacks[windowId];
|
|
267
|
+
return [2 /*return*/];
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
},
|
|
271
|
+
sendMessageString: function (windowId, message) {
|
|
272
|
+
var _a = JSON.parse(message), type = _a.type, payload = _a.payload;
|
|
273
|
+
record('sendMessageString', windowId, type, payload);
|
|
274
|
+
if (hooks.onMessage)
|
|
275
|
+
hooks.onMessage(windowId, type, payload);
|
|
276
|
+
},
|
|
277
|
+
sendMessageObject: function (windowId, type, payload) {
|
|
278
|
+
record('sendMessageObject', windowId, type, payload);
|
|
279
|
+
},
|
|
280
|
+
muteWindow: function (windowId) {
|
|
281
|
+
record('muteWindow', windowId);
|
|
282
|
+
},
|
|
283
|
+
unMuteWindow: function (windowId) {
|
|
284
|
+
record('unMuteWindow', windowId);
|
|
285
|
+
},
|
|
286
|
+
setWindowBackground: function (windowId, color) {
|
|
287
|
+
record('setWindowBackground', windowId, color);
|
|
288
|
+
},
|
|
289
|
+
};
|
|
290
|
+
return {
|
|
291
|
+
renderer: renderer,
|
|
292
|
+
calls: calls,
|
|
293
|
+
received: received,
|
|
294
|
+
hooks: hooks,
|
|
295
|
+
// Deliver a message from the app in a window to the window manager.
|
|
296
|
+
receive: function (windowId, type, payload) {
|
|
297
|
+
var callback = callbacks[windowId];
|
|
298
|
+
if (!callback)
|
|
299
|
+
return;
|
|
300
|
+
received.push({
|
|
301
|
+
method: type,
|
|
302
|
+
windowId: windowId,
|
|
303
|
+
args: [payload],
|
|
304
|
+
seq: nextSequence(),
|
|
305
|
+
});
|
|
306
|
+
callback(type, payload);
|
|
307
|
+
},
|
|
308
|
+
// Report a window as crashed or unresponsive, as a renderer would.
|
|
309
|
+
fail: function (windowId, err) {
|
|
310
|
+
var callback = errorCallbacks[windowId];
|
|
311
|
+
if (callback && callbacks[windowId])
|
|
312
|
+
callback(err);
|
|
313
|
+
},
|
|
314
|
+
// Windows created and not yet removed, oldest first.
|
|
315
|
+
liveWindowIds: function () {
|
|
316
|
+
return Object.keys(callbacks);
|
|
317
|
+
},
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
exports.createRecordingRenderer = createRecordingRenderer;
|
|
321
|
+
// A stand-in for the Signage Stick host's video surface, typed to the video
|
|
322
|
+
// contract. It records every call in order and records a rule break, rather
|
|
323
|
+
// than throwing, so a test can play a whole playlist and then assert that no
|
|
324
|
+
// rule was broken.
|
|
325
|
+
function createFakeSurfaceHost(opts) {
|
|
326
|
+
var _a, _b, _c;
|
|
327
|
+
if (opts === void 0) { opts = {}; }
|
|
328
|
+
var decoders = (_a = opts.decoders) !== null && _a !== void 0 ? _a : 4;
|
|
329
|
+
var refuseNext = opts.refuse;
|
|
330
|
+
var hangNext = (_b = opts.hang) !== null && _b !== void 0 ? _b : false;
|
|
331
|
+
var hangShowNext = (_c = opts.hangShow) !== null && _c !== void 0 ? _c : false;
|
|
332
|
+
var calls = [];
|
|
333
|
+
var violations = [];
|
|
334
|
+
var states = {};
|
|
335
|
+
var hung = [];
|
|
336
|
+
var lastId = 0;
|
|
337
|
+
var record = function (method, args, id) {
|
|
338
|
+
calls.push({ id: id, method: method, args: args, seq: nextSequence() });
|
|
339
|
+
};
|
|
340
|
+
var emit = function (id, event) {
|
|
341
|
+
var state = states[id];
|
|
342
|
+
if (!state)
|
|
343
|
+
throw new Error("No fake surface ".concat(id));
|
|
344
|
+
if (event.type === 'first_frame')
|
|
345
|
+
state.firstFrame = true;
|
|
346
|
+
if (state.listener) {
|
|
347
|
+
state.listener(event);
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
state.buffered.push(event);
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
var live = function () {
|
|
354
|
+
return Object.keys(states)
|
|
355
|
+
.map(function (key) { return states[Number(key)]; })
|
|
356
|
+
.filter(function (s) { return s.live; });
|
|
357
|
+
};
|
|
358
|
+
var checkBounds = function (bounds) {
|
|
359
|
+
var values = [bounds.x, bounds.y, bounds.width, bounds.height];
|
|
360
|
+
if (values.some(function (v) { return Math.round(v) !== v; })) {
|
|
361
|
+
violations.push("fractional bounds ".concat(JSON.stringify(bounds)));
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
var createSurface = function () {
|
|
365
|
+
lastId += 1;
|
|
366
|
+
var id = lastId;
|
|
367
|
+
var state = {
|
|
368
|
+
id: id,
|
|
369
|
+
live: true,
|
|
370
|
+
firstFrame: false,
|
|
371
|
+
muted: true,
|
|
372
|
+
shown: false,
|
|
373
|
+
listener: null,
|
|
374
|
+
buffered: [],
|
|
375
|
+
};
|
|
376
|
+
states[id] = state;
|
|
377
|
+
return {
|
|
378
|
+
id: id,
|
|
379
|
+
show: function (showOpts) {
|
|
380
|
+
record('show', [showOpts], id);
|
|
381
|
+
if (!state.live)
|
|
382
|
+
return Promise.resolve();
|
|
383
|
+
if (!state.firstFrame) {
|
|
384
|
+
violations.push("surface ".concat(id, " shown before first_frame"));
|
|
385
|
+
}
|
|
386
|
+
live()
|
|
387
|
+
.filter(function (s) { return s !== state && s.shown && !s.muted; })
|
|
388
|
+
.forEach(function (s) {
|
|
389
|
+
violations.push("surface ".concat(id, " shown before surface ").concat(s.id, " was muted"));
|
|
390
|
+
});
|
|
391
|
+
state.shown = true;
|
|
392
|
+
if (hangShowNext) {
|
|
393
|
+
hangShowNext = false;
|
|
394
|
+
return new Promise(function () { return undefined; });
|
|
395
|
+
}
|
|
396
|
+
return Promise.resolve();
|
|
397
|
+
},
|
|
398
|
+
hide: function (hideOpts) {
|
|
399
|
+
record('hide', [hideOpts], id);
|
|
400
|
+
state.shown = false;
|
|
401
|
+
return Promise.resolve();
|
|
402
|
+
},
|
|
403
|
+
play: function () {
|
|
404
|
+
record('play', [], id);
|
|
405
|
+
if (opts.playingAfterMs !== undefined) {
|
|
406
|
+
setTimeout(function () {
|
|
407
|
+
if (state.live)
|
|
408
|
+
emit(id, { type: 'playing' });
|
|
409
|
+
}, opts.playingAfterMs);
|
|
410
|
+
}
|
|
411
|
+
return Promise.resolve();
|
|
412
|
+
},
|
|
413
|
+
pause: function () {
|
|
414
|
+
record('pause', [], id);
|
|
415
|
+
return Promise.resolve();
|
|
416
|
+
},
|
|
417
|
+
seek: function (seconds) {
|
|
418
|
+
record('seek', [seconds], id);
|
|
419
|
+
return Promise.resolve();
|
|
420
|
+
},
|
|
421
|
+
setBounds: function (r) {
|
|
422
|
+
record('setBounds', [r], id);
|
|
423
|
+
checkBounds(r);
|
|
424
|
+
return Promise.resolve();
|
|
425
|
+
},
|
|
426
|
+
setMuted: function (m) {
|
|
427
|
+
record('setMuted', [m], id);
|
|
428
|
+
if (!m && state.live) {
|
|
429
|
+
live()
|
|
430
|
+
.filter(function (s) { return s !== state && !s.muted; })
|
|
431
|
+
.forEach(function (s) {
|
|
432
|
+
violations.push("surface ".concat(id, " unmuted while surface ").concat(s.id, " sounds"));
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
if (state.live)
|
|
436
|
+
state.muted = m;
|
|
437
|
+
return Promise.resolve();
|
|
438
|
+
},
|
|
439
|
+
close: function () {
|
|
440
|
+
record('close', [], id);
|
|
441
|
+
state.live = false;
|
|
442
|
+
return Promise.resolve();
|
|
443
|
+
},
|
|
444
|
+
onEvent: function (fn) {
|
|
445
|
+
state.listener = fn;
|
|
446
|
+
var buffered = state.buffered;
|
|
447
|
+
state.buffered = [];
|
|
448
|
+
buffered.forEach(function (e) { return fn(e); });
|
|
449
|
+
},
|
|
450
|
+
};
|
|
451
|
+
};
|
|
452
|
+
var surfaces = {
|
|
453
|
+
openVideo: function (videoOpts) {
|
|
454
|
+
record('openVideo', [videoOpts]);
|
|
455
|
+
checkBounds(videoOpts.bounds);
|
|
456
|
+
if (hangNext) {
|
|
457
|
+
hangNext = false;
|
|
458
|
+
return new Promise(function (resolve) {
|
|
459
|
+
hung.push(function () { return resolve(createSurface()); });
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
if (refuseNext) {
|
|
463
|
+
var reason = refuseNext;
|
|
464
|
+
refuseNext = undefined;
|
|
465
|
+
return Promise.reject(new Error(reason));
|
|
466
|
+
}
|
|
467
|
+
if (live().length >= decoders) {
|
|
468
|
+
return Promise.reject(new Error('no_decoder'));
|
|
469
|
+
}
|
|
470
|
+
if (live().length >= 2) {
|
|
471
|
+
violations.push('a third live surface');
|
|
472
|
+
}
|
|
473
|
+
var surface = createSurface();
|
|
474
|
+
calls[calls.length - 1].id = surface.id;
|
|
475
|
+
return Promise.resolve(surface);
|
|
476
|
+
},
|
|
477
|
+
onHole: function (open) {
|
|
478
|
+
record('onHole', [open]);
|
|
479
|
+
},
|
|
480
|
+
};
|
|
481
|
+
return {
|
|
482
|
+
surfaces: surfaces,
|
|
483
|
+
calls: calls,
|
|
484
|
+
violations: violations,
|
|
485
|
+
// Raises an event on a surface, as the host would.
|
|
486
|
+
emit: emit,
|
|
487
|
+
// Lets a hung open resolve, late.
|
|
488
|
+
releaseHung: function () {
|
|
489
|
+
hung.splice(0).forEach(function (release) { return release(); });
|
|
490
|
+
},
|
|
491
|
+
liveIds: function () {
|
|
492
|
+
return live().map(function (s) { return s.id; });
|
|
493
|
+
},
|
|
494
|
+
lastId: function () {
|
|
495
|
+
return lastId;
|
|
496
|
+
},
|
|
497
|
+
// The next open is refused, as the host would refuse it.
|
|
498
|
+
refuseNext: function (reason) {
|
|
499
|
+
refuseNext = reason;
|
|
500
|
+
},
|
|
501
|
+
hangNextOpen: function () {
|
|
502
|
+
hangNext = true;
|
|
503
|
+
},
|
|
504
|
+
hangNextShow: function () {
|
|
505
|
+
hangShowNext = true;
|
|
506
|
+
},
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
exports.createFakeSurfaceHost = createFakeSurfaceHost;
|
|
510
|
+
// Lets pending promise callbacks and zero-delay timers run.
|
|
511
|
+
function flush(times) {
|
|
512
|
+
if (times === void 0) { times = 5; }
|
|
513
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
514
|
+
var i;
|
|
515
|
+
return __generator(this, function (_a) {
|
|
516
|
+
switch (_a.label) {
|
|
517
|
+
case 0:
|
|
518
|
+
i = 0;
|
|
519
|
+
_a.label = 1;
|
|
520
|
+
case 1:
|
|
521
|
+
if (!(i < times)) return [3 /*break*/, 4];
|
|
522
|
+
return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 0); })];
|
|
523
|
+
case 2:
|
|
524
|
+
_a.sent();
|
|
525
|
+
_a.label = 3;
|
|
526
|
+
case 3:
|
|
527
|
+
i += 1;
|
|
528
|
+
return [3 /*break*/, 1];
|
|
529
|
+
case 4: return [2 /*return*/];
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
exports.flush = flush;
|
package/build/types.d.ts
CHANGED
|
@@ -113,6 +113,7 @@ export interface PresentationWindowRenderer {
|
|
|
113
113
|
muteWindow(windowId: string): void;
|
|
114
114
|
unMuteWindow(windowId: string): void;
|
|
115
115
|
sendOnlineStatus?(windowId: string, isOnline: boolean): void;
|
|
116
|
+
setWindowBackground?(windowId: string, color: string): void;
|
|
116
117
|
}
|
|
117
118
|
export interface OpenChildWindowOptions {
|
|
118
119
|
windowId: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@raydiant/playlist-window-manager",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.30.0",
|
|
4
4
|
"description": "Window manager for playlist playback.",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"@types/uuid": "8.3.0",
|
|
25
25
|
"electron": "38.0.0",
|
|
26
26
|
"jest": "^24.8.0",
|
|
27
|
+
"mockdate": "^3.0.2",
|
|
27
28
|
"ts-jest": "^24.0.2",
|
|
28
29
|
"typescript": "^4.1.2"
|
|
29
30
|
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import IFrameRenderer from './IFrameRenderer';
|
|
2
|
+
|
|
3
|
+
function setup() {
|
|
4
|
+
const container = document.createElement('div');
|
|
5
|
+
document.body.appendChild(container);
|
|
6
|
+
const renderer = new IFrameRenderer(container);
|
|
7
|
+
const received: Array<[string, unknown]> = [];
|
|
8
|
+
renderer.createWindow({
|
|
9
|
+
windowId: 'w1',
|
|
10
|
+
backgroundColor: '#000000',
|
|
11
|
+
receiveMessageCallback: (type, payload) => received.push([type, payload]),
|
|
12
|
+
errorCallback: () => undefined,
|
|
13
|
+
});
|
|
14
|
+
const frame = container.querySelector('iframe') as HTMLIFrameElement;
|
|
15
|
+
|
|
16
|
+
const post = (type: string, source: Window | null) => {
|
|
17
|
+
window.dispatchEvent(
|
|
18
|
+
new MessageEvent('message', {
|
|
19
|
+
data: `raydiant:${JSON.stringify({ windowId: 'w1', type })}`,
|
|
20
|
+
source,
|
|
21
|
+
})
|
|
22
|
+
);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const teardown = () => {
|
|
26
|
+
renderer.destroy();
|
|
27
|
+
document.body.removeChild(container);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
return { renderer, frame, received, post, teardown };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe('only the window’s own frame asks for a video surface', () => {
|
|
34
|
+
test.each(['open_video', 'video_bounds', 'video_hole_open', 'close_video'])(
|
|
35
|
+
"%s from another frame's source is dropped",
|
|
36
|
+
(type) => {
|
|
37
|
+
const { frame, received, post, teardown } = setup();
|
|
38
|
+
const other = document.createElement('iframe');
|
|
39
|
+
document.body.appendChild(other);
|
|
40
|
+
|
|
41
|
+
post(type, other.contentWindow);
|
|
42
|
+
expect(received).toEqual([]);
|
|
43
|
+
|
|
44
|
+
post(type, frame.contentWindow);
|
|
45
|
+
expect(received).toEqual([[type, undefined]]);
|
|
46
|
+
|
|
47
|
+
document.body.removeChild(other);
|
|
48
|
+
teardown();
|
|
49
|
+
}
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
test("every other message keeps today's routing by window id", () => {
|
|
53
|
+
const { received, post, teardown } = setup();
|
|
54
|
+
|
|
55
|
+
post('presentation_ready', null);
|
|
56
|
+
expect(received).toEqual([['presentation_ready', undefined]]);
|
|
57
|
+
|
|
58
|
+
teardown();
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('the frame background', () => {
|
|
63
|
+
test('setWindowBackground sets the frame’s inline background', () => {
|
|
64
|
+
const { renderer, frame, teardown } = setup();
|
|
65
|
+
expect(frame.style.backgroundColor).toBe('rgb(0, 0, 0)');
|
|
66
|
+
|
|
67
|
+
renderer.setWindowBackground('w1', 'transparent');
|
|
68
|
+
expect(frame.style.backgroundColor).toBe('transparent');
|
|
69
|
+
|
|
70
|
+
renderer.setWindowBackground('w1', '#000000');
|
|
71
|
+
expect(frame.style.backgroundColor).toBe('rgb(0, 0, 0)');
|
|
72
|
+
|
|
73
|
+
teardown();
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe('no colour scheme', () => {
|
|
78
|
+
test('a created frame declares no color-scheme', () => {
|
|
79
|
+
const { frame, renderer, teardown } = setup();
|
|
80
|
+
renderer.showWindow('w1', false);
|
|
81
|
+
renderer.setWindowBackground('w1', 'transparent');
|
|
82
|
+
|
|
83
|
+
expect(frame.style.getPropertyValue('color-scheme')).toBe('');
|
|
84
|
+
expect(frame.getAttribute('style')).not.toMatch(/color-scheme/);
|
|
85
|
+
|
|
86
|
+
teardown();
|
|
87
|
+
});
|
|
88
|
+
});
|
package/src/IFrameRenderer.ts
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
CreateWindowOptions,
|
|
4
4
|
WindowBounds,
|
|
5
5
|
} from './types';
|
|
6
|
+
import { VIDEO_REQUESTS } from './surfaces';
|
|
6
7
|
|
|
7
8
|
interface IFrameRendererWindow {
|
|
8
9
|
frame: HTMLIFrameElement;
|
|
@@ -219,6 +220,17 @@ export default class IFrameRenderer implements PresentationWindowRenderer {
|
|
|
219
220
|
// mute presentation properties.
|
|
220
221
|
}
|
|
221
222
|
|
|
223
|
+
public setWindowBackground(windowId: string, color: string) {
|
|
224
|
+
const window = this.getWindow(windowId);
|
|
225
|
+
if (!window) {
|
|
226
|
+
throw new Error(
|
|
227
|
+
`Failed to set window background, missing window id '${windowId}'`
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
window.frame.style.backgroundColor = color;
|
|
232
|
+
}
|
|
233
|
+
|
|
222
234
|
public destroy() {
|
|
223
235
|
window.removeEventListener('message', this.globalMessageHandler, false);
|
|
224
236
|
}
|
|
@@ -246,6 +258,15 @@ export default class IFrameRenderer implements PresentationWindowRenderer {
|
|
|
246
258
|
);
|
|
247
259
|
}
|
|
248
260
|
|
|
261
|
+
// A surface costs a hardware decoder, so only the window's own frame may
|
|
262
|
+
// ask for one; any frame can post here and name any window.
|
|
263
|
+
if (
|
|
264
|
+
VIDEO_REQUESTS.indexOf(type) !== -1 &&
|
|
265
|
+
event.source !== window.frame.contentWindow
|
|
266
|
+
) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
249
270
|
if (window.receiveMessageCallback) {
|
|
250
271
|
window.receiveMessageCallback(type, payload);
|
|
251
272
|
}
|