2
* Copyright 2014-2016 Canonical Ltd.
4
* This file is part of webbrowser-app.
6
* webbrowser-app is free software; you can redistribute it and/or modify
7
* it under the terms of the GNU General Public License as published by
8
* the Free Software Foundation; version 3.
10
* webbrowser-app is distributed in the hope that it will be useful,
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
* GNU General Public License for more details.
15
* You should have received a copy of the GNU General Public License
16
* along with this program. If not, see <http://www.gnu.org/licenses/>.
20
import Ubuntu.Components 1.3
21
import Ubuntu.Components.Popups 1.3
22
import com.canonical.Oxide 1.15 as Oxide
23
import webbrowserapp.private 0.1
24
import "../actions" as Actions
27
// FIXME: This component breaks encapsulation: it uses variables not defined in
28
// itself. However this is an acceptable tradeoff with regards to
29
// startup time performance. Indeed having this component defined as a separate
30
// QML file as opposed to inline makes it possible to cache its compiled form.
37
incognito: browser.incognito
38
current: tabsModel && tabsModel.currentTab === this
42
id: contextualMenuTarget
46
webviewComponent: WebViewImpl {
49
property BrowserTab tab
50
readonly property bool current: tab.current
52
currentWebview: browser.currentWebview
53
filePicker: filePickerLoader.item
59
enabled: current && !bottomEdgeHandle.dragging && !recentView.visible && tabContainer.focus
61
locationBarController {
63
mode: chromeController.defaultMode
66
//experimental.preferences.developerExtrasEnabled: developerExtrasEnabled
67
preferences.localStorageEnabled: true
68
preferences.appCacheEnabled: true
70
property QtObject contextModel: null
71
contextualActions: ActionList {
72
Actions.OpenLinkInNewTab {
73
objectName: "OpenLinkInNewTabContextualAction"
74
enabled: contextModel && contextModel.linkUrl.toString()
75
onTriggered: internal.openUrlInNewTab(contextModel.linkUrl, true)
77
Actions.OpenLinkInNewBackgroundTab {
78
objectName: "OpenLinkInNewBackgroundTabContextualAction"
79
enabled: contextModel && contextModel.linkUrl.toString()
80
onTriggered: internal.openUrlInNewTab(contextModel.linkUrl, false)
82
Actions.OpenLinkInNewWindow {
83
objectName: "OpenLinkInNewWindowContextualAction"
84
enabled: contextModel && contextModel.linkUrl.toString()
85
onTriggered: browser.openLinkInWindowRequested(contextModel.linkUrl, false)
87
Actions.OpenLinkInPrivateWindow {
88
objectName: "OpenLinkInPrivateWindowContextualAction"
89
enabled: contextModel && contextModel.linkUrl.toString()
90
onTriggered: browser.openLinkInWindowRequested(contextModel.linkUrl, true)
92
Actions.BookmarkLink {
93
objectName: "BookmarkLinkContextualAction"
94
enabled: contextModel && contextModel.linkUrl.toString()
95
&& !BookmarksModel.contains(contextModel.linkUrl)
97
// position the menu target with a one-off assignement instead of a binding
98
// since the contents of the contextModel have meaning only while the context
100
contextualMenuTarget.x = contextModel.position.x
101
contextualMenuTarget.y = contextModel.position.y + locationBarController.height + locationBarController.offset
102
internal.addBookmark(contextModel.linkUrl, contextModel.linkText,
103
"", contextualMenuTarget)
107
objectName: "CopyLinkContextualAction"
108
enabled: contextModel && contextModel.linkUrl.toString()
109
onTriggered: Clipboard.push(["text/plain", contextModel.linkUrl.toString()])
112
objectName: "SaveLinkContextualAction"
113
enabled: contextModel && contextModel.linkUrl.toString()
114
onTriggered: contextModel.saveLink()
117
objectName: "ShareContextualAction"
118
enabled: (contentHandlerLoader.status == Loader.Ready) && contextModel &&
119
(contextModel.linkUrl.toString() || contextModel.selectionText)
121
if (contextModel.linkUrl.toString()) {
122
internal.shareLink(contextModel.linkUrl.toString(), contextModel.linkText)
123
} else if (contextModel.selectionText) {
124
internal.shareText(contextModel.selectionText)
128
Actions.OpenImageInNewTab {
129
objectName: "OpenImageInNewTabContextualAction"
130
enabled: contextModel &&
131
(contextModel.mediaType === Oxide.WebView.MediaTypeImage) &&
132
contextModel.srcUrl.toString()
133
onTriggered: internal.openUrlInNewTab(contextModel.srcUrl, true)
136
objectName: "CopyImageContextualAction"
137
enabled: contextModel &&
138
(contextModel.mediaType === Oxide.WebView.MediaTypeImage) &&
139
contextModel.srcUrl.toString()
140
onTriggered: Clipboard.push(["text/plain", contextModel.srcUrl.toString()])
143
objectName: "SaveImageContextualAction"
144
enabled: contextModel &&
145
((contextModel.mediaType === Oxide.WebView.MediaTypeImage) ||
146
(contextModel.mediaType === Oxide.WebView.MediaTypeCanvas)) &&
147
contextModel.hasImageContents
148
onTriggered: contextModel.saveMedia()
150
Actions.OpenVideoInNewTab {
151
objectName: "OpenVideoInNewTabContextualAction"
152
enabled: contextModel &&
153
(contextModel.mediaType === Oxide.WebView.MediaTypeVideo) &&
154
contextModel.srcUrl.toString()
155
onTriggered: internal.openUrlInNewTab(contextModel.srcUrl, true)
158
objectName: "SaveVideoContextualAction"
159
enabled: contextModel &&
160
(contextModel.mediaType === Oxide.WebView.MediaTypeVideo) &&
161
contextModel.srcUrl.toString()
162
onTriggered: contextModel.saveMedia()
165
objectName: "UndoContextualAction"
166
enabled: contextModel && contextModel.isEditable &&
167
(contextModel.editFlags & Oxide.WebView.UndoCapability)
168
onTriggered: webviewimpl.executeEditingCommand(Oxide.WebView.EditingCommandUndo)
171
objectName: "RedoContextualAction"
172
enabled: contextModel && contextModel.isEditable &&
173
(contextModel.editFlags & Oxide.WebView.RedoCapability)
174
onTriggered: webviewimpl.executeEditingCommand(Oxide.WebView.EditingCommandRedo)
177
objectName: "CutContextualAction"
178
enabled: contextModel && contextModel.isEditable &&
179
(contextModel.editFlags & Oxide.WebView.CutCapability)
180
onTriggered: webviewimpl.executeEditingCommand(Oxide.WebView.EditingCommandCut)
183
objectName: "CopyContextualAction"
184
enabled: contextModel && (contextModel.selectionText ||
185
(contextModel.isEditable &&
186
(contextModel.editFlags & Oxide.WebView.CopyCapability)))
187
onTriggered: webviewimpl.executeEditingCommand(Oxide.WebView.EditingCommandCopy)
190
objectName: "PasteContextualAction"
191
enabled: contextModel && contextModel.isEditable &&
192
(contextModel.editFlags & Oxide.WebView.PasteCapability)
193
onTriggered: webviewimpl.executeEditingCommand(Oxide.WebView.EditingCommandPaste)
196
objectName: "EraseContextualAction"
197
enabled: contextModel && contextModel.isEditable &&
198
(contextModel.editFlags & Oxide.WebView.EraseCapability)
199
onTriggered: webviewimpl.executeEditingCommand(Oxide.WebView.EditingCommandErase)
202
objectName: "SelectAllContextualAction"
203
enabled: contextModel && contextModel.isEditable &&
204
(contextModel.editFlags & Oxide.WebView.SelectAllCapability)
205
onTriggered: webviewimpl.executeEditingCommand(Oxide.WebView.EditingCommandSelectAll)
209
function contextMenuOnCompleted(menu) {
210
contextModel = menu.contextModel
211
if (contextModel.linkUrl.toString() ||
212
contextModel.srcUrl.toString() ||
213
contextModel.selectionText ||
214
(contextModel.isEditable && contextModel.editFlags) ||
215
(((contextModel.mediaType == Oxide.WebView.MediaTypeImage) ||
216
(contextModel.mediaType == Oxide.WebView.MediaTypeCanvas)) &&
217
contextModel.hasImageContents)) {
225
id: contextMenuNarrowComponent
227
actions: contextualActions
228
Component.onCompleted: webviewimpl.contextMenuOnCompleted(this)
232
id: contextMenuWideComponent
236
actions: contextualActions
237
Component.onCompleted: webviewimpl.contextMenuOnCompleted(this)
240
contextMenu: browser.wide ? contextMenuWideComponent : contextMenuNarrowComponent
242
onNewViewRequested: {
243
var tab = tabComponent.createObject(tabContainer, {"request": request})
244
var setCurrent = (request.disposition == Oxide.NewViewRequest.DispositionNewForegroundTab)
245
internal.addTab(tab, setCurrent)
246
if (setCurrent) tabContainer.forceActiveFocus()
249
onCloseRequested: prepareToClose()
250
onPrepareToCloseResponse: {
253
for (var i = 0; i < tabsModel.count; ++i) {
254
if (tabsModel.get(i) === tab) {
261
if (tabsModel.count === 0) {
262
internal.openUrlInNewTab("", true, true)
269
property url storedUrl: ""
270
property bool titleSet: false
271
property string title: ""
274
if (event.type == Oxide.LoadEvent.TypeCommitted) {
275
chrome.findInPageMode = false
276
webviewInternal.titleSet = false
277
webviewInternal.title = title
280
if (webviewimpl.incognito) {
284
if ((event.type == Oxide.LoadEvent.TypeCommitted) &&
286
(300 > event.httpStatusCode) && (event.httpStatusCode >= 200)) {
287
webviewInternal.storedUrl = event.url
288
HistoryModel.add(event.url, title, icon)
291
// If the page has started, stopped, redirected, errored
292
// then clear the cache for the history update
293
// Otherwise if no title change has occurred the next title
294
// change will be the url of the next page causing the
295
// history entry to be incorrect (pad.lv/1603835)
296
if (event.type == Oxide.LoadEvent.TypeFailed ||
297
event.type == Oxide.LoadEvent.TypeRedirected ||
298
event.type == Oxide.LoadEvent.TypeStarted ||
299
event.type == Oxide.LoadEvent.TypeStopped) {
300
webviewInternal.titleSet = true
301
webviewInternal.storedUrl = ""
305
if (!webviewInternal.titleSet && webviewInternal.storedUrl.toString()) {
306
// Record the title to avoid updating the history database
307
// every time the page dynamically updates its title.
308
// We don’t want pages that update their title every second
309
// to achieve an ugly "scrolling title" effect to flood the
310
// history database with updates.
311
webviewInternal.titleSet = true
312
if (webviewInternal.title != title) {
313
webviewInternal.title = title
314
HistoryModel.update(webviewInternal.storedUrl, title, icon)
319
if (webviewInternal.storedUrl.toString()) {
320
HistoryModel.update(webviewInternal.storedUrl, webviewInternal.title, icon)
324
onGeolocationPermissionRequested: requestGeolocationPermission(request)
326
property var certificateError
327
function resetCertificateError() {
328
certificateError = null
330
onCertificateError: {
331
if (!error.isMainFrame || error.isSubresource) {
332
// Not a main frame document error, just block the content
333
// (it’s not overridable anyway).
336
if (internal.isCertificateErrorAllowed(error)) {
339
certificateError = error
340
error.onCancelled.connect(webviewimpl.resetCertificateError)
344
onFullscreenChanged: {
346
fullscreenExitHintComponent.createObject(webviewimpl)
350
id: fullscreenExitHintComponent
353
id: fullscreenExitHint
354
objectName: "fullscreenExitHint"
356
anchors.centerIn: parent
358
width: Math.min(units.gu(50), parent.width - units.gu(12))
363
Behavior on opacity {
364
UbuntuNumberAnimation {
365
duration: UbuntuAnimation.SlowDuration
369
if (opacity == 0.0) {
370
fullscreenExitHint.destroy()
374
// Delay showing the hint to prevent it from jumping up while the
375
// webview is being resized (https://launchpad.net/bugs/1454097).
380
onTriggered: fullscreenExitHint.visible = true
385
font.weight: Font.Light
386
anchors.centerIn: parent
387
text: bottomEdgeHandle.enabled
388
? i18n.tr("Swipe Up To Exit Full Screen")
389
: i18n.tr("Press ESC To Exit Full Screen")
393
running: fullscreenExitHint.visible
395
onTriggered: fullscreenExitHint.opacity = 0
400
onFullscreenChanged: {
401
if (!webviewimpl.fullscreen) {
402
fullscreenExitHint.destroy()
407
Component.onCompleted: bottomEdgeHint.forceShow = true
408
Component.onDestruction: bottomEdgeHint.forceShow = false
412
onShowDownloadDialog: {
413
if (downloadDialogLoader.status === Loader.Ready) {
414
var downloadDialog = PopupUtils.open(downloadDialogLoader.item, browser, {"contentType" : contentType,
415
"downloadId" : downloadId,
416
"singleDownload" : downloader,
417
"filename" : filename,
418
"mimeType" : mimeType})
419
downloadDialog.startDownload.connect(startDownload)
423
function showDownloadsPage() {
424
downloadsViewLoader.active = true
425
return downloadsViewLoader.item
428
function startDownload(downloadId, download, mimeType) {
429
DownloadsModel.add(downloadId, download.url, mimeType, incognito)
431
downloadsViewLoader.active = true