Merge lp:~osomon/webbrowser-app/thumbnails into lp:webbrowser-app

Proposed by Olivier Tilloy
Status: Merged
Approved by: Olivier Tilloy
Approved revision: 207
Merged at revision: 220
Proposed branch: lp:~osomon/webbrowser-app/thumbnails
Merge into: lp:webbrowser-app
Diff against target: 988 lines (+568/-55)
21 files modified
CMakeLists.txt (+0/-2)
debian/control (+5/-1)
src/Ubuntu/Components/Extras/Browser/CMakeLists.txt (+13/-1)
src/Ubuntu/Components/Extras/Browser/PageDelegate.qml (+23/-6)
src/Ubuntu/Components/Extras/Browser/TabsList.qml (+8/-6)
src/Ubuntu/Components/Extras/Browser/TimelineView.qml (+13/-10)
src/Ubuntu/Components/Extras/Browser/UbuntuWebView.qml (+21/-0)
src/Ubuntu/Components/Extras/Browser/history-hostlist-model.cpp (+33/-2)
src/Ubuntu/Components/Extras/Browser/history-hostlist-model.h (+1/-0)
src/Ubuntu/Components/Extras/Browser/plugin.cpp (+7/-0)
src/Ubuntu/Components/Extras/Browser/tabs-model.cpp (+1/-5)
src/Ubuntu/Components/Extras/Browser/tabs-model.h (+0/-1)
src/Ubuntu/Components/Extras/Browser/webthumbnail-provider.cpp (+53/-0)
src/Ubuntu/Components/Extras/Browser/webthumbnail-provider.h (+39/-0)
src/Ubuntu/Components/Extras/Browser/webthumbnail-utils.cpp (+42/-0)
src/Ubuntu/Components/Extras/Browser/webthumbnail-utils.h (+35/-0)
src/Ubuntu/Components/Extras/Browser/webview-thumbnailer.cpp (+162/-0)
src/Ubuntu/Components/Extras/Browser/webview-thumbnailer.h (+67/-0)
tests/autopilot/webbrowser_app/emulators/main_window.py (+1/-1)
tests/unittests/history-hostlist-model/CMakeLists.txt (+1/-0)
tests/unittests/history-hostlist-model/tst_HistoryHostListModelTests.cpp (+43/-20)
To merge this branch: bzr merge lp:~osomon/webbrowser-app/thumbnails
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Approve
Bill Filler (community) Approve
Review via email: mp+173978@code.launchpad.net

Commit message

Display thumbnails of the web pages in the activity view.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
lp:~osomon/webbrowser-app/thumbnails updated
205. By Olivier Tilloy

Fix FTBFS in a clean environment.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
lp:~osomon/webbrowser-app/thumbnails updated
206. By Olivier Tilloy

Set the original size of the image.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Bill Filler (bfiller) wrote :

Looks like there is a jenkins issue causing a failure, but I tested the functionality and it works well and looks good. Before we land this I'd like to understand the impact on memory this has to make sure it is acceptable. And I have a few other questions..

Specifically:
- roughly what is the memory usage associated with generating and loading each thumbnail
- how can we limit the thumbnail cache as this seems it will grow limitlessly as history grows. We probably need some sort of hard limit on disk size or number stored so we don't chew up the users disk space. And a way to recreate if one from the cache doesn't exist.
- Are all thumbnails for the history items loaded in memory when the browser is started?
- Or do we only load thumbnails for images that are visible on the screen? I hope so :)

We should ensure that if you have hundreds/thousands of history items (which is likely) that we're not creating and loading thumbnails for all of these up front but only on demand.

Revision history for this message
Olivier Tilloy (osomon) wrote :

> Looks like there is a jenkins issue causing a failure, but I tested the
> functionality and it works well and looks good. Before we land this I'd like
> to understand the impact on memory this has to make sure it is acceptable. And
> I have a few other questions..
>
> Specifically:
> - roughly what is the memory usage associated with generating and loading each
> thumbnail
> - how can we limit the thumbnail cache as this seems it will grow limitlessly
> as history grows. We probably need some sort of hard limit on disk size or
> number stored so we don't chew up the users disk space. And a way to recreate
> if one from the cache doesn't exist.
> - Are all thumbnails for the history items loaded in memory when the browser
> is started?
> - Or do we only load thumbnails for images that are visible on the screen? I
> hope so :)
>
> We should ensure that if you have hundreds/thousands of history items (which
> is likely) that we're not creating and loading thumbnails for all of these up
> front but only on demand.

As the timeline view is using nested list views, delegates are created on demand only when needed, and so are images loaded. I haven’t done any specific memory measurements, but I expect the memory consumption related to thumbnails to remain very reasonable (the thumbnails are square PNG images 12×12 grid units), and especially on a small screen such as a phone, where very few delegates are visible at any given time.
We’re not even loading any thumbnail when the application starts, as the timeline view isn’t visible yet, so it doesn’t instantiate any delegate.

Limiting the size of the cache is indeed a problem which this branch doesn’t address at all. I guess we could define a size limit, and when that limit is reached do some cleanup by removing the oldest thumbnails.

There’s also one issue that this branch doesn’t address: if a thumbnail already exists for a page, it’s never going to be re-generated, even if the contents of the page change. An option would be to force re-generating the thumbnail if it’s older than a given interval, e.g. one week.

I believe those two issues can (should) be addressed in other branches.

Revision history for this message
Bill Filler (bfiller) wrote :

Sounds reasonable. Can you file bugs for those two issues please so we can track? I'll approve this but you need to fix the jenks failure before it can land.

review: Approve
Revision history for this message
Olivier Tilloy (osomon) wrote :

The jenkins failures are due to bug #1199662, a regression in the UITK, which has been fixed yesterday, I’ll trigger a re-build now.
As soon as this is merged I’ll file bugs to track those two issues. Thanks for the review!

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
lp:~osomon/webbrowser-app/thumbnails updated
207. By Olivier Tilloy

Fix autopilot tests.

Revision history for this message
Olivier Tilloy (osomon) wrote :

Some of the failures were actually a bug in the tests, this should now be fixed.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Olivier Tilloy (osomon) wrote :

> As soon as this is merged I’ll file bugs to track those two issues.

Filed bug #1200525 (Thumbnails are never updated) and bug #1200526 (The thumbnail cache grows forever).

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2013-06-05 07:55:27 +0000
3+++ CMakeLists.txt 2013-07-12 06:47:24 +0000
4@@ -13,8 +13,6 @@
5 find_package(Qt5Widgets REQUIRED)
6 find_package(Qt5Quick REQUIRED)
7
8-add_definitions(-DQT_NO_KEYWORDS)
9-
10 set(CMAKE_INCLUDE_CURRENT_DIR ON)
11 set(CMAKE_AUTOMOC ON)
12
13
14=== modified file 'debian/control'
15--- debian/control 2013-07-09 09:55:10 +0000
16+++ debian/control 2013-07-12 06:47:24 +0000
17@@ -6,11 +6,15 @@
18 debhelper (>= 9),
19 dh-translations,
20 python,
21+ libqt5sql5-sqlite,
22+ libqt5v8-5-private-dev,
23+ libqt5webkit5-dev,
24 qt5-default,
25 qt5-qmake,
26 qtbase5-dev,
27+ qtbase5-private-dev,
28 qtdeclarative5-dev,
29- libqt5sql5-sqlite,
30+ qtdeclarative5-private-dev,
31 qtdeclarative5-qtquick2-plugin,
32 qtdeclarative5-test-plugin,
33 qtdeclarative5-ubuntu-ui-toolkit-plugin,
34
35=== modified file 'src/Ubuntu/Components/Extras/Browser/CMakeLists.txt'
36--- src/Ubuntu/Components/Extras/Browser/CMakeLists.txt 2013-07-03 12:00:08 +0000
37+++ src/Ubuntu/Components/Extras/Browser/CMakeLists.txt 2013-07-12 06:47:24 +0000
38@@ -9,12 +9,24 @@
39 history-host-model.cpp
40 history-hostlist-model.cpp
41 tabs-model.cpp
42+ webthumbnail-utils.cpp
43+ webthumbnail-provider.cpp
44+ webview-thumbnailer.cpp
45 plugin.cpp
46 )
47
48 add_library(${PLUGIN} MODULE ${PLUGIN_SRC})
49
50-qt5_use_modules(${PLUGIN} Core Qml Sql)
51+qt5_use_modules(${PLUGIN} Core Qml Quick Sql WebKit)
52+
53+# work around the lack of a public cmake module for Qt5V8
54+set(Qt5V8_PRIVATE_INCLUDE_DIRS
55+ "/usr/include/qt5/QtV8/${Qt5Qml_VERSION_STRING}"
56+ "/usr/include/qt5/QtV8/${Qt5Qml_VERSION_STRING}/QtV8")
57+include_directories(${Qt5Core_PRIVATE_INCLUDE_DIRS}
58+ ${Qt5V8_PRIVATE_INCLUDE_DIRS}
59+ ${Qt5Quick_PRIVATE_INCLUDE_DIRS}
60+ ${Qt5WebKit_PRIVATE_INCLUDE_DIRS})
61
62 file(GLOB QML_FILES *.qml qmldir *.js)
63 install(TARGETS ${PLUGIN} DESTINATION ${WEBBROWSER_IMPORTS_DIR})
64
65=== modified file 'src/Ubuntu/Components/Extras/Browser/PageDelegate.qml'
66--- src/Ubuntu/Components/Extras/Browser/PageDelegate.qml 2013-06-05 11:06:28 +0000
67+++ src/Ubuntu/Components/Extras/Browser/PageDelegate.qml 2013-07-12 06:47:24 +0000
68@@ -19,17 +19,34 @@
69 import QtQuick 2.0
70 import Ubuntu.Components 0.1
71
72-UbuntuShape {
73- property alias title: title.text
74+Item {
75+ property alias thumbnail: thumbnail.source
76+ property alias label: label.text
77+
78+ UbuntuShape {
79+ id: shape
80+ anchors {
81+ top: parent.top
82+ left: parent.left
83+ right: parent.right
84+ }
85+ height: width
86+
87+ image: Image {
88+ id: thumbnail
89+ }
90+ }
91
92 Label {
93- id: title
94+ id: label
95 anchors {
96- fill: parent
97- margins: units.gu(0.5)
98+ top: shape.bottom
99+ topMargin: units.gu(1)
100+ left: parent.left
101+ right: parent.right
102 }
103+ height: units.gu(1)
104 fontSize: "small"
105- wrapMode: Text.Wrap
106 elide: Text.ElideRight
107 }
108 }
109
110=== modified file 'src/Ubuntu/Components/Extras/Browser/TabsList.qml'
111--- src/Ubuntu/Components/Extras/Browser/TabsList.qml 2013-07-09 05:55:49 +0000
112+++ src/Ubuntu/Components/Extras/Browser/TabsList.qml 2013-07-12 06:47:24 +0000
113@@ -1,4 +1,4 @@
114-/*
115+/*
116 * Copyright 2013 Canonical Ltd.
117 *
118 * This file is part of webbrowser-app.
119@@ -41,7 +41,7 @@
120 right: parent.right
121 margins: units.gu(2)
122 }
123- height: units.gu(14)
124+ height: units.gu(16)
125 spacing: units.gu(2)
126 orientation: ListView.Horizontal
127 currentIndex: model.currentIndex
128@@ -50,7 +50,7 @@
129 width: units.gu(14)
130 height: parent.height
131
132- PageDelegate {
133+ UbuntuShape {
134 objectName: "newTabDelegate"
135 width: units.gu(12)
136 height: units.gu(12)
137@@ -69,7 +69,7 @@
138
139 delegate: ListItem.Empty {
140 width: units.gu(12)
141- height: units.gu(12)
142+ height: units.gu(14)
143 showDivider: false
144
145 // FIXME: http://pad.lv/1187476 makes it impossible to swipe a
146@@ -78,10 +78,12 @@
147 onItemRemoved: tabRemoved(index)
148
149 PageDelegate {
150+ id: openTabDelegate
151 objectName: "openTabDelegate"
152 anchors.fill: parent
153- color: (index == currentIndex) ? UbuntuColors.darkAubergine : "white"
154- title: model.title
155+
156+ label: model.url
157+ thumbnail: model.webview.thumbnail
158 }
159
160 onClicked: switchToTabClicked(index)
161
162=== modified file 'src/Ubuntu/Components/Extras/Browser/TimelineView.qml'
163--- src/Ubuntu/Components/Extras/Browser/TimelineView.qml 2013-07-05 14:37:29 +0000
164+++ src/Ubuntu/Components/Extras/Browser/TimelineView.qml 2013-07-12 06:47:24 +0000
165@@ -57,7 +57,7 @@
166
167 header: TabsList {
168 width: parent.width
169- height: units.gu(20)
170+ height: units.gu(23)
171
172 model: tabsModel
173
174@@ -107,7 +107,7 @@
175 right: parent.right
176 margins: units.gu(2)
177 }
178- height: units.gu(12)
179+ height: units.gu(14)
180
181 spacing: units.gu(2)
182 orientation: ListView.Horizontal
183@@ -160,10 +160,10 @@
184
185 delegate: PageDelegate {
186 width: units.gu(12)
187- height: units.gu(12)
188- color: "white"
189+ height: units.gu(14)
190
191- title: model.host ? model.host : i18n.tr("(local files)")
192+ label: model.host ? model.host : i18n.tr("(local files)")
193+ thumbnail: model.thumbnail
194
195 MouseArea {
196 anchors.fill: parent
197@@ -196,10 +196,12 @@
198
199 delegate: PageDelegate {
200 width: units.gu(12)
201- height: units.gu(12)
202- color: "white"
203-
204- title: model.title
205+ height: units.gu(14)
206+
207+ label: model.title ? model.title : model.url
208+
209+ property url thumbnailSource: "image://webthumbnail/" + model.url
210+ thumbnail: WebThumbnailer.thumbnailExists(model.url) ? thumbnailSource : ""
211
212 MouseArea {
213 anchors.fill: parent
214@@ -213,7 +215,8 @@
215 when: timelineIndex == timeline.currentIndex
216 PropertyChanges {
217 target: entriesView
218- height: units.gu(12)
219+ height: units.gu(14)
220+ clip: false
221 }
222 }
223 ]
224
225=== modified file 'src/Ubuntu/Components/Extras/Browser/UbuntuWebView.qml'
226--- src/Ubuntu/Components/Extras/Browser/UbuntuWebView.qml 2013-06-13 08:36:55 +0000
227+++ src/Ubuntu/Components/Extras/Browser/UbuntuWebView.qml 2013-07-12 06:47:24 +0000
228@@ -21,6 +21,7 @@
229 import QtWebKit 3.0
230 import QtWebKit.experimental 1.0
231 import Ubuntu.Components 0.1
232+import Ubuntu.Components.Extras.Browser 0.1
233 import Ubuntu.Components.Popups 0.1
234
235 WebView {
236@@ -187,4 +188,24 @@
237 flickableItem: _webview
238 align: Qt.AlignBottom
239 }
240+
241+ WebviewThumbnailer {
242+ id: thumbnailer
243+ webview: _webview
244+ targetSize: Qt.size(units.gu(12), units.gu(12))
245+ property url thumbnailSource: "image://webthumbnail/" + _webview.url
246+ onThumbnailRendered: {
247+ if (url == _webview.url) {
248+ _webview.thumbnail = thumbnailer.thumbnailSource
249+ }
250+ }
251+ }
252+ property url thumbnail: (url && thumbnailer.thumbnailExists()) ? thumbnailer.thumbnailSource : ""
253+ onLoadingChanged: {
254+ if (loadRequest.status === WebView.LoadSucceededStatus) {
255+ if (!thumbnailer.thumbnailExists()) {
256+ thumbnailer.renderThumbnail()
257+ }
258+ }
259+ }
260 }
261
262=== modified file 'src/Ubuntu/Components/Extras/Browser/history-hostlist-model.cpp'
263--- src/Ubuntu/Components/Extras/Browser/history-hostlist-model.cpp 2013-06-19 14:45:49 +0000
264+++ src/Ubuntu/Components/Extras/Browser/history-hostlist-model.cpp 2013-07-12 06:47:24 +0000
265@@ -20,6 +20,7 @@
266 #include "history-model.h"
267 #include "history-host-model.h"
268 #include "history-timeframe-model.h"
269+#include "webthumbnail-utils.h"
270
271 // Qt
272 #include <QtCore/QSet>
273@@ -30,8 +31,9 @@
274 \brief List model that exposes history entries grouped by host
275
276 HistoryHostListModel is a list model that exposes history entries from a
277- HistoryTimeframeModel grouped by host. Each item in the list has two roles:
278- 'host' for the host name, and 'entries' for the corresponding
279+ HistoryTimeframeModel grouped by host. Each item in the list has three
280+ roles: 'host' for the host name, 'thumbnail' for a thumbnail picture of a
281+ page corresponding to this host, and 'entries' for the corresponding
282 HistoryHostModel that contains all entries in this group.
283 */
284 HistoryHostListModel::HistoryHostListModel(QObject* parent)
285@@ -50,6 +52,7 @@
286 static QHash<int, QByteArray> roles;
287 if (roles.isEmpty()) {
288 roles[Host] = "host";
289+ roles[Thumbnail] = "thumbnail";
290 roles[Entries] = "entries";
291 }
292 return roles;
293@@ -74,6 +77,20 @@
294 switch (role) {
295 case Host:
296 return host;
297+ case Thumbnail:
298+ {
299+ // Iterate over all the entries, and return the first valid thumbnail.
300+ HistoryHostModel* entries = m_hosts.value(host);
301+ int count = entries->rowCount();
302+ for (int i = 0; i < count; ++i) {
303+ QUrl url = entries->data(entries->index(i, 0), HistoryModel::Url).toUrl();
304+ QFileInfo thumbnailFile = WebThumbnailUtils::thumbnailFile(url);
305+ if (thumbnailFile.exists()) {
306+ return thumbnailFile.absoluteFilePath();
307+ }
308+ }
309+ return QUrl();
310+ }
311 case Entries:
312 return QVariant::fromValue(m_hosts.value(host));
313 default:
314@@ -130,6 +147,7 @@
315
316 void HistoryHostListModel::onRowsInserted(const QModelIndex& parent, int start, int end)
317 {
318+ QStringList updated;
319 for (int i = start; i <= end; ++i) {
320 QString host = getHostFromSourceModel(m_sourceModel->index(i, 0, parent));
321 if (!m_hosts.contains(host)) {
322@@ -144,8 +162,16 @@
323 beginInsertRows(QModelIndex(), insertAt, insertAt);
324 insertNewHost(host);
325 endInsertRows();
326+ } else {
327+ updated.append(host);
328 }
329 }
330+ QVector<int> updatedRoles = QVector<int>() << Thumbnail << Entries;
331+ QStringList hosts = m_hosts.keys();
332+ Q_FOREACH(const QString& host, updated) {
333+ QModelIndex index = this->index(hosts.indexOf(host), 0);
334+ Q_EMIT dataChanged(index, index, updatedRoles);
335+ }
336 }
337
338 void HistoryHostListModel::onRowsRemoved(const QModelIndex& parent, int start, int end)
339@@ -166,6 +192,11 @@
340 delete m_hosts.take(host);
341 endRemoveRows();
342 }
343+ // XXX: unfortunately there is no way to get a list of hosts that had some
344+ // (but not all) entries removed. To ensure the views are correctly updated,
345+ // let’s emit the signal for all entries, even those that haven’t changed.
346+ Q_EMIT dataChanged(this->index(0, 0), this->index(rowCount() - 1, 0),
347+ QVector<int>() << Thumbnail << Entries);
348 }
349
350 void HistoryHostListModel::onModelReset()
351
352=== modified file 'src/Ubuntu/Components/Extras/Browser/history-hostlist-model.h'
353--- src/Ubuntu/Components/Extras/Browser/history-hostlist-model.h 2013-06-19 14:45:49 +0000
354+++ src/Ubuntu/Components/Extras/Browser/history-hostlist-model.h 2013-07-12 06:47:24 +0000
355@@ -41,6 +41,7 @@
356
357 enum Roles {
358 Host = Qt::UserRole + 1,
359+ Thumbnail,
360 Entries
361 };
362
363
364=== modified file 'src/Ubuntu/Components/Extras/Browser/plugin.cpp'
365--- src/Ubuntu/Components/Extras/Browser/plugin.cpp 2013-07-03 12:00:08 +0000
366+++ src/Ubuntu/Components/Extras/Browser/plugin.cpp 2013-07-12 06:47:24 +0000
367@@ -23,6 +23,8 @@
368 #include "history-host-model.h"
369 #include "history-hostlist-model.h"
370 #include "tabs-model.h"
371+#include "webthumbnail-provider.h"
372+#include "webview-thumbnailer.h"
373
374 // Qt
375 #include <QtCore/QDir>
376@@ -38,6 +40,10 @@
377 }
378 QQmlContext* context = engine->rootContext();
379 context->setContextProperty("dataLocation", dataLocation.absolutePath());
380+
381+ WebThumbnailProvider* thumbnailer = new WebThumbnailProvider;
382+ engine->addImageProvider(QLatin1String("webthumbnail"), thumbnailer);
383+ context->setContextProperty("WebThumbnailer", thumbnailer);
384 }
385
386 void UbuntuBrowserPlugin::registerTypes(const char* uri)
387@@ -49,4 +55,5 @@
388 qmlRegisterType<HistoryHostModel>(uri, 0, 1, "HistoryHostModel");
389 qmlRegisterType<HistoryHostListModel>(uri, 0, 1, "HistoryHostListModel");
390 qmlRegisterType<TabsModel>(uri, 0, 1, "TabsModel");
391+ qmlRegisterType<WebviewThumbnailer>(uri, 0, 1, "WebviewThumbnailer");
392 }
393
394=== modified file 'src/Ubuntu/Components/Extras/Browser/tabs-model.cpp'
395--- src/Ubuntu/Components/Extras/Browser/tabs-model.cpp 2013-06-06 07:40:22 +0000
396+++ src/Ubuntu/Components/Extras/Browser/tabs-model.cpp 2013-07-12 06:47:24 +0000
397@@ -28,7 +28,7 @@
398
399 TabsModel is a list model that stores the list of currently open tabs.
400 Each tab holds a pointer to a WebView and associated metadata (URL, title,
401- icon, thumbnail).
402+ icon).
403
404 The model doesn’t own the WebView, so it is the responsibility of whoever
405 adds a tab to instantiate the corresponding WebView, and to destroy it after
406@@ -51,7 +51,6 @@
407 roles[Url] = "url";
408 roles[Title] = "title";
409 roles[Icon] = "icon";
410- roles[Thumbnail] = "thumbnail";
411 roles[WebView] = "webview";
412 }
413 return roles;
414@@ -80,9 +79,6 @@
415 return webview->property("title");
416 case Icon:
417 return webview->property("icon");
418- case Thumbnail:
419- // XXX: not implemented yet
420- return QVariant();
421 case WebView:
422 return QVariant::fromValue(webview);
423 default:
424
425=== modified file 'src/Ubuntu/Components/Extras/Browser/tabs-model.h'
426--- src/Ubuntu/Components/Extras/Browser/tabs-model.h 2013-06-06 07:40:22 +0000
427+++ src/Ubuntu/Components/Extras/Browser/tabs-model.h 2013-07-12 06:47:24 +0000
428@@ -43,7 +43,6 @@
429 Url = Qt::UserRole + 1,
430 Title,
431 Icon,
432- Thumbnail,
433 WebView
434 };
435
436
437=== added file 'src/Ubuntu/Components/Extras/Browser/webthumbnail-provider.cpp'
438--- src/Ubuntu/Components/Extras/Browser/webthumbnail-provider.cpp 1970-01-01 00:00:00 +0000
439+++ src/Ubuntu/Components/Extras/Browser/webthumbnail-provider.cpp 2013-07-12 06:47:24 +0000
440@@ -0,0 +1,53 @@
441+/*
442+ * Copyright 2013 Canonical Ltd.
443+ *
444+ * This file is part of webbrowser-app.
445+ *
446+ * webbrowser-app is free software; you can redistribute it and/or modify
447+ * it under the terms of the GNU General Public License as published by
448+ * the Free Software Foundation; version 3.
449+ *
450+ * webbrowser-app is distributed in the hope that it will be useful,
451+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
452+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
453+ * GNU General Public License for more details.
454+ *
455+ * You should have received a copy of the GNU General Public License
456+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
457+ */
458+
459+#include "webthumbnail-provider.h"
460+#include "webthumbnail-utils.h"
461+
462+// Qt
463+#include <QtCore/QDebug>
464+#include <QtGui/QImageReader>
465+
466+WebThumbnailProvider::WebThumbnailProvider(QObject* parent)
467+ : QObject(parent)
468+ , QQuickImageProvider(QQuickImageProvider::Image)
469+{
470+}
471+
472+QImage WebThumbnailProvider::requestImage(const QString& id, QSize* size, const QSize& requestedSize)
473+{
474+ QImage image;
475+ QFileInfo cached = WebThumbnailUtils::thumbnailFile(QUrl(id));
476+ if (cached.exists()) {
477+ QImageReader reader(cached.absoluteFilePath(), "PNG");
478+ if (requestedSize.isValid()) {
479+ reader.setScaledSize(requestedSize);
480+ }
481+ *size = reader.size();
482+ reader.read(&image);
483+ if (image.isNull()) {
484+ qWarning() << "Failed to load cached thumbnail:" << reader.errorString();
485+ }
486+ }
487+ return image;
488+}
489+
490+bool WebThumbnailProvider::thumbnailExists(const QUrl& url) const
491+{
492+ return WebThumbnailUtils::thumbnailFile(url).exists();
493+}
494
495=== added file 'src/Ubuntu/Components/Extras/Browser/webthumbnail-provider.h'
496--- src/Ubuntu/Components/Extras/Browser/webthumbnail-provider.h 1970-01-01 00:00:00 +0000
497+++ src/Ubuntu/Components/Extras/Browser/webthumbnail-provider.h 2013-07-12 06:47:24 +0000
498@@ -0,0 +1,39 @@
499+/*
500+ * Copyright 2013 Canonical Ltd.
501+ *
502+ * This file is part of webbrowser-app.
503+ *
504+ * webbrowser-app is free software; you can redistribute it and/or modify
505+ * it under the terms of the GNU General Public License as published by
506+ * the Free Software Foundation; version 3.
507+ *
508+ * webbrowser-app is distributed in the hope that it will be useful,
509+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
510+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
511+ * GNU General Public License for more details.
512+ *
513+ * You should have received a copy of the GNU General Public License
514+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
515+ */
516+
517+#ifndef __WEBTHUMBNAIL_PROVIDER_H__
518+#define __WEBTHUMBNAIL_PROVIDER_H__
519+
520+// Qt
521+#include <QtCore/QObject>
522+#include <QtCore/QUrl>
523+#include <QtQuick/QQuickImageProvider>
524+
525+class WebThumbnailProvider : public QObject, public QQuickImageProvider
526+{
527+ Q_OBJECT
528+
529+public:
530+ WebThumbnailProvider(QObject* parent=0);
531+
532+ virtual QImage requestImage(const QString& id, QSize* size, const QSize& requestedSize);
533+
534+ Q_INVOKABLE bool thumbnailExists(const QUrl& url) const;
535+};
536+
537+#endif // __WEBTHUMBNAIL_PROVIDER_H__
538
539=== added file 'src/Ubuntu/Components/Extras/Browser/webthumbnail-utils.cpp'
540--- src/Ubuntu/Components/Extras/Browser/webthumbnail-utils.cpp 1970-01-01 00:00:00 +0000
541+++ src/Ubuntu/Components/Extras/Browser/webthumbnail-utils.cpp 2013-07-12 06:47:24 +0000
542@@ -0,0 +1,42 @@
543+/*
544+ * Copyright 2013 Canonical Ltd.
545+ *
546+ * This file is part of webbrowser-app.
547+ *
548+ * webbrowser-app is free software; you can redistribute it and/or modify
549+ * it under the terms of the GNU General Public License as published by
550+ * the Free Software Foundation; version 3.
551+ *
552+ * webbrowser-app is distributed in the hope that it will be useful,
553+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
554+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
555+ * GNU General Public License for more details.
556+ *
557+ * You should have received a copy of the GNU General Public License
558+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
559+ */
560+
561+#include "webthumbnail-utils.h"
562+
563+// Qt
564+#include <QtCore/QCryptographicHash>
565+#include <QtCore/QStandardPaths>
566+
567+QDir WebThumbnailUtils::cacheLocation()
568+{
569+ return QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/thumbnails";
570+}
571+
572+void WebThumbnailUtils::ensureCacheLocation()
573+{
574+ QDir cache = cacheLocation();
575+ if (!cache.exists()) {
576+ QDir::root().mkpath(cache.absolutePath());
577+ }
578+}
579+
580+QFileInfo WebThumbnailUtils::thumbnailFile(const QUrl& url)
581+{
582+ QString hash(QCryptographicHash::hash(url.toEncoded(), QCryptographicHash::Md5).toHex());
583+ return cacheLocation().absoluteFilePath(hash + ".png");
584+}
585
586=== added file 'src/Ubuntu/Components/Extras/Browser/webthumbnail-utils.h'
587--- src/Ubuntu/Components/Extras/Browser/webthumbnail-utils.h 1970-01-01 00:00:00 +0000
588+++ src/Ubuntu/Components/Extras/Browser/webthumbnail-utils.h 2013-07-12 06:47:24 +0000
589@@ -0,0 +1,35 @@
590+/*
591+ * Copyright 2013 Canonical Ltd.
592+ *
593+ * This file is part of webbrowser-app.
594+ *
595+ * webbrowser-app is free software; you can redistribute it and/or modify
596+ * it under the terms of the GNU General Public License as published by
597+ * the Free Software Foundation; version 3.
598+ *
599+ * webbrowser-app is distributed in the hope that it will be useful,
600+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
601+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
602+ * GNU General Public License for more details.
603+ *
604+ * You should have received a copy of the GNU General Public License
605+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
606+ */
607+
608+#ifndef __WEBTHUMBNAIL_UTILS_H__
609+#define __WEBTHUMBNAIL_UTILS_H__
610+
611+// Qt
612+#include <QtCore/QDir>
613+#include <QtCore/QFileInfo>
614+#include <QtCore/QUrl>
615+
616+class WebThumbnailUtils
617+{
618+public:
619+ static QDir cacheLocation();
620+ static void ensureCacheLocation();
621+ static QFileInfo thumbnailFile(const QUrl& url);
622+};
623+
624+#endif // __WEBTHUMBNAIL_UTILS_H__
625
626=== added file 'src/Ubuntu/Components/Extras/Browser/webview-thumbnailer.cpp'
627--- src/Ubuntu/Components/Extras/Browser/webview-thumbnailer.cpp 1970-01-01 00:00:00 +0000
628+++ src/Ubuntu/Components/Extras/Browser/webview-thumbnailer.cpp 2013-07-12 06:47:24 +0000
629@@ -0,0 +1,162 @@
630+/*
631+ * Copyright 2013 Canonical Ltd.
632+ *
633+ * This file is part of webbrowser-app.
634+ *
635+ * webbrowser-app is free software; you can redistribute it and/or modify
636+ * it under the terms of the GNU General Public License as published by
637+ * the Free Software Foundation; version 3.
638+ *
639+ * webbrowser-app is distributed in the hope that it will be useful,
640+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
641+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
642+ * GNU General Public License for more details.
643+ *
644+ * You should have received a copy of the GNU General Public License
645+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
646+ */
647+
648+#include "webview-thumbnailer.h"
649+#include "webthumbnail-utils.h"
650+
651+// Qt
652+#include <QtCore/QTimer>
653+#include <QtQuick/private/qsgrenderer_p.h>
654+#include <QtWebKit/private/qquickwebpage_p.h>
655+#include <QtWebKit/private/qquickwebview_p.h>
656+
657+class BindableFbo : public QSGBindable
658+{
659+public:
660+ BindableFbo(QOpenGLFramebufferObject* fbo) : m_fbo(fbo) {}
661+ virtual void bind() const { m_fbo->bind(); }
662+
663+private:
664+ QOpenGLFramebufferObject *m_fbo;
665+};
666+
667+WebviewThumbnailer::WebviewThumbnailer(QQuickItem* parent)
668+ : QQuickItem(parent)
669+ , m_webview(0)
670+ , m_renderer(0)
671+{
672+}
673+
674+WebviewThumbnailer::~WebviewThumbnailer()
675+{
676+ delete m_renderer;
677+}
678+
679+QQuickWebView* WebviewThumbnailer::webview() const
680+{
681+ return m_webview;
682+}
683+
684+void WebviewThumbnailer::setWebview(QQuickWebView* webview)
685+{
686+ if (webview != m_webview) {
687+ m_webview = webview;
688+ setFlag(QQuickItem::ItemHasContents, false);
689+ Q_EMIT webviewChanged();
690+ }
691+}
692+
693+const QSize& WebviewThumbnailer::targetSize() const
694+{
695+ return m_targetSize;
696+}
697+
698+void WebviewThumbnailer::setTargetSize(const QSize& targetSize)
699+{
700+ if (targetSize != m_targetSize) {
701+ m_targetSize = targetSize;
702+ Q_EMIT targetSizeChanged();
703+ }
704+}
705+
706+bool WebviewThumbnailer::thumbnailExists() const
707+{
708+ if (m_webview) {
709+ QUrl url = m_webview->url();
710+ if (url.isValid()) {
711+ return WebThumbnailUtils::thumbnailFile(url).exists();
712+ }
713+ }
714+ return false;
715+}
716+
717+void WebviewThumbnailer::renderThumbnail()
718+{
719+ // Delay the actual rendering to give all elements on the page
720+ // a chance to be fully rendered.
721+ QTimer::singleShot(1000, this, SLOT(doRenderThumbnail()));
722+}
723+
724+void WebviewThumbnailer::doRenderThumbnail()
725+{
726+ if (m_webview) {
727+ setFlag(QQuickItem::ItemHasContents);
728+ update();
729+ }
730+}
731+
732+QSGNode* WebviewThumbnailer::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData* updatePaintNodeData)
733+{
734+ Q_UNUSED(updatePaintNodeData);
735+
736+ if (!(m_webview && (flags() & QQuickItem::ItemHasContents))) {
737+ return oldNode;
738+ }
739+ setFlag(QQuickItem::ItemHasContents, false);
740+
741+ QQuickWebPage* page = m_webview->page();
742+ qreal min = qMin(page->width(), page->height());
743+ QSize size(min, min);
744+
745+ QSGNode* node = QQuickItemPrivate::get(page)->itemNode();
746+ QSGNode* parent = node->QSGNode::parent();
747+ QSGNode* previousSibling = node->previousSibling();
748+ if (parent) {
749+ parent->removeChildNode(node);
750+ }
751+ QSGRootNode root;
752+ root.appendChildNode(node);
753+
754+ if (m_renderer == 0) {
755+ m_renderer = QQuickItemPrivate::get(this)->sceneGraphContext()->createRenderer();
756+ }
757+ m_renderer->setRootNode(static_cast<QSGRootNode*>(&root));
758+
759+ QOpenGLFramebufferObject fbo(size);
760+
761+ m_renderer->setDeviceRect(size);
762+ m_renderer->setViewportRect(size);
763+ m_renderer->setProjectionMatrixToRect(QRectF(QPointF(), size));
764+ m_renderer->setClearColor(Qt::transparent);
765+
766+ m_renderer->renderScene(BindableFbo(&fbo));
767+
768+ fbo.release();
769+
770+ QImage image = fbo.toImage().scaled(m_targetSize, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
771+
772+ WebThumbnailUtils::ensureCacheLocation();
773+ QUrl url = m_webview->url();
774+ bool saved = image.save(WebThumbnailUtils::thumbnailFile(url).absoluteFilePath());
775+
776+ root.removeChildNode(node);
777+
778+ if (parent) {
779+ if (previousSibling) {
780+ parent->insertChildNodeAfter(node, previousSibling);
781+ } else {
782+ parent->prependChildNode(node);
783+ }
784+ }
785+
786+ if (saved) {
787+ Q_EMIT thumbnailRendered(url);
788+ }
789+
790+ return oldNode;
791+}
792
793=== added file 'src/Ubuntu/Components/Extras/Browser/webview-thumbnailer.h'
794--- src/Ubuntu/Components/Extras/Browser/webview-thumbnailer.h 1970-01-01 00:00:00 +0000
795+++ src/Ubuntu/Components/Extras/Browser/webview-thumbnailer.h 2013-07-12 06:47:24 +0000
796@@ -0,0 +1,67 @@
797+/*
798+ * Copyright 2013 Canonical Ltd.
799+ *
800+ * This file is part of webbrowser-app.
801+ *
802+ * webbrowser-app is free software; you can redistribute it and/or modify
803+ * it under the terms of the GNU General Public License as published by
804+ * the Free Software Foundation; version 3.
805+ *
806+ * webbrowser-app is distributed in the hope that it will be useful,
807+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
808+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
809+ * GNU General Public License for more details.
810+ *
811+ * You should have received a copy of the GNU General Public License
812+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
813+ */
814+
815+#ifndef __WEBVIEW_THUMBNAILER_H__
816+#define __WEBVIEW_THUMBNAILER_H__
817+
818+// Qt
819+#include <QtCore/QSize>
820+#include <QtCore/QUrl>
821+#include <QtQuick/private/qquickitem_p.h>
822+
823+class QQuickWebView;
824+class QSGRenderer;
825+
826+class WebviewThumbnailer : public QQuickItem
827+{
828+ Q_OBJECT
829+
830+ Q_PROPERTY(QQuickWebView* webview READ webview WRITE setWebview NOTIFY webviewChanged)
831+ Q_PROPERTY(QSize targetSize READ targetSize WRITE setTargetSize NOTIFY targetSizeChanged)
832+
833+public:
834+ WebviewThumbnailer(QQuickItem* parent=0);
835+ ~WebviewThumbnailer();
836+
837+ QQuickWebView* webview() const;
838+ void setWebview(QQuickWebView* webview);
839+
840+ const QSize& targetSize() const;
841+ void setTargetSize(const QSize& targetSize);
842+
843+ Q_INVOKABLE bool thumbnailExists() const;
844+ Q_INVOKABLE void renderThumbnail();
845+
846+Q_SIGNALS:
847+ void webviewChanged() const;
848+ void targetSizeChanged() const;
849+ void thumbnailRendered(const QUrl& url) const;
850+
851+protected:
852+ virtual QSGNode* updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData* updatePaintNodeData);
853+
854+private Q_SLOTS:
855+ void doRenderThumbnail();
856+
857+private:
858+ QQuickWebView* m_webview;
859+ QSize m_targetSize;
860+ QSGRenderer* m_renderer;
861+};
862+
863+#endif // __WEBVIEW_THUMBNAILER_H__
864
865=== modified file 'tests/autopilot/webbrowser_app/emulators/main_window.py'
866--- tests/autopilot/webbrowser_app/emulators/main_window.py 2013-07-05 11:21:52 +0000
867+++ tests/autopilot/webbrowser_app/emulators/main_window.py 2013-07-12 06:47:24 +0000
868@@ -79,7 +79,7 @@
869 return self.get_activity_view().select_single("TabsList")
870
871 def get_tabslist_newtab_delegate(self):
872- return self.get_tabslist().select_single("PageDelegate",
873+ return self.get_tabslist().select_single("UbuntuShape",
874 objectName="newTabDelegate")
875
876 def get_tabslist_view(self):
877
878=== modified file 'tests/unittests/history-hostlist-model/CMakeLists.txt'
879--- tests/unittests/history-hostlist-model/CMakeLists.txt 2013-07-02 11:29:03 +0000
880+++ tests/unittests/history-hostlist-model/CMakeLists.txt 2013-07-12 06:47:24 +0000
881@@ -4,6 +4,7 @@
882 ${webbrowser-plugin_SOURCE_DIR}/history-hostlist-model.cpp
883 ${webbrowser-plugin_SOURCE_DIR}/history-model.cpp
884 ${webbrowser-plugin_SOURCE_DIR}/history-timeframe-model.cpp
885+ ${webbrowser-plugin_SOURCE_DIR}/webthumbnail-utils.cpp
886 tst_HistoryHostListModelTests.cpp
887 )
888 add_executable(${TEST} ${SOURCES})
889
890=== modified file 'tests/unittests/history-hostlist-model/tst_HistoryHostListModelTests.cpp'
891--- tests/unittests/history-hostlist-model/tst_HistoryHostListModelTests.cpp 2013-07-02 11:29:03 +0000
892+++ tests/unittests/history-hostlist-model/tst_HistoryHostListModelTests.cpp 2013-07-12 06:47:24 +0000
893@@ -60,52 +60,75 @@
894
895 void shouldUpdateHostListWhenInsertingEntries()
896 {
897- QSignalSpy spy(model, SIGNAL(rowsInserted(const QModelIndex&, int, int)));
898+ QSignalSpy spyRowsInserted(model, SIGNAL(rowsInserted(const QModelIndex&, int, int)));
899+ qRegisterMetaType<QVector<int> >();
900+ QSignalSpy spyDataChanged(model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&)));
901
902 history->add(QUrl("http://example.org/"), "Example Domain", QUrl());
903- QCOMPARE(spy.count(), 1);
904- QList<QVariant> args = spy.takeFirst();
905+ QVERIFY(spyDataChanged.isEmpty());
906+ QCOMPARE(spyRowsInserted.count(), 1);
907+ QList<QVariant> args = spyRowsInserted.takeFirst();
908 QCOMPARE(args.at(1).toInt(), 0);
909 QCOMPARE(args.at(2).toInt(), 0);
910 QCOMPARE(model->rowCount(), 1);
911 QCOMPARE(model->data(model->index(0, 0), HistoryHostListModel::Host).toString(), QString("example.org"));
912
913 history->add(QUrl("http://example.com/"), "Example Domain", QUrl());
914- QCOMPARE(spy.count(), 1);
915- args = spy.takeFirst();
916+ QVERIFY(spyDataChanged.isEmpty());
917+ QCOMPARE(spyRowsInserted.count(), 1);
918+ args = spyRowsInserted.takeFirst();
919 QCOMPARE(args.at(1).toInt(), 0);
920 QCOMPARE(args.at(2).toInt(), 0);
921 QCOMPARE(model->rowCount(), 2);
922 QCOMPARE(model->data(model->index(0, 0), HistoryHostListModel::Host).toString(), QString("example.com"));
923
924 history->add(QUrl("http://example.org/test.html"), "Test page", QUrl());
925- QVERIFY(spy.isEmpty());
926+ QVERIFY(spyRowsInserted.isEmpty());
927+ QCOMPARE(spyDataChanged.count(), 1);
928+ args = spyDataChanged.takeFirst();
929+ QCOMPARE(args.at(0).toModelIndex().row(), 1);
930+ QCOMPARE(args.at(1).toModelIndex().row(), 1);
931 QCOMPARE(model->rowCount(), 2);
932 }
933
934 void shouldUpdateHostListWhenRemovingEntries()
935 {
936- QSignalSpy spy(model, SIGNAL(rowsRemoved(const QModelIndex&, int, int)));
937 history->add(QUrl("http://example.org/"), "Example Domain", QUrl());
938 QTest::qWait(100);
939 QDateTime t0 = QDateTime::currentDateTimeUtc();
940 QTest::qWait(100);
941 history->add(QUrl("http://example.com/"), "Example Domain", QUrl());
942- QCOMPARE(model->rowCount(), 2);
943-
944- timeframe->setEnd(t0);
945- QCOMPARE(spy.count(), 1);
946- QList<QVariant> args = spy.takeFirst();
947- QCOMPARE(args.at(1).toInt(), 0);
948- QCOMPARE(args.at(2).toInt(), 0);
949- QCOMPARE(model->rowCount(), 1);
950+ QTest::qWait(100);
951+ QDateTime t1 = QDateTime::currentDateTimeUtc();
952+ QTest::qWait(100);
953+ history->add(QUrl("http://example.org/test"), "Example Domain", QUrl());
954+ QCOMPARE(model->rowCount(), 2);
955+
956+ QSignalSpy spyRowsRemoved(model, SIGNAL(rowsRemoved(const QModelIndex&, int, int)));
957+ qRegisterMetaType<QVector<int> >();
958+ QSignalSpy spyDataChanged(model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&)));
959+
960+ timeframe->setEnd(t1);
961+ QVERIFY(spyRowsRemoved.isEmpty());
962+ QVERIFY(!spyDataChanged.isEmpty());
963+ QList<QVariant> args;
964+ bool changed = false;
965+ int expectedIndex = 1;
966+ while(!changed && !spyDataChanged.isEmpty()) {
967+ args = spyDataChanged.takeFirst();
968+ int start = args.at(0).toModelIndex().row();
969+ int end = args.at(1).toModelIndex().row();
970+ changed = (start <= expectedIndex) && (expectedIndex <= end);
971+ }
972+ QVERIFY(changed);
973+ QCOMPARE(model->rowCount(), 2);
974
975 timeframe->setStart(t0);
976- QCOMPARE(spy.count(), 1);
977- args = spy.takeFirst();
978- QCOMPARE(args.at(1).toInt(), 0);
979- QCOMPARE(args.at(2).toInt(), 0);
980- QCOMPARE(model->rowCount(), 0);
981+ QCOMPARE(spyRowsRemoved.count(), 1);
982+ args = spyRowsRemoved.takeFirst();
983+ QCOMPARE(args.at(1).toInt(), 1);
984+ QCOMPARE(args.at(2).toInt(), 1);
985+ QCOMPARE(model->rowCount(), 1);
986 }
987
988 void shouldUpdateWhenChangingSourceModel()

Subscribers

People subscribed via source and target branches

to status/vote changes: