Merge lp:~osomon/oxide/save-restore-state into lp:~oxide-developers/oxide/oxide.trunk
- save-restore-state
- Merge into 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 | ||||
Related bugs: |
|
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.
Description of the change
To post a comment you must log in.
- 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
- 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', |
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: :NavigationCont roller: :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 "RestoreTypeLas tSessionCrashed " 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() ).