1 /* ***************************************************************************
2 * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20 * DEALINGS IN THE SOFTWARE.
21 * ***************************************************************************
23 * Authors: Yonghwi Park <yonghwi0324.park@samsung.com>
24 * Wonseop Kim <wonseop.kim@samsung.com>
29 * MultiMediaView is a widget that lets the user view and handle multimedia contents.
30 * Video and audio elements are coded as standard HTML elements and enhanced by the
31 * MultiMediaview to make them attractive and usable on a mobile device.
34 * data-theme : Set a theme of widget.
35 * If this value is not defined, widget will use parent`s theme. (optional)
36 * data-controls : If this value is 'true', widget will use belonging controller.
37 * If this value is 'false', widget will use browser`s controller.
38 * Default value is 'true'.
39 * data-full-screen : Set a status that full-screen when inital start.
40 * Default value is 'false'.
44 * : Get or set the width of widget.
45 * The first argument is the width of widget.
46 * If no first argument is specified, will act as a getter.
48 * : Get or set the height of widget.
49 * The first argument is the height of widget.
50 * If no first argument is specified, will act as a getter.
51 * fullScreen( [boolean] )
52 * : Get or Set the status of full-screen.
53 * If no first argument is specified, will act as a getter.
57 * create : triggered when a multimediaview is created.
62 * <video data-controls="true" style="width:100%;">
63 * <source src="media/oceans-clip.mp4" type="video/mp4" />
64 * Your browser does not support the video tag.
68 * <audio data-controls="true" style="width:100%;">
69 * <source src="media/Over the horizon.mp3" type="audio/mp3" />
70 * Your browser does not support the audio tag.
76 The multimedia view widget shows a player control that you can use to view and handle multimedia content. This widget uses the standard HTML video and audio elements, which have been enhanced for use on a mobile device.
78 To add a multimedia view widget to the application, use the following code:
80 // Video player control
81 <video data-controls="true" style="width:100%;">
82 <source src="<VIDEO_FILE_URL>" type="video/mp4" /> Your browser does not support the video tag. </video>
83 // Audio player control
84 <audio data-controls="true" style="width:100%;"> <source src="<AUDIO_FILE_URL>" type="audio/mp3" /> Your browser does not support the audio tag.
87 The multimedia view can define a callback for the create event, which is fired when the widget is created.
88 $('.selector').multimediaview({
89 create:function(event, u){...}
91 $(".selector").bind("create", function(event, ui)
93 // Respond to the multimedia view widget creation
97 @property {Boolean} data-control
98 Sets the controls for the widget.
99 The default value is true. If the value is set to true, the widget uses its own player controls. If the value is set to false, the widget uses the browser's player controls.
102 @property {Boolean} data-full-screen
103 Defines whether the widget opens in the fullscreen view mode.
104 The default value is false.
107 @property {String} data-theme
108 Sets the widget theme.
109 If the value is not set, the parent control's theme is used
113 The width method is used to get (if no value is defined) or set the multimedia view widget width:
115 <source src="test.mp4" type="video/mp4" />
117 $(".selector").multimediaview("width", [value]);
121 The height method is used to get (if no value is defined) or set the multimedia view widget height:
123 <source src="test.mp4" type="video/mp4" />
125 $(".selector").multimediaview("height", [value]);
129 The fullScreen method is used to get (if no value is defined) or set the full-screen mode of the multimedia view widget. If the value is true, the full-screen mode is used; otherwise the multimedia view widget runs in the normal mode.
132 <source src="test.mp4" type="video/mp4" />
134 $(".selector").multimediaview("fullScreen", [value]);
136 ( function ( $, document, window, undefined ) {
137 $.widget( "tizen.multimediaview", $.mobile.widget, {
142 initSelector : "video, audio"
144 _create : function () {
147 viewElement = view[0],
148 option = self.options,
149 role = "multimediaview",
154 isControlHide : false,
162 view.addClass( "ui-multimediaview" );
163 control = self._createControl();
165 if ( view[0].nodeName === "VIDEO" ) {
166 control.addClass( "ui-multimediaview-video" );
170 view.wrap( "<div class='ui-multimediaview-wrap'>" ).after( control );
171 if ( option.controls ) {
172 if ( view.attr("controls") ) {
173 view.removeAttr( "controls" );
179 $( document ).bind( "pagechange.multimediaview", function ( e ) {
180 var $page = $( e.target );
181 if ( $page.find( view ).length > 0 && viewElement.autoplay ) {
185 if ( option.controls ) {
189 }).bind( "pagebeforechange.multimediaview", function ( e ) {
190 if ( viewElement.played.length !== 0 ) {
195 $( window ).bind( "resize.multimediaview orientationchange.multimediaview", function ( e ) {
196 if ( !option.controls ) {
199 var $page = $( e.target ),
200 $scrollview = view.parents( ".ui-scrollview-clip" );
202 $scrollview.each( function ( i ) {
203 if ( $.data( this, "scrollview" ) ) {
204 $( this ).scrollview( "scrollTo", 0, 0 );
208 // for maintaining page layout
209 if ( !option.fullScreen ) {
210 $( ".ui-footer:visible" ).show();
212 $( ".ui-footer" ).hide();
213 self._fitContentArea( $page );
219 _resize : function () {
220 var view = this.element,
221 parent = view.parent(),
222 control = parent.find( ".ui-multimediaview-control" ),
227 this._resizeFullscreen( this.options.fullScreen );
228 viewWidth = ( ( view[0].nodeName === "VIDEO" ) ? view.width() : parent.width() );
229 viewHeight = ( ( view[0].nodeName === "VIDEO" ) ? view.height() : control.height() );
230 viewOffset = view.offset();
232 this._resizeControl( viewOffset, viewWidth, viewHeight );
234 this._updateSeekBar();
235 this._updateVolumeState();
237 _resizeControl : function ( offset, width, height ) {
240 viewElement = view[0],
241 control = view.parent().find( ".ui-multimediaview-control" ),
242 buttons = control.find( ".ui-button" ),
243 playpauseButton = control.find( ".ui-playpausebutton" ),
244 seekBar = control.find( ".ui-seekbar" ),
245 durationLabel = control.find( ".ui-durationlabel" ),
246 timestampLabel = control.find( ".ui-timestamplabel" ),
247 volumeControl = control.find( ".ui-volumecontrol" ),
248 volumeBar = volumeControl.find( ".ui-volumebar" ),
249 controlWidth = width,
250 controlHeight = control.outerHeight( true ),
252 controlOffset = null;
255 if ( view[0].nodeName === "VIDEO" ) {
256 controlOffset = control.offset();
257 controlOffset.left = offset.left;
258 controlOffset.top = offset.top + height - controlHeight;
259 control.offset( controlOffset );
262 control.width( controlWidth );
266 availableWidth = control.width() - ( buttons.outerWidth( true ) * buttons.length );
267 availableWidth -= ( parseInt( buttons.eq( 0 ).css( "margin-left" ), 10 ) + parseInt( buttons.eq( 0 ).css( "margin-right" ), 10 ) ) * buttons.length;
268 if ( !self.isVolumeHide ) {
269 availableWidth -= volumeControl.outerWidth( true );
271 seekBar.width( availableWidth );
274 if ( durationLabel && !isNaN( viewElement.duration ) ) {
275 durationLabel.find( "p" ).text( self._convertTimeFormat( viewElement.duration ) );
278 if ( viewElement.autoplay && viewElement.paused === false ) {
279 playpauseButton.removeClass( "ui-play-icon" ).addClass( "ui-pause-icon" );
282 if ( seekBar.width() < ( volumeBar.width() + timestampLabel.width() + durationLabel.width() ) ) {
283 durationLabel.hide();
285 durationLabel.show();
288 _resizeFullscreen : function ( isFullscreen ) {
291 parent = view.parent(),
292 control = view.parent().find( ".ui-multimediaview-control" ),
293 playpauseButton = control.find( ".ui-playpausebutton" ),
294 timestampLabel = control.find( ".ui-timestamplabel" ),
295 seekBar = control.find( ".ui-seekbar" ),
296 durationBar = seekBar.find( ".ui-duration" ),
297 currenttimeBar = seekBar.find( ".ui-currenttime" ),
301 if ( isFullscreen ) {
302 if ( !self.backupView ) {
304 width : view[0].style.getPropertyValue( "width" ) || "",
305 height : view[0].style.getPropertyValue( "height" ) || "",
306 position : view.css( "position" ),
307 zindex : view.css( "z-index" )
310 docWidth = $( "body" )[0].clientWidth;
311 docHeight = $( "body" )[0].clientHeight;
313 view.width( docWidth ).height( docHeight - 1 );
314 view.addClass( "ui-" + self.role + "-fullscreen" );
320 if ( !self.backupView ) {
324 view.removeClass( "ui-" + self.role + "-fullscreen" );
326 "width" : self.backupView.width,
327 "height" : self.backupView.height,
328 "position": self.backupView.position,
329 "z-index": self.backupView.zindex
331 self.backupView = null;
335 _addEvent : function () {
338 viewElement = view[0],
339 control = view.parent().find( ".ui-multimediaview-control" ),
340 playpauseButton = control.find( ".ui-playpausebutton" ),
341 timestampLabel = control.find( ".ui-timestamplabel" ),
342 durationLabel = control.find( ".ui-durationlabel" ),
343 volumeButton = control.find( ".ui-volumebutton" ),
344 volumeControl = control.find( ".ui-volumecontrol" ),
345 volumeBar = volumeControl.find( ".ui-volumebar" ),
346 volumeGuide = volumeControl.find( ".ui-guide" ),
347 volumeHandle = volumeControl.find( ".ui-handler" ),
348 fullscreenButton = control.find( ".ui-fullscreenbutton" ),
349 seekBar = control.find( ".ui-seekbar" ),
350 durationBar = seekBar.find( ".ui-duration" ),
351 currenttimeBar = seekBar.find( ".ui-currenttime" );
353 view.bind( "loadedmetadata.multimediaview", function ( e ) {
354 if ( !isNaN( viewElement.duration ) ) {
355 durationLabel.find( "p" ).text( self._convertTimeFormat( viewElement.duration ) );
358 }).bind( "timeupdate.multimediaview", function ( e ) {
359 self._updateSeekBar();
360 }).bind( "play.multimediaview", function ( e ) {
361 playpauseButton.removeClass( "ui-play-icon" ).addClass( "ui-pause-icon" );
362 }).bind( "pause.multimediaview", function ( e ) {
363 playpauseButton.removeClass( "ui-pause-icon" ).addClass( "ui-play-icon" );
364 }).bind( "ended.multimediaview", function ( e ) {
365 if ( typeof viewElement.loop == "undefined" || viewElement.loop === "" ) {
368 }).bind( "volumechange.multimediaview", function ( e ) {
369 if ( viewElement.volume < 0.1 ) {
370 viewElement.muted = true;
371 volumeButton.removeClass( "ui-volume-icon" ).addClass( "ui-mute-icon" );
373 viewElement.muted = false;
374 volumeButton.removeClass( "ui-mute-icon" ).addClass( "ui-volume-icon" );
377 if ( !self.isVolumeHide ) {
378 self._updateVolumeState();
380 }).bind( "durationchange.multimediaview", function ( e ) {
381 if ( !isNaN( viewElement.duration ) ) {
382 durationLabel.find( "p" ).text( self._convertTimeFormat( viewElement.duration ) );
385 }).bind( "error.multimediaview", function ( e ) {
386 switch ( e.target.error.code ) {
387 case e.target.error.MEDIA_ERR_ABORTED :
388 window.alert( 'You aborted the video playback.' );
390 case e.target.error.MEDIA_ERR_NETWORK :
391 window.alert( 'A network error caused the video download to fail part-way.' );
393 case e.target.error.MEDIA_ERR_DECODE :
394 window.alert( 'The video playback was aborted due to a corruption problem or because the video used features your browser did not support.' );
396 case e.target.error.MEDIA_ERR_SRC_NOT_SUPPORTED :
397 window.alert( 'The video could not be loaded, either because the server or network failed or because the format is not supported.' );
400 window.alert( 'An unknown error occurred.' );
403 }).bind( "vclick.multimediaview", function ( e ) {
404 if ( !self.options.controls ) {
408 control.fadeToggle( "fast", function () {
409 var offset = control.offset();
410 self.isControlHide = !self.isControlHide;
411 if ( self.options.mediatype == "video" ) {
418 playpauseButton.bind( "vclick.multimediaview", function () {
421 if ( viewElement.paused ) {
427 if ( self.options.mediatype == "video" ) {
432 fullscreenButton.bind( "vclick.multimediaview", function ( e ) {
433 self.fullScreen( !self.options.fullScreen );
434 control.fadeIn( "fast" );
440 seekBar.bind( "vmousedown.multimediaview", function ( e ) {
442 duration = viewElement.duration,
443 durationOffset = durationBar.offset(),
444 durationWidth = durationBar.width(),
445 timerate = ( x - durationOffset.left ) / durationWidth,
446 time = duration * timerate;
448 viewElement.currentTime = time;
455 $( document ).bind( "vmousemove.multimediaview", function ( e ) {
457 timerate = ( x - durationOffset.left ) / durationWidth;
459 viewElement.currentTime = duration * timerate;
463 }).bind( "vmouseup.multimediaview", function () {
464 $( document ).unbind( "vmousemove.multimediaview vmouseup.multimediaview" );
465 if ( viewElement.paused ) {
473 volumeButton.bind( "vclick.multimediaview", function () {
474 if ( self.isVolumeHide ) {
475 var view = self.element,
476 volume = viewElement.volume;
478 self.isVolumeHide = false;
480 volumeControl.fadeIn( "fast" );
481 self._updateVolumeState();
482 self._updateSeekBar();
484 self.isVolumeHide = true;
485 volumeControl.fadeOut( "fast", function () {
488 self._updateSeekBar();
492 volumeBar.bind( "vmousedown.multimediaview", function ( e ) {
493 var baseX = e.clientX,
494 volumeGuideLeft = volumeGuide.offset().left,
495 volumeGuideWidth = volumeGuide.width(),
496 volumeBase = volumeGuideLeft + volumeGuideWidth,
497 handlerOffset = volumeHandle.offset(),
498 volumerate = ( baseX - volumeGuideLeft ) / volumeGuideWidth,
499 currentVolume = ( baseX - volumeGuideLeft ) / volumeGuideWidth;
502 self._setVolume( currentVolume.toFixed( 2 ) );
507 $( document ).bind( "vmousemove.multimediaview", function ( e ) {
508 var currentX = e.clientX,
509 currentVolume = ( currentX - volumeGuideLeft ) / volumeGuideWidth;
511 self._setVolume( currentVolume.toFixed( 2 ) );
515 }).bind( "vmouseup.multimediaview", function () {
516 $( document ).unbind( "vmousemove.multimediaview vmouseup.multimediaview" );
518 if ( self.options.mediatype == "video" ) {
524 _removeEvent : function () {
527 control = view.parent().find( ".ui-multimediaview-control" ),
528 playpauseButton = control.find( ".ui-playpausebutton" ),
529 fullscreenButton = control.find( ".ui-fullscreenbutton" ),
530 seekBar = control.find( ".ui-seekbar" ),
531 volumeControl = control.find( ".ui-volumecontrol" ),
532 volumeBar = volumeControl.find( ".ui-volumebar" ),
533 volumeHandle = volumeControl.find( ".ui-handler" );
535 view.unbind( ".multimediaview" );
536 playpauseButton.unbind( ".multimediaview" );
537 fullscreenButton.unbind( ".multimediaview" );
538 seekBar.unbind( ".multimediaview" );
539 volumeBar.unbind( ".multimediaview" );
540 volumeHandle.unbind( ".multimediaview" );
542 _createControl : function () {
545 control = $( "<span></span>" ),
546 playpauseButton = $( "<span></span>" ),
547 seekBar = $( "<span></span>" ),
548 timestampLabel = $( "<span><p>00:00:00</p></span>" ),
549 durationLabel = $( "<span><p>00:00:00</p></span>" ),
550 volumeButton = $( "<span></span>" ),
551 volumeControl = $( "<span></span>" ),
552 volumeBar = $( "<div></div>" ),
553 volumeGuide = $( "<span></span>" ),
554 volumeValue = $( "<span></span>" ),
555 volumeHandle = $( "<span></span>" ),
556 fullscreenButton = $( "<span></span>" ),
557 durationBar = $( "<span></span>" ),
558 currenttimeBar = $( "<span></span>" );
560 control.addClass( "ui-" + self.role + "-control" );
561 playpauseButton.addClass( "ui-playpausebutton ui-button" );
562 seekBar.addClass( "ui-seekbar" );
563 timestampLabel.addClass( "ui-timestamplabel" );
564 durationLabel.addClass( "ui-durationlabel" );
565 volumeButton.addClass( "ui-volumebutton ui-button" );
566 fullscreenButton.addClass( "ui-fullscreenbutton ui-button" );
567 durationBar.addClass( "ui-duration" );
568 currenttimeBar.addClass( "ui-currenttime" );
569 volumeControl.addClass( "ui-volumecontrol" );
570 volumeBar.addClass( "ui-volumebar" );
571 volumeGuide.addClass( "ui-guide" );
572 volumeValue.addClass( "ui-value" );
573 volumeHandle.addClass( "ui-handler" );
575 seekBar.append( durationBar ).append( currenttimeBar ).append( durationLabel ).append( timestampLabel );
577 playpauseButton.addClass( "ui-play-icon" );
578 if ( view[0].muted ) {
579 $( volumeButton ).addClass( "ui-mute-icon" );
581 $( volumeButton ).addClass( "ui-volume-icon" );
584 volumeBar.append( volumeGuide ).append( volumeValue ).append( volumeHandle );
585 volumeControl.append( volumeBar );
587 control.append( playpauseButton ).append( seekBar ).append( volumeControl ).append( volumeButton );
589 if ( self.element[0].nodeName === "VIDEO" ) {
590 $( fullscreenButton ).addClass( "ui-fullscreen-on" );
591 control.append( fullscreenButton );
593 volumeControl.hide();
597 _startTimer : function ( duration ) {
606 control = view.parent().find( ".ui-multimediaview-control" ),
607 volumeControl = control.find( ".ui-volumecontrol" );
609 self.controlTimer = setTimeout( function () {
610 self.isVolumeHide = true;
611 self.isControlHide = true;
612 self.controlTimer = null;
613 volumeControl.hide();
614 control.fadeOut( "fast" );
617 _endTimer : function () {
618 if ( this.controlTimer ) {
619 clearTimeout( this.controlTimer );
620 this.controlTimer = null;
623 _convertTimeFormat : function ( systime ) {
624 var ss = parseInt( systime % 60, 10 ).toString(),
625 mm = parseInt( ( systime / 60 ) % 60, 10 ).toString(),
626 hh = parseInt( systime / 3600, 10 ).toString(),
627 time = ( ( hh.length < 2 ) ? "0" + hh : hh ) + ":" +
628 ( ( mm.length < 2 ) ? "0" + mm : mm ) + ":" +
629 ( ( ss.length < 2 ) ? "0" + ss : ss );
633 _updateSeekBar : function ( currenttime ) {
636 duration = view[0].duration,
637 control = view.parent().find( ".ui-multimediaview-control" ),
638 seekBar = control.find( ".ui-seekbar" ),
639 durationBar = seekBar.find( ".ui-duration" ),
640 currenttimeBar = seekBar.find( ".ui-currenttime" ),
641 timestampLabel = control.find( ".ui-timestamplabel" ),
642 durationOffset = durationBar.offset(),
643 durationWidth = durationBar.width(),
644 durationHeight = durationBar.height(),
647 if ( typeof currenttime == "undefined" ) {
648 currenttime = view[0].currentTime;
650 timebarWidth = parseInt( currenttime / duration * durationWidth, 10 );
651 durationBar.offset( durationOffset );
652 currenttimeBar.offset( durationOffset ).width( timebarWidth );
653 timestampLabel.find( "p" ).text( self._convertTimeFormat( currenttime ) );
655 _updateVolumeState : function () {
658 control = view.parent().find( ".ui-multimediaview-control" ),
659 volumeControl = control.find( ".ui-volumecontrol" ),
660 volumeButton = control.find( ".ui-volumebutton" ),
661 volumeBar = volumeControl.find( ".ui-volumebar" ),
662 volumeGuide = volumeControl.find( ".ui-guide" ),
663 volumeValue = volumeControl.find( ".ui-value" ),
664 volumeHandle = volumeControl.find( ".ui-handler" ),
665 handlerWidth = volumeHandle.width(),
666 handlerHeight = volumeHandle.height(),
667 volumeGuideHeight = volumeGuide.height(),
668 volumeGuideWidth = volumeGuide.width(),
672 handlerOffset = null,
673 volume = view[0].volume;
675 volumeGuideTop = parseInt( volumeGuide.offset().top, 10 );
676 volumeGuideLeft = parseInt( volumeGuide.offset().left, 10 );
677 volumeBase = volumeGuideLeft;
678 handlerOffset = volumeHandle.offset();
679 handlerOffset.top = volumeGuideTop - parseInt( ( handlerHeight - volumeGuideHeight ) / 2, 10 );
680 handlerOffset.left = volumeBase + parseInt( volumeGuideWidth * volume, 10 ) - parseInt( handlerWidth / 2, 10 );
681 volumeHandle.offset( handlerOffset );
682 volumeValue.width( parseInt( volumeGuideWidth * ( volume ), 10 ) );
684 _setVolume : function ( value ) {
685 var viewElement = this.element[0];
687 if ( value < 0.0 || value > 1.0 ) {
691 viewElement.volume = value;
693 _fitContentArea: function ( page, parent ) {
694 if ( typeof parent == "undefined" ) {
698 var $page = $( page ),
699 $content = $( ".ui-content:visible:first" ),
700 hh = $( ".ui-header:visible" ).outerHeight() || 0,
701 fh = $( ".ui-footer:visible" ).outerHeight() || 0,
702 pt = parseFloat( $content.css( "padding-top" ) ),
703 pb = parseFloat( $content.css( "padding-bottom" ) ),
704 wh = ( ( parent === window ) ? window.innerHeight : $( parent ).height() ),
705 height = wh - ( hh + fh ) - ( pt + pb );
711 width : function ( value ) {
716 if ( args.length === 0 ) {
719 if ( args.length === 1 ) {
724 height : function ( value ) {
729 if ( args.length === 0 ) {
730 return view.height();
732 if ( args.length === 1 ) {
733 view.height( value );
737 fullScreen : function ( value ) {
740 control = view.parent().find( ".ui-multimediaview-control" ),
741 fullscreenButton = control.find( ".ui-fullscreenbutton" ),
743 option = self.options,
744 currentPage = $( ".ui-page-active" );
746 if ( args.length === 0 ) {
747 return option.fullScreen;
749 if ( args.length === 1 ) {
750 view.parents( ".ui-content" ).scrollview( "scrollTo", 0, 0 );
752 this.options.fullScreen = value;
754 currentPage.children( ".ui-header" ).hide();
755 currentPage.children( ".ui-footer" ).hide();
756 this._fitContentArea( currentPage );
757 fullscreenButton.removeClass( "ui-fullscreen-on" ).addClass( "ui-fullscreen-off" );
759 currentPage.children( ".ui-header" ).show();
760 currentPage.children( ".ui-footer" ).show();
761 this._fitContentArea( currentPage );
762 fullscreenButton.removeClass( "ui-fullscreen-off" ).addClass( "ui-fullscreen-on" );
767 refresh : function () {
772 $( document ).bind( "pagecreate create", function ( e ) {
773 $.tizen.multimediaview.prototype.enhanceWithin( e.target );
775 } ( jQuery, document, window ) );