Merge lp:~osomon/webbrowser-app/oxide-context-menu into lp:webbrowser-app

Proposed by Olivier Tilloy
Status: Merged
Approved by: Olivier Tilloy
Approved revision: 1142
Merged at revision: 1152
Proposed branch: lp:~osomon/webbrowser-app/oxide-context-menu
Merge into: lp:webbrowser-app
Diff against target: 2579 lines (+1390/-690)
34 files modified
debian/control (+2/-12)
debian/qtdeclarative5-ubuntu-web-plugin-assets.install (+0/-1)
debian/rules (+0/-4)
doc/WebView.qdoc (+44/-5)
src/Ubuntu/Web/CMakeLists.txt (+0/-3)
src/Ubuntu/Web/Selection.qml (+0/-161)
src/Ubuntu/Web/SelectionHandle.qml (+0/-47)
src/Ubuntu/Web/UbuntuWebView02.qml (+60/-156)
src/Ubuntu/Web/selection02.js (+2/-131)
src/app/CMakeLists.txt (+1/-0)
src/app/FileExtensionMapper.js (+11/-7)
src/app/FilePickerDialog.qml (+2/-2)
src/app/WebViewImpl.qml (+20/-9)
src/app/actions/Cut.qml (+23/-0)
src/app/actions/Erase.qml (+23/-0)
src/app/actions/Paste.qml (+23/-0)
src/app/actions/Redo.qml (+23/-0)
src/app/actions/SaveLink.qml (+23/-0)
src/app/actions/SelectAll.qml (+23/-0)
src/app/actions/Undo.qml (+23/-0)
src/app/browserapplication.cpp (+9/-0)
src/app/mime-database.cpp (+33/-0)
src/app/mime-database.h (+39/-0)
src/app/webbrowser/Browser.qml (+103/-26)
src/app/webbrowser/ContextMenuMobile.qml (+168/-0)
src/app/webbrowser/ContextMenuWide.qml (+158/-0)
src/app/webbrowser/assets/stock_link.svg (+164/-0)
src/app/webcontainer/WebViewImplOxide.qml (+42/-5)
tests/autopilot/webbrowser_app/emulators/browser.py (+33/-16)
tests/autopilot/webbrowser_app/tests/http_server.py (+28/-0)
tests/autopilot/webbrowser_app/tests/test_contextmenu.py (+177/-0)
tests/autopilot/webbrowser_app/tests/test_selection.py (+0/-92)
tests/unittests/qml/tst_FileExtensionMapper.qml (+39/-0)
tests/unittests/qml/tst_UbuntuWebView02.qml (+94/-13)
To merge this branch: bzr merge lp:~osomon/webbrowser-app/oxide-context-menu
Reviewer Review Type Date Requested Status
Ken VanDine Approve
Ugo Riboni (community) Approve
PS Jenkins bot continuous-integration Approve
Review via email: mp+268786@code.launchpad.net

Commit message

Use the contextMenu API new in oxide 1.8.
Update the visuals for the context menu in narrow and wide form factors.
Add text editing commands to the context menu.
Add unit and autopilot tests for the context menu features.
This bumps the runtime dependency of webapp-container and qtdeclarative5-ubuntu-web-plugin on liboxideqt-qmlplugin to 1.8.
This also removes the qtdeclarative5-ubuntu-web-plugin-assets binary package, which contained only one PNG asset which is not used anywhere any longer.

Description of the change

Use the contextMenu API new in oxide 1.8.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Ugo Riboni (uriboni) wrote :

Works mostly as expected on desktop. Have not tested on device yet or ran tests myself.

I included various comments inline based on review of the code.

I am not sure if revision 1133 belongs in this MR, even though I understand why it was easy to include it. Up to you if you want to consider splitting it off, but it would certainly seem cleaner from a repository cleanliness point of view.

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

Thanks for your thorough review. I replied all your comments inline (and updated the code where relevant). The only pending task is to update the code to use QMimeDatabase per your recommendation, which I’m working on now.

I removed the selection mechanism as part of this MR because in fact this new context menu implementation broke it even further (it had already been quite broken since its initial implementation, but this made things slightly worse, and I spent way too much time trying to fix it). In that regard it made sense to remove it now.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Ugo Riboni (uriboni) wrote :

LGTM now and it all seems to work as expected. Tests are all ok too.

review: Approve
Revision history for this message
Ken VanDine (ken-vandine) wrote :

Packaging changes are fine

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'debian/control'
2--- debian/control 2015-08-16 23:11:48 +0000
3+++ debian/control 2015-08-27 14:01:06 +0000
4@@ -60,7 +60,7 @@
5 Depends: ${misc:Depends},
6 ${shlibs:Depends},
7 fonts-liberation,
8- liboxideqt-qmlplugin (>= 1.5),
9+ liboxideqt-qmlplugin (>= 1.8),
10 libqt5sql5-sqlite,
11 qml-module-qtquick2 (>= 5.4),
12 qml-module-qtquick-window2 (>= 5.3),
13@@ -97,24 +97,14 @@
14 Pre-Depends: ${misc:Pre-Depends}
15 Depends: ${misc:Depends},
16 ${shlibs:Depends},
17- liboxideqt-qmlplugin (>= 1.6),
18+ liboxideqt-qmlplugin (>= 1.8),
19 qml-module-qtquick2 (>= 5.4),
20 qml-module-qtquick-window2 (>= 5.3),
21 qtdeclarative5-ubuntu-ui-toolkit-plugin (>= 1.3) | qtdeclarative5-ubuntu-ui-toolkit-plugin-gles (>= 1.3),
22- qtdeclarative5-ubuntu-web-plugin-assets (>= ${source:Version}),
23 Description: Ubuntu web QML plugin
24 A standalone QML plugin that contains the WebView component,
25 in the Ubuntu.Web module.
26
27-Package: qtdeclarative5-ubuntu-web-plugin-assets
28-Architecture: all
29-Multi-Arch: foreign
30-Depends: ${misc:Depends},
31-Description: Ubuntu web QML plugin assets
32- A standalone QML plugin that contains the WebView component,
33- in the Ubuntu.Web module. This package contains the PNGs used
34- as UI elements by the plugin.
35-
36 Package: qtdeclarative5-ubuntu-web-plugin-doc
37 Section: doc
38 Architecture: all
39
40=== removed file 'debian/qtdeclarative5-ubuntu-web-plugin-assets.install'
41--- debian/qtdeclarative5-ubuntu-web-plugin-assets.install 2014-06-19 08:26:10 +0000
42+++ debian/qtdeclarative5-ubuntu-web-plugin-assets.install 1970-01-01 00:00:00 +0000
43@@ -1,1 +0,0 @@
44-usr/share/qtdeclarative5-ubuntu-web-plugin/
45
46=== modified file 'debian/rules'
47--- debian/rules 2015-02-26 18:10:41 +0000
48+++ debian/rules 2015-08-27 14:01:06 +0000
49@@ -11,10 +11,6 @@
50 dh $@ --parallel --with translations
51
52 override_dh_install:
53- ln -sf /usr/share/qtdeclarative5-ubuntu-web-plugin/assets \
54- $(CURDIR)/debian/tmp/usr/lib/*/qt5/qml/Ubuntu/Web
55- ln -sf /usr/share/qtdeclarative5-ubuntu-web-plugin/assets \
56- $(CURDIR)/debian/tmp/usr/lib/*/qt5/qml/Ubuntu/Components/Extras/Browser
57 dh_install --fail-missing
58
59 override_dh_translations:
60
61=== modified file 'doc/WebView.qdoc'
62--- doc/WebView.qdoc 2015-08-10 15:22:00 +0000
63+++ doc/WebView.qdoc 2015-08-27 14:01:06 +0000
64@@ -172,30 +172,33 @@
65 context menu (by way of a right click on desktop, or a long press on a
66 touch-enabled device, on an image or a hyperlink).
67 By default the list is empty, and no menu is shown.
68- User-defined actions can access the \l {contextualData} {contextual data}.
69+ User-defined actions can access the \l {contextModel} {context model}.
70
71 Example of user-defined actions:
72 \code
73- import Ubuntu.Components 1.1
74+ import Ubuntu.Components 1.3
75 import Ubuntu.Web 0.2
76
77 WebView {
78 contextualActions: ActionList {
79 Action {
80 text: i18n.tr("Open link in browser")
81- enabled: contextualData.href.toString()
82- onTriggered: Qt.openUrlExternally(contextualData.href)
83+ enabled: contextModel && contextModel.linkUrl.toString()
84+ onTriggered: Qt.openUrlExternally(contextModel.linkUrl)
85 }
86 }
87 }
88 \endcode
89
90- \sa contextualData
91+ \sa contextModel
92 */
93
94 /*!
95+ \deprecated
96 \qmlproperty QtObject WebView::contextualData
97
98+ This property is deprecated, use the \l {contextModel} property instead.
99+
100 An object that holds the contextual data associated with the current context
101 menu. User-defined \l {contextualActions} {contextual actions} can use this
102 data to process it when triggered.
103@@ -211,6 +214,42 @@
104 and \c img will be available, allowing a user-defined contextual action to
105 operate on both elements.
106
107+ \sa contextualActions, contextModel
108+ */
109+
110+/*!
111+ \qmlproperty QtObject WebView::contextModel
112+
113+ An object that holds the contextual data associated with the current context
114+ menu, as well as methods to interact with this data. User-defined
115+ \l {contextualActions} {contextual actions} can use this data to process it
116+ when triggered.
117+
118+ It has the following properties:
119+ \list
120+ \li linkUrl (url): the full URI of the hyperlink, if any
121+ \li srcUrl (url): the full URI of the image/media, if any
122+ \li mediaType (int): the type of media (one of Oxide.WebView.MediaTypeNone,
123+ Oxide.WebView.MediaTypeImage, Oxide.WebView.MediaTypeCanvas,
124+ Oxide.WebView.MediaTypeAudio, Oxide.WebView.MediaTypeVideo)
125+ \li isEditable (bool): whether the current element is editable
126+ \li editFlags (int): for editable elements, an OR-combined list of flags that
127+ define the current editing capabilities (Oxide.WebView.UndoCapability,
128+ Oxide.WebView.RedoCapability, Oxide.WebView.CutCapability,
129+ Oxide.WebView.CopyCapability, Oxide.WebView.PasteCapability,
130+ Oxide.WebView.EraseCapability, Oxide.WebView.SelectAllCapability)
131+ \endlist
132+
133+ It has the following methods:
134+ \list
135+ \li saveLink(): initiates a download request for the resource pointed
136+ to by the hyperlink, if any
137+ \li saveMedia(): initiates a download request for the media
138+ (image, canvas, audio, video), if any
139+ \endlist
140+
141+ When there is no active context menu, \c contextModel is null.
142+
143 \sa contextualActions
144 */
145
146
147=== removed symlink 'src/Ubuntu/Components/Extras/Browser/Selection.qml'
148=== target was u'../../../Web/Selection.qml'
149=== removed symlink 'src/Ubuntu/Components/Extras/Browser/SelectionHandle.qml'
150=== target was u'../../../Web/SelectionHandle.qml'
151=== removed symlink 'src/Ubuntu/Components/Extras/Browser/assets'
152=== target was u'../../../Web/assets/'
153=== modified file 'src/Ubuntu/Web/CMakeLists.txt'
154--- src/Ubuntu/Web/CMakeLists.txt 2015-06-22 10:29:20 +0000
155+++ src/Ubuntu/Web/CMakeLists.txt 2015-08-27 14:01:06 +0000
156@@ -20,9 +20,6 @@
157 file(GLOB QML_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.qml qmldir *.js)
158 install(TARGETS ${PLUGIN} DESTINATION ${UBUNTU_WEB_IMPORTS_DIR})
159 install(FILES ${QML_FILES} DESTINATION ${UBUNTU_WEB_IMPORTS_DIR})
160-install(DIRECTORY assets
161- DESTINATION ${CMAKE_INSTALL_DATADIR}/qtdeclarative5-ubuntu-web-plugin
162- FILES_MATCHING PATTERN *.png)
163
164 if(NOT ${CMAKE_CURRENT_BINARY_DIR} STREQUAL ${CMAKE_CURRENT_SOURCE_DIR})
165 # copy qml files over to build dir to be able to import them uninstalled
166
167=== removed file 'src/Ubuntu/Web/Selection.qml'
168--- src/Ubuntu/Web/Selection.qml 2015-08-10 15:22:00 +0000
169+++ src/Ubuntu/Web/Selection.qml 1970-01-01 00:00:00 +0000
170@@ -1,161 +0,0 @@
171-/*
172- * Copyright 2013-2015 Canonical Ltd.
173- *
174- * This file is part of webbrowser-app.
175- *
176- * webbrowser-app is free software; you can redistribute it and/or modify
177- * it under the terms of the GNU General Public License as published by
178- * the Free Software Foundation; version 3.
179- *
180- * webbrowser-app is distributed in the hope that it will be useful,
181- * but WITHOUT ANY WARRANTY; without even the implied warranty of
182- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
183- * GNU General Public License for more details.
184- *
185- * You should have received a copy of the GNU General Public License
186- * along with this program. If not, see <http://www.gnu.org/licenses/>.
187- */
188-
189-import QtQuick 2.4
190-import Ubuntu.Components 1.3
191-
192-Item {
193- id: __container
194-
195- property alias rect: __rect
196-
197- property real __minimumWidth: units.gu(5)
198- property real __minimumHeight: units.gu(5)
199-
200- readonly property bool resizing: __leftHandle.dragging || __topHandle.dragging || __rightHandle.dragging || __bottomHandle.dragging
201-
202- signal resized()
203- signal dismissed()
204-
205- MouseArea {
206- anchors.fill: parent
207- // dismiss the selection when tapping anywhere except for the handles
208- onClicked: __container.dismissed()
209- }
210-
211- Item {
212- id: __rect
213- objectName: "rectangle"
214- }
215-
216- Rectangle {
217- id: __outline
218-
219- color: "transparent"
220-
221- Rectangle {
222- anchors.fill: parent
223- radius: parent.radius
224- color: parent.border.color
225- opacity: 0.1
226- z: -1
227- }
228-
229- border {
230- width: units.dp(3)
231- color: "#19B6EE"
232- }
233- radius: units.dp(3)
234- antialiasing: true
235-
236- x: __rect.x
237- width: {
238- if (__leftHandle.dragging) {
239- return __rect.x + __rect.width - (__leftHandle.x + __leftHandle.width / 2)
240- } else if (__rightHandle.dragging) {
241- return __rightHandle.x + __rightHandle.width / 2 - __rect.x
242- } else {
243- return __rect.width
244- }
245- }
246-
247- y: __rect.y
248- height: {
249- if (__topHandle.dragging) {
250- return __rect.y + __rect.height - (__topHandle.y + __topHandle.height / 2)
251- } else if (__bottomHandle.dragging) {
252- return __bottomHandle.y + __bottomHandle.height / 2 - __rect.y
253- } else {
254- return __rect.height
255- }
256- }
257-
258- anchors {
259- left: __rightHandle.dragging ? __rect.left: undefined
260- right: __leftHandle.dragging ? __rect.right : undefined
261- top: __bottomHandle.dragging ? __rect.top : undefined
262- bottom: __topHandle.dragging ? __rect.bottom : undefined
263- }
264- }
265-
266- SelectionHandle {
267- id: __leftHandle
268- objectName: "leftHandle"
269- axis: Drag.XAxis
270- x: __rect.x - width / 2
271- y: (__topHandle.y + __bottomHandle.y) / 2
272- minimum: 0
273- maximum: __rightHandle.x - __container.__minimumWidth
274- onDraggingChanged: {
275- if (!dragging) {
276- __rect.width = __rightHandle.x - __leftHandle.x
277- __rect.x = __leftHandle.x + __leftHandle.width / 2
278- __container.resized()
279- }
280- }
281- }
282-
283- SelectionHandle {
284- id: __topHandle
285- objectName: "topHandle"
286- axis: Drag.YAxis
287- x: (__leftHandle.x + __rightHandle.x) / 2
288- y: __rect.y - height / 2
289- minimum: 0
290- maximum: __bottomHandle.y - __container.__minimumHeight
291- onDraggingChanged: {
292- if (!dragging) {
293- __rect.height = __bottomHandle.y - __topHandle.y
294- __rect.y = __topHandle.y + __topHandle.height / 2
295- __container.resized()
296- }
297- }
298- }
299-
300- SelectionHandle {
301- id: __rightHandle
302- objectName: "rightHandle"
303- axis: Drag.XAxis
304- x: __rect.x + __rect.width - width / 2
305- y: (__topHandle.y + __bottomHandle.y) / 2
306- minimum: __leftHandle.x + __container.__minimumWidth
307- maximum: __container.width
308- onDraggingChanged: {
309- if (!dragging) {
310- __rect.width = __rightHandle.x - __leftHandle.x
311- __container.resized()
312- }
313- }
314- }
315-
316- SelectionHandle {
317- id: __bottomHandle
318- objectName: "bottomHandle"
319- axis: Drag.YAxis
320- x: (__leftHandle.x + __rightHandle.x) / 2
321- y: __rect.y + __rect.height - height / 2
322- minimum: __topHandle.y + __container.__minimumHeight
323- maximum: __container.height
324- onDraggingChanged: {
325- if (!dragging) {
326- __rect.height = __bottomHandle.y - __topHandle.y
327- __container.resized()
328- }
329- }
330- }
331-}
332
333=== removed file 'src/Ubuntu/Web/SelectionHandle.qml'
334--- src/Ubuntu/Web/SelectionHandle.qml 2015-08-10 15:22:00 +0000
335+++ src/Ubuntu/Web/SelectionHandle.qml 1970-01-01 00:00:00 +0000
336@@ -1,47 +0,0 @@
337-/*
338- * Copyright 2013-2015 Canonical Ltd.
339- *
340- * This file is part of webbrowser-app.
341- *
342- * webbrowser-app is free software; you can redistribute it and/or modify
343- * it under the terms of the GNU General Public License as published by
344- * the Free Software Foundation; version 3.
345- *
346- * webbrowser-app is distributed in the hope that it will be useful,
347- * but WITHOUT ANY WARRANTY; without even the implied warranty of
348- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
349- * GNU General Public License for more details.
350- *
351- * You should have received a copy of the GNU General Public License
352- * along with this program. If not, see <http://www.gnu.org/licenses/>.
353- */
354-
355-import QtQuick 2.4
356-import Ubuntu.Components 1.3
357-
358-Image {
359- property int axis
360- property real minimum
361- property real maximum
362- property bool dragging: __mousearea.drag.active
363-
364- width: units.gu(3)
365- height: units.gu(3)
366-
367- source: "assets/multi_selection_handle.png"
368-
369- MouseArea {
370- id: __mousearea
371-
372- anchors.fill: parent
373-
374- drag {
375- target: parent
376- axis: parent.axis
377- minimumX: parent.minimum
378- maximumX: parent.maximum
379- minimumY: parent.minimum
380- maximumY: parent.maximum
381- }
382- }
383-}
384
385=== modified file 'src/Ubuntu/Web/UbuntuWebView02.qml'
386--- src/Ubuntu/Web/UbuntuWebView02.qml 2015-08-13 19:06:11 +0000
387+++ src/Ubuntu/Web/UbuntuWebView02.qml 2015-08-27 14:01:06 +0000
388@@ -18,7 +18,7 @@
389
390 import QtQuick 2.4
391 import QtQuick.Window 2.2
392-import com.canonical.Oxide 1.5 as Oxide
393+import com.canonical.Oxide 1.8 as Oxide
394 import Ubuntu.Components 1.3
395 import Ubuntu.Components.Popups 1.3
396 import "." // QTBUG-34418
397@@ -38,40 +38,10 @@
398
399 messageHandlers: [
400 Oxide.ScriptMessageHandler {
401- msgId: "contextmenu"
402- contexts: ["oxide://selection/"]
403- callback: function(msg, frame) {
404- internal.dismissCurrentContextualMenu()
405- internal.dismissCurrentSelection()
406- internal.fillContextualData(msg.args)
407- if (contextualActions != null) {
408- for (var i = 0; i < contextualActions.actions.length; ++i) {
409- if (contextualActions.actions[i].enabled) {
410- contextualRectangle.position(msg.args)
411- internal.currentContextualMenu = PopupUtils.open(contextualPopover, contextualRectangle)
412- break
413- }
414- }
415- }
416- }
417- },
418- Oxide.ScriptMessageHandler {
419- msgId: "selection"
420- contexts: ["oxide://selection/"]
421- callback: function(msg, frame) {
422- internal.dismissCurrentSelection()
423- internal.dismissCurrentContextualMenu()
424- if (selectionActions != null) {
425- for (var i = 0; i < selectionActions.actions.length; ++i) {
426- if (selectionActions.actions[i].enabled) {
427- var mimedata = internal.buildMimedata(msg.args)
428- var bounds = internal.computeBounds(msg.args)
429- internal.currentSelection = selection.createObject(_webview, {mimedata: mimedata, bounds: bounds})
430- internal.currentSelection.showActions()
431- break
432- }
433- }
434- }
435+ msgId: "dpr"
436+ contexts: ["oxide://selection/"]
437+ callback: function(msg, frame) {
438+ internal.devicePixelRatio = msg.args.dpr
439 }
440 },
441 Oxide.ScriptMessageHandler {
442@@ -79,7 +49,6 @@
443 contexts: ["oxide://selection/"]
444 callback: function(msg, frame) {
445 internal.dismissCurrentContextualMenu()
446- internal.dismissCurrentSelection()
447 }
448 }
449 ]
450@@ -97,16 +66,16 @@
451
452 Item {
453 id: contextualRectangle
454-
455 visible: false
456+ readonly property real locationBarOffset: _webview.locationBarController.height + _webview.locationBarController.offset
457+ // XXX: Because the context model’s position is incorrectly reported in
458+ // device-independent pixels (see https://launchpad.net/bugs/1471181),
459+ // it needs to be multiplied by the device pixel ratio to get physical pixels.
460+ x: internal.contextModel ? internal.contextModel.position.x * internal.devicePixelRatio : 0
461+ y: internal.contextModel ? internal.contextModel.position.y * internal.devicePixelRatio + locationBarOffset : 0
462+ }
463
464- function position(data) {
465- x = data.left * data.scaleX
466- y = data.top * data.scaleY
467- width = data.width * data.scaleX
468- height = data.height * data.scaleY
469- }
470- }
471+ // XXX: This property is deprecated in favour of contextModel.
472 property QtObject contextualData: QtObject {
473 property url href
474 property string title
475@@ -120,134 +89,71 @@
476 }
477
478 property var contextualActions // type: ActionList
479- Component {
480- id: contextualPopover
481- ActionSelectionPopover {
482- actions: contextualActions
483+ contextMenu: ActionSelectionPopover {
484+ objectName: "contextMenu"
485+ actions: contextualActions
486+ caller: contextualRectangle
487+ Component.onCompleted: {
488+ internal.dismissCurrentContextualMenu()
489+ internal.contextModel = model
490+ var empty = true
491+ if (actions) {
492+ for (var i in actions.actions) {
493+ if (actions.actions[i].enabled) {
494+ empty = false
495+ break
496+ }
497+ }
498+ }
499+ if (empty) {
500+ internal.dismissCurrentContextualMenu()
501+ } else {
502+ contextualData.clear()
503+ contextualData.href = model.linkUrl
504+ contextualData.title = model.linkText
505+ if ((model.mediaType == Oxide.WebView.MediaTypeImage) && model.hasImageContents) {
506+ contextualData.img = model.srcUrl
507+ }
508+ show()
509+ }
510+ }
511+ onVisibleChanged: {
512+ if (!visible) {
513+ internal.dismissCurrentContextualMenu()
514+ }
515 }
516 }
517+ readonly property QtObject contextModel: internal.contextModel
518
519 property var selectionActions // type: ActionList
520- onSelectionActionsChanged: {
521- for (var i in selectionActions.actions) {
522- selectionActions.actions[i].onTriggered.connect(function () {
523- internal.dismissCurrentSelection()
524- })
525- }
526- }
527- Component {
528- id: selection
529- Selection {
530- anchors.fill: parent
531- property var mimedata: null
532- property rect bounds
533- onBoundsChanged: {
534- rect.x = bounds.x
535- rect.y = bounds.y
536- rect.width = bounds.width
537- rect.height = bounds.height
538- }
539- property Item actions: null
540- Component {
541- id: selectionPopover
542- ActionSelectionPopover {
543- objectName: "selectionActions"
544- autoClose: false
545- actions: selectionActions
546- }
547- }
548- function showActions() {
549- if (actions != null) {
550- actions.destroy()
551- }
552- actions = PopupUtils.open(selectionPopover, rect)
553- }
554- onResizingChanged: {
555- if (resizing) {
556- if (actions != null) {
557- actions.destroy()
558- }
559- }
560- }
561- onResized: {
562- var args = {x: rect.x, y: rect.y, width: rect.width, height: rect.height}
563- var msg = _webview.rootFrame.sendMessage("oxide://selection/", "adjustselection", args)
564- msg.onreply = function(response) {
565- internal.currentSelection.mimedata = internal.buildMimedata(response)
566- // Ensure that the bounds are updated
567- internal.currentSelection.bounds = Qt.rect(0, 0, 0, 0)
568- internal.currentSelection.bounds = internal.computeBounds(response)
569- internal.currentSelection.showActions()
570- }
571- msg.onerror = function(error) {
572- internal.dismissCurrentSelection()
573- }
574- }
575- onDismissed: internal.dismissCurrentSelection()
576- }
577- }
578+ onSelectionActionsChanged: console.warn("WARNING: the 'selectionActions' property is deprecated and ignored.")
579 function copy() {
580- if (internal.currentSelection != null) {
581- Clipboard.push(internal.currentSelection.mimedata)
582- } else {
583- console.warn("No current selection")
584- }
585+ console.warn("WARNING: the copy() function is deprecated and does nothing.")
586 }
587
588+ readonly property real devicePixelRatio: internal.devicePixelRatio
589+
590 QtObject {
591 id: internal
592 property int lastLoadRequestStatus: -1
593- property Item currentContextualMenu: null
594- property Item currentSelection: null
595-
596- function fillContextualData(data) {
597- contextualData.clear()
598- if ('img' in data) {
599- contextualData.img = data.img
600- }
601- if ('href' in data) {
602- contextualData.href = data.href
603- contextualData.title = data.title
604- }
605- }
606-
607- function buildMimedata(data) {
608- var mimedata = Clipboard.newData()
609- if ('html' in data) {
610- mimedata.html = data.html
611- }
612- // FIXME: push the text and image data in the order
613- // they appear in the selected block.
614- if ('text' in data) {
615- mimedata.text = data.text
616- }
617- if ('images' in data) {
618- // TODO: download and cache the images locally
619- // (grab them from the webview’s cache, if possible),
620- // and forward local URLs.
621- mimedata.urls = data.images
622- }
623- return mimedata
624- }
625+ property QtObject contextModel: null
626+ property real devicePixelRatio: 1.0
627
628 function computeBounds(data) {
629- return Qt.rect(data.left * data.scaleX, data.top * data.scaleY,
630- data.width * data.scaleX, data.height * data.scaleY)
631+ var locationBarOffset = _webview.locationBarController.height + _webview.locationBarController.offset
632+ var scaleX = data.outerWidth / data.innerWidth * internal.devicePixelRatio
633+ var scaleY = data.outerHeight / (data.innerHeight + locationBarOffset) * internal.devicePixelRatio
634+ return Qt.rect(data.left * scaleX, data.top * scaleY + locationBarOffset,
635+ data.width * scaleX, data.height * scaleY)
636 }
637
638 function dismissCurrentContextualMenu() {
639- if (currentContextualMenu != null) {
640- PopupUtils.close(currentContextualMenu)
641+ if (contextModel) {
642+ contextModel.close()
643 }
644 }
645
646- function dismissCurrentSelection() {
647- if (currentSelection != null) {
648- // For some reason a 0 delay fails to destroy the selection
649- // when it was requested upon a screen orientation change…
650- currentSelection.destroy(1)
651- }
652- }
653+ onContextModelChanged: if (!contextModel) _webview.contextualData.clear()
654 }
655
656 readonly property bool lastLoadSucceeded: internal.lastLoadRequestStatus === Oxide.LoadEvent.TypeSucceeded
657@@ -258,13 +164,11 @@
658 internal.lastLoadRequestStatus = event.type
659 }
660 internal.dismissCurrentContextualMenu()
661- internal.dismissCurrentSelection()
662 }
663
664 readonly property int screenOrientation: Screen.orientation
665 onScreenOrientationChanged: {
666 internal.dismissCurrentContextualMenu()
667- internal.dismissCurrentSelection()
668 }
669
670 onJavaScriptConsoleMessage: {
671
672=== removed directory 'src/Ubuntu/Web/assets'
673=== removed file 'src/Ubuntu/Web/assets/multi_selection_handle@20.png'
674Binary files src/Ubuntu/Web/assets/multi_selection_handle@20.png 2013-02-07 11:56:01 +0000 and src/Ubuntu/Web/assets/multi_selection_handle@20.png 1970-01-01 00:00:00 +0000 differ
675=== modified file 'src/Ubuntu/Web/selection02.js'
676--- src/Ubuntu/Web/selection02.js 2015-01-23 13:26:40 +0000
677+++ src/Ubuntu/Web/selection02.js 2015-08-27 14:01:06 +0000
678@@ -1,5 +1,5 @@
679 /*
680- * Copyright 2013-2014 Canonical Ltd.
681+ * Copyright 2013-2015 Canonical Ltd.
682 *
683 * This file is part of webbrowser-app.
684 *
685@@ -16,137 +16,8 @@
686 * along with this program. If not, see <http://www.gnu.org/licenses/>.
687 */
688
689-function elementContainedInBox(element, box) {
690- var rect = element.getBoundingClientRect();
691- return ((box.left <= rect.left) && (box.right >= rect.right) &&
692- (box.top <= rect.top) && (box.bottom >= rect.bottom));
693-}
694-
695-function getImgFullUri(uri) {
696- if ((uri.slice(0, 7) === 'http://') ||
697- (uri.slice(0, 8) === 'https://') ||
698- (uri.slice(0, 7) === 'file://') ||
699- (uri.slice(0, 5) === 'data:')) {
700- return uri;
701- } else if (uri.slice(0, 1) === '/') {
702- var docuri = document.documentURI;
703- var firstcolon = docuri.indexOf('://');
704- var protocol = 'http://';
705- if (firstcolon !== -1) {
706- protocol = docuri.slice(0, firstcolon + 3);
707- }
708- if (uri.slice(0, 2) === '//') {
709- // URLs beginning with a // should just inherit the protocol
710- // from the current page
711- return protocol + uri.slice(2);
712- } else {
713- return protocol + document.domain + uri;
714- }
715- } else {
716- var base = document.baseURI;
717- var lastslash = base.lastIndexOf('/');
718- if (lastslash === -1) {
719- return base + '/' + uri;
720- } else {
721- return base.slice(0, lastslash + 1) + uri;
722- }
723- }
724-}
725-
726-function getSelectedData(element) {
727- var node = element;
728- var data = new Object;
729-
730- var nodeName = node.nodeName.toLowerCase();
731- if (nodeName === 'img') {
732- data.img = getImgFullUri(node.getAttribute('src'));
733- } else if (nodeName === 'a') {
734- data.href = node.href;
735- data.title = node.title;
736- }
737-
738- // If the parent tag is a hyperlink, we want it too.
739- var parent = node.parentNode;
740- if ((nodeName !== 'a') && parent && (parent.nodeName.toLowerCase() === 'a')) {
741- data.href = parent.href;
742- data.title = parent.title;
743- node = parent;
744- }
745-
746- var boundingRect = node.getBoundingClientRect();
747- data.left = boundingRect.left;
748- data.top = boundingRect.top;
749- data.width = boundingRect.width;
750- data.height = boundingRect.height;
751-
752- node = node.cloneNode(true);
753- // filter out script nodes
754- var scripts = node.getElementsByTagName('script');
755- while (scripts.length > 0) {
756- var scriptNode = scripts[0];
757- if (scriptNode.parentNode) {
758- scriptNode.parentNode.removeChild(scriptNode);
759- }
760- }
761- data.html = node.outerHTML;
762- data.nodeName = node.nodeName.toLowerCase();
763- // FIXME: extract the text and images in the order they appear in the block,
764- // so that this order is respected when the data is pushed to the clipboard.
765- data.text = node.textContent;
766- var images = [];
767- var imgs = node.getElementsByTagName('img');
768- for (var i = 0; i < imgs.length; i++) {
769- images.push(getImgFullUri(imgs[i].getAttribute('src')));
770- }
771- if (images.length > 0) {
772- data.images = images;
773- }
774-
775- return data;
776-}
777-
778-function adjustSelection(selection) {
779- // FIXME: allow selecting two consecutive blocks, instead of
780- // interpolating to the containing block.
781- var centerX = (selection.left + selection.right) / 2;
782- var centerY = (selection.top + selection.bottom) / 2;
783- var element = document.elementFromPoint(centerX, centerY);
784- var parent = element;
785- while (elementContainedInBox(parent, selection)) {
786- parent = parent.parentNode;
787- }
788- element = parent;
789- return getSelectedData(element);
790-}
791-
792-document.documentElement.addEventListener('contextmenu', function(event) {
793- var element = document.elementFromPoint(event.clientX, event.clientY);
794- var data = getSelectedData(element);
795- var w = document.defaultView;
796- data['scaleX'] = w.outerWidth / w.innerWidth * w.devicePixelRatio;
797- data['scaleY'] = w.outerHeight / w.innerHeight * w.devicePixelRatio;
798- if (('img' in data) || ('href' in data)) {
799- oxide.sendMessage('contextmenu', data);
800- } else {
801- oxide.sendMessage('selection', data);
802- }
803-});
804-
805 document.defaultView.addEventListener('scroll', function(event) {
806 oxide.sendMessage('scroll', {});
807 });
808
809-oxide.addMessageHandler("adjustselection", function (msg) {
810- var w = document.defaultView;
811- var scaleX = w.outerWidth / w.innerWidth * w.devicePixelRatio;
812- var scaleY = w.outerHeight / w.innerHeight * w.devicePixelRatio;
813- var selection = new Object;
814- selection.left = msg.args.x / scaleX;
815- selection.right = selection.left + msg.args.width / scaleX;
816- selection.top = msg.args.y / scaleY;
817- selection.bottom = selection.top + msg.args.height / scaleY;
818- var adjusted = adjustSelection(selection);
819- adjusted['scaleX'] = scaleX;
820- adjusted['scaleY'] = scaleY;
821- msg.reply(adjusted);
822-});
823+oxide.sendMessage('dpr', {dpr: document.defaultView.devicePixelRatio});
824
825=== modified file 'src/app/CMakeLists.txt'
826--- src/app/CMakeLists.txt 2015-06-22 10:29:20 +0000
827+++ src/app/CMakeLists.txt 2015-08-27 14:01:06 +0000
828@@ -19,6 +19,7 @@
829 set(COMMONLIB_SRC
830 browserapplication.cpp
831 favicon-fetcher.cpp
832+ mime-database.cpp
833 session-storage.cpp
834 webbrowser-window.cpp
835 )
836
837=== modified file 'src/app/FileExtensionMapper.js'
838--- src/app/FileExtensionMapper.js 2014-11-11 15:07:46 +0000
839+++ src/app/FileExtensionMapper.js 2015-08-27 14:01:06 +0000
840@@ -1,5 +1,5 @@
841 /*
842- * Copyright 2014 Canonical Ltd.
843+ * Copyright 2014-2015 Canonical Ltd.
844 *
845 * This file is part of webbrowser-app.
846 *
847@@ -15,15 +15,19 @@
848 * You should have received a copy of the GNU General Public License
849 * along with this program. If not, see <http://www.gnu.org/licenses/>.
850 */
851+'use strict';
852+
853+function getExtension(filename) {
854+ var filenameParts = filename.split(".");
855+ if (filenameParts.length === 1 || (filenameParts[0] === "" && filenameParts.length === 2)) {
856+ return ""
857+ }
858+ return filenameParts.pop().toLowerCase();
859+}
860
861 // Constructed from /etc/mime.types
862 function filenameToContentType(filename) {
863- var filenameParts = filename.split(".");
864- if(filenameParts.length === 1 || (filenameParts[0] === "" && filenameParts.length === 2)) {
865- return ContentType.Unknown;
866- }
867- var ext = filenameParts.pop().toLowerCase();
868- switch(ext) {
869+ switch(getExtension(filename)) {
870 case "art":
871 case "bmp":
872 case "cdr":
873
874=== modified file 'src/app/FilePickerDialog.qml'
875--- src/app/FilePickerDialog.qml 2015-08-10 15:22:00 +0000
876+++ src/app/FilePickerDialog.qml 2015-08-27 14:01:06 +0000
877@@ -27,7 +27,7 @@
878 id: fileDialog
879 title: i18n.tr("Please choose a file")
880 selectMultiple: model.allowMultipleFiles
881-
882+
883 onAccepted: {
884 var selectedFiles = []
885 for(var i in fileDialog.fileUrls) {
886@@ -35,7 +35,7 @@
887 }
888 model.accept(selectedFiles)
889 }
890-
891+
892 onRejected: {
893 model.reject()
894 }
895
896=== modified file 'src/app/WebViewImpl.qml'
897--- src/app/WebViewImpl.qml 2015-08-10 15:22:00 +0000
898+++ src/app/WebViewImpl.qml 2015-08-27 14:01:06 +0000
899@@ -20,6 +20,7 @@
900 import Ubuntu.Components 1.3
901 import Ubuntu.Components.Popups 1.3
902 import Ubuntu.Web 0.2
903+import webbrowsercommon.private 0.1
904 import "actions" as Actions
905
906 WebView {
907@@ -54,14 +55,27 @@
908
909 if (downloadLoader.status == Loader.Ready) {
910 var headers = { }
911- if(request.cookies.length > 0) {
912+ if (request.cookies.length > 0) {
913 headers["Cookie"] = request.cookies.join(";")
914 }
915- if(request.referrer) {
916+ if (request.referrer) {
917 headers["Referer"] = request.referrer
918 }
919 headers["User-Agent"] = webview.context.userAgent
920- downloadLoader.item.downloadMimeType(request.url, request.mimeType, headers, request.suggestedFilename)
921+ // Work around https://launchpad.net/bugs/1487090 by guessing the mime type
922+ // from the suggested filename or URL if oxide hasn’t provided one.
923+ var mimeType = request.mimeType
924+ if (!mimeType) {
925+ mimeType = MimeDatabase.filenameToMimeType(request.suggestedFilename)
926+ }
927+ if (!mimeType) {
928+ var scheme = request.url.toString().split('://').shift().toLowerCase()
929+ var filename = request.url.toString().split('/').pop().split('?').shift()
930+ if ((scheme == "file") || (filename.indexOf('.') > -1)) {
931+ mimeType = MimeDatabase.filenameToMimeType(filename)
932+ }
933+ }
934+ downloadLoader.item.downloadMimeType(request.url, mimeType, headers, request.suggestedFilename)
935 } else {
936 // Desktop form factor case
937 Qt.openUrlExternally(request.url)
938@@ -76,16 +90,13 @@
939
940 Loader {
941 id: downloadLoader
942+ // TODO: Use the ubuntu download manager on desktop as well
943+ // (https://launchpad.net/bugs/1477310). This will require to have
944+ // ubuntu-download-manager in main (https://launchpad.net/bugs/1488425).
945 source: formFactor == "desktop" ? "" : "Downloader.qml"
946 asynchronous: true
947 }
948
949- selectionActions: ActionList {
950- Actions.Copy {
951- onTriggered: copy()
952- }
953- }
954-
955 function requestGeolocationPermission(request) {
956 PopupUtils.open(Qt.resolvedUrl("GeolocationPermissionRequest.qml"),
957 webview.currentWebview, {"request": request})
958
959=== added file 'src/app/actions/Cut.qml'
960--- src/app/actions/Cut.qml 1970-01-01 00:00:00 +0000
961+++ src/app/actions/Cut.qml 2015-08-27 14:01:06 +0000
962@@ -0,0 +1,23 @@
963+/*
964+ * Copyright 2015 Canonical Ltd.
965+ *
966+ * This file is part of webbrowser-app.
967+ *
968+ * webbrowser-app is free software; you can redistribute it and/or modify
969+ * it under the terms of the GNU General Public License as published by
970+ * the Free Software Foundation; version 3.
971+ *
972+ * webbrowser-app is distributed in the hope that it will be useful,
973+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
974+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
975+ * GNU General Public License for more details.
976+ *
977+ * You should have received a copy of the GNU General Public License
978+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
979+ */
980+
981+import Ubuntu.Components 1.3
982+
983+Action {
984+ text: i18n.tr("Cut")
985+}
986
987=== added file 'src/app/actions/Erase.qml'
988--- src/app/actions/Erase.qml 1970-01-01 00:00:00 +0000
989+++ src/app/actions/Erase.qml 2015-08-27 14:01:06 +0000
990@@ -0,0 +1,23 @@
991+/*
992+ * Copyright 2015 Canonical Ltd.
993+ *
994+ * This file is part of webbrowser-app.
995+ *
996+ * webbrowser-app is free software; you can redistribute it and/or modify
997+ * it under the terms of the GNU General Public License as published by
998+ * the Free Software Foundation; version 3.
999+ *
1000+ * webbrowser-app is distributed in the hope that it will be useful,
1001+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1002+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1003+ * GNU General Public License for more details.
1004+ *
1005+ * You should have received a copy of the GNU General Public License
1006+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1007+ */
1008+
1009+import Ubuntu.Components 1.3
1010+
1011+Action {
1012+ text: i18n.tr("Erase")
1013+}
1014
1015=== added file 'src/app/actions/Paste.qml'
1016--- src/app/actions/Paste.qml 1970-01-01 00:00:00 +0000
1017+++ src/app/actions/Paste.qml 2015-08-27 14:01:06 +0000
1018@@ -0,0 +1,23 @@
1019+/*
1020+ * Copyright 2015 Canonical Ltd.
1021+ *
1022+ * This file is part of webbrowser-app.
1023+ *
1024+ * webbrowser-app is free software; you can redistribute it and/or modify
1025+ * it under the terms of the GNU General Public License as published by
1026+ * the Free Software Foundation; version 3.
1027+ *
1028+ * webbrowser-app is distributed in the hope that it will be useful,
1029+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1030+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1031+ * GNU General Public License for more details.
1032+ *
1033+ * You should have received a copy of the GNU General Public License
1034+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1035+ */
1036+
1037+import Ubuntu.Components 1.3
1038+
1039+Action {
1040+ text: i18n.tr("Paste")
1041+}
1042
1043=== added file 'src/app/actions/Redo.qml'
1044--- src/app/actions/Redo.qml 1970-01-01 00:00:00 +0000
1045+++ src/app/actions/Redo.qml 2015-08-27 14:01:06 +0000
1046@@ -0,0 +1,23 @@
1047+/*
1048+ * Copyright 2015 Canonical Ltd.
1049+ *
1050+ * This file is part of webbrowser-app.
1051+ *
1052+ * webbrowser-app is free software; you can redistribute it and/or modify
1053+ * it under the terms of the GNU General Public License as published by
1054+ * the Free Software Foundation; version 3.
1055+ *
1056+ * webbrowser-app is distributed in the hope that it will be useful,
1057+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1058+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1059+ * GNU General Public License for more details.
1060+ *
1061+ * You should have received a copy of the GNU General Public License
1062+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1063+ */
1064+
1065+import Ubuntu.Components 1.3
1066+
1067+Action {
1068+ text: i18n.tr("Redo")
1069+}
1070
1071=== added file 'src/app/actions/SaveLink.qml'
1072--- src/app/actions/SaveLink.qml 1970-01-01 00:00:00 +0000
1073+++ src/app/actions/SaveLink.qml 2015-08-27 14:01:06 +0000
1074@@ -0,0 +1,23 @@
1075+/*
1076+ * Copyright 2015 Canonical Ltd.
1077+ *
1078+ * This file is part of webbrowser-app.
1079+ *
1080+ * webbrowser-app is free software; you can redistribute it and/or modify
1081+ * it under the terms of the GNU General Public License as published by
1082+ * the Free Software Foundation; version 3.
1083+ *
1084+ * webbrowser-app is distributed in the hope that it will be useful,
1085+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1086+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1087+ * GNU General Public License for more details.
1088+ *
1089+ * You should have received a copy of the GNU General Public License
1090+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1091+ */
1092+
1093+import Ubuntu.Components 1.3
1094+
1095+Action {
1096+ text: i18n.tr("Save link")
1097+}
1098
1099=== added file 'src/app/actions/SelectAll.qml'
1100--- src/app/actions/SelectAll.qml 1970-01-01 00:00:00 +0000
1101+++ src/app/actions/SelectAll.qml 2015-08-27 14:01:06 +0000
1102@@ -0,0 +1,23 @@
1103+/*
1104+ * Copyright 2015 Canonical Ltd.
1105+ *
1106+ * This file is part of webbrowser-app.
1107+ *
1108+ * webbrowser-app is free software; you can redistribute it and/or modify
1109+ * it under the terms of the GNU General Public License as published by
1110+ * the Free Software Foundation; version 3.
1111+ *
1112+ * webbrowser-app is distributed in the hope that it will be useful,
1113+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1114+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1115+ * GNU General Public License for more details.
1116+ *
1117+ * You should have received a copy of the GNU General Public License
1118+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1119+ */
1120+
1121+import Ubuntu.Components 1.3
1122+
1123+Action {
1124+ text: i18n.tr("Select all")
1125+}
1126
1127=== added file 'src/app/actions/Undo.qml'
1128--- src/app/actions/Undo.qml 1970-01-01 00:00:00 +0000
1129+++ src/app/actions/Undo.qml 2015-08-27 14:01:06 +0000
1130@@ -0,0 +1,23 @@
1131+/*
1132+ * Copyright 2015 Canonical Ltd.
1133+ *
1134+ * This file is part of webbrowser-app.
1135+ *
1136+ * webbrowser-app is free software; you can redistribute it and/or modify
1137+ * it under the terms of the GNU General Public License as published by
1138+ * the Free Software Foundation; version 3.
1139+ *
1140+ * webbrowser-app is distributed in the hope that it will be useful,
1141+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1142+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1143+ * GNU General Public License for more details.
1144+ *
1145+ * You should have received a copy of the GNU General Public License
1146+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1147+ */
1148+
1149+import Ubuntu.Components 1.3
1150+
1151+Action {
1152+ text: i18n.tr("Undo")
1153+}
1154
1155=== modified file 'src/app/browserapplication.cpp'
1156--- src/app/browserapplication.cpp 2015-04-09 07:06:50 +0000
1157+++ src/app/browserapplication.cpp 2015-08-27 14:01:06 +0000
1158@@ -31,6 +31,7 @@
1159 #include "browserapplication.h"
1160 #include "config.h"
1161 #include "favicon-fetcher.h"
1162+#include "mime-database.h"
1163 #include "session-storage.h"
1164 #include "webbrowser-window.h"
1165
1166@@ -99,6 +100,13 @@
1167 return QString();
1168 }
1169
1170+static QObject* MimeDatabase_singleton_factory(QQmlEngine* engine, QJSEngine* scriptEngine)
1171+{
1172+ Q_UNUSED(engine);
1173+ Q_UNUSED(scriptEngine);
1174+ return new MimeDatabase();
1175+}
1176+
1177 static QObject* Direction_singleton_factory(QQmlEngine* engine, QJSEngine* scriptEngine)
1178 {
1179 Q_UNUSED(engine);
1180@@ -143,6 +151,7 @@
1181
1182 const char* uri = "webbrowsercommon.private";
1183 qmlRegisterType<FaviconFetcher>(uri, 0, 1, "FaviconFetcher");
1184+ qmlRegisterSingletonType<MimeDatabase>(uri, 0, 1, "MimeDatabase", MimeDatabase_singleton_factory);
1185 qmlRegisterType<SessionStorage>(uri, 0, 1, "SessionStorage");
1186
1187 const char* gesturesUri = "Ubuntu.Gestures";
1188
1189=== added file 'src/app/mime-database.cpp'
1190--- src/app/mime-database.cpp 1970-01-01 00:00:00 +0000
1191+++ src/app/mime-database.cpp 2015-08-27 14:01:06 +0000
1192@@ -0,0 +1,33 @@
1193+/*
1194+ * Copyright 2015 Canonical Ltd.
1195+ *
1196+ * This file is part of webbrowser-app.
1197+ *
1198+ * webbrowser-app is free software; you can redistribute it and/or modify
1199+ * it under the terms of the GNU General Public License as published by
1200+ * the Free Software Foundation; version 3.
1201+ *
1202+ * webbrowser-app is distributed in the hope that it will be useful,
1203+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1204+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1205+ * GNU General Public License for more details.
1206+ *
1207+ * You should have received a copy of the GNU General Public License
1208+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1209+ */
1210+
1211+#include "mime-database.h"
1212+
1213+MimeDatabase::MimeDatabase(QObject* parent)
1214+ : QObject(parent)
1215+{
1216+}
1217+
1218+QString MimeDatabase::filenameToMimeType(const QString& filename) const
1219+{
1220+ QMimeType type = m_database.mimeTypeForFile(filename, QMimeDatabase::MatchExtension);
1221+ if (!type.isDefault()) {
1222+ return type.name();
1223+ }
1224+ return QString();
1225+}
1226
1227=== added file 'src/app/mime-database.h'
1228--- src/app/mime-database.h 1970-01-01 00:00:00 +0000
1229+++ src/app/mime-database.h 2015-08-27 14:01:06 +0000
1230@@ -0,0 +1,39 @@
1231+/*
1232+ * Copyright 2015 Canonical Ltd.
1233+ *
1234+ * This file is part of webbrowser-app.
1235+ *
1236+ * webbrowser-app is free software; you can redistribute it and/or modify
1237+ * it under the terms of the GNU General Public License as published by
1238+ * the Free Software Foundation; version 3.
1239+ *
1240+ * webbrowser-app is distributed in the hope that it will be useful,
1241+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1242+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1243+ * GNU General Public License for more details.
1244+ *
1245+ * You should have received a copy of the GNU General Public License
1246+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1247+ */
1248+
1249+#ifndef __MIME_DATABASE_H__
1250+#define __MIME_DATABASE_H__
1251+
1252+#include <QtCore/QMimeDatabase>
1253+#include <QtCore/QObject>
1254+#include <QtCore/QString>
1255+
1256+class MimeDatabase : public QObject
1257+{
1258+ Q_OBJECT
1259+
1260+public:
1261+ explicit MimeDatabase(QObject* parent=0);
1262+
1263+ Q_INVOKABLE QString filenameToMimeType(const QString& filename) const;
1264+
1265+private:
1266+ QMimeDatabase m_database;
1267+};
1268+
1269+#endif // __MIME_DATABASE_H__
1270
1271=== modified file 'src/app/webbrowser/Browser.qml'
1272--- src/app/webbrowser/Browser.qml 2015-08-20 08:58:11 +0000
1273+++ src/app/webbrowser/Browser.qml 2015-08-27 14:01:06 +0000
1274@@ -19,7 +19,7 @@
1275 import QtQuick 2.4
1276 import QtQuick.Window 2.2
1277 import Qt.labs.settings 1.0
1278-import com.canonical.Oxide 1.5 as Oxide
1279+import com.canonical.Oxide 1.8 as Oxide
1280 import Ubuntu.Components 1.3
1281 import Ubuntu.Components.Popups 1.3
1282 import webbrowserapp.private 0.1
1283@@ -924,41 +924,124 @@
1284 preferences.localStorageEnabled: true
1285 preferences.appCacheEnabled: true
1286
1287+ property QtObject contextModel: null
1288 contextualActions: ActionList {
1289 Actions.OpenLinkInNewTab {
1290- enabled: contextualData.href.toString()
1291- onTriggered: browser.openUrlInNewTab(contextualData.href, true)
1292+ objectName: "openLinkInNewTabContextualAction"
1293+ enabled: contextModel && contextModel.linkUrl.toString()
1294+ onTriggered: browser.openUrlInNewTab(contextModel.linkUrl, true)
1295 }
1296 Actions.OpenLinkInNewBackgroundTab {
1297- enabled: contextualData.href.toString() && ((settings.allowOpenInBackgroundTab === "true") ||
1298- ((settings.allowOpenInBackgroundTab === "default") && (formFactor === "desktop")))
1299- onTriggered: browser.openUrlInNewTab(contextualData.href, false)
1300+ objectName: "openLinkInNewBackgroundTabContextualAction"
1301+ enabled: contextModel && contextModel.linkUrl.toString() &&
1302+ ((settings.allowOpenInBackgroundTab === "true") ||
1303+ ((settings.allowOpenInBackgroundTab === "default") &&
1304+ (formFactor === "desktop")))
1305+ onTriggered: browser.openUrlInNewTab(contextModel.linkUrl, false)
1306 }
1307 Actions.BookmarkLink {
1308- enabled: contextualData.href.toString() && browser.bookmarksModel
1309- onTriggered: bookmarksModel.add(contextualData.href, contextualData.title, "", "")
1310+ objectName: "bookmarkLinkContextualAction"
1311+ enabled: contextModel && contextModel.linkUrl.toString() &&
1312+ browser.bookmarksModel
1313+ onTriggered: bookmarksModel.add(contextModel.linkUrl, contextModel.linkText, "", "")
1314 }
1315 Actions.CopyLink {
1316- enabled: contextualData.href.toString()
1317- onTriggered: Clipboard.push(["text/plain", contextualData.href.toString()])
1318+ objectName: "copyLinkContextualAction"
1319+ enabled: contextModel && contextModel.linkUrl.toString()
1320+ onTriggered: Clipboard.push(["text/plain", contextModel.linkUrl.toString()])
1321+ }
1322+ Actions.SaveLink {
1323+ objectName: "SaveLinkContextualAction"
1324+ enabled: contextModel && contextModel.linkUrl.toString()
1325+ onTriggered: contextModel.saveLink()
1326 }
1327 Actions.ShareLink {
1328- enabled: (formFactor == "mobile") && contextualData.href.toString()
1329- onTriggered: internal.shareLink(contextualData.href.toString(), contextualData.title)
1330+ objectName: "ShareLinkContextualAction"
1331+ enabled: (formFactor == "mobile") &&
1332+ contextModel && contextModel.linkUrl.toString()
1333+ onTriggered: internal.shareLink(contextModel.linkUrl.toString(), contextModel.linkText)
1334 }
1335 Actions.OpenImageInNewTab {
1336- enabled: contextualData.img.toString()
1337- onTriggered: browser.openUrlInNewTab(contextualData.img, true)
1338+ objectName: "OpenImageInNewTabContextualAction"
1339+ enabled: contextModel && contextModel.srcUrl.toString()
1340+ onTriggered: browser.openUrlInNewTab(contextModel.srcUrl, true)
1341 }
1342 Actions.CopyImage {
1343- enabled: contextualData.img.toString()
1344- onTriggered: Clipboard.push(["text/plain", contextualData.img.toString()])
1345+ objectName: "CopyImageContextualAction"
1346+ enabled: contextModel &&
1347+ (contextModel.mediaType === Oxide.WebView.MediaTypeImage) &&
1348+ contextModel.srcUrl.toString()
1349+ onTriggered: Clipboard.push(["text/plain", contextModel.srcUrl.toString()])
1350 }
1351 Actions.SaveImage {
1352- enabled: contextualData.img.toString() && downloadLoader.status == Loader.Ready
1353- onTriggered: downloadLoader.item.downloadPicture(contextualData.img)
1354- }
1355- }
1356+ objectName: "SaveImageContextualAction"
1357+ enabled: contextModel &&
1358+ ((contextModel.mediaType === Oxide.WebView.MediaTypeImage) ||
1359+ (contextModel.mediaType === Oxide.WebView.MediaTypeCanvas)) &&
1360+ contextModel.srcUrl.toString()
1361+ onTriggered: contextModel.saveMedia()
1362+ }
1363+ Actions.Undo {
1364+ objectName: "UndoContextualAction"
1365+ enabled: contextModel && contextModel.isEditable &&
1366+ (contextModel.editFlags & Oxide.WebView.UndoCapability)
1367+ onTriggered: webviewimpl.executeEditingCommand(Oxide.WebView.EditingCommandUndo)
1368+ }
1369+ Actions.Redo {
1370+ objectName: "RedoContextualAction"
1371+ enabled: contextModel && contextModel.isEditable &&
1372+ (contextModel.editFlags & Oxide.WebView.RedoCapability)
1373+ onTriggered: webviewimpl.executeEditingCommand(Oxide.WebView.EditingCommandRedo)
1374+ }
1375+ Actions.Cut {
1376+ objectName: "CutContextualAction"
1377+ enabled: contextModel && contextModel.isEditable &&
1378+ (contextModel.editFlags & Oxide.WebView.CutCapability)
1379+ onTriggered: webviewimpl.executeEditingCommand(Oxide.WebView.EditingCommandCut)
1380+ }
1381+ Actions.Copy {
1382+ objectName: "CopyContextualAction"
1383+ enabled: contextModel && contextModel.isEditable &&
1384+ (contextModel.editFlags & Oxide.WebView.CopyCapability)
1385+ onTriggered: webviewimpl.executeEditingCommand(Oxide.WebView.EditingCommandCopy)
1386+ }
1387+ Actions.Paste {
1388+ objectName: "PasteContextualAction"
1389+ enabled: contextModel && contextModel.isEditable &&
1390+ (contextModel.editFlags & Oxide.WebView.PasteCapability)
1391+ onTriggered: webviewimpl.executeEditingCommand(Oxide.WebView.EditingCommandPaste)
1392+ }
1393+ Actions.Erase {
1394+ objectName: "EraseContextualAction"
1395+ enabled: contextModel && contextModel.isEditable &&
1396+ (contextModel.editFlags & Oxide.WebView.EraseCapability)
1397+ onTriggered: webviewimpl.executeEditingCommand(Oxide.WebView.EditingCommandErase)
1398+ }
1399+ Actions.SelectAll {
1400+ objectName: "SelectAllContextualAction"
1401+ enabled: contextModel && contextModel.isEditable &&
1402+ (contextModel.editFlags & Oxide.WebView.SelectAllCapability)
1403+ onTriggered: webviewimpl.executeEditingCommand(Oxide.WebView.EditingCommandSelectAll)
1404+ }
1405+ }
1406+
1407+ Component {
1408+ id: contextMenuNarrowComponent
1409+ ContextMenuMobile {
1410+ actions: contextualActions
1411+ Component.onCompleted: webviewimpl.contextModel = contextModel
1412+ }
1413+ }
1414+ Component {
1415+ id: contextMenuWideComponent
1416+ ContextMenuWide {
1417+ webview: webviewimpl
1418+ parent: browser
1419+ actions: contextualActions
1420+ Component.onCompleted: webviewimpl.contextModel = contextModel
1421+ }
1422+ }
1423+ contextMenu: browser.wide ? contextMenuWideComponent : contextMenuNarrowComponent
1424
1425 onNewViewRequested: {
1426 var tab = tabComponent.createObject(tabContainer, {"request": request, 'incognito': browser.incognito})
1427@@ -1089,12 +1172,6 @@
1428 }
1429 }
1430
1431- Loader {
1432- id: downloadLoader
1433- source: formFactor == "desktop" ? "" : "../Downloader.qml"
1434- asynchronous: true
1435- }
1436-
1437 QtObject {
1438 id: internal
1439
1440
1441=== added file 'src/app/webbrowser/ContextMenuMobile.qml'
1442--- src/app/webbrowser/ContextMenuMobile.qml 1970-01-01 00:00:00 +0000
1443+++ src/app/webbrowser/ContextMenuMobile.qml 2015-08-27 14:01:06 +0000
1444@@ -0,0 +1,168 @@
1445+/*
1446+ * Copyright 2015 Canonical Ltd.
1447+ *
1448+ * This file is part of webbrowser-app.
1449+ *
1450+ * webbrowser-app is free software; you can redistribute it and/or modify
1451+ * it under the terms of the GNU General Public License as published by
1452+ * the Free Software Foundation; version 3.
1453+ *
1454+ * webbrowser-app is distributed in the hope that it will be useful,
1455+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1456+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1457+ * GNU General Public License for more details.
1458+ *
1459+ * You should have received a copy of the GNU General Public License
1460+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1461+ */
1462+
1463+import QtQuick 2.4
1464+import Ubuntu.Components 1.3
1465+import Ubuntu.Components.ListItems 1.3 as ListItems
1466+import Ubuntu.Components.Popups 1.3 as Popups
1467+import com.canonical.Oxide 1.8 as Oxide
1468+
1469+Popups.Dialog {
1470+ property QtObject contextModel: model
1471+ property ActionList actions: null
1472+
1473+ QtObject {
1474+ id: internal
1475+ readonly property bool isImage: ((contextModel.mediaType === Oxide.WebView.MediaTypeImage) ||
1476+ (contextModel.mediaType === Oxide.WebView.MediaTypeCanvas)) &&
1477+ contextModel.srcUrl.toString()
1478+ }
1479+
1480+ Row {
1481+ spacing: units.gu(2)
1482+ anchors {
1483+ left: parent.left
1484+ leftMargin: units.gu(2)
1485+ right: parent.right
1486+ rightMargin: units.gu(2)
1487+ }
1488+ height: units.gu(2 * title.lineCount + 3)
1489+ visible: !contextModel.isEditable
1490+
1491+ Icon {
1492+ width: units.gu(2)
1493+ height: units.gu(2)
1494+ anchors {
1495+ top: parent.top
1496+ topMargin: units.gu(2)
1497+ }
1498+ name: internal.isImage ? "stock_image" : ""
1499+ // work around the lack of a standard stock_link symbolic icon in the theme
1500+ Component.onCompleted: {
1501+ if (!name) {
1502+ source = "assets/stock_link.svg"
1503+ }
1504+ }
1505+ }
1506+
1507+ Label {
1508+ id: title
1509+ objectName: "titleLabel"
1510+ text: internal.isImage ? contextModel.srcUrl : contextModel.linkUrl
1511+ width: parent.width - units.gu(4)
1512+ anchors {
1513+ top: parent.top
1514+ topMargin: units.gu(2)
1515+ bottom: parent.bottom
1516+ }
1517+ fontSize: "x-small"
1518+ maximumLineCount: 2
1519+ wrapMode: Text.Wrap
1520+ height: contentHeight
1521+ }
1522+ }
1523+
1524+ ListItems.ThinDivider {
1525+ anchors {
1526+ left: parent.left
1527+ leftMargin: units.gu(2)
1528+ right: parent.right
1529+ rightMargin: units.gu(2)
1530+ }
1531+ visible: !contextModel.isEditable
1532+ }
1533+
1534+ Repeater {
1535+ model: actions.actions
1536+ delegate: ListItems.Empty {
1537+ readonly property var action: actions.actions[index]
1538+ objectName: action.objectName + "_item"
1539+ visible: action.enabled
1540+ showDivider: false
1541+
1542+ height: units.gu(5)
1543+
1544+ Label {
1545+ anchors {
1546+ left: parent.left
1547+ leftMargin: units.gu(2)
1548+ right: parent.right
1549+ rightMargin: units.gu(2)
1550+ verticalCenter: parent.verticalCenter
1551+ }
1552+ fontSize: "x-small"
1553+ text: action.text
1554+ }
1555+
1556+ ListItems.ThinDivider {
1557+ anchors {
1558+ left: parent.left
1559+ leftMargin: units.gu(2)
1560+ right: parent.right
1561+ rightMargin: units.gu(2)
1562+ bottom: parent.bottom
1563+ }
1564+ }
1565+
1566+ onTriggered: {
1567+ action.trigger()
1568+ contextModel.close()
1569+ }
1570+ }
1571+ }
1572+
1573+ ListItems.Empty {
1574+ objectName: "cancelAction"
1575+ height: units.gu(5)
1576+ showDivider: false
1577+ Label {
1578+ anchors {
1579+ left: parent.left
1580+ leftMargin: units.gu(2)
1581+ right: parent.right
1582+ rightMargin: units.gu(2)
1583+ verticalCenter: parent.verticalCenter
1584+ }
1585+ fontSize: "x-small"
1586+ text: i18n.tr("Cancel")
1587+ }
1588+ onTriggered: contextModel.close()
1589+ }
1590+
1591+ Component.onCompleted: {
1592+ if (contextModel.linkUrl.toString() ||
1593+ contextModel.srcUrl.toString() ||
1594+ (contextModel.isEditable && contextModel.editFlags)) {
1595+ show()
1596+ } else {
1597+ contextModel.close()
1598+ }
1599+ }
1600+
1601+ // adjust default dialog visuals to custom requirements for the context menu
1602+ Binding {
1603+ target: __foreground
1604+ property: "margins"
1605+ value: 0
1606+ }
1607+ Binding {
1608+ target: __foreground
1609+ property: "itemSpacing"
1610+ value: 0
1611+ }
1612+}
1613
1614=== added file 'src/app/webbrowser/ContextMenuWide.qml'
1615--- src/app/webbrowser/ContextMenuWide.qml 1970-01-01 00:00:00 +0000
1616+++ src/app/webbrowser/ContextMenuWide.qml 2015-08-27 14:01:06 +0000
1617@@ -0,0 +1,158 @@
1618+/*
1619+ * Copyright 2015 Canonical Ltd.
1620+ *
1621+ * This file is part of webbrowser-app.
1622+ *
1623+ * webbrowser-app is free software; you can redistribute it and/or modify
1624+ * it under the terms of the GNU General Public License as published by
1625+ * the Free Software Foundation; version 3.
1626+ *
1627+ * webbrowser-app is distributed in the hope that it will be useful,
1628+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1629+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1630+ * GNU General Public License for more details.
1631+ *
1632+ * You should have received a copy of the GNU General Public License
1633+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1634+ */
1635+
1636+import QtQuick 2.4
1637+import Ubuntu.Components 1.3
1638+import Ubuntu.Components.ListItems 1.3 as ListItems
1639+import Ubuntu.Components.Popups 1.3 as Popups
1640+import com.canonical.Oxide 1.8 as Oxide
1641+
1642+Popups.Popover {
1643+ id: contextMenu
1644+
1645+ property QtObject contextModel: model
1646+ property ActionList actions: null
1647+ property var webview: null
1648+
1649+ QtObject {
1650+ id: internal
1651+
1652+ readonly property bool isImage: ((contextModel.mediaType === Oxide.WebView.MediaTypeImage) ||
1653+ (contextModel.mediaType === Oxide.WebView.MediaTypeCanvas)) &&
1654+ contextModel.srcUrl.toString()
1655+
1656+ readonly property int lastEnabledActionIndex: {
1657+ var last = -1
1658+ for (var i in actions.actions) {
1659+ if (actions.actions[i].enabled) {
1660+ last = i
1661+ }
1662+ }
1663+ return last
1664+ }
1665+
1666+ readonly property real locationBarOffset: contextMenu.webview.locationBarController.height + contextMenu.webview.locationBarController.offset
1667+ }
1668+
1669+ Rectangle {
1670+ anchors.fill: parent
1671+ color: "#ececec"
1672+ }
1673+
1674+ Column {
1675+ anchors {
1676+ left: parent.left
1677+ right: parent.right
1678+ }
1679+
1680+ Label {
1681+ objectName: "titleLabel"
1682+ text: internal.isImage ? contextModel.srcUrl : contextModel.linkUrl
1683+ anchors {
1684+ left: parent.left
1685+ leftMargin: units.gu(2)
1686+ right: parent.right
1687+ rightMargin: units.gu(2)
1688+ }
1689+ height: units.gu(5)
1690+ visible: !contextModel.isEditable
1691+ fontSize: "x-small"
1692+ color: "#888888"
1693+ elide: Text.ElideRight
1694+ verticalAlignment: Text.AlignVCenter
1695+ }
1696+
1697+ ListItems.ThinDivider {
1698+ anchors {
1699+ left: parent.left
1700+ leftMargin: units.gu(2)
1701+ right: parent.right
1702+ rightMargin: units.gu(2)
1703+ }
1704+ visible: !contextModel.isEditable
1705+ }
1706+
1707+ Repeater {
1708+ model: actions.actions
1709+ delegate: ListItems.Empty {
1710+ readonly property var action: actions.actions[index]
1711+ objectName: action.objectName + "_item"
1712+ visible: action.enabled
1713+ showDivider: false
1714+
1715+ height: units.gu(5)
1716+
1717+ Label {
1718+ anchors {
1719+ left: parent.left
1720+ leftMargin: units.gu(2)
1721+ right: parent.right
1722+ rightMargin: units.gu(2)
1723+ verticalCenter: parent.verticalCenter
1724+ }
1725+ fontSize: "small"
1726+ text: action.text
1727+ }
1728+
1729+ ListItems.ThinDivider {
1730+ visible: index < internal.lastEnabledActionIndex
1731+ anchors {
1732+ left: parent.left
1733+ leftMargin: units.gu(2)
1734+ right: parent.right
1735+ rightMargin: units.gu(2)
1736+ bottom: parent.bottom
1737+ }
1738+ }
1739+
1740+ onTriggered: {
1741+ action.trigger()
1742+ contextMenu.hide()
1743+ }
1744+ }
1745+ }
1746+ }
1747+
1748+ Item {
1749+ id: positioner
1750+ visible: false
1751+ parent: contextMenu.webview
1752+ // XXX: Because the context model’s position is incorrectly reported in
1753+ // device-independent pixels (see https://launchpad.net/bugs/1471181),
1754+ // it needs to be multiplied by the device pixel ratio to get physical pixels.
1755+ x: contextModel.position.x * contextMenu.webview.devicePixelRatio
1756+ y: contextModel.position.y * contextMenu.webview.devicePixelRatio + internal.locationBarOffset
1757+ }
1758+ caller: positioner
1759+
1760+ Component.onCompleted: {
1761+ if (contextModel.linkUrl.toString() ||
1762+ contextModel.srcUrl.toString() ||
1763+ (contextModel.isEditable && contextModel.editFlags)) {
1764+ show()
1765+ } else {
1766+ contextModel.close()
1767+ }
1768+ }
1769+
1770+ onVisibleChanged: {
1771+ if (!visible) {
1772+ contextModel.close()
1773+ }
1774+ }
1775+}
1776
1777=== added file 'src/app/webbrowser/assets/stock_link.svg'
1778--- src/app/webbrowser/assets/stock_link.svg 1970-01-01 00:00:00 +0000
1779+++ src/app/webbrowser/assets/stock_link.svg 2015-08-27 14:01:06 +0000
1780@@ -0,0 +1,164 @@
1781+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
1782+<!-- Created with Inkscape (http://www.inkscape.org/) -->
1783+
1784+<svg
1785+ xmlns:dc="http://purl.org/dc/elements/1.1/"
1786+ xmlns:cc="http://creativecommons.org/ns#"
1787+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
1788+ xmlns:svg="http://www.w3.org/2000/svg"
1789+ xmlns="http://www.w3.org/2000/svg"
1790+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
1791+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
1792+ width="90"
1793+ height="90"
1794+ id="svg4874"
1795+ version="1.1"
1796+ inkscape:version="0.48+devel r"
1797+ viewBox="0 0 90 90.000001"
1798+ sodipodi:docname="insert-link02b.svg">
1799+ <defs
1800+ id="defs4876" />
1801+ <sodipodi:namedview
1802+ id="base"
1803+ pagecolor="#ffffff"
1804+ bordercolor="#666666"
1805+ borderopacity="1.0"
1806+ inkscape:pageopacity="0.0"
1807+ inkscape:pageshadow="2"
1808+ inkscape:zoom="5.0931704"
1809+ inkscape:cx="51.019301"
1810+ inkscape:cy="39.641321"
1811+ inkscape:document-units="px"
1812+ inkscape:current-layer="g4978"
1813+ showgrid="true"
1814+ showborder="true"
1815+ fit-margin-top="0"
1816+ fit-margin-left="0"
1817+ fit-margin-right="0"
1818+ fit-margin-bottom="0"
1819+ inkscape:snap-bbox="true"
1820+ inkscape:bbox-paths="true"
1821+ inkscape:bbox-nodes="true"
1822+ inkscape:snap-bbox-edge-midpoints="true"
1823+ inkscape:snap-bbox-midpoints="true"
1824+ inkscape:object-paths="true"
1825+ inkscape:snap-intersection-paths="true"
1826+ inkscape:object-nodes="true"
1827+ inkscape:snap-smooth-nodes="true"
1828+ inkscape:snap-midpoints="true"
1829+ inkscape:snap-object-midpoints="true"
1830+ inkscape:snap-center="true"
1831+ showguides="true"
1832+ inkscape:guide-bbox="true">
1833+ <inkscape:grid
1834+ type="xygrid"
1835+ id="grid5451"
1836+ empspacing="6" />
1837+ <sodipodi:guide
1838+ orientation="1,0"
1839+ position="6,77"
1840+ id="guide4063" />
1841+ <sodipodi:guide
1842+ orientation="1,0"
1843+ position="3,78"
1844+ id="guide4065" />
1845+ <sodipodi:guide
1846+ orientation="0,1"
1847+ position="55,84"
1848+ id="guide4067" />
1849+ <sodipodi:guide
1850+ orientation="0,1"
1851+ position="53,87"
1852+ id="guide4069" />
1853+ <sodipodi:guide
1854+ orientation="0,1"
1855+ position="20,3"
1856+ id="guide4071" />
1857+ <sodipodi:guide
1858+ orientation="0,1"
1859+ position="20,6"
1860+ id="guide4073" />
1861+ <sodipodi:guide
1862+ orientation="1,0"
1863+ position="87,7"
1864+ id="guide4075" />
1865+ <sodipodi:guide
1866+ orientation="1,0"
1867+ position="84,7"
1868+ id="guide4077" />
1869+ <sodipodi:guide
1870+ orientation="0,1"
1871+ position="58,81"
1872+ id="guide4074" />
1873+ <sodipodi:guide
1874+ orientation="1,0"
1875+ position="9,74"
1876+ id="guide4076" />
1877+ <sodipodi:guide
1878+ orientation="0,1"
1879+ position="21,9"
1880+ id="guide4078" />
1881+ <sodipodi:guide
1882+ orientation="1,0"
1883+ position="81,4"
1884+ id="guide4080" />
1885+ </sodipodi:namedview>
1886+ <metadata
1887+ id="metadata4879">
1888+ <rdf:RDF>
1889+ <cc:Work
1890+ rdf:about="">
1891+ <dc:format>image/svg+xml</dc:format>
1892+ <dc:type
1893+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
1894+ <dc:title></dc:title>
1895+ </cc:Work>
1896+ </rdf:RDF>
1897+ </metadata>
1898+ <g
1899+ inkscape:label="Layer 1"
1900+ inkscape:groupmode="layer"
1901+ id="layer1"
1902+ transform="translate(67.857146,-84.50504)">
1903+ <g
1904+ transform="matrix(0,-1,-1,0,373.50506,516.50504)"
1905+ id="g4845"
1906+ style="display:inline">
1907+ <g
1908+ inkscape:label="Layer 1"
1909+ id="layer1-8"
1910+ transform="matrix(0,-1,-1,0,1394.3622,441.36221)">
1911+ <rect
1912+ style="color:#000000;fill:none;stroke:none;stroke-width:7.5;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
1913+ id="rect4198"
1914+ width="90"
1915+ height="90"
1916+ x="0"
1917+ y="962.36218" />
1918+ <g
1919+ id="g4978"
1920+ transform="translate(-60,548.00002)">
1921+ <rect
1922+ style="color:#000000;fill:none;stroke:none;stroke-width:6;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
1923+ id="rect3038"
1924+ width="90"
1925+ height="90"
1926+ x="0"
1927+ y="1.7382814e-05"
1928+ transform="translate(60,414.36216)" />
1929+ <path
1930+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#808080;stroke-width:6;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
1931+ d="M 92.075086,472.28711 117.92492,446.43727"
1932+ id="path4188"
1933+ inkscape:connector-curvature="0"
1934+ sodipodi:nodetypes="cc" />
1935+ <path
1936+ style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:5.99999952;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
1937+ d="M 63.667969 9.0371094 C 63.165875 9.0539444 62.671019 9.1114844 62.183594 9.2089844 C 58.284224 9.9890744 55.804541 12.722793 53.650391 14.876953 L 47.1875 21.339844 C 45.03334 23.493994 42.301574 25.973667 41.521484 29.873047 C 41.188085 31.539603 41.327146 33.294372 41.955078 35.121094 L 49.910156 27.164062 C 50.372184 26.657905 50.880803 26.130913 51.429688 25.582031 L 57.892578 19.119141 C 60.046728 16.964991 61.875558 15.389037 63.361328 15.091797 C 63.547048 15.054647 63.742359 15.027806 63.949219 15.017578 C 64.156079 15.007351 64.374492 15.013756 64.607422 15.041016 C 66.005022 15.204646 67.917154 16.153481 70.882812 19.119141 C 74.837032 23.073351 75.205443 25.154865 74.908203 26.640625 C 74.610973 28.126395 73.035009 29.953262 70.880859 32.107422 L 64.419922 38.570312 C 63.865878 39.124359 63.33492 39.635932 62.824219 40.101562 L 54.878906 48.046875 C 56.706268 48.675332 58.461808 48.813981 60.128906 48.480469 C 64.028276 47.700379 66.507959 44.96665 68.662109 42.8125 L 75.125 36.349609 C 77.27915 34.195449 80.010926 31.715786 80.791016 27.816406 C 81.571096 23.917036 79.78641 19.5364 75.125 14.875 C 71.62895 11.37895 68.290852 9.500825 65.195312 9.109375 C 64.679394 9.044135 64.170062 9.0202744 63.667969 9.0371094 z M 31.355469 41.349609 C 30.853375 41.366444 30.358515 41.423984 29.871094 41.521484 C 25.971725 42.301574 23.492043 45.035303 21.337891 47.189453 L 14.875 53.650391 C 12.720847 55.804541 9.9890734 58.286177 9.2089844 62.185547 C 8.4288954 66.084907 10.213595 70.465553 14.875 75.126953 C 19.536407 79.788363 23.917035 81.573059 27.816406 80.792969 C 31.715777 80.012879 34.195456 77.27915 36.349609 75.125 L 42.8125 68.662109 C 44.96665 66.507949 47.698426 64.028286 48.478516 60.128906 C 48.812028 58.461808 48.673379 56.706268 48.044922 54.878906 L 40.109375 62.814453 C 39.641569 63.32819 39.12796 63.862274 38.570312 64.419922 L 32.107422 70.882812 C 29.953269 73.036972 28.124437 74.610973 26.638672 74.908203 C 25.152907 75.205443 23.071405 74.837002 19.117188 70.882812 C 15.162971 66.928594 14.794562 64.847088 15.091797 63.361328 C 15.389032 61.875558 16.964989 60.048681 19.119141 57.894531 L 25.580078 51.431641 C 26.132543 50.879177 26.662506 50.368883 27.171875 49.904297 L 35.119141 41.957031 C 34.361083 41.696439 33.61486 41.514447 32.882812 41.421875 C 32.366891 41.356635 31.857562 41.332774 31.355469 41.349609 z "
1938+ transform="translate(60.000003,414.36218)"
1939+ id="path4190" />
1940+ </g>
1941+ </g>
1942+ </g>
1943+ </g>
1944+</svg>
1945
1946=== modified file 'src/app/webcontainer/WebViewImplOxide.qml'
1947--- src/app/webcontainer/WebViewImplOxide.qml 2015-08-10 15:33:43 +0000
1948+++ src/app/webcontainer/WebViewImplOxide.qml 2015-08-27 14:01:06 +0000
1949@@ -18,7 +18,7 @@
1950
1951 import QtQuick 2.4
1952 import QtQuick.Window 2.2
1953-import com.canonical.Oxide 1.3 as Oxide
1954+import com.canonical.Oxide 1.8 as Oxide
1955 import Ubuntu.Components 1.3
1956 import Ubuntu.Components.Popups 1.3
1957 import Ubuntu.UnityWebApps 0.1 as UnityWebApps
1958@@ -68,12 +68,49 @@
1959
1960 contextualActions: ActionList {
1961 Actions.CopyLink {
1962- enabled: webview.contextualData.href.toString()
1963- onTriggered: Clipboard.push(["text/plain", webview.contextualData.href.toString()])
1964+ enabled: webview.contextModel && webview.contextModel.linkUrl.toString()
1965+ onTriggered: Clipboard.push(["text/plain", contextModel.linkUrl.toString()])
1966 }
1967 Actions.CopyImage {
1968- enabled: webview.contextualData.img.toString()
1969- onTriggered: Clipboard.push(["text/plain", webview.contextualData.img.toString()])
1970+ enabled: webview.contextModel &&
1971+ (webview.contextModel.mediaType === Oxide.WebView.MediaTypeImage) &&
1972+ webview.contextModel.srcUrl.toString()
1973+ onTriggered: Clipboard.push(["text/plain", contextModel.srcUrl.toString()])
1974+ }
1975+ Actions.Undo {
1976+ enabled: webview.contextModel && webview.contextModel.isEditable &&
1977+ (webview.contextModel.editFlags & Oxide.WebView.UndoCapability)
1978+ onTriggered: webview.executeEditingCommand(Oxide.WebView.EditingCommandUndo)
1979+ }
1980+ Actions.Redo {
1981+ enabled: webview.contextModel && webview.contextModel.isEditable &&
1982+ (webview.contextModel.editFlags & Oxide.WebView.RedoCapability)
1983+ onTriggered: webview.executeEditingCommand(Oxide.WebView.EditingCommandRedo)
1984+ }
1985+ Actions.Cut {
1986+ enabled: webview.contextModel && webview.contextModel.isEditable &&
1987+ (webview.contextModel.editFlags & Oxide.WebView.CutCapability)
1988+ onTriggered: webview.executeEditingCommand(Oxide.WebView.EditingCommandCut)
1989+ }
1990+ Actions.Copy {
1991+ enabled: webview.contextModel && webview.contextModel.isEditable &&
1992+ (webview.contextModel.editFlags & Oxide.WebView.CopyCapability)
1993+ onTriggered: webview.executeEditingCommand(Oxide.WebView.EditingCommandCopy)
1994+ }
1995+ Actions.Paste {
1996+ enabled: webview.contextModel && webview.contextModel.isEditable &&
1997+ (webview.contextModel.editFlags & Oxide.WebView.PasteCapability)
1998+ onTriggered: webview.executeEditingCommand(Oxide.WebView.EditingCommandPaste)
1999+ }
2000+ Actions.Erase {
2001+ enabled: webview.contextModel && webview.contextModel.isEditable &&
2002+ (webview.contextModel.editFlags & Oxide.WebView.EraseCapability)
2003+ onTriggered: webview.executeEditingCommand(Oxide.WebView.EditingCommandErase)
2004+ }
2005+ Actions.SelectAll {
2006+ enabled: webview.contextModel && webview.contextModel.isEditable &&
2007+ (webview.contextModel.editFlags & Oxide.WebView.SelectAllCapability)
2008+ onTriggered: webview.executeEditingCommand(Oxide.WebView.EditingCommandSelectAll)
2009 }
2010 }
2011
2012
2013=== modified file 'tests/autopilot/webbrowser_app/emulators/browser.py'
2014--- tests/autopilot/webbrowser_app/emulators/browser.py 2015-08-12 19:03:44 +0000
2015+++ tests/autopilot/webbrowser_app/emulators/browser.py 2015-08-27 14:01:06 +0000
2016@@ -115,13 +115,6 @@
2017 def get_geolocation_dialog(self):
2018 return self.wait_select_single(GeolocationPermissionRequest)
2019
2020- def get_selection(self):
2021- return self.wait_select_single(Selection)
2022-
2023- def get_selection_actions(self):
2024- return self.wait_select_single("ActionSelectionPopover",
2025- objectName="selectionActions")
2026-
2027 def get_tabs_view(self):
2028 return self.wait_select_single(TabsList, visible=True)
2029
2030@@ -175,6 +168,12 @@
2031 def press_key(self, key):
2032 self.keyboard.press_and_release(key)
2033
2034+ def get_context_menu(self):
2035+ if self.wide:
2036+ return self.wait_select_single(ContextMenuWide)
2037+ else:
2038+ return self.wait_select_single(ContextMenuMobile)
2039+
2040
2041 class Chrome(uitk.UbuntuUIToolkitCustomProxyObjectBase):
2042
2043@@ -324,15 +323,6 @@
2044 return self.select_single("Button", objectName="allow")
2045
2046
2047-class Selection(uitk.UbuntuUIToolkitCustomProxyObjectBase):
2048-
2049- def get_rectangle(self):
2050- return self.select_single("QQuickItem", objectName="rectangle")
2051-
2052- def get_handle(self, name):
2053- return self.select_single("SelectionHandle", objectName=name)
2054-
2055-
2056 class TabPreview(uitk.UbuntuUIToolkitCustomProxyObjectBase):
2057
2058 @autopilot.logging.log_action(logger.info)
2059@@ -574,3 +564,30 @@
2060 def get_header_from_folder(self, folder):
2061 return folder.wait_select_single("QQuickItem",
2062 objectName="bookmarkFolderHeader")
2063+
2064+
2065+class ContextMenuBase(uitk.UbuntuUIToolkitCustomProxyObjectBase):
2066+
2067+ def get_title_label(self):
2068+ return self.select_single("Label", objectName="titleLabel")
2069+
2070+ def get_visible_actions(self):
2071+ return self.select_many("Empty", visible=True)
2072+
2073+ def click_action(self, objectName):
2074+ name = objectName + "_item"
2075+ action = self.select_single("Empty", visible=True,
2076+ enabled=True, objectName=name)
2077+ self.pointing_device.click_object(action)
2078+
2079+
2080+class ContextMenuWide(ContextMenuBase):
2081+
2082+ pass
2083+
2084+
2085+class ContextMenuMobile(ContextMenuBase):
2086+
2087+ def click_cancel_action(self):
2088+ action = self.select_single("Empty", objectName="cancelAction")
2089+ self.pointing_device.click_object(action)
2090
2091=== modified file 'tests/autopilot/webbrowser_app/tests/http_server.py'
2092--- tests/autopilot/webbrowser_app/tests/http_server.py 2015-07-02 10:03:20 +0000
2093+++ tests/autopilot/webbrowser_app/tests/http_server.py 2015-08-27 14:01:06 +0000
2094@@ -20,8 +20,14 @@
2095 """
2096 A custom HTTP request handler that serves GET resources.
2097 """
2098+
2099 suggestions_data = {}
2100
2101+ base64_png_data = \
2102+ "iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAIAAACRXR/mAAAACXBIWXMAAAsTAAALEwE" \
2103+ "AmpwYAAAAOUlEQVRYw+3OAQ0AAAgDoGv/zlpDN0hATS7qaGlpaWlpaWlpaWlpaWlpaW" \
2104+ "lpaWlpaWlpaWlpab1qLUGqAWNyFWTYAAAAAElFTkSuQmCC"
2105+
2106 def make_html(self, title, body):
2107 html = "<html><title>{}</title><body>{}</body></html>"
2108 return html.format(title, body)
2109@@ -81,6 +87,28 @@
2110 html += 'src="/blanktargetlink" />'
2111 html += '</body></html>'
2112 self.send_html(html)
2113+ elif self.path == "/image":
2114+ self.send_response(200)
2115+ html = '<html><body>'
2116+ html += '<img src="data:image/png;base64,' + self.base64_png_data
2117+ html += '" style="position: fixed; top: 50%; left: 50%; '
2118+ html += 'transform: translate(-50%, -50%)" />'
2119+ html += '</body></html>'
2120+ self.send_html(html)
2121+ elif self.path == "/imagelink":
2122+ self.send_response(200)
2123+ html = '<html><body><a href="/test1">'
2124+ html += '<img src="data:image/png;base64,' + self.base64_png_data
2125+ html += '" style="position: fixed; top: 50%; left: 50%; '
2126+ html += 'transform: translate(-50%, -50%)" />'
2127+ html += '</a></body></html>'
2128+ self.send_html(html)
2129+ elif self.path == "/textarea":
2130+ self.send_response(200)
2131+ html = '<html><body style="margin: 0">'
2132+ html += '<textarea style="width: 100%; height: 100%">some text'
2133+ html += '</textarea></body></html>'
2134+ self.send_html(html)
2135 elif self.path == "/uploadform":
2136 # craft a page that accepts clicks anywhere inside its window
2137 # and on a click opens up the content picker.
2138
2139=== added file 'tests/autopilot/webbrowser_app/tests/test_contextmenu.py'
2140--- tests/autopilot/webbrowser_app/tests/test_contextmenu.py 1970-01-01 00:00:00 +0000
2141+++ tests/autopilot/webbrowser_app/tests/test_contextmenu.py 2015-08-27 14:01:06 +0000
2142@@ -0,0 +1,177 @@
2143+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2144+#
2145+# Copyright 2015 Canonical
2146+#
2147+# This program is free software: you can redistribute it and/or modify it
2148+# under the terms of the GNU General Public License version 3, as published
2149+# by the Free Software Foundation.
2150+#
2151+# This program is distributed in the hope that it will be useful,
2152+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2153+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2154+# GNU General Public License for more details.
2155+#
2156+# You should have received a copy of the GNU General Public License
2157+# along with this program. If not, see <http://www.gnu.org/licenses/>.
2158+
2159+import time
2160+from autopilot.platform import model
2161+from autopilot.matchers import Eventually
2162+import testtools
2163+from testtools.matchers import Equals, GreaterThan, StartsWith
2164+
2165+from webbrowser_app.tests import StartOpenRemotePageTestCaseBase
2166+
2167+
2168+class TestContextMenuBase(StartOpenRemotePageTestCaseBase):
2169+
2170+ data_uri_prefix = "data:image/png;base64,"
2171+
2172+ def setUp(self, path):
2173+ super(TestContextMenuBase, self).setUp(path)
2174+ self.menu = self.open_context_menu()
2175+
2176+ def open_context_menu(self):
2177+ webview = self.main_window.get_current_webview()
2178+ chrome = self.main_window.chrome
2179+ x = webview.globalRect.x + webview.globalRect.width // 2
2180+ y = webview.globalRect.y + \
2181+ (webview.globalRect.height + chrome.height) // 2
2182+ self.pointing_device.move(x, y)
2183+ if model() == 'Desktop':
2184+ self.pointing_device.click(button=3)
2185+ else:
2186+ self.pointing_device.press()
2187+ time.sleep(1.5)
2188+ self.pointing_device.release()
2189+ return self.main_window.get_context_menu()
2190+
2191+ def click_action(self, name):
2192+ self.menu.click_action(name)
2193+ self.menu.wait_until_destroyed()
2194+
2195+ def verify_link_opened_in_a_new_tab(self):
2196+ self.assert_number_webviews_eventually(2)
2197+ webview = self.main_window.get_current_webview()
2198+ new_url = self.base_url + "/test1"
2199+ self.assertThat(webview.url, Eventually(Equals(new_url)))
2200+
2201+ def verify_link_bookmarked(self):
2202+ url = self.base_url + "/test1"
2203+ self.main_window.go_to_url(url)
2204+ self.main_window.wait_until_page_loaded(url)
2205+ self.main_window.chrome.bookmarked.wait_for(True)
2206+
2207+ def verify_image_opened_in_a_new_tab(self):
2208+ self.assert_number_webviews_eventually(2)
2209+ webview = self.main_window.get_current_webview()
2210+ self.assertThat(webview.url,
2211+ Eventually(StartsWith(self.data_uri_prefix)))
2212+
2213+
2214+class TestContextMenuLink(TestContextMenuBase):
2215+
2216+ def setUp(self):
2217+ super(TestContextMenuLink, self).setUp(path="/link")
2218+ self.assertThat(self.menu.get_title_label().text,
2219+ Equals(self.base_url + "/test1"))
2220+
2221+ def test_dismiss_menu(self):
2222+ if self.main_window.wide:
2223+ # Verify that clicking outside the menu dismisses it
2224+ webview_rect = self.main_window.get_current_webview().globalRect
2225+ actions = self.menu.get_visible_actions()
2226+ self.assertThat(actions[0].globalRect.x,
2227+ GreaterThan(webview_rect.x))
2228+ outside_x = (webview_rect.x + actions[0].globalRect.x) // 2
2229+ outside_y = webview_rect.y + webview_rect.height // 2
2230+ self.pointing_device.move(outside_x, outside_y)
2231+ self.pointing_device.click()
2232+ else:
2233+ # Verify that clicking the cancel action dismisses it
2234+ self.menu.click_cancel_action()
2235+ self.menu.wait_until_destroyed()
2236+
2237+ def test_open_link_in_new_tab(self):
2238+ self.click_action("openLinkInNewTabContextualAction")
2239+ self.verify_link_opened_in_a_new_tab()
2240+
2241+ def test_bookmark_link(self):
2242+ self.click_action("bookmarkLinkContextualAction")
2243+ self.verify_link_bookmarked()
2244+
2245+ def test_copy_link(self):
2246+ # There is no easy way to test the contents of the clipboard,
2247+ # but we can at least verify that the context menu was dismissed.
2248+ self.click_action("copyLinkContextualAction")
2249+
2250+ @testtools.skipIf(model() == "Desktop", "on devices only")
2251+ def test_share_link(self):
2252+ self.click_action("ShareLinkContextualAction")
2253+ self.main_window.wait_select_single("ContentShareDialog")
2254+
2255+
2256+class TestContextMenuImage(TestContextMenuBase):
2257+
2258+ def setUp(self):
2259+ super(TestContextMenuImage, self).setUp(path="/image")
2260+ self.assertThat(self.menu.get_title_label().text,
2261+ StartsWith(self.data_uri_prefix))
2262+
2263+ def test_open_image_in_new_tab(self):
2264+ self.click_action("OpenImageInNewTabContextualAction")
2265+ self.verify_image_opened_in_a_new_tab()
2266+
2267+ def test_copy_image(self):
2268+ # There is no easy way to test the contents of the clipboard,
2269+ # but we can at least verify that the context menu was dismissed.
2270+ self.click_action("CopyImageContextualAction")
2271+
2272+
2273+class TestContextMenuImageAndLink(TestContextMenuBase):
2274+
2275+ def setUp(self):
2276+ super(TestContextMenuImageAndLink, self).setUp(path="/imagelink")
2277+ self.assertThat(self.menu.get_title_label().text,
2278+ StartsWith(self.data_uri_prefix))
2279+
2280+ def test_open_link_in_new_tab(self):
2281+ self.click_action("openLinkInNewTabContextualAction")
2282+ self.verify_link_opened_in_a_new_tab()
2283+
2284+ def test_bookmark_link(self):
2285+ self.click_action("bookmarkLinkContextualAction")
2286+ self.verify_link_bookmarked()
2287+
2288+ def test_copy_link(self):
2289+ # There is no easy way to test the contents of the clipboard,
2290+ # but we can at least verify that the context menu was dismissed.
2291+ self.click_action("copyLinkContextualAction")
2292+
2293+ @testtools.skipIf(model() == "Desktop", "on devices only")
2294+ def test_share_link(self):
2295+ self.click_action("ShareLinkContextualAction")
2296+ self.main_window.wait_select_single("ContentShareDialog")
2297+
2298+ def test_open_image_in_new_tab(self):
2299+ self.click_action("OpenImageInNewTabContextualAction")
2300+ self.verify_image_opened_in_a_new_tab()
2301+
2302+ def test_copy_image(self):
2303+ # There is no easy way to test the contents of the clipboard,
2304+ # but we can at least verify that the context menu was dismissed.
2305+ self.click_action("CopyImageContextualAction")
2306+
2307+
2308+class TestContextMenuTextArea(TestContextMenuBase):
2309+
2310+ def setUp(self):
2311+ super(TestContextMenuTextArea, self).setUp(path="/textarea")
2312+ self.assertThat(self.menu.get_title_label().visible, Equals(False))
2313+
2314+ def test_actions(self):
2315+ actions = ["SelectAll", "Cut", "Undo", "Redo",
2316+ "Paste", "SelectAll", "Copy", "Erase"]
2317+ for action in actions:
2318+ self.click_action("{}ContextualAction".format(action))
2319+ self.menu = self.open_context_menu()
2320
2321=== removed file 'tests/autopilot/webbrowser_app/tests/test_selection.py'
2322--- tests/autopilot/webbrowser_app/tests/test_selection.py 2015-07-03 16:04:21 +0000
2323+++ tests/autopilot/webbrowser_app/tests/test_selection.py 1970-01-01 00:00:00 +0000
2324@@ -1,92 +0,0 @@
2325-# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2326-#
2327-# Copyright 2014-2015 Canonical
2328-#
2329-# This program is free software: you can redistribute it and/or modify it
2330-# under the terms of the GNU General Public License version 3, as published
2331-# by the Free Software Foundation.
2332-#
2333-# This program is distributed in the hope that it will be useful,
2334-# but WITHOUT ANY WARRANTY; without even the implied warranty of
2335-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2336-# GNU General Public License for more details.
2337-#
2338-# You should have received a copy of the GNU General Public License
2339-# along with this program. If not, see <http://www.gnu.org/licenses/>.
2340-
2341-import time
2342-from autopilot.platform import model
2343-from autopilot.matchers import Eventually
2344-from testtools.matchers import Equals, GreaterThan, LessThan
2345-
2346-from webbrowser_app.tests import StartOpenRemotePageTestCaseBase
2347-
2348-
2349-class TestSelection(StartOpenRemotePageTestCaseBase):
2350-
2351- def setUp(self):
2352- super(TestSelection, self).setUp(path="/selection")
2353- webview = self.main_window.get_current_webview()
2354- self.pointing_device.move_to_object(webview)
2355- if model() == 'Desktop':
2356- self.pointing_device.click(button=3)
2357- else:
2358- self.pointing_device.press()
2359- time.sleep(1.5)
2360- self.pointing_device.release()
2361- self.selection = self.main_window.get_selection()
2362- self.rectangle = self.selection.get_rectangle()
2363- self.assertThat(self.rectangle.width, LessThan(webview.width))
2364- self.assertThat(self.rectangle.height, LessThan(webview.height))
2365- self.actions = self.main_window.get_selection_actions()
2366- self.assertThat(len(self.actions.select_many("Empty")), Equals(1))
2367-
2368- def assert_selection_eventually_dismissed(self):
2369- self.actions.wait_until_destroyed()
2370- self.selection.wait_until_destroyed()
2371-
2372- def test_copy_selection(self):
2373- copy_action = self.actions.select_single("Empty")
2374- self.pointing_device.click_object(copy_action)
2375- self.assert_selection_eventually_dismissed()
2376-
2377- def test_cancel_selection(self):
2378- webview = self.main_window.get_current_webview()
2379- x = int((webview.globalRect.x + self.rectangle.globalRect.x) / 2)
2380- y = int(webview.globalRect.y + webview.globalRect.height / 2)
2381- self.pointing_device.move(x, y)
2382- self.pointing_device.click()
2383- self.assert_selection_eventually_dismissed()
2384-
2385- def test_resize_selection(self):
2386- webview = self.main_window.get_current_webview()
2387- rect = self.rectangle.globalRect
2388-
2389- # Grow selection to the right
2390- handle = self.selection.get_handle("rightHandle")
2391- x0 = handle.globalRect.x + int(handle.globalRect.width / 2)
2392- y0 = handle.globalRect.y + int(handle.globalRect.height / 2)
2393- x1 = int((x0 + webview.globalRect.x + webview.globalRect.width) / 2)
2394- y1 = y0
2395- self.pointing_device.drag(x0, y0, x1, y1)
2396- self.assertThat(self.rectangle.width,
2397- Eventually(GreaterThan(rect.width)))
2398- self.assertThat(self.rectangle.height,
2399- Eventually(GreaterThan(rect.height)))
2400- self.actions.wait_until_destroyed()
2401- self.actions = self.main_window.get_selection_actions()
2402-
2403- # Shrink selection from the bottom
2404- handle = self.selection.get_handle("bottomHandle")
2405- x0 = handle.globalRect.x + int(handle.globalRect.width / 2)
2406- y0 = handle.globalRect.y + int(handle.globalRect.height / 2)
2407- x1 = x0
2408- y1 = webview.globalRect.y + int(webview.globalRect.height * 0.6)
2409- self.pointing_device.drag(x0, y0, x1, y1)
2410- self.assertThat(self.rectangle.globalRect, Eventually(Equals(rect)))
2411- self.actions.wait_until_destroyed()
2412- self.actions = self.main_window.get_selection_actions()
2413-
2414- def test_navigating_discards_selection(self):
2415- self.main_window.go_to_url(self.base_url + "/test1")
2416- self.assert_selection_eventually_dismissed()
2417
2418=== added file 'tests/unittests/qml/tst_FileExtensionMapper.qml'
2419--- tests/unittests/qml/tst_FileExtensionMapper.qml 1970-01-01 00:00:00 +0000
2420+++ tests/unittests/qml/tst_FileExtensionMapper.qml 2015-08-27 14:01:06 +0000
2421@@ -0,0 +1,39 @@
2422+/*
2423+ * Copyright 2015 Canonical Ltd.
2424+ *
2425+ * This file is part of webbrowser-app.
2426+ *
2427+ * webbrowser-app is free software; you can redistribute it and/or modify
2428+ * it under the terms of the GNU General Public License as published by
2429+ * the Free Software Foundation; version 3.
2430+ *
2431+ * webbrowser-app is distributed in the hope that it will be useful,
2432+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2433+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2434+ * GNU General Public License for more details.
2435+ *
2436+ * You should have received a copy of the GNU General Public License
2437+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2438+ */
2439+
2440+import QtTest 1.0
2441+import "../../../src/app/FileExtensionMapper.js" as FileExtensionMapper
2442+
2443+TestCase {
2444+ name: "FileExtensionMapper"
2445+
2446+ function test_getExtension_data() {
2447+ return [
2448+ {in: "", ext: ""},
2449+ {in: "pdf", ext: ""},
2450+ {in: ".vimrc", ext: ""},
2451+ {in: "example.pdf", ext: "pdf"},
2452+ {in: "http://example.org/path/example.pdf", ext: "pdf"},
2453+ {in: "EXAMPLE.PDF", ext: "pdf"}
2454+ ]
2455+ }
2456+
2457+ function test_getExtension(data) {
2458+ compare(FileExtensionMapper.getExtension(data.in), data.ext)
2459+ }
2460+}
2461
2462=== modified file 'tests/unittests/qml/tst_UbuntuWebView02.qml'
2463--- tests/unittests/qml/tst_UbuntuWebView02.qml 2015-08-10 15:22:00 +0000
2464+++ tests/unittests/qml/tst_UbuntuWebView02.qml 2015-08-27 14:01:06 +0000
2465@@ -18,20 +18,101 @@
2466
2467 import QtQuick 2.4
2468 import QtTest 1.0
2469+import Ubuntu.Test 1.0
2470+import Ubuntu.Components 1.3
2471 import Ubuntu.Web 0.2
2472
2473-TestCase {
2474- name: "WebView"
2475-
2476- function test_context_singleton() {
2477- compare(webview1.context, webview2.context)
2478- }
2479-
2480- WebView {
2481- id: webview1
2482- }
2483-
2484- WebView {
2485- id: webview2
2486+Item {
2487+ id: root
2488+
2489+ width: 200
2490+ height: 200
2491+
2492+ Component {
2493+ id: webviewComponent
2494+ WebView {
2495+ anchors.fill: parent
2496+ }
2497+ }
2498+
2499+ ActionList {
2500+ id: actionList
2501+ Action {
2502+ id: action1
2503+ }
2504+ Action {
2505+ id: action2
2506+ }
2507+ }
2508+
2509+ readonly property string htmlWithHyperlink: '<html><body style="margin: 0"><a href="http://example.org/"><div style="height: 100%"></div></a></body></html>'
2510+
2511+ UbuntuTestCase {
2512+ name: "UbuntuWebView02"
2513+ when: windowShown
2514+
2515+ property var webview: null
2516+
2517+ function init() {
2518+ webview = webviewComponent.createObject(root)
2519+ }
2520+
2521+ function cleanup() {
2522+ webview.destroy()
2523+ }
2524+
2525+ function test_context_singleton() {
2526+ var other = webviewComponent.createObject(root)
2527+ compare(other.context, webview.context)
2528+ other.destroy()
2529+ }
2530+
2531+ function rightClickWebview() {
2532+ var center = centerOf(webview)
2533+ mouseClick(webview, center.x, center.y, Qt.RightButton)
2534+ // give the context menu a chance to appear before carrying on
2535+ wait(500)
2536+ }
2537+
2538+ function getContextMenu() {
2539+ return findChild(webview, "contextMenu")
2540+ }
2541+
2542+ function dismissContextMenu() {
2543+ var center = centerOf(webview)
2544+ mouseClick(webview, center.x, center.y)
2545+ wait(500)
2546+ compare(getContextMenu(), null)
2547+ }
2548+
2549+ function test_no_contextual_actions() {
2550+ webview.loadHtml(root.htmlWithHyperlink, "file:///")
2551+ tryCompare(webview, "loading", false)
2552+ rightClickWebview()
2553+ compare(getContextMenu(), null)
2554+ }
2555+
2556+ function test_contextual_actions() {
2557+ webview.contextualActions = actionList
2558+ webview.loadHtml(root.htmlWithHyperlink, "file:///")
2559+ tryCompare(webview, "loading", false)
2560+ rightClickWebview()
2561+ compare(getContextMenu().actions, actionList)
2562+ compare(webview.contextualData.href, "http://example.org/")
2563+ dismissContextMenu()
2564+ compare(webview.contextualData.href, "")
2565+ }
2566+
2567+ function test_contextual_actions_all_disabled() {
2568+ webview.contextualActions = actionList
2569+ action1.enabled = false
2570+ action2.enabled = false
2571+ webview.loadHtml(root.htmlWithHyperlink, "file:///")
2572+ tryCompare(webview, "loading", false)
2573+ rightClickWebview()
2574+ compare(getContextMenu(), null)
2575+ action1.enabled = true
2576+ action2.enabled = true
2577+ }
2578 }
2579 }

Subscribers

People subscribed via source and target branches

to status/vote changes: