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
175bool PageLoadState::hasUncommittedLoad() const
176{
177 return isLoading(m_uncommittedState);
178}
179
180String PageLoadState::activeURL(const Data& data)
181{
182 // If there is a currently pending URL, it is the active URL,
183 // even when there's no main frame yet, as it might be the
184 // first API request.
185 if (!data.pendingAPIRequestURL.isNull())
186 return data.pendingAPIRequestURL;
187
188 if (!data.unreachableURL.isEmpty())
189 return data.unreachableURL;
190
191 switch (data.state) {
192 case State::Provisional:
193 return data.provisionalURL;
194 case State::Committed:
195 case State::Finished:
196 return data.url;
197 }
198
199 ASSERT_NOT_REACHED();
200 return String();
201}
202
203String PageLoadState::activeURL() const
204{
205 return activeURL(m_committedState);
206}
207
208bool PageLoadState::hasOnlySecureContent(const Data& data)
209{
210 if (data.hasInsecureContent)
211 return false;
212
213 if (data.state == State::Provisional)
214 return WTF::protocolIs(data.provisionalURL, "https");
215
216 return WTF::protocolIs(data.url, "https");
217}
218
219bool PageLoadState::hasOnlySecureContent() const
220{
221 return hasOnlySecureContent(m_committedState);
222}
223
224double PageLoadState::estimatedProgress(const Data& data)
225{
226 if (!data.pendingAPIRequestURL.isNull())
227 return initialProgressValue;
228
229 return data.estimatedProgress;
230}
231
232double PageLoadState::estimatedProgress() const
233{
234 return estimatedProgress(m_committedState);
235}
236
237const String& PageLoadState::pendingAPIRequestURL() const
238{
239 return m_committedState.pendingAPIRequestURL;
240}
241
242void PageLoadState::setPendingAPIRequestURL(const Transaction::Token& token, const String& pendingAPIRequestURL)
243{
244 ASSERT_UNUSED(token, &token.m_pageLoadState == this);
245 m_uncommittedState.pendingAPIRequestURL = pendingAPIRequestURL;
246}
247
248void PageLoadState::clearPendingAPIRequestURL(const Transaction::Token& token)
249{
250 ASSERT_UNUSED(token, &token.m_pageLoadState == this);
251 m_uncommittedState.pendingAPIRequestURL = String();
252}
253
254void PageLoadState::didExplicitOpen(const Transaction::Token& token, const String& url)
255{
256 ASSERT_UNUSED(token, &token.m_pageLoadState == this);
257
258 m_uncommittedState.url = url;
259 m_uncommittedState.provisionalURL = String();
260}
261
262void PageLoadState::didStartProvisionalLoad(const Transaction::Token& token, const String& url, const String& unreachableURL)
263{
264 ASSERT_UNUSED(token, &token.m_pageLoadState == this);
265 ASSERT(m_uncommittedState.provisionalURL.isEmpty());
266
267 m_uncommittedState.state = State::Provisional;
268
269 m_uncommittedState.provisionalURL = url;
270
271 setUnreachableURL(token, unreachableURL);
272}
273
274void PageLoadState::didReceiveServerRedirectForProvisionalLoad(const Transaction::Token& token, const String& url)
275{
276 ASSERT_UNUSED(token, &token.m_pageLoadState == this);
277 ASSERT(m_uncommittedState.state == State::Provisional);
278
279 m_uncommittedState.provisionalURL = url;
280}
281
282void PageLoadState::didFailProvisionalLoad(const Transaction::Token& token)
283{
284 ASSERT_UNUSED(token, &token.m_pageLoadState == this);
285 ASSERT(m_uncommittedState.state == State::Provisional);
286
287 m_uncommittedState.state = State::Finished;
288
289 m_uncommittedState.provisionalURL = String();
290 m_uncommittedState.unreachableURL = m_lastUnreachableURL;
291}
292
293void PageLoadState::didCommitLoad(const Transaction::Token& token, WebCertificateInfo& certificateInfo, bool hasInsecureContent)
294{
295 ASSERT_UNUSED(token, &token.m_pageLoadState == this);
296 ASSERT(m_uncommittedState.state == State::Provisional);
297
298 m_uncommittedState.state = State::Committed;
299 m_uncommittedState.hasInsecureContent = hasInsecureContent;
300 m_uncommittedState.certificateInfo = &certificateInfo;
301
302 m_uncommittedState.url = m_uncommittedState.provisionalURL;
303 m_uncommittedState.provisionalURL = String();
304
305 m_uncommittedState.title = String();
306}
307
308void PageLoadState::didFinishLoad(const Transaction::Token& token)
309{
310 ASSERT_UNUSED(token, &token.m_pageLoadState == this);
311 ASSERT(m_uncommittedState.state == State::Committed);
312 ASSERT(m_uncommittedState.provisionalURL.isEmpty());
313
314 m_uncommittedState.state = State::Finished;
315}
316
317void PageLoadState::didFailLoad(const Transaction::Token& token)
318{
319 ASSERT_UNUSED(token, &token.m_pageLoadState == this);
320 ASSERT(m_uncommittedState.provisionalURL.isEmpty());
321
322 m_uncommittedState.state = State::Finished;
323}
324
325void PageLoadState::didSameDocumentNavigation(const Transaction::Token& token, const String& url)
326{
327 ASSERT_UNUSED(token, &token.m_pageLoadState == this);
328 ASSERT(!m_uncommittedState.url.isEmpty());
329
330 m_uncommittedState.url = url;
331}
332
333void PageLoadState::didDisplayOrRunInsecureContent(const Transaction::Token& token)
334{
335 ASSERT_UNUSED(token, &token.m_pageLoadState == this);
336
337 m_uncommittedState.hasInsecureContent = true;
338}
339
340void PageLoadState::setUnreachableURL(const Transaction::Token& token, const String& unreachableURL)
341{
342 ASSERT_UNUSED(token, &token.m_pageLoadState == this);
343
344 m_lastUnreachableURL = m_uncommittedState.unreachableURL;
345 m_uncommittedState.unreachableURL = unreachableURL;
346}
347
348const String& PageLoadState::title() const
349{
350 return m_committedState.title;
351}
352
353void PageLoadState::setTitle(const Transaction::Token& token, const String& title)
354{
355 ASSERT_UNUSED(token, &token.m_pageLoadState == this);
356 m_uncommittedState.title = title;
357}
358
359bool PageLoadState::canGoBack() const
360{
361 return m_committedState.canGoBack;
362}
363
364void PageLoadState::setCanGoBack(const Transaction::Token& token, bool canGoBack)
365{
366 ASSERT_UNUSED(token, &token.m_pageLoadState == this);
367 m_uncommittedState.canGoBack = canGoBack;
368}
369
370bool PageLoadState::canGoForward() const
371{
372 return m_committedState.canGoForward;
373}
374
375void PageLoadState::setCanGoForward(const Transaction::Token& token, bool canGoForward)
376{
377 ASSERT_UNUSED(token, &token.m_pageLoadState == this);
378 m_uncommittedState.canGoForward = canGoForward;
379}
380
381void PageLoadState::didStartProgress(const Transaction::Token& token)
382{
383 ASSERT_UNUSED(token, &token.m_pageLoadState == this);
384 m_uncommittedState.estimatedProgress = initialProgressValue;
385}
386
387void PageLoadState::didChangeProgress(const Transaction::Token& token, double value)
388{
389 ASSERT_UNUSED(token, &token.m_pageLoadState == this);
390 m_uncommittedState.estimatedProgress = value;
391}
392
393void PageLoadState::didFinishProgress(const Transaction::Token& token)
394{
395 ASSERT_UNUSED(token, &token.m_pageLoadState == this);
396 m_uncommittedState.estimatedProgress = 1;
397}
398
399void PageLoadState::setNetworkRequestsInProgress(const Transaction::Token& token, bool networkRequestsInProgress)
400{
401 ASSERT_UNUSED(token, &token.m_pageLoadState == this);
402 m_uncommittedState.networkRequestsInProgress = networkRequestsInProgress;
403}
404
405bool PageLoadState::isLoading(const Data& data)
406{
407 if (!data.pendingAPIRequestURL.isNull())
408 return true;
409
410 switch (data.state) {
411 case State::Provisional:
412 case State::Committed:
413 return true;
414
415 case State::Finished:
416 return false;
417 }
418
419 ASSERT_NOT_REACHED();
420 return false;
421}
422
423void PageLoadState::didSwapWebProcesses()
424{
425 callObserverCallback(&Observer::didSwapWebProcesses);
426}
427
428void PageLoadState::willChangeProcessIsResponsive()
429{
430 callObserverCallback(&Observer::willChangeWebProcessIsResponsive);
431}
432
433void PageLoadState::didChangeProcessIsResponsive()
434{
435 callObserverCallback(&Observer::didChangeWebProcessIsResponsive);
436}
437
438void PageLoadState::callObserverCallback(void (Observer::*callback)())
439{
440 auto protectedPage = makeRef(m_webPageProxy);
441
442 auto observerCopy = m_observers;
443 for (auto* observer : observerCopy) {
444 // This appears potentially inefficient on the surface (searching in a Vector)
445 // but in practice - using only API - there will only ever be (1) observer.
446 if (!m_observers.contains(observer))
447 continue;
448
449 (observer->*callback)();
450 }
451}
452
453} // namespace WebKit
454