Source: js/hierarchy_manager.js

  1. /* global BaseModule */
  2. 'use strict';
  3. (function() {
  4. var HierarchyManager = function() {};
  5. HierarchyManager.SERVICES = [
  6. 'focus',
  7. 'registerHierarchy',
  8. 'unregisterHierarchy'
  9. ];
  10. HierarchyManager.STATES = [
  11. 'getTopMostWindow',
  12. 'getTopMostUI'
  13. ];
  14. HierarchyManager.EVENTS = [
  15. 'home',
  16. 'holdhome',
  17. 'system-resize',
  18. 'launchactivity',
  19. 'mozChromeEvent',
  20. 'windowopened',
  21. 'windowclosed'
  22. ];
  23. BaseModule.create(HierarchyManager, {
  24. name: 'HierarchyManager',
  25. EVENT_PREFIX: 'hierarchy',
  26. _ui_list: null,
  27. _topMost: null,
  28. DEBUG: false,
  29. _start: function() {
  30. this._ui_list = [];
  31. },
  32. /**
  33. * Provide the top most window information.
  34. * Usually used by integration tests.
  35. * @return {AppWindow|undefined} The top most window instance.
  36. */
  37. getTopMostWindow: function() {
  38. var topMostWindowManager;
  39. this._ui_list.some(function(module) {
  40. if (module.getActiveWindow && module.isActive()) {
  41. topMostWindowManager = module;
  42. return true;
  43. }
  44. }, this);
  45. return topMostWindowManager &&
  46. topMostWindowManager.getActiveWindow() &&
  47. topMostWindowManager.getActiveWindow().getTopMostWindow();
  48. },
  49. getTopMostUI: function() {
  50. this.debug('getting top most...', this._topMost);
  51. return this._topMost;
  52. },
  53. /**
  54. * Predefined priorities per module by module name.
  55. * @type {Array}
  56. */
  57. PRIORITIES: [
  58. 'OverlayWindowManager',
  59. 'CoverScreen',
  60. 'AttentionWindowManager',
  61. 'SecureWindowManager',
  62. 'LockScreenWindowManager',
  63. 'UtilityTray',
  64. 'Rocketbar',
  65. 'SystemDialogManager',
  66. 'AppWindowManager',
  67. 'TaskManager'
  68. ],
  69. _stop: function() {
  70. this._ui_list.forEach(function(module) {
  71. window.removeEventListener(module + 'active', this);
  72. window.removeEventListener(module + 'inactive', this);
  73. }, this);
  74. this._ui_list = [];
  75. },
  76. updateHierarchy: function() {
  77. if (this._ui_list.length === 0) {
  78. this.debug('no any module watching.');
  79. return;
  80. }
  81. var lastTopMost = this._topMost;
  82. this._topMost = null;
  83. var found = this._ui_list.some(function(module) {
  84. if (module.isActive()) {
  85. this.debug(module.name + ' is becoming active now.');
  86. this._topMost = module;
  87. return true;
  88. }
  89. }, this);
  90. if (this._topMost !== lastTopMost) {
  91. if (lastTopMost) {
  92. this.debug('last top most is ' + lastTopMost.name);
  93. } else {
  94. this.debug('last top most is null.');
  95. }
  96. if (found) {
  97. this.debug('next top most is ' + this._topMost.name);
  98. } else {
  99. this.debug('next top most is null.');
  100. }
  101. if (this._topMost && this._topMost.setHierarchy &&
  102. this._topMost.setHierarchy(true)) {
  103. // Blur previous module only when current module is successfully
  104. // focused.
  105. lastTopMost && lastTopMost.setHierarchy &&
  106. lastTopMost.setHierarchy(false);
  107. }
  108. this._topMost && this._topMost.setHierarchy &&
  109. this._topMost.setHierarchy(true);
  110. this.publish('changed');
  111. } else {
  112. this.debug('top most is the same.', this._topMost ?
  113. this._topMost.name : 'NaN');
  114. }
  115. },
  116. dumpHierarchy: function() {
  117. this._ui_list.forEach(function(module, index) {
  118. this.debug(
  119. '[' + index + '] (' +
  120. this.PRIORITIES.indexOf(module.name) +')' +
  121. module.name +
  122. ', active state = ' + module.isActive());
  123. }, this);
  124. },
  125. focus: function(module) {
  126. if (!module) {
  127. this._topMost.setHierarchy(true);
  128. } else if (this._topMost === module) {
  129. module.setHierarchy(true);
  130. }
  131. },
  132. updateTopMostWindow: function() {
  133. var topMostWindow = this.getTopMostWindow();
  134. if (topMostWindow !== this._topMostWindow) {
  135. this._topMostWindow = topMostWindow;
  136. this.publish('topmostwindowchanged');
  137. }
  138. },
  139. handleEvent: function(evt) {
  140. this.debug(evt.type);
  141. switch (evt.type) {
  142. case 'windowopened':
  143. case 'windowclosed':
  144. this.updateTopMostWindow();
  145. break;
  146. case 'mozChromeEvent':
  147. if (!evt.detail ||
  148. evt.detail.type !== 'inputmethod-contextchange') {
  149. break;
  150. }
  151. /* falls through */
  152. case 'home':
  153. case 'holdhome':
  154. case 'launchactivity':
  155. case 'webapps-launch':
  156. case 'system-resize':
  157. this.broadcast(evt);
  158. break;
  159. default:
  160. this.debug('handling ' + evt.type);
  161. this.updateHierarchy();
  162. break;
  163. }
  164. return false;
  165. },
  166. /**
  167. * Broadcast hierarchy based event until it's blocked
  168. * @param {DOMEvent} evt Event to be broadcast
  169. */
  170. broadcast: function(evt) {
  171. this._ui_list.some(function(ui, index) {
  172. // The last one will always catch the event if
  173. // there is nobody block it.
  174. // This rule is for task manager who is inactive but
  175. // needs to catch holdhome event as no one else
  176. // needs this event. If task manager's hierarchy is changed
  177. // we may need to change this rule as well.
  178. if ((ui.isActive() || index === this._ui_list.length - 1) &&
  179. ui.respondToHierarchyEvent) {
  180. // If the module wants to interrupt the event,
  181. // it should return false in the broadcast function.
  182. this.debug('handover ' + evt.type + ' to ' + ui.name);
  183. return (ui.respondToHierarchyEvent(evt) !== true);
  184. }
  185. }, this);
  186. },
  187. /**
  188. * This function is used for any UI module who wants to occupy the hierachy.
  189. */
  190. registerHierarchy: function(module) {
  191. if (!module.isActive) {
  192. return;
  193. }
  194. if (this._ui_list.indexOf(module) >= 0) {
  195. return;
  196. }
  197. this.debug(module.name + ' is registering the hierarchy');
  198. this._ui_list.push(module);
  199. this.sortHierarchy();
  200. window.addEventListener(module.EVENT_PREFIX + '-activating', this);
  201. window.addEventListener(module.EVENT_PREFIX + '-activated', this);
  202. window.addEventListener(module.EVENT_PREFIX + '-deactivating', this);
  203. window.addEventListener(module.EVENT_PREFIX + '-deactivated', this);
  204. this.updateHierarchy();
  205. },
  206. sortHierarchy: function() {
  207. this.debug('before sorting...');
  208. this.dumpHierarchy();
  209. var self = this;
  210. this._ui_list.sort(function(a, b) {
  211. return (self.PRIORITIES.indexOf(b.name) <
  212. self.PRIORITIES.indexOf(a.name));
  213. });
  214. this.debug('after sorting...');
  215. this.dumpHierarchy();
  216. },
  217. /**
  218. * Remove the module reference from the living UI list.
  219. */
  220. unregisterHierarchy: function(module) {
  221. var index = this._ui_list.indexOf(module);
  222. if (index < 0) {
  223. return;
  224. }
  225. this.debug(module.name + ' is unregistering the hierarchy');
  226. var removed = this._ui_list.splice(index, 1);
  227. this.sortHierarchy();
  228. this.debug(removed.name);
  229. window.removeEventListener(module.EVENT_PREFIX + '-activated', this);
  230. window.removeEventListener(module.EVENT_PREFIX + '-deactivated', this);
  231. this.updateHierarchy();
  232. }
  233. });
  234. }());