Resource.js 80 KB

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