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