initial commit
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user