Merge lp:~osomon/webbrowser-app/find-in-page into lp:webbrowser-app
- find-in-page
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Olivier Tilloy |
Approved revision: | 1034 |
Merged at revision: | 1122 |
Proposed branch: | lp:~osomon/webbrowser-app/find-in-page |
Merge into: | lp:webbrowser-app |
Diff against target: |
825 lines (+430/-59) 11 files modified
debian/control (+2/-2) src/app/actions/FindInPage.qml (+27/-0) src/app/webbrowser/AddressBar.qml (+76/-25) src/app/webbrowser/Browser.qml (+49/-9) src/app/webbrowser/Chrome.qml (+1/-0) src/app/webbrowser/NavigationBar.qml (+74/-23) tests/autopilot/webbrowser_app/emulators/browser.py (+11/-0) tests/autopilot/webbrowser_app/tests/http_server.py (+11/-0) tests/autopilot/webbrowser_app/tests/test_findinpage.py (+156/-0) tests/autopilot/webbrowser_app/tests/test_keyboard.py (+8/-0) tests/unittests/qml/tst_AddressBar.qml (+15/-0) |
To merge this branch: | bzr merge lp:~osomon/webbrowser-app/find-in-page |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot | continuous-integration | Needs Fixing | |
Riccardo Padovani (community) | Approve | ||
Ubuntu Phablet Team | Pending | ||
Review via email:
|
Commit message
Implement the "Find in Page" feature.
This bumps the runtime dependency on liboxideqt-
Description of the change
Note: this supersedes https:/
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Riccardo Padovani (rpadovani) wrote : | # |
# Issue
If you click on a link while it's in search mode it doesn't exit the search mode but it insert the url in the address bar and then when you insert a new url it search it in the page.
When you open a new tab it should exit the search mode
In all the other browsers the search mode is sticked to the tab itself, so I can have it enabled on one tab and switch the tab and return to the same tab and having the same search inserted.
I appreciate this is difficult to implement, so could be done in a second branch, but I urge you to dismiss the search when you change the tab
# Nitpick
49 + text: i18n.tr("Find in Page")
243 + text: i18n.tr("Find in page")
Be coherent :-)
# Opinions
80 + // Only start searches when the user types two or more characters
Could I say I find it not well designed? If I insert one letter it gives 0/0 so I think there isn't any occurence of that letter in the page. All other browsers search also with only one char.
Also, I think we need an icon to put in the addressbar when it's in find-in-page mode
- 1028. By Olivier Tilloy
-
Exit find in page mode when navigating.
- 1029. By Olivier Tilloy
-
Exit find in page mode when opening a new tab.
- 1030. By Olivier Tilloy
-
Make i18n’d strings consistent.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Olivier Tilloy (osomon) wrote : | # |
Thanks for your thorough review Riccardo, I’m glad you caught those really nasty issues.
I addressed them in the revisions I just pushed.
Regarding the issue with the search starting only when 2 or more characters are input, I’ve asked the UX designer in charge the rationale for this specification, still waiting for an answer. Note that I tend to agree with you, I would expect the search to start with the first character input.
> I think we need an icon to put in the addressbar
> when it's in find-in-page mode
This is per specification: https:/
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1030
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Riccardo Padovani (rpadovani) wrote : | # |
Now works like a charm, thanks so much :-)
- 1031. By Olivier Tilloy
-
Remove the lower limit on two characters, as the UX specification was updated.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Olivier Tilloy (osomon) wrote : | # |
> Regarding the issue with the search starting only when 2 or more
> characters are input, I’ve asked the UX designer in charge the
> rationale for this specification, still waiting for an answer.
> Note that I tend to agree with you, I would expect the search to
> start with the first character input.
And the UX designer agrees with us too. The specification was updated, and I updated the implementation accordingly.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1031
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
- 1032. By Olivier Tilloy
-
Merge the latest changes from trunk and resolve a trivial conflict.
- 1033. By Olivier Tilloy
-
Fix positioning of the bookmark toggle placeholder item, which got messed up during the merge.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1033
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
- 1034. By Olivier Tilloy
-
Merge the latest changes from trunk and resolve numerous conflicts.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1034
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1034
http://
Executed test runs:
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1034
http://
Executed test runs:
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1034
http://
Executed test runs:
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 1035. By Olivier Tilloy
-
Merge the latest changes from trunk and resolve a few conflicts.
- 1036. By Olivier Tilloy
-
Update import.
- 1037. By Olivier Tilloy
-
Fix a failing autopilot test on devices.
Preview Diff
1 | === modified file 'debian/control' |
2 | --- debian/control 2015-08-10 15:33:43 +0000 |
3 | +++ debian/control 2015-08-11 11:17:03 +0000 |
4 | @@ -6,7 +6,7 @@ |
5 | debhelper (>= 9), |
6 | dh-translations, |
7 | hardening-wrapper, |
8 | - liboxideqt-qmlplugin (>= 1.6), |
9 | + liboxideqt-qmlplugin (>= 1.8), |
10 | libqt5sql5-sqlite, |
11 | python3-all, |
12 | python3-flake8, |
13 | @@ -34,7 +34,7 @@ |
14 | Depends: ${misc:Depends}, |
15 | ${shlibs:Depends}, |
16 | fonts-liberation, |
17 | - liboxideqt-qmlplugin (>= 1.7), |
18 | + liboxideqt-qmlplugin (>= 1.8), |
19 | libqt5sql5-sqlite, |
20 | qml-module-qt-labs-folderlistmodel, |
21 | qml-module-qt-labs-settings, |
22 | |
23 | === added file 'src/app/actions/FindInPage.qml' |
24 | --- src/app/actions/FindInPage.qml 1970-01-01 00:00:00 +0000 |
25 | +++ src/app/actions/FindInPage.qml 2015-08-11 11:17:03 +0000 |
26 | @@ -0,0 +1,27 @@ |
27 | +/* |
28 | + * Copyright 2015 Canonical Ltd. |
29 | + * |
30 | + * This file is part of webbrowser-app. |
31 | + * |
32 | + * webbrowser-app is free software; you can redistribute it and/or modify |
33 | + * it under the terms of the GNU General Public License as published by |
34 | + * the Free Software Foundation; version 3. |
35 | + * |
36 | + * webbrowser-app is distributed in the hope that it will be useful, |
37 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
38 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
39 | + * GNU General Public License for more details. |
40 | + * |
41 | + * You should have received a copy of the GNU General Public License |
42 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
43 | + */ |
44 | + |
45 | +import Ubuntu.Components 1.3 |
46 | +import Ubuntu.Unity.Action 1.1 as UnityActions |
47 | + |
48 | +UnityActions.Action { |
49 | + text: i18n.tr("Find in page") |
50 | + // TRANSLATORS: This is a free-form list of keywords associated to the 'Find in Page' action. |
51 | + // Keywords may actually be sentences, and must be separated by semi-colons. |
52 | + keywords: i18n.tr("Search in Page") |
53 | +} |
54 | |
55 | === modified file 'src/app/webbrowser/AddressBar.qml' |
56 | --- src/app/webbrowser/AddressBar.qml 2015-08-10 16:16:02 +0000 |
57 | +++ src/app/webbrowser/AddressBar.qml 2015-08-11 11:17:03 +0000 |
58 | @@ -19,7 +19,7 @@ |
59 | import QtQuick 2.4 |
60 | import Ubuntu.Components 1.3 |
61 | import Ubuntu.Components.Popups 1.3 |
62 | -import com.canonical.Oxide 1.0 as Oxide |
63 | +import com.canonical.Oxide 1.8 as Oxide |
64 | import ".." |
65 | import "urlManagement.js" as UrlManagement |
66 | |
67 | @@ -40,6 +40,8 @@ |
68 | property bool canSimplifyText: true |
69 | property bool editing: false |
70 | property bool showFavicon: true |
71 | + property bool findInPageMode: false |
72 | + property var findController: null |
73 | |
74 | property var securityStatus: null |
75 | |
76 | @@ -57,6 +59,12 @@ |
77 | textField.selectAll() |
78 | } |
79 | |
80 | + Binding { |
81 | + target: findController |
82 | + property: "text" |
83 | + value: findInPageMode ? textField.text : "" |
84 | + } |
85 | + |
86 | TextField { |
87 | id: textField |
88 | objectName: "addressBarTextField" |
89 | @@ -68,6 +76,7 @@ |
90 | |
91 | width: iconsRow.width + units.gu(1) |
92 | height: units.gu(2) |
93 | + visible: !findInPageMode |
94 | |
95 | Row { |
96 | id: iconsRow |
97 | @@ -179,29 +188,47 @@ |
98 | } |
99 | } |
100 | |
101 | - secondaryItem: MouseArea { |
102 | - id: bookmarkToggle |
103 | - objectName: "bookmarkToggle" |
104 | - |
105 | + secondaryItem: Row { |
106 | height: textField.height |
107 | - width: visible ? height : 0 |
108 | - |
109 | - visible: internal.idle && addressbar.actualUrl.toString() |
110 | - |
111 | - Icon { |
112 | - height: parent.height - units.gu(2) |
113 | - width: height |
114 | - anchors.centerIn: parent |
115 | - |
116 | - name: addressbar.bookmarked ? "starred" : "non-starred" |
117 | - color: addressbar.bookmarked ? UbuntuColors.orange : UbuntuColors.darkGrey |
118 | + |
119 | + Label { |
120 | + objectName: "findInPageCounter" |
121 | + anchors.verticalCenter: parent.verticalCenter |
122 | + fontSize: "x-small" |
123 | + color: UbuntuColors.darkGrey |
124 | + opacity: findController && findController.count > 0 ? 1.0 : 0.6 |
125 | + visible: findInPageMode |
126 | + |
127 | + // TRANSLATORS: %2 refers to the total number of find in page results and %1 to the highlighted result |
128 | + text: i18n.tr("%1/%2").arg(current).arg(count) |
129 | + property int current: findController ? findController.current : 0 |
130 | + property int count: findController ? findController.count : 0 |
131 | } |
132 | |
133 | - onClicked: addressbar.bookmarked = !addressbar.bookmarked |
134 | - |
135 | - Item { |
136 | - id: bookmarkTogglePlaceHolderItem |
137 | - anchors.fill: parent |
138 | + MouseArea { |
139 | + id: bookmarkToggle |
140 | + objectName: "bookmarkToggle" |
141 | + |
142 | + height: parent.height |
143 | + width: visible ? height : 0 |
144 | + |
145 | + visible: !findInPageMode && internal.idle && addressbar.actualUrl.toString() |
146 | + |
147 | + Icon { |
148 | + height: parent.height - units.gu(2) |
149 | + width: height |
150 | + anchors.centerIn: parent |
151 | + |
152 | + name: addressbar.bookmarked ? "starred" : "non-starred" |
153 | + color: addressbar.bookmarked ? UbuntuColors.orange : UbuntuColors.darkGrey |
154 | + } |
155 | + |
156 | + onClicked: addressbar.bookmarked = !addressbar.bookmarked |
157 | + |
158 | + Item { |
159 | + id: bookmarkTogglePlaceHolderItem |
160 | + anchors.fill: parent |
161 | + } |
162 | } |
163 | } |
164 | |
165 | @@ -209,7 +236,8 @@ |
166 | color: UbuntuColors.darkGrey |
167 | inputMethodHints: Qt.ImhNoPredictiveText | Qt.ImhUrlCharactersOnly |
168 | |
169 | - placeholderText: i18n.tr("search or enter an address") |
170 | + placeholderText: findInPageMode ? i18n.tr("find in page") |
171 | + : i18n.tr("search or enter an address") |
172 | |
173 | // Work around the "fix" for http://pad.lv/1089370 which |
174 | // unsets focus on the TextField when it becomes invisible |
175 | @@ -224,6 +252,16 @@ |
176 | highlighted: true |
177 | |
178 | onAccepted: if (!internal.idle) internal.validate() |
179 | + |
180 | + Keys.onReturnPressed: { |
181 | + if (!findInPageMode) { |
182 | + accepted() |
183 | + } else if (event.modifiers & Qt.ShiftModifier) { |
184 | + findController.previous() |
185 | + } else { |
186 | + findController.next() |
187 | + } |
188 | + } |
189 | } |
190 | |
191 | // Make sure that all the text is selected at the first click |
192 | @@ -233,6 +271,7 @@ |
193 | leftMargin: icons.width |
194 | rightMargin: bookmarkToggle.width |
195 | } |
196 | + |
197 | enabled: !addressbar.activeFocus |
198 | onClicked: { |
199 | textField.forceActiveFocus() |
200 | @@ -309,6 +348,7 @@ |
201 | } |
202 | |
203 | onEditingChanged: { |
204 | + if (findInPageMode) return |
205 | if (editing && internal.simplified) { |
206 | text = actualUrl |
207 | internal.simplified = false |
208 | @@ -324,7 +364,7 @@ |
209 | } |
210 | |
211 | onCanSimplifyTextChanged: { |
212 | - if (editing) return |
213 | + if (editing || findInPageMode) return |
214 | if (canSimplifyText && !loading && actualUrl.toString()) { |
215 | text = internal.simplifyUrl(actualUrl) |
216 | internal.simplified = true |
217 | @@ -335,7 +375,7 @@ |
218 | } |
219 | |
220 | onActualUrlChanged: { |
221 | - if (editing && actualUrl.toString()) return |
222 | + if ((editing && actualUrl.toString()) || findInPageMode) return |
223 | if (canSimplifyText) { |
224 | text = internal.simplifyUrl(actualUrl) |
225 | internal.simplified = true |
226 | @@ -346,7 +386,7 @@ |
227 | } |
228 | |
229 | onRequestedUrlChanged: { |
230 | - if (editing) return |
231 | + if (editing || findInPageMode) return |
232 | if (canSimplifyText) { |
233 | text = internal.simplifyUrl(requestedUrl) |
234 | internal.simplified = true |
235 | @@ -356,6 +396,17 @@ |
236 | } |
237 | } |
238 | |
239 | + onFindInPageModeChanged: { |
240 | + if (findInPageMode) return |
241 | + if (canSimplifyText) { |
242 | + text = internal.simplifyUrl(actualUrl) |
243 | + internal.simplified = true |
244 | + } else { |
245 | + text = actualUrl |
246 | + internal.simplified = false |
247 | + } |
248 | + } |
249 | + |
250 | function showSecurityCertificateDetails() { |
251 | if (!internal.securityCertificateDetails) { |
252 | internal.securityCertificateDetails = PopupUtils.open(Qt.resolvedUrl("SecurityCertificatePopover.qml"), certificatePopoverPositioner, {"securityStatus": securityStatus}) |
253 | |
254 | === modified file 'src/app/webbrowser/Browser.qml' |
255 | --- src/app/webbrowser/Browser.qml 2015-08-10 15:33:43 +0000 |
256 | +++ src/app/webbrowser/Browser.qml 2015-08-11 11:17:03 +0000 |
257 | @@ -29,7 +29,6 @@ |
258 | import "../UrlUtils.js" as UrlUtils |
259 | import "urlManagement.js" as UrlManagement |
260 | |
261 | - |
262 | BrowserView { |
263 | id: browser |
264 | |
265 | @@ -103,6 +102,13 @@ |
266 | Actions.ClearHistory { |
267 | enabled: browser.historyModel |
268 | onTriggered: browser.historyModel.clearAll() |
269 | + }, |
270 | + Actions.FindInPage { |
271 | + enabled: !chrome.findInPageMode |
272 | + onTriggered: { |
273 | + chrome.findInPageMode = true |
274 | + chrome.focus = true |
275 | + } |
276 | } |
277 | ] |
278 | |
279 | @@ -301,6 +307,8 @@ |
280 | |
281 | onRequestNewTab: browser.openUrlInNewTab("", true) |
282 | |
283 | + onFindInPageModeChanged: if (!chrome.findInPageMode) internal.resetFocus() |
284 | + |
285 | anchors { |
286 | left: parent.left |
287 | right: parent.right |
288 | @@ -342,12 +350,13 @@ |
289 | onTriggered: browser.openUrlInNewTab("", true) |
290 | }, |
291 | Action { |
292 | - objectName: "settings" |
293 | - text: i18n.tr("Settings") |
294 | - iconName: "settings" |
295 | + objectName: "findinpage" |
296 | + text: i18n.tr("Find in page") |
297 | + iconName: "search" |
298 | + enabled: !chrome.findInPageMode |
299 | onTriggered: { |
300 | - settingsComponent.createObject(settingsContainer) |
301 | - settingsContainer.focus = true |
302 | + chrome.findInPageMode = true |
303 | + chrome.focus = true |
304 | } |
305 | }, |
306 | Action { |
307 | @@ -367,6 +376,15 @@ |
308 | browser.incognito = true |
309 | } |
310 | } |
311 | + }, |
312 | + Action { |
313 | + objectName: "settings" |
314 | + text: i18n.tr("Settings") |
315 | + iconName: "settings" |
316 | + onTriggered: { |
317 | + settingsComponent.createObject(settingsContainer) |
318 | + settingsContainer.focus = true |
319 | + } |
320 | } |
321 | ] |
322 | |
323 | @@ -374,7 +392,13 @@ |
324 | editing: activeFocus || suggestionsList.activeFocus |
325 | |
326 | Keys.onDownPressed: if (suggestionsList.count) suggestionsList.focus = true |
327 | - Keys.onEscapePressed: internal.resetFocus() |
328 | + Keys.onEscapePressed: { |
329 | + if (chrome.findInPageMode) { |
330 | + chrome.findInPageMode = false |
331 | + } else { |
332 | + internal.resetFocus() |
333 | + } |
334 | + } |
335 | } |
336 | |
337 | ChromeController { |
338 | @@ -387,7 +411,8 @@ |
339 | |
340 | Suggestions { |
341 | id: suggestionsList |
342 | - opacity: ((chrome.state == "shown") && (activeFocus || chrome.activeFocus) && count > 0 && !chrome.drawerOpen) ? 1.0 : 0.0 |
343 | + opacity: ((chrome.state == "shown") && (activeFocus || chrome.activeFocus) && |
344 | + (count > 0) && !chrome.drawerOpen && !chrome.findInPageMode) ? 1.0 : 0.0 |
345 | Behavior on opacity { |
346 | UbuntuNumberAnimation {} |
347 | } |
348 | @@ -437,7 +462,7 @@ |
349 | terms: suggestionsList.searchTerms |
350 | searchEngine: currentSearchEngine |
351 | active: (chrome.activeFocus || suggestionsList.activeFocus) && |
352 | - !browser.incognito && |
353 | + !browser.incognito && !chrome.findInPageMode && |
354 | !UrlManagement.looksLikeAUrl(chrome.text.replace(/ /g, "+")) |
355 | |
356 | function limit(number) { |
357 | @@ -897,6 +922,10 @@ |
358 | } |
359 | |
360 | onLoadEvent: { |
361 | + if (event.type == Oxide.LoadEvent.TypeCommitted) { |
362 | + chrome.findInPageMode = false |
363 | + } |
364 | + |
365 | if (webviewimpl.incognito) { |
366 | return |
367 | } |
368 | @@ -1277,6 +1306,7 @@ |
369 | Connections { |
370 | target: tabsModel |
371 | onCurrentTabChanged: { |
372 | + chrome.findInPageMode = false |
373 | var tab = tabsModel.currentTab |
374 | if (tab) { |
375 | tab.load() |
376 | @@ -1466,5 +1496,15 @@ |
377 | enabled: chrome.visible |
378 | onTriggered: if (currentWebview) currentWebview.reload() |
379 | } |
380 | + |
381 | + // Ctrl + F: Find in Page |
382 | + KeyboardShortcut { |
383 | + modifiers: Qt.ControlModifier |
384 | + key: Qt.Key_F |
385 | + onTriggered: { |
386 | + chrome.findInPageMode = true |
387 | + chrome.focus = true |
388 | + } |
389 | + } |
390 | } |
391 | } |
392 | |
393 | === modified file 'src/app/webbrowser/Chrome.qml' |
394 | --- src/app/webbrowser/Chrome.qml 2015-08-10 15:22:00 +0000 |
395 | +++ src/app/webbrowser/Chrome.qml 2015-08-11 11:17:03 +0000 |
396 | @@ -31,6 +31,7 @@ |
397 | property alias drawerOpen: navigationBar.drawerOpen |
398 | property alias requestedUrl: navigationBar.requestedUrl |
399 | property alias canSimplifyText: navigationBar.canSimplifyText |
400 | + property alias findInPageMode: navigationBar.findInPageMode |
401 | property alias editing: navigationBar.editing |
402 | property alias incognito: navigationBar.incognito |
403 | property alias showTabsBar: tabsBar.active |
404 | |
405 | === modified file 'src/app/webbrowser/NavigationBar.qml' |
406 | --- src/app/webbrowser/NavigationBar.qml 2015-08-10 15:22:00 +0000 |
407 | +++ src/app/webbrowser/NavigationBar.qml 2015-08-11 11:17:03 +0000 |
408 | @@ -31,12 +31,16 @@ |
409 | readonly property bool drawerOpen: internal.openDrawer |
410 | property alias requestedUrl: addressbar.requestedUrl |
411 | property alias canSimplifyText: addressbar.canSimplifyText |
412 | + property alias findInPageMode: addressbar.findInPageMode |
413 | property alias editing: addressbar.editing |
414 | property alias incognito: addressbar.incognito |
415 | property alias showFaviconInAddressBar: addressbar.showFavicon |
416 | readonly property alias bookmarkTogglePlaceHolder: addressbar.bookmarkTogglePlaceHolder |
417 | property color iconColor: UbuntuColors.darkGrey |
418 | |
419 | + onFindInPageModeChanged: if (findInPageMode) addressbar.text = "" |
420 | + onIncognitoChanged: findInPageMode = false |
421 | + |
422 | function selectAll() { |
423 | addressbar.selectAll() |
424 | } |
425 | @@ -65,8 +69,8 @@ |
426 | verticalCenter: parent.verticalCenter |
427 | } |
428 | |
429 | - enabled: webview ? webview.canGoBack : false |
430 | - onTriggered: webview.goBack() |
431 | + enabled: findInPageMode || (webview ? webview.canGoBack : false) |
432 | + onTriggered: findInPageMode ? (findInPageMode = false) : webview.goBack() |
433 | } |
434 | |
435 | ChromeButton { |
436 | @@ -86,7 +90,8 @@ |
437 | verticalCenter: parent.verticalCenter |
438 | } |
439 | |
440 | - enabled: webview ? webview.canGoForward : false |
441 | + enabled: findInPageMode ? false : |
442 | + (webview ? webview.canGoForward : false) |
443 | onTriggered: webview.goForward() |
444 | } |
445 | |
446 | @@ -95,10 +100,13 @@ |
447 | |
448 | focus: true |
449 | |
450 | + findInPageMode: findInPageMode |
451 | + findController: webview ? webview.findController : null |
452 | + |
453 | anchors { |
454 | left: forwardButton.right |
455 | leftMargin: units.gu(1) |
456 | - right: drawerButton.left |
457 | + right: rightButtonsBar.left |
458 | rightMargin: units.gu(1) |
459 | verticalCenter: parent.verticalCenter |
460 | } |
461 | @@ -108,8 +116,10 @@ |
462 | loading: webview ? webview.loading : false |
463 | |
464 | onValidated: { |
465 | - webview.forceActiveFocus() |
466 | - webview.url = requestedUrl |
467 | + if (!findInPageMode) { |
468 | + webview.forceActiveFocus() |
469 | + webview.url = requestedUrl |
470 | + } |
471 | } |
472 | onRequestReload: { |
473 | webview.forceActiveFocus() |
474 | @@ -130,26 +140,67 @@ |
475 | } |
476 | } |
477 | |
478 | - ChromeButton { |
479 | - id: drawerButton |
480 | - objectName: "drawerButton" |
481 | - |
482 | - iconName: "contextual-menu" |
483 | - iconSize: 0.5 * height |
484 | - iconColor: root.iconColor |
485 | - |
486 | - height: root.height |
487 | - width: height * 0.8 |
488 | + Row { |
489 | + id: rightButtonsBar |
490 | |
491 | anchors { |
492 | right: parent.right |
493 | - verticalCenter: parent.verticalCenter |
494 | - } |
495 | - |
496 | - onTriggered: { |
497 | - if (!internal.openDrawer) { |
498 | - internal.openDrawer = drawerComponent.createObject(chrome) |
499 | - internal.openDrawer.opened = true |
500 | + top: parent.top |
501 | + bottom: parent.bottom |
502 | + } |
503 | + |
504 | + ChromeButton { |
505 | + id: findPreviousButton |
506 | + objectName: "findPreviousButton" |
507 | + |
508 | + iconName: "up" |
509 | + iconSize: 0.5 * height |
510 | + |
511 | + height: root.height |
512 | + width: height * 0.8 |
513 | + |
514 | + anchors.verticalCenter: parent.verticalCenter |
515 | + |
516 | + visible: findInPageMode |
517 | + enabled: webview && webview.findController && webview.findController.count > 1 |
518 | + onTriggered: webview.findController.previous() |
519 | + } |
520 | + |
521 | + ChromeButton { |
522 | + id: findNextButton |
523 | + objectName: "findNextButton" |
524 | + |
525 | + iconName: "down" |
526 | + iconSize: 0.5 * height |
527 | + |
528 | + height: root.height |
529 | + width: height * 0.8 |
530 | + |
531 | + anchors.verticalCenter: parent.verticalCenter |
532 | + |
533 | + visible: findInPageMode |
534 | + enabled: webview && webview.findController && webview.findController.count > 1 |
535 | + onTriggered: webview.findController.next() |
536 | + } |
537 | + |
538 | + ChromeButton { |
539 | + id: drawerButton |
540 | + objectName: "drawerButton" |
541 | + |
542 | + iconName: "contextual-menu" |
543 | + iconSize: 0.5 * height |
544 | + iconColor: root.iconColor |
545 | + |
546 | + height: root.height |
547 | + width: height * 0.8 |
548 | + |
549 | + anchors.verticalCenter: parent.verticalCenter |
550 | + |
551 | + onTriggered: { |
552 | + if (!internal.openDrawer) { |
553 | + internal.openDrawer = drawerComponent.createObject(chrome) |
554 | + internal.openDrawer.opened = true |
555 | + } |
556 | } |
557 | } |
558 | } |
559 | |
560 | === modified file 'tests/autopilot/webbrowser_app/emulators/browser.py' |
561 | --- tests/autopilot/webbrowser_app/emulators/browser.py 2015-08-10 16:15:43 +0000 |
562 | +++ tests/autopilot/webbrowser_app/emulators/browser.py 2015-08-11 11:17:03 +0000 |
563 | @@ -230,6 +230,14 @@ |
564 | def get_tabs_bar(self): |
565 | return self.select_single(TabsBar) |
566 | |
567 | + def get_find_next_button(self): |
568 | + return self.select_single("ChromeButton", |
569 | + objectName="findNextButton") |
570 | + |
571 | + def get_find_prev_button(self): |
572 | + return self.select_single("ChromeButton", |
573 | + objectName="findPreviousButton") |
574 | + |
575 | |
576 | class AddressBar(uitk.UbuntuUIToolkitCustomProxyObjectBase): |
577 | |
578 | @@ -267,6 +275,9 @@ |
579 | return self.select_single("QQuickMouseArea", |
580 | objectName="bookmarkToggle") |
581 | |
582 | + def get_find_in_page_counter(self): |
583 | + return self.select_single("Label", objectName="findInPageCounter") |
584 | + |
585 | |
586 | class TabsBar(uitk.UbuntuUIToolkitCustomProxyObjectBase): |
587 | |
588 | |
589 | === modified file 'tests/autopilot/webbrowser_app/tests/http_server.py' |
590 | --- tests/autopilot/webbrowser_app/tests/http_server.py 2015-05-26 12:30:23 +0000 |
591 | +++ tests/autopilot/webbrowser_app/tests/http_server.py 2015-08-11 11:17:03 +0000 |
592 | @@ -49,6 +49,12 @@ |
593 | body = "<p>test page 2</p>" |
594 | html = self.make_html(title, body) |
595 | self.send_html(html) |
596 | + elif self.path == "/link": |
597 | + self.send_response(200) |
598 | + html = '<html><body style="margin: 0">' |
599 | + html += '<a href="/test1"><div style="height: 100%"></div></a>' |
600 | + html += '</body></html>' |
601 | + self.send_html(html) |
602 | elif self.path.startswith("/wait/"): |
603 | delay = int(self.path[6:]) |
604 | self.send_response(200) |
605 | @@ -132,6 +138,11 @@ |
606 | html += '<div style="height: 100%"></div>' |
607 | html += '</a></body></html>' |
608 | self.send_html(html) |
609 | + elif self.path == "/findinpage": |
610 | + # send a page with some searchable text |
611 | + self.send_response(200) |
612 | + html = '<html><body>hello this is text and more text</body></html>' |
613 | + self.send_html(html) |
614 | elif self.path.startswith("/suggest"): |
615 | self.send_response(200) |
616 | self.send_header("Content-Type", "text/x-suggestions+json") |
617 | |
618 | === added file 'tests/autopilot/webbrowser_app/tests/test_findinpage.py' |
619 | --- tests/autopilot/webbrowser_app/tests/test_findinpage.py 1970-01-01 00:00:00 +0000 |
620 | +++ tests/autopilot/webbrowser_app/tests/test_findinpage.py 2015-08-11 11:17:03 +0000 |
621 | @@ -0,0 +1,156 @@ |
622 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
623 | +# |
624 | +# Copyright 2015 Canonical |
625 | +# |
626 | +# This program is free software: you can redistribute it and/or modify it |
627 | +# under the terms of the GNU General Public License version 3, as published |
628 | +# by the Free Software Foundation. |
629 | +# |
630 | +# This program is distributed in the hope that it will be useful, |
631 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
632 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
633 | +# GNU General Public License for more details. |
634 | +# |
635 | +# You should have received a copy of the GNU General Public License |
636 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
637 | + |
638 | +from autopilot.matchers import Eventually |
639 | +from testtools.matchers import Equals |
640 | + |
641 | +from webbrowser_app.tests import StartOpenRemotePageTestCaseBase |
642 | + |
643 | + |
644 | +class TestFindInPage(StartOpenRemotePageTestCaseBase): |
645 | + |
646 | + """Tests the find in page functionality.""" |
647 | + |
648 | + def setUp(self): |
649 | + super().setUp() |
650 | + self.chrome = self.main_window.chrome |
651 | + self.url = self.base_url + "/findinpage" |
652 | + |
653 | + def activate_find_in_page(self, navigateFirst=True): |
654 | + if navigateFirst: |
655 | + self.main_window.go_to_url(self.url) |
656 | + self.main_window.wait_until_page_loaded(self.url) |
657 | + |
658 | + drawer_button = self.chrome.get_drawer_button() |
659 | + self.pointing_device.click_object(drawer_button) |
660 | + self.chrome.get_drawer() |
661 | + action = self.chrome.get_drawer_action("findinpage") |
662 | + self.pointing_device.click_object(action) |
663 | + |
664 | + def test_activation(self): |
665 | + next = self.chrome.get_find_next_button() |
666 | + prev = self.chrome.get_find_prev_button() |
667 | + bar = self.chrome.address_bar |
668 | + counter = bar.get_find_in_page_counter() |
669 | + |
670 | + self.assertThat(bar.findInPageMode, Equals(False)) |
671 | + self.assertThat(next.visible, Equals(False)) |
672 | + self.assertThat(prev.visible, Equals(False)) |
673 | + self.assertThat(counter.visible, Equals(False)) |
674 | + self.assertThat(bar.activeFocus, Equals(False)) |
675 | + |
676 | + previous_text = bar.text |
677 | + |
678 | + self.activate_find_in_page(False) |
679 | + self.assertThat(bar.findInPageMode, Eventually(Equals(True))) |
680 | + self.assertThat(next.visible, Eventually(Equals(True))) |
681 | + self.assertThat(prev.visible, Eventually(Equals(True))) |
682 | + self.assertThat(counter.visible, Eventually(Equals(True))) |
683 | + self.assertThat(bar.activeFocus, Eventually(Equals(True))) |
684 | + |
685 | + self.assertThat(self.chrome.is_back_button_enabled(), Equals(True)) |
686 | + self.assertThat(self.chrome.is_forward_button_enabled(), Equals(False)) |
687 | + self.chrome.go_back() |
688 | + |
689 | + self.assertThat(bar.findInPageMode, Eventually(Equals(False))) |
690 | + self.assertThat(next.visible, Eventually(Equals(False))) |
691 | + self.assertThat(prev.visible, Eventually(Equals(False))) |
692 | + self.assertThat(counter.visible, Eventually(Equals(False))) |
693 | + self.assertThat(bar.activeFocus, Eventually(Equals(False))) |
694 | + self.assertThat(bar.text, Eventually(Equals(previous_text))) |
695 | + |
696 | + def test_counter(self): |
697 | + bar = self.chrome.address_bar |
698 | + counter = bar.get_find_in_page_counter() |
699 | + |
700 | + self.activate_find_in_page() |
701 | + bar.write("text") |
702 | + self.assertThat(counter.current, Eventually(Equals(1))) |
703 | + self.assertThat(counter.count, Eventually(Equals(2))) |
704 | + |
705 | + bar.write("hello") |
706 | + self.assertThat(counter.current, Eventually(Equals(1))) |
707 | + self.assertThat(counter.count, Eventually(Equals(1))) |
708 | + |
709 | + bar.write("") |
710 | + self.assertThat(counter.current, Eventually(Equals(0))) |
711 | + self.assertThat(counter.count, Eventually(Equals(0))) |
712 | + |
713 | + def test_navigation(self): |
714 | + bar = self.chrome.address_bar |
715 | + counter = bar.get_find_in_page_counter() |
716 | + next = self.chrome.get_find_next_button() |
717 | + prev = self.chrome.get_find_prev_button() |
718 | + |
719 | + self.activate_find_in_page() |
720 | + self.assertThat(next.enabled, Eventually(Equals(False))) |
721 | + self.assertThat(prev.enabled, Eventually(Equals(False))) |
722 | + |
723 | + bar.write("text") |
724 | + self.assertThat(next.enabled, Eventually(Equals(True))) |
725 | + self.assertThat(prev.enabled, Eventually(Equals(True))) |
726 | + |
727 | + self.pointing_device.click_object(next) |
728 | + self.assertThat(counter.current, Eventually(Equals(2))) |
729 | + self.assertThat(counter.count, Eventually(Equals(2))) |
730 | + self.pointing_device.click_object(next) |
731 | + self.assertThat(counter.current, Eventually(Equals(1))) |
732 | + self.assertThat(counter.count, Eventually(Equals(2))) |
733 | + self.pointing_device.click_object(prev) |
734 | + self.assertThat(counter.current, Eventually(Equals(2))) |
735 | + self.assertThat(counter.count, Eventually(Equals(2))) |
736 | + self.pointing_device.click_object(next) |
737 | + self.assertThat(counter.current, Eventually(Equals(1))) |
738 | + self.assertThat(counter.count, Eventually(Equals(2))) |
739 | + |
740 | + bar.write("hello") |
741 | + self.assertThat(next.enabled, Eventually(Equals(False))) |
742 | + self.assertThat(prev.enabled, Eventually(Equals(False))) |
743 | + |
744 | + def test_navigation_exits_findinpage_mode(self): |
745 | + url = self.base_url + "/link" |
746 | + self.main_window.go_to_url(url) |
747 | + self.main_window.wait_until_page_loaded(url) |
748 | + self.activate_find_in_page(False) |
749 | + bar = self.chrome.address_bar |
750 | + self.assertThat(bar.findInPageMode, Eventually(Equals(True))) |
751 | + webview = self.main_window.get_current_webview() |
752 | + self.pointing_device.click_object(webview) |
753 | + self.assertThat(bar.findInPageMode, Eventually(Equals(False))) |
754 | + |
755 | + def test_opening_new_tab_exits_findinpage_mode(self): |
756 | + self.activate_find_in_page(False) |
757 | + bar = self.chrome.address_bar |
758 | + self.assertThat(bar.findInPageMode, Eventually(Equals(True))) |
759 | + if not self.main_window.wide: |
760 | + # Remove focus from the address bar to hide the OSK |
761 | + # (that would otherwise prevent a bottom edge swipe gesture) |
762 | + webview = self.main_window.get_current_webview() |
763 | + self.pointing_device.click_object(webview) |
764 | + self.open_tabs_view() |
765 | + self.open_new_tab() |
766 | + self.assertThat(bar.findInPageMode, Eventually(Equals(False))) |
767 | + |
768 | + def test_navigation_in_new_tab_exits_findinpage_mode(self): |
769 | + url = self.base_url + "/blanktargetlink" |
770 | + self.main_window.go_to_url(url) |
771 | + self.main_window.wait_until_page_loaded(url) |
772 | + self.activate_find_in_page(False) |
773 | + bar = self.chrome.address_bar |
774 | + self.assertThat(bar.findInPageMode, Eventually(Equals(True))) |
775 | + webview = self.main_window.get_current_webview() |
776 | + self.pointing_device.click_object(webview) |
777 | + self.assertThat(bar.findInPageMode, Eventually(Equals(False))) |
778 | |
779 | === modified file 'tests/autopilot/webbrowser_app/tests/test_keyboard.py' |
780 | --- tests/autopilot/webbrowser_app/tests/test_keyboard.py 2015-07-03 15:06:08 +0000 |
781 | +++ tests/autopilot/webbrowser_app/tests/test_keyboard.py 2015-08-11 11:17:03 +0000 |
782 | @@ -294,3 +294,11 @@ |
783 | settings.wait_until_destroyed() |
784 | webview = self.main_window.get_current_webview() |
785 | self.assertThat(webview.activeFocus, Eventually(Equals(True))) |
786 | + |
787 | + def test_find_in_page_ctrl_f(self): |
788 | + address_bar = self.main_window.chrome.address_bar |
789 | + self.assertThat(address_bar.findInPageMode, Equals(False)) |
790 | + self.main_window.press_key('Ctrl+F') |
791 | + self.assertThat(address_bar.findInPageMode, Eventually(Equals(True))) |
792 | + self.main_window.press_key('Escape') |
793 | + self.assertThat(address_bar.findInPageMode, Eventually(Equals(False))) |
794 | |
795 | === modified file 'tests/unittests/qml/tst_AddressBar.qml' |
796 | --- tests/unittests/qml/tst_AddressBar.qml 2015-08-10 15:22:00 +0000 |
797 | +++ tests/unittests/qml/tst_AddressBar.qml 2015-08-11 11:17:03 +0000 |
798 | @@ -42,6 +42,11 @@ |
799 | |
800 | editing: activeFocus |
801 | canSimplifyText: true |
802 | + |
803 | + findController: QtObject { |
804 | + property int current |
805 | + property int count |
806 | + } |
807 | } |
808 | |
809 | // only exists to steal focus from the address bar |
810 | @@ -367,5 +372,15 @@ |
811 | clickItem(addressBar) |
812 | compare(addressBar.text, url) |
813 | } |
814 | + |
815 | + function test_exitingFindInPageRestoresUrl() { |
816 | + addressBar.actualUrl = "http://example.org/" |
817 | + addressBar.findInPageMode = true |
818 | + verify(addressBar.activeFocus) |
819 | + compare(addressBar.text, "") |
820 | + typeString("hello") |
821 | + addressBar.findInPageMode = false |
822 | + compare(addressBar.text, "example.org") |
823 | + } |
824 | } |
825 | } |
FAILED: Continuous integration, rev:1027 jenkins. qa.ubuntu. com/job/ webbrowser- app-ci/ 1943/ jenkins. qa.ubuntu. com/job/ generic- deb-autopilot- vivid-touch/ 3345/console jenkins. qa.ubuntu. com/job/ webbrowser- app-vivid- amd64-ci/ 700/console jenkins. qa.ubuntu. com/job/ webbrowser- app-vivid- armhf-ci/ 700/console jenkins. qa.ubuntu. com/job/ webbrowser- app-vivid- i386-ci/ 700/console jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- vivid-armhf/ 3343/console
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/webbrowser- app-ci/ 1943/rebuild
http://