initial commit
This commit is contained in:
+594
@@ -0,0 +1,594 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
import QtQuick
|
||||
import QtWebEngine
|
||||
|
||||
Item {
|
||||
id: pane
|
||||
clip: true
|
||||
property var views: ({})
|
||||
property var activeView: null
|
||||
property var pendingView: null
|
||||
property string searchQuery: ""
|
||||
|
||||
property var companionView: null
|
||||
property var spreadSelection: ({})
|
||||
property string failedCompanionUrl: ""
|
||||
property bool arrangingPages: false
|
||||
property real spreadZoom: 1
|
||||
property bool spreadFitWidth: false
|
||||
property real pagePanX: 0
|
||||
property real pagePanY: 0
|
||||
property real maxPagePanX: 0
|
||||
property real maxPagePanY: 0
|
||||
|
||||
// Navigation requests expose a display string, while signal arguments and
|
||||
// QML url properties can use percent encoding. Compare the same URL type.
|
||||
function canonicalUrl(value) { return String(Qt.url(value)) }
|
||||
|
||||
// A spread always follows spine order; CSS direction does not reorder pages.
|
||||
// Unspecified first pages occupy the trailing slot (the conventional cover).
|
||||
function spreadFor(spine, current, rtl, landscape) {
|
||||
var leading = rtl ? "right" : "left", trailing = rtl ? "left" : "right"
|
||||
var groups = [], pending = null, first = true
|
||||
function flush() { if (pending) { groups.push(pending); pending = null } }
|
||||
for (var i = 0; i < spine.length; ++i) {
|
||||
var row = spine[i]
|
||||
if (row.linear === false) continue
|
||||
if (row.missing || !row.href) { flush(); continue }
|
||||
var policy = row.renditionSpread || "auto"
|
||||
var eligible = row.layout === "fixed" && policy !== "none" && (policy === "both" || landscape)
|
||||
if (!eligible || row.spread === "center") {
|
||||
flush(); groups.push({center:i}); first = false; continue
|
||||
}
|
||||
var side = row.spread === "left" || row.spread === "right" ? row.spread : (pending ? trailing : (first ? trailing : leading))
|
||||
first = false
|
||||
if (side === leading) { flush(); pending = {}; pending[side] = i }
|
||||
else { var group = pending || {}; group[side] = i; groups.push(group); pending = null }
|
||||
}
|
||||
flush()
|
||||
for (var j = 0; j < groups.length; ++j)
|
||||
if (groups[j].left === current || groups[j].right === current || groups[j].center === current) return groups[j]
|
||||
return {center:current} // nonlinear items are independently navigable.
|
||||
}
|
||||
function resourcePath(url) {
|
||||
return decodeURIComponent(String(url).replace(/^doc:\/\/[^/]+\//, "").split("#")[0])
|
||||
}
|
||||
function retireView(view) {
|
||||
if (!view) return
|
||||
resetHeartbeat(view)
|
||||
view.alive = false
|
||||
view.stop()
|
||||
view.visible = false
|
||||
if (typeof reader.registerRenderer === "function") reader.registerRenderer(view.documentToken, 0, view.rendererSlot)
|
||||
view.destroy()
|
||||
}
|
||||
function closeCompanion() {
|
||||
var old = companionView
|
||||
companionView = null
|
||||
if (old) retireView(old)
|
||||
}
|
||||
function synchronizeSpread() {
|
||||
var view = activeView
|
||||
if (!view || !view.alive || !view.documentReady) return
|
||||
var opts = optionsFor(view), spine = opts.spine || [], current = -1
|
||||
for (var i = 0; i < spine.length; ++i)
|
||||
if (resourcePath(view.url) === decodeURIComponent(String(spine[i].href || "").split("#")[0])) { current = i; break }
|
||||
spreadSelection = current >= 0 ? spreadFor(spine, current, !!opts.rtl, width > height) : ({center:-1})
|
||||
var other = spreadSelection.left === current ? spreadSelection.right : spreadSelection.left
|
||||
if (other === undefined || other === current || !view.fixedLayout || pendingView) {
|
||||
closeCompanion(); arrangePages(); return
|
||||
}
|
||||
var url = "doc://" + view.documentToken + "/" + spine[other].href
|
||||
if (canonicalUrl(url) === canonicalUrl(failedCompanionUrl) || !reader.navigationAllowed(url, WebEngineView.TypedNavigation, view.documentToken)) {
|
||||
closeCompanion(); arrangePages(); return
|
||||
}
|
||||
if (!companionView) {
|
||||
companionView = webComponent.createObject(pane, {profile:view.profile, documentToken:view.documentToken,
|
||||
rendererSlot:1, isCompanion:true, permittedUrl:url})
|
||||
if (!companionView) { arrangePages(); return }
|
||||
companionView.url = url
|
||||
} else if (canonicalUrl(companionView.url) !== canonicalUrl(url)) {
|
||||
companionView.documentReady = false
|
||||
companionView.pagePlaced = false
|
||||
companionView.fixedViewport = ({})
|
||||
companionView.permittedUrl = url
|
||||
companionView.url = url
|
||||
}
|
||||
arrangePages()
|
||||
}
|
||||
function arrangePages() {
|
||||
if (arrangingPages || !activeView) return
|
||||
arrangingPages = true
|
||||
var view = activeView, other = companionView
|
||||
var a = view.fixedViewport, b = other ? other.fixedViewport : ({})
|
||||
if (!view.fixedLayout || !(a.width > 0) || !(a.height > 0)) {
|
||||
view.x = 0; view.y = 0; view.width = width; view.height = height; view.scale = 1
|
||||
} else {
|
||||
var currentLeft = spreadSelection.left !== undefined && resourcePath(view.url) === decodeURIComponent(String((optionsFor(view).spine || [])[spreadSelection.left].href).split("#")[0])
|
||||
var paired = other && other.documentReady && b.width > 0 && b.height > 0
|
||||
var slots = spreadSelection.center === undefined
|
||||
var leftSize = paired && !currentLeft ? b : a, rightSize = paired && currentLeft ? b : a
|
||||
var totalWidth = slots ? leftSize.width + rightSize.width : a.width
|
||||
var totalHeight = paired ? Math.max(a.height, b.height) : a.height
|
||||
var factor = (spreadFitWidth ? width / totalWidth : Math.min(width / totalWidth, height / totalHeight)) * spreadZoom
|
||||
maxPagePanX = Math.max(0, totalWidth * factor - width)
|
||||
maxPagePanY = Math.max(0, totalHeight * factor - height)
|
||||
pagePanX = Math.max(0, Math.min(maxPagePanX, pagePanX))
|
||||
pagePanY = Math.max(0, Math.min(maxPagePanY, pagePanY))
|
||||
var origin = Math.max(0, (width - totalWidth * factor) / 2) - pagePanX
|
||||
function place(page, size, left) {
|
||||
page.zoomFactor = Math.max(0.25, Math.min(5, factor))
|
||||
page.scale = factor / page.zoomFactor
|
||||
page.width = size.width * page.zoomFactor; page.height = size.height * page.zoomFactor
|
||||
page.x = origin + left * factor; page.y = Math.max(0, (height - size.height * factor) / 2) - pagePanY
|
||||
page.pagePlaced = true
|
||||
}
|
||||
place(view, a, slots && !currentLeft ? leftSize.width : 0)
|
||||
if (paired) place(other, b, currentLeft ? leftSize.width : 0)
|
||||
}
|
||||
arrangingPages = false
|
||||
}
|
||||
function collectResourceIssues(view) {
|
||||
if (typeof reader.webResourceIssues !== "function") return
|
||||
run(view, "resource.issues", {}, function(value) { if (value) reader.webResourceIssues(view.documentToken, value) })
|
||||
}
|
||||
WheelHandler {
|
||||
target: null
|
||||
enabled: !!pane.activeView && pane.activeView.fixedLayout
|
||||
onWheel: function(event) {
|
||||
pane.pagePanX -= event.angleDelta.x / 2
|
||||
pane.pagePanY -= event.angleDelta.y / 2
|
||||
pane.arrangePages()
|
||||
}
|
||||
}
|
||||
onWidthChanged: Qt.callLater(synchronizeSpread)
|
||||
onHeightChanged: Qt.callLater(synchronizeSpread)
|
||||
|
||||
function focusContent() {
|
||||
if (activeView) activeView.forceActiveFocus()
|
||||
}
|
||||
function optionsFor(view) {
|
||||
var opts = typeof reader.webOptions === "function" ? reader.webOptions(view.documentToken) : ({})
|
||||
var reading = opts.reading || reader.settings.reading || ({})
|
||||
var currentPath = decodeURIComponent(String(view.url).replace(/^doc:\/\/[^/]+\//, "").split("#")[0])
|
||||
var spine = opts.spine || []
|
||||
for (var i = 0; i < spine.length; ++i) {
|
||||
if (decodeURIComponent(String(spine[i].href || "").split("#")[0]) === currentPath) { opts.fixed = spine[i].layout === "fixed"; break }
|
||||
}
|
||||
opts.publisherStyle = reading.use_publisher_style !== false
|
||||
opts.fontSize = reading.font_size || 18
|
||||
opts.lineHeight = reading.line_height || 1.6
|
||||
return opts
|
||||
}
|
||||
function resetHeartbeat(view) {
|
||||
if (view.heartbeatPending && typeof reader.finishRendererProbe === "function")
|
||||
reader.finishRendererProbe(view.documentToken, view.rendererSlot, view.heartbeatPending)
|
||||
view.heartbeatPending = 0
|
||||
}
|
||||
function trackRenderer(view, pid) {
|
||||
if (!view || !view.alive || view.registeredRendererPid === pid || typeof reader.registerRenderer !== "function") return
|
||||
resetHeartbeat(view)
|
||||
view.registeredRendererPid = pid
|
||||
reader.registerRenderer(view.documentToken, pid, view.rendererSlot)
|
||||
}
|
||||
function heartbeat(view) {
|
||||
if (!view || !view.alive) return
|
||||
// Qt can expose the initial process through the getter before delivering
|
||||
// a PID notification. Reconcile once, without resetting healthy probes.
|
||||
trackRenderer(view, view.renderProcessPid)
|
||||
if (!view || !view.alive || view.heartbeatPending || view.renderProcessPid <= 0 ||
|
||||
typeof reader.beginRendererProbe !== "function") return
|
||||
var probe = reader.beginRendererProbe(view.documentToken, view.rendererSlot)
|
||||
if (!probe) return
|
||||
view.heartbeatPending = probe
|
||||
var serial = view.documentSerial, token = view.documentToken
|
||||
// The response requires the renderer main thread, including while the
|
||||
// document is loading. Document JavaScript remains disabled.
|
||||
view.runJavaScript(String(probe), WebEngineScript.ApplicationWorld, function(result) {
|
||||
if (!view || !view.alive || view.documentSerial !== serial || view.documentToken !== token ||
|
||||
view.heartbeatPending !== probe || result !== probe) return
|
||||
reader.finishRendererProbe(token, view.rendererSlot, probe)
|
||||
view.heartbeatPending = 0
|
||||
// Polling work is queued only after the previous heartbeat returns;
|
||||
// a stalled renderer cannot accumulate periodic DOM operations.
|
||||
if (view === activeView) updateLocation(view)
|
||||
collectResourceIssues(view)
|
||||
})
|
||||
}
|
||||
function run(view, id, args, callback) {
|
||||
if (!view || !view.alive || !view.documentReady) return
|
||||
var serial = view.documentSerial
|
||||
var token = view.documentToken
|
||||
// Only JSON data is interpolated; all executable source is app-owned.
|
||||
var data = JSON.stringify({id: id, args: args || ({})})
|
||||
var script = "(function(data){return globalThis.__docviewReader ? globalThis.__docviewReader.command(data.id,data.args) : null;})(" + data + ")"
|
||||
view.runJavaScript(script, WebEngineScript.ApplicationWorld, function(result) {
|
||||
if (!view || !view.alive || view.documentSerial !== serial || view.documentToken !== token) return
|
||||
if (callback) callback(result)
|
||||
})
|
||||
}
|
||||
function updateLocation(view) {
|
||||
run(view, "location", {}, function(value) {
|
||||
if (!value || typeof value.url !== "string") return
|
||||
value.zoom = (view.fixedLayout ? spreadZoom : view.zoomFactor) * 100
|
||||
if (view.fixedLayout) {
|
||||
value.fitWidth = spreadFitWidth
|
||||
value.panX = maxPagePanX > 0 ? pagePanX / maxPagePanX : 0
|
||||
value.panY = maxPagePanY > 0 ? pagePanY / maxPagePanY : 0
|
||||
}
|
||||
view.lastLocation = value
|
||||
if (view === activeView) reader.webLocation(view.documentToken, value)
|
||||
})
|
||||
}
|
||||
function configureNavigation(view) {
|
||||
if (!view || view !== activeView || typeof reader.webNavigationTargets !== "function") return
|
||||
var args = reader.webNavigationTargets(view.documentToken, view.url)
|
||||
run(view, "navigation.configure", args, function() { updateLocation(view) })
|
||||
}
|
||||
function collectIndex(view) {
|
||||
run(view, "index", {}, function(value) {
|
||||
if (!value) return
|
||||
if (view === activeView) reader.webIndex(view.documentToken, value)
|
||||
if (view === activeView && value.truncated) reader.notify(qsTr("見出し一覧は先頭2,000件を表示しています"))
|
||||
view.fixedViewport = value.fixedViewport || ({})
|
||||
if (view.fixedLayout) arrangePages()
|
||||
if (view === activeView) configureNavigation(view)
|
||||
})
|
||||
}
|
||||
function fitFixed(view, widthOnly) {
|
||||
var size = view.fixedViewport
|
||||
if (!view.fixedLayout || !(size.width > 0) || !(size.height > 0)) return
|
||||
var factor = widthOnly ? view.width / size.width : Math.min(view.width / size.width, view.height / size.height)
|
||||
view.zoomFactor = Math.max(0.25, Math.min(5, factor))
|
||||
}
|
||||
function applyLayout(view, callback) {
|
||||
if (!view || !view.documentReady) return
|
||||
var opts = optionsFor(view)
|
||||
var wasFixed = view.fixedLayout
|
||||
view.fixedLayout = !!opts.fixed
|
||||
if (wasFixed && !view.fixedLayout) view.zoomFactor = 1
|
||||
run(view, "layout", opts, function(result) {
|
||||
if (!result) return
|
||||
view.fixedViewport = result.fixedViewport || ({})
|
||||
if (view === activeView) synchronizeSpread()
|
||||
else if (view.isCompanion) arrangePages()
|
||||
if (callback) callback()
|
||||
})
|
||||
}
|
||||
function restoreAfterLayout(view, saved, restoreDisplay, applied) {
|
||||
if (!saved || !view || !view.alive) return
|
||||
if (restoreDisplay) {
|
||||
if (typeof saved.zoom === "number" && isFinite(saved.zoom) && saved.zoom >= 25 && saved.zoom <= 500) {
|
||||
if (view.fixedLayout) spreadZoom = saved.zoom / 100
|
||||
else view.zoomFactor = saved.zoom / 100
|
||||
}
|
||||
if (view.fixedLayout) {
|
||||
spreadFitWidth = saved.fitWidth === true
|
||||
arrangePages()
|
||||
pagePanX = (saved.panX || 0) * maxPagePanX
|
||||
pagePanY = (saved.panY || 0) * maxPagePanY
|
||||
arrangePages()
|
||||
}
|
||||
}
|
||||
run(view, "location.restore", saved, function(result) {
|
||||
if (result && result.approximate) reader.notify(qsTr("E_LOCATOR_UNRESOLVED: 近い読書位置へ戻りました"))
|
||||
if (applied) applied()
|
||||
updateLocation(view)
|
||||
})
|
||||
}
|
||||
function applyBoundary(view) {
|
||||
if (!view || !view.documentReady || !view.pendingBoundary) return
|
||||
var boundary = view.pendingBoundary
|
||||
var request = view.pendingBoundaryRequest
|
||||
view.pendingBoundary = ""
|
||||
if (view !== activeView || request !== view.commandSerial) return
|
||||
run(view, boundary, {}, function() {
|
||||
if (view !== activeView || request !== view.commandSerial) return
|
||||
reader.webApplied(view.documentToken, boundary, request)
|
||||
updateLocation(view)
|
||||
})
|
||||
}
|
||||
function command(id, args) {
|
||||
var view = activeView
|
||||
if (!view) return
|
||||
var request = ++view.commandSerial
|
||||
var operation = id === "navigate" && args.boundary ? args.boundary : id
|
||||
reader.webStarted(view.documentToken, operation, request)
|
||||
view.cancelResizeRestore()
|
||||
view.restorePending = null // A newer user command supersedes a deferred restoration.
|
||||
view.pendingBoundary = ""
|
||||
if (id === "navigate") {
|
||||
view.pendingBoundary = ["nav.document_start", "nav.document_end", "heading.first", "heading.last"].indexOf(args.boundary) >= 0 ? args.boundary : ""
|
||||
view.pendingBoundaryRequest = request
|
||||
view.permittedUrl = String(args.url)
|
||||
var sameResource = canonicalUrl(view.url).split("#")[0] === canonicalUrl(args.url).split("#")[0]
|
||||
// A following CFI restoration supplies the exact position. Avoid
|
||||
// an identical-URL reload or fragment scroll overwriting that jump.
|
||||
if (sameResource && args.preserveLocation) { view.permittedUrl = ""; return }
|
||||
// Assigning an identical URL may reload the page. Keep an endpoint
|
||||
// jump inside this DOM so a new load cannot consume its callback.
|
||||
if (sameResource && view.documentReady && view.pendingBoundary) {
|
||||
view.permittedUrl = ""
|
||||
applyLayout(view, function() { applyBoundary(view) })
|
||||
return
|
||||
}
|
||||
if (!sameResource) { view.documentReady = false; failedCompanionUrl = ""; pagePanX = 0; pagePanY = 0; closeCompanion() }
|
||||
view.url = args.url
|
||||
return
|
||||
}
|
||||
if (view.fixedLayout && id.indexOf("scroll.") === 0) {
|
||||
var distance = id.indexOf("half_") >= 0 ? height / 2 : id.indexOf("page_") >= 0 ? height * 0.9 : 48
|
||||
if (id === "scroll.left") pagePanX -= 48
|
||||
else if (id === "scroll.right") pagePanX += 48
|
||||
else if (id.indexOf("up") >= 0) pagePanY -= distance
|
||||
else if (id.indexOf("down") >= 0) pagePanY += distance
|
||||
arrangePages()
|
||||
return
|
||||
}
|
||||
if (id === "location.restore") {
|
||||
if (!view.documentReady) view.restorePending = args
|
||||
else applyLayout(view, function() {
|
||||
if (view === activeView && request === view.commandSerial)
|
||||
restoreAfterLayout(view, args, true, function() {
|
||||
if (view === activeView && request === view.commandSerial) reader.webApplied(view.documentToken, id, request)
|
||||
})
|
||||
})
|
||||
return
|
||||
}
|
||||
if (id === "layout") { applyLayout(view); return }
|
||||
if (id === "zoom.in" || id === "zoom.out" || id === "zoom.fit_width") {
|
||||
if (view.fixedLayout) {
|
||||
spreadFitWidth = id === "zoom.fit_width"
|
||||
spreadZoom = spreadFitWidth ? 1 : Math.max(0.25, Math.min(5, spreadZoom * (id === "zoom.in" ? 1.15 : 1 / 1.15)))
|
||||
arrangePages()
|
||||
updateLocation(view)
|
||||
return
|
||||
}
|
||||
var saved = view.lastLocation
|
||||
if (id === "zoom.fit_width") {
|
||||
if (view.fixedLayout) fitFixed(view, true)
|
||||
else view.zoomFactor = 1
|
||||
} else view.zoomFactor = Math.max(0.25, Math.min(5, view.zoomFactor * (id === "zoom.in" ? 1.15 : 1 / 1.15)))
|
||||
Qt.callLater(function() {
|
||||
if (view !== activeView || !view.alive || request !== view.commandSerial) return
|
||||
if (saved) restoreAfterLayout(view, saved)
|
||||
else updateLocation(view)
|
||||
})
|
||||
return
|
||||
}
|
||||
if (id === "search.cancel") {
|
||||
++view.searchRevision
|
||||
view.findText("")
|
||||
return
|
||||
}
|
||||
if (id === "search" || id === "search.next" || id === "search.previous") {
|
||||
if (id === "search") searchQuery = String(args.query || "").slice(0, 4096)
|
||||
var serial = view.documentSerial
|
||||
var revision = ++view.searchRevision
|
||||
view.findText(searchQuery, id === "search.previous" ? WebEngineView.FindBackward : 0, function(found) {
|
||||
if (!view || !view.alive || view !== activeView || view.documentSerial !== serial || view.searchRevision !== revision) return
|
||||
if (!found) reader.notify(qsTr("一致する文字がありません"))
|
||||
updateLocation(view)
|
||||
})
|
||||
return
|
||||
}
|
||||
run(view, id, args, function(result) {
|
||||
if (view !== activeView || request !== view.commandSerial) return
|
||||
if (typeof reader.webApplied === "function") reader.webApplied(view.documentToken, id, request)
|
||||
if (id === "link.activate" && result) {
|
||||
if (result.url && reader.navigationAllowed(result.url, WebEngineView.LinkClickedNavigation, view.documentToken))
|
||||
pane.command("navigate", {url: result.url})
|
||||
else if (result.blocked) reader.notify(qsTr("E_RESOURCE_BLOCKED: このリンクは開けません"))
|
||||
else if (result.empty) reader.notify(qsTr("Tabでリンクを選択してください"))
|
||||
} else if ((id === "link.next" || id === "link.previous") && result && result.empty)
|
||||
reader.notify(qsTr("この文書にはリンクがありません"))
|
||||
if (result && result.boundary) {
|
||||
if (reader.format === "epub") {
|
||||
reader.execute(result.boundary > 0 ? "nav.chapter_next" : "nav.chapter_previous",
|
||||
{headingBoundary: result.boundary > 0 ? "heading.first" : "heading.last"})
|
||||
} else reader.notify(result.empty ? qsTr("この文書には見出しがありません") : qsTr("見出しの端です"))
|
||||
}
|
||||
if (id === "location.restore" && result && result.approximate) reader.notify(qsTr("E_LOCATOR_UNRESOLVED: 近い読書位置へ戻りました"))
|
||||
updateLocation(view)
|
||||
})
|
||||
}
|
||||
Connections {
|
||||
target: reader
|
||||
function onStageWeb(profile, url, token) {
|
||||
closeCompanion()
|
||||
var view = webComponent.createObject(pane, {profile: profile, documentToken: token, permittedUrl: String(url)})
|
||||
if (!view) { reader.webLoaded(token, false); return }
|
||||
views[token] = view
|
||||
pendingView = view
|
||||
view.url = url
|
||||
}
|
||||
function onActivateWeb(token) {
|
||||
if (activeView) { activeView.isCurrent = false; activeView.enabled = false }
|
||||
activeView = views[token] || null
|
||||
if (activeView) {
|
||||
spreadZoom = 1; spreadFitWidth = false; pagePanX = 0; pagePanY = 0
|
||||
activeView.isCurrent = true
|
||||
activeView.enabled = true
|
||||
if (pendingView === activeView) pendingView = null
|
||||
Qt.callLater(function() { if (activeView && activeView.documentToken === token) { applyLayout(activeView); collectIndex(activeView) } })
|
||||
}
|
||||
}
|
||||
function onDisposeWeb(token) {
|
||||
var view = views[token]
|
||||
if (!view) return
|
||||
if (companionView && companionView.documentToken === token) closeCompanion()
|
||||
if (activeView === view) activeView = null
|
||||
if (pendingView === view) pendingView = null
|
||||
delete views[token]
|
||||
retireView(view)
|
||||
if (activeView && !pendingView) Qt.callLater(synchronizeSpread)
|
||||
}
|
||||
function onWebCommand(id, args) { pane.command(id, args) }
|
||||
function onNavigationChanged() { pane.configureNavigation(pane.activeView) }
|
||||
function onSettingsChanged() {
|
||||
if (!activeView) return
|
||||
var view = activeView
|
||||
run(view, "location", {}, function(saved) {
|
||||
applyLayout(view, function() { restoreAfterLayout(view, saved) })
|
||||
})
|
||||
}
|
||||
}
|
||||
Timer {
|
||||
interval: 400
|
||||
running: !!pane.activeView || !!pane.pendingView || !!pane.companionView
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
pane.heartbeat(pane.activeView)
|
||||
pane.heartbeat(pane.pendingView)
|
||||
pane.heartbeat(pane.companionView)
|
||||
}
|
||||
}
|
||||
Component {
|
||||
id: webComponent
|
||||
WebEngineView {
|
||||
id: web
|
||||
width: pane.width
|
||||
height: pane.height
|
||||
transformOrigin: Item.TopLeft
|
||||
property int rendererSlot: 0
|
||||
property bool isCompanion: false
|
||||
property bool pagePlaced: false
|
||||
property string documentToken: ""
|
||||
property string permittedUrl: ""
|
||||
property bool alive: true
|
||||
property bool isCurrent: false
|
||||
property bool documentReady: false
|
||||
property int documentSerial: 0
|
||||
property double heartbeatPending: 0
|
||||
property double registeredRendererPid: 0
|
||||
property int searchRevision: 0
|
||||
property double commandSerial: 0
|
||||
property double pendingBoundaryRequest: 0
|
||||
property var lastLocation: null
|
||||
property var resizeLocation: null
|
||||
property var restorePending: null
|
||||
property var fixedViewport: ({})
|
||||
property bool fixedLayout: false
|
||||
property string pendingBoundary: ""
|
||||
opacity: isCurrent || (isCompanion && documentReady && pagePlaced) ? 1 : 0
|
||||
z: isCurrent || isCompanion ? 1 : 0
|
||||
enabled: isCurrent || (isCompanion && pagePlaced)
|
||||
audioMuted: true
|
||||
backgroundColor: "white"
|
||||
webChannel: null
|
||||
activeFocusOnPress: true
|
||||
settings.javascriptEnabled: false
|
||||
settings.javascriptCanOpenWindows: false
|
||||
settings.javascriptCanAccessClipboard: false
|
||||
settings.javascriptCanPaste: false
|
||||
settings.localStorageEnabled: false
|
||||
settings.localContentCanAccessRemoteUrls: false
|
||||
settings.localContentCanAccessFileUrls: false
|
||||
settings.hyperlinkAuditingEnabled: false
|
||||
settings.pluginsEnabled: false
|
||||
settings.fullScreenSupportEnabled: false
|
||||
settings.screenCaptureEnabled: false
|
||||
settings.webGLEnabled: false
|
||||
settings.accelerated2dCanvasEnabled: false
|
||||
settings.autoLoadIconsForPage: false
|
||||
settings.touchIconsEnabled: false
|
||||
settings.allowRunningInsecureContent: false
|
||||
settings.allowGeolocationOnInsecureOrigins: false
|
||||
settings.allowWindowActivationFromJavaScript: false
|
||||
settings.dnsPrefetchEnabled: false
|
||||
settings.pdfViewerEnabled: false
|
||||
settings.navigateOnDropEnabled: false
|
||||
settings.playbackRequiresUserGesture: true
|
||||
settings.webRTCPublicInterfacesOnly: true
|
||||
settings.unknownUrlSchemePolicy: WebEngineSettings.DisallowUnknownUrlSchemes
|
||||
settings.focusOnNavigationEnabled: false
|
||||
userScripts.collection: [{
|
||||
name: "DocView Reader",
|
||||
injectionPoint: WebEngineScript.DocumentCreation,
|
||||
worldId: WebEngineScript.ApplicationWorld,
|
||||
runsOnSubFrames: false,
|
||||
sourceCode: reader.script("reader")
|
||||
}]
|
||||
onActiveFocusChanged: if (activeFocus) reader.context = "content"
|
||||
onNavigationRequested: function(request) {
|
||||
var type = request.navigationType
|
||||
var known = type === WebEngineView.LinkClickedNavigation ||
|
||||
((type === WebEngineView.OtherNavigation || type === WebEngineView.TypedNavigation) && pane.canonicalUrl(request.url) === pane.canonicalUrl(permittedUrl))
|
||||
if (isCompanion && type === WebEngineView.LinkClickedNavigation) {
|
||||
request.action = WebEngineView.IgnoreRequest
|
||||
if (reader.navigationAllowed(request.url, type, documentToken)) pane.command("navigate", {url:request.url})
|
||||
return
|
||||
}
|
||||
request.action = known && reader.navigationAllowed(request.url, type, documentToken)
|
||||
? WebEngineView.AcceptRequest : WebEngineView.IgnoreRequest
|
||||
if (known && type !== WebEngineView.LinkClickedNavigation) permittedUrl = ""
|
||||
}
|
||||
onLoadingChanged: function(info) {
|
||||
if (info.status === WebEngineView.LoadStartedStatus) { pane.resetHeartbeat(web); ++documentSerial; documentReady = false }
|
||||
else if (info.status === WebEngineView.LoadSucceededStatus) {
|
||||
documentReady = true
|
||||
if (!isCompanion) {
|
||||
reader.webLoaded(documentToken, true)
|
||||
if (typeof reader.webDocumentLoaded === "function") reader.webDocumentLoaded(documentToken, url)
|
||||
}
|
||||
pane.applyLayout(web, function() {
|
||||
pane.applyBoundary(web)
|
||||
if (restorePending) { var saved = restorePending; restorePending = null; pane.restoreAfterLayout(web, saved, true) }
|
||||
})
|
||||
pane.collectIndex(web)
|
||||
pane.collectResourceIssues(web)
|
||||
} else if (info.status === WebEngineView.LoadFailedStatus && alive) {
|
||||
if (isCompanion) { pane.failedCompanionUrl = String(url); pane.closeCompanion(); pane.arrangePages() }
|
||||
else reader.webLoaded(documentToken, false)
|
||||
}
|
||||
}
|
||||
onNewWindowRequested: function(request) {
|
||||
if (request.userInitiated && reader.navigationAllowed(request.requestedUrl, WebEngineView.LinkClickedNavigation, documentToken)) {
|
||||
pane.command("navigate", {url:request.requestedUrl})
|
||||
}
|
||||
}
|
||||
onJavaScriptConsoleMessage: function(level, message, lineNumber, sourceId) { /* Document text is not written to application logs. */ }
|
||||
onPermissionRequested: function(permissionRequest) { permissionRequest.deny() }
|
||||
onFeaturePermissionRequested: function(origin, feature) { grantFeaturePermission(origin, feature, false) }
|
||||
onFileDialogRequested: function(request) { request.accepted = true; request.dialogReject() }
|
||||
onColorDialogRequested: function(request) { request.accepted = true; request.dialogReject() }
|
||||
onAuthenticationDialogRequested: function(request) { request.accepted = true; request.dialogReject() }
|
||||
onJavaScriptDialogRequested: function(request) { request.accepted = true; request.dialogReject() }
|
||||
onContextMenuRequested: function(request) { request.accepted = true }
|
||||
onFullScreenRequested: function(request) { request.reject() }
|
||||
onCertificateError: function(error) { error.rejectCertificate() }
|
||||
onQuotaRequested: function(request) { request.reject() }
|
||||
onRegisterProtocolHandlerRequested: function(request) { request.reject() }
|
||||
onFileSystemAccessRequested: function(request) { request.reject() }
|
||||
onSelectClientCertificate: function(selection) { selection.selectNone() }
|
||||
onDesktopMediaRequested: function(request) { request.cancel() }
|
||||
onWebAuthUxRequested: function(request) { request.cancel() }
|
||||
onRenderProcessTerminated: function(status, code) {
|
||||
if (!alive) return
|
||||
documentReady = false
|
||||
if (isCurrent || isCompanion) reader.renderStopped()
|
||||
else reader.webLoaded(documentToken, false)
|
||||
}
|
||||
onRenderProcessPidChanged: function(pid) {
|
||||
pane.trackRenderer(web, pid)
|
||||
}
|
||||
Timer {
|
||||
id: resizeDelay
|
||||
interval: 150
|
||||
onTriggered: {
|
||||
if (web.fixedLayout) pane.arrangePages()
|
||||
else pane.restoreAfterLayout(web, web.resizeLocation)
|
||||
web.resizeLocation = null
|
||||
}
|
||||
}
|
||||
function preserveResizeLocation() {
|
||||
if (!documentReady || pane.arrangingPages) return
|
||||
if (!resizeDelay.running) resizeLocation = lastLocation
|
||||
resizeDelay.restart()
|
||||
}
|
||||
function cancelResizeRestore() { resizeDelay.stop(); resizeLocation = null }
|
||||
onWidthChanged: preserveResizeLocation()
|
||||
onHeightChanged: preserveResizeLocation()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user