Merge lp:~ahayzen/webbrowser-app/fix-1633040-add-js-dialog-ap-tests into lp:webbrowser-app
- fix-1633040-add-js-dialog-ap-tests
- Merge into trunk
Proposed by
Andrew Hayzen
Status: | Superseded | ||||
---|---|---|---|---|---|
Proposed branch: | lp:~ahayzen/webbrowser-app/fix-1633040-add-js-dialog-ap-tests | ||||
Merge into: | lp:webbrowser-app | ||||
Diff against target: |
5186 lines (+2471/-1203) 51 files modified
.bzrignore (+1/-0) CMakeLists.txt (+3/-6) debian/control (+1/-2) make-snap.sh (+4/-0) po/webbrowser-app.pot (+49/-43) setup/gui/webbrowser-app.desktop.in (+27/-0) snap/webbrowser-app.launcher (+10/-0) snapcraft.yaml (+66/-0) src/Ubuntu/CMakeLists.txt (+0/-8) src/Ubuntu/Web/UbuntuWebContext.qml (+7/-1) src/Ubuntu/Web/ua-overrides-desktop.js.in (+9/-9) src/Ubuntu/Web/ua-overrides-mobile.js.in (+11/-11) src/app/AlertDialog.qml (+2/-0) src/app/BeforeUnloadDialog.qml (+3/-0) src/app/ChromeBase.qml (+5/-3) src/app/ConfirmDialog.qml (+3/-0) src/app/Downloader.qml (+3/-11) src/app/PromptDialog.qml (+4/-0) src/app/ThinProgressBar.qml (+3/-6) src/app/WebViewImpl.qml (+2/-2) src/app/config.h.in (+3/-2) src/app/webbrowser/BookmarkOptions.qml (+25/-4) src/app/webbrowser/Browser.qml (+112/-596) src/app/webbrowser/BrowserTab.qml (+4/-3) src/app/webbrowser/Chrome.qml (+30/-5) src/app/webbrowser/DownloadDelegate.qml (+151/-149) src/app/webbrowser/DownloadsPage.qml (+10/-2) src/app/webbrowser/HistoryViewWithExpansion.qml (+67/-0) src/app/webbrowser/NavigationBar.qml (+3/-11) src/app/webbrowser/TabComponent.qml (+436/-0) src/app/webbrowser/TabItem.qml (+16/-4) src/app/webbrowser/TabsBar.qml (+51/-6) src/app/webbrowser/downloads-model.cpp (+139/-80) src/app/webbrowser/downloads-model.h (+7/-9) src/app/webbrowser/history-model.cpp (+251/-152) src/app/webbrowser/history-model.h (+64/-7) src/app/webbrowser/webbrowser-app.qml (+28/-9) src/app/webcontainer/Chrome.qml (+1/-0) src/app/webcontainer/WebApp.qml (+2/-1) tests/autopilot/webapp_container/tests/__init__.py (+9/-6) tests/autopilot/webapp_container/tests/fake_servers.py (+34/-0) tests/autopilot/webapp_container/tests/test_js_dialogs.py (+214/-0) tests/autopilot/webbrowser_app/emulators/browser.py (+64/-2) tests/autopilot/webbrowser_app/tests/http_server.py (+48/-0) tests/autopilot/webbrowser_app/tests/test_history.py (+35/-0) tests/autopilot/webbrowser_app/tests/test_js_dialogs.py (+156/-0) tests/autopilot/webbrowser_app/tests/test_new_tab_view.py (+1/-8) tests/unittests/downloads-model/tst_DownloadsModelTests.cpp (+262/-43) tests/unittests/history-model/tst_HistoryModelTests.cpp (+3/-2) tests/unittests/qml/CMakeLists.txt (+6/-0) tests/unittests/qml/tst_TabsBar.qml (+26/-0) |
||||
To merge this branch: | bzr merge lp:~ahayzen/webbrowser-app/fix-1633040-add-js-dialog-ap-tests | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
system-apps-ci-bot | continuous-integration | Needs Fixing | |
Ubuntu Phablet Team | Pending | ||
Review via email: mp+308390@code.launchpad.net |
Commit message
* Add autopilot tests javascript dialogs to webbrowser and webapp-container - alertDialog, beforeUnloadDialog, confirmDialog and promptDialog
Description of the change
* Add autopilot tests javascript dialogs to webbrowser and webapp-container - alertDialog, beforeUnloadDialog, confirmDialog and promptDialog
TESTING:
Run the following test suites:
webapp_
webbrowser_
To post a comment you must log in.
- 1561. By Andrew Hayzen
-
* Revert .pot changes
Revision history for this message
system-apps-ci-bot (system-apps-ci-bot) wrote : | # |
review:
Needs Fixing
(continuous-integration)
- 1562. By Andrew Hayzen
-
* Ensure dialog closes on before unload tests
Unmerged revisions
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file '.bzrignore' | |||
2 | --- .bzrignore 2016-06-13 13:41:48 +0000 | |||
3 | +++ .bzrignore 2016-10-13 14:41:36 +0000 | |||
4 | @@ -31,6 +31,7 @@ | |||
5 | 31 | doc/html | 31 | doc/html |
6 | 32 | click-hooks/webapp-container-hook | 32 | click-hooks/webapp-container-hook |
7 | 33 | click-hooks/webapp-container.hook | 33 | click-hooks/webapp-container.hook |
8 | 34 | setup/gui/webbrowser-app.desktop | ||
9 | 34 | 35 | ||
10 | 35 | obj-* | 36 | obj-* |
11 | 36 | debian/usr.bin.webbrowser-app | 37 | debian/usr.bin.webbrowser-app |
12 | 37 | 38 | ||
13 | === modified file 'CMakeLists.txt' | |||
14 | --- CMakeLists.txt 2016-07-01 13:09:27 +0000 | |||
15 | +++ CMakeLists.txt 2016-10-13 14:41:36 +0000 | |||
16 | @@ -11,11 +11,6 @@ | |||
17 | 11 | if(NOT INTLTOOL_EXTRACT) | 11 | if(NOT INTLTOOL_EXTRACT) |
18 | 12 | message(FATAL_ERROR "Could not find intltool-extract, please install the intltool package") | 12 | message(FATAL_ERROR "Could not find intltool-extract, please install the intltool package") |
19 | 13 | endif() | 13 | endif() |
20 | 14 | find_program(XVFBRUN xvfb-run) | ||
21 | 15 | if(NOT XVFBRUN) | ||
22 | 16 | message(FATAL_ERROR "Could not find xvfb-run, please install the xvfb package") | ||
23 | 17 | endif() | ||
24 | 18 | set(XVFB_COMMAND ${XVFBRUN} -s "-screen 0 640x480x24" -a) | ||
25 | 19 | 14 | ||
26 | 20 | # Standard install paths | 15 | # Standard install paths |
27 | 21 | include(GNUInstallDirs) | 16 | include(GNUInstallDirs) |
28 | @@ -67,13 +62,15 @@ | |||
29 | 67 | add_subdirectory(tests) | 62 | add_subdirectory(tests) |
30 | 68 | 63 | ||
31 | 69 | # make non compiled files (QML, JS, images, etc.) visible in QtCreator | 64 | # make non compiled files (QML, JS, images, etc.) visible in QtCreator |
33 | 70 | file(GLOB NON_COMPILED_ROOT *.png .bzrignore COPYING README) | 65 | file(GLOB NON_COMPILED_ROOT *.png .bzrignore COPYING make-snap.sh README snapcraft.yaml) |
34 | 71 | file(GLOB_RECURSE NON_COMPILED_SUBDIRS | 66 | file(GLOB_RECURSE NON_COMPILED_SUBDIRS |
35 | 72 | debian/*.dirs debian/*.install debian/*.lintian-overrides debian/*.manifest | 67 | debian/*.dirs debian/*.install debian/*.lintian-overrides debian/*.manifest |
36 | 73 | debian/compat debian/control debian/copyright debian/rules debian/source/format | 68 | debian/compat debian/control debian/copyright debian/rules debian/source/format |
37 | 74 | debian/tests/* | 69 | debian/tests/* |
38 | 75 | doc/*.css doc/*.qdoc doc/*.qdocconf | 70 | doc/*.css doc/*.qdoc doc/*.qdocconf |
39 | 76 | po/*.po po/*.pot | 71 | po/*.po po/*.pot |
40 | 72 | setup/gui/*.png setup/gui/webbrowser-app.desktop.in | ||
41 | 73 | snap/webbrowser-app.launcher | ||
42 | 77 | src/*.js src/*.qml src/*.sci src/README | 74 | src/*.js src/*.qml src/*.sci src/README |
43 | 78 | tests/*.py tests/*.qml) | 75 | tests/*.py tests/*.qml) |
44 | 79 | add_custom_target(NON_COMPILED_TARGET ALL SOURCES ${NON_COMPILED_ROOT} ${NON_COMPILED_SUBDIRS}) | 76 | add_custom_target(NON_COMPILED_TARGET ALL SOURCES ${NON_COMPILED_ROOT} ${NON_COMPILED_SUBDIRS}) |
45 | 80 | 77 | ||
46 | === modified file 'debian/control' | |||
47 | --- debian/control 2016-08-25 09:52:00 +0000 | |||
48 | +++ debian/control 2016-10-13 14:41:36 +0000 | |||
49 | @@ -11,7 +11,7 @@ | |||
50 | 11 | dh-translations, | 11 | dh-translations, |
51 | 12 | libapparmor-dev, | 12 | libapparmor-dev, |
52 | 13 | libevdev-dev, | 13 | libevdev-dev, |
54 | 14 | liboxideqt-qmlplugin (>= 1.15), | 14 | liboxideqt-qmlplugin (>= 1.12), |
55 | 15 | libqt5sql5-sqlite, | 15 | libqt5sql5-sqlite, |
56 | 16 | libudev-dev, | 16 | libudev-dev, |
57 | 17 | lsb-release, | 17 | lsb-release, |
58 | @@ -23,7 +23,6 @@ | |||
59 | 23 | qml-module-qtquick2 (>= 5.4), | 23 | qml-module-qtquick2 (>= 5.4), |
60 | 24 | qml-module-qtquick-layouts, | 24 | qml-module-qtquick-layouts, |
61 | 25 | qml-module-qttest, | 25 | qml-module-qttest, |
62 | 26 | qmlscene, | ||
63 | 27 | qt5-default, | 26 | qt5-default, |
64 | 28 | qt5-qmake, | 27 | qt5-qmake, |
65 | 29 | qtbase5-dev (>= 5.4), | 28 | qtbase5-dev (>= 5.4), |
66 | 30 | 29 | ||
67 | === added file 'make-snap.sh' | |||
68 | --- make-snap.sh 1970-01-01 00:00:00 +0000 | |||
69 | +++ make-snap.sh 2016-10-13 14:41:36 +0000 | |||
70 | @@ -0,0 +1,4 @@ | |||
71 | 1 | #!/bin/sh | ||
72 | 2 | SNAP_DESKTOP_FILE=setup/gui/webbrowser-app.desktop | ||
73 | 3 | intltool-merge -d -u po $SNAP_DESKTOP_FILE.in $SNAP_DESKTOP_FILE | ||
74 | 4 | snapcraft | ||
75 | 0 | 5 | ||
76 | === modified file 'po/webbrowser-app.pot' | |||
77 | --- po/webbrowser-app.pot 2016-10-04 14:07:17 +0000 | |||
78 | +++ po/webbrowser-app.pot 2016-10-13 14:41:36 +0000 | |||
79 | @@ -8,7 +8,7 @@ | |||
80 | 8 | msgstr "" | 8 | msgstr "" |
81 | 9 | "Project-Id-Version: webbrowser-app\n" | 9 | "Project-Id-Version: webbrowser-app\n" |
82 | 10 | "Report-Msgid-Bugs-To: \n" | 10 | "Report-Msgid-Bugs-To: \n" |
84 | 11 | "POT-Creation-Date: 2016-10-04 16:02+0200\n" | 11 | "POT-Creation-Date: 2016-10-13 12:01+0100\n" |
85 | 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" | 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" |
86 | 13 | "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" | 13 | "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" |
87 | 14 | "Language-Team: LANGUAGE <LL@li.org>\n" | 14 | "Language-Team: LANGUAGE <LL@li.org>\n" |
88 | @@ -24,7 +24,7 @@ | |||
89 | 24 | 24 | ||
90 | 25 | #: src/app/AlertDialog.qml:26 src/app/AuthenticationDialog.qml:47 | 25 | #: src/app/AlertDialog.qml:26 src/app/AuthenticationDialog.qml:47 |
91 | 26 | #: src/app/ConfirmDialog.qml:26 src/app/HttpAuthenticationDialog.qml:59 | 26 | #: src/app/ConfirmDialog.qml:26 src/app/HttpAuthenticationDialog.qml:59 |
93 | 27 | #: src/app/PromptDialog.qml:32 src/app/webbrowser/BookmarkOptions.qml:101 | 27 | #: src/app/PromptDialog.qml:32 src/app/webbrowser/BookmarkOptions.qml:122 |
94 | 28 | msgid "OK" | 28 | msgid "OK" |
95 | 29 | msgstr "" | 29 | msgstr "" |
96 | 30 | 30 | ||
97 | @@ -48,10 +48,10 @@ | |||
98 | 48 | 48 | ||
99 | 49 | #: src/app/AuthenticationDialog.qml:53 src/app/ConfirmDialog.qml:32 | 49 | #: src/app/AuthenticationDialog.qml:53 src/app/ConfirmDialog.qml:32 |
100 | 50 | #: src/app/HttpAuthenticationDialog.qml:69 src/app/PromptDialog.qml:38 | 50 | #: src/app/HttpAuthenticationDialog.qml:69 src/app/PromptDialog.qml:38 |
102 | 51 | #: src/app/webbrowser/BookmarkOptions.qml:143 | 51 | #: src/app/webbrowser/BookmarkOptions.qml:164 |
103 | 52 | #: src/app/webbrowser/ContentDownloadDialog.qml:114 | 52 | #: src/app/webbrowser/ContentDownloadDialog.qml:114 |
104 | 53 | #: src/app/webbrowser/ContextMenuMobile.qml:139 | 53 | #: src/app/webbrowser/ContextMenuMobile.qml:139 |
106 | 54 | #: src/app/webbrowser/DownloadDelegate.qml:187 | 54 | #: src/app/webbrowser/DownloadDelegate.qml:178 |
107 | 55 | #: src/app/webbrowser/SettingsPage.qml:258 | 55 | #: src/app/webbrowser/SettingsPage.qml:258 |
108 | 56 | #: src/app/webbrowser/SettingsPage.qml:312 | 56 | #: src/app/webbrowser/SettingsPage.qml:312 |
109 | 57 | #: src/app/webcontainer/AccountChooserDialog.qml:96 | 57 | #: src/app/webcontainer/AccountChooserDialog.qml:96 |
110 | @@ -378,7 +378,7 @@ | |||
111 | 378 | msgid "Erase" | 378 | msgid "Erase" |
112 | 379 | msgstr "" | 379 | msgstr "" |
113 | 380 | 380 | ||
115 | 381 | #: src/app/actions/FindInPage.qml:23 src/app/webbrowser/Browser.qml:569 | 381 | #: src/app/actions/FindInPage.qml:23 src/app/webbrowser/Browser.qml:548 |
116 | 382 | msgid "Find in page" | 382 | msgid "Find in page" |
117 | 383 | msgstr "" | 383 | msgstr "" |
118 | 384 | 384 | ||
119 | @@ -408,8 +408,8 @@ | |||
120 | 408 | msgid "Address;URL;www" | 408 | msgid "Address;URL;www" |
121 | 409 | msgstr "" | 409 | msgstr "" |
122 | 410 | 410 | ||
125 | 411 | #: src/app/actions/NewTab.qml:23 src/app/webbrowser/Browser.qml:440 | 411 | #: src/app/actions/NewTab.qml:23 src/app/webbrowser/Browser.qml:420 |
126 | 412 | #: src/app/webbrowser/TabsBar.qml:91 | 412 | #: src/app/webbrowser/TabsBar.qml:103 |
127 | 413 | msgid "New Tab" | 413 | msgid "New Tab" |
128 | 414 | msgstr "" | 414 | msgstr "" |
129 | 415 | 415 | ||
130 | @@ -456,7 +456,7 @@ | |||
131 | 456 | msgstr "" | 456 | msgstr "" |
132 | 457 | 457 | ||
133 | 458 | #: src/app/actions/Reload.qml:23 src/app/webbrowser/SadTab.qml:86 | 458 | #: src/app/actions/Reload.qml:23 src/app/webbrowser/SadTab.qml:86 |
135 | 459 | #: src/app/webbrowser/TabsBar.qml:96 src/app/webcontainer/SadPage.qml:51 | 459 | #: src/app/webbrowser/TabsBar.qml:108 src/app/webcontainer/SadPage.qml:51 |
136 | 460 | msgid "Reload" | 460 | msgid "Reload" |
137 | 461 | msgstr "" | 461 | msgstr "" |
138 | 462 | 462 | ||
139 | @@ -478,11 +478,11 @@ | |||
140 | 478 | msgid "Save video" | 478 | msgid "Save video" |
141 | 479 | msgstr "" | 479 | msgstr "" |
142 | 480 | 480 | ||
144 | 481 | #: src/app/actions/SelectAll.qml:22 src/app/webbrowser/DownloadsPage.qml:83 | 481 | #: src/app/actions/SelectAll.qml:22 src/app/webbrowser/DownloadsPage.qml:84 |
145 | 482 | msgid "Select all" | 482 | msgid "Select all" |
146 | 483 | msgstr "" | 483 | msgstr "" |
147 | 484 | 484 | ||
149 | 485 | #: src/app/actions/Share.qml:22 src/app/webbrowser/Browser.qml:549 | 485 | #: src/app/actions/Share.qml:22 src/app/webbrowser/Browser.qml:528 |
150 | 486 | msgid "Share" | 486 | msgid "Share" |
151 | 487 | msgstr "" | 487 | msgstr "" |
152 | 488 | 488 | ||
153 | @@ -504,36 +504,36 @@ | |||
154 | 504 | msgid "search or enter an address" | 504 | msgid "search or enter an address" |
155 | 505 | msgstr "" | 505 | msgstr "" |
156 | 506 | 506 | ||
158 | 507 | #: src/app/webbrowser/BookmarkOptions.qml:48 | 507 | #: src/app/webbrowser/BookmarkOptions.qml:66 |
159 | 508 | msgid "Bookmark Added" | 508 | msgid "Bookmark Added" |
160 | 509 | msgstr "" | 509 | msgstr "" |
161 | 510 | 510 | ||
162 | 511 | #. TRANSLATORS: Field where the title of bookmarked URL can be changed | 511 | #. TRANSLATORS: Field where the title of bookmarked URL can be changed |
164 | 512 | #: src/app/webbrowser/BookmarkOptions.qml:53 | 512 | #: src/app/webbrowser/BookmarkOptions.qml:71 |
165 | 513 | msgid "Name" | 513 | msgid "Name" |
166 | 514 | msgstr "" | 514 | msgstr "" |
167 | 515 | 515 | ||
168 | 516 | #. TRANSLATORS: Field to choose the folder where bookmarked URL will be saved in | 516 | #. TRANSLATORS: Field to choose the folder where bookmarked URL will be saved in |
170 | 517 | #: src/app/webbrowser/BookmarkOptions.qml:71 | 517 | #: src/app/webbrowser/BookmarkOptions.qml:89 |
171 | 518 | msgid "Save in" | 518 | msgid "Save in" |
172 | 519 | msgstr "" | 519 | msgstr "" |
173 | 520 | 520 | ||
175 | 521 | #: src/app/webbrowser/BookmarkOptions.qml:78 | 521 | #: src/app/webbrowser/BookmarkOptions.qml:96 |
176 | 522 | #: src/app/webbrowser/BookmarksFoldersView.qml:133 | 522 | #: src/app/webbrowser/BookmarksFoldersView.qml:133 |
177 | 523 | #: src/app/webbrowser/BookmarksFoldersViewWide.qml:105 | 523 | #: src/app/webbrowser/BookmarksFoldersViewWide.qml:105 |
178 | 524 | msgid "All Bookmarks" | 524 | msgid "All Bookmarks" |
179 | 525 | msgstr "" | 525 | msgstr "" |
180 | 526 | 526 | ||
183 | 527 | #: src/app/webbrowser/BookmarkOptions.qml:93 | 527 | #: src/app/webbrowser/BookmarkOptions.qml:114 |
184 | 528 | #: src/app/webbrowser/BookmarkOptions.qml:133 | 528 | #: src/app/webbrowser/BookmarkOptions.qml:154 |
185 | 529 | msgid "New Folder" | 529 | msgid "New Folder" |
186 | 530 | msgstr "" | 530 | msgstr "" |
187 | 531 | 531 | ||
189 | 532 | #: src/app/webbrowser/BookmarkOptions.qml:115 | 532 | #: src/app/webbrowser/BookmarkOptions.qml:136 |
190 | 533 | msgid "Create new folder" | 533 | msgid "Create new folder" |
191 | 534 | msgstr "" | 534 | msgstr "" |
192 | 535 | 535 | ||
194 | 536 | #: src/app/webbrowser/BookmarkOptions.qml:153 | 536 | #: src/app/webbrowser/BookmarkOptions.qml:174 |
195 | 537 | #: src/app/webbrowser/SettingsPage.qml:322 | 537 | #: src/app/webbrowser/SettingsPage.qml:322 |
196 | 538 | msgid "Save" | 538 | msgid "Save" |
197 | 539 | msgstr "" | 539 | msgstr "" |
198 | @@ -547,14 +547,14 @@ | |||
199 | 547 | 547 | ||
200 | 548 | #: src/app/webbrowser/BookmarksView.qml:32 | 548 | #: src/app/webbrowser/BookmarksView.qml:32 |
201 | 549 | #: src/app/webbrowser/BookmarksViewWide.qml:32 | 549 | #: src/app/webbrowser/BookmarksViewWide.qml:32 |
203 | 550 | #: src/app/webbrowser/Browser.qml:557 src/app/webbrowser/NewTabView.qml:130 | 550 | #: src/app/webbrowser/Browser.qml:536 src/app/webbrowser/NewTabView.qml:130 |
204 | 551 | #: src/app/webbrowser/NewTabViewWide.qml:139 | 551 | #: src/app/webbrowser/NewTabViewWide.qml:139 |
205 | 552 | msgid "Bookmarks" | 552 | msgid "Bookmarks" |
206 | 553 | msgstr "" | 553 | msgstr "" |
207 | 554 | 554 | ||
208 | 555 | #: src/app/webbrowser/BookmarksView.qml:76 | 555 | #: src/app/webbrowser/BookmarksView.qml:76 |
209 | 556 | #: src/app/webbrowser/BookmarksViewWide.qml:75 | 556 | #: src/app/webbrowser/BookmarksViewWide.qml:75 |
211 | 557 | #: src/app/webbrowser/Browser.qml:426 src/app/webbrowser/HistoryView.qml:126 | 557 | #: src/app/webbrowser/Browser.qml:406 src/app/webbrowser/HistoryView.qml:126 |
212 | 558 | #: src/app/webbrowser/HistoryViewWide.qml:407 | 558 | #: src/app/webbrowser/HistoryViewWide.qml:407 |
213 | 559 | msgid "Done" | 559 | msgid "Done" |
214 | 560 | msgstr "" | 560 | msgstr "" |
215 | @@ -563,45 +563,37 @@ | |||
216 | 563 | #: src/app/webbrowser/BookmarksViewWide.qml:89 | 563 | #: src/app/webbrowser/BookmarksViewWide.qml:89 |
217 | 564 | #: src/app/webbrowser/HistoryView.qml:140 | 564 | #: src/app/webbrowser/HistoryView.qml:140 |
218 | 565 | #: src/app/webbrowser/HistoryViewWide.qml:421 | 565 | #: src/app/webbrowser/HistoryViewWide.qml:421 |
220 | 566 | #: src/app/webbrowser/TabsBar.qml:153 src/app/webbrowser/TabsList.qml:99 | 566 | #: src/app/webbrowser/TabsBar.qml:165 src/app/webbrowser/TabsList.qml:99 |
221 | 567 | msgid "New tab" | 567 | msgid "New tab" |
222 | 568 | msgstr "" | 568 | msgstr "" |
223 | 569 | 569 | ||
225 | 570 | #: src/app/webbrowser/Browser.qml:537 | 570 | #: src/app/webbrowser/Browser.qml:516 |
226 | 571 | msgid "New window" | 571 | msgid "New window" |
227 | 572 | msgstr "" | 572 | msgstr "" |
228 | 573 | 573 | ||
230 | 574 | #: src/app/webbrowser/Browser.qml:543 | 574 | #: src/app/webbrowser/Browser.qml:522 |
231 | 575 | msgid "New private window" | 575 | msgid "New private window" |
232 | 576 | msgstr "" | 576 | msgstr "" |
233 | 577 | 577 | ||
235 | 578 | #: src/app/webbrowser/Browser.qml:563 src/app/webbrowser/HistoryView.qml:30 | 578 | #: src/app/webbrowser/Browser.qml:542 src/app/webbrowser/HistoryView.qml:30 |
236 | 579 | #: src/app/webbrowser/HistoryViewWide.qml:35 | 579 | #: src/app/webbrowser/HistoryViewWide.qml:35 |
237 | 580 | msgid "History" | 580 | msgid "History" |
238 | 581 | msgstr "" | 581 | msgstr "" |
239 | 582 | 582 | ||
241 | 583 | #: src/app/webbrowser/Browser.qml:576 src/app/webbrowser/DownloadsPage.qml:45 | 583 | #: src/app/webbrowser/Browser.qml:555 src/app/webbrowser/DownloadsPage.qml:46 |
242 | 584 | msgid "Downloads" | 584 | msgid "Downloads" |
243 | 585 | msgstr "" | 585 | msgstr "" |
244 | 586 | 586 | ||
246 | 587 | #: src/app/webbrowser/Browser.qml:583 src/app/webbrowser/SettingsPage.qml:41 | 587 | #: src/app/webbrowser/Browser.qml:562 src/app/webbrowser/SettingsPage.qml:41 |
247 | 588 | msgid "Settings" | 588 | msgid "Settings" |
248 | 589 | msgstr "" | 589 | msgstr "" |
249 | 590 | 590 | ||
250 | 591 | #. TRANSLATORS: %1 refers to the current number of tabs opened | 591 | #. TRANSLATORS: %1 refers to the current number of tabs opened |
252 | 592 | #: src/app/webbrowser/Browser.qml:756 src/app/webbrowser/Browser.qml:794 | 592 | #: src/app/webbrowser/Browser.qml:735 src/app/webbrowser/Browser.qml:773 |
253 | 593 | #, qt-format | 593 | #, qt-format |
254 | 594 | msgid "(%1)" | 594 | msgid "(%1)" |
255 | 595 | msgstr "" | 595 | msgstr "" |
256 | 596 | 596 | ||
257 | 597 | #: src/app/webbrowser/Browser.qml:1337 | ||
258 | 598 | msgid "Swipe Up To Exit Full Screen" | ||
259 | 599 | msgstr "" | ||
260 | 600 | |||
261 | 601 | #: src/app/webbrowser/Browser.qml:1338 | ||
262 | 602 | msgid "Press ESC To Exit Full Screen" | ||
263 | 603 | msgstr "" | ||
264 | 604 | |||
265 | 605 | #: src/app/webbrowser/ContentDownloadDialog.qml:83 | 597 | #: src/app/webbrowser/ContentDownloadDialog.qml:83 |
266 | 606 | msgid "" | 598 | msgid "" |
267 | 607 | "Choose an application to open this file or add it to the downloads folder." | 599 | "Choose an application to open this file or add it to the downloads folder." |
268 | @@ -615,23 +607,29 @@ | |||
269 | 615 | msgid "Download" | 607 | msgid "Download" |
270 | 616 | msgstr "" | 608 | msgstr "" |
271 | 617 | 609 | ||
273 | 618 | #: src/app/webbrowser/DownloadDelegate.qml:159 | 610 | #: src/app/webbrowser/DownloadDelegate.qml:151 |
274 | 619 | msgid "Download failed" | 611 | msgid "Download failed" |
275 | 620 | msgstr "" | 612 | msgstr "" |
276 | 621 | 613 | ||
278 | 622 | #: src/app/webbrowser/DownloadDelegate.qml:208 | 614 | #. TRANSLATORS: %1 is the percentage of the download completed so far |
279 | 615 | #: src/app/webbrowser/DownloadDelegate.qml:194 | ||
280 | 616 | #, qt-format | ||
281 | 617 | msgid "%1%" | ||
282 | 618 | msgstr "" | ||
283 | 619 | |||
284 | 620 | #: src/app/webbrowser/DownloadDelegate.qml:199 | ||
285 | 623 | msgid "Resume" | 621 | msgid "Resume" |
286 | 624 | msgstr "" | 622 | msgstr "" |
287 | 625 | 623 | ||
289 | 626 | #: src/app/webbrowser/DownloadsPage.qml:59 | 624 | #: src/app/webbrowser/DownloadsPage.qml:60 |
290 | 627 | msgid "Confirm selection" | 625 | msgid "Confirm selection" |
291 | 628 | msgstr "" | 626 | msgstr "" |
292 | 629 | 627 | ||
294 | 630 | #: src/app/webbrowser/DownloadsPage.qml:99 | 628 | #: src/app/webbrowser/DownloadsPage.qml:100 |
295 | 631 | msgid "Delete" | 629 | msgid "Delete" |
296 | 632 | msgstr "" | 630 | msgstr "" |
297 | 633 | 631 | ||
299 | 634 | #: src/app/webbrowser/DownloadsPage.qml:252 | 632 | #: src/app/webbrowser/DownloadsPage.qml:260 |
300 | 635 | msgid "No downloads available" | 633 | msgid "No downloads available" |
301 | 636 | msgstr "" | 634 | msgstr "" |
302 | 637 | 635 | ||
303 | @@ -819,11 +817,19 @@ | |||
304 | 819 | msgid "Camera" | 817 | msgid "Camera" |
305 | 820 | msgstr "" | 818 | msgstr "" |
306 | 821 | 819 | ||
307 | 820 | #: src/app/webbrowser/TabComponent.qml:388 | ||
308 | 821 | msgid "Swipe Up To Exit Full Screen" | ||
309 | 822 | msgstr "" | ||
310 | 823 | |||
311 | 824 | #: src/app/webbrowser/TabComponent.qml:389 | ||
312 | 825 | msgid "Press ESC To Exit Full Screen" | ||
313 | 826 | msgstr "" | ||
314 | 827 | |||
315 | 822 | #: src/app/webbrowser/TabPreview.qml:86 | 828 | #: src/app/webbrowser/TabPreview.qml:86 |
316 | 823 | msgid "Tap to view" | 829 | msgid "Tap to view" |
317 | 824 | msgstr "" | 830 | msgstr "" |
318 | 825 | 831 | ||
320 | 826 | #: src/app/webbrowser/TabsBar.qml:102 | 832 | #: src/app/webbrowser/TabsBar.qml:114 |
321 | 827 | msgid "Close Tab" | 833 | msgid "Close Tab" |
322 | 828 | msgstr "" | 834 | msgstr "" |
323 | 829 | 835 | ||
324 | @@ -832,13 +838,13 @@ | |||
325 | 832 | msgstr "" | 838 | msgstr "" |
326 | 833 | 839 | ||
327 | 834 | #. TRANSLATORS: %1 refers to the current page’s title | 840 | #. TRANSLATORS: %1 refers to the current page’s title |
329 | 835 | #: src/app/webbrowser/webbrowser-app.qml:108 | 841 | #: src/app/webbrowser/webbrowser-app.qml:99 |
330 | 836 | #: src/app/webcontainer/webapp-container.qml:72 | 842 | #: src/app/webcontainer/webapp-container.qml:72 |
331 | 837 | #, qt-format | 843 | #, qt-format |
332 | 838 | msgid "%1 - Ubuntu Web Browser" | 844 | msgid "%1 - Ubuntu Web Browser" |
333 | 839 | msgstr "" | 845 | msgstr "" |
334 | 840 | 846 | ||
336 | 841 | #: src/app/webbrowser/webbrowser-app.qml:110 | 847 | #: src/app/webbrowser/webbrowser-app.qml:101 |
337 | 842 | #: src/app/webcontainer/webapp-container.qml:74 | 848 | #: src/app/webcontainer/webapp-container.qml:74 |
338 | 843 | msgid "Ubuntu Web Browser" | 849 | msgid "Ubuntu Web Browser" |
339 | 844 | msgstr "" | 850 | msgstr "" |
340 | 845 | 851 | ||
341 | === added directory 'setup' | |||
342 | === added directory 'setup/gui' | |||
343 | === added symlink 'setup/gui/icon.png' | |||
344 | === target is u'../../webbrowser-app.png' | |||
345 | === added symlink 'setup/gui/screenshot.png' | |||
346 | === target is u'../../screenshot.png' | |||
347 | === added file 'setup/gui/webbrowser-app.desktop.in' | |||
348 | --- setup/gui/webbrowser-app.desktop.in 1970-01-01 00:00:00 +0000 | |||
349 | +++ setup/gui/webbrowser-app.desktop.in 2016-10-13 14:41:36 +0000 | |||
350 | @@ -0,0 +1,27 @@ | |||
351 | 1 | [Desktop Entry] | ||
352 | 2 | Version=1.0 | ||
353 | 3 | _Name=Browser | ||
354 | 4 | _GenericName=Web Browser | ||
355 | 5 | _Comment=Browse the World Wide Web | ||
356 | 6 | _Keywords=Internet;WWW;Browser;Web;Explorer | ||
357 | 7 | Type=Application | ||
358 | 8 | Icon=${SNAP}/meta/gui/icon.png | ||
359 | 9 | Exec=webbrowser-app %u | ||
360 | 10 | Terminal=false | ||
361 | 11 | Categories=Network;WebBrowser; | ||
362 | 12 | MimeType=text/html;text/xml;application/xhtml+xml;x-scheme-handler/http;x-scheme-handler/https; | ||
363 | 13 | X-Ubuntu-Touch=true | ||
364 | 14 | X-Ubuntu-Gettext-Domain=webbrowser-app | ||
365 | 15 | X-Ubuntu-Single-Instance=true | ||
366 | 16 | X-Ubuntu-Default-Department-ID=web-browsers | ||
367 | 17 | X-Screenshot=${SNAP}/meta/gui/screenshot.png | ||
368 | 18 | X-Ubuntu-Splash-Color=#FFFFFF | ||
369 | 19 | Actions=NewWindow;Incognito; | ||
370 | 20 | |||
371 | 21 | [Desktop Action NewWindow] | ||
372 | 22 | _Name=Open a New Window | ||
373 | 23 | Exec=webbrowser-app --new-window | ||
374 | 24 | |||
375 | 25 | [Desktop Action Incognito] | ||
376 | 26 | _Name=Open a New Private Window | ||
377 | 27 | Exec=webbrowser-app --incognito | ||
378 | 0 | 28 | ||
379 | === added directory 'snap' | |||
380 | === added file 'snap/webbrowser-app.launcher' | |||
381 | --- snap/webbrowser-app.launcher 1970-01-01 00:00:00 +0000 | |||
382 | +++ snap/webbrowser-app.launcher 2016-10-13 14:41:36 +0000 | |||
383 | @@ -0,0 +1,10 @@ | |||
384 | 1 | #!/bin/sh | ||
385 | 2 | |||
386 | 3 | # Disable the chromium sandbox to work around https://launchpad.net/bugs/1599234. | ||
387 | 4 | # Rely on snapd’s security policy instead. | ||
388 | 5 | export OXIDE_NO_SANDBOX=1 | ||
389 | 6 | |||
390 | 7 | # Explicitly set APP_ID. | ||
391 | 8 | export APP_ID=webbrowser-app | ||
392 | 9 | |||
393 | 10 | exec "$SNAP/bin/desktop-launch" "webbrowser-app" --desktop_file_hint=unity8 "$@" | ||
394 | 0 | 11 | ||
395 | === added file 'snapcraft.yaml' | |||
396 | --- snapcraft.yaml 1970-01-01 00:00:00 +0000 | |||
397 | +++ snapcraft.yaml 2016-10-13 14:41:36 +0000 | |||
398 | @@ -0,0 +1,66 @@ | |||
399 | 1 | name: webbrowser-app | ||
400 | 2 | version: 0.23+16.10.20160928-0ubuntu1 | ||
401 | 3 | summary: Ubuntu web browser | ||
402 | 4 | description: A lightweight web browser tailored for Ubuntu, based on the Oxide browser engine and using the Ubuntu UI components. | ||
403 | 5 | confinement: strict | ||
404 | 6 | |||
405 | 7 | apps: | ||
406 | 8 | webbrowser-app: | ||
407 | 9 | command: webbrowser-app.launcher | ||
408 | 10 | plugs: | ||
409 | 11 | - browser-sandbox | ||
410 | 12 | - camera | ||
411 | 13 | - network | ||
412 | 14 | - network-bind | ||
413 | 15 | - opengl | ||
414 | 16 | - pulseaudio | ||
415 | 17 | - screen-inhibit-control | ||
416 | 18 | - unity7 | ||
417 | 19 | |||
418 | 20 | plugs: | ||
419 | 21 | browser-sandbox: | ||
420 | 22 | interface: browser-support | ||
421 | 23 | allow-sandbox: true | ||
422 | 24 | |||
423 | 25 | parts: | ||
424 | 26 | webbrowser-app: | ||
425 | 27 | plugin: cmake | ||
426 | 28 | source: . | ||
427 | 29 | build-packages: | ||
428 | 30 | - intltool | ||
429 | 31 | - libapparmor-dev | ||
430 | 32 | - libevdev-dev | ||
431 | 33 | - libudev-dev | ||
432 | 34 | - lsb-release | ||
433 | 35 | - pkg-config | ||
434 | 36 | - qt5-default | ||
435 | 37 | - qt5-qmake | ||
436 | 38 | - qtbase5-dev | ||
437 | 39 | - qtbase5-dev-tools | ||
438 | 40 | - qtbase5-private-dev | ||
439 | 41 | - qtdeclarative5-dev | ||
440 | 42 | - qttools5-dev-tools | ||
441 | 43 | - xvfb | ||
442 | 44 | stage-packages: | ||
443 | 45 | - fonts-liberation | ||
444 | 46 | - liboxideqt-qmlplugin | ||
445 | 47 | - libqt5sql5-sqlite | ||
446 | 48 | - mir-graphics-drivers-desktop | ||
447 | 49 | - qml-module-qt-labs-folderlistmodel | ||
448 | 50 | - qml-module-qt-labs-settings | ||
449 | 51 | - qml-module-qtquick2 | ||
450 | 52 | - qml-module-qtquick-layouts | ||
451 | 53 | - qml-module-qtquick-window2 | ||
452 | 54 | - qml-module-ubuntu-components | ||
453 | 55 | - qml-module-ubuntu-thumbnailer0.1 | ||
454 | 56 | - qtdeclarative5-ubuntu-content1 | ||
455 | 57 | - qtdeclarative5-ubuntu-download-manager0.1 | ||
456 | 58 | - qtdeclarative5-unity-action-plugin | ||
457 | 59 | - qtubuntu-desktop | ||
458 | 60 | after: [desktop-qt5] | ||
459 | 61 | |||
460 | 62 | launcher: | ||
461 | 63 | plugin: dump | ||
462 | 64 | source: snap | ||
463 | 65 | organize: | ||
464 | 66 | webbrowser-app.launcher: bin/webbrowser-app.launcher | ||
465 | 0 | 67 | ||
466 | === modified file 'src/Ubuntu/CMakeLists.txt' | |||
467 | --- src/Ubuntu/CMakeLists.txt 2016-07-01 13:06:40 +0000 | |||
468 | +++ src/Ubuntu/CMakeLists.txt 2016-10-13 14:41:36 +0000 | |||
469 | @@ -23,13 +23,5 @@ | |||
470 | 23 | OUTPUT_VARIABLE UBUNTU_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE) | 23 | OUTPUT_VARIABLE UBUNTU_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE) |
471 | 24 | add_definitions(-DUBUNTU_VERSION="${UBUNTU_VERSION}") | 24 | add_definitions(-DUBUNTU_VERSION="${UBUNTU_VERSION}") |
472 | 25 | 25 | ||
473 | 26 | execute_process(COMMAND ${XVFB_COMMAND} qmlscene --quit ${CMAKE_CURRENT_SOURCE_DIR}/chromium-version.qml | ||
474 | 27 | OUTPUT_VARIABLE CHROMIUM_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE) | ||
475 | 28 | string(REGEX MATCH "\\[(.*)\\]" _ ${CHROMIUM_VERSION}) | ||
476 | 29 | set(CHROMIUM_VERSION ${CMAKE_MATCH_1}) | ||
477 | 30 | if(NOT CHROMIUM_VERSION MATCHES "^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$") | ||
478 | 31 | message(FATAL_ERROR "Invalid chromium version: '${CHROMIUM_VERSION}'") | ||
479 | 32 | endif() | ||
480 | 33 | |||
481 | 34 | add_subdirectory(Components) | 26 | add_subdirectory(Components) |
482 | 35 | add_subdirectory(Web) | 27 | add_subdirectory(Web) |
483 | 36 | 28 | ||
484 | === modified file 'src/Ubuntu/Web/UbuntuWebContext.qml' | |||
485 | --- src/Ubuntu/Web/UbuntuWebContext.qml 2016-09-13 16:07:06 +0000 | |||
486 | +++ src/Ubuntu/Web/UbuntuWebContext.qml 2016-10-13 14:41:36 +0000 | |||
487 | @@ -91,7 +91,13 @@ | |||
488 | 91 | } | 91 | } |
489 | 92 | if (temp !== null) { | 92 | if (temp !== null) { |
490 | 93 | console.log("Loaded %1 UA override(s) from %2".arg(temp.overrides.length).arg(Qt.resolvedUrl(script))) | 93 | console.log("Loaded %1 UA override(s) from %2".arg(temp.overrides.length).arg(Qt.resolvedUrl(script))) |
492 | 94 | userAgentOverrides = temp.overrides | 94 | var chromiumVersion = Oxide.Oxide.chromiumVersion |
493 | 95 | var overrides = [] | ||
494 | 96 | for (var o in temp.overrides) { | ||
495 | 97 | var override = temp.overrides[o] | ||
496 | 98 | overrides.push([override[0], override[1].replace(/\$\{CHROMIUM_VERSION\}/g, chromiumVersion)]) | ||
497 | 99 | } | ||
498 | 100 | userAgentOverrides = overrides | ||
499 | 95 | temp.destroy() | 101 | temp.destroy() |
500 | 96 | } | 102 | } |
501 | 97 | } | 103 | } |
502 | 98 | 104 | ||
503 | === modified file 'src/Ubuntu/Web/ua-overrides-desktop.js.in' | |||
504 | --- src/Ubuntu/Web/ua-overrides-desktop.js.in 2016-09-21 16:15:10 +0000 | |||
505 | +++ src/Ubuntu/Web/ua-overrides-desktop.js.in 2016-10-13 14:41:36 +0000 | |||
506 | @@ -19,17 +19,17 @@ | |||
507 | 19 | .pragma library | 19 | .pragma library |
508 | 20 | 20 | ||
509 | 21 | var overrides = [ | 21 | var overrides = [ |
515 | 22 | ["^https?:\/\/.+\.google\.com\/calendar", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chromium/@CHROMIUM_VERSION@ Chrome/@CHROMIUM_VERSION@ Safari/537.36"], | 22 | ["^https?:\/\/.+\.google\.com\/calendar", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chromium/${CHROMIUM_VERSION} Chrome/${CHROMIUM_VERSION} Safari/537.36"], |
516 | 23 | ["^http:\/\/chrome\.angrybirds\.com\/", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/@CHROMIUM_VERSION@ Safari/537.36"], // http://pad.lv/1284158 | 23 | ["^http:\/\/chrome\.angrybirds\.com\/", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/${CHROMIUM_VERSION} Safari/537.36"], // http://pad.lv/1284158 |
517 | 24 | ["^https?:\/\/(www\.)?youtube\.com\/", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/@CHROMIUM_VERSION@ Safari/537.36"], // http://pad.lv/1412880 | 24 | ["^https?:\/\/(www\.)?youtube\.com\/", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/${CHROMIUM_VERSION} Safari/537.36"], // http://pad.lv/1412880 |
518 | 25 | ["^https?:\/\/(www\.)?google\..+\/maps", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/@CHROMIUM_VERSION@ Safari/537.36"], // http://pad.lv/1503506, http://pad.lv/1551649 | 25 | ["^https?:\/\/(www\.)?google\..+\/maps", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/${CHROMIUM_VERSION} Safari/537.36"], // http://pad.lv/1503506, http://pad.lv/1551649 |
519 | 26 | ["^https?:\/\/mail\.google\.com\/", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/@CHROMIUM_VERSION@ Safari/537.36"], // http://pad.lv/1452616 | 26 | ["^https?:\/\/mail\.google\.com\/", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/${CHROMIUM_VERSION} Safari/537.36"], // http://pad.lv/1452616 |
520 | 27 | 27 | ||
521 | 28 | // Google hangouts (https://launchpad.net/bugs/1565055) | 28 | // Google hangouts (https://launchpad.net/bugs/1565055) |
525 | 29 | ["^https?:\/\/hangouts\.google\.com\/", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/@CHROMIUM_VERSION@ Safari/537.36"], | 29 | ["^https?:\/\/hangouts\.google\.com\/", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/${CHROMIUM_VERSION} Safari/537.36"], |
526 | 30 | ["^https?:\/\/talkgadget\.google\.com\/hangouts\/", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/@CHROMIUM_VERSION@ Safari/537.36"], | 30 | ["^https?:\/\/talkgadget\.google\.com\/hangouts\/", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/${CHROMIUM_VERSION} Safari/537.36"], |
527 | 31 | ["^https?:\/\/plus\.google\.com\/hangouts\/", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/@CHROMIUM_VERSION@ Safari/537.36"], | 31 | ["^https?:\/\/plus\.google\.com\/hangouts\/", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/${CHROMIUM_VERSION} Safari/537.36"], |
528 | 32 | 32 | ||
529 | 33 | // Google recaptcha (https://launchpad.net/bugs/1599146) | 33 | // Google recaptcha (https://launchpad.net/bugs/1599146) |
531 | 34 | ["^https:\/\/www\.google\.com\/recaptcha\/", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/@CHROMIUM_VERSION@ Safari/537.36"], | 34 | ["^https:\/\/www\.google\.com\/recaptcha\/", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/${CHROMIUM_VERSION} Safari/537.36"], |
532 | 35 | ]; | 35 | ]; |
533 | 36 | 36 | ||
534 | === modified file 'src/Ubuntu/Web/ua-overrides-mobile.js.in' | |||
535 | --- src/Ubuntu/Web/ua-overrides-mobile.js.in 2016-08-19 10:10:12 +0000 | |||
536 | +++ src/Ubuntu/Web/ua-overrides-mobile.js.in 2016-10-13 14:41:36 +0000 | |||
537 | @@ -19,18 +19,18 @@ | |||
538 | 19 | .pragma library | 19 | .pragma library |
539 | 20 | 20 | ||
540 | 21 | var overrides = [ | 21 | var overrides = [ |
548 | 22 | ["^https?:\/\/mail\.google\.com\/", "Mozilla/5.0 (Linux; Android 5.0;) AppleWebKit/537.36 Chrome/@CHROMIUM_VERSION@ Mobile Safari/537.36"], // http://pad.lv/1375889 | 22 | ["^https?:\/\/mail\.google\.com\/", "Mozilla/5.0 (Linux; Android 5.0;) AppleWebKit/537.36 Chrome/${CHROMIUM_VERSION} Mobile Safari/537.36"], // http://pad.lv/1375889 |
549 | 23 | ["^https?:\/\/(www|m)\.youtube\.com\/", "Mozilla/5.0 (Linux; Android 5.0;) AppleWebKit/537.36 Chrome/@CHROMIUM_VERSION@ Mobile Safari/537.36"], // http://pad.lv/1228415, http://pad.lv/1415107, http://pad.lv/1417258, http://pad.lv/1499394, http://pad.lv/1408760, http://pad.lv/1437485 | 23 | ["^https?:\/\/(www|m)\.youtube\.com\/", "Mozilla/5.0 (Linux; Android 5.0;) AppleWebKit/537.36 Chrome/${CHROMIUM_VERSION} Mobile Safari/537.36"], // http://pad.lv/1228415, http://pad.lv/1415107, http://pad.lv/1417258, http://pad.lv/1499394, http://pad.lv/1408760, http://pad.lv/1437485 |
550 | 24 | ["^http:\/\/chrome\.angrybirds\.com\/", "Mozilla/5.0 (Linux; Ubuntu @UBUNTU_VERSION@ like Android 4.4;) AppleWebKit/537.36 Chrome/@CHROMIUM_VERSION@ Mobile Safari/537.36"], // http://pad.lv/1284158 | 24 | ["^http:\/\/chrome\.angrybirds\.com\/", "Mozilla/5.0 (Linux; Ubuntu @UBUNTU_VERSION@ like Android 4.4;) AppleWebKit/537.36 Chrome/${CHROMIUM_VERSION} Mobile Safari/537.36"], // http://pad.lv/1284158 |
551 | 25 | ["^https?:\/\/(\w+\.)*hsbc\.com\.br\/", "Mozilla/5.0 (Linux; Ubuntu @UBUNTU_VERSION@ like Android 4.4;) AppleWebKit/537.36 Chrome/@CHROMIUM_VERSION@ Mobile Safari/537.36"], // http://pad.lv/1380657 | 25 | ["^https?:\/\/(\w+\.)*hsbc\.com\.br\/", "Mozilla/5.0 (Linux; Ubuntu @UBUNTU_VERSION@ like Android 4.4;) AppleWebKit/537.36 Chrome/${CHROMIUM_VERSION} Mobile Safari/537.36"], // http://pad.lv/1380657 |
552 | 26 | ["^http:\/\/(\w+\.)*espn\.(go\.)?com\/", "Mozilla/5.0 (Linux; Ubuntu @UBUNTU_VERSION@ like Android 4.4;) AppleWebKit/537.36 Chrome/@CHROMIUM_VERSION@ Mobile Safari/537.36"], // http://pad.lv/1316259 | 26 | ["^http:\/\/(\w+\.)*espn\.(go\.)?com\/", "Mozilla/5.0 (Linux; Ubuntu @UBUNTU_VERSION@ like Android 4.4;) AppleWebKit/537.36 Chrome/${CHROMIUM_VERSION} Mobile Safari/537.36"], // http://pad.lv/1316259 |
553 | 27 | ["^https?:\/\/(www|m)\.facebook\.com\/", "Mozilla/5.0 (Linux; Ubuntu @UBUNTU_VERSION@; Android 5.0; Nexus 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/@CHROMIUM_VERSION@ Mobile Safari/537.36"], // http://pad.lv/1538056, http://pad.lv/1457661 | 27 | ["^https?:\/\/(www|m)\.facebook\.com\/", "Mozilla/5.0 (Linux; Ubuntu @UBUNTU_VERSION@; Android 5.0; Nexus 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${CHROMIUM_VERSION} Mobile Safari/537.36"], // http://pad.lv/1538056, http://pad.lv/1457661 |
554 | 28 | ["^https?:\/\/(mobile\.)?nytimes\.com\/", "Mozilla/5.0 (Linux; Ubuntu @UBUNTU_VERSION@; Android 5.0; Nexus 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/@CHROMIUM_VERSION@ Mobile Safari/537.36"], // http://pad.lv/1573620 | 28 | ["^https?:\/\/(mobile\.)?nytimes\.com\/", "Mozilla/5.0 (Linux; Ubuntu @UBUNTU_VERSION@; Android 5.0; Nexus 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${CHROMIUM_VERSION} Mobile Safari/537.36"], // http://pad.lv/1573620 |
555 | 29 | // Google hangouts (https://launchpad.net/bugs/1565055) | 29 | // Google hangouts (https://launchpad.net/bugs/1565055) |
559 | 30 | ["^https?:\/\/hangouts\.google\.com\/", "Mozilla/5.0 (Linux; Ubuntu @UBUNTU_VERSION@ like Android 4.4;) AppleWebKit/537.36 Chrome/@CHROMIUM_VERSION@ Mobile Safari/537.36"], | 30 | ["^https?:\/\/hangouts\.google\.com\/", "Mozilla/5.0 (Linux; Ubuntu @UBUNTU_VERSION@ like Android 4.4;) AppleWebKit/537.36 Chrome/${CHROMIUM_VERSION} Mobile Safari/537.36"], |
560 | 31 | ["^https?:\/\/talkgadget\.google\.com\/hangouts\/", "Mozilla/5.0 (Linux; Ubuntu @UBUNTU_VERSION@ like Android 4.4;) AppleWebKit/537.36 Chrome/@CHROMIUM_VERSION@ Mobile Safari/537.36"], | 31 | ["^https?:\/\/talkgadget\.google\.com\/hangouts\/", "Mozilla/5.0 (Linux; Ubuntu @UBUNTU_VERSION@ like Android 4.4;) AppleWebKit/537.36 Chrome/${CHROMIUM_VERSION} Mobile Safari/537.36"], |
561 | 32 | ["^https?:\/\/plus\.google\.com\/hangouts\/", "Mozilla/5.0 (Linux; Ubuntu @UBUNTU_VERSION@ like Android 4.4;) AppleWebKit/537.36 Chrome/@CHROMIUM_VERSION@ Mobile Safari/537.36"], | 32 | ["^https?:\/\/plus\.google\.com\/hangouts\/", "Mozilla/5.0 (Linux; Ubuntu @UBUNTU_VERSION@ like Android 4.4;) AppleWebKit/537.36 Chrome/${CHROMIUM_VERSION} Mobile Safari/537.36"], |
562 | 33 | 33 | ||
563 | 34 | // Google recaptcha (https://launchpad.net/bugs/1599146) | 34 | // Google recaptcha (https://launchpad.net/bugs/1599146) |
565 | 35 | ["^https:\/\/www\.google\.com\/recaptcha\/", "Mozilla/5.0 (Linux; Ubuntu @UBUNTU_VERSION@ like Android 4.4;) AppleWebKit/537.36 Chrome/@CHROMIUM_VERSION@ Safari/537.36"], | 35 | ["^https:\/\/www\.google\.com\/recaptcha\/", "Mozilla/5.0 (Linux; Ubuntu @UBUNTU_VERSION@ like Android 4.4;) AppleWebKit/537.36 Chrome/${CHROMIUM_VERSION} Safari/537.36"], |
566 | 36 | ]; | 36 | ]; |
567 | 37 | 37 | ||
568 | === modified file 'src/app/AlertDialog.qml' | |||
569 | --- src/app/AlertDialog.qml 2016-05-23 13:25:46 +0000 | |||
570 | +++ src/app/AlertDialog.qml 2016-10-13 14:41:36 +0000 | |||
571 | @@ -20,11 +20,13 @@ | |||
572 | 20 | import Ubuntu.Components 1.3 | 20 | import Ubuntu.Components 1.3 |
573 | 21 | 21 | ||
574 | 22 | ModalDialog { | 22 | ModalDialog { |
575 | 23 | objectName: "alertDialog" | ||
576 | 23 | title: i18n.tr("JavaScript Alert") | 24 | title: i18n.tr("JavaScript Alert") |
577 | 24 | 25 | ||
578 | 25 | Button { | 26 | Button { |
579 | 26 | text: i18n.tr("OK") | 27 | text: i18n.tr("OK") |
580 | 27 | color: theme.palette.normal.positive | 28 | color: theme.palette.normal.positive |
581 | 29 | objectName: "okButton" | ||
582 | 28 | onClicked: model.accept() | 30 | onClicked: model.accept() |
583 | 29 | } | 31 | } |
584 | 30 | } | 32 | } |
585 | 31 | 33 | ||
586 | === modified file 'src/app/BeforeUnloadDialog.qml' | |||
587 | --- src/app/BeforeUnloadDialog.qml 2016-05-23 13:25:46 +0000 | |||
588 | +++ src/app/BeforeUnloadDialog.qml 2016-10-13 14:41:36 +0000 | |||
589 | @@ -20,15 +20,18 @@ | |||
590 | 20 | import Ubuntu.Components 1.3 | 20 | import Ubuntu.Components 1.3 |
591 | 21 | 21 | ||
592 | 22 | ModalDialog { | 22 | ModalDialog { |
593 | 23 | objectName: "beforeUnloadDialog" | ||
594 | 23 | title: i18n.tr("Confirm Navigation") | 24 | title: i18n.tr("Confirm Navigation") |
595 | 24 | 25 | ||
596 | 25 | Button { | 26 | Button { |
597 | 26 | text: i18n.tr("Leave") | 27 | text: i18n.tr("Leave") |
598 | 27 | color: theme.palette.normal.negative | 28 | color: theme.palette.normal.negative |
599 | 29 | objectName: "leaveButton" | ||
600 | 28 | onClicked: model.accept() | 30 | onClicked: model.accept() |
601 | 29 | } | 31 | } |
602 | 30 | 32 | ||
603 | 31 | Button { | 33 | Button { |
604 | 34 | objectName: "stayButton" | ||
605 | 32 | text: i18n.tr("Stay") | 35 | text: i18n.tr("Stay") |
606 | 33 | onClicked: model.reject() | 36 | onClicked: model.reject() |
607 | 34 | } | 37 | } |
608 | 35 | 38 | ||
609 | === modified file 'src/app/ChromeBase.qml' | |||
610 | --- src/app/ChromeBase.qml 2015-12-02 18:01:18 +0000 | |||
611 | +++ src/app/ChromeBase.qml 2016-10-13 14:41:36 +0000 | |||
612 | @@ -1,5 +1,5 @@ | |||
613 | 1 | /* | 1 | /* |
615 | 2 | * Copyright 2014-2015 Canonical Ltd. | 2 | * Copyright 2014-2016 Canonical Ltd. |
616 | 3 | * | 3 | * |
617 | 4 | * This file is part of webbrowser-app. | 4 | * This file is part of webbrowser-app. |
618 | 5 | * | 5 | * |
619 | @@ -25,9 +25,11 @@ | |||
620 | 25 | 25 | ||
621 | 26 | objectName: "chromeBase" | 26 | objectName: "chromeBase" |
622 | 27 | 27 | ||
623 | 28 | property var webview | ||
624 | 29 | property alias backgroundColor: backgroundRect.color | 28 | property alias backgroundColor: backgroundRect.color |
625 | 30 | 29 | ||
626 | 30 | property alias loading: progressBar.visible | ||
627 | 31 | property alias loadProgress: progressBar.value | ||
628 | 32 | |||
629 | 31 | states: [ | 33 | states: [ |
630 | 32 | State { | 34 | State { |
631 | 33 | name: "shown" | 35 | name: "shown" |
632 | @@ -53,7 +55,7 @@ | |||
633 | 53 | } | 55 | } |
634 | 54 | 56 | ||
635 | 55 | ThinProgressBar { | 57 | ThinProgressBar { |
637 | 56 | webview: chrome.webview | 58 | id: progressBar |
638 | 57 | 59 | ||
639 | 58 | anchors { | 60 | anchors { |
640 | 59 | left: parent.left | 61 | left: parent.left |
641 | 60 | 62 | ||
642 | === modified file 'src/app/ConfirmDialog.qml' | |||
643 | --- src/app/ConfirmDialog.qml 2016-05-23 13:25:46 +0000 | |||
644 | +++ src/app/ConfirmDialog.qml 2016-10-13 14:41:36 +0000 | |||
645 | @@ -20,15 +20,18 @@ | |||
646 | 20 | import Ubuntu.Components 1.3 | 20 | import Ubuntu.Components 1.3 |
647 | 21 | 21 | ||
648 | 22 | ModalDialog { | 22 | ModalDialog { |
649 | 23 | objectName: "confirmDialog" | ||
650 | 23 | title: i18n.tr("JavaScript Confirmation") | 24 | title: i18n.tr("JavaScript Confirmation") |
651 | 24 | 25 | ||
652 | 25 | Button { | 26 | Button { |
653 | 26 | text: i18n.tr("OK") | 27 | text: i18n.tr("OK") |
654 | 27 | color: theme.palette.normal.positive | 28 | color: theme.palette.normal.positive |
655 | 29 | objectName: "okButton" | ||
656 | 28 | onClicked: model.accept() | 30 | onClicked: model.accept() |
657 | 29 | } | 31 | } |
658 | 30 | 32 | ||
659 | 31 | Button { | 33 | Button { |
660 | 34 | objectName: "cancelButton" | ||
661 | 32 | text: i18n.tr("Cancel") | 35 | text: i18n.tr("Cancel") |
662 | 33 | onClicked: model.reject() | 36 | onClicked: model.reject() |
663 | 34 | } | 37 | } |
664 | 35 | 38 | ||
665 | === modified file 'src/app/Downloader.qml' | |||
666 | --- src/app/Downloader.qml 2016-01-12 14:50:27 +0000 | |||
667 | +++ src/app/Downloader.qml 2016-10-13 14:41:36 +0000 | |||
668 | @@ -34,9 +34,7 @@ | |||
669 | 34 | 34 | ||
670 | 35 | Component { | 35 | Component { |
671 | 36 | id: metadataComponent | 36 | id: metadataComponent |
675 | 37 | Metadata { | 37 | Metadata {} |
673 | 38 | showInIndicator: true | ||
674 | 39 | } | ||
676 | 40 | } | 38 | } |
677 | 41 | 39 | ||
678 | 42 | Component { | 40 | Component { |
679 | @@ -62,14 +60,8 @@ | |||
680 | 62 | singleDownload.download(url) | 60 | singleDownload.download(url) |
681 | 63 | } | 61 | } |
682 | 64 | 62 | ||
691 | 65 | function downloadPicture(url, headers) { | 63 | function downloadMimeType(url, mimeType, headers, filename, incognito) { |
692 | 66 | var metadata = metadataComponent.createObject(downloadItem) | 64 | var metadata = metadataComponent.createObject(downloadItem, {"showInIndicator": !incognito}) |
685 | 67 | downloadItem.mimeType = "image/*" | ||
686 | 68 | download(url, ContentType.Pictures, headers, metadata) | ||
687 | 69 | } | ||
688 | 70 | |||
689 | 71 | function downloadMimeType(url, mimeType, headers, filename) { | ||
690 | 72 | var metadata = metadataComponent.createObject(downloadItem) | ||
693 | 73 | var contentType = MimeTypeMapper.mimeTypeToContentType(mimeType) | 65 | var contentType = MimeTypeMapper.mimeTypeToContentType(mimeType) |
694 | 74 | if (contentType == ContentType.Unknown && filename) { | 66 | if (contentType == ContentType.Unknown && filename) { |
695 | 75 | // If we can't determine the content type from the mime-type | 67 | // If we can't determine the content type from the mime-type |
696 | 76 | 68 | ||
697 | === modified file 'src/app/PromptDialog.qml' | |||
698 | --- src/app/PromptDialog.qml 2016-05-23 13:25:46 +0000 | |||
699 | +++ src/app/PromptDialog.qml 2016-10-13 14:41:36 +0000 | |||
700 | @@ -20,10 +20,12 @@ | |||
701 | 20 | import Ubuntu.Components 1.3 | 20 | import Ubuntu.Components 1.3 |
702 | 21 | 21 | ||
703 | 22 | ModalDialog { | 22 | ModalDialog { |
704 | 23 | objectName: "promptDialog" | ||
705 | 23 | title: i18n.tr("JavaScript Prompt") | 24 | title: i18n.tr("JavaScript Prompt") |
706 | 24 | 25 | ||
707 | 25 | TextField { | 26 | TextField { |
708 | 26 | id: input | 27 | id: input |
709 | 28 | objectName: "inputTextField" | ||
710 | 27 | text: model.defaultValue | 29 | text: model.defaultValue |
711 | 28 | onAccepted: model.accept(input.text) | 30 | onAccepted: model.accept(input.text) |
712 | 29 | } | 31 | } |
713 | @@ -31,10 +33,12 @@ | |||
714 | 31 | Button { | 33 | Button { |
715 | 32 | text: i18n.tr("OK") | 34 | text: i18n.tr("OK") |
716 | 33 | color: theme.palette.normal.positive | 35 | color: theme.palette.normal.positive |
717 | 36 | objectName: "okButton" | ||
718 | 34 | onClicked: model.accept(input.text) | 37 | onClicked: model.accept(input.text) |
719 | 35 | } | 38 | } |
720 | 36 | 39 | ||
721 | 37 | Button { | 40 | Button { |
722 | 41 | objectName: "cancelButton" | ||
723 | 38 | text: i18n.tr("Cancel") | 42 | text: i18n.tr("Cancel") |
724 | 39 | onClicked: model.reject() | 43 | onClicked: model.reject() |
725 | 40 | } | 44 | } |
726 | 41 | 45 | ||
727 | === modified file 'src/app/ThinProgressBar.qml' | |||
728 | --- src/app/ThinProgressBar.qml 2015-08-10 15:22:00 +0000 | |||
729 | +++ src/app/ThinProgressBar.qml 2016-10-13 14:41:36 +0000 | |||
730 | @@ -1,5 +1,5 @@ | |||
731 | 1 | /* | 1 | /* |
733 | 2 | * Copyright 2014-2015 Canonical Ltd. | 2 | * Copyright 2014-2016 Canonical Ltd. |
734 | 3 | * | 3 | * |
735 | 4 | * This file is part of webbrowser-app. | 4 | * This file is part of webbrowser-app. |
736 | 5 | * | 5 | * |
737 | @@ -20,11 +20,8 @@ | |||
738 | 20 | import Ubuntu.Components 1.3 | 20 | import Ubuntu.Components 1.3 |
739 | 21 | 21 | ||
740 | 22 | ProgressBar { | 22 | ProgressBar { |
741 | 23 | property var webview | ||
742 | 24 | |||
743 | 25 | height: units.dp(3) | 23 | height: units.dp(3) |
744 | 26 | |||
745 | 27 | showProgressPercentage: false | 24 | showProgressPercentage: false |
748 | 28 | value: webview ? webview.loadProgress / 100 : 0.0 | 25 | minimumValue: 0 |
749 | 29 | visible: webview ? webview.loading : false | 26 | maximumValue: 100 |
750 | 30 | } | 27 | } |
751 | 31 | 28 | ||
752 | === modified file 'src/app/WebViewImpl.qml' | |||
753 | --- src/app/WebViewImpl.qml 2015-12-15 12:37:34 +0000 | |||
754 | +++ src/app/WebViewImpl.qml 2016-10-13 14:41:36 +0000 | |||
755 | @@ -1,5 +1,5 @@ | |||
756 | 1 | /* | 1 | /* |
758 | 2 | * Copyright 2013-2015 Canonical Ltd. | 2 | * Copyright 2013-2016 Canonical Ltd. |
759 | 3 | * | 3 | * |
760 | 4 | * This file is part of webbrowser-app. | 4 | * This file is part of webbrowser-app. |
761 | 5 | * | 5 | * |
762 | @@ -76,7 +76,7 @@ | |||
763 | 76 | mimeType = MimeDatabase.filenameToMimeType(filename) | 76 | mimeType = MimeDatabase.filenameToMimeType(filename) |
764 | 77 | } | 77 | } |
765 | 78 | } | 78 | } |
767 | 79 | downloadLoader.item.downloadMimeType(request.url, mimeType, headers, request.suggestedFilename) | 79 | downloadLoader.item.downloadMimeType(request.url, mimeType, headers, request.suggestedFilename, incognito) |
768 | 80 | } else { | 80 | } else { |
769 | 81 | // Desktop form factor case | 81 | // Desktop form factor case |
770 | 82 | Qt.openUrlExternally(request.url) | 82 | Qt.openUrlExternally(request.url) |
771 | 83 | 83 | ||
772 | === modified file 'src/app/config.h.in' | |||
773 | --- src/app/config.h.in 2016-05-26 17:00:55 +0000 | |||
774 | +++ src/app/config.h.in 2016-10-13 14:41:36 +0000 | |||
775 | @@ -22,19 +22,20 @@ | |||
776 | 22 | #include <QtCore/QCoreApplication> | 22 | #include <QtCore/QCoreApplication> |
777 | 23 | #include <QtCore/QDir> | 23 | #include <QtCore/QDir> |
778 | 24 | #include <QtCore/QString> | 24 | #include <QtCore/QString> |
779 | 25 | #include <QtCore/QtGlobal> | ||
780 | 25 | 26 | ||
781 | 26 | #define REMOTE_INSPECTOR_PORT 9221 | 27 | #define REMOTE_INSPECTOR_PORT 9221 |
782 | 27 | 28 | ||
783 | 28 | inline bool isRunningInstalled() | 29 | inline bool isRunningInstalled() |
784 | 29 | { | 30 | { |
786 | 30 | static bool installed = (QCoreApplication::applicationDirPath() == QDir("@CMAKE_INSTALL_FULL_BINDIR@").canonicalPath()); | 31 | static bool installed = (QCoreApplication::applicationDirPath() == QDir(qgetenv("SNAP").append("@CMAKE_INSTALL_FULL_BINDIR@")).canonicalPath()); |
787 | 31 | return installed; | 32 | return installed; |
788 | 32 | } | 33 | } |
789 | 33 | 34 | ||
790 | 34 | inline QString UbuntuBrowserDirectory() | 35 | inline QString UbuntuBrowserDirectory() |
791 | 35 | { | 36 | { |
792 | 36 | if (isRunningInstalled()) { | 37 | if (isRunningInstalled()) { |
794 | 37 | return QStringLiteral("@CMAKE_INSTALL_FULL_DATADIR@/webbrowser-app"); | 38 | return qgetenv("SNAP").append("@CMAKE_INSTALL_FULL_DATADIR@/webbrowser-app"); |
795 | 38 | } else { | 39 | } else { |
796 | 39 | return QStringLiteral("@CMAKE_SOURCE_DIR@/src/app"); | 40 | return QStringLiteral("@CMAKE_SOURCE_DIR@/src/app"); |
797 | 40 | } | 41 | } |
798 | 41 | 42 | ||
799 | === modified file 'src/app/webbrowser/BookmarkOptions.qml' | |||
800 | --- src/app/webbrowser/BookmarkOptions.qml 2016-05-23 02:52:50 +0000 | |||
801 | +++ src/app/webbrowser/BookmarkOptions.qml 2016-10-13 14:41:36 +0000 | |||
802 | @@ -19,18 +19,36 @@ | |||
803 | 19 | import QtQuick 2.4 | 19 | import QtQuick 2.4 |
804 | 20 | import Ubuntu.Components 1.3 | 20 | import Ubuntu.Components 1.3 |
805 | 21 | import Ubuntu.Components.Popups 1.3 | 21 | import Ubuntu.Components.Popups 1.3 |
806 | 22 | import webbrowserapp.private 0.1 | ||
807 | 22 | 23 | ||
808 | 23 | Popover { | 24 | Popover { |
809 | 24 | id: bookmarkOptions | 25 | id: bookmarkOptions |
810 | 25 | 26 | ||
811 | 26 | property url bookmarkUrl | 27 | property url bookmarkUrl |
812 | 27 | property alias bookmarkTitle: titleTextField.text | 28 | property alias bookmarkTitle: titleTextField.text |
813 | 28 | property alias folderModel: folderOptionSelector.model | ||
814 | 29 | 29 | ||
816 | 30 | readonly property string bookmarkFolder: folderModel.get(folderOptionSelector.selectedIndex).folder | 30 | readonly property string bookmarkFolder: folderOptionSelector.model.get(folderOptionSelector.selectedIndex).folder |
817 | 31 | 31 | ||
818 | 32 | contentHeight: bookmarkOptionsColumn.childrenRect.height + units.gu(2) | 32 | contentHeight: bookmarkOptionsColumn.childrenRect.height + units.gu(2) |
819 | 33 | 33 | ||
820 | 34 | onVisibleChanged: { | ||
821 | 35 | if (!visible) { | ||
822 | 36 | BookmarksModel.remove(bookmarkUrl) | ||
823 | 37 | } | ||
824 | 38 | } | ||
825 | 39 | |||
826 | 40 | Component.onDestruction: { | ||
827 | 41 | if (BookmarksModel.contains(bookmarkUrl)) { | ||
828 | 42 | BookmarksModel.update(bookmarkUrl, bookmarkTitle, bookmarkFolder) | ||
829 | 43 | } | ||
830 | 44 | } | ||
831 | 45 | |||
832 | 46 | // Fragile workaround for https://launchpad.net/bugs/1546677. | ||
833 | 47 | // By destroying the popover, its visibility isn’t changed to | ||
834 | 48 | // false, and thus the bookmark is not removed. | ||
835 | 49 | Keys.onEnterPressed: bookmarkOptions.destroy() | ||
836 | 50 | Keys.onReturnPressed: bookmarkOptions.destroy() | ||
837 | 51 | |||
838 | 34 | Column { | 52 | Column { |
839 | 35 | id: bookmarkOptionsColumn | 53 | id: bookmarkOptionsColumn |
840 | 36 | 54 | ||
841 | @@ -77,6 +95,9 @@ | |||
842 | 77 | 95 | ||
843 | 78 | delegate: OptionSelectorDelegate { text: folder === "" ? i18n.tr("All Bookmarks") : folder } | 96 | delegate: OptionSelectorDelegate { text: folder === "" ? i18n.tr("All Bookmarks") : folder } |
844 | 79 | containerHeight: itemHeight * 3 | 97 | containerHeight: itemHeight * 3 |
845 | 98 | model: BookmarksFolderListModel { | ||
846 | 99 | sourceModel: BookmarksModel | ||
847 | 100 | } | ||
848 | 80 | } | 101 | } |
849 | 81 | 102 | ||
850 | 82 | Item { | 103 | Item { |
851 | @@ -120,8 +141,8 @@ | |||
852 | 120 | 141 | ||
853 | 121 | function createNewFolder(folder) { | 142 | function createNewFolder(folder) { |
854 | 122 | Qt.inputMethod.hide() | 143 | Qt.inputMethod.hide() |
857 | 123 | folderModel.createNewFolder(folder) | 144 | folderOptionSelector.model.createNewFolder(folder) |
858 | 124 | folderOptionSelector.selectedIndex = folderModel.indexOf(folder) | 145 | folderOptionSelector.selectedIndex = folderOptionSelector.model.indexOf(folder) |
859 | 125 | folderOptionSelector.currentlyExpanded = false | 146 | folderOptionSelector.currentlyExpanded = false |
860 | 126 | PopupUtils.close(dialogue) | 147 | PopupUtils.close(dialogue) |
861 | 127 | } | 148 | } |
862 | 128 | 149 | ||
863 | === modified file 'src/app/webbrowser/Browser.qml' | |||
864 | --- src/app/webbrowser/Browser.qml 2016-09-28 08:24:06 +0000 | |||
865 | +++ src/app/webbrowser/Browser.qml 2016-10-13 14:41:36 +0000 | |||
866 | @@ -88,7 +88,7 @@ | |||
867 | 88 | tabs will have their mediaAccessPermissionRequested signal handled by | 88 | tabs will have their mediaAccessPermissionRequested signal handled by |
868 | 89 | creating one of these new dialogs. | 89 | creating one of these new dialogs. |
869 | 90 | */ | 90 | */ |
871 | 91 | onMediaAccessPermissionRequested: PopupUtils.open(mediaAccessDialogComponent, null, { request: request }) | 91 | onMediaAccessPermissionRequested: PopupUtils.open(Qt.resolvedUrl("../MediaAccessDialog.qml"), null, { request: request }) |
872 | 92 | } | 92 | } |
873 | 93 | 93 | ||
874 | 94 | currentWebcontext: SharedWebContext.sharedContext | 94 | currentWebcontext: SharedWebContext.sharedContext |
875 | @@ -119,11 +119,6 @@ | |||
876 | 119 | id: keyboardModel | 119 | id: keyboardModel |
877 | 120 | } | 120 | } |
878 | 121 | 121 | ||
879 | 122 | Component { | ||
880 | 123 | id: mediaAccessDialogComponent | ||
881 | 124 | MediaAccessDialog {} | ||
882 | 125 | } | ||
883 | 126 | |||
884 | 127 | actions: [ | 122 | actions: [ |
885 | 128 | Actions.GoTo { | 123 | Actions.GoTo { |
886 | 129 | onTriggered: currentWebview.url = value | 124 | onTriggered: currentWebview.url = value |
887 | @@ -200,12 +195,16 @@ | |||
888 | 200 | fill: tabContainer | 195 | fill: tabContainer |
889 | 201 | topMargin: (chrome.state == "shown") ? chrome.height : 0 | 196 | topMargin: (chrome.state == "shown") ? chrome.height : 0 |
890 | 202 | } | 197 | } |
894 | 203 | sourceComponent: ErrorSheet { | 198 | Component.onCompleted: setSource("../ErrorSheet.qml", { |
895 | 204 | visible: currentWebview ? currentWebview.lastLoadFailed : false | 199 | "visible": Qt.binding(function(){ return currentWebview ? currentWebview.lastLoadFailed : false }), |
896 | 205 | url: currentWebview ? currentWebview.url : "" | 200 | "url": Qt.binding(function(){ return currentWebview ? currentWebview.url : "" }) |
897 | 201 | }) | ||
898 | 202 | Connections { | ||
899 | 203 | target: errorSheetLoader.item | ||
900 | 206 | onRefreshClicked: currentWebview.reload() | 204 | onRefreshClicked: currentWebview.reload() |
901 | 207 | } | 205 | } |
903 | 208 | focus: item.visible | 206 | |
904 | 207 | focus: item && item.visible | ||
905 | 209 | asynchronous: true | 208 | asynchronous: true |
906 | 210 | } | 209 | } |
907 | 211 | 210 | ||
908 | @@ -215,9 +214,12 @@ | |||
909 | 215 | fill: tabContainer | 214 | fill: tabContainer |
910 | 216 | topMargin: (chrome.state == "shown") ? chrome.height : 0 | 215 | topMargin: (chrome.state == "shown") ? chrome.height : 0 |
911 | 217 | } | 216 | } |
915 | 218 | sourceComponent: InvalidCertificateErrorSheet { | 217 | Component.onCompleted: setSource("../InvalidCertificateErrorSheet.qml", { |
916 | 219 | visible: currentWebview && currentWebview.certificateError != null | 218 | "visible": Qt.binding(function(){ return currentWebview && currentWebview.certificateError != null }), |
917 | 220 | certificateError: currentWebview ? currentWebview.certificateError : null | 219 | "certificateError": Qt.binding(function(){ return currentWebview ? currentWebview.certificateError : null }) |
918 | 220 | }) | ||
919 | 221 | Connections { | ||
920 | 222 | target: invalidCertificateErrorSheetLoader.item | ||
921 | 221 | onAllowed: { | 223 | onAllowed: { |
922 | 222 | // Automatically allow future requests involving this | 224 | // Automatically allow future requests involving this |
923 | 223 | // certificate for the duration of the session. | 225 | // certificate for the duration of the session. |
924 | @@ -228,7 +230,7 @@ | |||
925 | 228 | currentWebview.resetCertificateError() | 230 | currentWebview.resetCertificateError() |
926 | 229 | } | 231 | } |
927 | 230 | } | 232 | } |
929 | 231 | focus: item.visible | 233 | focus: item && item.visible |
930 | 232 | asynchronous: true | 234 | asynchronous: true |
931 | 233 | } | 235 | } |
932 | 234 | 236 | ||
933 | @@ -262,60 +264,35 @@ | |||
934 | 262 | newTabViewLoader.active = !tab.url.toString() && !tab.restoreState | 264 | newTabViewLoader.active = !tab.url.toString() && !tab.restoreState |
935 | 263 | } | 265 | } |
936 | 264 | } | 266 | } |
991 | 265 | } | 267 | onWideChanged: newTabViewLoader.selectTabView() |
992 | 266 | 268 | } | |
993 | 267 | sourceComponent: browser.incognito ? newPrivateTabView : | 269 | Component.onCompleted: newTabViewLoader.selectTabView() |
994 | 268 | (browser.wide ? newTabViewWide : newTabView) | 270 | |
995 | 269 | 271 | function selectTabView() { | |
996 | 270 | Component { | 272 | var source = browser.incognito ? "NewPrivateTabView.qml" : |
997 | 271 | id: newTabView | 273 | (browser.wide ? "NewTabViewWide.qml" : |
998 | 272 | 274 | "NewTabView.qml"); | |
999 | 273 | NewTabView { | 275 | var properties = browser.incognito ? {} : {"settingsObject": settings, |
1000 | 274 | anchors.fill: parent | 276 | "focus": true}; |
1001 | 275 | settingsObject: settings | 277 | |
1002 | 276 | focus: true | 278 | newTabViewLoader.setSource(source, properties); |
1003 | 277 | onBookmarkClicked: { | 279 | } |
1004 | 278 | chrome.requestedUrl = url | 280 | |
1005 | 279 | currentWebview.url = url | 281 | Connections { |
1006 | 280 | tabContainer.forceActiveFocus() | 282 | target: newTabViewLoader.item && !browser.incognito ? newTabViewLoader.item : null |
1007 | 281 | } | 283 | onBookmarkClicked: { |
1008 | 282 | onBookmarkRemoved: BookmarksModel.remove(url) | 284 | chrome.requestedUrl = url |
1009 | 283 | onHistoryEntryClicked: { | 285 | currentWebview.url = url |
1010 | 284 | chrome.requestedUrl = url | 286 | tabContainer.forceActiveFocus() |
1011 | 285 | currentWebview.url = url | 287 | } |
1012 | 286 | tabContainer.forceActiveFocus() | 288 | onBookmarkRemoved: BookmarksModel.remove(url) |
1013 | 287 | } | 289 | onHistoryEntryClicked: { |
1014 | 288 | Keys.onUpPressed: chrome.focus = true | 290 | chrome.requestedUrl = url |
1015 | 289 | } | 291 | currentWebview.url = url |
1016 | 290 | } | 292 | tabContainer.forceActiveFocus() |
1017 | 291 | 293 | } | |
1018 | 292 | Component { | 294 | } |
1019 | 293 | id: newTabViewWide | 295 | Keys.onUpPressed: chrome.focus = true |
966 | 294 | |||
967 | 295 | NewTabViewWide { | ||
968 | 296 | anchors.fill: parent | ||
969 | 297 | settingsObject: settings | ||
970 | 298 | focus: true | ||
971 | 299 | onBookmarkClicked: { | ||
972 | 300 | chrome.requestedUrl = url | ||
973 | 301 | currentWebview.url = url | ||
974 | 302 | tabContainer.forceActiveFocus() | ||
975 | 303 | } | ||
976 | 304 | onBookmarkRemoved: BookmarksModel.remove(url) | ||
977 | 305 | onHistoryEntryClicked: { | ||
978 | 306 | chrome.requestedUrl = url | ||
979 | 307 | currentWebview.url = url | ||
980 | 308 | tabContainer.forceActiveFocus() | ||
981 | 309 | } | ||
982 | 310 | Keys.onUpPressed: chrome.focus = true | ||
983 | 311 | } | ||
984 | 312 | } | ||
985 | 313 | |||
986 | 314 | Component { | ||
987 | 315 | id: newPrivateTabView | ||
988 | 316 | |||
989 | 317 | NewPrivateTabView { anchors.fill: parent } | ||
990 | 318 | } | ||
1020 | 319 | } | 296 | } |
1021 | 320 | 297 | ||
1022 | 321 | Loader { | 298 | Loader { |
1023 | @@ -328,8 +305,11 @@ | |||
1024 | 328 | active: webProcessMonitor.crashed || (webProcessMonitor.killed && !currentWebview.loading) | 305 | active: webProcessMonitor.crashed || (webProcessMonitor.killed && !currentWebview.loading) |
1025 | 329 | focus: active | 306 | focus: active |
1026 | 330 | 307 | ||
1029 | 331 | sourceComponent: SadTab { | 308 | Component.onCompleted: setSource("SadTab.qml", { |
1030 | 332 | webview: currentWebview | 309 | "webview": Qt.binding(function () {return browser.currentWebview}) |
1031 | 310 | }) | ||
1032 | 311 | Connections { | ||
1033 | 312 | target: sadTabLoader.item | ||
1034 | 333 | onCloseTabRequested: internal.closeCurrentTab() | 313 | onCloseTabRequested: internal.closeCurrentTab() |
1035 | 334 | } | 314 | } |
1036 | 335 | 315 | ||
1037 | @@ -477,7 +457,6 @@ | |||
1038 | 477 | id: chrome | 457 | id: chrome |
1039 | 478 | 458 | ||
1040 | 479 | tab: internal.nextTab || tabsModel.currentTab | 459 | tab: internal.nextTab || tabsModel.currentTab |
1041 | 480 | webview: tab ? tab.webview : null | ||
1042 | 481 | tabsModel: browser.tabsModel | 460 | tabsModel: browser.tabsModel |
1043 | 482 | searchUrl: currentSearchEngine.urlTemplate | 461 | searchUrl: currentSearchEngine.urlTemplate |
1044 | 483 | 462 | ||
1045 | @@ -800,7 +779,19 @@ | |||
1046 | 800 | 779 | ||
1047 | 801 | anchors.fill: parent | 780 | anchors.fill: parent |
1048 | 802 | active: false | 781 | active: false |
1050 | 803 | sourceComponent: browser.wide ? bookmarksViewWideComponent : bookmarksViewComponent | 782 | asynchronous: true |
1051 | 783 | Connections { | ||
1052 | 784 | target: browser | ||
1053 | 785 | onWideChanged: bookmarksViewLoader.selectBookmarksView() | ||
1054 | 786 | } | ||
1055 | 787 | Component.onCompleted: bookmarksViewLoader.selectBookmarksView() | ||
1056 | 788 | |||
1057 | 789 | function selectBookmarksView() { | ||
1058 | 790 | bookmarksViewLoader.setSource(browser.wide ? "BookmarksViewWide.qml" : "BookmarksView.qml", | ||
1059 | 791 | {"focus": true, | ||
1060 | 792 | "homepageUrl": Qt.binding(function () {return settings.homepage}) | ||
1061 | 793 | }); | ||
1062 | 794 | } | ||
1063 | 804 | 795 | ||
1064 | 805 | onStatusChanged: { | 796 | onStatusChanged: { |
1065 | 806 | if (status == Loader.Ready) { | 797 | if (status == Loader.Ready) { |
1066 | @@ -824,26 +815,6 @@ | |||
1067 | 824 | bookmarksViewLoader.active = false | 815 | bookmarksViewLoader.active = false |
1068 | 825 | } | 816 | } |
1069 | 826 | } | 817 | } |
1070 | 827 | |||
1071 | 828 | Component { | ||
1072 | 829 | id: bookmarksViewComponent | ||
1073 | 830 | |||
1074 | 831 | BookmarksView { | ||
1075 | 832 | anchors.fill: parent | ||
1076 | 833 | focus: true | ||
1077 | 834 | homepageUrl: settings.homepage | ||
1078 | 835 | } | ||
1079 | 836 | } | ||
1080 | 837 | |||
1081 | 838 | Component { | ||
1082 | 839 | id: bookmarksViewWideComponent | ||
1083 | 840 | |||
1084 | 841 | BookmarksViewWide { | ||
1085 | 842 | anchors.fill: parent | ||
1086 | 843 | focus: true | ||
1087 | 844 | homepageUrl: settings.homepage | ||
1088 | 845 | } | ||
1089 | 846 | } | ||
1090 | 847 | } | 818 | } |
1091 | 848 | 819 | ||
1092 | 849 | Loader { | 820 | Loader { |
1093 | @@ -851,7 +822,34 @@ | |||
1094 | 851 | 822 | ||
1095 | 852 | anchors.fill: parent | 823 | anchors.fill: parent |
1096 | 853 | active: false | 824 | active: false |
1098 | 854 | sourceComponent: browser.wide ? historyViewWideComponent : historyViewComponent | 825 | asynchronous: true |
1099 | 826 | Connections { | ||
1100 | 827 | target: browser | ||
1101 | 828 | onWideChanged: historyViewLoader.selectHistoryView() | ||
1102 | 829 | } | ||
1103 | 830 | Component.onCompleted: historyViewLoader.selectHistoryView() | ||
1104 | 831 | |||
1105 | 832 | function selectHistoryView() { | ||
1106 | 833 | historyViewLoader.setSource(browser.wide ? "HistoryViewWide.qml" : "HistoryViewWithExpansion.qml", | ||
1107 | 834 | {"focus": true}); | ||
1108 | 835 | } | ||
1109 | 836 | |||
1110 | 837 | Connections { | ||
1111 | 838 | target: historyViewLoader.item | ||
1112 | 839 | onHistoryEntryClicked: { | ||
1113 | 840 | historyViewLoader.active = false | ||
1114 | 841 | internal.openUrlInNewTab(url, true) | ||
1115 | 842 | } | ||
1116 | 843 | onNewTabRequested: { | ||
1117 | 844 | historyViewLoader.active = false | ||
1118 | 845 | internal.openUrlInNewTab("", true) | ||
1119 | 846 | } | ||
1120 | 847 | onDone: { | ||
1121 | 848 | historyViewLoader.active = false | ||
1122 | 849 | internal.resetFocus() | ||
1123 | 850 | } | ||
1124 | 851 | onBack: historyViewLoader.active = false | ||
1125 | 852 | } | ||
1126 | 855 | 853 | ||
1127 | 856 | onStatusChanged: { | 854 | onStatusChanged: { |
1128 | 857 | if (status == Loader.Ready) { | 855 | if (status == Loader.Ready) { |
1129 | @@ -862,75 +860,6 @@ | |||
1130 | 862 | internal.resetFocus() | 860 | internal.resetFocus() |
1131 | 863 | } | 861 | } |
1132 | 864 | } | 862 | } |
1133 | 865 | |||
1134 | 866 | Component { | ||
1135 | 867 | id: historyViewComponent | ||
1136 | 868 | |||
1137 | 869 | FocusScope { | ||
1138 | 870 | focus: true | ||
1139 | 871 | |||
1140 | 872 | signal loadModel() | ||
1141 | 873 | onLoadModel: children[0].loadModel() | ||
1142 | 874 | |||
1143 | 875 | HistoryView { | ||
1144 | 876 | anchors.fill: parent | ||
1145 | 877 | focus: !expandedHistoryViewLoader.focus | ||
1146 | 878 | visible: focus | ||
1147 | 879 | onSeeMoreEntriesClicked: { | ||
1148 | 880 | expandedHistoryViewLoader.model = model | ||
1149 | 881 | expandedHistoryViewLoader.active = true | ||
1150 | 882 | } | ||
1151 | 883 | onNewTabRequested: internal.openUrlInNewTab("", true) | ||
1152 | 884 | onBack: historyViewLoader.active = false | ||
1153 | 885 | } | ||
1154 | 886 | |||
1155 | 887 | Loader { | ||
1156 | 888 | id: expandedHistoryViewLoader | ||
1157 | 889 | asynchronous: true | ||
1158 | 890 | anchors.fill: parent | ||
1159 | 891 | active: false | ||
1160 | 892 | focus: active | ||
1161 | 893 | property var model: null | ||
1162 | 894 | sourceComponent: ExpandedHistoryView { | ||
1163 | 895 | focus: true | ||
1164 | 896 | model: expandedHistoryViewLoader.model | ||
1165 | 897 | onHistoryEntryClicked: { | ||
1166 | 898 | internal.openUrlInNewTab(url, true) | ||
1167 | 899 | historyViewLoader.active = false | ||
1168 | 900 | } | ||
1169 | 901 | onHistoryEntryRemoved: { | ||
1170 | 902 | if (count == 1) { | ||
1171 | 903 | done() | ||
1172 | 904 | } | ||
1173 | 905 | HistoryModel.removeEntryByUrl(url) | ||
1174 | 906 | } | ||
1175 | 907 | onDone: expandedHistoryViewLoader.active = false | ||
1176 | 908 | } | ||
1177 | 909 | } | ||
1178 | 910 | } | ||
1179 | 911 | } | ||
1180 | 912 | |||
1181 | 913 | Component { | ||
1182 | 914 | id: historyViewWideComponent | ||
1183 | 915 | |||
1184 | 916 | HistoryViewWide { | ||
1185 | 917 | anchors.fill: parent | ||
1186 | 918 | focus: true | ||
1187 | 919 | |||
1188 | 920 | onHistoryEntryClicked: { | ||
1189 | 921 | historyViewLoader.active = false | ||
1190 | 922 | internal.openUrlInNewTab(url, true) | ||
1191 | 923 | } | ||
1192 | 924 | onNewTabRequested: { | ||
1193 | 925 | historyViewLoader.active = false | ||
1194 | 926 | internal.openUrlInNewTab("", true) | ||
1195 | 927 | } | ||
1196 | 928 | onDone: { | ||
1197 | 929 | historyViewLoader.active = false | ||
1198 | 930 | internal.resetFocus() | ||
1199 | 931 | } | ||
1200 | 932 | } | ||
1201 | 933 | } | ||
1202 | 934 | } | 863 | } |
1203 | 935 | 864 | ||
1204 | 936 | Loader { | 865 | Loader { |
1205 | @@ -938,6 +867,7 @@ | |||
1206 | 938 | 867 | ||
1207 | 939 | anchors.fill: parent | 868 | anchors.fill: parent |
1208 | 940 | active: false | 869 | active: false |
1209 | 870 | asynchronous: true | ||
1210 | 941 | 871 | ||
1211 | 942 | onStatusChanged: { | 872 | onStatusChanged: { |
1212 | 943 | if (status == Loader.Ready) { | 873 | if (status == Loader.Ready) { |
1213 | @@ -948,10 +878,12 @@ | |||
1214 | 948 | } | 878 | } |
1215 | 949 | } | 879 | } |
1216 | 950 | 880 | ||
1221 | 951 | sourceComponent: SettingsPage { | 881 | Component.onCompleted: setSource("SettingsPage.qml", { |
1222 | 952 | anchors.fill: parent | 882 | "focus": true, |
1223 | 953 | focus: true | 883 | "settingsObject": settings |
1224 | 954 | settingsObject: settings | 884 | }) |
1225 | 885 | Connections { | ||
1226 | 886 | target: settingsViewLoader.item | ||
1227 | 955 | onDone: settingsViewLoader.active = false | 887 | onDone: settingsViewLoader.active = false |
1228 | 956 | } | 888 | } |
1229 | 957 | } | 889 | } |
1230 | @@ -961,18 +893,15 @@ | |||
1231 | 961 | 893 | ||
1232 | 962 | anchors.fill: parent | 894 | anchors.fill: parent |
1233 | 963 | active: false | 895 | active: false |
1235 | 964 | source: "DownloadsPage.qml" | 896 | asynchronous: true |
1236 | 897 | Component.onCompleted: { | ||
1237 | 898 | setSource("DownloadsPage.qml", { | ||
1238 | 899 | "downloadManager": Qt.binding(function () {return downloadHandlerLoader.item}), | ||
1239 | 900 | "incognito": incognito, | ||
1240 | 901 | "focus": true | ||
1241 | 902 | }) | ||
1242 | 903 | } | ||
1243 | 965 | 904 | ||
1244 | 966 | Binding { | ||
1245 | 967 | target: downloadsViewLoader.item | ||
1246 | 968 | property: "downloadManager" | ||
1247 | 969 | value: downloadHandlerLoader.item | ||
1248 | 970 | } | ||
1249 | 971 | Binding { | ||
1250 | 972 | target: downloadsViewLoader.item | ||
1251 | 973 | property: "focus" | ||
1252 | 974 | value: true | ||
1253 | 975 | } | ||
1254 | 976 | Connections { | 905 | Connections { |
1255 | 977 | target: downloadsViewLoader.item | 906 | target: downloadsViewLoader.item |
1256 | 978 | onDone: downloadsViewLoader.active = false | 907 | onDone: downloadsViewLoader.active = false |
1257 | @@ -993,424 +922,10 @@ | |||
1258 | 993 | asynchronous: true | 922 | asynchronous: true |
1259 | 994 | } | 923 | } |
1260 | 995 | 924 | ||
1679 | 996 | Component { | 925 | property Component tabComponent |
1680 | 997 | id: tabComponent | 926 | Loader { |
1681 | 998 | 927 | source: "TabComponent.qml" | |
1682 | 999 | BrowserTab { | 928 | onLoaded: tabComponent = item |
1265 | 1000 | anchors.fill: parent | ||
1266 | 1001 | incognito: browser.incognito | ||
1267 | 1002 | current: tabsModel && tabsModel.currentTab === this | ||
1268 | 1003 | focus: current | ||
1269 | 1004 | |||
1270 | 1005 | Item { | ||
1271 | 1006 | id: contextualMenuTarget | ||
1272 | 1007 | visible: false | ||
1273 | 1008 | } | ||
1274 | 1009 | |||
1275 | 1010 | webviewComponent: WebViewImpl { | ||
1276 | 1011 | id: webviewimpl | ||
1277 | 1012 | |||
1278 | 1013 | property BrowserTab tab | ||
1279 | 1014 | readonly property bool current: tab.current | ||
1280 | 1015 | |||
1281 | 1016 | currentWebview: browser.currentWebview | ||
1282 | 1017 | filePicker: filePickerLoader.item | ||
1283 | 1018 | |||
1284 | 1019 | anchors.fill: parent | ||
1285 | 1020 | |||
1286 | 1021 | focus: true | ||
1287 | 1022 | |||
1288 | 1023 | enabled: current && !bottomEdgeHandle.dragging && !recentView.visible | ||
1289 | 1024 | |||
1290 | 1025 | locationBarController { | ||
1291 | 1026 | height: chrome.height | ||
1292 | 1027 | mode: chromeController.defaultMode | ||
1293 | 1028 | } | ||
1294 | 1029 | |||
1295 | 1030 | //experimental.preferences.developerExtrasEnabled: developerExtrasEnabled | ||
1296 | 1031 | preferences.localStorageEnabled: true | ||
1297 | 1032 | preferences.appCacheEnabled: true | ||
1298 | 1033 | |||
1299 | 1034 | property QtObject contextModel: null | ||
1300 | 1035 | contextualActions: ActionList { | ||
1301 | 1036 | Actions.OpenLinkInNewTab { | ||
1302 | 1037 | objectName: "OpenLinkInNewTabContextualAction" | ||
1303 | 1038 | enabled: contextModel && contextModel.linkUrl.toString() | ||
1304 | 1039 | onTriggered: internal.openUrlInNewTab(contextModel.linkUrl, true) | ||
1305 | 1040 | } | ||
1306 | 1041 | Actions.OpenLinkInNewBackgroundTab { | ||
1307 | 1042 | objectName: "OpenLinkInNewBackgroundTabContextualAction" | ||
1308 | 1043 | enabled: contextModel && contextModel.linkUrl.toString() | ||
1309 | 1044 | onTriggered: internal.openUrlInNewTab(contextModel.linkUrl, false) | ||
1310 | 1045 | } | ||
1311 | 1046 | Actions.OpenLinkInNewWindow { | ||
1312 | 1047 | objectName: "OpenLinkInNewWindowContextualAction" | ||
1313 | 1048 | enabled: contextModel && contextModel.linkUrl.toString() | ||
1314 | 1049 | onTriggered: browser.openLinkInWindowRequested(contextModel.linkUrl, false) | ||
1315 | 1050 | } | ||
1316 | 1051 | Actions.OpenLinkInPrivateWindow { | ||
1317 | 1052 | objectName: "OpenLinkInPrivateWindowContextualAction" | ||
1318 | 1053 | enabled: contextModel && contextModel.linkUrl.toString() | ||
1319 | 1054 | onTriggered: browser.openLinkInWindowRequested(contextModel.linkUrl, true) | ||
1320 | 1055 | } | ||
1321 | 1056 | Actions.BookmarkLink { | ||
1322 | 1057 | objectName: "BookmarkLinkContextualAction" | ||
1323 | 1058 | enabled: contextModel && contextModel.linkUrl.toString() | ||
1324 | 1059 | && !BookmarksModel.contains(contextModel.linkUrl) | ||
1325 | 1060 | onTriggered: { | ||
1326 | 1061 | // position the menu target with a one-off assignement instead of a binding | ||
1327 | 1062 | // since the contents of the contextModel have meaning only while the context | ||
1328 | 1063 | // menu is active | ||
1329 | 1064 | contextualMenuTarget.x = contextModel.position.x | ||
1330 | 1065 | contextualMenuTarget.y = contextModel.position.y + locationBarController.height + locationBarController.offset | ||
1331 | 1066 | internal.addBookmark(contextModel.linkUrl, contextModel.linkText, | ||
1332 | 1067 | "", contextualMenuTarget) | ||
1333 | 1068 | } | ||
1334 | 1069 | } | ||
1335 | 1070 | Actions.CopyLink { | ||
1336 | 1071 | objectName: "CopyLinkContextualAction" | ||
1337 | 1072 | enabled: contextModel && contextModel.linkUrl.toString() | ||
1338 | 1073 | onTriggered: Clipboard.push(["text/plain", contextModel.linkUrl.toString()]) | ||
1339 | 1074 | } | ||
1340 | 1075 | Actions.SaveLink { | ||
1341 | 1076 | objectName: "SaveLinkContextualAction" | ||
1342 | 1077 | enabled: contextModel && contextModel.linkUrl.toString() | ||
1343 | 1078 | onTriggered: contextModel.saveLink() | ||
1344 | 1079 | } | ||
1345 | 1080 | Actions.Share { | ||
1346 | 1081 | objectName: "ShareContextualAction" | ||
1347 | 1082 | enabled: (contentHandlerLoader.status == Loader.Ready) && contextModel && | ||
1348 | 1083 | (contextModel.linkUrl.toString() || contextModel.selectionText) | ||
1349 | 1084 | onTriggered: { | ||
1350 | 1085 | if (contextModel.linkUrl.toString()) { | ||
1351 | 1086 | internal.shareLink(contextModel.linkUrl.toString(), contextModel.linkText) | ||
1352 | 1087 | } else if (contextModel.selectionText) { | ||
1353 | 1088 | internal.shareText(contextModel.selectionText) | ||
1354 | 1089 | } | ||
1355 | 1090 | } | ||
1356 | 1091 | } | ||
1357 | 1092 | Actions.OpenImageInNewTab { | ||
1358 | 1093 | objectName: "OpenImageInNewTabContextualAction" | ||
1359 | 1094 | enabled: contextModel && | ||
1360 | 1095 | (contextModel.mediaType === Oxide.WebView.MediaTypeImage) && | ||
1361 | 1096 | contextModel.srcUrl.toString() | ||
1362 | 1097 | onTriggered: internal.openUrlInNewTab(contextModel.srcUrl, true) | ||
1363 | 1098 | } | ||
1364 | 1099 | Actions.CopyImage { | ||
1365 | 1100 | objectName: "CopyImageContextualAction" | ||
1366 | 1101 | enabled: contextModel && | ||
1367 | 1102 | (contextModel.mediaType === Oxide.WebView.MediaTypeImage) && | ||
1368 | 1103 | contextModel.srcUrl.toString() | ||
1369 | 1104 | onTriggered: Clipboard.push(["text/plain", contextModel.srcUrl.toString()]) | ||
1370 | 1105 | } | ||
1371 | 1106 | Actions.SaveImage { | ||
1372 | 1107 | objectName: "SaveImageContextualAction" | ||
1373 | 1108 | enabled: contextModel && | ||
1374 | 1109 | ((contextModel.mediaType === Oxide.WebView.MediaTypeImage) || | ||
1375 | 1110 | (contextModel.mediaType === Oxide.WebView.MediaTypeCanvas)) && | ||
1376 | 1111 | contextModel.hasImageContents | ||
1377 | 1112 | onTriggered: contextModel.saveMedia() | ||
1378 | 1113 | } | ||
1379 | 1114 | Actions.OpenVideoInNewTab { | ||
1380 | 1115 | objectName: "OpenVideoInNewTabContextualAction" | ||
1381 | 1116 | enabled: contextModel && | ||
1382 | 1117 | (contextModel.mediaType === Oxide.WebView.MediaTypeVideo) && | ||
1383 | 1118 | contextModel.srcUrl.toString() | ||
1384 | 1119 | onTriggered: internal.openUrlInNewTab(contextModel.srcUrl, true) | ||
1385 | 1120 | } | ||
1386 | 1121 | Actions.SaveVideo { | ||
1387 | 1122 | objectName: "SaveVideoContextualAction" | ||
1388 | 1123 | enabled: contextModel && | ||
1389 | 1124 | (contextModel.mediaType === Oxide.WebView.MediaTypeVideo) && | ||
1390 | 1125 | contextModel.srcUrl.toString() | ||
1391 | 1126 | onTriggered: contextModel.saveMedia() | ||
1392 | 1127 | } | ||
1393 | 1128 | Actions.Undo { | ||
1394 | 1129 | objectName: "UndoContextualAction" | ||
1395 | 1130 | enabled: contextModel && contextModel.isEditable && | ||
1396 | 1131 | (contextModel.editFlags & Oxide.WebView.UndoCapability) | ||
1397 | 1132 | onTriggered: webviewimpl.executeEditingCommand(Oxide.WebView.EditingCommandUndo) | ||
1398 | 1133 | } | ||
1399 | 1134 | Actions.Redo { | ||
1400 | 1135 | objectName: "RedoContextualAction" | ||
1401 | 1136 | enabled: contextModel && contextModel.isEditable && | ||
1402 | 1137 | (contextModel.editFlags & Oxide.WebView.RedoCapability) | ||
1403 | 1138 | onTriggered: webviewimpl.executeEditingCommand(Oxide.WebView.EditingCommandRedo) | ||
1404 | 1139 | } | ||
1405 | 1140 | Actions.Cut { | ||
1406 | 1141 | objectName: "CutContextualAction" | ||
1407 | 1142 | enabled: contextModel && contextModel.isEditable && | ||
1408 | 1143 | (contextModel.editFlags & Oxide.WebView.CutCapability) | ||
1409 | 1144 | onTriggered: webviewimpl.executeEditingCommand(Oxide.WebView.EditingCommandCut) | ||
1410 | 1145 | } | ||
1411 | 1146 | Actions.Copy { | ||
1412 | 1147 | objectName: "CopyContextualAction" | ||
1413 | 1148 | enabled: contextModel && (contextModel.selectionText || | ||
1414 | 1149 | (contextModel.isEditable && | ||
1415 | 1150 | (contextModel.editFlags & Oxide.WebView.CopyCapability))) | ||
1416 | 1151 | onTriggered: webviewimpl.executeEditingCommand(Oxide.WebView.EditingCommandCopy) | ||
1417 | 1152 | } | ||
1418 | 1153 | Actions.Paste { | ||
1419 | 1154 | objectName: "PasteContextualAction" | ||
1420 | 1155 | enabled: contextModel && contextModel.isEditable && | ||
1421 | 1156 | (contextModel.editFlags & Oxide.WebView.PasteCapability) | ||
1422 | 1157 | onTriggered: webviewimpl.executeEditingCommand(Oxide.WebView.EditingCommandPaste) | ||
1423 | 1158 | } | ||
1424 | 1159 | Actions.Erase { | ||
1425 | 1160 | objectName: "EraseContextualAction" | ||
1426 | 1161 | enabled: contextModel && contextModel.isEditable && | ||
1427 | 1162 | (contextModel.editFlags & Oxide.WebView.EraseCapability) | ||
1428 | 1163 | onTriggered: webviewimpl.executeEditingCommand(Oxide.WebView.EditingCommandErase) | ||
1429 | 1164 | } | ||
1430 | 1165 | Actions.SelectAll { | ||
1431 | 1166 | objectName: "SelectAllContextualAction" | ||
1432 | 1167 | enabled: contextModel && contextModel.isEditable && | ||
1433 | 1168 | (contextModel.editFlags & Oxide.WebView.SelectAllCapability) | ||
1434 | 1169 | onTriggered: webviewimpl.executeEditingCommand(Oxide.WebView.EditingCommandSelectAll) | ||
1435 | 1170 | } | ||
1436 | 1171 | } | ||
1437 | 1172 | |||
1438 | 1173 | function contextMenuOnCompleted(menu) { | ||
1439 | 1174 | contextModel = menu.contextModel | ||
1440 | 1175 | if (contextModel.linkUrl.toString() || | ||
1441 | 1176 | contextModel.srcUrl.toString() || | ||
1442 | 1177 | contextModel.selectionText || | ||
1443 | 1178 | (contextModel.isEditable && contextModel.editFlags) || | ||
1444 | 1179 | (((contextModel.mediaType == Oxide.WebView.MediaTypeImage) || | ||
1445 | 1180 | (contextModel.mediaType == Oxide.WebView.MediaTypeCanvas)) && | ||
1446 | 1181 | contextModel.hasImageContents)) { | ||
1447 | 1182 | menu.show() | ||
1448 | 1183 | } else { | ||
1449 | 1184 | contextModel.close() | ||
1450 | 1185 | } | ||
1451 | 1186 | } | ||
1452 | 1187 | |||
1453 | 1188 | Component { | ||
1454 | 1189 | id: contextMenuNarrowComponent | ||
1455 | 1190 | ContextMenuMobile { | ||
1456 | 1191 | actions: contextualActions | ||
1457 | 1192 | Component.onCompleted: webviewimpl.contextMenuOnCompleted(this) | ||
1458 | 1193 | } | ||
1459 | 1194 | } | ||
1460 | 1195 | Component { | ||
1461 | 1196 | id: contextMenuWideComponent | ||
1462 | 1197 | ContextMenuWide { | ||
1463 | 1198 | webview: webviewimpl | ||
1464 | 1199 | parent: browser | ||
1465 | 1200 | actions: contextualActions | ||
1466 | 1201 | Component.onCompleted: webviewimpl.contextMenuOnCompleted(this) | ||
1467 | 1202 | } | ||
1468 | 1203 | } | ||
1469 | 1204 | contextMenu: browser.wide ? contextMenuWideComponent : contextMenuNarrowComponent | ||
1470 | 1205 | |||
1471 | 1206 | onNewViewRequested: { | ||
1472 | 1207 | var tab = tabComponent.createObject(tabContainer, {"request": request}) | ||
1473 | 1208 | var setCurrent = (request.disposition == Oxide.NewViewRequest.DispositionNewForegroundTab) | ||
1474 | 1209 | internal.addTab(tab, setCurrent) | ||
1475 | 1210 | if (setCurrent) tabContainer.forceActiveFocus() | ||
1476 | 1211 | } | ||
1477 | 1212 | |||
1478 | 1213 | onCloseRequested: prepareToClose() | ||
1479 | 1214 | onPrepareToCloseResponse: { | ||
1480 | 1215 | if (proceed) { | ||
1481 | 1216 | if (tab) { | ||
1482 | 1217 | for (var i = 0; i < tabsModel.count; ++i) { | ||
1483 | 1218 | if (tabsModel.get(i) === tab) { | ||
1484 | 1219 | tabsModel.remove(i) | ||
1485 | 1220 | break | ||
1486 | 1221 | } | ||
1487 | 1222 | } | ||
1488 | 1223 | tab.close() | ||
1489 | 1224 | } | ||
1490 | 1225 | if (tabsModel.count === 0) { | ||
1491 | 1226 | internal.openUrlInNewTab("", true, true) | ||
1492 | 1227 | } | ||
1493 | 1228 | } | ||
1494 | 1229 | } | ||
1495 | 1230 | |||
1496 | 1231 | QtObject { | ||
1497 | 1232 | id: webviewInternal | ||
1498 | 1233 | property url storedUrl: "" | ||
1499 | 1234 | property bool titleSet: false | ||
1500 | 1235 | property string title: "" | ||
1501 | 1236 | } | ||
1502 | 1237 | onLoadEvent: { | ||
1503 | 1238 | if (event.type == Oxide.LoadEvent.TypeCommitted) { | ||
1504 | 1239 | chrome.findInPageMode = false | ||
1505 | 1240 | webviewInternal.titleSet = false | ||
1506 | 1241 | webviewInternal.title = title | ||
1507 | 1242 | } | ||
1508 | 1243 | |||
1509 | 1244 | if (webviewimpl.incognito) { | ||
1510 | 1245 | return | ||
1511 | 1246 | } | ||
1512 | 1247 | |||
1513 | 1248 | if ((event.type == Oxide.LoadEvent.TypeCommitted) && | ||
1514 | 1249 | !event.isError && | ||
1515 | 1250 | (300 > event.httpStatusCode) && (event.httpStatusCode >= 200)) { | ||
1516 | 1251 | webviewInternal.storedUrl = event.url | ||
1517 | 1252 | HistoryModel.add(event.url, title, icon) | ||
1518 | 1253 | } | ||
1519 | 1254 | } | ||
1520 | 1255 | onTitleChanged: { | ||
1521 | 1256 | if (!webviewInternal.titleSet && webviewInternal.storedUrl.toString()) { | ||
1522 | 1257 | // Record the title to avoid updating the history database | ||
1523 | 1258 | // every time the page dynamically updates its title. | ||
1524 | 1259 | // We don’t want pages that update their title every second | ||
1525 | 1260 | // to achieve an ugly "scrolling title" effect to flood the | ||
1526 | 1261 | // history database with updates. | ||
1527 | 1262 | webviewInternal.titleSet = true | ||
1528 | 1263 | webviewInternal.title = title | ||
1529 | 1264 | HistoryModel.update(webviewInternal.storedUrl, title, icon) | ||
1530 | 1265 | } | ||
1531 | 1266 | } | ||
1532 | 1267 | onIconChanged: { | ||
1533 | 1268 | if (webviewInternal.storedUrl.toString()) { | ||
1534 | 1269 | HistoryModel.update(webviewInternal.storedUrl, webviewInternal.title, icon) | ||
1535 | 1270 | } | ||
1536 | 1271 | } | ||
1537 | 1272 | |||
1538 | 1273 | onGeolocationPermissionRequested: requestGeolocationPermission(request) | ||
1539 | 1274 | |||
1540 | 1275 | property var certificateError | ||
1541 | 1276 | function resetCertificateError() { | ||
1542 | 1277 | certificateError = null | ||
1543 | 1278 | } | ||
1544 | 1279 | onCertificateError: { | ||
1545 | 1280 | if (!error.isMainFrame || error.isSubresource) { | ||
1546 | 1281 | // Not a main frame document error, just block the content | ||
1547 | 1282 | // (it’s not overridable anyway). | ||
1548 | 1283 | return | ||
1549 | 1284 | } | ||
1550 | 1285 | if (internal.isCertificateErrorAllowed(error)) { | ||
1551 | 1286 | error.allow() | ||
1552 | 1287 | } else { | ||
1553 | 1288 | certificateError = error | ||
1554 | 1289 | error.onCancelled.connect(webviewimpl.resetCertificateError) | ||
1555 | 1290 | } | ||
1556 | 1291 | } | ||
1557 | 1292 | |||
1558 | 1293 | onFullscreenChanged: { | ||
1559 | 1294 | if (fullscreen) { | ||
1560 | 1295 | fullscreenExitHintComponent.createObject(webviewimpl) | ||
1561 | 1296 | } | ||
1562 | 1297 | } | ||
1563 | 1298 | Component { | ||
1564 | 1299 | id: fullscreenExitHintComponent | ||
1565 | 1300 | |||
1566 | 1301 | Rectangle { | ||
1567 | 1302 | id: fullscreenExitHint | ||
1568 | 1303 | objectName: "fullscreenExitHint" | ||
1569 | 1304 | |||
1570 | 1305 | anchors.centerIn: parent | ||
1571 | 1306 | height: units.gu(6) | ||
1572 | 1307 | width: Math.min(units.gu(50), parent.width - units.gu(12)) | ||
1573 | 1308 | radius: units.gu(1) | ||
1574 | 1309 | color: "#3e3b39" | ||
1575 | 1310 | opacity: 0.85 | ||
1576 | 1311 | |||
1577 | 1312 | Behavior on opacity { | ||
1578 | 1313 | UbuntuNumberAnimation { | ||
1579 | 1314 | duration: UbuntuAnimation.SlowDuration | ||
1580 | 1315 | } | ||
1581 | 1316 | } | ||
1582 | 1317 | onOpacityChanged: { | ||
1583 | 1318 | if (opacity == 0.0) { | ||
1584 | 1319 | fullscreenExitHint.destroy() | ||
1585 | 1320 | } | ||
1586 | 1321 | } | ||
1587 | 1322 | |||
1588 | 1323 | // Delay showing the hint to prevent it from jumping up while the | ||
1589 | 1324 | // webview is being resized (https://launchpad.net/bugs/1454097). | ||
1590 | 1325 | visible: false | ||
1591 | 1326 | Timer { | ||
1592 | 1327 | running: true | ||
1593 | 1328 | interval: 250 | ||
1594 | 1329 | onTriggered: fullscreenExitHint.visible = true | ||
1595 | 1330 | } | ||
1596 | 1331 | |||
1597 | 1332 | Label { | ||
1598 | 1333 | color: "white" | ||
1599 | 1334 | font.weight: Font.Light | ||
1600 | 1335 | anchors.centerIn: parent | ||
1601 | 1336 | text: bottomEdgeHandle.enabled | ||
1602 | 1337 | ? i18n.tr("Swipe Up To Exit Full Screen") | ||
1603 | 1338 | : i18n.tr("Press ESC To Exit Full Screen") | ||
1604 | 1339 | } | ||
1605 | 1340 | |||
1606 | 1341 | Timer { | ||
1607 | 1342 | running: fullscreenExitHint.visible | ||
1608 | 1343 | interval: 2000 | ||
1609 | 1344 | onTriggered: fullscreenExitHint.opacity = 0 | ||
1610 | 1345 | } | ||
1611 | 1346 | |||
1612 | 1347 | Connections { | ||
1613 | 1348 | target: webviewimpl | ||
1614 | 1349 | onFullscreenChanged: { | ||
1615 | 1350 | if (!webviewimpl.fullscreen) { | ||
1616 | 1351 | fullscreenExitHint.destroy() | ||
1617 | 1352 | } | ||
1618 | 1353 | } | ||
1619 | 1354 | } | ||
1620 | 1355 | |||
1621 | 1356 | Component.onCompleted: bottomEdgeHint.forceShow = true | ||
1622 | 1357 | Component.onDestruction: bottomEdgeHint.forceShow = false | ||
1623 | 1358 | } | ||
1624 | 1359 | } | ||
1625 | 1360 | |||
1626 | 1361 | onShowDownloadDialog: { | ||
1627 | 1362 | if (downloadDialogLoader.status === Loader.Ready) { | ||
1628 | 1363 | var downloadDialog = PopupUtils.open(downloadDialogLoader.item, browser, {"contentType" : contentType, | ||
1629 | 1364 | "downloadId" : downloadId, | ||
1630 | 1365 | "singleDownload" : downloader, | ||
1631 | 1366 | "filename" : filename, | ||
1632 | 1367 | "mimeType" : mimeType}) | ||
1633 | 1368 | downloadDialog.startDownload.connect(startDownload) | ||
1634 | 1369 | } | ||
1635 | 1370 | } | ||
1636 | 1371 | |||
1637 | 1372 | function showDownloadsPage() { | ||
1638 | 1373 | downloadsViewLoader.active = true | ||
1639 | 1374 | return downloadsViewLoader.item | ||
1640 | 1375 | } | ||
1641 | 1376 | |||
1642 | 1377 | function startDownload(downloadId, download, mimeType) { | ||
1643 | 1378 | DownloadsModel.add(downloadId, download.url, mimeType) | ||
1644 | 1379 | download.start() | ||
1645 | 1380 | downloadsViewLoader.active = true | ||
1646 | 1381 | } | ||
1647 | 1382 | |||
1648 | 1383 | } | ||
1649 | 1384 | } | ||
1650 | 1385 | } | ||
1651 | 1386 | |||
1652 | 1387 | Component { | ||
1653 | 1388 | id: bookmarkOptionsComponent | ||
1654 | 1389 | BookmarkOptions { | ||
1655 | 1390 | folderModel: BookmarksFolderListModel { | ||
1656 | 1391 | sourceModel: BookmarksModel | ||
1657 | 1392 | } | ||
1658 | 1393 | |||
1659 | 1394 | Component.onCompleted: forceActiveFocus() | ||
1660 | 1395 | |||
1661 | 1396 | onVisibleChanged: { | ||
1662 | 1397 | if (!visible) { | ||
1663 | 1398 | BookmarksModel.remove(bookmarkUrl) | ||
1664 | 1399 | } | ||
1665 | 1400 | } | ||
1666 | 1401 | |||
1667 | 1402 | Component.onDestruction: { | ||
1668 | 1403 | if (BookmarksModel.contains(bookmarkUrl)) { | ||
1669 | 1404 | BookmarksModel.update(bookmarkUrl, bookmarkTitle, bookmarkFolder) | ||
1670 | 1405 | } | ||
1671 | 1406 | } | ||
1672 | 1407 | |||
1673 | 1408 | // Fragile workaround for https://launchpad.net/bugs/1546677. | ||
1674 | 1409 | // By destroying the popover, its visibility isn’t changed to | ||
1675 | 1410 | // false, and thus the bookmark is not removed. | ||
1676 | 1411 | Keys.onEnterPressed: destroy() | ||
1677 | 1412 | Keys.onReturnPressed: destroy() | ||
1678 | 1413 | } | ||
1683 | 1414 | } | 929 | } |
1684 | 1415 | 930 | ||
1685 | 1416 | QtObject { | 931 | QtObject { |
1686 | @@ -1513,6 +1028,7 @@ | |||
1687 | 1513 | var tabInfo = closedTabHistory.pop() | 1028 | var tabInfo = closedTabHistory.pop() |
1688 | 1514 | var tab = restoreTabState(tabInfo.state) | 1029 | var tab = restoreTabState(tabInfo.state) |
1689 | 1515 | addTab(tab, true, tabInfo.index) | 1030 | addTab(tab, true, tabInfo.index) |
1690 | 1031 | tab.load() | ||
1691 | 1516 | } | 1032 | } |
1692 | 1517 | } | 1033 | } |
1693 | 1518 | 1034 | ||
1694 | @@ -1621,7 +1137,7 @@ | |||
1695 | 1621 | BookmarksModel.add(url, title, icon, "") | 1137 | BookmarksModel.add(url, title, icon, "") |
1696 | 1622 | if (location === undefined) location = chrome.bookmarkTogglePlaceHolder | 1138 | if (location === undefined) location = chrome.bookmarkTogglePlaceHolder |
1697 | 1623 | var properties = {"bookmarkUrl": url, "bookmarkTitle": title} | 1139 | var properties = {"bookmarkUrl": url, "bookmarkTitle": title} |
1699 | 1624 | currentBookmarkOptionsDialog = PopupUtils.open(bookmarkOptionsComponent, | 1140 | internal.currentBookmarkOptionsDialog = PopupUtils.open(Qt.resolvedUrl("BookmarkOptions.qml"), |
1700 | 1625 | location, properties) | 1141 | location, properties) |
1701 | 1626 | } | 1142 | } |
1702 | 1627 | } | 1143 | } |
1703 | 1628 | 1144 | ||
1704 | === modified file 'src/app/webbrowser/BrowserTab.qml' | |||
1705 | --- src/app/webbrowser/BrowserTab.qml 2016-08-11 11:19:40 +0000 | |||
1706 | +++ src/app/webbrowser/BrowserTab.qml 2016-10-13 14:41:36 +0000 | |||
1707 | @@ -193,9 +193,10 @@ | |||
1708 | 193 | } | 193 | } |
1709 | 194 | } | 194 | } |
1710 | 195 | Connections { | 195 | Connections { |
1714 | 196 | target: webview | 196 | target: incognito ? null : webview |
1715 | 197 | onLoadingStateChanged: { | 197 | onLoadEvent: { |
1716 | 198 | if (!webview.loading && !webview.incognito) { | 198 | if ((event.type == Oxide.LoadEvent.TypeSucceeded) || |
1717 | 199 | (event.type == Oxide.LoadEvent.TypeFailed)) { | ||
1718 | 199 | delayedCapture.restart() | 200 | delayedCapture.restart() |
1719 | 200 | } | 201 | } |
1720 | 201 | } | 202 | } |
1721 | 202 | 203 | ||
1722 | === modified file 'src/app/webbrowser/Chrome.qml' | |||
1723 | --- src/app/webbrowser/Chrome.qml 2016-02-09 22:01:57 +0000 | |||
1724 | +++ src/app/webbrowser/Chrome.qml 2016-10-13 14:41:36 +0000 | |||
1725 | @@ -25,6 +25,7 @@ | |||
1726 | 25 | 25 | ||
1727 | 26 | property var tabsModel | 26 | property var tabsModel |
1728 | 27 | property alias tab: navigationBar.tab | 27 | property alias tab: navigationBar.tab |
1729 | 28 | readonly property var webview: tab ? tab.webview : null | ||
1730 | 28 | property alias searchUrl: navigationBar.searchUrl | 29 | property alias searchUrl: navigationBar.searchUrl |
1731 | 29 | property alias text: navigationBar.text | 30 | property alias text: navigationBar.text |
1732 | 30 | property alias bookmarked: navigationBar.bookmarked | 31 | property alias bookmarked: navigationBar.bookmarked |
1733 | @@ -69,15 +70,23 @@ | |||
1734 | 69 | Loader { | 70 | Loader { |
1735 | 70 | id: tabsBar | 71 | id: tabsBar |
1736 | 71 | 72 | ||
1742 | 72 | sourceComponent: TabsBar { | 73 | Component.onCompleted: { |
1743 | 73 | model: tabsModel | 74 | tabsBar.setSource("TabsBar.qml", { |
1744 | 74 | incognito: chrome.incognito | 75 | "model": Qt.binding(function () {return chrome.tabsModel}), |
1745 | 75 | fgColor: navigationBar.fgColor | 76 | "incognito": Qt.binding(function () {return chrome.incognito}), |
1746 | 76 | touchEnabled: chrome.touchEnabled | 77 | "fgColor": Qt.binding(function () {return navigationBar.fgColor}), |
1747 | 78 | "touchEnabled": Qt.binding(function () {return chrome.touchEnabled}) | ||
1748 | 79 | }) | ||
1749 | 80 | } | ||
1750 | 81 | |||
1751 | 82 | Connections { | ||
1752 | 83 | target: tabsBar.item | ||
1753 | 84 | |||
1754 | 77 | onSwitchToTab: chrome.switchToTab(index) | 85 | onSwitchToTab: chrome.switchToTab(index) |
1755 | 78 | onRequestNewTab: chrome.requestNewTab(index, makeCurrent) | 86 | onRequestNewTab: chrome.requestNewTab(index, makeCurrent) |
1756 | 79 | onTabClosed: chrome.tabClosed(index) | 87 | onTabClosed: chrome.tabClosed(index) |
1757 | 80 | } | 88 | } |
1758 | 89 | asynchronous: true | ||
1759 | 81 | 90 | ||
1760 | 82 | anchors { | 91 | anchors { |
1761 | 83 | top: parent.top | 92 | top: parent.top |
1762 | @@ -90,6 +99,7 @@ | |||
1763 | 90 | NavigationBar { | 99 | NavigationBar { |
1764 | 91 | id: navigationBar | 100 | id: navigationBar |
1765 | 92 | 101 | ||
1766 | 102 | loading: chrome.loading | ||
1767 | 93 | fgColor: "#111111" | 103 | fgColor: "#111111" |
1768 | 94 | iconColor: (incognito && !showTabsBar) ? "white" : fgColor | 104 | iconColor: (incognito && !showTabsBar) ? "white" : fgColor |
1769 | 95 | 105 | ||
1770 | @@ -105,4 +115,19 @@ | |||
1771 | 105 | onToggleBookmark: chrome.toggleBookmark() | 115 | onToggleBookmark: chrome.toggleBookmark() |
1772 | 106 | } | 116 | } |
1773 | 107 | } | 117 | } |
1774 | 118 | |||
1775 | 119 | // Delay changing the 'loading' state, to allow for very brief load | ||
1776 | 120 | // sequences to not update the UI, which would result in inelegant | ||
1777 | 121 | // flickering (https://launchpad.net/bugs/1611680). | ||
1778 | 122 | Connections { | ||
1779 | 123 | target: webview | ||
1780 | 124 | onLoadingStateChanged: delayedLoadingNotifier.restart() | ||
1781 | 125 | } | ||
1782 | 126 | Timer { | ||
1783 | 127 | id: delayedLoadingNotifier | ||
1784 | 128 | interval: 100 | ||
1785 | 129 | onTriggered: loading = webview.loading | ||
1786 | 130 | } | ||
1787 | 131 | |||
1788 | 132 | loadProgress: (loading && webview) ? webview.loadProgress : 0 | ||
1789 | 108 | } | 133 | } |
1790 | 109 | 134 | ||
1791 | === modified file 'src/app/webbrowser/DownloadDelegate.qml' | |||
1792 | --- src/app/webbrowser/DownloadDelegate.qml 2016-05-23 02:52:50 +0000 | |||
1793 | +++ src/app/webbrowser/DownloadDelegate.qml 2016-10-13 14:41:36 +0000 | |||
1794 | @@ -25,7 +25,7 @@ | |||
1795 | 25 | 25 | ||
1796 | 26 | property var downloadManager | 26 | property var downloadManager |
1797 | 27 | 27 | ||
1799 | 28 | property alias icon: mimeicon.name | 28 | property string icon |
1800 | 29 | property alias image: thumbimage.source | 29 | property alias image: thumbimage.source |
1801 | 30 | property alias title: title.text | 30 | property alias title: title.text |
1802 | 31 | property alias url: url.text | 31 | property alias url: url.text |
1803 | @@ -33,15 +33,16 @@ | |||
1804 | 33 | property bool incomplete: false | 33 | property bool incomplete: false |
1805 | 34 | property string downloadId | 34 | property string downloadId |
1806 | 35 | property var download | 35 | property var download |
1808 | 36 | property int progress: download ? download.progress : 0 | 36 | readonly property int progress: download ? download.progress : 0 |
1809 | 37 | property bool paused | 37 | property bool paused |
1810 | 38 | property alias incognito: incognitoIcon.visible | ||
1811 | 38 | 39 | ||
1812 | 39 | divider.visible: false | 40 | divider.visible: false |
1813 | 40 | 41 | ||
1814 | 41 | signal removed() | 42 | signal removed() |
1815 | 42 | signal cancelled() | 43 | signal cancelled() |
1816 | 43 | 44 | ||
1818 | 44 | height: visible ? (incomplete ? (paused ? units.gu(13) : units.gu(10)) : units.gu(7)) : 0 | 45 | height: visible ? layout.height : 0 |
1819 | 45 | 46 | ||
1820 | 46 | QtObject { | 47 | QtObject { |
1821 | 47 | id: internal | 48 | id: internal |
1822 | @@ -60,165 +61,167 @@ | |||
1823 | 60 | Component.onCompleted: internal.connectToDownloadObject() | 61 | Component.onCompleted: internal.connectToDownloadObject() |
1824 | 61 | onDownloadManagerChanged: internal.connectToDownloadObject() | 62 | onDownloadManagerChanged: internal.connectToDownloadObject() |
1825 | 62 | 63 | ||
1835 | 63 | Item { | 64 | SlotsLayout { |
1836 | 64 | 65 | id: layout | |
1828 | 65 | anchors { | ||
1829 | 66 | verticalCenter: parent.verticalCenter | ||
1830 | 67 | left: parent.left | ||
1831 | 68 | leftMargin: units.gu(2) | ||
1832 | 69 | right: parent.right | ||
1833 | 70 | rightMargin: units.gu(2) | ||
1834 | 71 | } | ||
1837 | 72 | 66 | ||
1838 | 73 | Item { | 67 | Item { |
1840 | 74 | id: iconContainer | 68 | SlotsLayout.position: SlotsLayout.Leading |
1841 | 75 | width: units.gu(3) | 69 | width: units.gu(3) |
1845 | 76 | height: width | 70 | height: units.gu(3) |
1843 | 77 | anchors.verticalCenter: parent.verticalCenter | ||
1844 | 78 | anchors.verticalCenterOffset: downloadDelegate.incomplete ? -units.gu(1) : 0 | ||
1846 | 79 | 71 | ||
1847 | 80 | Image { | 72 | Image { |
1848 | 81 | id: thumbimage | 73 | id: thumbimage |
1849 | 82 | asynchronous: true | 74 | asynchronous: true |
1852 | 83 | width: parent.width | 75 | anchors.fill: parent |
1851 | 84 | height: parent.height | ||
1853 | 85 | fillMode: Image.PreserveAspectFit | 76 | fillMode: Image.PreserveAspectFit |
1857 | 86 | sourceSize.width: parent.width | 77 | sourceSize.width: width |
1858 | 87 | sourceSize.height: parent.height | 78 | sourceSize.height: height |
1856 | 88 | anchors.verticalCenter: parent.verticalCenter | ||
1859 | 89 | } | 79 | } |
1860 | 90 | 80 | ||
1861 | 91 | Image { | 81 | Image { |
1862 | 92 | id: mimeicon | ||
1863 | 93 | asynchronous: true | 82 | asynchronous: true |
1864 | 94 | anchors.fill: parent | 83 | anchors.fill: parent |
1865 | 95 | anchors.margins: units.gu(0.2) | 84 | anchors.margins: units.gu(0.2) |
1867 | 96 | source: "image://theme/%1".arg(name != "" ? name : "save") | 85 | source: "image://theme/%1".arg(downloadDelegate.icon || "save") |
1868 | 97 | visible: thumbimage.status !== Image.Ready | 86 | visible: thumbimage.status !== Image.Ready |
1869 | 98 | cache: true | 87 | cache: true |
1993 | 99 | property string name | 88 | } |
1994 | 100 | } | 89 | } |
1995 | 101 | } | 90 | |
1996 | 102 | 91 | mainSlot: Column { | |
1997 | 103 | Item { | 92 | Label { |
1998 | 104 | anchors.top: iconContainer.top | 93 | id: title |
1999 | 105 | anchors.left: iconContainer.right | 94 | fontSize: "x-small" |
2000 | 106 | anchors.leftMargin: units.gu(2) | 95 | color: "#5d5d5d" |
2001 | 107 | anchors.right: parent.right | 96 | elide: Text.ElideRight |
2002 | 108 | 97 | anchors { | |
2003 | 109 | Column { | 98 | left: parent.left |
2004 | 110 | id: detailsColumn | 99 | right: parent.right |
2005 | 111 | width: parent.width - cancelColumn.width | 100 | } |
2006 | 112 | height: parent.height | 101 | } |
2007 | 113 | 102 | ||
2008 | 114 | Label { | 103 | Label { |
2009 | 115 | id: title | 104 | id: url |
2010 | 116 | fontSize: "x-small" | 105 | fontSize: "x-small" |
2011 | 117 | color: "#5d5d5d" | 106 | color: "#5d5d5d" |
2012 | 118 | elide: Text.ElideRight | 107 | elide: Text.ElideRight |
2013 | 119 | width: parent.width | 108 | anchors { |
2014 | 120 | } | 109 | left: parent.left |
2015 | 121 | 110 | right: parent.right | |
2016 | 122 | Label { | 111 | } |
2017 | 123 | id: url | 112 | } |
2018 | 124 | fontSize: "x-small" | 113 | |
2019 | 125 | color: "#5d5d5d" | 114 | Item { |
2020 | 126 | elide: Text.ElideRight | 115 | height: error.visible ? units.gu(1) : units.gu(2) |
2021 | 127 | width: parent.width | 116 | anchors { |
2022 | 128 | } | 117 | left: parent.left |
2023 | 129 | 118 | right: parent.right | |
2024 | 130 | Item { | 119 | } |
2025 | 131 | height: error.visible ? units.gu(1) : units.gu(2) | 120 | visible: incomplete |
2026 | 132 | width: parent.width | 121 | } |
2027 | 133 | visible: downloadDelegate.incomplete | 122 | |
2028 | 134 | } | 123 | Item { |
2029 | 135 | 124 | id: error | |
2030 | 136 | Item { | 125 | visible: (incomplete && (download === undefined)) || errorMessage |
2031 | 137 | id: error | 126 | height: units.gu(3) |
2032 | 138 | visible: incomplete && download === undefined || errorMessage !== "" | 127 | anchors { |
2033 | 139 | height: units.gu(3) | 128 | left: parent.left |
2034 | 140 | width: parent.width | 129 | right: parent.right |
2035 | 141 | 130 | } | |
2036 | 142 | Icon { | 131 | |
2037 | 143 | id: errorIcon | 132 | Icon { |
2038 | 144 | width: units.gu(2) | 133 | id: errorIcon |
2039 | 145 | height: width | 134 | width: units.gu(2) |
2040 | 146 | anchors.verticalCenter: parent.verticalCenter | 135 | height: units.gu(2) |
2041 | 147 | name: "dialog-warning-symbolic" | 136 | anchors.verticalCenter: parent.verticalCenter |
2042 | 148 | color: theme.palette.normal.negative | 137 | name: "dialog-warning-symbolic" |
2043 | 149 | } | 138 | color: theme.palette.normal.negative |
2044 | 150 | 139 | } | |
2045 | 151 | Label { | 140 | |
2046 | 152 | width: parent.width - errorIcon.width | 141 | Label { |
2047 | 153 | anchors.left: errorIcon.right | 142 | anchors { |
2048 | 154 | anchors.leftMargin: units.gu(1) | 143 | left: errorIcon.right |
2049 | 155 | anchors.verticalCenter: errorIcon.verticalCenter | 144 | leftMargin: units.gu(1) |
2050 | 156 | fontSize: "x-small" | 145 | right: parent.right |
2051 | 157 | color: theme.palette.normal.negative | 146 | verticalCenter: parent.verticalCenter |
2052 | 158 | text: errorMessage !== "" ? errorMessage | 147 | } |
2053 | 159 | : (incomplete && download === undefined) ? i18n.tr("Download failed") | 148 | fontSize: "x-small" |
2054 | 160 | : "" | 149 | color: theme.palette.normal.negative |
2055 | 161 | elide: Text.ElideRight | 150 | text: errorMessage || |
2056 | 162 | } | 151 | ((incomplete && download === undefined) ? i18n.tr("Download failed") : "") |
2057 | 163 | } | 152 | elide: Text.ElideRight |
2058 | 164 | 153 | } | |
2059 | 165 | IndeterminateProgressBar { | 154 | } |
2060 | 166 | id: progressBar | 155 | |
2061 | 167 | width: parent.width | 156 | IndeterminateProgressBar { |
2062 | 168 | height: units.gu(0.5) | 157 | id: progressBar |
2063 | 169 | visible: downloadDelegate.incomplete && !error.visible | 158 | anchors { |
2064 | 170 | progress: downloadDelegate.progress | 159 | left: parent.left |
2065 | 171 | // Work around UDM bug #1450144 | 160 | right: parent.right |
2066 | 172 | indeterminateProgress: downloadDelegate.progress < 0 || downloadDelegate.progress > 100 | 161 | } |
2067 | 173 | } | 162 | height: units.gu(0.5) |
2068 | 174 | } | 163 | visible: incomplete && !error.visible |
2069 | 175 | 164 | progress: downloadDelegate.progress | |
2070 | 176 | Column { | 165 | // Work around UDM bug #1450144 |
2071 | 177 | id: cancelColumn | 166 | indeterminateProgress: progress < 0 || progress > 100 |
2072 | 178 | spacing: units.gu(1) | 167 | } |
2073 | 179 | anchors.top: detailsColumn.top | 168 | } |
2074 | 180 | anchors.left: detailsColumn.right | 169 | |
2075 | 181 | anchors.leftMargin: units.gu(2) | 170 | Column { |
2076 | 182 | width: downloadDelegate.incomplete && !error.visible ? cancelButton.width + units.gu(2) : 0 | 171 | SlotsLayout.position: SlotsLayout.Trailing |
2077 | 183 | 172 | spacing: units.gu(1) | |
2078 | 184 | Button { | 173 | width: (incomplete && !error.visible) ? cancelButton.width : 0 |
2079 | 185 | visible: downloadDelegate.incomplete && !error.visible | 174 | |
2080 | 186 | id: cancelButton | 175 | Button { |
2081 | 187 | text: i18n.tr("Cancel") | 176 | id: cancelButton |
2082 | 188 | onClicked: { | 177 | visible: incomplete && !error.visible |
2083 | 189 | if (download) { | 178 | text: i18n.tr("Cancel") |
2084 | 190 | download.cancel() | 179 | onClicked: { |
2085 | 191 | cancelled() | 180 | if (download) { |
2086 | 192 | } | 181 | download.cancel() |
2087 | 193 | } | 182 | cancelled() |
2088 | 194 | } | 183 | } |
2089 | 195 | 184 | } | |
2090 | 196 | Label { | 185 | } |
2091 | 197 | visible: !progressBar.indeterminateProgress && downloadDelegate.incomplete | 186 | |
2092 | 198 | && !error.visible | 187 | Label { |
2093 | 199 | && !downloadDelegate.paused | 188 | visible: !progressBar.indeterminateProgress && incomplete |
2094 | 200 | width: cancelButton.width | 189 | && !error.visible && !paused |
2095 | 201 | horizontalAlignment: Text.AlignHCenter | 190 | width: cancelButton.width |
2096 | 202 | fontSize: "x-small" | 191 | horizontalAlignment: Text.AlignHCenter |
2097 | 203 | text: progressBar.progress + "%" | 192 | fontSize: "x-small" |
2098 | 204 | } | 193 | // TRANSLATORS: %1 is the percentage of the download completed so far |
2099 | 205 | 194 | text: i18n.tr("%1%").arg(progressBar.progress) | |
2100 | 206 | Button { | 195 | } |
2101 | 207 | visible: downloadDelegate.paused | 196 | |
2102 | 208 | text: i18n.tr("Resume") | 197 | Button { |
2103 | 209 | width: cancelButton.width | 198 | visible: paused |
2104 | 210 | onClicked: { | 199 | text: i18n.tr("Resume") |
2105 | 211 | if (download) { | 200 | width: cancelButton.width |
2106 | 212 | download.resume() | 201 | onClicked: { |
2107 | 213 | } | 202 | if (download) { |
2108 | 214 | } | 203 | download.resume() |
2109 | 215 | } | 204 | } |
2110 | 216 | } | 205 | } |
2111 | 217 | 206 | } | |
2112 | 218 | } | 207 | } |
2113 | 219 | } | 208 | } |
2114 | 220 | 209 | ||
2115 | 221 | leadingActions: error.visible || !downloadDelegate.incomplete ? deleteActionList : null | 210 | Icon { |
2116 | 211 | id: incognitoIcon | ||
2117 | 212 | anchors { | ||
2118 | 213 | right: parent.right | ||
2119 | 214 | rightMargin: units.gu(2) | ||
2120 | 215 | bottom: parent.bottom | ||
2121 | 216 | bottomMargin: units.gu(1) | ||
2122 | 217 | } | ||
2123 | 218 | width: units.gu(2) | ||
2124 | 219 | height: units.gu(2) | ||
2125 | 220 | asynchronous: true | ||
2126 | 221 | name: "private-browsing" | ||
2127 | 222 | } | ||
2128 | 223 | |||
2129 | 224 | leadingActions: error.visible || !incomplete ? deleteActionList : null | ||
2130 | 222 | 225 | ||
2131 | 223 | ListItemActions { | 226 | ListItemActions { |
2132 | 224 | id: deleteActionList | 227 | id: deleteActionList |
2133 | @@ -226,9 +229,8 @@ | |||
2134 | 226 | Action { | 229 | Action { |
2135 | 227 | objectName: "leadingAction.delete" | 230 | objectName: "leadingAction.delete" |
2136 | 228 | iconName: "delete" | 231 | iconName: "delete" |
2140 | 229 | enabled: error.visible || !downloadDelegate.incomplete | 232 | enabled: error.visible || !incomplete |
2141 | 230 | onTriggered: error.visible ? downloadDelegate.cancelled() | 233 | onTriggered: error.visible ? cancelled() : removed() |
2139 | 231 | : downloadDelegate.removed() | ||
2142 | 232 | } | 234 | } |
2143 | 233 | ] | 235 | ] |
2144 | 234 | } | 236 | } |
2145 | 235 | 237 | ||
2146 | === modified file 'src/app/webbrowser/DownloadsPage.qml' | |||
2147 | --- src/app/webbrowser/DownloadsPage.qml 2016-06-06 09:09:47 +0000 | |||
2148 | +++ src/app/webbrowser/DownloadsPage.qml 2016-10-13 14:41:36 +0000 | |||
2149 | @@ -39,6 +39,7 @@ | |||
2150 | 39 | property bool pickingMode | 39 | property bool pickingMode |
2151 | 40 | property bool multiSelect | 40 | property bool multiSelect |
2152 | 41 | property alias mimetypeFilter: downloadModelFilter.pattern | 41 | property alias mimetypeFilter: downloadModelFilter.pattern |
2153 | 42 | property bool incognito: false | ||
2154 | 42 | 43 | ||
2155 | 43 | signal done() | 44 | signal done() |
2156 | 44 | 45 | ||
2157 | @@ -154,8 +155,14 @@ | |||
2158 | 154 | focus: !exportPeerPicker.focus | 155 | focus: !exportPeerPicker.focus |
2159 | 155 | 156 | ||
2160 | 156 | model: SortFilterModel { | 157 | model: SortFilterModel { |
2163 | 157 | model: DownloadsModel | 158 | model: SortFilterModel { |
2164 | 158 | filter { | 159 | model: DownloadsModel |
2165 | 160 | filter { | ||
2166 | 161 | property: "incognito" | ||
2167 | 162 | pattern: RegExp(downloadsItem.incognito ? "" : "^false$") | ||
2168 | 163 | } | ||
2169 | 164 | } | ||
2170 | 165 | filter { | ||
2171 | 159 | id: downloadModelFilter | 166 | id: downloadModelFilter |
2172 | 160 | property: "mimetype" | 167 | property: "mimetype" |
2173 | 161 | } | 168 | } |
2174 | @@ -197,6 +204,7 @@ | |||
2175 | 197 | visible: !(selectMode && incomplete) | 204 | visible: !(selectMode && incomplete) |
2176 | 198 | errorMessage: model.error | 205 | errorMessage: model.error |
2177 | 199 | paused: model.paused | 206 | paused: model.paused |
2178 | 207 | incognito: model.incognito | ||
2179 | 200 | 208 | ||
2180 | 201 | onClicked: { | 209 | onClicked: { |
2181 | 202 | if (model.complete && !selectMode) { | 210 | if (model.complete && !selectMode) { |
2182 | 203 | 211 | ||
2183 | === added file 'src/app/webbrowser/HistoryViewWithExpansion.qml' | |||
2184 | --- src/app/webbrowser/HistoryViewWithExpansion.qml 1970-01-01 00:00:00 +0000 | |||
2185 | +++ src/app/webbrowser/HistoryViewWithExpansion.qml 2016-10-13 14:41:36 +0000 | |||
2186 | @@ -0,0 +1,67 @@ | |||
2187 | 1 | /* | ||
2188 | 2 | * Copyright 2014-2016 Canonical Ltd. | ||
2189 | 3 | * | ||
2190 | 4 | * This file is part of webbrowser-app. | ||
2191 | 5 | * | ||
2192 | 6 | * webbrowser-app is free software; you can redistribute it and/or modify | ||
2193 | 7 | * it under the terms of the GNU General Public License as published by | ||
2194 | 8 | * the Free Software Foundation; version 3. | ||
2195 | 9 | * | ||
2196 | 10 | * webbrowser-app is distributed in the hope that it will be useful, | ||
2197 | 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
2198 | 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
2199 | 13 | * GNU General Public License for more details. | ||
2200 | 14 | * | ||
2201 | 15 | * You should have received a copy of the GNU General Public License | ||
2202 | 16 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
2203 | 17 | */ | ||
2204 | 18 | |||
2205 | 19 | import QtQuick 2.4 | ||
2206 | 20 | import Ubuntu.Components 1.3 | ||
2207 | 21 | |||
2208 | 22 | FocusScope { | ||
2209 | 23 | id: historyViewWithExpansion | ||
2210 | 24 | |||
2211 | 25 | function loadModel() { | ||
2212 | 26 | historyView.loadModel() | ||
2213 | 27 | } | ||
2214 | 28 | |||
2215 | 29 | signal newTabRequested() | ||
2216 | 30 | signal historyEntryClicked(url url) | ||
2217 | 31 | signal done() | ||
2218 | 32 | signal back() | ||
2219 | 33 | |||
2220 | 34 | HistoryView { | ||
2221 | 35 | id: historyView | ||
2222 | 36 | anchors.fill: parent | ||
2223 | 37 | focus: !expandedHistoryViewLoader.focus | ||
2224 | 38 | visible: focus | ||
2225 | 39 | onSeeMoreEntriesClicked: { | ||
2226 | 40 | expandedHistoryViewLoader.model = model | ||
2227 | 41 | expandedHistoryViewLoader.active = true | ||
2228 | 42 | } | ||
2229 | 43 | onNewTabRequested: historyViewWithExpansion.newTabRequested() | ||
2230 | 44 | onBack: historyViewWithExpansion.back() | ||
2231 | 45 | } | ||
2232 | 46 | |||
2233 | 47 | Loader { | ||
2234 | 48 | id: expandedHistoryViewLoader | ||
2235 | 49 | asynchronous: true | ||
2236 | 50 | anchors.fill: parent | ||
2237 | 51 | active: false | ||
2238 | 52 | focus: active | ||
2239 | 53 | property var model: null | ||
2240 | 54 | sourceComponent: ExpandedHistoryView { | ||
2241 | 55 | focus: true | ||
2242 | 56 | model: expandedHistoryViewLoader.model | ||
2243 | 57 | onHistoryEntryClicked: historyViewWithExpansion.historyEntryClicked(url) | ||
2244 | 58 | onHistoryEntryRemoved: { | ||
2245 | 59 | if (count == 1) { | ||
2246 | 60 | done() | ||
2247 | 61 | } | ||
2248 | 62 | HistoryModel.removeEntryByUrl(url) | ||
2249 | 63 | } | ||
2250 | 64 | onDone: expandedHistoryViewLoader.active = false | ||
2251 | 65 | } | ||
2252 | 66 | } | ||
2253 | 67 | } | ||
2254 | 0 | 68 | ||
2255 | === modified file 'src/app/webbrowser/NavigationBar.qml' | |||
2256 | --- src/app/webbrowser/NavigationBar.qml 2016-05-17 17:23:42 +0000 | |||
2257 | +++ src/app/webbrowser/NavigationBar.qml 2016-10-13 14:41:36 +0000 | |||
2258 | @@ -24,6 +24,7 @@ | |||
2259 | 24 | id: root | 24 | id: root |
2260 | 25 | 25 | ||
2261 | 26 | property var tab | 26 | property var tab |
2262 | 27 | property alias loading: addressbar.loading | ||
2263 | 27 | property alias searchUrl: addressbar.searchUrl | 28 | property alias searchUrl: addressbar.searchUrl |
2264 | 28 | readonly property string text: addressbar.text | 29 | readonly property string text: addressbar.text |
2265 | 29 | property alias bookmarked: addressbar.bookmarked | 30 | property alias bookmarked: addressbar.bookmarked |
2266 | @@ -122,8 +123,6 @@ | |||
2267 | 122 | 123 | ||
2268 | 123 | icon: (internal.webview && internal.webview.certificateError) ? "" : tab ? tab.icon : "" | 124 | icon: (internal.webview && internal.webview.certificateError) ? "" : tab ? tab.icon : "" |
2269 | 124 | 125 | ||
2270 | 125 | loading: internal.webview ? internal.webview.loading : false | ||
2271 | 126 | |||
2272 | 127 | onValidated: { | 126 | onValidated: { |
2273 | 128 | if (!findInPageMode) { | 127 | if (!findInPageMode) { |
2274 | 129 | internal.webview.forceActiveFocus() | 128 | internal.webview.forceActiveFocus() |
2275 | @@ -138,15 +137,8 @@ | |||
2276 | 138 | onToggleBookmark: root.toggleBookmark() | 137 | onToggleBookmark: root.toggleBookmark() |
2277 | 139 | 138 | ||
2278 | 140 | Connections { | 139 | Connections { |
2288 | 141 | target: internal.webview | 140 | target: tab |
2289 | 142 | onUrlChanged: { | 141 | onUrlChanged: addressbar.actualUrl = tab.url |
2281 | 143 | // ensure that the URL actually changes so that the | ||
2282 | 144 | // address bar is updated in case the user has entered a | ||
2283 | 145 | // new address that redirects to where she previously was | ||
2284 | 146 | // (https://launchpad.net/bugs/1306615) | ||
2285 | 147 | addressbar.actualUrl = "" | ||
2286 | 148 | addressbar.actualUrl = internal.webview.url | ||
2287 | 149 | } | ||
2290 | 150 | } | 142 | } |
2291 | 151 | } | 143 | } |
2292 | 152 | 144 | ||
2293 | 153 | 145 | ||
2294 | === added file 'src/app/webbrowser/TabComponent.qml' | |||
2295 | --- src/app/webbrowser/TabComponent.qml 1970-01-01 00:00:00 +0000 | |||
2296 | +++ src/app/webbrowser/TabComponent.qml 2016-10-13 14:41:36 +0000 | |||
2297 | @@ -0,0 +1,436 @@ | |||
2298 | 1 | /* | ||
2299 | 2 | * Copyright 2014-2016 Canonical Ltd. | ||
2300 | 3 | * | ||
2301 | 4 | * This file is part of webbrowser-app. | ||
2302 | 5 | * | ||
2303 | 6 | * webbrowser-app is free software; you can redistribute it and/or modify | ||
2304 | 7 | * it under the terms of the GNU General Public License as published by | ||
2305 | 8 | * the Free Software Foundation; version 3. | ||
2306 | 9 | * | ||
2307 | 10 | * webbrowser-app is distributed in the hope that it will be useful, | ||
2308 | 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
2309 | 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
2310 | 13 | * GNU General Public License for more details. | ||
2311 | 14 | * | ||
2312 | 15 | * You should have received a copy of the GNU General Public License | ||
2313 | 16 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
2314 | 17 | */ | ||
2315 | 18 | |||
2316 | 19 | import QtQuick 2.4 | ||
2317 | 20 | import Ubuntu.Components 1.3 | ||
2318 | 21 | import Ubuntu.Components.Popups 1.3 | ||
2319 | 22 | import com.canonical.Oxide 1.15 as Oxide | ||
2320 | 23 | import webbrowserapp.private 0.1 | ||
2321 | 24 | import "../actions" as Actions | ||
2322 | 25 | import ".." | ||
2323 | 26 | |||
2324 | 27 | // FIXME: This component breaks encapsulation: it uses variables not defined in | ||
2325 | 28 | // itself. However this is an acceptable tradeoff with regards to | ||
2326 | 29 | // startup time performance. Indeed having this component defined as a separate | ||
2327 | 30 | // QML file as opposed to inline makes it possible to cache its compiled form. | ||
2328 | 31 | |||
2329 | 32 | Component { | ||
2330 | 33 | id: tabComponent | ||
2331 | 34 | |||
2332 | 35 | BrowserTab { | ||
2333 | 36 | anchors.fill: parent | ||
2334 | 37 | incognito: browser.incognito | ||
2335 | 38 | current: tabsModel && tabsModel.currentTab === this | ||
2336 | 39 | focus: current | ||
2337 | 40 | |||
2338 | 41 | Item { | ||
2339 | 42 | id: contextualMenuTarget | ||
2340 | 43 | visible: false | ||
2341 | 44 | } | ||
2342 | 45 | |||
2343 | 46 | webviewComponent: WebViewImpl { | ||
2344 | 47 | id: webviewimpl | ||
2345 | 48 | |||
2346 | 49 | property BrowserTab tab | ||
2347 | 50 | readonly property bool current: tab.current | ||
2348 | 51 | |||
2349 | 52 | currentWebview: browser.currentWebview | ||
2350 | 53 | filePicker: filePickerLoader.item | ||
2351 | 54 | |||
2352 | 55 | anchors.fill: parent | ||
2353 | 56 | |||
2354 | 57 | focus: true | ||
2355 | 58 | |||
2356 | 59 | enabled: current && !bottomEdgeHandle.dragging && !recentView.visible && tabContainer.focus | ||
2357 | 60 | |||
2358 | 61 | locationBarController { | ||
2359 | 62 | height: chrome.height | ||
2360 | 63 | mode: chromeController.defaultMode | ||
2361 | 64 | } | ||
2362 | 65 | |||
2363 | 66 | //experimental.preferences.developerExtrasEnabled: developerExtrasEnabled | ||
2364 | 67 | preferences.localStorageEnabled: true | ||
2365 | 68 | preferences.appCacheEnabled: true | ||
2366 | 69 | |||
2367 | 70 | property QtObject contextModel: null | ||
2368 | 71 | contextualActions: ActionList { | ||
2369 | 72 | Actions.OpenLinkInNewTab { | ||
2370 | 73 | objectName: "OpenLinkInNewTabContextualAction" | ||
2371 | 74 | enabled: contextModel && contextModel.linkUrl.toString() | ||
2372 | 75 | onTriggered: internal.openUrlInNewTab(contextModel.linkUrl, true) | ||
2373 | 76 | } | ||
2374 | 77 | Actions.OpenLinkInNewBackgroundTab { | ||
2375 | 78 | objectName: "OpenLinkInNewBackgroundTabContextualAction" | ||
2376 | 79 | enabled: contextModel && contextModel.linkUrl.toString() | ||
2377 | 80 | onTriggered: internal.openUrlInNewTab(contextModel.linkUrl, false) | ||
2378 | 81 | } | ||
2379 | 82 | Actions.OpenLinkInNewWindow { | ||
2380 | 83 | objectName: "OpenLinkInNewWindowContextualAction" | ||
2381 | 84 | enabled: contextModel && contextModel.linkUrl.toString() | ||
2382 | 85 | onTriggered: browser.openLinkInWindowRequested(contextModel.linkUrl, false) | ||
2383 | 86 | } | ||
2384 | 87 | Actions.OpenLinkInPrivateWindow { | ||
2385 | 88 | objectName: "OpenLinkInPrivateWindowContextualAction" | ||
2386 | 89 | enabled: contextModel && contextModel.linkUrl.toString() | ||
2387 | 90 | onTriggered: browser.openLinkInWindowRequested(contextModel.linkUrl, true) | ||
2388 | 91 | } | ||
2389 | 92 | Actions.BookmarkLink { | ||
2390 | 93 | objectName: "BookmarkLinkContextualAction" | ||
2391 | 94 | enabled: contextModel && contextModel.linkUrl.toString() | ||
2392 | 95 | && !BookmarksModel.contains(contextModel.linkUrl) | ||
2393 | 96 | onTriggered: { | ||
2394 | 97 | // position the menu target with a one-off assignement instead of a binding | ||
2395 | 98 | // since the contents of the contextModel have meaning only while the context | ||
2396 | 99 | // menu is active | ||
2397 | 100 | contextualMenuTarget.x = contextModel.position.x | ||
2398 | 101 | contextualMenuTarget.y = contextModel.position.y + locationBarController.height + locationBarController.offset | ||
2399 | 102 | internal.addBookmark(contextModel.linkUrl, contextModel.linkText, | ||
2400 | 103 | "", contextualMenuTarget) | ||
2401 | 104 | } | ||
2402 | 105 | } | ||
2403 | 106 | Actions.CopyLink { | ||
2404 | 107 | objectName: "CopyLinkContextualAction" | ||
2405 | 108 | enabled: contextModel && contextModel.linkUrl.toString() | ||
2406 | 109 | onTriggered: Clipboard.push(["text/plain", contextModel.linkUrl.toString()]) | ||
2407 | 110 | } | ||
2408 | 111 | Actions.SaveLink { | ||
2409 | 112 | objectName: "SaveLinkContextualAction" | ||
2410 | 113 | enabled: contextModel && contextModel.linkUrl.toString() | ||
2411 | 114 | onTriggered: contextModel.saveLink() | ||
2412 | 115 | } | ||
2413 | 116 | Actions.Share { | ||
2414 | 117 | objectName: "ShareContextualAction" | ||
2415 | 118 | enabled: (contentHandlerLoader.status == Loader.Ready) && contextModel && | ||
2416 | 119 | (contextModel.linkUrl.toString() || contextModel.selectionText) | ||
2417 | 120 | onTriggered: { | ||
2418 | 121 | if (contextModel.linkUrl.toString()) { | ||
2419 | 122 | internal.shareLink(contextModel.linkUrl.toString(), contextModel.linkText) | ||
2420 | 123 | } else if (contextModel.selectionText) { | ||
2421 | 124 | internal.shareText(contextModel.selectionText) | ||
2422 | 125 | } | ||
2423 | 126 | } | ||
2424 | 127 | } | ||
2425 | 128 | Actions.OpenImageInNewTab { | ||
2426 | 129 | objectName: "OpenImageInNewTabContextualAction" | ||
2427 | 130 | enabled: contextModel && | ||
2428 | 131 | (contextModel.mediaType === Oxide.WebView.MediaTypeImage) && | ||
2429 | 132 | contextModel.srcUrl.toString() | ||
2430 | 133 | onTriggered: internal.openUrlInNewTab(contextModel.srcUrl, true) | ||
2431 | 134 | } | ||
2432 | 135 | Actions.CopyImage { | ||
2433 | 136 | objectName: "CopyImageContextualAction" | ||
2434 | 137 | enabled: contextModel && | ||
2435 | 138 | (contextModel.mediaType === Oxide.WebView.MediaTypeImage) && | ||
2436 | 139 | contextModel.srcUrl.toString() | ||
2437 | 140 | onTriggered: Clipboard.push(["text/plain", contextModel.srcUrl.toString()]) | ||
2438 | 141 | } | ||
2439 | 142 | Actions.SaveImage { | ||
2440 | 143 | objectName: "SaveImageContextualAction" | ||
2441 | 144 | enabled: contextModel && | ||
2442 | 145 | ((contextModel.mediaType === Oxide.WebView.MediaTypeImage) || | ||
2443 | 146 | (contextModel.mediaType === Oxide.WebView.MediaTypeCanvas)) && | ||
2444 | 147 | contextModel.hasImageContents | ||
2445 | 148 | onTriggered: contextModel.saveMedia() | ||
2446 | 149 | } | ||
2447 | 150 | Actions.OpenVideoInNewTab { | ||
2448 | 151 | objectName: "OpenVideoInNewTabContextualAction" | ||
2449 | 152 | enabled: contextModel && | ||
2450 | 153 | (contextModel.mediaType === Oxide.WebView.MediaTypeVideo) && | ||
2451 | 154 | contextModel.srcUrl.toString() | ||
2452 | 155 | onTriggered: internal.openUrlInNewTab(contextModel.srcUrl, true) | ||
2453 | 156 | } | ||
2454 | 157 | Actions.SaveVideo { | ||
2455 | 158 | objectName: "SaveVideoContextualAction" | ||
2456 | 159 | enabled: contextModel && | ||
2457 | 160 | (contextModel.mediaType === Oxide.WebView.MediaTypeVideo) && | ||
2458 | 161 | contextModel.srcUrl.toString() | ||
2459 | 162 | onTriggered: contextModel.saveMedia() | ||
2460 | 163 | } | ||
2461 | 164 | Actions.Undo { | ||
2462 | 165 | objectName: "UndoContextualAction" | ||
2463 | 166 | enabled: contextModel && contextModel.isEditable && | ||
2464 | 167 | (contextModel.editFlags & Oxide.WebView.UndoCapability) | ||
2465 | 168 | onTriggered: webviewimpl.executeEditingCommand(Oxide.WebView.EditingCommandUndo) | ||
2466 | 169 | } | ||
2467 | 170 | Actions.Redo { | ||
2468 | 171 | objectName: "RedoContextualAction" | ||
2469 | 172 | enabled: contextModel && contextModel.isEditable && | ||
2470 | 173 | (contextModel.editFlags & Oxide.WebView.RedoCapability) | ||
2471 | 174 | onTriggered: webviewimpl.executeEditingCommand(Oxide.WebView.EditingCommandRedo) | ||
2472 | 175 | } | ||
2473 | 176 | Actions.Cut { | ||
2474 | 177 | objectName: "CutContextualAction" | ||
2475 | 178 | enabled: contextModel && contextModel.isEditable && | ||
2476 | 179 | (contextModel.editFlags & Oxide.WebView.CutCapability) | ||
2477 | 180 | onTriggered: webviewimpl.executeEditingCommand(Oxide.WebView.EditingCommandCut) | ||
2478 | 181 | } | ||
2479 | 182 | Actions.Copy { | ||
2480 | 183 | objectName: "CopyContextualAction" | ||
2481 | 184 | enabled: contextModel && (contextModel.selectionText || | ||
2482 | 185 | (contextModel.isEditable && | ||
2483 | 186 | (contextModel.editFlags & Oxide.WebView.CopyCapability))) | ||
2484 | 187 | onTriggered: webviewimpl.executeEditingCommand(Oxide.WebView.EditingCommandCopy) | ||
2485 | 188 | } | ||
2486 | 189 | Actions.Paste { | ||
2487 | 190 | objectName: "PasteContextualAction" | ||
2488 | 191 | enabled: contextModel && contextModel.isEditable && | ||
2489 | 192 | (contextModel.editFlags & Oxide.WebView.PasteCapability) | ||
2490 | 193 | onTriggered: webviewimpl.executeEditingCommand(Oxide.WebView.EditingCommandPaste) | ||
2491 | 194 | } | ||
2492 | 195 | Actions.Erase { | ||
2493 | 196 | objectName: "EraseContextualAction" | ||
2494 | 197 | enabled: contextModel && contextModel.isEditable && | ||
2495 | 198 | (contextModel.editFlags & Oxide.WebView.EraseCapability) | ||
2496 | 199 | onTriggered: webviewimpl.executeEditingCommand(Oxide.WebView.EditingCommandErase) | ||
2497 | 200 | } | ||
2498 | 201 | Actions.SelectAll { | ||
2499 | 202 | objectName: "SelectAllContextualAction" | ||
2500 | 203 | enabled: contextModel && contextModel.isEditable && | ||
2501 | 204 | (contextModel.editFlags & Oxide.WebView.SelectAllCapability) | ||
2502 | 205 | onTriggered: webviewimpl.executeEditingCommand(Oxide.WebView.EditingCommandSelectAll) | ||
2503 | 206 | } | ||
2504 | 207 | } | ||
2505 | 208 | |||
2506 | 209 | function contextMenuOnCompleted(menu) { | ||
2507 | 210 | contextModel = menu.contextModel | ||
2508 | 211 | if (contextModel.linkUrl.toString() || | ||
2509 | 212 | contextModel.srcUrl.toString() || | ||
2510 | 213 | contextModel.selectionText || | ||
2511 | 214 | (contextModel.isEditable && contextModel.editFlags) || | ||
2512 | 215 | (((contextModel.mediaType == Oxide.WebView.MediaTypeImage) || | ||
2513 | 216 | (contextModel.mediaType == Oxide.WebView.MediaTypeCanvas)) && | ||
2514 | 217 | contextModel.hasImageContents)) { | ||
2515 | 218 | menu.show() | ||
2516 | 219 | } else { | ||
2517 | 220 | contextModel.close() | ||
2518 | 221 | } | ||
2519 | 222 | } | ||
2520 | 223 | |||
2521 | 224 | Component { | ||
2522 | 225 | id: contextMenuNarrowComponent | ||
2523 | 226 | ContextMenuMobile { | ||
2524 | 227 | actions: contextualActions | ||
2525 | 228 | Component.onCompleted: webviewimpl.contextMenuOnCompleted(this) | ||
2526 | 229 | } | ||
2527 | 230 | } | ||
2528 | 231 | Component { | ||
2529 | 232 | id: contextMenuWideComponent | ||
2530 | 233 | ContextMenuWide { | ||
2531 | 234 | webview: webviewimpl | ||
2532 | 235 | parent: browser | ||
2533 | 236 | actions: contextualActions | ||
2534 | 237 | Component.onCompleted: webviewimpl.contextMenuOnCompleted(this) | ||
2535 | 238 | } | ||
2536 | 239 | } | ||
2537 | 240 | contextMenu: browser.wide ? contextMenuWideComponent : contextMenuNarrowComponent | ||
2538 | 241 | |||
2539 | 242 | onNewViewRequested: { | ||
2540 | 243 | var tab = tabComponent.createObject(tabContainer, {"request": request}) | ||
2541 | 244 | var setCurrent = (request.disposition == Oxide.NewViewRequest.DispositionNewForegroundTab) | ||
2542 | 245 | internal.addTab(tab, setCurrent) | ||
2543 | 246 | if (setCurrent) tabContainer.forceActiveFocus() | ||
2544 | 247 | } | ||
2545 | 248 | |||
2546 | 249 | onCloseRequested: prepareToClose() | ||
2547 | 250 | onPrepareToCloseResponse: { | ||
2548 | 251 | if (proceed) { | ||
2549 | 252 | if (tab) { | ||
2550 | 253 | for (var i = 0; i < tabsModel.count; ++i) { | ||
2551 | 254 | if (tabsModel.get(i) === tab) { | ||
2552 | 255 | tabsModel.remove(i) | ||
2553 | 256 | break | ||
2554 | 257 | } | ||
2555 | 258 | } | ||
2556 | 259 | tab.close() | ||
2557 | 260 | } | ||
2558 | 261 | if (tabsModel.count === 0) { | ||
2559 | 262 | internal.openUrlInNewTab("", true, true) | ||
2560 | 263 | } | ||
2561 | 264 | } | ||
2562 | 265 | } | ||
2563 | 266 | |||
2564 | 267 | QtObject { | ||
2565 | 268 | id: webviewInternal | ||
2566 | 269 | property url storedUrl: "" | ||
2567 | 270 | property bool titleSet: false | ||
2568 | 271 | property string title: "" | ||
2569 | 272 | } | ||
2570 | 273 | onLoadEvent: { | ||
2571 | 274 | if (event.type == Oxide.LoadEvent.TypeCommitted) { | ||
2572 | 275 | chrome.findInPageMode = false | ||
2573 | 276 | webviewInternal.titleSet = false | ||
2574 | 277 | webviewInternal.title = title | ||
2575 | 278 | } | ||
2576 | 279 | |||
2577 | 280 | if (webviewimpl.incognito) { | ||
2578 | 281 | return | ||
2579 | 282 | } | ||
2580 | 283 | |||
2581 | 284 | if ((event.type == Oxide.LoadEvent.TypeCommitted) && | ||
2582 | 285 | !event.isError && | ||
2583 | 286 | (300 > event.httpStatusCode) && (event.httpStatusCode >= 200)) { | ||
2584 | 287 | webviewInternal.storedUrl = event.url | ||
2585 | 288 | HistoryModel.add(event.url, title, icon) | ||
2586 | 289 | } | ||
2587 | 290 | |||
2588 | 291 | // If the page has started, stopped, redirected, errored | ||
2589 | 292 | // then clear the cache for the history update | ||
2590 | 293 | // Otherwise if no title change has occurred the next title | ||
2591 | 294 | // change will be the url of the next page causing the | ||
2592 | 295 | // history entry to be incorrect (pad.lv/1603835) | ||
2593 | 296 | if (event.type == Oxide.LoadEvent.TypeFailed || | ||
2594 | 297 | event.type == Oxide.LoadEvent.TypeRedirected || | ||
2595 | 298 | event.type == Oxide.LoadEvent.TypeStarted || | ||
2596 | 299 | event.type == Oxide.LoadEvent.TypeStopped) { | ||
2597 | 300 | webviewInternal.titleSet = true | ||
2598 | 301 | webviewInternal.storedUrl = "" | ||
2599 | 302 | } | ||
2600 | 303 | } | ||
2601 | 304 | onTitleChanged: { | ||
2602 | 305 | if (!webviewInternal.titleSet && webviewInternal.storedUrl.toString()) { | ||
2603 | 306 | // Record the title to avoid updating the history database | ||
2604 | 307 | // every time the page dynamically updates its title. | ||
2605 | 308 | // We don’t want pages that update their title every second | ||
2606 | 309 | // to achieve an ugly "scrolling title" effect to flood the | ||
2607 | 310 | // history database with updates. | ||
2608 | 311 | webviewInternal.titleSet = true | ||
2609 | 312 | if (webviewInternal.title != title) { | ||
2610 | 313 | webviewInternal.title = title | ||
2611 | 314 | HistoryModel.update(webviewInternal.storedUrl, title, icon) | ||
2612 | 315 | } | ||
2613 | 316 | } | ||
2614 | 317 | } | ||
2615 | 318 | onIconChanged: { | ||
2616 | 319 | if (webviewInternal.storedUrl.toString()) { | ||
2617 | 320 | HistoryModel.update(webviewInternal.storedUrl, webviewInternal.title, icon) | ||
2618 | 321 | } | ||
2619 | 322 | } | ||
2620 | 323 | |||
2621 | 324 | onGeolocationPermissionRequested: requestGeolocationPermission(request) | ||
2622 | 325 | |||
2623 | 326 | property var certificateError | ||
2624 | 327 | function resetCertificateError() { | ||
2625 | 328 | certificateError = null | ||
2626 | 329 | } | ||
2627 | 330 | onCertificateError: { | ||
2628 | 331 | if (!error.isMainFrame || error.isSubresource) { | ||
2629 | 332 | // Not a main frame document error, just block the content | ||
2630 | 333 | // (it’s not overridable anyway). | ||
2631 | 334 | return | ||
2632 | 335 | } | ||
2633 | 336 | if (internal.isCertificateErrorAllowed(error)) { | ||
2634 | 337 | error.allow() | ||
2635 | 338 | } else { | ||
2636 | 339 | certificateError = error | ||
2637 | 340 | error.onCancelled.connect(webviewimpl.resetCertificateError) | ||
2638 | 341 | } | ||
2639 | 342 | } | ||
2640 | 343 | |||
2641 | 344 | onFullscreenChanged: { | ||
2642 | 345 | if (fullscreen) { | ||
2643 | 346 | fullscreenExitHintComponent.createObject(webviewimpl) | ||
2644 | 347 | } | ||
2645 | 348 | } | ||
2646 | 349 | Component { | ||
2647 | 350 | id: fullscreenExitHintComponent | ||
2648 | 351 | |||
2649 | 352 | Rectangle { | ||
2650 | 353 | id: fullscreenExitHint | ||
2651 | 354 | objectName: "fullscreenExitHint" | ||
2652 | 355 | |||
2653 | 356 | anchors.centerIn: parent | ||
2654 | 357 | height: units.gu(6) | ||
2655 | 358 | width: Math.min(units.gu(50), parent.width - units.gu(12)) | ||
2656 | 359 | radius: units.gu(1) | ||
2657 | 360 | color: "#3e3b39" | ||
2658 | 361 | opacity: 0.85 | ||
2659 | 362 | |||
2660 | 363 | Behavior on opacity { | ||
2661 | 364 | UbuntuNumberAnimation { | ||
2662 | 365 | duration: UbuntuAnimation.SlowDuration | ||
2663 | 366 | } | ||
2664 | 367 | } | ||
2665 | 368 | onOpacityChanged: { | ||
2666 | 369 | if (opacity == 0.0) { | ||
2667 | 370 | fullscreenExitHint.destroy() | ||
2668 | 371 | } | ||
2669 | 372 | } | ||
2670 | 373 | |||
2671 | 374 | // Delay showing the hint to prevent it from jumping up while the | ||
2672 | 375 | // webview is being resized (https://launchpad.net/bugs/1454097). | ||
2673 | 376 | visible: false | ||
2674 | 377 | Timer { | ||
2675 | 378 | running: true | ||
2676 | 379 | interval: 250 | ||
2677 | 380 | onTriggered: fullscreenExitHint.visible = true | ||
2678 | 381 | } | ||
2679 | 382 | |||
2680 | 383 | Label { | ||
2681 | 384 | color: "white" | ||
2682 | 385 | font.weight: Font.Light | ||
2683 | 386 | anchors.centerIn: parent | ||
2684 | 387 | text: bottomEdgeHandle.enabled | ||
2685 | 388 | ? i18n.tr("Swipe Up To Exit Full Screen") | ||
2686 | 389 | : i18n.tr("Press ESC To Exit Full Screen") | ||
2687 | 390 | } | ||
2688 | 391 | |||
2689 | 392 | Timer { | ||
2690 | 393 | running: fullscreenExitHint.visible | ||
2691 | 394 | interval: 2000 | ||
2692 | 395 | onTriggered: fullscreenExitHint.opacity = 0 | ||
2693 | 396 | } | ||
2694 | 397 | |||
2695 | 398 | Connections { | ||
2696 | 399 | target: webviewimpl | ||
2697 | 400 | onFullscreenChanged: { | ||
2698 | 401 | if (!webviewimpl.fullscreen) { | ||
2699 | 402 | fullscreenExitHint.destroy() | ||
2700 | 403 | } | ||
2701 | 404 | } | ||
2702 | 405 | } | ||
2703 | 406 | |||
2704 | 407 | Component.onCompleted: bottomEdgeHint.forceShow = true | ||
2705 | 408 | Component.onDestruction: bottomEdgeHint.forceShow = false | ||
2706 | 409 | } | ||
2707 | 410 | } | ||
2708 | 411 | |||
2709 | 412 | onShowDownloadDialog: { | ||
2710 | 413 | if (downloadDialogLoader.status === Loader.Ready) { | ||
2711 | 414 | var downloadDialog = PopupUtils.open(downloadDialogLoader.item, browser, {"contentType" : contentType, | ||
2712 | 415 | "downloadId" : downloadId, | ||
2713 | 416 | "singleDownload" : downloader, | ||
2714 | 417 | "filename" : filename, | ||
2715 | 418 | "mimeType" : mimeType}) | ||
2716 | 419 | downloadDialog.startDownload.connect(startDownload) | ||
2717 | 420 | } | ||
2718 | 421 | } | ||
2719 | 422 | |||
2720 | 423 | function showDownloadsPage() { | ||
2721 | 424 | downloadsViewLoader.active = true | ||
2722 | 425 | return downloadsViewLoader.item | ||
2723 | 426 | } | ||
2724 | 427 | |||
2725 | 428 | function startDownload(downloadId, download, mimeType) { | ||
2726 | 429 | DownloadsModel.add(downloadId, download.url, mimeType, incognito) | ||
2727 | 430 | download.start() | ||
2728 | 431 | downloadsViewLoader.active = true | ||
2729 | 432 | } | ||
2730 | 433 | |||
2731 | 434 | } | ||
2732 | 435 | } | ||
2733 | 436 | } | ||
2734 | 0 | 437 | ||
2735 | === modified file 'src/app/webbrowser/TabItem.qml' | |||
2736 | --- src/app/webbrowser/TabItem.qml 2016-07-01 08:52:37 +0000 | |||
2737 | +++ src/app/webbrowser/TabItem.qml 2016-10-13 14:41:36 +0000 | |||
2738 | @@ -22,6 +22,7 @@ | |||
2739 | 22 | 22 | ||
2740 | 23 | Item { | 23 | Item { |
2741 | 24 | id: tabItem | 24 | id: tabItem |
2742 | 25 | objectName: "tabItem" | ||
2743 | 25 | 26 | ||
2744 | 26 | property bool incognito: false | 27 | property bool incognito: false |
2745 | 27 | property bool active: false | 28 | property bool active: false |
2746 | @@ -38,6 +39,8 @@ | |||
2747 | 38 | property color fgColor: Theme.palette.normal.baseText | 39 | property color fgColor: Theme.palette.normal.baseText |
2748 | 39 | 40 | ||
2749 | 40 | property bool touchEnabled: true | 41 | property bool touchEnabled: true |
2750 | 42 | |||
2751 | 43 | readonly property bool showCloseIcon: closeIcon.x > units.gu(1) + tabItem.width / 2 | ||
2752 | 41 | 44 | ||
2753 | 42 | signal selected() | 45 | signal selected() |
2754 | 43 | signal closed() | 46 | signal closed() |
2755 | @@ -54,10 +57,17 @@ | |||
2756 | 54 | 57 | ||
2757 | 55 | Favicon { | 58 | Favicon { |
2758 | 56 | id: favicon | 59 | id: favicon |
2762 | 57 | anchors.verticalCenter: parent.verticalCenter | 60 | anchors { |
2763 | 58 | anchors.left: parent.left | 61 | left: tabItem.showCloseIcon ? parent.left : undefined |
2764 | 59 | anchors.leftMargin: units.gu(2) | 62 | leftMargin: Math.min(tabItem.width / 4, units.gu(2)) |
2765 | 63 | horizontalCenter: tabItem.showCloseIcon ? undefined : parent.horizontalCenter | ||
2766 | 64 | verticalCenter: parent.verticalCenter | ||
2767 | 65 | } | ||
2768 | 60 | shouldCache: !incognito | 66 | shouldCache: !incognito |
2769 | 67 | |||
2770 | 68 | // Scale width and height of favicon when tabWidth becomes small | ||
2771 | 69 | height: width | ||
2772 | 70 | width: Math.min(units.dp(16), Math.min(tabItem.width - anchors.leftMargin * 2, tabItem.height)) | ||
2773 | 61 | } | 71 | } |
2774 | 62 | 72 | ||
2775 | 63 | Item { | 73 | Item { |
2776 | @@ -132,6 +142,7 @@ | |||
2777 | 132 | anchors.bottom: touchEnabled ? parent.bottom : undefined | 142 | anchors.bottom: touchEnabled ? parent.bottom : undefined |
2778 | 133 | anchors.right: touchEnabled ? parent.right : undefined | 143 | anchors.right: touchEnabled ? parent.right : undefined |
2779 | 134 | width: touchEnabled ? units.gu(4) : closeIcon.width | 144 | width: touchEnabled ? units.gu(4) : closeIcon.width |
2780 | 145 | visible: closeIcon.visible | ||
2781 | 135 | 146 | ||
2782 | 136 | onClicked: closed() | 147 | onClicked: closed() |
2783 | 137 | 148 | ||
2784 | @@ -149,9 +160,10 @@ | |||
2785 | 149 | anchors.right: parent.right | 160 | anchors.right: parent.right |
2786 | 150 | anchors.rightMargin: units.gu(1) | 161 | anchors.rightMargin: units.gu(1) |
2787 | 151 | anchors.verticalCenter: parent.verticalCenter | 162 | anchors.verticalCenter: parent.verticalCenter |
2788 | 163 | asynchronous: true | ||
2789 | 152 | name: "close" | 164 | name: "close" |
2790 | 153 | color: tabItem.fgColor | 165 | color: tabItem.fgColor |
2792 | 154 | asynchronous: true | 166 | visible: tabItem.showCloseIcon |
2793 | 155 | } | 167 | } |
2794 | 156 | } | 168 | } |
2795 | 157 | } | 169 | } |
2796 | 158 | 170 | ||
2797 | === modified file 'src/app/webbrowser/TabsBar.qml' | |||
2798 | --- src/app/webbrowser/TabsBar.qml 2016-02-05 11:21:32 +0000 | |||
2799 | +++ src/app/webbrowser/TabsBar.qml 2016-10-13 14:41:36 +0000 | |||
2800 | @@ -30,6 +30,18 @@ | |||
2801 | 30 | property real maxTabWidth: units.gu(20) | 30 | property real maxTabWidth: units.gu(20) |
2802 | 31 | property real tabWidth: model ? Math.max(Math.min(tabsContainer.maxWidth / model.count, maxTabWidth), minTabWidth) : 0 | 31 | property real tabWidth: model ? Math.max(Math.min(tabsContainer.maxWidth / model.count, maxTabWidth), minTabWidth) : 0 |
2803 | 32 | 32 | ||
2804 | 33 | // Minimum size of the larger tab | ||
2805 | 34 | readonly property real minActiveTabWidth: units.gu(10) | ||
2806 | 35 | |||
2807 | 36 | // When there is a larger tab, calc the smaller tab size | ||
2808 | 37 | readonly property real nonActiveTabWidth: (tabsContainer.maxWidth - minActiveTabWidth) / Math.max(model.count - 1, 1) | ||
2809 | 38 | |||
2810 | 39 | // The size of the right margin of the tab | ||
2811 | 40 | readonly property real rightMargin: units.dp(1) | ||
2812 | 41 | |||
2813 | 42 | // Whether there will be one larger tab or not | ||
2814 | 43 | readonly property bool unevenTabWidth: tabWidth + rightMargin < minActiveTabWidth | ||
2815 | 44 | |||
2816 | 33 | property bool incognito: false | 45 | property bool incognito: false |
2817 | 34 | 46 | ||
2818 | 35 | property color fgColor: Theme.palette.normal.baseText | 47 | property color fgColor: Theme.palette.normal.baseText |
2819 | @@ -130,8 +142,8 @@ | |||
2820 | 130 | readonly property int tabIndex: index | 142 | readonly property int tabIndex: index |
2821 | 131 | 143 | ||
2822 | 132 | anchors.top: tabsContainer.top | 144 | anchors.top: tabsContainer.top |
2825 | 133 | property real rightMargin: units.dp(1) | 145 | |
2826 | 134 | width: tabWidth + rightMargin | 146 | width: getSize(index) |
2827 | 135 | height: tabsContainer.height | 147 | height: tabsContainer.height |
2828 | 136 | 148 | ||
2829 | 137 | acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton | 149 | acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton |
2830 | @@ -156,7 +168,7 @@ | |||
2831 | 156 | 168 | ||
2832 | 157 | touchEnabled: root.touchEnabled | 169 | touchEnabled: root.touchEnabled |
2833 | 158 | 170 | ||
2835 | 159 | rightMargin: tabDelegate.rightMargin | 171 | rightMargin: root.rightMargin |
2836 | 160 | 172 | ||
2837 | 161 | onClosed: root.tabClosed(index) | 173 | onClosed: root.tabClosed(index) |
2838 | 162 | onSelected: root.switchToTab(index) | 174 | onSelected: root.switchToTab(index) |
2839 | @@ -168,19 +180,52 @@ | |||
2840 | 168 | property: "reordering" | 180 | property: "reordering" |
2841 | 169 | value: dragging | 181 | value: dragging |
2842 | 170 | } | 182 | } |
2843 | 183 | |||
2844 | 184 | Behavior on width { NumberAnimation { duration: 250 } } | ||
2845 | 171 | 185 | ||
2846 | 172 | Binding on x { | 186 | Binding on x { |
2847 | 173 | when: !dragging | 187 | when: !dragging |
2849 | 174 | value: index * width | 188 | value: getLeftX(index) |
2850 | 175 | } | 189 | } |
2851 | 176 | 190 | ||
2852 | 177 | Behavior on x { NumberAnimation { duration: 250 } } | 191 | Behavior on x { NumberAnimation { duration: 250 } } |
2853 | 178 | 192 | ||
2854 | 193 | function getLeftX(index) { | ||
2855 | 194 | if (unevenTabWidth) { | ||
2856 | 195 | if (index > root.model.currentIndex) { | ||
2857 | 196 | return minActiveTabWidth + (nonActiveTabWidth * (index - 1)) | ||
2858 | 197 | } else { | ||
2859 | 198 | return nonActiveTabWidth * index | ||
2860 | 199 | } | ||
2861 | 200 | } else { | ||
2862 | 201 | // Do not depend on width otherwise X updates after | ||
2863 | 202 | // Width causing the animation to be two stage | ||
2864 | 203 | // instead perform same calculation (tabWidth + rightMargin) | ||
2865 | 204 | return index * (tabWidth + rightMargin) | ||
2866 | 205 | } | ||
2867 | 206 | } | ||
2868 | 207 | |||
2869 | 208 | function getSize(index) { | ||
2870 | 209 | if (unevenTabWidth) { | ||
2871 | 210 | // Uneven tabs so use large or small depending which index | ||
2872 | 211 | if (index === root.model.currentIndex) { | ||
2873 | 212 | return minActiveTabWidth | ||
2874 | 213 | } else { | ||
2875 | 214 | return nonActiveTabWidth | ||
2876 | 215 | } | ||
2877 | 216 | } else { | ||
2878 | 217 | return tabWidth + rightMargin | ||
2879 | 218 | } | ||
2880 | 219 | } | ||
2881 | 220 | |||
2882 | 179 | onXChanged: { | 221 | onXChanged: { |
2883 | 180 | if (!dragging) return | 222 | if (!dragging) return |
2885 | 181 | if (x < (index * width - width / 2)) { | 223 | |
2886 | 224 | var leftX = getLeftX(index) | ||
2887 | 225 | |||
2888 | 226 | if (x < (leftX - getSize(index - 1) / 2) && index > 0) { | ||
2889 | 182 | root.model.move(index, index - 1) | 227 | root.model.move(index, index - 1) |
2891 | 183 | } else if ((x > (index * width + width / 2)) && (index < (root.model.count - 1))) { | 228 | } else if ((x > (leftX + getSize(index + 1) / 2)) && (index < (root.model.count - 1))) { |
2892 | 184 | root.model.move(index + 1, index) | 229 | root.model.move(index + 1, index) |
2893 | 185 | } | 230 | } |
2894 | 186 | } | 231 | } |
2895 | 187 | 232 | ||
2896 | === modified file 'src/app/webbrowser/downloads-model.cpp' | |||
2897 | --- src/app/webbrowser/downloads-model.cpp 2016-07-06 09:31:57 +0000 | |||
2898 | +++ src/app/webbrowser/downloads-model.cpp 2016-10-13 14:41:36 +0000 | |||
2899 | @@ -104,6 +104,7 @@ | |||
2900 | 104 | int count = 0; // size() isn't supported on the sqlite backend | 104 | int count = 0; // size() isn't supported on the sqlite backend |
2901 | 105 | while (populateQuery.next()) { | 105 | while (populateQuery.next()) { |
2902 | 106 | DownloadEntry entry; | 106 | DownloadEntry entry; |
2903 | 107 | entry.incognito = false; | ||
2904 | 107 | entry.downloadId = populateQuery.value(0).toString(); | 108 | entry.downloadId = populateQuery.value(0).toString(); |
2905 | 108 | entry.url = populateQuery.value(1).toUrl(); | 109 | entry.url = populateQuery.value(1).toUrl(); |
2906 | 109 | entry.path = populateQuery.value(2).toString(); | 110 | entry.path = populateQuery.value(2).toString(); |
2907 | @@ -147,6 +148,7 @@ | |||
2908 | 147 | roles[Paused] = "paused"; | 148 | roles[Paused] = "paused"; |
2909 | 148 | roles[Error] = "error"; | 149 | roles[Error] = "error"; |
2910 | 149 | roles[Created] = "created"; | 150 | roles[Created] = "created"; |
2911 | 151 | roles[Incognito] = "incognito"; | ||
2912 | 150 | } | 152 | } |
2913 | 151 | return roles; | 153 | return roles; |
2914 | 152 | } | 154 | } |
2915 | @@ -182,6 +184,8 @@ | |||
2916 | 182 | return entry.error; | 184 | return entry.error; |
2917 | 183 | case Created: | 185 | case Created: |
2918 | 184 | return entry.created; | 186 | return entry.created; |
2919 | 187 | case Incognito: | ||
2920 | 188 | return entry.incognito; | ||
2921 | 185 | default: | 189 | default: |
2922 | 186 | return QVariant(); | 190 | return QVariant(); |
2923 | 187 | } | 191 | } |
2924 | @@ -218,7 +222,7 @@ | |||
2925 | 218 | Add a download to the database. This should happen as soon as the download | 222 | Add a download to the database. This should happen as soon as the download |
2926 | 219 | is started. | 223 | is started. |
2927 | 220 | */ | 224 | */ |
2929 | 221 | void DownloadsModel::add(const QString& downloadId, const QUrl& url, const QString& mimetype) | 225 | void DownloadsModel::add(const QString& downloadId, const QUrl& url, const QString& mimetype, bool incognito) |
2930 | 222 | { | 226 | { |
2931 | 223 | beginInsertRows(QModelIndex(), 0, 0); | 227 | beginInsertRows(QModelIndex(), 0, 0); |
2932 | 224 | DownloadEntry entry; | 228 | DownloadEntry entry; |
2933 | @@ -227,83 +231,111 @@ | |||
2934 | 227 | entry.paused = false; | 231 | entry.paused = false; |
2935 | 228 | entry.url = url; | 232 | entry.url = url; |
2936 | 229 | entry.mimetype = mimetype; | 233 | entry.mimetype = mimetype; |
2937 | 234 | entry.incognito = incognito; | ||
2938 | 230 | m_orderedEntries.prepend(entry); | 235 | m_orderedEntries.prepend(entry); |
2939 | 231 | m_numRows++; | 236 | m_numRows++; |
2940 | 232 | m_fetchedCount++; | ||
2941 | 233 | endInsertRows(); | 237 | endInsertRows(); |
2942 | 234 | Q_EMIT added(downloadId, url, mimetype); | ||
2943 | 235 | insertNewEntryInDatabase(entry); | ||
2944 | 236 | Q_EMIT rowCountChanged(); | 238 | Q_EMIT rowCountChanged(); |
2963 | 237 | } | 239 | if (!incognito) { |
2964 | 238 | 240 | insertNewEntryInDatabase(entry); | |
2965 | 239 | void DownloadsModel::setPath(const QString& downloadId, const QString& path) | 241 | m_fetchedCount++; |
2966 | 240 | { | 242 | } |
2949 | 241 | QSqlQuery query(m_database); | ||
2950 | 242 | |||
2951 | 243 | // Override reported mimetype from server with detected mimetype from file once downloaded | ||
2952 | 244 | QMimeDatabase mimeDatabase; | ||
2953 | 245 | QString mimetype = mimeDatabase.mimeTypeForFile(path).name(); | ||
2954 | 246 | |||
2955 | 247 | static QString updateStatement = QLatin1String("UPDATE downloads SET mimetype = ?, " | ||
2956 | 248 | "path = ? WHERE downloadId = ?"); | ||
2957 | 249 | query.prepare(updateStatement); | ||
2958 | 250 | query.addBindValue(mimetype); | ||
2959 | 251 | query.addBindValue(path); | ||
2960 | 252 | query.addBindValue(downloadId); | ||
2961 | 253 | query.exec(); | ||
2962 | 254 | Q_EMIT pathChanged(downloadId, path); | ||
2967 | 255 | } | 243 | } |
2968 | 256 | 244 | ||
2969 | 257 | void DownloadsModel::setComplete(const QString& downloadId, const bool complete) | 245 | void DownloadsModel::setComplete(const QString& downloadId, const bool complete) |
2970 | 258 | { | 246 | { |
2980 | 259 | QSqlQuery query(m_database); | 247 | int index = getIndexForDownloadId(downloadId); |
2981 | 260 | static QString updateStatement = QLatin1String("UPDATE downloads SET complete = ? " | 248 | if (index != -1) { |
2982 | 261 | "WHERE downloadId = ?"); | 249 | DownloadEntry& entry = m_orderedEntries[index]; |
2983 | 262 | query.prepare(updateStatement); | 250 | if (entry.complete == complete) { |
2984 | 263 | query.addBindValue(complete); | 251 | return; |
2985 | 264 | query.addBindValue(downloadId); | 252 | } |
2986 | 265 | query.exec(); | 253 | entry.complete = complete; |
2987 | 266 | Q_EMIT completeChanged(downloadId, complete); | 254 | Q_EMIT dataChanged(this->index(index, 0), this->index(index, 0), QVector<int>() << Complete); |
2988 | 267 | reload(); | 255 | if (!entry.incognito) { |
2989 | 256 | QSqlQuery query(m_database); | ||
2990 | 257 | static QString updateStatement = QLatin1String("UPDATE downloads SET complete=? WHERE downloadId=?;"); | ||
2991 | 258 | query.prepare(updateStatement); | ||
2992 | 259 | query.addBindValue(complete); | ||
2993 | 260 | query.addBindValue(downloadId); | ||
2994 | 261 | query.exec(); | ||
2995 | 262 | } | ||
2996 | 263 | } | ||
2997 | 268 | } | 264 | } |
2998 | 269 | 265 | ||
2999 | 270 | void DownloadsModel::setError(const QString& downloadId, const QString& error) | 266 | void DownloadsModel::setError(const QString& downloadId, const QString& error) |
3000 | 271 | { | 267 | { |
3010 | 272 | QSqlQuery query(m_database); | 268 | int index = getIndexForDownloadId(downloadId); |
3011 | 273 | static QString updateStatement = QLatin1String("UPDATE downloads SET error = ? " | 269 | if (index != -1) { |
3012 | 274 | "WHERE downloadId = ?"); | 270 | DownloadEntry& entry = m_orderedEntries[index]; |
3013 | 275 | query.prepare(updateStatement); | 271 | if (entry.error == error) { |
3014 | 276 | query.addBindValue(error); | 272 | return; |
3015 | 277 | query.addBindValue(downloadId); | 273 | } |
3016 | 278 | query.exec(); | 274 | entry.error = error; |
3017 | 279 | Q_EMIT errorChanged(downloadId, error); | 275 | Q_EMIT dataChanged(this->index(index, 0), this->index(index, 0), QVector<int>() << Error); |
3018 | 280 | reload(); | 276 | if (!entry.incognito) { |
3019 | 277 | QSqlQuery query(m_database); | ||
3020 | 278 | static QString updateStatement = QLatin1String("UPDATE downloads SET error=? WHERE downloadId=?;"); | ||
3021 | 279 | query.prepare(updateStatement); | ||
3022 | 280 | query.addBindValue(error); | ||
3023 | 281 | query.addBindValue(downloadId); | ||
3024 | 282 | query.exec(); | ||
3025 | 283 | } | ||
3026 | 284 | } | ||
3027 | 281 | } | 285 | } |
3028 | 282 | 286 | ||
3029 | 283 | void DownloadsModel::moveToDownloads(const QString& downloadId, const QString& path) | 287 | void DownloadsModel::moveToDownloads(const QString& downloadId, const QString& path) |
3030 | 284 | { | 288 | { |
3031 | 289 | int index = getIndexForDownloadId(downloadId); | ||
3032 | 290 | if (index == -1) { | ||
3033 | 291 | return; | ||
3034 | 292 | } | ||
3035 | 285 | QFile file(path); | 293 | QFile file(path); |
3036 | 286 | if (file.exists()) { | 294 | if (file.exists()) { |
3037 | 287 | QFileInfo fi(path); | 295 | QFileInfo fi(path); |
3041 | 288 | QString suffix = fi.completeSuffix(); | 296 | DownloadEntry& entry = m_orderedEntries[index]; |
3042 | 289 | QString filename = fi.fileName(); | 297 | QVector<int> updatedRoles; |
3043 | 290 | QString filenameWithoutSuffix = filename.left(filename.size() - suffix.size()); | 298 | |
3044 | 299 | // Override reported mimetype from server with detected mimetype from file once downloaded | ||
3045 | 300 | QMimeDatabase mimeDatabase; | ||
3046 | 301 | QString mimetype = mimeDatabase.mimeTypeForFile(fi).name(); | ||
3047 | 302 | if (mimetype != entry.mimetype) { | ||
3048 | 303 | entry.mimetype = mimetype; | ||
3049 | 304 | updatedRoles.append(Mimetype); | ||
3050 | 305 | } | ||
3051 | 306 | |||
3052 | 307 | // Move file to XDG Downloads folder | ||
3053 | 291 | QDir dir(QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)); | 308 | QDir dir(QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)); |
3054 | 292 | if (!dir.exists()) { | 309 | if (!dir.exists()) { |
3055 | 293 | QDir::root().mkpath(dir.absolutePath()); | 310 | QDir::root().mkpath(dir.absolutePath()); |
3056 | 294 | } | 311 | } |
3058 | 295 | QString destination = dir.absoluteFilePath(filenameWithoutSuffix + suffix); | 312 | QString baseName = fi.baseName(); |
3059 | 313 | QString suffix = fi.completeSuffix(); | ||
3060 | 314 | QString destination = dir.absoluteFilePath(QString("%1.%2").arg(baseName, suffix)); | ||
3061 | 296 | // Avoid filename collision by automatically inserting an incremented | 315 | // Avoid filename collision by automatically inserting an incremented |
3062 | 297 | // number into the filename if the original name already exists. | 316 | // number into the filename if the original name already exists. |
3063 | 298 | int append = 1; | 317 | int append = 1; |
3064 | 299 | while (QFile::exists(destination)) { | 318 | while (QFile::exists(destination)) { |
3066 | 300 | destination = dir.absoluteFilePath(QString("%1%2.%3").arg(filenameWithoutSuffix, QString::number(append++), suffix)); | 319 | destination = dir.absoluteFilePath(QString("%1.%2.%3").arg(baseName, QString::number(append++), suffix)); |
3067 | 301 | } | 320 | } |
3068 | 302 | if (file.rename(destination)) { | 321 | if (file.rename(destination)) { |
3070 | 303 | setPath(downloadId, destination); | 322 | entry.path = destination; |
3071 | 323 | updatedRoles.append(Path); | ||
3072 | 304 | } else { | 324 | } else { |
3073 | 305 | qWarning() << "Failed moving file from" << path << "to" << destination; | 325 | qWarning() << "Failed moving file from" << path << "to" << destination; |
3074 | 306 | } | 326 | } |
3075 | 327 | |||
3076 | 328 | Q_EMIT dataChanged(this->index(index, 0), this->index(index, 0), updatedRoles); | ||
3077 | 329 | if (!entry.incognito && !updatedRoles.isEmpty()) { | ||
3078 | 330 | QSqlQuery query(m_database); | ||
3079 | 331 | static QString updateStatement = QLatin1String("UPDATE downloads SET mimetype = ?, " | ||
3080 | 332 | "path = ? WHERE downloadId = ?"); | ||
3081 | 333 | query.prepare(updateStatement); | ||
3082 | 334 | query.addBindValue(mimetype); | ||
3083 | 335 | query.addBindValue(destination); | ||
3084 | 336 | query.addBindValue(downloadId); | ||
3085 | 337 | query.exec(); | ||
3086 | 338 | } | ||
3087 | 307 | } else { | 339 | } else { |
3088 | 308 | qWarning() << "Download not found:" << path; | 340 | qWarning() << "Download not found:" << path; |
3089 | 309 | } | 341 | } |
3090 | @@ -331,15 +363,17 @@ | |||
3091 | 331 | int index = 0; | 363 | int index = 0; |
3092 | 332 | Q_FOREACH(DownloadEntry entry, m_orderedEntries) { | 364 | Q_FOREACH(DownloadEntry entry, m_orderedEntries) { |
3093 | 333 | if (entry.path == path) { | 365 | if (entry.path == path) { |
3094 | 366 | bool incognito = entry.incognito; | ||
3095 | 334 | beginRemoveRows(QModelIndex(), index, index); | 367 | beginRemoveRows(QModelIndex(), index, index); |
3096 | 335 | m_orderedEntries.removeAt(index); | 368 | m_orderedEntries.removeAt(index); |
3097 | 336 | endRemoveRows(); | 369 | endRemoveRows(); |
3098 | 337 | Q_EMIT deleted(path); | ||
3099 | 338 | removeExistingEntryFromDatabase(path); | ||
3100 | 339 | m_fetchedCount--; | ||
3101 | 340 | m_numRows--; | 370 | m_numRows--; |
3102 | 341 | Q_EMIT rowCountChanged(); | 371 | Q_EMIT rowCountChanged(); |
3103 | 342 | QFile::remove(path); | 372 | QFile::remove(path); |
3104 | 373 | if (!incognito) { | ||
3105 | 374 | removeExistingEntryFromDatabase(path); | ||
3106 | 375 | m_fetchedCount--; | ||
3107 | 376 | } | ||
3108 | 343 | return; | 377 | return; |
3109 | 344 | } else { | 378 | } else { |
3110 | 345 | index++; | 379 | index++; |
3111 | @@ -352,45 +386,69 @@ | |||
3112 | 352 | */ | 386 | */ |
3113 | 353 | void DownloadsModel::cancelDownload(const QString& downloadId) | 387 | void DownloadsModel::cancelDownload(const QString& downloadId) |
3114 | 354 | { | 388 | { |
3120 | 355 | int index=0; | 389 | int index = getIndexForDownloadId(downloadId); |
3121 | 356 | Q_FOREACH(DownloadEntry entry, m_orderedEntries) { | 390 | if (index != -1) { |
3122 | 357 | if (entry.downloadId == downloadId) { | 391 | const DownloadEntry& entry = m_orderedEntries.at(index); |
3123 | 358 | beginRemoveRows(QModelIndex(), index, index); | 392 | bool incognito = entry.incognito; |
3124 | 359 | m_orderedEntries.removeAt(index); | 393 | beginRemoveRows(QModelIndex(), index, index); |
3125 | 394 | m_orderedEntries.removeAt(index); | ||
3126 | 395 | endRemoveRows(); | ||
3127 | 396 | m_numRows--; | ||
3128 | 397 | Q_EMIT rowCountChanged(); | ||
3129 | 398 | if (!incognito) { | ||
3130 | 360 | QSqlQuery query(m_database); | 399 | QSqlQuery query(m_database); |
3131 | 361 | static QString deleteStatement = QLatin1String("DELETE FROM downloads WHERE downloadId=?;"); | 400 | static QString deleteStatement = QLatin1String("DELETE FROM downloads WHERE downloadId=?;"); |
3132 | 362 | query.prepare(deleteStatement); | 401 | query.prepare(deleteStatement); |
3133 | 363 | query.addBindValue(downloadId); | 402 | query.addBindValue(downloadId); |
3134 | 364 | query.exec(); | 403 | query.exec(); |
3135 | 365 | endRemoveRows(); | ||
3136 | 366 | m_fetchedCount--; | 404 | m_fetchedCount--; |
3139 | 367 | m_numRows--; | 405 | } |
3140 | 368 | Q_EMIT rowCountChanged(); | 406 | } |
3141 | 407 | } | ||
3142 | 408 | |||
3143 | 409 | void DownloadsModel::setPaused(const QString& downloadId, bool paused) | ||
3144 | 410 | { | ||
3145 | 411 | int index = getIndexForDownloadId(downloadId); | ||
3146 | 412 | if (index != -1) { | ||
3147 | 413 | DownloadEntry& entry = m_orderedEntries[index]; | ||
3148 | 414 | if (entry.paused == paused) { | ||
3149 | 369 | return; | 415 | return; |
3152 | 370 | } else { | 416 | } |
3153 | 371 | index++; | 417 | entry.paused = paused; |
3154 | 418 | Q_EMIT dataChanged(this->index(index, 0), this->index(index, 0), QVector<int>() << Paused); | ||
3155 | 419 | if (!entry.incognito) { | ||
3156 | 420 | QSqlQuery query(m_database); | ||
3157 | 421 | static QString pauseStatement = QLatin1String("UPDATE downloads SET paused=? WHERE downloadId=?;"); | ||
3158 | 422 | query.prepare(pauseStatement); | ||
3159 | 423 | query.addBindValue(paused); | ||
3160 | 424 | query.addBindValue(downloadId); | ||
3161 | 425 | query.exec(); | ||
3162 | 372 | } | 426 | } |
3163 | 373 | } | 427 | } |
3164 | 374 | } | 428 | } |
3165 | 375 | 429 | ||
3166 | 376 | void DownloadsModel::pauseDownload(const QString& downloadId) | 430 | void DownloadsModel::pauseDownload(const QString& downloadId) |
3167 | 377 | { | 431 | { |
3174 | 378 | QSqlQuery query(m_database); | 432 | setPaused(downloadId, true); |
3169 | 379 | static QString pauseStatement = QLatin1String("UPDATE downloads SET paused=1 WHERE downloadId=?;"); | ||
3170 | 380 | query.prepare(pauseStatement); | ||
3171 | 381 | query.addBindValue(downloadId); | ||
3172 | 382 | query.exec(); | ||
3173 | 383 | reload(); | ||
3175 | 384 | } | 433 | } |
3176 | 385 | 434 | ||
3177 | 386 | void DownloadsModel::resumeDownload(const QString& downloadId) | 435 | void DownloadsModel::resumeDownload(const QString& downloadId) |
3178 | 387 | { | 436 | { |
3185 | 388 | QSqlQuery query(m_database); | 437 | setPaused(downloadId, false); |
3186 | 389 | static QString resumeStatement = QLatin1String("UPDATE downloads SET paused=0 WHERE downloadId=?;"); | 438 | } |
3187 | 390 | query.prepare(resumeStatement); | 439 | |
3188 | 391 | query.addBindValue(downloadId); | 440 | void DownloadsModel::pruneIncognitoDownloads() |
3189 | 392 | query.exec(); | 441 | { |
3190 | 393 | reload(); | 442 | for (int i = m_orderedEntries.size() - 1; i >= 0; --i) { |
3191 | 443 | const DownloadEntry& entry = m_orderedEntries.at(i); | ||
3192 | 444 | if (entry.incognito) { | ||
3193 | 445 | beginRemoveRows(QModelIndex(), i, i); | ||
3194 | 446 | m_orderedEntries.removeAt(i); | ||
3195 | 447 | endRemoveRows(); | ||
3196 | 448 | m_numRows--; | ||
3197 | 449 | Q_EMIT rowCountChanged(); | ||
3198 | 450 | } | ||
3199 | 451 | } | ||
3200 | 394 | } | 452 | } |
3201 | 395 | 453 | ||
3202 | 396 | void DownloadsModel::removeExistingEntryFromDatabase(const QString& path) | 454 | void DownloadsModel::removeExistingEntryFromDatabase(const QString& path) |
3203 | @@ -409,14 +467,15 @@ | |||
3204 | 409 | return m_canFetchMore; | 467 | return m_canFetchMore; |
3205 | 410 | } | 468 | } |
3206 | 411 | 469 | ||
3208 | 412 | void DownloadsModel::reload() | 470 | int DownloadsModel::getIndexForDownloadId(const QString& downloadId) const |
3209 | 413 | { | 471 | { |
3218 | 414 | beginResetModel(); | 472 | int index = 0; |
3219 | 415 | m_orderedEntries.clear(); | 473 | Q_FOREACH(const DownloadEntry& entry, m_orderedEntries) { |
3220 | 416 | m_canFetchMore = true; | 474 | if (entry.downloadId == downloadId) { |
3221 | 417 | m_fetchedCount = 0; | 475 | return index; |
3222 | 418 | m_numRows = 0; | 476 | } else { |
3223 | 419 | endResetModel(); | 477 | ++index; |
3224 | 420 | fetchMore(); | 478 | } |
3225 | 421 | Q_EMIT rowCountChanged(); | 479 | } |
3226 | 480 | return -1; | ||
3227 | 422 | } | 481 | } |
3228 | 423 | 482 | ||
3229 | === modified file 'src/app/webbrowser/downloads-model.h' | |||
3230 | --- src/app/webbrowser/downloads-model.h 2016-01-12 10:37:15 +0000 | |||
3231 | +++ src/app/webbrowser/downloads-model.h 2016-10-13 14:41:36 +0000 | |||
3232 | @@ -49,7 +49,8 @@ | |||
3233 | 49 | Complete, | 49 | Complete, |
3234 | 50 | Paused, | 50 | Paused, |
3235 | 51 | Error, | 51 | Error, |
3237 | 52 | Created | 52 | Created, |
3238 | 53 | Incognito | ||
3239 | 53 | }; | 54 | }; |
3240 | 54 | 55 | ||
3241 | 55 | // reimplemented from QAbstractListModel | 56 | // reimplemented from QAbstractListModel |
3242 | @@ -63,23 +64,18 @@ | |||
3243 | 63 | void setDatabasePath(const QString& path); | 64 | void setDatabasePath(const QString& path); |
3244 | 64 | 65 | ||
3245 | 65 | Q_INVOKABLE bool contains(const QString& downloadId) const; | 66 | Q_INVOKABLE bool contains(const QString& downloadId) const; |
3247 | 66 | Q_INVOKABLE void add(const QString& downloadId, const QUrl& url, const QString& mimetype); | 67 | Q_INVOKABLE void add(const QString& downloadId, const QUrl& url, const QString& mimetype, bool incognito); |
3248 | 67 | Q_INVOKABLE void moveToDownloads(const QString& downloadId, const QString& path); | 68 | Q_INVOKABLE void moveToDownloads(const QString& downloadId, const QString& path); |
3249 | 68 | Q_INVOKABLE void setPath(const QString& downloadId, const QString& path); | ||
3250 | 69 | Q_INVOKABLE void setComplete(const QString& downloadId, const bool complete); | 69 | Q_INVOKABLE void setComplete(const QString& downloadId, const bool complete); |
3251 | 70 | Q_INVOKABLE void setError(const QString& downloadId, const QString& error); | 70 | Q_INVOKABLE void setError(const QString& downloadId, const QString& error); |
3252 | 71 | Q_INVOKABLE void deleteDownload(const QString& path); | 71 | Q_INVOKABLE void deleteDownload(const QString& path); |
3253 | 72 | Q_INVOKABLE void cancelDownload(const QString& downloadId); | 72 | Q_INVOKABLE void cancelDownload(const QString& downloadId); |
3254 | 73 | Q_INVOKABLE void pauseDownload(const QString& downloadId); | 73 | Q_INVOKABLE void pauseDownload(const QString& downloadId); |
3255 | 74 | Q_INVOKABLE void resumeDownload(const QString& downloadId); | 74 | Q_INVOKABLE void resumeDownload(const QString& downloadId); |
3256 | 75 | Q_INVOKABLE void pruneIncognitoDownloads(); | ||
3257 | 75 | 76 | ||
3258 | 76 | Q_SIGNALS: | 77 | Q_SIGNALS: |
3259 | 77 | void databasePathChanged() const; | 78 | void databasePathChanged() const; |
3260 | 78 | void added(const QString& downloadId, const QUrl& url, const QString& mimetype) const; | ||
3261 | 79 | void pathChanged(const QString& downloadId, const QString& path) const; | ||
3262 | 80 | void completeChanged(const QString& downloadId, const bool complete) const; | ||
3263 | 81 | void errorChanged(const QString& downloadId, const QString& error) const; | ||
3264 | 82 | void deleted(const QString& path) const; | ||
3265 | 83 | void rowCountChanged(); | 79 | void rowCountChanged(); |
3266 | 84 | 80 | ||
3267 | 85 | private: | 81 | private: |
3268 | @@ -98,6 +94,7 @@ | |||
3269 | 98 | bool paused; | 94 | bool paused; |
3270 | 99 | QString error; | 95 | QString error; |
3271 | 100 | QDateTime created; | 96 | QDateTime created; |
3272 | 97 | bool incognito; | ||
3273 | 101 | }; | 98 | }; |
3274 | 102 | QList<DownloadEntry> m_orderedEntries; | 99 | QList<DownloadEntry> m_orderedEntries; |
3275 | 103 | 100 | ||
3276 | @@ -105,7 +102,8 @@ | |||
3277 | 105 | void createOrAlterDatabaseSchema(); | 102 | void createOrAlterDatabaseSchema(); |
3278 | 106 | void insertNewEntryInDatabase(const DownloadEntry& entry); | 103 | void insertNewEntryInDatabase(const DownloadEntry& entry); |
3279 | 107 | void removeExistingEntryFromDatabase(const QString& path); | 104 | void removeExistingEntryFromDatabase(const QString& path); |
3281 | 108 | void reload(); | 105 | void setPaused(const QString& downloadId, bool paused); |
3282 | 106 | int getIndexForDownloadId(const QString& downloadId) const; | ||
3283 | 109 | }; | 107 | }; |
3284 | 110 | 108 | ||
3285 | 111 | #endif // __DOWNLOADS_MODEL_H__ | 109 | #endif // __DOWNLOADS_MODEL_H__ |
3286 | 112 | 110 | ||
3287 | === modified file 'src/app/webbrowser/history-model.cpp' | |||
3288 | --- src/app/webbrowser/history-model.cpp 2016-03-01 09:30:41 +0000 | |||
3289 | +++ src/app/webbrowser/history-model.cpp 2016-10-13 14:41:36 +0000 | |||
3290 | @@ -20,10 +20,12 @@ | |||
3291 | 20 | #include "history-model.h" | 20 | #include "history-model.h" |
3292 | 21 | 21 | ||
3293 | 22 | // Qt | 22 | // Qt |
3295 | 23 | #include <QtCore/QMutexLocker> | 23 | #include <QtCore/QTimer> |
3296 | 24 | #include <QtCore/QWriteLocker> | ||
3297 | 24 | #include <QtSql/QSqlQuery> | 25 | #include <QtSql/QSqlQuery> |
3298 | 25 | 26 | ||
3300 | 26 | #define CONNECTION_NAME "webbrowser-app-history" | 27 | #define SQL_DRIVER QStringLiteral("QSQLITE") |
3301 | 28 | #define CONNECTION_NAME QStringLiteral("webbrowser-app-history") | ||
3302 | 27 | 29 | ||
3303 | 28 | /*! | 30 | /*! |
3304 | 29 | \class HistoryModel | 31 | \class HistoryModel |
3305 | @@ -39,18 +41,31 @@ | |||
3306 | 39 | The database is read at startup to populate the model, and whenever a new | 41 | The database is read at startup to populate the model, and whenever a new |
3307 | 40 | entry is added to the model the database is updated. | 42 | entry is added to the model the database is updated. |
3308 | 41 | However the model doesn’t monitor the database for external changes. | 43 | However the model doesn’t monitor the database for external changes. |
3309 | 44 | All database operations are performed on a separate thread in order not to | ||
3310 | 45 | block the UI thread. | ||
3311 | 42 | */ | 46 | */ |
3312 | 43 | HistoryModel::HistoryModel(QObject* parent) | 47 | HistoryModel::HistoryModel(QObject* parent) |
3313 | 44 | : QAbstractListModel(parent) | 48 | : QAbstractListModel(parent) |
3314 | 45 | { | 49 | { |
3316 | 46 | m_database = QSqlDatabase::addDatabase(QLatin1String("QSQLITE"), CONNECTION_NAME); | 50 | m_dbWorker = new DbWorker; |
3317 | 51 | m_dbWorker->moveToThread(&m_dbWorkerThread); | ||
3318 | 52 | connect(m_dbWorker, SIGNAL(hiddenEntryFetched(const QUrl&)), | ||
3319 | 53 | SLOT(onHiddenEntryFetched(const QUrl&)), Qt::QueuedConnection); | ||
3320 | 54 | connect(m_dbWorker, | ||
3321 | 55 | SIGNAL(entryFetched(const QUrl&, const QString&, const QString&, | ||
3322 | 56 | const QUrl&, int, const QDateTime&)), | ||
3323 | 57 | SLOT(onEntryFetched(const QUrl&, const QString&, const QString&, | ||
3324 | 58 | const QUrl&, int, const QDateTime&)), | ||
3325 | 59 | Qt::QueuedConnection); | ||
3326 | 60 | connect(m_dbWorker, SIGNAL(loaded()), SIGNAL(loaded())); | ||
3327 | 61 | m_dbWorkerThread.start(QThread::LowPriority); | ||
3328 | 47 | } | 62 | } |
3329 | 48 | 63 | ||
3330 | 49 | HistoryModel::~HistoryModel() | 64 | HistoryModel::~HistoryModel() |
3331 | 50 | { | 65 | { |
3335 | 51 | m_database.close(); | 66 | m_dbWorker->deleteLater(); |
3336 | 52 | m_database = QSqlDatabase(); | 67 | m_dbWorkerThread.quit(); |
3337 | 53 | QSqlDatabase::removeDatabase(CONNECTION_NAME); | 68 | m_dbWorkerThread.wait(); |
3338 | 54 | } | 69 | } |
3339 | 55 | 70 | ||
3340 | 56 | void HistoryModel::resetDatabase(const QString& databaseName) | 71 | void HistoryModel::resetDatabase(const QString& databaseName) |
3341 | @@ -58,85 +73,35 @@ | |||
3342 | 58 | beginResetModel(); | 73 | beginResetModel(); |
3343 | 59 | m_hiddenEntries.clear(); | 74 | m_hiddenEntries.clear(); |
3344 | 60 | m_entries.clear(); | 75 | m_entries.clear(); |
3349 | 61 | m_database.close(); | 76 | Q_EMIT m_dbWorker->resetDatabase(databaseName); |
3346 | 62 | m_database.setDatabaseName(databaseName); | ||
3347 | 63 | m_database.open(); | ||
3348 | 64 | createOrAlterDatabaseSchema(); | ||
3350 | 65 | endResetModel(); | 77 | endResetModel(); |
3425 | 66 | populateFromDatabase(); | 78 | Q_EMIT m_dbWorker->fetchEntries(); |
3426 | 67 | } | 79 | } |
3427 | 68 | 80 | ||
3428 | 69 | void HistoryModel::createOrAlterDatabaseSchema() | 81 | void HistoryModel::onHiddenEntryFetched(const QUrl& url) |
3429 | 70 | { | 82 | { |
3430 | 71 | QMutexLocker ml(&m_dbMutex); | 83 | m_hiddenEntries.insert(url); |
3431 | 72 | QSqlQuery createQuery(m_database); | 84 | } |
3432 | 73 | QString query = QLatin1String("CREATE TABLE IF NOT EXISTS history " | 85 | |
3433 | 74 | "(url VARCHAR, domain VARCHAR, title VARCHAR," | 86 | void HistoryModel::onEntryFetched(const QUrl& url, const QString& domain, const QString& title, |
3434 | 75 | " icon VARCHAR, visits INTEGER, lastVisit DATETIME);"); | 87 | const QUrl& icon, int visits, const QDateTime& lastVisit) |
3435 | 76 | createQuery.prepare(query); | 88 | { |
3436 | 77 | createQuery.exec(); | 89 | HistoryEntry entry; |
3437 | 78 | 90 | entry.url = url; | |
3438 | 79 | // The first version of the database schema didn’t have a 'domain' column | 91 | if (domain.isEmpty()) { |
3439 | 80 | QSqlQuery tableInfoQuery(m_database); | 92 | entry.domain = DomainUtils::extractTopLevelDomainName(url); |
3440 | 81 | query = QLatin1String("PRAGMA TABLE_INFO(history);"); | 93 | } else { |
3441 | 82 | tableInfoQuery.prepare(query); | 94 | entry.domain = domain; |
3442 | 83 | tableInfoQuery.exec(); | 95 | } |
3443 | 84 | while (tableInfoQuery.next()) { | 96 | entry.title = title; |
3444 | 85 | if (tableInfoQuery.value("name").toString() == "domain") { | 97 | entry.icon = icon; |
3445 | 86 | break; | 98 | entry.visits = visits; |
3446 | 87 | } | 99 | entry.lastVisit = lastVisit; |
3447 | 88 | } | 100 | entry.hidden = m_hiddenEntries.contains(url); |
3448 | 89 | if (!tableInfoQuery.isValid()) { | 101 | int index = m_entries.count(); |
3449 | 90 | QSqlQuery addDomainColumnQuery(m_database); | 102 | beginInsertRows(QModelIndex(), index, index); |
3450 | 91 | query = QLatin1String("ALTER TABLE history ADD COLUMN domain VARCHAR;"); | 103 | m_entries.append(entry); |
3451 | 92 | addDomainColumnQuery.prepare(query); | 104 | endInsertRows(); |
3378 | 93 | addDomainColumnQuery.exec(); | ||
3379 | 94 | // Updating all the entries in the database to add the domain is a | ||
3380 | 95 | // costly operation that would slow down the application startup, | ||
3381 | 96 | // do not do it here. | ||
3382 | 97 | } | ||
3383 | 98 | |||
3384 | 99 | QSqlQuery createHiddenQuery(m_database); | ||
3385 | 100 | query = QLatin1String("CREATE TABLE IF NOT EXISTS history_hidden (url VARCHAR);"); | ||
3386 | 101 | createHiddenQuery.prepare(query); | ||
3387 | 102 | createHiddenQuery.exec(); | ||
3388 | 103 | } | ||
3389 | 104 | |||
3390 | 105 | void HistoryModel::populateFromDatabase() | ||
3391 | 106 | { | ||
3392 | 107 | QSqlQuery populateQuery(m_database); | ||
3393 | 108 | QString query = QLatin1String("SELECT url, domain, title, icon, visits, lastVisit " | ||
3394 | 109 | "FROM history ORDER BY lastVisit DESC;"); | ||
3395 | 110 | populateQuery.prepare(query); | ||
3396 | 111 | populateQuery.exec(); | ||
3397 | 112 | |||
3398 | 113 | QSqlQuery populateHiddenQuery(m_database); | ||
3399 | 114 | query = QLatin1String("SELECT url FROM history_hidden;"); | ||
3400 | 115 | populateHiddenQuery.prepare(query); | ||
3401 | 116 | populateHiddenQuery.exec(); | ||
3402 | 117 | |||
3403 | 118 | while (populateHiddenQuery.next()) { | ||
3404 | 119 | m_hiddenEntries.append(populateHiddenQuery.value(0).toUrl()); | ||
3405 | 120 | } | ||
3406 | 121 | |||
3407 | 122 | int count = 0; | ||
3408 | 123 | while (populateQuery.next()) { | ||
3409 | 124 | HistoryEntry entry; | ||
3410 | 125 | entry.url = populateQuery.value(0).toUrl(); | ||
3411 | 126 | entry.domain = populateQuery.value(1).toString(); | ||
3412 | 127 | if (entry.domain.isEmpty()) { | ||
3413 | 128 | entry.domain = DomainUtils::extractTopLevelDomainName(entry.url); | ||
3414 | 129 | } | ||
3415 | 130 | entry.title = populateQuery.value(2).toString(); | ||
3416 | 131 | entry.icon = populateQuery.value(3).toUrl(); | ||
3417 | 132 | entry.visits = populateQuery.value(4).toInt(); | ||
3418 | 133 | entry.lastVisit = QDateTime::fromTime_t(populateQuery.value(5).toInt()); | ||
3419 | 134 | entry.hidden = m_hiddenEntries.contains(entry.url); | ||
3420 | 135 | beginInsertRows(QModelIndex(), count, count); | ||
3421 | 136 | m_entries.append(entry); | ||
3422 | 137 | endInsertRows(); | ||
3423 | 138 | ++count; | ||
3424 | 139 | } | ||
3452 | 140 | } | 105 | } |
3453 | 141 | 106 | ||
3454 | 142 | QHash<int, QByteArray> HistoryModel::roleNames() const | 107 | QHash<int, QByteArray> HistoryModel::roleNames() const |
3455 | @@ -194,12 +159,13 @@ | |||
3456 | 194 | 159 | ||
3457 | 195 | const QString HistoryModel::databasePath() const | 160 | const QString HistoryModel::databasePath() const |
3458 | 196 | { | 161 | { |
3460 | 197 | return m_database.databaseName(); | 162 | return m_databasePath; |
3461 | 198 | } | 163 | } |
3462 | 199 | 164 | ||
3463 | 200 | void HistoryModel::setDatabasePath(const QString& path) | 165 | void HistoryModel::setDatabasePath(const QString& path) |
3464 | 201 | { | 166 | { |
3466 | 202 | if (path != databasePath()) { | 167 | if (path != m_databasePath) { |
3467 | 168 | m_databasePath = path; | ||
3468 | 203 | if (path.isEmpty()) { | 169 | if (path.isEmpty()) { |
3469 | 204 | resetDatabase(":memory:"); | 170 | resetDatabase(":memory:"); |
3470 | 205 | } else { | 171 | } else { |
3471 | @@ -399,86 +365,55 @@ | |||
3472 | 399 | 365 | ||
3473 | 400 | void HistoryModel::insertNewEntryInDatabase(const HistoryEntry& entry) | 366 | void HistoryModel::insertNewEntryInDatabase(const HistoryEntry& entry) |
3474 | 401 | { | 367 | { |
3486 | 402 | QMutexLocker ml(&m_dbMutex); | 368 | QVariantList values; |
3487 | 403 | QSqlQuery query(m_database); | 369 | values << entry.url.toString(); |
3488 | 404 | static QString insertStatement = QLatin1String("INSERT INTO history (url, domain, title, icon, " | 370 | values << entry.domain; |
3489 | 405 | "visits, lastVisit) VALUES (?, ?, ?, ?, 1, ?);"); | 371 | values << entry.title; |
3490 | 406 | query.prepare(insertStatement); | 372 | values << entry.icon.toString(); |
3491 | 407 | query.addBindValue(entry.url.toString()); | 373 | values << entry.lastVisit.toTime_t(); |
3492 | 408 | query.addBindValue(entry.domain); | 374 | Q_EMIT m_dbWorker->enqueue(DbWorker::InsertNewEntry, values); |
3482 | 409 | query.addBindValue(entry.title); | ||
3483 | 410 | query.addBindValue(entry.icon.toString()); | ||
3484 | 411 | query.addBindValue(entry.lastVisit.toTime_t()); | ||
3485 | 412 | query.exec(); | ||
3493 | 413 | } | 375 | } |
3494 | 414 | 376 | ||
3495 | 415 | void HistoryModel::insertNewEntryInHiddenDatabase(const QUrl& url) | 377 | void HistoryModel::insertNewEntryInHiddenDatabase(const QUrl& url) |
3496 | 416 | { | 378 | { |
3503 | 417 | QMutexLocker ml(&m_dbMutex); | 379 | Q_EMIT m_dbWorker->enqueue(DbWorker::InsertNewHiddenEntry, QVariantList() << url.toString()); |
3498 | 418 | QSqlQuery query(m_database); | ||
3499 | 419 | static QString insertStatement = QLatin1String("INSERT INTO history_hidden (url) VALUES (?);"); | ||
3500 | 420 | query.prepare(insertStatement); | ||
3501 | 421 | query.addBindValue(url.toString()); | ||
3502 | 422 | query.exec(); | ||
3504 | 423 | } | 380 | } |
3505 | 424 | 381 | ||
3506 | 425 | void HistoryModel::updateExistingEntryInDatabase(const HistoryEntry& entry) | 382 | void HistoryModel::updateExistingEntryInDatabase(const HistoryEntry& entry) |
3507 | 426 | { | 383 | { |
3520 | 427 | QMutexLocker ml(&m_dbMutex); | 384 | QVariantList values; |
3521 | 428 | QSqlQuery query(m_database); | 385 | values << entry.domain; |
3522 | 429 | static QString updateStatement = QLatin1String("UPDATE history SET domain=?, title=?, icon=?, " | 386 | values << entry.title; |
3523 | 430 | "visits=?, lastVisit=? WHERE url=?;"); | 387 | values << entry.icon.toString(); |
3524 | 431 | query.prepare(updateStatement); | 388 | values << entry.visits; |
3525 | 432 | query.addBindValue(entry.domain); | 389 | values << entry.lastVisit.toTime_t(); |
3526 | 433 | query.addBindValue(entry.title); | 390 | values << entry.url.toString(); |
3527 | 434 | query.addBindValue(entry.icon.toString()); | 391 | Q_EMIT m_dbWorker->enqueue(DbWorker::UpdateExistingEntry, values); |
3516 | 435 | query.addBindValue(entry.visits); | ||
3517 | 436 | query.addBindValue(entry.lastVisit.toTime_t()); | ||
3518 | 437 | query.addBindValue(entry.url.toString()); | ||
3519 | 438 | query.exec(); | ||
3528 | 439 | } | 392 | } |
3529 | 440 | 393 | ||
3530 | 441 | void HistoryModel::removeEntryFromDatabaseByUrl(const QUrl& url) | 394 | void HistoryModel::removeEntryFromDatabaseByUrl(const QUrl& url) |
3531 | 442 | { | 395 | { |
3538 | 443 | QMutexLocker ml(&m_dbMutex); | 396 | Q_EMIT m_dbWorker->enqueue(DbWorker::RemoveEntryByUrl, QVariantList() << url.toString()); |
3533 | 444 | QSqlQuery query(m_database); | ||
3534 | 445 | static QString deleteStatement = QLatin1String("DELETE FROM history WHERE url=?;"); | ||
3535 | 446 | query.prepare(deleteStatement); | ||
3536 | 447 | query.addBindValue(url.toString()); | ||
3537 | 448 | query.exec(); | ||
3539 | 449 | } | 397 | } |
3540 | 450 | 398 | ||
3541 | 451 | void HistoryModel::removeEntryFromHiddenDatabaseByUrl(const QUrl& url) | 399 | void HistoryModel::removeEntryFromHiddenDatabaseByUrl(const QUrl& url) |
3542 | 452 | { | 400 | { |
3549 | 453 | QMutexLocker ml(&m_dbMutex); | 401 | Q_EMIT m_dbWorker->enqueue(DbWorker::RemoveHiddenEntryByUrl, QVariantList() << url.toString()); |
3544 | 454 | QSqlQuery query(m_database); | ||
3545 | 455 | static QString deleteStatement = QLatin1String("DELETE FROM history_hidden WHERE url=?;"); | ||
3546 | 456 | query.prepare(deleteStatement); | ||
3547 | 457 | query.addBindValue(url.toString()); | ||
3548 | 458 | query.exec(); | ||
3550 | 459 | } | 402 | } |
3551 | 460 | 403 | ||
3552 | 461 | void HistoryModel::removeEntriesFromDatabaseByDate(const QDate& date) | 404 | void HistoryModel::removeEntriesFromDatabaseByDate(const QDate& date) |
3553 | 462 | { | 405 | { |
3558 | 463 | QMutexLocker ml(&m_dbMutex); | 406 | QVariantList values; |
3555 | 464 | QSqlQuery query(m_database); | ||
3556 | 465 | static QString deleteStatement = QLatin1String("DELETE FROM history WHERE lastVisit BETWEEN ? AND ?;"); | ||
3557 | 466 | query.prepare(deleteStatement); | ||
3559 | 467 | QDateTime dateTime = QDateTime(date); | 407 | QDateTime dateTime = QDateTime(date); |
3561 | 468 | query.addBindValue(dateTime.toTime_t()); | 408 | values << dateTime.toTime_t(); |
3562 | 469 | dateTime.setTime(QTime(23, 59, 59, 999)); | 409 | dateTime.setTime(QTime(23, 59, 59, 999)); |
3565 | 470 | query.addBindValue(dateTime.toTime_t()); | 410 | values << dateTime.toTime_t(); |
3566 | 471 | query.exec(); | 411 | Q_EMIT m_dbWorker->enqueue(DbWorker::RemoveEntriesByDate, values); |
3567 | 472 | } | 412 | } |
3568 | 473 | 413 | ||
3569 | 474 | void HistoryModel::removeEntriesFromDatabaseByDomain(const QString& domain) | 414 | void HistoryModel::removeEntriesFromDatabaseByDomain(const QString& domain) |
3570 | 475 | { | 415 | { |
3577 | 476 | QMutexLocker ml(&m_dbMutex); | 416 | Q_EMIT m_dbWorker->enqueue(DbWorker::RemoveEntriesByDomain, QVariantList() << domain); |
3572 | 477 | QSqlQuery query(m_database); | ||
3573 | 478 | static QString deleteStatement = QLatin1String("DELETE FROM history WHERE domain=?;"); | ||
3574 | 479 | query.prepare(deleteStatement); | ||
3575 | 480 | query.addBindValue(domain); | ||
3576 | 481 | query.exec(); | ||
3578 | 482 | } | 417 | } |
3579 | 483 | 418 | ||
3580 | 484 | void HistoryModel::clearAll() | 419 | void HistoryModel::clearAll() |
3581 | @@ -495,16 +430,8 @@ | |||
3582 | 495 | 430 | ||
3583 | 496 | void HistoryModel::clearDatabase() | 431 | void HistoryModel::clearDatabase() |
3584 | 497 | { | 432 | { |
3595 | 498 | QMutexLocker ml(&m_dbMutex); | 433 | Q_EMIT m_dbWorker->enqueue(DbWorker::Clear, QVariantList() << QStringLiteral("history")); |
3596 | 499 | QSqlQuery deleteQuery(m_database); | 434 | Q_EMIT m_dbWorker->enqueue(DbWorker::Clear, QVariantList() << QStringLiteral("history_hidden")); |
3587 | 500 | QString deleteStatement = QLatin1String("DELETE FROM history;"); | ||
3588 | 501 | deleteQuery.prepare(deleteStatement); | ||
3589 | 502 | deleteQuery.exec(); | ||
3590 | 503 | |||
3591 | 504 | QSqlQuery deleteHiddenQuery(m_database); | ||
3592 | 505 | deleteStatement = QLatin1String("DELETE FROM history_hidden;"); | ||
3593 | 506 | deleteHiddenQuery.prepare(deleteStatement); | ||
3594 | 507 | deleteHiddenQuery.exec(); | ||
3597 | 508 | } | 435 | } |
3598 | 509 | 436 | ||
3599 | 510 | /*! | 437 | /*! |
3600 | @@ -519,7 +446,7 @@ | |||
3601 | 519 | return; | 446 | return; |
3602 | 520 | } | 447 | } |
3603 | 521 | 448 | ||
3605 | 522 | m_hiddenEntries.append(url); | 449 | m_hiddenEntries.insert(url); |
3606 | 523 | 450 | ||
3607 | 524 | QVector<int> roles; | 451 | QVector<int> roles; |
3608 | 525 | roles << Hidden; | 452 | roles << Hidden; |
3609 | @@ -547,7 +474,7 @@ | |||
3610 | 547 | return; | 474 | return; |
3611 | 548 | } | 475 | } |
3612 | 549 | 476 | ||
3614 | 550 | m_hiddenEntries.removeAll(url); | 477 | m_hiddenEntries.remove(url); |
3615 | 551 | 478 | ||
3616 | 552 | QVector<int> roles; | 479 | QVector<int> roles; |
3617 | 553 | roles << Hidden; | 480 | roles << Hidden; |
3618 | @@ -577,3 +504,175 @@ | |||
3619 | 577 | } | 504 | } |
3620 | 578 | return item; | 505 | return item; |
3621 | 579 | } | 506 | } |
3622 | 507 | |||
3623 | 508 | DbWorker::DbWorker() | ||
3624 | 509 | : QObject() | ||
3625 | 510 | , m_flush(nullptr) | ||
3626 | 511 | { | ||
3627 | 512 | // Ensure all database operations are performed on the same thread | ||
3628 | 513 | connect(this, SIGNAL(resetDatabase(const QString&)), | ||
3629 | 514 | SLOT(doResetDatabase(const QString&)), Qt::QueuedConnection); | ||
3630 | 515 | connect(this, SIGNAL(fetchEntries()), | ||
3631 | 516 | SLOT(doFetchEntries()), Qt::QueuedConnection); | ||
3632 | 517 | qRegisterMetaType<Operation>("Operation"); | ||
3633 | 518 | connect(this, SIGNAL(enqueue(Operation, QVariantList)), | ||
3634 | 519 | SLOT(doEnqueue(Operation, QVariantList)), Qt::QueuedConnection); | ||
3635 | 520 | } | ||
3636 | 521 | |||
3637 | 522 | DbWorker::~DbWorker() | ||
3638 | 523 | { | ||
3639 | 524 | if (m_flush) { | ||
3640 | 525 | m_flush->stop(); | ||
3641 | 526 | delete m_flush; | ||
3642 | 527 | m_flush = nullptr; | ||
3643 | 528 | } | ||
3644 | 529 | doFlush(); | ||
3645 | 530 | if (m_database.isOpen()) { | ||
3646 | 531 | m_database.close(); | ||
3647 | 532 | } | ||
3648 | 533 | m_database = QSqlDatabase(); | ||
3649 | 534 | QSqlDatabase::removeDatabase(CONNECTION_NAME); | ||
3650 | 535 | } | ||
3651 | 536 | |||
3652 | 537 | void DbWorker::doResetDatabase(const QString& databaseName) | ||
3653 | 538 | { | ||
3654 | 539 | if (m_flush) { | ||
3655 | 540 | m_flush->stop(); | ||
3656 | 541 | delete m_flush; | ||
3657 | 542 | m_flush = nullptr; | ||
3658 | 543 | } | ||
3659 | 544 | doFlush(); | ||
3660 | 545 | if (m_database.isOpen()) { | ||
3661 | 546 | m_database.close(); | ||
3662 | 547 | } | ||
3663 | 548 | if (!m_database.isValid()) { | ||
3664 | 549 | m_database = QSqlDatabase::addDatabase(SQL_DRIVER, CONNECTION_NAME); | ||
3665 | 550 | } | ||
3666 | 551 | m_database.setDatabaseName(databaseName); | ||
3667 | 552 | m_database.open(); | ||
3668 | 553 | doCreateOrAlterDatabaseSchema(); | ||
3669 | 554 | } | ||
3670 | 555 | |||
3671 | 556 | void DbWorker::doCreateOrAlterDatabaseSchema() | ||
3672 | 557 | { | ||
3673 | 558 | QSqlQuery createQuery(m_database); | ||
3674 | 559 | QString query = QStringLiteral("CREATE TABLE IF NOT EXISTS history " | ||
3675 | 560 | "(url VARCHAR, domain VARCHAR, title VARCHAR," | ||
3676 | 561 | " icon VARCHAR, visits INTEGER, lastVisit DATETIME);"); | ||
3677 | 562 | createQuery.prepare(query); | ||
3678 | 563 | createQuery.exec(); | ||
3679 | 564 | |||
3680 | 565 | // The first version of the database schema didn't have a 'domain' column | ||
3681 | 566 | QSqlQuery tableInfoQuery(m_database); | ||
3682 | 567 | query = QStringLiteral("PRAGMA TABLE_INFO(history);"); | ||
3683 | 568 | tableInfoQuery.prepare(query); | ||
3684 | 569 | tableInfoQuery.exec(); | ||
3685 | 570 | while (tableInfoQuery.next()) { | ||
3686 | 571 | if (tableInfoQuery.value(QStringLiteral("name")).toString() == QStringLiteral("domain")) { | ||
3687 | 572 | break; | ||
3688 | 573 | } | ||
3689 | 574 | } | ||
3690 | 575 | if (!tableInfoQuery.isValid()) { | ||
3691 | 576 | QSqlQuery addDomainColumnQuery(m_database); | ||
3692 | 577 | query = QStringLiteral("ALTER TABLE history ADD COLUMN domain VARCHAR;"); | ||
3693 | 578 | addDomainColumnQuery.prepare(query); | ||
3694 | 579 | addDomainColumnQuery.exec(); | ||
3695 | 580 | // Updating all the entries in the database to add the domain is a | ||
3696 | 581 | // costly operation that would slow down the application startup, | ||
3697 | 582 | // do not do it here. | ||
3698 | 583 | } | ||
3699 | 584 | |||
3700 | 585 | QSqlQuery createHiddenQuery(m_database); | ||
3701 | 586 | query = QStringLiteral("CREATE TABLE IF NOT EXISTS history_hidden (url VARCHAR);"); | ||
3702 | 587 | createHiddenQuery.prepare(query); | ||
3703 | 588 | createHiddenQuery.exec(); | ||
3704 | 589 | } | ||
3705 | 590 | |||
3706 | 591 | void DbWorker::doFetchEntries() | ||
3707 | 592 | { | ||
3708 | 593 | QSqlQuery populateHiddenQuery(m_database); | ||
3709 | 594 | QString query = QStringLiteral("SELECT url FROM history_hidden;"); | ||
3710 | 595 | populateHiddenQuery.prepare(query); | ||
3711 | 596 | populateHiddenQuery.exec(); | ||
3712 | 597 | while (populateHiddenQuery.next()) { | ||
3713 | 598 | Q_EMIT hiddenEntryFetched(populateHiddenQuery.value(0).toUrl()); | ||
3714 | 599 | } | ||
3715 | 600 | |||
3716 | 601 | QSqlQuery populateQuery(m_database); | ||
3717 | 602 | query = QStringLiteral("SELECT url, domain, title, icon, visits, lastVisit " | ||
3718 | 603 | "FROM history ORDER BY lastVisit DESC;"); | ||
3719 | 604 | populateQuery.prepare(query); | ||
3720 | 605 | populateQuery.exec(); | ||
3721 | 606 | while (populateQuery.next()) { | ||
3722 | 607 | Q_EMIT entryFetched(populateQuery.value(0).toUrl(), | ||
3723 | 608 | populateQuery.value(1).toString(), | ||
3724 | 609 | populateQuery.value(2).toString(), | ||
3725 | 610 | populateQuery.value(3).toUrl(), | ||
3726 | 611 | populateQuery.value(4).toInt(), | ||
3727 | 612 | QDateTime::fromTime_t(populateQuery.value(5).toInt())); | ||
3728 | 613 | } | ||
3729 | 614 | Q_EMIT loaded(); | ||
3730 | 615 | } | ||
3731 | 616 | |||
3732 | 617 | void DbWorker::doEnqueue(DbWorker::Operation operation, QVariantList values) | ||
3733 | 618 | { | ||
3734 | 619 | if (!m_flush) { | ||
3735 | 620 | m_flush = new QTimer; | ||
3736 | 621 | m_flush->setInterval(1000); | ||
3737 | 622 | m_flush->setSingleShot(true); | ||
3738 | 623 | connect(m_flush, SIGNAL(timeout()), SLOT(doFlush())); | ||
3739 | 624 | } | ||
3740 | 625 | QWriteLocker locker(&m_lock); | ||
3741 | 626 | m_pending.enqueue(qMakePair(operation, values)); | ||
3742 | 627 | m_flush->start(); | ||
3743 | 628 | } | ||
3744 | 629 | |||
3745 | 630 | void DbWorker::doFlush() | ||
3746 | 631 | { | ||
3747 | 632 | QWriteLocker locker(&m_lock); | ||
3748 | 633 | while (!m_pending.isEmpty()) { | ||
3749 | 634 | QPair<Operation, QVariantList> args = m_pending.dequeue(); | ||
3750 | 635 | QString statement; | ||
3751 | 636 | switch (args.first) { | ||
3752 | 637 | case InsertNewEntry: | ||
3753 | 638 | statement = QStringLiteral("INSERT INTO history (url, domain, title, icon, " | ||
3754 | 639 | "visits, lastVisit) VALUES (?, ?, ?, ?, 1, ?);"); | ||
3755 | 640 | break; | ||
3756 | 641 | case InsertNewHiddenEntry: | ||
3757 | 642 | statement = QStringLiteral("INSERT INTO history_hidden (url) VALUES (?);"); | ||
3758 | 643 | break; | ||
3759 | 644 | case UpdateExistingEntry: | ||
3760 | 645 | statement = QStringLiteral("UPDATE history SET domain=?, title=?, icon=?, " | ||
3761 | 646 | "visits=?, lastVisit=? WHERE url=?;"); | ||
3762 | 647 | break; | ||
3763 | 648 | case RemoveEntryByUrl: | ||
3764 | 649 | statement = QStringLiteral("DELETE FROM history WHERE url=?;"); | ||
3765 | 650 | break; | ||
3766 | 651 | case RemoveHiddenEntryByUrl: | ||
3767 | 652 | statement = QStringLiteral("DELETE FROM history_hidden WHERE url=?;"); | ||
3768 | 653 | break; | ||
3769 | 654 | case RemoveEntriesByDate: | ||
3770 | 655 | statement = QStringLiteral("DELETE FROM history WHERE lastVisit BETWEEN ? AND ?;"); | ||
3771 | 656 | break; | ||
3772 | 657 | case RemoveEntriesByDomain: | ||
3773 | 658 | statement = QStringLiteral("DELETE FROM history WHERE domain=?;"); | ||
3774 | 659 | break; | ||
3775 | 660 | case Clear: | ||
3776 | 661 | statement = QStringLiteral("DELETE FROM %1;").arg(args.second.takeFirst().toString()); | ||
3777 | 662 | break; | ||
3778 | 663 | default: | ||
3779 | 664 | Q_UNREACHABLE(); | ||
3780 | 665 | } | ||
3781 | 666 | if (statement.isEmpty()) { | ||
3782 | 667 | return; | ||
3783 | 668 | } | ||
3784 | 669 | QSqlQuery query(m_database); | ||
3785 | 670 | if (!query.prepare(statement)) { | ||
3786 | 671 | continue; | ||
3787 | 672 | } | ||
3788 | 673 | Q_FOREACH(const QVariant& value, args.second) { | ||
3789 | 674 | query.addBindValue(value); | ||
3790 | 675 | } | ||
3791 | 676 | query.exec(); | ||
3792 | 677 | } | ||
3793 | 678 | } | ||
3794 | 580 | 679 | ||
3795 | === modified file 'src/app/webbrowser/history-model.h' | |||
3796 | --- src/app/webbrowser/history-model.h 2016-02-26 12:26:20 +0000 | |||
3797 | +++ src/app/webbrowser/history-model.h 2016-10-13 14:41:36 +0000 | |||
3798 | @@ -23,11 +23,20 @@ | |||
3799 | 23 | #include <QtCore/QAbstractListModel> | 23 | #include <QtCore/QAbstractListModel> |
3800 | 24 | #include <QtCore/QDateTime> | 24 | #include <QtCore/QDateTime> |
3801 | 25 | #include <QtCore/QList> | 25 | #include <QtCore/QList> |
3803 | 26 | #include <QtCore/QMutex> | 26 | #include <QtCore/QPair> |
3804 | 27 | #include <QtCore/QQueue> | ||
3805 | 28 | #include <QtCore/QReadWriteLock> | ||
3806 | 29 | #include <QtCore/QSet> | ||
3807 | 27 | #include <QtCore/QString> | 30 | #include <QtCore/QString> |
3808 | 31 | #include <QtCore/QThread> | ||
3809 | 28 | #include <QtCore/QUrl> | 32 | #include <QtCore/QUrl> |
3810 | 33 | #include <QtCore/QVariant> | ||
3811 | 29 | #include <QtSql/QSqlDatabase> | 34 | #include <QtSql/QSqlDatabase> |
3812 | 30 | 35 | ||
3813 | 36 | class QTimer; | ||
3814 | 37 | |||
3815 | 38 | class DbWorker; | ||
3816 | 39 | |||
3817 | 31 | class HistoryModel : public QAbstractListModel | 40 | class HistoryModel : public QAbstractListModel |
3818 | 32 | { | 41 | { |
3819 | 33 | Q_OBJECT | 42 | Q_OBJECT |
3820 | @@ -74,6 +83,7 @@ | |||
3821 | 74 | Q_SIGNALS: | 83 | Q_SIGNALS: |
3822 | 75 | void databasePathChanged() const; | 84 | void databasePathChanged() const; |
3823 | 76 | void rowCountChanged(); | 85 | void rowCountChanged(); |
3824 | 86 | void loaded() const; | ||
3825 | 77 | 87 | ||
3826 | 78 | protected: | 88 | protected: |
3827 | 79 | struct HistoryEntry { | 89 | struct HistoryEntry { |
3828 | @@ -89,15 +99,16 @@ | |||
3829 | 89 | int getEntryIndex(const QUrl& url) const; | 99 | int getEntryIndex(const QUrl& url) const; |
3830 | 90 | void updateExistingEntryInDatabase(const HistoryEntry& entry); | 100 | void updateExistingEntryInDatabase(const HistoryEntry& entry); |
3831 | 91 | 101 | ||
3832 | 102 | private Q_SLOTS: | ||
3833 | 103 | void onHiddenEntryFetched(const QUrl& url); | ||
3834 | 104 | void onEntryFetched(const QUrl& url, const QString& domain, const QString& title, | ||
3835 | 105 | const QUrl& icon, int visits, const QDateTime& lastVisit); | ||
3836 | 106 | |||
3837 | 92 | private: | 107 | private: |
3842 | 93 | QMutex m_dbMutex; | 108 | QString m_databasePath; |
3843 | 94 | QSqlDatabase m_database; | 109 | QSet<QUrl> m_hiddenEntries; |
3840 | 95 | |||
3841 | 96 | QList<QUrl> m_hiddenEntries; | ||
3844 | 97 | 110 | ||
3845 | 98 | void resetDatabase(const QString& databaseName); | 111 | void resetDatabase(const QString& databaseName); |
3846 | 99 | void createOrAlterDatabaseSchema(); | ||
3847 | 100 | void populateFromDatabase(); | ||
3848 | 101 | void removeByIndex(int index); | 112 | void removeByIndex(int index); |
3849 | 102 | void insertNewEntryInDatabase(const HistoryEntry& entry); | 113 | void insertNewEntryInDatabase(const HistoryEntry& entry); |
3850 | 103 | void insertNewEntryInHiddenDatabase(const QUrl& url); | 114 | void insertNewEntryInHiddenDatabase(const QUrl& url); |
3851 | @@ -106,6 +117,52 @@ | |||
3852 | 106 | void removeEntriesFromDatabaseByDate(const QDate& date); | 117 | void removeEntriesFromDatabaseByDate(const QDate& date); |
3853 | 107 | void removeEntriesFromDatabaseByDomain(const QString& domain); | 118 | void removeEntriesFromDatabaseByDomain(const QString& domain); |
3854 | 108 | void clearDatabase(); | 119 | void clearDatabase(); |
3855 | 120 | |||
3856 | 121 | QThread m_dbWorkerThread; | ||
3857 | 122 | DbWorker* m_dbWorker; | ||
3858 | 123 | }; | ||
3859 | 124 | |||
3860 | 125 | class DbWorker : public QObject { | ||
3861 | 126 | Q_OBJECT | ||
3862 | 127 | |||
3863 | 128 | Q_ENUMS(Operation) | ||
3864 | 129 | |||
3865 | 130 | public: | ||
3866 | 131 | DbWorker(); | ||
3867 | 132 | ~DbWorker(); | ||
3868 | 133 | |||
3869 | 134 | enum Operation { | ||
3870 | 135 | InsertNewEntry, | ||
3871 | 136 | InsertNewHiddenEntry, | ||
3872 | 137 | UpdateExistingEntry, | ||
3873 | 138 | RemoveEntryByUrl, | ||
3874 | 139 | RemoveHiddenEntryByUrl, | ||
3875 | 140 | RemoveEntriesByDate, | ||
3876 | 141 | RemoveEntriesByDomain, | ||
3877 | 142 | Clear, | ||
3878 | 143 | }; | ||
3879 | 144 | |||
3880 | 145 | Q_SIGNALS: | ||
3881 | 146 | void resetDatabase(const QString& databaseName); | ||
3882 | 147 | void fetchEntries(); | ||
3883 | 148 | void hiddenEntryFetched(const QUrl& url); | ||
3884 | 149 | void entryFetched(const QUrl& url, const QString& domain, const QString& title, | ||
3885 | 150 | const QUrl& icon, int visits, const QDateTime& lastVisit); | ||
3886 | 151 | void loaded(); | ||
3887 | 152 | void enqueue(Operation operation, QVariantList values); | ||
3888 | 153 | |||
3889 | 154 | private Q_SLOTS: | ||
3890 | 155 | void doResetDatabase(const QString& databaseName); | ||
3891 | 156 | void doCreateOrAlterDatabaseSchema(); | ||
3892 | 157 | void doFetchEntries(); | ||
3893 | 158 | void doEnqueue(Operation operation, QVariantList values); | ||
3894 | 159 | void doFlush(); | ||
3895 | 160 | |||
3896 | 161 | private: | ||
3897 | 162 | QSqlDatabase m_database; | ||
3898 | 163 | QReadWriteLock m_lock; | ||
3899 | 164 | QQueue<QPair<Operation, QVariantList>> m_pending; | ||
3900 | 165 | QTimer* m_flush; | ||
3901 | 109 | }; | 166 | }; |
3902 | 110 | 167 | ||
3903 | 111 | #endif // __HISTORY_MODEL_H__ | 168 | #endif // __HISTORY_MODEL_H__ |
3904 | 112 | 169 | ||
3905 | === modified file 'src/app/webbrowser/webbrowser-app.qml' | |||
3906 | --- src/app/webbrowser/webbrowser-app.qml 2016-09-20 19:55:22 +0000 | |||
3907 | +++ src/app/webbrowser/webbrowser-app.qml 2016-10-13 14:41:36 +0000 | |||
3908 | @@ -53,15 +53,6 @@ | |||
3909 | 53 | BookmarksModel.databasePath = dataLocation + "/bookmarks.sqlite" | 53 | BookmarksModel.databasePath = dataLocation + "/bookmarks.sqlite" |
3910 | 54 | HistoryModel.databasePath = dataLocation + "/history.sqlite" | 54 | HistoryModel.databasePath = dataLocation + "/history.sqlite" |
3911 | 55 | DownloadsModel.databasePath = dataLocation + "/downloads.sqlite" | 55 | DownloadsModel.databasePath = dataLocation + "/downloads.sqlite" |
3912 | 56 | |||
3913 | 57 | var doNotCleanUrls = [] | ||
3914 | 58 | for (var x in allWindows) { | ||
3915 | 59 | var tabs = allWindows[x].tabsModel | ||
3916 | 60 | for (var t = 0; t < tabs.count; ++t) { | ||
3917 | 61 | doNotCleanUrls.push(tabs.get(t).url) | ||
3918 | 62 | } | ||
3919 | 63 | } | ||
3920 | 64 | PreviewManager.cleanUnusedPreviews(doNotCleanUrls) | ||
3921 | 65 | } | 56 | } |
3922 | 66 | 57 | ||
3923 | 67 | // Array of all windows, sorted chronologically (most recently active last) | 58 | // Array of all windows, sorted chronologically (most recently active last) |
3924 | @@ -128,6 +119,20 @@ | |||
3925 | 128 | session.clear() | 119 | session.clear() |
3926 | 129 | } | 120 | } |
3927 | 130 | } | 121 | } |
3928 | 122 | if (incognito && (allWindows.length > 1)) { | ||
3929 | 123 | // If the last incognito window is being closed, | ||
3930 | 124 | // prune incognito entries from the downloads model | ||
3931 | 125 | var incognitoWindows = 0 | ||
3932 | 126 | for (var w in allWindows) { | ||
3933 | 127 | var window = allWindows[w] | ||
3934 | 128 | if ((window !== this) && window.incognito) { | ||
3935 | 129 | ++incognitoWindows | ||
3936 | 130 | } | ||
3937 | 131 | } | ||
3938 | 132 | if (incognitoWindows == 0) { | ||
3939 | 133 | DownloadsModel.pruneIncognitoDownloads() | ||
3940 | 134 | } | ||
3941 | 135 | } | ||
3942 | 131 | destroy() | 136 | destroy() |
3943 | 132 | } | 137 | } |
3944 | 133 | 138 | ||
3945 | @@ -446,4 +451,18 @@ | |||
3946 | 446 | } | 451 | } |
3947 | 447 | } | 452 | } |
3948 | 448 | } | 453 | } |
3949 | 454 | |||
3950 | 455 | property var historyModelMonitor: Connections { | ||
3951 | 456 | target: HistoryModel | ||
3952 | 457 | onLoaded: { | ||
3953 | 458 | var doNotCleanUrls = [] | ||
3954 | 459 | for (var x in allWindows) { | ||
3955 | 460 | var tabs = allWindows[x].tabsModel | ||
3956 | 461 | for (var t = 0; t < tabs.count; ++t) { | ||
3957 | 462 | doNotCleanUrls.push(tabs.get(t).url) | ||
3958 | 463 | } | ||
3959 | 464 | } | ||
3960 | 465 | PreviewManager.cleanUnusedPreviews(doNotCleanUrls) | ||
3961 | 466 | } | ||
3962 | 467 | } | ||
3963 | 449 | } | 468 | } |
3964 | 450 | 469 | ||
3965 | === modified file 'src/app/webcontainer/Chrome.qml' | |||
3966 | --- src/app/webcontainer/Chrome.qml 2016-05-26 17:07:44 +0000 | |||
3967 | +++ src/app/webcontainer/Chrome.qml 2016-10-13 14:41:36 +0000 | |||
3968 | @@ -23,6 +23,7 @@ | |||
3969 | 23 | ChromeBase { | 23 | ChromeBase { |
3970 | 24 | id: chrome | 24 | id: chrome |
3971 | 25 | 25 | ||
3972 | 26 | property var webview: null | ||
3973 | 26 | property bool navigationButtonsVisible: false | 27 | property bool navigationButtonsVisible: false |
3974 | 27 | property bool accountSwitcher: false | 28 | property bool accountSwitcher: false |
3975 | 28 | 29 | ||
3976 | 29 | 30 | ||
3977 | === modified file 'src/app/webcontainer/WebApp.qml' | |||
3978 | --- src/app/webcontainer/WebApp.qml 2016-09-20 15:32:49 +0000 | |||
3979 | +++ src/app/webcontainer/WebApp.qml 2016-10-13 14:41:36 +0000 | |||
3980 | @@ -249,7 +249,8 @@ | |||
3981 | 249 | id: progressbarComponent | 249 | id: progressbarComponent |
3982 | 250 | 250 | ||
3983 | 251 | ThinProgressBar { | 251 | ThinProgressBar { |
3985 | 252 | webview: webapp.currentWebview | 252 | visible: webapp.currentWebview && webapp.currentWebview.loading |
3986 | 253 | value: visible ? webapp.currentWebview.loadProgress : 0 | ||
3987 | 253 | 254 | ||
3988 | 254 | anchors { | 255 | anchors { |
3989 | 255 | left: parent.left | 256 | left: parent.left |
3990 | 256 | 257 | ||
3991 | === modified file 'tests/autopilot/webapp_container/tests/__init__.py' | |||
3992 | --- tests/autopilot/webapp_container/tests/__init__.py 2016-07-13 16:23:18 +0000 | |||
3993 | +++ tests/autopilot/webapp_container/tests/__init__.py 2016-10-13 14:41:36 +0000 | |||
3994 | @@ -52,7 +52,8 @@ | |||
3995 | 52 | return LOCAL_BROWSER_CONTAINER_PATH_NAME | 52 | return LOCAL_BROWSER_CONTAINER_PATH_NAME |
3996 | 53 | return INSTALLED_BROWSER_CONTAINER_PATH_NAME | 53 | return INSTALLED_BROWSER_CONTAINER_PATH_NAME |
3997 | 54 | 54 | ||
3999 | 55 | def launch_webcontainer_app(self, args, envvars={}, is_local_app=False): | 55 | def launch_webcontainer_app(self, args, envvars={}, is_local_app=False, |
4000 | 56 | ignore_focus=False): | ||
4001 | 56 | if model() != 'Desktop': | 57 | if model() != 'Desktop': |
4002 | 57 | args.append( | 58 | args.append( |
4003 | 58 | '--desktop_file_hint=/usr/share/applications/' | 59 | '--desktop_file_hint=/usr/share/applications/' |
4004 | @@ -74,7 +75,7 @@ | |||
4005 | 74 | except: | 75 | except: |
4006 | 75 | self.app = None | 76 | self.app = None |
4007 | 76 | 77 | ||
4009 | 77 | if not is_local_app: | 78 | if not is_local_app and not ignore_focus: |
4010 | 78 | webview = self.get_oxide_webview() | 79 | webview = self.get_oxide_webview() |
4011 | 79 | self.assertThat( | 80 | self.assertThat( |
4012 | 80 | lambda: webview.activeFocus, | 81 | lambda: webview.activeFocus, |
4013 | @@ -128,10 +129,12 @@ | |||
4014 | 128 | 'schemeUriHandleFilterResult(QString)')[-1][0] | 129 | 'schemeUriHandleFilterResult(QString)')[-1][0] |
4015 | 129 | return result | 130 | return result |
4016 | 130 | 131 | ||
4018 | 131 | def browse_to(self, url): | 132 | def browse_to(self, url, wait_for_load=True): |
4019 | 132 | webview = self.get_oxide_webview() | 133 | webview = self.get_oxide_webview() |
4020 | 133 | webview.slots.navigateToUrl(url) | 134 | webview.slots.navigateToUrl(url) |
4022 | 134 | self.assert_page_eventually_loaded(url) | 135 | |
4023 | 136 | if wait_for_load: | ||
4024 | 137 | self.assert_page_eventually_loaded(url) | ||
4025 | 135 | 138 | ||
4026 | 136 | def kill_app(self, signal=signal.SIGKILL): | 139 | def kill_app(self, signal=signal.SIGKILL): |
4027 | 137 | os.kill(self.app.pid, signal) | 140 | os.kill(self.app.pid, signal) |
4028 | @@ -161,9 +164,9 @@ | |||
4029 | 161 | return self.base_url[len(self.BASE_URL_SCHEME):] | 164 | return self.base_url[len(self.BASE_URL_SCHEME):] |
4030 | 162 | 165 | ||
4031 | 163 | def launch_webcontainer_app_with_local_http_server( | 166 | def launch_webcontainer_app_with_local_http_server( |
4033 | 164 | self, args, path='/', envvars={}, homepage=''): | 167 | self, args, path='/', envvars={}, homepage='', ignore_focus=False): |
4034 | 165 | self.url = self.base_url + path | 168 | self.url = self.base_url + path |
4035 | 166 | if len(homepage) != 0: | 169 | if len(homepage) != 0: |
4036 | 167 | self.url = homepage | 170 | self.url = homepage |
4037 | 168 | args.append(self.url) | 171 | args.append(self.url) |
4039 | 169 | self.launch_webcontainer_app(args, envvars) | 172 | self.launch_webcontainer_app(args, envvars, ignore_focus=ignore_focus) |
4040 | 170 | 173 | ||
4041 | === modified file 'tests/autopilot/webapp_container/tests/fake_servers.py' | |||
4042 | --- tests/autopilot/webapp_container/tests/fake_servers.py 2016-05-26 17:07:44 +0000 | |||
4043 | +++ tests/autopilot/webapp_container/tests/fake_servers.py 2016-10-13 14:41:36 +0000 | |||
4044 | @@ -391,6 +391,40 @@ | |||
4045 | 391 | color_url = qs['color_url_part'][0] | 391 | color_url = qs['color_url_part'][0] |
4046 | 392 | self.serve_content( | 392 | self.serve_content( |
4047 | 393 | self.local_browse_link_chain_content(next, color_url)) | 393 | self.local_browse_link_chain_content(next, color_url)) |
4048 | 394 | elif self.path == "/js-alert-dialog": | ||
4049 | 395 | self.send_response(200) | ||
4050 | 396 | html = '<html><body><script type="text/javascript">' | ||
4051 | 397 | html += 'window.onload = function() {' | ||
4052 | 398 | html += ' window.alert("Alert Dialog")' | ||
4053 | 399 | html += '} </script></body></html>' | ||
4054 | 400 | self.serve_content(html) | ||
4055 | 401 | elif self.path == "/js-before-unload-dialog": | ||
4056 | 402 | self.send_response(200) | ||
4057 | 403 | html = '<html><body><script type="text/javascript">' | ||
4058 | 404 | html += 'window.onbeforeunload = function(e) {' | ||
4059 | 405 | html += ' var dialogText = "Dialog text here";' | ||
4060 | 406 | html += ' e.returnValue = dialogText;' | ||
4061 | 407 | html += ' return dialogText;' | ||
4062 | 408 | html += '}; </script></body></html>' | ||
4063 | 409 | self.serve_content(html) | ||
4064 | 410 | elif self.path == "/js-confirm-dialog": | ||
4065 | 411 | self.send_response(200) | ||
4066 | 412 | html = '<html><body><script type="text/javascript">' | ||
4067 | 413 | html += 'window.onload = function() {' | ||
4068 | 414 | html += ' if (window.confirm("Confirm Dialog") == true) {' | ||
4069 | 415 | html += ' document.title = "OK" } ' | ||
4070 | 416 | html += ' else { document.title = "CANCEL" }' | ||
4071 | 417 | html += '} </script></body></html>' | ||
4072 | 418 | self.serve_content(html) | ||
4073 | 419 | elif self.path == "/js-prompt-dialog": | ||
4074 | 420 | self.send_response(200) | ||
4075 | 421 | html = '<html><body><script type="text/javascript">' | ||
4076 | 422 | html += 'window.onload = function() {' | ||
4077 | 423 | html += ' var result = window.prompt("Prompt Dialog", "Default");' | ||
4078 | 424 | html += ' if (result != null) { document.title = result; } ' | ||
4079 | 425 | html += ' else { document.title = "CANCEL" }' | ||
4080 | 426 | html += '} </script></body></html>' | ||
4081 | 427 | self.serve_content(html) | ||
4082 | 394 | else: | 428 | else: |
4083 | 395 | self.send_error(404) | 429 | self.send_error(404) |
4084 | 396 | 430 | ||
4085 | 397 | 431 | ||
4086 | === added file 'tests/autopilot/webapp_container/tests/test_js_dialogs.py' | |||
4087 | --- tests/autopilot/webapp_container/tests/test_js_dialogs.py 1970-01-01 00:00:00 +0000 | |||
4088 | +++ tests/autopilot/webapp_container/tests/test_js_dialogs.py 2016-10-13 14:41:36 +0000 | |||
4089 | @@ -0,0 +1,214 @@ | |||
4090 | 1 | # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- | ||
4091 | 2 | # | ||
4092 | 3 | # Copyright 2016 Canonical | ||
4093 | 4 | # | ||
4094 | 5 | # This program is free software: you can redistribute it and/or modify it | ||
4095 | 6 | # under the terms of the GNU General Public License version 3, as published | ||
4096 | 7 | # by the Free Software Foundation. | ||
4097 | 8 | # | ||
4098 | 9 | # This program is distributed in the hope that it will be useful, | ||
4099 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
4100 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
4101 | 12 | # GNU General Public License for more details. | ||
4102 | 13 | # | ||
4103 | 14 | # You should have received a copy of the GNU General Public License | ||
4104 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
4105 | 16 | |||
4106 | 17 | from webapp_container.tests import WebappContainerTestCaseWithLocalContentBase | ||
4107 | 18 | |||
4108 | 19 | from testtools.matchers import Equals | ||
4109 | 20 | from autopilot.matchers import Eventually | ||
4110 | 21 | |||
4111 | 22 | |||
4112 | 23 | class DialogWrapper(object): | ||
4113 | 24 | def __init__(self, dialog): | ||
4114 | 25 | self.dialog = dialog | ||
4115 | 26 | |||
4116 | 27 | self.text = self.dialog.text | ||
4117 | 28 | self.wait_until_destroyed = self.dialog.wait_until_destroyed | ||
4118 | 29 | self.visible = self.dialog.visible | ||
4119 | 30 | |||
4120 | 31 | |||
4121 | 32 | class AlertDialog(DialogWrapper): | ||
4122 | 33 | def get_ok_button(self): | ||
4123 | 34 | return self.dialog.select_single("Button", objectName="okButton") | ||
4124 | 35 | |||
4125 | 36 | |||
4126 | 37 | class BeforeUnloadDialog(DialogWrapper): | ||
4127 | 38 | def get_leave_button(self): | ||
4128 | 39 | return self.dialog.select_single("Button", objectName="leaveButton") | ||
4129 | 40 | |||
4130 | 41 | def get_stay_button(self): | ||
4131 | 42 | return self.dialog.select_single("Button", objectName="stayButton") | ||
4132 | 43 | |||
4133 | 44 | |||
4134 | 45 | class ConfirmDialog(DialogWrapper): | ||
4135 | 46 | def get_cancel_button(self): | ||
4136 | 47 | return self.dialog.select_single("Button", objectName="cancelButton") | ||
4137 | 48 | |||
4138 | 49 | def get_ok_button(self): | ||
4139 | 50 | return self.dialog.select_single("Button", objectName="okButton") | ||
4140 | 51 | |||
4141 | 52 | |||
4142 | 53 | class PromptDialog(DialogWrapper): | ||
4143 | 54 | def get_cancel_button(self): | ||
4144 | 55 | return self.dialog.select_single("Button", objectName="cancelButton") | ||
4145 | 56 | |||
4146 | 57 | def get_input_textfield(self): | ||
4147 | 58 | return self.dialog.select_single("TextField", | ||
4148 | 59 | objectName="inputTextField") | ||
4149 | 60 | |||
4150 | 61 | def get_ok_button(self): | ||
4151 | 62 | return self.dialog.select_single("Button", objectName="okButton") | ||
4152 | 63 | |||
4153 | 64 | |||
4154 | 65 | class TestJSDialogs(WebappContainerTestCaseWithLocalContentBase): | ||
4155 | 66 | |||
4156 | 67 | def test_alert(self): | ||
4157 | 68 | self.launch_webcontainer_app_with_local_http_server( | ||
4158 | 69 | [], '/js-alert-dialog', ignore_focus=True) | ||
4159 | 70 | |||
4160 | 71 | dialog = AlertDialog( | ||
4161 | 72 | self.app.wait_select_single("Dialog", objectName="alertDialog") | ||
4162 | 73 | ) | ||
4163 | 74 | dialog.visible.wait_for(True) | ||
4164 | 75 | |||
4165 | 76 | # Check alert text is correct | ||
4166 | 77 | self.assertThat(dialog.text, Equals("Alert Dialog")) | ||
4167 | 78 | |||
4168 | 79 | # Click OK, check dialog is destroyed | ||
4169 | 80 | self.pointing_device.click_object(dialog.get_ok_button()) | ||
4170 | 81 | dialog.wait_until_destroyed() | ||
4171 | 82 | |||
4172 | 83 | def test_before_unload_leave(self): | ||
4173 | 84 | testUrl = self.base_url + "/" | ||
4174 | 85 | |||
4175 | 86 | self.launch_webcontainer_app_with_local_http_server( | ||
4176 | 87 | [], '/js-before-unload-dialog', ignore_focus=True) | ||
4177 | 88 | |||
4178 | 89 | # Change the url to trigger window.onBeforeUnload | ||
4179 | 90 | self.browse_to(testUrl, wait_for_load=False) | ||
4180 | 91 | |||
4181 | 92 | dialog = BeforeUnloadDialog( | ||
4182 | 93 | self.app.wait_select_single("Dialog", | ||
4183 | 94 | objectName="beforeUnloadDialog") | ||
4184 | 95 | ) | ||
4185 | 96 | dialog.visible.wait_for(True) | ||
4186 | 97 | |||
4187 | 98 | # Click leave and check that url changes | ||
4188 | 99 | self.pointing_device.click_object(dialog.get_leave_button()) | ||
4189 | 100 | |||
4190 | 101 | self.assertThat(self.get_oxide_webview().url, | ||
4191 | 102 | Eventually(Equals(testUrl))) | ||
4192 | 103 | |||
4193 | 104 | def test_before_unload_stay(self): | ||
4194 | 105 | page = '/js-before-unload-dialog' | ||
4195 | 106 | beforeUnloadUrl = self.base_url + page | ||
4196 | 107 | testUrl = self.base_url + "/" | ||
4197 | 108 | |||
4198 | 109 | self.launch_webcontainer_app_with_local_http_server( | ||
4199 | 110 | [], page, ignore_focus=True) | ||
4200 | 111 | |||
4201 | 112 | # Change the url to trigger window.onBeforeUnload | ||
4202 | 113 | self.browse_to(testUrl, wait_for_load=False) | ||
4203 | 114 | |||
4204 | 115 | dialog = BeforeUnloadDialog( | ||
4205 | 116 | self.app.wait_select_single("Dialog", | ||
4206 | 117 | objectName="beforeUnloadDialog") | ||
4207 | 118 | ) | ||
4208 | 119 | dialog.visible.wait_for(True) | ||
4209 | 120 | |||
4210 | 121 | # Click stay and check url does not change | ||
4211 | 122 | self.pointing_device.click_object(dialog.get_stay_button()) | ||
4212 | 123 | |||
4213 | 124 | self.assertThat(self.get_oxide_webview().url, | ||
4214 | 125 | Eventually(Equals(beforeUnloadUrl))) | ||
4215 | 126 | |||
4216 | 127 | def test_confirm_cancel(self): | ||
4217 | 128 | self.launch_webcontainer_app_with_local_http_server( | ||
4218 | 129 | [], '/js-confirm-dialog', ignore_focus=True) | ||
4219 | 130 | |||
4220 | 131 | dialog = ConfirmDialog( | ||
4221 | 132 | self.app.wait_select_single("Dialog", objectName="confirmDialog") | ||
4222 | 133 | ) | ||
4223 | 134 | dialog.visible.wait_for(True) | ||
4224 | 135 | |||
4225 | 136 | # Check that confirm text is correct | ||
4226 | 137 | self.assertThat(dialog.text, Equals("Confirm Dialog")) | ||
4227 | 138 | |||
4228 | 139 | # Click cancel and check that dialog is destroyed | ||
4229 | 140 | self.pointing_device.click_object(dialog.get_cancel_button()) | ||
4230 | 141 | dialog.wait_until_destroyed() | ||
4231 | 142 | |||
4232 | 143 | # Check that title changes to cancel | ||
4233 | 144 | self.assertThat(self.get_webcontainer_webview().title, | ||
4234 | 145 | Eventually(Equals("CANCEL"))) | ||
4235 | 146 | |||
4236 | 147 | def test_confirm_ok(self): | ||
4237 | 148 | self.launch_webcontainer_app_with_local_http_server( | ||
4238 | 149 | [], '/js-confirm-dialog', ignore_focus=True) | ||
4239 | 150 | |||
4240 | 151 | dialog = ConfirmDialog( | ||
4241 | 152 | self.app.wait_select_single("Dialog", objectName="confirmDialog") | ||
4242 | 153 | ) | ||
4243 | 154 | dialog.visible.wait_for(True) | ||
4244 | 155 | |||
4245 | 156 | # Check that confirm text is correct | ||
4246 | 157 | self.assertThat(dialog.text, Equals("Confirm Dialog")) | ||
4247 | 158 | |||
4248 | 159 | # Click OK and check that dialog is destroyed | ||
4249 | 160 | self.pointing_device.click_object(dialog.get_ok_button()) | ||
4250 | 161 | dialog.wait_until_destroyed() | ||
4251 | 162 | |||
4252 | 163 | # Check that title changes to OK | ||
4253 | 164 | self.assertThat(self.get_webcontainer_webview().title, | ||
4254 | 165 | Eventually(Equals("OK"))) | ||
4255 | 166 | |||
4256 | 167 | def test_prompt_cancel(self): | ||
4257 | 168 | self.launch_webcontainer_app_with_local_http_server( | ||
4258 | 169 | [], '/js-prompt-dialog', ignore_focus=True) | ||
4259 | 170 | |||
4260 | 171 | dialog = PromptDialog( | ||
4261 | 172 | self.app.wait_select_single("Dialog", objectName="promptDialog") | ||
4262 | 173 | ) | ||
4263 | 174 | dialog.visible.wait_for(True) | ||
4264 | 175 | |||
4265 | 176 | # Check that prompt text is correct and default textfield | ||
4266 | 177 | self.assertThat(dialog.text, Equals("Prompt Dialog")) | ||
4267 | 178 | self.assertThat(dialog.get_input_textfield().text, | ||
4268 | 179 | Equals("Default")) | ||
4269 | 180 | |||
4270 | 181 | # Click cancel and check that dialog is destroyed | ||
4271 | 182 | self.pointing_device.click_object(dialog.get_cancel_button()) | ||
4272 | 183 | dialog.wait_until_destroyed() | ||
4273 | 184 | |||
4274 | 185 | # Check that title changes to cancel | ||
4275 | 186 | self.assertThat(self.get_webcontainer_webview().title, | ||
4276 | 187 | Eventually(Equals("CANCEL"))) | ||
4277 | 188 | |||
4278 | 189 | def test_prompt_ok(self): | ||
4279 | 190 | self.launch_webcontainer_app_with_local_http_server( | ||
4280 | 191 | [], '/js-prompt-dialog', ignore_focus=True) | ||
4281 | 192 | |||
4282 | 193 | dialog = PromptDialog( | ||
4283 | 194 | self.app.wait_select_single("Dialog", objectName="promptDialog") | ||
4284 | 195 | ) | ||
4285 | 196 | dialog.visible.wait_for(True) | ||
4286 | 197 | |||
4287 | 198 | # Check that prompt text is correct and default textfield | ||
4288 | 199 | self.assertThat(dialog.text, Equals("Prompt Dialog")) | ||
4289 | 200 | self.assertThat(dialog.get_input_textfield().text, | ||
4290 | 201 | Equals("Default")) | ||
4291 | 202 | |||
4292 | 203 | # Enter text into textfield | ||
4293 | 204 | text = "TEST" | ||
4294 | 205 | entry = dialog.get_input_textfield() | ||
4295 | 206 | entry.write(text) | ||
4296 | 207 | |||
4297 | 208 | # Click ok and check that dialog is destroyed | ||
4298 | 209 | self.pointing_device.click_object(dialog.get_ok_button()) | ||
4299 | 210 | dialog.wait_until_destroyed() | ||
4300 | 211 | |||
4301 | 212 | # Check that title changes to text entered in textfield | ||
4302 | 213 | self.assertThat(self.get_webcontainer_webview().title, | ||
4303 | 214 | Eventually(Equals(text))) | ||
4304 | 0 | 215 | ||
4305 | === modified file 'tests/autopilot/webbrowser_app/emulators/browser.py' | |||
4306 | --- tests/autopilot/webbrowser_app/emulators/browser.py 2016-08-10 15:43:04 +0000 | |||
4307 | +++ tests/autopilot/webbrowser_app/emulators/browser.py 2016-10-13 14:41:36 +0000 | |||
4308 | @@ -179,9 +179,9 @@ | |||
4309 | 179 | def get_history_view(self): | 179 | def get_history_view(self): |
4310 | 180 | try: | 180 | try: |
4311 | 181 | if self.wide: | 181 | if self.wide: |
4313 | 182 | return self.select_single(HistoryViewWide) | 182 | return self.wait_select_single(HistoryViewWide) |
4314 | 183 | else: | 183 | else: |
4316 | 184 | return self.select_single(HistoryView) | 184 | return self.wait_select_single(HistoryView) |
4317 | 185 | except exceptions.StateNotFoundError: | 185 | except exceptions.StateNotFoundError: |
4318 | 186 | return None | 186 | return None |
4319 | 187 | 187 | ||
4320 | @@ -238,6 +238,26 @@ | |||
4321 | 238 | menu.click_cancel_action() | 238 | menu.click_cancel_action() |
4322 | 239 | menu.wait_until_destroyed() | 239 | menu.wait_until_destroyed() |
4323 | 240 | 240 | ||
4324 | 241 | def get_alert_dialog(self): | ||
4325 | 242 | return AlertDialog( | ||
4326 | 243 | self.wait_select_single("Dialog", objectName="alertDialog") | ||
4327 | 244 | ) | ||
4328 | 245 | |||
4329 | 246 | def get_before_unload_dialog(self): | ||
4330 | 247 | return BeforeUnloadDialog( | ||
4331 | 248 | self.wait_select_single("Dialog", objectName="beforeUnloadDialog") | ||
4332 | 249 | ) | ||
4333 | 250 | |||
4334 | 251 | def get_confirm_dialog(self): | ||
4335 | 252 | return ConfirmDialog( | ||
4336 | 253 | self.wait_select_single("Dialog", objectName="confirmDialog") | ||
4337 | 254 | ) | ||
4338 | 255 | |||
4339 | 256 | def get_prompt_dialog(self): | ||
4340 | 257 | return PromptDialog( | ||
4341 | 258 | self.wait_select_single("Dialog", objectName="promptDialog") | ||
4342 | 259 | ) | ||
4343 | 260 | |||
4344 | 241 | 261 | ||
4345 | 242 | class Chrome(uitk.UbuntuUIToolkitCustomProxyObjectBase): | 262 | class Chrome(uitk.UbuntuUIToolkitCustomProxyObjectBase): |
4346 | 243 | 263 | ||
4347 | @@ -724,3 +744,45 @@ | |||
4348 | 724 | def click_cancel_action(self): | 744 | def click_cancel_action(self): |
4349 | 725 | action = self.select_single("Empty", objectName="cancelAction") | 745 | action = self.select_single("Empty", objectName="cancelAction") |
4350 | 726 | self.pointing_device.click_object(action) | 746 | self.pointing_device.click_object(action) |
4351 | 747 | |||
4352 | 748 | |||
4353 | 749 | class DialogWrapper(object): | ||
4354 | 750 | def __init__(self, dialog): | ||
4355 | 751 | self.dialog = dialog | ||
4356 | 752 | |||
4357 | 753 | self.text = self.dialog.text | ||
4358 | 754 | self.wait_until_destroyed = self.dialog.wait_until_destroyed | ||
4359 | 755 | self.visible = self.dialog.visible | ||
4360 | 756 | |||
4361 | 757 | |||
4362 | 758 | class AlertDialog(DialogWrapper): | ||
4363 | 759 | def get_ok_button(self): | ||
4364 | 760 | return self.dialog.select_single("Button", objectName="okButton") | ||
4365 | 761 | |||
4366 | 762 | |||
4367 | 763 | class BeforeUnloadDialog(DialogWrapper): | ||
4368 | 764 | def get_leave_button(self): | ||
4369 | 765 | return self.dialog.select_single("Button", objectName="leaveButton") | ||
4370 | 766 | |||
4371 | 767 | def get_stay_button(self): | ||
4372 | 768 | return self.dialog.select_single("Button", objectName="stayButton") | ||
4373 | 769 | |||
4374 | 770 | |||
4375 | 771 | class ConfirmDialog(DialogWrapper): | ||
4376 | 772 | def get_cancel_button(self): | ||
4377 | 773 | return self.dialog.select_single("Button", objectName="cancelButton") | ||
4378 | 774 | |||
4379 | 775 | def get_ok_button(self): | ||
4380 | 776 | return self.dialog.select_single("Button", objectName="okButton") | ||
4381 | 777 | |||
4382 | 778 | |||
4383 | 779 | class PromptDialog(DialogWrapper): | ||
4384 | 780 | def get_cancel_button(self): | ||
4385 | 781 | return self.dialog.select_single("Button", objectName="cancelButton") | ||
4386 | 782 | |||
4387 | 783 | def get_input_textfield(self): | ||
4388 | 784 | return self.dialog.select_single("TextField", | ||
4389 | 785 | objectName="inputTextField") | ||
4390 | 786 | |||
4391 | 787 | def get_ok_button(self): | ||
4392 | 788 | return self.dialog.select_single("Button", objectName="okButton") | ||
4393 | 727 | 789 | ||
4394 | === modified file 'tests/autopilot/webbrowser_app/tests/http_server.py' | |||
4395 | --- tests/autopilot/webbrowser_app/tests/http_server.py 2016-03-07 18:29:23 +0000 | |||
4396 | +++ tests/autopilot/webbrowser_app/tests/http_server.py 2016-10-13 14:41:36 +0000 | |||
4397 | @@ -252,6 +252,54 @@ | |||
4398 | 252 | html += '50%; transform: translate(-50%, -50%); font-size: 500%">' | 252 | html += '50%; transform: translate(-50%, -50%); font-size: 500%">' |
4399 | 253 | html += 'Supercalifragilisticexpialidocious</div></body></html>' | 253 | html += 'Supercalifragilisticexpialidocious</div></body></html>' |
4400 | 254 | self.send_html(html) | 254 | self.send_html(html) |
4401 | 255 | elif self.path == "/redirect-no-title-header": | ||
4402 | 256 | self.send_response(301) | ||
4403 | 257 | self.send_header("Location", "/redirect-destination") | ||
4404 | 258 | self.end_headers() | ||
4405 | 259 | elif self.path == "/redirect-no-title-js": | ||
4406 | 260 | self.send_response(200) | ||
4407 | 261 | html = '<html><body><script type="text/javascript">' | ||
4408 | 262 | html += 'window.location.href = "/redirect-destination"' | ||
4409 | 263 | html += '</script></body></html>' | ||
4410 | 264 | self.send_html(html) | ||
4411 | 265 | elif self.path == "/redirect-destination": | ||
4412 | 266 | self.send_response(200) | ||
4413 | 267 | html = '<html><body><p>redirect-destination</p></body></html>' | ||
4414 | 268 | self.send_html(html) | ||
4415 | 269 | elif self.path == "/js-alert-dialog": | ||
4416 | 270 | self.send_response(200) | ||
4417 | 271 | html = '<html><body><script type="text/javascript">' | ||
4418 | 272 | html += 'window.onload = function() {' | ||
4419 | 273 | html += ' window.alert("Alert Dialog")' | ||
4420 | 274 | html += '} </script></body></html>' | ||
4421 | 275 | self.send_html(html) | ||
4422 | 276 | elif self.path == "/js-before-unload-dialog": | ||
4423 | 277 | self.send_response(200) | ||
4424 | 278 | html = '<html><body><script type="text/javascript">' | ||
4425 | 279 | html += 'window.onbeforeunload = function(e) {' | ||
4426 | 280 | html += ' var dialogText = "Dialog text here";' | ||
4427 | 281 | html += ' e.returnValue = dialogText;' | ||
4428 | 282 | html += ' return dialogText;' | ||
4429 | 283 | html += '}; </script></body></html>' | ||
4430 | 284 | self.send_html(html) | ||
4431 | 285 | elif self.path == "/js-confirm-dialog": | ||
4432 | 286 | self.send_response(200) | ||
4433 | 287 | html = '<html><body><script type="text/javascript">' | ||
4434 | 288 | html += 'window.onload = function() {' | ||
4435 | 289 | html += ' if (window.confirm("Confirm Dialog") == true) {' | ||
4436 | 290 | html += ' document.title = "OK" } ' | ||
4437 | 291 | html += ' else { document.title = "CANCEL" }' | ||
4438 | 292 | html += '} </script></body></html>' | ||
4439 | 293 | self.send_html(html) | ||
4440 | 294 | elif self.path == "/js-prompt-dialog": | ||
4441 | 295 | self.send_response(200) | ||
4442 | 296 | html = '<html><body><script type="text/javascript">' | ||
4443 | 297 | html += 'window.onload = function() {' | ||
4444 | 298 | html += ' var result = window.prompt("Prompt Dialog", "Default");' | ||
4445 | 299 | html += ' if (result != null) { document.title = result; } ' | ||
4446 | 300 | html += ' else { document.title = "CANCEL" }' | ||
4447 | 301 | html += '} </script></body></html>' | ||
4448 | 302 | self.send_html(html) | ||
4449 | 255 | else: | 303 | else: |
4450 | 256 | self.send_error(404) | 304 | self.send_error(404) |
4451 | 257 | 305 | ||
4452 | 258 | 306 | ||
4453 | === modified file 'tests/autopilot/webbrowser_app/tests/test_history.py' | |||
4454 | --- tests/autopilot/webbrowser_app/tests/test_history.py 2016-02-29 20:42:13 +0000 | |||
4455 | +++ tests/autopilot/webbrowser_app/tests/test_history.py 2016-10-13 14:41:36 +0000 | |||
4456 | @@ -124,3 +124,38 @@ | |||
4457 | 124 | self.main_window.wait_until_page_loaded(pushed) | 124 | self.main_window.wait_until_page_loaded(pushed) |
4458 | 125 | self.open_history() | 125 | self.open_history() |
4459 | 126 | self.expect_history_entries([pushed, url, self.url]) | 126 | self.expect_history_entries([pushed, url, self.url]) |
4460 | 127 | |||
4461 | 128 | def test_title_correct_redirect_header(self): | ||
4462 | 129 | # Regression test for https://launchpad.net/bugs/1603835 | ||
4463 | 130 | url_redirect = self.base_url + "/redirect-no-title-header" | ||
4464 | 131 | url_destination = self.base_url + "/redirect-destination" | ||
4465 | 132 | url_test = self.base_url + "/test1" | ||
4466 | 133 | |||
4467 | 134 | self.main_window.go_to_url(url_redirect) | ||
4468 | 135 | self.main_window.wait_until_page_loaded(url_destination) | ||
4469 | 136 | |||
4470 | 137 | self.open_history() | ||
4471 | 138 | |||
4472 | 139 | entries = self.expect_history_entries( | ||
4473 | 140 | [url_destination, url_test] | ||
4474 | 141 | ) | ||
4475 | 142 | self.assertThat(entries[0].title, Equals("test/redirect-destination")) | ||
4476 | 143 | self.assertThat(entries[1].title, Equals("test page 1")) | ||
4477 | 144 | |||
4478 | 145 | def test_title_correct_redirect_js(self): | ||
4479 | 146 | # Regression test for https://launchpad.net/bugs/1603835 | ||
4480 | 147 | url_redirect = self.base_url + "/redirect-no-title-js" | ||
4481 | 148 | url_destination = self.base_url + "/redirect-destination" | ||
4482 | 149 | url_test = self.base_url + "/test1" | ||
4483 | 150 | |||
4484 | 151 | self.main_window.go_to_url(url_redirect) | ||
4485 | 152 | self.main_window.wait_until_page_loaded(url_destination) | ||
4486 | 153 | |||
4487 | 154 | self.open_history() | ||
4488 | 155 | |||
4489 | 156 | entries = self.expect_history_entries( | ||
4490 | 157 | [url_destination, url_redirect, url_test] | ||
4491 | 158 | ) | ||
4492 | 159 | self.assertThat(entries[0].title, Equals("test/redirect-destination")) | ||
4493 | 160 | self.assertThat(entries[1].title, Equals("test/redirect-no-title-js")) | ||
4494 | 161 | self.assertThat(entries[2].title, Equals("test page 1")) | ||
4495 | 127 | 162 | ||
4496 | === added file 'tests/autopilot/webbrowser_app/tests/test_js_dialogs.py' | |||
4497 | --- tests/autopilot/webbrowser_app/tests/test_js_dialogs.py 1970-01-01 00:00:00 +0000 | |||
4498 | +++ tests/autopilot/webbrowser_app/tests/test_js_dialogs.py 2016-10-13 14:41:36 +0000 | |||
4499 | @@ -0,0 +1,156 @@ | |||
4500 | 1 | # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- | ||
4501 | 2 | # | ||
4502 | 3 | # Copyright 2016 Canonical | ||
4503 | 4 | # | ||
4504 | 5 | # This program is free software: you can redistribute it and/or modify it | ||
4505 | 6 | # under the terms of the GNU General Public License version 3, as published | ||
4506 | 7 | # by the Free Software Foundation. | ||
4507 | 8 | # | ||
4508 | 9 | # This program is distributed in the hope that it will be useful, | ||
4509 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
4510 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
4511 | 12 | # GNU General Public License for more details. | ||
4512 | 13 | # | ||
4513 | 14 | # You should have received a copy of the GNU General Public License | ||
4514 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
4515 | 16 | |||
4516 | 17 | from testtools.matchers import Equals | ||
4517 | 18 | from autopilot.matchers import Eventually | ||
4518 | 19 | |||
4519 | 20 | from webbrowser_app.tests import StartOpenRemotePageTestCaseBase | ||
4520 | 21 | |||
4521 | 22 | |||
4522 | 23 | class TestJSDialogs(StartOpenRemotePageTestCaseBase): | ||
4523 | 24 | |||
4524 | 25 | def test_alert(self): | ||
4525 | 26 | url = self.base_url + "/js-alert-dialog" | ||
4526 | 27 | self.main_window.go_to_url(url) | ||
4527 | 28 | |||
4528 | 29 | dialog = self.main_window.get_alert_dialog() | ||
4529 | 30 | dialog.visible.wait_for(True) | ||
4530 | 31 | |||
4531 | 32 | # Check alert text is correct | ||
4532 | 33 | self.assertThat(dialog.text, Equals("Alert Dialog")) | ||
4533 | 34 | |||
4534 | 35 | # Click OK, check dialog is destroyed | ||
4535 | 36 | self.pointing_device.click_object(dialog.get_ok_button()) | ||
4536 | 37 | dialog.wait_until_destroyed() | ||
4537 | 38 | |||
4538 | 39 | def test_before_unload_leave(self): | ||
4539 | 40 | beforeUnloadUrl = self.base_url + "/js-before-unload-dialog" | ||
4540 | 41 | testUrl = self.base_url + "/test1" | ||
4541 | 42 | |||
4542 | 43 | self.main_window.go_to_url(beforeUnloadUrl) | ||
4543 | 44 | self.main_window.wait_until_page_loaded(beforeUnloadUrl) | ||
4544 | 45 | |||
4545 | 46 | # Change the url to trigger window.onBeforeUnload | ||
4546 | 47 | self.main_window.go_to_url(testUrl) | ||
4547 | 48 | |||
4548 | 49 | dialog = self.main_window.get_before_unload_dialog() | ||
4549 | 50 | dialog.visible.wait_for(True) | ||
4550 | 51 | |||
4551 | 52 | # Click leave and check that url changes | ||
4552 | 53 | self.pointing_device.click_object(dialog.get_leave_button()) | ||
4553 | 54 | |||
4554 | 55 | self.assertThat(self.main_window.get_current_webview().url, | ||
4555 | 56 | Eventually(Equals(testUrl))) | ||
4556 | 57 | |||
4557 | 58 | def test_before_unload_stay(self): | ||
4558 | 59 | beforeUnloadUrl = self.base_url + "/js-before-unload-dialog" | ||
4559 | 60 | testUrl = self.base_url + "/test1" | ||
4560 | 61 | |||
4561 | 62 | self.main_window.go_to_url(beforeUnloadUrl) | ||
4562 | 63 | self.main_window.wait_until_page_loaded(beforeUnloadUrl) | ||
4563 | 64 | |||
4564 | 65 | # Change the url to trigger window.onBeforeUnload | ||
4565 | 66 | self.main_window.go_to_url(testUrl) | ||
4566 | 67 | |||
4567 | 68 | dialog = self.main_window.get_before_unload_dialog() | ||
4568 | 69 | dialog.visible.wait_for(True) | ||
4569 | 70 | |||
4570 | 71 | # Click stay and check url does not change | ||
4571 | 72 | self.pointing_device.click_object(dialog.get_stay_button()) | ||
4572 | 73 | |||
4573 | 74 | self.assertThat(self.main_window.get_current_webview().url, | ||
4574 | 75 | Eventually(Equals(beforeUnloadUrl))) | ||
4575 | 76 | |||
4576 | 77 | def test_confirm_cancel(self): | ||
4577 | 78 | url = self.base_url + "/js-confirm-dialog" | ||
4578 | 79 | self.main_window.go_to_url(url) | ||
4579 | 80 | |||
4580 | 81 | dialog = self.main_window.get_confirm_dialog() | ||
4581 | 82 | dialog.visible.wait_for(True) | ||
4582 | 83 | |||
4583 | 84 | # Check that confirm text is correct | ||
4584 | 85 | self.assertThat(dialog.text, Equals("Confirm Dialog")) | ||
4585 | 86 | |||
4586 | 87 | # Click cancel and check that dialog is destroyed | ||
4587 | 88 | self.pointing_device.click_object(dialog.get_cancel_button()) | ||
4588 | 89 | dialog.wait_until_destroyed() | ||
4589 | 90 | |||
4590 | 91 | # Check that title changes to cancel | ||
4591 | 92 | self.assertThat(self.main_window.get_current_webview().title, | ||
4592 | 93 | Eventually(Equals("CANCEL"))) | ||
4593 | 94 | |||
4594 | 95 | def test_confirm_ok(self): | ||
4595 | 96 | url = self.base_url + "/js-confirm-dialog" | ||
4596 | 97 | self.main_window.go_to_url(url) | ||
4597 | 98 | |||
4598 | 99 | dialog = self.main_window.get_confirm_dialog() | ||
4599 | 100 | dialog.visible.wait_for(True) | ||
4600 | 101 | |||
4601 | 102 | # Check that confirm text is correct | ||
4602 | 103 | self.assertThat(dialog.text, Equals("Confirm Dialog")) | ||
4603 | 104 | |||
4604 | 105 | # Click OK and check that dialog is destroyed | ||
4605 | 106 | self.pointing_device.click_object(dialog.get_ok_button()) | ||
4606 | 107 | dialog.wait_until_destroyed() | ||
4607 | 108 | |||
4608 | 109 | # Check that title changes to OK | ||
4609 | 110 | self.assertThat(self.main_window.get_current_webview().title, | ||
4610 | 111 | Eventually(Equals("OK"))) | ||
4611 | 112 | |||
4612 | 113 | def test_prompt_cancel(self): | ||
4613 | 114 | url = self.base_url + "/js-prompt-dialog" | ||
4614 | 115 | self.main_window.go_to_url(url) | ||
4615 | 116 | |||
4616 | 117 | dialog = self.main_window.get_prompt_dialog() | ||
4617 | 118 | dialog.visible.wait_for(True) | ||
4618 | 119 | |||
4619 | 120 | # Check that prompt text is correct and default textfield | ||
4620 | 121 | self.assertThat(dialog.text, Equals("Prompt Dialog")) | ||
4621 | 122 | self.assertThat(dialog.get_input_textfield().text, | ||
4622 | 123 | Equals("Default")) | ||
4623 | 124 | |||
4624 | 125 | # Click cancel and check that dialog is destroyed | ||
4625 | 126 | self.pointing_device.click_object(dialog.get_cancel_button()) | ||
4626 | 127 | dialog.wait_until_destroyed() | ||
4627 | 128 | |||
4628 | 129 | # Check that title changes to cancel | ||
4629 | 130 | self.assertThat(self.main_window.get_current_webview().title, | ||
4630 | 131 | Eventually(Equals("CANCEL"))) | ||
4631 | 132 | |||
4632 | 133 | def test_prompt_ok(self): | ||
4633 | 134 | url = self.base_url + "/js-prompt-dialog" | ||
4634 | 135 | self.main_window.go_to_url(url) | ||
4635 | 136 | |||
4636 | 137 | dialog = self.main_window.get_prompt_dialog() | ||
4637 | 138 | dialog.visible.wait_for(True) | ||
4638 | 139 | |||
4639 | 140 | # Check that prompt text is correct and default textfield | ||
4640 | 141 | self.assertThat(dialog.text, Equals("Prompt Dialog")) | ||
4641 | 142 | self.assertThat(dialog.get_input_textfield().text, | ||
4642 | 143 | Equals("Default")) | ||
4643 | 144 | |||
4644 | 145 | # Enter text into textfield | ||
4645 | 146 | text = "TEST" | ||
4646 | 147 | entry = dialog.get_input_textfield() | ||
4647 | 148 | entry.write(text) | ||
4648 | 149 | |||
4649 | 150 | # Click ok and check that dialog is destroyed | ||
4650 | 151 | self.pointing_device.click_object(dialog.get_ok_button()) | ||
4651 | 152 | dialog.wait_until_destroyed() | ||
4652 | 153 | |||
4653 | 154 | # Check that title changes to text entered in textfield | ||
4654 | 155 | self.assertThat(self.main_window.get_current_webview().title, | ||
4655 | 156 | Eventually(Equals(text))) | ||
4656 | 0 | 157 | ||
4657 | === modified file 'tests/autopilot/webbrowser_app/tests/test_new_tab_view.py' | |||
4658 | --- tests/autopilot/webbrowser_app/tests/test_new_tab_view.py 2016-08-10 15:43:04 +0000 | |||
4659 | +++ tests/autopilot/webbrowser_app/tests/test_new_tab_view.py 2016-10-13 14:41:36 +0000 | |||
4660 | @@ -440,14 +440,7 @@ | |||
4661 | 440 | folder = folders[0] | 440 | folder = folders[0] |
4662 | 441 | folder_cx = folder.globalRect.x + folder.width / 2 | 441 | folder_cx = folder.globalRect.x + folder.width / 2 |
4663 | 442 | folder_cy = folder.globalRect.y + folder.height / 2 | 442 | folder_cy = folder.globalRect.y + folder.height / 2 |
4672 | 443 | # Work around https://launchpad.net/bugs/1499437 by dragging downwards | 443 | self.pointing_device.drag(rect.x, rect.y, folder_cx, folder_cy) |
4665 | 444 | # a little bit first, then to the target folder. | ||
4666 | 445 | self.pointing_device.move_to_object(grip) | ||
4667 | 446 | pos = self.pointing_device.position() | ||
4668 | 447 | self.pointing_device.press() | ||
4669 | 448 | self.pointing_device.move(pos[0], pos[1] + 20) | ||
4670 | 449 | self.pointing_device.move(folder_cx, folder_cy) | ||
4671 | 450 | self.pointing_device.release() | ||
4673 | 451 | self.assertThat(grip.globalRect, Eventually(Equals(rect))) | 444 | self.assertThat(grip.globalRect, Eventually(Equals(rect))) |
4674 | 452 | 445 | ||
4675 | 453 | # Test that dragging an item to another folder removes it from this one | 446 | # Test that dragging an item to another folder removes it from this one |
4676 | 454 | 447 | ||
4677 | === modified file 'tests/unittests/downloads-model/tst_DownloadsModelTests.cpp' | |||
4678 | --- tests/unittests/downloads-model/tst_DownloadsModelTests.cpp 2016-01-12 10:37:15 +0000 | |||
4679 | +++ tests/unittests/downloads-model/tst_DownloadsModelTests.cpp 2016-10-13 14:41:36 +0000 | |||
4680 | @@ -17,6 +17,8 @@ | |||
4681 | 17 | */ | 17 | */ |
4682 | 18 | 18 | ||
4683 | 19 | #include <QtCore/QDir> | 19 | #include <QtCore/QDir> |
4684 | 20 | #include <QtCore/QFileInfo> | ||
4685 | 21 | #include <QtCore/QTemporaryDir> | ||
4686 | 20 | #include <QtCore/QTemporaryFile> | 22 | #include <QtCore/QTemporaryFile> |
4687 | 21 | #include <QtTest/QSignalSpy> | 23 | #include <QtTest/QSignalSpy> |
4688 | 22 | #include <QtTest/QtTest> | 24 | #include <QtTest/QtTest> |
4689 | @@ -27,11 +29,16 @@ | |||
4690 | 27 | Q_OBJECT | 29 | Q_OBJECT |
4691 | 28 | 30 | ||
4692 | 29 | private: | 31 | private: |
4693 | 32 | QTemporaryDir homeDir; | ||
4694 | 30 | DownloadsModel* model; | 33 | DownloadsModel* model; |
4695 | 31 | 34 | ||
4696 | 32 | private Q_SLOTS: | 35 | private Q_SLOTS: |
4697 | 33 | void init() | 36 | void init() |
4698 | 34 | { | 37 | { |
4699 | 38 | // QStandardPaths::setTestModeEnabled() doesn't affect | ||
4700 | 39 | // QStandardPaths::DownloadLocation, so we must override $HOME to | ||
4701 | 40 | // ensure the test won't write data to the user's home directory. | ||
4702 | 41 | qputenv("HOME", homeDir.path().toUtf8()); | ||
4703 | 35 | model = new DownloadsModel; | 42 | model = new DownloadsModel; |
4704 | 36 | model->setDatabasePath(":memory:"); | 43 | model->setDatabasePath(":memory:"); |
4705 | 37 | } | 44 | } |
4706 | @@ -39,6 +46,7 @@ | |||
4707 | 39 | void cleanup() | 46 | void cleanup() |
4708 | 40 | { | 47 | { |
4709 | 41 | delete model; | 48 | delete model; |
4710 | 49 | qunsetenv("HOME"); | ||
4711 | 42 | } | 50 | } |
4712 | 43 | 51 | ||
4713 | 44 | void shouldBeInitiallyEmpty() | 52 | void shouldBeInitiallyEmpty() |
4714 | @@ -58,90 +66,177 @@ | |||
4715 | 58 | QVERIFY(roleNames.contains("paused")); | 66 | QVERIFY(roleNames.contains("paused")); |
4716 | 59 | QVERIFY(roleNames.contains("error")); | 67 | QVERIFY(roleNames.contains("error")); |
4717 | 60 | QVERIFY(roleNames.contains("created")); | 68 | QVERIFY(roleNames.contains("created")); |
4718 | 69 | QVERIFY(roleNames.contains("incognito")); | ||
4719 | 61 | } | 70 | } |
4720 | 62 | 71 | ||
4721 | 63 | void shouldContainAddedEntries() | 72 | void shouldContainAddedEntries() |
4722 | 64 | { | 73 | { |
4723 | 65 | QVERIFY(!model->contains(QStringLiteral("testid"))); | 74 | QVERIFY(!model->contains(QStringLiteral("testid"))); |
4725 | 66 | model->add(QStringLiteral("testid"), QUrl(QStringLiteral("http://example.org/")), QStringLiteral("text/html")); | 75 | model->add(QStringLiteral("testid"), QUrl(QStringLiteral("http://example.org/")), QStringLiteral("text/html"), false); |
4726 | 67 | QVERIFY(model->contains(QStringLiteral("testid"))); | 76 | QVERIFY(model->contains(QStringLiteral("testid"))); |
4727 | 68 | } | 77 | } |
4728 | 69 | 78 | ||
4729 | 70 | void shouldAddNewEntries() | 79 | void shouldAddNewEntries() |
4730 | 71 | { | 80 | { |
4732 | 72 | QSignalSpy spy(model, SIGNAL(added(QString, QUrl, QString))); | 81 | QSignalSpy spy(model, SIGNAL(rowsInserted(const QModelIndex&, int, int))); |
4733 | 73 | 82 | ||
4735 | 74 | model->add("testid", QUrl("http://example.org/"), "text/plain"); | 83 | model->add(QStringLiteral("testid"), QUrl(QStringLiteral("http://example.org/")), QStringLiteral("text/plain"), false); |
4736 | 75 | QCOMPARE(model->rowCount(), 1); | 84 | QCOMPARE(model->rowCount(), 1); |
4737 | 76 | QCOMPARE(spy.count(), 1); | 85 | QCOMPARE(spy.count(), 1); |
4738 | 77 | QVariantList args = spy.takeFirst(); | 86 | QVariantList args = spy.takeFirst(); |
4742 | 78 | QCOMPARE(args.at(0).toString(), QString("testid")); | 87 | QCOMPARE(args.at(0).toInt(), 0); |
4743 | 79 | QCOMPARE(args.at(1).toUrl(), QUrl("http://example.org/")); | 88 | QCOMPARE(args.at(1).toInt(), 0); |
4744 | 80 | QCOMPARE(args.at(2).toString(), QString("text/plain")); | 89 | QCOMPARE(model->data(model->index(0), DownloadsModel::DownloadId).toString(), QStringLiteral("testid")); |
4745 | 90 | QCOMPARE(model->data(model->index(0), DownloadsModel::Url).toUrl(), QUrl(QStringLiteral("http://example.org/"))); | ||
4746 | 91 | QCOMPARE(model->data(model->index(0), DownloadsModel::Mimetype).toString(), QStringLiteral("text/plain")); | ||
4747 | 81 | 92 | ||
4749 | 82 | model->add("testid2", QUrl("http://example.org/pdf"), "application/pdf"); | 93 | model->add(QStringLiteral("testid2"), QUrl(QStringLiteral("http://example.org/pdf")), QStringLiteral("application/pdf"), false); |
4750 | 83 | QCOMPARE(model->rowCount(), 2); | 94 | QCOMPARE(model->rowCount(), 2); |
4751 | 84 | QCOMPARE(spy.count(), 1); | 95 | QCOMPARE(spy.count(), 1); |
4752 | 85 | args = spy.takeFirst(); | 96 | args = spy.takeFirst(); |
4756 | 86 | QCOMPARE(args.at(0).toString(), QString("testid2")); | 97 | QCOMPARE(args.at(0).toInt(), 0); |
4757 | 87 | QCOMPARE(args.at(1).toUrl(), QUrl("http://example.org/pdf")); | 98 | QCOMPARE(args.at(1).toInt(), 0); |
4758 | 88 | QCOMPARE(args.at(2).toString(), QString("application/pdf")); | 99 | QCOMPARE(model->data(model->index(0), DownloadsModel::DownloadId).toString(), QStringLiteral("testid2")); |
4759 | 100 | QCOMPARE(model->data(model->index(0), DownloadsModel::Url).toUrl(), QUrl(QStringLiteral("http://example.org/pdf"))); | ||
4760 | 101 | QCOMPARE(model->data(model->index(0), DownloadsModel::Mimetype).toString(), QStringLiteral("application/pdf")); | ||
4761 | 89 | } | 102 | } |
4762 | 90 | 103 | ||
4763 | 91 | void shouldRemoveCancelled() | 104 | void shouldRemoveCancelled() |
4764 | 92 | { | 105 | { |
4768 | 93 | model->add("testid", QUrl("http://example.org/"), "text/plain"); | 106 | model->add(QStringLiteral("testid"), QUrl(QStringLiteral("http://example.org/")), QStringLiteral("text/plain"), false); |
4769 | 94 | model->add("testid2", QUrl("http://example.org/pdf"), "application/pdf"); | 107 | model->add(QStringLiteral("testid2"), QUrl(QStringLiteral("http://example.org/pdf")), QStringLiteral("application/pdf"), false); |
4770 | 95 | model->add("testid3", QUrl("https://example.org/secure.png"), "image/png"); | 108 | model->add(QStringLiteral("testid3"), QUrl(QStringLiteral("https://example.org/secure.png")), QStringLiteral("image/png"), false); |
4771 | 96 | QCOMPARE(model->rowCount(), 3); | 109 | QCOMPARE(model->rowCount(), 3); |
4772 | 97 | 110 | ||
4774 | 98 | model->cancelDownload("testid2"); | 111 | model->cancelDownload(QStringLiteral("testid2")); |
4775 | 99 | QCOMPARE(model->rowCount(), 2); | 112 | QCOMPARE(model->rowCount(), 2); |
4776 | 100 | 113 | ||
4778 | 101 | model->cancelDownload("invalid"); | 114 | model->cancelDownload(QStringLiteral("invalid")); |
4779 | 102 | QCOMPARE(model->rowCount(), 2); | 115 | QCOMPARE(model->rowCount(), 2); |
4780 | 103 | } | 116 | } |
4781 | 104 | 117 | ||
4782 | 105 | void shouldCompleteDownloads() | 118 | void shouldCompleteDownloads() |
4783 | 106 | { | 119 | { |
4793 | 107 | QSignalSpy spy(model, SIGNAL(completeChanged(QString, bool))); | 120 | model->add(QStringLiteral("testid"), QUrl(QStringLiteral("http://example.org/")), QStringLiteral("text/plain"), false); |
4794 | 108 | 121 | QVERIFY(!model->data(model->index(0, 0), DownloadsModel::Complete).toBool()); | |
4795 | 109 | model->add("testid", QUrl("http://example.org/"), "text/plain"); | 122 | QSignalSpy spy(model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&))); |
4796 | 110 | QVERIFY(!model->data(model->index(0, 0), DownloadsModel::Complete).toBool()); | 123 | |
4797 | 111 | model->setComplete("testid", true); | 124 | model->setComplete(QStringLiteral("testid"), true); |
4798 | 112 | QCOMPARE(spy.count(), 1); | 125 | QVERIFY(model->data(model->index(0, 0), DownloadsModel::Complete).toBool()); |
4799 | 113 | QVariantList args = spy.takeFirst(); | 126 | QCOMPARE(spy.count(), 1); |
4800 | 114 | QCOMPARE(args.at(0).toString(), QString("testid")); | 127 | QVariantList args = spy.takeFirst(); |
4801 | 115 | QCOMPARE(args.at(1).toBool(), true); | 128 | QCOMPARE(args.at(0).toModelIndex().row(), 0); |
4802 | 129 | QCOMPARE(args.at(1).toModelIndex().row(), 0); | ||
4803 | 130 | QVector<int> roles = args.at(2).value<QVector<int> >(); | ||
4804 | 131 | QCOMPARE(roles.size(), 1); | ||
4805 | 132 | QCOMPARE(roles.at(0), (int) DownloadsModel::Complete); | ||
4806 | 133 | |||
4807 | 134 | model->setComplete(QStringLiteral("testid"), true); | ||
4808 | 135 | QVERIFY(model->data(model->index(0, 0), DownloadsModel::Complete).toBool()); | ||
4809 | 136 | QVERIFY(spy.isEmpty()); | ||
4810 | 137 | |||
4811 | 138 | model->setComplete(QStringLiteral("testid"), false); | ||
4812 | 139 | QVERIFY(!model->data(model->index(0, 0), DownloadsModel::Complete).toBool()); | ||
4813 | 140 | QCOMPARE(spy.count(), 1); | ||
4814 | 141 | args = spy.takeFirst(); | ||
4815 | 142 | QCOMPARE(args.at(0).toModelIndex().row(), 0); | ||
4816 | 143 | QCOMPARE(args.at(1).toModelIndex().row(), 0); | ||
4817 | 144 | roles = args.at(2).value<QVector<int> >(); | ||
4818 | 145 | QCOMPARE(roles.size(), 1); | ||
4819 | 146 | QCOMPARE(roles.at(0), (int) DownloadsModel::Complete); | ||
4820 | 147 | } | ||
4821 | 148 | |||
4822 | 149 | void shouldSetError() | ||
4823 | 150 | { | ||
4824 | 151 | model->add(QStringLiteral("testid"), QUrl(QStringLiteral("http://example.org/")), QStringLiteral("text/plain"), false); | ||
4825 | 152 | QVERIFY(model->data(model->index(0, 0), DownloadsModel::Error).toString().isEmpty()); | ||
4826 | 153 | QSignalSpy spy(model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&))); | ||
4827 | 154 | |||
4828 | 155 | model->setError(QStringLiteral("testid"), QStringLiteral("foo")); | ||
4829 | 156 | QCOMPARE(model->data(model->index(0, 0), DownloadsModel::Error).toString(), QStringLiteral("foo")); | ||
4830 | 157 | QCOMPARE(spy.count(), 1); | ||
4831 | 158 | QVariantList args = spy.takeFirst(); | ||
4832 | 159 | QCOMPARE(args.at(0).toModelIndex().row(), 0); | ||
4833 | 160 | QCOMPARE(args.at(1).toModelIndex().row(), 0); | ||
4834 | 161 | QVector<int> roles = args.at(2).value<QVector<int> >(); | ||
4835 | 162 | QCOMPARE(roles.size(), 1); | ||
4836 | 163 | QCOMPARE(roles.at(0), (int) DownloadsModel::Error); | ||
4837 | 164 | |||
4838 | 165 | model->setError(QStringLiteral("testid"), QStringLiteral("foo")); | ||
4839 | 166 | QCOMPARE(model->data(model->index(0, 0), DownloadsModel::Error).toString(), QStringLiteral("foo")); | ||
4840 | 167 | QVERIFY(spy.isEmpty()); | ||
4841 | 168 | |||
4842 | 169 | model->setError(QStringLiteral("testid"), QString("bar")); | ||
4843 | 170 | QCOMPARE(model->data(model->index(0, 0), DownloadsModel::Error).toString(), QStringLiteral("bar")); | ||
4844 | 171 | QCOMPARE(spy.count(), 1); | ||
4845 | 172 | args = spy.takeFirst(); | ||
4846 | 173 | QCOMPARE(args.at(0).toModelIndex().row(), 0); | ||
4847 | 174 | QCOMPARE(args.at(1).toModelIndex().row(), 0); | ||
4848 | 175 | roles = args.at(2).value<QVector<int> >(); | ||
4849 | 176 | QCOMPARE(roles.size(), 1); | ||
4850 | 177 | QCOMPARE(roles.at(0), (int) DownloadsModel::Error); | ||
4851 | 178 | } | ||
4852 | 179 | |||
4853 | 180 | void shouldPauseAndResumeDownload() | ||
4854 | 181 | { | ||
4855 | 182 | model->add(QStringLiteral("testid"), QUrl(QStringLiteral("http://example.org/")), QStringLiteral("text/plain"), false); | ||
4856 | 183 | QVERIFY(!model->data(model->index(0, 0), DownloadsModel::Paused).toBool()); | ||
4857 | 184 | QSignalSpy spy(model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&))); | ||
4858 | 185 | |||
4859 | 186 | model->pauseDownload(QStringLiteral("testid")); | ||
4860 | 187 | QVERIFY(model->data(model->index(0, 0), DownloadsModel::Paused).toBool()); | ||
4861 | 188 | QCOMPARE(spy.count(), 1); | ||
4862 | 189 | QVariantList args = spy.takeFirst(); | ||
4863 | 190 | QCOMPARE(args.at(0).toModelIndex().row(), 0); | ||
4864 | 191 | QCOMPARE(args.at(1).toModelIndex().row(), 0); | ||
4865 | 192 | QVector<int> roles = args.at(2).value<QVector<int> >(); | ||
4866 | 193 | QCOMPARE(roles.size(), 1); | ||
4867 | 194 | QCOMPARE(roles.at(0), (int) DownloadsModel::Paused); | ||
4868 | 195 | |||
4869 | 196 | model->pauseDownload(QStringLiteral("testid")); | ||
4870 | 197 | QVERIFY(model->data(model->index(0, 0), DownloadsModel::Paused).toBool()); | ||
4871 | 198 | QVERIFY(spy.isEmpty()); | ||
4872 | 199 | |||
4873 | 200 | model->resumeDownload(QStringLiteral("testid")); | ||
4874 | 201 | QVERIFY(!model->data(model->index(0, 0), DownloadsModel::Paused).toBool()); | ||
4875 | 202 | QCOMPARE(spy.count(), 1); | ||
4876 | 203 | args = spy.takeFirst(); | ||
4877 | 204 | QCOMPARE(args.at(0).toModelIndex().row(), 0); | ||
4878 | 205 | QCOMPARE(args.at(1).toModelIndex().row(), 0); | ||
4879 | 206 | roles = args.at(2).value<QVector<int> >(); | ||
4880 | 207 | QCOMPARE(roles.size(), 1); | ||
4881 | 208 | QCOMPARE(roles.at(0), (int) DownloadsModel::Paused); | ||
4882 | 116 | } | 209 | } |
4883 | 117 | 210 | ||
4884 | 118 | void shouldKeepEntriesSortedChronologically() | 211 | void shouldKeepEntriesSortedChronologically() |
4885 | 119 | { | 212 | { |
4889 | 120 | model->add("testid", QUrl("http://example.org/"), "text/plain"); | 213 | model->add(QStringLiteral("testid"), QUrl(QStringLiteral("http://example.org/")), QStringLiteral("text/plain"), false); |
4890 | 121 | model->add("testid2", QUrl("http://example.org/pdf"), "application/pdf"); | 214 | model->add(QStringLiteral("testid2"), QUrl(QStringLiteral("http://example.org/pdf")), QStringLiteral("application/pdf"), false); |
4891 | 122 | model->add("testid3", QUrl("https://example.org/secure.png"), "image/png"); | 215 | model->add(QStringLiteral("testid3"), QUrl(QStringLiteral("https://example.org/secure.png")), QStringLiteral("image/png"), false); |
4892 | 123 | 216 | ||
4896 | 124 | QCOMPARE(model->data(model->index(0, 0), DownloadsModel::DownloadId).toString(), QString("testid3")); | 217 | QCOMPARE(model->data(model->index(0, 0), DownloadsModel::DownloadId).toString(), QStringLiteral("testid3")); |
4897 | 125 | QCOMPARE(model->data(model->index(1, 0), DownloadsModel::DownloadId).toString(), QString("testid2")); | 218 | QCOMPARE(model->data(model->index(1, 0), DownloadsModel::DownloadId).toString(), QStringLiteral("testid2")); |
4898 | 126 | QCOMPARE(model->data(model->index(2, 0), DownloadsModel::DownloadId).toString(), QString("testid")); | 219 | QCOMPARE(model->data(model->index(2, 0), DownloadsModel::DownloadId).toString(), QStringLiteral("testid")); |
4899 | 127 | } | 220 | } |
4900 | 128 | 221 | ||
4901 | 129 | void shouldReturnData() | 222 | void shouldReturnData() |
4902 | 130 | { | 223 | { |
4904 | 131 | model->add("testid", QUrl("http://example.org/"), "text/plain"); | 224 | model->add(QStringLiteral("testid"), QUrl(QStringLiteral("http://example.org/")), QStringLiteral("text/plain"), false); |
4905 | 132 | QVERIFY(!model->data(QModelIndex(), DownloadsModel::DownloadId).isValid()); | 225 | QVERIFY(!model->data(QModelIndex(), DownloadsModel::DownloadId).isValid()); |
4906 | 133 | QVERIFY(!model->data(model->index(-1, 0), DownloadsModel::DownloadId).isValid()); | 226 | QVERIFY(!model->data(model->index(-1, 0), DownloadsModel::DownloadId).isValid()); |
4907 | 134 | QVERIFY(!model->data(model->index(3, 0), DownloadsModel::DownloadId).isValid()); | 227 | QVERIFY(!model->data(model->index(3, 0), DownloadsModel::DownloadId).isValid()); |
4911 | 135 | QCOMPARE(model->data(model->index(0, 0), DownloadsModel::DownloadId).toString(), QString("testid")); | 228 | QCOMPARE(model->data(model->index(0, 0), DownloadsModel::DownloadId).toString(), QStringLiteral("testid")); |
4912 | 136 | QCOMPARE(model->data(model->index(0, 0), DownloadsModel::Url).toUrl(), QUrl("http://example.org/")); | 229 | QCOMPARE(model->data(model->index(0, 0), DownloadsModel::Url).toUrl(), QUrl(QStringLiteral("http://example.org/"))); |
4913 | 137 | QCOMPARE(model->data(model->index(0, 0), DownloadsModel::Mimetype).toString(), QString("text/plain")); | 230 | QCOMPARE(model->data(model->index(0, 0), DownloadsModel::Mimetype).toString(), QStringLiteral("text/plain")); |
4914 | 138 | QVERIFY(model->data(model->index(0, 0), DownloadsModel::Created).toDateTime() <= QDateTime::currentDateTime()); | 231 | QVERIFY(model->data(model->index(0, 0), DownloadsModel::Created).toDateTime() <= QDateTime::currentDateTime()); |
4915 | 139 | QVERIFY(!model->data(model->index(0, 0), DownloadsModel::Complete).toBool()); | 232 | QVERIFY(!model->data(model->index(0, 0), DownloadsModel::Complete).toBool()); |
4916 | 233 | QVERIFY(!model->data(model->index(0, 0), DownloadsModel::Incognito).toBool()); | ||
4917 | 234 | QVERIFY(!model->data(model->index(0, 0), -1).isValid()); | ||
4918 | 140 | } | 235 | } |
4919 | 141 | 236 | ||
4920 | 142 | void shouldReturnDatabasePath() | 237 | void shouldReturnDatabasePath() |
4921 | 143 | { | 238 | { |
4923 | 144 | QCOMPARE(model->databasePath(), QString(":memory:")); | 239 | QCOMPARE(model->databasePath(), QStringLiteral(":memory:")); |
4924 | 145 | } | 240 | } |
4925 | 146 | 241 | ||
4926 | 147 | void shouldNotifyWhenSettingDatabasePath() | 242 | void shouldNotifyWhenSettingDatabasePath() |
4927 | @@ -149,14 +244,14 @@ | |||
4928 | 149 | QSignalSpy spyPath(model, SIGNAL(databasePathChanged())); | 244 | QSignalSpy spyPath(model, SIGNAL(databasePathChanged())); |
4929 | 150 | QSignalSpy spyReset(model, SIGNAL(modelReset())); | 245 | QSignalSpy spyReset(model, SIGNAL(modelReset())); |
4930 | 151 | 246 | ||
4932 | 152 | model->setDatabasePath(":memory:"); | 247 | model->setDatabasePath(QStringLiteral(":memory:")); |
4933 | 153 | QVERIFY(spyPath.isEmpty()); | 248 | QVERIFY(spyPath.isEmpty()); |
4934 | 154 | QVERIFY(spyReset.isEmpty()); | 249 | QVERIFY(spyReset.isEmpty()); |
4935 | 155 | 250 | ||
4937 | 156 | model->setDatabasePath(""); | 251 | model->setDatabasePath(QStringLiteral("")); |
4938 | 157 | QCOMPARE(spyPath.count(), 1); | 252 | QCOMPARE(spyPath.count(), 1); |
4939 | 158 | QCOMPARE(spyReset.count(), 1); | 253 | QCOMPARE(spyReset.count(), 1); |
4941 | 159 | QCOMPARE(model->databasePath(), QString(":memory:")); | 254 | QCOMPARE(model->databasePath(), QStringLiteral(":memory:")); |
4942 | 160 | } | 255 | } |
4943 | 161 | 256 | ||
4944 | 162 | void shouldSerializeOnDisk() | 257 | void shouldSerializeOnDisk() |
4945 | @@ -167,8 +262,10 @@ | |||
4946 | 167 | delete model; | 262 | delete model; |
4947 | 168 | model = new DownloadsModel; | 263 | model = new DownloadsModel; |
4948 | 169 | model->setDatabasePath(fileName); | 264 | model->setDatabasePath(fileName); |
4951 | 170 | model->add("testid", QUrl("http://example.org/"), "text/plain"); | 265 | model->add(QStringLiteral("testid"), QUrl(QStringLiteral("http://example.org/")), QStringLiteral("text/plain"), false); |
4952 | 171 | model->add("testid2", QUrl("http://example.org/pdf"), "application/pdf"); | 266 | model->add(QStringLiteral("testid2"), QUrl(QStringLiteral("http://example.org/pdf")), QStringLiteral("application/pdf"), false); |
4953 | 267 | model->add(QStringLiteral("testid3"), QUrl(QStringLiteral("http://example.org/incognito.pdf")), QStringLiteral("application/pdf"), true); | ||
4954 | 268 | QCOMPARE(model->rowCount(), 3); | ||
4955 | 172 | delete model; | 269 | delete model; |
4956 | 173 | model = new DownloadsModel; | 270 | model = new DownloadsModel; |
4957 | 174 | model->setDatabasePath(fileName); | 271 | model->setDatabasePath(fileName); |
4958 | @@ -180,17 +277,139 @@ | |||
4959 | 180 | { | 277 | { |
4960 | 181 | QCOMPARE(model->property("count").toInt(), 0); | 278 | QCOMPARE(model->property("count").toInt(), 0); |
4961 | 182 | QCOMPARE(model->rowCount(), 0); | 279 | QCOMPARE(model->rowCount(), 0); |
4963 | 183 | model->add("testid", QUrl("http://example.org/"), "text/plain"); | 280 | model->add(QStringLiteral("testid"), QUrl(QStringLiteral("http://example.org/")), QStringLiteral("text/plain"), false); |
4964 | 184 | QCOMPARE(model->property("count").toInt(), 1); | 281 | QCOMPARE(model->property("count").toInt(), 1); |
4965 | 185 | QCOMPARE(model->rowCount(), 1); | 282 | QCOMPARE(model->rowCount(), 1); |
4967 | 186 | model->add("testid2", QUrl("http://example.org/pdf"), "application/pdf"); | 283 | model->add(QStringLiteral("testid2"), QUrl(QStringLiteral("http://example.org/pdf")), QStringLiteral("application/pdf"), false); |
4968 | 187 | QCOMPARE(model->property("count").toInt(), 2); | 284 | QCOMPARE(model->property("count").toInt(), 2); |
4969 | 188 | QCOMPARE(model->rowCount(), 2); | 285 | QCOMPARE(model->rowCount(), 2); |
4971 | 189 | model->add("testid3", QUrl("https://example.org/secure.png"), "image/png"); | 286 | model->add(QStringLiteral("testid3"), QUrl(QStringLiteral("https://example.org/secure.png")), QStringLiteral("image/png"), false); |
4972 | 190 | QCOMPARE(model->property("count").toInt(), 3); | 287 | QCOMPARE(model->property("count").toInt(), 3); |
4973 | 191 | QCOMPARE(model->rowCount(), 3); | 288 | QCOMPARE(model->rowCount(), 3); |
4974 | 192 | } | 289 | } |
4975 | 193 | 290 | ||
4976 | 291 | void shouldPruneIncognitoDownloads() | ||
4977 | 292 | { | ||
4978 | 293 | model->add(QStringLiteral("testid1"), QUrl(QStringLiteral("http://example.org/1")), QStringLiteral("text/plain"), false); | ||
4979 | 294 | model->add(QStringLiteral("testid2"), QUrl(QStringLiteral("http://example.org/2")), QStringLiteral("text/plain"), true); | ||
4980 | 295 | model->add(QStringLiteral("testid3"), QUrl(QStringLiteral("http://example.org/3")), QStringLiteral("text/plain"), false); | ||
4981 | 296 | model->add(QStringLiteral("testid4"), QUrl(QStringLiteral("http://example.org/4")), QStringLiteral("text/plain"), true); | ||
4982 | 297 | QCOMPARE(model->rowCount(), 4); | ||
4983 | 298 | QSignalSpy spyRowsRemoved(model, SIGNAL(rowsRemoved(const QModelIndex&, int, int))); | ||
4984 | 299 | QSignalSpy spyRowCountChanged(model, SIGNAL(rowCountChanged())); | ||
4985 | 300 | model->pruneIncognitoDownloads(); | ||
4986 | 301 | QCOMPARE(model->rowCount(), 2); | ||
4987 | 302 | QCOMPARE(model->data(model->index(0), DownloadsModel::DownloadId).toString(), QStringLiteral("testid3")); | ||
4988 | 303 | QCOMPARE(model->data(model->index(1), DownloadsModel::DownloadId).toString(), QStringLiteral("testid1")); | ||
4989 | 304 | QCOMPARE(spyRowsRemoved.count(), 2); | ||
4990 | 305 | QCOMPARE(spyRowCountChanged.count(), 2); | ||
4991 | 306 | } | ||
4992 | 307 | |||
4993 | 308 | void shouldFailToMoveInvalidDownload() | ||
4994 | 309 | { | ||
4995 | 310 | model->add(QStringLiteral("testid"), QUrl(QStringLiteral("http://example.org/")), QStringLiteral("text/plain"), false); | ||
4996 | 311 | QTemporaryFile tempFile; | ||
4997 | 312 | tempFile.open(); | ||
4998 | 313 | QSignalSpy spy(model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&))); | ||
4999 | 314 | model->moveToDownloads(QStringLiteral("foobar"), tempFile.fileName()); | ||
5000 | 315 | QVERIFY(spy.isEmpty()); |
The diff has been truncated for viewing.
FAILED: Continuous integration, rev:1560 /jenkins. canonical. com/system- apps/job/ lp-webbrowser- app-ci/ 705/ /jenkins. canonical. com/system- apps/job/ build/1837/ console /jenkins. canonical. com/system- apps/job/ build-0- fetch/1838 /jenkins. canonical. com/system- apps/job/ build-2- binpkg/ arch=amd64, release= vivid+overlay/ 1678 /jenkins. canonical. com/system- apps/job/ build-2- binpkg/ arch=amd64, release= vivid+overlay/ 1678/artifact/ output/ *zip*/output. zip /jenkins. canonical. com/system- apps/job/ build-2- binpkg/ arch=amd64, release= xenial+ overlay/ 1678 /jenkins. canonical. com/system- apps/job/ build-2- binpkg/ arch=amd64, release= xenial+ overlay/ 1678/artifact/ output/ *zip*/output. zip /jenkins. canonical. com/system- apps/job/ build-2- binpkg/ arch=amd64, release= yakkety/ 1678 /jenkins. canonical. com/system- apps/job/ build-2- binpkg/ arch=amd64, release= yakkety/ 1678/artifact/ output/ *zip*/output. zip /jenkins. canonical. com/system- apps/job/ build-2- binpkg/ arch=armhf, release= vivid+overlay/ 1678 /jenkins. canonical. com/system- apps/job/ build-2- binpkg/ arch=armhf, release= vivid+overlay/ 1678/artifact/ output/ *zip*/output. zip /jenkins. canonical. com/system- apps/job/ build-2- binpkg/ arch=armhf, release= xenial+ overlay/ 1678 /jenkins. canonical. com/system- apps/job/ build-2- binpkg/ arch=armhf, release= xenial+ overlay/ 1678/artifact/ output/ *zip*/output. zip /jenkins. canonical. com/system- apps/job/ build-2- binpkg/ arch=armhf, release= yakkety/ 1678 /jenkins. canonical. com/system- apps/job/ build-2- binpkg/ arch=armhf, release= yakkety/ 1678/artifact/ output/ *zip*/output. zip /jenkins. canonical. com/system- apps/job/ build-2- binpkg/ arch=i386, release= vivid+overlay/ 1678/console /jenkins. canonical. com/system- apps/job/ build-2- binpkg/ arch=i386, release= xenial+ overlay/ 1678/console /jenkins. canonical. com/system- apps/job/ build-2- binpkg/ arch=i386, release= yakkety/ 1678 /jenkins. canonical. com/system- apps/job/ build-2- binpkg/ arch=i386, release= yakkety/ 1678/artifact/ output/ *zip*/output. zip
https:/
Executed test runs:
FAILURE: https:/
SUCCESS: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
SUCCESS: https:/
deb: https:/
FAILURE: https:/
FAILURE: https:/
SUCCESS: https:/
deb: https:/
Click here to trigger a rebuild: /jenkins. canonical. com/system- apps/job/ lp-webbrowser- app-ci/ 705/rebuild
https:/