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
1=== added file 'src/app/webcontainer/PopupWindowController.qml'
2--- src/app/webcontainer/PopupWindowController.qml 1970-01-01 00:00:00 +0000
3+++ src/app/webcontainer/PopupWindowController.qml 2015-04-30 16:33:24 +0000
4@@ -0,0 +1,181 @@
5+/*
6+ * Copyright 2014 Canonical Ltd.
7+ *
8+ * This file is part of webbrowser-app.
9+ *
10+ * webbrowser-app is free software; you can redistribute it and/or modify
11+ * it under the terms of the GNU General Public License as published by
12+ * the Free Software Foundation; version 3.
13+ *
14+ * webbrowser-app is distributed in the hope that it will be useful,
15+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
16+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+ * GNU General Public License for more details.
18+ *
19+ * You should have received a copy of the GNU General Public License
20+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
21+ */
22+
23+import QtQuick 2.0
24+import com.canonical.Oxide 1.0 as Oxide
25+import Ubuntu.Components 1.1
26+import Ubuntu.Components.Popups 1.0
27+
28+Item {
29+ id: controller
30+
31+ property var webappUrlPatterns
32+ property var mainWebappView
33+ property var views: []
34+ property bool blockOpenExternalUrls: false
35+
36+ // Used to access runtime behavior during tests
37+ signal openExternalUrlTriggered(string url)
38+ signal newViewCreated(string url)
39+ signal windowOverlayOpenAnimationDone()
40+
41+ readonly property int maxSimultaneousViews: 3
42+
43+ function openUrlExternally(url) {
44+ if (!blockOpenExternalUrls) {
45+ Qt.openUrlExternally(url)
46+ }
47+ openExternalUrlTriggered(url)
48+ }
49+
50+ function onOverlayMoved(popup, diffY) {
51+ if ((popup.y + diffY) > 0) {
52+ popup.y += diffY
53+ }
54+ }
55+ function handleNewViewAdded(view) {
56+ if (views.length !== 0) {
57+ var topView = views[views.length-1]
58+ }
59+ views.push(view)
60+ }
61+ function handleOpenInUrlBrowserForView(url, view) {
62+ handleViewRemoved(view)
63+ openExternalUrlTriggered(url)
64+ openUrlExternally(url)
65+ }
66+ function createViewSlidingHandlerFor(newView, viewBelow) {
67+ var parentHeight = viewBelow.parent.height
68+ return function() {
69+ if (viewBelow && newView) {
70+ viewBelow.opacity =
71+ newView.y / parentHeight
72+ }
73+ }
74+ }
75+ function topViewOnStack() {
76+ if (views.length !== 0) {
77+ return views[views.length-1]
78+ }
79+ return mainWebappView
80+ }
81+ function handleViewRemoved(view) {
82+ if (views.length === 0) {
83+ return
84+ }
85+
86+ var topMostView = views[views.length-1]
87+ if (topMostView !== view) {
88+ return
89+ }
90+ views.pop()
91+
92+ var parentHeight = topMostView.parent.height
93+ var nextView = topViewOnStack()
94+ nextView.visible = true
95+
96+ function onViewSlidingOut() {
97+ if (topMostView.y >= (topMostView.parent.height - 10)) {
98+ topMostView.yChanged.disconnect(onViewSlidingOut)
99+ topMostView.destroy()
100+
101+ updateViewVisibility(nextView, true)
102+ }
103+ }
104+ topMostView.yChanged.connect(onViewSlidingOut)
105+ topMostView.y = topMostView.parent.height
106+ }
107+ function createPopupView(parentView, request, isRequestFromMainWebappWebview, context) {
108+ var view = popupWebOverlayFactory.createObject(
109+ parentView,
110+ { request: request,
111+ webContext: context,
112+ popupWindowController: controller });
113+
114+ var topMostView = topViewOnStack()
115+
116+ // handle opacity updates of the view below this one
117+ // when the view is sliding
118+ view.yChanged.connect(
119+ createViewSlidingHandlerFor(view, topMostView))
120+
121+ function onViewSlidingIn() {
122+ var parentHeight = view.parent.height
123+
124+ if (view.y <= 10) {
125+ view.yChanged.disconnect(onViewSlidingIn)
126+
127+ updateViewVisibility(topMostView, false)
128+ }
129+ }
130+ view.yChanged.connect(onViewSlidingIn)
131+
132+ view.y = 0
133+ handleNewViewAdded(view)
134+ newViewCreated(view.url)
135+ }
136+ function updateViewVisibility(view, visible) {
137+ if (view) {
138+ view.opacity = visible ? 1.0 : 0.0
139+ }
140+ }
141+
142+ Component {
143+ id: popupWebOverlayFactory
144+ PopupWindowOverlay {
145+ id: overlay
146+
147+ height: parent.height
148+ width: parent.width
149+
150+ y: overlay.parent.height
151+
152+ // Poor mans heuristic to know when an overlay has been
153+ // loaded and is in full view. We cannot rely on the
154+ // NumberAnimation running/started since they dont
155+ // work properly when inside a Behavior
156+ onYChanged: {
157+ if (y === 0) {
158+ windowOverlayOpenAnimationDone()
159+ }
160+ }
161+
162+ Behavior on y {
163+ NumberAnimation {
164+ duration: 500
165+ easing.type: Easing.InOutQuad
166+ }
167+ }
168+ }
169+ }
170+
171+ function handleNewForegroundNavigationRequest(
172+ url, request, isRequestFromMainWebappWebview) {
173+
174+ if (views.length >= maxSimultaneousViews) {
175+ request.action = Oxide.NavigationRequest.ActionReject
176+ // Default to open externally, maybe should present a dialog
177+ openUrlExternally(url.toString())
178+ console.log("Maximum number of popup overlay opened, opening: "
179+ + url
180+ + " in the browser")
181+ return
182+ }
183+ request.action = Oxide.NavigationRequest.ActionAccept
184+ }
185+}
186
187=== added file 'src/app/webcontainer/PopupWindowOverlay.qml'
188--- src/app/webcontainer/PopupWindowOverlay.qml 1970-01-01 00:00:00 +0000
189+++ src/app/webcontainer/PopupWindowOverlay.qml 2015-04-30 16:33:24 +0000
190@@ -0,0 +1,199 @@
191+/*
192+ * Copyright 2014 Canonical Ltd.
193+ *
194+ * This file is part of webbrowser-app.
195+ *
196+ * webbrowser-app is free software; you can redistribute it and/or modify
197+ * it under the terms of the GNU General Public License as published by
198+ * the Free Software Foundation; version 3.
199+ *
200+ * webbrowser-app is distributed in the hope that it will be useful,
201+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
202+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
203+ * GNU General Public License for more details.
204+ *
205+ * You should have received a copy of the GNU General Public License
206+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
207+ */
208+
209+import QtQuick 2.0
210+import QtQuick.Window 2.0
211+import com.canonical.Oxide 1.4 as Oxide
212+import Ubuntu.Components 1.1
213+import ".."
214+
215+Item {
216+ id: popup
217+
218+ property var popupWindowController
219+ property var webContext
220+ property alias request: popupWebview.request
221+ property alias url: popupWebview.url
222+
223+ Rectangle {
224+ color: "#F2F1F0"
225+ anchors.fill: parent
226+ }
227+
228+ Item {
229+ id: menubar
230+
231+ height: units.gu(6)
232+ width: parent.width
233+
234+ anchors {
235+ top: parent.top
236+ horizontalCenter: parent.horizontalCenter
237+ }
238+
239+ ChromeButton {
240+ id: closeButton
241+ objectName: "overlayCloseButton"
242+ anchors {
243+ left: parent.left
244+ verticalCenter: parent.verticalCenter
245+ }
246+
247+ height: parent.height
248+ width: height
249+
250+ iconName: "dropdown-menu"
251+ iconSize: 0.6 * height
252+
253+ enabled: true
254+ visible: true
255+
256+ MouseArea {
257+ anchors.fill: parent
258+ onClicked: {
259+ if (popupWindowController) {
260+ popupWindowController.handleViewRemoved(popup)
261+ }
262+ }
263+ }
264+ }
265+
266+ Item {
267+ anchors {
268+ top: parent.top
269+ bottom: parent.bottom
270+ left: closeButton.right
271+ right: buttonOpenInBrowser.left
272+ }
273+
274+ Label {
275+ anchors {
276+ rightMargin: units.gu(2)
277+ verticalCenter: parent.verticalCenter
278+ left: parent.left
279+ right: parent.right
280+ }
281+
282+ text: popupWebview.title ? popupWebview.title : popupWebview.url
283+ elide: Text.ElideRight
284+ }
285+
286+ MouseArea {
287+ anchors.fill: parent
288+
289+ property int initMouseY: 0
290+ property int prevMouseY: 0
291+
292+ onPressed: {
293+ initMouseY = mouse.y
294+ prevMouseY = initMouseY
295+ }
296+ onReleased: {
297+ if ((prevMouseY - initMouseY) > (popup.height / 8) ||
298+ popup.y > popup.height/2) {
299+ if (popupWindowController) {
300+ popupWindowController.handleViewRemoved(popup)
301+ return
302+ }
303+ }
304+ popup.y = 0
305+ }
306+ onMouseYChanged: {
307+ if (popupWindowController) {
308+ var diff = mouseY - initMouseY
309+ prevMouseY = mouseY
310+ popupWindowController.onOverlayMoved(popup, diff)
311+ }
312+ }
313+ }
314+ }
315+
316+ ChromeButton {
317+ id: buttonOpenInBrowser
318+ objectName: "overlayButtonOpenInBrowser"
319+ anchors {
320+ right: parent.right
321+ verticalCenter: parent.verticalCenter
322+ rightMargin: units.gu(1)
323+ }
324+
325+ height: parent.height
326+ width: height
327+
328+ iconName: "external-link"
329+ iconSize: 0.6 * height
330+
331+ enabled: true
332+ visible: true
333+
334+ MouseArea {
335+ anchors.fill: parent
336+ onClicked: {
337+ if (popupWindowController) {
338+ popupWindowController.handleOpenInUrlBrowserForView(
339+ popupWebview.url, popup)
340+ }
341+ }
342+ }
343+ }
344+ }
345+
346+ WebViewImpl {
347+ id: popupWebview
348+
349+ objectName: "overlayWebview"
350+
351+ context: webContext
352+
353+ anchors {
354+ bottom: parent.bottom
355+ left: parent.left
356+ right: parent.right
357+ top: menubar.bottom
358+ }
359+
360+ onNewViewRequested: {
361+ if (popupWindowController) {
362+ popupWindowController.createPopupView(
363+ popup.parent, request, false, context)
364+ }
365+ }
366+
367+ function isNewForegroundWebViewDisposition(disposition) {
368+ return disposition === Oxide.NavigationRequest.DispositionNewPopup ||
369+ disposition === Oxide.NavigationRequest.DispositionNewForegroundTab;
370+ }
371+
372+ onNavigationRequested: {
373+ var url = request.url.toString()
374+ if (isNewForegroundWebViewDisposition(request.disposition)) {
375+ popupWindowController.handleNewForegroundNavigationRequest(
376+ url, request, false)
377+ return
378+ }
379+ request.action = Oxide.NavigationRequest.ActionAccept
380+ }
381+
382+ onCloseRequested: {
383+ if (popupWindowController) {
384+ popupWindowController.handleViewRemoved(popup)
385+ }
386+ }
387+ }
388+
389+}
390
391=== modified file 'src/app/webcontainer/WebViewImplOxide.qml'
392--- src/app/webcontainer/WebViewImplOxide.qml 2015-02-19 11:50:21 +0000
393+++ src/app/webcontainer/WebViewImplOxide.qml 2015-04-30 16:33:24 +0000
394@@ -35,9 +35,13 @@
395 property var webappUrlPatterns: null
396 property string popupRedirectionUrlPrefixPattern: ""
397 property url dataPath
398+ property var popupController
399+ property var overlayViewsParent: webview.parent
400
401 // Mostly used for testing & avoid external urls to
402- // "leak" in the default browser
403+ // "leak" in the default browser. External URLs corresponds
404+ // to URLs that are not included in the set defined by the url patterns
405+ // (if any) or navigations resulting in new windows being created.
406 property bool blockOpenExternalUrls: false
407
408 // Those signals are used for testing purposes to externally
409@@ -55,6 +59,10 @@
410
411 preferences.allowFileAccessFromFileUrls: runningLocalApplication
412 preferences.allowUniversalAccessFromFileUrls: runningLocalApplication
413+ preferences.localStorageEnabled: true
414+ preferences.appCacheEnabled: true
415+
416+ onNewViewRequested: popupController.createPopupView(overlayViewsParent, request, true, context)
417
418 contextualActions: ActionList {
419 Actions.CopyLink {
420@@ -119,77 +127,17 @@
421 }
422
423 function navigationRequestedDelegate(request) {
424- var newForegroundPageRequest = isNewForegroundWebViewDisposition(request.disposition)
425 var url = request.url.toString()
426-
427- console.log("navigationRequestedDelegate - newForegroundPageRequest: "
428- + newForegroundPageRequest
429- + ', url: ' + url)
430-
431 if (runningLocalApplication && url.indexOf("file://") !== 0) {
432 request.action = Oxide.NavigationRequest.ActionReject
433 openUrlExternally(url)
434 return
435 }
436
437- // Covers some edge cases corresponding to the default window.open() behavior.
438- // When it is being called, the targetted URL will not load right away but
439- // will first round trip to an "about:blank".
440- // See https://developer.mozilla.org/en-US/docs/Web/API/Window.open
441- if (newForegroundPageRequest) {
442- if (url == 'about:blank') {
443- console.log('Accepting a new window request to navigate to "about:blank"')
444- request.action = Oxide.NavigationRequest.ActionAccept
445- return
446- }
447-
448- var isRedirectionUrl = false;
449- var targetUrl = url;
450- if (popupRedirectionUrlPrefixPattern) {
451- // NOTE: very nasty workaround to be backward compatibility, will be deleted as soon
452- // as the FB webapp is updated.
453- if (popupRedirectionUrlPrefixPattern.indexOf('(') === -1) {
454- isRedirectionUrl = (url.indexOf(popupRedirectionUrlPrefixPattern) === 0);
455- targetUrl = isRedirectionUrl ?
456- url.slice(popupRedirectionUrlPrefixPattern.length) : url;
457-
458- // Quick fix for http://pad.lv/1358622 (trim trailing parameters).
459- var extraParams = targetUrl.indexOf("&");
460- if (extraParams !== -1) {
461- targetUrl = targetUrl.slice(0, extraParams);
462- }
463- } else {
464- var redirectionPatternMatch = url.match(popupRedirectionUrlPrefixPattern);
465- isRedirectionUrl =
466- popupRedirectionUrlPrefixPattern
467- && redirectionPatternMatch
468- && redirectionPatternMatch.length >= 2;
469-
470- // Assume that the first group is the matching one
471- targetUrl = isRedirectionUrl ?
472- redirectionPatternMatch[1] : url;
473- }
474- }
475-
476- if (isRedirectionUrl) {
477- console.debug("Got a redirection URL with target URL: " + targetUrl)
478- targetUrl = decodeURIComponent(targetUrl)
479- gotRedirectionUrl(targetUrl)
480- }
481-
482- if (webview.shouldAllowNavigationTo(targetUrl)) {
483- console.debug('Redirecting popup browsing ' + targetUrl + ' in the current container window.')
484- request.action = Oxide.NavigationRequest.ActionReject
485- webappContainerHelper.browseToUrlRequested(webview, targetUrl)
486- return
487- }
488-
489- if (shouldOpenPopupsInDefaultBrowser()) {
490- console.debug('Opening popup window ' + targetUrl + ' in the browser window.')
491- request.action = Oxide.NavigationRequest.ActionReject
492- openUrlExternally(targetUrl)
493- return;
494- }
495+ request.action = Oxide.NavigationRequest.ActionReject
496+ if (isNewForegroundWebViewDisposition(request.disposition)) {
497+ request.action = Oxide.NavigationRequest.ActionAccept
498+ popupController.handleNewForegroundNavigationRequest(url, request, true)
499 return
500 }
501
502@@ -201,7 +149,6 @@
503 return
504 }
505
506- request.action = Oxide.NavigationRequest.ActionReject
507 if (webview.shouldAllowNavigationTo(url))
508 request.action = Oxide.NavigationRequest.ActionAccept
509
510@@ -228,70 +175,6 @@
511 }
512 }
513
514- function createPopupWindow(request) {
515- popupWebViewFactory.createObject(webview, { request: request, width: 500, height: 800 });
516- }
517-
518- Component {
519- id: popupWebViewFactory
520- Window {
521- id: popup
522- property alias request: popupBrowser.request
523- WebView {
524- id: popupBrowser
525- anchors.fill: parent
526-
527- function navigationRequestedDelegate(request) {
528- var url = request.url.toString()
529-
530- // If we are to browse in the popup to a place where we are not allows
531- if ( ! isNewForegroundWebViewDisposition(request.disposition) &&
532- ! webview.shouldAllowNavigationTo(url)) {
533- request.action = Oxide.NavigationRequest.ActionReject
534- openUrlExternally(url);
535- popup.close()
536- return;
537- }
538-
539- // Fallback to regulat checks (there is a bit of overlap)
540- webview.navigationRequestedDelegate(request)
541- }
542-
543- onNewViewRequested: webview.createPopupWindow(request)
544-
545- // Oxide (and Chromium) does not inform of non user
546- // driven navigations (or more specifically redirects that
547- // would be part of an popup/webview load (after its been
548- // granted). Quite a few sites (e.g. Youtube),
549- // create popups when clicking on links (or following a window.open())
550- // with proper youtube.com address but w/ redirection
551- // params, e.g.:
552- // http://www.youtube.com/redirect?q=http%3A%2F%2Fgodzillamovie.com%2F&redir_token=b8WPI1pq9FHXeHm2bN3KVLAJSfp8MTM5NzI2NDg3NEAxMzk3MTc4NDc0
553- // In this instance the popup & navigation is granted, but then
554- // a redirect happens inside the popup to the real target url (here http://godzillamovie.com)
555- // which is not trapped by a navigation requested and therefore not filtered.
556- // The only way to do it atm is to listen to url changed in popups & also
557- // filter there.
558- onUrlChanged: {
559- var _url = url.toString();
560- if (_url.trim().length === 0)
561- return;
562-
563- if (_url != 'about:blank' && ! webview.shouldAllowNavigationTo(_url)) {
564- openUrlExternally(_url);
565- popup.close()
566- }
567- }
568- }
569- Component.onCompleted: popup.show()
570- }
571- }
572-
573- onNewViewRequested: createPopupWindow(request)
574-
575- preferences.localStorageEnabled: true
576- preferences.appCacheEnabled: true
577-
578 // Small shim needed when running as a webapp to wire-up connections
579 // with the webview (message received, etc…).
580 // This is being called (and expected) internally by the webapps
581
582=== modified file 'src/app/webcontainer/WebappContainerWebview.qml'
583--- src/app/webcontainer/WebappContainerWebview.qml 2015-03-25 18:42:20 +0000
584+++ src/app/webcontainer/WebappContainerWebview.qml 2015-04-30 16:33:24 +0000
585@@ -40,6 +40,14 @@
586 property bool blockOpenExternalUrls: false
587 property bool runningLocalApplication: false
588
589+ PopupWindowController {
590+ id: popupController
591+ objectName: "popupController"
592+ webappUrlPatterns: containerWebview.webappUrlPatterns
593+ mainWebappView: containerWebview.currentWebview
594+ blockOpenExternalUrls: containerWebview.blockOpenExternalUrls
595+ }
596+
597 Loader {
598 id: webappContainerWebViewLoader
599 objectName: "containerWebviewLoader"
600@@ -68,7 +76,9 @@
601 , developerExtrasEnabled: containerWebview.developerExtrasEnabled
602 , popupRedirectionUrlPrefixPattern: containerWebview.popupRedirectionUrlPrefixPattern
603 , blockOpenExternalUrls: containerWebview.blockOpenExternalUrls
604- , runningLocalApplication: containerWebview.runningLocalApplication})
605+ , runningLocalApplication: containerWebview.runningLocalApplication
606+ , popupController: popupController
607+ , overlayViewsParent: containerWebview.parent})
608 }
609 }
610
611
612=== modified file 'tests/autopilot/webapp_container/tests/__init__.py'
613--- tests/autopilot/webapp_container/tests/__init__.py 2015-03-20 18:22:41 +0000
614+++ tests/autopilot/webapp_container/tests/__init__.py 2015-04-30 16:33:24 +0000
615@@ -83,6 +83,12 @@
616 def get_webview(self):
617 return self.app.select_single(objectName="webview")
618
619+ def get_popup_overlay_views(self):
620+ return self.app.select_many("PopupWindowOverlay")
621+
622+ def get_popup_controller(self):
623+ return self.app.select_single(objectName="popupController")
624+
625 def get_oxide_webview(self):
626 container = self.get_webview().select_single(
627 objectName='containerWebviewLoader')
628
629=== modified file 'tests/autopilot/webapp_container/tests/fake_servers.py'
630--- tests/autopilot/webapp_container/tests/fake_servers.py 2014-11-28 17:42:50 +0000
631+++ tests/autopilot/webapp_container/tests/fake_servers.py 2015-04-30 16:33:24 +0000
632@@ -66,22 +66,22 @@
633 </html>
634 """
635
636- def targetted_click_content(self, differentDomain=True):
637- url = 'http://www.test.com/'
638- if differentDomain:
639- url = 'http://www.ubuntu.com/'
640+ def targetted_click_content(self):
641 return """
642 <html>
643 <head>
644 <title>Some content</title>
645 </head>
646 <body>
647-<div><a href='{}' target='_blank'>
648-<div style="height: 100%; width: 100%"></div>
649-</a></div>
650+<div>
651+<a href="/open-close-content" target="_blank">
652+<div style="height: 100%; width: 100%">
653+</div>
654+</a>
655+</div>
656 </body>
657 </html>
658- """.format(url)
659+ """
660
661 def display_ua_content(self):
662 return """
663@@ -99,6 +99,27 @@
664 </html>
665 """.format("'"+self.headers['user-agent']+"'")
666
667+ def open_close_content(self):
668+ return """
669+<html>
670+<head>
671+<title>open-close</title>
672+<script>
673+</script>
674+</head>
675+<body>
676+ <a href="/open-close-content" target="_blank">
677+ <div style="height: 50%; width: 100%; background-color: red">
678+ target blank link
679+ </div>
680+ </a>
681+ <div id="lorem" style="height: 50%; width: 100%; background-color: blue">
682+ Lorem ipsum dolor sit amet
683+ </div>
684+</body>
685+</html>
686+ """
687+
688 def do_GET(self):
689 if self.path == '/':
690 self.send_response(200)
691@@ -114,13 +135,13 @@
692 self.serve_content(self.external_click_content())
693 elif self.path == '/with-targetted-link':
694 self.send_response(200)
695- self.serve_content(self.targetted_click_content(False))
696- elif self.path == '/with-different-targetted-link':
697- self.send_response(200)
698 self.serve_content(self.targetted_click_content())
699 elif self.path == '/show-user-agent':
700 self.send_response(200)
701 self.serve_content(self.display_ua_content())
702+ elif self.path == '/open-close-content':
703+ self.send_response(200)
704+ self.serve_content(self.open_close_content())
705 else:
706 self.send_error(404)
707
708
709=== added file 'tests/autopilot/webapp_container/tests/test_popup_webview_overlay.py'
710--- tests/autopilot/webapp_container/tests/test_popup_webview_overlay.py 1970-01-01 00:00:00 +0000
711+++ tests/autopilot/webapp_container/tests/test_popup_webview_overlay.py 2015-04-30 16:33:24 +0000
712@@ -0,0 +1,170 @@
713+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
714+# Copyright 2015 Canonical
715+#
716+# This program is free software: you can redistribute it and/or modify it
717+# under the terms of the GNU General Public License version 3, as published
718+# by the Free Software Foundation.
719+#
720+# This program is distributed in the hope that it will be useful,
721+# but WITHOUT ANY WARRANTY; without even the implied warranty of
722+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
723+# GNU General Public License for more details.
724+#
725+# You should have received a copy of the GNU General Public License
726+# along with this program. If not, see <http://www.gnu.org/licenses/>.
727+
728+from testtools.matchers import Equals, Contains, GreaterThan
729+from autopilot.matchers import Eventually
730+
731+from webapp_container.tests import WebappContainerTestCaseWithLocalContentBase
732+
733+
734+class WebappContainerPopupWebViewOverlayTestCase(
735+ WebappContainerTestCaseWithLocalContentBase):
736+
737+ def click_href_target_blank(self):
738+ webview = self.get_oxide_webview()
739+ self.assertThat(webview.url, Contains('/open-close-content'))
740+ gr = webview.globalRect
741+ self.pointing_device.move(
742+ gr.x + gr.width/4,
743+ gr.y + gr.height/4)
744+ self.pointing_device.click()
745+
746+ def click_window_open(self):
747+ webview = self.get_oxide_webview()
748+ self.assertThat(webview.url.endswith('/open-close-content'))
749+ gr = webview.globalRect
750+ self.pointing_device.move(
751+ gr.x + webview.width*3/4,
752+ gr.y + webview.height*3/4)
753+ self.pointing_device.click()
754+
755+ def test_open_close_back_to_mainview(self):
756+ args = []
757+ self.launch_webcontainer_app_with_local_http_server(
758+ args,
759+ '/open-close-content')
760+ self.get_webcontainer_window().visible.wait_for(True)
761+
762+ popup_controller = self.get_popup_controller()
763+ new_view_watcher = popup_controller.watch_signal(
764+ 'newViewCreated(QString)')
765+ animation_watcher = popup_controller.watch_signal(
766+ 'windowOverlayOpenAnimationDone()')
767+ animation_signal_emission = animation_watcher.num_emissions
768+
769+ views = self.get_popup_overlay_views()
770+ self.assertThat(len(views), Equals(0))
771+
772+ self.click_href_target_blank()
773+
774+ self.assertThat(
775+ lambda: new_view_watcher.was_emitted,
776+ Eventually(Equals(True)))
777+ self.assertThat(
778+ lambda: len(self.get_popup_overlay_views()),
779+ Eventually(Equals(1)))
780+ views = self.get_popup_overlay_views()
781+ overlay = views[0]
782+ self.assertThat(
783+ overlay.select_single(objectName="overlayWebview").url,
784+ Contains('/open-close-content'))
785+
786+ self.assertThat(
787+ lambda: animation_watcher.num_emissions,
788+ Eventually(GreaterThan(animation_signal_emission)))
789+ animation_signal_emission = animation_watcher.num_emissions
790+
791+ closeButton = overlay.select_single(
792+ objectName='overlayCloseButton')
793+
794+ self.pointing_device.click_object(closeButton)
795+
796+ self.assertThat(
797+ lambda: len(self.get_popup_overlay_views()),
798+ Eventually(Equals(0)))
799+
800+ def test_open_overlay_in_main_browser(self):
801+ args = []
802+ self.launch_webcontainer_app_with_local_http_server(
803+ args,
804+ '/open-close-content',
805+ {'WEBAPP_CONTAINER_BLOCK_OPEN_URL_EXTERNALLY': '1'})
806+ self.get_webcontainer_window().visible.wait_for(True)
807+
808+ popup_controller = self.get_popup_controller()
809+ webview = self.get_oxide_webview()
810+ self.assertThat(
811+ lambda: webview.visible,
812+ Eventually(Equals(True)))
813+ external_open_watcher = popup_controller.watch_signal(
814+ 'openExternalUrlTriggered(QString)')
815+
816+ animation_watcher = popup_controller.watch_signal(
817+ 'windowOverlayOpenAnimationDone()')
818+ animation_signal_emission = animation_watcher.num_emissions
819+
820+ self.click_href_target_blank()
821+
822+ self.assertThat(
823+ lambda: len(self.get_popup_overlay_views()),
824+ Eventually(Equals(1)))
825+
826+ views = self.get_popup_overlay_views()
827+ overlay = views[0]
828+
829+ self.assertThat(
830+ lambda: animation_watcher.num_emissions,
831+ Eventually(GreaterThan(animation_signal_emission)))
832+ animation_signal_emission = animation_watcher.num_emissions
833+
834+ openInBrowserButton = overlay.select_single(
835+ objectName='overlayButtonOpenInBrowser')
836+
837+ self.pointing_device.click_object(openInBrowserButton)
838+
839+ self.assertThat(
840+ lambda: len(self.get_popup_overlay_views()),
841+ Eventually(Equals(0)))
842+ self.assertThat(
843+ lambda: external_open_watcher.was_emitted,
844+ Eventually(Equals(True)))
845+ self.assertThat(
846+ lambda: webview.visible,
847+ Eventually(Equals(True)))
848+
849+ def test_max_overlay_count_reached(self):
850+ args = []
851+ self.launch_webcontainer_app_with_local_http_server(
852+ args,
853+ '/open-close-content',
854+ {'WEBAPP_CONTAINER_BLOCK_OPEN_URL_EXTERNALLY': '1'})
855+ self.get_webcontainer_window().visible.wait_for(True)
856+
857+ popup_controller = self.get_popup_controller()
858+ webview = self.get_oxide_webview()
859+ self.assertThat(
860+ lambda: webview.visible,
861+ Eventually(Equals(True)))
862+
863+ animation_watcher = popup_controller.watch_signal(
864+ 'windowOverlayOpenAnimationDone()')
865+ animation_signal_emission = animation_watcher.num_emissions
866+
867+ OVERLAY_MAX_COUNT = 3
868+ for i in range(0, OVERLAY_MAX_COUNT):
869+ self.click_href_target_blank()
870+ self.assertThat(
871+ lambda: animation_watcher.num_emissions,
872+ Eventually(GreaterThan(animation_signal_emission)))
873+ animation_signal_emission = animation_watcher.num_emissions
874+
875+ external_open_watcher = popup_controller.watch_signal(
876+ 'openExternalUrlTriggered(QString)')
877+
878+ self.click_href_target_blank()
879+
880+ self.assertThat(
881+ lambda: external_open_watcher.was_emitted,
882+ Eventually(Equals(True)))
883
884=== removed file 'tests/autopilot/webapp_container/tests/test_redirection_pattern.py'
885--- tests/autopilot/webapp_container/tests/test_redirection_pattern.py 2014-10-07 16:16:13 +0000
886+++ tests/autopilot/webapp_container/tests/test_redirection_pattern.py 1970-01-01 00:00:00 +0000
887@@ -1,59 +0,0 @@
888-# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
889-# Copyright 2014 Canonical
890-#
891-# This program is free software: you can redistribute it and/or modify it
892-# under the terms of the GNU General Public License version 3, as published
893-# by the Free Software Foundation.
894-#
895-# This program is distributed in the hope that it will be useful,
896-# but WITHOUT ANY WARRANTY; without even the implied warranty of
897-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
898-# GNU General Public License for more details.
899-#
900-# You should have received a copy of the GNU General Public License
901-# along with this program. If not, see <http://www.gnu.org/licenses/>.
902-
903-from testtools.matchers import Equals
904-from autopilot.matchers import Eventually
905-
906-from webapp_container.tests import WebappContainerTestCaseWithLocalContentBase
907-
908-
909-class WebappContainerRedirectionPatternTestCase(
910- WebappContainerTestCaseWithLocalContentBase):
911-
912- def test_browse_to_redirection_pattern_url(self):
913- REDIRECTION_HOSTNAME = self.get_base_url_hostname()
914- args = ["--popup-redirection-url-prefix={}{}{}".format(
915- 'http://', REDIRECTION_HOSTNAME.replace('.', '\.'),
916- '/redirect\\?url=([^&]*).*')]
917- self.launch_webcontainer_app_with_local_http_server(
918- args,
919- '/get-redirect',
920- {'WEBAPP_CONTAINER_BLOCK_OPEN_URL_EXTERNALLY': '1',
921- 'WEBAPP_CONTAINER_DO_NOT_FILTER_PATTERN_URL': '1'})
922- self.get_webcontainer_window().visible.wait_for(True)
923-
924- webview = self.get_oxide_webview()
925- external_open_watcher = webview.watch_signal(
926- 'openExternalUrlTriggered(QString)')
927- got_redirection_url_watcher = webview.watch_signal(
928- 'gotRedirectionUrl(QString)')
929-
930- self.assertThat(external_open_watcher.was_emitted, Equals(False))
931- self.assertThat(got_redirection_url_watcher.was_emitted, Equals(False))
932- self.browse_to(
933- "http://{}/get-redirect".format(REDIRECTION_HOSTNAME))
934-
935- self.pointing_device.click_object(webview)
936-
937- self.assertThat(
938- lambda: got_redirection_url_watcher.was_emitted,
939- Eventually(Equals(True)))
940- self.assertThat(
941- webview.get_signal_emissions(
942- 'gotRedirectionUrl(QString)')[0][0],
943- Equals('myredirect'))
944- self.assertThat(
945- lambda: external_open_watcher.was_emitted,
946- Eventually(Equals(True)))
947
948=== modified file 'tests/autopilot/webapp_container/tests/test_url_patterns.py'
949--- tests/autopilot/webapp_container/tests/test_url_patterns.py 2015-02-18 11:05:35 +0000
950+++ tests/autopilot/webapp_container/tests/test_url_patterns.py 2015-04-30 16:33:24 +0000
951@@ -43,55 +43,3 @@
952 self.assertThat(
953 lambda: external_open_watcher.was_emitted,
954 Eventually(Equals(True)))
955-
956- def test_pattern_with_allowed_targetted_url(self):
957- args = ["--webappUrlPatterns=http://www.test.com/*"]
958- rule = 'MAP *.test.com:80 ' + self.get_base_url_hostname()
959- self.launch_webcontainer_app_with_local_http_server(
960- args,
961- '',
962- {'WEBAPP_CONTAINER_BLOCK_OPEN_URL_EXTERNALLY': '1',
963- 'UBUNTU_WEBVIEW_HOST_MAPPING_RULES': rule},
964- "http://www.test.com/with-targetted-link")
965-
966- self.get_webcontainer_window().visible.wait_for(True)
967-
968- webview = self.get_oxide_webview()
969- external_open_watcher = webview.watch_signal(
970- 'openExternalUrlTriggered(QString)')
971-
972- self.pointing_device.click_object(webview)
973-
974- self.assertThat(
975- webview.url,
976- Eventually(Equals("http://www.test.com/")))
977-
978- self.assertThat(
979- external_open_watcher.was_emitted,
980- Equals(False))
981-
982- def test_pattern_with_different_targetted_url(self):
983- args = ["--webappUrlPatterns=http://www.test.com/*"]
984- rule = 'MAP *.test.com:80 ' + self.get_base_url_hostname()
985- self.launch_webcontainer_app_with_local_http_server(
986- args,
987- '',
988- {'WEBAPP_CONTAINER_BLOCK_OPEN_URL_EXTERNALLY': '1',
989- 'UBUNTU_WEBVIEW_HOST_MAPPING_RULES': rule},
990- "http://www.test.com/with-different-targetted-link")
991-
992- self.get_webcontainer_window().visible.wait_for(True)
993-
994- webview = self.get_oxide_webview()
995- external_open_watcher = webview.watch_signal(
996- 'openExternalUrlTriggered(QString)')
997-
998- self.pointing_device.click_object(webview)
999-
1000- self.assertThat(
1001- webview.url,
1002- Equals("http://www.test.com/with-different-targetted-link"))
1003-
1004- self.assertThat(
1005- lambda: external_open_watcher.was_emitted,
1006- Eventually(Equals(True)))

Subscribers

People subscribed via source and target branches

to status/vote changes: