blob: 67e3149e06810d137b20934861aea1de93e29748 [file] [log] [blame]
/*
* Copyright (C) 2013-2015 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "PageLoadState.h"
#include "WebPageProxy.h"
namespace WebKit {
// Progress always starts at this value. This helps provide feedback as soon as a load starts.
static const double initialProgressValue = 0.1;
PageLoadState::PageLoadState(WebPageProxy& webPageProxy)
: m_webPageProxy(webPageProxy)
, m_mayHaveUncommittedChanges(false)
, m_outstandingTransactionCount(0)
{
}
PageLoadState::~PageLoadState()
{
ASSERT(m_observers.isEmpty());
}
PageLoadState::Transaction::Transaction(PageLoadState& pageLoadState)
: m_webPageProxy(&pageLoadState.m_webPageProxy)
, m_pageLoadState(&pageLoadState)
{
m_pageLoadState->beginTransaction();
}
PageLoadState::Transaction::Transaction(Transaction&& other)
: m_webPageProxy(WTFMove(other.m_webPageProxy))
, m_pageLoadState(other.m_pageLoadState)
{
other.m_pageLoadState = nullptr;
}
PageLoadState::Transaction::~Transaction()
{
if (m_pageLoadState)
m_pageLoadState->endTransaction();
}
void PageLoadState::addObserver(Observer& observer)
{
ASSERT(!m_observers.contains(&observer));
m_observers.append(&observer);
}
void PageLoadState::removeObserver(Observer& observer)
{
bool removed = m_observers.removeFirst(&observer);
ASSERT_UNUSED(removed, removed);
}
void PageLoadState::endTransaction()
{
ASSERT(m_outstandingTransactionCount > 0);
if (!--m_outstandingTransactionCount)
commitChanges();
}
void PageLoadState::commitChanges()
{
if (!m_mayHaveUncommittedChanges)
return;
m_mayHaveUncommittedChanges = false;
bool canGoBackChanged = m_committedState.canGoBack != m_uncommittedState.canGoBack;
bool canGoForwardChanged = m_committedState.canGoForward != m_uncommittedState.canGoForward;
bool titleChanged = m_committedState.title != m_uncommittedState.title;
bool isLoadingChanged = isLoading(m_committedState) != isLoading(m_uncommittedState);
bool activeURLChanged = activeURL(m_committedState) != activeURL(m_uncommittedState);
bool hasOnlySecureContentChanged = hasOnlySecureContent(m_committedState) != hasOnlySecureContent(m_uncommittedState);
bool estimatedProgressChanged = estimatedProgress(m_committedState) != estimatedProgress(m_uncommittedState);
bool networkRequestsInProgressChanged = m_committedState.networkRequestsInProgress != m_uncommittedState.networkRequestsInProgress;
bool certificateInfoChanged = m_committedState.certificateInfo != m_uncommittedState.certificateInfo;
if (canGoBackChanged)
callObserverCallback(&Observer::willChangeCanGoBack);
if (canGoForwardChanged)
callObserverCallback(&Observer::willChangeCanGoForward);
if (titleChanged)
callObserverCallback(&Observer::willChangeTitle);
if (isLoadingChanged)
callObserverCallback(&Observer::willChangeIsLoading);
if (activeURLChanged)
callObserverCallback(&Observer::willChangeActiveURL);
if (hasOnlySecureContentChanged)
callObserverCallback(&Observer::willChangeHasOnlySecureContent);
if (estimatedProgressChanged)
callObserverCallback(&Observer::willChangeEstimatedProgress);
if (networkRequestsInProgressChanged)
callObserverCallback(&Observer::willChangeNetworkRequestsInProgress);
if (certificateInfoChanged)
callObserverCallback(&Observer::willChangeCertificateInfo);
m_committedState = m_uncommittedState;
m_webPageProxy.isLoadingChanged();
// The "did" ordering is the reverse of the "will". This is a requirement of Cocoa Key-Value Observing.
if (certificateInfoChanged)
callObserverCallback(&Observer::didChangeCertificateInfo);
if (networkRequestsInProgressChanged)
callObserverCallback(&Observer::didChangeNetworkRequestsInProgress);
if (estimatedProgressChanged)
callObserverCallback(&Observer::didChangeEstimatedProgress);
if (hasOnlySecureContentChanged)
callObserverCallback(&Observer::didChangeHasOnlySecureContent);
if (activeURLChanged)
callObserverCallback(&Observer::didChangeActiveURL);
if (isLoadingChanged)
callObserverCallback(&Observer::didChangeIsLoading);
if (titleChanged)
callObserverCallback(&Observer::didChangeTitle);
if (canGoForwardChanged)
callObserverCallback(&Observer::didChangeCanGoForward);
if (canGoBackChanged)
callObserverCallback(&Observer::didChangeCanGoBack);
}
void PageLoadState::reset(const Transaction::Token& token)
{
ASSERT_UNUSED(token, &token.m_pageLoadState == this);
m_uncommittedState.state = State::Finished;
m_uncommittedState.hasInsecureContent = false;
m_uncommittedState.pendingAPIRequest = { };
m_uncommittedState.provisionalURL = String();
m_uncommittedState.url = String();
m_uncommittedState.unreachableURL = String();
m_lastUnreachableURL = String();
m_uncommittedState.title = String();
m_uncommittedState.estimatedProgress = 0;
m_uncommittedState.networkRequestsInProgress = false;
}
bool PageLoadState::isLoading() const
{
return isLoading(m_committedState);
}
bool PageLoadState::hasUncommittedLoad() const
{
return isLoading(m_uncommittedState);
}
String PageLoadState::activeURL(const Data& data)
{
// If there is a currently pending URL, it is the active URL,
// even when there's no main frame yet, as it might be the
// first API request.
if (!data.pendingAPIRequest.url.isNull())
return data.pendingAPIRequest.url;
if (!data.unreachableURL.isEmpty())
return data.unreachableURL;
switch (data.state) {
case State::Provisional:
return data.provisionalURL;
case State::Committed:
case State::Finished:
return data.url;
}
ASSERT_NOT_REACHED();
return String();
}
String PageLoadState::activeURL() const
{
return activeURL(m_committedState);
}
bool PageLoadState::hasOnlySecureContent(const Data& data)
{
if (data.hasInsecureContent)
return false;
if (data.state == State::Provisional)
return WTF::protocolIs(data.provisionalURL, "https");
return WTF::protocolIs(data.url, "https");
}
bool PageLoadState::hasOnlySecureContent() const
{
return hasOnlySecureContent(m_committedState);
}
double PageLoadState::estimatedProgress(const Data& data)
{
if (!data.pendingAPIRequest.url.isNull())
return initialProgressValue;
return data.estimatedProgress;
}
double PageLoadState::estimatedProgress() const
{
return estimatedProgress(m_committedState);
}
const String& PageLoadState::pendingAPIRequestURL() const
{
return m_committedState.pendingAPIRequest.url;
}
auto PageLoadState::pendingAPIRequest() const -> const PendingAPIRequest&
{
return m_committedState.pendingAPIRequest;
}
const URL& PageLoadState::resourceDirectoryURL() const
{
return m_committedState.resourceDirectoryURL;
}
void PageLoadState::setPendingAPIRequest(const Transaction::Token& token, PendingAPIRequest&& pendingAPIRequest, const URL& resourceDirectoryURL)
{
ASSERT_UNUSED(token, &token.m_pageLoadState == this);
m_uncommittedState.pendingAPIRequest = WTFMove(pendingAPIRequest);
m_uncommittedState.resourceDirectoryURL = resourceDirectoryURL;
}
void PageLoadState::clearPendingAPIRequest(const Transaction::Token& token)
{
ASSERT_UNUSED(token, &token.m_pageLoadState == this);
m_uncommittedState.pendingAPIRequest = { };
}
void PageLoadState::didExplicitOpen(const Transaction::Token& token, const String& url)
{
ASSERT_UNUSED(token, &token.m_pageLoadState == this);
m_uncommittedState.url = url;
m_uncommittedState.provisionalURL = String();
}
void PageLoadState::didStartProvisionalLoad(const Transaction::Token& token, const String& url, const String& unreachableURL)
{
ASSERT_UNUSED(token, &token.m_pageLoadState == this);
ASSERT(m_uncommittedState.provisionalURL.isEmpty());
m_uncommittedState.state = State::Provisional;
m_uncommittedState.provisionalURL = url;
setUnreachableURL(token, unreachableURL);
}
void PageLoadState::didReceiveServerRedirectForProvisionalLoad(const Transaction::Token& token, const String& url)
{
ASSERT_UNUSED(token, &token.m_pageLoadState == this);
ASSERT(m_uncommittedState.state == State::Provisional);
m_uncommittedState.provisionalURL = url;
}
void PageLoadState::didFailProvisionalLoad(const Transaction::Token& token)
{
ASSERT_UNUSED(token, &token.m_pageLoadState == this);
ASSERT(m_uncommittedState.state == State::Provisional);
m_uncommittedState.state = State::Finished;
m_uncommittedState.provisionalURL = String();
m_uncommittedState.unreachableURL = m_lastUnreachableURL;
}
void PageLoadState::didCommitLoad(const Transaction::Token& token, WebCertificateInfo& certificateInfo, bool hasInsecureContent)
{
ASSERT_UNUSED(token, &token.m_pageLoadState == this);
ASSERT(m_uncommittedState.state == State::Provisional);
m_uncommittedState.state = State::Committed;
m_uncommittedState.hasInsecureContent = hasInsecureContent;
m_uncommittedState.certificateInfo = &certificateInfo;
m_uncommittedState.url = m_uncommittedState.provisionalURL;
m_uncommittedState.provisionalURL = String();
m_uncommittedState.title = String();
}
void PageLoadState::didFinishLoad(const Transaction::Token& token)
{
ASSERT_UNUSED(token, &token.m_pageLoadState == this);
ASSERT(m_uncommittedState.state == State::Committed);
ASSERT(m_uncommittedState.provisionalURL.isEmpty());
m_uncommittedState.state = State::Finished;
}
void PageLoadState::didFailLoad(const Transaction::Token& token)
{
ASSERT_UNUSED(token, &token.m_pageLoadState == this);
ASSERT(m_uncommittedState.provisionalURL.isEmpty());
m_uncommittedState.state = State::Finished;
}
void PageLoadState::didSameDocumentNavigation(const Transaction::Token& token, const String& url)
{
ASSERT_UNUSED(token, &token.m_pageLoadState == this);
ASSERT(!m_uncommittedState.url.isEmpty());
m_uncommittedState.url = url;
}
void PageLoadState::didDisplayOrRunInsecureContent(const Transaction::Token& token)
{
ASSERT_UNUSED(token, &token.m_pageLoadState == this);
m_uncommittedState.hasInsecureContent = true;
}
void PageLoadState::setUnreachableURL(const Transaction::Token& token, const String& unreachableURL)
{
ASSERT_UNUSED(token, &token.m_pageLoadState == this);
m_lastUnreachableURL = m_uncommittedState.unreachableURL;
m_uncommittedState.unreachableURL = unreachableURL;
}
const String& PageLoadState::title() const
{
return m_committedState.title;
}
void PageLoadState::setTitle(const Transaction::Token& token, const String& title)
{
ASSERT_UNUSED(token, &token.m_pageLoadState == this);
m_uncommittedState.title = title;
}
bool PageLoadState::canGoBack() const
{
return m_committedState.canGoBack;
}
void PageLoadState::setCanGoBack(const Transaction::Token& token, bool canGoBack)
{
ASSERT_UNUSED(token, &token.m_pageLoadState == this);
m_uncommittedState.canGoBack = canGoBack;
}
bool PageLoadState::canGoForward() const
{
return m_committedState.canGoForward;
}
void PageLoadState::setCanGoForward(const Transaction::Token& token, bool canGoForward)
{
ASSERT_UNUSED(token, &token.m_pageLoadState == this);
m_uncommittedState.canGoForward = canGoForward;
}
void PageLoadState::didStartProgress(const Transaction::Token& token)
{
ASSERT_UNUSED(token, &token.m_pageLoadState == this);
m_uncommittedState.estimatedProgress = initialProgressValue;
}
void PageLoadState::didChangeProgress(const Transaction::Token& token, double value)
{
ASSERT_UNUSED(token, &token.m_pageLoadState == this);
m_uncommittedState.estimatedProgress = value;
}
void PageLoadState::didFinishProgress(const Transaction::Token& token)
{
ASSERT_UNUSED(token, &token.m_pageLoadState == this);
m_uncommittedState.estimatedProgress = 1;
}
void PageLoadState::setNetworkRequestsInProgress(const Transaction::Token& token, bool networkRequestsInProgress)
{
ASSERT_UNUSED(token, &token.m_pageLoadState == this);
m_uncommittedState.networkRequestsInProgress = networkRequestsInProgress;
}
bool PageLoadState::isLoading(const Data& data)
{
if (!data.pendingAPIRequest.url.isNull())
return true;
switch (data.state) {
case State::Provisional:
case State::Committed:
return true;
case State::Finished:
return false;
}
ASSERT_NOT_REACHED();
return false;
}
void PageLoadState::didSwapWebProcesses()
{
callObserverCallback(&Observer::didSwapWebProcesses);
}
void PageLoadState::willChangeProcessIsResponsive()
{
callObserverCallback(&Observer::willChangeWebProcessIsResponsive);
}
void PageLoadState::didChangeProcessIsResponsive()
{
callObserverCallback(&Observer::didChangeWebProcessIsResponsive);
}
void PageLoadState::callObserverCallback(void (Observer::*callback)())
{
auto protectedPage = makeRef(m_webPageProxy);
auto observerCopy = m_observers;
for (auto* observer : observerCopy) {
// This appears potentially inefficient on the surface (searching in a Vector)
// but in practice - using only API - there will only ever be (1) observer.
if (!m_observers.contains(observer))
continue;
(observer->*callback)();
}
}
} // namespace WebKit