Resource.js 84 KB

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