@@ -33,6 +33,7 @@ goog.require('goog.dom.ViewportSizeMonitor');
33
33
goog . require ( 'goog.events.EventTarget' ) ;
34
34
goog . require ( 'goog.events.EventType' ) ;
35
35
goog . require ( 'goog.soy' ) ;
36
+ goog . require ( 'goog.string' ) ;
36
37
goog . require ( 'goog.ui.Component.EventType' ) ;
37
38
38
39
@@ -41,6 +42,7 @@ goog.require('goog.ui.Component.EventType');
41
42
* @constructor
42
43
* @struct
43
44
* @final
45
+ * @export
44
46
*/
45
47
cwc . ui . Preview = function ( helper ) {
46
48
/** @type {string } */
@@ -102,6 +104,8 @@ cwc.ui.Preview = function(helper) {
102
104
103
105
/** @private {!cwc.Messenger} */
104
106
this . messenger_ = new cwc . Messenger ( this . eventHandler_ ) ;
107
+ this . messenger_ . addListener ( '__exec_result__' , this . handleExecResponse_ ,
108
+ this ) ;
105
109
106
110
/** @private {string} */
107
111
this . partition_ = 'preview' ;
@@ -111,12 +115,16 @@ cwc.ui.Preview = function(helper) {
111
115
112
116
/** @private {!cwc.utils.Logger|null} */
113
117
this . log_ = new cwc . utils . Logger ( this . name ) ;
118
+
119
+ /** @private {!Object<string, Function>} */
120
+ this . pendingExecCallbacks = { } ;
114
121
} ;
115
122
116
123
117
124
/**
118
125
* Decorates the given node and adds the preview window.
119
126
* @param {Element= } node The target node to add the preview window.
127
+ * @export
120
128
*/
121
129
cwc . ui . Preview . prototype . decorate = function ( node ) {
122
130
this . node = node || goog . dom . getElement ( this . prefix + 'chrome' ) ;
@@ -345,6 +353,7 @@ cwc.ui.Preview.prototype.terminate = function() {
345
353
346
354
/**
347
355
* @return {Object }
356
+ * @export
348
357
*/
349
358
cwc . ui . Preview . prototype . getContent = function ( ) {
350
359
return this . content ;
@@ -367,6 +376,7 @@ cwc.ui.Preview.prototype.getContentUrl = function() {
367
376
368
377
/**
369
378
* @param {string } url
379
+ * @export
370
380
*/
371
381
cwc . ui . Preview . prototype . setContentUrl = function ( url ) {
372
382
if ( ! url || ! this . content ) {
@@ -463,11 +473,83 @@ cwc.ui.Preview.prototype.focus = function() {
463
473
/**
464
474
* Injects and executes the passed code in the preview content, if supported.
465
475
* @param {!(string|Function) } code
476
+ * @param {Number } timeout
477
+ * @return {!Promise }
478
+ * @export
479
+ * @TODO ([email protected] ): Move logic to messenger instance.
466
480
*/
467
- cwc . ui . Preview . prototype . executeScript = function ( code ) {
481
+ cwc . ui . Preview . prototype . executeScript = function ( code , timeout = 250 ) {
468
482
this . log_ . info ( 'Execute script' , code ) ;
469
- this . messenger_ . send ( '__exec__' ,
470
- typeof code === 'function' ? code . toString ( ) : code ) ;
483
+ let execSpec = {
484
+ 'code' : typeof code === 'function' ? code . toString ( ) : code ,
485
+ 'id' : false ,
486
+ } ;
487
+ let id = goog . string . createUniqueString ( ) ;
488
+ let promise = new Promise ( function ( resolve , reject ) {
489
+ this . pendingExecCallbacks [ id ] = resolve ;
490
+ setTimeout ( function ( ) {
491
+ if ( this . pendingExecCallbacks . hasOwnProperty ( id ) ) {
492
+ this . pendingExecCallbacks [ id ] = false ;
493
+ reject ( `Preview script timed out after ${ timeout } ms` ) ;
494
+ }
495
+ } . bind ( this ) , timeout ) ;
496
+ } . bind ( this ) ) ;
497
+ execSpec [ 'id' ] = id ;
498
+ this . messenger_ . send ( '__exec__' , execSpec ) ;
499
+ return promise ;
500
+ } ;
501
+
502
+ /**
503
+ * Calls the callback registered for the script execution
504
+ * @param {Object } response
505
+ * @private
506
+ * @TODO ([email protected] ): Move logic to messenger instance.
507
+ */
508
+ cwc . ui . Preview . prototype . handleExecResponse_ = function ( response ) {
509
+ if ( ( typeof response ) !== 'object' ) {
510
+ this . log_ . warn ( 'Received non-object as response from script execution' ,
511
+ response ) ;
512
+ return ;
513
+ }
514
+ if ( ! response . hasOwnProperty ( 'id' ) ) {
515
+ this . log_ . warn ( 'executeScript response has no \'id\', can\'t match to a ' +
516
+ 'callback' ) ;
517
+ return ;
518
+ }
519
+ if ( ( typeof response [ 'id' ] ) !== 'string' ) {
520
+ this . log_ . warn ( 'Ignorning executeScript response with non-string id' ,
521
+ response ) ;
522
+ return ;
523
+ }
524
+ // The callback is called from setTimeout() to give the executeScript timeout
525
+ // a chance to run first. When run in a WebView, the preview runs in a
526
+ // separate thread and the executeScript timeout will have already triggered
527
+ // if the user's functon exceeded the timeout. However, iframes run in the
528
+ // same thread, so a long-running script will prevent the executeScript
529
+ // timeout from firing until the it completes.
530
+ // This does make timeouts ineffective for iframes, but ensuring the correct
531
+ // order allows test logic to execute correctly in karma/chrome (which only
532
+ // supports iframes).
533
+ setTimeout ( ( ) => {
534
+ if ( ! this . pendingExecCallbacks . hasOwnProperty ( response [ 'id' ] ) ) {
535
+ this . log_ . warn ( 'Callback for executeScript response' , response [ 'id' ] ,
536
+ 'missing. Response was' , response ) ;
537
+ return ;
538
+ }
539
+ let callback = this . pendingExecCallbacks [ response [ 'id' ] ] ;
540
+ delete this . pendingExecCallbacks [ response [ 'id' ] ] ;
541
+ if ( callback === false ) {
542
+ this . log_ . info ( 'executeScript ' , response [ 'id' ] , 'timed out' ) ;
543
+ return ;
544
+ }
545
+ let result ;
546
+ if ( response . hasOwnProperty ( 'result' ) ) {
547
+ result = response [ 'result' ] ;
548
+ }
549
+ this . log_ . info ( 'Executing callback ' , response [ 'id' ] , 'with result' ,
550
+ result ) ;
551
+ callback ( result ) ;
552
+ } , 0 ) ;
471
553
} ;
472
554
473
555
0 commit comments