Merge lp:~abreu-alexandre/webbrowser-app/window-open-overlay into lp:webbrowser-app

Proposed by Alexandre Abreu
Status: Merged
Approved by: Olivier Tilloy
Approved revision: 989
Merged at revision: 989
Proposed branch: lp:~abreu-alexandre/webbrowser-app/window-open-overlay
Merge into: lp:webbrowser-app
Diff against target: 1006 lines (+612/-253)
9 files modified
src/app/webcontainer/PopupWindowController.qml (+181/-0)
src/app/webcontainer/PopupWindowOverlay.qml (+199/-0)
src/app/webcontainer/WebViewImplOxide.qml (+13/-130)
src/app/webcontainer/WebappContainerWebview.qml (+11/-1)
tests/autopilot/webapp_container/tests/__init__.py (+6/-0)
tests/autopilot/webapp_container/tests/fake_servers.py (+32/-11)
tests/autopilot/webapp_container/tests/test_popup_webview_overlay.py (+170/-0)
tests/autopilot/webapp_container/tests/test_redirection_pattern.py (+0/-59)
tests/autopilot/webapp_container/tests/test_url_patterns.py (+0/-52)
To merge this branch: bzr merge lp:~abreu-alexandre/webbrowser-app/window-open-overlay
Reviewer Review Type Date Requested Status
Olivier Tilloy Approve
PS Jenkins bot continuous-integration Needs Fixing
Review via email: mp+248000@code.launchpad.net

Commit message

Add multi-window support for webapps

Description of the change

Add multi-window support for webapps

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: Needs Fixing (continuous-integration)
Revision history for this message
Olivier Tilloy (osomon) wrote :

Not sure exactly what the intended UX behaviour is (the spec is a bit summary), this is what I’m seeing when testing on the desktop:

 - The animation to open an overlay is quite fast, almost imperceptible (could we maybe make it a tad slower?)

 - When dismissing an overlay (by dragging it downwards or clicking on the chevron), it’s destroyed, right? I’m thinking that in that case maybe a cross icon would be more appropriate than a chevron, but of course that’s for a visual designer to decide.

 - When opening an overlay, the current webview seems to be hidden instantaneously, it should be hidden only once the animation has completed.

 - Similarly, when dragging an overlay downwards, nothing is visible behind, I think the main webview (or the previous overlay if any) should be visible.

 - After opening 3 overlays, the fourth external link gets open in my desktop browser (chromium) as expected, but then after closing one overlay and starting to re-open more external links, I got in a situation where blank chromium windows would open instead of a new tab in the current instance, this is weird (tested with initially opening the container on http://plus.google.com). When that happens, I’m seeing the following message on the console:

    qml: Maximum number of popup overlay opened, opening: about:blank in the browser

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

Also, I’m thinking that this behaviour makes sense on devices, but does it on desktop, where it’s much more visible when a new tab opens in a browser?

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
948. By Launchpad Translations on behalf of phablet-team

Launchpad automatic translations update.

Revision history for this message
Alexandre Abreu (abreu-alexandre) wrote :

> Not sure exactly what the intended UX behaviour is (the spec is a bit
> summary), this is what I’m seeing when testing on the desktop:
>
> - The animation to open an overlay is quite fast, almost imperceptible (could
> we maybe make it a tad slower?)

agree, done

> - When dismissing an overlay (by dragging it downwards or clicking on the
> chevron), it’s destroyed, right? I’m thinking that in that case maybe a cross
> icon would be more appropriate than a chevron, but of course that’s for a
> visual designer to decide.

yeah, those are the designs I had from "design", ... imo it is rather understandable,

> - When opening an overlay, the current webview seems to be hidden
> instantaneously, it should be hidden only once the animation has completed.
>
> - Similarly, when dragging an overlay downwards, nothing is visible behind, I
> think the main webview (or the previous overlay if any) should be visible.

I updated the behavior to be cleaner & slicker,

> - After opening 3 overlays, the fourth external link gets open in my desktop
> browser (chromium) as expected, but then after closing one overlay and
> starting to re-open more external links, I got in a situation where blank
> chromium windows would open instead of a new tab in the current instance, this
> is weird (tested with initially opening the container on
> http://plus.google.com). When that happens, I’m seeing the following message
> on the console:
>
> qml: Maximum number of popup overlay opened, opening: about:blank in the
> browser

mmh I was not able to repro that ...

Revision history for this message
Alexandre Abreu (abreu-alexandre) wrote :

> Also, I’m thinking that this behaviour makes sense on devices, but does it on
> desktop, where it’s much more visible when a new tab opens in a browser?

I would have to check with design, but I still does make sense on desktop too.
The behavior might be adjusted a bit but it does not feel too awkward.

We dont support tabs for webapps no matter what the environment is, and when thinking
about tabs+webapps on desktop it is not obvious to me how to have it work in a clean way
for the user. Adding tabs is somewhat out of the question (at least atm) I think,

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
949. By Olivier Tilloy

Use the new locationBarController API available in oxide 1.5 to control the position of the chrome. Fixes: #1365179, #1429132
Approved by: Alexandre Abreu

950. By Olivier Tilloy

Rewrite URLs with an uppercase scheme. Fixes: #1436312

951. By Olivier Tilloy

Remove two broken symlinks.

952. By Olivier Tilloy

Always initialize member attribute at construction time.

Not really an issue here, but flagged by coverity (https://scan.coverity.com/projects/4565), and easy enough to address.

953. By CI Train Bot Account

Releasing 0.23+15.04.20150331.2-0ubuntu1

954. By CI Train Bot Account

Resync trunk.

955. By Launchpad Translations on behalf of phablet-team

Launchpad automatic translations update.

956. By Riccardo Padovani

Add settings page, per design specification. This adds qml-module-qt-labs-folderlistmodel and qml-module-qt-labs-settings as runtime dependencies for webbrowser-app. Fixes: #1351183
Approved by: Bill Filler, Olivier Tilloy

957. By Olivier Tilloy

Autopilot tests for the settings UI.

958. By Olivier Tilloy

Add a "Clear Cache" entry under the privacy settings. Fixes: #1260014, #1296364
Approved by: PS Jenkins bot

959. By CI Train Bot Account

Releasing 0.23+15.04.20150408-0ubuntu1

960. By Olivier Tilloy

Update translation template.

961. By Launchpad Translations on behalf of phablet-team

Launchpad automatic translations update.

962. By Launchpad Translations on behalf of phablet-team

Launchpad automatic translations update.

963. By Alexandre Abreu

remove qtwebkit deps (LP: #1362640) Fixes: #1362640
Approved by: Timo Jyrinki, PS Jenkins bot, Olivier Tilloy

964. By Justin McPherson <justin@phablet-dev>

Command line options for media-hub use through Oxide.
Approved by: PS Jenkins bot

965. By CI Train Bot Account

Releasing 0.23+15.04.20150410-0ubuntu1

966. By CI Train Bot Account

Resync trunk.

967. By Launchpad Translations on behalf of phablet-team

Launchpad automatic translations update.

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

There’s a couple of trivial conflicts when merging into the latest trunk.

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

I’m seeing 3 failures when running the autopilot tests on my desktop:

    webapp_container.tests.test_popup_webview_overlay.WebappContainerPopupWebViewOverlayTestCase.test_max_overlay_count_reached
    webapp_container.tests.test_popup_webview_overlay.WebappContainerPopupWebViewOverlayTestCase.test_open_close_back_to_mainview
    webapp_container.tests.test_popup_webview_overlay.WebappContainerPopupWebViewOverlayTestCase.test_open_overlay_in_main_browser

review: Needs Fixing
Revision history for this message
Alexandre Abreu (abreu-alexandre) wrote :

updated

968. By Arthur Mello

Add model support to control which history entries will be displayed based on a blacklist database
Approved by: PS Jenkins bot, Olivier Tilloy

969. By Arthur Mello

Make Top Sites format equal to Bookmarks on the New Tab view
Approved by: PS Jenkins bot

970. By CI Train Bot Account

Releasing 0.23+15.04.20150416-0ubuntu1

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
971. By CI Train Bot Account

Resync trunk.

972. By Launchpad Translations on behalf of phablet-team

Launchpad automatic translations update.

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

I’m still seeing autopilot test failures when run on my desktop (up-to-date vivid): http://pastebin.ubuntu.com/10879666/

review: Needs Fixing
973. By Ugo Riboni

Include bookmark results in the suggestions list Fixes: #1351177
Approved by: Olivier Tilloy, PS Jenkins bot

974. By Alexandre Abreu

Add missing reload button from the webapp container as specified in the design document.
Approved by: Olivier Tilloy, PS Jenkins bot

975. By Ken VanDine

added ShareLink to contextualActions

Approved by: Olivier Tilloy, PS Jenkins bot

976. By Olivier Tilloy

Save the updated homepage when pressing return. Fixes: #1441874
Approved by: PS Jenkins bot, Riccardo Padovani

977. By Leo Arias

In the autopilot tests, removed the extra focus step to write a URL. Fixes: #1441551
Approved by: Olivier Tilloy, PS Jenkins bot

978. By Olivier Tilloy

Always exit fullscreen mode when the application becomes inactive. Fixes: #1331475
Approved by: PS Jenkins bot, Bill Filler

979. By Olivier Tilloy

Recognize about:blank as a valid URL. Fixes: #1444139
Approved by: PS Jenkins bot

980. By CI Train Bot Account

Releasing 0.23+15.04.20150422.1-0ubuntu1

981. By CI Train Bot Account

Resync trunk.

982. By Olivier Tilloy

Update translation template.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
983. By Launchpad Translations on behalf of phablet-team

Launchpad automatic translations update.

984. By Launchpad Translations on behalf of phablet-team

Launchpad automatic translations update.

985. By Launchpad Translations on behalf of phablet-team

Launchpad automatic translations update.

986. By Launchpad Translations on behalf of phablet-team

Launchpad automatic translations update.

987. By Launchpad Translations on behalf of phablet-team

Launchpad automatic translations update.

988. By Alexandre Abreu

Window overlay for popups

989. By Alexandre Abreu

Fix AP global coords used for the clicks

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
Olivier Tilloy (osomon) wrote :

That looks good to me now, and the autopilot tests are all passing on my laptop.

review: Approve
990. By Alexandre Abreu

tweak to properly trigger elide

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'src/app/webcontainer/PopupWindowController.qml'
--- src/app/webcontainer/PopupWindowController.qml 1970-01-01 00:00:00 +0000
+++ src/app/webcontainer/PopupWindowController.qml 2015-04-30 16:33:24 +0000
@@ -0,0 +1,181 @@
1/*
2 * Copyright 2014 Canonical Ltd.
3 *
4 * This file is part of webbrowser-app.
5 *
6 * webbrowser-app is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; version 3.
9 *
10 * webbrowser-app is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19import QtQuick 2.0
20import com.canonical.Oxide 1.0 as Oxide
21import Ubuntu.Components 1.1
22import Ubuntu.Components.Popups 1.0
23
24Item {
25 id: controller
26
27 property var webappUrlPatterns
28 property var mainWebappView
29 property var views: []
30 property bool blockOpenExternalUrls: false
31
32 // Used to access runtime behavior during tests
33 signal openExternalUrlTriggered(string url)
34 signal newViewCreated(string url)
35 signal windowOverlayOpenAnimationDone()
36
37 readonly property int maxSimultaneousViews: 3
38
39 function openUrlExternally(url) {
40 if (!blockOpenExternalUrls) {
41 Qt.openUrlExternally(url)
42 }
43 openExternalUrlTriggered(url)
44 }
45
46 function onOverlayMoved(popup, diffY) {
47 if ((popup.y + diffY) > 0) {
48 popup.y += diffY
49 }
50 }
51 function handleNewViewAdded(view) {
52 if (views.length !== 0) {
53 var topView = views[views.length-1]
54 }
55 views.push(view)
56 }
57 function handleOpenInUrlBrowserForView(url, view) {
58 handleViewRemoved(view)
59 openExternalUrlTriggered(url)
60 openUrlExternally(url)
61 }
62 function createViewSlidingHandlerFor(newView, viewBelow) {
63 var parentHeight = viewBelow.parent.height
64 return function() {
65 if (viewBelow && newView) {
66 viewBelow.opacity =
67 newView.y / parentHeight
68 }
69 }
70 }
71 function topViewOnStack() {
72 if (views.length !== 0) {
73 return views[views.length-1]
74 }
75 return mainWebappView
76 }
77 function handleViewRemoved(view) {
78 if (views.length === 0) {
79 return
80 }
81
82 var topMostView = views[views.length-1]
83 if (topMostView !== view) {
84 return
85 }
86 views.pop()
87
88 var parentHeight = topMostView.parent.height
89 var nextView = topViewOnStack()
90 nextView.visible = true
91
92 function onViewSlidingOut() {
93 if (topMostView.y >= (topMostView.parent.height - 10)) {
94 topMostView.yChanged.disconnect(onViewSlidingOut)
95 topMostView.destroy()
96
97 updateViewVisibility(nextView, true)
98 }
99 }
100 topMostView.yChanged.connect(onViewSlidingOut)
101 topMostView.y = topMostView.parent.height
102 }
103 function createPopupView(parentView, request, isRequestFromMainWebappWebview, context) {
104 var view = popupWebOverlayFactory.createObject(
105 parentView,
106 { request: request,
107 webContext: context,
108 popupWindowController: controller });
109
110 var topMostView = topViewOnStack()
111
112 // handle opacity updates of the view below this one
113 // when the view is sliding
114 view.yChanged.connect(
115 createViewSlidingHandlerFor(view, topMostView))
116
117 function onViewSlidingIn() {
118 var parentHeight = view.parent.height
119
120 if (view.y <= 10) {
121 view.yChanged.disconnect(onViewSlidingIn)
122
123 updateViewVisibility(topMostView, false)
124 }
125 }
126 view.yChanged.connect(onViewSlidingIn)
127
128 view.y = 0
129 handleNewViewAdded(view)
130 newViewCreated(view.url)
131 }
132 function updateViewVisibility(view, visible) {
133 if (view) {
134 view.opacity = visible ? 1.0 : 0.0
135 }
136 }
137
138 Component {
139 id: popupWebOverlayFactory
140 PopupWindowOverlay {
141 id: overlay
142
143 height: parent.height
144 width: parent.width
145
146 y: overlay.parent.height
147
148 // Poor mans heuristic to know when an overlay has been
149 // loaded and is in full view. We cannot rely on the
150 // NumberAnimation running/started since they dont
151 // work properly when inside a Behavior
152 onYChanged: {
153 if (y === 0) {
154 windowOverlayOpenAnimationDone()
155 }
156 }
157
158 Behavior on y {
159 NumberAnimation {
160 duration: 500
161 easing.type: Easing.InOutQuad
162 }
163 }
164 }
165 }
166
167 function handleNewForegroundNavigationRequest(
168 url, request, isRequestFromMainWebappWebview) {
169
170 if (views.length >= maxSimultaneousViews) {
171 request.action = Oxide.NavigationRequest.ActionReject
172 // Default to open externally, maybe should present a dialog
173 openUrlExternally(url.toString())
174 console.log("Maximum number of popup overlay opened, opening: "
175 + url
176 + " in the browser")
177 return
178 }
179 request.action = Oxide.NavigationRequest.ActionAccept
180 }
181}
0182
=== added file 'src/app/webcontainer/PopupWindowOverlay.qml'
--- src/app/webcontainer/PopupWindowOverlay.qml 1970-01-01 00:00:00 +0000
+++ src/app/webcontainer/PopupWindowOverlay.qml 2015-04-30 16:33:24 +0000
@@ -0,0 +1,199 @@
1/*
2 * Copyright 2014 Canonical Ltd.
3 *
4 * This file is part of webbrowser-app.
5 *
6 * webbrowser-app is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; version 3.
9 *
10 * webbrowser-app is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19import QtQuick 2.0
20import QtQuick.Window 2.0
21import com.canonical.Oxide 1.4 as Oxide
22import Ubuntu.Components 1.1
23import ".."
24
25Item {
26 id: popup
27
28 property var popupWindowController
29 property var webContext
30 property alias request: popupWebview.request
31 property alias url: popupWebview.url
32
33 Rectangle {
34 color: "#F2F1F0"
35 anchors.fill: parent
36 }
37
38 Item {
39 id: menubar
40
41 height: units.gu(6)
42 width: parent.width
43
44 anchors {
45 top: parent.top
46 horizontalCenter: parent.horizontalCenter
47 }
48
49 ChromeButton {
50 id: closeButton
51 objectName: "overlayCloseButton"
52 anchors {
53 left: parent.left
54 verticalCenter: parent.verticalCenter
55 }
56
57 height: parent.height
58 width: height
59
60 iconName: "dropdown-menu"
61 iconSize: 0.6 * height
62
63 enabled: true
64 visible: true
65
66 MouseArea {
67 anchors.fill: parent
68 onClicked: {
69 if (popupWindowController) {
70 popupWindowController.handleViewRemoved(popup)
71 }
72 }
73 }
74 }
75
76 Item {
77 anchors {
78 top: parent.top
79 bottom: parent.bottom
80 left: closeButton.right
81 right: buttonOpenInBrowser.left
82 }
83
84 Label {
85 anchors {
86 rightMargin: units.gu(2)
87 verticalCenter: parent.verticalCenter
88 left: parent.left
89 right: parent.right
90 }
91
92 text: popupWebview.title ? popupWebview.title : popupWebview.url
93 elide: Text.ElideRight
94 }
95
96 MouseArea {
97 anchors.fill: parent
98
99 property int initMouseY: 0
100 property int prevMouseY: 0
101
102 onPressed: {
103 initMouseY = mouse.y
104 prevMouseY = initMouseY
105 }
106 onReleased: {
107 if ((prevMouseY - initMouseY) > (popup.height / 8) ||
108 popup.y > popup.height/2) {
109 if (popupWindowController) {
110 popupWindowController.handleViewRemoved(popup)
111 return
112 }
113 }
114 popup.y = 0
115 }
116 onMouseYChanged: {
117 if (popupWindowController) {
118 var diff = mouseY - initMouseY
119 prevMouseY = mouseY
120 popupWindowController.onOverlayMoved(popup, diff)
121 }
122 }
123 }
124 }
125
126 ChromeButton {
127 id: buttonOpenInBrowser
128 objectName: "overlayButtonOpenInBrowser"
129 anchors {
130 right: parent.right
131 verticalCenter: parent.verticalCenter
132 rightMargin: units.gu(1)
133 }
134
135 height: parent.height
136 width: height
137
138 iconName: "external-link"
139 iconSize: 0.6 * height
140
141 enabled: true
142 visible: true
143
144 MouseArea {
145 anchors.fill: parent
146 onClicked: {
147 if (popupWindowController) {
148 popupWindowController.handleOpenInUrlBrowserForView(
149 popupWebview.url, popup)
150 }
151 }
152 }
153 }
154 }
155
156 WebViewImpl {
157 id: popupWebview
158
159 objectName: "overlayWebview"
160
161 context: webContext
162
163 anchors {
164 bottom: parent.bottom
165 left: parent.left
166 right: parent.right
167 top: menubar.bottom
168 }
169
170 onNewViewRequested: {
171 if (popupWindowController) {
172 popupWindowController.createPopupView(
173 popup.parent, request, false, context)
174 }
175 }
176
177 function isNewForegroundWebViewDisposition(disposition) {
178 return disposition === Oxide.NavigationRequest.DispositionNewPopup ||
179 disposition === Oxide.NavigationRequest.DispositionNewForegroundTab;
180 }
181
182 onNavigationRequested: {
183 var url = request.url.toString()
184 if (isNewForegroundWebViewDisposition(request.disposition)) {
185 popupWindowController.handleNewForegroundNavigationRequest(
186 url, request, false)
187 return
188 }
189 request.action = Oxide.NavigationRequest.ActionAccept
190 }
191
192 onCloseRequested: {
193 if (popupWindowController) {
194 popupWindowController.handleViewRemoved(popup)
195 }
196 }
197 }
198
199}
0200
=== modified file 'src/app/webcontainer/WebViewImplOxide.qml'
--- src/app/webcontainer/WebViewImplOxide.qml 2015-02-19 11:50:21 +0000
+++ src/app/webcontainer/WebViewImplOxide.qml 2015-04-30 16:33:24 +0000
@@ -35,9 +35,13 @@
35 property var webappUrlPatterns: null35 property var webappUrlPatterns: null
36 property string popupRedirectionUrlPrefixPattern: ""36 property string popupRedirectionUrlPrefixPattern: ""
37 property url dataPath37 property url dataPath
38 property var popupController
39 property var overlayViewsParent: webview.parent
3840
39 // Mostly used for testing & avoid external urls to41 // Mostly used for testing & avoid external urls to
40 // "leak" in the default browser42 // "leak" in the default browser. External URLs corresponds
43 // to URLs that are not included in the set defined by the url patterns
44 // (if any) or navigations resulting in new windows being created.
41 property bool blockOpenExternalUrls: false45 property bool blockOpenExternalUrls: false
4246
43 // Those signals are used for testing purposes to externally47 // Those signals are used for testing purposes to externally
@@ -55,6 +59,10 @@
5559
56 preferences.allowFileAccessFromFileUrls: runningLocalApplication60 preferences.allowFileAccessFromFileUrls: runningLocalApplication
57 preferences.allowUniversalAccessFromFileUrls: runningLocalApplication61 preferences.allowUniversalAccessFromFileUrls: runningLocalApplication
62 preferences.localStorageEnabled: true
63 preferences.appCacheEnabled: true
64
65 onNewViewRequested: popupController.createPopupView(overlayViewsParent, request, true, context)
5866
59 contextualActions: ActionList {67 contextualActions: ActionList {
60 Actions.CopyLink {68 Actions.CopyLink {
@@ -119,77 +127,17 @@
119 }127 }
120128
121 function navigationRequestedDelegate(request) {129 function navigationRequestedDelegate(request) {
122 var newForegroundPageRequest = isNewForegroundWebViewDisposition(request.disposition)
123 var url = request.url.toString()130 var url = request.url.toString()
124
125 console.log("navigationRequestedDelegate - newForegroundPageRequest: "
126 + newForegroundPageRequest
127 + ', url: ' + url)
128
129 if (runningLocalApplication && url.indexOf("file://") !== 0) {131 if (runningLocalApplication && url.indexOf("file://") !== 0) {
130 request.action = Oxide.NavigationRequest.ActionReject132 request.action = Oxide.NavigationRequest.ActionReject
131 openUrlExternally(url)133 openUrlExternally(url)
132 return134 return
133 }135 }
134136
135 // Covers some edge cases corresponding to the default window.open() behavior.137 request.action = Oxide.NavigationRequest.ActionReject
136 // When it is being called, the targetted URL will not load right away but138 if (isNewForegroundWebViewDisposition(request.disposition)) {
137 // will first round trip to an "about:blank".139 request.action = Oxide.NavigationRequest.ActionAccept
138 // See https://developer.mozilla.org/en-US/docs/Web/API/Window.open140 popupController.handleNewForegroundNavigationRequest(url, request, true)
139 if (newForegroundPageRequest) {
140 if (url == 'about:blank') {
141 console.log('Accepting a new window request to navigate to "about:blank"')
142 request.action = Oxide.NavigationRequest.ActionAccept
143 return
144 }
145
146 var isRedirectionUrl = false;
147 var targetUrl = url;
148 if (popupRedirectionUrlPrefixPattern) {
149 // NOTE: very nasty workaround to be backward compatibility, will be deleted as soon
150 // as the FB webapp is updated.
151 if (popupRedirectionUrlPrefixPattern.indexOf('(') === -1) {
152 isRedirectionUrl = (url.indexOf(popupRedirectionUrlPrefixPattern) === 0);
153 targetUrl = isRedirectionUrl ?
154 url.slice(popupRedirectionUrlPrefixPattern.length) : url;
155
156 // Quick fix for http://pad.lv/1358622 (trim trailing parameters).
157 var extraParams = targetUrl.indexOf("&");
158 if (extraParams !== -1) {
159 targetUrl = targetUrl.slice(0, extraParams);
160 }
161 } else {
162 var redirectionPatternMatch = url.match(popupRedirectionUrlPrefixPattern);
163 isRedirectionUrl =
164 popupRedirectionUrlPrefixPattern
165 && redirectionPatternMatch
166 && redirectionPatternMatch.length >= 2;
167
168 // Assume that the first group is the matching one
169 targetUrl = isRedirectionUrl ?
170 redirectionPatternMatch[1] : url;
171 }
172 }
173
174 if (isRedirectionUrl) {
175 console.debug("Got a redirection URL with target URL: " + targetUrl)
176 targetUrl = decodeURIComponent(targetUrl)
177 gotRedirectionUrl(targetUrl)
178 }
179
180 if (webview.shouldAllowNavigationTo(targetUrl)) {
181 console.debug('Redirecting popup browsing ' + targetUrl + ' in the current container window.')
182 request.action = Oxide.NavigationRequest.ActionReject
183 webappContainerHelper.browseToUrlRequested(webview, targetUrl)
184 return
185 }
186
187 if (shouldOpenPopupsInDefaultBrowser()) {
188 console.debug('Opening popup window ' + targetUrl + ' in the browser window.')
189 request.action = Oxide.NavigationRequest.ActionReject
190 openUrlExternally(targetUrl)
191 return;
192 }
193 return141 return
194 }142 }
195143
@@ -201,7 +149,6 @@
201 return149 return
202 }150 }
203151
204 request.action = Oxide.NavigationRequest.ActionReject
205 if (webview.shouldAllowNavigationTo(url))152 if (webview.shouldAllowNavigationTo(url))
206 request.action = Oxide.NavigationRequest.ActionAccept153 request.action = Oxide.NavigationRequest.ActionAccept
207154
@@ -228,70 +175,6 @@
228 }175 }
229 }176 }
230177
231 function createPopupWindow(request) {
232 popupWebViewFactory.createObject(webview, { request: request, width: 500, height: 800 });
233 }
234
235 Component {
236 id: popupWebViewFactory
237 Window {
238 id: popup
239 property alias request: popupBrowser.request
240 WebView {
241 id: popupBrowser
242 anchors.fill: parent
243
244 function navigationRequestedDelegate(request) {
245 var url = request.url.toString()
246
247 // If we are to browse in the popup to a place where we are not allows
248 if ( ! isNewForegroundWebViewDisposition(request.disposition) &&
249 ! webview.shouldAllowNavigationTo(url)) {
250 request.action = Oxide.NavigationRequest.ActionReject
251 openUrlExternally(url);
252 popup.close()
253 return;
254 }
255
256 // Fallback to regulat checks (there is a bit of overlap)
257 webview.navigationRequestedDelegate(request)
258 }
259
260 onNewViewRequested: webview.createPopupWindow(request)
261
262 // Oxide (and Chromium) does not inform of non user
263 // driven navigations (or more specifically redirects that
264 // would be part of an popup/webview load (after its been
265 // granted). Quite a few sites (e.g. Youtube),
266 // create popups when clicking on links (or following a window.open())
267 // with proper youtube.com address but w/ redirection
268 // params, e.g.:
269 // http://www.youtube.com/redirect?q=http%3A%2F%2Fgodzillamovie.com%2F&redir_token=b8WPI1pq9FHXeHm2bN3KVLAJSfp8MTM5NzI2NDg3NEAxMzk3MTc4NDc0
270 // In this instance the popup & navigation is granted, but then
271 // a redirect happens inside the popup to the real target url (here http://godzillamovie.com)
272 // which is not trapped by a navigation requested and therefore not filtered.
273 // The only way to do it atm is to listen to url changed in popups & also
274 // filter there.
275 onUrlChanged: {
276 var _url = url.toString();
277 if (_url.trim().length === 0)
278 return;
279
280 if (_url != 'about:blank' && ! webview.shouldAllowNavigationTo(_url)) {
281 openUrlExternally(_url);
282 popup.close()
283 }
284 }
285 }
286 Component.onCompleted: popup.show()
287 }
288 }
289
290 onNewViewRequested: createPopupWindow(request)
291
292 preferences.localStorageEnabled: true
293 preferences.appCacheEnabled: true
294
295 // Small shim needed when running as a webapp to wire-up connections178 // Small shim needed when running as a webapp to wire-up connections
296 // with the webview (message received, etc…).179 // with the webview (message received, etc…).
297 // This is being called (and expected) internally by the webapps180 // This is being called (and expected) internally by the webapps
298181
=== modified file 'src/app/webcontainer/WebappContainerWebview.qml'
--- src/app/webcontainer/WebappContainerWebview.qml 2015-03-25 18:42:20 +0000
+++ src/app/webcontainer/WebappContainerWebview.qml 2015-04-30 16:33:24 +0000
@@ -40,6 +40,14 @@
40 property bool blockOpenExternalUrls: false40 property bool blockOpenExternalUrls: false
41 property bool runningLocalApplication: false41 property bool runningLocalApplication: false
4242
43 PopupWindowController {
44 id: popupController
45 objectName: "popupController"
46 webappUrlPatterns: containerWebview.webappUrlPatterns
47 mainWebappView: containerWebview.currentWebview
48 blockOpenExternalUrls: containerWebview.blockOpenExternalUrls
49 }
50
43 Loader {51 Loader {
44 id: webappContainerWebViewLoader52 id: webappContainerWebViewLoader
45 objectName: "containerWebviewLoader"53 objectName: "containerWebviewLoader"
@@ -68,7 +76,9 @@
68 , developerExtrasEnabled: containerWebview.developerExtrasEnabled76 , developerExtrasEnabled: containerWebview.developerExtrasEnabled
69 , popupRedirectionUrlPrefixPattern: containerWebview.popupRedirectionUrlPrefixPattern77 , popupRedirectionUrlPrefixPattern: containerWebview.popupRedirectionUrlPrefixPattern
70 , blockOpenExternalUrls: containerWebview.blockOpenExternalUrls78 , blockOpenExternalUrls: containerWebview.blockOpenExternalUrls
71 , runningLocalApplication: containerWebview.runningLocalApplication})79 , runningLocalApplication: containerWebview.runningLocalApplication
80 , popupController: popupController
81 , overlayViewsParent: containerWebview.parent})
72 }82 }
73}83}
7484
7585
=== modified file 'tests/autopilot/webapp_container/tests/__init__.py'
--- tests/autopilot/webapp_container/tests/__init__.py 2015-03-20 18:22:41 +0000
+++ tests/autopilot/webapp_container/tests/__init__.py 2015-04-30 16:33:24 +0000
@@ -83,6 +83,12 @@
83 def get_webview(self):83 def get_webview(self):
84 return self.app.select_single(objectName="webview")84 return self.app.select_single(objectName="webview")
8585
86 def get_popup_overlay_views(self):
87 return self.app.select_many("PopupWindowOverlay")
88
89 def get_popup_controller(self):
90 return self.app.select_single(objectName="popupController")
91
86 def get_oxide_webview(self):92 def get_oxide_webview(self):
87 container = self.get_webview().select_single(93 container = self.get_webview().select_single(
88 objectName='containerWebviewLoader')94 objectName='containerWebviewLoader')
8995
=== modified file 'tests/autopilot/webapp_container/tests/fake_servers.py'
--- tests/autopilot/webapp_container/tests/fake_servers.py 2014-11-28 17:42:50 +0000
+++ tests/autopilot/webapp_container/tests/fake_servers.py 2015-04-30 16:33:24 +0000
@@ -66,22 +66,22 @@
66</html>66</html>
67 """67 """
6868
69 def targetted_click_content(self, differentDomain=True):69 def targetted_click_content(self):
70 url = 'http://www.test.com/'
71 if differentDomain:
72 url = 'http://www.ubuntu.com/'
73 return """70 return """
74<html>71<html>
75<head>72<head>
76<title>Some content</title>73<title>Some content</title>
77</head>74</head>
78<body>75<body>
79<div><a href='{}' target='_blank'>76<div>
80<div style="height: 100%; width: 100%"></div>77<a href="/open-close-content" target="_blank">
81</a></div>78<div style="height: 100%; width: 100%">
79</div>
80</a>
81</div>
82</body>82</body>
83</html>83</html>
84 """.format(url)84 """
8585
86 def display_ua_content(self):86 def display_ua_content(self):
87 return """87 return """
@@ -99,6 +99,27 @@
99</html>99</html>
100 """.format("'"+self.headers['user-agent']+"'")100 """.format("'"+self.headers['user-agent']+"'")
101101
102 def open_close_content(self):
103 return """
104<html>
105<head>
106<title>open-close</title>
107<script>
108</script>
109</head>
110<body>
111 <a href="/open-close-content" target="_blank">
112 <div style="height: 50%; width: 100%; background-color: red">
113 target blank link
114 </div>
115 </a>
116 <div id="lorem" style="height: 50%; width: 100%; background-color: blue">
117 Lorem ipsum dolor sit amet
118 </div>
119</body>
120</html>
121 """
122
102 def do_GET(self):123 def do_GET(self):
103 if self.path == '/':124 if self.path == '/':
104 self.send_response(200)125 self.send_response(200)
@@ -114,13 +135,13 @@
114 self.serve_content(self.external_click_content())135 self.serve_content(self.external_click_content())
115 elif self.path == '/with-targetted-link':136 elif self.path == '/with-targetted-link':
116 self.send_response(200)137 self.send_response(200)
117 self.serve_content(self.targetted_click_content(False))
118 elif self.path == '/with-different-targetted-link':
119 self.send_response(200)
120 self.serve_content(self.targetted_click_content())138 self.serve_content(self.targetted_click_content())
121 elif self.path == '/show-user-agent':139 elif self.path == '/show-user-agent':
122 self.send_response(200)140 self.send_response(200)
123 self.serve_content(self.display_ua_content())141 self.serve_content(self.display_ua_content())
142 elif self.path == '/open-close-content':
143 self.send_response(200)
144 self.serve_content(self.open_close_content())
124 else:145 else:
125 self.send_error(404)146 self.send_error(404)
126147
127148
=== added file 'tests/autopilot/webapp_container/tests/test_popup_webview_overlay.py'
--- tests/autopilot/webapp_container/tests/test_popup_webview_overlay.py 1970-01-01 00:00:00 +0000
+++ tests/autopilot/webapp_container/tests/test_popup_webview_overlay.py 2015-04-30 16:33:24 +0000
@@ -0,0 +1,170 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2# Copyright 2015 Canonical
3#
4# This program is free software: you can redistribute it and/or modify it
5# under the terms of the GNU General Public License version 3, as published
6# by the Free Software Foundation.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16from testtools.matchers import Equals, Contains, GreaterThan
17from autopilot.matchers import Eventually
18
19from webapp_container.tests import WebappContainerTestCaseWithLocalContentBase
20
21
22class WebappContainerPopupWebViewOverlayTestCase(
23 WebappContainerTestCaseWithLocalContentBase):
24
25 def click_href_target_blank(self):
26 webview = self.get_oxide_webview()
27 self.assertThat(webview.url, Contains('/open-close-content'))
28 gr = webview.globalRect
29 self.pointing_device.move(
30 gr.x + gr.width/4,
31 gr.y + gr.height/4)
32 self.pointing_device.click()
33
34 def click_window_open(self):
35 webview = self.get_oxide_webview()
36 self.assertThat(webview.url.endswith('/open-close-content'))
37 gr = webview.globalRect
38 self.pointing_device.move(
39 gr.x + webview.width*3/4,
40 gr.y + webview.height*3/4)
41 self.pointing_device.click()
42
43 def test_open_close_back_to_mainview(self):
44 args = []
45 self.launch_webcontainer_app_with_local_http_server(
46 args,
47 '/open-close-content')
48 self.get_webcontainer_window().visible.wait_for(True)
49
50 popup_controller = self.get_popup_controller()
51 new_view_watcher = popup_controller.watch_signal(
52 'newViewCreated(QString)')
53 animation_watcher = popup_controller.watch_signal(
54 'windowOverlayOpenAnimationDone()')
55 animation_signal_emission = animation_watcher.num_emissions
56
57 views = self.get_popup_overlay_views()
58 self.assertThat(len(views), Equals(0))
59
60 self.click_href_target_blank()
61
62 self.assertThat(
63 lambda: new_view_watcher.was_emitted,
64 Eventually(Equals(True)))
65 self.assertThat(
66 lambda: len(self.get_popup_overlay_views()),
67 Eventually(Equals(1)))
68 views = self.get_popup_overlay_views()
69 overlay = views[0]
70 self.assertThat(
71 overlay.select_single(objectName="overlayWebview").url,
72 Contains('/open-close-content'))
73
74 self.assertThat(
75 lambda: animation_watcher.num_emissions,
76 Eventually(GreaterThan(animation_signal_emission)))
77 animation_signal_emission = animation_watcher.num_emissions
78
79 closeButton = overlay.select_single(
80 objectName='overlayCloseButton')
81
82 self.pointing_device.click_object(closeButton)
83
84 self.assertThat(
85 lambda: len(self.get_popup_overlay_views()),
86 Eventually(Equals(0)))
87
88 def test_open_overlay_in_main_browser(self):
89 args = []
90 self.launch_webcontainer_app_with_local_http_server(
91 args,
92 '/open-close-content',
93 {'WEBAPP_CONTAINER_BLOCK_OPEN_URL_EXTERNALLY': '1'})
94 self.get_webcontainer_window().visible.wait_for(True)
95
96 popup_controller = self.get_popup_controller()
97 webview = self.get_oxide_webview()
98 self.assertThat(
99 lambda: webview.visible,
100 Eventually(Equals(True)))
101 external_open_watcher = popup_controller.watch_signal(
102 'openExternalUrlTriggered(QString)')
103
104 animation_watcher = popup_controller.watch_signal(
105 'windowOverlayOpenAnimationDone()')
106 animation_signal_emission = animation_watcher.num_emissions
107
108 self.click_href_target_blank()
109
110 self.assertThat(
111 lambda: len(self.get_popup_overlay_views()),
112 Eventually(Equals(1)))
113
114 views = self.get_popup_overlay_views()
115 overlay = views[0]
116
117 self.assertThat(
118 lambda: animation_watcher.num_emissions,
119 Eventually(GreaterThan(animation_signal_emission)))
120 animation_signal_emission = animation_watcher.num_emissions
121
122 openInBrowserButton = overlay.select_single(
123 objectName='overlayButtonOpenInBrowser')
124
125 self.pointing_device.click_object(openInBrowserButton)
126
127 self.assertThat(
128 lambda: len(self.get_popup_overlay_views()),
129 Eventually(Equals(0)))
130 self.assertThat(
131 lambda: external_open_watcher.was_emitted,
132 Eventually(Equals(True)))
133 self.assertThat(
134 lambda: webview.visible,
135 Eventually(Equals(True)))
136
137 def test_max_overlay_count_reached(self):
138 args = []
139 self.launch_webcontainer_app_with_local_http_server(
140 args,
141 '/open-close-content',
142 {'WEBAPP_CONTAINER_BLOCK_OPEN_URL_EXTERNALLY': '1'})
143 self.get_webcontainer_window().visible.wait_for(True)
144
145 popup_controller = self.get_popup_controller()
146 webview = self.get_oxide_webview()
147 self.assertThat(
148 lambda: webview.visible,
149 Eventually(Equals(True)))
150
151 animation_watcher = popup_controller.watch_signal(
152 'windowOverlayOpenAnimationDone()')
153 animation_signal_emission = animation_watcher.num_emissions
154
155 OVERLAY_MAX_COUNT = 3
156 for i in range(0, OVERLAY_MAX_COUNT):
157 self.click_href_target_blank()
158 self.assertThat(
159 lambda: animation_watcher.num_emissions,
160 Eventually(GreaterThan(animation_signal_emission)))
161 animation_signal_emission = animation_watcher.num_emissions
162
163 external_open_watcher = popup_controller.watch_signal(
164 'openExternalUrlTriggered(QString)')
165
166 self.click_href_target_blank()
167
168 self.assertThat(
169 lambda: external_open_watcher.was_emitted,
170 Eventually(Equals(True)))
0171
=== removed file 'tests/autopilot/webapp_container/tests/test_redirection_pattern.py'
--- tests/autopilot/webapp_container/tests/test_redirection_pattern.py 2014-10-07 16:16:13 +0000
+++ tests/autopilot/webapp_container/tests/test_redirection_pattern.py 1970-01-01 00:00:00 +0000
@@ -1,59 +0,0 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2# Copyright 2014 Canonical
3#
4# This program is free software: you can redistribute it and/or modify it
5# under the terms of the GNU General Public License version 3, as published
6# by the Free Software Foundation.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16from testtools.matchers import Equals
17from autopilot.matchers import Eventually
18
19from webapp_container.tests import WebappContainerTestCaseWithLocalContentBase
20
21
22class WebappContainerRedirectionPatternTestCase(
23 WebappContainerTestCaseWithLocalContentBase):
24
25 def test_browse_to_redirection_pattern_url(self):
26 REDIRECTION_HOSTNAME = self.get_base_url_hostname()
27 args = ["--popup-redirection-url-prefix={}{}{}".format(
28 'http://', REDIRECTION_HOSTNAME.replace('.', '\.'),
29 '/redirect\\?url=([^&]*).*')]
30 self.launch_webcontainer_app_with_local_http_server(
31 args,
32 '/get-redirect',
33 {'WEBAPP_CONTAINER_BLOCK_OPEN_URL_EXTERNALLY': '1',
34 'WEBAPP_CONTAINER_DO_NOT_FILTER_PATTERN_URL': '1'})
35 self.get_webcontainer_window().visible.wait_for(True)
36
37 webview = self.get_oxide_webview()
38 external_open_watcher = webview.watch_signal(
39 'openExternalUrlTriggered(QString)')
40 got_redirection_url_watcher = webview.watch_signal(
41 'gotRedirectionUrl(QString)')
42
43 self.assertThat(external_open_watcher.was_emitted, Equals(False))
44 self.assertThat(got_redirection_url_watcher.was_emitted, Equals(False))
45 self.browse_to(
46 "http://{}/get-redirect".format(REDIRECTION_HOSTNAME))
47
48 self.pointing_device.click_object(webview)
49
50 self.assertThat(
51 lambda: got_redirection_url_watcher.was_emitted,
52 Eventually(Equals(True)))
53 self.assertThat(
54 webview.get_signal_emissions(
55 'gotRedirectionUrl(QString)')[0][0],
56 Equals('myredirect'))
57 self.assertThat(
58 lambda: external_open_watcher.was_emitted,
59 Eventually(Equals(True)))
600
=== modified file 'tests/autopilot/webapp_container/tests/test_url_patterns.py'
--- tests/autopilot/webapp_container/tests/test_url_patterns.py 2015-02-18 11:05:35 +0000
+++ tests/autopilot/webapp_container/tests/test_url_patterns.py 2015-04-30 16:33:24 +0000
@@ -43,55 +43,3 @@
43 self.assertThat(43 self.assertThat(
44 lambda: external_open_watcher.was_emitted,44 lambda: external_open_watcher.was_emitted,
45 Eventually(Equals(True)))45 Eventually(Equals(True)))
46
47 def test_pattern_with_allowed_targetted_url(self):
48 args = ["--webappUrlPatterns=http://www.test.com/*"]
49 rule = 'MAP *.test.com:80 ' + self.get_base_url_hostname()
50 self.launch_webcontainer_app_with_local_http_server(
51 args,
52 '',
53 {'WEBAPP_CONTAINER_BLOCK_OPEN_URL_EXTERNALLY': '1',
54 'UBUNTU_WEBVIEW_HOST_MAPPING_RULES': rule},
55 "http://www.test.com/with-targetted-link")
56
57 self.get_webcontainer_window().visible.wait_for(True)
58
59 webview = self.get_oxide_webview()
60 external_open_watcher = webview.watch_signal(
61 'openExternalUrlTriggered(QString)')
62
63 self.pointing_device.click_object(webview)
64
65 self.assertThat(
66 webview.url,
67 Eventually(Equals("http://www.test.com/")))
68
69 self.assertThat(
70 external_open_watcher.was_emitted,
71 Equals(False))
72
73 def test_pattern_with_different_targetted_url(self):
74 args = ["--webappUrlPatterns=http://www.test.com/*"]
75 rule = 'MAP *.test.com:80 ' + self.get_base_url_hostname()
76 self.launch_webcontainer_app_with_local_http_server(
77 args,
78 '',
79 {'WEBAPP_CONTAINER_BLOCK_OPEN_URL_EXTERNALLY': '1',
80 'UBUNTU_WEBVIEW_HOST_MAPPING_RULES': rule},
81 "http://www.test.com/with-different-targetted-link")
82
83 self.get_webcontainer_window().visible.wait_for(True)
84
85 webview = self.get_oxide_webview()
86 external_open_watcher = webview.watch_signal(
87 'openExternalUrlTriggered(QString)')
88
89 self.pointing_device.click_object(webview)
90
91 self.assertThat(
92 webview.url,
93 Equals("http://www.test.com/with-different-targetted-link"))
94
95 self.assertThat(
96 lambda: external_open_watcher.was_emitted,
97 Eventually(Equals(True)))

Subscribers

People subscribed via source and target branches

to status/vote changes: