2
0

chunked_stream.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485
  1. /**
  2. * @licstart The following is the entire license notice for the
  3. * Javascript code in this page
  4. *
  5. * Copyright 2017 Mozilla Foundation
  6. *
  7. * Licensed under the Apache License, Version 2.0 (the "License");
  8. * you may not use this file except in compliance with the License.
  9. * You may obtain a copy of the License at
  10. *
  11. * http://www.apache.org/licenses/LICENSE-2.0
  12. *
  13. * Unless required by applicable law or agreed to in writing, software
  14. * distributed under the License is distributed on an "AS IS" BASIS,
  15. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16. * See the License for the specific language governing permissions and
  17. * limitations under the License.
  18. *
  19. * @licend The above is the entire license notice for the
  20. * Javascript code in this page
  21. */
  22. 'use strict';
  23. Object.defineProperty(exports, "__esModule", {
  24. value: true
  25. });
  26. exports.ChunkedStreamManager = exports.ChunkedStream = undefined;
  27. var _util = require('../shared/util');
  28. var ChunkedStream = function ChunkedStreamClosure() {
  29. function ChunkedStream(length, chunkSize, manager) {
  30. this.bytes = new Uint8Array(length);
  31. this.start = 0;
  32. this.pos = 0;
  33. this.end = length;
  34. this.chunkSize = chunkSize;
  35. this.loadedChunks = [];
  36. this.numChunksLoaded = 0;
  37. this.numChunks = Math.ceil(length / chunkSize);
  38. this.manager = manager;
  39. this.progressiveDataLength = 0;
  40. this.lastSuccessfulEnsureByteChunk = -1;
  41. }
  42. ChunkedStream.prototype = {
  43. getMissingChunks: function ChunkedStream_getMissingChunks() {
  44. var chunks = [];
  45. for (var chunk = 0, n = this.numChunks; chunk < n; ++chunk) {
  46. if (!this.loadedChunks[chunk]) {
  47. chunks.push(chunk);
  48. }
  49. }
  50. return chunks;
  51. },
  52. getBaseStreams: function ChunkedStream_getBaseStreams() {
  53. return [this];
  54. },
  55. allChunksLoaded: function ChunkedStream_allChunksLoaded() {
  56. return this.numChunksLoaded === this.numChunks;
  57. },
  58. onReceiveData: function ChunkedStream_onReceiveData(begin, chunk) {
  59. var end = begin + chunk.byteLength;
  60. if (begin % this.chunkSize !== 0) {
  61. throw new Error('Bad begin offset: ' + begin);
  62. }
  63. var length = this.bytes.length;
  64. if (end % this.chunkSize !== 0 && end !== length) {
  65. throw new Error('Bad end offset: ' + end);
  66. }
  67. this.bytes.set(new Uint8Array(chunk), begin);
  68. var chunkSize = this.chunkSize;
  69. var beginChunk = Math.floor(begin / chunkSize);
  70. var endChunk = Math.floor((end - 1) / chunkSize) + 1;
  71. var curChunk;
  72. for (curChunk = beginChunk; curChunk < endChunk; ++curChunk) {
  73. if (!this.loadedChunks[curChunk]) {
  74. this.loadedChunks[curChunk] = true;
  75. ++this.numChunksLoaded;
  76. }
  77. }
  78. },
  79. onReceiveProgressiveData: function ChunkedStream_onReceiveProgressiveData(data) {
  80. var position = this.progressiveDataLength;
  81. var beginChunk = Math.floor(position / this.chunkSize);
  82. this.bytes.set(new Uint8Array(data), position);
  83. position += data.byteLength;
  84. this.progressiveDataLength = position;
  85. var endChunk = position >= this.end ? this.numChunks : Math.floor(position / this.chunkSize);
  86. var curChunk;
  87. for (curChunk = beginChunk; curChunk < endChunk; ++curChunk) {
  88. if (!this.loadedChunks[curChunk]) {
  89. this.loadedChunks[curChunk] = true;
  90. ++this.numChunksLoaded;
  91. }
  92. }
  93. },
  94. ensureByte: function ChunkedStream_ensureByte(pos) {
  95. var chunk = Math.floor(pos / this.chunkSize);
  96. if (chunk === this.lastSuccessfulEnsureByteChunk) {
  97. return;
  98. }
  99. if (!this.loadedChunks[chunk]) {
  100. throw new _util.MissingDataException(pos, pos + 1);
  101. }
  102. this.lastSuccessfulEnsureByteChunk = chunk;
  103. },
  104. ensureRange: function ChunkedStream_ensureRange(begin, end) {
  105. if (begin >= end) {
  106. return;
  107. }
  108. if (end <= this.progressiveDataLength) {
  109. return;
  110. }
  111. var chunkSize = this.chunkSize;
  112. var beginChunk = Math.floor(begin / chunkSize);
  113. var endChunk = Math.floor((end - 1) / chunkSize) + 1;
  114. for (var chunk = beginChunk; chunk < endChunk; ++chunk) {
  115. if (!this.loadedChunks[chunk]) {
  116. throw new _util.MissingDataException(begin, end);
  117. }
  118. }
  119. },
  120. nextEmptyChunk: function ChunkedStream_nextEmptyChunk(beginChunk) {
  121. var chunk,
  122. numChunks = this.numChunks;
  123. for (var i = 0; i < numChunks; ++i) {
  124. chunk = (beginChunk + i) % numChunks;
  125. if (!this.loadedChunks[chunk]) {
  126. return chunk;
  127. }
  128. }
  129. return null;
  130. },
  131. hasChunk: function ChunkedStream_hasChunk(chunk) {
  132. return !!this.loadedChunks[chunk];
  133. },
  134. get length() {
  135. return this.end - this.start;
  136. },
  137. get isEmpty() {
  138. return this.length === 0;
  139. },
  140. getByte: function ChunkedStream_getByte() {
  141. var pos = this.pos;
  142. if (pos >= this.end) {
  143. return -1;
  144. }
  145. this.ensureByte(pos);
  146. return this.bytes[this.pos++];
  147. },
  148. getUint16: function ChunkedStream_getUint16() {
  149. var b0 = this.getByte();
  150. var b1 = this.getByte();
  151. if (b0 === -1 || b1 === -1) {
  152. return -1;
  153. }
  154. return (b0 << 8) + b1;
  155. },
  156. getInt32: function ChunkedStream_getInt32() {
  157. var b0 = this.getByte();
  158. var b1 = this.getByte();
  159. var b2 = this.getByte();
  160. var b3 = this.getByte();
  161. return (b0 << 24) + (b1 << 16) + (b2 << 8) + b3;
  162. },
  163. getBytes: function ChunkedStream_getBytes(length) {
  164. var bytes = this.bytes;
  165. var pos = this.pos;
  166. var strEnd = this.end;
  167. if (!length) {
  168. this.ensureRange(pos, strEnd);
  169. return bytes.subarray(pos, strEnd);
  170. }
  171. var end = pos + length;
  172. if (end > strEnd) {
  173. end = strEnd;
  174. }
  175. this.ensureRange(pos, end);
  176. this.pos = end;
  177. return bytes.subarray(pos, end);
  178. },
  179. peekByte: function ChunkedStream_peekByte() {
  180. var peekedByte = this.getByte();
  181. this.pos--;
  182. return peekedByte;
  183. },
  184. peekBytes: function ChunkedStream_peekBytes(length) {
  185. var bytes = this.getBytes(length);
  186. this.pos -= bytes.length;
  187. return bytes;
  188. },
  189. getByteRange: function ChunkedStream_getBytes(begin, end) {
  190. this.ensureRange(begin, end);
  191. return this.bytes.subarray(begin, end);
  192. },
  193. skip: function ChunkedStream_skip(n) {
  194. if (!n) {
  195. n = 1;
  196. }
  197. this.pos += n;
  198. },
  199. reset: function ChunkedStream_reset() {
  200. this.pos = this.start;
  201. },
  202. moveStart: function ChunkedStream_moveStart() {
  203. this.start = this.pos;
  204. },
  205. makeSubStream: function ChunkedStream_makeSubStream(start, length, dict) {
  206. this.ensureRange(start, start + length);
  207. function ChunkedStreamSubstream() {}
  208. ChunkedStreamSubstream.prototype = Object.create(this);
  209. ChunkedStreamSubstream.prototype.getMissingChunks = function () {
  210. var chunkSize = this.chunkSize;
  211. var beginChunk = Math.floor(this.start / chunkSize);
  212. var endChunk = Math.floor((this.end - 1) / chunkSize) + 1;
  213. var missingChunks = [];
  214. for (var chunk = beginChunk; chunk < endChunk; ++chunk) {
  215. if (!this.loadedChunks[chunk]) {
  216. missingChunks.push(chunk);
  217. }
  218. }
  219. return missingChunks;
  220. };
  221. var subStream = new ChunkedStreamSubstream();
  222. subStream.pos = subStream.start = start;
  223. subStream.end = start + length || this.end;
  224. subStream.dict = dict;
  225. return subStream;
  226. }
  227. };
  228. return ChunkedStream;
  229. }();
  230. var ChunkedStreamManager = function ChunkedStreamManagerClosure() {
  231. function ChunkedStreamManager(pdfNetworkStream, args) {
  232. var chunkSize = args.rangeChunkSize;
  233. var length = args.length;
  234. this.stream = new ChunkedStream(length, chunkSize, this);
  235. this.length = length;
  236. this.chunkSize = chunkSize;
  237. this.pdfNetworkStream = pdfNetworkStream;
  238. this.url = args.url;
  239. this.disableAutoFetch = args.disableAutoFetch;
  240. this.msgHandler = args.msgHandler;
  241. this.currRequestId = 0;
  242. this.chunksNeededByRequest = Object.create(null);
  243. this.requestsByChunk = Object.create(null);
  244. this.promisesByRequest = Object.create(null);
  245. this.progressiveDataLength = 0;
  246. this.aborted = false;
  247. this._loadedStreamCapability = (0, _util.createPromiseCapability)();
  248. }
  249. ChunkedStreamManager.prototype = {
  250. onLoadedStream: function ChunkedStreamManager_getLoadedStream() {
  251. return this._loadedStreamCapability.promise;
  252. },
  253. sendRequest: function ChunkedStreamManager_sendRequest(begin, end) {
  254. var _this = this;
  255. var rangeReader = this.pdfNetworkStream.getRangeReader(begin, end);
  256. if (!rangeReader.isStreamingSupported) {
  257. rangeReader.onProgress = this.onProgress.bind(this);
  258. }
  259. var chunks = [],
  260. loaded = 0;
  261. var manager = this;
  262. var promise = new Promise(function (resolve, reject) {
  263. var readChunk = function readChunk(chunk) {
  264. try {
  265. if (!chunk.done) {
  266. var data = chunk.value;
  267. chunks.push(data);
  268. loaded += (0, _util.arrayByteLength)(data);
  269. if (rangeReader.isStreamingSupported) {
  270. manager.onProgress({ loaded: loaded });
  271. }
  272. rangeReader.read().then(readChunk, reject);
  273. return;
  274. }
  275. var chunkData = (0, _util.arraysToBytes)(chunks);
  276. chunks = null;
  277. resolve(chunkData);
  278. } catch (e) {
  279. reject(e);
  280. }
  281. };
  282. rangeReader.read().then(readChunk, reject);
  283. });
  284. promise.then(function (data) {
  285. if (_this.aborted) {
  286. return;
  287. }
  288. _this.onReceiveData({
  289. chunk: data,
  290. begin: begin
  291. });
  292. });
  293. },
  294. requestAllChunks: function ChunkedStreamManager_requestAllChunks() {
  295. var missingChunks = this.stream.getMissingChunks();
  296. this._requestChunks(missingChunks);
  297. return this._loadedStreamCapability.promise;
  298. },
  299. _requestChunks: function ChunkedStreamManager_requestChunks(chunks) {
  300. var requestId = this.currRequestId++;
  301. var i, ii;
  302. var chunksNeeded = Object.create(null);
  303. this.chunksNeededByRequest[requestId] = chunksNeeded;
  304. for (i = 0, ii = chunks.length; i < ii; i++) {
  305. if (!this.stream.hasChunk(chunks[i])) {
  306. chunksNeeded[chunks[i]] = true;
  307. }
  308. }
  309. if ((0, _util.isEmptyObj)(chunksNeeded)) {
  310. return Promise.resolve();
  311. }
  312. var capability = (0, _util.createPromiseCapability)();
  313. this.promisesByRequest[requestId] = capability;
  314. var chunksToRequest = [];
  315. for (var chunk in chunksNeeded) {
  316. chunk = chunk | 0;
  317. if (!(chunk in this.requestsByChunk)) {
  318. this.requestsByChunk[chunk] = [];
  319. chunksToRequest.push(chunk);
  320. }
  321. this.requestsByChunk[chunk].push(requestId);
  322. }
  323. if (!chunksToRequest.length) {
  324. return capability.promise;
  325. }
  326. var groupedChunksToRequest = this.groupChunks(chunksToRequest);
  327. for (i = 0; i < groupedChunksToRequest.length; ++i) {
  328. var groupedChunk = groupedChunksToRequest[i];
  329. var begin = groupedChunk.beginChunk * this.chunkSize;
  330. var end = Math.min(groupedChunk.endChunk * this.chunkSize, this.length);
  331. this.sendRequest(begin, end);
  332. }
  333. return capability.promise;
  334. },
  335. getStream: function ChunkedStreamManager_getStream() {
  336. return this.stream;
  337. },
  338. requestRange: function ChunkedStreamManager_requestRange(begin, end) {
  339. end = Math.min(end, this.length);
  340. var beginChunk = this.getBeginChunk(begin);
  341. var endChunk = this.getEndChunk(end);
  342. var chunks = [];
  343. for (var chunk = beginChunk; chunk < endChunk; ++chunk) {
  344. chunks.push(chunk);
  345. }
  346. return this._requestChunks(chunks);
  347. },
  348. requestRanges: function ChunkedStreamManager_requestRanges(ranges) {
  349. ranges = ranges || [];
  350. var chunksToRequest = [];
  351. for (var i = 0; i < ranges.length; i++) {
  352. var beginChunk = this.getBeginChunk(ranges[i].begin);
  353. var endChunk = this.getEndChunk(ranges[i].end);
  354. for (var chunk = beginChunk; chunk < endChunk; ++chunk) {
  355. if (!chunksToRequest.includes(chunk)) {
  356. chunksToRequest.push(chunk);
  357. }
  358. }
  359. }
  360. chunksToRequest.sort(function (a, b) {
  361. return a - b;
  362. });
  363. return this._requestChunks(chunksToRequest);
  364. },
  365. groupChunks: function ChunkedStreamManager_groupChunks(chunks) {
  366. var groupedChunks = [];
  367. var beginChunk = -1;
  368. var prevChunk = -1;
  369. for (var i = 0; i < chunks.length; ++i) {
  370. var chunk = chunks[i];
  371. if (beginChunk < 0) {
  372. beginChunk = chunk;
  373. }
  374. if (prevChunk >= 0 && prevChunk + 1 !== chunk) {
  375. groupedChunks.push({
  376. beginChunk: beginChunk,
  377. endChunk: prevChunk + 1
  378. });
  379. beginChunk = chunk;
  380. }
  381. if (i + 1 === chunks.length) {
  382. groupedChunks.push({
  383. beginChunk: beginChunk,
  384. endChunk: chunk + 1
  385. });
  386. }
  387. prevChunk = chunk;
  388. }
  389. return groupedChunks;
  390. },
  391. onProgress: function ChunkedStreamManager_onProgress(args) {
  392. var bytesLoaded = this.stream.numChunksLoaded * this.chunkSize + args.loaded;
  393. this.msgHandler.send('DocProgress', {
  394. loaded: bytesLoaded,
  395. total: this.length
  396. });
  397. },
  398. onReceiveData: function ChunkedStreamManager_onReceiveData(args) {
  399. var chunk = args.chunk;
  400. var isProgressive = args.begin === undefined;
  401. var begin = isProgressive ? this.progressiveDataLength : args.begin;
  402. var end = begin + chunk.byteLength;
  403. var beginChunk = Math.floor(begin / this.chunkSize);
  404. var endChunk = end < this.length ? Math.floor(end / this.chunkSize) : Math.ceil(end / this.chunkSize);
  405. if (isProgressive) {
  406. this.stream.onReceiveProgressiveData(chunk);
  407. this.progressiveDataLength = end;
  408. } else {
  409. this.stream.onReceiveData(begin, chunk);
  410. }
  411. if (this.stream.allChunksLoaded()) {
  412. this._loadedStreamCapability.resolve(this.stream);
  413. }
  414. var loadedRequests = [];
  415. var i, requestId;
  416. for (chunk = beginChunk; chunk < endChunk; ++chunk) {
  417. var requestIds = this.requestsByChunk[chunk] || [];
  418. delete this.requestsByChunk[chunk];
  419. for (i = 0; i < requestIds.length; ++i) {
  420. requestId = requestIds[i];
  421. var chunksNeeded = this.chunksNeededByRequest[requestId];
  422. if (chunk in chunksNeeded) {
  423. delete chunksNeeded[chunk];
  424. }
  425. if (!(0, _util.isEmptyObj)(chunksNeeded)) {
  426. continue;
  427. }
  428. loadedRequests.push(requestId);
  429. }
  430. }
  431. if (!this.disableAutoFetch && (0, _util.isEmptyObj)(this.requestsByChunk)) {
  432. var nextEmptyChunk;
  433. if (this.stream.numChunksLoaded === 1) {
  434. var lastChunk = this.stream.numChunks - 1;
  435. if (!this.stream.hasChunk(lastChunk)) {
  436. nextEmptyChunk = lastChunk;
  437. }
  438. } else {
  439. nextEmptyChunk = this.stream.nextEmptyChunk(endChunk);
  440. }
  441. if (Number.isInteger(nextEmptyChunk)) {
  442. this._requestChunks([nextEmptyChunk]);
  443. }
  444. }
  445. for (i = 0; i < loadedRequests.length; ++i) {
  446. requestId = loadedRequests[i];
  447. var capability = this.promisesByRequest[requestId];
  448. delete this.promisesByRequest[requestId];
  449. capability.resolve();
  450. }
  451. this.msgHandler.send('DocProgress', {
  452. loaded: this.stream.numChunksLoaded * this.chunkSize,
  453. total: this.length
  454. });
  455. },
  456. onError: function ChunkedStreamManager_onError(err) {
  457. this._loadedStreamCapability.reject(err);
  458. },
  459. getBeginChunk: function ChunkedStreamManager_getBeginChunk(begin) {
  460. var chunk = Math.floor(begin / this.chunkSize);
  461. return chunk;
  462. },
  463. getEndChunk: function ChunkedStreamManager_getEndChunk(end) {
  464. var chunk = Math.floor((end - 1) / this.chunkSize) + 1;
  465. return chunk;
  466. },
  467. abort: function ChunkedStreamManager_abort() {
  468. this.aborted = true;
  469. if (this.pdfNetworkStream) {
  470. this.pdfNetworkStream.cancelAllRequests('abort');
  471. }
  472. for (var requestId in this.promisesByRequest) {
  473. var capability = this.promisesByRequest[requestId];
  474. capability.reject(new Error('Request was aborted'));
  475. }
  476. }
  477. };
  478. return ChunkedStreamManager;
  479. }();
  480. exports.ChunkedStream = ChunkedStream;
  481. exports.ChunkedStreamManager = ChunkedStreamManager;