initial commit

This commit is contained in:
2026-09-21 13:41:40 +09:00
commit 855c7328df
411 changed files with 85352 additions and 0 deletions
+514
View File
@@ -0,0 +1,514 @@
import QtQuick
import QtTest
import QtWebEngine
import "../../qml" as App
Item {
id: harness
width: 900
height: 650
App.WebPane { id: pane; anchors.fill: parent }
TestCase {
id: test
name: "WebDocumentSecurity"
when: windowShown
function evaluate(source, world) {
var complete = false
var output
pane.activeView.runJavaScript(source, world === undefined ? WebEngineScript.ApplicationWorld : world, function(result) { output = result; complete = true })
tryVerify(function() { return complete }, 5000)
return output
}
function load(path) {
reader.open(path)
tryCompare(reader, "loaded", true, 15000)
tryVerify(function() { return pane.activeView && pane.activeView.documentReady }, 5000)
tryVerify(function() { return reader.index.headings && reader.index.headings.length > 0 }, 5000)
}
function anchorFraction(saved) {
var offset = Number(/:([0-9]+)\)$/.exec(saved.cfi)[1])
var coordinate = /^(vertical|sideways)/.test(saved.writingMode || "") ? "left/innerWidth" : "top/innerHeight"
return evaluate("(function(){var n=document.getElementById(" + JSON.stringify(saved.fragment) + ").firstChild;var r=document.createRange();r.setStart(n," + offset + ");r.collapse(true);return r.getBoundingClientRect()." + coordinate + "})()")
}
function cleanup() {
reader.releaseLateResources()
reader.deferCommit = false
reader.cancelPending()
reader.resumeRenderers()
}
function test_encoded_filename_data() {
return [{tag:"spaces", path:"space name.html"}, {tag:"Japanese", path:"日本語.html"},
{tag:"reserved characters", path:"hash#percent%.html"},
{tag:"mixed", path:"日本語 j k 123 #%.html"}]
}
function test_encoded_filename(data) {
load(data.path)
compare(evaluate("document.getElementById('value').textContent"), "Correct document")
compare(evaluate("decodeURIComponent(location.pathname).slice(1)"), data.path)
var original = String(pane.activeView.url)
wait(1200) // Canonical comparison must still reject author meta-refresh.
compare(String(pane.activeView.url), original)
compare(evaluate("document.getElementById('value').textContent"), "Correct document")
pane.command("navigate", {url:original + "#target"})
tryVerify(function() { return String(pane.activeView.url).endsWith("#target") }, 3000)
tryVerify(function() { return evaluate("scrollY") > 500 }, 3000)
}
function test_late_resource_restores_logical_character_data() {
return [{tag:"decoded image", kind:"image"}, {tag:"font swap", kind:"font"},
{tag:"vertical image", kind:"image", vertical:true}, {tag:"vertical font", kind:"font", vertical:true},
{tag:"native anchor image", kind:"image", nativeAnchor:true}, {tag:"native anchor font", kind:"font", nativeAnchor:true}]
}
function test_generated_heading_ignores_author_base() {
load("base.html")
compare(reader.index.headings[0].href, "base.html#one")
compare(reader.index.headings[1].href, "base.html#two")
compare(reader.index.outline[0].href, "folder/other.html#two")
pane.command("heading.index", {index:1})
tryVerify(function() { return evaluate("scrollY") > 1000 }, 3000)
verify(String(pane.activeView.url).endsWith("/base.html"))
}
function test_xhtml_native_and_aria_heading_levels_data() {
return [{tag:"XHTML", path:"headings.xhtml", mime:"application/xhtml+xml", nativeTag:"h1"},
{tag:"HTML", path:"headings.html", mime:"text/html", nativeTag:"H1"}]
}
function test_xhtml_native_and_aria_heading_levels(data) {
load(data.path)
compare(evaluate("document.contentType"), data.mime)
compare(evaluate("document.querySelector('h1').tagName"), data.nativeTag)
compare(reader.index.headings.length, 3)
compare(reader.index.headings.map(function(row) { return row.level }), [1, 2, 3])
compare(reader.index.headings.map(function(row) { return row.title }), ["Native first", "Native second", "ARIA third"])
compare(reader.index.outline.map(function(row) { return row.depth }), [0, 1, 2])
pane.command("heading.index", {index:0})
tryVerify(function() { return Math.abs(evaluate("document.querySelector('h1').getBoundingClientRect().top")) < 3 }, 3000)
pane.command("nav.heading_next", {})
tryVerify(function() { return Math.abs(evaluate("document.querySelector('h2').getBoundingClientRect().top")) < 3 }, 3000)
pane.command("nav.heading_previous", {})
tryVerify(function() { return Math.abs(evaluate("document.querySelector('h1').getBoundingClientRect().top")) < 3 }, 3000)
verify(String(pane.activeView.url).endsWith("/" + data.path))
}
function test_clamped_heading_destinations_are_not_reselected_data() {
return [{tag:"horizontal", path:"clamped-headings.xhtml"},
{tag:"vertical-rl", path:"clamped-headings-vertical.xhtml"}]
}
function test_nested_heading_scroll_counts_visible_movement() {
load("nested-headings.xhtml")
var result = evaluate("(()=>{var r=__docviewReader.command('nav.heading_next',{});return {result:r,windowY:scrollY,innerY:document.getElementById('scroller').scrollTop}})()")
compare(result.result.moved, true)
compare(result.result.title, "Inner last")
compare(result.windowY, 0)
verify(result.innerY > 700)
result = evaluate("__docviewReader.command('nav.heading_next',{})")
compare(result.moved, false)
compare(result.boundary, 1)
result = evaluate("__docviewReader.command('nav.heading_previous',{})")
compare(result.moved, true)
compare(result.title, "Inner first")
compare(evaluate("document.getElementById('scroller').scrollTop"), 0)
}
function test_clamped_heading_destinations_are_not_reselected(data) {
load(data.path)
compare(reader.index.headings.length, 4)
var next = evaluate("(()=>{__docviewReader.command('heading.index',{index:0});return [0,1,2,3].map(()=>{var r=__docviewReader.command('nav.heading_next',{});return {moved:r.moved,boundary:r.boundary||0,title:r.title||'',x:scrollX,y:scrollY}})})()")
compare(next[0].title, "Middle")
compare(next[1].title, "Last A")
verify(next[0].x !== next[1].x || next[0].y !== next[1].y)
compare(next[2].moved, false)
compare(next[2].boundary, 1)
compare(next[3].moved, false)
compare(next[3].boundary, 1)
compare(next[2].x, next[1].x); compare(next[2].y, next[1].y)
var previous = evaluate("[0,1,2].map(()=>{var r=__docviewReader.command('nav.heading_previous',{});return {moved:r.moved,boundary:r.boundary||0,title:r.title||''}})")
compare(previous[0].title, "Middle")
compare(previous[1].title, "First")
compare(previous[2].moved, false)
compare(previous[2].boundary, -1)
// Scrolling away permits the destination again; extending the
// layout makes the two former duplicates independently reachable.
var changed = evaluate("(()=>{__docviewReader.command('nav.document_end',{});__docviewReader.command(getComputedStyle(document.body).writingMode==='vertical-rl'?'scroll.right':'scroll.up',{});var resumed=__docviewReader.command('nav.heading_next',{});document.body.style.paddingBlockEnd='900px';var a=__docviewReader.command('nav.heading_next',{}),b=__docviewReader.command('nav.heading_next',{});return {resumed:resumed,a:a,b:b}})()")
compare(changed.resumed.moved, true)
compare(changed.resumed.title, "Last A")
compare(changed.a.moved, true); compare(changed.a.title, "Last A")
compare(changed.b.moved, true); compare(changed.b.title, "Last B")
}
function test_late_resource_restores_logical_character(data) {
load(data.vertical ? "late-vertical.html" : "late-layout.html")
if (data.nativeAnchor) evaluate("document.documentElement.style.overflowAnchor='auto'")
evaluate("document.getElementById('paragraph-40').scrollIntoView()")
var saved = evaluate("__docviewReader.location()")
var originalOffset = anchorFraction(saved)
var extent = data.vertical ? "document.body.scrollWidth" : "document.body.scrollHeight"
var originalHeight = evaluate(extent)
reader.holdLateResources = true
if (data.kind === "image") evaluate("document.getElementById('late-image').src='late-image.svg'")
else evaluate("document.body.style.fontFamily='Late, serif'")
tryVerify(function() { return reader.delayedResources > 0 }, 5000)
reader.releaseLateResources()
tryVerify(function() {
return data.kind === "image" ? evaluate("document.getElementById('late-image').naturalHeight") === 900 : evaluate("document.fonts.check('14px Late')")
}, 5000)
tryVerify(function() { return Math.abs(anchorFraction(saved) - originalOffset) < 0.04 }, 5000)
verify(evaluate(extent) > originalHeight + 700)
compare(evaluate("__docviewReader.location().cfi"), saved.cfi)
compare(evaluate("getComputedStyle(document.documentElement).overflowAnchor"), data.nativeAnchor ? "auto" : "none")
}
function test_late_image_after_continued_scroll() {
load("late-layout.html")
reader.holdLateResources = true
evaluate("document.getElementById('late-image').src='late-image.svg'")
tryVerify(function() { return reader.delayedResources > 0 }, 5000)
// The final scroll has no intervening location poll. This exercises
// the scroll-event path also used by scrollbar dragging/inertia.
var saved = evaluate("(function(){document.getElementById('paragraph-40').scrollIntoView();var s=__docviewReader.location();document.getElementById('paragraph-20').scrollIntoView();__docviewReader.location();document.getElementById('paragraph-40').scrollIntoView();return s})()")
var offset = anchorFraction(saved)
reader.releaseLateResources()
tryVerify(function() { return evaluate("document.getElementById('late-image').naturalHeight") === 900 }, 5000)
tryVerify(function() { return Math.abs(anchorFraction(saved) - offset) < 0.04 }, 3000)
compare(evaluate("__docviewReader.location().cfi"), saved.cfi)
}
function test_late_image_after_pointer_without_scroll() {
load("late-layout.html")
evaluate("document.getElementById('paragraph-40').scrollIntoView()")
var saved = evaluate("__docviewReader.location()"), offset = anchorFraction(saved)
reader.holdLateResources = true
evaluate("document.getElementById('late-image').src='late-image.svg'")
tryVerify(function() { return reader.delayedResources > 0 }, 5000)
mouseClick(pane.activeView, 400, 100)
reader.releaseLateResources()
tryVerify(function() { return evaluate("document.getElementById('late-image').naturalHeight") === 900 }, 5000)
tryVerify(function() { return Math.abs(anchorFraction(saved) - offset) < 0.04 }, 3000)
}
function test_layout_change_before_location_poll() {
load("late-layout.html")
evaluate("document.getElementById('paragraph-40').scrollIntoView()")
var saved = evaluate("__docviewReader.location()"), offset = anchorFraction(saved)
// Model a layout update observed by the heartbeat before the
// corresponding image/font completion event is dispatched.
evaluate("document.body.style.paddingTop='900px';__docviewReader.location()")
tryVerify(function() { return Math.abs(anchorFraction(saved) - offset) < 0.04 }, 3000)
compare(evaluate("__docviewReader.location().cfi"), saved.cfi)
}
function test_late_image_preserves_new_navigation() {
load("late-layout.html")
evaluate("document.getElementById('paragraph-40').scrollIntoView();__docviewReader.location()")
reader.holdLateResources = true
evaluate("document.getElementById('late-image').src='late-image.svg'")
tryVerify(function() { return reader.delayedResources > 0 }, 5000)
pane.command("nav.document_end", {})
tryVerify(function() { return evaluate("__docviewReader.location().progression") > 0.999 }, 5000)
var current = evaluate("__docviewReader.location()")
reader.releaseLateResources()
tryCompare(pane.activeView, "heartbeatPending", 0, 3000)
tryVerify(function() { return evaluate("document.getElementById('late-image').naturalHeight") === 900 }, 5000)
tryVerify(function() { return evaluate("__docviewReader.location().progression") > 0.999 }, 5000)
compare(evaluate("__docviewReader.location().cfi"), current.cfi)
}
function test_renderer_response_timeout_active_and_reopen() {
if (!reader.canPauseRenderers) skip("Real SIGSTOP test requires Linux pidfd")
load("reflow.html")
var view = pane.activeView, failures = reader.watchdogFailures
tryVerify(function() { return reader.hasRenderer(view.documentToken, view.rendererSlot) }, 5000)
tryCompare(view, "heartbeatPending", 0, 3000)
verify(reader.pauseRenderer(view.documentToken, view.rendererSlot))
pane.heartbeat(view)
verify(view.heartbeatPending > 0)
var starts = reader.heartbeatStarts, probe = view.heartbeatPending
for (var i = 0; i < 10; ++i) pane.heartbeat(view)
compare(reader.heartbeatStarts, starts)
compare(view.heartbeatPending, probe)
tryCompare(reader, "watchdogFailures", failures + 1, 6000)
compare(pane.activeView, null)
compare(reader.notice, "E_RENDERER_TIMEOUT")
load("reflow.html")
compare(evaluate("document.querySelector('h1').textContent"), "Logical position")
}
function test_renderer_response_timeout_pending_preserves_old_document() {
if (!reader.canPauseRenderers) skip("Real SIGSTOP test requires Linux pidfd")
load("reflow.html")
var old = pane.activeView, failures = reader.watchdogFailures
reader.deferCommit = true
reader.open("vertical.html")
tryVerify(function() { return pane.pendingView && pane.pendingView.documentReady }, 5000)
var pending = pane.pendingView
verify(pending.renderProcessPid !== old.renderProcessPid)
tryVerify(function() { return reader.hasRenderer(pending.documentToken, pending.rendererSlot) }, 5000)
tryCompare(pending, "heartbeatPending", 0, 3000)
verify(reader.pauseRenderer(pending.documentToken, pending.rendererSlot))
pane.heartbeat(pending)
tryCompare(reader, "watchdogFailures", failures + 1, 6000)
compare(pane.pendingView, null)
compare(pane.activeView, old)
compare(evaluate("document.querySelector('h1').textContent"), "Logical position")
}
function test_renderer_response_probe_cancelled_with_pending_document() {
if (!reader.canPauseRenderers) skip("Real SIGSTOP test requires Linux pidfd")
load("reflow.html")
var old = pane.activeView, failures = reader.watchdogFailures
reader.deferCommit = true
reader.open("vertical.html")
tryVerify(function() { return pane.pendingView && pane.pendingView.documentReady }, 5000)
var pending = pane.pendingView
tryVerify(function() { return reader.hasRenderer(pending.documentToken, pending.rendererSlot) }, 5000)
tryCompare(pending, "heartbeatPending", 0, 3000)
verify(reader.pauseRenderer(pending.documentToken, pending.rendererSlot))
pane.heartbeat(pending)
verify(pending.heartbeatPending > 0)
reader.cancelPending()
reader.resumeRenderers()
wait(2000)
compare(reader.watchdogFailures, failures)
compare(pane.pendingView, null)
compare(pane.activeView, old)
compare(evaluate("document.querySelector('h1').textContent"), "Logical position")
}
function test_spread_pairing_boundaries() {
function row(side, policy, layout) { return {href:"p.xhtml",linear:true,layout:layout||"fixed",spread:side||"",renditionSpread:policy||"both"} }
var spine = [row("left"),row("right"),row("center"),row("left"),row("right"),row("","none"),row("","both","reflowable")]
compare(pane.spreadFor(spine,0,false,true), {left:0,right:1})
compare(pane.spreadFor(spine,1,false,true), {left:0,right:1})
compare(pane.spreadFor(spine,2,false,true), {center:2})
compare(pane.spreadFor(spine,4,false,true), {left:3,right:4})
compare(pane.spreadFor(spine,5,false,true), {center:5})
compare(pane.spreadFor(spine,6,false,true), {center:6})
compare(pane.spreadFor([row("right"),row("left")],0,true,true), {right:0,left:1})
compare(pane.spreadFor([row("left"),row("left"),row("right")],0,false,true), {left:0})
compare(pane.spreadFor([row("left"),row("left"),row("right")],1,false,true), {left:1,right:2})
compare(pane.spreadFor([row("left","landscape"),row("right","landscape")],0,false,false), {center:0})
compare(pane.spreadFor([row("left","auto"),row("right","auto")],0,false,true), {left:0,right:1})
compare(pane.spreadFor([row(),row(),row()],0,false,true), {right:0})
compare(pane.spreadFor([row(),row(),row()],1,false,true), {left:1,right:2})
}
function test_resource_issues_count_real_interceptor_denial() {
load("index.html")
reader.allowNetworkProbe = true
pane.activeView.permittedUrl = String(reader.networkProbeUrl)
pane.activeView.url = reader.networkProbeUrl
tryVerify(function() { return reader.interceptedRequests > 0 }, 5000)
compare(reader.forbiddenConnections, 0)
reader.allowNetworkProbe = false
}
function test_resource_issues_count_real_csp_events() {
load("index.html")
tryVerify(function() { return reader.resourceIssues["csp.style"] >= 1 && reader.resourceIssues["csp.font"] >= 1 && reader.resourceIssues["csp.image"] >= 1 }, 5000)
compare(reader.forbiddenConnections, 0)
var previous = JSON.stringify(reader.resourceIssues)
// Forged events cannot add warnings, including from the privileged
// test world. Real events above came from authored resource loads.
evaluate("document.dispatchEvent(new SecurityPolicyViolationEvent('securitypolicyviolation',{effectiveDirective:'img-src',disposition:'enforce'}))")
wait(500)
compare(JSON.stringify(reader.resourceIssues), previous)
pane.collectResourceIssues(pane.activeView)
wait(100)
compare(JSON.stringify(reader.resourceIssues), previous)
Object.keys(reader.resourceIssues).forEach(function(key) {
verify(/^csp\.(image|style|font|script|frame|connect|form|base|object|media|other)$/.test(key))
verify(Number.isInteger(reader.resourceIssues[key]) && reader.resourceIssues[key] > 0)
})
}
function test_static_boundary_and_resources() {
load("index.html")
compare(evaluate("document.getElementById('marker').textContent"), "SCRIPT BLOCKED")
compare(evaluate("typeof globalThis.__docviewReader"), "object")
var mainWorld = evaluate("typeof globalThis.__docviewReader", WebEngineScript.MainWorld)
verify(mainWorld === undefined || mainWorld === "undefined")
compare(evaluate("getComputedStyle(document.querySelector('h1')).color"), "rgb(17, 34, 51)")
verify(evaluate("document.getElementById('local-image').complete && document.getElementById('local-image').naturalWidth===80"))
compare(reader.index.headings.length, 3)
compare(reader.index.outline.length, 1)
compare(reader.index.headings[2].source, "html-heading")
// Observe the authored refresh and any deferred CSS/font requests.
wait(1400)
verify(String(pane.activeView.url).endsWith("/index.html"))
compare(reader.forbiddenConnections, 0)
compare(evaluate("document.querySelectorAll('iframe').length"), 2)
verify(evaluate("Array.from(document.querySelectorAll('iframe')).every(f=>{try{return !f.contentDocument||!f.contentDocument.body||!f.contentDocument.body.textContent}catch(e){return true}})"))
compare(evaluate("document.getElementById('submit').click(); document.getElementById('input').value"), "ORIGINAL")
pane.focusContent()
evaluate("document.getElementById('input').focus()")
keyClick(Qt.Key_X)
compare(evaluate("document.getElementById('input').value"), "ORIGINAL")
wait(100)
compare(reader.forbiddenConnections, 0)
}
function test_headings_and_cfi_restoration() {
load("index.html")
var before = evaluate("document.documentElement.outerHTML")
pane.command("nav.heading_next", {})
wait(150)
pane.command("nav.heading_next", {})
tryVerify(function() { return evaluate("window.scrollY") > 400 }, 3000)
var saved = evaluate("__docviewReader.location()")
verify(saved.cfi.indexOf("epubcfi(") === 0)
verify(saved.progression > 0)
pane.command("nav.document_start", {})
tryVerify(function() { return evaluate("window.scrollY") < 10 }, 3000)
pane.command("location.restore", saved)
tryVerify(function() { return evaluate("window.scrollY") > 400 }, 3000)
// Indexing and position helpers do not insert IDs or nodes.
compare(evaluate("document.documentElement.outerHTML"), before)
}
function test_vertical_reading_axis() {
load("vertical.html")
compare(evaluate("getComputedStyle(document.body).writingMode"), "vertical-rl")
pane.command("scroll.half_down", {})
tryVerify(function() { return evaluate("window.scrollX") < -100 }, 3000)
verify(evaluate("__docviewReader.location().progression") > 0)
pane.command("nav.document_start", {})
tryVerify(function() { return Math.abs(evaluate("window.scrollX")) < 2 }, 3000)
}
function test_keyboard_link_navigation() {
load("index.html")
pane.focusContent()
pane.command("link.next", {})
tryCompare(pane.activeView, "activeFocus", true, 2000)
compare(evaluate("document.activeElement.textContent"), "Second heading")
pane.command("link.activate", {})
tryVerify(function() { return String(pane.activeView.url).endsWith("#second") }, 5000)
verify(evaluate("window.scrollY") > 400)
compare(reader.forbiddenConnections, 0)
}
function test_link_clear_removes_selection_and_restores_style() {
load("index.html")
pane.focusContent()
var originalUrl = String(pane.activeView.url)
var authored = evaluate("(()=>{var a=document.querySelector('nav a');a.style.setProperty('outline-color','red','important');a.style.setProperty('outline-width','5px','important');a.style.setProperty('outline-style','dotted','important');a.style.setProperty('outline-offset','7px','important');return a.getAttribute('style')})()")
pane.command("link.next", {})
tryCompare(pane.activeView, "activeFocus", true, 2000)
tryVerify(function() { return evaluate("document.activeElement.localName") === "a" }, 3000)
pane.command("link.clear", {})
tryVerify(function() { return evaluate("__docviewReader.command('link.activate',{}).empty") === true }, 3000)
compare(evaluate("document.querySelector('nav a').getAttribute('style')"), authored)
verify(evaluate("document.activeElement.localName") !== "a")
pane.command("link.activate", {})
tryCompare(reader, "notice", "Tabでリンクを選択してください", 3000)
compare(String(pane.activeView.url), originalUrl)
pane.command("link.next", {})
tryVerify(function() { return evaluate("document.activeElement.textContent") === "Second heading" }, 3000)
}
function test_heading_boundary_is_bound_to_navigation() {
load("headings.xhtml")
var resource = String(pane.activeView.url)
pane.command("navigate", {url:resource, boundary:"heading.last"})
tryVerify(function() { return Math.abs(evaluate("document.getElementById('aria').getBoundingClientRect().top")) < 3 }, 3000)
pane.command("navigate", {url:resource, boundary:"heading.first"})
tryVerify(function() { return Math.abs(evaluate("document.querySelector('h1').getBoundingClientRect().top")) < 3 }, 3000)
// A replacement navigation must not inherit a previous heading target.
pane.command("navigate", {url:resource, boundary:"heading.last"})
pane.command("navigate", {url:resource + "#two"})
tryVerify(function() { return Math.abs(evaluate("document.querySelector('h2').getBoundingClientRect().top")) < 3 }, 3000)
compare(pane.activeView.pendingBoundary, "")
}
function test_typography_preserves_logical_text() {
load("reflow.html")
var original = evaluate("document.documentElement.outerHTML")
compare(evaluate("getComputedStyle(document.body).fontSize"), "14px")
evaluate("document.getElementById('paragraph-35').scrollIntoView();scrollBy(0,30)")
var saved = evaluate("__docviewReader.location()")
verify(saved.textQuote.length > 20)
var count = evaluate("document.querySelectorAll('*').length")
// Change immediately: restoration must not depend on the polling timer.
reader.changeReading(30, 2, false)
tryCompare(pane.activeView, "documentReady", true)
tryVerify(function() { return evaluate("getComputedStyle(document.body).fontSize") === "30px" }, 5000)
wait(250)
var after = evaluate("__docviewReader.location()")
compare(after.cfi.split(":")[0], saved.cfi.split(":")[0])
verify(Math.abs(anchorFraction(saved) - saved.offsetY) < 0.01, JSON.stringify({before:saved, after:evaluate("__docviewReader.location()"), anchor:anchorFraction(saved)}))
compare(evaluate("getComputedStyle(document.getElementById('paragraph-35')).fontSize"), "30px")
compare(evaluate("getComputedStyle(document.getElementById('paragraph-35')).lineHeight"), "60px")
compare(evaluate("document.querySelectorAll('*').length"), count)
reader.changeReading(18, 1.6, true)
tryVerify(function() { return evaluate("getComputedStyle(document.body).fontSize") === "14px" }, 5000)
wait(200)
compare(evaluate("__docviewReader.location().cfi.split(':')[0]"), saved.cfi.split(":")[0])
compare(evaluate("document.documentElement.outerHTML"), original)
// Reopen the same authored DOM in a fresh document session.
load("reflow.html")
pane.command("location.restore", saved)
tryVerify(function() { return Math.abs(anchorFraction(saved) - saved.offsetY) < 0.01 }, 5000)
pane.command("scroll.half_down", {})
tryVerify(function() { return evaluate("__docviewReader.location().cfi") !== saved.cfi }, 3000)
reader.notice = ""
pane.command("location.restore", {cfi:"epubcfi(/broken)", fragment:saved.fragment, progression:0.5})
tryVerify(function() { return reader.notice.indexOf("E_LOCATOR_UNRESOLVED") >= 0 }, 3000)
}
function test_resize_preserves_logical_text() {
load("reflow.html")
evaluate("document.getElementById('paragraph-42').scrollIntoView();scrollBy(0,25)")
var saved = evaluate("__docviewReader.location()")
pane.updateLocation(pane.activeView)
tryVerify(function() { return pane.activeView.lastLocation && pane.activeView.lastLocation.fragment === saved.fragment }, 3000)
var oldWidth = harness.width
harness.width = 620
wait(350)
verify(Math.abs(anchorFraction(saved) - saved.offsetY) < 0.01, JSON.stringify({before:saved, after:evaluate("__docviewReader.location()"), anchor:anchorFraction(saved)}))
harness.width = oldWidth
wait(350)
verify(Math.abs(anchorFraction(saved) - saved.offsetY) < 0.01, JSON.stringify({before:saved, after:evaluate("__docviewReader.location()"), anchor:anchorFraction(saved)}))
}
function test_fixed_viewport_ignores_font_override() {
load("fixed.html")
tryCompare(pane.activeView, "fixedLayout", true)
compare(pane.activeView.fixedViewport.width, 800)
compare(pane.activeView.fixedViewport.height, 600)
verify(Math.abs(pane.activeView.zoomFactor - Math.min(pane.width / 800, pane.height / 600)) < 0.02)
verify(Math.abs(evaluate("document.getElementById('frame').getBoundingClientRect().width") - 800) < 0.05)
reader.changeReading(40, 2.5, false)
wait(250)
compare(evaluate("getComputedStyle(document.body).fontSize"), "19px")
verify(Math.abs(evaluate("document.getElementById('frame').getBoundingClientRect().height") - 600) < 0.05)
}
function test_rtl_direction_and_logical_navigation() {
load("rtl.html")
compare(evaluate("getComputedStyle(document.body).direction"), "rtl")
pane.command("scroll.half_down", {})
tryVerify(function() { return evaluate("scrollY") > 100 }, 3000)
compare(evaluate("scrollX"), 0)
pane.command("nav.document_start", {})
tryVerify(function() { return evaluate("scrollY") < 2 }, 3000)
pane.command("nav.heading_next", {})
wait(100)
pane.command("nav.heading_next", {})
tryVerify(function() { return evaluate("scrollY") > 1000 }, 3000)
compare(evaluate("getComputedStyle(document.body).direction"), "rtl")
}
function test_search_forward_backward_and_wrap() {
load("search.html")
var result = null
function found(value) { result = {count:value.numberOfMatches, active:value.activeMatch} }
pane.activeView.findTextFinished.connect(found)
pane.command("search", {query:"needle"})
tryVerify(function() { return result && result.count === 3 && result.active === 1 }, 5000)
result = null
pane.command("search.next", {})
tryVerify(function() { return result && result.active === 2 }, 5000)
result = null
pane.command("search.previous", {})
tryVerify(function() { return result && result.active === 1 }, 5000)
result = null
pane.command("search.previous", {})
tryVerify(function() { return result && result.active === 3 }, 5000)
result = null
pane.command("search.next", {})
tryVerify(function() { return result && result.active === 1 }, 5000)
pane.command("nav.document_start", {})
result = null
pane.command("search", {query:"日本語"})
tryVerify(function() { return result && result.count === 2 }, 5000)
pane.activeView.findTextFinished.disconnect(found)
}
function test_search_cancel_discards_completion() {
load("search.html")
reader.notice = ""
var cleared = false
function found(value) { if (value.numberOfMatches === 0) cleared = true }
pane.activeView.findTextFinished.connect(found)
pane.command("search", {query:"not-present-in-this-document"})
pane.command("search.cancel", {})
tryVerify(function() { return cleared }, 5000)
wait(100)
compare(reader.notice, "")
pane.activeView.findTextFinished.disconnect(found)
}
}
}