page.js 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. #ifdef 0
  2. /* This Source Code Form is subject to the terms of the Mozilla Public
  3. * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  4. * You can obtain one at http://mozilla.org/MPL/2.0/. */
  5. #endif
  6. // The amount of time we wait while coalescing updates for hidden pages.
  7. const SCHEDULE_UPDATE_TIMEOUT_MS = 1000;
  8. /**
  9. * This singleton represents the whole 'New Tab Page' and takes care of
  10. * initializing all its components.
  11. */
  12. var gPage = {
  13. /**
  14. * Initializes the page.
  15. */
  16. init: function() {
  17. // Add ourselves to the list of pages to receive notifications.
  18. gAllPages.register(this);
  19. // Listen for 'unload' to unregister this page.
  20. addEventListener("unload", this, false);
  21. // Listen for toggle button clicks.
  22. let button = document.getElementById("newtab-toggle");
  23. button.addEventListener("click", e => this.toggleEnabled(e));
  24. // XXX bug 991111 - Not all click events are correctly triggered when
  25. // listening from xhtml nodes -- in particular middle clicks on sites, so
  26. // listen from the xul window and filter then delegate
  27. addEventListener("click", this, false);
  28. // Check if the new tab feature is enabled.
  29. let enabled = gAllPages.enabled;
  30. if (enabled)
  31. this._init();
  32. this._updateAttributes(enabled);
  33. },
  34. /**
  35. * Listens for notifications specific to this page.
  36. */
  37. observe: function(aSubject, aTopic, aData) {
  38. if (aTopic == "nsPref:changed") {
  39. let enabled = gAllPages.enabled;
  40. this._updateAttributes(enabled);
  41. // Initialize the whole page if we haven't done that, yet.
  42. if (enabled) {
  43. this._init();
  44. } else {
  45. gUndoDialog.hide();
  46. }
  47. } else if (aTopic == "page-thumbnail:create" && gGrid.ready) {
  48. for (let site of gGrid.sites) {
  49. if (site && site.url === aData) {
  50. site.refreshThumbnail();
  51. }
  52. }
  53. }
  54. },
  55. /**
  56. * Updates the page's grid right away for visible pages. If the page is
  57. * currently hidden, i.e. in a background tab or in the preloader, then we
  58. * batch multiple update requests and refresh the grid once after a short
  59. * delay. Accepts a single parameter the specifies the reason for requesting
  60. * a page update. The page may decide to delay or prevent a requested updated
  61. * based on the given reason.
  62. */
  63. update(reason = "") {
  64. // Update immediately if we're visible.
  65. if (!document.hidden) {
  66. // Ignore updates where reason=links-changed as those signal that the
  67. // provider's set of links changed. We don't want to update visible pages
  68. // in that case, it is ok to wait until the user opens the next tab.
  69. if (reason != "links-changed" && gGrid.ready) {
  70. gGrid.refresh();
  71. }
  72. return;
  73. }
  74. // Bail out if we scheduled before.
  75. if (this._scheduleUpdateTimeout) {
  76. return;
  77. }
  78. this._scheduleUpdateTimeout = setTimeout(() => {
  79. // Refresh if the grid is ready.
  80. if (gGrid.ready) {
  81. gGrid.refresh();
  82. }
  83. this._scheduleUpdateTimeout = null;
  84. }, SCHEDULE_UPDATE_TIMEOUT_MS);
  85. },
  86. /**
  87. * Internally initializes the page. This runs only when/if the feature
  88. * is/gets enabled.
  89. */
  90. _init: function() {
  91. if (this._initialized)
  92. return;
  93. this._initialized = true;
  94. // XXX: This comment makes no sense for what it does. There is no HC check,
  95. // and it changes the button unconditionally to a "play" button, which is
  96. // wrong for a search action. Commented out for now.
  97. // Set submit button label for when CSS background are disabled (e.g.
  98. // high contrast mode).
  99. //document.getElementById("searchSubmit").value =
  100. // document.body.getAttribute("dir") == "ltr" ? "\u25B6" : "\u25C0";
  101. if (document.hidden) {
  102. addEventListener("visibilitychange", this);
  103. } else {
  104. setTimeout(() => this.onPageFirstVisible());
  105. }
  106. // Initialize and render the grid.
  107. gGrid.init();
  108. // Initialize the drop target shim.
  109. gDropTargetShim.init();
  110. },
  111. /**
  112. * Updates the 'page-disabled' attributes of the respective DOM nodes.
  113. * @param aValue Whether the New Tab Page is enabled or not.
  114. */
  115. _updateAttributes: function(aValue) {
  116. // Set the nodes' states.
  117. let nodeSelector = "#newtab-grid, #searchContainer";
  118. for (let node of document.querySelectorAll(nodeSelector)) {
  119. if (aValue)
  120. node.removeAttribute("page-disabled");
  121. else
  122. node.setAttribute("page-disabled", "true");
  123. }
  124. // Enables/disables the control and link elements.
  125. let inputSelector = ".newtab-control, .newtab-link";
  126. for (let input of document.querySelectorAll(inputSelector)) {
  127. if (aValue)
  128. input.removeAttribute("tabindex");
  129. else
  130. input.setAttribute("tabindex", "-1");
  131. }
  132. },
  133. /**
  134. * Handles unload event
  135. */
  136. _handleUnloadEvent: function() {
  137. gAllPages.unregister(this);
  138. },
  139. /**
  140. * Handles all page events.
  141. */
  142. handleEvent: function(aEvent) {
  143. switch (aEvent.type) {
  144. case "load":
  145. this.onPageVisibleAndLoaded();
  146. break;
  147. case "unload":
  148. this._handleUnloadEvent();
  149. break;
  150. case "click":
  151. let {button, target} = aEvent;
  152. // Go up ancestors until we find a Site or not
  153. while (target) {
  154. if (target.hasOwnProperty("_newtabSite")) {
  155. target._newtabSite.onClick(aEvent);
  156. break;
  157. }
  158. target = target.parentNode;
  159. }
  160. break;
  161. case "dragover":
  162. if (gDrag.isValid(aEvent) && gDrag.draggedSite)
  163. aEvent.preventDefault();
  164. break;
  165. case "drop":
  166. if (gDrag.isValid(aEvent) && gDrag.draggedSite) {
  167. aEvent.preventDefault();
  168. aEvent.stopPropagation();
  169. }
  170. break;
  171. case "visibilitychange":
  172. // Cancel any delayed updates for hidden pages now that we're visible.
  173. if (this._scheduleUpdateTimeout) {
  174. clearTimeout(this._scheduleUpdateTimeout);
  175. this._scheduleUpdateTimeout = null;
  176. // An update was pending so force an update now.
  177. this.update();
  178. }
  179. setTimeout(() => this.onPageFirstVisible());
  180. removeEventListener("visibilitychange", this);
  181. break;
  182. }
  183. },
  184. onPageFirstVisible: function() {
  185. // Record another page impression.
  186. Services.telemetry.getHistogramById("NEWTAB_PAGE_SHOWN").add(true);
  187. for (let site of gGrid.sites) {
  188. if (site) {
  189. // The site may need to modify and/or re-render itself if
  190. // something changed after newtab was created by preloader.
  191. // For example, the suggested tile endTime may have passed.
  192. site.onFirstVisible();
  193. }
  194. }
  195. // save timestamp to compute page life-span delta
  196. this._firstVisibleTime = Date.now();
  197. if (document.readyState == "complete") {
  198. this.onPageVisibleAndLoaded();
  199. } else {
  200. addEventListener("load", this);
  201. }
  202. },
  203. onPageVisibleAndLoaded() {
  204. },
  205. toggleEnabled: function(aEvent) {
  206. gAllPages.enabled = !gAllPages.enabled;
  207. aEvent.stopPropagation();
  208. }
  209. };