XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFESystem

【Rust】XFE系统,基于Rust和汇编

公开
关注 0 Fork 0 Star 0
UTF-8
"""Measure real Markdown first presentation, before enhancement JavaScript.

Only physical QMP input is used. DOM diagnostics are read-only, the screenshot
is the guest framebuffer, and a timing miss is retained as a failed objective.
"""
import hashlib
import json
from pathlib import Path
import re
import time
from PIL import Image

ROOT = Path(__file__).resolve().parents[1]
PRESENT = re.compile(r'XFE_DOCUMENT_FIRST_PRESENTED\|Ticks\|(\d+)\|Frequency\|(\d+)\|OriginalStyles\|(\d+)\|ExecutedScripts\|(\d+)')
PROOF = re.compile(r'XFE_DOCUMENT_FIRST_PROOF_BEGIN\r?\n(.*?)\r?\nXFE_DOCUMENT_FIRST_PROOF_END', re.S)


def fingerprint(source):
    return bytes_fingerprint(source.encode('utf-16-le'))


def bytes_fingerprint(data):
    value = 14695981039346656037
    for byte in data:
        value = ((value ^ byte) * 1099511628211) & ((1 << 64) - 1)
    return f'{value:016x}'


def verify_first_frame_binding(path, proof):
    samples = []
    with Image.open(path) as original:
        image = original.convert('RGB')
        for kind in ('Heading', 'Paragraph'):
            binding = proof[kind].get('PresentedPixels')
            assert binding and binding['FingerprintAlgorithm'] == 'RGB24-FNV1a64', ('Missing actual scanout fingerprint', kind)
            rect = binding['NativeBounds']
            x, y, width, height = (rect[key] for key in ('X', 'Y', 'Width', 'Height'))
            assert x >= 0 and y >= 0 and width > 0 and height > 0 and width * height <= 262144
            assert x + width <= image.width and y + height <= image.height
            actual = bytes_fingerprint(image.crop((x, y, x + width, y + height)).tobytes())
            assert actual == binding['Fingerprint'], ('Screenshot differs from actual first-presented scanout pixels', kind, binding, actual)
            assert binding['PresentationSequence'] > 0
            samples.append({'Kind': kind, 'NativeBounds': rect, 'RgbFingerprint': actual,
                            'PresentationSequence': binding['PresentationSequence']})
    assert samples[0]['PresentationSequence'] == samples[1]['PresentationSequence']
    return {'Passed': True, 'FirstPresentedScanoutRegionsMatchScreenshot': True, 'Samples': samples,
            'FingerprintIsCryptographic': False, 'ScreenshotSha256BoundSeparately': True}


def verify_pixels(path, proof, scale):
    viewport = proof['Viewport']
    samples = []
    with Image.open(path) as original:
        image = original.convert('RGB')
        for kind in ('Heading', 'Paragraph'):
            element = proof[kind]
            assert element and element['Text'].strip(), (kind, element)
            ink = 0
            for run in element['Runs']:
                if not run['Text'].strip():
                    continue
                bounds = run['Bounds']
                left = max(0, bounds['X']); top = max(0, bounds['Y'])
                right = min(viewport['Width'], bounds['X'] + bounds['Width'])
                bottom = min(viewport['Height'], bounds['Y'] + bounds['Height'])
                if right <= left or bottom <= top:
                    continue
                rect = [round((viewport['X'] + left) * scale), round((viewport['Y'] + top) * scale),
                        round((viewport['X'] + right) * scale), round((viewport['Y'] + bottom) * scale)]
                color = run['Color']; rgb = ((color >> 16) & 255, (color >> 8) & 255, color & 255)
                crop = image.crop(rect)
                colors = crop.getcolors(crop.width * crop.height)
                dominant = max(colors, key=lambda pair: pair[0])[1]
                count = sum(all(abs(pixel[channel] - rgb[channel]) <= 12 for channel in range(3))
                            for pixel in crop.getdata())
                # A blank foreground-colored rectangle is not text. Require a
                # contrasting, dominant background and sparse foreground ink.
                contrast = max(abs(dominant[channel] - rgb[channel]) for channel in range(3))
                area = crop.width * crop.height
                valid = contrast >= 32 and max(4 * scale * scale, area * .01) <= count <= area * .8
                if valid:
                    ink += count
                samples.append({'Kind': kind, 'Text': run['Text'], 'BoundsPhysical': rect,
                                'ExpectedRgb': list(rgb), 'MatchingPixels': count,
                                'DominantBackgroundRgb': list(dominant), 'Contrast': contrast,
                                'ForegroundCoverage': count / area, 'TextInkAccepted': valid})
            assert ink >= 4 * scale * scale, ('No actual first-frame text ink', kind, ink, samples)
    return {'Passed': True, 'HeadingAndParagraphPainted': True, 'Samples': samples,
            'Meaning': 'Real text ink inside guest DOM text-run rectangles; no placeholder or host-rendered substitute.'}


def run_document_first_paint(qmp, read_log, wait_log, screenshot, width, height, scale, pointer):
    fixture = ROOT / 'Source/XFESystem.Documents/Assets/Website/Fixtures/WebsiteParity.md'
    source = fixture.read_text(encoding='utf-8-sig').replace('\r\n', '\n')
    result = {'Kind': 'NativeDocumentFirstPaint', 'Passed': False, 'Samples': [],
              'FixturePath': str(fixture), 'FixtureSha256': hashlib.sha256(fixture.read_bytes()).hexdigest(),
              'GpuRendering': False, 'CudaExecution': False, 'DomInjection': False,
              'TimingTargetSeconds': 1.0, 'FirstPaintRequirementMet': False}
    folder = None

    def move(x, y):
        target = [round(x * scale), round(y * scale)]
        while pointer != target:
            dx = max(-80, min(80, target[0] - pointer[0])); dy = max(-80, min(80, target[1] - pointer[1]))
            qmp('input-send-event', {'events': [
                {'type': 'rel', 'data': {'axis': 'x', 'value': dx}},
                {'type': 'rel', 'data': {'axis': 'y', 'value': dy}}]})
            pointer[0] += dx; pointer[1] += dy
            time.sleep(.018)

    def click(x, y):
        move(x, y)
        started = time.monotonic()
        qmp('input-send-event', {'events': [{'type': 'btn', 'data': {'down': True, 'button': 'left'}}]})
        time.sleep(.06)
        qmp('input-send-event', {'events': [{'type': 'btn', 'data': {'down': False, 'button': 'left'}}]})
        return started

    def wait_first(start, clicked, timeout=120):
        deadline = time.monotonic() + timeout
        first_observed = None
        while time.monotonic() < deadline:
            log = read_log()[start:]
            assert not any(value in log for value in ('CPU EXCEPTION:', '[FAILFAST]', '*** UNHANDLED EXCEPTION')), log[-4000:]
            markers = list(PRESENT.finditer(log)); proofs = list(PROOF.finditer(log))
            if markers and first_observed is None:
                first_observed = time.monotonic() - clicked
            if markers and proofs:
                return markers[-1], json.loads(proofs[-1].group(1)), first_observed
            time.sleep(.01)
        raise AssertionError('No complete native first-frame proof after actual Preview click')

    try:
        start = len(read_log()); click(60, 120)
        wait_log('XFE_DESKTOP_ACTIVE|Files', start, 30)
        start = len(read_log()); click(220, 258)
        wait_log('XFE_DESKTOP_ACTIVE|Editor', start, 30)
        folder = Path(screenshot('DocumentFirstPaint-Source')).parent
        for index in range(2):
            move(300, 137)
            start = len(read_log()); clicked = click(300, 137)
            marker, proof, observed = wait_first(start, clicked)
            elapsed = int(marker.group(1)) / int(marker.group(2))
            assert proof['Kind'] == 'ActualNativeFirstDocumentPaint' and proof['ReadOnly']
            assert proof['NoHostScreenshotSubstitution'] and not proof['JavaScriptExecutedForProof']
            assert proof['SourceFingerprintAlgorithm'] == 'UTF16LE-FNV1a64'
            assert proof['SourceFingerprint'] == fingerprint(source)
            assert proof['SourceCharacters'] == len(source.encode('utf-16-le')) // 2
            assert proof['OriginalStyleCount'] == int(marker.group(3)) == 9
            assert proof['ExecutedScriptCount'] == int(marker.group(4)) == 0
            assert proof['Heading']['Text'] == 'XFESystem 文档预览'
            assert proof['ScalePercent'] == round(scale * 100)
            # Pause only after the completed-presentation marker and full proof
            # have arrived, to inspect that actual frame without running later
            # scripts during capture. Pause/capture is outside the timing span.
            qmp('stop')
            try:
                frame = Path(screenshot('DocumentFirstPaint-' + str(index))).resolve()
                pixels = verify_pixels(frame, proof, scale)
                binding = verify_first_frame_binding(frame, proof)
            finally:
                qmp('cont')
            sample = {'NativeClickToPresentedSeconds': elapsed,
                      'HostButtonDownToPresentationMarkerSeconds': observed,
                      'UnderOneSecond': elapsed < 1.0 and observed < 1.0,
                      'Proof': proof, 'Frame': {'Path': str(frame), 'Sha256': hashlib.sha256(frame.read_bytes()).hexdigest(),
                                               'PixelEvidence': pixels, 'FirstFrameBinding': binding},
                      'CapturePauseOutsideTiming': True}
            result['Samples'].append(sample)
            before = len(read_log()); requested = time.monotonic()
            qmp('human-monitor-command', {'command-line': 'sendkey alt-f4'})
            wait_log('XFE_DESKTOP_ACTIVE|Editor', before, 120)
            sample['CloseAfterFirstFrameSeconds'] = time.monotonic() - requested
            time.sleep(.15)
        result['FirstPaintRequirementMet'] = all(sample['UnderOneSecond'] for sample in result['Samples'])
        result['Passed'] = True  # Correctness proof; the timing objective has its own explicit result.
        return result
    except Exception as error:
        result['Failure'] = str(error)
        raise
    finally:
        if folder:
            (folder / 'DocumentFirstPaint.json').write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding='utf-8')