image_loader.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. // Copyright 2013 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. import {assert} from 'chrome://resources/js/assert.m.js';
  5. import {ImageCache} from './cache.js';
  6. import {ImageOrientation} from './image_orientation.js';
  7. import {ImageRequestTask} from './image_request_task.js';
  8. import {LoadImageRequest, LoadImageResponse} from './load_image_request.js';
  9. import {Scheduler} from './scheduler.js';
  10. /**
  11. * Loads and resizes an image.
  12. * @constructor
  13. */
  14. export function ImageLoader() {
  15. /**
  16. * Persistent cache object.
  17. * @type {ImageCache}
  18. * @private
  19. */
  20. this.cache_ = new ImageCache();
  21. /**
  22. * Manages pending requests and runs them in order of priorities.
  23. * @type {Scheduler}
  24. * @private
  25. */
  26. this.scheduler_ = new Scheduler();
  27. // Grant permissions to all volumes, initialize the cache and then start the
  28. // scheduler.
  29. chrome.fileManagerPrivate.getVolumeMetadataList(function(volumeMetadataList) {
  30. // Listen for mount events, and grant permissions to volumes being mounted.
  31. chrome.fileManagerPrivate.onMountCompleted.addListener(
  32. function(event) {
  33. if (event.eventType === 'mount' && event.status === 'success') {
  34. chrome.fileSystem.requestFileSystem(
  35. {volumeId: event.volumeMetadata.volumeId}, function() {});
  36. }
  37. });
  38. const initPromises = volumeMetadataList.map(function(volumeMetadata) {
  39. const requestPromise = new Promise(function(callback) {
  40. chrome.fileSystem.requestFileSystem(
  41. {volumeId: volumeMetadata.volumeId},
  42. /** @type {function(FileSystem=)} */(callback));
  43. });
  44. return requestPromise;
  45. });
  46. initPromises.push(new Promise(function(resolve, reject) {
  47. this.cache_.initialize(resolve);
  48. }.bind(this)));
  49. // After all initialization promises are done, start the scheduler.
  50. Promise.all(initPromises).then(this.scheduler_.start.bind(this.scheduler_));
  51. }.bind(this));
  52. // Listen for incoming requests.
  53. chrome.runtime.onMessageExternal.addListener((msg, sender, sendResponse) => {
  54. if (!sender.origin || !msg) {
  55. return;
  56. }
  57. if (ImageLoader.ALLOWED_CLIENT_ORIGINS.indexOf(sender.origin) === -1) {
  58. return;
  59. }
  60. this.onIncomingRequest_(msg, sender.origin, sendResponse);
  61. });
  62. chrome.runtime['onConnectNative'].addListener((port) => {
  63. if (port.sender.nativeApplication != 'com.google.ash_thumbnail_loader') {
  64. port.disconnect();
  65. return;
  66. }
  67. port.onMessage.addListener((msg) => {
  68. // Each connection is expected to handle a single request only.
  69. const started = this.onIncomingRequest_(
  70. msg, port.sender.nativeApplication, response => {
  71. port.postMessage(response);
  72. port.disconnect();
  73. });
  74. if (!started) {
  75. port.disconnect();
  76. }
  77. });
  78. });
  79. }
  80. /**
  81. * List of extensions allowed to perform image requests.
  82. *
  83. * @const
  84. * @type {Array<string>}
  85. */
  86. ImageLoader.ALLOWED_CLIENT_ORIGINS = [
  87. 'chrome-extension://hhaomjibdihmijegdhdafkllkbggdgoj', // File Manager
  88. 'chrome://file-manager', // File Manager SWA
  89. ];
  90. /**
  91. * Handler for incoming requests.
  92. *
  93. * @param {*} request_data A LoadImageRequest (received untyped).
  94. * @param {!string} senderOrigin
  95. * @param {function(*): void} sendResponse
  96. */
  97. ImageLoader.prototype.onIncomingRequest_ = function(
  98. request_data, senderOrigin, sendResponse) {
  99. const request = /** @type {!LoadImageRequest} */ (request_data);
  100. // Sending a response may fail if the receiver already went offline.
  101. // This is not an error, but a normal and quite common situation.
  102. const failSafeSendResponse = function(response) {
  103. try {
  104. sendResponse(response);
  105. } catch (e) {
  106. // Ignore the error.
  107. }
  108. };
  109. // Incoming requests won't have the full type.
  110. assert(!(request.orientation instanceof ImageOrientation));
  111. assert(!(typeof request.orientation === 'number'));
  112. if (request.orientation) {
  113. request.orientation =
  114. ImageOrientation.fromRotationAndScale(request.orientation);
  115. } else {
  116. request.orientation = new ImageOrientation(1, 0, 0, 1);
  117. }
  118. return this.onMessage_(senderOrigin, request, failSafeSendResponse);
  119. };
  120. /**
  121. * Handles a request. Depending on type of the request, starts or stops
  122. * an image task.
  123. *
  124. * @param {string} senderOrigin Sender's origin.
  125. * @param {!LoadImageRequest} request Pre-processed request.
  126. * @param {function(!LoadImageResponse)} callback Callback to be called to
  127. * return response.
  128. * @return {boolean} True if the message channel should stay alive until the
  129. * callback is called.
  130. * @private
  131. */
  132. ImageLoader.prototype.onMessage_ = function(senderOrigin, request, callback) {
  133. const requestId = senderOrigin + ':' + request.taskId;
  134. if (request.cancel) {
  135. // Cancel a task.
  136. this.scheduler_.remove(requestId);
  137. return false; // No callback calls.
  138. } else {
  139. // Create a request task and add it to the scheduler (queue).
  140. const requestTask =
  141. new ImageRequestTask(requestId, this.cache_, request, callback);
  142. this.scheduler_.add(requestTask);
  143. return true; // Request will call the callback.
  144. }
  145. };
  146. /**
  147. * Returns the singleton instance.
  148. * @return {ImageLoader} ImageLoader object.
  149. */
  150. ImageLoader.getInstance = function() {
  151. if (!ImageLoader.instance_) {
  152. ImageLoader.instance_ = new ImageLoader();
  153. }
  154. return ImageLoader.instance_;
  155. };