Merge lp:~ahayzen/webbrowser-app/dnd-tabs-001 into lp:webbrowser-app

Proposed by Andrew Hayzen
Status: Superseded
Proposed branch: lp:~ahayzen/webbrowser-app/dnd-tabs-001
Merge into: lp:webbrowser-app
Diff against target: 3380 lines (+1348/-1063)
41 files modified
src/Ubuntu/Web/UbuntuWebContext.qml (+5/-2)
src/Ubuntu/Web/UserAgent02.qml (+3/-3)
src/app/BrowserView.qml (+2/-5)
src/app/BrowserWindow.qml (+0/-13)
src/app/CMakeLists.txt (+0/-1)
src/app/actions/OpenLinkInNewWindow.qml (+23/-0)
src/app/actions/OpenLinkInPrivateWindow.qml (+23/-0)
src/app/browserapplication.cpp (+10/-45)
src/app/browserapplication.h (+3/-7)
src/app/webbrowser-window.cpp (+0/-51)
src/app/webbrowser-window.h (+0/-57)
src/app/webbrowser/Browser.qml (+147/-418)
src/app/webbrowser/BrowserTab.qml (+18/-1)
src/app/webbrowser/CMakeLists.txt (+2/-0)
src/app/webbrowser/LeavePrivateModeDialog.qml (+0/-43)
src/app/webbrowser/TabsBar.qml (+68/-3)
src/app/webbrowser/drag-helper.cpp (+100/-0)
src/app/webbrowser/drag-helper.h (+61/-0)
src/app/webbrowser/reparenter.cpp (+36/-0)
src/app/webbrowser/reparenter.h (+36/-0)
src/app/webbrowser/webbrowser-app.cpp (+38/-6)
src/app/webbrowser/webbrowser-app.desktop.in.in (+9/-0)
src/app/webbrowser/webbrowser-app.h (+4/-1)
src/app/webbrowser/webbrowser-app.qml (+455/-49)
src/app/webcontainer/WebApp.qml (+2/-0)
src/app/webcontainer/WebViewImplOxide.qml (+5/-7)
src/app/webcontainer/WebappContainerWebview.qml (+5/-1)
src/app/webcontainer/webapp-container.cpp (+51/-26)
src/app/webcontainer/webapp-container.h (+7/-2)
src/app/webcontainer/webapp-container.qml (+12/-3)
tests/autopilot/webbrowser_app/__init__.py (+4/-1)
tests/autopilot/webbrowser_app/emulators/browser.py (+0/-55)
tests/autopilot/webbrowser_app/tests/__init__.py (+22/-0)
tests/autopilot/webbrowser_app/tests/test_contextmenu.py (+29/-0)
tests/autopilot/webbrowser_app/tests/test_fullscreen.py (+0/-54)
tests/autopilot/webbrowser_app/tests/test_keyboard.py (+22/-0)
tests/autopilot/webbrowser_app/tests/test_multiple_windows.py (+40/-0)
tests/autopilot/webbrowser_app/tests/test_new_tab_view.py (+0/-49)
tests/autopilot/webbrowser_app/tests/test_private.py (+0/-115)
tests/autopilot/webbrowser_app/tests/test_tabs.py (+0/-45)
tests/unittests/qml/tst_BrowserWindow.qml (+106/-0)
To merge this branch: bzr merge lp:~ahayzen/webbrowser-app/dnd-tabs-001
Reviewer Review Type Date Requested Status
system-apps-ci-bot continuous-integration Needs Fixing
Ubuntu Phablet Team Pending
Review via email: mp+307051@code.launchpad.net

This proposal has been superseded by a proposal from 2016-09-28.

Commit message

WIP Drag and drop support

Description of the change

WIP Drag and drop support, proposing so that we can get debs easily.

To post a comment you must log in.
Revision history for this message
system-apps-ci-bot (system-apps-ci-bot) wrote :

FAILED: Continuous integration, rev:1518
https://jenkins.canonical.com/system-apps/job/lp-webbrowser-app-ci/661/
Executed test runs:
    FAILURE: https://jenkins.canonical.com/system-apps/job/build/1655/console
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-0-fetch/1655
    FAILURE: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=vivid+overlay/1501/console
    FAILURE: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/1501/console
    FAILURE: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=yakkety/1501/console
    FAILURE: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=vivid+overlay/1501/console
    FAILURE: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/1501/console
    FAILURE: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=yakkety/1501/console
    FAILURE: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=vivid+overlay/1501/console
    FAILURE: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/1501/console
    FAILURE: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=yakkety/1501/console

Click here to trigger a rebuild:
https://jenkins.canonical.com/system-apps/job/lp-webbrowser-app-ci/661/rebuild

review: Needs Fixing (continuous-integration)
1519. By Andrew Hayzen

* Fix reparenting by ensuring that QML context is not set to a window
* Changes to BrowserTab declaration so that properties can be set after creation
* Allow unit tests to run

1520. By Andrew Hayzen

* Add destruction code to repearenter so that it correctly destroys any contexts and objects left

1521. By Andrew Hayzen

* Change behaviour so if dropped in the bottom part of the browser window it generates a new window

1522. By Andrew Hayzen

* If dragging within the original window and Chrome when in multi-window drag, move tab delegate with mouse
* Move drag handle to bottom right of mouse instead of centred
* When dragging the tab delegate don't animate it

1523. By Andrew Hayzen

* Reenable animation when dragging as model moves depend on it

1524. By Andrew Hayzen

* Only allow tabs to be dragged to windows of the same incognito type

1525. By Andrew Hayzen

* Raise window when DropArea is entered
* Change styling of DropArea
* Add "Move to New Window" context item to tab delegates

1526. By Andrew Hayzen

* Add autopilot tests TestMultipleWindowsDrag to ensure that drag and drop functions correct in wide mode

1527. By Andrew Hayzen

* Merge of trunk

1528. By Andrew Hayzen

* Update autopilot tests to use new switch_to_unfocused_window which improves switching on unity7 env

1529. By Andrew Hayzen

* Add Reparenter unit tests

1530. By Andrew Hayzen

* Merge of lp:webbrowser-app/staging and resolve conflicts

1531. By Andrew Hayzen

* Fix for progress bar appearing after tab is dragged out

1532. By Andrew Hayzen

* Be more declarative in DropArea
* Change use of DropActions to make more sense
* Change unit test to use default timeout
* Allow setting of expectedAction in DragHelper
* Update uses of null to Q_NULLPTR
* Disable dragging on mir clients

1533. By Andrew Hayzen

* Limit y diff of visual tabitem when dragging

1534. By Andrew Hayzen

* Fix regression where new tabs have massive favicons

1535. By Andrew Hayzen

* Remove white shade on bottom part of browser to match design

1536. By Andrew Hayzen

* Revert change in use of DropAction as dropping outside of a DropArea is always IgnoreAction
* Change test_drag_tab_outside_new_window to move the tab below the window
* Change property tabsBarHeight to be real rather than int

1537. By Andrew Hayzen

* Revert .pot changes

1538. By Andrew Hayzen

* Revert changes to test as it was fine before :-)

1539. By Andrew Hayzen

* Remove referenes to browser, tabModel and windo in TabsBar.qml
* Remove reference to builder in Browser.qml
* Allowed passing of properties to Reparenter::createObject()
* Made Reparenter a singleton
* Moved buildContextProperties() and createTabHelper() into internal to make them private
* Change dropArea.heightThreshold to be real not int
* Removed many console.debug() calls
* Changes includes to be fully-qualified in drag-helper and reparenter
* Renamed closeMethod to callback for newWindowFromTab()

1540. By Andrew Hayzen

* Ensure that switchToTab is called after binding a new tab, otherwise the chrome's position can be broken

1541. By Andrew Hayzen

* Merge of lp:webbrowser-app/staging

1542. By Andrew Hayzen

* Update dropArea shade to match new design
* Fix for bad conflict resolution

1543. By Andrew Hayzen

* Fix for typo

1544. By Andrew Hayzen

* Add .dragging property to DragHelper so that drop area shade knows when a drag event is occuring
* Change DragHelper to a singleton

1545. By Andrew Hayzen

* Changed DragHelper to be a QObject and use member initialisation
* Changed Reparenter::createObject to use beginCreate and completeCreate
* Various fixes to reduce warnings under QML tests

1546. By Andrew Hayzen

* Use forward declaration of QQuickItem

1547. By Andrew Hayzen

* Fix for missed case of createTabHelper -> createTab

1548. By Andrew Hayzen

* Ensure webviews are not shown when newTabView is active

1549. By Andrew Hayzen

* Change keyboard shortcuts to use contentsContainer visibility instead of tabsContainer

1550. By Andrew Hayzen

* Use enabled rather than visible on the tabContainer to prevent events being stolen, otherwise chrome disappears when opening new tabs as the locationBarController.offset doesn't get set

1551. By Andrew Hayzen

* Before tab is closed, check if a new tab needs to be generated as the context will disappear

1552. By Andrew Hayzen

* Use deleteLater rather than delete item; otherwise on slower devices such as phones it crashes
* Don't delete the context if it was not removed from the reparenter context store
* Change qml test for reparenter to respect that the delete may take time to happen

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/Ubuntu/Web/UbuntuWebContext.qml'
2--- src/Ubuntu/Web/UbuntuWebContext.qml 2016-06-14 18:49:01 +0000
3+++ src/Ubuntu/Web/UbuntuWebContext.qml 2016-09-28 15:27:35 +0000
4@@ -70,13 +70,16 @@
5 ]
6
7 property QtObject __ua: UserAgent02 {
8- onSmallScreenChanged: reloadOverrides()
9+ onScreenSizeChanged: reloadOverrides()
10 Component.onCompleted: reloadOverrides()
11
12 property string _target: ""
13
14 function reloadOverrides() {
15- var target = smallScreen ? "mobile" : "desktop"
16+ if (screenSize === "unknown") {
17+ return
18+ }
19+ var target = screenSize === "small" ? "mobile" : "desktop"
20 if (target == _target) return
21 _target = target
22 var script = "ua-overrides-%1.js".arg(target)
23
24=== modified file 'src/Ubuntu/Web/UserAgent02.qml'
25--- src/Ubuntu/Web/UserAgent02.qml 2016-06-03 08:08:45 +0000
26+++ src/Ubuntu/Web/UserAgent02.qml 2016-09-28 15:27:35 +0000
27@@ -31,7 +31,7 @@
28 QtObject {
29 // Empirical value: screens smaller than 19cm are considered small enough that a
30 // mobile UA string is used, screens bigger than that will get desktop content.
31- readonly property bool smallScreen: screenDiagonal < 190
32+ readonly property string screenSize: (screenDiagonal === 0) ? "unknown" : (screenDiagonal > 0 && screenDiagonal < 190) ? "small" : "large"
33
34 // %1: Ubuntu version, e.g. "14.04"
35 // %2: optional token to specify further attributes of the platform, e.g. "like Android"
36@@ -48,7 +48,7 @@
37 // difference in the content served by certain sites (e.g. gmail.com)
38 readonly property string _template: "Mozilla/5.0 (Linux; Ubuntu %1%2%3) AppleWebKit/%4 Chromium/%5 %6Safari/%7%8"
39
40- readonly property string _attributes: smallScreen ? "like Android 4.4" : ""
41+ readonly property string _attributes: screenSize === "small" ? "like Android 4.4" : ""
42
43 readonly property string _hardwareID: ""
44
45@@ -57,7 +57,7 @@
46
47 readonly property string _chromiumVersion: Oxide.chromiumVersion
48
49- readonly property string _formFactor: smallScreen ? "Mobile" : ""
50+ readonly property string _formFactor: screenSize === "small" ? "Mobile" : ""
51
52 readonly property string _more: ""
53
54
55=== modified file 'src/app/BrowserView.qml'
56--- src/app/BrowserView.qml 2016-06-16 18:21:58 +0000
57+++ src/app/BrowserView.qml 2016-09-28 15:27:35 +0000
58@@ -17,6 +17,7 @@
59 */
60
61 import QtQuick 2.4
62+import QtQuick.Window 2.2
63 import Ubuntu.Components 1.3
64 import Ubuntu.Unity.Action 1.1 as UnityActions
65 import com.canonical.Oxide 1.15 as Oxide
66@@ -27,16 +28,12 @@
67 property var currentWebview: null
68 property string title: currentWebview ? currentWebview.title : ""
69
70- property var initialUrls
71-
72- property var webbrowserWindow: null
73-
74 property var osk: _osk
75
76 property bool hasTouchScreen: false
77
78 // See http://design.canonical.com/2015/05/to-converge-onto-mobile-tablet-and-desktop-think-grid-units/
79- readonly property bool wide: width >= units.gu(90)
80+ readonly property bool wide: Window.contentItem.width >= units.gu(90)
81
82 focus: true
83
84
85=== modified file 'src/app/BrowserWindow.qml'
86--- src/app/BrowserWindow.qml 2016-05-25 15:46:41 +0000
87+++ src/app/BrowserWindow.qml 2016-09-28 15:27:35 +0000
88@@ -28,8 +28,6 @@
89 property var currentWebview: null
90 property bool hasTouchScreen: false
91
92- signal openUrls(var urls)
93-
94 contentOrientation: Screen.orientation
95
96 minimumWidth: units.gu(50)
97@@ -58,15 +56,4 @@
98 }
99 }
100 }
101-
102- // Handle runtime requests to open urls as defined
103- // by the freedesktop application dbus interface's open
104- // method for DBUS application activation:
105- // http://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#dbus
106- // The dispatch on the org.freedesktop.Application if is done per appId at the
107- // url-dispatcher/upstart level.
108- Connections {
109- target: UriHandler
110- onOpened: window.openUrls(uris)
111- }
112 }
113
114=== modified file 'src/app/CMakeLists.txt'
115--- src/app/CMakeLists.txt 2016-04-12 19:59:00 +0000
116+++ src/app/CMakeLists.txt 2016-09-28 15:27:35 +0000
117@@ -26,7 +26,6 @@
118 mime-database.cpp
119 session-storage.cpp
120 single-instance-manager.cpp
121- webbrowser-window.cpp
122 qquickshortcut.cpp
123 )
124
125
126=== added file 'src/app/actions/OpenLinkInNewWindow.qml'
127--- src/app/actions/OpenLinkInNewWindow.qml 1970-01-01 00:00:00 +0000
128+++ src/app/actions/OpenLinkInNewWindow.qml 2016-09-28 15:27:35 +0000
129@@ -0,0 +1,23 @@
130+/*
131+ * Copyright 2016 Canonical Ltd.
132+ *
133+ * This file is part of webbrowser-app.
134+ *
135+ * webbrowser-app is free software; you can redistribute it and/or modify
136+ * it under the terms of the GNU General Public License as published by
137+ * the Free Software Foundation; version 3.
138+ *
139+ * webbrowser-app is distributed in the hope that it will be useful,
140+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
141+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
142+ * GNU General Public License for more details.
143+ *
144+ * You should have received a copy of the GNU General Public License
145+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
146+ */
147+
148+import Ubuntu.Components 1.3
149+
150+Action {
151+ text: i18n.tr("Open link in new window")
152+}
153
154=== added file 'src/app/actions/OpenLinkInPrivateWindow.qml'
155--- src/app/actions/OpenLinkInPrivateWindow.qml 1970-01-01 00:00:00 +0000
156+++ src/app/actions/OpenLinkInPrivateWindow.qml 2016-09-28 15:27:35 +0000
157@@ -0,0 +1,23 @@
158+/*
159+ * Copyright 2016 Canonical Ltd.
160+ *
161+ * This file is part of webbrowser-app.
162+ *
163+ * webbrowser-app is free software; you can redistribute it and/or modify
164+ * it under the terms of the GNU General Public License as published by
165+ * the Free Software Foundation; version 3.
166+ *
167+ * webbrowser-app is distributed in the hope that it will be useful,
168+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
169+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
170+ * GNU General Public License for more details.
171+ *
172+ * You should have received a copy of the GNU General Public License
173+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
174+ */
175+
176+import Ubuntu.Components 1.3
177+
178+Action {
179+ text: i18n.tr("Open link in private window")
180+}
181
182=== modified file 'src/app/browserapplication.cpp'
183--- src/app/browserapplication.cpp 2016-06-16 13:30:01 +0000
184+++ src/app/browserapplication.cpp 2016-09-28 15:27:35 +0000
185@@ -29,8 +29,8 @@
186 #include <QtQml/QQmlComponent>
187 #include <QtQml/QQmlContext>
188 #include <QtQml/QQmlEngine>
189+#include <QtQml/QQmlProperty>
190 #include <QtQml/QtQml>
191-#include <QtQuick/QQuickWindow>
192
193 // local
194 #include "browserapplication.h"
195@@ -40,16 +40,14 @@
196 #include "mime-database.h"
197 #include "qquickshortcut_p.h"
198 #include "session-storage.h"
199-#include "webbrowser-window.h"
200
201 #include "Unity/InputInfo/qdeclarativeinputdevicemodel_p.h"
202
203 BrowserApplication::BrowserApplication(int& argc, char** argv)
204 : QApplication(argc, argv)
205 , m_engine(0)
206- , m_window(0)
207 , m_component(0)
208- , m_webbrowserWindowProxy(0)
209+ , m_object(nullptr)
210 {
211 m_arguments = arguments();
212 m_arguments.removeFirst();
213@@ -57,11 +55,7 @@
214
215 BrowserApplication::~BrowserApplication()
216 {
217- if (m_webbrowserWindowProxy) {
218- m_webbrowserWindowProxy->setWindow(NULL);
219- }
220- delete m_window;
221- delete m_webbrowserWindowProxy;
222+ delete m_object;
223 delete m_component;
224 delete m_engine;
225 }
226@@ -108,7 +102,7 @@
227 bool BrowserApplication::initialize(const QString& qmlFileSubPath
228 , const QString& appId)
229 {
230- Q_ASSERT(m_window == 0);
231+ Q_ASSERT(m_object == nullptr);
232
233 if (helpRequested()) {
234 printUsage();
235@@ -195,7 +189,6 @@
236 if (!isRunningInstalled()) {
237 m_engine->addImportPath(UbuntuBrowserImportsDirectory());
238 }
239-
240 qmlEngineCreated(m_engine);
241
242 QQmlContext* context = m_engine->rootContext();
243@@ -208,15 +201,10 @@
244 qWarning() << m_component->errorString();
245 return false;
246 }
247- m_webbrowserWindowProxy = new WebBrowserWindow();
248- context->setContextProperty("webbrowserWindowProxy", m_webbrowserWindowProxy);
249-
250- QObject* browser = m_component->beginCreate(context);
251- m_window = qobject_cast<QQuickWindow*>(browser);
252- m_webbrowserWindowProxy->setWindow(m_window);
253-
254- browser->setProperty("developerExtrasEnabled", inspectorEnabled);
255- browser->setProperty("forceFullscreen", m_arguments.contains("--fullscreen"));
256+
257+ m_object = m_component->beginCreate(context);
258+
259+ QQmlProperty::write(m_object, QStringLiteral("developerExtrasEnabled"), inspectorEnabled);
260
261 bool hasTouchScreen = false;
262 Q_FOREACH(const QTouchDevice* device, QTouchDevice::devices()) {
263@@ -224,40 +212,17 @@
264 hasTouchScreen = true;
265 }
266 }
267- browser->setProperty("hasTouchScreen", hasTouchScreen);
268+ QQmlProperty::write(m_object, QStringLiteral("hasTouchScreen"), hasTouchScreen);
269
270 return true;
271 }
272
273-void BrowserApplication::onNewInstanceLaunched(const QStringList& arguments) const
274-{
275- QVariantList urls;
276- Q_FOREACH(const QString& argument, arguments) {
277- if (!argument.startsWith(QStringLiteral("-"))) {
278- QUrl url = QUrl::fromUserInput(argument);
279- if (url.isValid()) {
280- urls.append(url);
281- }
282- }
283- }
284- QMetaObject::invokeMethod(m_window, "openUrls", Q_ARG(QVariant, QVariant(urls)));
285- m_window->requestActivate();
286-}
287-
288 void BrowserApplication::qmlEngineCreated(QQmlEngine*)
289 {}
290
291 int BrowserApplication::run()
292 {
293- Q_ASSERT(m_window != 0);
294-
295- if (m_arguments.contains("--fullscreen")) {
296- m_window->showFullScreen();
297- } else if (m_arguments.contains("--maximized")) {
298- m_window->showMaximized();
299- } else {
300- m_window->show();
301- }
302+ Q_ASSERT(m_object != nullptr);
303 return exec();
304 }
305
306
307=== modified file 'src/app/browserapplication.h'
308--- src/app/browserapplication.h 2016-06-16 13:30:01 +0000
309+++ src/app/browserapplication.h 2016-09-28 15:27:35 +0000
310@@ -31,8 +31,6 @@
311
312 class QQmlComponent;
313 class QQmlEngine;
314-class QQuickWindow;
315-class WebBrowserWindow;
316
317 // We want the browser to be QApplication based rather than QGuiApplication
318 // to provide a widget based file picker on the desktop, rather than the
319@@ -58,18 +56,16 @@
320
321 QStringList m_arguments;
322 QQmlEngine* m_engine;
323- QQuickWindow* m_window;
324 QQmlComponent* m_component;
325+ QObject* m_object;
326
327-private Q_SLOTS:
328- void onNewInstanceLaunched(const QStringList& arguments) const;
329+protected Q_SLOTS:
330+ virtual void onNewInstanceLaunched(const QStringList& arguments) const = 0;
331
332 private:
333 QString inspectorPort() const;
334 QString inspectorHost() const;
335
336- WebBrowserWindow *m_webbrowserWindowProxy;
337-
338 SingleInstanceManager m_singleton;
339 };
340
341
342=== removed file 'src/app/webbrowser-window.cpp'
343--- src/app/webbrowser-window.cpp 2013-09-14 13:49:29 +0000
344+++ src/app/webbrowser-window.cpp 1970-01-01 00:00:00 +0000
345@@ -1,51 +0,0 @@
346-/*
347- * Copyright 2013 Canonical Ltd.
348- *
349- * This file is part of webbrowser-app.
350- *
351- * webbrowser-app is free software; you can redistribute it and/or modify
352- * it under the terms of the GNU General Public License as published by
353- * the Free Software Foundation; version 3.
354- *
355- * webbrowser-app is distributed in the hope that it will be useful,
356- * but WITHOUT ANY WARRANTY; without even the implied warranty of
357- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
358- * GNU General Public License for more details.
359- *
360- * You should have received a copy of the GNU General Public License
361- * along with this program. If not, see <http://www.gnu.org/licenses/>.
362- */
363-
364-#include <QDebug>
365-#include "webbrowser-window.h"
366-
367-
368-WebBrowserWindow::WebBrowserWindow(QObject *parent) :
369- QObject(parent),
370- _window(0)
371-{}
372-
373-QQuickWindow * WebBrowserWindow::window() const
374-{
375- return _window;
376-}
377-
378-void WebBrowserWindow::setWindow(QQuickWindow * window)
379-{
380- if (_window != window)
381- {
382- _window = window;
383- Q_EMIT windowChanged(window);
384- }
385-}
386-
387-void WebBrowserWindow::raise()
388-{
389- if ( ! _window)
390- return;
391-
392- _window->raise();
393- _window->show();
394-}
395-
396-
397
398=== removed file 'src/app/webbrowser-window.h'
399--- src/app/webbrowser-window.h 2013-09-14 13:49:29 +0000
400+++ src/app/webbrowser-window.h 1970-01-01 00:00:00 +0000
401@@ -1,57 +0,0 @@
402-/*
403- * Copyright 2013 Canonical Ltd.
404- *
405- * This file is part of webbrowser-app.
406- *
407- * webbrowser-app is free software; you can redistribute it and/or modify
408- * it under the terms of the GNU General Public License as published by
409- * the Free Software Foundation; version 3.
410- *
411- * webbrowser-app is distributed in the hope that it will be useful,
412- * but WITHOUT ANY WARRANTY; without even the implied warranty of
413- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
414- * GNU General Public License for more details.
415- *
416- * You should have received a copy of the GNU General Public License
417- * along with this program. If not, see <http://www.gnu.org/licenses/>.
418- */
419-
420-#ifndef __WEBBROWSER_WINDOW_H__
421-#define __WEBBROWSER_WINDOW_H__
422-
423-#include <QObject>
424-#include <QQuickWindow>
425-
426-
427-class WebBrowserWindow : public QObject
428-{
429- Q_OBJECT
430- Q_PROPERTY(QQuickWindow *window READ window WRITE setWindow NOTIFY windowChanged)
431-
432-
433-public:
434- explicit WebBrowserWindow(QObject *parent = 0);
435-
436-
437- QQuickWindow * window() const;
438- void setWindow(QQuickWindow *);
439-
440-
441-public slots:
442-
443- void raise();
444-
445-
446-signals:
447-
448- void windowChanged(QQuickWindow *);
449-
450-
451-private:
452-
453- QQuickWindow * _window;
454-};
455-
456-
457-#endif // __WEBBROWSER_WINDOW_H__
458-
459
460=== modified file 'src/app/webbrowser/Browser.qml'
461--- src/app/webbrowser/Browser.qml 2016-07-01 08:52:37 +0000
462+++ src/app/webbrowser/Browser.qml 2016-09-28 15:27:35 +0000
463@@ -38,30 +38,50 @@
464 // Should be true when the containing window is fullscreen.
465 property bool fullscreen: false
466
467+ property Settings settings
468+
469 currentWebview: tabsModel && tabsModel.currentTab ? tabsModel.currentTab.webview : null
470
471- readonly property var downloadManager: (downloadHandlerLoader.status == Loader.Ready) ? downloadHandlerLoader.item : null
472-
473- property bool newSession: false
474-
475 property bool incognito: false
476
477- readonly property var tabsModel: incognito ? privateTabsModelLoader.item : publicTabsModel
478-
479- // Restore only the n most recent tabs at startup,
480- // to limit the overhead of instantiating too many
481- // tab objects (see http://pad.lv/1376433).
482- readonly property int maxTabsToRestore: 10
483-
484- onTabsModelChanged: {
485- if (incognito && privateTabsModelLoader.item) {
486- browser.openUrlInNewTab("", true)
487- } else if (!incognito && tabsModel.currentTab) {
488- // If the system is low on memory, the current public tab might
489- // have been unloaded while browsing incognito, so reload it.
490- tabsModel.currentTab.load()
491- }
492- }
493+ property var tabsModel: TabsModel {}
494+
495+ function serializeTabState(tab) {
496+ var state = {}
497+ state.uniqueId = tab.uniqueId
498+ state.url = tab.url.toString()
499+ state.title = tab.title
500+ state.icon = tab.icon.toString()
501+ state.preview = tab.preview.toString()
502+ state.savedState = tab.webview ? tab.webview.currentState : tab.restoreState
503+ return state
504+ }
505+
506+ function restoreTabState(state) {
507+ var properties = {'initialUrl': state.url, 'initialTitle': state.title,
508+ 'uniqueId': state.uniqueId, 'initialIcon': state.icon,
509+ 'preview': state.preview, 'restoreState': state.savedState,
510+ 'restoreType': Oxide.WebView.RestoreLastSessionExitedCleanly}
511+ return createTab(properties)
512+ }
513+
514+ function createTab(properties) {
515+ return tabComponent.createObject(tabContainer, properties)
516+ }
517+
518+ function bindExistingTab(tab) {
519+ reparenter.reparent(tab, tabContainer);
520+
521+ tab.current = tabsModel && tabsModel.currentTab === tab;
522+ tab.focus = tab.current;
523+
524+ tab.webview.currentWebview = browser.currentWebview;
525+ tab.webview.filePicker = filePickerLoader.item;
526+ }
527+
528+ signal newWindowRequested(bool incognito)
529+ signal newWindowFromTab(var tab, var closeMethod)
530+ signal openLinkInWindowRequested(url url, bool incognito)
531
532 Connections {
533 target: currentWebview
534@@ -112,7 +132,7 @@
535
536 Component {
537 id: mediaAccessDialogComponent
538- MediaAccessDialog { }
539+ MediaAccessDialog {}
540 }
541
542 actions: [
543@@ -136,7 +156,7 @@
544 onTriggered: internal.addBookmark(currentWebview.url, currentWebview.title, currentWebview.icon)
545 },
546 Actions.NewTab {
547- onTriggered: browser.openUrlInNewTab("", true)
548+ onTriggered: internal.openUrlInNewTab("", true)
549 },
550 Actions.ClearHistory {
551 onTriggered: HistoryModel.clearAll()
552@@ -150,37 +170,6 @@
553 }
554 ]
555
556- Settings {
557- id: settings
558-
559- property url homepage: settingsDefaults.homepage
560- property string searchEngine: settingsDefaults.searchEngine
561- property bool restoreSession: settingsDefaults.restoreSession
562- property int newTabDefaultSection: settingsDefaults.newTabDefaultSection
563- property string defaultAudioDevice
564- property string defaultVideoDevice
565-
566- function restoreDefaults() {
567- homepage = settingsDefaults.homepage
568- searchEngine = settingsDefaults.searchEngine
569- restoreSession = settingsDefaults.restoreSession
570- newTabDefaultSection = settingsDefaults.newTabDefaultSection
571- defaultAudioDevice = settingsDefaults.defaultAudioDevice
572- defaultVideoDevice = settingsDefaults.defaultVideoDevice
573- }
574- }
575-
576- QtObject {
577- id: settingsDefaults
578-
579- readonly property url homepage: "http://start.ubuntu.com"
580- readonly property string searchEngine: "google"
581- readonly property bool restoreSession: true
582- readonly property int newTabDefaultSection: 0
583- readonly property string defaultAudioDevice: ""
584- readonly property string defaultVideoDevice: ""
585- }
586-
587 FocusScope {
588 id: contentsContainer
589 anchors.fill: parent
590@@ -465,7 +454,7 @@
591
592 onClicked: {
593 recentView.reset()
594- browser.openUrlInNewTab("", true)
595+ internal.openUrlInNewTab("", true)
596 }
597 }
598 }
599@@ -539,7 +528,7 @@
600 }
601
602 onSwitchToTab: internal.switchToTab(index, true)
603- onRequestNewTab: browser.openUrlInNewTab("", makeCurrent, true, index)
604+ onRequestNewTab: internal.openUrlInNewTab("", makeCurrent, true, index)
605 onTabClosed: internal.closeTab(index)
606
607 onFindInPageModeChanged: {
608@@ -554,6 +543,18 @@
609
610 drawerActions: [
611 Action {
612+ objectName: "newwindow"
613+ text: i18n.tr("New window")
614+ iconName: "browser-tabs"
615+ onTriggered: browser.newWindowRequested(false)
616+ },
617+ Action {
618+ objectName: "newprivatewindow"
619+ text: i18n.tr("New private window")
620+ iconName: "private-browsing"
621+ onTriggered: browser.newWindowRequested(true)
622+ },
623+ Action {
624 objectName: "share"
625 text: i18n.tr("Share")
626 iconName: "share"
627@@ -588,24 +589,6 @@
628 onTriggered: downloadsViewLoader.active = true
629 },
630 Action {
631- objectName: "privatemode"
632- text: browser.incognito ? i18n.tr("Leave Private Mode") : i18n.tr("Private Mode")
633- iconName: "private-browsing"
634- iconSource: browser.incognito ? Qt.resolvedUrl("assets/private-browsing-exit.svg") : ""
635- onTriggered: {
636- if (browser.incognito) {
637- if (tabsModel.count > 1) {
638- PopupUtils.open(leavePrivateModeDialog)
639- } else {
640- browser.incognito = false
641- internal.resetFocus()
642- }
643- } else {
644- browser.incognito = true
645- }
646- }
647- },
648- Action {
649 objectName: "settings"
650 text: i18n.tr("Settings")
651 iconName: "settings"
652@@ -842,12 +825,12 @@
653 target: bookmarksViewLoader.item
654
655 onBookmarkEntryClicked: {
656- browser.openUrlInNewTab(url, true)
657+ internal.openUrlInNewTab(url, true)
658 bookmarksViewLoader.active = false
659 }
660 onBack: bookmarksViewLoader.active = false
661 onNewTabClicked: {
662- browser.openUrlInNewTab("", true)
663+ internal.openUrlInNewTab("", true)
664 bookmarksViewLoader.active = false
665 }
666 }
667@@ -907,7 +890,7 @@
668 expandedHistoryViewLoader.model = model
669 expandedHistoryViewLoader.active = true
670 }
671- onNewTabRequested: browser.openUrlInNewTab("", true)
672+ onNewTabRequested: internal.openUrlInNewTab("", true)
673 onBack: historyViewLoader.active = false
674 }
675
676@@ -922,7 +905,7 @@
677 focus: true
678 model: expandedHistoryViewLoader.model
679 onHistoryEntryClicked: {
680- browser.openUrlInNewTab(url, true)
681+ internal.openUrlInNewTab(url, true)
682 historyViewLoader.active = false
683 }
684 onHistoryEntryRemoved: {
685@@ -946,11 +929,11 @@
686
687 onHistoryEntryClicked: {
688 historyViewLoader.active = false
689- browser.openUrlInNewTab(url, true)
690+ internal.openUrlInNewTab(url, true)
691 }
692 onNewTabRequested: {
693 historyViewLoader.active = false
694- browser.openUrlInNewTab("", true)
695+ internal.openUrlInNewTab("", true)
696 }
697 onDone: {
698 historyViewLoader.active = false
699@@ -993,7 +976,7 @@
700 Binding {
701 target: downloadsViewLoader.item
702 property: "downloadManager"
703- value: browser.downloadManager
704+ value: downloadHandlerLoader.item
705 }
706 Binding {
707 target: downloadsViewLoader.item
708@@ -1014,31 +997,6 @@
709 }
710 }
711
712- TabsModel {
713- id: publicTabsModel
714- }
715-
716- Loader {
717- id: privateTabsModelLoader
718-
719- sourceComponent: browser.incognito ? privateTabsModelComponent : undefined
720-
721- Component {
722- id: privateTabsModelComponent
723-
724- TabsModel {
725- Component.onDestruction: {
726- while (count > 0) {
727- var tab = remove(count - 1)
728- if (tab) {
729- tab.close()
730- }
731- }
732- }
733- }
734- }
735- }
736-
737 Loader {
738 id: downloadHandlerLoader
739 source: "DownloadHandler.qml"
740@@ -1050,6 +1008,7 @@
741
742 BrowserTab {
743 anchors.fill: parent
744+ incognito: browser.incognito
745 current: tabsModel && tabsModel.currentTab === this
746 focus: current
747
748@@ -1068,6 +1027,7 @@
749 filePicker: filePickerLoader.item
750
751 anchors.fill: parent
752+
753 focus: true
754
755 enabled: current && !bottomEdgeHandle.dragging && !recentView.visible
756@@ -1086,12 +1046,22 @@
757 Actions.OpenLinkInNewTab {
758 objectName: "OpenLinkInNewTabContextualAction"
759 enabled: contextModel && contextModel.linkUrl.toString()
760- onTriggered: browser.openUrlInNewTab(contextModel.linkUrl, true)
761+ onTriggered: internal.openUrlInNewTab(contextModel.linkUrl, true)
762 }
763 Actions.OpenLinkInNewBackgroundTab {
764 objectName: "OpenLinkInNewBackgroundTabContextualAction"
765 enabled: contextModel && contextModel.linkUrl.toString()
766- onTriggered: browser.openUrlInNewTab(contextModel.linkUrl, false)
767+ onTriggered: internal.openUrlInNewTab(contextModel.linkUrl, false)
768+ }
769+ Actions.OpenLinkInNewWindow {
770+ objectName: "OpenLinkInNewWindowContextualAction"
771+ enabled: contextModel && contextModel.linkUrl.toString()
772+ onTriggered: browser.openLinkInWindowRequested(contextModel.linkUrl, false)
773+ }
774+ Actions.OpenLinkInPrivateWindow {
775+ objectName: "OpenLinkInPrivateWindowContextualAction"
776+ enabled: contextModel && contextModel.linkUrl.toString()
777+ onTriggered: browser.openLinkInWindowRequested(contextModel.linkUrl, true)
778 }
779 Actions.BookmarkLink {
780 objectName: "BookmarkLinkContextualAction"
781@@ -1134,7 +1104,7 @@
782 enabled: contextModel &&
783 (contextModel.mediaType === Oxide.WebView.MediaTypeImage) &&
784 contextModel.srcUrl.toString()
785- onTriggered: browser.openUrlInNewTab(contextModel.srcUrl, true)
786+ onTriggered: internal.openUrlInNewTab(contextModel.srcUrl, true)
787 }
788 Actions.CopyImage {
789 objectName: "CopyImageContextualAction"
790@@ -1156,7 +1126,7 @@
791 enabled: contextModel &&
792 (contextModel.mediaType === Oxide.WebView.MediaTypeVideo) &&
793 contextModel.srcUrl.toString()
794- onTriggered: browser.openUrlInNewTab(contextModel.srcUrl, true)
795+ onTriggered: internal.openUrlInNewTab(contextModel.srcUrl, true)
796 }
797 Actions.SaveVideo {
798 objectName: "SaveVideoContextualAction"
799@@ -1244,7 +1214,7 @@
800 contextMenu: browser.wide ? contextMenuWideComponent : contextMenuNarrowComponent
801
802 onNewViewRequested: {
803- var tab = tabComponent.createObject(tabContainer, {"request": request, 'incognito': browser.incognito})
804+ var tab = tabComponent.createObject(tabContainer, {"request": request})
805 var setCurrent = (request.disposition == Oxide.NewViewRequest.DispositionNewForegroundTab)
806 internal.addTab(tab, setCurrent)
807 if (setCurrent) tabContainer.forceActiveFocus()
808@@ -1263,7 +1233,7 @@
809 tab.close()
810 }
811 if (tabsModel.count === 0) {
812- browser.openUrlInNewTab("", true, true)
813+ internal.openUrlInNewTab("", true, true)
814 }
815 }
816 }
817@@ -1480,15 +1450,6 @@
818 }
819 }
820
821- function getOpenPages() {
822- var urls = []
823- for (var i = 0; i < tabsModel.count; i++) {
824- var url = tabsModel.get(i).url
825- if (url.toString()) urls.push(url) // exclude "new tab" tabs
826- }
827- return urls
828- }
829-
830 function instantiateShareComponent() {
831 var component = Qt.createComponent("../Share.qml")
832 if (component.status == Component.Ready) {
833@@ -1509,6 +1470,18 @@
834 if (share) share.shareText(text)
835 }
836
837+ function openUrlInNewTab(url, setCurrent, load, index) {
838+ load = typeof load !== 'undefined' ? load : true
839+ var tab = tabComponent.createObject(tabContainer, {"initialUrl": url})
840+ addTab(tab, setCurrent, index)
841+ if (load) {
842+ tab.load()
843+ }
844+ if (!url.toString()) {
845+ maybeFocusAddressBar()
846+ }
847+ }
848+
849 function addTab(tab, setCurrent, index) {
850 if (index === undefined) index = tabsModel.add(tab)
851 else index = tabsModel.insert(tab, index)
852@@ -1519,23 +1492,22 @@
853 }
854
855 function closeTab(index) {
856- // Save the incognito state before removing the tab, because
857- // removing the last tab in the model will switch out incognito
858- // mode, thus causing the check below to fail and save the tab
859- // into the undo stack when it should be forgotten instead.
860- var wasIncognito = incognito
861- var tab = tabsModel.remove(index)
862+ var tab = tabsModel.get(index)
863 if (tab) {
864- if (!wasIncognito && tab.url.toString().length > 0) {
865+ if (!incognito && tab.url.toString().length > 0) {
866 closedTabHistory.push({
867- state: session.serializeTabState(tab),
868+ state: serializeTabState(tab),
869 index: index
870 })
871 }
872 tab.close()
873 }
874+ tabsModel.remove(index)
875+ if (tabsModel.currentTab) {
876+ tabsModel.currentTab.load()
877+ }
878 if (tabsModel.count === 0) {
879- browser.openUrlInNewTab("", true)
880+ internal.openUrlInNewTab("", true)
881 recentView.reset()
882 }
883 }
884@@ -1549,7 +1521,7 @@
885 function undoCloseTab() {
886 if (!incognito && closedTabHistory.length > 0) {
887 var tabInfo = closedTabHistory.pop()
888- var tab = session.createTabFromState(tabInfo.state)
889+ var tab = restoreTabState(tabInfo.state)
890 addTab(tab, true, tabInfo.index)
891 }
892 }
893@@ -1601,7 +1573,7 @@
894 if (!browser.currentWebview.url.toString()) {
895 internal.maybeFocusAddressBar()
896 } else {
897- contentsContainer.forceActiveFocus()
898+ contentsContainer.focus = true;
899 }
900 }
901 }
902@@ -1672,296 +1644,12 @@
903 onTriggered: internal.switchToTab(internal.nextTabIndex, false)
904 }
905
906- function openUrlInNewTab(url, setCurrent, load, index) {
907- load = typeof load !== 'undefined' ? load : true
908- var tab = tabComponent.createObject(tabContainer, {"initialUrl": url, 'incognito': browser.incognito})
909- internal.addTab(tab, setCurrent, index)
910- if (load) {
911- tab.load()
912- }
913- if (!url.toString()) {
914- internal.maybeFocusAddressBar()
915- }
916- }
917-
918- SessionStorage {
919- id: session
920-
921- dataFile: dataLocation + "/session.json"
922-
923- function save() {
924- if (!locked) {
925- return
926- }
927- var tabs = []
928- for (var i = 0; i < publicTabsModel.count; ++i) {
929- var tab = publicTabsModel.get(i)
930- tabs.push(serializeTabState(tab))
931- }
932- store(JSON.stringify({tabs: tabs, currentIndex: publicTabsModel.currentIndex}))
933- }
934-
935- property bool restoring: false
936- function restore() {
937- restoring = true
938- _doRestore()
939- restoring = false
940- }
941- function _doRestore() {
942- if (!locked) {
943- return
944- }
945- var state = null
946- try {
947- state = JSON.parse(retrieve())
948- } catch (e) {
949- return
950- }
951- if (state) {
952- var tabs = state.tabs
953- if (tabs) {
954- for (var i = 0; i < Math.min(tabs.length, browser.maxTabsToRestore); ++i) {
955- var tab = createTabFromState(tabs[i])
956- internal.addTab(tab, false)
957- }
958- }
959- if ('currentIndex' in state) {
960- internal.switchToTab(state.currentIndex, false)
961- }
962- }
963- }
964-
965- // Those two functions are used to save/restore the current state of a tab.
966- function serializeTabState(tab) {
967- var state = {}
968- state.uniqueId = tab.uniqueId
969- state.url = tab.url.toString()
970- state.title = tab.title
971- state.icon = tab.icon.toString()
972- state.preview = tab.preview.toString()
973- state.savedState = tab.webview ? tab.webview.currentState : tab.restoreState
974- return state
975- }
976-
977- function createTabFromState(state) {
978- var properties = {'initialUrl': state.url, 'initialTitle': state.title}
979- if ('uniqueId' in state) {
980- properties["uniqueId"] = state.uniqueId
981- }
982- if ('icon' in state) {
983- properties["initialIcon"] = state.icon
984- }
985- if ('preview' in state) {
986- properties["preview"] = state.preview
987- }
988- if ('savedState' in state) {
989- properties['restoreState'] = state.savedState
990- properties['restoreType'] = Oxide.WebView.RestoreLastSessionExitedCleanly
991- }
992- return tabComponent.createObject(tabContainer, properties)
993- }
994- }
995- Timer {
996- id: delayedSessionSaver
997- interval: 500
998- onTriggered: session.save()
999- }
1000- Timer {
1001- // Save session periodically to mitigate state loss when the application crashes
1002- interval: 60000 // every minute
1003- repeat: true
1004- running: !browser.incognito
1005- onTriggered: delayedSessionSaver.restart()
1006- }
1007- Timer {
1008- id: exitFullscreenOnLostFocus
1009- interval: 500
1010- onTriggered: {
1011- if (browser.currentWebview) browser.currentWebview.fullscreen = false
1012- }
1013- }
1014- Connections {
1015- target: Qt.application
1016- onStateChanged: {
1017- if (Qt.application.state != Qt.ApplicationActive) {
1018- if (!browser.incognito) {
1019- session.save()
1020- }
1021- if (browser.currentWebview) {
1022- // Workaround for a desktop bug where changing volume causes
1023- // the app to briefly lose focus to notify-osd, and therefore
1024- // exit fullscreen mode. We prevent this by exiting fullscreen
1025- // only if the focus remains lost for longer than a certain
1026- // threshold. See: https://launchpad.net/bugs/694224.
1027- if (__platformName == "xcb") exitFullscreenOnLostFocus.start()
1028- else browser.currentWebview.fullscreen = false
1029- }
1030- } else exitFullscreenOnLostFocus.stop()
1031- }
1032- onAboutToQuit: {
1033- if (!browser.incognito) {
1034- session.save()
1035- }
1036- }
1037- }
1038- Connections {
1039- target: browser.incognito ? null : publicTabsModel
1040- onCurrentTabChanged: delayedSessionSaver.restart()
1041- onCountChanged: delayedSessionSaver.restart()
1042- }
1043- onIncognitoChanged: {
1044- if (incognito) {
1045- // When going incognito, save the current session right
1046- // away, as periodic session saving is disabled.
1047- session.save()
1048- }
1049- }
1050-
1051- // Schedule various expensive tasks to a point after the initialization and
1052- // first rendering of the application have already happened.
1053- //
1054- // Scheduling a Timer with the shortest non-zero interval possible (1ms) will
1055- // effectively queue its onTriggered function to run immediately after anything
1056- // that is currently in the event loop queue at the moment the Timer starts.
1057- //
1058- // The tasks are:
1059- // - creating the webviews for all initial tabs. This should ideally be done
1060- // asynchronously via object incubation, but http://pad.lv/1359911 prevents it
1061- // - loading the HistoryModel and BookmarksModel from the database
1062- // - deleting any page screenshots that are no longer needed
1063- Timer {
1064- running: true
1065- interval: 1
1066- onTriggered: {
1067- if (!browser.newSession && settings.restoreSession) {
1068- session.restore()
1069- }
1070-
1071- // Sanity check
1072- console.assert(tabsModel.count <= browser.maxTabsToRestore,
1073- "WARNING: too many tabs were restored")
1074- for (var i in browser.initialUrls) {
1075- browser.openUrlInNewTab(browser.initialUrls[i], true, false)
1076- }
1077- if (tabsModel.count == 0) {
1078- browser.openUrlInNewTab(settings.homepage, true, false)
1079- }
1080- if (!delayedTabSwitcher.running) {
1081- tabsModel.currentTab.load()
1082- }
1083- if (!tabsModel.currentTab.url.toString() && !tabsModel.currentTab.restoreState) {
1084- internal.maybeFocusAddressBar()
1085- }
1086-
1087- BookmarksModel.databasePath = dataLocation + "/bookmarks.sqlite"
1088- HistoryModel.databasePath = dataLocation + "/history.sqlite"
1089- DownloadsModel.databasePath = dataLocation + "/downloads.sqlite"
1090-
1091- // Note that the property setter for databasePath won't return until
1092- // the entire model has been loaded, so it is safe to call this here
1093- PreviewManager.cleanUnusedPreviews(internal.getOpenPages())
1094- }
1095- }
1096-
1097- Connections {
1098- target: MemInfo
1099- onFreeChanged: {
1100- var freeMemRatio = (MemInfo.total > 0) ? (MemInfo.free / MemInfo.total) : 1.0
1101- // Under that threshold, available memory is considered "low", and the
1102- // browser is going to try and free up memory from unused tabs. This
1103- // value was chosen empirically, it is subject to change to better
1104- // reflect what a system under memory pressure might look like.
1105- var lowOnMemory = (freeMemRatio < 0.2)
1106- if (lowOnMemory) {
1107- // Unload an inactive tab to (hopefully) free up some memory
1108- function getCandidate(model) {
1109- // Naive implementation that only takes into account the
1110- // last time a tab was current. In the future we might
1111- // want to take into account other parameters such as
1112- // whether the tab is currently playing audio/video.
1113- var candidate = null
1114- for (var i = 0; i < model.count; ++i) {
1115- var tab = model.get(i)
1116- if (tab.current || !tab.webview) {
1117- continue
1118- }
1119- if (!candidate || (candidate.lastCurrent > tab.lastCurrent)) {
1120- candidate = tab
1121- }
1122- }
1123- return candidate
1124- }
1125- var candidate = getCandidate(publicTabsModel)
1126- if (candidate) {
1127- console.warn("Unloading background tab (%1) to free up some memory".arg(candidate.url))
1128- candidate.unload()
1129- return
1130- } else if (browser.incognito) {
1131- candidate = getCandidate(privateTabsModelLoader.item)
1132- if (candidate) {
1133- console.warn("Unloading a background incognito tab to free up some memory")
1134- candidate.unload()
1135- return
1136- }
1137- }
1138- console.warn("System low on memory, but unable to pick a tab to unload")
1139- }
1140- }
1141- }
1142-
1143- Connections {
1144- target: session.restoring ? null : tabsModel
1145- onCurrentIndexChanged: {
1146- // In narrow mode, the tabslist is a stack:
1147- // the current tab is always at the top.
1148- if (!browser.wide) {
1149- tabsModel.move(tabsModel.currentIndex, 0)
1150- }
1151- }
1152+ Connections {
1153+ target: tabsModel
1154 onCurrentTabChanged: {
1155 chrome.findInPageMode = false
1156- var tab = tabsModel.currentTab
1157- if (tab) {
1158- tab.load()
1159- }
1160 internal.resetFocus()
1161 }
1162- onCountChanged: {
1163- if (tabsModel.count == 0) {
1164- if (browser.incognito) {
1165- browser.incognito = false
1166- internal.resetFocus()
1167- } else if (browser.wide) {
1168- Qt.quit()
1169- }
1170- }
1171- }
1172- }
1173-
1174- Component {
1175- id: leavePrivateModeDialog
1176-
1177- LeavePrivateModeDialog {
1178- id: dialogue
1179- objectName: "leavePrivateModeDialog"
1180-
1181- // This dialog inherits from PopupBase, which has a restoreActiveFocus
1182- // function that is called when the dialog is hidden. That keeps the
1183- // focus in the address bar/webview when we leave private mode. So any
1184- // change on the active focus should be done after the run of such
1185- // function
1186- Component.onDestruction: {
1187- if (!browser.incognito) {
1188- internal.resetFocus()
1189- }
1190- }
1191-
1192- onCancelButtonClicked: PopupUtils.close(dialogue)
1193- onOkButtonClicked: {
1194- PopupUtils.close(dialogue)
1195- browser.incognito = false
1196- }
1197- }
1198 }
1199
1200 // TODO: internationalize non-standard key sequences?
1201@@ -2025,7 +1713,7 @@
1202 enabled: tabContainer.visible || recentView.visible ||
1203 bookmarksViewLoader.active || historyViewLoader.active
1204 onActivated: {
1205- openUrlInNewTab("", true)
1206+ internal.openUrlInNewTab("", true)
1207 if (recentView.visible) recentView.reset()
1208 bookmarksViewLoader.active = false
1209 historyViewLoader.active = false
1210@@ -2190,4 +1878,45 @@
1211 source: "ContentPickerDialog.qml"
1212 asynchronous: true
1213 }
1214+
1215+ DropArea {
1216+ anchors {
1217+ fill: parent
1218+ }
1219+ keys: ["webbrowser/tab"]
1220+
1221+ onEntered: item.opacity = 0.5
1222+ onExited: item.opacity = 0
1223+ onDropped: {
1224+ if (drag.source.tabWindow === window) {
1225+ console.debug("Dropped in same window");
1226+ drop.accept(Qt.CopyAction);
1227+ } else {
1228+ console.debug("Dropped in new window, moving tab");
1229+
1230+ window.addTab(drop.getDataAsString("webbrowser/tab"));
1231+ window.tabsModel.currentIndex = window.tabsModel.count - 1;
1232+ window.tabsModel.currentTab.loadExisting(drag.source.tab);
1233+
1234+ drop.accept(Qt.MoveAction);
1235+ }
1236+
1237+ item.opacity = 0;
1238+ }
1239+
1240+ Rectangle {
1241+ id: item
1242+ anchors {
1243+ fill: parent
1244+ }
1245+ color: "#0F0"
1246+ opacity: 0
1247+
1248+ Behavior on opacity {
1249+ NumberAnimation {
1250+
1251+ }
1252+ }
1253+ }
1254+ }
1255 }
1256
1257=== modified file 'src/app/webbrowser/BrowserTab.qml'
1258--- src/app/webbrowser/BrowserTab.qml 2016-05-09 17:54:20 +0000
1259+++ src/app/webbrowser/BrowserTab.qml 2016-09-28 15:27:35 +0000
1260@@ -17,6 +17,7 @@
1261 */
1262
1263 import QtQuick 2.4
1264+import QtQuick.Window 2.2
1265 import Ubuntu.Web 0.2
1266 import com.canonical.Oxide 1.4 as Oxide
1267 import webbrowserapp.private 0.1
1268@@ -96,7 +97,18 @@
1269 }
1270 }
1271 }
1272-
1273+
1274+ function loadExisting(existingTab) {
1275+ if (!webview && !internal.incubator) {
1276+ // Reparent the webview and any other vars
1277+ existingTab.webview.parent = webviewContainer;
1278+ existingTab.webview.tab = tab;
1279+
1280+ // Set the webview into this window
1281+ webviewContainer.webview = existingTab.webview;
1282+ }
1283+ }
1284+
1285 function unload() {
1286 if (webview) {
1287 initialUrl = webview.url
1288@@ -159,6 +171,11 @@
1289 return
1290 }
1291
1292+ if (Window.visibility == Window.Hidden) {
1293+ visible = false
1294+ return
1295+ }
1296+
1297 internal.hiding = true
1298 webview.grabToImage(function(result) {
1299 if (!internal.hiding) {
1300
1301=== modified file 'src/app/webbrowser/CMakeLists.txt'
1302--- src/app/webbrowser/CMakeLists.txt 2015-12-10 09:06:06 +0000
1303+++ src/app/webbrowser/CMakeLists.txt 2016-09-28 15:27:35 +0000
1304@@ -33,7 +33,9 @@
1305
1306 set(WEBBROWSER_APP_SRC
1307 cache-deleter.cpp
1308+ drag-helper.cpp
1309 file-operations.cpp
1310+ reparenter.cpp
1311 searchengine.cpp
1312 webbrowser-app.cpp
1313 )
1314
1315=== removed file 'src/app/webbrowser/LeavePrivateModeDialog.qml'
1316--- src/app/webbrowser/LeavePrivateModeDialog.qml 2016-05-23 13:25:46 +0000
1317+++ src/app/webbrowser/LeavePrivateModeDialog.qml 1970-01-01 00:00:00 +0000
1318@@ -1,43 +0,0 @@
1319-/*
1320- * Copyright 2015-2016 Canonical Ltd.
1321- *
1322- * This file is part of webbrowser-app.
1323- *
1324- * webbrowser-app is free software; you can redistribute it and/or modify
1325- * it under the terms of the GNU General Public License as published by
1326- * the Free Software Foundation; version 3.
1327- *
1328- * webbrowser-app is distributed in the hope that it will be useful,
1329- * but WITHOUT ANY WARRANTY; without even the implied warranty of
1330- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1331- * GNU General Public License for more details.
1332- *
1333- * You should have received a copy of the GNU General Public License
1334- * along with this program. If not, see <http://www.gnu.org/licenses/>.
1335- */
1336-
1337-import QtQuick 2.4
1338-import Ubuntu.Components 1.3
1339-import Ubuntu.Components.Popups 1.3
1340-
1341-Dialog {
1342- title: i18n.tr("Going to public mode will close all private tabs")
1343-
1344- signal cancelButtonClicked()
1345- signal okButtonClicked()
1346-
1347- Button {
1348- objectName: "leavePrivateModeDialog.cancelButton"
1349- anchors { left: parent.left; right: parent.right }
1350- text: i18n.tr("Cancel")
1351- onClicked: cancelButtonClicked()
1352- }
1353-
1354- Button {
1355- objectName: "leavePrivateModeDialog.okButton"
1356- anchors { left: parent.left; right: parent.right }
1357- text: i18n.tr("OK")
1358- color: theme.palette.normal.positive
1359- onClicked: okButtonClicked()
1360- }
1361-}
1362
1363=== modified file 'src/app/webbrowser/TabsBar.qml'
1364--- src/app/webbrowser/TabsBar.qml 2016-02-05 11:21:32 +0000
1365+++ src/app/webbrowser/TabsBar.qml 2016-09-28 15:27:35 +0000
1366@@ -19,6 +19,10 @@
1367 import QtQuick 2.4
1368 import Ubuntu.Components 1.3
1369 import Ubuntu.Components.Popups 1.3
1370+
1371+import webbrowserapp.private 0.1
1372+
1373+import "."
1374 import ".."
1375
1376 Item {
1377@@ -128,21 +132,28 @@
1378 objectName: "tabDelegate"
1379
1380 readonly property int tabIndex: index
1381+ readonly property BrowserTab tab: tabsModel.get(index)
1382+ readonly property BrowserWindow tabWindow: window
1383
1384- anchors.top: tabsContainer.top
1385 property real rightMargin: units.dp(1)
1386 width: tabWidth + rightMargin
1387 height: tabsContainer.height
1388+ y: tabsContainer.y // don't use anchor otherwise drag doesn't work
1389
1390 acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
1391 readonly property bool dragging: drag.active
1392 drag {
1393 target: (pressedButtons === Qt.LeftButton) ? tabDelegate : null
1394- axis: Drag.XAxis
1395+ axis: Drag.XAndYAxis
1396 minimumX: 0
1397 maximumX: root.width - tabDelegate.width
1398 filterChildren: true
1399 }
1400+
1401+ DragHelper {
1402+ id: dragHelper
1403+ source: tabDelegate
1404+ }
1405
1406 TabItem {
1407 anchors.fill: parent
1408@@ -175,7 +186,61 @@
1409 }
1410
1411 Behavior on x { NumberAnimation { duration: 250 } }
1412-
1413+
1414+ NumberAnimation {
1415+ id: resetVerticalAnimation
1416+ target: tabDelegate
1417+ duration: 250
1418+ property: "y"
1419+ to: 0
1420+ }
1421+
1422+ onPositionChanged: {
1423+ if (Math.abs(y) > height) {
1424+ // Reset visual position of tab delegate
1425+ resetVerticalAnimation.start();
1426+
1427+ if (mouse.buttons === Qt.LeftButton) {
1428+ // Generate tab preview for drag handle
1429+ dragHelper.previewUrl = PreviewManager.previewPathFromUrl(tab.url);
1430+
1431+ var dropAction = dragHelper.execDrag(tab.url);
1432+
1433+ if (dropAction === Qt.MoveAction) {
1434+ // Moved into another window
1435+ console.debug("Moved to another window, closing tab");
1436+
1437+ // drag.active does not become false when
1438+ // closing the tab so set reordering back
1439+ repeater.reordering = false;
1440+
1441+ // Just remove from model and do not destory
1442+ // as webview is used in other window
1443+ tabsModel.remove(index);
1444+ } else if (dropAction === Qt.CopyAction) {
1445+ // Moved into the same window
1446+
1447+ // So no action
1448+ console.debug("No action, dropped in same window");
1449+ } else if (dropAction === Qt.IgnoreAction) {
1450+ // Moved outside of any window
1451+ console.debug("Moved outside, generating new window");
1452+
1453+ // drag.active does not become false when
1454+ // closing the tab so set reordering back
1455+ repeater.reordering = false;
1456+
1457+ // callback function only removes from model
1458+ // and not destroy as webview is in new window
1459+ browser.newWindowFromTab(tab, function() { tabsModel.remove(index); })
1460+ } else {
1461+ // Unknown state
1462+ console.debug("Unknown drop action:", dropAction);
1463+ }
1464+ }
1465+ }
1466+ }
1467+ onReleased: resetVerticalAnimation.start();
1468 onXChanged: {
1469 if (!dragging) return
1470 if (x < (index * width - width / 2)) {
1471
1472=== added file 'src/app/webbrowser/drag-helper.cpp'
1473--- src/app/webbrowser/drag-helper.cpp 1970-01-01 00:00:00 +0000
1474+++ src/app/webbrowser/drag-helper.cpp 2016-09-28 15:27:35 +0000
1475@@ -0,0 +1,100 @@
1476+/*
1477+ * Copyright 2016 Canonical Ltd.
1478+ *
1479+ * This file is part of webbrowser-app.
1480+ *
1481+ * webbrowser-app is free software; you can redistribute it and/or modify
1482+ * it under the terms of the GNU General Public License as published by
1483+ * the Free Software Foundation; version 3.
1484+ *
1485+ * webbrowser-app is distributed in the hope that it will be useful,
1486+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1487+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1488+ * GNU General Public License for more details.
1489+ *
1490+ * You should have received a copy of the GNU General Public License
1491+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1492+ */
1493+
1494+#include "drag-helper.h"
1495+
1496+#include <QDrag>
1497+#include <QDropEvent>
1498+#include <QMimeData>
1499+#include <QPainter>
1500+#include <QPixmap>
1501+#include <QSize>
1502+
1503+DragHelper::DragHelper()
1504+{
1505+ m_active = false;
1506+ m_mime_type = QStringLiteral("webbrowser/tab");
1507+ m_preview_url = "";
1508+ m_source = NULL;
1509+}
1510+
1511+Qt::DropAction DragHelper::execDrag(QString tabId)
1512+{
1513+ QDrag *drag = new QDrag(m_source);
1514+
1515+ QMimeData *mimeData = new QMimeData;
1516+ mimeData->setData(mimeType(), tabId.toLatin1());
1517+
1518+ QSize pixmapSize(200, 150);
1519+
1520+ QPixmap pixmap(previewUrl());
1521+
1522+ if (pixmap.isNull()) {
1523+ // If loading pixmap failed, draw a white rectangle
1524+ pixmap = QPixmap(pixmapSize);
1525+ QPainter painter(&pixmap);
1526+ painter.eraseRect(0, 0, pixmapSize.width(), pixmapSize.height());
1527+ painter.fillRect(0, 0, pixmapSize.width(), pixmapSize.height(), QColor(255, 255, 255, 255));
1528+ } else {
1529+ // Scale image to fit the expected size
1530+ pixmap = pixmap.scaled(pixmapSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
1531+ }
1532+
1533+ drag->setHotSpot(QPoint(pixmapSize.width() / 2, pixmapSize.height() / 2));
1534+ drag->setMimeData(mimeData);
1535+ drag->setPixmap(pixmap);
1536+
1537+ return drag->exec(Qt::CopyAction | Qt::MoveAction | Qt::IgnoreAction);
1538+}
1539+
1540+void DragHelper::setActive(bool active)
1541+{
1542+ if (m_active != active) {
1543+ m_active = active;
1544+
1545+ Q_EMIT activeChanged(m_active);
1546+ }
1547+}
1548+
1549+void DragHelper::setMimeType(QString mimeType)
1550+{
1551+ if (m_mime_type != mimeType) {
1552+ m_mime_type = mimeType;
1553+
1554+ Q_EMIT mimeTypeChanged(m_mime_type);
1555+ }
1556+}
1557+
1558+void DragHelper::setPreviewUrl(QString previewUrl)
1559+{
1560+ if (m_preview_url != previewUrl) {
1561+ m_preview_url = previewUrl;
1562+
1563+ Q_EMIT previewUrlChanged(m_preview_url);
1564+ }
1565+}
1566+
1567+void DragHelper::setSource(QQuickItem *source)
1568+{
1569+ if (m_source != source) {
1570+ m_source = source;
1571+
1572+ Q_EMIT sourceChanged(m_source);
1573+ }
1574+}
1575+
1576
1577=== added file 'src/app/webbrowser/drag-helper.h'
1578--- src/app/webbrowser/drag-helper.h 1970-01-01 00:00:00 +0000
1579+++ src/app/webbrowser/drag-helper.h 2016-09-28 15:27:35 +0000
1580@@ -0,0 +1,61 @@
1581+/*
1582+ * Copyright 2016 Canonical Ltd.
1583+ *
1584+ * This file is part of webbrowser-app.
1585+ *
1586+ * webbrowser-app is free software; you can redistribute it and/or modify
1587+ * it under the terms of the GNU General Public License as published by
1588+ * the Free Software Foundation; version 3.
1589+ *
1590+ * webbrowser-app is distributed in the hope that it will be useful,
1591+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1592+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1593+ * GNU General Public License for more details.
1594+ *
1595+ * You should have received a copy of the GNU General Public License
1596+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1597+ */
1598+
1599+#ifndef __DRAGHELPER_H__
1600+#define __DRAGHELPER_H__
1601+
1602+#include <QQuickItem>
1603+#include <QMouseEvent>
1604+#include <QRect>
1605+
1606+class DragHelper : public QQuickItem
1607+{
1608+ Q_OBJECT
1609+
1610+ // TODO: add expectedAction
1611+
1612+ Q_PROPERTY(bool active READ active WRITE setActive NOTIFY activeChanged)
1613+ Q_PROPERTY(QString mimeType READ mimeType WRITE setMimeType NOTIFY mimeTypeChanged)
1614+ Q_PROPERTY(QString previewUrl READ previewUrl WRITE setPreviewUrl NOTIFY previewUrlChanged)
1615+ Q_PROPERTY(QQuickItem* source READ source WRITE setSource NOTIFY sourceChanged)
1616+public:
1617+ DragHelper();
1618+ bool active() { return m_active; }
1619+ QString mimeType() { return m_mime_type; }
1620+ QString previewUrl() { return m_preview_url; }
1621+ QQuickItem *source() { return m_source; }
1622+signals:
1623+ void activeChanged(bool active);
1624+ void mimeTypeChanged(QString mimeType);
1625+ void previewUrlChanged(QString previewUrl);
1626+ void sourceChanged(QQuickItem *source);
1627+public slots:
1628+ Q_INVOKABLE Qt::DropAction execDrag(QString tabId);
1629+ void setActive(bool active);
1630+ void setMimeType(QString mimeType);
1631+ void setPreviewUrl(QString previewUrl);
1632+ void setSource(QQuickItem *source);
1633+private:
1634+ bool m_active;
1635+ QString m_mime_type;
1636+ QString m_preview_url;
1637+ QQuickItem *m_source;
1638+};
1639+
1640+#endif // __DRAGHELPER_H__
1641+
1642
1643=== added file 'src/app/webbrowser/reparenter.cpp'
1644--- src/app/webbrowser/reparenter.cpp 1970-01-01 00:00:00 +0000
1645+++ src/app/webbrowser/reparenter.cpp 2016-09-28 15:27:35 +0000
1646@@ -0,0 +1,36 @@
1647+/*
1648+ * Copyright 2016 Canonical Ltd.
1649+ *
1650+ * This file is part of webbrowser-app.
1651+ *
1652+ * webbrowser-app is free software; you can redistribute it and/or modify
1653+ * it under the terms of the GNU General Public License as published by
1654+ * the Free Software Foundation; version 3.
1655+ *
1656+ * webbrowser-app is distributed in the hope that it will be useful,
1657+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1658+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1659+ * GNU General Public License for more details.
1660+ *
1661+ * You should have received a copy of the GNU General Public License
1662+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1663+ */
1664+
1665+#include "reparenter.h"
1666+
1667+#include <QDebug>
1668+
1669+Reparenter::Reparenter()
1670+{
1671+}
1672+
1673+void Reparenter::reparent(QQuickItem *obj, QQuickItem *newParent)
1674+{
1675+ qDebug() << "Parent:" << obj->parent() << obj->parentItem();
1676+
1677+ obj->setParent(newParent);
1678+ obj->setParentItem(newParent);
1679+
1680+ qDebug() << "Parent:" << obj->parent() << obj->parentItem();
1681+}
1682+
1683
1684=== added file 'src/app/webbrowser/reparenter.h'
1685--- src/app/webbrowser/reparenter.h 1970-01-01 00:00:00 +0000
1686+++ src/app/webbrowser/reparenter.h 2016-09-28 15:27:35 +0000
1687@@ -0,0 +1,36 @@
1688+/*
1689+ * Copyright 2016 Canonical Ltd.
1690+ *
1691+ * This file is part of webbrowser-app.
1692+ *
1693+ * webbrowser-app is free software; you can redistribute it and/or modify
1694+ * it under the terms of the GNU General Public License as published by
1695+ * the Free Software Foundation; version 3.
1696+ *
1697+ * webbrowser-app is distributed in the hope that it will be useful,
1698+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1699+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1700+ * GNU General Public License for more details.
1701+ *
1702+ * You should have received a copy of the GNU General Public License
1703+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1704+ */
1705+
1706+#ifndef __REPARENTER_H__
1707+#define __REPARENTER_H__
1708+
1709+#include <QObject>
1710+#include <QQuickItem>
1711+
1712+class Reparenter : public QQuickItem
1713+{
1714+ Q_OBJECT
1715+
1716+public:
1717+ Reparenter();
1718+
1719+ Q_INVOKABLE void reparent(QQuickItem *obj, QQuickItem *newParent);
1720+};
1721+
1722+#endif // __REPARENTER_H__
1723+
1724
1725=== modified file 'src/app/webbrowser/webbrowser-app.cpp'
1726--- src/app/webbrowser/webbrowser-app.cpp 2016-05-09 22:51:50 +0000
1727+++ src/app/webbrowser/webbrowser-app.cpp 2016-09-28 15:27:35 +0000
1728@@ -21,11 +21,13 @@
1729 #include "cache-deleter.h"
1730 #include "config.h"
1731 #include "downloads-model.h"
1732+#include "drag-helper.h"
1733 #include "file-operations.h"
1734 #include "history-domainlist-model.h"
1735 #include "history-lastvisitdatelist-model.h"
1736 #include "history-model.h"
1737 #include "limit-proxy-model.h"
1738+#include "reparenter.h"
1739 #include "searchengine.h"
1740 #include "text-search-filter-model.h"
1741 #include "tabs-model.h"
1742@@ -39,6 +41,7 @@
1743 #include <QtCore/QTextStream>
1744 #include <QtCore/QtGlobal>
1745 #include <QtCore/QVariant>
1746+#include <QtQml/QQmlProperty>
1747 #include <QtQml/QtQml>
1748 #include <QtQuick/QQuickWindow>
1749
1750@@ -75,6 +78,8 @@
1751 qmlRegisterSingletonType<CacheDeleter>(uri, 0, 1, "CacheDeleter", CacheDeleter_singleton_factory);
1752 qmlRegisterSingletonType<DownloadsModel>(uri, 0, 1, "DownloadsModel", DownloadsModel_singleton_factory);
1753 qmlRegisterType<TextSearchFilterModel>(uri, 0, 1, "TextSearchFilterModel");
1754+ qmlRegisterType<DragHelper>(uri, 0, 1, "DragHelper");
1755+ qmlRegisterType<Reparenter>(uri, 0, 1, "Reparenter");
1756
1757 if (BrowserApplication::initialize("webbrowser/webbrowser-app.qml", QStringLiteral("webbrowser-app"))) {
1758 QStringList searchEnginesSearchPaths;
1759@@ -84,15 +89,19 @@
1760
1761 m_engine->rootContext()->setContextProperty("__platformName", platformName());
1762
1763- m_window->setProperty("newSession", m_arguments.contains("--new-session"));
1764+ m_component->completeCreate();
1765
1766 QVariantList urls;
1767 Q_FOREACH(const QUrl& url, this->urls()) {
1768 urls.append(url);
1769 }
1770- m_window->setProperty("urls", urls);
1771+ bool newSession = m_arguments.contains(QStringLiteral("--new-session"));
1772+ bool incognito = m_arguments.contains(QStringLiteral("--incognito"));
1773+ QMetaObject::invokeMethod(m_object, "init",
1774+ Q_ARG(QVariant, QVariant(urls)),
1775+ Q_ARG(QVariant, newSession),
1776+ Q_ARG(QVariant, incognito));
1777
1778- m_component->completeCreate();
1779 return true;
1780 } else {
1781 return false;
1782@@ -103,15 +112,38 @@
1783 {
1784 QTextStream out(stdout);
1785 QString command = QFileInfo(QCoreApplication::applicationFilePath()).fileName();
1786- out << "Usage: " << command << " [-h|--help] [--fullscreen] [--maximized] [--inspector]"
1787+ out << "Usage: " << command << " [-h|--help] [--inspector]"
1788 << " [--app-id=APP_ID] [--new-session] [URL]" << endl;
1789 out << "Options:" << endl;
1790 out << " -h, --help display this help message and exit" << endl;
1791- out << " --fullscreen display full screen" << endl;
1792- out << " --maximized opens the application maximized" << endl;
1793 out << " --inspector[=PORT] run a remote inspector on a specified port or " << REMOTE_INSPECTOR_PORT << " as the default port" << endl;
1794 out << " --app-id=APP_ID run the application with a specific APP_ID" << endl;
1795 out << " --new-session do not restore open tabs from the last session" << endl;
1796+ out << " --new-window open (the passed URLs in) a new browser window" << endl;
1797+ out << " --incognito open (the passed URLs in) an incognito window" << endl;
1798+}
1799+
1800+void WebbrowserApp::onNewInstanceLaunched(const QStringList& arguments) const
1801+{
1802+ bool newWindow = false;
1803+ bool incognito = false;
1804+ QVariantList urls;
1805+ Q_FOREACH(const QString& argument, arguments) {
1806+ if (argument == QStringLiteral("--new-window")) {
1807+ newWindow = true;
1808+ } else if (argument == QStringLiteral("--incognito")) {
1809+ incognito = true;
1810+ } else if (!argument.startsWith(QStringLiteral("-"))) {
1811+ QUrl url = QUrl::fromUserInput(argument);
1812+ if (url.isValid()) {
1813+ urls.append(url);
1814+ }
1815+ }
1816+ }
1817+ QMetaObject::invokeMethod(m_object, "openUrls",
1818+ Q_ARG(QVariant, QVariant(urls)),
1819+ Q_ARG(QVariant, newWindow),
1820+ Q_ARG(QVariant, incognito));
1821 }
1822
1823 int main(int argc, char** argv)
1824
1825=== modified file 'src/app/webbrowser/webbrowser-app.desktop.in.in'
1826--- src/app/webbrowser/webbrowser-app.desktop.in.in 2015-01-28 17:06:59 +0000
1827+++ src/app/webbrowser/webbrowser-app.desktop.in.in 2016-09-28 15:27:35 +0000
1828@@ -16,3 +16,12 @@
1829 X-Ubuntu-Default-Department-ID=web-browsers
1830 X-Screenshot=@CMAKE_INSTALL_FULL_DATADIR@/webbrowser-app/screenshot.png
1831 X-Ubuntu-Splash-Color=#FFFFFF
1832+Actions=NewWindow;Incognito;
1833+
1834+[Desktop Action NewWindow]
1835+_Name=Open a New Window
1836+Exec=webbrowser-app --new-window
1837+
1838+[Desktop Action Incognito]
1839+_Name=Open a New Private Window
1840+Exec=webbrowser-app --incognito
1841
1842=== modified file 'src/app/webbrowser/webbrowser-app.h'
1843--- src/app/webbrowser/webbrowser-app.h 2016-01-15 09:29:22 +0000
1844+++ src/app/webbrowser/webbrowser-app.h 2016-09-28 15:27:35 +0000
1845@@ -31,7 +31,10 @@
1846 bool initialize();
1847
1848 private:
1849- virtual void printUsage() const;
1850+ void printUsage() const final;
1851+
1852+private Q_SLOTS:
1853+ void onNewInstanceLaunched(const QStringList& arguments) const final;
1854 };
1855
1856 #endif // __WEBBROWSER_APP_H__
1857
1858=== modified file 'src/app/webbrowser/webbrowser-app.qml'
1859--- src/app/webbrowser/webbrowser-app.qml 2016-01-15 09:29:22 +0000
1860+++ src/app/webbrowser/webbrowser-app.qml 2016-09-28 15:27:35 +0000
1861@@ -18,57 +18,463 @@
1862
1863 import QtQuick 2.4
1864 import QtQuick.Window 2.2
1865+import Qt.labs.settings 1.0
1866 import Ubuntu.Components 1.3
1867+import "."
1868 import ".."
1869-
1870-BrowserWindow {
1871- id: window
1872-
1873- property alias urls: browser.initialUrls
1874- property alias newSession: browser.newSession
1875-
1876- currentWebview: browser.currentWebview
1877-
1878- title: {
1879- if (browser.title) {
1880- // TRANSLATORS: %1 refers to the current page’s title
1881- return i18n.tr("%1 - Ubuntu Web Browser").arg(browser.title)
1882- } else {
1883- return i18n.tr("Ubuntu Web Browser")
1884- }
1885- }
1886-
1887- Browser {
1888- id: browser
1889- anchors.fill: parent
1890- webbrowserWindow: webbrowserWindowProxy
1891- developerExtrasEnabled: window.developerExtrasEnabled
1892-
1893- fullscreen: window.visibility === Window.FullScreen
1894-
1895- Component.onCompleted: i18n.domain = "webbrowser-app"
1896-
1897- Keys.onPressed: {
1898- if ((event.key === Qt.Key_F11) && (event.modifiers === Qt.NoModifier)) {
1899- // F11 to toggle application-level fullscreen
1900- window.setFullscreen(window.visibility !== Window.FullScreen)
1901- if (currentWebview.fullscreen) {
1902- currentWebview.fullscreen = false
1903- }
1904- }
1905- }
1906- Keys.onEscapePressed: {
1907- // ESC to exit fullscreen, regardless of whether it was requested
1908- // by the page or toggled on by the user.
1909- window.setFullscreen(false)
1910- currentWebview.fullscreen = false
1911- }
1912- }
1913-
1914- onOpenUrls: {
1915- for (var i = 0; i < urls.length; ++i) {
1916- var setCurrent = (i == urls.length - 1)
1917- browser.openUrlInNewTab(urls[i], setCurrent, setCurrent)
1918+import webbrowsercommon.private 0.1
1919+import webbrowserapp.private 0.1
1920+
1921+QtObject {
1922+ id: webbrowserapp
1923+
1924+ function init(urls, newSession, incognito) {
1925+ i18n.domain = "webbrowser-app"
1926+ if (!newSession && settings.restoreSession && !incognito) {
1927+ session.restore()
1928+ }
1929+ if (allWindows.length == 0) {
1930+ windowFactory.createObject(null, {"incognito": incognito}).show()
1931+ }
1932+ var window = allWindows[allWindows.length - 1]
1933+ for (var i in urls) {
1934+ window.addTab(urls[i]).load()
1935+ window.tabsModel.currentIndex = window.tabsModel.count - 1
1936+ }
1937+ if (window.tabsModel.count == 0) {
1938+ window.addTab(incognito ? "" : settings.homepage).load()
1939+ window.tabsModel.currentIndex = 0
1940+ }
1941+ for (var w in allWindows) {
1942+ allWindows[w].tabsModel.currentTab.load()
1943+ }
1944+
1945+ // FIXME: do this asynchronously
1946+ BookmarksModel.databasePath = dataLocation + "/bookmarks.sqlite"
1947+ HistoryModel.databasePath = dataLocation + "/history.sqlite"
1948+ DownloadsModel.databasePath = dataLocation + "/downloads.sqlite"
1949+
1950+ var doNotCleanUrls = []
1951+ for (var x in allWindows) {
1952+ var tabs = allWindows[x].tabsModel
1953+ for (var t = 0; t < tabs.count; ++t) {
1954+ doNotCleanUrls.push(tabs.get(t).url)
1955+ }
1956+ }
1957+ PreviewManager.cleanUnusedPreviews(doNotCleanUrls)
1958+ }
1959+
1960+ // Array of all windows, sorted chronologically (most recently active last)
1961+ readonly property var allWindows: []
1962+
1963+ function getLastActiveWindow(incognito) {
1964+ for (var i = allWindows.length - 1; i >= 0; --i) {
1965+ var window = allWindows[i]
1966+ if (window.incognito == incognito) {
1967+ return window
1968+ }
1969+ }
1970+ return null
1971+ }
1972+
1973+ function openUrls(urls, newWindow, incognito) {
1974+ var window = getLastActiveWindow(incognito)
1975+ if (!window || newWindow) {
1976+ window = windowFactory.createObject(null, {"incognito": incognito})
1977+ }
1978+ for (var i in urls) {
1979+ window.addTab(urls[i]).load()
1980+ }
1981+ if (window.tabsModel.count == 0) {
1982+ window.addTab().load()
1983+ }
1984+ window.tabsModel.currentIndex = window.tabsModel.count - 1
1985+ window.show()
1986+ window.requestActivate()
1987+ }
1988+
1989+ property Reparenter reparenter: Reparenter {
1990+
1991+ }
1992+
1993+ property var windowFactory: Component {
1994+ BrowserWindow {
1995+ id: window
1996+
1997+ property alias incognito: browser.incognito
1998+ readonly property var tabsModel: browser.tabsModel
1999+
2000+ currentWebview: browser.currentWebview
2001+
2002+ title: {
2003+ if (browser.title) {
2004+ // TRANSLATORS: %1 refers to the current page’s title
2005+ return i18n.tr("%1 - Ubuntu Web Browser").arg(browser.title)
2006+ } else {
2007+ return i18n.tr("Ubuntu Web Browser")
2008+ }
2009+ }
2010+
2011+ onActiveChanged: {
2012+ if (active) {
2013+ var index = allWindows.indexOf(this)
2014+ if (index > -1) {
2015+ allWindows.push(allWindows.splice(index, 1)[0])
2016+ }
2017+ }
2018+ }
2019+
2020+ onClosing: {
2021+ if (allWindows.length == 1) {
2022+ if (tabsModel.count > 0) {
2023+ session.save()
2024+ } else {
2025+ session.clear()
2026+ }
2027+ }
2028+ destroy()
2029+ }
2030+
2031+ Shortcut {
2032+ sequence: StandardKey.Quit
2033+ onActivated: Qt.quit()
2034+ }
2035+
2036+ function toggleApplicationLevelFullscreen() {
2037+ setFullscreen(visibility !== Window.FullScreen)
2038+ if (browser.currentWebview.fullscreen) {
2039+ browser.currentWebview.fullscreen = false
2040+ }
2041+ }
2042+
2043+ Shortcut {
2044+ sequence: StandardKey.FullScreen
2045+ onActivated: window.toggleApplicationLevelFullscreen()
2046+ }
2047+
2048+ Shortcut {
2049+ sequence: "F11"
2050+ onActivated: window.toggleApplicationLevelFullscreen()
2051+ }
2052+
2053+ Shortcut {
2054+ sequence: "Ctrl+N"
2055+ onActivated: browser.newWindowRequested(false)
2056+ }
2057+
2058+ Shortcut {
2059+ sequence: "Ctrl+Shift+N"
2060+ onActivated: browser.newWindowRequested(true)
2061+ }
2062+
2063+ Component.onCompleted: allWindows.push(this)
2064+ Component.onDestruction: {
2065+ for (var w in allWindows) {
2066+ if (this === allWindows[w]) {
2067+ allWindows.splice(w, 1)
2068+ return
2069+ }
2070+ }
2071+ }
2072+
2073+ Browser {
2074+ id: browser
2075+ anchors.fill: parent
2076+ settings: webbrowserapp.settings
2077+ onNewWindowFromTab: {
2078+ var window = windowFactory.createObject(
2079+ null,
2080+ {
2081+ "incognito": tab.incognito,
2082+ "height": parent.height,
2083+ "width": parent.width,
2084+ }
2085+ );
2086+
2087+ window.addExistingTab(tab);
2088+ window.tabsModel.currentIndex = window.tabsModel.count - 1;
2089+ window.show();
2090+ window.requestActivate();
2091+
2092+ window.tabsModel.currentTab.load();
2093+
2094+ closeMethod();
2095+ }
2096+ onNewWindowRequested: {
2097+ var window = windowFactory.createObject(
2098+ null,
2099+ {
2100+ "incognito": incognito,
2101+ "height": parent.height,
2102+ "width": parent.width,
2103+ }
2104+ )
2105+ window.addTab()
2106+ window.tabsModel.currentIndex = 0
2107+ window.tabsModel.currentTab.load()
2108+ window.show()
2109+ }
2110+ onOpenLinkInWindowRequested: {
2111+ var window = null
2112+ if (incognito) {
2113+ window = getLastActiveWindow(true)
2114+ }
2115+ if (!window) {
2116+ window = windowFactory.createObject(
2117+ null,
2118+ {
2119+ "incognito": incognito,
2120+ "height": parent.height,
2121+ "width": parent.width,
2122+ }
2123+ )
2124+ }
2125+ window.addTab(url)
2126+ window.tabsModel.currentIndex = window.tabsModel.count - 1
2127+ window.tabsModel.currentTab.load()
2128+ window.show()
2129+ window.requestActivate()
2130+ }
2131+
2132+ // Not handled as a window-level shortcut as it would take
2133+ // precedence over key events in web content.
2134+ Keys.onEscapePressed: {
2135+ // ESC to exit fullscreen, regardless of whether it was
2136+ // requested by the page or toggled on by the user.
2137+ window.setFullscreen(false)
2138+ browser.currentWebview.fullscreen = false
2139+ }
2140+ }
2141+
2142+ Connections {
2143+ target: window.tabsModel
2144+ onCountChanged: {
2145+ if ((window.tabsModel.count === 0) && browser.wide) {
2146+ window.close()
2147+ }
2148+ }
2149+ }
2150+
2151+ Connections {
2152+ target: window.incognito ? null : window.tabsModel
2153+ onCurrentIndexChanged: delayedSessionSaver.restart()
2154+ onCountChanged: delayedSessionSaver.restart()
2155+ }
2156+
2157+ Connections {
2158+ target: (session.restoring || !window.visible || browser.wide) ? null : window.tabsModel
2159+ onCurrentIndexChanged: {
2160+ // In narrow mode, the tabslist is a stack:
2161+ // the current tab is always at the top.
2162+ window.tabsModel.move(window.tabsModel.currentIndex, 0)
2163+ }
2164+ }
2165+
2166+ function serializeTabState(tab) {
2167+ return browser.serializeTabState(tab)
2168+ }
2169+
2170+ function restoreTabState(state) {
2171+ return browser.restoreTabState(state)
2172+ }
2173+
2174+ function addTab(url) {
2175+ var tab = browser.createTab({"initialUrl": url})
2176+ tabsModel.add(tab)
2177+ return tab
2178+ }
2179+
2180+ function addExistingTab(tab) {
2181+ tabsModel.add(tab);
2182+
2183+ browser.bindExistingTab(tab);
2184+
2185+ return tab;
2186+ }
2187+ }
2188+ }
2189+
2190+ property var settings: Settings {
2191+ property url homepage: "http://start.ubuntu.com"
2192+ property string searchEngine: "google"
2193+ property bool restoreSession: true
2194+ property int newTabDefaultSection: 0
2195+ property string defaultAudioDevice: ""
2196+ property string defaultVideoDevice: ""
2197+
2198+ function restoreDefaults() {
2199+ homepage = "http://start.ubuntu.com"
2200+ searchEngine = "google"
2201+ restoreSession = true
2202+ newTabDefaultSection = 0
2203+ defaultAudioDevice = ""
2204+ defaultVideoDevice = ""
2205+ }
2206+ }
2207+
2208+ // Handle runtime requests to open urls as defined
2209+ // by the freedesktop application dbus interface's open
2210+ // method for DBUS application activation:
2211+ // http://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#dbus
2212+ // The dispatch on the org.freedesktop.Application if is done per appId at the
2213+ // url-dispatcher/upstart level.
2214+ property var openUrlsHandler: Connections {
2215+ target: UriHandler
2216+ onOpened: webbrowserapp.openUrls(uris, false, false)
2217+ }
2218+
2219+ property var session: SessionStorage {
2220+ dataFile: dataLocation + "/session.json"
2221+
2222+ // TODO: do we want to save/restore window positions too (https://launchpad.net/bugs/1312892)?
2223+
2224+ function save() {
2225+ if (!locked || restoring) {
2226+ return
2227+ }
2228+ var windows = []
2229+ for (var w in allWindows) {
2230+ var window = allWindows[w]
2231+ if (window.incognito) {
2232+ continue
2233+ }
2234+ windows.push(serializeWindowState(window))
2235+ }
2236+ if (windows.length > 0) {
2237+ store(JSON.stringify({windows: windows}))
2238+ }
2239+ }
2240+
2241+ property bool restoring: false
2242+ function restore() {
2243+ restoring = true
2244+ _doRestore()
2245+ restoring = false
2246+ }
2247+ function _doRestore() {
2248+ if (!locked) {
2249+ return
2250+ }
2251+ var state = null
2252+ try {
2253+ state = JSON.parse(retrieve())
2254+ } catch (e) {
2255+ return
2256+ }
2257+ if (state) {
2258+ var windows = state.windows
2259+ if (windows) {
2260+ for (var w in windows) {
2261+ restoreWindowState(windows[w])
2262+ }
2263+ } else if (state.tabs) {
2264+ // One-off code path: when launching the app for the first time
2265+ // after the upgrade that adds support for multiple windows, the
2266+ // saved session contains a list of tabs, not windows.
2267+ restoreWindowState(state)
2268+ }
2269+ if (allWindows.length > 0) {
2270+ var window = allWindows[allWindows.length - 1]
2271+ window.requestActivate()
2272+ window.raise()
2273+ }
2274+ }
2275+ }
2276+
2277+ function serializeWindowState(window) {
2278+ var tabs = []
2279+ for (var i = 0; i < window.tabsModel.count; ++i) {
2280+ tabs.push(window.serializeTabState(window.tabsModel.get(i)))
2281+ }
2282+ return {tabs: tabs, currentIndex: window.tabsModel.currentIndex}
2283+ }
2284+
2285+ function restoreWindowState(state) {
2286+ var window = windowFactory.createObject(null)
2287+ for (var i in state.tabs) {
2288+ window.tabsModel.add(window.restoreTabState(state.tabs[i]))
2289+ }
2290+ window.tabsModel.currentIndex = state.currentIndex
2291+ window.show()
2292+ }
2293+
2294+ function clear() {
2295+ if (!locked) {
2296+ return
2297+ }
2298+ store("")
2299+ }
2300+ }
2301+
2302+ property var delayedSessionSaver: Timer {
2303+ interval: 500
2304+ onTriggered: session.save()
2305+ }
2306+
2307+ property var periodicSessionSaver: Timer {
2308+ // Save session periodically to mitigate state loss when the application crashes
2309+ interval: 60000 // every minute
2310+ repeat: true
2311+ running: true
2312+ onTriggered: delayedSessionSaver.restart()
2313+ }
2314+
2315+ property var applicationMonitor: Connections {
2316+ target: Qt.application
2317+ onStateChanged: {
2318+ if (Qt.application.state != Qt.ApplicationActive) {
2319+ session.save()
2320+ }
2321+ }
2322+ onAboutToQuit: {
2323+ if (allWindows.length > 0) {
2324+ session.save()
2325+ }
2326+ }
2327+ }
2328+
2329+ property var memoryPressureMonitor: Connections {
2330+ target: MemInfo
2331+ onFreeChanged: {
2332+ var freeMemRatio = (MemInfo.total > 0) ? (MemInfo.free / MemInfo.total) : 1.0
2333+ // Under that threshold, available memory is considered "low", and the
2334+ // browser is going to try and free up memory from unused tabs. This
2335+ // value was chosen empirically, it is subject to change to better
2336+ // reflect what a system under memory pressure might look like.
2337+ var lowOnMemory = (freeMemRatio < 0.2)
2338+ if (lowOnMemory) {
2339+ // Unload an inactive tab to (hopefully) free up some memory
2340+ function getCandidate(model) {
2341+ // Naive implementation that only takes into account the
2342+ // last time a tab was current. In the future we might
2343+ // want to take into account other parameters such as
2344+ // whether the tab is currently playing audio/video.
2345+ var candidate = null
2346+ for (var i = 0; i < model.count; ++i) {
2347+ var tab = model.get(i)
2348+ if (tab.current || !tab.webview) {
2349+ continue
2350+ }
2351+ if (!candidate || (candidate.lastCurrent > tab.lastCurrent)) {
2352+ candidate = tab
2353+ }
2354+ }
2355+ return candidate
2356+ }
2357+ for (var w in allWindows) {
2358+ var candidate = getCandidate(allWindows[w].tabsModel)
2359+ if (candidate) {
2360+ if (browser.incognito) {
2361+ console.warn("Unloading a background incognito tab to free up some memory")
2362+ } else {
2363+ console.warn("Unloading background tab (%1) to free up some memory".arg(candidate.url))
2364+ }
2365+ candidate.unload()
2366+ return
2367+ }
2368+ }
2369+ console.warn("System low on memory, but unable to pick a tab to unload")
2370+ }
2371 }
2372 }
2373 }
2374
2375=== modified file 'src/app/webcontainer/WebApp.qml'
2376--- src/app/webcontainer/WebApp.qml 2016-08-03 09:28:48 +0000
2377+++ src/app/webcontainer/WebApp.qml 2016-09-28 15:27:35 +0000
2378@@ -33,6 +33,8 @@
2379
2380 currentWebview: containerWebView.currentWebview
2381
2382+ property alias window: containerWebView.window
2383+
2384 property alias url: containerWebView.url
2385
2386 property bool accountSwitcher
2387
2388=== modified file 'src/app/webcontainer/WebViewImplOxide.qml'
2389--- src/app/webcontainer/WebViewImplOxide.qml 2016-07-13 16:23:18 +0000
2390+++ src/app/webcontainer/WebViewImplOxide.qml 2016-09-28 15:27:35 +0000
2391@@ -28,6 +28,10 @@
2392 WebappWebview {
2393 id: webview
2394
2395+ // XXX: Ideally, we would use the Window.window
2396+ // attached property, but it is new in Qt 5.7.
2397+ property Window window
2398+
2399 property bool developerExtrasEnabled: false
2400 property string webappName: ""
2401 property string localUserAgentOverride: ""
2402@@ -261,13 +265,7 @@
2403 function getUnityWebappsProxies() {
2404 var eventHandlers = {
2405 onAppRaised: function () {
2406- if (webbrowserWindow) {
2407- try {
2408- webbrowserWindow.raise();
2409- } catch (e) {
2410- console.debug('Error while raising: ' + e);
2411- }
2412- }
2413+ window.raise();
2414 }
2415 };
2416 return UnityWebAppsUtils.makeProxiesForWebViewBindee(webview, eventHandlers)
2417
2418=== modified file 'src/app/webcontainer/WebappContainerWebview.qml'
2419--- src/app/webcontainer/WebappContainerWebview.qml 2016-07-13 16:23:18 +0000
2420+++ src/app/webcontainer/WebappContainerWebview.qml 2016-09-28 15:27:35 +0000
2421@@ -17,6 +17,7 @@
2422 */
2423
2424 import QtQuick 2.4
2425+import QtQuick.Window 2.2
2426 import Ubuntu.Components 1.3
2427 import Ubuntu.Unity.Action 1.1 as UnityActions
2428 import Ubuntu.UnityWebApps 0.1 as UnityWebApps
2429@@ -26,6 +27,8 @@
2430 FocusScope {
2431 id: containerWebview
2432
2433+ property Window window
2434+
2435 property string url: ""
2436 property bool developerExtrasEnabled: false
2437 property string webappName: ""
2438@@ -115,7 +118,8 @@
2439
2440 webappContainerWebViewLoader.setSource(
2441 webappEngineSource,
2442- { localUserAgentOverride: containerWebview.localUserAgentOverride
2443+ { window: containerWebView.window
2444+ , localUserAgentOverride: containerWebview.localUserAgentOverride
2445 , url: containerWebview.url
2446 , webappName: containerWebview.webappName
2447 , dataPath: dataPath
2448
2449=== modified file 'src/app/webcontainer/webapp-container.cpp'
2450--- src/app/webcontainer/webapp-container.cpp 2016-06-20 15:07:52 +0000
2451+++ src/app/webcontainer/webapp-container.cpp 2016-09-28 15:27:35 +0000
2452@@ -41,6 +41,7 @@
2453 #include <QtQml/QQmlComponent>
2454 #include <QtQml/QQmlContext>
2455 #include <QtQml/QQmlEngine>
2456+#include <QtQml/QQmlProperty>
2457 #include <QtQml>
2458 #include <QtQuick/QQuickWindow>
2459
2460@@ -88,7 +89,9 @@
2461 m_addressBarVisible(false),
2462 m_localWebappManifest(false),
2463 m_openExternalUrlInOverlay(false),
2464- m_webappContainerHelper(new WebappContainerHelper())
2465+ m_webappContainerHelper(new WebappContainerHelper()),
2466+ m_fullscreen(false),
2467+ m_maximized(false)
2468 {
2469 }
2470
2471@@ -135,34 +138,33 @@
2472 QDir searchDir(m_webappModelSearchPath);
2473 searchDir.makeAbsolute();
2474 if (searchDir.exists()) {
2475- m_window->setProperty("webappModelSearchPath", searchDir.path());
2476+ QQmlProperty::write(m_object, QStringLiteral("webappModelSearchPath"), searchDir.path());
2477 }
2478 }
2479 if ( ! m_localCookieStoreDbPath.isEmpty()) {
2480- m_window->setProperty("localCookieStoreDbPath", m_localCookieStoreDbPath);
2481+ QQmlProperty::write(m_object, QStringLiteral("localCookieStoreDbPath"), m_localCookieStoreDbPath);
2482 }
2483
2484- m_window->setProperty("webappName", m_webappName);
2485+ QQmlProperty::write(m_object, QStringLiteral("webappName"), m_webappName);
2486 QFileInfo iconInfo(m_webappIcon);
2487 QUrl iconUrl;
2488 if (iconInfo.isReadable()) {
2489 iconUrl = QUrl::fromLocalFile(iconInfo.absoluteFilePath());
2490 }
2491- m_window->setProperty("webappIcon", iconUrl);
2492- m_window->setProperty("backForwardButtonsVisible", m_backForwardButtonsVisible);
2493- m_window->setProperty("chromeVisible", m_addressBarVisible);
2494- m_window->setProperty("accountProvider", m_accountProvider);
2495- m_window->setProperty("accountSwitcher", m_accountSwitcher);
2496- m_window->setProperty("openExternalUrlInOverlay", m_openExternalUrlInOverlay);
2497- m_window->setProperty("defaultVideoCaptureCameraPosition", m_defaultVideoCaptureCameraPosition);
2498-
2499- m_window->setProperty("webappUrlPatterns", m_webappUrlPatterns);
2500+ QQmlProperty::write(m_object, QStringLiteral("webappIcon"), iconUrl);
2501+ QQmlProperty::write(m_object, QStringLiteral("backForwardButtonsVisible"), m_backForwardButtonsVisible);
2502+ QQmlProperty::write(m_object, QStringLiteral("chromeVisible"), m_addressBarVisible);
2503+ QQmlProperty::write(m_object, QStringLiteral("accountProvider"), m_accountProvider);
2504+ QQmlProperty::write(m_object, QStringLiteral("accountSwitcher"), m_accountSwitcher);
2505+ QQmlProperty::write(m_object, QStringLiteral("openExternalUrlInOverlay"), m_openExternalUrlInOverlay);
2506+ QQmlProperty::write(m_object, QStringLiteral("defaultVideoCaptureCameraPosition"), m_defaultVideoCaptureCameraPosition);
2507+ QQmlProperty::write(m_object, QStringLiteral("webappUrlPatterns"), m_webappUrlPatterns);
2508 QQmlContext* context = m_engine->rootContext();
2509 if (m_storeSessionCookies) {
2510 QString sessionCookieMode = SessionUtils::firstRun(m_webappName) ?
2511 QStringLiteral("persistent") : QStringLiteral("restored");
2512 qDebug() << "Setting session cookie mode to" << sessionCookieMode;
2513- m_window->setProperty("webContextSessionCookieMode", sessionCookieMode);
2514+ QQmlProperty::write(m_object, QStringLiteral("webContextSessionCookieMode"), sessionCookieMode);
2515 }
2516
2517 context->setContextProperty("webappContainerHelper", m_webappContainerHelper.data());
2518@@ -170,35 +172,36 @@
2519 if ( ! m_popupRedirectionUrlPrefixPattern.isEmpty()) {
2520 const QString WEBAPP_CONTAINER_DO_NOT_FILTER_PATTERN_URL_ENV_VAR =
2521 qgetenv("WEBAPP_CONTAINER_DO_NOT_FILTER_PATTERN_URL");
2522- m_window->setProperty(
2523- "popupRedirectionUrlPrefixPattern",
2524- WEBAPP_CONTAINER_DO_NOT_FILTER_PATTERN_URL_ENV_VAR == "1"
2525- ? m_popupRedirectionUrlPrefixPattern
2526- : UrlPatternUtils::transformWebappSearchPatternToSafePattern(
2527- m_popupRedirectionUrlPrefixPattern, false));
2528+ QQmlProperty::write(
2529+ m_object, QStringLiteral("popupRedirectionUrlPrefixPattern"),
2530+ WEBAPP_CONTAINER_DO_NOT_FILTER_PATTERN_URL_ENV_VAR == "1"
2531+ ? m_popupRedirectionUrlPrefixPattern
2532+ : UrlPatternUtils::transformWebappSearchPatternToSafePattern(
2533+ m_popupRedirectionUrlPrefixPattern, false));
2534 }
2535
2536 if (!m_userAgentOverride.isEmpty()) {
2537- m_window->setProperty("localUserAgentOverride", m_userAgentOverride);
2538+ QQmlProperty::write(m_object, QStringLiteral("localUserAgentOverride"), m_userAgentOverride);
2539 }
2540
2541 // Experimental, unsupported API, to override the webview
2542 QFileInfo overrideFile("webview-override.qml");
2543 if (overrideFile.exists()) {
2544- m_window->setProperty("webviewOverrideFile", QUrl::fromLocalFile(overrideFile.absoluteFilePath()));
2545+ QQmlProperty::write(m_object, QStringLiteral("webviewOverrideFile"),
2546+ QUrl::fromLocalFile(overrideFile.absoluteFilePath()));
2547 }
2548
2549 const QString WEBAPP_CONTAINER_BLOCK_OPEN_URL_EXTERNALLY_ENV_VAR =
2550 qgetenv("WEBAPP_CONTAINER_BLOCK_OPEN_URL_EXTERNALLY");
2551 if (WEBAPP_CONTAINER_BLOCK_OPEN_URL_EXTERNALLY_ENV_VAR == "1") {
2552- m_window->setProperty("blockOpenExternalUrls", true);
2553+ QQmlProperty::write(m_object, QStringLiteral("blockOpenExternalUrls"), true);
2554 }
2555
2556 bool runningLocalApp = false;
2557 QList<QUrl> urls = this->urls();
2558 if (!urls.isEmpty()) {
2559 QUrl homeUrl = urls.last();
2560- m_window->setProperty("url", homeUrl);
2561+ QQmlProperty::write(m_object, QStringLiteral("url"), homeUrl);
2562 if (UrlPatternUtils::isLocalHtml5ApplicationHomeUrl(homeUrl)) {
2563 qDebug() << "Started as a local application container.";
2564 runningLocalApp = true;
2565@@ -213,7 +216,7 @@
2566 // webapp-properties.json file pulled from the webapp model element
2567 // or from a default local system install (if any).
2568
2569- m_window->setProperty("runningLocalApplication", runningLocalApp);
2570+ QQmlProperty::write(m_object, QStringLiteral("runningLocalApplication"), runningLocalApp);
2571
2572 // Handle the invalid runtime conditions for the local apps
2573 if (runningLocalApp && !isValidLocalApplicationRunningContext()) {
2574@@ -226,9 +229,12 @@
2575
2576 if (qEnvironmentVariableIsSet("WEBAPP_CONTAINER_BLOCKER_DISABLED")
2577 && QString(qgetenv("WEBAPP_CONTAINER_BLOCKER_DISABLED")) == "1") {
2578- m_window->setProperty("popupBlockerEnabled", false);
2579+ QQmlProperty::write(m_object, QStringLiteral("popupBlockerEnabled"), false);
2580 }
2581
2582+ QQmlProperty::write(m_object, QStringLiteral("forceFullscreen"), m_fullscreen);
2583+ QQmlProperty::write(m_object, QStringLiteral("startMaximized"), m_maximized);
2584+
2585 m_component->completeCreate();
2586
2587 return true;
2588@@ -393,6 +399,10 @@
2589 m_openExternalUrlInOverlay = true;
2590 } else if (argument.startsWith("--camera-capture-default=")) {
2591 m_defaultVideoCaptureCameraPosition = argument.split("--camera-capture-default=")[1];
2592+ } else if (argument == QStringLiteral("--fullscreen")) {
2593+ m_fullscreen = true;
2594+ } else if (argument == QStringLiteral("--maximized")) {
2595+ m_maximized = true;
2596 }
2597 }
2598 }
2599@@ -482,6 +492,21 @@
2600 return urls;
2601 }
2602
2603+void WebappContainer::onNewInstanceLaunched(const QStringList& arguments) const
2604+{
2605+ QVariantList urls;
2606+ Q_FOREACH(const QString& argument, arguments) {
2607+ if (!argument.startsWith(QStringLiteral("-"))) {
2608+ QUrl url = QUrl::fromUserInput(argument);
2609+ if (url.isValid()) {
2610+ urls.append(url);
2611+ }
2612+ }
2613+ }
2614+ QMetaObject::invokeMethod(m_object, "openUrls", Q_ARG(QVariant, QVariant(urls)));
2615+ QMetaObject::invokeMethod(m_object, "requestActivate");
2616+}
2617+
2618 int main(int argc, char** argv)
2619 {
2620 QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
2621
2622=== modified file 'src/app/webcontainer/webapp-container.h'
2623--- src/app/webcontainer/webapp-container.h 2016-06-06 18:46:49 +0000
2624+++ src/app/webcontainer/webapp-container.h 2016-09-28 15:27:35 +0000
2625@@ -1,5 +1,5 @@
2626 /*
2627- * Copyright 2013 Canonical Ltd.
2628+ * Copyright 2013-2016 Canonical Ltd.
2629 *
2630 * This file is part of webbrowser-app.
2631 *
2632@@ -44,7 +44,7 @@
2633 virtual QList<QUrl> urls() const;
2634
2635 private:
2636- virtual void printUsage() const;
2637+ void printUsage() const final;
2638 void earlyEnvironment();
2639 void parseCommandLine();
2640 void parseExtraConfiguration();
2641@@ -56,6 +56,9 @@
2642 void setupLocalSchemeFilterIfAny(QQmlContext* context, const QString& webappSearchPath);
2643 QString appId() const;
2644
2645+private Q_SLOTS:
2646+ void onNewInstanceLaunched(const QStringList& arguments) const final;
2647+
2648 private:
2649 QString m_webappName;
2650 QString m_webappIcon;
2651@@ -74,6 +77,8 @@
2652 QScopedPointer<WebappContainerHelper> m_webappContainerHelper;
2653 QScopedPointer<SchemeFilter> m_schemeFilter;
2654 QString m_defaultVideoCaptureCameraPosition;
2655+ bool m_fullscreen;
2656+ bool m_maximized;
2657
2658 static const QString URL_PATTERN_SEPARATOR;
2659 static const QString LOCAL_SCHEME_FILTER_FILENAME;
2660
2661=== modified file 'src/app/webcontainer/webapp-container.qml'
2662--- src/app/webcontainer/webapp-container.qml 2016-07-13 16:23:18 +0000
2663+++ src/app/webcontainer/webapp-container.qml 2016-09-28 15:27:35 +0000
2664@@ -53,6 +53,8 @@
2665
2666 property bool runningLocalApplication: false
2667
2668+ property bool startMaximized: false
2669+
2670 title: getWindowTitle()
2671
2672 // Used for testing
2673@@ -79,6 +81,8 @@
2674 WebApp {
2675 id: browser
2676
2677+ window: root
2678+
2679 url: accountProvider.length !== 0 ? "" : root.url
2680
2681 accountSwitcher: root.accountSwitcher
2682@@ -108,8 +112,6 @@
2683
2684 anchors.fill: parent
2685
2686- webbrowserWindow: webbrowserWindowProxy
2687-
2688 onWebappNameChanged: {
2689 if (root.webappName !== browser.webappName) {
2690 root.webappName = browser.webappName;
2691@@ -215,6 +217,13 @@
2692
2693 Component.onCompleted: {
2694 i18n.domain = "webbrowser-app"
2695+ if (forceFullscreen) {
2696+ showFullScreen()
2697+ } else if (startMaximized) {
2698+ showMaximized()
2699+ } else {
2700+ show()
2701+ }
2702 }
2703
2704 function showWebView() {
2705@@ -300,7 +309,7 @@
2706 return uri
2707 }
2708
2709- onOpenUrls: {
2710+ function openUrls(urls) {
2711 // only consider the first one (if multiple)
2712 if (urls.length === 0 || !root.currentWebview) {
2713 return;
2714
2715=== modified file 'tests/autopilot/webbrowser_app/__init__.py'
2716--- tests/autopilot/webbrowser_app/__init__.py 2015-05-06 22:33:59 +0000
2717+++ tests/autopilot/webbrowser_app/__init__.py 2016-09-28 15:27:35 +0000
2718@@ -1,6 +1,6 @@
2719 # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2720 #
2721-# Copyright 2013-2015 Canonical
2722+# Copyright 2013-2016 Canonical
2723 #
2724 # This program is free software: you can redistribute it and/or modify it
2725 # under the terms of the GNU General Public License version 3, as published
2726@@ -39,3 +39,6 @@
2727 @property
2728 def main_window(self):
2729 return self.select_single(browser.Browser)
2730+
2731+ def get_windows(self, **kwargs):
2732+ return self.select_many(browser.Browser, **kwargs)
2733
2734=== modified file 'tests/autopilot/webbrowser_app/emulators/browser.py'
2735--- tests/autopilot/webbrowser_app/emulators/browser.py 2016-05-03 11:28:56 +0000
2736+++ tests/autopilot/webbrowser_app/emulators/browser.py 2016-09-28 15:27:35 +0000
2737@@ -52,39 +52,6 @@
2738 def go_forward(self):
2739 self.chrome.go_forward()
2740
2741- @autopilot.logging.log_action(logger.info)
2742- def enter_private_mode(self):
2743- if not self.is_in_private_mode():
2744- self.chrome.toggle_private_mode()
2745- else:
2746- logger.warning('The browser is already in private mode.')
2747-
2748- def is_in_private_mode(self):
2749- return self.get_current_webview().incognito
2750-
2751- @autopilot.logging.log_action(logger.info)
2752- def leave_private_mode(self):
2753- if self.is_in_private_mode():
2754- self.chrome.toggle_private_mode()
2755- else:
2756- logger.warning('The browser is not in private mode.')
2757-
2758- @autopilot.logging.log_action(logger.info)
2759- def leave_private_mode_with_confirmation(self, confirm=True):
2760- if self.is_in_private_mode():
2761- self.chrome.toggle_private_mode()
2762- dialog = self._get_leave_private_mode_dialog()
2763- if confirm:
2764- dialog.confirm()
2765- else:
2766- dialog.cancel()
2767- dialog.wait_until_destroyed()
2768- else:
2769- logger.warning('The browser is not in private mode.')
2770-
2771- def _get_leave_private_mode_dialog(self):
2772- return self.wait_select_single(LeavePrivateModeDialog, visible=True)
2773-
2774 # Since the NewPrivateTabView does not define any new QML property in its
2775 # extended file, it does not report itself to autopilot with the same name
2776 # as the extended file. (See http://pad.lv/1454394)
2777@@ -307,13 +274,6 @@
2778 forward_button = self._get_forward_button()
2779 return forward_button.enabled
2780
2781- def toggle_private_mode(self):
2782- drawer_button = self.get_drawer_button()
2783- self.pointing_device.click_object(drawer_button)
2784- self.get_drawer()
2785- private_mode_action = self.get_drawer_action("privatemode")
2786- self.pointing_device.click_object(private_mode_action)
2787-
2788 def get_drawer_button(self):
2789 return self.select_single("ChromeButton", objectName="drawerButton")
2790
2791@@ -573,21 +533,6 @@
2792 key=lambda item: item.globalRect.y)
2793
2794
2795-class LeavePrivateModeDialog(uitk.Dialog):
2796-
2797- @autopilot.logging.log_action(logger.info)
2798- def confirm(self):
2799- confirm_button = self.select_single(
2800- "Button", objectName="leavePrivateModeDialog.okButton")
2801- self.pointing_device.click_object(confirm_button)
2802-
2803- @autopilot.logging.log_action(logger.info)
2804- def cancel(self):
2805- cancel_button = self.select_single(
2806- "Button", objectName="leavePrivateModeDialog.cancelButton")
2807- self.pointing_device.click_object(cancel_button)
2808-
2809-
2810 class NewTabView(uitk.UbuntuUIToolkitCustomProxyObjectBase):
2811
2812 def get_bookmarks_more_button(self):
2813
2814=== modified file 'tests/autopilot/webbrowser_app/tests/__init__.py'
2815--- tests/autopilot/webbrowser_app/tests/__init__.py 2016-03-21 16:13:49 +0000
2816+++ tests/autopilot/webbrowser_app/tests/__init__.py 2016-09-28 15:27:35 +0000
2817@@ -176,6 +176,28 @@
2818
2819 return new_tab_view
2820
2821+ def open_new_window(self):
2822+ windows = self.app.get_windows(incognito=False)
2823+ chrome = self.main_window.chrome
2824+ drawer_button = chrome.get_drawer_button()
2825+ self.pointing_device.click_object(drawer_button)
2826+ chrome.get_drawer()
2827+ new_window_action = chrome.get_drawer_action("newwindow")
2828+ self.pointing_device.click_object(new_window_action)
2829+ self.assertThat(lambda: len(self.app.get_windows(incognito=False)),
2830+ Eventually(Equals(len(windows) + 1)))
2831+
2832+ def open_new_private_window(self):
2833+ windows = self.app.get_windows(incognito=True)
2834+ chrome = self.main_window.chrome
2835+ drawer_button = chrome.get_drawer_button()
2836+ self.pointing_device.click_object(drawer_button)
2837+ chrome.get_drawer()
2838+ new_window_action = chrome.get_drawer_action("newprivatewindow")
2839+ self.pointing_device.click_object(new_window_action)
2840+ self.assertThat(lambda: len(self.app.get_windows(incognito=True)),
2841+ Eventually(Equals(len(windows) + 1)))
2842+
2843 def open_settings(self):
2844 chrome = self.main_window.chrome
2845 drawer_button = chrome.get_drawer_button()
2846
2847=== modified file 'tests/autopilot/webbrowser_app/tests/test_contextmenu.py'
2848--- tests/autopilot/webbrowser_app/tests/test_contextmenu.py 2016-03-07 11:35:28 +0000
2849+++ tests/autopilot/webbrowser_app/tests/test_contextmenu.py 2016-09-28 15:27:35 +0000
2850@@ -54,6 +54,19 @@
2851 self.assertThat(webview.url,
2852 Eventually(StartsWith(self.data_uri_prefix)))
2853
2854+ def verify_link_opened_in_a_new_window(self, incognito):
2855+ self.assertThat(lambda: len(self.app.get_windows(incognito=incognito)),
2856+ Eventually(Equals(1 if incognito else 2)))
2857+ windows = self.app.get_windows()
2858+ urls = [window.get_current_webview().url for window in windows]
2859+ self.assertThat(sorted(urls),
2860+ Equals(sorted([self.base_url + "/test1", self.url])))
2861+ if incognito:
2862+ windows = self.app.get_windows(incognito=True)
2863+ self.assertThat(len(windows), Equals(1))
2864+ self.assertThat(windows[0].get_current_webview().url,
2865+ Equals(self.base_url + "/test1"))
2866+
2867
2868 class TestContextMenuLink(TestContextMenuBase):
2869
2870@@ -66,6 +79,14 @@
2871 self.menu.click_action("OpenLinkInNewTabContextualAction")
2872 self.verify_link_opened_in_a_new_tab()
2873
2874+ def test_open_link_in_new_window(self):
2875+ self.menu.click_action("OpenLinkInNewWindowContextualAction")
2876+ self.verify_link_opened_in_a_new_window(False)
2877+
2878+ def test_open_link_in_private_window(self):
2879+ self.menu.click_action("OpenLinkInPrivateWindowContextualAction")
2880+ self.verify_link_opened_in_a_new_window(True)
2881+
2882 def test_bookmark_link(self):
2883 self.menu.click_action("BookmarkLinkContextualAction")
2884 bookmark_options = self.main_window.get_bookmark_options()
2885@@ -111,6 +132,14 @@
2886 self.menu.click_action("OpenLinkInNewTabContextualAction")
2887 self.verify_link_opened_in_a_new_tab()
2888
2889+ def test_open_link_in_new_window(self):
2890+ self.menu.click_action("OpenLinkInNewWindowContextualAction")
2891+ self.verify_link_opened_in_a_new_window(False)
2892+
2893+ def test_open_link_in_private_window(self):
2894+ self.menu.click_action("OpenLinkInPrivateWindowContextualAction")
2895+ self.verify_link_opened_in_a_new_window(True)
2896+
2897 def test_bookmark_link(self):
2898 self.menu.click_action("BookmarkLinkContextualAction")
2899 bookmark_options = self.main_window.get_bookmark_options()
2900
2901=== modified file 'tests/autopilot/webbrowser_app/tests/test_fullscreen.py'
2902--- tests/autopilot/webbrowser_app/tests/test_fullscreen.py 2015-07-03 16:04:21 +0000
2903+++ tests/autopilot/webbrowser_app/tests/test_fullscreen.py 2016-09-28 15:27:35 +0000
2904@@ -98,57 +98,3 @@
2905 self.main_window.press_key('F11')
2906 self.assert_window_fullscreen(False)
2907 self.assert_webview_fullscreen(False)
2908-
2909-
2910-class TestForcedFullscreen(TestFullscreenBase):
2911-
2912- def setUp(self):
2913- self.ARGS = self.ARGS + ["--fullscreen"]
2914- super(TestForcedFullscreen, self).setUp(path="/fullscreen")
2915- self.assert_window_fullscreen(True)
2916- self.assert_webview_fullscreen(False)
2917-
2918- def tearDown(self):
2919- super(TestForcedFullscreen, self).tearDown()
2920- self.assert_window_fullscreen(True)
2921-
2922- @testtools.skipIf(model() == "Desktop", "on touch devices only")
2923- def test_disabled_user_exit_swipe_up(self):
2924- self.open_tabs_view()
2925-
2926- @testtools.skipIf(model() != "Desktop", "on desktop only")
2927- def test_disabled_user_exit_ESC(self):
2928- self.main_window.press_key('Escape')
2929-
2930- @testtools.skipIf(model() != "Desktop", "on desktop only")
2931- def test_disabled_user_exit_F11(self):
2932- self.main_window.press_key('F11')
2933-
2934- def trigger_page_fullscreen(self):
2935- webview = self.main_window.get_current_webview()
2936- self.pointing_device.click_object(webview)
2937- self.assert_webview_fullscreen(True)
2938-
2939- def test_page_exit(self):
2940- self.trigger_page_fullscreen()
2941- webview = self.main_window.get_current_webview()
2942- self.pointing_device.click_object(webview)
2943- self.assert_webview_fullscreen(False)
2944-
2945- @testtools.skipIf(model() == "Desktop", "on touch devices only")
2946- def test_user_exit_swipe_up(self):
2947- self.trigger_page_fullscreen()
2948- self.open_tabs_view()
2949- self.assert_webview_fullscreen(False)
2950-
2951- @testtools.skipIf(model() != "Desktop", "on desktop only")
2952- def test_user_exit_ESC(self):
2953- self.trigger_page_fullscreen()
2954- self.main_window.press_key('Escape')
2955- self.assert_webview_fullscreen(False)
2956-
2957- @testtools.skipIf(model() != "Desktop", "on desktop only")
2958- def test_user_exit_F11(self):
2959- self.trigger_page_fullscreen()
2960- self.main_window.press_key('F11')
2961- self.assert_webview_fullscreen(False)
2962
2963=== modified file 'tests/autopilot/webbrowser_app/tests/test_keyboard.py'
2964--- tests/autopilot/webbrowser_app/tests/test_keyboard.py 2016-05-23 15:49:49 +0000
2965+++ tests/autopilot/webbrowser_app/tests/test_keyboard.py 2016-09-28 15:27:35 +0000
2966@@ -572,3 +572,25 @@
2967 self.check_tab_number(0)
2968 webview = self.main_window.get_current_webview()
2969 self.assertThat(webview.zoomFactor, Eventually(AlmostEquals(1.1)))
2970+
2971+ def test_new_window(self):
2972+ self.main_window.press_key('Ctrl+n')
2973+ self.assertThat(lambda: len(self.app.get_windows(incognito=False)),
2974+ Eventually(Equals(2)))
2975+ window = self.app.get_windows(activeFocus=True)[0]
2976+ if window.wide:
2977+ window.press_key('Ctrl+w')
2978+ window.wait_until_destroyed()
2979+ windows = self.app.get_windows(activeFocus=True, incognito=False)
2980+ self.assertThat(len(windows), Equals(1))
2981+
2982+ def test_new_private_window(self):
2983+ self.main_window.press_key('Ctrl+Shift+n')
2984+ self.assertThat(lambda: len(self.app.get_windows(incognito=True)),
2985+ Eventually(Equals(1)))
2986+ window = self.app.get_windows(activeFocus=True, incognito=True)[0]
2987+ if window.wide:
2988+ window.press_key('Ctrl+w')
2989+ window.wait_until_destroyed()
2990+ windows = self.app.get_windows(activeFocus=True, incognito=False)
2991+ self.assertThat(len(windows), Equals(1))
2992
2993=== added file 'tests/autopilot/webbrowser_app/tests/test_multiple_windows.py'
2994--- tests/autopilot/webbrowser_app/tests/test_multiple_windows.py 1970-01-01 00:00:00 +0000
2995+++ tests/autopilot/webbrowser_app/tests/test_multiple_windows.py 2016-09-28 15:27:35 +0000
2996@@ -0,0 +1,40 @@
2997+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2998+#
2999+# Copyright 2016 Canonical
3000+#
3001+# This program is free software: you can redistribute it and/or modify it
3002+# under the terms of the GNU General Public License version 3, as published
3003+# by the Free Software Foundation.
3004+#
3005+# This program is distributed in the hope that it will be useful,
3006+# but WITHOUT ANY WARRANTY; without even the implied warranty of
3007+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3008+# GNU General Public License for more details.
3009+#
3010+# You should have received a copy of the GNU General Public License
3011+# along with this program. If not, see <http://www.gnu.org/licenses/>.
3012+
3013+from testtools.matchers import Equals
3014+
3015+from webbrowser_app.tests import StartOpenRemotePageTestCaseBase
3016+
3017+
3018+class TestMultipleWindows(StartOpenRemotePageTestCaseBase):
3019+
3020+ def test_open_new_window(self):
3021+ windows = self.app.get_windows()
3022+ self.assertThat(len(windows), Equals(1))
3023+ self.open_new_window()
3024+ windows = self.app.get_windows()
3025+ self.assertThat(len(windows), Equals(2))
3026+ for window in windows:
3027+ self.assertThat(window.incognito, Equals(False))
3028+
3029+ def test_open_new_private_window(self):
3030+ windows = self.app.get_windows()
3031+ self.assertThat(len(windows), Equals(1))
3032+ self.open_new_private_window()
3033+ windows = self.app.get_windows()
3034+ self.assertThat(len(windows), Equals(2))
3035+ self.assertThat(len(self.app.get_windows(incognito=False)), Equals(1))
3036+ self.assertThat(len(self.app.get_windows(incognito=True)), Equals(1))
3037
3038=== modified file 'tests/autopilot/webbrowser_app/tests/test_new_tab_view.py'
3039--- tests/autopilot/webbrowser_app/tests/test_new_tab_view.py 2016-06-15 16:21:51 +0000
3040+++ tests/autopilot/webbrowser_app/tests/test_new_tab_view.py 2016-09-28 15:27:35 +0000
3041@@ -95,55 +95,6 @@
3042 self.assertThat(new_tab_view.visible, Equals(True))
3043
3044
3045-class TestNewPrivateTabViewLifetime(StartOpenRemotePageTestCaseBase):
3046-
3047- def test_new_private_tab_view_destroyed_when_browsing(self):
3048- self.main_window.enter_private_mode()
3049- new_private_tab_view = self.main_window.get_new_private_tab_view()
3050- self.main_window.go_to_url(self.base_url + "/test2")
3051- new_private_tab_view.wait_until_destroyed()
3052-
3053- def test_new_private_tab_view_destroyed_when_leaving_private_mode(self):
3054- self.main_window.enter_private_mode()
3055- new_private_tab_view = self.main_window.get_new_private_tab_view()
3056- self.main_window.leave_private_mode()
3057- new_private_tab_view.wait_until_destroyed()
3058-
3059- def test_new_private_tab_view_is_shared_between_tabs(self):
3060- self.main_window.enter_private_mode()
3061- new_private_tab_view = self.main_window.get_new_private_tab_view()
3062- self.main_window.go_to_url(self.base_url + "/test2")
3063- new_private_tab_view.wait_until_destroyed()
3064- # Open one new private tab
3065- new_private_tab_view = self.open_new_tab(open_tabs_view=True)
3066- # Open a second new private tab
3067- new_private_tab_view_2 = self.open_new_tab(open_tabs_view=True)
3068- # Verify that they share the same NewPrivateTabView instance
3069- self.assertThat(new_private_tab_view_2.id,
3070- Equals(new_private_tab_view.id))
3071- # Close the second new private tab, and verify that the
3072- # NewPrivateTabView instance is still there
3073- if self.main_window.wide:
3074- self.main_window.chrome.get_tabs_bar().close_tab(2)
3075- else:
3076- tabs_view = self.open_tabs_view()
3077- tabs_view.get_previews()[0].close()
3078- toolbar = self.main_window.get_recent_view_toolbar()
3079- toolbar.click_button("doneButton")
3080- tabs_view.visible.wait_for(False)
3081- self.assertThat(new_private_tab_view.visible, Equals(True))
3082- # Close the first new private tab, and verify that the
3083- # NewPrivateTabView instance is destroyed
3084- if self.main_window.wide:
3085- self.main_window.chrome.get_tabs_bar().close_tab(1)
3086- else:
3087- tabs_view = self.open_tabs_view()
3088- tabs_view.get_previews()[0].close()
3089- toolbar = self.main_window.get_recent_view_toolbar()
3090- toolbar.click_button("doneButton")
3091- new_private_tab_view.wait_until_destroyed()
3092-
3093-
3094 class TestNewTabViewContentsBase(StartOpenRemotePageTestCaseBase):
3095
3096 def setUp(self):
3097
3098=== removed file 'tests/autopilot/webbrowser_app/tests/test_private.py'
3099--- tests/autopilot/webbrowser_app/tests/test_private.py 2016-03-29 20:08:14 +0000
3100+++ tests/autopilot/webbrowser_app/tests/test_private.py 1970-01-01 00:00:00 +0000
3101@@ -1,115 +0,0 @@
3102-# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
3103-#
3104-# Copyright 2015-2016 Canonical
3105-#
3106-# This program is free software: you can redistribute it and/or modify it
3107-# under the terms of the GNU General Public License version 3, as published
3108-# by the Free Software Foundation.
3109-#
3110-# This program is distributed in the hope that it will be useful,
3111-# but WITHOUT ANY WARRANTY; without even the implied warranty of
3112-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3113-# GNU General Public License for more details.
3114-#
3115-# You should have received a copy of the GNU General Public License
3116-# along with this program. If not, see <http://www.gnu.org/licenses/>.
3117-
3118-from testtools.matchers import Equals, NotEquals
3119-from autopilot.matchers import Eventually
3120-from autopilot.platform import model
3121-
3122-from webbrowser_app.tests import StartOpenRemotePageTestCaseBase
3123-
3124-
3125-class TestPrivateView(StartOpenRemotePageTestCaseBase):
3126-
3127- def test_going_in_and_out_private_mode(self):
3128- address_bar = self.main_window.address_bar
3129- self.main_window.enter_private_mode()
3130- self.assertThat(self.main_window.is_in_private_mode,
3131- Eventually(Equals(True)))
3132- self.assert_number_incognito_webviews_eventually(1)
3133- self.assertTrue(self.main_window.is_new_private_tab_view_visible())
3134- self.assertThat(address_bar.activeFocus,
3135- Eventually(Equals(model() == 'Desktop')))
3136- self.assertThat(address_bar.text, Eventually(Equals("")))
3137-
3138- self.main_window.leave_private_mode()
3139- self.assertThat(self.main_window.is_in_private_mode,
3140- Eventually(Equals(False)))
3141- self.assert_number_incognito_webviews_eventually(0)
3142- self.assertThat(address_bar.text, Eventually(NotEquals("")))
3143- self.assertThat(address_bar.activeFocus, Eventually(Equals(False)))
3144-
3145- def test_leaving_private_mode_with_multiples_tabs_ask_confirmation(self):
3146- self.main_window.enter_private_mode()
3147- self.assertThat(self.main_window.is_in_private_mode,
3148- Eventually(Equals(True)))
3149- self.assertTrue(self.main_window.is_new_private_tab_view_visible())
3150- if not self.main_window.wide:
3151- self.open_tabs_view()
3152- self.open_new_tab()
3153- self.main_window.leave_private_mode_with_confirmation()
3154- self.assertThat(self.main_window.is_in_private_mode,
3155- Eventually(Equals(False)))
3156-
3157- def test_cancel_leaving_private_mode(self):
3158- self.main_window.enter_private_mode()
3159- self.assertThat(self.main_window.is_in_private_mode,
3160- Eventually(Equals(True)))
3161- self.assertTrue(self.main_window.is_new_private_tab_view_visible())
3162- if not self.main_window.wide:
3163- self.open_tabs_view()
3164- self.open_new_tab()
3165- self.main_window.leave_private_mode_with_confirmation(confirm=False)
3166- self.assertThat(self.main_window.is_in_private_mode,
3167- Eventually(Equals(True)))
3168- self.assertTrue(self.main_window.is_new_private_tab_view_visible())
3169-
3170- def test_url_showing_in_top_sites_in_and_out_private_mode(self):
3171- new_tab = self.open_new_tab(open_tabs_view=True)
3172- urls = [site.url for site in new_tab.get_top_site_items()]
3173- self.assertIn(self.url, urls)
3174-
3175- self.main_window.enter_private_mode()
3176- self.assertThat(self.main_window.is_in_private_mode,
3177- Eventually(Equals(True)))
3178- url = self.base_url + "/test2"
3179- self.main_window.go_to_url(url)
3180- self.main_window.wait_until_page_loaded(url)
3181- self.main_window.leave_private_mode()
3182- self.assertThat(self.main_window.is_in_private_mode,
3183- Eventually(Equals(False)))
3184-
3185- new_tab = self.open_new_tab(open_tabs_view=True)
3186- urls = [site.url for site in new_tab.get_top_site_items()]
3187- self.assertNotIn(url, urls)
3188-
3189- def test_public_tabs_should_not_be_visible_in_private_mode(self):
3190- self.open_new_tab(open_tabs_view=True)
3191- new_tab_view = self.main_window.get_new_tab_view()
3192- url = self.base_url + "/test2"
3193- self.main_window.go_to_url(url)
3194- new_tab_view.wait_until_destroyed()
3195- if self.main_window.wide:
3196- tabs = self.main_window.chrome.get_tabs_bar().get_tabs()
3197- self.assertThat(len(tabs), Equals(2))
3198- else:
3199- tabs_view = self.open_tabs_view()
3200- previews = tabs_view.get_previews()
3201- self.assertThat(len(previews), Equals(2))
3202- toolbar = self.main_window.get_recent_view_toolbar()
3203- toolbar.click_button("doneButton")
3204- tabs_view.visible.wait_for(False)
3205-
3206- self.main_window.enter_private_mode()
3207- self.assertThat(self.main_window.is_in_private_mode,
3208- Eventually(Equals(True)))
3209- self.assertTrue(self.main_window.is_new_private_tab_view_visible())
3210- if self.main_window.wide:
3211- tabs = self.main_window.chrome.get_tabs_bar().get_tabs()
3212- self.assertThat(len(tabs), Equals(1))
3213- else:
3214- tabs_view = self.open_tabs_view()
3215- previews = tabs_view.get_previews()
3216- self.assertThat(len(previews), Equals(1))
3217
3218=== modified file 'tests/autopilot/webbrowser_app/tests/test_tabs.py'
3219--- tests/autopilot/webbrowser_app/tests/test_tabs.py 2016-01-21 10:29:17 +0000
3220+++ tests/autopilot/webbrowser_app/tests/test_tabs.py 2016-09-28 15:27:35 +0000
3221@@ -319,48 +319,3 @@
3222 self.main_window.press_key('Ctrl+Shift+t')
3223 self.assert_number_webviews_eventually(3)
3224 self.check_current_tab(url2)
3225-
3226- @testtools.skipIf(model() != "Desktop", "on desktop only")
3227- def test_undo_close_tab_incognito(self):
3228- start_url = self.main_window.get_current_webview().url
3229- self.open_new_tab(open_tabs_view=not self.main_window.wide)
3230- url = self.base_url + "/tab/1"
3231- self.main_window.go_to_url(url)
3232- self.main_window.wait_until_page_loaded(url)
3233-
3234- self.main_window.press_key('Ctrl+w')
3235- self.assert_number_webviews_eventually(1)
3236- self.check_current_tab(start_url)
3237-
3238- incognito_url = self.base_url + "/tab/2"
3239- self.main_window.enter_private_mode()
3240- self.assertThat(self.main_window.is_in_private_mode,
3241- Eventually(Equals(True)))
3242- self.main_window.go_to_url(incognito_url)
3243- self.main_window.wait_until_page_loaded(incognito_url)
3244-
3245- self.open_new_tab(open_tabs_view=not self.main_window.wide)
3246- self.main_window.go_to_url(incognito_url)
3247- self.main_window.wait_until_page_loaded(incognito_url)
3248- self.main_window.press_key('Ctrl+w')
3249- self.assert_number_incognito_webviews_eventually(1)
3250-
3251- # Test that no tabs will be restored in incognito mode.
3252- # We sleep for a bit after the keypress to give more confidence that
3253- # the tab still hasn't appeared within a reasonable amount of time.
3254- self.main_window.press_key('Ctrl+Shift+w')
3255- time.sleep(1)
3256- self.assert_number_incognito_webviews_eventually(1)
3257-
3258- # Close the last incognito tab to exit the mode. This is done on
3259- # purpose instead of using leave_private_mode since we want to
3260- # make sure the last tab is also not saved, as it is a corner case
3261- self.main_window.press_key('Ctrl+w')
3262- self.assertThat(self.main_window.is_in_private_mode,
3263- Eventually(Equals(False)))
3264-
3265- # Tabs that we closed before going incognito will be restored
3266- # when going back to default mode
3267- self.main_window.press_key('Ctrl+Shift+w')
3268- self.assert_number_webviews_eventually(2)
3269- self.check_current_tab(url)
3270
3271=== added file 'tests/unittests/qml/tst_BrowserWindow.qml'
3272--- tests/unittests/qml/tst_BrowserWindow.qml 1970-01-01 00:00:00 +0000
3273+++ tests/unittests/qml/tst_BrowserWindow.qml 2016-09-28 15:27:35 +0000
3274@@ -0,0 +1,106 @@
3275+/*
3276+ * Copyright 2016 Canonical Ltd.
3277+ *
3278+ * This file is part of webbrowser-app.
3279+ *
3280+ * webbrowser-app is free software; you can redistribute it and/or modify
3281+ * it under the terms of the GNU General Public License as published by
3282+ * the Free Software Foundation; version 3.
3283+ *
3284+ * webbrowser-app is distributed in the hope that it will be useful,
3285+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3286+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3287+ * GNU General Public License for more details.
3288+ *
3289+ * You should have received a copy of the GNU General Public License
3290+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3291+ */
3292+
3293+import QtQuick 2.4
3294+import QtQuick.Window 2.2
3295+import QtTest 1.0
3296+import "../../../src/app"
3297+
3298+Item {
3299+ width: 200
3300+ height: 200
3301+
3302+ QtObject {
3303+ id: webviewMock
3304+ property bool fullscreen: false
3305+ }
3306+
3307+ Component {
3308+ id: windowFactory
3309+ BrowserWindow {
3310+ currentWebview: webviewMock
3311+ }
3312+ }
3313+
3314+ SignalSpy {
3315+ id: visibilitySpy
3316+ target: testcase.currentWindow
3317+ signalName: "visibilityChanged"
3318+ }
3319+
3320+ TestCase {
3321+ id: testcase
3322+ name: "BrowserWindow"
3323+ when: windowShown
3324+
3325+ property var currentWindow
3326+
3327+ function init() {
3328+ currentWindow = windowFactory.createObject(null)
3329+ currentWindow.show()
3330+ compare(currentWindow.visibility, Window.Windowed)
3331+ visibilitySpy.clear()
3332+ }
3333+
3334+ function cleanup() {
3335+ currentWindow.destroy()
3336+ }
3337+
3338+ function test_fullscreen_data() {
3339+ return [
3340+ {forceFullscreen: false, state: Window.Windowed},
3341+ {forceFullscreen: false, state: Window.Minimized},
3342+ {forceFullscreen: false, state: Window.Maximized},
3343+ {forceFullscreen: false, state: Window.Hidden},
3344+ {forceFullscreen: true, state: Window.FullScreen},
3345+ ]
3346+ }
3347+
3348+ function test_fullscreen_on_webview_fullscreen_data() {
3349+ return test_fullscreen_data()
3350+ }
3351+
3352+ function test_fullscreen_on_webview_fullscreen(data) {
3353+ currentWindow.forceFullscreen = data.forceFullscreen
3354+ currentWindow.visibility = data.state
3355+ visibilitySpy.clear()
3356+ webviewMock.fullscreen = true
3357+ tryCompare(visibilitySpy, "count", data.forceFullscreen ? 0 : 1)
3358+ compare(currentWindow.visibility, Window.FullScreen)
3359+ webviewMock.fullscreen = false
3360+ tryCompare(visibilitySpy, "count", data.forceFullscreen ? 0 : 2)
3361+ compare(currentWindow.visibility, data.state)
3362+ }
3363+
3364+ function test_setfullscreen_data() {
3365+ return test_fullscreen_data()
3366+ }
3367+
3368+ function test_setfullscreen(data) {
3369+ currentWindow.forceFullscreen = data.forceFullscreen
3370+ currentWindow.visibility = data.state
3371+ visibilitySpy.clear()
3372+ currentWindow.setFullscreen(true)
3373+ tryCompare(visibilitySpy, "count", data.forceFullscreen ? 0 : 1)
3374+ compare(currentWindow.visibility, Window.FullScreen)
3375+ currentWindow.setFullscreen(false)
3376+ tryCompare(visibilitySpy, "count", data.forceFullscreen ? 0 : 2)
3377+ compare(currentWindow.visibility, data.state)
3378+ }
3379+ }
3380+}

Subscribers

People subscribed via source and target branches

to status/vote changes: