Merge lp:~osomon/webbrowser-app/touch-selection-controller into lp:webbrowser-app
- touch-selection-controller
- Merge into trunk
Status: | Merged | ||||||||
---|---|---|---|---|---|---|---|---|---|
Approved by: | Olivier Tilloy | ||||||||
Approved revision: | 1347 | ||||||||
Merged at revision: | 1380 | ||||||||
Proposed branch: | lp:~osomon/webbrowser-app/touch-selection-controller | ||||||||
Merge into: | lp:webbrowser-app | ||||||||
Diff against target: |
348 lines (+222/-12) 7 files modified
debian/control (+2/-2) src/Ubuntu/Web/CMakeLists.txt (+4/-4) src/Ubuntu/Web/UbuntuWebView02.qml (+101/-1) tests/autopilot/webapp_container/tests/test_context_menu.py (+4/-4) tests/autopilot/webbrowser_app/tests/http_server.py (+6/-0) tests/autopilot/webbrowser_app/tests/test_contextmenu.py (+2/-1) tests/autopilot/webbrowser_app/tests/test_touchselection.py (+103/-0) |
||||||||
To merge this branch: | bzr merge lp:~osomon/webbrowser-app/touch-selection-controller | ||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Alexandre Abreu (community) | Approve | ||
PS Jenkins bot | continuous-integration | Needs Fixing | |
Review via email: mp+285165@code.launchpad.net |
Commit message
Add a touch selection controller.
Description of the change
PS Jenkins bot (ps-jenkins) wrote : | # |
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1333
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://
Alexandre Abreu (abreu-alexandre) wrote : | # |
branch is OK, although I found a few bugs (weird behaviors rather) in the underlying api impl,
Alexandre Abreu (abreu-alexandre) wrote : | # |
> branch is OK, although I found a few bugs (weird behaviors rather) in the
> underlying api impl,
(just to complement on that, I'll test a bit more & add oxide bugs for those)
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1337
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://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1338
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://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1339
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://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1342
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1343
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://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1344
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://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1346
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://
Preview Diff
1 | === modified file 'debian/control' |
2 | --- debian/control 2016-03-01 10:42:32 +0000 |
3 | +++ debian/control 2016-03-10 05:58:20 +0000 |
4 | @@ -12,7 +12,7 @@ |
5 | hardening-wrapper, |
6 | libapparmor-dev, |
7 | libevdev-dev, |
8 | - liboxideqt-qmlplugin (>= 1.9), |
9 | + liboxideqt-qmlplugin (>= 1.12), |
10 | libqt5sql5-sqlite, |
11 | libudev-dev, |
12 | pkg-config, |
13 | @@ -106,7 +106,7 @@ |
14 | Pre-Depends: ${misc:Pre-Depends} |
15 | Depends: ${misc:Depends}, |
16 | ${shlibs:Depends}, |
17 | - liboxideqt-qmlplugin (>= 1.9), |
18 | + liboxideqt-qmlplugin (>= 1.12), |
19 | qml-module-qtquick2 (>= 5.4), |
20 | qml-module-qtquick-window2 (>= 5.3), |
21 | qtdeclarative5-ubuntu-ui-toolkit-plugin (>= 1.3) | qtdeclarative5-ubuntu-ui-toolkit-plugin-gles (>= 1.3), |
22 | |
23 | === modified file 'src/Ubuntu/Web/CMakeLists.txt' |
24 | --- src/Ubuntu/Web/CMakeLists.txt 2015-08-25 13:56:58 +0000 |
25 | +++ src/Ubuntu/Web/CMakeLists.txt 2016-03-10 05:58:20 +0000 |
26 | @@ -17,17 +17,17 @@ |
27 | Qt5::Qml |
28 | ) |
29 | |
30 | -file(GLOB QML_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.qml qmldir *.js) |
31 | +file(GLOB PLUGIN_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.qml qmldir *.js *.png) |
32 | install(TARGETS ${PLUGIN} DESTINATION ${UBUNTU_WEB_IMPORTS_DIR}) |
33 | -install(FILES ${QML_FILES} DESTINATION ${UBUNTU_WEB_IMPORTS_DIR}) |
34 | +install(FILES ${PLUGIN_FILES} DESTINATION ${UBUNTU_WEB_IMPORTS_DIR}) |
35 | |
36 | if(NOT ${CMAKE_CURRENT_BINARY_DIR} STREQUAL ${CMAKE_CURRENT_SOURCE_DIR}) |
37 | # copy qml files over to build dir to be able to import them uninstalled |
38 | - foreach(_file ${QML_FILES}) |
39 | + foreach(_file ${PLUGIN_FILES}) |
40 | add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${_file} |
41 | DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${_file} |
42 | COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/${_file} ${CMAKE_CURRENT_BINARY_DIR}/${_file}) |
43 | endforeach(_file) |
44 | - add_custom_target(copy_files_to_build_dir DEPENDS ${QML_FILES}) |
45 | + add_custom_target(copy_files_to_build_dir DEPENDS ${PLUGIN_FILES}) |
46 | add_dependencies(${PLUGIN} copy_files_to_build_dir) |
47 | endif() |
48 | |
49 | === modified file 'src/Ubuntu/Web/UbuntuWebView02.qml' |
50 | --- src/Ubuntu/Web/UbuntuWebView02.qml 2016-02-09 15:34:24 +0000 |
51 | +++ src/Ubuntu/Web/UbuntuWebView02.qml 2016-03-10 05:58:20 +0000 |
52 | @@ -18,7 +18,7 @@ |
53 | |
54 | import QtQuick 2.4 |
55 | import QtQuick.Window 2.2 |
56 | -import com.canonical.Oxide 1.9 as Oxide |
57 | +import com.canonical.Oxide 1.12 as Oxide |
58 | import Ubuntu.Components 1.3 |
59 | import Ubuntu.Components.Popups 1.3 |
60 | import "." // QTBUG-34418 |
61 | @@ -138,6 +138,106 @@ |
62 | console.warn("WARNING: the copy() function is deprecated and does nothing.") |
63 | } |
64 | |
65 | + touchSelectionController.handle: Image { |
66 | + objectName: "touchSelectionHandle" |
67 | + readonly property int handleOrientation: orientation |
68 | + width: units.gu(1.5) |
69 | + height: units.gu(1.5) |
70 | + source: "handle.png" |
71 | + Component.onCompleted: horizontalPaddingRatio = 0.5 |
72 | + } |
73 | + |
74 | + UbuntuShape { |
75 | + objectName: "touchSelectionActions" |
76 | + // FIXME: hide contextual actions while resizing the |
77 | + // selection (needs an additional API in oxide?) |
78 | + visible: _webview.activeFocus && _webview.touchSelectionController.active && !selectionOutOfSight |
79 | + aspect: UbuntuShape.DropShadow |
80 | + backgroundColor: "white" |
81 | + readonly property int padding: units.gu(1) |
82 | + width: touchSelectionActionsRow.width + padding * 2 |
83 | + height: childrenRect.height + padding * 2 |
84 | + |
85 | + readonly property rect bounds: _webview.touchSelectionController.bounds |
86 | + readonly property bool selectionOutOfSight: (bounds.x > _webview.width) || ((bounds.x + bounds.width) < 0) || (bounds.y > _webview.height) || ((bounds.y + bounds.height) < 0) |
87 | + readonly property real handleHeight: units.gu(1.5) |
88 | + readonly property real spacing: units.gu(1) |
89 | + readonly property bool fitsBelow: (bounds.y + bounds.height + handleHeight + spacing + height) <= _webview.height |
90 | + readonly property bool fitsAbove: (bounds.y - spacing - height) >= (_webview.locationBarController.height + _webview.locationBarController.offset) |
91 | + readonly property real xCentered: bounds.x + (bounds.width - width) / 2 |
92 | + x: ((xCentered >= 0) && ((xCentered + width) <= _webview.width)) |
93 | + ? xCentered : (xCentered < 0) ? 0 : _webview.width - width |
94 | + y: fitsBelow ? (bounds.y + bounds.height + handleHeight + spacing) |
95 | + : fitsAbove ? (bounds.y - spacing - height) |
96 | + : (_webview.height + _webview.locationBarController.height + _webview.locationBarController.offset - height) / 2 |
97 | + |
98 | + ActionList { |
99 | + id: touchSelectionActions |
100 | + Action { |
101 | + name: "selectall" |
102 | + text: i18n.dtr('ubuntu-ui-toolkit', "Select All") |
103 | + iconName: "edit-select-all" |
104 | + enabled: _webview.editingCapabilities & Oxide.WebView.SelectAllCapability |
105 | + visible: enabled |
106 | + onTriggered: _webview.executeEditingCommand(Oxide.WebView.EditingCommandSelectAll) |
107 | + } |
108 | + Action { |
109 | + name: "cut" |
110 | + text: i18n.dtr('ubuntu-ui-toolkit', "Cut") |
111 | + iconName: "edit-cut" |
112 | + enabled: _webview.editingCapabilities & Oxide.WebView.CutCapability |
113 | + visible: enabled |
114 | + onTriggered: _webview.executeEditingCommand(Oxide.WebView.EditingCommandCut) |
115 | + } |
116 | + Action { |
117 | + name: "copy" |
118 | + text: i18n.dtr('ubuntu-ui-toolkit', "Copy") |
119 | + iconName: "edit-copy" |
120 | + enabled: _webview.editingCapabilities & Oxide.WebView.CopyCapability |
121 | + visible: enabled |
122 | + onTriggered: _webview.executeEditingCommand(Oxide.WebView.EditingCommandCopy) |
123 | + } |
124 | + Action { |
125 | + name: "paste" |
126 | + text: i18n.dtr('ubuntu-ui-toolkit', "Paste") |
127 | + iconName: "edit-paste" |
128 | + enabled: _webview.editingCapabilities & Oxide.WebView.PasteCapability |
129 | + visible: enabled |
130 | + onTriggered: _webview.executeEditingCommand(Oxide.WebView.EditingCommandPaste) |
131 | + } |
132 | + } |
133 | + |
134 | + Row { |
135 | + id: touchSelectionActionsRow |
136 | + x: parent.padding |
137 | + y: parent.padding |
138 | + width: { |
139 | + // work around what seems to be a bug in Row’s childrenRect.width |
140 | + var w = 0 |
141 | + for (var i in visibleChildren) { |
142 | + w += visibleChildren[i].width |
143 | + } |
144 | + return w |
145 | + } |
146 | + height: units.gu(6) |
147 | + |
148 | + Repeater { |
149 | + model: touchSelectionActions.actions.length |
150 | + AbstractButton { |
151 | + objectName: "touchSelectionAction_" + action.name |
152 | + anchors { |
153 | + top: parent.top |
154 | + bottom: parent.bottom |
155 | + } |
156 | + width: Math.max(units.gu(5), implicitWidth) + units.gu(2) |
157 | + action: touchSelectionActions.actions[modelData] |
158 | + styleName: "ToolbarButtonStyle" |
159 | + activeFocusOnPress: false |
160 | + } |
161 | + } |
162 | + } |
163 | + } |
164 | + |
165 | QtObject { |
166 | id: internal |
167 | property int lastLoadRequestStatus: -1 |
168 | |
169 | === added file 'src/Ubuntu/Web/handle@27.png' |
170 | Binary files src/Ubuntu/Web/handle@27.png 1970-01-01 00:00:00 +0000 and src/Ubuntu/Web/handle@27.png 2016-03-10 05:58:20 +0000 differ |
171 | === modified file 'tests/autopilot/webapp_container/tests/test_context_menu.py' |
172 | --- tests/autopilot/webapp_container/tests/test_context_menu.py 2016-03-07 18:10:35 +0000 |
173 | +++ tests/autopilot/webapp_container/tests/test_context_menu.py 2016-03-10 05:58:20 +0000 |
174 | @@ -1,6 +1,6 @@ |
175 | # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
176 | # |
177 | -# Copyright 2015 Canonical |
178 | +# Copyright 2015-2016 Canonical |
179 | # |
180 | # This program is free software: you can redistribute it and/or modify it |
181 | # under the terms of the GNU General Public License version 3, as published |
182 | @@ -16,11 +16,10 @@ |
183 | |
184 | import time |
185 | |
186 | -import testtools |
187 | - |
188 | from autopilot.platform import model |
189 | from autopilot.matchers import Eventually |
190 | -from testtools.matchers import Equals, StartsWith, GreaterThan |
191 | +import testtools |
192 | +from testtools.matchers import Equals, GreaterThan, StartsWith |
193 | |
194 | from webapp_container.tests import WebappContainerTestCaseWithLocalContentBase |
195 | |
196 | @@ -299,6 +298,7 @@ |
197 | self._test_copy_image() |
198 | |
199 | |
200 | +@testtools.skipIf(model() != "Desktop", "on desktop only") |
201 | class TestContextMenuTextArea(TestContextMenuBase): |
202 | |
203 | def _test_actions(self): |
204 | |
205 | === modified file 'tests/autopilot/webbrowser_app/tests/http_server.py' |
206 | --- tests/autopilot/webbrowser_app/tests/http_server.py 2016-02-29 20:42:13 +0000 |
207 | +++ tests/autopilot/webbrowser_app/tests/http_server.py 2016-03-10 05:58:20 +0000 |
208 | @@ -246,6 +246,12 @@ |
209 | html += 'history.pushState(null, null, "/statepushed"); });' |
210 | html += '</script></body></html>' |
211 | self.send_html(html) |
212 | + elif self.path == "/super": |
213 | + self.send_response(200) |
214 | + html = '<html><body><div style="position: fixed; top: 50%; left: ' |
215 | + html += '50%; transform: translate(-50%, -50%); font-size: 500%">' |
216 | + html += 'Supercalifragilisticexpialidocious</div></body></html>' |
217 | + self.send_html(html) |
218 | else: |
219 | self.send_error(404) |
220 | |
221 | |
222 | === modified file 'tests/autopilot/webbrowser_app/tests/test_contextmenu.py' |
223 | --- tests/autopilot/webbrowser_app/tests/test_contextmenu.py 2016-01-05 08:35:58 +0000 |
224 | +++ tests/autopilot/webbrowser_app/tests/test_contextmenu.py 2016-03-10 05:58:20 +0000 |
225 | @@ -1,6 +1,6 @@ |
226 | # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
227 | # |
228 | -# Copyright 2015 Canonical |
229 | +# Copyright 2015-2016 Canonical |
230 | # |
231 | # This program is free software: you can redistribute it and/or modify it |
232 | # under the terms of the GNU General Public License version 3, as published |
233 | @@ -137,6 +137,7 @@ |
234 | self.menu.click_action("CopyImageContextualAction") |
235 | |
236 | |
237 | +@testtools.skipIf(model() != "Desktop", "on desktop only") |
238 | class TestContextMenuTextArea(TestContextMenuBase): |
239 | |
240 | def setUp(self): |
241 | |
242 | === added file 'tests/autopilot/webbrowser_app/tests/test_touchselection.py' |
243 | --- tests/autopilot/webbrowser_app/tests/test_touchselection.py 1970-01-01 00:00:00 +0000 |
244 | +++ tests/autopilot/webbrowser_app/tests/test_touchselection.py 2016-03-10 05:58:20 +0000 |
245 | @@ -0,0 +1,103 @@ |
246 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
247 | +# |
248 | +# Copyright 2016 Canonical |
249 | +# |
250 | +# This program is free software: you can redistribute it and/or modify it |
251 | +# under the terms of the GNU General Public License version 3, as published |
252 | +# by the Free Software Foundation. |
253 | +# |
254 | +# This program is distributed in the hope that it will be useful, |
255 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
256 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
257 | +# GNU General Public License for more details. |
258 | +# |
259 | +# You should have received a copy of the GNU General Public License |
260 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
261 | + |
262 | +from autopilot.platform import model |
263 | +import testtools |
264 | +from testtools.matchers import Equals, MatchesAny |
265 | +import time |
266 | + |
267 | +from webbrowser_app.tests import StartOpenRemotePageTestCaseBase |
268 | + |
269 | + |
270 | +class TestTouchSelectionBase(StartOpenRemotePageTestCaseBase): |
271 | + |
272 | + def get_actions(self): |
273 | + webview = self.main_window.get_current_webview() |
274 | + return webview.wait_select_single(objectName="touchSelectionActions", |
275 | + visible=True) |
276 | + |
277 | + def long_press_webview(self): |
278 | + webview = self.main_window.get_current_webview() |
279 | + chrome = self.main_window.chrome |
280 | + x = webview.globalRect.x + webview.globalRect.width // 2 |
281 | + y = webview.globalRect.y + \ |
282 | + (webview.globalRect.height + chrome.height) // 2 |
283 | + self.pointing_device.move(x, y) |
284 | + self.pointing_device.press() |
285 | + time.sleep(1.5) |
286 | + self.pointing_device.release() |
287 | + return self.get_actions() |
288 | + |
289 | + def get_visible_actions(self, actions): |
290 | + return actions.select_many(styleName="ToolbarButtonStyle", |
291 | + visible=True) |
292 | + |
293 | + def get_handles(self): |
294 | + webview = self.main_window.get_current_webview() |
295 | + handles = webview.select_many(objectName="touchSelectionHandle", |
296 | + visible=True) |
297 | + handles.sort(key=lambda handle: handle.globalRect.x) |
298 | + return handles |
299 | + |
300 | + |
301 | +@testtools.skipIf(model() == "Desktop", "on devices only") |
302 | +class TestTouchSelection(TestTouchSelectionBase): |
303 | + |
304 | + def setUp(self): |
305 | + super(TestTouchSelection, self).setUp(path="/super") |
306 | + |
307 | + def test_touch_selection(self): |
308 | + actions = self.long_press_webview() |
309 | + self.assertThat(len(self.get_visible_actions(actions)), Equals(2)) |
310 | + actions.select_single(objectName="touchSelectionAction_selectall", |
311 | + visible=True) |
312 | + actions.select_single(objectName="touchSelectionAction_copy", |
313 | + visible=True) |
314 | + |
315 | + handles = self.get_handles() |
316 | + self.assertThat(len(handles), Equals(2)) |
317 | + left = 0 # Oxide.TouchSelectionController.HandleOrientationLeft |
318 | + self.assertThat(handles[0].handleOrientation, Equals(left)) |
319 | + right = 2 # Oxide.TouchSelectionController.HandleOrientationRight |
320 | + self.assertThat(handles[1].handleOrientation, Equals(right)) |
321 | + |
322 | + |
323 | +@testtools.skipIf(model() == "Desktop", "on devices only") |
324 | +class TestTouchInsertion(TestTouchSelectionBase): |
325 | + |
326 | + def setUp(self): |
327 | + super(TestTouchInsertion, self).setUp(path="/textarea") |
328 | + |
329 | + def test_touch_insertion(self): |
330 | + webview = self.main_window.get_current_webview() |
331 | + self.pointing_device.click_object(webview) |
332 | + actions = self.get_actions() |
333 | + self.assertThat(len(self.get_visible_actions(actions)), |
334 | + MatchesAny(Equals(1), Equals(2))) |
335 | + actions.select_single(objectName="touchSelectionAction_selectall", |
336 | + visible=True) |
337 | + if len(self.get_visible_actions(actions)) == 2: |
338 | + actions.select_single(objectName="touchSelectionAction_paste", |
339 | + visible=True) |
340 | + |
341 | + handles = self.get_handles() |
342 | + self.assertThat(len(handles), Equals(1)) |
343 | + center = 1 # Oxide.TouchSelectionController.HandleOrientationCenter |
344 | + self.assertThat(handles[0].handleOrientation, Equals(center)) |
345 | + |
346 | + |
347 | +# TODO: add tests for selection resizing, and activation of the contextual |
348 | +# actions (verifying the contents of the clipboard is a complex task). |
FAILED: Continuous integration, rev:1332 jenkins. qa.ubuntu. com/job/ webbrowser- app-ci/ 2611/ jenkins. qa.ubuntu. com/job/ generic- deb-autopilot- vivid-touch/ 6362 jenkins. qa.ubuntu. com/job/ webbrowser- app-vivid- amd64-ci/ 1364 jenkins. qa.ubuntu. com/job/ webbrowser- app-vivid- armhf-ci/ 1364 jenkins. qa.ubuntu. com/job/ webbrowser- app-vivid- armhf-ci/ 1364/artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ webbrowser- app-vivid- i386-ci/ 1364 jenkins. qa.ubuntu. com/job/ generic- deb-autopilot- runner- vivid-touch/ 4880 jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- vivid-armhf/ 6373 jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- vivid-armhf/ 6373/artifact/ work/output/ *zip*/output. zip s-jenkins. ubuntu- ci:8080/ job/touch- flash-device/ 27354
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: s-jenkins. ubuntu- ci:8080/ job/webbrowser- app-ci/ 2611/rebuild
http://