Merge ~osomon/oxide:fix-1649861-serialized-navigation-entries into oxide:master

Proposed by Olivier Tilloy
Status: Merged
Approved by: Chris Coulson
Approved revision: b90c7c8c4bb669c2a64fe7d8e1e67e6bbd3dfc08
Merged at revision: f40a17d2841c51309f605fab1a229b08e85d5305
Proposed branch: ~osomon/oxide:fix-1649861-serialized-navigation-entries
Merge into: oxide:master
Diff against target: 341 lines (+245/-14)
5 files modified
qt/core/browser/oxide_qt_web_view.cc (+148/-14)
qt/tests/qmltests/api/tst_WebView_save_restore_entry1.html (+5/-0)
qt/tests/qmltests/api/tst_WebView_save_restore_entry2.html (+5/-0)
qt/tests/qmltests/api/tst_WebView_save_restore_entry3.html (+5/-0)
qt/tests/qmltests/api/tst_WebView_save_restore_state.qml (+82/-0)
Reviewer Review Type Date Requested Status
Chris Coulson Approve
Review via email: mp+313310@code.launchpad.net

Commit message

Fix session save/restore across oxide versions (LP: #1649861).

Make session save/restore future-proof by storing each navigation entry separately.

To post a comment you must log in.
Revision history for this message
Chris Coulson (chrisccoulson) wrote :

Thanks for this - I've left a comment inline.

Also, there needs to be some tests for the upgrade path too.

review: Needs Fixing
Revision history for this message
Olivier Tilloy (osomon) wrote :

Thanks for the review. I’ve replied to your comment with a (hopefully) thorough analysis of the potential issue, and I’ll add a comment in the code for future reference. I’ll also look into adding tests for the upgrade path.

0097afa... by Olivier Tilloy

Add a comment for future reference.

554b6ad... by Olivier Tilloy

Add a unit test for the upgrade path.

Revision history for this message
Olivier Tilloy (osomon) wrote :

Updated with a comment for future reference and a unit test for the upgrade path.

802c915... by Olivier Tilloy

Add missing test files.

Revision history for this message
Santosh (santoshbit2007) :
Revision history for this message
Olivier Tilloy (osomon) wrote :

Thanks for your review Santosh. See my answers to your comments inline.

Revision history for this message
Chris Coulson (chrisccoulson) wrote :

Ok, I've replied inline.

One other thing to consider - 1.19 has already shipped to the archive with this bug. I'm not sure if there's a way to handle upgrades from the current 1.19 release without breaking restoration for a second time, as state saved with the current 1.19 release at least has an extra integer for each navigation entry. Given the extra integer is always zero, and each subsequent serialized navigation entry starts with the index (which should always be non-zero), then there's probably a clever way to detect this...

Revision history for this message
Chris Coulson (chrisccoulson) wrote :

> Ok, I've replied inline.
>
> One other thing to consider - 1.19 has already shipped to the archive with
> this bug. I'm not sure if there's a way to handle upgrades from the current
> 1.19 release without breaking restoration for a second time, as state saved
> with the current 1.19 release at least has an extra integer for each
> navigation entry. Given the extra integer is always zero, and each subsequent
> serialized navigation entry starts with the index (which should always be non-
> zero), then there's probably a clever way to detect this...

Also, given this, it might be worth serializing the Oxide version in the new format.

46a689b... by Olivier Tilloy

Also serialize oxide version in new state format.

b90c7c8... by Olivier Tilloy

Also handle the upgrade path from 1.19.6 to 1.19.7.

Revision history for this message
Olivier Tilloy (osomon) wrote :

All addressed. Comments welcome.

Revision history for this message
Chris Coulson (chrisccoulson) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/qt/core/browser/oxide_qt_web_view.cc b/qt/core/browser/oxide_qt_web_view.cc
index 975c937..af07a4e 100644
--- a/qt/core/browser/oxide_qt_web_view.cc
+++ b/qt/core/browser/oxide_qt_web_view.cc
@@ -34,6 +34,7 @@
34#include "base/macros.h"34#include "base/macros.h"
35#include "base/pickle.h"35#include "base/pickle.h"
36#include "base/strings/utf_string_conversions.h"36#include "base/strings/utf_string_conversions.h"
37#include "base/version.h"
37#include "cc/output/compositor_frame_metadata.h"38#include "cc/output/compositor_frame_metadata.h"
38#include "content/public/browser/host_zoom_map.h"39#include "content/public/browser/host_zoom_map.h"
39#include "content/public/browser/native_web_keyboard_event.h"40#include "content/public/browser/native_web_keyboard_event.h"
@@ -50,6 +51,7 @@
50#include "url/gurl.h"51#include "url/gurl.h"
5152
52#include "qt/core/api/oxideqdownloadrequest.h"53#include "qt/core/api/oxideqdownloadrequest.h"
54#include "qt/core/api/oxideqglobal.h"
53#include "qt/core/api/oxideqloadevent.h"55#include "qt/core/api/oxideqloadevent.h"
54#include "qt/core/api/oxideqhttpauthenticationrequest_p.h"56#include "qt/core/api/oxideqhttpauthenticationrequest_p.h"
55#include "qt/core/api/oxideqnavigationrequest.h"57#include "qt/core/api/oxideqnavigationrequest.h"
@@ -142,7 +144,7 @@ OxideQLoadEvent::ErrorDomain ErrorDomainFromErrorCode(int error_code) {
142}144}
143145
144static const char* STATE_SERIALIZER_MAGIC_NUMBER = "oxide";146static const char* STATE_SERIALIZER_MAGIC_NUMBER = "oxide";
145static uint16_t STATE_SERIALIZER_VERSION = 1;147static uint16_t STATE_SERIALIZER_VERSION = 2;
146148
147void CreateRestoreEntriesFromRestoreState(149void CreateRestoreEntriesFromRestoreState(
148 const QByteArray& state,150 const QByteArray& state,
@@ -167,28 +169,157 @@ void CreateRestoreEntriesFromRestoreState(
167 WARN_INVALID_DATA;169 WARN_INVALID_DATA;
168 return;170 return;
169 }171 }
170 if (version != STATE_SERIALIZER_VERSION) {172 if (version < 1 || version > STATE_SERIALIZER_VERSION) {
171 WARN_INVALID_DATA;173 WARN_INVALID_DATA;
172 return;174 return;
173 }175 }
176 base::Version oxide_version;
177 if (version >= 2) {
178 std::string v;
179 if (!i.ReadString(&v)) {
180 WARN_INVALID_DATA;
181 return;
182 }
183 oxide_version = base::Version(v);
184 if (oxide_version.CompareTo(base::Version(std::string("1.19.7"))) == -1) {
185 // The oxide version in the serialized state was added in oxide 1.19.7
186 WARN_INVALID_DATA;
187 return;
188 }
189 }
174 int count;190 int count;
175 if (!i.ReadLength(&count)) {191 if (!i.ReadLength(&count)) {
176 WARN_INVALID_DATA;192 WARN_INVALID_DATA;
177 return;193 return;
178 }194 }
179 entries.resize(count);195 entries.resize(count);
180 for (int j = 0; j < count; ++j) {
181 sessions::SerializedNavigationEntry entry;
182 if (!entry.ReadFromPickle(&i)) {
183 WARN_INVALID_DATA;
184 return;
185 }
186 entries[j] = entry;
187 }
188 int index;196 int index;
189 if (!i.ReadInt(&index)) {197 switch (version) {
190 WARN_INVALID_DATA; 198 case 1: {
191 return;199 // In chromium < 55, the serialized navigation
200 // entries did not store extended info
201 // (https://chromium.googlesource.com/chromium/src/+/f62ca6f).
202 // Because of the way oxide concatenated all serialized navigation
203 // entries, oxide 1.19 would fail to deserialize entries stored with
204 // oxide < 1.19 (https://launchpad.net/bugs/1649861).
205 // To work around that issue, a bit of careful pointer arithmetic is
206 // required.
207 // Note that states serialized with oxide >=1.19.0 and <=1.19.6 still
208 // advertise version 1, but used chromium 55, so they store extended info,
209 // and therefore the pointer arithmetic is not needed. To detect that, we
210 // rely on the fact that each navigation entry will have an extra integer
211 // whose value is always 0 (the size of the extended info map), and if
212 // read as part of the next entry, it will be mistaken for the entry's
213 // index which should never be 0 (except maybe for the first entry).
214 bool reading_too_much_data = true;
215 base::PickleSizer sizer;
216 sizer.AddString(magic_number); // magic number
217 sizer.AddUInt16(); // version
218 sizer.AddInt(); // count
219 sizer.AddInt(); // index
220 const char* raw_data = nullptr;
221 int length = state.size() - sizer.payload_size();
222 if (!i.ReadBytes(&raw_data, length)) {
223 WARN_INVALID_DATA;
224 return;
225 }
226 for (int j = 0; j < count; ++j) {
227 base::Pickle pickled_entry;
228 pickled_entry.WriteBytes(raw_data, length);
229 base::PickleIterator k(pickled_entry);
230 sessions::SerializedNavigationEntry entry;
231 // This will potentially try to read too much data (the inexistent
232 // extended_info_map_), so we need to compute the actual size of the
233 // serialized entry to correctly advance the pointer to the beginning
234 // of the next entry.
235 // Reading too much data will potentially add garbage entries to the
236 // extended info map (if for any invalid entry two strings can be read,
237 // key and value). For each entry in the resulting map,
238 // ContentSerializedNavigationBuilder::ToNavigationEntry() looks up an
239 // extended info handler (by key). If it can't find one, it just skips
240 // the unknown entry (most likely case). As of today (January 2017),
241 // there's only one handler registered by chrome on android, which is
242 // not relevant here. This would become a concern if oxide registered
243 // its own handlers, and their implementation would need to check for
244 // value correctness and silently ignore garbage values.
245 if (j == 1) {
246 // Try to read an integer first, if its value is 0 (invalid entry
247 // index) then we were not actually reading too much data (the
248 // extended info map had been serialized).
249 int m = 0;
250 if (!k.ReadInt(&m)) {
251 WARN_INVALID_DATA;
252 return;
253 }
254 if (m == 0) {
255 reading_too_much_data = false;
256 base::PickleSizer int_sizer;
257 int_sizer.AddInt();
258 raw_data += int_sizer.payload_size();
259 length -= int_sizer.payload_size();
260 } else {
261 k = base::PickleIterator(pickled_entry);
262 }
263 }
264 if (!entry.ReadFromPickle(&k)) {
265 WARN_INVALID_DATA;
266 return;
267 }
268 entries[j] = entry;
269 base::PickleSizer entry_sizer;
270 entry_sizer.AddInt(); // index
271 entry_sizer.AddString(entry.virtual_url().spec());
272 entry_sizer.AddString16(entry.title());
273 entry_sizer.AddString(entry.encoded_page_state());
274 entry_sizer.AddInt(); // transition type
275 entry_sizer.AddInt(); // type mask
276 entry_sizer.AddString(entry.referrer_url().spec());
277 entry_sizer.AddInt(); // mapped referrer policy
278 entry_sizer.AddString(entry.original_request_url().spec());
279 entry_sizer.AddBool(); // is overriding user agent
280 entry_sizer.AddInt64(); // timestamp
281 entry_sizer.AddString16(entry.search_terms());
282 entry_sizer.AddInt(); // HTTP status code
283 entry_sizer.AddInt(); // referrer policy
284 if (!reading_too_much_data) {
285 entry_sizer.AddInt(); // extended info map size, always 0
286 }
287 raw_data += entry_sizer.payload_size();
288 length -= entry_sizer.payload_size();
289 }
290 base::Pickle remainder;
291 remainder.WriteBytes(raw_data, length);
292 i = base::PickleIterator(remainder);
293 if (!i.ReadInt(&index)) {
294 WARN_INVALID_DATA;
295 return;
296 }
297 break;
298 }
299 case 2:
300 default: {
301 for (int j = 0; j < count; ++j) {
302 const char* data;
303 int length;
304 if (!i.ReadData(&data, &length)) {
305 WARN_INVALID_DATA;
306 return;
307 }
308 base::Pickle pickled_entry(data, length);
309 base::PickleIterator k(pickled_entry);
310 sessions::SerializedNavigationEntry entry;
311 if (!entry.ReadFromPickle(&k)) {
312 WARN_INVALID_DATA;
313 return;
314 }
315 entries[j] = entry;
316 }
317 if (!i.ReadInt(&index)) {
318 WARN_INVALID_DATA;
319 return;
320 }
321 break;
322 }
192 }323 }
193#undef WARN_INVALID_DATA324#undef WARN_INVALID_DATA
194325
@@ -765,12 +896,15 @@ QByteArray WebView::currentState() const {
765 base::Pickle pickle;896 base::Pickle pickle;
766 pickle.WriteString(STATE_SERIALIZER_MAGIC_NUMBER);897 pickle.WriteString(STATE_SERIALIZER_MAGIC_NUMBER);
767 pickle.WriteUInt16(STATE_SERIALIZER_VERSION);898 pickle.WriteUInt16(STATE_SERIALIZER_VERSION);
899 pickle.WriteString(oxideGetVersion().toStdString());
768 pickle.WriteInt(entries.size());900 pickle.WriteInt(entries.size());
769 std::vector<sessions::SerializedNavigationEntry>::const_iterator i;901 std::vector<sessions::SerializedNavigationEntry>::const_iterator i;
770 static const size_t max_state_size =902 static const size_t max_state_size =
771 std::numeric_limits<uint16_t>::max() - 1024;903 std::numeric_limits<uint16_t>::max() - 1024;
772 for (i = entries.begin(); i != entries.end(); ++i) {904 for (i = entries.begin(); i != entries.end(); ++i) {
773 i->WriteToPickle(max_state_size, &pickle);905 base::Pickle entry;
906 i->WriteToPickle(max_state_size, &entry);
907 pickle.WriteData(static_cast<const char*>(entry.data()), entry.size());
774 }908 }
775 pickle.WriteInt(909 pickle.WriteInt(
776 web_view_->GetWebContents()->GetController().GetCurrentEntryIndex());910 web_view_->GetWebContents()->GetController().GetCurrentEntryIndex());
diff --git a/qt/tests/qmltests/api/tst_WebView_save_restore_entry1.html b/qt/tests/qmltests/api/tst_WebView_save_restore_entry1.html
777new file mode 100644911new file mode 100644
index 0000000..5add6f1
--- /dev/null
+++ b/qt/tests/qmltests/api/tst_WebView_save_restore_entry1.html
@@ -0,0 +1,5 @@
1<html>
2<body>
3 page 1
4</body>
5</html>
diff --git a/qt/tests/qmltests/api/tst_WebView_save_restore_entry2.html b/qt/tests/qmltests/api/tst_WebView_save_restore_entry2.html
0new file mode 1006446new file mode 100644
index 0000000..ff2f811
--- /dev/null
+++ b/qt/tests/qmltests/api/tst_WebView_save_restore_entry2.html
@@ -0,0 +1,5 @@
1<html>
2<body>
3 page 2
4</body>
5</html>
diff --git a/qt/tests/qmltests/api/tst_WebView_save_restore_entry3.html b/qt/tests/qmltests/api/tst_WebView_save_restore_entry3.html
0new file mode 1006446new file mode 100644
index 0000000..c8af8f4
--- /dev/null
+++ b/qt/tests/qmltests/api/tst_WebView_save_restore_entry3.html
@@ -0,0 +1,5 @@
1<html>
2<body>
3 page 3
4</body>
5</html>
diff --git a/qt/tests/qmltests/api/tst_WebView_save_restore_state.qml b/qt/tests/qmltests/api/tst_WebView_save_restore_state.qml
index 51743df..5665eb2 100644
--- a/qt/tests/qmltests/api/tst_WebView_save_restore_state.qml
+++ b/qt/tests/qmltests/api/tst_WebView_save_restore_state.qml
@@ -193,5 +193,87 @@ Item {
193 });193 });
194 restored.destroy();194 restored.destroy();
195 }195 }
196
197 function test_WebView_restore_from_old_state_data() {
198 // All serialized state have version 1 (oxide 1.19.6 and older) and they
199 // contain three navigation entries (current index is 2).
200 return [
201 // State serialized with oxide 1.18 or older and restored with oxide
202 // 1.19 or newer (https://launchpad.net/bugs/1649861).
203 {state:
204 "sAQAAAUAAABveGlkZQAAAAEAAAADAAAAAAAAADUAAABodHRwOi8vdGVzdHN1aXRlL3Rz\
205 dF9XZWJWaWV3X3NhdmVfcmVzdG9yZV9lbnRyeTEuaHRtbAAAAAAAAADcAAAA2AAAABcA\
206 AAAAAAAAagAAAGgAdAB0AHAAOgAvAC8AdABlAHMAdABzAHUAaQB0AGUALwB0AHMAdABf\
207 AFcAZQBiAFYAaQBlAHcAXwBzAGEAdgBlAF8AcgBlAHMAdABvAHIAZQBfAGUAbgB0AHIA\
208 eQAxAC4AaAB0AG0AbAAAAP////8AAAAAAAAAAP////8AAAAACAAAAAAAAAAAAPA/bZlL\
209 pUxFBQBumUulTEUFAAIAAAAIAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAA\
210 AAD/////AAAAAAEAAAgAAAAAAAAAAAEAAAA1AAAAaHR0cDovL3Rlc3RzdWl0ZS90c3Rf\
211 V2ViVmlld19zYXZlX3Jlc3RvcmVfZW50cnkxLmh0bWwAAAAAAAAANwPS7eKjLgAAAAAA\
212 yAAAAAEAAAABAAAANQAAAGh0dHA6Ly90ZXN0c3VpdGUvdHN0X1dlYlZpZXdfc2F2ZV9y\
213 ZXN0b3JlX2VudHJ5Mi5odG1sAAAAAAAAANwAAADYAAAAFwAAAAAAAABqAAAAaAB0AHQA\
214 cAA6AC8ALwB0AGUAcwB0AHMAdQBpAHQAZQAvAHQAcwB0AF8AVwBlAGIAVgBpAGUAdwBf\
215 AHMAYQB2AGUAXwByAGUAcwB0AG8AcgBlAF8AZQBuAHQAcgB5ADIALgBoAHQAbQBsAAAA\
216 /////wAAAAA\AAAAA/////wAAAAAIAAAAAAAAAAAA8D9vmUulTEUFAHCZS6VMRQUAAgA\
217 AAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP////8AAAAAAQAACAA\
218 AAAAAAAAAAQAAADUAAABodHRwOi8vdGVzdHN1aXRlL3RzdF9XZWJWaWV3X3NhdmVfcmV\
219 zdG9yZV9lbnRyeTIuaHRtbAAAAAAAAAACKdPt4qMuAAAAAADIAAAAAQAAAAIAAAA1AAA\
220 AaHR0cDovL3Rlc3RzdWl0ZS90c3RfV2ViVmlld19zYXZlX3Jlc3RvcmVfZW50cnkzLmh\
221 0bWwAAAAAAAAA3AAAANgAAAAXAAAAAAAAAGoAAABoAHQAdABwADoALwAvAHQAZQBzAHQ\
222 AcwB1AGkAdABlAC8AdABzAHQAXwBXAGUAYgBWAGkAZQB3AF8AcwBhAHYAZQBfAHIAZQB\
223 zAHQAbwByAGUAXwBlAG4AdAByAHkAMwAuAGgAdABtAGwAAAD/////AAAAAAAAAAD////\
224 /AAAAAAgAAAAAAAAAAAAAAHGZS6VMRQUAcplLpUxFBQACAAAACAAAAAAAAAAAAAAACAA\
225 AAAAAAAAAAAAAAAAAAAAAAAAAAAAA/////wAAAAABAAAIAAAAAAAAAAABAAAANQAAAGh\
226 0dHA6Ly90ZXN0c3VpdGUvdHN0X1dlYlZpZXdfc2F2ZV9yZXN0b3JlX2VudHJ5My5odG1\
227 sAAAAAAAAAB8q1O3ioy4AAAAAAMgAAAABAAAAAgAAAA=="},
228
229 // State serialized with oxide >=1.19.0 and <=1.19.6 and restored with
230 // oxide 1.19.7 or newer.
231 {state:
232 "vAQAAAUAAABveGlkZQAAAAEAAAADAAAAAAAAADUAAABodHRwOi8vdGVzdHN1aXRlL3Rz\
233 dF9XZWJWaWV3X3NhdmVfcmVzdG9yZV9lbnRyeTEuaHRtbAAAAAAAAADcAAAA2AAAABcA\
234 AAAAAAAAagAAAGgAdAB0AHAAOgAvAC8AdABlAHMAdABzAHUAaQB0AGUALwB0AHMAdABf\
235 AFcAZQBiAFYAaQBlAHcAXwBzAGEAdgBlAF8AcgBlAHMAdABvAHIAZQBfAGUAbgB0AHIA\
236 eQAxAC4AaAB0AG0AbAAAAP////8AAAAAAAAAAP////8AAAAACAAAAAAAAAAAAPA/xS5g\
237 QuhFBQDGLmBC6EUFAAIAAAAIAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAA\
238 AAD/////AAAAAAEAAAgAAAAAAAAAAAEAAAA1AAAAaHR0cDovL3Rlc3RzdWl0ZS90c3Rf\
239 V2ViVmlld19zYXZlX3Jlc3RvcmVfZW50cnkxLmh0bWwAAAAAAAAA55Pmin6kLgAAAAAA\
240 yAAAAAEAAAAAAAAAAQAAADUAAABodHRwOi8vdGVzdHN1aXRlL3RzdF9XZWJWaWV3X3Nh\
241 dmVfcmVzdG9yZV9lbnRyeTIuaHRtbAAAAAAAAADcAAAA2AAAABcAAAAAAAAAagAAAGgA\
242 dAB0AHAAOgAvAC8AdABlAHMAdABzAHUAaQB0AGUALwB0AHMAdABfAFcAZQBiAFYAaQBl\
243 AHcAXwBzAGEAdgBlAF8AcgBlAHMAdABvAHIAZQBfAGUAbgB0AHIAeQAyAC4AaAB0AG0A\
244 bAAAAP////8AAAAAAAAAAP////8AAAAACAAAAAAAAAAAAPA/xy5gQuhFBQDILmBC6EUF\
245 AAIAAAAIAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////AAAAAAEA\
246 AAgAAAAAAAAAAAEAAAA1AAAAaHR0cDovL3Rlc3RzdWl0ZS90c3RfV2ViVmlld19zYXZl\
247 X3Jlc3RvcmVfZW50cnkyLmh0bWwAAAAAAAAA9eLnin6kLgAAAAAAyAAAAAEAAAAAAAAA\
248 AgAAADUAAABodHRwOi8vdGVzdHN1aXRlL3RzdF9XZWJWaWV3X3NhdmVfcmVzdG9yZV9l\
249 bnRyeTMuaHRtbAAAAAAAAADcAAAA2AAAABcAAAAAAAAAagAAAGgAdAB0AHAAOgAvAC8A\
250 dABlAHMAdABzAHUAaQB0AGUALwB0AHMAdABfAFcAZQBiAFYAaQBlAHcAXwBzAGEAdgBl\
251 AF8AcgBlAHMAdABvAHIAZQBfAGUAbgB0AHIAeQAzAC4AaAB0AG0AbAAAAP////8AAAAA\
252 AAAAAP////8AAAAACAAAAAAAAAAAAAAAyS5gQuhFBQDKLmBC6EUFAAIAAAAIAAAAAAAA\
253 AAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////AAAAAAEAAAgAAAAAAAAAAAEA\
254 AAA1AAAAaHR0cDovL3Rlc3RzdWl0ZS90c3RfV2ViVmlld19zYXZlX3Jlc3RvcmVfZW50\
255 cnkzLmh0bWwAAAAAAAAA5o/oin6kLgAAAAAAyAAAAAEAAAAAAAAAAgAAAA=="}
256 ];
257 }
258
259 function test_WebView_restore_from_old_state(data) {
260 var restored = webViewComponent.createObject(
261 root,
262 {"restoreType": WebView.RestoreCurrentSession,
263 "restoreState": data.state});
264 verify(restored !== null);
265 tryCompare(restored, "url",
266 "http://testsuite/tst_WebView_save_restore_entry3.html");
267 verify(restored.waitForLoadSucceeded(),
268 "Timed out waiting for successful load");
269 compare(restored.navigationHistory.currentItemIndex, 2);
270 var items = restored.navigationHistory.items;
271 for (var i = 0; i < 3; ++i) {
272 compare(items[i].url,
273 "http://testsuite/tst_WebView_save_restore_entry%1.html"
274 .arg(i + 1));
275 }
276 restored.destroy();
277 }
196 }278 }
197}279}

Subscribers

People subscribed via source and target branches