Merge lp:~abreu-alexandre/webbrowser-app/context-menu-to-overlay-webviews into lp:webbrowser-app

Proposed by Alexandre Abreu on 2016-01-13
Status: Merged
Approved by: Olivier Tilloy on 2016-03-07
Approved revision: 1372
Merged at revision: 1376
Proposed branch: lp:~abreu-alexandre/webbrowser-app/context-menu-to-overlay-webviews
Merge into: lp:webbrowser-app
Diff against target: 969 lines (+463/-188)
14 files modified
src/app/ContentHandler.qml (+1/-1)
src/app/webbrowser/Browser.qml (+1/-1)
src/app/webbrowser/ContentDownloadDialog.qml (+1/-0)
src/app/webbrowser/ContentPickerDialog.qml (+1/-0)
src/app/webbrowser/DownloadsPage.qml (+1/-0)
src/app/webcontainer/ContentDownloadDialog.qml (+1/-0)
src/app/webcontainer/ContentPickerDialog.qml (+1/-0)
src/app/webcontainer/PopupWindowController.qml (+3/-0)
src/app/webcontainer/PopupWindowOverlay.qml (+9/-2)
src/app/webcontainer/WebViewImplOxide.qml (+3/-138)
src/app/webcontainer/WebappContainerWebview.qml (+7/-1)
src/app/webcontainer/WebappWebview.qml (+234/-0)
tests/autopilot/webapp_container/tests/fake_servers.py (+10/-4)
tests/autopilot/webapp_container/tests/test_context_menu.py (+190/-41)
To merge this branch: bzr merge lp:~abreu-alexandre/webbrowser-app/context-menu-to-overlay-webviews
Reviewer Review Type Date Requested Status
Olivier Tilloy Approve on 2016-03-07
Alberto Mardegan (community) 2016-01-13 Approve on 2016-02-24
David Barth (community) Approve on 2016-02-04
PS Jenkins bot continuous-integration Needs Fixing on 2016-02-03
Review via email: mp+282489@code.launchpad.net

Commit Message

Handle context menu in overlay webviews.

Description of the Change

Handle context menu in overlay webviews.

To post a comment you must log in.
Alberto Mardegan (mardy) wrote :

A couple of minor inline comments.

review: Needs Fixing
David Barth (dbarth) :
review: Approve
Alexandre Abreu (abreu-alexandre) wrote :

Sorry Alberto I seem to have missed your inline comments, the branch has been updated

Alberto Mardegan (mardy) wrote :

LGTM!

review: Approve
Olivier Tilloy (osomon) wrote :

The copyright year in the header for src/app/webcontainer/WebappWebview.qml should be 2016.

It would be good to add autopilot tests to verify that the menu also works in overlays.

The changes otherwise look good (and I’ve verified they don’t introduce regressions in the browser).

Alexandre Abreu (abreu-alexandre) wrote :

> The copyright year in the header for src/app/webcontainer/WebappWebview.qml
> should be 2016.

done

>
> It would be good to add autopilot tests to verify that the menu also works in
> overlays.

done

Olivier Tilloy (osomon) wrote :

There are multiple flake8 errors in the changed python files.

review: Needs Fixing
Olivier Tilloy (osomon) wrote :

LGTM now.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== renamed file 'src/app/webbrowser/ContentHandler.qml' => 'src/app/ContentHandler.qml'
2--- src/app/webbrowser/ContentHandler.qml 2015-12-16 10:11:46 +0000
3+++ src/app/ContentHandler.qml 2016-03-07 18:11:04 +0000
4@@ -18,7 +18,7 @@
5
6 import QtQuick 2.4
7 import Ubuntu.Content 1.3
8-import "../MimeTypeMapper.js" as MimeTypeMapper
9+import "MimeTypeMapper.js" as MimeTypeMapper
10
11 Item {
12 signal exportFromDownloads(var transfer, var mimetypeFilter, bool multiSelect)
13
14=== modified file 'src/app/webbrowser/Browser.qml'
15--- src/app/webbrowser/Browser.qml 2016-03-03 19:01:36 +0000
16+++ src/app/webbrowser/Browser.qml 2016-03-07 18:11:04 +0000
17@@ -2176,7 +2176,7 @@
18
19 Loader {
20 id: contentHandlerLoader
21- source: "ContentHandler.qml"
22+ source: "../ContentHandler.qml"
23 asynchronous: true
24 }
25
26
27=== modified file 'src/app/webbrowser/ContentDownloadDialog.qml'
28--- src/app/webbrowser/ContentDownloadDialog.qml 2016-01-12 09:37:24 +0000
29+++ src/app/webbrowser/ContentDownloadDialog.qml 2016-03-07 18:11:04 +0000
30@@ -21,6 +21,7 @@
31 import Ubuntu.Components.Popups 1.3
32 import Ubuntu.Content 1.3
33 import webbrowsercommon.private 0.1
34+import ".."
35
36 Component {
37 PopupBase {
38
39=== modified file 'src/app/webbrowser/ContentPickerDialog.qml'
40--- src/app/webbrowser/ContentPickerDialog.qml 2016-01-12 14:32:38 +0000
41+++ src/app/webbrowser/ContentPickerDialog.qml 2016-03-07 18:11:04 +0000
42@@ -22,6 +22,7 @@
43 import Ubuntu.Content 1.3
44 import com.canonical.Oxide 1.8
45 import "../MimeTypeMapper.js" as MimeTypeMapper
46+import ".."
47
48 Component {
49 Popups.PopupBase {
50
51=== modified file 'src/app/webbrowser/DownloadsPage.qml'
52--- src/app/webbrowser/DownloadsPage.qml 2016-02-01 12:32:23 +0000
53+++ src/app/webbrowser/DownloadsPage.qml 2016-03-07 18:11:04 +0000
54@@ -23,6 +23,7 @@
55 import webbrowsercommon.private 0.1
56
57 import "../MimeTypeMapper.js" as MimeTypeMapper
58+import ".."
59
60 FocusScope {
61 id: downloadsItem
62
63=== modified file 'src/app/webcontainer/ContentDownloadDialog.qml'
64--- src/app/webcontainer/ContentDownloadDialog.qml 2015-12-16 10:11:46 +0000
65+++ src/app/webcontainer/ContentDownloadDialog.qml 2016-03-07 18:11:04 +0000
66@@ -21,6 +21,7 @@
67 import Ubuntu.Components.Popups 1.3
68 import Ubuntu.Content 1.3
69 import webbrowsercommon.private 0.1
70+import ".."
71
72 Component {
73 PopupBase {
74
75=== modified file 'src/app/webcontainer/ContentPickerDialog.qml'
76--- src/app/webcontainer/ContentPickerDialog.qml 2015-12-16 10:11:46 +0000
77+++ src/app/webcontainer/ContentPickerDialog.qml 2016-03-07 18:11:04 +0000
78@@ -21,6 +21,7 @@
79 import Ubuntu.Components.Popups 1.3 as Popups
80 import Ubuntu.Content 1.3
81 import "../MimeTypeMapper.js" as MimeTypeMapper
82+import ".."
83
84 Component {
85 Popups.PopupBase {
86
87=== modified file 'src/app/webcontainer/PopupWindowController.qml'
88--- src/app/webcontainer/PopupWindowController.qml 2016-01-20 20:14:54 +0000
89+++ src/app/webcontainer/PopupWindowController.qml 2016-03-07 18:11:04 +0000
90@@ -29,6 +29,7 @@
91 property var mainWebappView
92 property var views: []
93 property bool blockOpenExternalUrls: false
94+ property bool wide: false
95
96 // Used to access runtime behavior during tests
97 signal openExternalUrlTriggered(string url)
98@@ -220,6 +221,8 @@
99 height: parent.height
100 width: parent.width
101
102+ wide: controller.wide
103+
104 y: overlay.parent.height
105
106 // Poor mans heuristic to know when an overlay has been
107
108=== modified file 'src/app/webcontainer/PopupWindowOverlay.qml'
109--- src/app/webcontainer/PopupWindowOverlay.qml 2015-12-01 20:11:50 +0000
110+++ src/app/webcontainer/PopupWindowOverlay.qml 2016-03-07 18:11:04 +0000
111@@ -30,7 +30,8 @@
112 property alias currentWebview: popupWebview
113 property alias request: popupWebview.request
114 property alias url: popupWebview.url
115-
116+ property alias wide: popupWebview.wide
117+
118 signal webviewUrlChanged(url webviewUrl)
119
120 Rectangle {
121@@ -156,7 +157,7 @@
122 }
123 }
124
125- WebViewImpl {
126+ WebappWebview {
127 id: popupWebview
128
129 objectName: "overlayWebview"
130@@ -165,6 +166,12 @@
131
132 onUrlChanged: webviewUrlChanged(popupWebview.url)
133
134+ onOpenUrlExternallyRequested: {
135+ if (popupWindowController) {
136+ popupWindowController.openUrlExternally(url)
137+ }
138+ }
139+
140 anchors {
141 bottom: parent.bottom
142 left: parent.left
143
144=== modified file 'src/app/webcontainer/WebViewImplOxide.qml'
145--- src/app/webcontainer/WebViewImplOxide.qml 2016-02-10 12:55:31 +0000
146+++ src/app/webcontainer/WebViewImplOxide.qml 2016-03-07 18:11:04 +0000
147@@ -23,10 +23,9 @@
148 import Ubuntu.Components.Popups 1.3
149 import Ubuntu.UnityWebApps 0.1 as UnityWebApps
150 import Ubuntu.Web 0.2
151-import "../actions" as Actions
152 import ".."
153
154-WebViewImpl {
155+WebappWebview {
156 id: webview
157
158 property bool developerExtrasEnabled: false
159@@ -37,7 +36,6 @@
160 property url dataPath
161 property var popupController
162 property var overlayViewsParent: webview.parent
163- property bool wide: false
164
165 // Mostly used for testing & avoid external urls to
166 // "leak" in the default browser. External URLs corresponds
167@@ -65,7 +63,6 @@
168 }
169
170 currentWebview: webview
171- filePicker: filePickerLoader.item
172
173 context: WebContext {
174 dataPath: webview.dataPath
175@@ -90,6 +87,8 @@
176 }
177 ]
178
179+ onOpenUrlExternallyRequested: openUrlExternally(url)
180+
181 preferences.allowFileAccessFromFileUrls: runningLocalApplication
182 preferences.allowUniversalAccessFromFileUrls: runningLocalApplication
183 preferences.localStorageEnabled: true
184@@ -97,107 +96,6 @@
185
186 onNewViewRequested: popupController.createPopupViewForRequest(overlayViewsParent, request, true, context)
187
188- property QtObject contextModel: null
189- contextualActions: ActionList {
190- Actions.OpenLinkInWebBrowser {
191- objectName: "OpenLinkInWebBrowser"
192- enabled: contextModel && contextModel.linkUrl.toString()
193- onTriggered: openUrlExternally(contextModel.linkUrl)
194- }
195- Actions.CopyLink {
196- enabled: webview.contextModel && webview.contextModel.linkUrl.toString()
197- onTriggered: Clipboard.push(["text/plain", contextModel.linkUrl.toString()])
198- objectName: "CopyLinkContextualAction"
199- }
200- Actions.CopyImage {
201- enabled: webview.contextModel &&
202- (webview.contextModel.mediaType === Oxide.WebView.MediaTypeImage) &&
203- webview.contextModel.srcUrl.toString()
204- onTriggered: Clipboard.push(["text/plain", contextModel.srcUrl.toString()])
205- objectName: "CopyImageContextualAction"
206- }
207- Actions.Undo {
208- enabled: webview.contextModel && webview.contextModel.isEditable &&
209- (webview.contextModel.editFlags & Oxide.WebView.UndoCapability)
210- onTriggered: webview.executeEditingCommand(Oxide.WebView.EditingCommandUndo)
211- objectName: "UndoContextualAction"
212- }
213- Actions.Redo {
214- enabled: webview.contextModel && webview.contextModel.isEditable &&
215- (webview.contextModel.editFlags & Oxide.WebView.RedoCapability)
216- onTriggered: webview.executeEditingCommand(Oxide.WebView.EditingCommandRedo)
217- objectName: "RedoContextualAction"
218- }
219- Actions.Cut {
220- enabled: webview.contextModel && webview.contextModel.isEditable &&
221- (webview.contextModel.editFlags & Oxide.WebView.CutCapability)
222- onTriggered: webview.executeEditingCommand(Oxide.WebView.EditingCommandCut)
223- objectName: "CutContextualAction"
224- }
225- Actions.Copy {
226- enabled: webview.contextModel && webview.contextModel.isEditable &&
227- (webview.contextModel.editFlags & Oxide.WebView.CopyCapability)
228- onTriggered: webview.executeEditingCommand(Oxide.WebView.EditingCommandCopy)
229- objectName: "CopyContextualAction"
230- }
231- Actions.Paste {
232- enabled: webview.contextModel && webview.contextModel.isEditable &&
233- (webview.contextModel.editFlags & Oxide.WebView.PasteCapability)
234- onTriggered: webview.executeEditingCommand(Oxide.WebView.EditingCommandPaste)
235- objectName: "PasteContextualAction"
236- }
237- Actions.Erase {
238- enabled: webview.contextModel && webview.contextModel.isEditable &&
239- (webview.contextModel.editFlags & Oxide.WebView.EraseCapability)
240- onTriggered: webview.executeEditingCommand(Oxide.WebView.EditingCommandErase)
241- objectName: "EraseContextualAction"
242- }
243- Actions.SelectAll {
244- enabled: webview.contextModel && webview.contextModel.isEditable &&
245- (webview.contextModel.editFlags & Oxide.WebView.SelectAllCapability)
246- onTriggered: webview.executeEditingCommand(Oxide.WebView.EditingCommandSelectAll)
247- objectName: "SelectAllContextualAction"
248- }
249- }
250- function contextMenuOnCompleted(menu) {
251- if (!menu || !menu.contextModel) {
252- return
253- }
254- contextModel = menu.contextModel
255-
256- var isImageMediaType =
257- ((contextModel.mediaType === Oxide.WebView.MediaTypeImage) ||
258- (contextModel.mediaType === Oxide.WebView.MediaTypeCanvas))
259- && contextModel.hasImageContents;
260-
261- if (contextModel.linkUrl.toString() ||
262- contextModel.srcUrl.toString() ||
263- contextModel.selectionText ||
264- (contextModel.isEditable && contextModel.editFlags) ||
265- isImageMediaType) {
266- menu.show()
267- } else {
268- contextModel.close()
269- }
270- }
271- Component {
272- id: contextMenuNarrowComponent
273- ContextMenuMobile {
274- actions: contextualActions
275- Component.onCompleted: webview.contextMenuOnCompleted(this)
276- }
277- }
278- Component {
279- id: contextMenuWideComponent
280- ContextMenuWide {
281- associatedWebview: webview
282- parent: browser
283- actions: contextualActions
284- Component.onCompleted: webview.contextMenuOnCompleted(this)
285- }
286- }
287- contextMenu: webview.wide ? contextMenuWideComponent : contextMenuNarrowComponent
288-
289 StateSaver.properties: "url"
290 StateSaver.enabled: !runningLocalApplication
291
292@@ -323,16 +221,6 @@
293 return UnityWebAppsUtils.makeProxiesForWebViewBindee(webview, eventHandlers)
294 }
295
296- onGeolocationPermissionRequested: {
297- if (__runningConfined && (request.origin == request.embedder)) {
298- // When running confined, querying the location service will trigger
299- // a system prompt (trust store), so no need for a custom one.
300- request.accept()
301- } else {
302- requestGeolocationPermission(request)
303- }
304- }
305-
306 function handlePageMetadata(metadata) {
307 if (metadata.type === 'manifest') {
308 var request = new XMLHttpRequest();
309@@ -356,27 +244,4 @@
310 }
311 }
312 }
313-
314- onShowDownloadDialog: {
315- if (downloadDialogLoader.status === Loader.Ready) {
316- var downloadDialog = PopupUtils.open(downloadDialogLoader.item, webview, {"contentType" : contentType,
317- "downloadId" : downloadId,
318- "singleDownload" : downloader,
319- "filename" : filename,
320- "mimeType" : mimeType})
321- downloadDialog.startDownload.connect(startDownload)
322- }
323- }
324-
325- Loader {
326- id: downloadDialogLoader
327- source: "ContentDownloadDialog.qml"
328- asynchronous: true
329- }
330-
331- Loader {
332- id: filePickerLoader
333- source: "ContentPickerDialog.qml"
334- asynchronous: true
335- }
336 }
337
338=== modified file 'src/app/webcontainer/WebappContainerWebview.qml'
339--- src/app/webcontainer/WebappContainerWebview.qml 2016-01-08 16:09:01 +0000
340+++ src/app/webcontainer/WebappContainerWebview.qml 2016-03-07 18:11:04 +0000
341@@ -44,7 +44,12 @@
342 signal samlRequestUrlPatternReceived(string urlPattern)
343 signal themeColorMetaInformationDetected(string theme_color)
344
345- onWideChanged: if (webappContainerWebViewLoader.item) webappContainerWebViewLoader.item.wide = wide
346+ onWideChanged: {
347+ if (webappContainerWebViewLoader.item
348+ && webappContainerWebViewLoader.item.wide !== undefined) {
349+ webappContainerWebViewLoader.item.wide = wide
350+ }
351+ }
352
353 PopupWindowController {
354 id: popupController
355@@ -52,6 +57,7 @@
356 webappUrlPatterns: containerWebview.webappUrlPatterns
357 mainWebappView: containerWebview.currentWebview
358 blockOpenExternalUrls: containerWebview.blockOpenExternalUrls
359+ wide: containerWebview.wide
360 onInitializeOverlayViewsWithUrls: {
361 if (webappContainerWebViewLoader.item) {
362 for (var i in urls) {
363
364=== added file 'src/app/webcontainer/WebappWebview.qml'
365--- src/app/webcontainer/WebappWebview.qml 1970-01-01 00:00:00 +0000
366+++ src/app/webcontainer/WebappWebview.qml 2016-03-07 18:11:04 +0000
367@@ -0,0 +1,234 @@
368+/*
369+ * Copyright 2016 Canonical Ltd.
370+ *
371+ * This file is part of webbrowser-app.
372+ *
373+ * webbrowser-app is free software; you can redistribute it and/or modify
374+ * it under the terms of the GNU General Public License as published by
375+ * the Free Software Foundation; version 3.
376+ *
377+ * webbrowser-app is distributed in the hope that it will be useful,
378+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
379+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
380+ * GNU General Public License for more details.
381+ *
382+ * You should have received a copy of the GNU General Public License
383+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
384+ */
385+
386+import QtQuick 2.4
387+import com.canonical.Oxide 1.8 as Oxide
388+import Ubuntu.Components 1.3
389+import Ubuntu.Components.Popups 1.3
390+import Ubuntu.Web 0.2
391+import "../actions" as Actions
392+import ".."
393+
394+WebViewImpl {
395+ id: webappWebview
396+
397+ property bool wide: false
398+
399+ signal openUrlExternallyRequested(string url)
400+
401+ filePicker: filePickerLoader.item
402+
403+ property QtObject contextModel: null
404+ contextualActions: ActionList {
405+ Actions.OpenLinkInWebBrowser {
406+ objectName: "OpenLinkInWebBrowser"
407+ enabled: contextModel && contextModel.linkUrl.toString()
408+ onTriggered: openUrlExternallyRequested(contextModel.linkUrl)
409+ }
410+ Actions.CopyLink {
411+ enabled: contextModel && contextModel.linkUrl.toString()
412+ onTriggered: Clipboard.push(["text/plain", contextModel.linkUrl.toString()])
413+ objectName: "CopyLinkContextualAction"
414+ }
415+ Actions.SaveLink {
416+ enabled: contextModel && contextModel.linkUrl.toString()
417+ onTriggered: contextModel.saveLink()
418+ objectName: "SaveLinkContextualAction"
419+ }
420+ Actions.Share {
421+ objectName: "ShareContextualAction"
422+ enabled: (contentHandlerLoader.status == Loader.Ready) && contextModel &&
423+ (contextModel.linkUrl.toString() || contextModel.selectionText)
424+ onTriggered: {
425+ if (contextModel.linkUrl.toString()) {
426+ internal.shareLink(contextModel.linkUrl.toString(), contextModel.linkText)
427+ } else if (contextModel.selectionText) {
428+ internal.shareText(contextModel.selectionText)
429+ }
430+ }
431+ }
432+ Actions.CopyImage {
433+ enabled: contextModel &&
434+ (contextModel.mediaType === Oxide.WebView.MediaTypeImage) &&
435+ contextModel.srcUrl.toString()
436+ onTriggered: Clipboard.push(["text/plain", contextModel.srcUrl.toString()])
437+ objectName: "CopyImageContextualAction"
438+ }
439+ Actions.SaveImage {
440+ enabled: contextModel &&
441+ ((contextModel.mediaType === Oxide.WebView.MediaTypeImage) ||
442+ (contextModel.mediaType === Oxide.WebView.MediaTypeCanvas)) &&
443+ contextModel.hasImageContents
444+ onTriggered: contextModel.saveMedia()
445+ objectName: "SaveImageContextualAction"
446+ }
447+ Actions.Undo {
448+ enabled: contextModel &&
449+ contextModel.isEditable &&
450+ (contextModel.editFlags & Oxide.WebView.UndoCapability)
451+ onTriggered: executeEditingCommand(Oxide.WebView.EditingCommandUndo)
452+ objectName: "UndoContextualAction"
453+ }
454+ Actions.Redo {
455+ enabled: contextModel &&
456+ contextModel.isEditable &&
457+ (contextModel.editFlags & Oxide.WebView.RedoCapability)
458+ onTriggered: executeEditingCommand(Oxide.WebView.EditingCommandRedo)
459+ objectName: "RedoContextualAction"
460+ }
461+ Actions.Cut {
462+ enabled: contextModel &&
463+ contextModel.isEditable &&
464+ (contextModel.editFlags & Oxide.WebView.CutCapability)
465+ onTriggered: executeEditingCommand(Oxide.WebView.EditingCommandCut)
466+ objectName: "CutContextualAction"
467+ }
468+ Actions.Copy {
469+ enabled: contextModel &&
470+ contextModel.isEditable &&
471+ (contextModel.editFlags & Oxide.WebView.CopyCapability)
472+ onTriggered: executeEditingCommand(Oxide.WebView.EditingCommandCopy)
473+ objectName: "CopyContextualAction"
474+ }
475+ Actions.Paste {
476+ enabled: contextModel &&
477+ contextModel.isEditable &&
478+ (contextModel.editFlags & Oxide.WebView.PasteCapability)
479+ onTriggered: executeEditingCommand(Oxide.WebView.EditingCommandPaste)
480+ objectName: "PasteContextualAction"
481+ }
482+ Actions.Erase {
483+ enabled: contextModel &&
484+ contextModel.isEditable &&
485+ (contextModel.editFlags & Oxide.WebView.EraseCapability)
486+ onTriggered: executeEditingCommand(Oxide.WebView.EditingCommandErase)
487+ objectName: "EraseContextualAction"
488+ }
489+ Actions.SelectAll {
490+ enabled: contextModel &&
491+ contextModel.isEditable &&
492+ (contextModel.editFlags & Oxide.WebView.SelectAllCapability)
493+ onTriggered: executeEditingCommand(Oxide.WebView.EditingCommandSelectAll)
494+ objectName: "SelectAllContextualAction"
495+ }
496+ }
497+ function contextMenuOnCompleted(menu) {
498+ if (!menu || !menu.contextModel) {
499+ return
500+ }
501+ contextModel = menu.contextModel
502+
503+ var isImageMediaType =
504+ ((contextModel.mediaType === Oxide.WebView.MediaTypeImage) ||
505+ (contextModel.mediaType === Oxide.WebView.MediaTypeCanvas))
506+ && contextModel.hasImageContents;
507+
508+ if (contextModel.linkUrl.toString() ||
509+ contextModel.srcUrl.toString() ||
510+ contextModel.selectionText ||
511+ (contextModel.isEditable && contextModel.editFlags) ||
512+ isImageMediaType) {
513+ menu.show()
514+ } else {
515+ contextModel.close()
516+ }
517+ }
518+ Component {
519+ id: contextMenuNarrowComponent
520+ ContextMenuMobile {
521+ actions: contextualActions
522+ Component.onCompleted: webappWebview.contextMenuOnCompleted(this)
523+ }
524+ }
525+ Component {
526+ id: contextMenuWideComponent
527+ ContextMenuWide {
528+ associatedWebview: webappWebview
529+ parent: webappWebview
530+ actions: contextualActions
531+ Component.onCompleted: webappWebview.contextMenuOnCompleted(this)
532+ }
533+ }
534+ contextMenu: webappWebview.wide ? contextMenuWideComponent : contextMenuNarrowComponent
535+
536+ onGeolocationPermissionRequested: {
537+ if (__runningConfined && (request.origin == request.embedder)) {
538+ // When running confined, querying the location service will trigger
539+ // a system prompt (trust store), so no need for a custom one.
540+ request.allow()
541+ } else {
542+ requestGeolocationPermission(request)
543+ }
544+ }
545+
546+ Loader {
547+ id: contentHandlerLoader
548+ source: "../ContentHandler.qml"
549+ asynchronous: true
550+ }
551+
552+ QtObject {
553+ id: internal
554+
555+ function instantiateShareComponent() {
556+ var component = Qt.createComponent("../Share.qml")
557+ if (component.status === Component.Ready) {
558+ var share = component.createObject(webappWebview)
559+ share.onDone.connect(share.destroy)
560+ return share
561+ }
562+ return null
563+ }
564+
565+ function shareLink(url, title) {
566+ var share = instantiateShareComponent()
567+ if (share) share.shareLink(url, title)
568+ }
569+
570+ function shareText(text) {
571+ var share = instantiateShareComponent()
572+ if (share) share.shareText(text)
573+ }
574+ }
575+
576+ onShowDownloadDialog: {
577+ if (downloadDialogLoader.status === Loader.Ready) {
578+ var downloadDialog =
579+ PopupUtils.open(downloadDialogLoader.item,
580+ webappWebview,
581+ {"contentType" : contentType,
582+ "downloadId" : downloadId,
583+ "singleDownload" : downloader,
584+ "filename" : filename,
585+ "mimeType" : mimeType})
586+ downloadDialog.startDownload.connect(startDownload)
587+ }
588+ }
589+
590+ Loader {
591+ id: downloadDialogLoader
592+ source: "ContentDownloadDialog.qml"
593+ asynchronous: true
594+ }
595+
596+ Loader {
597+ id: filePickerLoader
598+ source: "ContentPickerDialog.qml"
599+ asynchronous: true
600+ }
601+}
602
603=== modified file 'tests/autopilot/webapp_container/tests/fake_servers.py'
604--- tests/autopilot/webapp_container/tests/fake_servers.py 2016-01-08 16:09:01 +0000
605+++ tests/autopilot/webapp_container/tests/fake_servers.py 2016-03-07 18:11:04 +0000
606@@ -18,6 +18,7 @@
607 import http.server as http
608 import logging
609 import threading
610+import urllib
611
612
613 class RequestHandler(http.BaseHTTPRequestHandler):
614@@ -66,7 +67,7 @@
615 </html>
616 """
617
618- def targetted_click_content(self):
619+ def external_href_with_link_content(self, path="open-close-content"):
620 return """
621 <html>
622 <head>
623@@ -74,14 +75,14 @@
624 </head>
625 <body>
626 <div>
627-<a href="/open-close-content" target="_blank">
628+<a href="/{}" target="_blank">
629 <div style="height: 100%; width: 100%">
630 </div>
631 </a>
632 </div>
633 </body>
634 </html>
635- """
636+ """.format(path)
637
638 def display_ua_content(self):
639 return """
640@@ -213,7 +214,7 @@
641 self.serve_content(html)
642 elif self.path == '/with-targetted-link':
643 self.send_response(200)
644- self.serve_content(self.targetted_click_content())
645+ self.serve_content(self.external_href_with_link_content())
646 elif self.path == '/show-user-agent':
647 self.send_response(200)
648 self.serve_content(self.display_ua_content())
649@@ -255,6 +256,11 @@
650 self.send_response(302)
651 self.send_header("Location", locationTarget)
652 self.end_headers()
653+ elif self.path.startswith('/with-overlay-link'):
654+ qs = urllib.parse.parse_qs(urllib.parse.urlparse(self.path).query)
655+ self.send_response(200)
656+ self.serve_content(
657+ self.external_href_with_link_content(qs['path'][0]))
658 else:
659 self.send_error(404)
660
661
662=== modified file 'tests/autopilot/webapp_container/tests/test_context_menu.py'
663--- tests/autopilot/webapp_container/tests/test_context_menu.py 2015-12-18 18:00:47 +0000
664+++ tests/autopilot/webapp_container/tests/test_context_menu.py 2016-03-07 18:11:04 +0000
665@@ -16,9 +16,11 @@
666
667 import time
668
669+import testtools
670+
671 from autopilot.platform import model
672 from autopilot.matchers import Eventually
673-from testtools.matchers import Equals, StartsWith
674+from testtools.matchers import Equals, StartsWith, GreaterThan
675
676 from webapp_container.tests import WebappContainerTestCaseWithLocalContentBase
677
678@@ -67,10 +69,10 @@
679 ContextMenuMobile,
680 objectName="contextMenuMobile")
681
682- def _open_context_menu(self):
683- webview = self.get_webview()
684- x = webview.globalRect.x + webview.globalRect.width // 2
685- y = webview.globalRect.y + webview.globalRect.height // 2
686+ def _open_context_menu(self, webview):
687+ gr = webview.globalRect
688+ x = gr.x + webview.width // 2
689+ y = gr.y + webview.height // 2
690 self.pointing_device.move(x, y)
691 if model() == 'Desktop':
692 self.pointing_device.click(button=3)
693@@ -94,28 +96,56 @@
694 menu.click_cancel_action()
695 menu.wait_until_destroyed()
696
697- def setUp(self, path):
698- super(TestContextMenuBase, self).setUp()
699+ def _click_window_open(self):
700+ webview = self.get_oxide_webview()
701+ gr = webview.globalRect
702+ self.pointing_device.move(
703+ gr.x + webview.width*3/4,
704+ gr.y + webview.height*3/4)
705+ self.pointing_device.click()
706+
707+ def _launch_application(self, path):
708 args = []
709 self.launch_webcontainer_app_with_local_http_server(
710 args,
711 path,
712 {'WEBAPP_CONTAINER_BLOCK_OPEN_URL_EXTERNALLY': '1'})
713 self.get_webcontainer_window().visible.wait_for(True)
714- self.menu = self._open_context_menu()
715+
716+ def _setup_overlay_webview_context_menu(self, path):
717+ overlay_path = "/with-overlay-link?path={}".format(path)
718+ self._launch_application(overlay_path)
719+
720+ popup_controller = self.get_popup_controller()
721+ animation_watcher = popup_controller.watch_signal(
722+ 'windowOverlayOpenAnimationDone()')
723+ animation_signal_emission = animation_watcher.num_emissions
724+
725+ self._click_window_open()
726+
727+ self.assertThat(
728+ lambda: len(self.get_popup_overlay_views()),
729+ Eventually(Equals(1)))
730+ self.assertThat(
731+ lambda: animation_watcher.num_emissions,
732+ Eventually(GreaterThan(animation_signal_emission)))
733+
734+ self.webview = self.get_popup_overlay_views()[0].select_single(
735+ objectName="overlayWebview")
736+ self.menu = self._open_context_menu(self.webview)
737+
738+ def _setup_webview_context_menu(self, path):
739+ self._launch_application("/{}".format(path))
740+
741+ self.webview = self.get_oxide_webview()
742+ self.menu = self._open_context_menu(self.webview)
743
744
745 class TestContextMenuLink(TestContextMenuBase):
746
747- def setUp(self):
748- super(TestContextMenuLink, self).setUp(path="/with-external-link")
749- self.assertThat(self.menu.get_title_label().text,
750- Equals("http://www.ubuntu.com/"))
751-
752- def test_open_link_(self):
753- main_webview = self.get_oxide_webview()
754- signal = main_webview.watch_signal(
755- 'openExternalUrlTriggered(QString)')
756+ def _test_open_link_(self):
757+ signal = self.webview.watch_signal(
758+ 'openUrlExternallyRequested(QString)')
759 self.assertThat(signal.was_emitted, Equals(False))
760
761 self.menu.click_action("OpenLinkInWebBrowser")
762@@ -123,34 +153,86 @@
763 self.assertThat(lambda: signal.was_emitted, Eventually(Equals(True)))
764 self.assertThat(signal.num_emissions, Equals(1))
765
766- def test_copy_link(self):
767+ def _test_copy_link(self):
768 self.menu.click_action("CopyLinkContextualAction")
769
770+ @testtools.skipIf(model() == "Desktop", "on devices only")
771+ def _test_share_link(self):
772+ self.menu.click_action("ShareContextualAction")
773+ self.app.wait_select_single("ContentShareDialog")
774+
775+
776+class TestContextMenuLinkOverlayWebView(TestContextMenuLink):
777+
778+ def setUp(self):
779+ super(TestContextMenuLinkOverlayWebView, self).setUp()
780+ self._setup_overlay_webview_context_menu("with-external-link")
781+
782+ def test_open_link_(self):
783+ self._test_open_link_()
784+
785+ def test_copy_link(self):
786+ self._test_copy_link()
787+
788+ @testtools.skipIf(model() == "Desktop", "on devices only")
789+ def test_share_link(self):
790+ self._test_share_link()
791+
792+
793+class TestContextMenuLinkMainWebView(TestContextMenuLink):
794+
795+ def setUp(self):
796+ super(TestContextMenuLinkMainWebView, self).setUp()
797+ self._setup_webview_context_menu("with-external-link")
798+
799+ def test_open_link_(self):
800+ self._test_open_link_()
801+
802+ def test_copy_link(self):
803+ self._test_copy_link()
804+
805+ @testtools.skipIf(model() == "Desktop", "on devices only")
806+ def test_share_link(self):
807+ self._test_share_link()
808+
809
810 class TestContextMenuImage(TestContextMenuBase):
811
812- def setUp(self):
813- super(TestContextMenuImage, self).setUp(path="/image")
814- self.assertThat(self.menu.get_title_label().text,
815- StartsWith(self.data_uri_prefix))
816-
817- def test_copy_image(self):
818+ def _test_copy_image(self):
819 # There is no easy way to test the contents of the clipboard,
820 # but we can at least verify that the context menu was dismissed.
821 self.menu.click_action("CopyImageContextualAction")
822
823
824+class TestContextMenuImageMainWebview(TestContextMenuImage):
825+
826+ def setUp(self):
827+ super(TestContextMenuImageMainWebview, self).setUp()
828+ self._setup_webview_context_menu("image")
829+ self.assertThat(self.menu.get_title_label().text,
830+ StartsWith(self.data_uri_prefix))
831+
832+ def test_copy_image(self):
833+ self._test_copy_image()
834+
835+
836+class TestContextMenuImageOverlayWebView(TestContextMenuImage):
837+
838+ def setUp(self):
839+ super(TestContextMenuImageOverlayWebView, self).setUp()
840+ self._setup_overlay_webview_context_menu("image")
841+ self.assertThat(self.menu.get_title_label().text,
842+ StartsWith(self.data_uri_prefix))
843+
844+ def test_copy_image(self):
845+ self._test_copy_image()
846+
847+
848 class TestContextMenuImageAndLink(TestContextMenuBase):
849
850- def setUp(self):
851- super(TestContextMenuImageAndLink, self).setUp(path="/imagelink")
852- self.assertThat(self.menu.get_title_label().text,
853- StartsWith(self.data_uri_prefix))
854-
855- def test_open_link_in_webbrowser(self):
856- main_webview = self.get_oxide_webview()
857- signal = main_webview.watch_signal(
858- 'openExternalUrlTriggered(QString)')
859+ def _test_open_link_in_webbrowser(self):
860+ signal = self.webview.watch_signal(
861+ 'openUrlExternallyRequested(QString)')
862 self.assertThat(signal.was_emitted, Equals(False))
863
864 self.menu.click_action("OpenLinkInWebBrowser")
865@@ -158,24 +240,68 @@
866 self.assertThat(lambda: signal.was_emitted, Eventually(Equals(True)))
867 self.assertThat(signal.num_emissions, Equals(1))
868
869- def test_copy_link(self):
870+ def _test_share_link(self):
871+ self.menu.click_action("ShareContextualAction")
872+ self.app.wait_select_single("ContentShareDialog")
873+
874+ def _test_copy_link(self):
875 # There is no easy way to test the contents of the clipboard,
876 # but we can at least verify that the context menu was dismissed.
877 self.menu.click_action("CopyLinkContextualAction")
878
879- def test_copy_image(self):
880+ def _test_copy_image(self):
881 # There is no easy way to test the contents of the clipboard,
882 # but we can at least verify that the context menu was dismissed.
883 self.menu.click_action("CopyImageContextualAction")
884
885
886+class TestContextMenuImageAndLinkMainWebView(TestContextMenuImageAndLink):
887+
888+ def setUp(self):
889+ super(TestContextMenuImageAndLinkMainWebView, self).setUp()
890+ self._setup_webview_context_menu("imagelink")
891+ self.assertThat(self.menu.get_title_label().text,
892+ StartsWith(self.data_uri_prefix))
893+
894+ def test_open_link_in_webbrowser(self):
895+ self._test_open_link_in_webbrowser()
896+
897+ @testtools.skipIf(model() == "Desktop", "on devices only")
898+ def test_share_link(self):
899+ self._test_share_link()
900+
901+ def test_copy_link(self):
902+ self._test_copy_link()
903+
904+ def test_copy_image(self):
905+ self._test_copy_image()
906+
907+
908+class TestContextMenuImageAndLinkOverlayWebView(TestContextMenuImageAndLink):
909+
910+ def setUp(self):
911+ super(TestContextMenuImageAndLinkOverlayWebView, self).setUp()
912+ self._setup_overlay_webview_context_menu("imagelink")
913+ self.assertThat(self.menu.get_title_label().text,
914+ StartsWith(self.data_uri_prefix))
915+
916+ def test_open_link_in_webbrowser(self):
917+ self._test_open_link_in_webbrowser()
918+
919+ @testtools.skipIf(model() == "Desktop", "on devices only")
920+ def test_share_link(self):
921+ self._test_share_link()
922+
923+ def test_copy_link(self):
924+ self._test_copy_link()
925+
926+ def test_copy_image(self):
927+ self._test_copy_image()
928+
929+
930 class TestContextMenuTextArea(TestContextMenuBase):
931
932- def setUp(self):
933- super(TestContextMenuTextArea, self).setUp(path="/textarea")
934- self.assertThat(self.menu.get_title_label().visible, Equals(False))
935-
936- def test_actions(self):
937+ def _test_actions(self):
938 actions = ["SelectAll",
939 "Cut",
940 "Undo",
941@@ -186,4 +312,27 @@
942 "Erase"]
943 for action in actions:
944 self.menu.click_action("{}ContextualAction".format(action))
945- self.menu = self._open_context_menu()
946+ webview = self.get_webview()
947+ self.menu = self._open_context_menu(webview)
948+
949+
950+class TestContextMenuTextAreaMainWebView(TestContextMenuTextArea):
951+
952+ def setUp(self):
953+ super(TestContextMenuTextAreaMainWebView, self).setUp()
954+ self._setup_webview_context_menu("textarea")
955+ self.assertThat(self.menu.get_title_label().visible, Equals(False))
956+
957+ def test_actions(self):
958+ self._test_actions()
959+
960+
961+class TestContextMenuTextAreaOverlayWebView(TestContextMenuTextArea):
962+
963+ def setUp(self):
964+ super(TestContextMenuTextAreaOverlayWebView, self).setUp()
965+ self._setup_overlay_webview_context_menu("textarea")
966+ self.assertThat(self.menu.get_title_label().visible, Equals(False))
967+
968+ def test_actions(self):
969+ self._test_actions()

Subscribers

People subscribed via source and target branches

to status/vote changes: