File size: 6,345 Bytes
2ecdf7d
 
 
 
 
 
f0cd651
2ecdf7d
 
 
 
 
 
f0cd651
2ecdf7d
 
 
f0cd651
2ecdf7d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4754d8a
2ecdf7d
 
 
 
 
 
 
 
 
f0cd651
 
 
 
 
2ecdf7d
 
 
 
 
 
 
4754d8a
 
 
 
 
72bf1e8
2ecdf7d
 
 
 
 
 
 
 
 
 
 
72bf1e8
2ecdf7d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
const assert = require("assert");
const fs = require("fs");
const path = require("path");
const vm = require("vm");

const source = "https://reader.test/api/reader-bucket-resource?path=objects/aa/" + "a".repeat(64) + "/" + "b".repeat(16) + "/page-manifest.json";
function loadPreload({ chunks = [], header, security = true, blockRead = false, status = 200, saveData = true, url = source } = {}) {
  const timers = new Map(), listeners = new Map(), state = { fetches: 0, reads: 0, cancels: 0, imageSrcs: [] };
  class ImageMock { set src(value) { state.imageSrcs.push(value); this.currentSrc = value; } get src() { return this.currentSrc; } }
  let readStarted;
  const reading = new Promise(resolve => { readStarted = resolve; });
  const sandbox = {
    self: {}, URL, URLSearchParams, TextDecoder, Uint8Array, DataView, Response, AbortController,
    location: { origin: "https://reader.test", search: "?url=" + encodeURIComponent(url) },
    navigator: { connection: { saveData } }, Image: ImageMock,
    addEventListener(type, listener) { listeners.set(type, listener); },
    removeEventListener(type, listener) { if (listeners.get(type) === listener) listeners.delete(type); },
    setTimeout(callback, ms) { state.timeoutMs = ms; timers.set(1, callback); return 1; },
    clearTimeout(id) { timers.delete(id); },
    fetch: async (url, options) => {
      state.fetches++;
      state.url = url;
      state.signal = options.signal;
      return {
        status, statusText: status === 200 ? "OK" : "Unavailable",
        headers: new Headers(header ? { "content-length": header } : {}),
        body: {
          cancel: async () => { state.cancels++; },
          getReader: () => ({
            read: async () => {
              readStarted();
              if (blockRead) return new Promise((resolve, reject) => options.signal.addEventListener("abort", () => reject(new DOMException("timeout", "AbortError")), { once: true }));
              const value = chunks[state.reads++];
              return value === undefined ? { done: true } : { done: false, value };
            },
            cancel: async () => { state.cancels++; },
            releaseLock() {},
          }),
        },
      };
    },
  };
  sandbox.self = sandbox;
  if (security) {
    vm.runInNewContext(fs.readFileSync(path.join(__dirname, "../static/reader-security.js"), "utf8"), sandbox);
    sandbox.VoiceOfMLReaderSecurity = {
      ...sandbox.VoiceOfMLReaderSecurity,
      LIMITS: { ...sandbox.VoiceOfMLReaderSecurity.LIMITS, manifestBytes: 8 },
    };
  }
  vm.runInNewContext(fs.readFileSync(path.join(__dirname, "../static/reader-contract.js"), "utf8"), sandbox);
  return { sandbox, preload: sandbox.__VOICE_PDF_PRELOAD__, contract: sandbox.VoiceOfMLReader, timers, listeners, state, reading };
}

async function main() {
  const missing = loadPreload({ security: false });
  assert.strictEqual(missing.state.fetches, 0);
  assert.strictEqual(missing.preload, undefined);
  assert.strictEqual(missing.contract.capability("pdf").mode, "pdf");

  const valid = loadPreload({ chunks: [Buffer.from("1234"), Buffer.from("5678")], header: "8" });
  assert.strictEqual(valid.state.timeoutMs, 120000);
  const directUrl = "https://huggingface.co/datasets/vomebook/Reader-Assets/resolve/main/objects/aa/" + "a".repeat(64) + "/1234567890abcdef/page-manifest.json";
  const direct = loadPreload({ url: directUrl, chunks: [Buffer.from("ok")] });
  assert.strictEqual(direct.state.timeoutMs, 1500);
  await direct.preload.manifest;
  assert.strictEqual(await (await valid.preload.manifest).text(), "12345678");
  assert.strictEqual(valid.state.url, source);
  assert.strictEqual(valid.state.cancels, 0);
  assert.strictEqual(valid.timers.size, 0);
  assert.strictEqual(valid.preload.image, null);

  const image = loadPreload({ chunks: [Buffer.from("ok")], saveData: false });
  image.sandbox.VoiceOfMLReader = { pdfPageSource() {}, txtRelativePath() {} };
  vm.runInNewContext(fs.readFileSync(path.join(__dirname, "../static/reader-contract.js"), "utf8"), image.sandbox);
  assert.strictEqual(typeof image.sandbox.VoiceOfMLReader.assetFields, "function");
  assert.strictEqual(image.sandbox.__VOICE_PDF_PRELOAD__, image.preload);
  assert.strictEqual(image.state.fetches, 1);
  assert.strictEqual(image.state.imageSrcs.length, 0);
  const imageRequest = image.preload.manifest;
  image.preload.dispose();
  image.preload.dispose();
  assert.strictEqual(image.preload.image, null);
  assert.strictEqual(image.listeners.has("pagehide"), false);
  await imageRequest;

  const bfcache = loadPreload({ chunks: [Buffer.from("ok")], saveData: false });
  const bfcacheListener = bfcache.listeners.get("pagehide");
  bfcacheListener({ persisted: true });
  assert.strictEqual(bfcache.listeners.has("pagehide"), true);
  assert.strictEqual(bfcache.preload.image, null);
  bfcacheListener({ persisted: false });
  assert.strictEqual(bfcache.listeners.has("pagehide"), false);
  assert.strictEqual(bfcache.preload.image, null);
  await bfcache.preload.manifest;

  const oversized = loadPreload({ header: "9" });
  await assert.rejects(oversized.preload.manifest, /READER_RESOURCE_LIMIT/);
  assert.strictEqual(oversized.state.reads, 0);
  assert.strictEqual(oversized.state.cancels, 1);
  assert.strictEqual(oversized.timers.size, 0);

  const streaming = loadPreload({ chunks: [new Uint8Array(4), new Uint8Array(5), new Uint8Array(5)] });
  await assert.rejects(streaming.preload.manifest, /READER_RESOURCE_LIMIT/);
  assert.strictEqual(streaming.state.reads, 2);
  assert.strictEqual(streaming.state.cancels, 1);
  assert.strictEqual(streaming.timers.size, 0);

  const failed = loadPreload({ status: 503, chunks: [Buffer.from("retry")] });
  const response = await failed.preload.manifest;
  assert.strictEqual(response.status, 503);
  assert.strictEqual(await response.text(), "retry");

  const timeout = loadPreload({ blockRead: true });
  await timeout.reading;
  [...timeout.timers.values()][0]();
  await assert.rejects(timeout.preload.manifest, error => error.name === "AbortError");
  assert.strictEqual(timeout.state.signal.aborted, true);
  assert.strictEqual(timeout.state.cancels, 1);
  assert.strictEqual(timeout.timers.size, 0);
  console.log("reader preload contracts passed");
}

main().catch(error => { console.error(error); process.exitCode = 1; });