blob: 868204995af2fe2c292f97109e7007a6f409b737 [file] [log] [blame]
// <copyright file="SlowLoadableComponent{T}.cs" company="Selenium Committers">
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
// </copyright>
using System;
using System.Globalization;
using System.Threading;
namespace OpenQA.Selenium.Support.UI;
/// <summary>
/// <para>A <see cref="LoadableComponent{T}"/> which might not have finished loading when Load() returns.</para>
/// <para>After a call to Load(), the IsLoaded property should continue to return false until the component has fully loaded.</para>
/// <para>
/// <code>
/// // Example usage:
/// new MySlowComponent().Load();
/// </code>
/// </para>
/// </summary>
/// <remarks>Override the HandleErrors() method to check for error conditions which caused <see cref="Load()"/> to fail.</remarks>
/// <typeparam name="T">The type to be returned (normally the subclass' type)</typeparam>
public abstract class SlowLoadableComponent<T> : LoadableComponent<T>
where T : SlowLoadableComponent<T>
{
/// <summary>
/// Initializes a new instance of the <see cref="SlowLoadableComponent{T}"/> class.
/// </summary>
/// <param name="timeout">The <see cref="TimeSpan"/> within which the component should be loaded.</param>
protected SlowLoadableComponent(TimeSpan timeout)
: this(timeout, SystemClock.Instance)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="SlowLoadableComponent{T}"/> class.
/// </summary>
/// <param name="timeout">The <see cref="TimeSpan"/> within which the component should be loaded.</param>
/// <param name="clock">The <see cref="IClock"/> to use when measuring the timeout.</param>
/// <exception cref="ArgumentNullException">If <paramref name="clock"/> is <see langword="null"/>.</exception>
protected SlowLoadableComponent(TimeSpan timeout, IClock clock)
{
this.Clock = clock ?? throw new ArgumentNullException(nameof(clock));
this.Timeout = timeout;
}
/// <summary>
/// Gets or sets the time to sleep between each check of the load status of the component.
/// </summary>
public TimeSpan SleepInterval { get; set; } = TimeSpan.FromMilliseconds(200);
/// <summary>
/// Gets the timeout interval before which this component must be considered loaded.
/// </summary>
protected TimeSpan Timeout { get; }
/// <summary>
/// Gets the clock object providing timing for monitoring the load status of this component.
/// </summary>
protected IClock Clock { get; }
/// <summary>
/// Ensures that the component is currently loaded.
/// </summary>
/// <returns>The loaded component.</returns>
/// <remarks>This is equivalent to the Get() method in Java version.</remarks>
public override T Load()
{
if (this.IsLoaded)
{
return (T)this;
}
else
{
this.TryLoad();
}
DateTime end = this.Clock.LaterBy(this.Timeout);
while (this.Clock.IsNowBefore(end))
{
if (this.IsLoaded)
{
return (T)this;
}
this.HandleErrors();
this.Wait();
}
if (this.IsLoaded)
{
return (T)this;
}
else
{
if (string.IsNullOrEmpty(UnableToLoadMessage))
{
this.UnableToLoadMessage = string.Format(CultureInfo.InvariantCulture, "Timed out after {0} seconds.", this.Timeout.TotalSeconds);
}
throw new WebDriverTimeoutException(this.UnableToLoadMessage);
}
}
/// <summary>
/// Checks for well known error cases, which would mean that loading has finished, but an error
/// condition was seen.
/// </summary>
/// <remarks>
/// This method should be overridden so that expected errors can be automatically handled.
/// </remarks>
protected virtual void HandleErrors()
{
// no-op by default
}
/// <summary>
/// Waits between polls of the load status of this component.
/// </summary>
protected virtual void Wait()
{
Thread.Sleep(this.SleepInterval);
}
}