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