Resource.js 90 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067
  1. import Uri from '../ThirdParty/Uri.js';
  2. import when from '../ThirdParty/when.js';
  3. import appendForwardSlash from './appendForwardSlash.js';
  4. import Check from './Check.js';
  5. import clone from './clone.js';
  6. import combine from './combine.js';
  7. import defaultValue from './defaultValue.js';
  8. import defined from './defined.js';
  9. import defineProperties from './defineProperties.js';
  10. import DeveloperError from './DeveloperError.js';
  11. import freezeObject from './freezeObject.js';
  12. import getAbsoluteUri from './getAbsoluteUri.js';
  13. import getBaseUri from './getBaseUri.js';
  14. import getExtensionFromUri from './getExtensionFromUri.js';
  15. import isBlobUri from './isBlobUri.js';
  16. import isCrossOriginUrl from './isCrossOriginUrl.js';
  17. import isDataUri from './isDataUri.js';
  18. import loadAndExecuteScript from './loadAndExecuteScript.js';
  19. import objectToQuery from './objectToQuery.js';
  20. import queryToObject from './queryToObject.js';
  21. import Request from './Request.js';
  22. import RequestErrorEvent from './RequestErrorEvent.js';
  23. import RequestScheduler from './RequestScheduler.js';
  24. import RequestState from './RequestState.js';
  25. import RuntimeError from './RuntimeError.js';
  26. import TrustedServers from './TrustedServers.js';
  27. var xhrBlobSupported = (function() {
  28. try {
  29. var xhr = new XMLHttpRequest();
  30. xhr.open('GET', '#', true);
  31. xhr.responseType = 'blob';
  32. return xhr.responseType === 'blob';
  33. } catch (e) {
  34. return false;
  35. }
  36. })();
  37. /**
  38. * Parses a query string and returns the object equivalent.
  39. *
  40. * @param {Uri} uri The Uri with a query object.
  41. * @param {Resource} resource The Resource that will be assigned queryParameters.
  42. * @param {Boolean} merge If true, we'll merge with the resource's existing queryParameters. Otherwise they will be replaced.
  43. * @param {Boolean} preserveQueryParameters If true duplicate parameters will be concatenated into an array. If false, keys in uri will take precedence.
  44. *
  45. * @private
  46. */
  47. function parseQuery(uri, resource, merge, preserveQueryParameters) {
  48. var queryString = uri.query;
  49. if (!defined(queryString) || (queryString.length === 0)) {
  50. return {};
  51. }
  52. var query;
  53. // Special case we run into where the querystring is just a string, not key/value pairs
  54. if (queryString.indexOf('=') === -1) {
  55. var result = {};
  56. result[queryString] = undefined;
  57. query = result;
  58. } else {
  59. query = queryToObject(queryString);
  60. }
  61. if (merge) {
  62. resource._queryParameters = combineQueryParameters(query, resource._queryParameters, preserveQueryParameters);
  63. } else {
  64. resource._queryParameters = query;
  65. }
  66. uri.query = undefined;
  67. }
  68. /**
  69. * Converts a query object into a string.
  70. *
  71. * @param {Uri} uri The Uri object that will have the query object set.
  72. * @param {Resource} resource The resource that has queryParameters
  73. *
  74. * @private
  75. */
  76. function stringifyQuery(uri, resource) {
  77. var queryObject = resource._queryParameters;
  78. var keys = Object.keys(queryObject);
  79. // We have 1 key with an undefined value, so this is just a string, not key/value pairs
  80. if (keys.length === 1 && !defined(queryObject[keys[0]])) {
  81. uri.query = keys[0];
  82. } else {
  83. uri.query = objectToQuery(queryObject);
  84. }
  85. }
  86. /**
  87. * Clones a value if it is defined, otherwise returns the default value
  88. *
  89. * @param {*} [val] The value to clone.
  90. * @param {*} [defaultVal] The default value.
  91. *
  92. * @returns {*} A clone of val or the defaultVal.
  93. *
  94. * @private
  95. */
  96. function defaultClone(val, defaultVal) {
  97. if (!defined(val)) {
  98. return defaultVal;
  99. }
  100. return defined(val.clone) ? val.clone() : clone(val);
  101. }
  102. /**
  103. * Checks to make sure the Resource isn't already being requested.
  104. *
  105. * @param {Request} request The request to check.
  106. *
  107. * @private
  108. */
  109. function checkAndResetRequest(request) {
  110. if (request.state === RequestState.ISSUED || request.state === RequestState.ACTIVE) {
  111. throw new RuntimeError('The Resource is already being fetched.');
  112. }
  113. request.state = RequestState.UNISSUED;
  114. request.deferred = undefined;
  115. }
  116. /**
  117. * This combines a map of query parameters.
  118. *
  119. * @param {Object} q1 The first map of query parameters. Values in this map will take precedence if preserveQueryParameters is false.
  120. * @param {Object} q2 The second map of query parameters.
  121. * @param {Boolean} preserveQueryParameters If true duplicate parameters will be concatenated into an array. If false, keys in q1 will take precedence.
  122. *
  123. * @returns {Object} The combined map of query parameters.
  124. *
  125. * @example
  126. * var q1 = {
  127. * a: 1,
  128. * b: 2
  129. * };
  130. * var q2 = {
  131. * a: 3,
  132. * c: 4
  133. * };
  134. * var q3 = {
  135. * b: [5, 6],
  136. * d: 7
  137. * }
  138. *
  139. * // Returns
  140. * // {
  141. * // a: [1, 3],
  142. * // b: 2,
  143. * // c: 4
  144. * // };
  145. * combineQueryParameters(q1, q2, true);
  146. *
  147. * // Returns
  148. * // {
  149. * // a: 1,
  150. * // b: 2,
  151. * // c: 4
  152. * // };
  153. * combineQueryParameters(q1, q2, false);
  154. *
  155. * // Returns
  156. * // {
  157. * // a: 1,
  158. * // b: [2, 5, 6],
  159. * // d: 7
  160. * // };
  161. * combineQueryParameters(q1, q3, true);
  162. *
  163. * // Returns
  164. * // {
  165. * // a: 1,
  166. * // b: 2,
  167. * // d: 7
  168. * // };
  169. * combineQueryParameters(q1, q3, false);
  170. *
  171. * @private
  172. */
  173. function combineQueryParameters(q1, q2, preserveQueryParameters) {
  174. if (!preserveQueryParameters) {
  175. return combine(q1, q2);
  176. }
  177. var result = clone(q1, true);
  178. for (var param in q2) {
  179. if (q2.hasOwnProperty(param)) {
  180. var value = result[param];
  181. var q2Value = q2[param];
  182. if (defined(value)) {
  183. if (!Array.isArray(value)) {
  184. value = result[param] = [value];
  185. }
  186. result[param] = value.concat(q2Value);
  187. } else {
  188. result[param] = Array.isArray(q2Value) ? q2Value.slice() : q2Value;
  189. }
  190. }
  191. }
  192. return result;
  193. }
  194. /**
  195. * A resource that includes the location and any other parameters we need to retrieve it or create derived resources. It also provides the ability to retry requests.
  196. *
  197. * @alias Resource
  198. * @constructor
  199. *
  200. * @param {String|Object} options A url or an object with the following properties
  201. * @param {String} options.url The url of the resource.
  202. * @param {Object} [options.queryParameters] An object containing query parameters that will be sent when retrieving the resource.
  203. * @param {Object} [options.templateValues] Key/Value pairs that are used to replace template values (eg. {x}).
  204. * @param {Object} [options.headers={}] Additional HTTP headers that will be sent.
  205. * @param {DefaultProxy} [options.proxy] A proxy to be used when loading the resource.
  206. * @param {Resource~RetryCallback} [options.retryCallback] The Function to call when a request for this resource fails. If it returns true, the request will be retried.
  207. * @param {Number} [options.retryAttempts=0] The number of times the retryCallback should be called before giving up.
  208. * @param {Request} [options.request] A Request object that will be used. Intended for internal use only.
  209. *
  210. * @example
  211. * function refreshTokenRetryCallback(resource, error) {
  212. * if (error.statusCode === 403) {
  213. * // 403 status code means a new token should be generated
  214. * return getNewAccessToken()
  215. * .then(function(token) {
  216. * resource.queryParameters.access_token = token;
  217. * return true;
  218. * })
  219. * .otherwise(function() {
  220. * return false;
  221. * });
  222. * }
  223. *
  224. * return false;
  225. * }
  226. *
  227. * var resource = new Resource({
  228. * url: 'http://server.com/path/to/resource.json',
  229. * proxy: new DefaultProxy('/proxy/'),
  230. * headers: {
  231. * 'X-My-Header': 'valueOfHeader'
  232. * },
  233. * queryParameters: {
  234. * 'access_token': '123-435-456-000'
  235. * },
  236. * retryCallback: refreshTokenRetryCallback,
  237. * retryAttempts: 1
  238. * });
  239. */
  240. function Resource(options) {
  241. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  242. if (typeof options === 'string') {
  243. options = {
  244. url: options
  245. };
  246. }
  247. //>>includeStart('debug', pragmas.debug);
  248. Check.typeOf.string('options.url', options.url);
  249. //>>includeEnd('debug');
  250. this._url = undefined;
  251. this._templateValues = defaultClone(options.templateValues, {});
  252. this._queryParameters = defaultClone(options.queryParameters, {});
  253. /**
  254. * Additional HTTP headers that will be sent with the request.
  255. *
  256. * @type {Object}
  257. */
  258. this.headers = defaultClone(options.headers, {});
  259. /**
  260. * A Request object that will be used. Intended for internal use only.
  261. *
  262. * @type {Request}
  263. */
  264. this.request = defaultValue(options.request, new Request());
  265. /**
  266. * A proxy to be used when loading the resource.
  267. *
  268. * @type {DefaultProxy}
  269. */
  270. this.proxy = options.proxy;
  271. /**
  272. * Function to call when a request for this resource fails. If it returns true or a Promise that resolves to true, the request will be retried.
  273. *
  274. * @type {Function}
  275. */
  276. this.retryCallback = options.retryCallback;
  277. /**
  278. * The number of times the retryCallback should be called before giving up.
  279. *
  280. * @type {Number}
  281. */
  282. this.retryAttempts = defaultValue(options.retryAttempts, 0);
  283. this._retryCount = 0;
  284. var uri = new Uri(options.url);
  285. parseQuery(uri, this, true, true);
  286. // Remove the fragment as it's not sent with a request
  287. uri.fragment = undefined;
  288. this._url = uri.toString();
  289. }
  290. /**
  291. * A helper function to create a resource depending on whether we have a String or a Resource
  292. *
  293. * @param {Resource|String} resource A Resource or a String to use when creating a new Resource.
  294. *
  295. * @returns {Resource} If resource is a String, a Resource constructed with the url and options. Otherwise the resource parameter is returned.
  296. *
  297. * @private
  298. */
  299. Resource.createIfNeeded = function(resource) {
  300. if (resource instanceof Resource) {
  301. // Keep existing request object. This function is used internally to duplicate a Resource, so that it can't
  302. // be modified outside of a class that holds it (eg. an imagery or terrain provider). Since the Request objects
  303. // are managed outside of the providers, by the tile loading code, we want to keep the request property the same so if it is changed
  304. // in the underlying tiling code the requests for this resource will use it.
  305. return resource.getDerivedResource({
  306. request: resource.request
  307. });
  308. }
  309. if (typeof resource !== 'string') {
  310. return resource;
  311. }
  312. return new Resource({
  313. url: resource
  314. });
  315. };
  316. var supportsImageBitmapOptionsPromise;
  317. /**
  318. * A helper function to check whether createImageBitmap supports passing ImageBitmapOptions.
  319. *
  320. * @returns {Promise<Boolean>} A promise that resolves to true if this browser supports creating an ImageBitmap with options.
  321. *
  322. * @private
  323. */
  324. Resource.supportsImageBitmapOptions = function() {
  325. // Until the HTML folks figure out what to do about this, we need to actually try loading an image to
  326. // know if this browser supports passing options to the createImageBitmap function.
  327. // https://github.com/whatwg/html/pull/4248
  328. if (defined(supportsImageBitmapOptionsPromise)) {
  329. return supportsImageBitmapOptionsPromise;
  330. }
  331. if (typeof createImageBitmap !== 'function') {
  332. supportsImageBitmapOptionsPromise = when.resolve(false);
  333. return supportsImageBitmapOptionsPromise;
  334. }
  335. var imageDataUri = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWP4////fwAJ+wP9CNHoHgAAAABJRU5ErkJggg==';
  336. supportsImageBitmapOptionsPromise = Resource.fetchBlob({
  337. url : imageDataUri
  338. })
  339. .then(function(blob) {
  340. return createImageBitmap(blob, {
  341. imageOrientation: 'flipY',
  342. premultiplyAlpha: 'none'
  343. });
  344. })
  345. .then(function(imageBitmap) {
  346. return true;
  347. })
  348. .otherwise(function() {
  349. return false;
  350. });
  351. return supportsImageBitmapOptionsPromise;
  352. };
  353. defineProperties(Resource, {
  354. /**
  355. * Returns true if blobs are supported.
  356. *
  357. * @memberof Resource
  358. * @type {Boolean}
  359. *
  360. * @readonly
  361. */
  362. isBlobSupported : {
  363. get : function() {
  364. return xhrBlobSupported;
  365. }
  366. }
  367. });
  368. defineProperties(Resource.prototype, {
  369. /**
  370. * Query parameters appended to the url.
  371. *
  372. * @memberof Resource.prototype
  373. * @type {Object}
  374. *
  375. * @readonly
  376. */
  377. queryParameters: {
  378. get: function() {
  379. return this._queryParameters;
  380. }
  381. },
  382. /**
  383. * The key/value pairs used to replace template parameters in the url.
  384. *
  385. * @memberof Resource.prototype
  386. * @type {Object}
  387. *
  388. * @readonly
  389. */
  390. templateValues: {
  391. get: function() {
  392. return this._templateValues;
  393. }
  394. },
  395. /**
  396. * The url to the resource with template values replaced, query string appended and encoded by proxy if one was set.
  397. *
  398. * @memberof Resource.prototype
  399. * @type {String}
  400. */
  401. url: {
  402. get: function() {
  403. return this.getUrlComponent(true, true);
  404. },
  405. set: function(value) {
  406. var uri = new Uri(value);
  407. parseQuery(uri, this, false);
  408. // Remove the fragment as it's not sent with a request
  409. uri.fragment = undefined;
  410. this._url = uri.toString();
  411. }
  412. },
  413. /**
  414. * The file extension of the resource.
  415. *
  416. * @memberof Resource.prototype
  417. * @type {String}
  418. *
  419. * @readonly
  420. */
  421. extension: {
  422. get: function() {
  423. return getExtensionFromUri(this._url);
  424. }
  425. },
  426. /**
  427. * True if the Resource refers to a data URI.
  428. *
  429. * @memberof Resource.prototype
  430. * @type {Boolean}
  431. */
  432. isDataUri: {
  433. get: function() {
  434. return isDataUri(this._url);
  435. }
  436. },
  437. /**
  438. * True if the Resource refers to a blob URI.
  439. *
  440. * @memberof Resource.prototype
  441. * @type {Boolean}
  442. */
  443. isBlobUri: {
  444. get: function() {
  445. return isBlobUri(this._url);
  446. }
  447. },
  448. /**
  449. * True if the Resource refers to a cross origin URL.
  450. *
  451. * @memberof Resource.prototype
  452. * @type {Boolean}
  453. */
  454. isCrossOriginUrl: {
  455. get: function() {
  456. return isCrossOriginUrl(this._url);
  457. }
  458. },
  459. /**
  460. * True if the Resource has request headers. This is equivalent to checking if the headers property has any keys.
  461. *
  462. * @memberof Resource.prototype
  463. * @type {Boolean}
  464. */
  465. hasHeaders: {
  466. get: function() {
  467. return (Object.keys(this.headers).length > 0);
  468. }
  469. }
  470. });
  471. /**
  472. * Returns the url, optional with the query string and processed by a proxy.
  473. *
  474. * @param {Boolean} [query=false] If true, the query string is included.
  475. * @param {Boolean} [proxy=false] If true, the url is processed the proxy object if defined.
  476. *
  477. * @returns {String} The url with all the requested components.
  478. */
  479. Resource.prototype.getUrlComponent = function(query, proxy) {
  480. if(this.isDataUri) {
  481. return this._url;
  482. }
  483. var uri = new Uri(this._url);
  484. if (query) {
  485. stringifyQuery(uri, this);
  486. }
  487. // objectToQuery escapes the placeholders. Undo that.
  488. var url = uri.toString().replace(/%7B/g, '{').replace(/%7D/g, '}');
  489. var templateValues = this._templateValues;
  490. url = url.replace(/{(.*?)}/g, function(match, key) {
  491. var replacement = templateValues[key];
  492. if (defined(replacement)) {
  493. // use the replacement value from templateValues if there is one...
  494. return encodeURIComponent(replacement);
  495. }
  496. // otherwise leave it unchanged
  497. return match;
  498. });
  499. if (proxy && defined(this.proxy)) {
  500. url = this.proxy.getURL(url);
  501. }
  502. return url;
  503. };
  504. /**
  505. * Combines the specified object and the existing query parameters. This allows you to add many parameters at once,
  506. * as opposed to adding them one at a time to the queryParameters property. If a value is already set, it will be replaced with the new value.
  507. *
  508. * @param {Object} params The query parameters
  509. * @param {Boolean} [useAsDefault=false] If true the params will be used as the default values, so they will only be set if they are undefined.
  510. */
  511. Resource.prototype.setQueryParameters = function(params, useAsDefault) {
  512. if (useAsDefault) {
  513. this._queryParameters = combineQueryParameters(this._queryParameters, params, false);
  514. } else {
  515. this._queryParameters = combineQueryParameters(params, this._queryParameters, false);
  516. }
  517. };
  518. /**
  519. * Combines the specified object and the existing query parameters. This allows you to add many parameters at once,
  520. * as opposed to adding them one at a time to the queryParameters property.
  521. *
  522. * @param {Object} params The query parameters
  523. */
  524. Resource.prototype.appendQueryParameters = function(params) {
  525. this._queryParameters = combineQueryParameters(params, this._queryParameters, true);
  526. };
  527. /**
  528. * Combines the specified object and the existing template values. This allows you to add many values at once,
  529. * as opposed to adding them one at a time to the templateValues property. If a value is already set, it will become an array and the new value will be appended.
  530. *
  531. * @param {Object} template The template values
  532. * @param {Boolean} [useAsDefault=false] If true the values will be used as the default values, so they will only be set if they are undefined.
  533. */
  534. Resource.prototype.setTemplateValues = function(template, useAsDefault) {
  535. if (useAsDefault) {
  536. this._templateValues = combine(this._templateValues, template);
  537. } else {
  538. this._templateValues = combine(template, this._templateValues);
  539. }
  540. };
  541. /**
  542. * Returns a resource relative to the current instance. All properties remain the same as the current instance unless overridden in options.
  543. *
  544. * @param {Object} options An object with the following properties
  545. * @param {String} [options.url] The url that will be resolved relative to the url of the current instance.
  546. * @param {Object} [options.queryParameters] An object containing query parameters that will be combined with those of the current instance.
  547. * @param {Object} [options.templateValues] Key/Value pairs that are used to replace template values (eg. {x}). These will be combined with those of the current instance.
  548. * @param {Object} [options.headers={}] Additional HTTP headers that will be sent.
  549. * @param {DefaultProxy} [options.proxy] A proxy to be used when loading the resource.
  550. * @param {Resource~RetryCallback} [options.retryCallback] The function to call when loading the resource fails.
  551. * @param {Number} [options.retryAttempts] The number of times the retryCallback should be called before giving up.
  552. * @param {Request} [options.request] A Request object that will be used. Intended for internal use only.
  553. * @param {Boolean} [options.preserveQueryParameters=false] If true, this will keep all query parameters from the current resource and derived resource. If false, derived parameters will replace those of the current resource.
  554. *
  555. * @returns {Resource} The resource derived from the current one.
  556. */
  557. Resource.prototype.getDerivedResource = function(options) {
  558. var resource = this.clone();
  559. resource._retryCount = 0;
  560. if (defined(options.url)) {
  561. var uri = new Uri(options.url);
  562. var preserveQueryParameters = defaultValue(options.preserveQueryParameters, false);
  563. parseQuery(uri, resource, true, preserveQueryParameters);
  564. // Remove the fragment as it's not sent with a request
  565. uri.fragment = undefined;
  566. resource._url = uri.resolve(new Uri(getAbsoluteUri(this._url))).toString();
  567. }
  568. if (defined(options.queryParameters)) {
  569. resource._queryParameters = combine(options.queryParameters, resource._queryParameters);
  570. }
  571. if (defined(options.templateValues)) {
  572. resource._templateValues = combine(options.templateValues, resource.templateValues);
  573. }
  574. if (defined(options.headers)) {
  575. resource.headers = combine(options.headers, resource.headers);
  576. }
  577. if (defined(options.proxy)) {
  578. resource.proxy = options.proxy;
  579. }
  580. if (defined(options.request)) {
  581. resource.request = options.request;
  582. }
  583. if (defined(options.retryCallback)) {
  584. resource.retryCallback = options.retryCallback;
  585. }
  586. if (defined(options.retryAttempts)) {
  587. resource.retryAttempts = options.retryAttempts;
  588. }
  589. return resource;
  590. };
  591. /**
  592. * Called when a resource fails to load. This will call the retryCallback function if defined until retryAttempts is reached.
  593. *
  594. * @param {Error} [error] The error that was encountered.
  595. *
  596. * @returns {Promise<Boolean>} A promise to a boolean, that if true will cause the resource request to be retried.
  597. *
  598. * @private
  599. */
  600. Resource.prototype.retryOnError = function(error) {
  601. var retryCallback = this.retryCallback;
  602. if ((typeof retryCallback !== 'function') || (this._retryCount >= this.retryAttempts)) {
  603. return when(false);
  604. }
  605. var that = this;
  606. return when(retryCallback(this, error))
  607. .then(function(result) {
  608. ++that._retryCount;
  609. return result;
  610. });
  611. };
  612. /**
  613. * Duplicates a Resource instance.
  614. *
  615. * @param {Resource} [result] The object onto which to store the result.
  616. *
  617. * @returns {Resource} The modified result parameter or a new Resource instance if one was not provided.
  618. */
  619. Resource.prototype.clone = function(result) {
  620. if (!defined(result)) {
  621. result = new Resource({
  622. url : this._url
  623. });
  624. }
  625. result._url = this._url;
  626. result._queryParameters = clone(this._queryParameters);
  627. result._templateValues = clone(this._templateValues);
  628. result.headers = clone(this.headers);
  629. result.proxy = this.proxy;
  630. result.retryCallback = this.retryCallback;
  631. result.retryAttempts = this.retryAttempts;
  632. result._retryCount = 0;
  633. result.request = this.request.clone();
  634. return result;
  635. };
  636. /**
  637. * Returns the base path of the Resource.
  638. *
  639. * @param {Boolean} [includeQuery = false] Whether or not to include the query string and fragment form the uri
  640. *
  641. * @returns {String} The base URI of the resource
  642. */
  643. Resource.prototype.getBaseUri = function(includeQuery) {
  644. return getBaseUri(this.getUrlComponent(includeQuery), includeQuery);
  645. };
  646. /**
  647. * Appends a forward slash to the URL.
  648. */
  649. Resource.prototype.appendForwardSlash = function() {
  650. this._url = appendForwardSlash(this._url);
  651. };
  652. /**
  653. * Asynchronously loads the resource as raw binary data. Returns a promise that will resolve to
  654. * an ArrayBuffer once loaded, or reject if the resource failed to load. The data is loaded
  655. * using XMLHttpRequest, which means that in order to make requests to another origin,
  656. * the server must have Cross-Origin Resource Sharing (CORS) headers enabled.
  657. *
  658. * @returns {Promise.<ArrayBuffer>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  659. *
  660. * @example
  661. * // load a single URL asynchronously
  662. * resource.fetchArrayBuffer().then(function(arrayBuffer) {
  663. * // use the data
  664. * }).otherwise(function(error) {
  665. * // an error occurred
  666. * });
  667. *
  668. * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
  669. * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
  670. */
  671. Resource.prototype.fetchArrayBuffer = function () {
  672. return this.fetch({
  673. responseType : 'arraybuffer'
  674. });
  675. };
  676. /**
  677. * Creates a Resource and calls fetchArrayBuffer() on it.
  678. *
  679. * @param {String|Object} options A url or an object with the following properties
  680. * @param {String} options.url The url of the resource.
  681. * @param {Object} [options.queryParameters] An object containing query parameters that will be sent when retrieving the resource.
  682. * @param {Object} [options.templateValues] Key/Value pairs that are used to replace template values (eg. {x}).
  683. * @param {Object} [options.headers={}] Additional HTTP headers that will be sent.
  684. * @param {DefaultProxy} [options.proxy] A proxy to be used when loading the resource.
  685. * @param {Resource~RetryCallback} [options.retryCallback] The Function to call when a request for this resource fails. If it returns true, the request will be retried.
  686. * @param {Number} [options.retryAttempts=0] The number of times the retryCallback should be called before giving up.
  687. * @param {Request} [options.request] A Request object that will be used. Intended for internal use only.
  688. * @returns {Promise.<ArrayBuffer>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  689. */
  690. Resource.fetchArrayBuffer = function (options) {
  691. var resource = new Resource(options);
  692. return resource.fetchArrayBuffer();
  693. };
  694. /**
  695. * Asynchronously loads the given resource as a blob. Returns a promise that will resolve to
  696. * a Blob once loaded, or reject if the resource failed to load. The data is loaded
  697. * using XMLHttpRequest, which means that in order to make requests to another origin,
  698. * the server must have Cross-Origin Resource Sharing (CORS) headers enabled.
  699. *
  700. * @returns {Promise.<Blob>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  701. *
  702. * @example
  703. * // load a single URL asynchronously
  704. * resource.fetchBlob().then(function(blob) {
  705. * // use the data
  706. * }).otherwise(function(error) {
  707. * // an error occurred
  708. * });
  709. *
  710. * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
  711. * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
  712. */
  713. Resource.prototype.fetchBlob = function () {
  714. return this.fetch({
  715. responseType : 'blob'
  716. });
  717. };
  718. /**
  719. * Creates a Resource and calls fetchBlob() on it.
  720. *
  721. * @param {String|Object} options A url or an object with the following properties
  722. * @param {String} options.url The url of the resource.
  723. * @param {Object} [options.queryParameters] An object containing query parameters that will be sent when retrieving the resource.
  724. * @param {Object} [options.templateValues] Key/Value pairs that are used to replace template values (eg. {x}).
  725. * @param {Object} [options.headers={}] Additional HTTP headers that will be sent.
  726. * @param {DefaultProxy} [options.proxy] A proxy to be used when loading the resource.
  727. * @param {Resource~RetryCallback} [options.retryCallback] The Function to call when a request for this resource fails. If it returns true, the request will be retried.
  728. * @param {Number} [options.retryAttempts=0] The number of times the retryCallback should be called before giving up.
  729. * @param {Request} [options.request] A Request object that will be used. Intended for internal use only.
  730. * @returns {Promise.<Blob>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  731. */
  732. Resource.fetchBlob = function (options) {
  733. var resource = new Resource(options);
  734. return resource.fetchBlob();
  735. };
  736. /**
  737. * Asynchronously loads the given image resource. Returns a promise that will resolve to
  738. * an {@link https://developer.mozilla.org/en-US/docs/Web/API/ImageBitmap|ImageBitmap} if <code>preferImageBitmap</code> is true and the browser supports <code>createImageBitmap</code> or otherwise an
  739. * {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement|Image} once loaded, or reject if the image failed to load.
  740. *
  741. * @param {Object} [options] An object with the following properties.
  742. * @param {Boolean} [options.preferBlob=false] If true, we will load the image via a blob.
  743. * @param {Boolean} [options.preferImageBitmap=false] If true, image will be decoded during fetch and an <code>ImageBitmap</code> is returned.
  744. * @param {Boolean} [options.flipY=false] If true, image will be vertically flipped during decode. Only applies if the browser supports <code>createImageBitmap</code>.
  745. * @returns {Promise.<ImageBitmap>|Promise.<Image>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  746. *
  747. *
  748. * @example
  749. * // load a single image asynchronously
  750. * resource.fetchImage().then(function(image) {
  751. * // use the loaded image
  752. * }).otherwise(function(error) {
  753. * // an error occurred
  754. * });
  755. *
  756. * // load several images in parallel
  757. * when.all([resource1.fetchImage(), resource2.fetchImage()]).then(function(images) {
  758. * // images is an array containing all the loaded images
  759. * });
  760. *
  761. * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
  762. * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
  763. */
  764. Resource.prototype.fetchImage = function (options) {
  765. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  766. var preferImageBitmap = defaultValue(options.preferImageBitmap, false);
  767. var preferBlob = defaultValue(options.preferBlob, false);
  768. var flipY = defaultValue(options.flipY, false);
  769. checkAndResetRequest(this.request);
  770. // We try to load the image normally if
  771. // 1. Blobs aren't supported
  772. // 2. It's a data URI
  773. // 3. It's a blob URI
  774. // 4. It doesn't have request headers and we preferBlob is false
  775. if (!xhrBlobSupported || this.isDataUri || this.isBlobUri || (!this.hasHeaders && !preferBlob)) {
  776. return fetchImage({
  777. resource: this,
  778. flipY: flipY,
  779. preferImageBitmap: preferImageBitmap
  780. });
  781. }
  782. var blobPromise = this.fetchBlob();
  783. if (!defined(blobPromise)) {
  784. return;
  785. }
  786. var supportsImageBitmap;
  787. var useImageBitmap;
  788. var generatedBlobResource;
  789. var generatedBlob;
  790. return Resource.supportsImageBitmapOptions()
  791. .then(function(result) {
  792. supportsImageBitmap = result;
  793. useImageBitmap = supportsImageBitmap && preferImageBitmap;
  794. return blobPromise;
  795. })
  796. .then(function(blob) {
  797. if (!defined(blob)) {
  798. return;
  799. }
  800. generatedBlob = blob;
  801. if (useImageBitmap) {
  802. return Resource.createImageBitmapFromBlob(blob, {
  803. flipY: flipY,
  804. premultiplyAlpha: false
  805. });
  806. }
  807. var blobUrl = window.URL.createObjectURL(blob);
  808. generatedBlobResource = new Resource({
  809. url: blobUrl
  810. });
  811. return fetchImage({
  812. resource: generatedBlobResource,
  813. flipY: flipY,
  814. preferImageBitmap: false
  815. });
  816. })
  817. .then(function(image) {
  818. if (!defined(image)) {
  819. return;
  820. }
  821. // The blob object may be needed for use by a TileDiscardPolicy,
  822. // so attach it to the image.
  823. image.blob = generatedBlob;
  824. if (useImageBitmap) {
  825. return image;
  826. }
  827. window.URL.revokeObjectURL(generatedBlobResource.url);
  828. return image;
  829. })
  830. .otherwise(function(error) {
  831. if (defined(generatedBlobResource)) {
  832. window.URL.revokeObjectURL(generatedBlobResource.url);
  833. }
  834. // If the blob load succeeded but the image decode failed, attach the blob
  835. // to the error object for use by a TileDiscardPolicy.
  836. // In particular, BingMapsImageryProvider uses this to detect the
  837. // zero-length response that is returned when a tile is not available.
  838. error.blob = generatedBlob;
  839. return when.reject(error);
  840. });
  841. };
  842. /**
  843. * Fetches an image and returns a promise to it.
  844. *
  845. * @param {Object} [options] An object with the following properties.
  846. * @param {Resource} [options.resource] Resource object that points to an image to fetch.
  847. * @param {Boolean} [options.preferImageBitmap] If true, image will be decoded during fetch and an <code>ImageBitmap</code> is returned.
  848. * @param {Boolean} [options.flipY] If true, image will be vertically flipped during decode. Only applies if the browser supports <code>createImageBitmap</code>.
  849. *
  850. * @private
  851. */
  852. function fetchImage(options) {
  853. var resource = options.resource;
  854. var flipY = options.flipY;
  855. var preferImageBitmap = options.preferImageBitmap;
  856. var request = resource.request;
  857. request.url = resource.url;
  858. request.requestFunction = function() {
  859. var url = resource.url;
  860. var crossOrigin = false;
  861. // data URIs can't have crossorigin set.
  862. if (!resource.isDataUri && !resource.isBlobUri) {
  863. crossOrigin = resource.isCrossOriginUrl;
  864. }
  865. var deferred = when.defer();
  866. Resource._Implementations.createImage(url, crossOrigin, deferred, flipY, preferImageBitmap);
  867. return deferred.promise;
  868. };
  869. var promise = RequestScheduler.request(request);
  870. if (!defined(promise)) {
  871. return;
  872. }
  873. return promise
  874. .otherwise(function(e) {
  875. // Don't retry cancelled or otherwise aborted requests
  876. if (request.state !== RequestState.FAILED) {
  877. return when.reject(e);
  878. }
  879. return resource.retryOnError(e)
  880. .then(function(retry) {
  881. if (retry) {
  882. // Reset request so it can try again
  883. request.state = RequestState.UNISSUED;
  884. request.deferred = undefined;
  885. return fetchImage({
  886. resource: resource,
  887. flipY: flipY,
  888. preferImageBitmap: preferImageBitmap
  889. });
  890. }
  891. return when.reject(e);
  892. });
  893. });
  894. }
  895. /**
  896. * Creates a Resource and calls fetchImage() on it.
  897. *
  898. * @param {String|Object} options A url or an object with the following properties
  899. * @param {String} options.url The url of the resource.
  900. * @param {Object} [options.queryParameters] An object containing query parameters that will be sent when retrieving the resource.
  901. * @param {Object} [options.templateValues] Key/Value pairs that are used to replace template values (eg. {x}).
  902. * @param {Object} [options.headers={}] Additional HTTP headers that will be sent.
  903. * @param {DefaultProxy} [options.proxy] A proxy to be used when loading the resource.
  904. * @param {Boolean} [options.flipY=false] Whether to vertically flip the image during fetch and decode. Only applies when requesting an image and the browser supports <code>createImageBitmap</code>.
  905. * @param {Resource~RetryCallback} [options.retryCallback] The Function to call when a request for this resource fails. If it returns true, the request will be retried.
  906. * @param {Number} [options.retryAttempts=0] The number of times the retryCallback should be called before giving up.
  907. * @param {Request} [options.request] A Request object that will be used. Intended for internal use only.
  908. * @param {Boolean} [options.preferBlob=false] If true, we will load the image via a blob.
  909. * @param {Boolean} [options.preferImageBitmap=false] If true, image will be decoded during fetch and an <code>ImageBitmap</code> is returned.
  910. * @returns {Promise.<ImageBitmap>|Promise.<Image>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  911. */
  912. Resource.fetchImage = function (options) {
  913. var resource = new Resource(options);
  914. return resource.fetchImage({
  915. flipY: options.flipY,
  916. preferBlob: options.preferBlob,
  917. preferImageBitmap: options.preferImageBitmap
  918. });
  919. };
  920. /**
  921. * Asynchronously loads the given resource as text. Returns a promise that will resolve to
  922. * a String once loaded, or reject if the resource failed to load. The data is loaded
  923. * using XMLHttpRequest, which means that in order to make requests to another origin,
  924. * the server must have Cross-Origin Resource Sharing (CORS) headers enabled.
  925. *
  926. * @returns {Promise.<String>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  927. *
  928. * @example
  929. * // load text from a URL, setting a custom header
  930. * var resource = new Resource({
  931. * url: 'http://someUrl.com/someJson.txt',
  932. * headers: {
  933. * 'X-Custom-Header' : 'some value'
  934. * }
  935. * });
  936. * resource.fetchText().then(function(text) {
  937. * // Do something with the text
  938. * }).otherwise(function(error) {
  939. * // an error occurred
  940. * });
  941. *
  942. * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest|XMLHttpRequest}
  943. * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
  944. * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
  945. */
  946. Resource.prototype.fetchText = function() {
  947. return this.fetch({
  948. responseType : 'text'
  949. });
  950. };
  951. /**
  952. * Creates a Resource and calls fetchText() on it.
  953. *
  954. * @param {String|Object} options A url or an object with the following properties
  955. * @param {String} options.url The url of the resource.
  956. * @param {Object} [options.queryParameters] An object containing query parameters that will be sent when retrieving the resource.
  957. * @param {Object} [options.templateValues] Key/Value pairs that are used to replace template values (eg. {x}).
  958. * @param {Object} [options.headers={}] Additional HTTP headers that will be sent.
  959. * @param {DefaultProxy} [options.proxy] A proxy to be used when loading the resource.
  960. * @param {Resource~RetryCallback} [options.retryCallback] The Function to call when a request for this resource fails. If it returns true, the request will be retried.
  961. * @param {Number} [options.retryAttempts=0] The number of times the retryCallback should be called before giving up.
  962. * @param {Request} [options.request] A Request object that will be used. Intended for internal use only.
  963. * @returns {Promise.<String>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  964. */
  965. Resource.fetchText = function (options) {
  966. var resource = new Resource(options);
  967. return resource.fetchText();
  968. };
  969. // note: &#42;&#47;&#42; below is */* but that ends the comment block early
  970. /**
  971. * Asynchronously loads the given resource as JSON. Returns a promise that will resolve to
  972. * a JSON object once loaded, or reject if the resource failed to load. The data is loaded
  973. * using XMLHttpRequest, which means that in order to make requests to another origin,
  974. * the server must have Cross-Origin Resource Sharing (CORS) headers enabled. This function
  975. * adds 'Accept: application/json,&#42;&#47;&#42;;q=0.01' to the request headers, if not
  976. * already specified.
  977. *
  978. * @returns {Promise.<Object>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  979. *
  980. *
  981. * @example
  982. * resource.fetchJson().then(function(jsonData) {
  983. * // Do something with the JSON object
  984. * }).otherwise(function(error) {
  985. * // an error occurred
  986. * });
  987. *
  988. * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
  989. * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
  990. */
  991. Resource.prototype.fetchJson = function() {
  992. var promise = this.fetch({
  993. responseType : 'text',
  994. headers: {
  995. Accept : 'application/json,*/*;q=0.01'
  996. }
  997. });
  998. if (!defined(promise)) {
  999. return undefined;
  1000. }
  1001. return promise
  1002. .then(function(value) {
  1003. if (!defined(value)) {
  1004. return;
  1005. }
  1006. return JSON.parse(value);
  1007. });
  1008. };
  1009. /**
  1010. * Creates a Resource and calls fetchJson() on it.
  1011. *
  1012. * @param {String|Object} options A url or an object with the following properties
  1013. * @param {String} options.url The url of the resource.
  1014. * @param {Object} [options.queryParameters] An object containing query parameters that will be sent when retrieving the resource.
  1015. * @param {Object} [options.templateValues] Key/Value pairs that are used to replace template values (eg. {x}).
  1016. * @param {Object} [options.headers={}] Additional HTTP headers that will be sent.
  1017. * @param {DefaultProxy} [options.proxy] A proxy to be used when loading the resource.
  1018. * @param {Resource~RetryCallback} [options.retryCallback] The Function to call when a request for this resource fails. If it returns true, the request will be retried.
  1019. * @param {Number} [options.retryAttempts=0] The number of times the retryCallback should be called before giving up.
  1020. * @param {Request} [options.request] A Request object that will be used. Intended for internal use only.
  1021. * @returns {Promise.<Object>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  1022. */
  1023. Resource.fetchJson = function (options) {
  1024. var resource = new Resource(options);
  1025. return resource.fetchJson();
  1026. };
  1027. /**
  1028. * Asynchronously loads the given resource as XML. Returns a promise that will resolve to
  1029. * an XML Document once loaded, or reject if the resource failed to load. The data is loaded
  1030. * using XMLHttpRequest, which means that in order to make requests to another origin,
  1031. * the server must have Cross-Origin Resource Sharing (CORS) headers enabled.
  1032. *
  1033. * @returns {Promise.<XMLDocument>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  1034. *
  1035. *
  1036. * @example
  1037. * // load XML from a URL, setting a custom header
  1038. * Cesium.loadXML('http://someUrl.com/someXML.xml', {
  1039. * 'X-Custom-Header' : 'some value'
  1040. * }).then(function(document) {
  1041. * // Do something with the document
  1042. * }).otherwise(function(error) {
  1043. * // an error occurred
  1044. * });
  1045. *
  1046. * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest|XMLHttpRequest}
  1047. * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
  1048. * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
  1049. */
  1050. Resource.prototype.fetchXML = function() {
  1051. return this.fetch({
  1052. responseType : 'document',
  1053. overrideMimeType : 'text/xml'
  1054. });
  1055. };
  1056. /**
  1057. * Creates a Resource and calls fetchXML() on it.
  1058. *
  1059. * @param {String|Object} options A url or an object with the following properties
  1060. * @param {String} options.url The url of the resource.
  1061. * @param {Object} [options.queryParameters] An object containing query parameters that will be sent when retrieving the resource.
  1062. * @param {Object} [options.templateValues] Key/Value pairs that are used to replace template values (eg. {x}).
  1063. * @param {Object} [options.headers={}] Additional HTTP headers that will be sent.
  1064. * @param {DefaultProxy} [options.proxy] A proxy to be used when loading the resource.
  1065. * @param {Resource~RetryCallback} [options.retryCallback] The Function to call when a request for this resource fails. If it returns true, the request will be retried.
  1066. * @param {Number} [options.retryAttempts=0] The number of times the retryCallback should be called before giving up.
  1067. * @param {Request} [options.request] A Request object that will be used. Intended for internal use only.
  1068. * @returns {Promise.<XMLDocument>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  1069. */
  1070. Resource.fetchXML = function (options) {
  1071. var resource = new Resource(options);
  1072. return resource.fetchXML();
  1073. };
  1074. /**
  1075. * Requests a resource using JSONP.
  1076. *
  1077. * @param {String} [callbackParameterName='callback'] The callback parameter name that the server expects.
  1078. * @returns {Promise.<Object>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  1079. *
  1080. *
  1081. * @example
  1082. * // load a data asynchronously
  1083. * resource.fetchJsonp().then(function(data) {
  1084. * // use the loaded data
  1085. * }).otherwise(function(error) {
  1086. * // an error occurred
  1087. * });
  1088. *
  1089. * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
  1090. */
  1091. Resource.prototype.fetchJsonp = function(callbackParameterName) {
  1092. callbackParameterName = defaultValue(callbackParameterName, 'callback');
  1093. checkAndResetRequest(this.request);
  1094. //generate a unique function name
  1095. var functionName;
  1096. do {
  1097. functionName = 'loadJsonp' + Math.random().toString().substring(2, 8);
  1098. } while (defined(window[functionName]));
  1099. return fetchJsonp(this, callbackParameterName, functionName);
  1100. };
  1101. function fetchJsonp(resource, callbackParameterName, functionName) {
  1102. var callbackQuery = {};
  1103. callbackQuery[callbackParameterName] = functionName;
  1104. resource.setQueryParameters(callbackQuery);
  1105. var request = resource.request;
  1106. request.url = resource.url;
  1107. request.requestFunction = function() {
  1108. var deferred = when.defer();
  1109. //assign a function with that name in the global scope
  1110. window[functionName] = function(data) {
  1111. deferred.resolve(data);
  1112. try {
  1113. delete window[functionName];
  1114. } catch (e) {
  1115. window[functionName] = undefined;
  1116. }
  1117. };
  1118. Resource._Implementations.loadAndExecuteScript(resource.url, functionName, deferred);
  1119. return deferred.promise;
  1120. };
  1121. var promise = RequestScheduler.request(request);
  1122. if (!defined(promise)) {
  1123. return;
  1124. }
  1125. return promise
  1126. .otherwise(function(e) {
  1127. if (request.state !== RequestState.FAILED) {
  1128. return when.reject(e);
  1129. }
  1130. return resource.retryOnError(e)
  1131. .then(function(retry) {
  1132. if (retry) {
  1133. // Reset request so it can try again
  1134. request.state = RequestState.UNISSUED;
  1135. request.deferred = undefined;
  1136. return fetchJsonp(resource, callbackParameterName, functionName);
  1137. }
  1138. return when.reject(e);
  1139. });
  1140. });
  1141. }
  1142. /**
  1143. * Creates a Resource from a URL and calls fetchJsonp() on it.
  1144. *
  1145. * @param {String|Object} options A url or an object with the following properties
  1146. * @param {String} options.url The url of the resource.
  1147. * @param {Object} [options.queryParameters] An object containing query parameters that will be sent when retrieving the resource.
  1148. * @param {Object} [options.templateValues] Key/Value pairs that are used to replace template values (eg. {x}).
  1149. * @param {Object} [options.headers={}] Additional HTTP headers that will be sent.
  1150. * @param {DefaultProxy} [options.proxy] A proxy to be used when loading the resource.
  1151. * @param {Resource~RetryCallback} [options.retryCallback] The Function to call when a request for this resource fails. If it returns true, the request will be retried.
  1152. * @param {Number} [options.retryAttempts=0] The number of times the retryCallback should be called before giving up.
  1153. * @param {Request} [options.request] A Request object that will be used. Intended for internal use only.
  1154. * @param {String} [options.callbackParameterName='callback'] The callback parameter name that the server expects.
  1155. * @returns {Promise.<Object>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  1156. */
  1157. Resource.fetchJsonp = function (options) {
  1158. var resource = new Resource(options);
  1159. return resource.fetchJsonp(options.callbackParameterName);
  1160. };
  1161. /**
  1162. * @private
  1163. */
  1164. Resource.prototype._makeRequest = function(options) {
  1165. var resource = this;
  1166. checkAndResetRequest(resource.request);
  1167. var request = resource.request;
  1168. request.url = resource.url;
  1169. request.requestFunction = function() {
  1170. var responseType = options.responseType;
  1171. var headers = combine(options.headers, resource.headers);
  1172. var overrideMimeType = options.overrideMimeType;
  1173. var method = options.method;
  1174. var data = options.data;
  1175. var deferred = when.defer();
  1176. var xhr = Resource._Implementations.loadWithXhr(resource.url, responseType, method, data, headers, deferred, overrideMimeType);
  1177. if (defined(xhr) && defined(xhr.abort)) {
  1178. request.cancelFunction = function() {
  1179. xhr.abort();
  1180. };
  1181. }
  1182. return deferred.promise;
  1183. };
  1184. var promise = RequestScheduler.request(request);
  1185. if (!defined(promise)) {
  1186. return;
  1187. }
  1188. return promise
  1189. .then(function(data) {
  1190. return data;
  1191. })
  1192. .otherwise(function(e) {
  1193. if (request.state !== RequestState.FAILED) {
  1194. return when.reject(e);
  1195. }
  1196. return resource.retryOnError(e)
  1197. .then(function(retry) {
  1198. if (retry) {
  1199. // Reset request so it can try again
  1200. request.state = RequestState.UNISSUED;
  1201. request.deferred = undefined;
  1202. return resource.fetch(options);
  1203. }
  1204. return when.reject(e);
  1205. });
  1206. });
  1207. };
  1208. var dataUriRegex = /^data:(.*?)(;base64)?,(.*)$/;
  1209. function decodeDataUriText(isBase64, data) {
  1210. var result = decodeURIComponent(data);
  1211. if (isBase64) {
  1212. return atob(result);
  1213. }
  1214. return result;
  1215. }
  1216. function decodeDataUriArrayBuffer(isBase64, data) {
  1217. var byteString = decodeDataUriText(isBase64, data);
  1218. var buffer = new ArrayBuffer(byteString.length);
  1219. var view = new Uint8Array(buffer);
  1220. for (var i = 0; i < byteString.length; i++) {
  1221. view[i] = byteString.charCodeAt(i);
  1222. }
  1223. return buffer;
  1224. }
  1225. function decodeDataUri(dataUriRegexResult, responseType) {
  1226. responseType = defaultValue(responseType, '');
  1227. var mimeType = dataUriRegexResult[1];
  1228. var isBase64 = !!dataUriRegexResult[2];
  1229. var data = dataUriRegexResult[3];
  1230. switch (responseType) {
  1231. case '':
  1232. case 'text':
  1233. return decodeDataUriText(isBase64, data);
  1234. case 'arraybuffer':
  1235. return decodeDataUriArrayBuffer(isBase64, data);
  1236. case 'blob':
  1237. var buffer = decodeDataUriArrayBuffer(isBase64, data);
  1238. return new Blob([buffer], {
  1239. type : mimeType
  1240. });
  1241. case 'document':
  1242. var parser = new DOMParser();
  1243. return parser.parseFromString(decodeDataUriText(isBase64, data), mimeType);
  1244. case 'json':
  1245. return JSON.parse(decodeDataUriText(isBase64, data));
  1246. default:
  1247. //>>includeStart('debug', pragmas.debug);
  1248. throw new DeveloperError('Unhandled responseType: ' + responseType);
  1249. //>>includeEnd('debug');
  1250. }
  1251. }
  1252. /**
  1253. * Asynchronously loads the given resource. Returns a promise that will resolve to
  1254. * the result once loaded, or reject if the resource failed to load. The data is loaded
  1255. * using XMLHttpRequest, which means that in order to make requests to another origin,
  1256. * the server must have Cross-Origin Resource Sharing (CORS) headers enabled. It's recommended that you use
  1257. * the more specific functions eg. fetchJson, fetchBlob, etc.
  1258. *
  1259. * @param {Object} [options] Object with the following properties:
  1260. * @param {String} [options.responseType] The type of response. This controls the type of item returned.
  1261. * @param {Object} [options.headers] Additional HTTP headers to send with the request, if any.
  1262. * @param {String} [options.overrideMimeType] Overrides the MIME type returned by the server.
  1263. * @returns {Promise.<Object>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  1264. *
  1265. *
  1266. * @example
  1267. * resource.fetch()
  1268. * .then(function(body) {
  1269. * // use the data
  1270. * }).otherwise(function(error) {
  1271. * // an error occurred
  1272. * });
  1273. *
  1274. * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
  1275. * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
  1276. */
  1277. Resource.prototype.fetch = function(options) {
  1278. options = defaultClone(options, {});
  1279. options.method = 'GET';
  1280. return this._makeRequest(options);
  1281. };
  1282. /**
  1283. * Creates a Resource from a URL and calls fetch() on it.
  1284. *
  1285. * @param {String|Object} options A url or an object with the following properties
  1286. * @param {String} options.url The url of the resource.
  1287. * @param {Object} [options.queryParameters] An object containing query parameters that will be sent when retrieving the resource.
  1288. * @param {Object} [options.templateValues] Key/Value pairs that are used to replace template values (eg. {x}).
  1289. * @param {Object} [options.headers={}] Additional HTTP headers that will be sent.
  1290. * @param {DefaultProxy} [options.proxy] A proxy to be used when loading the resource.
  1291. * @param {Resource~RetryCallback} [options.retryCallback] The Function to call when a request for this resource fails. If it returns true, the request will be retried.
  1292. * @param {Number} [options.retryAttempts=0] The number of times the retryCallback should be called before giving up.
  1293. * @param {Request} [options.request] A Request object that will be used. Intended for internal use only.
  1294. * @param {String} [options.responseType] The type of response. This controls the type of item returned.
  1295. * @param {String} [options.overrideMimeType] Overrides the MIME type returned by the server.
  1296. * @returns {Promise.<Object>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  1297. */
  1298. Resource.fetch = function (options) {
  1299. var resource = new Resource(options);
  1300. return resource.fetch({
  1301. // Make copy of just the needed fields because headers can be passed to both the constructor and to fetch
  1302. responseType: options.responseType,
  1303. overrideMimeType: options.overrideMimeType
  1304. });
  1305. };
  1306. /**
  1307. * Asynchronously deletes the given resource. Returns a promise that will resolve to
  1308. * the result once loaded, or reject if the resource failed to load. The data is loaded
  1309. * using XMLHttpRequest, which means that in order to make requests to another origin,
  1310. * the server must have Cross-Origin Resource Sharing (CORS) headers enabled.
  1311. *
  1312. * @param {Object} [options] Object with the following properties:
  1313. * @param {String} [options.responseType] The type of response. This controls the type of item returned.
  1314. * @param {Object} [options.headers] Additional HTTP headers to send with the request, if any.
  1315. * @param {String} [options.overrideMimeType] Overrides the MIME type returned by the server.
  1316. * @returns {Promise.<Object>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  1317. *
  1318. *
  1319. * @example
  1320. * resource.delete()
  1321. * .then(function(body) {
  1322. * // use the data
  1323. * }).otherwise(function(error) {
  1324. * // an error occurred
  1325. * });
  1326. *
  1327. * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
  1328. * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
  1329. */
  1330. Resource.prototype.delete = function(options) {
  1331. options = defaultClone(options, {});
  1332. options.method = 'DELETE';
  1333. return this._makeRequest(options);
  1334. };
  1335. /**
  1336. * Creates a Resource from a URL and calls delete() on it.
  1337. *
  1338. * @param {String|Object} options A url or an object with the following properties
  1339. * @param {String} options.url The url of the resource.
  1340. * @param {Object} [options.data] Data that is posted with the resource.
  1341. * @param {Object} [options.queryParameters] An object containing query parameters that will be sent when retrieving the resource.
  1342. * @param {Object} [options.templateValues] Key/Value pairs that are used to replace template values (eg. {x}).
  1343. * @param {Object} [options.headers={}] Additional HTTP headers that will be sent.
  1344. * @param {DefaultProxy} [options.proxy] A proxy to be used when loading the resource.
  1345. * @param {Resource~RetryCallback} [options.retryCallback] The Function to call when a request for this resource fails. If it returns true, the request will be retried.
  1346. * @param {Number} [options.retryAttempts=0] The number of times the retryCallback should be called before giving up.
  1347. * @param {Request} [options.request] A Request object that will be used. Intended for internal use only.
  1348. * @param {String} [options.responseType] The type of response. This controls the type of item returned.
  1349. * @param {String} [options.overrideMimeType] Overrides the MIME type returned by the server.
  1350. * @returns {Promise.<Object>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  1351. */
  1352. Resource.delete = function (options) {
  1353. var resource = new Resource(options);
  1354. return resource.delete({
  1355. // Make copy of just the needed fields because headers can be passed to both the constructor and to fetch
  1356. responseType: options.responseType,
  1357. overrideMimeType: options.overrideMimeType,
  1358. data: options.data
  1359. });
  1360. };
  1361. /**
  1362. * Asynchronously gets headers the given resource. Returns a promise that will resolve to
  1363. * the result once loaded, or reject if the resource failed to load. The data is loaded
  1364. * using XMLHttpRequest, which means that in order to make requests to another origin,
  1365. * the server must have Cross-Origin Resource Sharing (CORS) headers enabled.
  1366. *
  1367. * @param {Object} [options] Object with the following properties:
  1368. * @param {String} [options.responseType] The type of response. This controls the type of item returned.
  1369. * @param {Object} [options.headers] Additional HTTP headers to send with the request, if any.
  1370. * @param {String} [options.overrideMimeType] Overrides the MIME type returned by the server.
  1371. * @returns {Promise.<Object>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  1372. *
  1373. *
  1374. * @example
  1375. * resource.head()
  1376. * .then(function(headers) {
  1377. * // use the data
  1378. * }).otherwise(function(error) {
  1379. * // an error occurred
  1380. * });
  1381. *
  1382. * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
  1383. * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
  1384. */
  1385. Resource.prototype.head = function(options) {
  1386. options = defaultClone(options, {});
  1387. options.method = 'HEAD';
  1388. return this._makeRequest(options);
  1389. };
  1390. /**
  1391. * Creates a Resource from a URL and calls head() on it.
  1392. *
  1393. * @param {String|Object} options A url or an object with the following properties
  1394. * @param {String} options.url The url of the resource.
  1395. * @param {Object} [options.queryParameters] An object containing query parameters that will be sent when retrieving the resource.
  1396. * @param {Object} [options.templateValues] Key/Value pairs that are used to replace template values (eg. {x}).
  1397. * @param {Object} [options.headers={}] Additional HTTP headers that will be sent.
  1398. * @param {DefaultProxy} [options.proxy] A proxy to be used when loading the resource.
  1399. * @param {Resource~RetryCallback} [options.retryCallback] The Function to call when a request for this resource fails. If it returns true, the request will be retried.
  1400. * @param {Number} [options.retryAttempts=0] The number of times the retryCallback should be called before giving up.
  1401. * @param {Request} [options.request] A Request object that will be used. Intended for internal use only.
  1402. * @param {String} [options.responseType] The type of response. This controls the type of item returned.
  1403. * @param {String} [options.overrideMimeType] Overrides the MIME type returned by the server.
  1404. * @returns {Promise.<Object>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  1405. */
  1406. Resource.head = function (options) {
  1407. var resource = new Resource(options);
  1408. return resource.head({
  1409. // Make copy of just the needed fields because headers can be passed to both the constructor and to fetch
  1410. responseType: options.responseType,
  1411. overrideMimeType: options.overrideMimeType
  1412. });
  1413. };
  1414. /**
  1415. * Asynchronously gets options the given resource. Returns a promise that will resolve to
  1416. * the result once loaded, or reject if the resource failed to load. The data is loaded
  1417. * using XMLHttpRequest, which means that in order to make requests to another origin,
  1418. * the server must have Cross-Origin Resource Sharing (CORS) headers enabled.
  1419. *
  1420. * @param {Object} [options] Object with the following properties:
  1421. * @param {String} [options.responseType] The type of response. This controls the type of item returned.
  1422. * @param {Object} [options.headers] Additional HTTP headers to send with the request, if any.
  1423. * @param {String} [options.overrideMimeType] Overrides the MIME type returned by the server.
  1424. * @returns {Promise.<Object>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  1425. *
  1426. *
  1427. * @example
  1428. * resource.options()
  1429. * .then(function(headers) {
  1430. * // use the data
  1431. * }).otherwise(function(error) {
  1432. * // an error occurred
  1433. * });
  1434. *
  1435. * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
  1436. * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
  1437. */
  1438. Resource.prototype.options = function(options) {
  1439. options = defaultClone(options, {});
  1440. options.method = 'OPTIONS';
  1441. return this._makeRequest(options);
  1442. };
  1443. /**
  1444. * Creates a Resource from a URL and calls options() on it.
  1445. *
  1446. * @param {String|Object} options A url or an object with the following properties
  1447. * @param {String} options.url The url of the resource.
  1448. * @param {Object} [options.queryParameters] An object containing query parameters that will be sent when retrieving the resource.
  1449. * @param {Object} [options.templateValues] Key/Value pairs that are used to replace template values (eg. {x}).
  1450. * @param {Object} [options.headers={}] Additional HTTP headers that will be sent.
  1451. * @param {DefaultProxy} [options.proxy] A proxy to be used when loading the resource.
  1452. * @param {Resource~RetryCallback} [options.retryCallback] The Function to call when a request for this resource fails. If it returns true, the request will be retried.
  1453. * @param {Number} [options.retryAttempts=0] The number of times the retryCallback should be called before giving up.
  1454. * @param {Request} [options.request] A Request object that will be used. Intended for internal use only.
  1455. * @param {String} [options.responseType] The type of response. This controls the type of item returned.
  1456. * @param {String} [options.overrideMimeType] Overrides the MIME type returned by the server.
  1457. * @returns {Promise.<Object>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  1458. */
  1459. Resource.options = function (options) {
  1460. var resource = new Resource(options);
  1461. return resource.options({
  1462. // Make copy of just the needed fields because headers can be passed to both the constructor and to fetch
  1463. responseType: options.responseType,
  1464. overrideMimeType: options.overrideMimeType
  1465. });
  1466. };
  1467. /**
  1468. * Asynchronously posts data to the given resource. Returns a promise that will resolve to
  1469. * the result once loaded, or reject if the resource failed to load. The data is loaded
  1470. * using XMLHttpRequest, which means that in order to make requests to another origin,
  1471. * the server must have Cross-Origin Resource Sharing (CORS) headers enabled.
  1472. *
  1473. * @param {Object} data Data that is posted with the resource.
  1474. * @param {Object} [options] Object with the following properties:
  1475. * @param {Object} [options.data] Data that is posted with the resource.
  1476. * @param {String} [options.responseType] The type of response. This controls the type of item returned.
  1477. * @param {Object} [options.headers] Additional HTTP headers to send with the request, if any.
  1478. * @param {String} [options.overrideMimeType] Overrides the MIME type returned by the server.
  1479. * @returns {Promise.<Object>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  1480. *
  1481. *
  1482. * @example
  1483. * resource.post(data)
  1484. * .then(function(result) {
  1485. * // use the result
  1486. * }).otherwise(function(error) {
  1487. * // an error occurred
  1488. * });
  1489. *
  1490. * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
  1491. * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
  1492. */
  1493. Resource.prototype.post = function(data, options) {
  1494. Check.defined('data', data);
  1495. options = defaultClone(options, {});
  1496. options.method = 'POST';
  1497. options.data = data;
  1498. return this._makeRequest(options);
  1499. };
  1500. /**
  1501. * Creates a Resource from a URL and calls post() on it.
  1502. *
  1503. * @param {Object} options A url or an object with the following properties
  1504. * @param {String} options.url The url of the resource.
  1505. * @param {Object} options.data Data that is posted with the resource.
  1506. * @param {Object} [options.queryParameters] An object containing query parameters that will be sent when retrieving the resource.
  1507. * @param {Object} [options.templateValues] Key/Value pairs that are used to replace template values (eg. {x}).
  1508. * @param {Object} [options.headers={}] Additional HTTP headers that will be sent.
  1509. * @param {DefaultProxy} [options.proxy] A proxy to be used when loading the resource.
  1510. * @param {Resource~RetryCallback} [options.retryCallback] The Function to call when a request for this resource fails. If it returns true, the request will be retried.
  1511. * @param {Number} [options.retryAttempts=0] The number of times the retryCallback should be called before giving up.
  1512. * @param {Request} [options.request] A Request object that will be used. Intended for internal use only.
  1513. * @param {String} [options.responseType] The type of response. This controls the type of item returned.
  1514. * @param {String} [options.overrideMimeType] Overrides the MIME type returned by the server.
  1515. * @returns {Promise.<Object>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  1516. */
  1517. Resource.post = function (options) {
  1518. var resource = new Resource(options);
  1519. return resource.post(options.data, {
  1520. // Make copy of just the needed fields because headers can be passed to both the constructor and to post
  1521. responseType: options.responseType,
  1522. overrideMimeType: options.overrideMimeType
  1523. });
  1524. };
  1525. /**
  1526. * Asynchronously puts data to the given resource. Returns a promise that will resolve to
  1527. * the result once loaded, or reject if the resource failed to load. The data is loaded
  1528. * using XMLHttpRequest, which means that in order to make requests to another origin,
  1529. * the server must have Cross-Origin Resource Sharing (CORS) headers enabled.
  1530. *
  1531. * @param {Object} data Data that is posted with the resource.
  1532. * @param {Object} [options] Object with the following properties:
  1533. * @param {String} [options.responseType] The type of response. This controls the type of item returned.
  1534. * @param {Object} [options.headers] Additional HTTP headers to send with the request, if any.
  1535. * @param {String} [options.overrideMimeType] Overrides the MIME type returned by the server.
  1536. * @returns {Promise.<Object>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  1537. *
  1538. *
  1539. * @example
  1540. * resource.put(data)
  1541. * .then(function(result) {
  1542. * // use the result
  1543. * }).otherwise(function(error) {
  1544. * // an error occurred
  1545. * });
  1546. *
  1547. * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
  1548. * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
  1549. */
  1550. Resource.prototype.put = function(data, options) {
  1551. Check.defined('data', data);
  1552. options = defaultClone(options, {});
  1553. options.method = 'PUT';
  1554. options.data = data;
  1555. return this._makeRequest(options);
  1556. };
  1557. /**
  1558. * Creates a Resource from a URL and calls put() on it.
  1559. *
  1560. * @param {Object} options A url or an object with the following properties
  1561. * @param {String} options.url The url of the resource.
  1562. * @param {Object} options.data Data that is posted with the resource.
  1563. * @param {Object} [options.queryParameters] An object containing query parameters that will be sent when retrieving the resource.
  1564. * @param {Object} [options.templateValues] Key/Value pairs that are used to replace template values (eg. {x}).
  1565. * @param {Object} [options.headers={}] Additional HTTP headers that will be sent.
  1566. * @param {DefaultProxy} [options.proxy] A proxy to be used when loading the resource.
  1567. * @param {Resource~RetryCallback} [options.retryCallback] The Function to call when a request for this resource fails. If it returns true, the request will be retried.
  1568. * @param {Number} [options.retryAttempts=0] The number of times the retryCallback should be called before giving up.
  1569. * @param {Request} [options.request] A Request object that will be used. Intended for internal use only.
  1570. * @param {String} [options.responseType] The type of response. This controls the type of item returned.
  1571. * @param {String} [options.overrideMimeType] Overrides the MIME type returned by the server.
  1572. * @returns {Promise.<Object>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  1573. */
  1574. Resource.put = function (options) {
  1575. var resource = new Resource(options);
  1576. return resource.put(options.data, {
  1577. // Make copy of just the needed fields because headers can be passed to both the constructor and to post
  1578. responseType: options.responseType,
  1579. overrideMimeType: options.overrideMimeType
  1580. });
  1581. };
  1582. /**
  1583. * Asynchronously patches data to the given resource. Returns a promise that will resolve to
  1584. * the result once loaded, or reject if the resource failed to load. The data is loaded
  1585. * using XMLHttpRequest, which means that in order to make requests to another origin,
  1586. * the server must have Cross-Origin Resource Sharing (CORS) headers enabled.
  1587. *
  1588. * @param {Object} data Data that is posted with the resource.
  1589. * @param {Object} [options] Object with the following properties:
  1590. * @param {String} [options.responseType] The type of response. This controls the type of item returned.
  1591. * @param {Object} [options.headers] Additional HTTP headers to send with the request, if any.
  1592. * @param {String} [options.overrideMimeType] Overrides the MIME type returned by the server.
  1593. * @returns {Promise.<Object>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  1594. *
  1595. *
  1596. * @example
  1597. * resource.patch(data)
  1598. * .then(function(result) {
  1599. * // use the result
  1600. * }).otherwise(function(error) {
  1601. * // an error occurred
  1602. * });
  1603. *
  1604. * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
  1605. * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
  1606. */
  1607. Resource.prototype.patch = function(data, options) {
  1608. Check.defined('data', data);
  1609. options = defaultClone(options, {});
  1610. options.method = 'PATCH';
  1611. options.data = data;
  1612. return this._makeRequest(options);
  1613. };
  1614. /**
  1615. * Creates a Resource from a URL and calls patch() on it.
  1616. *
  1617. * @param {Object} options A url or an object with the following properties
  1618. * @param {String} options.url The url of the resource.
  1619. * @param {Object} options.data Data that is posted with the resource.
  1620. * @param {Object} [options.queryParameters] An object containing query parameters that will be sent when retrieving the resource.
  1621. * @param {Object} [options.templateValues] Key/Value pairs that are used to replace template values (eg. {x}).
  1622. * @param {Object} [options.headers={}] Additional HTTP headers that will be sent.
  1623. * @param {DefaultProxy} [options.proxy] A proxy to be used when loading the resource.
  1624. * @param {Resource~RetryCallback} [options.retryCallback] The Function to call when a request for this resource fails. If it returns true, the request will be retried.
  1625. * @param {Number} [options.retryAttempts=0] The number of times the retryCallback should be called before giving up.
  1626. * @param {Request} [options.request] A Request object that will be used. Intended for internal use only.
  1627. * @param {String} [options.responseType] The type of response. This controls the type of item returned.
  1628. * @param {String} [options.overrideMimeType] Overrides the MIME type returned by the server.
  1629. * @returns {Promise.<Object>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
  1630. */
  1631. Resource.patch = function (options) {
  1632. var resource = new Resource(options);
  1633. return resource.patch(options.data, {
  1634. // Make copy of just the needed fields because headers can be passed to both the constructor and to post
  1635. responseType: options.responseType,
  1636. overrideMimeType: options.overrideMimeType
  1637. });
  1638. };
  1639. /**
  1640. * Contains implementations of functions that can be replaced for testing
  1641. *
  1642. * @private
  1643. */
  1644. Resource._Implementations = {};
  1645. function loadImageElement(url, crossOrigin, deferred) {
  1646. var image = new Image();
  1647. image.onload = function() {
  1648. deferred.resolve(image);
  1649. };
  1650. image.onerror = function(e) {
  1651. deferred.reject(e);
  1652. };
  1653. if (crossOrigin) {
  1654. if (TrustedServers.contains(url)) {
  1655. image.crossOrigin = 'use-credentials';
  1656. } else {
  1657. image.crossOrigin = '';
  1658. }
  1659. }
  1660. image.src = url;
  1661. }
  1662. Resource._Implementations.createImage = function(url, crossOrigin, deferred, flipY, preferImageBitmap) {
  1663. // Passing an Image to createImageBitmap will force it to run on the main thread
  1664. // since DOM elements don't exist on workers. We convert it to a blob so it's non-blocking.
  1665. // See:
  1666. // https://bugzilla.mozilla.org/show_bug.cgi?id=1044102#c38
  1667. // https://bugs.chromium.org/p/chromium/issues/detail?id=580202#c10
  1668. Resource.supportsImageBitmapOptions()
  1669. .then(function(supportsImageBitmap) {
  1670. // We can only use ImageBitmap if we can flip on decode.
  1671. // See: https://github.com/AnalyticalGraphicsInc/cesium/pull/7579#issuecomment-466146898
  1672. if (!(supportsImageBitmap && preferImageBitmap)) {
  1673. loadImageElement(url, crossOrigin, deferred);
  1674. return;
  1675. }
  1676. return Resource.fetchBlob({
  1677. url: url
  1678. })
  1679. .then(function(blob) {
  1680. if (!defined(blob)) {
  1681. deferred.reject(new RuntimeError('Successfully retrieved ' + url + ' but it contained no content.'));
  1682. return;
  1683. }
  1684. return Resource.createImageBitmapFromBlob(blob, {
  1685. flipY: flipY,
  1686. premultiplyAlpha: false
  1687. });
  1688. }).then(deferred.resolve);
  1689. })
  1690. .otherwise(deferred.reject);
  1691. };
  1692. /**
  1693. * Wrapper for createImageBitmap
  1694. *
  1695. * @private
  1696. */
  1697. Resource.createImageBitmapFromBlob = function(blob, options) {
  1698. Check.defined('options', options);
  1699. Check.typeOf.bool('options.flipY', options.flipY);
  1700. Check.typeOf.bool('options.premultiplyAlpha', options.premultiplyAlpha);
  1701. return createImageBitmap(blob, {
  1702. imageOrientation: options.flipY ? 'flipY' : 'none',
  1703. premultiplyAlpha: options.premultiplyAlpha ? 'premultiply' : 'none'
  1704. });
  1705. };
  1706. function decodeResponse(loadWithHttpResponse, responseType) {
  1707. switch (responseType) {
  1708. case 'text':
  1709. return loadWithHttpResponse.toString('utf8');
  1710. case 'json':
  1711. return JSON.parse(loadWithHttpResponse.toString('utf8'));
  1712. default:
  1713. return new Uint8Array(loadWithHttpResponse).buffer;
  1714. }
  1715. }
  1716. function loadWithHttpRequest(url, responseType, method, data, headers, deferred, overrideMimeType) {
  1717. // Specifically use the Node version of require to avoid conflicts with the global
  1718. // require defined in the built version of Cesium.
  1719. var nodeRequire = global.require; // eslint-disable-line
  1720. // Note: only the 'json' and 'text' responseTypes transforms the loaded buffer
  1721. var URL = nodeRequire('url').parse(url);
  1722. var http = URL.protocol === 'https:' ? nodeRequire('https') : nodeRequire('http');
  1723. var zlib = nodeRequire('zlib');
  1724. var options = {
  1725. protocol : URL.protocol,
  1726. hostname : URL.hostname,
  1727. port : URL.port,
  1728. path : URL.path,
  1729. query : URL.query,
  1730. method : method,
  1731. headers : headers
  1732. };
  1733. http.request(options)
  1734. .on('response', function(res) {
  1735. if (res.statusCode < 200 || res.statusCode >= 300) {
  1736. deferred.reject(new RequestErrorEvent(res.statusCode, res, res.headers));
  1737. return;
  1738. }
  1739. var chunkArray = [];
  1740. res.on('data', function(chunk) {
  1741. chunkArray.push(chunk);
  1742. });
  1743. res.on('end', function() {
  1744. var result = Buffer.concat(chunkArray); // eslint-disable-line
  1745. if (res.headers['content-encoding'] === 'gzip') {
  1746. zlib.gunzip(result, function(error, resultUnzipped) {
  1747. if (error) {
  1748. deferred.reject(new RuntimeError('Error decompressing response.'));
  1749. } else {
  1750. deferred.resolve(decodeResponse(resultUnzipped, responseType));
  1751. }
  1752. });
  1753. } else {
  1754. deferred.resolve(decodeResponse(result, responseType));
  1755. }
  1756. });
  1757. }).on('error', function(e) {
  1758. deferred.reject(new RequestErrorEvent());
  1759. }).end();
  1760. }
  1761. var noXMLHttpRequest = typeof XMLHttpRequest === 'undefined';
  1762. Resource._Implementations.loadWithXhr = function(url, responseType, method, data, headers, deferred, overrideMimeType) {
  1763. var dataUriRegexResult = dataUriRegex.exec(url);
  1764. if (dataUriRegexResult !== null) {
  1765. deferred.resolve(decodeDataUri(dataUriRegexResult, responseType));
  1766. return;
  1767. }
  1768. if (noXMLHttpRequest) {
  1769. loadWithHttpRequest(url, responseType, method, data, headers, deferred, overrideMimeType);
  1770. return;
  1771. }
  1772. var xhr = new XMLHttpRequest();
  1773. if (TrustedServers.contains(url)) {
  1774. xhr.withCredentials = true;
  1775. }
  1776. xhr.open(method, url, true);
  1777. if (defined(overrideMimeType) && defined(xhr.overrideMimeType)) {
  1778. xhr.overrideMimeType(overrideMimeType);
  1779. }
  1780. if (defined(headers)) {
  1781. for (var key in headers) {
  1782. if (headers.hasOwnProperty(key)) {
  1783. xhr.setRequestHeader(key, headers[key]);
  1784. }
  1785. }
  1786. }
  1787. if (defined(responseType)) {
  1788. xhr.responseType = responseType;
  1789. }
  1790. // While non-standard, file protocol always returns a status of 0 on success
  1791. var localFile = false;
  1792. if (typeof url === 'string') {
  1793. localFile = (url.indexOf('file://') === 0) || (typeof window !== 'undefined' && window.location.origin === 'file://');
  1794. }
  1795. xhr.onload = function() {
  1796. if ((xhr.status < 200 || xhr.status >= 300) && !(localFile && xhr.status === 0)) {
  1797. deferred.reject(new RequestErrorEvent(xhr.status, xhr.response, xhr.getAllResponseHeaders()));
  1798. return;
  1799. }
  1800. var response = xhr.response;
  1801. var browserResponseType = xhr.responseType;
  1802. if (method === 'HEAD' || method === 'OPTIONS') {
  1803. var responseHeaderString = xhr.getAllResponseHeaders();
  1804. var splitHeaders = responseHeaderString.trim().split(/[\r\n]+/);
  1805. var responseHeaders = {};
  1806. splitHeaders.forEach(function (line) {
  1807. var parts = line.split(': ');
  1808. var header = parts.shift();
  1809. responseHeaders[header] = parts.join(': ');
  1810. });
  1811. deferred.resolve(responseHeaders);
  1812. return;
  1813. }
  1814. //All modern browsers will go into either the first or second if block or last else block.
  1815. //Other code paths support older browsers that either do not support the supplied responseType
  1816. //or do not support the xhr.response property.
  1817. if (xhr.status === 204) {
  1818. // accept no content
  1819. deferred.resolve();
  1820. } else if (defined(response) && (!defined(responseType) || (browserResponseType === responseType))) {
  1821. deferred.resolve(response);
  1822. } else if ((responseType === 'json') && typeof response === 'string') {
  1823. try {
  1824. deferred.resolve(JSON.parse(response));
  1825. } catch (e) {
  1826. deferred.reject(e);
  1827. }
  1828. } else if ((browserResponseType === '' || browserResponseType === 'document') && defined(xhr.responseXML) && xhr.responseXML.hasChildNodes()) {
  1829. deferred.resolve(xhr.responseXML);
  1830. } else if ((browserResponseType === '' || browserResponseType === 'text') && defined(xhr.responseText)) {
  1831. deferred.resolve(xhr.responseText);
  1832. } else {
  1833. deferred.reject(new RuntimeError('Invalid XMLHttpRequest response type.'));
  1834. }
  1835. };
  1836. xhr.onerror = function(e) {
  1837. deferred.reject(new RequestErrorEvent());
  1838. };
  1839. xhr.send(data);
  1840. return xhr;
  1841. };
  1842. Resource._Implementations.loadAndExecuteScript = function(url, functionName, deferred) {
  1843. return loadAndExecuteScript(url, functionName).otherwise(deferred.reject);
  1844. };
  1845. /**
  1846. * The default implementations
  1847. *
  1848. * @private
  1849. */
  1850. Resource._DefaultImplementations = {};
  1851. Resource._DefaultImplementations.createImage = Resource._Implementations.createImage;
  1852. Resource._DefaultImplementations.loadWithXhr = Resource._Implementations.loadWithXhr;
  1853. Resource._DefaultImplementations.loadAndExecuteScript = Resource._Implementations.loadAndExecuteScript;
  1854. /**
  1855. * A resource instance initialized to the current browser location
  1856. *
  1857. * @type {Resource}
  1858. * @constant
  1859. */
  1860. Resource.DEFAULT = freezeObject(new Resource({
  1861. url: (typeof document === 'undefined') ? '' : document.location.href.split('?')[0]
  1862. }));
  1863. /**
  1864. * A function that returns the value of the property.
  1865. * @callback Resource~RetryCallback
  1866. *
  1867. * @param {Resource} [resource] The resource that failed to load.
  1868. * @param {Error} [error] The error that occurred during the loading of the resource.
  1869. * @returns {Boolean|Promise<Boolean>} If true or a promise that resolved to true, the resource will be retried. Otherwise the failure will be returned.
  1870. */
  1871. export default Resource;