chunked_stream.js 16 KB

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