Merge lp:~osomon/webbrowser-app/lazy-webviews into lp:webbrowser-app
- lazy-webviews
- Merge into trunk
Status: | Merged | ||||||||
---|---|---|---|---|---|---|---|---|---|
Approved by: | Michael Sheldon | ||||||||
Approved revision: | 695 | ||||||||
Merged at revision: | 686 | ||||||||
Proposed branch: | lp:~osomon/webbrowser-app/lazy-webviews | ||||||||
Merge into: | lp:webbrowser-app | ||||||||
Diff against target: |
1200 lines (+314/-203) 12 files modified
po/webbrowser-app.pot (+17/-13) src/app/webbrowser/Browser.qml (+98/-41) src/app/webbrowser/TabPreview.qml (+33/-9) src/app/webbrowser/TabsView.qml (+11/-7) src/app/webbrowser/tabs-model.cpp (+36/-36) src/app/webbrowser/tabs-model.h (+7/-7) src/app/webbrowser/webbrowser-app.qml (+2/-1) tests/autopilot/webbrowser_app/emulators/browser.py (+3/-0) tests/autopilot/webbrowser_app/tests/__init__.py (+6/-0) tests/autopilot/webbrowser_app/tests/test_session_save_restore.py (+1/-0) tests/autopilot/webbrowser_app/tests/test_tabs.py (+6/-1) tests/unittests/tabs-model/tst_TabsModelTests.cpp (+94/-88) |
||||||||
To merge this branch: | bzr merge lp:~osomon/webbrowser-app/lazy-webviews | ||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot | continuous-integration | Needs Fixing | |
Michael Sheldon (community) | Approve | ||
Review via email: mp+231576@code.launchpad.net |
Commit message
Instantiate webviews on demand, only when they really need to be shown.
Show placeholder artwork and text in empty tab previews.
Description of the change
- 688. By Olivier Tilloy
-
Add placeholder artwork and text for empty tab previews.
- 689. By Olivier Tilloy
-
Add missing asset.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:687
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
deb: http://
FAILURE: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
- 690. By Olivier Tilloy
-
Fix the "Open [link|image] in new tab" contextual actions to instantiate the requested webview right away.
Olivier Tilloy (osomon) wrote : | # |
> The "Open link in new tab" behaviour seems to be broken, the currently focused
> tab remains visible but unresponsive to interaction (the new tab can be
> switched to via the "Open tabs" menu and switching back to the original tab
> restores responsiveness). Other than that it's looking really good!
Good catch, thanks Michael! I keep meaning to write autopilot tests for the contextual actions, but never get around to doing it. I’ll see if I can bump the priority of this task, such tests would have caught this regression. I fixed it with revision 690.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:689
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:690
http://
Executed test runs:
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
- 691. By Olivier Tilloy
-
Ensure the tab previews fill up the available vertical space if there aren’t enough of them to fill it with their default height.
- 692. By Olivier Tilloy
-
Fix a harmless warning.
- 693. By Olivier Tilloy
-
Update translation template.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:693
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
- 694. By Olivier Tilloy
-
Fix opening new tabs from the URL dispatcher.
Changed the default behaviour of openUrlInNewTab() to instantiate the corresponding webview by default (can be overridden). - 695. By Olivier Tilloy
-
Update translation template again.
Michael Sheldon (michael-sheldon) wrote : | # |
All working very nicely now!
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:695
http://
Executed test runs:
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
Preview Diff
1 | === modified file 'po/webbrowser-app.pot' |
2 | --- po/webbrowser-app.pot 2014-08-14 09:15:21 +0000 |
3 | +++ po/webbrowser-app.pot 2014-08-21 11:29:14 +0000 |
4 | @@ -8,7 +8,7 @@ |
5 | msgstr "" |
6 | "Project-Id-Version: webbrowser-app\n" |
7 | "Report-Msgid-Bugs-To: \n" |
8 | -"POT-Creation-Date: 2014-08-14 11:13+0200\n" |
9 | +"POT-Creation-Date: 2014-08-21 13:27+0200\n" |
10 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" |
11 | "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" |
12 | "Language-Team: LANGUAGE <LL@li.org>\n" |
13 | @@ -249,30 +249,30 @@ |
14 | msgid "search or enter an address" |
15 | msgstr "" |
16 | |
17 | -#: src/app/webbrowser/Browser.qml:150 |
18 | +#: src/app/webbrowser/Browser.qml:154 |
19 | msgid "Share" |
20 | msgstr "" |
21 | |
22 | -#: src/app/webbrowser/Browser.qml:164 |
23 | +#: src/app/webbrowser/Browser.qml:168 |
24 | msgid "History" |
25 | msgstr "" |
26 | |
27 | -#: src/app/webbrowser/Browser.qml:170 |
28 | +#: src/app/webbrowser/Browser.qml:174 |
29 | msgid "Open tabs" |
30 | msgstr "" |
31 | |
32 | -#: src/app/webbrowser/Browser.qml:176 src/app/webbrowser/TabsView.qml:55 |
33 | +#: src/app/webbrowser/Browser.qml:180 src/app/webbrowser/TabsView.qml:57 |
34 | msgid "New tab" |
35 | msgstr "" |
36 | |
37 | -#: src/app/webbrowser/ExpandedHistoryView.qml:99 |
38 | +#: src/app/webbrowser/ExpandedHistoryView.qml:107 |
39 | #, qt-format |
40 | msgid "%1 page" |
41 | msgid_plural "%1 pages" |
42 | msgstr[0] "" |
43 | msgstr[1] "" |
44 | |
45 | -#: src/app/webbrowser/ExpandedHistoryView.qml:113 |
46 | +#: src/app/webbrowser/ExpandedHistoryView.qml:121 |
47 | msgid "Less" |
48 | msgstr "" |
49 | |
50 | @@ -284,23 +284,23 @@ |
51 | msgid "Yesterday" |
52 | msgstr "" |
53 | |
54 | -#: src/app/webbrowser/HistoryView.qml:95 src/app/webbrowser/TabsView.qml:96 |
55 | +#: src/app/webbrowser/HistoryView.qml:99 src/app/webbrowser/TabsView.qml:100 |
56 | msgid "Done" |
57 | msgstr "" |
58 | |
59 | -#: src/app/webbrowser/HistoryView.qml:109 |
60 | +#: src/app/webbrowser/HistoryView.qml:113 |
61 | msgid "Clear" |
62 | msgstr "" |
63 | |
64 | -#: src/app/webbrowser/HistoryView.qml:123 |
65 | +#: src/app/webbrowser/HistoryView.qml:127 |
66 | msgid "Delete all history?" |
67 | msgstr "" |
68 | |
69 | -#: src/app/webbrowser/HistoryView.qml:126 |
70 | +#: src/app/webbrowser/HistoryView.qml:130 |
71 | msgid "Yes" |
72 | msgstr "" |
73 | |
74 | -#: src/app/webbrowser/HistoryView.qml:135 |
75 | +#: src/app/webbrowser/HistoryView.qml:139 |
76 | msgid "No" |
77 | msgstr "" |
78 | |
79 | @@ -320,7 +320,11 @@ |
80 | msgid "see more" |
81 | msgstr "" |
82 | |
83 | -#: src/app/webbrowser/TabsView.qml:111 |
84 | +#: src/app/webbrowser/TabPreview.qml:162 |
85 | +msgid "Tap to view" |
86 | +msgstr "" |
87 | + |
88 | +#: src/app/webbrowser/TabsView.qml:115 |
89 | msgid "Add" |
90 | msgstr "" |
91 | |
92 | |
93 | === modified file 'src/app/webbrowser/Browser.qml' |
94 | --- src/app/webbrowser/Browser.qml 2014-08-18 13:04:52 +0000 |
95 | +++ src/app/webbrowser/Browser.qml 2014-08-21 11:29:14 +0000 |
96 | @@ -27,7 +27,7 @@ |
97 | BrowserView { |
98 | id: browser |
99 | |
100 | - currentWebview: tabsModel.currentWebview |
101 | + currentWebview: tabsModel.currentTab ? tabsModel.currentTab.webview : null |
102 | |
103 | property url homepage |
104 | property QtObject searchEngine |
105 | @@ -53,7 +53,7 @@ |
106 | onTriggered: _bookmarksModel.add(currentWebview.url, currentWebview.title, currentWebview.icon) |
107 | }, |
108 | Actions.NewTab { |
109 | - onTriggered: openUrlInNewTab("", true) |
110 | + onTriggered: browser.openUrlInNewTab("", true) |
111 | }, |
112 | Actions.ClearHistory { |
113 | onTriggered: _historyModel.clearAll() |
114 | @@ -63,9 +63,9 @@ |
115 | Item { |
116 | id: previewsContainer |
117 | |
118 | - width: webviewContainer.width |
119 | - height: webviewContainer.height |
120 | - y: webviewContainer.y |
121 | + width: tabContainer.width |
122 | + height: tabContainer.height |
123 | + y: tabContainer.y |
124 | |
125 | Component { |
126 | id: previewComponent |
127 | @@ -73,11 +73,15 @@ |
128 | ShaderEffectSource { |
129 | id: preview |
130 | |
131 | + property var tab |
132 | + |
133 | width: parent.width |
134 | height: parent.height |
135 | |
136 | - onSourceItemChanged: { |
137 | - if (!sourceItem) { |
138 | + sourceItem: tab ? tab.webview : null |
139 | + |
140 | + onTabChanged: { |
141 | + if (!tab) { |
142 | this.destroy() |
143 | } |
144 | } |
145 | @@ -94,7 +98,7 @@ |
146 | visible: !historyViewContainer.visible && !tabsViewContainer.visible |
147 | |
148 | Item { |
149 | - id: webviewContainer |
150 | + id: tabContainer |
151 | anchors { |
152 | left: parent.left |
153 | right: parent.right |
154 | @@ -104,7 +108,7 @@ |
155 | } |
156 | |
157 | ErrorSheet { |
158 | - anchors.fill: webviewContainer |
159 | + anchors.fill: tabContainer |
160 | visible: currentWebview ? currentWebview.lastLoadFailed : false |
161 | url: currentWebview ? currentWebview.url : "" |
162 | onRefreshClicked: currentWebview.reload() |
163 | @@ -214,7 +218,7 @@ |
164 | horizontalCenter: parent.horizontalCenter |
165 | } |
166 | width: chrome.width - units.gu(5) |
167 | - height: enabled ? Math.min(contentHeight, webviewContainer.height - units.gu(2)) : 0 |
168 | + height: enabled ? Math.min(contentHeight, tabContainer.height - units.gu(2)) : 0 |
169 | model: historyMatches |
170 | onSelected: { |
171 | browser.currentWebview.url = url |
172 | @@ -235,8 +239,11 @@ |
173 | TabsView { |
174 | anchors.fill: parent |
175 | model: tabsModel |
176 | - onNewTabRequested: browser.openUrlInNewTab("", true) |
177 | - onDone: this.destroy() |
178 | + onNewTabRequested: browser.openUrlInNewTab("", true, false) |
179 | + onDone: { |
180 | + tabsModel.currentTab.load() |
181 | + this.destroy() |
182 | + } |
183 | } |
184 | } |
185 | } |
186 | @@ -308,14 +315,50 @@ |
187 | } |
188 | |
189 | Component { |
190 | + id: tabComponent |
191 | + |
192 | + FocusScope { |
193 | + property url initialUrl |
194 | + property string initialTitle |
195 | + property var request |
196 | + readonly property var webview: (children.length == 1) ? children[0] : null |
197 | + readonly property url url: webview ? webview.url : initialUrl |
198 | + readonly property string title: webview ? webview.title : initialTitle |
199 | + readonly property url icon: webview ? webview.icon : "" |
200 | + property var preview |
201 | + |
202 | + anchors.fill: parent |
203 | + |
204 | + function load() { |
205 | + if (!webview) { |
206 | + webviewComponent.createObject(this, {"url": initialUrl}) |
207 | + } |
208 | + } |
209 | + |
210 | + function unload() { |
211 | + if (webview) { |
212 | + webview.destroy() |
213 | + } |
214 | + } |
215 | + |
216 | + Component.onCompleted: { |
217 | + if (request) { |
218 | + // Instantiating the webview cannot be delayed because the request |
219 | + // object is destroyed after exiting the newViewRequested signal handler. |
220 | + webviewComponent.createObject(this, {"request": request}) |
221 | + } |
222 | + } |
223 | + } |
224 | + } |
225 | + |
226 | + Component { |
227 | id: webviewComponent |
228 | |
229 | WebViewImpl { |
230 | currentWebview: browser.currentWebview |
231 | |
232 | - property var preview |
233 | - |
234 | anchors.fill: parent |
235 | + focus: true |
236 | |
237 | readonly property bool current: currentWebview === this |
238 | enabled: current |
239 | @@ -328,7 +371,7 @@ |
240 | contextualActions: ActionList { |
241 | Actions.OpenLinkInNewTab { |
242 | enabled: contextualData.href.toString() |
243 | - onTriggered: openUrlInNewTab(contextualData.href, true) |
244 | + onTriggered: browser.openUrlInNewTab(contextualData.href, true) |
245 | } |
246 | Actions.BookmarkLink { |
247 | enabled: contextualData.href.toString() |
248 | @@ -340,7 +383,7 @@ |
249 | } |
250 | Actions.OpenImageInNewTab { |
251 | enabled: contextualData.img.toString() |
252 | - onTriggered: openUrlInNewTab(contextualData.img, true) |
253 | + onTriggered: browser.openUrlInNewTab(contextualData.img, true) |
254 | } |
255 | Actions.CopyImage { |
256 | enabled: contextualData.img.toString() |
257 | @@ -353,9 +396,9 @@ |
258 | } |
259 | |
260 | onNewViewRequested: { |
261 | - var webview = webviewComponent.createObject(webviewContainer, {"request": request}) |
262 | + var tab = tabComponent.createObject(tabContainer, {"request": request}) |
263 | var setCurrent = (request.disposition == Oxide.NewViewRequest.DispositionNewForegroundTab) |
264 | - internal.addTab(webview, setCurrent, false) |
265 | + internal.addTab(tab, setCurrent, false) |
266 | } |
267 | |
268 | onLoadingChanged: { |
269 | @@ -402,22 +445,30 @@ |
270 | QtObject { |
271 | id: internal |
272 | |
273 | - function addTab(webview, setCurrent, focusAddressBar) { |
274 | - var index = tabsModel.add(webview) |
275 | + function addTab(tab, setCurrent, focusAddressBar) { |
276 | + var index = tabsModel.add(tab) |
277 | if (setCurrent) { |
278 | tabsModel.setCurrent(index) |
279 | if (focusAddressBar) { |
280 | - chrome.forceActiveFocus() |
281 | - Qt.inputMethod.show() // work around http://pad.lv/1316057 |
282 | + internal.focusAddressBar() |
283 | } |
284 | } |
285 | - webview.preview = previewComponent.createObject(previewsContainer, {sourceItem: webview}) |
286 | + tab.preview = previewComponent.createObject(previewsContainer, {tab: tab}) |
287 | + } |
288 | + |
289 | + function focusAddressBar() { |
290 | + chrome.forceActiveFocus() |
291 | + Qt.inputMethod.show() // work around http://pad.lv/1316057 |
292 | } |
293 | } |
294 | |
295 | - function openUrlInNewTab(url, setCurrent) { |
296 | - var webview = webviewComponent.createObject(webviewContainer, {"url": url}) |
297 | - internal.addTab(webview, setCurrent, !url.toString() && (formFactor == "desktop")) |
298 | + function openUrlInNewTab(url, setCurrent, load) { |
299 | + load = typeof load !== 'undefined' ? load : true |
300 | + var tab = tabComponent.createObject(tabContainer, {"initialUrl": url}) |
301 | + internal.addTab(tab, setCurrent, !url.toString() && (formFactor == "desktop")) |
302 | + if (load) { |
303 | + tabsModel.currentTab.load() |
304 | + } |
305 | } |
306 | |
307 | SessionStorage { |
308 | @@ -431,9 +482,8 @@ |
309 | } |
310 | var tabs = [] |
311 | for (var i = 0; i < tabsModel.count; ++i) { |
312 | - var webview = tabsModel.get(i) |
313 | - var tab = serializeWebviewState(webview) |
314 | - tabs.push(tab) |
315 | + var tab = tabsModel.get(i) |
316 | + tabs.push(serializeTabState(tab)) |
317 | } |
318 | store(JSON.stringify({tabs: tabs})) |
319 | } |
320 | @@ -452,46 +502,53 @@ |
321 | var tabs = state.tabs |
322 | if (tabs) { |
323 | for (var i = 0; i < tabs.length; ++i) { |
324 | - var webview = createWebviewFromState(tabs[i]) |
325 | - internal.addTab(webview, i == 0, false) |
326 | + var tab = createTabFromState(tabs[i]) |
327 | + internal.addTab(tab, i == 0, false) |
328 | } |
329 | } |
330 | } |
331 | } |
332 | |
333 | - // Those two functions are used to save/restore the current state of a webview. |
334 | + // Those two functions are used to save/restore the current state of a tab. |
335 | // The current implementation is naive, it only saves/restores the current URL. |
336 | // In the future, we’ll want to rely on oxide to save and restore a full state |
337 | - // of the webview as a binary blob, which includes navigation history, current |
338 | - // scroll offset and form data. See http://pad.lv/1353143. |
339 | - function serializeWebviewState(webview) { |
340 | + // of the corresponding webview as a binary blob, which includes navigation |
341 | + // history, current scroll offset and form data. See http://pad.lv/1353143. |
342 | + function serializeTabState(tab) { |
343 | var state = {} |
344 | - state.url = webview.url.toString() |
345 | + state.url = tab.url.toString() |
346 | + state.title = tab.title |
347 | return state |
348 | } |
349 | |
350 | - function createWebviewFromState(state) { |
351 | - return webviewComponent.createObject(webviewContainer, {"url": state.url}) |
352 | + function createTabFromState(state) { |
353 | + var properties = {"initialUrl": state.url, "initialTitle": state.title} |
354 | + return tabComponent.createObject(tabContainer, properties) |
355 | } |
356 | } |
357 | Connections { |
358 | target: tabsModel |
359 | - onCurrentWebviewChanged: session.save() |
360 | + onCurrentTabChanged: session.save() |
361 | onCountChanged: session.save() |
362 | } |
363 | Connections { |
364 | target: browser.currentWebview |
365 | onUrlChanged: session.save() |
366 | + onTitleChanged: session.save() |
367 | } |
368 | Component.onCompleted: { |
369 | if (browser.restoreSession) { |
370 | session.restore() |
371 | } |
372 | for (var i in browser.initialUrls) { |
373 | - browser.openUrlInNewTab(browser.initialUrls[i], true) |
374 | + browser.openUrlInNewTab(browser.initialUrls[i], true, false) |
375 | } |
376 | if (tabsModel.count == 0) { |
377 | - browser.openUrlInNewTab(browser.homepage, true) |
378 | + browser.openUrlInNewTab(browser.homepage, true, false) |
379 | + } |
380 | + tabsModel.currentTab.load() |
381 | + if (!tabsModel.currentTab.url.toString() && (formFactor == "desktop")) { |
382 | + internal.focusAddressBar() |
383 | } |
384 | } |
385 | } |
386 | |
387 | === modified file 'src/app/webbrowser/TabPreview.qml' |
388 | --- src/app/webbrowser/TabPreview.qml 2014-08-12 06:04:50 +0000 |
389 | +++ src/app/webbrowser/TabPreview.qml 2014-08-21 11:29:14 +0000 |
390 | @@ -20,11 +20,11 @@ |
391 | import Ubuntu.Components 1.1 |
392 | |
393 | Column { |
394 | - id: tab |
395 | + id: tabPreview |
396 | |
397 | property alias title: label.text |
398 | - property var webview |
399 | - readonly property url url: webview ? webview.url : "" |
400 | + property var tab |
401 | + readonly property url url: tab ? tab.url : "" |
402 | |
403 | signal selected() |
404 | signal closeRequested() |
405 | @@ -56,7 +56,7 @@ |
406 | name: "close" |
407 | } |
408 | |
409 | - onTriggered: tab.closeRequested() |
410 | + onTriggered: tabPreview.closeRequested() |
411 | |
412 | Rectangle { |
413 | anchors { |
414 | @@ -122,7 +122,7 @@ |
415 | } |
416 | width: parent.width / 2 |
417 | |
418 | - onClicked: tab.selected() |
419 | + onClicked: tabPreview.selected() |
420 | } |
421 | } |
422 | } |
423 | @@ -143,15 +143,39 @@ |
424 | width: parent.width |
425 | height: parent.height - header.height |
426 | |
427 | + Image { |
428 | + visible: !previewContainer.visible |
429 | + source: "assets/tab-artwork.png" |
430 | + fillMode: Image.PreserveAspectFit |
431 | + height: Math.min(parent.height / 1.6, units.gu(28)) |
432 | + width: height |
433 | + anchors { |
434 | + right: parent.right |
435 | + rightMargin: -width / 5 |
436 | + bottom: parent.bottom |
437 | + bottomMargin: -height / 10 |
438 | + } |
439 | + } |
440 | + |
441 | + Label { |
442 | + visible: !previewContainer.visible |
443 | + text: i18n.tr("Tap to view") |
444 | + anchors { |
445 | + centerIn: parent |
446 | + verticalCenterOffset: units.gu(-2) |
447 | + } |
448 | + } |
449 | + |
450 | Item { |
451 | id: previewContainer |
452 | + visible: tabPreview.tab ? tabPreview.tab.webview : false |
453 | anchors.fill: parent |
454 | clip: true |
455 | } |
456 | |
457 | MouseArea { |
458 | anchors.fill: parent |
459 | - onClicked: tab.selected() |
460 | + onClicked: tabPreview.selected() |
461 | } |
462 | |
463 | Rectangle { |
464 | @@ -172,15 +196,15 @@ |
465 | } |
466 | |
467 | Component.onCompleted: { |
468 | - var preview = tab.webview.preview |
469 | + var preview = tabPreview.tab.preview |
470 | internal.previewParent = preview.parent |
471 | preview.parent = previewContainer |
472 | preview.width = internal.previewParent.width |
473 | preview.height = internal.previewParent.height |
474 | } |
475 | Component.onDestruction: { |
476 | - if (tab.webview) { |
477 | - var preview = tab.webview.preview |
478 | + if (tabPreview.tab) { |
479 | + var preview = tabPreview.tab.preview |
480 | preview.parent = internal.previewParent |
481 | preview.width = preview.parent.width |
482 | preview.height = preview.parent.height |
483 | |
484 | === modified file 'src/app/webbrowser/TabsView.qml' |
485 | --- src/app/webbrowser/TabsView.qml 2014-08-08 17:29:21 +0000 |
486 | +++ src/app/webbrowser/TabsView.qml 2014-08-21 11:29:14 +0000 |
487 | @@ -43,27 +43,31 @@ |
488 | bottom: toolbar.top |
489 | } |
490 | |
491 | - spacing: units.gu(-10) |
492 | + spacing: units.gu(-5) |
493 | |
494 | boundsBehavior: Flickable.StopAtBounds |
495 | |
496 | delegate: TabPreview { |
497 | width: parent.width |
498 | - height: (listview.count == 1) ? listview.height : units.gu(40) |
499 | + readonly property real minHeight: units.gu(35) |
500 | + height: (listview.count * minHeight + (listview.count - 1) * listview.spacing) < listview.height ? (listview.height + (1 - listview.count) * listview.spacing) / listview.count : minHeight |
501 | + |
502 | z: index |
503 | |
504 | title: model.title ? model.title : (model.url.toString() ? model.url : i18n.tr("New tab")) |
505 | - webview: model.webview |
506 | + tab: model.tab |
507 | |
508 | onSelected: { |
509 | + tab.load() |
510 | + tab.forceActiveFocus() |
511 | tabsview.model.setCurrent(index) |
512 | - webview.forceActiveFocus() |
513 | tabsview.done() |
514 | } |
515 | onCloseRequested: { |
516 | - var webview = tabsview.model.remove(index) |
517 | - if (webview) { |
518 | - webview.destroy() |
519 | + var tab = tabsview.model.remove(index) |
520 | + if (tab) { |
521 | + tab.unload() |
522 | + tab.destroy() |
523 | } |
524 | if (tabsview.model.count === 0) { |
525 | tabsview.newTabRequested() |
526 | |
527 | === added file 'src/app/webbrowser/assets/tab-artwork.png' |
528 | Binary files src/app/webbrowser/assets/tab-artwork.png 1970-01-01 00:00:00 +0000 and src/app/webbrowser/assets/tab-artwork.png 2014-08-21 11:29:14 +0000 differ |
529 | === modified file 'src/app/webbrowser/tabs-model.cpp' |
530 | --- src/app/webbrowser/tabs-model.cpp 2014-08-06 13:57:06 +0000 |
531 | +++ src/app/webbrowser/tabs-model.cpp 2014-08-21 11:29:14 +0000 |
532 | @@ -27,11 +27,11 @@ |
533 | \brief List model that stores the list of currently open tabs. |
534 | |
535 | TabsModel is a list model that stores the list of currently open tabs. |
536 | - Each tab holds a pointer to a WebView and associated metadata (URL, title, |
537 | + Each tab holds a pointer to a Tab and associated metadata (URL, title, |
538 | icon). |
539 | |
540 | - The model doesn’t own the WebView, so it is the responsibility of whoever |
541 | - adds a tab to instantiate the corresponding WebView, and to destroy it after |
542 | + The model doesn’t own the Tab, so it is the responsibility of whoever |
543 | + adds a tab to instantiate the corresponding Tab, and to destroy it after |
544 | it’s removed from the model. |
545 | */ |
546 | TabsModel::TabsModel(QObject* parent) |
547 | @@ -50,7 +50,7 @@ |
548 | roles[Url] = "url"; |
549 | roles[Title] = "title"; |
550 | roles[Icon] = "icon"; |
551 | - roles[WebView] = "webview"; |
552 | + roles[Tab] = "tab"; |
553 | } |
554 | return roles; |
555 | } |
556 | @@ -58,7 +58,7 @@ |
557 | int TabsModel::rowCount(const QModelIndex& parent) const |
558 | { |
559 | Q_UNUSED(parent); |
560 | - return m_webviews.count(); |
561 | + return m_tabs.count(); |
562 | } |
563 | |
564 | QVariant TabsModel::data(const QModelIndex& index, int role) const |
565 | @@ -66,61 +66,61 @@ |
566 | if (!index.isValid()) { |
567 | return QVariant(); |
568 | } |
569 | - QObject* webview = m_webviews.at(index.row()); |
570 | + QObject* tab = m_tabs.at(index.row()); |
571 | switch (role) { |
572 | case Url: |
573 | - return webview->property("url"); |
574 | + return tab->property("url"); |
575 | case Title: |
576 | - return webview->property("title"); |
577 | + return tab->property("title"); |
578 | case Icon: |
579 | - return webview->property("icon"); |
580 | - case WebView: |
581 | - return QVariant::fromValue(webview); |
582 | + return tab->property("icon"); |
583 | + case Tab: |
584 | + return QVariant::fromValue(tab); |
585 | default: |
586 | return QVariant(); |
587 | } |
588 | } |
589 | |
590 | -QObject* TabsModel::currentWebview() const |
591 | +QObject* TabsModel::currentTab() const |
592 | { |
593 | - if (m_webviews.isEmpty()) { |
594 | + if (m_tabs.isEmpty()) { |
595 | return 0; |
596 | } |
597 | - return m_webviews.first(); |
598 | + return m_tabs.first(); |
599 | } |
600 | |
601 | /*! |
602 | Add a tab to the model and return the corresponding index in the model. |
603 | |
604 | It is the responsibility of the caller to instantiate the corresponding |
605 | - WebView beforehand. |
606 | + Tab beforehand. |
607 | */ |
608 | -int TabsModel::add(QObject* webview) |
609 | +int TabsModel::add(QObject* tab) |
610 | { |
611 | - if (webview == 0) { |
612 | - qWarning() << "Invalid WebView"; |
613 | + if (tab == 0) { |
614 | + qWarning() << "Invalid Tab"; |
615 | return -1; |
616 | } |
617 | - int index = m_webviews.count(); |
618 | + int index = m_tabs.count(); |
619 | beginInsertRows(QModelIndex(), index, index); |
620 | - m_webviews.append(webview); |
621 | - connect(webview, SIGNAL(urlChanged()), SLOT(onUrlChanged())); |
622 | - connect(webview, SIGNAL(titleChanged()), SLOT(onTitleChanged())); |
623 | - connect(webview, SIGNAL(iconChanged()), SLOT(onIconChanged())); |
624 | + m_tabs.append(tab); |
625 | + connect(tab, SIGNAL(urlChanged()), SLOT(onUrlChanged())); |
626 | + connect(tab, SIGNAL(titleChanged()), SLOT(onTitleChanged())); |
627 | + connect(tab, SIGNAL(iconChanged()), SLOT(onIconChanged())); |
628 | endInsertRows(); |
629 | Q_EMIT countChanged(); |
630 | if (index == 0) { |
631 | - Q_EMIT currentWebviewChanged(); |
632 | + Q_EMIT currentTabChanged(); |
633 | } |
634 | return index; |
635 | } |
636 | |
637 | /*! |
638 | Given its index, remove a tab from the model, and return the corresponding |
639 | - WebView. |
640 | + Tab. |
641 | |
642 | It is the responsibility of the caller to destroy the corresponding |
643 | - WebView afterwards. |
644 | + Tab afterwards. |
645 | */ |
646 | QObject* TabsModel::remove(int index) |
647 | { |
648 | @@ -128,14 +128,14 @@ |
649 | return 0; |
650 | } |
651 | beginRemoveRows(QModelIndex(), index, index); |
652 | - QObject* webview = m_webviews.takeAt(index); |
653 | - webview->disconnect(this); |
654 | + QObject* tab = m_tabs.takeAt(index); |
655 | + tab->disconnect(this); |
656 | endRemoveRows(); |
657 | Q_EMIT countChanged(); |
658 | if (index == 0) { |
659 | - Q_EMIT currentWebviewChanged(); |
660 | + Q_EMIT currentTabChanged(); |
661 | } |
662 | - return webview; |
663 | + return tab; |
664 | } |
665 | |
666 | void TabsModel::setCurrent(int index) |
667 | @@ -147,9 +147,9 @@ |
668 | return; |
669 | } |
670 | beginMoveRows(QModelIndex(), index, index, QModelIndex(), 0); |
671 | - m_webviews.prepend(m_webviews.takeAt(index)); |
672 | + m_tabs.prepend(m_tabs.takeAt(index)); |
673 | endMoveRows(); |
674 | - Q_EMIT currentWebviewChanged(); |
675 | + Q_EMIT currentTabChanged(); |
676 | } |
677 | |
678 | QObject* TabsModel::get(int index) const |
679 | @@ -157,21 +157,21 @@ |
680 | if (!checkValidTabIndex(index)) { |
681 | return 0; |
682 | } |
683 | - return m_webviews.at(index); |
684 | + return m_tabs.at(index); |
685 | } |
686 | |
687 | bool TabsModel::checkValidTabIndex(int index) const |
688 | { |
689 | - if ((index < 0) || (index >= m_webviews.count())) { |
690 | + if ((index < 0) || (index >= m_tabs.count())) { |
691 | qWarning() << "Invalid tab index:" << index; |
692 | return false; |
693 | } |
694 | return true; |
695 | } |
696 | |
697 | -void TabsModel::onDataChanged(QObject* webview, int role) |
698 | +void TabsModel::onDataChanged(QObject* tab, int role) |
699 | { |
700 | - int index = m_webviews.indexOf(webview); |
701 | + int index = m_tabs.indexOf(tab); |
702 | if (checkValidTabIndex(index)) { |
703 | Q_EMIT dataChanged(this->index(index, 0), this->index(index, 0), QVector<int>() << role); |
704 | } |
705 | |
706 | === modified file 'src/app/webbrowser/tabs-model.h' |
707 | --- src/app/webbrowser/tabs-model.h 2014-08-06 13:57:06 +0000 |
708 | +++ src/app/webbrowser/tabs-model.h 2014-08-21 11:29:14 +0000 |
709 | @@ -31,7 +31,7 @@ |
710 | |
711 | Q_ENUMS(Roles) |
712 | |
713 | - Q_PROPERTY(QObject* currentWebview READ currentWebview NOTIFY currentWebviewChanged) |
714 | + Q_PROPERTY(QObject* currentTab READ currentTab NOTIFY currentTabChanged) |
715 | Q_PROPERTY(int count READ rowCount NOTIFY countChanged) |
716 | |
717 | public: |
718 | @@ -42,7 +42,7 @@ |
719 | Url = Qt::UserRole + 1, |
720 | Title, |
721 | Icon, |
722 | - WebView |
723 | + Tab |
724 | }; |
725 | |
726 | // reimplemented from QAbstractListModel |
727 | @@ -50,15 +50,15 @@ |
728 | int rowCount(const QModelIndex& parent=QModelIndex()) const; |
729 | QVariant data(const QModelIndex& index, int role) const; |
730 | |
731 | - QObject* currentWebview() const; |
732 | + QObject* currentTab() const; |
733 | |
734 | - Q_INVOKABLE int add(QObject* webview); |
735 | + Q_INVOKABLE int add(QObject* tab); |
736 | Q_INVOKABLE QObject* remove(int index); |
737 | Q_INVOKABLE void setCurrent(int index); |
738 | Q_INVOKABLE QObject* get(int index) const; |
739 | |
740 | Q_SIGNALS: |
741 | - void currentWebviewChanged() const; |
742 | + void currentTabChanged() const; |
743 | void countChanged() const; |
744 | |
745 | private Q_SLOTS: |
746 | @@ -67,10 +67,10 @@ |
747 | void onIconChanged(); |
748 | |
749 | private: |
750 | - QList<QObject*> m_webviews; |
751 | + QList<QObject*> m_tabs; |
752 | |
753 | bool checkValidTabIndex(int index) const; |
754 | - void onDataChanged(QObject* webview, int role); |
755 | + void onDataChanged(QObject* tab, int role); |
756 | }; |
757 | |
758 | #endif // __TABS_MODEL_H__ |
759 | |
760 | === modified file 'src/app/webbrowser/webbrowser-app.qml' |
761 | --- src/app/webbrowser/webbrowser-app.qml 2014-08-08 13:51:25 +0000 |
762 | +++ src/app/webbrowser/webbrowser-app.qml 2014-08-21 11:29:14 +0000 |
763 | @@ -65,7 +65,8 @@ |
764 | target: UriHandler |
765 | onOpened: { |
766 | for (var i = 0; i < uris.length; ++i) { |
767 | - browser.openUrlInNewTab(uris[i], i == uris.length - 1) |
768 | + var setCurrent = (i == uris.length - 1) |
769 | + browser.openUrlInNewTab(uris[i], setCurrent, setCurrent) |
770 | } |
771 | } |
772 | } |
773 | |
774 | === modified file 'tests/autopilot/webbrowser_app/emulators/browser.py' |
775 | --- tests/autopilot/webbrowser_app/emulators/browser.py 2014-08-08 17:29:21 +0000 |
776 | +++ tests/autopilot/webbrowser_app/emulators/browser.py 2014-08-21 11:29:14 +0000 |
777 | @@ -119,6 +119,9 @@ |
778 | def get_current_webview(self): |
779 | return self.select_single("WebViewImpl", current=True) |
780 | |
781 | + def get_webviews(self): |
782 | + return self.select_many("WebViewImpl") |
783 | + |
784 | def get_visible_webviews(self): |
785 | return self.select_many("WebViewImpl", visible=True) |
786 | |
787 | |
788 | === modified file 'tests/autopilot/webbrowser_app/tests/__init__.py' |
789 | --- tests/autopilot/webbrowser_app/tests/__init__.py 2014-08-08 17:29:21 +0000 |
790 | +++ tests/autopilot/webbrowser_app/tests/__init__.py 2014-08-21 11:29:14 +0000 |
791 | @@ -149,16 +149,22 @@ |
792 | self.main_window.get_tabs_view() |
793 | |
794 | def open_new_tab(self): |
795 | + count = len(self.main_window.get_webviews()) |
796 | # assumes the tabs view is already open |
797 | tabs_view = self.main_window.get_tabs_view() |
798 | add_button = tabs_view.get_add_button() |
799 | self.pointing_device.click_object(add_button) |
800 | tabs_view.wait_until_destroyed() |
801 | + self.assert_number_webviews_eventually(count + 1) |
802 | self.main_window.get_new_tab_view() |
803 | if model() == 'Desktop': |
804 | address_bar = self.main_window.get_chrome().get_address_bar() |
805 | self.assertThat(address_bar.activeFocus, Eventually(Equals(True))) |
806 | |
807 | + def assert_number_webviews_eventually(self, count): |
808 | + self.assertThat(lambda: len(self.main_window.get_webviews()), |
809 | + Eventually(Equals(count))) |
810 | + |
811 | def ping_server(self): |
812 | ping = urllib.request.urlopen(self.base_url + "/ping") |
813 | self.assertThat(ping.read(), Equals(b"pong")) |
814 | |
815 | === modified file 'tests/autopilot/webbrowser_app/tests/test_session_save_restore.py' |
816 | --- tests/autopilot/webbrowser_app/tests/test_session_save_restore.py 2014-08-11 06:31:21 +0000 |
817 | +++ tests/autopilot/webbrowser_app/tests/test_session_save_restore.py 2014-08-21 11:29:14 +0000 |
818 | @@ -52,3 +52,4 @@ |
819 | for i in range(len(paths)): |
820 | self.assertThat(previews[len(paths) - 1 - i].url, |
821 | Eventually(Equals(self.base_url + paths[i]))) |
822 | + self.assert_number_webviews_eventually(1) |
823 | |
824 | === modified file 'tests/autopilot/webbrowser_app/tests/test_tabs.py' |
825 | --- tests/autopilot/webbrowser_app/tests/test_tabs.py 2014-07-25 11:07:31 +0000 |
826 | +++ tests/autopilot/webbrowser_app/tests/test_tabs.py 2014-08-21 11:29:14 +0000 |
827 | @@ -60,6 +60,7 @@ |
828 | close_button = preview.get_close_button() |
829 | self.pointing_device.click_object(close_button) |
830 | tabs_view.wait_until_destroyed() |
831 | + self.assert_number_webviews_eventually(1) |
832 | self.main_window.get_new_tab_view() |
833 | if model() == 'Desktop': |
834 | address_bar = self.main_window.get_chrome().get_address_bar() |
835 | @@ -76,16 +77,18 @@ |
836 | self.type_in_address_bar(url) |
837 | self.keyboard.press_and_release("Enter") |
838 | new_tab_view.wait_until_destroyed() |
839 | + self.assert_number_webviews_eventually(2) |
840 | self.open_tabs_view() |
841 | tabs_view = self.main_window.get_tabs_view() |
842 | previews = tabs_view.get_ordered_previews() |
843 | self.assertThat(len(previews), Equals(2)) |
844 | - preview = previews[1] |
845 | + preview = previews[0] |
846 | close_button = preview.get_close_button() |
847 | self.pointing_device.click_object(close_button) |
848 | self.assertThat(lambda: len(tabs_view.get_previews()), |
849 | Eventually(Equals(1))) |
850 | preview = tabs_view.get_previews()[0] |
851 | + self.assert_number_webviews_eventually(1) |
852 | webview = self.main_window.get_current_webview() |
853 | self.assertThat(preview.title, Equals(webview.title)) |
854 | |
855 | @@ -142,6 +145,7 @@ |
856 | webview = self.main_window.get_current_webview() |
857 | self.pointing_device.click_object(webview) |
858 | self.check_current_tab(self.base_url + "/aleaiactaest") |
859 | + self.assert_number_webviews_eventually(2) |
860 | |
861 | def test_open_iframe_target_blank_in_new_tab(self): |
862 | url = self.base_url + "/fulliframewithblanktargetlink" |
863 | @@ -150,6 +154,7 @@ |
864 | webview = self.main_window.get_current_webview() |
865 | self.pointing_device.click_object(webview) |
866 | self.check_current_tab(self.base_url + "/aleaiactaest") |
867 | + self.assert_number_webviews_eventually(2) |
868 | |
869 | def test_selecting_tab_focuses_webview(self): |
870 | self.focus_address_bar() |
871 | |
872 | === modified file 'tests/unittests/tabs-model/tst_TabsModelTests.cpp' |
873 | --- tests/unittests/tabs-model/tst_TabsModelTests.cpp 2014-07-25 11:03:45 +0000 |
874 | +++ tests/unittests/tabs-model/tst_TabsModelTests.cpp 2014-08-21 11:29:14 +0000 |
875 | @@ -34,7 +34,7 @@ |
876 | private: |
877 | TabsModel* model; |
878 | |
879 | - QQuickItem* createWebView() |
880 | + QQuickItem* createTab() |
881 | { |
882 | QQmlEngine engine; |
883 | QQmlComponent component(&engine); |
884 | @@ -55,13 +55,16 @@ |
885 | |
886 | void cleanup() |
887 | { |
888 | + while (model->rowCount() > 0) { |
889 | + delete model->remove(0); |
890 | + } |
891 | delete model; |
892 | } |
893 | |
894 | void shouldBeInitiallyEmpty() |
895 | { |
896 | QCOMPARE(model->rowCount(), 0); |
897 | - QCOMPARE(model->currentWebview(), (QObject*) 0); |
898 | + QCOMPARE(model->currentTab(), (QObject*) 0); |
899 | } |
900 | |
901 | void shouldExposeRoleNames() |
902 | @@ -70,45 +73,45 @@ |
903 | QVERIFY(roleNames.contains("url")); |
904 | QVERIFY(roleNames.contains("title")); |
905 | QVERIFY(roleNames.contains("icon")); |
906 | - QVERIFY(roleNames.contains("webview")); |
907 | + QVERIFY(roleNames.contains("tab")); |
908 | } |
909 | |
910 | void shouldNotAllowSettingTheIndexToAnInvalidValue() |
911 | { |
912 | model->setCurrent(0); |
913 | - QCOMPARE(model->currentWebview(), (QObject*) 0); |
914 | + QCOMPARE(model->currentTab(), (QObject*) 0); |
915 | model->setCurrent(2); |
916 | - QCOMPARE(model->currentWebview(), (QObject*) 0); |
917 | + QCOMPARE(model->currentTab(), (QObject*) 0); |
918 | model->setCurrent(-2); |
919 | - QCOMPARE(model->currentWebview(), (QObject*) 0); |
920 | + QCOMPARE(model->currentTab(), (QObject*) 0); |
921 | } |
922 | |
923 | - void shouldNotAddNullWebView() |
924 | + void shouldNotAddNullTab() |
925 | { |
926 | QCOMPARE(model->add(0), -1); |
927 | QCOMPARE(model->rowCount(), 0); |
928 | } |
929 | |
930 | - void shouldReturnIndexWhenAddingWebView() |
931 | + void shouldReturnIndexWhenAddingTab() |
932 | { |
933 | for(int i = 0; i < 3; ++i) { |
934 | - QCOMPARE(model->add(createWebView()), i); |
935 | + QCOMPARE(model->add(createTab()), i); |
936 | } |
937 | } |
938 | |
939 | - void shouldUpdateCountWhenAddingWebView() |
940 | + void shouldUpdateCountWhenAddingTab() |
941 | { |
942 | QSignalSpy spy(model, SIGNAL(countChanged())); |
943 | - model->add(createWebView()); |
944 | + model->add(createTab()); |
945 | QCOMPARE(spy.count(), 1); |
946 | QCOMPARE(model->rowCount(), 1); |
947 | } |
948 | |
949 | - void shouldUpdateCountWhenRemovingWebView() |
950 | + void shouldUpdateCountWhenRemovingTab() |
951 | { |
952 | - model->add(createWebView()); |
953 | + model->add(createTab()); |
954 | QSignalSpy spy(model, SIGNAL(countChanged())); |
955 | - model->remove(0); |
956 | + delete model->remove(0); |
957 | QCOMPARE(spy.count(), 1); |
958 | QCOMPARE(model->rowCount(), 0); |
959 | } |
960 | @@ -120,39 +123,42 @@ |
961 | QCOMPARE(model->remove(-2), (QObject*) 0); |
962 | } |
963 | |
964 | - void shouldReturnWebViewWhenRemoving() |
965 | + void shouldReturnTabWhenRemoving() |
966 | { |
967 | - QQuickItem* webview = createWebView(); |
968 | - model->add(webview); |
969 | + QQuickItem* tab = createTab(); |
970 | + model->add(tab); |
971 | QObject* removed = model->remove(0); |
972 | - QCOMPARE(removed, webview); |
973 | - } |
974 | - |
975 | - void shouldNotChangeCurrentWebviewWhenAddingUnlessModelWasEmpty() |
976 | - { |
977 | - QSignalSpy spy(model, SIGNAL(currentWebviewChanged())); |
978 | - QQuickItem* webview = createWebView(); |
979 | - model->add(webview); |
980 | - QCOMPARE(spy.count(), 1); |
981 | - QCOMPARE(model->currentWebview(), webview); |
982 | - model->add(createWebView()); |
983 | - QCOMPARE(spy.count(), 1); |
984 | - QCOMPARE(model->currentWebview(), webview); |
985 | - } |
986 | - |
987 | - void shouldNotDeleteWebViewWhenRemoving() |
988 | - { |
989 | - QQuickItem* webview = createWebView(); |
990 | - model->add(webview); |
991 | + QCOMPARE(removed, tab); |
992 | + delete removed; |
993 | + } |
994 | + |
995 | + void shouldNotChangeCurrentTabWhenAddingUnlessModelWasEmpty() |
996 | + { |
997 | + QSignalSpy spy(model, SIGNAL(currentTabChanged())); |
998 | + QQuickItem* tab = createTab(); |
999 | + model->add(tab); |
1000 | + QCOMPARE(spy.count(), 1); |
1001 | + QCOMPARE(model->currentTab(), tab); |
1002 | + spy.clear(); |
1003 | + model->add(createTab()); |
1004 | + QVERIFY(spy.isEmpty()); |
1005 | + QCOMPARE(model->currentTab(), tab); |
1006 | + } |
1007 | + |
1008 | + void shouldNotDeleteTabWhenRemoving() |
1009 | + { |
1010 | + QQuickItem* tab = createTab(); |
1011 | + model->add(tab); |
1012 | model->remove(0); |
1013 | - QCOMPARE(webview->parent(), this); |
1014 | + QCOMPARE(tab->parent(), this); |
1015 | + delete tab; |
1016 | } |
1017 | |
1018 | - void shouldNotifyWhenAddingWebView() |
1019 | + void shouldNotifyWhenAddingTab() |
1020 | { |
1021 | QSignalSpy spy(model, SIGNAL(rowsInserted(const QModelIndex&, int, int))); |
1022 | for(int i = 0; i < 3; ++i) { |
1023 | - model->add(createWebView()); |
1024 | + model->add(createTab()); |
1025 | QCOMPARE(spy.count(), 1); |
1026 | QList<QVariant> args = spy.takeFirst(); |
1027 | QCOMPARE(args.at(1).toInt(), i); |
1028 | @@ -160,19 +166,19 @@ |
1029 | } |
1030 | } |
1031 | |
1032 | - void shouldNotifyWhenRemovingWebView() |
1033 | + void shouldNotifyWhenRemovingTab() |
1034 | { |
1035 | QSignalSpy spy(model, SIGNAL(rowsRemoved(const QModelIndex&, int, int))); |
1036 | for(int i = 0; i < 5; ++i) { |
1037 | - model->add(createWebView()); |
1038 | + model->add(createTab()); |
1039 | } |
1040 | - model->remove(3); |
1041 | + delete model->remove(3); |
1042 | QCOMPARE(spy.count(), 1); |
1043 | QList<QVariant> args = spy.takeFirst(); |
1044 | QCOMPARE(args.at(1).toInt(), 3); |
1045 | QCOMPARE(args.at(2).toInt(), 3); |
1046 | for(int i = 3; i >= 0; --i) { |
1047 | - model->remove(i); |
1048 | + delete model->remove(i); |
1049 | QCOMPARE(spy.count(), 1); |
1050 | args = spy.takeFirst(); |
1051 | QCOMPARE(args.at(1).toInt(), i); |
1052 | @@ -180,14 +186,14 @@ |
1053 | } |
1054 | } |
1055 | |
1056 | - void shouldNotifyWhenWebViewPropertiesChange() |
1057 | + void shouldNotifyWhenTabPropertiesChange() |
1058 | { |
1059 | qRegisterMetaType<QVector<int> >(); |
1060 | QSignalSpy spy(model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&))); |
1061 | - QQuickItem* webview = createWebView(); |
1062 | - model->add(webview); |
1063 | + QQuickItem* tab = createTab(); |
1064 | + model->add(tab); |
1065 | |
1066 | - QQmlProperty(webview, "url").write(QUrl("http://ubuntu.com")); |
1067 | + QQmlProperty(tab, "url").write(QUrl("http://ubuntu.com")); |
1068 | QCOMPARE(spy.count(), 1); |
1069 | QList<QVariant> args = spy.takeFirst(); |
1070 | QCOMPARE(args.at(0).toModelIndex().row(), 0); |
1071 | @@ -196,7 +202,7 @@ |
1072 | QCOMPARE(roles.size(), 1); |
1073 | QVERIFY(roles.contains(TabsModel::Url)); |
1074 | |
1075 | - QQmlProperty(webview, "title").write(QString("Lorem Ipsum")); |
1076 | + QQmlProperty(tab, "title").write(QString("Lorem Ipsum")); |
1077 | QCOMPARE(spy.count(), 1); |
1078 | args = spy.takeFirst(); |
1079 | QCOMPARE(args.at(0).toModelIndex().row(), 0); |
1080 | @@ -205,7 +211,7 @@ |
1081 | QCOMPARE(roles.size(), 1); |
1082 | QVERIFY(roles.contains(TabsModel::Title)); |
1083 | |
1084 | - QQmlProperty(webview, "icon").write(QUrl("image://webicon/123")); |
1085 | + QQmlProperty(tab, "icon").write(QUrl("image://webicon/123")); |
1086 | QCOMPARE(spy.count(), 1); |
1087 | args = spy.takeFirst(); |
1088 | QCOMPARE(args.at(0).toModelIndex().row(), 0); |
1089 | @@ -215,73 +221,73 @@ |
1090 | QVERIFY(roles.contains(TabsModel::Icon)); |
1091 | } |
1092 | |
1093 | - void shouldUpdateCurrentWebviewWhenSettingCurrent() |
1094 | + void shouldUpdateCurrentTabWhenSettingCurrent() |
1095 | { |
1096 | - QQuickItem* webview1 = createWebView(); |
1097 | - model->add(webview1); |
1098 | - QSignalSpy spy(model, SIGNAL(currentWebviewChanged())); |
1099 | + QQuickItem* tab1 = createTab(); |
1100 | + model->add(tab1); |
1101 | + QSignalSpy spy(model, SIGNAL(currentTabChanged())); |
1102 | model->setCurrent(0); |
1103 | - QCOMPARE(spy.count(), 0); |
1104 | - QCOMPARE(model->currentWebview(), webview1); |
1105 | - QQuickItem* webview2 = createWebView(); |
1106 | - model->add(webview2); |
1107 | + QVERIFY(spy.isEmpty()); |
1108 | + QCOMPARE(model->currentTab(), tab1); |
1109 | + QQuickItem* tab2 = createTab(); |
1110 | + model->add(tab2); |
1111 | model->setCurrent(1); |
1112 | QCOMPARE(spy.count(), 1); |
1113 | - QCOMPARE(model->currentWebview(), webview2); |
1114 | + QCOMPARE(model->currentTab(), tab2); |
1115 | } |
1116 | |
1117 | - void shouldUpdateCurrentWebviewWhenRemoving() |
1118 | + void shouldUpdateCurrentTabWhenRemoving() |
1119 | { |
1120 | - QSignalSpy spy(model, SIGNAL(currentWebviewChanged())); |
1121 | + QSignalSpy spy(model, SIGNAL(currentTabChanged())); |
1122 | |
1123 | - // Adding a webview to an empty model should update the current webview. |
1124 | - // Removing the last webview from the model should update it too. |
1125 | - model->add(createWebView()); |
1126 | - model->remove(0); |
1127 | + // Adding a tab to an empty model should update the current tab. |
1128 | + // Removing the last tab from the model should update it too. |
1129 | + model->add(createTab()); |
1130 | + delete model->remove(0); |
1131 | QCOMPARE(spy.count(), 2); |
1132 | |
1133 | - // When removing a webview after the current one, |
1134 | - // the current webview shouldn’t change. |
1135 | - QQuickItem* webview1 = createWebView(); |
1136 | - model->add(webview1); |
1137 | - model->add(createWebView()); |
1138 | + // When removing a tab after the current one, |
1139 | + // the current tab shouldn’t change. |
1140 | + QQuickItem* tab1 = createTab(); |
1141 | + model->add(tab1); |
1142 | + model->add(createTab()); |
1143 | spy.clear(); |
1144 | - model->remove(1); |
1145 | - QCOMPARE(model->currentWebview(), webview1); |
1146 | - QCOMPARE(spy.count(), 0); |
1147 | + delete model->remove(1); |
1148 | + QCOMPARE(model->currentTab(), tab1); |
1149 | + QVERIFY(spy.isEmpty()); |
1150 | |
1151 | - // When removing the current webview, if there is a webview after it, |
1152 | + // When removing the current tab, if there is a tab after it, |
1153 | // it becomes the current one. |
1154 | - QQuickItem* webview2 = createWebView(); |
1155 | - model->add(webview2); |
1156 | + QQuickItem* tab2 = createTab(); |
1157 | + model->add(tab2); |
1158 | spy.clear(); |
1159 | - model->remove(0); |
1160 | + delete model->remove(0); |
1161 | QCOMPARE(spy.count(), 1); |
1162 | - QCOMPARE(model->currentWebview(), webview2); |
1163 | + QCOMPARE(model->currentTab(), tab2); |
1164 | |
1165 | - // When removing the current webview, if it was the last one, the |
1166 | - // current webview should be reset to 0. |
1167 | + // When removing the current tab, if it was the last one, the |
1168 | + // current tab should be reset to 0. |
1169 | spy.clear(); |
1170 | - model->remove(0); |
1171 | + delete model->remove(0); |
1172 | QCOMPARE(spy.count(), 1); |
1173 | - QCOMPARE(model->currentWebview(), (QObject*) 0); |
1174 | + QCOMPARE(model->currentTab(), (QObject*) 0); |
1175 | } |
1176 | |
1177 | void shouldReturnData() |
1178 | { |
1179 | - QQuickItem* webview = createWebView(); |
1180 | - QQmlProperty(webview, "url").write(QUrl("http://ubuntu.com/")); |
1181 | - QQmlProperty(webview, "title").write(QString("Lorem Ipsum")); |
1182 | - QQmlProperty(webview, "icon").write(QUrl("image://webicon/123")); |
1183 | - model->add(webview); |
1184 | + QQuickItem* tab = createTab(); |
1185 | + QQmlProperty(tab, "url").write(QUrl("http://ubuntu.com/")); |
1186 | + QQmlProperty(tab, "title").write(QString("Lorem Ipsum")); |
1187 | + QQmlProperty(tab, "icon").write(QUrl("image://webicon/123")); |
1188 | + model->add(tab); |
1189 | QVERIFY(!model->data(QModelIndex(), TabsModel::Url).isValid()); |
1190 | QVERIFY(!model->data(model->index(-1, 0), TabsModel::Url).isValid()); |
1191 | QVERIFY(!model->data(model->index(3, 0), TabsModel::Url).isValid()); |
1192 | QCOMPARE(model->data(model->index(0, 0), TabsModel::Url).toUrl(), QUrl("http://ubuntu.com/")); |
1193 | QCOMPARE(model->data(model->index(0, 0), TabsModel::Title).toString(), QString("Lorem Ipsum")); |
1194 | QCOMPARE(model->data(model->index(0, 0), TabsModel::Icon).toUrl(), QUrl("image://webicon/123")); |
1195 | - QCOMPARE(model->data(model->index(0, 0), TabsModel::WebView).value<QQuickItem*>(), webview); |
1196 | - QVERIFY(!model->data(model->index(0, 0), TabsModel::WebView + 3).isValid()); |
1197 | + QCOMPARE(model->data(model->index(0, 0), TabsModel::Tab).value<QQuickItem*>(), tab); |
1198 | + QVERIFY(!model->data(model->index(0, 0), TabsModel::Tab + 3).isValid()); |
1199 | } |
1200 | }; |
1201 |
The "Open link in new tab" behaviour seems to be broken, the currently focused tab remains visible but unresponsive to interaction (the new tab can be switched to via the "Open tabs" menu and switching back to the original tab restores responsiveness). Other than that it's looking really good!