Merge lp:~fboucault/webbrowser-app/switch_to_parent_tab_on_close into lp:webbrowser-app/staging

Proposed by Florian Boucault
Status: Needs review
Proposed branch: lp:~fboucault/webbrowser-app/switch_to_parent_tab_on_close
Merge into: lp:webbrowser-app/staging
Diff against target: 388 lines (+140/-58)
11 files modified
src/app/webbrowser/Browser.qml (+12/-2)
src/app/webbrowser/BrowserTab.qml (+1/-0)
src/app/webbrowser/TabComponent.qml (+10/-5)
src/app/webbrowser/tabs-model.cpp (+16/-0)
src/app/webbrowser/tabs-model.h (+1/-0)
tests/autopilot/webbrowser_app/tests/__init__.py (+10/-0)
tests/autopilot/webbrowser_app/tests/http_server.py (+6/-0)
tests/autopilot/webbrowser_app/tests/test_new_tab_view.py (+5/-34)
tests/autopilot/webbrowser_app/tests/test_site_previews.py (+0/-9)
tests/autopilot/webbrowser_app/tests/test_tabs.py (+65/-8)
tests/unittests/tabs-model/tst_TabsModelTests.cpp (+14/-0)
To merge this branch: bzr merge lp:~fboucault/webbrowser-app/switch_to_parent_tab_on_close
Reviewer Review Type Date Requested Status
Olivier Tilloy Needs Fixing
Review via email: mp+317952@code.launchpad.net

Commit message

Restore focus on parent tab when closing child tab

Description of the change

Restore focus on parent tab when closing child tab

To post a comment you must log in.
Revision history for this message
Olivier Tilloy (osomon) wrote :

flake8 unit test is failing.

review: Needs Fixing
1624. By Florian Boucault

Fixed flake8

Revision history for this message
Florian Boucault (fboucault) wrote :

> flake8 unit test is failing.

Fixed

Unmerged revisions

1624. By Florian Boucault

Fixed flake8

1623. By Florian Boucault

Added corresponding AP tests

1622. By Florian Boucault

Restore focus on parent tab when closing child tab

1621. By Florian Boucault

AP test: simplified tab closing

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/app/webbrowser/Browser.qml'
2--- src/app/webbrowser/Browser.qml 2017-02-16 17:25:49 +0000
3+++ src/app/webbrowser/Browser.qml 2017-03-03 14:26:48 +0000
4@@ -87,6 +87,7 @@
5 function serializeTabState(tab) {
6 var state = {}
7 state.uniqueId = tab.uniqueId
8+ state.parentUniqueId = tab.parentUniqueId
9 state.url = tab.url.toString()
10 state.title = tab.title
11 state.icon = tab.icon.toString()
12@@ -98,6 +99,7 @@
13 function restoreTabState(state) {
14 var properties = {'initialUrl': state.url, 'initialTitle': state.title,
15 'uniqueId': state.uniqueId, 'initialIcon': state.icon,
16+ 'parentUniqueId': state.parentUniqueId,
17 'preview': state.preview, 'restoreState': state.savedState,
18 'restoreType': Oxide.WebView.RestoreLastSessionExitedCleanly}
19 return createTab(properties)
20@@ -1047,9 +1049,10 @@
21 if (share) share.shareText(text)
22 }
23
24- function openUrlInNewTab(url, setCurrent, load, index) {
25+ function openUrlInNewTab(url, setCurrent, load, index, parentUniqueId) {
26 load = typeof load !== 'undefined' ? load : true
27- var tab = internal.createTabHelper({"initialUrl": url})
28+ var tab = internal.createTabHelper({"initialUrl": url,
29+ "parentUniqueId": parentUniqueId})
30 addTab(tab, setCurrent, index)
31 if (load) {
32 tab.load()
33@@ -1096,6 +1099,13 @@
34 moving = moving === undefined ? false : moving;
35
36 var tab = tabsModel.get(index)
37+ if (tab.parentUniqueId) {
38+ var parentIndex = -1
39+ parentIndex = tabsModel.match("uniqueId", tab.parentUniqueId)
40+ if (parentIndex != -1) {
41+ internal.switchToTab(parentIndex)
42+ }
43+ }
44 tabsModel.remove(index)
45
46 if (tab) {
47
48=== modified file 'src/app/webbrowser/BrowserTab.qml'
49--- src/app/webbrowser/BrowserTab.qml 2017-02-16 17:25:49 +0000
50+++ src/app/webbrowser/BrowserTab.qml 2017-03-03 14:26:48 +0000
51@@ -28,6 +28,7 @@
52 id: tab
53
54 property string uniqueId: this.toString() + "-" + Date.now()
55+ property string parentUniqueId
56 property url initialUrl
57 property string initialTitle
58 property url initialIcon
59
60=== modified file 'src/app/webbrowser/TabComponent.qml'
61--- src/app/webbrowser/TabComponent.qml 2017-02-15 11:22:05 +0000
62+++ src/app/webbrowser/TabComponent.qml 2017-03-03 14:26:48 +0000
63@@ -86,13 +86,15 @@
64 objectName: "OpenLinkInNewTabContextualAction"
65 enabled: contextModel && contextModel.linkUrl.toString()
66 onTriggered: internal.openUrlInNewTab(contextModel.linkUrl, true,
67- true, tabsModel.indexOf(browserTab) + 1)
68+ true, tabsModel.indexOf(browserTab) + 1,
69+ tab.uniqueId)
70 }
71 Actions.OpenLinkInNewBackgroundTab {
72 objectName: "OpenLinkInNewBackgroundTabContextualAction"
73 enabled: contextModel && contextModel.linkUrl.toString()
74 onTriggered: internal.openUrlInNewTab(contextModel.linkUrl, false,
75- true, tabsModel.indexOf(browserTab) + 1)
76+ true, tabsModel.indexOf(browserTab) + 1,
77+ tab.uniqueId)
78 }
79 Actions.OpenLinkInNewWindow {
80 objectName: "OpenLinkInNewWindowContextualAction"
81@@ -146,7 +148,8 @@
82 (contextModel.mediaType === Oxide.WebView.MediaTypeImage) &&
83 contextModel.srcUrl.toString()
84 onTriggered: internal.openUrlInNewTab(contextModel.srcUrl, true,
85- true, tabsModel.indexOf(browserTab) + 1)
86+ true, tabsModel.indexOf(browserTab) + 1,
87+ tab.uniqueId)
88 }
89 Actions.CopyImage {
90 objectName: "CopyImageContextualAction"
91@@ -170,7 +173,8 @@
92 (contextModel.mediaType === Oxide.WebView.MediaTypeVideo) &&
93 contextModel.srcUrl.toString()
94 onTriggered: internal.openUrlInNewTab(contextModel.srcUrl, true,
95- true, tabsModel.indexOf(browserTab) + 1)
96+ true, tabsModel.indexOf(browserTab) + 1,
97+ tab.uniqueId)
98 }
99 Actions.SaveVideo {
100 objectName: "SaveVideoContextualAction"
101@@ -258,7 +262,8 @@
102 contextMenu: browser && browser.wide ? contextMenuWideComponent : contextMenuNarrowComponent
103
104 onNewViewRequested: {
105- var newTab = browser.createTab({"request": request})
106+ var newTab = browser.createTab({"parentUniqueId": tab.uniqueId,
107+ "request": request})
108 var setCurrent = (request.disposition == Oxide.NewViewRequest.DispositionNewForegroundTab)
109 internal.addTab(newTab, setCurrent, tabsModel.indexOf(browserTab) + 1)
110 if (setCurrent) tabContainer.forceActiveFocus()
111
112=== modified file 'src/app/webbrowser/tabs-model.cpp'
113--- src/app/webbrowser/tabs-model.cpp 2017-02-15 19:41:01 +0000
114+++ src/app/webbrowser/tabs-model.cpp 2017-03-03 14:26:48 +0000
115@@ -212,6 +212,22 @@
116 return m_tabs.indexOf(tab);
117 }
118
119+/*!
120+ Returns the index position of the first occurrence of tab for which
121+ property is set to value.
122+ Returns -1 if no item matched.
123+*/
124+int TabsModel::match(const QString& property, const QVariant &value) const
125+{
126+ for (int i = 0; i < m_tabs.size(); ++i) {
127+ QVariant propertyValue = m_tabs[i]->property(property.toLocal8Bit().constData());
128+ if (propertyValue == value) {
129+ return i;
130+ }
131+ }
132+ return -1;
133+}
134+
135 void TabsModel::move(int from, int to)
136 {
137 if ((from == to) || !checkValidTabIndex(from) || !checkValidTabIndex(to)) {
138
139=== modified file 'src/app/webbrowser/tabs-model.h'
140--- src/app/webbrowser/tabs-model.h 2017-02-15 11:22:05 +0000
141+++ src/app/webbrowser/tabs-model.h 2017-03-03 14:26:48 +0000
142@@ -61,6 +61,7 @@
143 Q_INVOKABLE QObject* remove(int index);
144 Q_INVOKABLE QObject* get(int index) const;
145 Q_INVOKABLE int indexOf(QObject* tab) const;
146+ Q_INVOKABLE int match(const QString& property, const QVariant &value) const;
147 Q_INVOKABLE void move(int from, int to);
148
149 Q_SIGNALS:
150
151=== modified file 'tests/autopilot/webbrowser_app/tests/__init__.py'
152--- tests/autopilot/webbrowser_app/tests/__init__.py 2017-02-15 11:22:05 +0000
153+++ tests/autopilot/webbrowser_app/tests/__init__.py 2017-03-03 14:26:48 +0000
154@@ -185,6 +185,16 @@
155 tabs_view.get_previews()[index].select()
156 tabs_view.visible.wait_for(False)
157
158+ def close_tab(self, index):
159+ if self.main_window.wide:
160+ self.main_window.chrome.get_tabs_bar().close_tab(index)
161+ else:
162+ tabs_view = self.open_tabs_view()
163+ tabs_view.get_previews()[index].close()
164+ toolbar = self.main_window.get_recent_view_toolbar()
165+ toolbar.click_button("doneButton")
166+ tabs_view.visible.wait_for(False)
167+
168 def open_new_window(self):
169 windows = self.app.get_windows(incognito=False)
170 chrome = self.main_window.chrome
171
172=== modified file 'tests/autopilot/webbrowser_app/tests/http_server.py'
173--- tests/autopilot/webbrowser_app/tests/http_server.py 2016-10-13 14:36:24 +0000
174+++ tests/autopilot/webbrowser_app/tests/http_server.py 2017-03-03 14:26:48 +0000
175@@ -68,6 +68,12 @@
176 html += '<a href="/test1"><div style="height: 100%"></div></a>'
177 html += '</body></html>'
178 self.send_html(html)
179+ elif self.path == "/linkcloseself":
180+ self.send_response(200)
181+ html = '<html><body style="margin: 0">'
182+ html += '<a href="/closeself"><div style="height: 100%"></div></a>'
183+ html += '</body></html>'
184+ self.send_html(html)
185 elif self.path.startswith("/wait/"):
186 delay = int(self.path[6:])
187 self.send_response(200)
188
189=== modified file 'tests/autopilot/webbrowser_app/tests/test_new_tab_view.py'
190--- tests/autopilot/webbrowser_app/tests/test_new_tab_view.py 2016-11-03 11:23:18 +0000
191+++ tests/autopilot/webbrowser_app/tests/test_new_tab_view.py 2017-03-03 14:26:48 +0000
192@@ -36,13 +36,7 @@
193
194 def test_new_tab_view_destroyed_when_closing_tab(self):
195 new_tab_view = self.open_new_tab(open_tabs_view=True)
196- if self.main_window.wide:
197- self.main_window.chrome.get_tabs_bar().close_tab(1)
198- else:
199- tabs_view = self.open_tabs_view()
200- tabs_view.get_previews()[0].close()
201- toolbar = self.main_window.get_recent_view_toolbar()
202- toolbar.click_button("doneButton")
203+ self.close_tab(1 if self.main_window.wide else 0)
204 new_tab_view.wait_until_destroyed()
205
206 def test_new_tab_view_is_shared_between_tabs(self):
207@@ -54,44 +48,21 @@
208 self.assertThat(new_tab_view_2.id, Equals(new_tab_view.id))
209 # Close the second new tab, and verify that the NewTabView instance
210 # is still there
211- if self.main_window.wide:
212- self.main_window.chrome.get_tabs_bar().close_tab(2)
213- else:
214- tabs_view = self.open_tabs_view()
215- tabs_view.get_previews()[0].close()
216- toolbar = self.main_window.get_recent_view_toolbar()
217- toolbar.click_button("doneButton")
218- tabs_view.visible.wait_for(False)
219+ self.close_tab(2 if self.main_window.wide else 0)
220 self.assertThat(new_tab_view.visible, Equals(True))
221 # Close the first new tab, and verify that the NewTabView instance
222 # is destroyed
223- if self.main_window.wide:
224- self.main_window.chrome.get_tabs_bar().close_tab(1)
225- else:
226- tabs_view = self.open_tabs_view()
227- tabs_view.get_previews()[0].close()
228- toolbar = self.main_window.get_recent_view_toolbar()
229- toolbar.click_button("doneButton")
230+ self.close_tab(1 if self.main_window.wide else 0)
231 new_tab_view.wait_until_destroyed()
232
233 @testtools.skipIf(model() == "Desktop",
234 "Closing the last open tab on desktop quits the app")
235 def test_new_tab_view_not_destroyed_when_closing_last_open_tab(self):
236- if self.main_window.wide:
237- self.main_window.chrome.get_tabs_bar().close_tab(0)
238- else:
239- tabs_view = self.open_tabs_view()
240- tabs_view.get_previews()[0].close()
241- tabs_view.visible.wait_for(False)
242+ self.close_tab(0)
243 new_tab_view = self.main_window.get_new_tab_view()
244 # Verify that the new tab view is not destroyed and then re-created
245 # when closing the last open tab if it was a blank one
246- if self.main_window.wide:
247- self.main_window.chrome.get_tabs_bar().close_tab(0)
248- else:
249- tabs_view = self.open_tabs_view()
250- tabs_view.get_previews()[0].close()
251- tabs_view.visible.wait_for(False)
252+ self.close_tab(0)
253 self.assertThat(new_tab_view.visible, Equals(True))
254
255
256
257=== modified file 'tests/autopilot/webbrowser_app/tests/test_site_previews.py'
258--- tests/autopilot/webbrowser_app/tests/test_site_previews.py 2016-11-03 11:23:18 +0000
259+++ tests/autopilot/webbrowser_app/tests/test_site_previews.py 2017-03-03 14:26:48 +0000
260@@ -107,15 +107,6 @@
261 super(TestSitePreviews, self).setUp()
262 self.launch_and_wait_for_page_loaded()
263
264- def close_tab(self, index):
265- if self.main_window.wide:
266- self.main_window.chrome.get_tabs_bar().close_tab(index)
267- else:
268- tabs_view = self.open_tabs_view()
269- tabs_view.get_previews()[index].close()
270- toolbar = self.main_window.get_recent_view_toolbar()
271- toolbar.click_button("doneButton")
272-
273 def get_captures(self):
274 if not path.exists(self.captures_dir):
275 return []
276
277=== modified file 'tests/autopilot/webbrowser_app/tests/test_tabs.py'
278--- tests/autopilot/webbrowser_app/tests/test_tabs.py 2017-02-15 11:22:05 +0000
279+++ tests/autopilot/webbrowser_app/tests/test_tabs.py 2017-03-03 14:26:48 +0000
280@@ -280,14 +280,7 @@
281 url = self.base_url + "/closeself"
282 self.main_window.go_to_url(url)
283 self.main_window.wait_until_page_loaded(url)
284- if self.main_window.wide:
285- self.main_window.chrome.get_tabs_bar().close_tab(0)
286- else:
287- tabs_view = self.open_tabs_view()
288- tabs_view.get_previews()[1].close()
289- toolbar = self.main_window.get_recent_view_toolbar()
290- toolbar.click_button("doneButton")
291- tabs_view.visible.wait_for(False)
292+ self.close_tab(0 if self.main_window.wide else 1)
293 webview = self.main_window.get_current_webview()
294 wide = self.main_window.wide
295 self.pointing_device.click_object(webview)
296@@ -339,3 +332,67 @@
297 self.main_window.press_key('Ctrl+Shift+t')
298 self.assert_number_webviews_eventually(3)
299 self.check_current_tab(url2)
300+
301+
302+class TestCloseTabsSelectParent(StartOpenRemotePageTestCaseBase,
303+ TestTabsMixin):
304+
305+ def setup_tabs(self, url):
306+ # create 3 tabs, the second one being a child of the first one
307+
308+ # first tab with a link
309+ self.main_window.go_to_url(url)
310+ self.main_window.wait_until_page_loaded(url)
311+
312+ # second tab by ctrl-clicking the link
313+ webview = self.main_window.get_current_webview()
314+ self.keyboard.press('Ctrl')
315+ self.pointing_device.click_object(webview)
316+ self.keyboard.release('Ctrl')
317+ self.assert_number_webviews_eventually(2)
318+
319+ # third empty tab
320+ self.open_new_tab(open_tabs_view=not self.main_window.wide)
321+ self.assert_number_webviews_eventually(3)
322+
323+ @testtools.skipIf(model() != "Desktop", "on desktop only")
324+ def test_click_close(self):
325+ if not self.main_window.wide:
326+ self.skipTest("Only on wide form factors")
327+ self.setup_tabs(self.base_url + "/link")
328+
329+ self.switch_to_tab(1 if self.main_window.wide else 2)
330+ self.check_current_tab(self.base_url + "/test1")
331+ self.close_tab(1 if self.main_window.wide else 0)
332+
333+ # verify that the parent tab that created the 'test1' tab is reselected
334+ self.check_current_tab(self.base_url + "/link")
335+
336+ @testtools.skipIf(model() != "Desktop", "on desktop only")
337+ def test_close_shortcut(self):
338+ if not self.main_window.wide:
339+ self.skipTest("Only on wide form factors")
340+ self.setup_tabs(self.base_url + "/link")
341+
342+ self.switch_to_tab(1 if self.main_window.wide else 2)
343+ self.check_current_tab(self.base_url + "/test1")
344+ self.main_window.press_key('Ctrl+w')
345+
346+ # verify that the parent tab that created the 'test1' tab is reselected
347+ self.check_current_tab(self.base_url + "/link")
348+
349+ @testtools.skipIf(model() != "Desktop", "on desktop only")
350+ def test_webview_requests_close(self):
351+ if not self.main_window.wide:
352+ self.skipTest("Only on wide form factors")
353+ self.setup_tabs(self.base_url + "/linkcloseself")
354+
355+ self.switch_to_tab(1)
356+ self.check_current_tab(self.base_url + "/closeself")
357+ webview = self.main_window.get_current_webview()
358+ self.pointing_device.click_object(webview)
359+ webview.wait_until_destroyed()
360+
361+ # verify that the parent tab that created the 'closeself' tab is
362+ # not reselected
363+ self.check_current_tab("")
364
365=== modified file 'tests/unittests/tabs-model/tst_TabsModelTests.cpp'
366--- tests/unittests/tabs-model/tst_TabsModelTests.cpp 2017-02-15 19:41:01 +0000
367+++ tests/unittests/tabs-model/tst_TabsModelTests.cpp 2017-03-03 14:26:48 +0000
368@@ -499,6 +499,20 @@
369 delete nonAddedTab;
370 }
371
372+ void shouldReturnTabWithPropertyValueIndex()
373+ {
374+ QQuickItem* tab1 = createTab();
375+ tab1->setProperty("testProperty", 38);
376+ model->add(tab1);
377+ QQuickItem* tab2 = createTab();
378+ tab2->setProperty("testProperty", 42);
379+ model->add(tab2);
380+ QCOMPARE(model->match("testProperty", 38), 0);
381+ QCOMPARE(model->match("testProperty", 42), 1);
382+ QCOMPARE(model->match("invalidProperty", 42), -1);
383+ QCOMPARE(model->match("testProperty", -256), -1);
384+ }
385+
386 private:
387 void moveTabs(int from, int to, bool moved, bool indexChanged, int newIndex)
388 {

Subscribers

People subscribed via source and target branches