Merge lp:~osomon/webbrowser-app/webview-capture into lp:webbrowser-app
- webview-capture
- Merge into trunk
Status: | Merged | ||||||||
---|---|---|---|---|---|---|---|---|---|
Approved by: | Olivier Tilloy | ||||||||
Approved revision: | 843 | ||||||||
Merged at revision: | 840 | ||||||||
Proposed branch: | lp:~osomon/webbrowser-app/webview-capture | ||||||||
Merge into: | lp:webbrowser-app | ||||||||
Diff against target: |
726 lines (+471/-65) 12 files modified
src/app/webbrowser/Browser.qml (+60/-33) src/app/webbrowser/CMakeLists.txt (+5/-1) src/app/webbrowser/TabPreview.qml (+19/-25) src/app/webbrowser/TabsView.qml (+1/-2) src/app/webbrowser/file-operations.cpp (+32/-0) src/app/webbrowser/file-operations.h (+36/-0) src/app/webbrowser/item-capture.cpp (+123/-0) src/app/webbrowser/item-capture.h (+63/-0) src/app/webbrowser/webbrowser-app.cpp (+11/-0) tests/unittests/qml/CMakeLists.txt (+7/-2) tests/unittests/qml/tst_ItemCapture.qml (+108/-0) tests/unittests/qml/tst_QmlTests.cpp (+6/-2) |
||||||||
To merge this branch: | bzr merge lp:~osomon/webbrowser-app/webview-capture | ||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot | continuous-integration | Needs Fixing | |
Ubuntu Phablet Team | Pending | ||
Review via email: mp+243034@code.launchpad.net |
Commit message
Take captures of live webviews and cache them on disk, to use them as tab previews.
Description of the change
PS Jenkins bot (ps-jenkins) wrote : | # |
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:830
http://
Executed test runs:
FAILURE: http://
UNSTABLE: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
FAILURE: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:831
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
SUCCESS: 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:831
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
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:832
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
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:836
http://
Executed test runs:
SUCCESS: http://
FAILURE: http://
FAILURE: http://
SUCCESS: http://
deb: http://
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
- 842. By Olivier Tilloy
-
Remove an unnecessary mutex, when updatePainNode() is called, the GUI thread is blocked.
- 843. By Olivier Tilloy
-
Remove the superfluous onCaptureFinished wrapper.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:841
http://
Executed test runs:
UNSTABLE: http://
UNSTABLE: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
Preview Diff
1 | === modified file 'src/app/webbrowser/Browser.qml' |
2 | --- src/app/webbrowser/Browser.qml 2014-11-11 18:30:38 +0000 |
3 | +++ src/app/webbrowser/Browser.qml 2014-12-11 18:51:44 +0000 |
4 | @@ -76,37 +76,6 @@ |
5 | ] |
6 | |
7 | Item { |
8 | - id: previewsContainer |
9 | - |
10 | - width: tabContainer.width |
11 | - height: tabContainer.height |
12 | - y: tabContainer.y |
13 | - |
14 | - Component { |
15 | - id: previewComponent |
16 | - |
17 | - ShaderEffectSource { |
18 | - id: preview |
19 | - |
20 | - property var tab |
21 | - |
22 | - width: parent.width |
23 | - height: parent.height |
24 | - |
25 | - sourceItem: tab ? tab.webview : null |
26 | - |
27 | - onTabChanged: { |
28 | - if (!tab) { |
29 | - this.destroy() |
30 | - } |
31 | - } |
32 | - |
33 | - live: mainView.visible && (browser.currentWebview === sourceItem) |
34 | - } |
35 | - } |
36 | - } |
37 | - |
38 | - Item { |
39 | id: mainView |
40 | |
41 | anchors.fill: parent |
42 | @@ -363,6 +332,7 @@ |
43 | id: tabComponent |
44 | |
45 | FocusScope { |
46 | + property string uniqueId: this.toString() + "-" + Date.now() |
47 | property url initialUrl |
48 | property string initialTitle |
49 | property var request |
50 | @@ -370,7 +340,7 @@ |
51 | readonly property url url: webview ? webview.url : initialUrl |
52 | readonly property string title: webview ? webview.title : initialTitle |
53 | readonly property url icon: webview ? webview.icon : "" |
54 | - property var preview |
55 | + property url preview |
56 | |
57 | anchors.fill: parent |
58 | |
59 | @@ -388,6 +358,56 @@ |
60 | } |
61 | } |
62 | |
63 | + function close() { |
64 | + unload() |
65 | + if (preview) { |
66 | + FileOperations.remove(preview) |
67 | + } |
68 | + destroy() |
69 | + } |
70 | + |
71 | + property var captureTaker |
72 | + Component { |
73 | + id: captureComponent |
74 | + ItemCapture { |
75 | + quality: 50 |
76 | + onCaptureFinished: { |
77 | + if ((request == uniqueId) && capture.toString()) { |
78 | + if (preview == capture) { |
79 | + // Ensure that the preview URL actually changes, |
80 | + // for the image to be reloaded |
81 | + preview = "" |
82 | + } |
83 | + preview = capture |
84 | + } |
85 | + if (!webview.visible) { |
86 | + captureTaker.destroy() |
87 | + } |
88 | + } |
89 | + } |
90 | + } |
91 | + function createCaptureTakerIfNeeded() { |
92 | + if (!captureTaker) { |
93 | + captureTaker = captureComponent.createObject(webview) |
94 | + } |
95 | + } |
96 | + onWebviewChanged: { |
97 | + if (webview) { |
98 | + createCaptureTakerIfNeeded() |
99 | + } |
100 | + } |
101 | + |
102 | + Connections { |
103 | + target: webview |
104 | + onVisibleChanged: { |
105 | + if (webview.visible) { |
106 | + createCaptureTakerIfNeeded() |
107 | + } else { |
108 | + captureTaker.requestCapture(uniqueId) |
109 | + } |
110 | + } |
111 | + } |
112 | + |
113 | Component.onCompleted: { |
114 | if (request) { |
115 | // Instantiating the webview cannot be delayed because the request |
116 | @@ -502,7 +522,6 @@ |
117 | tabsModel.setCurrent(index) |
118 | chrome.requestedUrl = tab.initialUrl |
119 | } |
120 | - tab.preview = previewComponent.createObject(previewsContainer, {tab: tab}) |
121 | } |
122 | |
123 | function focusAddressBar() { |
124 | @@ -568,13 +587,21 @@ |
125 | // history, current scroll offset and form data. See http://pad.lv/1353143. |
126 | function serializeTabState(tab) { |
127 | var state = {} |
128 | + state.uniqueId = tab.uniqueId |
129 | state.url = tab.url.toString() |
130 | state.title = tab.title |
131 | + state.preview = tab.preview.toString() |
132 | return state |
133 | } |
134 | |
135 | function createTabFromState(state) { |
136 | var properties = {"initialUrl": state.url, "initialTitle": state.title} |
137 | + if ('uniqueId' in state) { |
138 | + properties["uniqueId"] = state.uniqueId |
139 | + } |
140 | + if ('preview' in state) { |
141 | + properties["preview"] = state.preview |
142 | + } |
143 | return tabComponent.createObject(tabContainer, properties) |
144 | } |
145 | } |
146 | |
147 | === modified file 'src/app/webbrowser/CMakeLists.txt' |
148 | --- src/app/webbrowser/CMakeLists.txt 2014-10-15 09:57:35 +0000 |
149 | +++ src/app/webbrowser/CMakeLists.txt 2014-12-11 18:51:44 +0000 |
150 | @@ -25,7 +25,11 @@ |
151 | |
152 | qt5_use_modules(${WEBBROWSER_APP_MODELS} Core Sql) |
153 | |
154 | +include_directories(${Qt5Quick_PRIVATE_INCLUDE_DIRS}) |
155 | + |
156 | set(WEBBROWSER_APP_SRC |
157 | + file-operations.cpp |
158 | + item-capture.cpp |
159 | searchengine.cpp |
160 | settings.cpp |
161 | webbrowser-app.cpp |
162 | @@ -37,7 +41,7 @@ |
163 | |
164 | target_link_libraries(${WEBBROWSER_APP} ${COMMONLIB} ${WEBBROWSER_APP_MODELS}) |
165 | |
166 | -qt5_use_modules(${WEBBROWSER_APP} Core Gui Qml Quick) |
167 | +qt5_use_modules(${WEBBROWSER_APP} Concurrent Core Gui Qml Quick) |
168 | |
169 | install(TARGETS ${WEBBROWSER_APP} |
170 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) |
171 | |
172 | === modified file 'src/app/webbrowser/TabPreview.qml' |
173 | --- src/app/webbrowser/TabPreview.qml 2014-08-21 08:02:42 +0000 |
174 | +++ src/app/webbrowser/TabPreview.qml 2014-12-11 18:51:44 +0000 |
175 | @@ -146,6 +146,7 @@ |
176 | Image { |
177 | visible: !previewContainer.visible |
178 | source: "assets/tab-artwork.png" |
179 | + asynchronous: true |
180 | fillMode: Image.PreserveAspectFit |
181 | height: Math.min(parent.height / 1.6, units.gu(28)) |
182 | width: height |
183 | @@ -166,11 +167,25 @@ |
184 | } |
185 | } |
186 | |
187 | - Item { |
188 | + Image { |
189 | id: previewContainer |
190 | - visible: tabPreview.tab ? tabPreview.tab.webview : false |
191 | - anchors.fill: parent |
192 | - clip: true |
193 | + visible: source.toString() && (status == Image.Ready) |
194 | + anchors { |
195 | + left: parent.left |
196 | + right: parent.right |
197 | + top: parent.top |
198 | + } |
199 | + height: sourceSize.height |
200 | + fillMode: Image.Pad |
201 | + source: tabPreview.tab ? tabPreview.tab.preview : "" |
202 | + asynchronous: true |
203 | + cache: false |
204 | + onStatusChanged: { |
205 | + if (status == Image.Error) { |
206 | + // The cached preview doesn’t exist any longer |
207 | + tabPreview.tab.preview = "" |
208 | + } |
209 | + } |
210 | } |
211 | |
212 | MouseArea { |
213 | @@ -189,25 +204,4 @@ |
214 | opacity: 0.3 |
215 | } |
216 | } |
217 | - |
218 | - QtObject { |
219 | - id: internal |
220 | - property var previewParent |
221 | - } |
222 | - |
223 | - Component.onCompleted: { |
224 | - var preview = tabPreview.tab.preview |
225 | - internal.previewParent = preview.parent |
226 | - preview.parent = previewContainer |
227 | - preview.width = internal.previewParent.width |
228 | - preview.height = internal.previewParent.height |
229 | - } |
230 | - Component.onDestruction: { |
231 | - if (tabPreview.tab) { |
232 | - var preview = tabPreview.tab.preview |
233 | - preview.parent = internal.previewParent |
234 | - preview.width = preview.parent.width |
235 | - preview.height = preview.parent.height |
236 | - } |
237 | - } |
238 | } |
239 | |
240 | === modified file 'src/app/webbrowser/TabsView.qml' |
241 | --- src/app/webbrowser/TabsView.qml 2014-11-12 11:04:34 +0000 |
242 | +++ src/app/webbrowser/TabsView.qml 2014-12-11 18:51:44 +0000 |
243 | @@ -66,8 +66,7 @@ |
244 | onCloseRequested: { |
245 | var tab = tabsview.model.remove(index) |
246 | if (tab) { |
247 | - tab.unload() |
248 | - tab.destroy() |
249 | + tab.close() |
250 | } |
251 | if (tabsview.model.count === 0) { |
252 | tabsview.newTabRequested() |
253 | |
254 | === added file 'src/app/webbrowser/file-operations.cpp' |
255 | --- src/app/webbrowser/file-operations.cpp 1970-01-01 00:00:00 +0000 |
256 | +++ src/app/webbrowser/file-operations.cpp 2014-12-11 18:51:44 +0000 |
257 | @@ -0,0 +1,32 @@ |
258 | +/* |
259 | + * Copyright 2014 Canonical Ltd. |
260 | + * |
261 | + * This file is part of webbrowser-app. |
262 | + * |
263 | + * webbrowser-app is free software; you can redistribute it and/or modify |
264 | + * it under the terms of the GNU General Public License as published by |
265 | + * the Free Software Foundation; version 3. |
266 | + * |
267 | + * webbrowser-app is distributed in the hope that it will be useful, |
268 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
269 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
270 | + * GNU General Public License for more details. |
271 | + * |
272 | + * You should have received a copy of the GNU General Public License |
273 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
274 | + */ |
275 | + |
276 | +#include "file-operations.h" |
277 | + |
278 | +#include <QtCore/QFile> |
279 | +#include <QtCore/QUrl> |
280 | + |
281 | +FileOperations::FileOperations(QObject* parent) |
282 | + : QObject(parent) |
283 | +{ |
284 | +} |
285 | + |
286 | +bool FileOperations::remove(const QUrl& file) const |
287 | +{ |
288 | + return QFile::remove(file.toLocalFile()); |
289 | +} |
290 | |
291 | === added file 'src/app/webbrowser/file-operations.h' |
292 | --- src/app/webbrowser/file-operations.h 1970-01-01 00:00:00 +0000 |
293 | +++ src/app/webbrowser/file-operations.h 2014-12-11 18:51:44 +0000 |
294 | @@ -0,0 +1,36 @@ |
295 | +/* |
296 | + * Copyright 2014 Canonical Ltd. |
297 | + * |
298 | + * This file is part of webbrowser-app. |
299 | + * |
300 | + * webbrowser-app is free software; you can redistribute it and/or modify |
301 | + * it under the terms of the GNU General Public License as published by |
302 | + * the Free Software Foundation; version 3. |
303 | + * |
304 | + * webbrowser-app is distributed in the hope that it will be useful, |
305 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
306 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
307 | + * GNU General Public License for more details. |
308 | + * |
309 | + * You should have received a copy of the GNU General Public License |
310 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
311 | + */ |
312 | + |
313 | +#ifndef __FILE_OPERATIONS_H__ |
314 | +#define __FILE_OPERATIONS_H__ |
315 | + |
316 | +#include <QtCore/QObject> |
317 | + |
318 | +class QUrl; |
319 | + |
320 | +class FileOperations : public QObject |
321 | +{ |
322 | + Q_OBJECT |
323 | + |
324 | +public: |
325 | + explicit FileOperations(QObject* parent=0); |
326 | + |
327 | + Q_INVOKABLE bool remove(const QUrl& file) const; |
328 | +}; |
329 | + |
330 | +#endif // __FILE_OPERATIONS_H__ |
331 | |
332 | === added file 'src/app/webbrowser/item-capture.cpp' |
333 | --- src/app/webbrowser/item-capture.cpp 1970-01-01 00:00:00 +0000 |
334 | +++ src/app/webbrowser/item-capture.cpp 2014-12-11 18:51:44 +0000 |
335 | @@ -0,0 +1,123 @@ |
336 | +/* |
337 | + * Copyright 2014 Canonical Ltd. |
338 | + * |
339 | + * This file is part of webbrowser-app. |
340 | + * |
341 | + * webbrowser-app is free software; you can redistribute it and/or modify |
342 | + * it under the terms of the GNU General Public License as published by |
343 | + * the Free Software Foundation; version 3. |
344 | + * |
345 | + * webbrowser-app is distributed in the hope that it will be useful, |
346 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
347 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
348 | + * GNU General Public License for more details. |
349 | + * |
350 | + * You should have received a copy of the GNU General Public License |
351 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
352 | + */ |
353 | + |
354 | +// local |
355 | +#include "item-capture.h" |
356 | + |
357 | +// Qt |
358 | +#include <QtConcurrent/QtConcurrent> |
359 | +#include <QtCore/QDebug> |
360 | +#include <QtCore/QDir> |
361 | +#include <QtCore/QMetaObject> |
362 | +#include <QtCore/QStandardPaths> |
363 | +#include <QtGui/QImage> |
364 | +#include <QtQuick/private/qquickitem_p.h> |
365 | + |
366 | +ItemCapture::ItemCapture(QQuickItem* parent) |
367 | + : QQuickShaderEffectSource(parent) |
368 | + , m_quality(-1) |
369 | +{ |
370 | + connect(this, SIGNAL(parentChanged(QQuickItem*)), SLOT(onParentChanged(QQuickItem*))); |
371 | + |
372 | + setScale(0); |
373 | + |
374 | + QDir cacheLocation(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/captures"); |
375 | + m_cacheLocation = cacheLocation.absolutePath(); |
376 | + if (!cacheLocation.exists()) { |
377 | + QDir::root().mkpath(m_cacheLocation); |
378 | + } |
379 | +} |
380 | + |
381 | +const int ItemCapture::quality() const |
382 | +{ |
383 | + return m_quality; |
384 | +} |
385 | + |
386 | +void ItemCapture::setQuality(const int quality) |
387 | +{ |
388 | + if (quality != m_quality) { |
389 | + if ((quality >= -1) && (quality <= 100)) { |
390 | + m_quality = quality; |
391 | + Q_EMIT qualityChanged(); |
392 | + } else { |
393 | + qWarning() << "Invalid value for quality, must be between 0 and 100 (or -1 for default)"; |
394 | + } |
395 | + } |
396 | +} |
397 | + |
398 | +void ItemCapture::onParentChanged(QQuickItem* parent) |
399 | +{ |
400 | + if (sourceItem()) { |
401 | + sourceItem()->disconnect(this); |
402 | + } |
403 | + QQuickItemPrivate::get(this)->anchors()->setFill(parent); |
404 | + setSourceItem(parent); |
405 | + if (parent) { |
406 | + connect(parent, SIGNAL(visibleChanged()), SLOT(onParentVisibleChanged())); |
407 | + } |
408 | +} |
409 | + |
410 | +void ItemCapture::onParentVisibleChanged() |
411 | +{ |
412 | + setLive(parentItem()->isVisible()); |
413 | +} |
414 | + |
415 | +QSGNode* ItemCapture::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData* updatePaintNodeData) |
416 | +{ |
417 | + QSGNode* newNode = QQuickShaderEffectSource::updatePaintNode(oldNode, updatePaintNodeData); |
418 | + if (!m_request.isEmpty()) { |
419 | + QString request = m_request; |
420 | + m_request.clear(); |
421 | + QQuickShaderEffectTexture* texture = |
422 | + qobject_cast<QQuickShaderEffectTexture*>(textureProvider()->texture()); |
423 | + QOpenGLContext* ctx = |
424 | + QQuickItemPrivate::get(this)->sceneGraphRenderContext()->openglContext(); |
425 | + if (ctx->makeCurrent(ctx->surface())) { |
426 | + QImage image = texture->toImage().mirrored(); |
427 | + if (!image.isNull()) { |
428 | + QString filePath = m_cacheLocation + "/" + request + ".jpg"; |
429 | + QtConcurrent::run(this, &ItemCapture::saveImage, image, filePath, m_quality, request); |
430 | + return newNode; |
431 | + } |
432 | + } |
433 | + QMetaObject::invokeMethod(this, "captureFinished", Qt::QueuedConnection, |
434 | + Q_ARG(QString, request), Q_ARG(QUrl, QUrl())); |
435 | + } |
436 | + return newNode; |
437 | +} |
438 | + |
439 | +void ItemCapture::requestCapture(const QString& id) |
440 | +{ |
441 | + if (id.contains("/")) { |
442 | + qWarning() << "Invalid ID (contains slashes)"; |
443 | + Q_EMIT captureFinished(id, QUrl()); |
444 | + } |
445 | + m_request = id; |
446 | + scheduleUpdate(); |
447 | +} |
448 | + |
449 | +void ItemCapture::saveImage(const QImage& image, const QString& filePath, |
450 | + const int quality, const QString& request) |
451 | +{ |
452 | + QUrl capture; |
453 | + if (image.save(filePath, 0, quality)) { |
454 | + capture = QUrl::fromLocalFile(filePath); |
455 | + } |
456 | + QMetaObject::invokeMethod(this, "captureFinished", Qt::QueuedConnection, |
457 | + Q_ARG(QString, request), Q_ARG(QUrl, capture)); |
458 | +} |
459 | |
460 | === added file 'src/app/webbrowser/item-capture.h' |
461 | --- src/app/webbrowser/item-capture.h 1970-01-01 00:00:00 +0000 |
462 | +++ src/app/webbrowser/item-capture.h 2014-12-11 18:51:44 +0000 |
463 | @@ -0,0 +1,63 @@ |
464 | +/* |
465 | + * Copyright 2014 Canonical Ltd. |
466 | + * |
467 | + * This file is part of webbrowser-app. |
468 | + * |
469 | + * webbrowser-app is free software; you can redistribute it and/or modify |
470 | + * it under the terms of the GNU General Public License as published by |
471 | + * the Free Software Foundation; version 3. |
472 | + * |
473 | + * webbrowser-app is distributed in the hope that it will be useful, |
474 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
475 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
476 | + * GNU General Public License for more details. |
477 | + * |
478 | + * You should have received a copy of the GNU General Public License |
479 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
480 | + */ |
481 | + |
482 | +#ifndef __ITEM_CAPTURE_H__ |
483 | +#define __ITEM_CAPTURE_H__ |
484 | + |
485 | +// Qt |
486 | +#include <QtCore/QString> |
487 | +#include <QtCore/QUrl> |
488 | +#include <QtQuick/private/qquickshadereffectsource_p.h> |
489 | + |
490 | +class QImage; |
491 | + |
492 | +class ItemCapture : public QQuickShaderEffectSource |
493 | +{ |
494 | + Q_OBJECT |
495 | + |
496 | + Q_PROPERTY(int quality READ quality WRITE setQuality NOTIFY qualityChanged) |
497 | + |
498 | +public: |
499 | + ItemCapture(QQuickItem* parent=0); |
500 | + |
501 | + const int quality() const; |
502 | + void setQuality(const int quality); |
503 | + |
504 | +public Q_SLOTS: |
505 | + void requestCapture(const QString& id); |
506 | + |
507 | +Q_SIGNALS: |
508 | + void qualityChanged() const; |
509 | + void captureFinished(QString request, QUrl capture) const; |
510 | + |
511 | +protected: |
512 | + QSGNode* updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData* updatePaintNodeData); |
513 | + |
514 | +private Q_SLOTS: |
515 | + void onParentChanged(QQuickItem* parent); |
516 | + void onParentVisibleChanged(); |
517 | + void saveImage(const QImage& image, const QString& filePath, |
518 | + const int quality, const QString& request); |
519 | + |
520 | +private: |
521 | + QString m_cacheLocation; |
522 | + QString m_request; |
523 | + int m_quality; |
524 | +}; |
525 | + |
526 | +#endif // __ITEM_CAPTURE_H__ |
527 | |
528 | === modified file 'src/app/webbrowser/webbrowser-app.cpp' |
529 | --- src/app/webbrowser/webbrowser-app.cpp 2014-10-22 18:02:03 +0000 |
530 | +++ src/app/webbrowser/webbrowser-app.cpp 2014-12-11 18:51:44 +0000 |
531 | @@ -18,12 +18,14 @@ |
532 | |
533 | #include "bookmarks-model.h" |
534 | #include "config.h" |
535 | +#include "file-operations.h" |
536 | #include "history-model.h" |
537 | #include "history-matches-model.h" |
538 | #include "history-timeframe-model.h" |
539 | #include "history-byvisits-model.h" |
540 | #include "history-domainlist-model.h" |
541 | #include "history-domainlist-chronological-model.h" |
542 | +#include "item-capture.h" |
543 | #include "limit-proxy-model.h" |
544 | #include "searchengine.h" |
545 | #include "settings.h" |
546 | @@ -49,6 +51,13 @@ |
547 | { |
548 | } |
549 | |
550 | +static QObject* FileOperations_singleton_factory(QQmlEngine* engine, QJSEngine* scriptEngine) |
551 | +{ |
552 | + Q_UNUSED(engine); |
553 | + Q_UNUSED(scriptEngine); |
554 | + return new FileOperations(); |
555 | +} |
556 | + |
557 | bool WebbrowserApp::initialize() |
558 | { |
559 | // Re-direct webapps to the dedicated container for backward compatibility |
560 | @@ -82,6 +91,8 @@ |
561 | qmlRegisterType<LimitProxyModel>(uri, 0 , 1, "LimitProxyModel"); |
562 | qmlRegisterType<TabsModel>(uri, 0, 1, "TabsModel"); |
563 | qmlRegisterType<BookmarksModel>(uri, 0, 1, "BookmarksModel"); |
564 | + qmlRegisterType<ItemCapture>(uri, 0, 1, "ItemCapture"); |
565 | + qmlRegisterSingletonType<FileOperations>(uri, 0, 1, "FileOperations", FileOperations_singleton_factory); |
566 | |
567 | if (BrowserApplication::initialize("webbrowser/webbrowser-app.qml")) { |
568 | Settings settings; |
569 | |
570 | === modified file 'tests/unittests/qml/CMakeLists.txt' |
571 | --- tests/unittests/qml/CMakeLists.txt 2014-11-26 11:33:10 +0000 |
572 | +++ tests/unittests/qml/CMakeLists.txt 2014-12-11 18:51:44 +0000 |
573 | @@ -9,11 +9,16 @@ |
574 | set(TEST tst_QmlTests) |
575 | set(SOURCES |
576 | ${webbrowser-common_SOURCE_DIR}/favicon-fetcher.cpp |
577 | + ${webbrowser-app_SOURCE_DIR}/item-capture.cpp |
578 | tst_QmlTests.cpp |
579 | ) |
580 | add_executable(${TEST} ${SOURCES}) |
581 | -qt5_use_modules(${TEST} Qml QuickTest) |
582 | -include_directories(${webbrowser-common_SOURCE_DIR}) |
583 | +qt5_use_modules(${TEST} Qml Quick QuickTest) |
584 | +include_directories( |
585 | + ${webbrowser-common_SOURCE_DIR} |
586 | + ${webbrowser-app_SOURCE_DIR} |
587 | + ${Qt5Quick_PRIVATE_INCLUDE_DIRS} |
588 | +) |
589 | add_test(${TEST} ${XVFB_COMMAND} ${CMAKE_CURRENT_BINARY_DIR}/${TEST} |
590 | -input ${CMAKE_CURRENT_SOURCE_DIR} |
591 | -import ${CMAKE_BINARY_DIR}/src) |
592 | |
593 | === added file 'tests/unittests/qml/tst_ItemCapture.qml' |
594 | --- tests/unittests/qml/tst_ItemCapture.qml 1970-01-01 00:00:00 +0000 |
595 | +++ tests/unittests/qml/tst_ItemCapture.qml 2014-12-11 18:51:44 +0000 |
596 | @@ -0,0 +1,108 @@ |
597 | +/* |
598 | + * Copyright 2014 Canonical Ltd. |
599 | + * |
600 | + * This file is part of webbrowser-app. |
601 | + * |
602 | + * webbrowser-app is free software; you can redistribute it and/or modify |
603 | + * it under the terms of the GNU General Public License as published by |
604 | + * the Free Software Foundation; version 3. |
605 | + * |
606 | + * webbrowser-app is distributed in the hope that it will be useful, |
607 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
608 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
609 | + * GNU General Public License for more details. |
610 | + * |
611 | + * You should have received a copy of the GNU General Public License |
612 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
613 | + */ |
614 | + |
615 | +import QtQuick 2.0 |
616 | +import QtTest 1.0 |
617 | +import webbrowserapp.private 0.1 |
618 | + |
619 | +Item { |
620 | + width: 200 |
621 | + height: 200 |
622 | + |
623 | + Rectangle { |
624 | + id: rect |
625 | + |
626 | + width: 123 |
627 | + height: 157 |
628 | + color: "red" |
629 | + anchors.centerIn: parent |
630 | + |
631 | + ItemCapture { |
632 | + id: capture |
633 | + onCaptureFinished: image.source = capture |
634 | + } |
635 | + |
636 | + SignalSpy { |
637 | + id: spyReady |
638 | + target: capture |
639 | + signalName: "scheduledUpdateCompleted" |
640 | + } |
641 | + |
642 | + SignalSpy { |
643 | + id: spyCaptured |
644 | + target: capture |
645 | + signalName: "captureFinished" |
646 | + } |
647 | + } |
648 | + |
649 | + Image { |
650 | + id: image |
651 | + } |
652 | + |
653 | + TestCase { |
654 | + name: "ItemCapture" |
655 | + when: windowShown |
656 | + |
657 | + function test_quality_data() { |
658 | + return [ |
659 | + {get: -1}, |
660 | + {set: 58, get: 58}, |
661 | + {set: 122}, |
662 | + {set: -39}, |
663 | + {set: -1, get: -1}, |
664 | + {set: 0, get: 0}, |
665 | + {set: 100, get: 100} |
666 | + ] |
667 | + } |
668 | + |
669 | + function test_quality(data) { |
670 | + var quality = capture.quality |
671 | + if ('set' in data) { |
672 | + capture.quality = data.set |
673 | + } |
674 | + if ('get' in data) { |
675 | + compare(capture.quality, data.get) |
676 | + } else { |
677 | + compare(capture.quality, quality) |
678 | + } |
679 | + } |
680 | + |
681 | + function test_capture() { |
682 | + spyReady.wait() |
683 | + spyCaptured.clear() |
684 | + var id = "test" |
685 | + capture.requestCapture(id) |
686 | + spyCaptured.wait() |
687 | + compare(spyCaptured.signalArguments[0][0], id) |
688 | + verify(image.source.toString()) |
689 | + compare(image.status, Image.Ready) |
690 | + compare(image.sourceSize.width, rect.width) |
691 | + compare(image.sourceSize.height, rect.height) |
692 | + } |
693 | + |
694 | + function test_capture_invalid_id() { |
695 | + spyReady.wait() |
696 | + spyCaptured.clear() |
697 | + var invalid = "foo/bar" |
698 | + capture.requestCapture(invalid) |
699 | + spyCaptured.wait() |
700 | + compare(spyCaptured.signalArguments[0][0], invalid) |
701 | + verify(!spyCaptured.signalArguments[0][1].toString()) |
702 | + } |
703 | + } |
704 | +} |
705 | |
706 | === modified file 'tests/unittests/qml/tst_QmlTests.cpp' |
707 | --- tests/unittests/qml/tst_QmlTests.cpp 2014-11-26 11:33:10 +0000 |
708 | +++ tests/unittests/qml/tst_QmlTests.cpp 2014-12-11 18:51:44 +0000 |
709 | @@ -22,11 +22,15 @@ |
710 | |
711 | // local |
712 | #include "favicon-fetcher.h" |
713 | +#include "item-capture.h" |
714 | |
715 | int main(int argc, char** argv) |
716 | { |
717 | - const char* uri = "webbrowsercommon.private"; |
718 | - qmlRegisterType<FaviconFetcher>(uri, 0, 1, "FaviconFetcher"); |
719 | + const char* commonUri = "webbrowsercommon.private"; |
720 | + qmlRegisterType<FaviconFetcher>(commonUri, 0, 1, "FaviconFetcher"); |
721 | + |
722 | + const char* browserUri = "webbrowserapp.private"; |
723 | + qmlRegisterType<ItemCapture>(browserUri, 0, 1, "ItemCapture"); |
724 | |
725 | return quick_test_main(argc, argv, "QmlTests", 0); |
726 | } |
FAILED: Continuous integration, rev:829 jenkins. qa.ubuntu. com/job/ webbrowser- app-ci/ 1314/ jenkins. qa.ubuntu. com/job/ generic- deb-autopilot- vivid-touch/ 395 jenkins. qa.ubuntu. com/job/ generic- mediumtests- vivid/243 jenkins. qa.ubuntu. com/job/ webbrowser- app-vivid- amd64-ci/ 72 jenkins. qa.ubuntu. com/job/ webbrowser- app-vivid- armhf-ci/ 72 jenkins. qa.ubuntu. com/job/ webbrowser- app-vivid- armhf-ci/ 72/artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ webbrowser- app-vivid- i386-ci/ 72/console jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- vivid-armhf/ 395 jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- vivid-armhf/ 395/artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ autopilot- testrunner- otto-vivid/ 215 jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- vivid-amd64/ 248 jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- vivid-amd64/ 248/artifact/ work/output/ *zip*/output. zip
http://
Executed test runs:
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
FAILURE: http://
SUCCESS: http://
deb: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/webbrowser- app-ci/ 1314/rebuild
http://