public final function fire( $eventName /* ... $eventArgs: any[] */ ) {

			if ( !is_string( $eventName ) ) {
				throw new \browserfs\Exception('Invalid argument $eventName: string expected' );
			} else
			if ( !strlen( $eventName ) ) {
				throw new \browserfs\Exception('Invalid argument $eventName: non-empty string expected');
			}

			if ( !isset( $this->events[ $eventName ] ) ) {
				return;
			}

			$eventArgs = array_slice( func_get_args(), 1 );

			$event = Event::create( $eventName, $eventArgs );

			try {

				$this->fireId++;

				// run the loop on a copy, in order to allow the $this->off method
				// to work properly
				$copy = [];

				foreach ( $this->events[ $eventName ] as &$subscriber ) {
					$copy[] = &$subscriber;
				}

				foreach ( $copy as &$subscriber ) {

					$subscriber['fireId'] = $this->fireId;

					call_user_func( $subscriber['callback'], $event );

					if ( $event->isPropagationStopped() ) {
						break;
					}
				}

				if ( isset( $this->events[ $eventName ] ) ) {

					// remove the fired "once" listeners
					for ( $i = count( $this->events[ $eventName ] ) - 1; $i >= 0; $i-- ) {

						if ( $this->events[ $eventName ][$i]['fireId'] === $this->fireId ) {
						
							if ( $this->events[ $eventName ][$i]['once'] === true ) {
								// remove event
								array_splice( $this->events[$eventName], $i, 1 );
							}
						
						}
					
					}

				}

			} catch ( \Exception $e ) {
				
				throw new \browserfs\Exception( "Error firing event " . $eventName, 0, $e );
			
			}

			return $event;

		}