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.
8 * Creates and starts downloading and then resizing of the image. Finally,
9 * returns the image using the callback.
11 * @param {string} id Request ID.
12 * @param {Cache} cache Cache object.
13 * @param {Object} request Request message as a hash array.
14 * @param {function} callback Callback used to send the response.
17 function Request(id, cache, request, callback) {
34 this.request_ = request;
40 this.sendResponse_ = callback;
43 * Temporary image used to download images.
47 this.image_ = new Image();
50 * MIME type of the fetched image.
54 this.contentType_ = null;
57 * Used to download remote images using http:// or https:// protocols.
58 * @type {AuthorizedXHR}
61 this.xhr_ = new AuthorizedXHR();
64 * Temporary canvas used to resize and compress the image.
65 * @type {HTMLCanvasElement}
68 this.canvas_ = document.createElement('canvas');
71 * @type {CanvasRenderingContext2D}
74 this.context_ = this.canvas_.getContext('2d');
77 * Callback to be called once downloading is finished.
81 this.downloadCallback_ = null;
85 * Returns ID of the request.
86 * @return {string} Request ID.
88 Request.prototype.getId = function() {
93 * Returns priority of the request. The higher priority, the faster it will
94 * be handled. The highest priority is 0. The default one is 2.
96 * @return {number} Priority.
98 Request.prototype.getPriority = function() {
99 return (this.request_.priority !== undefined) ? this.request_.priority : 2;
103 * Tries to load the image from cache if exists and sends the response.
105 * @param {function()} onSuccess Success callback.
106 * @param {function()} onFailure Failure callback.
108 Request.prototype.loadFromCacheAndProcess = function(onSuccess, onFailure) {
110 function(data) { // Found in cache.
111 this.sendImageData_(data);
114 onFailure); // Not found in cache.
118 * Tries to download the image, resizes and sends the response.
119 * @param {function()} callback Completion callback.
121 Request.prototype.downloadAndProcess = function(callback) {
122 if (this.downloadCallback_)
123 throw new Error('Downloading already started.');
125 this.downloadCallback_ = callback;
126 this.downloadOriginal_(this.onImageLoad_.bind(this),
127 this.onImageError_.bind(this));
131 * Fetches the image from the persistent cache.
133 * @param {function()} onSuccess Success callback.
134 * @param {function()} onFailure Failure callback.
137 Request.prototype.loadFromCache_ = function(onSuccess, onFailure) {
138 var cacheKey = Cache.createKey(this.request_);
140 if (!this.request_.cache) {
141 // Cache is disabled for this request; therefore, remove it from cache
143 this.cache_.removeImage(cacheKey);
148 if (!this.request_.timestamp) {
149 // Persistent cache is available only when a timestamp is provided.
154 this.cache_.loadImage(cacheKey,
155 this.request_.timestamp,
161 * Saves the image to the persistent cache.
163 * @param {string} data The image's data.
166 Request.prototype.saveToCache_ = function(data) {
167 if (!this.request_.cache || !this.request_.timestamp) {
168 // Persistent cache is available only when a timestamp is provided.
172 var cacheKey = Cache.createKey(this.request_);
173 this.cache_.saveImage(cacheKey,
175 this.request_.timestamp);
179 * Downloads an image directly or for remote resources using the XmlHttpRequest.
181 * @param {function()} onSuccess Success callback.
182 * @param {function()} onFailure Failure callback.
185 Request.prototype.downloadOriginal_ = function(onSuccess, onFailure) {
186 this.image_.onload = onSuccess;
187 this.image_.onerror = onFailure;
189 // Download data urls directly since they are not supported by XmlHttpRequest.
190 var dataUrlMatches = this.request_.url.match(/^data:([^,;]*)[,;]/);
191 if (dataUrlMatches) {
192 this.image_.src = this.request_.url;
193 this.contentType_ = dataUrlMatches[1];
197 // Fetch the image via authorized XHR and parse it.
198 var parseImage = function(contentType, blob) {
199 var reader = new FileReader();
200 reader.onerror = onFailure;
201 reader.onload = function(e) {
202 this.image_.src = e.target.result;
205 // Load the data to the image as a data url.
206 reader.readAsDataURL(blob);
209 // Request raw data via XHR.
210 this.xhr_.load(this.request_.url, parseImage, onFailure);
214 * Creates a XmlHttpRequest wrapper with injected OAuth2 authentication headers.
217 function AuthorizedXHR() {
219 this.aborted_ = false;
223 * Aborts the current request (if running).
225 AuthorizedXHR.prototype.abort = function() {
226 this.aborted_ = true;
232 * Loads an image using a OAuth2 token. If it fails, then tries to retry with
233 * a refreshed OAuth2 token.
235 * @param {string} url URL to the resource to be fetched.
236 * @param {function(string, Blob}) onSuccess Success callback with the content
237 * type and the fetched data.
238 * @param {function()} onFailure Failure callback.
240 AuthorizedXHR.prototype.load = function(url, onSuccess, onFailure) {
241 this.aborted_ = false;
243 // Do not call any callbacks when aborting.
244 var onMaybeSuccess = function(contentType, response) {
246 onSuccess(contentType, response);
248 var onMaybeFailure = function(opt_code) {
253 // Fetches the access token and makes an authorized call. If refresh is true,
254 // then forces refreshing the access token.
255 var requestTokenAndCall = function(refresh, onInnerSuccess, onInnerFailure) {
256 chrome.fileManagerPrivate.requestAccessToken(refresh, function(token) {
263 this.xhr_ = AuthorizedXHR.load_(
264 token, url, onInnerSuccess, onInnerFailure);
268 // Refreshes the access token and retries the request.
269 var maybeRetryCall = function(code) {
272 requestTokenAndCall(true, onMaybeSuccess, onMaybeFailure);
275 // Do not request a token for local resources, since it is not necessary.
276 if (/^filesystem:/.test(url)) {
277 // The query parameter is workaround for
278 // crbug.com/379678, which force to obtain the latest contents of the image.
279 var noCacheUrl = url + '?nocache=' + Date.now();
280 this.xhr_ = AuthorizedXHR.load_(
288 // Make the request with reusing the current token. If it fails, then retry.
289 requestTokenAndCall(false, onMaybeSuccess, maybeRetryCall);
293 * Fetches data using authorized XmlHttpRequest with the provided OAuth2 token.
294 * If the token is invalid, the request will fail.
296 * @param {?string} token OAuth2 token to be injected to the request. Null for
298 * @param {string} url URL to the resource to be fetched.
299 * @param {function(string, Blob}) onSuccess Success callback with the content
300 * type and the fetched data.
301 * @param {function(number=)} onFailure Failure callback with the error code
303 * @return {AuthorizedXHR} XHR instance.
306 AuthorizedXHR.load_ = function(token, url, onSuccess, onFailure) {
307 var xhr = new XMLHttpRequest();
308 xhr.responseType = 'blob';
310 xhr.onreadystatechange = function() {
311 if (xhr.readyState != 4)
313 if (xhr.status != 200) {
314 onFailure(xhr.status);
317 var contentType = xhr.getResponseHeader('Content-Type');
318 onSuccess(contentType, xhr.response);
321 // Perform a xhr request.
323 xhr.open('GET', url, true);
325 xhr.setRequestHeader('Authorization', 'Bearer ' + token);
335 * Sends the resized image via the callback. If the image has been changed,
336 * then packs the canvas contents, otherwise sends the raw image data.
338 * @param {boolean} imageChanged Whether the image has been changed.
341 Request.prototype.sendImage_ = function(imageChanged) {
344 // The image hasn't been processed, so the raw data can be directly
345 // forwarded for speed (no need to encode the image again).
346 imageData = this.image_.src;
348 // The image has been resized or rotated, therefore the canvas has to be
349 // encoded to get the correct compressed image data.
350 switch (this.contentType_) {
355 imageData = this.canvas_.toDataURL('image/png');
359 imageData = this.canvas_.toDataURL('image/jpeg', 0.9);
363 // Send and store in the persistent cache.
364 this.sendImageData_(imageData);
365 this.saveToCache_(imageData);
369 * Sends the resized image via the callback.
370 * @param {string} data Compressed image data.
373 Request.prototype.sendImageData_ = function(data) {
375 {status: 'success', data: data, taskId: this.request_.taskId});
379 * Handler, when contents are loaded into the image element. Performs resizing
380 * and finalizes the request process.
382 * @param {function()} callback Completion callback.
385 Request.prototype.onImageLoad_ = function(callback) {
386 // Perform processing if the url is not a data url, or if there are some
387 // operations requested.
388 if (!this.request_.url.match(/^data/) ||
389 ImageLoader.shouldProcess(this.image_.width,
392 ImageLoader.resize(this.image_, this.canvas_, this.request_);
393 this.sendImage_(true); // Image changed.
395 this.sendImage_(false); // Image not changed.
398 this.downloadCallback_();
402 * Handler, when loading of the image fails. Sends a failure response and
403 * finalizes the request process.
405 * @param {function()} callback Completion callback.
408 Request.prototype.onImageError_ = function(callback) {
410 {status: 'error', taskId: this.request_.taskId});
412 this.downloadCallback_();
416 * Cancels the request.
418 Request.prototype.cancel = function() {
421 // If downloading has started, then call the callback.
422 if (this.downloadCallback_)
423 this.downloadCallback_();
427 * Cleans up memory used by this request.
430 Request.prototype.cleanup_ = function() {
431 this.image_.onerror = function() {};
432 this.image_.onload = function() {};
434 // Transparent 1x1 pixel gif, to force garbage collecting.
435 this.image_.src = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAA' +
436 'ABAAEAAAICTAEAOw==';
438 this.xhr_.onload = function() {};
441 // Dispose memory allocated by Canvas.
442 this.canvas_.width = 0;
443 this.canvas_.height = 0;