1/*
2 * Copyright (C) 2013-2015 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "PageLoadState.h"
28
29#include "WebPageProxy.h"
30
31namespace WebKit {
32
33// Progress always starts at this value. This helps provide feedback as soon as a load starts.
34static const double initialProgressValue = 0.1;
35
36PageLoadState::PageLoadState(WebPageProxy& webPageProxy)
37 : m_webPageProxy(webPageProxy)
38 , m_mayHaveUncommittedChanges(false)
39 , m_outstandingTransactionCount(0)
40{
41}
42
43PageLoadState::~PageLoadState()
44{
45 ASSERT(m_observers.isEmpty());
46}
47
48PageLoadState::Transaction::Transaction(PageLoadState& pageLoadState)
49 : m_webPageProxy(&pageLoadState.m_webPageProxy)
50 , m_pageLoadState(&pageLoadState)
51{
52 m_pageLoadState->beginTransaction();
53}
54
55PageLoadState::Transaction::Transaction(Transaction&& other)
56 : m_webPageProxy(WTFMove(other.m_webPageProxy))
57 , m_pageLoadState(other.m_pageLoadState)
58{
59 other.m_pageLoadState = nullptr;
60}
61
62PageLoadState::Transaction::~Transaction()
63{
64 if (m_pageLoadState)
65 m_pageLoadState->endTransaction();
66}
67
68void PageLoadState::addObserver(Observer& observer)
69{
70 ASSERT(!m_observers.contains(&observer));
71
72 m_observers.append(&observer);
73}
74
75void PageLoadState::removeObserver(Observer& observer)
76{
77 bool removed = m_observers.removeFirst(&observer);
78 ASSERT_UNUSED(removed, removed);
79}
80
81void PageLoadState::endTransaction()
82{
83 ASSERT(m_outstandingTransactionCount > 0);
84
85 if (!--m_outstandingTransactionCount)
86 commitChanges();
87}
88
89void PageLoadState::commitChanges()
90{
91 if (!m_mayHaveUncommittedChanges)
92 return;
93
94 m_mayHaveUncommittedChanges = false;
95
96 bool canGoBackChanged = m_committedState.canGoBack != m_uncommittedState.canGoBack;
97 bool canGoForwardChanged = m_committedState.canGoForward != m_uncommittedState.canGoForward;
98 bool titleChanged = m_committedState.title != m_uncommittedState.title;
99 bool isLoadingChanged = isLoading(m_committedState) != isLoading(m_uncommittedState);
100 bool activeURLChanged = activeURL(m_committedState) != activeURL(m_uncommittedState);
101 bool hasOnlySecureContentChanged = hasOnlySecureContent(m_committedState) != hasOnlySecureContent(m_uncommittedState);
102 bool estimatedProgressChanged = estimatedProgress(m_committedState) != estimatedProgress(m_uncommittedState);
103 bool networkRequestsInProgressChanged = m_committedState.networkRequestsInProgress != m_uncommittedState.networkRequestsInProgress;
104 bool certificateInfoChanged = m_committedState.certificateInfo != m_uncommittedState.certificateInfo;
105
106 if (canGoBackChanged)
107 callObserverCallback(&Observer::willChangeCanGoBack);
108 if (canGoForwardChanged)
109 callObserverCallback(&Observer::willChangeCanGoForward);
110 if (titleChanged)
111 callObserverCallback(&Observer::willChangeTitle);
112 if (isLoadingChanged)
113 callObserverCallback(&Observer::willChangeIsLoading);
114 if (activeURLChanged)
115 callObserverCallback(&Observer::willChangeActiveURL);
116 if (hasOnlySecureContentChanged)
117 callObserverCallback(&Observer::willChangeHasOnlySecureContent);
118 if (estimatedProgressChanged)
119 callObserverCallback(&Observer::willChangeEstimatedProgress);
120 if (networkRequestsInProgressChanged)
121 callObserverCallback(&Observer::willChangeNetworkRequestsInProgress);
122 if (certificateInfoChanged)
123 callObserverCallback(&Observer::willChangeCertificateInfo);
124
125 m_committedState = m_uncommittedState;
126
127 m_webPageProxy.isLoadingChanged();
128
129 // The "did" ordering is the reverse of the "will". This is a requirement of Cocoa Key-Value Observing.
130 if (certificateInfoChanged)
131 callObserverCallback(&Observer::didChangeCertificateInfo);
132 if (networkRequestsInProgressChanged)
133 callObserverCallback(&Observer::didChangeNetworkRequestsInProgress);
134 if (estimatedProgressChanged)
135 callObserverCallback(&Observer::didChangeEstimatedProgress);
136 if (hasOnlySecureContentChanged)
137 callObserverCallback(&Observer::didChangeHasOnlySecureContent);
138 if (activeURLChanged)
139 callObserverCallback(&Observer::didChangeActiveURL);
140 if (isLoadingChanged)
141 callObserverCallback(&Observer::didChangeIsLoading);
142 if (titleChanged)
143 callObserverCallback(&Observer::didChangeTitle);
144 if (canGoForwardChanged)
145 callObserverCallback(&Observer::didChangeCanGoForward);
146 if (canGoBackChanged)
147 callObserverCallback(&Observer::didChangeCanGoBack);
148}
149
150void PageLoadState::reset(const Transaction::Token& token)
151{
152 ASSERT_UNUSED(token, &token.m_pageLoadState == this);
153
154 m_uncommittedState.state = State::Finished;
155 m_uncommittedState.hasInsecureContent = false;
156
157 m_uncommittedState.pendingAPIRequestURL = String();
158 m_uncommittedState.provisionalURL = String();
159 m_uncommittedState.url = String();
160
161 m_uncommittedState.unreachableURL = String();
162 m_lastUnreachableURL = String();
163
164 m_uncommittedState.title = String();
165
166 m_uncommittedState.estimatedProgress = 0;
167 m_uncommittedState.networkRequestsInProgress = false;
168}
169
170bool PageLoadState::isLoading() const
171{
172 return isLoading(m_committedState);
173}
174
175String PageLoadState::activeURL(const Data& data)
176{
177 // If there is a currently pending URL, it is the active URL,
178 // even when there's no main frame yet, as it might be the
179 // first API request.
180 if (!data.pendingAPIRequestURL.isNull())
181 return data.pendingAPIRequestURL;
182
183 if (!data.unreachableURL.isEmpty())
184 return data.unreachableURL;
185
186 switch (data.state) {
187 case State::Provisional:
188 return data.provisionalURL;
189 case State::Committed:
190 case State::Finished:
191 return data.url;
192 }
193
194 ASSERT_NOT_REACHED();
195 return String();
196}
197
198String PageLoadState::activeURL() const
199{
200 return activeURL(m_committedState);
201}
202
203bool PageLoadState::hasOnlySecureContent(const Data& data)
204{
205 if (data.hasInsecureContent)
206 return false;
207
208 if (data.state == State::Provisional)
209 return WTF::protocolIs(data.provisionalURL, "https");
210
211 return WTF::protocolIs(data.url, "https");
212}
213
214bool PageLoadState::hasOnlySecureContent() const
215{
216 return hasOnlySecureContent(m_committedState);
217}
218
219double PageLoadState::estimatedProgress(const Data& data)
220{
221 if (!data.pendingAPIRequestURL.isNull())
222 return initialProgressValue;
223
224 return data.estimatedProgress;
225}
226
227double PageLoadState::estimatedProgress() const
228{
229 return estimatedProgress(m_committedState);
230}
231
232const String& PageLoadState::pendingAPIRequestURL() const
233{
234 return m_committedState.pendingAPIRequestURL;
235}
236
237void PageLoadState::setPendingAPIRequestURL(const Transaction::Token& token, const String& pendingAPIRequestURL)
238{
239 ASSERT_UNUSED(token, &token.m_pageLoadState == this);
240 m_uncommittedState.pendingAPIRequestURL = pendingAPIRequestURL;
241}
242
243void PageLoadState::clearPendingAPIRequestURL(const Transaction::Token& token)
244{
245 ASSERT_UNUSED(token, &token.m_pageLoadState == this);
246 m_uncommittedState.pendingAPIRequestURL = String();
247}
248
249void PageLoadState::didExplicitOpen(const Transaction::Token& token, const String& url)
250{
251 ASSERT_UNUSED(token, &token.m_pageLoadState == this);
252
253 m_uncommittedState.url = url;
254 m_uncommittedState.provisionalURL = String();
255}
256
257void PageLoadState::didStartProvisionalLoad(const Transaction::Token& token, const String& url, const String& unreachableURL)
258{
259 ASSERT_UNUSED(token, &token.m_pageLoadState == this);
260 ASSERT(m_uncommittedState.provisionalURL.isEmpty());
261
262 m_uncommittedState.state = State::Provisional;
263
264 m_uncommittedState.provisionalURL = url;
265
266 setUnreachableURL(token, unreachableURL);
267}
268
269void PageLoadState::didReceiveServerRedirectForProvisionalLoad(const Transaction::Token& token, const String& url)
270{
271 ASSERT_UNUSED(token, &token.m_pageLoadState == this);
272 ASSERT(m_uncommittedState.state == State::Provisional);
273
274 m_uncommittedState.provisionalURL = url;
275}
276
277void PageLoadState::didFailProvisionalLoad(const Transaction::Token& token)
278{
279 ASSERT_UNUSED(token, &token.m_pageLoadState == this);
280 ASSERT(m_uncommittedState.state == State::Provisional);
281
282 m_uncommittedState.state = State::Finished;
283
284 m_uncommittedState.provisionalURL = String();
285 m_uncommittedState.unreachableURL = m_lastUnreachableURL;
286}
287
288void PageLoadState::didCommitLoad(const Transaction::Token& token, WebCertificateInfo& certificateInfo, bool hasInsecureContent)
289{
290 ASSERT_UNUSED(token, &token.m_pageLoadState == this);
291 ASSERT(m_uncommittedState.state == State::Provisional);
292
293 m_uncommittedState.state = State::Committed;
294 m_uncommittedState.hasInsecureContent = hasInsecureContent;
295 m_uncommittedState.certificateInfo = &certificateInfo;
296
297 m_uncommittedState.url = m_uncommittedState.provisionalURL;
298 m_uncommittedState.provisionalURL = String();
299
300 m_uncommittedState.title = String();
301}
302
303void PageLoadState::didFinishLoad(const Transaction::Token& token)
304{
305 ASSERT_UNUSED(token, &token.m_pageLoadState == this);
306 ASSERT(m_uncommittedState.state == State::Committed);
307 ASSERT(m_uncommittedState.provisionalURL.isEmpty());
308
309 m_uncommittedState.state = State::Finished;
310}
311
312void PageLoadState::didFailLoad(const Transaction::Token& token)
313{
314 ASSERT_UNUSED(token, &token.m_pageLoadState == this);
315 ASSERT(m_uncommittedState.provisionalURL.isEmpty());
316
317 m_uncommittedState.state = State::Finished;
318}
319
320void PageLoadState::didSameDocumentNavigation(const Transaction::Token& token, const String& url)
321{
322 ASSERT_UNUSED(token, &token.m_pageLoadState == this);
323 ASSERT(!m_uncommittedState.url.isEmpty());
324
325 m_uncommittedState.url = url;
326}
327
328void PageLoadState::didDisplayOrRunInsecureContent(const Transaction::Token& token)
329{
330 ASSERT_UNUSED(token, &token.m_pageLoadState == this);
331
332 m_uncommittedState.hasInsecureContent = true;
333}
334
335void PageLoadState::setUnreachableURL(const Transaction::Token& token, const String& unreachableURL)
336{
337 ASSERT_UNUSED(token, &token.m_pageLoadState == this);
338
339 m_lastUnreachableURL = m_uncommittedState.unreachableURL;
340 m_uncommittedState.unreachableURL = unreachableURL;
341}
342
343const String& PageLoadState::title() const
344{
345 return m_committedState.title;
346}
347
348void PageLoadState::setTitle(const Transaction::Token& token, const String& title)
349{
350 ASSERT_UNUSED(token, &token.m_pageLoadState == this);
351 m_uncommittedState.title = title;
352}
353
354bool PageLoadState::canGoBack() const
355{
356 return m_committedState.canGoBack;
357}
358
359void PageLoadState::setCanGoBack(const Transaction::Token& token, bool canGoBack)
360{
361 ASSERT_UNUSED(token, &token.m_pageLoadState == this);
362 m_uncommittedState.canGoBack = canGoBack;
363}
364
365bool PageLoadState::canGoForward() const
366{
367 return m_committedState.canGoForward;
368}
369
370void PageLoadState::setCanGoForward(const Transaction::Token& token, bool canGoForward)
371{
372 ASSERT_UNUSED(token, &token.m_pageLoadState == this);
373 m_uncommittedState.canGoForward = canGoForward;
374}
375
376void PageLoadState::didStartProgress(const Transaction::Token& token)
377{
378 ASSERT_UNUSED(token, &token.m_pageLoadState == this);
379 m_uncommittedState.estimatedProgress = initialProgressValue;
380}
381
382void PageLoadState::didChangeProgress(const Transaction::Token& token, double value)
383{
384 ASSERT_UNUSED(token, &token.m_pageLoadState == this);
385 m_uncommittedState.estimatedProgress = value;
386}
387
388void PageLoadState::didFinishProgress(const Transaction::Token& token)
389{
390 ASSERT_UNUSED(token, &token.m_pageLoadState == this);
391 m_uncommittedState.estimatedProgress = 1;
392}
393
394void PageLoadState::setNetworkRequestsInProgress(const Transaction::Token& token, bool networkRequestsInProgress)
395{
396 ASSERT_UNUSED(token, &token.m_pageLoadState == this);
397 m_uncommittedState.networkRequestsInProgress = networkRequestsInProgress;
398}
399
400bool PageLoadState::isLoading(const Data& data)
401{
402 if (!data.pendingAPIRequestURL.isNull())
403 return true;
404
405 switch (data.state) {
406 case State::Provisional:
407 case State::Committed:
408 return true;
409
410 case State::Finished:
411 return false;
412 }
413
414 ASSERT_NOT_REACHED();
415 return false;
416}
417
418void PageLoadState::didSwapWebProcesses()
419{
420 callObserverCallback(&Observer::didSwapWebProcesses);
421}
422
423void PageLoadState::willChangeProcessIsResponsive()
424{
425 callObserverCallback(&Observer::willChangeWebProcessIsResponsive);
426}
427
428void PageLoadState::didChangeProcessIsResponsive()
429{
430 callObserverCallback(&Observer::didChangeWebProcessIsResponsive);
431}
432
433void PageLoadState::callObserverCallback(void (Observer::*callback)())
434{
435 auto protectedPage = makeRef(m_webPageProxy);
436
437 auto observerCopy = m_observers;
438 for (auto* observer : observerCopy) {
439 // This appears potentially inefficient on the surface (searching in a Vector)
440 // but in practice - using only API - there will only ever be (1) observer.
441 if (!m_observers.contains(observer))
442 continue;
443
444 (observer->*callback)();
445 }
446}
447
448} // namespace WebKit
449