Merge lp:~osomon/oxide/save-restore-state into lp:~oxide-developers/oxide/oxide.trunk

Proposed by Olivier Tilloy
Status: Merged
Merged at revision: 872
Proposed branch: lp:~osomon/oxide/save-restore-state
Merge into: lp:~oxide-developers/oxide/oxide.trunk
Diff against target: 683 lines (+418/-4)
10 files modified
qt/core/glue/oxide_qt_web_view_adapter.cc (+97/-1)
qt/core/glue/oxide_qt_web_view_adapter.h (+13/-1)
qt/quick/api/oxideqquickwebview.cc (+62/-1)
qt/quick/api/oxideqquickwebview_p.h (+21/-0)
qt/quick/api/oxideqquickwebview_p_p.h (+5/-1)
qt/tests/qmltests/api/tst_WebView_save_restore_form_data.html (+5/-0)
qt/tests/qmltests/api/tst_WebView_save_restore_state.qml (+152/-0)
shared/browser/oxide_web_view.cc (+46/-0)
shared/browser/oxide_web_view.h (+16/-0)
shared/shared.gyp (+1/-0)
To merge this branch: bzr merge lp:~osomon/oxide/save-restore-state
Reviewer Review Type Date Requested Status
Chris Coulson Approve
Review via email: mp+238963@code.launchpad.net

Commit message

Expose an API to save/restore the current state of a webview.

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

Thanks for working on this. I have some general comments (I mentioned these the other day):

- I've been thinking about how we would adapt this in the future to provide a way to restore from a crash. Doing this requires continuous updating of the persistent state, which means that my original suggestion to use a function to access the serialized state (as opposed to a notifiable property) in order to avoid applications binding to it (and requiring us to serialize the data continuously) may not actually be the best approach. In fact, I think that being able to do continuous updates would require this to a notifiable property.

I would be tempted to convert this to a property, but ensure that the browser only polls it at shutdown for now - at least until we've evaluated to the performance impact of serializing the full state, and maybe figured out a way to do incremental updates.

- I'd add a magic number and version to the start of the serialized data. This would future-proof it if we found a need to modify the contents later on (eg, adding support for storage partitions), and would allow us to handle upgrades from old data formats gracefully.

- WebView.currentState is fine, but I'd rename WebView.state to WebView.restoreState or something like that.

- Based on the assumption that we eventually want crash-restore, it might be worth exposing WebView.restoreType (mapping to content::NavigationController::RestoreType). There is, at least, a difference between RESTORE_LAST_SESSION_EXITED_CLEANLY and the other 2 enums (it does a cache-only load for a page with POST-data). The difference between RESTORE_CURRENT and RESTORE_LAST_SESSION_CRASHED seems to be only in metrics data (which we don't use), but it's probably worth exposing them separately anyway because we wouldn't want to have to use "RestoreTypeLastSessionCrashed" for a tab restored from the current session.

- Note that r855 probably breaks this a bit (I've moved all of the construct properties to WebViewAdapter::init()).

review: Needs Fixing
lp:~osomon/oxide/save-restore-state updated
800. By Olivier Tilloy

Merge the latest changes from trunk.

801. By Olivier Tilloy

Rename the 'state' property to 'restoreState'.

802. By Olivier Tilloy

Make currentState a read-only property, instead of a method.

803. By Olivier Tilloy

Add a magic number and a version to the serialized state.

804. By Olivier Tilloy

Expose WebView.restoreType.

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

Updated to address all your comments.

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

Thanks, that looks fine now. One thing I should have mentioned last time is that the new properties should be annotated with a revision number (in this case, 2). Other than that, this is fine to merge

review: Approve
lp:~osomon/oxide/save-restore-state updated
805. By Olivier Tilloy

Version the new properties (will be available starting with oxide 1.4).

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'qt/core/glue/oxide_qt_web_view_adapter.cc'
2--- qt/core/glue/oxide_qt_web_view_adapter.cc 2014-11-13 09:17:40 +0000
3+++ qt/core/glue/oxide_qt_web_view_adapter.cc 2014-11-20 12:42:26 +0000
4@@ -17,12 +17,15 @@
5
6 #include "oxide_qt_web_view_adapter.h"
7
8+#include <limits>
9+
10 #include <QSize>
11 #include <QtDebug>
12
13 #include "base/logging.h"
14 #include "base/macros.h"
15 #include "base/memory/ref_counted.h"
16+#include "base/pickle.h"
17 #include "cc/output/compositor_frame_metadata.h"
18 #include "ui/gfx/size.h"
19 #include "url/gurl.h"
20@@ -44,6 +47,11 @@
21 namespace oxide {
22 namespace qt {
23
24+namespace {
25+static const char* STATE_SERIALIZER_MAGIC_NUMBER = "oxide";
26+static uint16_t STATE_SERIALIZER_VERSION = 1;
27+}
28+
29 class CompositorFrameHandleImpl : public CompositorFrameHandle {
30 public:
31 CompositorFrameHandleImpl(oxide::CompositorFrameHandle* frame)
32@@ -104,6 +112,70 @@
33 OxideQWebPreferencesPrivate::get(p)->preferences());
34 }
35
36+void WebViewAdapter::RestoreState(RestoreType type, const QByteArray& state) {
37+ COMPILE_ASSERT(
38+ RESTORE_CURRENT_SESSION == static_cast<RestoreType>(
39+ content::NavigationController::RESTORE_CURRENT_SESSION),
40+ restore_type_enums_current_doesnt_match);
41+ COMPILE_ASSERT(
42+ RESTORE_LAST_SESSION_EXITED_CLEANLY == static_cast<RestoreType>(
43+ content::NavigationController::RESTORE_LAST_SESSION_EXITED_CLEANLY),
44+ restore_type_enums_exited_cleanly_doesnt_match);
45+ COMPILE_ASSERT(
46+ RESTORE_LAST_SESSION_CRASHED == static_cast<RestoreType>(
47+ content::NavigationController::RESTORE_LAST_SESSION_CRASHED),
48+ restore_type_enums_crashed_doesnt_match);
49+
50+ content::NavigationController::RestoreType restore_type =
51+ static_cast<content::NavigationController::RestoreType>(type);
52+
53+#define WARN_INVALID_DATA \
54+ qWarning() << "Failed to read initial state: invalid data"
55+ std::vector<sessions::SerializedNavigationEntry> entries;
56+ Pickle pickle(state.data(), state.size());
57+ PickleIterator i(pickle);
58+ std::string magic_number;
59+ if (!i.ReadString(&magic_number)) {
60+ WARN_INVALID_DATA;
61+ return;
62+ }
63+ if (magic_number != STATE_SERIALIZER_MAGIC_NUMBER) {
64+ WARN_INVALID_DATA;
65+ return;
66+ }
67+ uint16_t version;
68+ if (!i.ReadUInt16(&version)) {
69+ WARN_INVALID_DATA;
70+ return;
71+ }
72+ if (version != STATE_SERIALIZER_VERSION) {
73+ WARN_INVALID_DATA;
74+ return;
75+ }
76+ int count;
77+ if (!i.ReadLength(&count)) {
78+ WARN_INVALID_DATA;
79+ return;
80+ }
81+ entries.resize(count);
82+ for (int j = 0; j < count; ++j) {
83+ sessions::SerializedNavigationEntry entry;
84+ if (!entry.ReadFromPickle(&i)) {
85+ WARN_INVALID_DATA;
86+ return;
87+ }
88+ entries[j] = entry;
89+ }
90+ int index;
91+ if (!i.ReadInt(&index)) {
92+ WARN_INVALID_DATA;
93+ return;
94+ }
95+#undef WARN_INVALID_DATA
96+
97+ view_->SetState(restore_type, entries, index);
98+}
99+
100 void WebViewAdapter::Initialized() {
101 DCHECK(isInitialized());
102
103@@ -134,7 +206,9 @@
104
105 void WebViewAdapter::init(bool incognito,
106 WebContextAdapter* context,
107- OxideQNewViewRequest* new_view_request) {
108+ OxideQNewViewRequest* new_view_request,
109+ const QByteArray& restoreState,
110+ RestoreType restoreType) {
111 DCHECK(!isInitialized());
112
113 bool script_opened = false;
114@@ -155,6 +229,10 @@
115 return;
116 }
117
118+ if (!restoreState.isEmpty()) {
119+ RestoreState(restoreType, restoreState);
120+ }
121+
122 CHECK(context) <<
123 "No context available for WebView. If you see this when running in "
124 "single-process mode, it is possible that the default WebContext has "
125@@ -325,6 +403,24 @@
126 view_->GetNavigationEntryTimestamp(index).ToJsTime());
127 }
128
129+QByteArray WebViewAdapter::currentState() const {
130+ std::vector<sessions::SerializedNavigationEntry> entries = view_->GetState();
131+ if (entries.size() == 0) {
132+ return QByteArray();
133+ }
134+ Pickle pickle;
135+ pickle.WriteString(STATE_SERIALIZER_MAGIC_NUMBER);
136+ pickle.WriteUInt16(STATE_SERIALIZER_VERSION);
137+ pickle.WriteInt(entries.size());
138+ std::vector<sessions::SerializedNavigationEntry>::const_iterator i;
139+ static const size_t max_state_size = std::numeric_limits<uint16>::max() - 1024;
140+ for (i = entries.begin(); i != entries.end(); ++i) {
141+ i->WriteToPickle(max_state_size, &pickle);
142+ }
143+ pickle.WriteInt(view_->GetNavigationCurrentEntryIndex());
144+ return QByteArray(static_cast<const char*>(pickle.data()), pickle.size());
145+}
146+
147 OxideQWebPreferences* WebViewAdapter::preferences() {
148 EnsurePreferences();
149 return static_cast<WebPreferences*>(
150
151=== modified file 'qt/core/glue/oxide_qt_web_view_adapter.h'
152--- qt/core/glue/oxide_qt_web_view_adapter.h 2014-11-13 00:34:18 +0000
153+++ qt/core/glue/oxide_qt_web_view_adapter.h 2014-11-20 12:42:26 +0000
154@@ -18,6 +18,7 @@
155 #ifndef _OXIDE_QT_CORE_GLUE_WEB_VIEW_ADAPTER_H_
156 #define _OXIDE_QT_CORE_GLUE_WEB_VIEW_ADAPTER_H_
157
158+#include <QByteArray>
159 #include <QDateTime>
160 #include <QImage>
161 #include <QList>
162@@ -85,6 +86,12 @@
163 CONTENT_TYPE_MIXED_SCRIPT = 1 << 1
164 };
165
166+enum RestoreType {
167+ RESTORE_CURRENT_SESSION,
168+ RESTORE_LAST_SESSION_EXITED_CLEANLY,
169+ RESTORE_LAST_SESSION_CRASHED,
170+};
171+
172 class Q_DECL_EXPORT AcceleratedFrameData final {
173 public:
174 AcceleratedFrameData(unsigned int id)
175@@ -120,7 +127,9 @@
176
177 void init(bool incognito,
178 WebContextAdapter* context,
179- OxideQNewViewRequest* new_view_request);
180+ OxideQNewViewRequest* new_view_request,
181+ const QByteArray& restoreState,
182+ RestoreType restoreType);
183
184 QUrl url() const;
185 void setUrl(const QUrl& url);
186@@ -171,6 +180,8 @@
187 QString getNavigationEntryTitle(int index) const;
188 QDateTime getNavigationEntryTimestamp(int index) const;
189
190+ QByteArray currentState() const;
191+
192 OxideQWebPreferences* preferences();
193 void setPreferences(OxideQWebPreferences* prefs);
194
195@@ -201,6 +212,7 @@
196 friend class WebView;
197
198 void EnsurePreferences();
199+ void RestoreState(RestoreType type, const QByteArray& state);
200
201 void Initialized();
202 void WebPreferencesDestroyed();
203
204=== modified file 'qt/quick/api/oxideqquickwebview.cc'
205--- qt/quick/api/oxideqquickwebview.cc 2014-11-13 09:17:40 +0000
206+++ qt/quick/api/oxideqquickwebview.cc 2014-11-20 12:42:26 +0000
207@@ -18,6 +18,7 @@
208 #include "oxideqquickwebview_p.h"
209 #include "oxideqquickwebview_p_p.h"
210
211+#include <QByteArray>
212 #include <QEvent>
213 #include <QFlags>
214 #include <QGuiApplication>
215@@ -656,7 +657,9 @@
216
217 init(construct_props_->incognito,
218 context ? OxideQQuickWebContextPrivate::get(context) : NULL,
219- construct_props_->new_view_request);
220+ construct_props_->new_view_request,
221+ construct_props_->restore_state,
222+ construct_props_->restore_type);
223 }
224
225 // static
226@@ -1432,6 +1435,64 @@
227 d->construct_props_->new_view_request = request;
228 }
229
230+// This exists purely to remove a moc warning. We don't store this initial state
231+// anywhere, it's only a transient blob and I can't think of any possible
232+// reason why anybody would want to read it back
233+QString OxideQQuickWebView::restoreState() const {
234+ return QString();
235+}
236+
237+void OxideQQuickWebView::setRestoreState(const QString& state) {
238+ Q_D(OxideQQuickWebView);
239+
240+ if (d->isInitialized()) {
241+ qWarning() << "Cannot assign state to an already constructed WebView";
242+ return;
243+ }
244+
245+ // state is expected to be a base64-encoded string
246+ d->construct_props_->restore_state =
247+ QByteArray::fromBase64(state.toLocal8Bit());
248+}
249+
250+// This exists purely to remove a moc warning. We don't store this restore type
251+// anywhere, it's only a transient property and I can't think of any possible
252+// reason why anybody would want to read it back
253+OxideQQuickWebView::RestoreType OxideQQuickWebView::restoreType() const {
254+ Q_D(const OxideQQuickWebView);
255+
256+ return RestoreLastSessionExitedCleanly;
257+}
258+
259+void OxideQQuickWebView::setRestoreType(OxideQQuickWebView::RestoreType type) {
260+ Q_D(OxideQQuickWebView);
261+
262+ if (d->isInitialized()) {
263+ qWarning() << "Cannot assign state to an already constructed WebView";
264+ return;
265+ }
266+
267+ Q_STATIC_ASSERT(
268+ RestoreCurrentSession ==
269+ static_cast<RestoreType>(oxide::qt::RESTORE_CURRENT_SESSION));
270+ Q_STATIC_ASSERT(
271+ RestoreLastSessionExitedCleanly ==
272+ static_cast<RestoreType>(oxide::qt::RESTORE_LAST_SESSION_EXITED_CLEANLY));
273+ Q_STATIC_ASSERT(
274+ RestoreLastSessionCrashed ==
275+ static_cast<RestoreType>(oxide::qt::RESTORE_LAST_SESSION_CRASHED));
276+
277+ d->construct_props_->restore_type = static_cast<oxide::qt::RestoreType>(type);
278+}
279+
280+QString OxideQQuickWebView::currentState() const {
281+ Q_D(const OxideQQuickWebView);
282+
283+ // Encode the current state in base64 so it can be safely passed around
284+ // as a string (QML doesn’t know of byte arrays)
285+ return QString::fromLocal8Bit(d->currentState().toBase64());
286+}
287+
288 // static
289 OxideQQuickWebViewAttached* OxideQQuickWebView::qmlAttachedProperties(
290 QObject* object) {
291
292=== modified file 'qt/quick/api/oxideqquickwebview_p.h'
293--- qt/quick/api/oxideqquickwebview_p.h 2014-11-06 16:55:49 +0000
294+++ qt/quick/api/oxideqquickwebview_p.h 2014-11-20 12:42:26 +0000
295@@ -67,6 +67,7 @@
296
297 Q_FLAGS(ContentType)
298 Q_ENUMS(LogMessageSeverityLevel);
299+ Q_ENUMS(RestoreType);
300
301 Q_PROPERTY(QUrl url READ url WRITE setUrl NOTIFY urlChanged)
302 Q_PROPERTY(QString title READ title NOTIFY titleChanged)
303@@ -106,6 +107,14 @@
304
305 Q_PROPERTY(OxideQNewViewRequest* request READ request WRITE setRequest)
306
307+ // Set at construction time only
308+ Q_PROPERTY(QString restoreState READ restoreState WRITE setRestoreState REVISION 2)
309+ Q_PROPERTY(RestoreType restoreType READ restoreType WRITE setRestoreType REVISION 2)
310+ // Use to query the current state, to restore later
311+ // XXX: not notify-able for now, until we figure out a way
312+ // to do incremental updates
313+ Q_PROPERTY(QString currentState READ currentState REVISION 2)
314+
315 Q_DECLARE_PRIVATE(OxideQQuickWebView)
316
317 public:
318@@ -130,6 +139,12 @@
319 };
320 Q_DECLARE_FLAGS(ContentType, ContentTypeFlags)
321
322+ enum RestoreType {
323+ RestoreCurrentSession,
324+ RestoreLastSessionExitedCleanly,
325+ RestoreLastSessionCrashed
326+ };
327+
328 void componentComplete();
329
330 QUrl url() const;
331@@ -197,6 +212,12 @@
332 OxideQNewViewRequest* request() const;
333 void setRequest(OxideQNewViewRequest* request);
334
335+ QString restoreState() const;
336+ void setRestoreState(const QString& state);
337+ RestoreType restoreType() const;
338+ void setRestoreType(RestoreType type);
339+ QString currentState() const;
340+
341 static OxideQQuickWebViewAttached* qmlAttachedProperties(QObject* object);
342
343 public Q_SLOTS:
344
345=== modified file 'qt/quick/api/oxideqquickwebview_p_p.h'
346--- qt/quick/api/oxideqquickwebview_p_p.h 2014-11-13 00:34:18 +0000
347+++ qt/quick/api/oxideqquickwebview_p_p.h 2014-11-20 12:42:26 +0000
348@@ -18,6 +18,7 @@
349 #ifndef _OXIDE_QT_QUICK_API_WEB_VIEW_P_P_H_
350 #define _OXIDE_QT_QUICK_API_WEB_VIEW_P_P_H_
351
352+#include <QByteArray>
353 #include <QPointer>
354 #include <QScopedPointer>
355 #include <QSharedPointer>
356@@ -175,11 +176,14 @@
357
358 struct ConstructProps {
359 ConstructProps()
360- : incognito(false) {}
361+ : incognito(false)
362+ , restore_type(oxide::qt::RESTORE_LAST_SESSION_EXITED_CLEANLY) {}
363
364 bool incognito;
365 QPointer<OxideQQuickWebContext> context;
366 QPointer<OxideQNewViewRequest> new_view_request;
367+ QByteArray restore_state;
368+ oxide::qt::RestoreType restore_type;
369 };
370
371 QScopedPointer<ConstructProps> construct_props_;
372
373=== added file 'qt/tests/qmltests/api/tst_WebView_save_restore_form_data.html'
374--- qt/tests/qmltests/api/tst_WebView_save_restore_form_data.html 1970-01-01 00:00:00 +0000
375+++ qt/tests/qmltests/api/tst_WebView_save_restore_form_data.html 2014-11-20 12:42:26 +0000
376@@ -0,0 +1,5 @@
377+<html>
378+<body>
379+ <input type="text" id="textInput" autofocus="autofocus">
380+</body>
381+</html>
382
383=== added file 'qt/tests/qmltests/api/tst_WebView_save_restore_state.qml'
384--- qt/tests/qmltests/api/tst_WebView_save_restore_state.qml 1970-01-01 00:00:00 +0000
385+++ qt/tests/qmltests/api/tst_WebView_save_restore_state.qml 2014-11-20 12:42:26 +0000
386@@ -0,0 +1,152 @@
387+import QtQuick 2.0
388+import QtTest 1.0
389+import com.canonical.Oxide 1.4
390+import com.canonical.Oxide.Testing 1.0
391+
392+TestCase {
393+ name: "WebView_save_restore_state"
394+ when: windowShown
395+ width: 200
396+ height: 200
397+
398+ property var webView
399+
400+ Component {
401+ id: webViewComponent
402+
403+ TestWebView {
404+ focus: true
405+ anchors.fill: parent
406+ }
407+ }
408+
409+ function get_restore_types() {
410+ return [
411+ {restoreType: WebView.RestoreCurrentSession},
412+ {restoreType: WebView.RestoreLastSessionExitedCleanly},
413+ {restoreType: WebView.RestoreLastSessionCrashed}
414+ ];
415+ }
416+
417+ function init() {
418+ webView = webViewComponent.createObject(this);
419+ }
420+
421+ function cleanup() {
422+ webView.destroy()
423+ }
424+
425+ function test_WebView_save_and_restore_current_page_data() {
426+ return get_restore_types();
427+ }
428+
429+ function test_WebView_save_and_restore_current_page(data) {
430+ webView.url = "http://testsuite/tst_WebView_navigation1.html";
431+ verify(webView.waitForLoadSucceeded(),
432+ "Timed out waiting for successful load");
433+
434+ var state = webView.currentState;
435+ verify(state.length > 0);
436+
437+ var restored = webViewComponent.createObject(
438+ webView, {"restoreType": data.restoreType, "restoreState": state});
439+ verify(restored !== null);
440+ tryCompare(restored, "url", webView.url);
441+ verify(restored.waitForLoadSucceeded(),
442+ "Timed out waiting for successful load");
443+ restored.destroy();
444+ }
445+
446+ function test_WebView_save_and_restore_navigation_history_data() {
447+ return get_restore_types();
448+ }
449+
450+ function test_WebView_save_and_restore_navigation_history(data) {
451+ webView.url = "http://testsuite/tst_WebView_navigation1.html";
452+ verify(webView.waitForLoadSucceeded(),
453+ "Timed out waiting for successful load");
454+ webView.url = "http://testsuite/tst_WebView_navigation2.html";
455+ verify(webView.waitForLoadSucceeded(),
456+ "Timed out waiting for successful load");
457+ webView.url = "http://testsuite/tst_WebView_navigation3.html";
458+ verify(webView.waitForLoadSucceeded(),
459+ "Timed out waiting for successful load");
460+ webView.goBack();
461+ verify(webView.waitForLoadSucceeded(),
462+ "Timed out waiting for successful load");
463+
464+ var state = webView.currentState;
465+ verify(state.length > 0);
466+
467+ var restored = webViewComponent.createObject(
468+ webView, {"restoreType": data.restoreType, "restoreState": state});
469+ verify(restored !== null);
470+ tryCompare(restored, "url", webView.url);
471+ verify(restored.waitForLoadSucceeded(),
472+ "Timed out waiting for successful load");
473+ verify(restored.canGoBack);
474+ verify(restored.canGoForward);
475+ compare(restored.navigationHistory.currentIndex, 1);
476+ restored.destroy();
477+ }
478+
479+ function test_WebView_save_and_restore_scroll_offset_data() {
480+ return get_restore_types();
481+ }
482+
483+ function test_WebView_save_and_restore_scroll_offset(data) {
484+ webView.url = "http://testsuite/tst_WebView_flickableLikeAPI.html";
485+ verify(webView.waitForLoadSucceeded(),
486+ "Timed out waiting for successful load");
487+ webView.getTestApi().evaluateCode("document.body.scrollLeft = 300");
488+ webView.getTestApi().evaluateCode("document.body.scrollTop = 700");
489+
490+ var state = webView.currentState;
491+ verify(state.length > 0);
492+
493+ var restored = webViewComponent.createObject(
494+ webView, {"restoreType": data.restoreType, "restoreState": state});
495+ verify(restored !== null);
496+ tryCompare(restored, "url", webView.url);
497+ verify(restored.waitForLoadSucceeded(),
498+ "Timed out waiting for successful load");
499+ restored.waitFor(function() {
500+ return parseFloat(restored.getTestApi().evaluateCode(
501+ "document.body.scrollLeft")) == 300; });
502+ restored.waitFor(function() {
503+ return parseFloat(restored.getTestApi().evaluateCode(
504+ "document.body.scrollTop")) == 700; });
505+ restored.destroy();
506+ }
507+
508+ function test_WebView_save_and_restore_form_data_input_data() {
509+ return get_restore_types();
510+ }
511+
512+ function test_WebView_save_and_restore_form_data_input(data) {
513+ webView.url = "http://testsuite/tst_WebView_save_restore_form_data.html";
514+ verify(webView.waitForLoadSucceeded(),
515+ "Timed out waiting for successful load");
516+ keyClick("T");
517+ keyClick("e");
518+ keyClick("$");
519+ keyClick("t");
520+ webView.waitFor(function() {
521+ return webView.getTestApi().evaluateCode(
522+ "document.querySelector('#textInput').value") == "Te$t"; });
523+
524+ var state = webView.currentState;
525+ verify(state.length > 0);
526+
527+ var restored = webViewComponent.createObject(
528+ webView, {"restoreType": data.restoreType, "restoreState": state});
529+ verify(restored !== null);
530+ tryCompare(restored, "url", webView.url);
531+ verify(restored.waitForLoadSucceeded(),
532+ "Timed out waiting for successful load");
533+ restored.waitFor(function() {
534+ return restored.getTestApi().evaluateCode(
535+ "document.querySelector('#textInput').value") == "Te$t"; });
536+ restored.destroy();
537+ }
538+}
539
540=== modified file 'shared/browser/oxide_web_view.cc'
541--- shared/browser/oxide_web_view.cc 2014-11-08 00:28:36 +0000
542+++ shared/browser/oxide_web_view.cc 2014-11-20 12:42:26 +0000
543@@ -26,6 +26,7 @@
544 #include "base/strings/string_util.h"
545 #include "base/strings/utf_string_conversions.h"
546 #include "base/supports_user_data.h"
547+#include "components/sessions/content/content_serialized_navigation_builder.h"
548 #include "content/browser/frame_host/frame_tree.h"
549 #include "content/browser/frame_host/frame_tree_node.h"
550 #include "content/browser/frame_host/render_frame_host_impl.h"
551@@ -1154,6 +1155,8 @@
552 compositor_(Compositor::Create(this, ShouldUseSoftwareCompositing())),
553 gesture_provider_(GestureProvider::Create(this)),
554 in_swap_(false),
555+ restore_type_(content::NavigationController::RESTORE_LAST_SESSION_EXITED_CLEANLY),
556+ initial_index_(0),
557 root_frame_(NULL),
558 is_fullscreen_(false),
559 blocked_content_(CONTENT_TYPE_NONE),
560@@ -1259,6 +1262,17 @@
561 content::WebContents::Create(content_params)));
562 CHECK(web_contents_.get()) << "Failed to create WebContents";
563
564+ if (!restore_state_.empty()) {
565+ ScopedVector<content::NavigationEntry> entries =
566+ sessions::ContentSerializedNavigationBuilder::ToNavigationEntries(
567+ restore_state_, context);
568+ web_contents_->GetController().Restore(
569+ initial_index_,
570+ content::NavigationController::RESTORE_LAST_SESSION_EXITED_CLEANLY,
571+ &entries.get());
572+ restore_state_.clear();
573+ }
574+
575 new WebViewContentsHelper(web_contents_.get());
576
577 compositor_->SetViewportSize(GetViewSizePix());
578@@ -1300,6 +1314,8 @@
579 }
580 }
581
582+ web_contents_->GetController().LoadIfNecessary();
583+
584 SetIsFullscreen(is_fullscreen_);
585
586 DCHECK(std::find(g_all_web_views.Get().begin(),
587@@ -1353,6 +1369,36 @@
588 web_contents_->GetController().LoadURLWithParams(params);
589 }
590
591+std::vector<sessions::SerializedNavigationEntry> WebView::GetState() const {
592+ std::vector<sessions::SerializedNavigationEntry> entries;
593+ if (!web_contents_) {
594+ return entries;
595+ }
596+ const content::NavigationController& controller = web_contents_->GetController();
597+ const int pending_index = controller.GetPendingEntryIndex();
598+ int entry_count = controller.GetEntryCount();
599+ if (entry_count == 0 && pending_index == 0) {
600+ entry_count++;
601+ }
602+ entries.resize(entry_count);
603+ for (int i = 0; i < entry_count; ++i) {
604+ content::NavigationEntry* entry = (i == pending_index) ?
605+ controller.GetPendingEntry() : controller.GetEntryAtIndex(i);
606+ entries[i] =
607+ sessions::ContentSerializedNavigationBuilder::FromNavigationEntry(i, *entry);
608+ }
609+ return entries;
610+}
611+
612+void WebView::SetState(content::NavigationController::RestoreType type,
613+ std::vector<sessions::SerializedNavigationEntry> state,
614+ int index) {
615+ DCHECK(!web_contents_);
616+ restore_type_ = type;
617+ restore_state_ = state;
618+ initial_index_ = index;
619+}
620+
621 void WebView::LoadData(const std::string& encodedData,
622 const std::string& mimeType,
623 const GURL& baseUrl) {
624
625=== modified file 'shared/browser/oxide_web_view.h'
626--- shared/browser/oxide_web_view.h 2014-11-07 20:31:37 +0000
627+++ shared/browser/oxide_web_view.h 2014-11-20 12:42:26 +0000
628@@ -30,6 +30,7 @@
629 #include "base/strings/string16.h"
630 #include "base/timer/timer.h"
631 #include "cc/output/compositor_frame_metadata.h"
632+#include "components/sessions/serialized_navigation_entry.h"
633 #include "content/browser/renderer_host/event_with_latency_info.h"
634 #include "content/common/input/input_event_ack_state.h"
635 #include "content/public/browser/certificate_request_result_type.h"
636@@ -146,6 +147,13 @@
637 public:
638 virtual ~WebView();
639
640+ // Maps to content::NavigationController::RestoreType
641+ enum RestoreType {
642+ RESTORE_CURRENT_SESSION,
643+ RESTORE_LAST_SESSION_EXITED_CLEANLY,
644+ RESTORE_LAST_SESSION_CRASHED,
645+ };
646+
647 struct Params {
648 Params() :
649 context(NULL),
650@@ -167,6 +175,11 @@
651 const GURL& GetURL() const;
652 void SetURL(const GURL& url);
653
654+ std::vector<sessions::SerializedNavigationEntry> GetState() const;
655+ void SetState(content::NavigationController::RestoreType type,
656+ std::vector<sessions::SerializedNavigationEntry> state,
657+ int index);
658+
659 void LoadData(const std::string& encodedData,
660 const std::string& mimeType,
661 const GURL& baseUrl);
662@@ -551,6 +564,9 @@
663
664 GURL initial_url_;
665 scoped_ptr<content::NavigationController::LoadURLParams> initial_data_;
666+ content::NavigationController::RestoreType restore_type_;
667+ std::vector<sessions::SerializedNavigationEntry> restore_state_;
668+ int initial_index_;
669
670 content::NotificationRegistrar registrar_;
671 WebFrame* root_frame_;
672
673=== modified file 'shared/shared.gyp'
674--- shared/shared.gyp 2014-11-14 11:51:24 +0000
675+++ shared/shared.gyp 2014-11-20 12:42:26 +0000
676@@ -183,6 +183,7 @@
677 '<(DEPTH)/build/linux/system.gyp:dbus',
678 '<(DEPTH)/build/linux/system.gyp:fontconfig',
679 '<(DEPTH)/cc/cc.gyp:cc',
680+ '<(DEPTH)/components/components.gyp:sessions_content',
681 '<(DEPTH)/content/content.gyp:content_app_both',
682 '<(DEPTH)/content/content.gyp:content_browser',
683 '<(DEPTH)/content/content.gyp:content_child',

Subscribers

People subscribed via source and target branches