123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993
  1. import { EventEmitter } from '@angular/core';
  2. import { getCss, isTextInput } from '../util/dom';
  3. import { QueryParams } from './query-params';
  4. import { removeArrayItem } from '../util/util';
  5. /**
  6. * @name Platform
  7. * @description
  8. * The Platform service can be used to get information about your current device.
  9. * You can get all of the platforms associated with the device using the [platforms](#platforms)
  10. * method, including whether the app is being viewed from a tablet, if it's
  11. * on a mobile device or browser, and the exact platform (iOS, Android, etc).
  12. * You can also get the orientation of the device, if it uses right-to-left
  13. * language direction, and much much more. With this information you can completely
  14. * customize your app to fit any device.
  15. *
  16. * @usage
  17. * ```ts
  18. * import { Platform } from 'ionic-angular';
  19. *
  20. * @Component({...})
  21. * export MyPage {
  22. * constructor(public platform: Platform) {
  23. *
  24. * }
  25. * }
  26. * ```
  27. * @demo /docs/demos/src/platform/
  28. */
  29. var Platform = (function () {
  30. function Platform() {
  31. var _this = this;
  32. this._versions = {};
  33. this._qp = new QueryParams();
  34. this._bbActions = [];
  35. this._pW = 0;
  36. this._pH = 0;
  37. this._lW = 0;
  38. this._lH = 0;
  39. this._isPortrait = null;
  40. this._uiEvtOpts = false;
  41. /** @internal */
  42. this._platforms = [];
  43. // Events meant to be triggered by the engine
  44. // **********************************************
  45. /**
  46. * @hidden
  47. */
  48. this.backButton = new EventEmitter();
  49. /**
  50. * The pause event emits when the native platform puts the application
  51. * into the background, typically when the user switches to a different
  52. * application. This event would emit when a Cordova app is put into
  53. * the background, however, it would not fire on a standard web browser.
  54. */
  55. this.pause = new EventEmitter();
  56. /**
  57. * The resume event emits when the native platform pulls the application
  58. * out from the background. This event would emit when a Cordova app comes
  59. * out from the background, however, it would not fire on a standard web browser.
  60. */
  61. this.resume = new EventEmitter();
  62. /**
  63. * The resize event emits when the browser window has changed dimensions. This
  64. * could be from a browser window being physically resized, or from a device
  65. * changing orientation.
  66. */
  67. this.resize = new EventEmitter();
  68. this._readyPromise = new Promise(function (res) { _this._readyResolve = res; });
  69. this.backButton.subscribe(function () {
  70. // the hardware back button event has been fired
  71. (void 0) /* console.debug */;
  72. // decide which backbutton action should run
  73. _this.runBackButtonAction();
  74. });
  75. }
  76. /**
  77. * @hidden
  78. */
  79. Platform.prototype.setWindow = function (win) {
  80. this._win = win;
  81. };
  82. /**
  83. * @hidden
  84. */
  85. Platform.prototype.win = function () {
  86. return this._win;
  87. };
  88. /**
  89. * @hidden
  90. */
  91. Platform.prototype.setDocument = function (doc) {
  92. this._doc = doc;
  93. };
  94. /**
  95. * @hidden
  96. */
  97. Platform.prototype.doc = function () {
  98. return this._doc;
  99. };
  100. /**
  101. * @hidden
  102. */
  103. Platform.prototype.setZone = function (zone) {
  104. this.zone = zone;
  105. };
  106. /**
  107. * @hidden
  108. */
  109. Platform.prototype.setCssProps = function (docElement) {
  110. this.Css = getCss(docElement);
  111. };
  112. // Methods
  113. // **********************************************
  114. /**
  115. * @returns {boolean} returns true/false based on platform.
  116. * @description
  117. * Depending on the platform the user is on, `is(platformName)` will
  118. * return `true` or `false`. Note that the same app can return `true`
  119. * for more than one platform name. For example, an app running from
  120. * an iPad would return `true` for the platform names: `mobile`,
  121. * `ios`, `ipad`, and `tablet`. Additionally, if the app was running
  122. * from Cordova then `cordova` would be true, and if it was running
  123. * from a web browser on the iPad then `mobileweb` would be `true`.
  124. *
  125. * ```
  126. * import { Platform } from 'ionic-angular';
  127. *
  128. * @Component({...})
  129. * export MyPage {
  130. * constructor(public platform: Platform) {
  131. * if (this.platform.is('ios')) {
  132. * // This will only print when on iOS
  133. * console.log('I am an iOS device!');
  134. * }
  135. * }
  136. * }
  137. * ```
  138. *
  139. * | Platform Name | Description |
  140. * |-----------------|------------------------------------|
  141. * | android | on a device running Android. |
  142. * | cordova | on a device running Cordova. |
  143. * | core | on a desktop device. |
  144. * | ios | on a device running iOS. |
  145. * | ipad | on an iPad device. |
  146. * | iphone | on an iPhone device. |
  147. * | mobile | on a mobile device. |
  148. * | mobileweb | in a browser on a mobile device. |
  149. * | phablet | on a phablet device. |
  150. * | tablet | on a tablet device. |
  151. * | windows | on a device running Windows. |
  152. *
  153. * @param {string} platformName
  154. */
  155. Platform.prototype.is = function (platformName) {
  156. return (this._platforms.indexOf(platformName) > -1);
  157. };
  158. /**
  159. * @returns {array} the array of platforms
  160. * @description
  161. * Depending on what device you are on, `platforms` can return multiple values.
  162. * Each possible value is a hierarchy of platforms. For example, on an iPhone,
  163. * it would return `mobile`, `ios`, and `iphone`.
  164. *
  165. * ```
  166. * import { Platform } from 'ionic-angular';
  167. *
  168. * @Component({...})
  169. * export MyPage {
  170. * constructor(public platform: Platform) {
  171. * // This will print an array of the current platforms
  172. * console.log(this.platform.platforms());
  173. * }
  174. * }
  175. * ```
  176. */
  177. Platform.prototype.platforms = function () {
  178. // get the array of active platforms, which also knows the hierarchy,
  179. // with the last one the most important
  180. return this._platforms;
  181. };
  182. /**
  183. * Returns an object containing version information about all of the platforms.
  184. *
  185. * ```
  186. * import { Platform } from 'ionic-angular';
  187. *
  188. * @Component({...})
  189. * export MyPage {
  190. * constructor(public platform: Platform) {
  191. * // This will print an object containing
  192. * // all of the platforms and their versions
  193. * console.log(platform.versions());
  194. * }
  195. * }
  196. * ```
  197. *
  198. * @returns {object} An object containing all of the platforms and their versions.
  199. */
  200. Platform.prototype.versions = function () {
  201. // get all the platforms that have a valid parsed version
  202. return this._versions;
  203. };
  204. /**
  205. * @hidden
  206. */
  207. Platform.prototype.version = function () {
  208. for (var platformName in this._versions) {
  209. if (this._versions[platformName]) {
  210. return this._versions[platformName];
  211. }
  212. }
  213. return {};
  214. };
  215. /**
  216. * Returns a promise when the platform is ready and native functionality
  217. * can be called. If the app is running from within a web browser, then
  218. * the promise will resolve when the DOM is ready. When the app is running
  219. * from an application engine such as Cordova, then the promise will
  220. * resolve when Cordova triggers the `deviceready` event.
  221. *
  222. * The resolved value is the `readySource`, which states which platform
  223. * ready was used. For example, when Cordova is ready, the resolved ready
  224. * source is `cordova`. The default ready source value will be `dom`. The
  225. * `readySource` is useful if different logic should run depending on the
  226. * platform the app is running from. For example, only Cordova can execute
  227. * the status bar plugin, so the web should not run status bar plugin logic.
  228. *
  229. * ```
  230. * import { Component } from '@angular/core';
  231. * import { Platform } from 'ionic-angular';
  232. *
  233. * @Component({...})
  234. * export MyApp {
  235. * constructor(public platform: Platform) {
  236. * this.platform.ready().then((readySource) => {
  237. * console.log('Platform ready from', readySource);
  238. * // Platform now ready, execute any required native code
  239. * });
  240. * }
  241. * }
  242. * ```
  243. * @returns {promise}
  244. */
  245. Platform.prototype.ready = function () {
  246. return this._readyPromise;
  247. };
  248. /**
  249. * @hidden
  250. * This should be triggered by the engine when the platform is
  251. * ready. If there was no custom prepareReady method from the engine,
  252. * such as Cordova or Electron, then it uses the default DOM ready.
  253. */
  254. Platform.prototype.triggerReady = function (readySource) {
  255. var _this = this;
  256. this.zone.run(function () {
  257. _this._readyResolve(readySource);
  258. });
  259. };
  260. /**
  261. * @hidden
  262. * This is the default prepareReady if it's not replaced by an engine,
  263. * such as Cordova or Electron. If there was no custom prepareReady
  264. * method from an engine then it uses the method below, which triggers
  265. * the platform ready on the DOM ready event, and the default resolved
  266. * value is `dom`.
  267. */
  268. Platform.prototype.prepareReady = function () {
  269. var self = this;
  270. if (self._doc.readyState === 'complete' || self._doc.readyState === 'interactive') {
  271. self.triggerReady('dom');
  272. }
  273. else {
  274. self._doc.addEventListener('DOMContentLoaded', completed, false);
  275. self._win.addEventListener('load', completed, false);
  276. }
  277. function completed() {
  278. self._doc.removeEventListener('DOMContentLoaded', completed, false);
  279. self._win.removeEventListener('load', completed, false);
  280. self.triggerReady('dom');
  281. }
  282. };
  283. /**
  284. * Set the app's language direction, which will update the `dir` attribute
  285. * on the app's root `<html>` element. We recommend the app's `index.html`
  286. * file already has the correct `dir` attribute value set, such as
  287. * `<html dir="ltr">` or `<html dir="rtl">`. This method is useful if the
  288. * direction needs to be dynamically changed per user/session.
  289. * [W3C: Structural markup and right-to-left text in HTML](http://www.w3.org/International/questions/qa-html-dir)
  290. * @param {DocumentDirection} dir Examples: `rtl`, `ltr`
  291. * @param {boolean} updateDocument
  292. */
  293. Platform.prototype.setDir = function (dir, updateDocument) {
  294. this._dir = dir;
  295. this.isRTL = (dir === 'rtl');
  296. if (updateDocument !== false) {
  297. this._doc['documentElement'].setAttribute('dir', dir);
  298. }
  299. };
  300. /**
  301. * Returns app's language direction.
  302. * We recommend the app's `index.html` file already has the correct `dir`
  303. * attribute value set, such as `<html dir="ltr">` or `<html dir="rtl">`.
  304. * [W3C: Structural markup and right-to-left text in HTML](http://www.w3.org/International/questions/qa-html-dir)
  305. * @returns {DocumentDirection}
  306. */
  307. Platform.prototype.dir = function () {
  308. return this._dir;
  309. };
  310. /**
  311. * Set the app's language and optionally the country code, which will update
  312. * the `lang` attribute on the app's root `<html>` element.
  313. * We recommend the app's `index.html` file already has the correct `lang`
  314. * attribute value set, such as `<html lang="en">`. This method is useful if
  315. * the language needs to be dynamically changed per user/session.
  316. * [W3C: Declaring language in HTML](http://www.w3.org/International/questions/qa-html-language-declarations)
  317. * @param {string} language Examples: `en-US`, `en-GB`, `ar`, `de`, `zh`, `es-MX`
  318. * @param {boolean} updateDocument Specifies whether the `lang` attribute of `<html>` should be updated
  319. */
  320. Platform.prototype.setLang = function (language, updateDocument) {
  321. this._lang = language;
  322. if (updateDocument !== false) {
  323. this._doc['documentElement'].setAttribute('lang', language);
  324. }
  325. };
  326. /**
  327. * Returns app's language and optional country code.
  328. * We recommend the app's `index.html` file already has the correct `lang`
  329. * attribute value set, such as `<html lang="en">`.
  330. * [W3C: Declaring language in HTML](http://www.w3.org/International/questions/qa-html-language-declarations)
  331. * @returns {string}
  332. */
  333. Platform.prototype.lang = function () {
  334. return this._lang;
  335. };
  336. // Methods meant to be overridden by the engine
  337. // **********************************************
  338. // Provided NOOP methods so they do not error when
  339. // called by engines (the browser)that do not provide them
  340. /**
  341. * @hidden
  342. */
  343. Platform.prototype.exitApp = function () { };
  344. /**
  345. * The back button event is triggered when the user presses the native
  346. * platform's back button, also referred to as the "hardware" back button.
  347. * This event is only used within Cordova apps running on Android and
  348. * Windows platforms. This event is not fired on iOS since iOS doesn't come
  349. * with a hardware back button in the same sense an Android or Windows device
  350. * does.
  351. *
  352. * Registering a hardware back button action and setting a priority allows
  353. * apps to control which action should be called when the hardware back
  354. * button is pressed. This method decides which of the registered back button
  355. * actions has the highest priority and should be called.
  356. *
  357. * @param {Function} fn Called when the back button is pressed,
  358. * if this registered action has the highest priority.
  359. * @param {number} priority Set the priority for this action. Only the highest priority will execute. Defaults to `0`.
  360. * @returns {Function} A function that, when called, will unregister
  361. * the back button action.
  362. */
  363. Platform.prototype.registerBackButtonAction = function (fn, priority) {
  364. var _this = this;
  365. if (priority === void 0) { priority = 0; }
  366. var action = { fn: fn, priority: priority };
  367. this._bbActions.push(action);
  368. // return a function to unregister this back button action
  369. return function () {
  370. removeArrayItem(_this._bbActions, action);
  371. };
  372. };
  373. /**
  374. * @hidden
  375. */
  376. Platform.prototype.runBackButtonAction = function () {
  377. // decide which one back button action should run
  378. var winner = null;
  379. this._bbActions.forEach(function (action) {
  380. if (!winner || action.priority >= winner.priority) {
  381. winner = action;
  382. }
  383. });
  384. // run the winning action if there is one
  385. winner && winner.fn && winner.fn();
  386. };
  387. // Getter/Setter Methods
  388. // **********************************************
  389. /**
  390. * @hidden
  391. */
  392. Platform.prototype.setUserAgent = function (userAgent) {
  393. this._ua = userAgent;
  394. };
  395. /**
  396. * @hidden
  397. */
  398. Platform.prototype.setQueryParams = function (url) {
  399. this._qp.parseUrl(url);
  400. };
  401. /**
  402. * Get the query string parameter
  403. */
  404. Platform.prototype.getQueryParam = function (key) {
  405. return this._qp.get(key);
  406. };
  407. /**
  408. * Get the current url.
  409. */
  410. Platform.prototype.url = function () {
  411. return this._win['location']['href'];
  412. };
  413. /**
  414. * @hidden
  415. */
  416. Platform.prototype.userAgent = function () {
  417. return this._ua || '';
  418. };
  419. /**
  420. * @hidden
  421. */
  422. Platform.prototype.setNavigatorPlatform = function (navigatorPlt) {
  423. this._nPlt = navigatorPlt;
  424. };
  425. /**
  426. * @hidden
  427. */
  428. Platform.prototype.navigatorPlatform = function () {
  429. return this._nPlt || '';
  430. };
  431. /**
  432. * Gets the width of the platform's viewport using `window.innerWidth`.
  433. * Using this method is preferred since the dimension is a cached value,
  434. * which reduces the chance of multiple and expensive DOM reads.
  435. */
  436. Platform.prototype.width = function () {
  437. this._calcDim();
  438. return this._isPortrait ? this._pW : this._lW;
  439. };
  440. /**
  441. * Gets the height of the platform's viewport using `window.innerHeight`.
  442. * Using this method is preferred since the dimension is a cached value,
  443. * which reduces the chance of multiple and expensive DOM reads.
  444. */
  445. Platform.prototype.height = function () {
  446. this._calcDim();
  447. return this._isPortrait ? this._pH : this._lH;
  448. };
  449. /**
  450. * @hidden
  451. */
  452. Platform.prototype.getElementComputedStyle = function (ele, pseudoEle) {
  453. return this._win['getComputedStyle'](ele, pseudoEle);
  454. };
  455. /**
  456. * @hidden
  457. */
  458. Platform.prototype.getElementFromPoint = function (x, y) {
  459. return this._doc['elementFromPoint'](x, y);
  460. };
  461. /**
  462. * @hidden
  463. */
  464. Platform.prototype.getElementBoundingClientRect = function (ele) {
  465. return ele['getBoundingClientRect']();
  466. };
  467. /**
  468. * Returns `true` if the app is in portait mode.
  469. */
  470. Platform.prototype.isPortrait = function () {
  471. this._calcDim();
  472. return this._isPortrait;
  473. };
  474. /**
  475. * Returns `true` if the app is in landscape mode.
  476. */
  477. Platform.prototype.isLandscape = function () {
  478. return !this.isPortrait();
  479. };
  480. Platform.prototype._calcDim = function () {
  481. // we're caching window dimensions so that
  482. // we're not forcing many layouts
  483. // if _isPortrait is null then that means
  484. // the dimensions needs to be looked up again
  485. // this also has to cover an edge case that only
  486. // happens on iOS 10 (not other versions of iOS)
  487. // where window.innerWidth is always bigger than
  488. // window.innerHeight when it is first measured,
  489. // even when the device is in portrait but
  490. // the second time it is measured it is correct.
  491. // Hopefully this check will not be needed in the future
  492. if (this._isPortrait === null || this._isPortrait === false && this._win['innerWidth'] < this._win['innerHeight']) {
  493. var win = this._win;
  494. var innerWidth = win['innerWidth'];
  495. var innerHeight = win['innerHeight'];
  496. // we're keeping track of portrait and landscape dimensions
  497. // separately because the virtual keyboard can really mess
  498. // up accurate values when the keyboard is up
  499. if (win.screen.width > 0 && win.screen.height > 0) {
  500. if (innerWidth < innerHeight) {
  501. // the device is in portrait
  502. // we have to do fancier checking here
  503. // because of the virtual keyboard resizing
  504. // the window
  505. if (this._pW <= innerWidth) {
  506. (void 0) /* console.debug */;
  507. this._isPortrait = true;
  508. this._pW = innerWidth;
  509. }
  510. if (this._pH <= innerHeight) {
  511. (void 0) /* console.debug */;
  512. this._isPortrait = true;
  513. this._pH = innerHeight;
  514. }
  515. }
  516. else {
  517. // the device is in landscape
  518. if (this._lW !== innerWidth) {
  519. (void 0) /* console.debug */;
  520. this._isPortrait = false;
  521. this._lW = innerWidth;
  522. }
  523. if (this._lH !== innerHeight) {
  524. (void 0) /* console.debug */;
  525. this._isPortrait = false;
  526. this._lH = innerHeight;
  527. }
  528. }
  529. }
  530. }
  531. };
  532. /**
  533. * @hidden
  534. * This requestAnimationFrame will NOT be wrapped by zone.
  535. */
  536. Platform.prototype.raf = function (callback) {
  537. var win = this._win;
  538. return win['__zone_symbol__requestAnimationFrame'](callback);
  539. };
  540. /**
  541. * @hidden
  542. */
  543. Platform.prototype.cancelRaf = function (rafId) {
  544. var win = this._win;
  545. return win['__zone_symbol__cancelAnimationFrame'](rafId);
  546. };
  547. /**
  548. * @hidden
  549. * This setTimeout will NOT be wrapped by zone.
  550. */
  551. Platform.prototype.timeout = function (callback, timeout) {
  552. var win = this._win;
  553. return win['__zone_symbol__setTimeout'](callback, timeout);
  554. };
  555. /**
  556. * @hidden
  557. * This setTimeout will NOT be wrapped by zone.
  558. */
  559. Platform.prototype.cancelTimeout = function (timeoutId) {
  560. var win = this._win;
  561. win['__zone_symbol__clearTimeout'](timeoutId);
  562. };
  563. /**
  564. * @hidden
  565. * Built to use modern event listener options, like "passive".
  566. * If options are not supported, then just return a boolean which
  567. * represents "capture". Returns a method to remove the listener.
  568. */
  569. Platform.prototype.registerListener = function (ele, eventName, callback, opts, unregisterListenersCollection) {
  570. // use event listener options when supported
  571. // otherwise it's just a boolean for the "capture" arg
  572. var listenerOpts = this._uiEvtOpts ? {
  573. 'capture': !!opts.capture,
  574. 'passive': !!opts.passive,
  575. } : !!opts.capture;
  576. var unReg;
  577. if (!opts.zone && ele['__zone_symbol__addEventListener']) {
  578. // do not wrap this event in zone and we've verified we can use the raw addEventListener
  579. ele['__zone_symbol__addEventListener'](eventName, callback, listenerOpts);
  580. unReg = function unregisterListener() {
  581. ele['__zone_symbol__removeEventListener'](eventName, callback, listenerOpts);
  582. };
  583. }
  584. else {
  585. // use the native addEventListener, which is wrapped with zone
  586. ele['addEventListener'](eventName, callback, listenerOpts);
  587. unReg = function unregisterListener() {
  588. ele['removeEventListener'](eventName, callback, listenerOpts);
  589. };
  590. }
  591. if (unregisterListenersCollection) {
  592. unregisterListenersCollection.push(unReg);
  593. }
  594. return unReg;
  595. };
  596. /**
  597. * @hidden
  598. */
  599. Platform.prototype.transitionEnd = function (el, callback, zone) {
  600. if (zone === void 0) { zone = true; }
  601. var unRegs = [];
  602. function unregister() {
  603. unRegs.forEach(function (unReg) {
  604. unReg();
  605. });
  606. }
  607. function onTransitionEnd(ev) {
  608. if (el === ev.target) {
  609. unregister();
  610. callback(ev);
  611. }
  612. }
  613. if (el) {
  614. this.registerListener(el, 'webkitTransitionEnd', onTransitionEnd, { zone: zone }, unRegs);
  615. this.registerListener(el, 'transitionend', onTransitionEnd, { zone: zone }, unRegs);
  616. }
  617. return unregister;
  618. };
  619. /**
  620. * @hidden
  621. */
  622. Platform.prototype.windowLoad = function (callback) {
  623. var win = this._win;
  624. var doc = this._doc;
  625. var unreg;
  626. if (doc.readyState === 'complete') {
  627. callback(win, doc);
  628. }
  629. else {
  630. unreg = this.registerListener(win, 'load', function () {
  631. unreg && unreg();
  632. callback(win, doc);
  633. }, { zone: false });
  634. }
  635. };
  636. /**
  637. * @hidden
  638. */
  639. Platform.prototype.isActiveElement = function (ele) {
  640. return !!(ele && (this.getActiveElement() === ele));
  641. };
  642. /**
  643. * @hidden
  644. */
  645. Platform.prototype.getActiveElement = function () {
  646. return this._doc['activeElement'];
  647. };
  648. /**
  649. * @hidden
  650. */
  651. Platform.prototype.hasFocus = function (ele) {
  652. return !!((ele && (this.getActiveElement() === ele)) && (ele.parentElement.querySelector(':focus') === ele));
  653. };
  654. /**
  655. * @hidden
  656. */
  657. Platform.prototype.hasFocusedTextInput = function () {
  658. var ele = this.getActiveElement();
  659. if (isTextInput(ele)) {
  660. return (ele.parentElement.querySelector(':focus') === ele);
  661. }
  662. return false;
  663. };
  664. /**
  665. * @hidden
  666. */
  667. Platform.prototype.focusOutActiveElement = function () {
  668. var activeElement = this.getActiveElement();
  669. activeElement && activeElement.blur && activeElement.blur();
  670. };
  671. Platform.prototype._initEvents = function () {
  672. var _this = this;
  673. // Test via a getter in the options object to see if the passive property is accessed
  674. try {
  675. var opts = Object.defineProperty({}, 'passive', {
  676. get: function () {
  677. _this._uiEvtOpts = true;
  678. }
  679. });
  680. this._win.addEventListener('optsTest', null, opts);
  681. }
  682. catch (e) { }
  683. // add the window resize event listener XXms after
  684. this.timeout(function () {
  685. var timerId;
  686. _this.registerListener(_this._win, 'resize', function () {
  687. clearTimeout(timerId);
  688. timerId = setTimeout(function () {
  689. // setting _isPortrait to null means the
  690. // dimensions will need to be looked up again
  691. if (_this.hasFocusedTextInput() === false) {
  692. _this._isPortrait = null;
  693. }
  694. _this.zone.run(function () { return _this.resize.emit(); });
  695. }, 200);
  696. }, { passive: true, zone: false });
  697. }, 2000);
  698. };
  699. // Platform Registry
  700. // **********************************************
  701. /**
  702. * @hidden
  703. */
  704. Platform.prototype.setPlatformConfigs = function (platformConfigs) {
  705. this._registry = platformConfigs || {};
  706. };
  707. /**
  708. * @hidden
  709. */
  710. Platform.prototype.getPlatformConfig = function (platformName) {
  711. return this._registry[platformName] || {};
  712. };
  713. /**
  714. * @hidden
  715. */
  716. Platform.prototype.registry = function () {
  717. return this._registry;
  718. };
  719. /**
  720. * @hidden
  721. */
  722. Platform.prototype.setDefault = function (platformName) {
  723. this._default = platformName;
  724. };
  725. /**
  726. * @hidden
  727. */
  728. Platform.prototype.testQuery = function (queryValue, queryTestValue) {
  729. var valueSplit = queryValue.toLowerCase().split(';');
  730. return valueSplit.indexOf(queryTestValue) > -1;
  731. };
  732. /**
  733. * @hidden
  734. */
  735. Platform.prototype.testNavigatorPlatform = function (navigatorPlatformExpression) {
  736. var rgx = new RegExp(navigatorPlatformExpression, 'i');
  737. return rgx.test(this._nPlt);
  738. };
  739. /**
  740. * @hidden
  741. */
  742. Platform.prototype.matchUserAgentVersion = function (userAgentExpression) {
  743. if (this._ua && userAgentExpression) {
  744. var val = this._ua.match(userAgentExpression);
  745. if (val) {
  746. return {
  747. major: val[1],
  748. minor: val[2]
  749. };
  750. }
  751. }
  752. };
  753. Platform.prototype.testUserAgent = function (expression) {
  754. if (this._ua) {
  755. return this._ua.indexOf(expression) >= 0;
  756. }
  757. return false;
  758. };
  759. /**
  760. * @hidden
  761. */
  762. Platform.prototype.isPlatformMatch = function (queryStringName, userAgentAtLeastHas, userAgentMustNotHave) {
  763. if (userAgentMustNotHave === void 0) { userAgentMustNotHave = []; }
  764. var queryValue = this._qp.get('ionicplatform');
  765. if (queryValue) {
  766. return this.testQuery(queryValue, queryStringName);
  767. }
  768. userAgentAtLeastHas = userAgentAtLeastHas || [queryStringName];
  769. var userAgent = this._ua.toLowerCase();
  770. for (var i = 0; i < userAgentAtLeastHas.length; i++) {
  771. if (userAgent.indexOf(userAgentAtLeastHas[i]) > -1) {
  772. for (var j = 0; j < userAgentMustNotHave.length; j++) {
  773. if (userAgent.indexOf(userAgentMustNotHave[j]) > -1) {
  774. return false;
  775. }
  776. }
  777. return true;
  778. }
  779. }
  780. return false;
  781. };
  782. /** @hidden */
  783. Platform.prototype.init = function () {
  784. this._initEvents();
  785. var rootPlatformNode;
  786. var enginePlatformNode;
  787. // figure out the most specific platform and active engine
  788. var tmpPlt;
  789. for (var platformName in this._registry) {
  790. tmpPlt = this.matchPlatform(platformName);
  791. if (tmpPlt) {
  792. // we found a platform match!
  793. // check if its more specific than the one we already have
  794. if (tmpPlt.isEngine) {
  795. // because it matched then this should be the active engine
  796. // you cannot have more than one active engine
  797. enginePlatformNode = tmpPlt;
  798. }
  799. else if (!rootPlatformNode || tmpPlt.depth > rootPlatformNode.depth) {
  800. // only find the root node for platforms that are not engines
  801. // set this node as the root since we either don't already
  802. // have one, or this one is more specific that the current one
  803. rootPlatformNode = tmpPlt;
  804. }
  805. }
  806. }
  807. if (!rootPlatformNode) {
  808. rootPlatformNode = new PlatformNode(this._registry, this._default);
  809. }
  810. // build a Platform instance filled with the
  811. // hierarchy of active platforms and settings
  812. if (rootPlatformNode) {
  813. // check if we found an engine node (cordova/node-webkit/etc)
  814. if (enginePlatformNode) {
  815. // add the engine to the first in the platform hierarchy
  816. // the original rootPlatformNode now becomes a child
  817. // of the engineNode, which is not the new root
  818. enginePlatformNode.child = rootPlatformNode;
  819. rootPlatformNode.parent = enginePlatformNode;
  820. rootPlatformNode = enginePlatformNode;
  821. }
  822. var platformNode = rootPlatformNode;
  823. while (platformNode) {
  824. insertSuperset(this._registry, platformNode);
  825. platformNode = platformNode.child;
  826. }
  827. // make sure the root noot is actually the root
  828. // incase a node was inserted before the root
  829. platformNode = rootPlatformNode.parent;
  830. while (platformNode) {
  831. rootPlatformNode = platformNode;
  832. platformNode = platformNode.parent;
  833. }
  834. platformNode = rootPlatformNode;
  835. while (platformNode) {
  836. platformNode.initialize(this);
  837. // extra check for ipad pro issue
  838. // https://forums.developer.apple.com/thread/25948
  839. if (platformNode.name === 'iphone' && this.navigatorPlatform() === 'iPad') {
  840. // this is an ipad pro so push ipad and tablet to platforms
  841. // and then return as we are done
  842. this._platforms.push('tablet');
  843. this._platforms.push('ipad');
  844. return;
  845. }
  846. // set the array of active platforms with
  847. // the last one in the array the most important
  848. this._platforms.push(platformNode.name);
  849. // get the platforms version if a version parser was provided
  850. this._versions[platformNode.name] = platformNode.version(this);
  851. // go to the next platform child
  852. platformNode = platformNode.child;
  853. }
  854. }
  855. if (this._platforms.indexOf('mobile') > -1 && this._platforms.indexOf('cordova') === -1) {
  856. this._platforms.push('mobileweb');
  857. }
  858. };
  859. /**
  860. * @hidden
  861. */
  862. Platform.prototype.matchPlatform = function (platformName) {
  863. // build a PlatformNode and assign config data to it
  864. // use it's getRoot method to build up its hierarchy
  865. // depending on which platforms match
  866. var platformNode = new PlatformNode(this._registry, platformName);
  867. var rootNode = platformNode.getRoot(this);
  868. if (rootNode) {
  869. rootNode.depth = 0;
  870. var childPlatform = rootNode.child;
  871. while (childPlatform) {
  872. rootNode.depth++;
  873. childPlatform = childPlatform.child;
  874. }
  875. }
  876. return rootNode;
  877. };
  878. return Platform;
  879. }());
  880. export { Platform };
  881. function insertSuperset(registry, platformNode) {
  882. var supersetPlaformName = platformNode.superset();
  883. if (supersetPlaformName) {
  884. // add a platform in between two exist platforms
  885. // so we can build the correct hierarchy of active platforms
  886. var supersetPlatform = new PlatformNode(registry, supersetPlaformName);
  887. supersetPlatform.parent = platformNode.parent;
  888. supersetPlatform.child = platformNode;
  889. if (supersetPlatform.parent) {
  890. supersetPlatform.parent.child = supersetPlatform;
  891. }
  892. platformNode.parent = supersetPlatform;
  893. }
  894. }
  895. /**
  896. * @hidden
  897. */
  898. var PlatformNode = (function () {
  899. function PlatformNode(registry, platformName) {
  900. this.registry = registry;
  901. this.c = registry[platformName];
  902. this.name = platformName;
  903. this.isEngine = this.c.isEngine;
  904. }
  905. PlatformNode.prototype.settings = function () {
  906. return this.c.settings || {};
  907. };
  908. PlatformNode.prototype.superset = function () {
  909. return this.c.superset;
  910. };
  911. PlatformNode.prototype.isMatch = function (p) {
  912. return this.c.isMatch && this.c.isMatch(p) || false;
  913. };
  914. PlatformNode.prototype.initialize = function (plt) {
  915. this.c.initialize && this.c.initialize(plt);
  916. };
  917. PlatformNode.prototype.version = function (plt) {
  918. if (this.c.versionParser) {
  919. var v = this.c.versionParser(plt);
  920. if (v) {
  921. var str = v.major + '.' + v.minor;
  922. return {
  923. str: str,
  924. num: parseFloat(str),
  925. major: parseInt(v.major, 10),
  926. minor: parseInt(v.minor, 10)
  927. };
  928. }
  929. }
  930. };
  931. PlatformNode.prototype.getRoot = function (plt) {
  932. if (this.isMatch(plt)) {
  933. var parents = this.getSubsetParents(this.name);
  934. if (!parents.length) {
  935. return this;
  936. }
  937. var platformNode = null;
  938. var rootPlatformNode = null;
  939. for (var i = 0; i < parents.length; i++) {
  940. platformNode = new PlatformNode(this.registry, parents[i]);
  941. platformNode.child = this;
  942. rootPlatformNode = platformNode.getRoot(plt);
  943. if (rootPlatformNode) {
  944. this.parent = platformNode;
  945. return rootPlatformNode;
  946. }
  947. }
  948. }
  949. return null;
  950. };
  951. PlatformNode.prototype.getSubsetParents = function (subsetPlatformName) {
  952. var parentPlatformNames = [];
  953. var pltConfig = null;
  954. for (var platformName in this.registry) {
  955. pltConfig = this.registry[platformName];
  956. if (pltConfig.subsets && pltConfig.subsets.indexOf(subsetPlatformName) > -1) {
  957. parentPlatformNames.push(platformName);
  958. }
  959. }
  960. return parentPlatformNames;
  961. };
  962. return PlatformNode;
  963. }());
  964. /**
  965. * @hidden
  966. */
  967. export function setupPlatform(doc, platformConfigs, zone) {
  968. var plt = new Platform();
  969. plt.setDefault('core');
  970. plt.setPlatformConfigs(platformConfigs);
  971. plt.setZone(zone);
  972. // set values from "document"
  973. var docElement = doc.documentElement;
  974. plt.setDocument(doc);
  975. var dir = docElement.dir;
  976. plt.setDir(dir === 'rtl' ? 'rtl' : 'ltr', !dir);
  977. plt.setLang(docElement.lang, false);
  978. // set css properties
  979. plt.setCssProps(docElement);
  980. // set values from "window"
  981. var win = doc.defaultView;
  982. plt.setWindow(win);
  983. plt.setNavigatorPlatform(win.navigator.platform);
  984. plt.setUserAgent(win.navigator.userAgent);
  985. // set location values
  986. plt.setQueryParams(win.location.href);
  987. plt.init();
  988. // add the platform obj to the window
  989. win['Ionic'] = win['Ionic'] || {};
  990. win['Ionic']['platform'] = plt;
  991. return plt;
  992. }
  993. //# sourceMappingURL=platform.js.map