1/*
2 * Copyright (C) 2011 Igalia S.L.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public License
15 * along with this library; see the file COPYING.LIB. If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20#include "config.h"
21#include "WebKitBackForwardList.h"
22
23#include "APIArray.h"
24#include "WebKitBackForwardListPrivate.h"
25#include <wtf/glib/GRefPtr.h>
26#include <wtf/glib/WTFGType.h>
27
28/**
29 * SECTION: WebKitBackForwardList
30 * @Short_description: List of visited pages
31 * @Title: WebKitBackForwardList
32 * @See_also: #WebKitWebView, #WebKitBackForwardListItem
33 *
34 * WebKitBackForwardList maintains a list of visited pages used to
35 * navigate to recent pages. Items are inserted in the list in the
36 * order they are visited.
37 *
38 * WebKitBackForwardList also maintains the notion of the current item
39 * (which is always at index 0), the preceding item (which is at index -1),
40 * and the following item (which is at index 1).
41 * Methods webkit_web_view_go_back() and webkit_web_view_go_forward() move
42 * the current item backward or forward by one. Method
43 * webkit_web_view_go_to_back_forward_list_item() sets the current item to the
44 * specified item. All other methods returning #WebKitBackForwardListItem<!-- -->s
45 * do not change the value of the current item, they just return the requested
46 * item or items.
47 */
48
49using namespace WebKit;
50
51enum {
52 CHANGED,
53
54 LAST_SIGNAL
55};
56
57typedef HashMap<WebBackForwardListItem*, GRefPtr<WebKitBackForwardListItem> > BackForwardListItemsMap;
58
59struct _WebKitBackForwardListPrivate {
60 WebBackForwardList* backForwardItems;
61 BackForwardListItemsMap itemsMap;
62};
63
64static guint signals[LAST_SIGNAL] = { 0, };
65
66WEBKIT_DEFINE_TYPE(WebKitBackForwardList, webkit_back_forward_list, G_TYPE_OBJECT)
67
68static void webkit_back_forward_list_class_init(WebKitBackForwardListClass* listClass)
69{
70 /**
71 * WebKitBackForwardList::changed:
72 * @back_forward_list: the #WebKitBackForwardList on which the signal was emitted
73 * @item_added: (allow-none): the #WebKitBackForwardListItem added or %NULL
74 * @items_removed: a #GList of #WebKitBackForwardListItem<!-- -->s
75 *
76 * This signal is emitted when @back_forward_list changes. This happens
77 * when the current item is updated, a new item is added or one or more
78 * items are removed. Note that both @item_added and @items_removed can
79 * %NULL when only the current item is updated. Items are only removed
80 * when the list is cleared or the maximum items limit is reached.
81 */
82 signals[CHANGED] = g_signal_new(
83 "changed",
84 G_TYPE_FROM_CLASS(listClass),
85 G_SIGNAL_RUN_LAST,
86 0, nullptr, nullptr,
87 g_cclosure_marshal_generic,
88 G_TYPE_NONE, 2,
89 WEBKIT_TYPE_BACK_FORWARD_LIST_ITEM,
90 G_TYPE_POINTER);
91}
92
93static WebKitBackForwardListItem* webkitBackForwardListGetOrCreateItem(WebKitBackForwardList* list, WebBackForwardListItem* webListItem)
94{
95 if (!webListItem)
96 return 0;
97
98 WebKitBackForwardListPrivate* priv = list->priv;
99 GRefPtr<WebKitBackForwardListItem> listItem = priv->itemsMap.get(webListItem);
100 if (listItem)
101 return listItem.get();
102
103 listItem = webkitBackForwardListItemGetOrCreate(webListItem);
104 priv->itemsMap.set(webListItem, listItem);
105
106 return listItem.get();
107}
108
109static GList* webkitBackForwardListCreateList(WebKitBackForwardList* list, API::Array* backForwardItems)
110{
111 if (!backForwardItems)
112 return 0;
113
114 GList* returnValue = 0;
115 for (size_t i = 0; i < backForwardItems->size(); ++i) {
116 WebBackForwardListItem* webItem = static_cast<WebBackForwardListItem*>(backForwardItems->at(i));
117 returnValue = g_list_prepend(returnValue, webkitBackForwardListGetOrCreateItem(list, webItem));
118 }
119
120 return returnValue;
121}
122
123WebKitBackForwardList* webkitBackForwardListCreate(WebBackForwardList* backForwardItems)
124{
125 WebKitBackForwardList* list = WEBKIT_BACK_FORWARD_LIST(g_object_new(WEBKIT_TYPE_BACK_FORWARD_LIST, NULL));
126 list->priv->backForwardItems = backForwardItems;
127
128 return list;
129}
130
131void webkitBackForwardListChanged(WebKitBackForwardList* backForwardList, WebBackForwardListItem* webAddedItem, const Vector<Ref<WebBackForwardListItem>>& webRemovedItems)
132{
133 WebKitBackForwardListItem* addedItem = webkitBackForwardListGetOrCreateItem(backForwardList, webAddedItem);
134 GList* removedItems = nullptr;
135
136 WebKitBackForwardListPrivate* priv = backForwardList->priv;
137 for (auto& webItem : webRemovedItems) {
138 // After a session restore, we still don't have wrappers for the newly added items, so it would be possible that
139 // the removed items are not in the map. In that case we create a wrapper now to pass it the changed signal, but
140 // without adding it to the item map. See https://bugs.webkit.org/show_bug.cgi?id=153233.
141 GRefPtr<WebKitBackForwardListItem> removedItem = priv->itemsMap.get(webItem.ptr());
142 if (removedItem) {
143 removedItems = g_list_prepend(removedItems, g_object_ref(removedItem.get()));
144 priv->itemsMap.remove(webItem.ptr());
145 } else
146 removedItems = g_list_prepend(removedItems, webkitBackForwardListItemGetOrCreate(webItem.ptr()));
147 }
148
149 g_signal_emit(backForwardList, signals[CHANGED], 0, addedItem, removedItems, nullptr);
150 g_list_free_full(removedItems, static_cast<GDestroyNotify>(g_object_unref));
151}
152
153/**
154 * webkit_back_forward_list_get_current_item:
155 * @back_forward_list: a #WebKitBackForwardList
156 *
157 * Returns the current item in @back_forward_list.
158 *
159 * Returns: (nullable) (transfer none): a #WebKitBackForwardListItem
160 * or %NULL if @back_forward_list is empty.
161 */
162WebKitBackForwardListItem* webkit_back_forward_list_get_current_item(WebKitBackForwardList* backForwardList)
163{
164 g_return_val_if_fail(WEBKIT_IS_BACK_FORWARD_LIST(backForwardList), 0);
165
166 return webkitBackForwardListGetOrCreateItem(backForwardList, backForwardList->priv->backForwardItems->currentItem());
167}
168
169/**
170 * webkit_back_forward_list_get_back_item:
171 * @back_forward_list: a #WebKitBackForwardList
172 *
173 * Returns the item that precedes the current item.
174 *
175 * Returns: (nullable) (transfer none): the #WebKitBackForwardListItem
176 * preceding the current item or %NULL.
177 */
178WebKitBackForwardListItem* webkit_back_forward_list_get_back_item(WebKitBackForwardList* backForwardList)
179{
180 g_return_val_if_fail(WEBKIT_IS_BACK_FORWARD_LIST(backForwardList), 0);
181
182 return webkitBackForwardListGetOrCreateItem(backForwardList, backForwardList->priv->backForwardItems->backItem());
183}
184
185/**
186 * webkit_back_forward_list_get_forward_item:
187 * @back_forward_list: a #WebKitBackForwardList
188 *
189 * Returns the item that follows the current item.
190 *
191 * Returns: (nullable) (transfer none): the #WebKitBackForwardListItem
192 * following the current item or %NULL.
193 */
194WebKitBackForwardListItem* webkit_back_forward_list_get_forward_item(WebKitBackForwardList* backForwardList)
195{
196 g_return_val_if_fail(WEBKIT_IS_BACK_FORWARD_LIST(backForwardList), 0);
197
198 return webkitBackForwardListGetOrCreateItem(backForwardList, backForwardList->priv->backForwardItems->forwardItem());
199}
200
201/**
202 * webkit_back_forward_list_get_nth_item:
203 * @back_forward_list: a #WebKitBackForwardList
204 * @index: the index of the item
205 *
206 * Returns the item at a given index relative to the current item.
207 *
208 * Returns: (nullable) (transfer none): the #WebKitBackForwardListItem
209 * located at the specified index relative to the current item or %NULL.
210 */
211WebKitBackForwardListItem* webkit_back_forward_list_get_nth_item(WebKitBackForwardList* backForwardList, gint index)
212{
213 g_return_val_if_fail(WEBKIT_IS_BACK_FORWARD_LIST(backForwardList), 0);
214
215 return webkitBackForwardListGetOrCreateItem(backForwardList, backForwardList->priv->backForwardItems->itemAtIndex(index));
216}
217
218/**
219 * webkit_back_forward_list_get_length:
220 * @back_forward_list: a #WebKitBackForwardList
221 *
222 * Returns: the length of @back_forward_list.
223 */
224guint webkit_back_forward_list_get_length(WebKitBackForwardList* backForwardList)
225{
226 g_return_val_if_fail(WEBKIT_IS_BACK_FORWARD_LIST(backForwardList), 0);
227
228 WebKitBackForwardListPrivate* priv = backForwardList->priv;
229 guint currentItem = webkit_back_forward_list_get_current_item(backForwardList) ? 1 : 0;
230 return priv->backForwardItems->backListCount() + priv->backForwardItems->forwardListCount() + currentItem;
231}
232
233/**
234 * webkit_back_forward_list_get_back_list:
235 * @back_forward_list: a #WebKitBackForwardList
236 *
237 * Returns: (element-type WebKit2.BackForwardListItem) (transfer container): a #GList of
238 * items preceding the current item.
239 */
240GList* webkit_back_forward_list_get_back_list(WebKitBackForwardList* backForwardList)
241{
242 g_return_val_if_fail(WEBKIT_IS_BACK_FORWARD_LIST(backForwardList), 0);
243
244 return webkit_back_forward_list_get_back_list_with_limit(backForwardList, backForwardList->priv->backForwardItems->backListCount());
245}
246
247/**
248 * webkit_back_forward_list_get_back_list_with_limit:
249 * @back_forward_list: a #WebKitBackForwardList
250 * @limit: the number of items to retrieve
251 *
252 * Returns: (element-type WebKit2.BackForwardListItem) (transfer container): a #GList of
253 * items preceding the current item limited by @limit.
254 */
255GList* webkit_back_forward_list_get_back_list_with_limit(WebKitBackForwardList* backForwardList, guint limit)
256{
257 g_return_val_if_fail(WEBKIT_IS_BACK_FORWARD_LIST(backForwardList), 0);
258
259 WebKitBackForwardListPrivate* priv = backForwardList->priv;
260 Ref<API::Array> apiArray = priv->backForwardItems->backListAsAPIArrayWithLimit(limit);
261 return webkitBackForwardListCreateList(backForwardList, apiArray.ptr());
262}
263
264/**
265 * webkit_back_forward_list_get_forward_list:
266 * @back_forward_list: a #WebKitBackForwardList
267 *
268 * Returns: (element-type WebKit2.BackForwardListItem) (transfer container): a #GList of
269 * items following the current item.
270 */
271GList* webkit_back_forward_list_get_forward_list(WebKitBackForwardList* backForwardList)
272{
273 g_return_val_if_fail(WEBKIT_IS_BACK_FORWARD_LIST(backForwardList), 0);
274
275 return webkit_back_forward_list_get_forward_list_with_limit(backForwardList, backForwardList->priv->backForwardItems->forwardListCount());
276}
277
278/**
279 * webkit_back_forward_list_get_forward_list_with_limit:
280 * @back_forward_list: a #WebKitBackForwardList
281 * @limit: the number of items to retrieve
282 *
283 * Returns: (element-type WebKit2.BackForwardListItem) (transfer container): a #GList of
284 * items following the current item limited by @limit.
285 */
286GList* webkit_back_forward_list_get_forward_list_with_limit(WebKitBackForwardList* backForwardList, guint limit)
287{
288 g_return_val_if_fail(WEBKIT_IS_BACK_FORWARD_LIST(backForwardList), 0);
289
290 WebKitBackForwardListPrivate* priv = backForwardList->priv;
291 Ref<API::Array> apiArray = priv->backForwardItems->forwardListAsAPIArrayWithLimit(limit);
292 return webkitBackForwardListCreateList(backForwardList, apiArray.ptr());
293}
294