1/*
2 * Copyright (C) 2005, 2006, 2008, 2011, 2014 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. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "HistoryItem.h"
28
29#include "CachedPage.h"
30#include "Document.h"
31#include "KeyedCoding.h"
32#include "PageCache.h"
33#include "ResourceRequest.h"
34#include "SerializedScriptValue.h"
35#include "SharedBuffer.h"
36#include <stdio.h>
37#include <wtf/DateMath.h>
38#include <wtf/DebugUtilities.h>
39#include <wtf/WallTime.h>
40#include <wtf/text/CString.h>
41
42namespace WebCore {
43
44int64_t HistoryItem::generateSequenceNumber()
45{
46 // Initialize to the current time to reduce the likelihood of generating
47 // identifiers that overlap with those from past/future browser sessions.
48 static long long next = static_cast<long long>(WallTime::now().secondsSinceEpoch().microseconds());
49 return ++next;
50}
51
52static void defaultNotifyHistoryItemChanged(HistoryItem&)
53{
54}
55
56void (*notifyHistoryItemChanged)(HistoryItem&) = defaultNotifyHistoryItemChanged;
57
58HistoryItem::HistoryItem()
59 : HistoryItem({ }, { })
60{
61}
62
63HistoryItem::HistoryItem(const String& urlString, const String& title)
64 : HistoryItem(urlString, title, { })
65{
66}
67
68HistoryItem::HistoryItem(const String& urlString, const String& title, const String& alternateTitle)
69 : HistoryItem(urlString, title, alternateTitle, { Process::identifier(), ObjectIdentifier<BackForwardItemIdentifier::ItemIdentifierType>::generate() })
70{
71}
72
73HistoryItem::HistoryItem(const String& urlString, const String& title, const String& alternateTitle, BackForwardItemIdentifier BackForwardItemIdentifier)
74 : m_urlString(urlString)
75 , m_originalURLString(urlString)
76 , m_title(title)
77 , m_displayTitle(alternateTitle)
78 , m_pruningReason(PruningReason::None)
79 , m_identifier(BackForwardItemIdentifier)
80{
81}
82
83HistoryItem::~HistoryItem()
84{
85 ASSERT(!m_cachedPage);
86}
87
88inline HistoryItem::HistoryItem(const HistoryItem& item)
89 : RefCounted<HistoryItem>()
90 , m_urlString(item.m_urlString)
91 , m_originalURLString(item.m_originalURLString)
92 , m_referrer(item.m_referrer)
93 , m_target(item.m_target)
94 , m_title(item.m_title)
95 , m_displayTitle(item.m_displayTitle)
96 , m_scrollPosition(item.m_scrollPosition)
97 , m_pageScaleFactor(item.m_pageScaleFactor)
98 , m_lastVisitWasFailure(item.m_lastVisitWasFailure)
99 , m_isTargetItem(item.m_isTargetItem)
100 , m_itemSequenceNumber(item.m_itemSequenceNumber)
101 , m_documentSequenceNumber(item.m_documentSequenceNumber)
102 , m_formContentType(item.m_formContentType)
103 , m_pruningReason(PruningReason::None)
104#if PLATFORM(IOS_FAMILY)
105 , m_obscuredInsets(item.m_obscuredInsets)
106 , m_scale(item.m_scale)
107 , m_scaleIsInitial(item.m_scaleIsInitial)
108#endif
109 , m_identifier(item.m_identifier)
110{
111 if (item.m_formData)
112 m_formData = item.m_formData->copy();
113
114 unsigned size = item.m_children.size();
115 m_children.reserveInitialCapacity(size);
116 for (unsigned i = 0; i < size; ++i)
117 m_children.uncheckedAppend(item.m_children[i]->copy());
118}
119
120Ref<HistoryItem> HistoryItem::copy() const
121{
122 return adoptRef(*new HistoryItem(*this));
123}
124
125void HistoryItem::reset()
126{
127 m_urlString = String();
128 m_originalURLString = String();
129 m_referrer = String();
130 m_target = String();
131 m_title = String();
132 m_displayTitle = String();
133
134 m_lastVisitWasFailure = false;
135 m_isTargetItem = false;
136
137 m_itemSequenceNumber = generateSequenceNumber();
138
139 m_stateObject = nullptr;
140 m_documentSequenceNumber = generateSequenceNumber();
141
142 m_formData = nullptr;
143 m_formContentType = String();
144
145 clearChildren();
146}
147
148const String& HistoryItem::urlString() const
149{
150 return m_urlString;
151}
152
153// The first URL we loaded to get to where this history item points. Includes both client
154// and server redirects.
155const String& HistoryItem::originalURLString() const
156{
157 return m_originalURLString;
158}
159
160const String& HistoryItem::title() const
161{
162 return m_title;
163}
164
165const String& HistoryItem::alternateTitle() const
166{
167 return m_displayTitle;
168}
169
170bool HistoryItem::hasCachedPageExpired() const
171{
172 return m_cachedPage ? m_cachedPage->hasExpired() : false;
173}
174
175URL HistoryItem::url() const
176{
177 return URL({ }, m_urlString);
178}
179
180URL HistoryItem::originalURL() const
181{
182 return URL({ }, m_originalURLString);
183}
184
185const String& HistoryItem::referrer() const
186{
187 return m_referrer;
188}
189
190const String& HistoryItem::target() const
191{
192 return m_target;
193}
194
195void HistoryItem::setAlternateTitle(const String& alternateTitle)
196{
197 m_displayTitle = alternateTitle;
198 notifyChanged();
199}
200
201void HistoryItem::setURLString(const String& urlString)
202{
203 m_urlString = urlString;
204 notifyChanged();
205}
206
207void HistoryItem::setURL(const URL& url)
208{
209 PageCache::singleton().remove(*this);
210 setURLString(url.string());
211 clearDocumentState();
212}
213
214void HistoryItem::setOriginalURLString(const String& urlString)
215{
216 m_originalURLString = urlString;
217 notifyChanged();
218}
219
220void HistoryItem::setReferrer(const String& referrer)
221{
222 m_referrer = referrer;
223 notifyChanged();
224}
225
226void HistoryItem::setTitle(const String& title)
227{
228 m_title = title;
229 notifyChanged();
230}
231
232void HistoryItem::setTarget(const String& target)
233{
234 m_target = target;
235 notifyChanged();
236}
237
238const IntPoint& HistoryItem::scrollPosition() const
239{
240 return m_scrollPosition;
241}
242
243void HistoryItem::setScrollPosition(const IntPoint& position)
244{
245 m_scrollPosition = position;
246}
247
248void HistoryItem::clearScrollPosition()
249{
250 m_scrollPosition = IntPoint();
251}
252
253bool HistoryItem::shouldRestoreScrollPosition() const
254{
255 return m_shouldRestoreScrollPosition;
256}
257
258void HistoryItem::setShouldRestoreScrollPosition(bool shouldRestore)
259{
260 m_shouldRestoreScrollPosition = shouldRestore;
261 notifyChanged();
262}
263
264float HistoryItem::pageScaleFactor() const
265{
266 return m_pageScaleFactor;
267}
268
269void HistoryItem::setPageScaleFactor(float scaleFactor)
270{
271 m_pageScaleFactor = scaleFactor;
272}
273
274void HistoryItem::setDocumentState(const Vector<String>& state)
275{
276 m_documentState = state;
277}
278
279const Vector<String>& HistoryItem::documentState() const
280{
281 return m_documentState;
282}
283
284void HistoryItem::clearDocumentState()
285{
286 m_documentState.clear();
287}
288
289void HistoryItem::setShouldOpenExternalURLsPolicy(ShouldOpenExternalURLsPolicy policy)
290{
291 m_shouldOpenExternalURLsPolicy = policy;
292}
293
294ShouldOpenExternalURLsPolicy HistoryItem::shouldOpenExternalURLsPolicy() const
295{
296 return m_shouldOpenExternalURLsPolicy;
297}
298
299bool HistoryItem::isTargetItem() const
300{
301 return m_isTargetItem;
302}
303
304void HistoryItem::setIsTargetItem(bool flag)
305{
306 m_isTargetItem = flag;
307}
308
309void HistoryItem::setStateObject(RefPtr<SerializedScriptValue>&& object)
310{
311 m_stateObject = WTFMove(object);
312 notifyChanged();
313}
314
315void HistoryItem::addChildItem(Ref<HistoryItem>&& child)
316{
317 ASSERT(!childItemWithTarget(child->target()));
318 m_children.append(WTFMove(child));
319}
320
321void HistoryItem::setChildItem(Ref<HistoryItem>&& child)
322{
323 ASSERT(!child->isTargetItem());
324 unsigned size = m_children.size();
325 for (unsigned i = 0; i < size; ++i) {
326 if (m_children[i]->target() == child->target()) {
327 child->setIsTargetItem(m_children[i]->isTargetItem());
328 m_children[i] = WTFMove(child);
329 return;
330 }
331 }
332 m_children.append(WTFMove(child));
333}
334
335HistoryItem* HistoryItem::childItemWithTarget(const String& target)
336{
337 unsigned size = m_children.size();
338 for (unsigned i = 0; i < size; ++i) {
339 if (m_children[i]->target() == target)
340 return m_children[i].ptr();
341 }
342 return nullptr;
343}
344
345HistoryItem* HistoryItem::childItemWithDocumentSequenceNumber(long long number)
346{
347 unsigned size = m_children.size();
348 for (unsigned i = 0; i < size; ++i) {
349 if (m_children[i]->documentSequenceNumber() == number)
350 return m_children[i].ptr();
351 }
352 return nullptr;
353}
354
355const Vector<Ref<HistoryItem>>& HistoryItem::children() const
356{
357 return m_children;
358}
359
360bool HistoryItem::hasChildren() const
361{
362 return !m_children.isEmpty();
363}
364
365void HistoryItem::clearChildren()
366{
367 m_children.clear();
368}
369
370// We do same-document navigation if going to a different item and if either of the following is true:
371// - The other item corresponds to the same document (for history entries created via pushState or fragment changes).
372// - The other item corresponds to the same set of documents, including frames (for history entries created via regular navigation)
373bool HistoryItem::shouldDoSameDocumentNavigationTo(HistoryItem& otherItem) const
374{
375 // The following logic must be kept in sync with WebKit::WebBackForwardListItem::itemIsInSameDocument().
376 if (this == &otherItem)
377 return false;
378
379 if (stateObject() || otherItem.stateObject())
380 return documentSequenceNumber() == otherItem.documentSequenceNumber();
381
382 if ((url().hasFragmentIdentifier() || otherItem.url().hasFragmentIdentifier()) && equalIgnoringFragmentIdentifier(url(), otherItem.url()))
383 return documentSequenceNumber() == otherItem.documentSequenceNumber();
384
385 return hasSameDocumentTree(otherItem);
386}
387
388// Does a recursive check that this item and its descendants have the same
389// document sequence numbers as the other item.
390bool HistoryItem::hasSameDocumentTree(HistoryItem& otherItem) const
391{
392 if (documentSequenceNumber() != otherItem.documentSequenceNumber())
393 return false;
394
395 if (children().size() != otherItem.children().size())
396 return false;
397
398 for (size_t i = 0; i < children().size(); i++) {
399 auto& child = children()[i].get();
400 auto* otherChild = otherItem.childItemWithDocumentSequenceNumber(child.documentSequenceNumber());
401 if (!otherChild || !child.hasSameDocumentTree(*otherChild))
402 return false;
403 }
404
405 return true;
406}
407
408// Does a non-recursive check that this item and its immediate children have the
409// same frames as the other item.
410bool HistoryItem::hasSameFrames(HistoryItem& otherItem) const
411{
412 if (target() != otherItem.target())
413 return false;
414
415 if (children().size() != otherItem.children().size())
416 return false;
417
418 for (size_t i = 0; i < children().size(); i++) {
419 if (!otherItem.childItemWithTarget(children()[i]->target()))
420 return false;
421 }
422
423 return true;
424}
425
426String HistoryItem::formContentType() const
427{
428 return m_formContentType;
429}
430
431void HistoryItem::setFormInfoFromRequest(const ResourceRequest& request)
432{
433 m_referrer = request.httpReferrer();
434
435 if (equalLettersIgnoringASCIICase(request.httpMethod(), "post")) {
436 // FIXME: Eventually we have to make this smart enough to handle the case where
437 // we have a stream for the body to handle the "data interspersed with files" feature.
438 m_formData = request.httpBody();
439 m_formContentType = request.httpContentType();
440 } else {
441 m_formData = nullptr;
442 m_formContentType = String();
443 }
444}
445
446void HistoryItem::setFormData(RefPtr<FormData>&& formData)
447{
448 m_formData = WTFMove(formData);
449}
450
451void HistoryItem::setFormContentType(const String& formContentType)
452{
453 m_formContentType = formContentType;
454}
455
456FormData* HistoryItem::formData()
457{
458 return m_formData.get();
459}
460
461bool HistoryItem::isCurrentDocument(Document& document) const
462{
463 // FIXME: We should find a better way to check if this is the current document.
464 return equalIgnoringFragmentIdentifier(url(), document.url());
465}
466
467void HistoryItem::notifyChanged()
468{
469 notifyHistoryItemChanged(*this);
470}
471
472#ifndef NDEBUG
473
474int HistoryItem::showTree() const
475{
476 return showTreeWithIndent(0);
477}
478
479int HistoryItem::showTreeWithIndent(unsigned indentLevel) const
480{
481 Vector<char> prefix;
482 for (unsigned i = 0; i < indentLevel; ++i)
483 prefix.append(" ", 2);
484 prefix.append("\0", 1);
485
486 fprintf(stderr, "%s+-%s (%p)\n", prefix.data(), m_urlString.utf8().data(), this);
487
488 int totalSubItems = 0;
489 for (unsigned i = 0; i < m_children.size(); ++i)
490 totalSubItems += m_children[i]->showTreeWithIndent(indentLevel + 1);
491 return totalSubItems + 1;
492}
493
494#endif
495
496#if !LOG_DISABLED
497const char* HistoryItem::logString() const
498{
499 return debugString("HistoryItem current URL ", urlString(), ", identifier ", m_identifier.logString());
500}
501#endif
502
503} // namespace WebCore
504
505#ifndef NDEBUG
506
507int showTree(const WebCore::HistoryItem* item)
508{
509 return item->showTree();
510}
511
512#endif
513