forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			615 lines
		
	
	
	
		
			18 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			615 lines
		
	
	
	
		
			18 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* vim: set ts=2 et sw=2 tw=80: */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 | |
|  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| /**
 | |
|  * Handles the indicator that displays the progress of ongoing downloads, which
 | |
|  * is also used as the anchor for the downloads panel.
 | |
|  *
 | |
|  * This module includes the following constructors and global objects:
 | |
|  *
 | |
|  * DownloadsButton
 | |
|  * Main entry point for the downloads indicator.  Depending on how the toolbars
 | |
|  * have been customized, this object determines if we should show a fully
 | |
|  * functional indicator, a placeholder used during customization and in the
 | |
|  * customization palette, or a neutral view as a temporary anchor for the
 | |
|  * downloads panel.
 | |
|  *
 | |
|  * DownloadsIndicatorView
 | |
|  * Builds and updates the actual downloads status widget, responding to changes
 | |
|  * in the global status data, or provides a neutral view if the indicator is
 | |
|  * removed from the toolbars and only used as a temporary anchor.  In addition,
 | |
|  * handles the user interaction events raised by the widget.
 | |
|  */
 | |
| 
 | |
| "use strict";
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| //// DownloadsButton
 | |
| 
 | |
| /**
 | |
|  * Main entry point for the downloads indicator.  Depending on how the toolbars
 | |
|  * have been customized, this object determines if we should show a fully
 | |
|  * functional indicator, a placeholder used during customization and in the
 | |
|  * customization palette, or a neutral view as a temporary anchor for the
 | |
|  * downloads panel.
 | |
|  */
 | |
| const DownloadsButton = {
 | |
|   /**
 | |
|    * Location of the indicator overlay.
 | |
|    */
 | |
|   get kIndicatorOverlay()
 | |
|       "chrome://browser/content/downloads/indicatorOverlay.xul",
 | |
| 
 | |
|   /**
 | |
|    * Returns a reference to the downloads button position placeholder, or null
 | |
|    * if not available because it has been removed from the toolbars.
 | |
|    */
 | |
|   get _placeholder()
 | |
|   {
 | |
|     return document.getElementById("downloads-button");
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * This function is called asynchronously just after window initialization.
 | |
|    *
 | |
|    * NOTE: This function should limit the input/output it performs to improve
 | |
|    *       startup time, and in particular should not cause the Download Manager
 | |
|    *       service to start.
 | |
|    */
 | |
|   initializeIndicator: function DB_initializeIndicator()
 | |
|   {
 | |
|     if (!DownloadsCommon.useToolkitUI) {
 | |
|       DownloadsIndicatorView.ensureInitialized();
 | |
|     } else {
 | |
|       DownloadsIndicatorView.ensureTerminated();
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Indicates whether toolbar customization is in progress.
 | |
|    */
 | |
|   _customizing: false,
 | |
| 
 | |
|   /**
 | |
|    * This function is called when toolbar customization starts.
 | |
|    *
 | |
|    * During customization, we never show the actual download progress indication
 | |
|    * or the event notifications, but we show a neutral placeholder.  The neutral
 | |
|    * placeholder is an ordinary button defined in the browser window that can be
 | |
|    * moved freely between the toolbars and the customization palette.
 | |
|    */
 | |
|   customizeStart: function DB_customizeStart()
 | |
|   {
 | |
|     // Prevent the indicator from being displayed as a temporary anchor
 | |
|     // during customization, even if requested using the getAnchor method.
 | |
|     this._customizing = true;
 | |
|     this._anchorRequested = false;
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * This function is called when toolbar customization ends.
 | |
|    */
 | |
|   customizeDone: function DB_customizeDone()
 | |
|   {
 | |
|     this._customizing = false;
 | |
|     if (!DownloadsCommon.useToolkitUI) {
 | |
|       DownloadsIndicatorView.afterCustomize();
 | |
|     } else {
 | |
|       DownloadsIndicatorView.ensureTerminated();
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Determines the position where the indicator should appear, and moves its
 | |
|    * associated element to the new position.
 | |
|    *
 | |
|    * @return Anchor element, or null if the indicator is not visible.
 | |
|    */
 | |
|   _getAnchorInternal: function DB_getAnchorInternal()
 | |
|   {
 | |
|     let indicator = DownloadsIndicatorView.indicator;
 | |
|     if (!indicator) {
 | |
|       // Exit now if the indicator overlay isn't loaded yet, or if the button
 | |
|       // is not in the document.
 | |
|       return null;
 | |
|     }
 | |
| 
 | |
|     indicator.open = this._anchorRequested;
 | |
| 
 | |
|     let widget = CustomizableUI.getWidget("downloads-button")
 | |
|                                .forWindow(window);
 | |
|      // Determine if the indicator is located on an invisible toolbar.
 | |
|      if (!isElementVisible(indicator.parentNode) && !widget.overflowed) {
 | |
|        return null;
 | |
|      }
 | |
| 
 | |
|     return DownloadsIndicatorView.indicatorAnchor;
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Checks whether the indicator is, or will soon be visible in the browser
 | |
|    * window.
 | |
|    *
 | |
|    * @param aCallback
 | |
|    *        Called once the indicator overlay has loaded. Gets a boolean
 | |
|    *        argument representing the indicator visibility.
 | |
|    */
 | |
|   checkIsVisible: function DB_checkIsVisible(aCallback)
 | |
|   {
 | |
|     function DB_CEV_callback() {
 | |
|       if (!this._placeholder) {
 | |
|         aCallback(false);
 | |
|       } else {
 | |
|         let element = DownloadsIndicatorView.indicator || this._placeholder;
 | |
|         aCallback(isElementVisible(element.parentNode));
 | |
|       }
 | |
|     }
 | |
|     DownloadsOverlayLoader.ensureOverlayLoaded(this.kIndicatorOverlay,
 | |
|                                                DB_CEV_callback.bind(this));
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Indicates whether we should try and show the indicator temporarily as an
 | |
|    * anchor for the panel, even if the indicator would be hidden by default.
 | |
|    */
 | |
|   _anchorRequested: false,
 | |
| 
 | |
|   /**
 | |
|    * Ensures that there is an anchor available for the panel.
 | |
|    *
 | |
|    * @param aCallback
 | |
|    *        Called when the anchor is available, passing the element where the
 | |
|    *        panel should be anchored, or null if an anchor is not available (for
 | |
|    *        example because both the tab bar and the navigation bar are hidden).
 | |
|    */
 | |
|   getAnchor: function DB_getAnchor(aCallback)
 | |
|   {
 | |
|     // Do not allow anchoring the panel to the element while customizing.
 | |
|     if (this._customizing) {
 | |
|       aCallback(null);
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     function DB_GA_callback() {
 | |
|       this._anchorRequested = true;
 | |
|       aCallback(this._getAnchorInternal());
 | |
|     }
 | |
| 
 | |
|     DownloadsOverlayLoader.ensureOverlayLoaded(this.kIndicatorOverlay,
 | |
|                                                DB_GA_callback.bind(this));
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Allows the temporary anchor to be hidden.
 | |
|    */
 | |
|   releaseAnchor: function DB_releaseAnchor()
 | |
|   {
 | |
|     this._anchorRequested = false;
 | |
|     this._getAnchorInternal();
 | |
|   },
 | |
| 
 | |
|   get _tabsToolbar()
 | |
|   {
 | |
|     delete this._tabsToolbar;
 | |
|     return this._tabsToolbar = document.getElementById("TabsToolbar");
 | |
|   },
 | |
| 
 | |
|   get _navBar()
 | |
|   {
 | |
|     delete this._navBar;
 | |
|     return this._navBar = document.getElementById("nav-bar");
 | |
|   }
 | |
| };
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| //// DownloadsIndicatorView
 | |
| 
 | |
| /**
 | |
|  * Builds and updates the actual downloads status widget, responding to changes
 | |
|  * in the global status data, or provides a neutral view if the indicator is
 | |
|  * removed from the toolbars and only used as a temporary anchor.  In addition,
 | |
|  * handles the user interaction events raised by the widget.
 | |
|  */
 | |
| const DownloadsIndicatorView = {
 | |
|   /**
 | |
|    * True when the view is connected with the underlying downloads data.
 | |
|    */
 | |
|   _initialized: false,
 | |
| 
 | |
|   /**
 | |
|    * True when the user interface elements required to display the indicator
 | |
|    * have finished loading in the browser window, and can be referenced.
 | |
|    */
 | |
|   _operational: false,
 | |
| 
 | |
|   /**
 | |
|    * Prepares the downloads indicator to be displayed.
 | |
|    */
 | |
|   ensureInitialized: function DIV_ensureInitialized()
 | |
|   {
 | |
|     if (this._initialized) {
 | |
|       return;
 | |
|     }
 | |
|     this._initialized = true;
 | |
| 
 | |
|     window.addEventListener("unload", this.onWindowUnload, false);
 | |
|     DownloadsCommon.getIndicatorData(window).addView(this);
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Frees the internal resources related to the indicator.
 | |
|    */
 | |
|   ensureTerminated: function DIV_ensureTerminated()
 | |
|   {
 | |
|     if (!this._initialized) {
 | |
|       return;
 | |
|     }
 | |
|     this._initialized = false;
 | |
| 
 | |
|     window.removeEventListener("unload", this.onWindowUnload, false);
 | |
|     DownloadsCommon.getIndicatorData(window).removeView(this);
 | |
| 
 | |
|     // Reset the view properties, so that a neutral indicator is displayed if we
 | |
|     // are visible only temporarily as an anchor.
 | |
|     this.counter = "";
 | |
|     this.percentComplete = 0;
 | |
|     this.paused = false;
 | |
|     this.attention = false;
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Ensures that the user interface elements required to display the indicator
 | |
|    * are loaded, then invokes the given callback.
 | |
|    */
 | |
|   _ensureOperational: function DIV_ensureOperational(aCallback)
 | |
|   {
 | |
|     if (this._operational) {
 | |
|       if (aCallback) {
 | |
|         aCallback();
 | |
|       }
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // If we don't have a _placeholder, there's no chance that the overlay
 | |
|     // will load correctly: bail (and don't set _operational to true!)
 | |
|     if (!DownloadsButton._placeholder) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     function DIV_EO_callback() {
 | |
|       this._operational = true;
 | |
| 
 | |
|       // If the view is initialized, we need to update the elements now that
 | |
|       // they are finally available in the document.
 | |
|       if (this._initialized) {
 | |
|         DownloadsCommon.getIndicatorData(window).refreshView(this);
 | |
|       }
 | |
| 
 | |
|       if (aCallback) {
 | |
|         aCallback();
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     DownloadsOverlayLoader.ensureOverlayLoaded(
 | |
|                                  DownloadsButton.kIndicatorOverlay,
 | |
|                                  DIV_EO_callback.bind(this));
 | |
|   },
 | |
| 
 | |
|   //////////////////////////////////////////////////////////////////////////////
 | |
|   //// Direct control functions
 | |
| 
 | |
|   /**
 | |
|    * Set while we are waiting for a notification to fade out.
 | |
|    */
 | |
|   _notificationTimeout: null,
 | |
| 
 | |
|   /**
 | |
|    * If the status indicator is visible in its assigned position, shows for a
 | |
|    * brief time a visual notification of a relevant event, like a new download.
 | |
|    *
 | |
|    * @param aType
 | |
|    *        Set to "start" for new downloads, "finish" for completed downloads.
 | |
|    */
 | |
|   showEventNotification: function DIV_showEventNotification(aType)
 | |
|   {
 | |
|     if (!this._initialized) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (!DownloadsCommon.animateNotifications) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // No need to show visual notification if the panel is visible.
 | |
|     if (DownloadsPanel.isPanelShowing) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     let anchor = DownloadsButton._placeholder;
 | |
|     let widgetGroup = CustomizableUI.getWidget("downloads-button");
 | |
|     let widgetInWindow = widgetGroup.forWindow(window);
 | |
|     if (widgetInWindow.overflowed || widgetGroup.areaType == CustomizableUI.TYPE_MENU_PANEL) {
 | |
|       if (anchor && isElementVisible(anchor.parentNode)) {
 | |
|         // If the panel is open, don't do anything:
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       // Otherwise, try to use the anchor of the panel:
 | |
|       anchor = widgetInWindow.anchor;
 | |
|     }
 | |
|     if (!anchor || !isElementVisible(anchor.parentNode)) {
 | |
|       // Our container isn't visible, so can't show the animation:
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (this._notificationTimeout) {
 | |
|       clearTimeout(this._notificationTimeout);
 | |
|     }
 | |
| 
 | |
|     // The notification element is positioned to show in the same location as
 | |
|     // the downloads button. It's not in the downloads button itself in order to
 | |
|     // be able to anchor the notification elsewhere if required, and to ensure
 | |
|     // the notification isn't clipped by overflow properties of the anchor's
 | |
|     // container.
 | |
|     let notifier = this.notifier;
 | |
|     if (notifier.style.transform == '') {
 | |
|       let anchorRect = anchor.getBoundingClientRect();
 | |
|       let notifierRect = notifier.getBoundingClientRect();
 | |
|       let topDiff = anchorRect.top - notifierRect.top;
 | |
|       let leftDiff = anchorRect.left - notifierRect.left;
 | |
|       let heightDiff = anchorRect.height - notifierRect.height;
 | |
|       let widthDiff = anchorRect.width - notifierRect.width;
 | |
|       let translateX = (leftDiff + .5 * widthDiff) + "px";
 | |
|       let translateY = (topDiff + .5 * heightDiff) + "px";
 | |
|       notifier.style.transform = "translate(" +  translateX + ", " + translateY + ")";
 | |
|     }
 | |
|     notifier.setAttribute("notification", aType);
 | |
|     this._notificationTimeout = setTimeout(function () {
 | |
|       notifier.removeAttribute("notification");
 | |
|       notifier.style.transform = '';
 | |
|     }, 1000);
 | |
|   },
 | |
| 
 | |
|   //////////////////////////////////////////////////////////////////////////////
 | |
|   //// Callback functions from DownloadsIndicatorData
 | |
| 
 | |
|   /**
 | |
|    * Indicates whether the indicator should be shown because there are some
 | |
|    * downloads to be displayed.
 | |
|    */
 | |
|   set hasDownloads(aValue)
 | |
|   {
 | |
|     if (this._hasDownloads != aValue) {
 | |
|       this._hasDownloads = aValue;
 | |
| 
 | |
|       // If there is at least one download, ensure that the view elements are
 | |
|       if (aValue) {
 | |
|         this._ensureOperational();
 | |
|       }
 | |
|     }
 | |
|     return aValue;
 | |
|   },
 | |
|   get hasDownloads()
 | |
|   {
 | |
|     return this._hasDownloads;
 | |
|   },
 | |
|   _hasDownloads: false,
 | |
| 
 | |
|   /**
 | |
|    * Status text displayed in the indicator.  If this is set to an empty value,
 | |
|    * then the small downloads icon is displayed instead of the text.
 | |
|    */
 | |
|   set counter(aValue)
 | |
|   {
 | |
|     if (!this._operational) {
 | |
|       return this._counter;
 | |
|     }
 | |
| 
 | |
|     if (this._counter !== aValue) {
 | |
|       this._counter = aValue;
 | |
|       if (this._counter)
 | |
|         this.indicator.setAttribute("counter", "true");
 | |
|       else
 | |
|         this.indicator.removeAttribute("counter");
 | |
|       // We have to set the attribute instead of using the property because the
 | |
|       // XBL binding isn't applied if the element is invisible for any reason.
 | |
|       this._indicatorCounter.setAttribute("value", aValue);
 | |
|     }
 | |
|     return aValue;
 | |
|   },
 | |
|   _counter: null,
 | |
| 
 | |
|   /**
 | |
|    * Progress indication to display, from 0 to 100, or -1 if unknown.  The
 | |
|    * progress bar is hidden if the current progress is unknown and no status
 | |
|    * text is set in the "counter" property.
 | |
|    */
 | |
|   set percentComplete(aValue)
 | |
|   {
 | |
|     if (!this._operational) {
 | |
|       return this._percentComplete;
 | |
|     }
 | |
| 
 | |
|     if (this._percentComplete !== aValue) {
 | |
|       this._percentComplete = aValue;
 | |
|       if (this._percentComplete >= 0)
 | |
|         this.indicator.setAttribute("progress", "true");
 | |
|       else
 | |
|         this.indicator.removeAttribute("progress");
 | |
|       // We have to set the attribute instead of using the property because the
 | |
|       // XBL binding isn't applied if the element is invisible for any reason.
 | |
|       this._indicatorProgress.setAttribute("value", Math.max(aValue, 0));
 | |
|     }
 | |
|     return aValue;
 | |
|   },
 | |
|   _percentComplete: null,
 | |
| 
 | |
|   /**
 | |
|    * Indicates whether the progress won't advance because of a paused state.
 | |
|    * Setting this property forces a paused progress bar to be displayed, even if
 | |
|    * the current progress information is unavailable.
 | |
|    */
 | |
|   set paused(aValue)
 | |
|   {
 | |
|     if (!this._operational) {
 | |
|       return this._paused;
 | |
|     }
 | |
| 
 | |
|     if (this._paused != aValue) {
 | |
|       this._paused = aValue;
 | |
|       if (this._paused) {
 | |
|         this.indicator.setAttribute("paused", "true")
 | |
|       } else {
 | |
|         this.indicator.removeAttribute("paused");
 | |
|       }
 | |
|     }
 | |
|     return aValue;
 | |
|   },
 | |
|   _paused: false,
 | |
| 
 | |
|   /**
 | |
|    * Set when the indicator should draw user attention to itself.
 | |
|    */
 | |
|   set attention(aValue)
 | |
|   {
 | |
|     if (!this._operational) {
 | |
|       return this._attention;
 | |
|     }
 | |
| 
 | |
|     if (this._attention != aValue) {
 | |
|       this._attention = aValue;
 | |
|       if (aValue) {
 | |
|         this.indicator.setAttribute("attention", "true");
 | |
|       } else {
 | |
|         this.indicator.removeAttribute("attention");
 | |
|       }
 | |
|     }
 | |
|     return aValue;
 | |
|   },
 | |
|   _attention: false,
 | |
| 
 | |
|   //////////////////////////////////////////////////////////////////////////////
 | |
|   //// User interface event functions
 | |
| 
 | |
|   onWindowUnload: function DIV_onWindowUnload()
 | |
|   {
 | |
|     // This function is registered as an event listener, we can't use "this".
 | |
|     DownloadsIndicatorView.ensureTerminated();
 | |
|   },
 | |
| 
 | |
|   onCommand: function DIV_onCommand(aEvent)
 | |
|   {
 | |
|     if (DownloadsCommon.useToolkitUI) {
 | |
|       // The panel won't suppress attention for us, we need to clear now.
 | |
|       DownloadsCommon.getIndicatorData(window).attention = false;
 | |
|       BrowserDownloadsUI();
 | |
|     } else {
 | |
|       // If the downloads button is in the menu panel, open the Library
 | |
|       let widgetGroup = CustomizableUI.getWidget("downloads-button");
 | |
|       if (widgetGroup.areaType == CustomizableUI.TYPE_MENU_PANEL) {
 | |
|         DownloadsPanel.showDownloadsHistory();
 | |
|       } else {
 | |
|         DownloadsPanel.showPanel();
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     aEvent.stopPropagation();
 | |
|   },
 | |
| 
 | |
|   onDragOver: function DIV_onDragOver(aEvent)
 | |
|   {
 | |
|     browserDragAndDrop.dragOver(aEvent);
 | |
|   },
 | |
| 
 | |
|   onDrop: function DIV_onDrop(aEvent)
 | |
|   {
 | |
|     let dt = aEvent.dataTransfer;
 | |
|     // If dragged item is from our source, do not try to
 | |
|     // redownload already downloaded file.
 | |
|     if (dt.mozGetDataAt("application/x-moz-file", 0))
 | |
|       return;
 | |
| 
 | |
|     let name = {};
 | |
|     let url = browserDragAndDrop.drop(aEvent, name);
 | |
|     if (url) {
 | |
|       if (url.startsWith("about:")) {
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       let sourceDoc = dt.mozSourceNode ? dt.mozSourceNode.ownerDocument : document;
 | |
|       saveURL(url, name.value, null, true, true, null, sourceDoc);
 | |
|       aEvent.preventDefault();
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   _indicator: null,
 | |
|   __indicatorCounter: null,
 | |
|   __indicatorProgress: null,
 | |
| 
 | |
|   /**
 | |
|    * Returns a reference to the main indicator element, or null if the element
 | |
|    * is not present in the browser window yet.
 | |
|    */
 | |
|   get indicator()
 | |
|   {
 | |
|     if (this._indicator) {
 | |
|       return this._indicator;
 | |
|     }
 | |
| 
 | |
|     let indicator = document.getElementById("downloads-button");
 | |
|     if (!indicator || indicator.getAttribute("indicator") != "true") {
 | |
|       return null;
 | |
|     }
 | |
| 
 | |
|     return this._indicator = indicator;
 | |
|   },
 | |
| 
 | |
|   get indicatorAnchor()
 | |
|   {
 | |
|     let widget = CustomizableUI.getWidget("downloads-button")
 | |
|                                .forWindow(window);
 | |
|     if (widget.overflowed) {
 | |
|       return widget.anchor;
 | |
|     }
 | |
|     return document.getElementById("downloads-indicator-anchor");
 | |
|   },
 | |
| 
 | |
|   get _indicatorCounter()
 | |
|   {
 | |
|     return this.__indicatorCounter ||
 | |
|       (this.__indicatorCounter = document.getElementById("downloads-indicator-counter"));
 | |
|   },
 | |
| 
 | |
|   get _indicatorProgress()
 | |
|   {
 | |
|     return this.__indicatorProgress ||
 | |
|       (this.__indicatorProgress = document.getElementById("downloads-indicator-progress"));
 | |
|   },
 | |
| 
 | |
|   get notifier()
 | |
|   {
 | |
|     return this._notifier ||
 | |
|       (this._notifier = document.getElementById("downloads-notification-anchor"));
 | |
|   },
 | |
| 
 | |
|   _onCustomizedAway: function() {
 | |
|     this._indicator = null;
 | |
|     this.__indicatorCounter = null;
 | |
|     this.__indicatorProgress = null;
 | |
|   },
 | |
| 
 | |
|   afterCustomize: function() {
 | |
|     // If the cached indicator is not the one currently in the document,
 | |
|     // invalidate our references
 | |
|     if (this._indicator != document.getElementById("downloads-button")) {
 | |
|       this._onCustomizedAway();
 | |
|       this._operational = false;
 | |
|       this.ensureTerminated();
 | |
|       this.ensureInitialized();
 | |
|     }
 | |
|   }
 | |
| };
 | |
| 
 | 
