mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-22 13:32:54 +08:00
fix(img): use img tag due to cordova limitations
This commit is contained in:
@ -1,175 +0,0 @@
|
|||||||
|
|
||||||
export class ImgLoader {
|
|
||||||
private imgs: ImgData[] = [];
|
|
||||||
|
|
||||||
load(src: string, useCache: boolean, callback: ImgLoadCallback) {
|
|
||||||
// see if we already have image data for this src
|
|
||||||
let img = this.imgs.find(i => i.src === src);
|
|
||||||
|
|
||||||
if (img && img.datauri && useCache) {
|
|
||||||
// we found image data, and it's cool if we use the cache
|
|
||||||
// so let's respond with the cached data
|
|
||||||
callback(200, null, img.datauri);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// so no cached image data, so we'll
|
|
||||||
// need to do a new http request
|
|
||||||
|
|
||||||
if (img && img.xhr && img.xhr.readyState !== 4) {
|
|
||||||
// looks like there's already an active http request going on
|
|
||||||
// for this same source, so let's just add another listener
|
|
||||||
img.xhr.addEventListener('load', (xhrEvent) => {
|
|
||||||
const target: any = xhrEvent.target;
|
|
||||||
const contentType = target.getResponseHeader('Content-Type');
|
|
||||||
onXhrLoad(callback, target.status, contentType, target.response, useCache, img, this.imgs);
|
|
||||||
});
|
|
||||||
img.xhr.addEventListener('error', (xhrErrorEvent) => {
|
|
||||||
onXhrError(callback, img, xhrErrorEvent);
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!img) {
|
|
||||||
// no image data yet, so let's create it
|
|
||||||
img = { src: src, len: 0 };
|
|
||||||
this.imgs.push(img);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ok, let's do a full request for the image
|
|
||||||
img.xhr = new XMLHttpRequest();
|
|
||||||
img.xhr.open('GET', src, true);
|
|
||||||
img.xhr.responseType = 'arraybuffer';
|
|
||||||
|
|
||||||
// add the listeners if it loaded or errored
|
|
||||||
img.xhr.addEventListener('load', (xhrEvent) => {
|
|
||||||
const target: any = xhrEvent.target;
|
|
||||||
const contentType = target.getResponseHeader('Content-Type');
|
|
||||||
onXhrLoad(callback, target.status, contentType, target.response, useCache, img, this.imgs);
|
|
||||||
});
|
|
||||||
img.xhr.addEventListener('error', (xhrErrorEvent) => {
|
|
||||||
onXhrError(callback, img, xhrErrorEvent);
|
|
||||||
});
|
|
||||||
|
|
||||||
// awesome, let's kick off the request
|
|
||||||
img.xhr.send();
|
|
||||||
}
|
|
||||||
|
|
||||||
abort(src: string) {
|
|
||||||
const img = this.imgs.find(i => i.src === src);
|
|
||||||
if (img && img.xhr && img.xhr.readyState !== 4) {
|
|
||||||
// we found the image data and there's an active
|
|
||||||
// http request, so let's abort the request
|
|
||||||
img.xhr.abort();
|
|
||||||
img.xhr = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export function onXhrLoad(callback: ImgLoadCallback, status: number, contentType: string, responseData: ArrayBuffer, useCache: boolean, img: ImgData, imgs: ImgData[]) {
|
|
||||||
if (!callback) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// the http request has been loaded
|
|
||||||
// create a rsp object to send back to the main thread
|
|
||||||
let datauri: string = null;
|
|
||||||
|
|
||||||
if (status === 200) {
|
|
||||||
// success!!
|
|
||||||
// now let's convert the response arraybuffer data into a datauri
|
|
||||||
datauri = getDataUri(contentType, responseData);
|
|
||||||
|
|
||||||
if (useCache) {
|
|
||||||
// if the image was successfully downloaded
|
|
||||||
// and this image is allowed to be cached
|
|
||||||
// then let's add it to our image data for later use
|
|
||||||
img.datauri = datauri;
|
|
||||||
img.len = datauri.length;
|
|
||||||
|
|
||||||
cleanCache(imgs, CACHE_LIMIT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// fire the callback with what we've learned today
|
|
||||||
callback(status, null, datauri);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export function cleanCache(imgs: ImgData[], cacheLimit: number) {
|
|
||||||
// let's loop through all our cached data and if we go
|
|
||||||
// over our limit then let's clean it out a bit
|
|
||||||
// oldest data should go first
|
|
||||||
let cacheSize = 0;
|
|
||||||
for (var i = imgs.length - 1; i >= 0; i--) {
|
|
||||||
cacheSize += imgs[i].len;
|
|
||||||
if (cacheSize > cacheLimit) {
|
|
||||||
console.debug(`img-loader, clear cache`);
|
|
||||||
imgs.splice(0, i + 1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function onXhrError(callback: ImgLoadCallback, imgData: ImgData, err: ErrorEvent) {
|
|
||||||
// darn, we got an error!
|
|
||||||
callback && callback(0, (err.message || ''), null);
|
|
||||||
imgData.xhr = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function getDataUri(contentType, arrayBuffer): string {
|
|
||||||
// take arraybuffer and content type and turn it into
|
|
||||||
// a datauri string that can be used by <img>
|
|
||||||
const rtn: string[] = ['data:' + contentType + ';base64,'];
|
|
||||||
|
|
||||||
const bytes = new Uint8Array(arrayBuffer);
|
|
||||||
const byteLength = bytes.byteLength;
|
|
||||||
const byteRemainder = byteLength % 3;
|
|
||||||
const mainLength = byteLength - byteRemainder;
|
|
||||||
let i, a, b, c, d, chunk;
|
|
||||||
|
|
||||||
for (i = 0; i < mainLength; i = i + 3) {
|
|
||||||
chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2];
|
|
||||||
a = (chunk & 16515072) >> 18;
|
|
||||||
b = (chunk & 258048) >> 12;
|
|
||||||
c = (chunk & 4032) >> 6;
|
|
||||||
d = chunk & 63;
|
|
||||||
rtn.push(ENCODINGS[a] + ENCODINGS[b] + ENCODINGS[c] + ENCODINGS[d]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (byteRemainder === 1) {
|
|
||||||
chunk = bytes[mainLength];
|
|
||||||
a = (chunk & 252) >> 2;
|
|
||||||
b = (chunk & 3) << 4;
|
|
||||||
rtn.push(ENCODINGS[a] + ENCODINGS[b] + '==');
|
|
||||||
|
|
||||||
} else if (byteRemainder === 2) {
|
|
||||||
chunk = (bytes[mainLength] << 8) | bytes[mainLength + 1];
|
|
||||||
a = (chunk & 64512) >> 10;
|
|
||||||
b = (chunk & 1008) >> 4;
|
|
||||||
c = (chunk & 15) << 2;
|
|
||||||
rtn.push(ENCODINGS[a] + ENCODINGS[b] + ENCODINGS[c] + '=');
|
|
||||||
}
|
|
||||||
|
|
||||||
return rtn.join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
// used by the setData function
|
|
||||||
const ENCODINGS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
||||||
|
|
||||||
const CACHE_LIMIT = 1381855 * 20;
|
|
||||||
|
|
||||||
export interface ImgData {
|
|
||||||
src: string;
|
|
||||||
datauri?: string;
|
|
||||||
len?: number;
|
|
||||||
xhr?: XMLHttpRequest;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ImgLoadCallback = {
|
|
||||||
(status: number, msg: string, datauri: string): void;
|
|
||||||
}
|
|
@ -2,8 +2,8 @@ import { ChangeDetectionStrategy, Component, ElementRef, Input, NgZone, OnDestro
|
|||||||
|
|
||||||
import { Content } from '../content/content';
|
import { Content } from '../content/content';
|
||||||
import { DomController } from '../../util/dom-controller';
|
import { DomController } from '../../util/dom-controller';
|
||||||
import { ImgLoader, ImgLoadCallback } from './img-loader';
|
|
||||||
import { isPresent, isTrueProperty } from '../../util/util';
|
import { isPresent, isTrueProperty } from '../../util/util';
|
||||||
|
import { listenEvent, eventOptions } from '../../util/ui-event-manager';
|
||||||
import { Platform } from '../../platform/platform';
|
import { Platform } from '../../platform/platform';
|
||||||
|
|
||||||
|
|
||||||
@ -80,40 +80,12 @@ import { Platform } from '../../platform/platform';
|
|||||||
* Its concrete object size is resolved as a cover constraint against the
|
* Its concrete object size is resolved as a cover constraint against the
|
||||||
* element’s used width and height.
|
* element’s used width and height.
|
||||||
*
|
*
|
||||||
|
* ### Future Optimizations
|
||||||
*
|
*
|
||||||
* ### Web Worker and XHR Requests
|
* Future goals are to place image requests within web workers, and cache
|
||||||
*
|
* images in-memory as datauris. This method has proven to be effective,
|
||||||
* Another big cause of scroll jank is kicking off a new HTTP request,
|
* however there are some current limitations with Cordova which we are
|
||||||
* which is exactly what images do. Normally, this isn't a problem for
|
* currently working on.
|
||||||
* something like a blog since all image HTTP requests are started immediately
|
|
||||||
* as HTML parses. However, Ionic has the ability to include hundreds, or even
|
|
||||||
* thousands of images within one page, but its not actually loading all of
|
|
||||||
* the images at the same time.
|
|
||||||
*
|
|
||||||
* Imagine an app where users can scroll slowly, or very quickly, through
|
|
||||||
* thousands of images. If they're scrolling extremely fast, ideally the app
|
|
||||||
* wouldn't want to start all of those image requests, but if they're scrolling
|
|
||||||
* slowly they would. Additionally, most browsers can only have six requests at
|
|
||||||
* one time for the same domain, so it's extemely important that we're managing
|
|
||||||
* exacctly which images we should downloading. Basically we want to ensure
|
|
||||||
* that the app is requesting the most important images, and aborting
|
|
||||||
* unnecessary requests, which is another benefit of using `ion-img`.
|
|
||||||
*
|
|
||||||
* Next, by running the image request within a web worker, we're able to pass
|
|
||||||
* off the heavy lifting to another thread. Not only are able to take the load
|
|
||||||
* of the main thread, but we're also able to accurately control exactly which
|
|
||||||
* images should be downloading, along with the ability to abort unnecessary
|
|
||||||
* requests. Aborting requets is just as important so that Ionic can free up
|
|
||||||
* connections for the most important images which are visible.
|
|
||||||
*
|
|
||||||
* One restriction however, is that all image requests must work with
|
|
||||||
* [cross-origin HTTP requests (CORS)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS).
|
|
||||||
* Traditionally, the `img` element does not have this issue, but because
|
|
||||||
* `ion-img` uses `XMLHttpRequest` within a web worker, then requests for
|
|
||||||
* images must be served from the same domain, or the image server's response
|
|
||||||
* must set the `Access-Control-Allow-Origin` HTTP header. Again, if your app
|
|
||||||
* does not have the same problems which `ion-img` is solving, then it's
|
|
||||||
* recommended to just use the standard `img` HTML element instead.
|
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
@ -130,12 +102,10 @@ export class Img implements OnDestroy {
|
|||||||
/** @internal */
|
/** @internal */
|
||||||
_renderedSrc: string;
|
_renderedSrc: string;
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_tmpDataUri: string;
|
_hasLoaded: boolean;
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_cache: boolean = true;
|
_cache: boolean = true;
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_cb: ImgLoadCallback;
|
|
||||||
/** @internal */
|
|
||||||
_bounds: any;
|
_bounds: any;
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_rect: any;
|
_rect: any;
|
||||||
@ -147,6 +117,10 @@ export class Img implements OnDestroy {
|
|||||||
_wQ: string = '';
|
_wQ: string = '';
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_hQ: string = '';
|
_hQ: string = '';
|
||||||
|
/** @internal */
|
||||||
|
_img: HTMLImageElement;
|
||||||
|
/** @internal */
|
||||||
|
_unreg: Function;
|
||||||
|
|
||||||
/** @private */
|
/** @private */
|
||||||
canRequest: boolean;
|
canRequest: boolean;
|
||||||
@ -155,7 +129,6 @@ export class Img implements OnDestroy {
|
|||||||
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private _ldr: ImgLoader,
|
|
||||||
private _elementRef: ElementRef,
|
private _elementRef: ElementRef,
|
||||||
private _renderer: Renderer,
|
private _renderer: Renderer,
|
||||||
private _platform: Platform,
|
private _platform: Platform,
|
||||||
@ -191,11 +164,11 @@ export class Img implements OnDestroy {
|
|||||||
|
|
||||||
if (newSrc.indexOf('data:') === 0) {
|
if (newSrc.indexOf('data:') === 0) {
|
||||||
// they're using an actual datauri already
|
// they're using an actual datauri already
|
||||||
this._tmpDataUri = newSrc;
|
this._hasLoaded = true;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// reset any existing datauri we might be holding onto
|
// reset any existing datauri we might be holding onto
|
||||||
this._tmpDataUri = null;
|
this._hasLoaded = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// run update to kick off requests or render if everything is good
|
// run update to kick off requests or render if everything is good
|
||||||
@ -210,7 +183,7 @@ export class Img implements OnDestroy {
|
|||||||
if (this._requestingSrc) {
|
if (this._requestingSrc) {
|
||||||
// abort any active requests
|
// abort any active requests
|
||||||
console.debug(`abortRequest ${this._requestingSrc} ${Date.now()}`);
|
console.debug(`abortRequest ${this._requestingSrc} ${Date.now()}`);
|
||||||
this._ldr.abort(this._requestingSrc);
|
this._srcAttr('');
|
||||||
this._requestingSrc = null;
|
this._requestingSrc = null;
|
||||||
}
|
}
|
||||||
if (this._renderedSrc) {
|
if (this._renderedSrc) {
|
||||||
@ -228,61 +201,34 @@ export class Img implements OnDestroy {
|
|||||||
// only attempt an update if there is an active src
|
// only attempt an update if there is an active src
|
||||||
// and the content containing the image considers it updatable
|
// and the content containing the image considers it updatable
|
||||||
if (this._src && this._content.isImgsUpdatable()) {
|
if (this._src && this._content.isImgsUpdatable()) {
|
||||||
if (this.canRequest && (this._src !== this._renderedSrc && this._src !== this._requestingSrc) && !this._tmpDataUri) {
|
if (this.canRequest && (this._src !== this._renderedSrc && this._src !== this._requestingSrc) && !this._hasLoaded) {
|
||||||
// only begin the request if we "can" request
|
// only begin the request if we "can" request
|
||||||
// begin the image request if the src is different from the rendered src
|
// begin the image request if the src is different from the rendered src
|
||||||
// and if we don't already has a tmpDataUri
|
// and if we don't already has a tmpDataUri
|
||||||
console.debug(`request ${this._src} ${Date.now()}`);
|
console.debug(`request ${this._src} ${Date.now()}`);
|
||||||
this._requestingSrc = this._src;
|
this._requestingSrc = this._src;
|
||||||
|
|
||||||
this._cb = (status, msg, datauri) => {
|
this._isLoaded(false);
|
||||||
this._loadResponse(status, msg, datauri);
|
this._srcAttr(this._src);
|
||||||
this._cb = null;
|
|
||||||
};
|
|
||||||
|
|
||||||
// post the message to the web worker
|
|
||||||
this._ldr.load(this._src, this._cache, this._cb);
|
|
||||||
|
|
||||||
// set the dimensions of the image if we do have different data
|
// set the dimensions of the image if we do have different data
|
||||||
this._setDims();
|
this._setDims();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.canRender && this._tmpDataUri && this._src !== this._renderedSrc) {
|
if (this.canRender && this._hasLoaded && this._src !== this._renderedSrc) {
|
||||||
// we can render and we have a datauri to render
|
// we can render and we have a datauri to render
|
||||||
this._renderedSrc = this._src;
|
this._renderedSrc = this._src;
|
||||||
this._setDims();
|
this._setDims();
|
||||||
this._dom.write(() => {
|
this._dom.write(() => {
|
||||||
if (this._tmpDataUri) {
|
if (this._hasLoaded) {
|
||||||
console.debug(`render ${this._src} ${Date.now()}`);
|
console.debug(`render ${this._src} ${Date.now()}`);
|
||||||
this._isLoaded(true);
|
this._isLoaded(true);
|
||||||
this._srcAttr(this._tmpDataUri);
|
|
||||||
this._tmpDataUri = null;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _loadResponse(status: number, msg: string, datauri: string) {
|
|
||||||
this._requestingSrc = null;
|
|
||||||
|
|
||||||
if (status === 200) {
|
|
||||||
// success :)
|
|
||||||
this._tmpDataUri = datauri;
|
|
||||||
this.update();
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// error :(
|
|
||||||
if (status) {
|
|
||||||
console.error(`img, status: ${status} ${msg}`);
|
|
||||||
}
|
|
||||||
this._renderedSrc = this._tmpDataUri = null;
|
|
||||||
this._dom.write(() => {
|
|
||||||
this._isLoaded(false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
@ -297,11 +243,10 @@ export class Img implements OnDestroy {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
_srcAttr(srcAttr: string) {
|
_srcAttr(srcAttr: string) {
|
||||||
const imgEle = this._elementRef.nativeElement.firstChild;
|
|
||||||
const renderer = this._renderer;
|
const renderer = this._renderer;
|
||||||
|
|
||||||
renderer.setElementAttribute(imgEle, 'src', srcAttr);
|
renderer.setElementAttribute(this._img, 'src', srcAttr);
|
||||||
renderer.setElementAttribute(imgEle, 'alt', this.alt);
|
renderer.setElementAttribute(this._img, 'alt', this.alt);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -409,11 +354,25 @@ export class Img implements OnDestroy {
|
|||||||
*/
|
*/
|
||||||
@Input() alt: string = '';
|
@Input() alt: string = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
ngAfterContentInit() {
|
||||||
|
this._img = this._elementRef.nativeElement.firstChild;
|
||||||
|
|
||||||
|
this._unreg && this._unreg();
|
||||||
|
const opts = eventOptions(false, true);
|
||||||
|
this._unreg = listenEvent(this._img, 'load', false, opts, () => {
|
||||||
|
this._hasLoaded = true;
|
||||||
|
this.update();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
this._cb = null;
|
this._unreg && this._unreg();
|
||||||
this._content && this._content.removeImg(this);
|
this._content && this._content.removeImg(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,55 +1,12 @@
|
|||||||
import { ElementRef, Renderer } from '@angular/core';
|
import { ElementRef, Renderer } from '@angular/core';
|
||||||
import { Content } from '../../content/content';
|
import { Content } from '../../content/content';
|
||||||
import { Img } from '../img';
|
import { Img } from '../img';
|
||||||
import { ImgLoader, ImgData, ImgLoadCallback, cleanCache, onXhrLoad } from '../img-loader';
|
|
||||||
import { mockContent, MockDomController, mockElementRef, mockPlatform, mockRenderer, mockZone } from '../../../util/mock-providers';
|
import { mockContent, MockDomController, mockElementRef, mockPlatform, mockRenderer, mockZone } from '../../../util/mock-providers';
|
||||||
import { Platform } from '../../../platform/platform';
|
import { Platform } from '../../../platform/platform';
|
||||||
|
|
||||||
|
|
||||||
describe('Img', () => {
|
describe('Img', () => {
|
||||||
|
|
||||||
describe('cleanCache', () => {
|
|
||||||
|
|
||||||
it('should clean out oldest img data when passing cache limit', () => {
|
|
||||||
const imgs: ImgData[] = [
|
|
||||||
{ src: 'img1.jpg', len: 100 },
|
|
||||||
{ src: 'img2.jpg', len: 0 },
|
|
||||||
{ src: 'img3.jpg', len: 100 },
|
|
||||||
{ src: 'img4.jpg', len: 100 },
|
|
||||||
];
|
|
||||||
cleanCache(imgs, 100);
|
|
||||||
expect(imgs.length).toEqual(1);
|
|
||||||
expect(imgs[0].src).toEqual('img4.jpg');
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('onXhrLoad', () => {
|
|
||||||
|
|
||||||
it('should cache img response', () => {
|
|
||||||
const callback: ImgLoadCallback = () => {};
|
|
||||||
const status = 200;
|
|
||||||
const contentType = 'image/jpeg';
|
|
||||||
const responseData = new ArrayBuffer(0);
|
|
||||||
const useCache = true;
|
|
||||||
const imgData: ImgData = {
|
|
||||||
src: 'image.jpg'
|
|
||||||
};
|
|
||||||
const imgs: ImgData[] = [];
|
|
||||||
|
|
||||||
onXhrLoad(callback, status, contentType, responseData, useCache, imgData, imgs);
|
|
||||||
|
|
||||||
expect(imgData.datauri).toEqual('data:image/jpeg;base64,');
|
|
||||||
expect(imgData.len).toEqual(imgData.datauri.length);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should do nothing when theres no callback', () => {
|
|
||||||
const r = onXhrLoad(null, 0, 'image/jpeg', new ArrayBuffer(0), true, null, null);
|
|
||||||
expect(r).toEqual(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('reset', () => {
|
describe('reset', () => {
|
||||||
|
|
||||||
it('should clear rendering src', () => {
|
it('should clear rendering src', () => {
|
||||||
@ -60,35 +17,14 @@ describe('Img', () => {
|
|||||||
expect(img._renderedSrc).toEqual(null);
|
expect(img._renderedSrc).toEqual(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should abort requesting src', () => {
|
|
||||||
spyOn(ldr, 'abort');
|
|
||||||
img._requestingSrc = '_requestingSrc.jpg';
|
|
||||||
img.reset();
|
|
||||||
expect(ldr.abort).toHaveBeenCalledWith('_requestingSrc.jpg');
|
|
||||||
expect(img._requestingSrc).toEqual(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('src setter', () => {
|
describe('src setter', () => {
|
||||||
|
|
||||||
it('should abort request if already requesting', () => {
|
|
||||||
spyOn(img, 'reset');
|
|
||||||
img._requestingSrc = 'requesting.jpg';
|
|
||||||
img._tmpDataUri = 'tmpDatauri.jpg';
|
|
||||||
|
|
||||||
img.src = 'image.jpg';
|
|
||||||
|
|
||||||
expect(img.reset).toHaveBeenCalled();
|
|
||||||
expect(img.src).toEqual('image.jpg');
|
|
||||||
expect(img._tmpDataUri).toEqual(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should set datauri src', () => {
|
it('should set datauri src', () => {
|
||||||
spyOn(img, 'update');
|
spyOn(img, 'update');
|
||||||
img.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAAAAACH5BAAAAAAALAAAAAABAAEAAAICTAEAOw==';
|
img.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAAAAACH5BAAAAAAALAAAAAABAAEAAAICTAEAOw==';
|
||||||
expect(img.src).toEqual('data:image/gif;base64,R0lGODlhAQABAIAAAAAAAAAAACH5BAAAAAAALAAAAAABAAEAAAICTAEAOw==');
|
expect(img.src).toEqual('data:image/gif;base64,R0lGODlhAQABAIAAAAAAAAAAACH5BAAAAAAALAAAAAABAAEAAAICTAEAOw==');
|
||||||
expect(img._tmpDataUri).toEqual(`data:image/gif;base64,R0lGODlhAQABAIAAAAAAAAAAACH5BAAAAAAALAAAAAABAAEAAAICTAEAOw==`);
|
|
||||||
expect(img.update).toHaveBeenCalled();
|
expect(img.update).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -112,7 +48,6 @@ describe('Img', () => {
|
|||||||
|
|
||||||
|
|
||||||
let img: Img;
|
let img: Img;
|
||||||
let ldr: ImgLoader;
|
|
||||||
let elementRef: ElementRef;
|
let elementRef: ElementRef;
|
||||||
let renderer: Renderer;
|
let renderer: Renderer;
|
||||||
let platform: Platform;
|
let platform: Platform;
|
||||||
@ -121,12 +56,11 @@ describe('Img', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
content = mockContent();
|
content = mockContent();
|
||||||
ldr = new ImgLoader();
|
|
||||||
elementRef = mockElementRef();
|
elementRef = mockElementRef();
|
||||||
renderer = mockRenderer();
|
renderer = mockRenderer();
|
||||||
platform = mockPlatform();
|
platform = mockPlatform();
|
||||||
dom = new MockDomController();
|
dom = new MockDomController();
|
||||||
img = new Img(ldr, elementRef, renderer, platform, mockZone(), content, dom);
|
img = new Img(elementRef, renderer, platform, mockZone(), content, dom);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -19,7 +19,6 @@ import { Events, setupProvideEvents } from './util/events';
|
|||||||
import { Form } from './util/form';
|
import { Form } from './util/form';
|
||||||
import { GestureController } from './gestures/gesture-controller';
|
import { GestureController } from './gestures/gesture-controller';
|
||||||
import { Haptic } from './util/haptic';
|
import { Haptic } from './util/haptic';
|
||||||
import { ImgLoader } from './components/img/img-loader';
|
|
||||||
import { IonicGestureConfig } from './gestures/gesture-config';
|
import { IonicGestureConfig } from './gestures/gesture-config';
|
||||||
import { Keyboard } from './util/keyboard';
|
import { Keyboard } from './util/keyboard';
|
||||||
import { LoadingController } from './components/loading/loading';
|
import { LoadingController } from './components/loading/loading';
|
||||||
@ -56,7 +55,6 @@ export { Config, setupConfig, ConfigToken } from './config/config';
|
|||||||
export { DomController, DomCallback } from './util/dom-controller';
|
export { DomController, DomCallback } from './util/dom-controller';
|
||||||
export { Platform, setupPlatform, UserAgentToken, DocumentDirToken, DocLangToken, NavigatorPlatformToken } from './platform/platform';
|
export { Platform, setupPlatform, UserAgentToken, DocumentDirToken, DocLangToken, NavigatorPlatformToken } from './platform/platform';
|
||||||
export { Haptic } from './util/haptic';
|
export { Haptic } from './util/haptic';
|
||||||
export { ImgLoader } from './components/img/img-loader';
|
|
||||||
export { QueryParams, setupQueryParams, UrlToken } from './platform/query-params';
|
export { QueryParams, setupQueryParams, UrlToken } from './platform/query-params';
|
||||||
export { DeepLinker } from './navigation/deep-linker';
|
export { DeepLinker } from './navigation/deep-linker';
|
||||||
export { NavController } from './navigation/nav-controller';
|
export { NavController } from './navigation/nav-controller';
|
||||||
@ -176,7 +174,6 @@ export class IonicModule {
|
|||||||
Form,
|
Form,
|
||||||
GestureController,
|
GestureController,
|
||||||
Haptic,
|
Haptic,
|
||||||
ImgLoader,
|
|
||||||
Keyboard,
|
Keyboard,
|
||||||
LoadingController,
|
LoadingController,
|
||||||
Location,
|
Location,
|
||||||
|
Reference in New Issue
Block a user