| OLD | NEW | 
|---|
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be | 
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. | 
| 4 | 4 | 
| 5 /** | 5 /** | 
| 6  * @param {!HTMLElement} playerContainer Main container. | 6  * @param {!HTMLElement} playerContainer Main container. | 
| 7  * @param {!HTMLElement} videoContainer Container for the video element. | 7  * @param {!HTMLElement} videoContainer Container for the video element. | 
| 8  * @param {!HTMLElement} controlsContainer Container for video controls. | 8  * @param {!HTMLElement} controlsContainer Container for video controls. | 
| 9  * @constructor | 9  * @constructor | 
| 10  * @struct | 10  * @struct | 
| (...skipping 96 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
| 107 FullWindowVideoControls.prototype.getInactivityWatcher = function() { | 107 FullWindowVideoControls.prototype.getInactivityWatcher = function() { | 
| 108   return this.inactivityWatcher_; | 108   return this.inactivityWatcher_; | 
| 109 }; | 109 }; | 
| 110 | 110 | 
| 111 /** | 111 /** | 
| 112  * Displays error message. | 112  * Displays error message. | 
| 113  * | 113  * | 
| 114  * @param {string} message Message id. | 114  * @param {string} message Message id. | 
| 115  */ | 115  */ | 
| 116 FullWindowVideoControls.prototype.showErrorMessage = function(message) { | 116 FullWindowVideoControls.prototype.showErrorMessage = function(message) { | 
| 117   var errorBanner = queryRequiredElement(document, '#error'); | 117   var errorBanner = getRequiredElement('error'); | 
| 118   errorBanner.textContent = loadTimeData.getString(message); | 118   errorBanner.textContent = loadTimeData.getString(message); | 
| 119   errorBanner.setAttribute('visible', 'true'); | 119   errorBanner.setAttribute('visible', 'true'); | 
| 120 | 120 | 
| 121   // The window is hidden if the video has not loaded yet. | 121   // The window is hidden if the video has not loaded yet. | 
| 122   chrome.app.window.current().show(); | 122   chrome.app.window.current().show(); | 
| 123 }; | 123 }; | 
| 124 | 124 | 
| 125 /** | 125 /** | 
| 126  * Handles playback (decoder) errors. | 126  * Handles playback (decoder) errors. | 
| 127  * @param {MediaError} error Error object. | 127  * @param {MediaError} error Error object. | 
| 128  * @private | 128  * @private | 
| 129  */ | 129  */ | 
| 130 FullWindowVideoControls.prototype.onPlaybackError_ = function(error) { | 130 FullWindowVideoControls.prototype.onPlaybackError_ = function(error) { | 
| 131   if (error.target && error.target.error && | 131   if (error.target && error.target.error && | 
| 132       error.target.error.code === MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED) { | 132       error.target.error.code === MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED) { | 
| 133     if (this.casting) | 133     if (this.casting) | 
| 134       this.showErrorMessage('VIDEO_PLAYER_VIDEO_FILE_UNSUPPORTED_FOR_CAST'); | 134       this.showErrorMessage('VIDEO_PLAYER_VIDEO_FILE_UNSUPPORTED_FOR_CAST'); | 
| 135     else | 135     else | 
| 136       this.showErrorMessage('VIDEO_PLAYER_VIDEO_FILE_UNSUPPORTED'); | 136       this.showErrorMessage('VIDEO_PLAYER_VIDEO_FILE_UNSUPPORTED'); | 
| 137     this.decodeErrorOccured = false; | 137     this.decodeErrorOccured = false; | 
| 138   } else { | 138   } else { | 
| 139     this.showErrorMessage('VIDEO_PLAYER_PLAYBACK_ERROR'); | 139     this.showErrorMessage('VIDEO_PLAYER_PLAYBACK_ERROR'); | 
| 140     this.decodeErrorOccured = true; | 140     this.decodeErrorOccured = true; | 
| 141   } | 141   } | 
| 142 | 142 | 
| 143   // Disable inactivity watcher, and disable the ui, by hiding tools manually. | 143   // Disable inactivity watcher, and disable the ui, by hiding tools manually. | 
| 144   this.getInactivityWatcher().disabled = true; | 144   this.getInactivityWatcher().disabled = true; | 
| 145   queryRequiredElement(document, '#video-player') | 145   getRequiredElement('video-player').setAttribute('disabled', 'true'); | 
| 146       .setAttribute('disabled', 'true'); |  | 
| 147 | 146 | 
| 148   // Detach the video element, since it may be unreliable and reset stored | 147   // Detach the video element, since it may be unreliable and reset stored | 
| 149   // current playback time. | 148   // current playback time. | 
| 150   this.cleanup(); | 149   this.cleanup(); | 
| 151   this.clearState(); | 150   this.clearState(); | 
| 152 | 151 | 
| 153   // Avoid reusing a video element. | 152   // Avoid reusing a video element. | 
| 154   player.unloadVideo(); | 153   player.unloadVideo(); | 
| 155 }; | 154 }; | 
| 156 | 155 | 
| (...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
| 212  * initialization. | 211  * initialization. | 
| 213  * @param {!Array<!FileEntry>} videos List of videos. | 212  * @param {!Array<!FileEntry>} videos List of videos. | 
| 214  */ | 213  */ | 
| 215 VideoPlayer.prototype.prepare = function(videos) { | 214 VideoPlayer.prototype.prepare = function(videos) { | 
| 216   this.videos_ = videos; | 215   this.videos_ = videos; | 
| 217 | 216 | 
| 218   var preventDefault = function(event) { event.preventDefault(); }.wrap(null); | 217   var preventDefault = function(event) { event.preventDefault(); }.wrap(null); | 
| 219 | 218 | 
| 220   document.ondragstart = preventDefault; | 219   document.ondragstart = preventDefault; | 
| 221 | 220 | 
| 222   var maximizeButton = queryRequiredElement(document, '.maximize-button'); | 221   var maximizeButton = queryRequiredElement('.maximize-button'); | 
| 223   maximizeButton.addEventListener( | 222   maximizeButton.addEventListener( | 
| 224       'click', | 223       'click', | 
| 225       function(event) { | 224       function(event) { | 
| 226         var appWindow = chrome.app.window.current(); | 225         var appWindow = chrome.app.window.current(); | 
| 227         if (appWindow.isMaximized()) | 226         if (appWindow.isMaximized()) | 
| 228           appWindow.restore(); | 227           appWindow.restore(); | 
| 229         else | 228         else | 
| 230           appWindow.maximize(); | 229           appWindow.maximize(); | 
| 231         event.stopPropagation(); | 230         event.stopPropagation(); | 
| 232       }.wrap(null)); | 231       }.wrap(null)); | 
| 233   maximizeButton.addEventListener('mousedown', preventDefault); | 232   maximizeButton.addEventListener('mousedown', preventDefault); | 
| 234 | 233 | 
| 235   var minimizeButton = queryRequiredElement(document, '.minimize-button'); | 234   var minimizeButton = queryRequiredElement('.minimize-button'); | 
| 236   minimizeButton.addEventListener( | 235   minimizeButton.addEventListener( | 
| 237       'click', | 236       'click', | 
| 238       function(event) { | 237       function(event) { | 
| 239         chrome.app.window.current().minimize(); | 238         chrome.app.window.current().minimize(); | 
| 240         event.stopPropagation(); | 239         event.stopPropagation(); | 
| 241       }.wrap(null)); | 240       }.wrap(null)); | 
| 242   minimizeButton.addEventListener('mousedown', preventDefault); | 241   minimizeButton.addEventListener('mousedown', preventDefault); | 
| 243 | 242 | 
| 244   var closeButton = queryRequiredElement(document, '.close-button'); | 243   var closeButton = queryRequiredElement('.close-button'); | 
| 245   closeButton.addEventListener( | 244   closeButton.addEventListener( | 
| 246       'click', | 245       'click', | 
| 247       function(event) { | 246       function(event) { | 
| 248         window.close(); | 247         window.close(); | 
| 249         event.stopPropagation(); | 248         event.stopPropagation(); | 
| 250       }.wrap(null)); | 249       }.wrap(null)); | 
| 251   closeButton.addEventListener('mousedown', preventDefault); | 250   closeButton.addEventListener('mousedown', preventDefault); | 
| 252 | 251 | 
| 253   var menu = queryRequiredElement(document, '#cast-menu'); | 252   cr.ui.decorate(getRequiredElement('cast-menu'), cr.ui.Menu); | 
| 254   cr.ui.decorate(menu, cr.ui.Menu); |  | 
| 255 | 253 | 
| 256   this.controls_ = new FullWindowVideoControls( | 254   this.controls_ = new FullWindowVideoControls( | 
| 257       queryRequiredElement(document, '#video-player'), | 255       getRequiredElement('video-player'), | 
| 258       queryRequiredElement(document, '#video-container'), | 256       getRequiredElement('video-container'), | 
| 259       queryRequiredElement(document, '#controls')); | 257       getRequiredElement('controls')); | 
| 260 | 258 | 
| 261   var reloadVideo = function(e) { | 259   var reloadVideo = function(e) { | 
| 262     if (this.controls_.decodeErrorOccured && | 260     if (this.controls_.decodeErrorOccured && | 
| 263         // Ignore shortcut keys | 261         // Ignore shortcut keys | 
| 264         !e.ctrlKey && !e.altKey && !e.shiftKey && !e.metaKey) { | 262         !e.ctrlKey && !e.altKey && !e.shiftKey && !e.metaKey) { | 
| 265       this.reloadCurrentVideo(function() { | 263       this.reloadCurrentVideo(function() { | 
| 266         this.videoElement_.play(); | 264         this.videoElement_.play(); | 
| 267       }.wrap(this)); | 265       }.wrap(this)); | 
| 268       e.preventDefault(); | 266       e.preventDefault(); | 
| 269     } | 267     } | 
| 270   }.wrap(this); | 268   }.wrap(this); | 
| 271 | 269 | 
| 272   var arrowRight = queryRequiredElement(document, '.arrow-box .arrow.right'); | 270   var arrowRight = queryRequiredElement('.arrow-box .arrow.right'); | 
| 273   arrowRight.addEventListener('click', this.advance_.wrap(this, 1)); | 271   arrowRight.addEventListener('click', this.advance_.wrap(this, 1)); | 
| 274   var arrowLeft = queryRequiredElement(document, '.arrow-box .arrow.left'); | 272   var arrowLeft = queryRequiredElement('.arrow-box .arrow.left'); | 
| 275   arrowLeft.addEventListener('click', this.advance_.wrap(this, 0)); | 273   arrowLeft.addEventListener('click', this.advance_.wrap(this, 0)); | 
| 276 | 274 | 
| 277   var videoPlayerElement = queryRequiredElement(document, '#video-player'); | 275   var videoPlayerElement = getRequiredElement('video-player'); | 
| 278   if (videos.length > 1) | 276   if (videos.length > 1) | 
| 279     videoPlayerElement.setAttribute('multiple', true); | 277     videoPlayerElement.setAttribute('multiple', true); | 
| 280   else | 278   else | 
| 281     videoPlayerElement.removeAttribute('multiple'); | 279     videoPlayerElement.removeAttribute('multiple'); | 
| 282 | 280 | 
| 283   document.addEventListener('keydown', reloadVideo); | 281   document.addEventListener('keydown', reloadVideo); | 
| 284   document.addEventListener('click', reloadVideo); | 282   document.addEventListener('click', reloadVideo); | 
| 285 }; | 283 }; | 
| 286 | 284 | 
| 287 /** | 285 /** | 
| (...skipping 15 matching lines...) Expand all  Loading... | 
| 303  * @param {!FileEntry} video Entry of the video to be played. | 301  * @param {!FileEntry} video Entry of the video to be played. | 
| 304  * @param {function()=} opt_callback Completion callback. | 302  * @param {function()=} opt_callback Completion callback. | 
| 305  * @private | 303  * @private | 
| 306  */ | 304  */ | 
| 307 VideoPlayer.prototype.loadVideo_ = function(video, opt_callback) { | 305 VideoPlayer.prototype.loadVideo_ = function(video, opt_callback) { | 
| 308   this.unloadVideo(true); | 306   this.unloadVideo(true); | 
| 309 | 307 | 
| 310   this.loadQueue_.run(function(callback) { | 308   this.loadQueue_.run(function(callback) { | 
| 311     document.title = video.name; | 309     document.title = video.name; | 
| 312 | 310 | 
| 313     queryRequiredElement(document, '#title').innerText = video.name; | 311     getRequiredElement('title').innerText = video.name; | 
| 314 | 312 | 
| 315     var videoPlayerElement = queryRequiredElement(document, '#video-player'); | 313     var videoPlayerElement = getRequiredElement('video-player'); | 
| 316     if (this.currentPos_ === (this.videos_.length - 1)) | 314     if (this.currentPos_ === (this.videos_.length - 1)) | 
| 317       videoPlayerElement.setAttribute('last-video', true); | 315       videoPlayerElement.setAttribute('last-video', true); | 
| 318     else | 316     else | 
| 319       videoPlayerElement.removeAttribute('last-video'); | 317       videoPlayerElement.removeAttribute('last-video'); | 
| 320 | 318 | 
| 321     if (this.currentPos_ === 0) | 319     if (this.currentPos_ === 0) | 
| 322       videoPlayerElement.setAttribute('first-video', true); | 320       videoPlayerElement.setAttribute('first-video', true); | 
| 323     else | 321     else | 
| 324       videoPlayerElement.removeAttribute('first-video'); | 322       videoPlayerElement.removeAttribute('first-video'); | 
| 325 | 323 | 
| 326     // Re-enables ui and hides error message if already displayed. | 324     // Re-enables ui and hides error message if already displayed. | 
| 327     queryRequiredElement(document, '#video-player').removeAttribute('disabled'); | 325     getRequiredElement('video-player').removeAttribute('disabled'); | 
| 328     queryRequiredElement(document, '#error').removeAttribute('visible'); | 326     getRequiredElement('error').removeAttribute('visible'); | 
| 329     this.controls.detachMedia(); | 327     this.controls.detachMedia(); | 
| 330     this.controls.getInactivityWatcher().disabled = true; | 328     this.controls.getInactivityWatcher().disabled = true; | 
| 331     this.controls.decodeErrorOccured = false; | 329     this.controls.decodeErrorOccured = false; | 
| 332     this.controls.casting = !!this.currentCast_; | 330     this.controls.casting = !!this.currentCast_; | 
| 333 | 331 | 
| 334     videoPlayerElement.setAttribute('loading', true); | 332     videoPlayerElement.setAttribute('loading', true); | 
| 335 | 333 | 
| 336     var media = new MediaManager(video); | 334     var media = new MediaManager(video); | 
| 337 | 335 | 
| 338     Promise.all([media.getThumbnail(), media.getToken(false)]) | 336     Promise.all([media.getThumbnail(), media.getToken(false)]) | 
| 339         .then(function(results) { | 337         .then(function(results) { | 
| 340           var url = results[0]; | 338           var url = results[0]; | 
| 341           var token = results[1]; | 339           var token = results[1]; | 
| 342           if (url && token) { | 340           if (url && token) { | 
| 343             queryRequiredElement(document, '#thumbnail').style.backgroundImage = | 341             getRequiredElement('thumbnail').style.backgroundImage = | 
| 344                 'url(' + url + '&access_token=' + token + ')'; | 342                 'url(' + url + '&access_token=' + token + ')'; | 
| 345           } else { | 343           } else { | 
| 346             queryRequiredElement(document, '#thumbnail').style.backgroundImage = | 344             getRequiredElement('thumbnail').style.backgroundImage = ''; | 
| 347                 ''; |  | 
| 348           } | 345           } | 
| 349         }) | 346         }) | 
| 350         .catch(function() { | 347         .catch(function() { | 
| 351           // Shows no image on error. | 348           // Shows no image on error. | 
| 352           queryRequiredElement(document, '#thumbnail').style.backgroundImage = | 349           getRequiredElement('thumbnail').style.backgroundImage = ''; | 
| 353               ''; |  | 
| 354         }); | 350         }); | 
| 355 | 351 | 
| 356     var videoElementInitializePromise; | 352     var videoElementInitializePromise; | 
| 357     if (this.currentCast_) { | 353     if (this.currentCast_) { | 
| 358       metrics.recordPlayType(metrics.PLAY_TYPE.CAST); | 354       metrics.recordPlayType(metrics.PLAY_TYPE.CAST); | 
| 359 | 355 | 
| 360       videoPlayerElement.setAttribute('casting', true); | 356       videoPlayerElement.setAttribute('casting', true); | 
| 361 | 357 | 
| 362       queryRequiredElement(document, '#cast-name').textContent = | 358       getRequiredElement('cast-name').textContent = | 
| 363           this.currentCast_.friendlyName; | 359           this.currentCast_.friendlyName; | 
| 364 | 360 | 
| 365       videoPlayerElement.setAttribute('castable', true); | 361       videoPlayerElement.setAttribute('castable', true); | 
| 366 | 362 | 
| 367       videoElementInitializePromise = media.isAvailableForCast() | 363       videoElementInitializePromise = media.isAvailableForCast() | 
| 368           .then(function(result) { | 364           .then(function(result) { | 
| 369             if (!result) | 365             if (!result) | 
| 370               return Promise.reject('No casts are available.'); | 366               return Promise.reject('No casts are available.'); | 
| 371 | 367 | 
| 372             return new Promise(function(fulfill, reject) { | 368             return new Promise(function(fulfill, reject) { | 
| 373               chrome.cast.requestSession( | 369               chrome.cast.requestSession( | 
| 374                   fulfill, reject, undefined, this.currentCast_.label); | 370                   fulfill, reject, undefined, this.currentCast_.label); | 
| 375             }.bind(this)).then(function(session) { | 371             }.bind(this)).then(function(session) { | 
| 376               session.addUpdateListener(this.onCastSessionUpdateBound_); | 372               session.addUpdateListener(this.onCastSessionUpdateBound_); | 
| 377 | 373 | 
| 378               this.currentSession_ = session; | 374               this.currentSession_ = session; | 
| 379               this.videoElement_ = new CastVideoElement(media, session); | 375               this.videoElement_ = new CastVideoElement(media, session); | 
| 380               this.controls.attachMedia(this.videoElement_); | 376               this.controls.attachMedia(this.videoElement_); | 
| 381             }.bind(this)); | 377             }.bind(this)); | 
| 382           }.bind(this)); | 378           }.bind(this)); | 
| 383     } else { | 379     } else { | 
| 384       metrics.recordPlayType(metrics.PLAY_TYPE.LOCAL); | 380       metrics.recordPlayType(metrics.PLAY_TYPE.LOCAL); | 
| 385       videoPlayerElement.removeAttribute('casting'); | 381       videoPlayerElement.removeAttribute('casting'); | 
| 386 | 382 | 
| 387       this.videoElement_ = document.createElement('video'); | 383       this.videoElement_ = document.createElement('video'); | 
| 388       queryRequiredElement(document, '#video-container').appendChild( | 384       getRequiredElement('video-container').appendChild(this.videoElement_); | 
| 389           this.videoElement_); |  | 
| 390 | 385 | 
| 391       this.controls.attachMedia(this.videoElement_); | 386       this.controls.attachMedia(this.videoElement_); | 
| 392       this.videoElement_.src = video.toURL(); | 387       this.videoElement_.src = video.toURL(); | 
| 393 | 388 | 
| 394       media.isAvailableForCast().then(function(result) { | 389       media.isAvailableForCast().then(function(result) { | 
| 395         if (result) | 390         if (result) | 
| 396           videoPlayerElement.setAttribute('castable', true); | 391           videoPlayerElement.setAttribute('castable', true); | 
| 397         else | 392         else | 
| 398           videoPlayerElement.removeAttribute('castable'); | 393           videoPlayerElement.removeAttribute('castable'); | 
| 399       }).catch(function() { | 394       }).catch(function() { | 
| (...skipping 171 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
| 571     this.reloadCurrentVideo(); | 566     this.reloadCurrentVideo(); | 
| 572     callback(); | 567     callback(); | 
| 573   }.wrap(this)); | 568   }.wrap(this)); | 
| 574 }; | 569 }; | 
| 575 | 570 | 
| 576 /** | 571 /** | 
| 577  * Set the list of casts. | 572  * Set the list of casts. | 
| 578  * @param {Array<Object>} casts List of casts. | 573  * @param {Array<Object>} casts List of casts. | 
| 579  */ | 574  */ | 
| 580 VideoPlayer.prototype.setCastList = function(casts) { | 575 VideoPlayer.prototype.setCastList = function(casts) { | 
| 581   var videoPlayerElement = queryRequiredElement(document, '#video-player'); | 576   var videoPlayerElement = getRequiredElement('video-player'); | 
| 582   var menu = queryRequiredElement(document, '#cast-menu'); | 577   var menu = getRequiredElement('cast-menu'); | 
| 583   menu.innerHTML = ''; | 578   menu.innerHTML = ''; | 
| 584 | 579 | 
| 585   // TODO(yoshiki): Handle the case that the current cast disappears. | 580   // TODO(yoshiki): Handle the case that the current cast disappears. | 
| 586 | 581 | 
| 587   if (casts.length === 0) { | 582   if (casts.length === 0) { | 
| 588     videoPlayerElement.removeAttribute('cast-available'); | 583     videoPlayerElement.removeAttribute('cast-available'); | 
| 589     if (this.currentCast_) | 584     if (this.currentCast_) | 
| 590       this.onCurrentCastDisappear_(); | 585       this.onCurrentCastDisappear_(); | 
| 591     return; | 586     return; | 
| 592   } | 587   } | 
| (...skipping 25 matching lines...) Expand all  Loading... | 
| 618   } | 613   } | 
| 619   this.updateCheckOnCastMenu_(); | 614   this.updateCheckOnCastMenu_(); | 
| 620   videoPlayerElement.setAttribute('cast-available', true); | 615   videoPlayerElement.setAttribute('cast-available', true); | 
| 621 }; | 616 }; | 
| 622 | 617 | 
| 623 /** | 618 /** | 
| 624  * Updates the check status of the cast menu items. | 619  * Updates the check status of the cast menu items. | 
| 625  * @private | 620  * @private | 
| 626  */ | 621  */ | 
| 627 VideoPlayer.prototype.updateCheckOnCastMenu_ = function() { | 622 VideoPlayer.prototype.updateCheckOnCastMenu_ = function() { | 
| 628   var menu = queryRequiredElement(document, '#cast-menu'); | 623   var menuItems = getRequiredElement('cast-menu').menuItems; | 
| 629   var menuItems = menu.menuItems; |  | 
| 630   for (var i = 0; i < menuItems.length; i++) { | 624   for (var i = 0; i < menuItems.length; i++) { | 
| 631     var item = menuItems[i]; | 625     var item = menuItems[i]; | 
| 632     if (this.currentCast_ === null) { | 626     if (this.currentCast_ === null) { | 
| 633       // Playing on this computer. | 627       // Playing on this computer. | 
| 634       if (item.castLabel === '') | 628       if (item.castLabel === '') | 
| 635         item.checked = true; | 629         item.checked = true; | 
| 636       else | 630       else | 
| 637         item.checked = false; | 631         item.checked = false; | 
| 638     } else { | 632     } else { | 
| 639       // Playing on cast device. | 633       // Playing on cast device. | 
| (...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
| 698   return new Promise(function(fulfill, reject) { | 692   return new Promise(function(fulfill, reject) { | 
| 699     util.URLsToEntries(window.appState.items, function(entries) { | 693     util.URLsToEntries(window.appState.items, function(entries) { | 
| 700       metrics.recordOpenVideoPlayerAction(); | 694       metrics.recordOpenVideoPlayerAction(); | 
| 701       metrics.recordNumberOfOpenedFiles(entries.length); | 695       metrics.recordNumberOfOpenedFiles(entries.length); | 
| 702 | 696 | 
| 703       player.prepare(entries); | 697       player.prepare(entries); | 
| 704       player.playFirstVideo(player, fulfill); | 698       player.playFirstVideo(player, fulfill); | 
| 705     }.wrap()); | 699     }.wrap()); | 
| 706   }.wrap()); | 700   }.wrap()); | 
| 707 }.wrap()); | 701 }.wrap()); | 
| OLD | NEW | 
|---|