OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 cr.define('options', function() { | |
6 ///////////////////////////////////////////////////////////////////////////// | |
7 // OptionsPage class: | |
8 | |
9 /** | |
10 * Base class for options page. | |
11 * @constructor | |
12 * @param {string} name Options page name. | |
13 * @param {string} title Options page title, used for history. | |
14 * @extends {EventTarget} | |
15 */ | |
16 function OptionsPage(name, title, pageDivName) { | |
17 this.name = name; | |
18 this.title = title; | |
19 this.pageDivName = pageDivName; | |
20 this.pageDiv = $(this.pageDivName); | |
21 this.tab = null; | |
22 this.lastFocusedElement = null; | |
23 } | |
24 | |
25 /** @const */ var HORIZONTAL_OFFSET = 155; | |
26 | |
27 /** | |
28 * This is the absolute difference maintained between standard and | |
29 * fixed-width font sizes. Refer http://crbug.com/91922. | |
30 */ | |
31 OptionsPage.SIZE_DIFFERENCE_FIXED_STANDARD = 3; | |
32 | |
33 /** | |
34 * Main level option pages. Maps lower-case page names to the respective page | |
35 * object. | |
36 * @protected | |
37 */ | |
38 OptionsPage.registeredPages = {}; | |
39 | |
40 /** | |
41 * Pages which are meant to behave like modal dialogs. Maps lower-case overlay | |
42 * names to the respective overlay object. | |
43 * @protected | |
44 */ | |
45 OptionsPage.registeredOverlayPages = {}; | |
46 | |
47 /** | |
48 * Gets the default page (to be shown on initial load). | |
49 */ | |
50 OptionsPage.getDefaultPage = function() { | |
51 return BrowserOptions.getInstance(); | |
52 }; | |
53 | |
54 /** | |
55 * Shows the default page. | |
56 */ | |
57 OptionsPage.showDefaultPage = function() { | |
58 this.navigateToPage(this.getDefaultPage().name); | |
59 }; | |
60 | |
61 /** | |
62 * "Navigates" to a page, meaning that the page will be shown and the | |
63 * appropriate entry is placed in the history. | |
64 * @param {string} pageName Page name. | |
65 */ | |
66 OptionsPage.navigateToPage = function(pageName) { | |
67 this.showPageByName(pageName, true); | |
68 }; | |
69 | |
70 /** | |
71 * Shows a registered page. This handles both top-level and overlay pages. | |
72 * @param {string} pageName Page name. | |
73 * @param {boolean} updateHistory True if we should update the history after | |
74 * showing the page. | |
75 * @param {Object=} opt_propertyBag An optional bag of properties including | |
76 * replaceState (if history state should be replaced instead of pushed). | |
77 * @private | |
78 */ | |
79 OptionsPage.showPageByName = function(pageName, | |
80 updateHistory, | |
81 opt_propertyBag) { | |
82 // If |opt_propertyBag| is non-truthy, homogenize to object. | |
83 opt_propertyBag = opt_propertyBag || {}; | |
84 | |
85 // Find the currently visible root-level page. | |
86 var rootPage = null; | |
87 for (var name in this.registeredPages) { | |
88 var page = this.registeredPages[name]; | |
89 if (page.visible && !page.parentPage) { | |
90 rootPage = page; | |
91 break; | |
92 } | |
93 } | |
94 | |
95 // Find the target page. | |
96 var targetPage = this.registeredPages[pageName.toLowerCase()]; | |
97 if (!targetPage || !targetPage.canShowPage()) { | |
98 // If it's not a page, try it as an overlay. | |
99 if (!targetPage && this.showOverlay_(pageName, rootPage)) { | |
100 if (updateHistory) | |
101 this.updateHistoryState_(!!opt_propertyBag.replaceState); | |
102 return; | |
103 } else { | |
104 targetPage = this.getDefaultPage(); | |
105 } | |
106 } | |
107 | |
108 pageName = targetPage.name.toLowerCase(); | |
109 var targetPageWasVisible = targetPage.visible; | |
110 | |
111 // Determine if the root page is 'sticky', meaning that it | |
112 // shouldn't change when showing an overlay. This can happen for special | |
113 // pages like Search. | |
114 var isRootPageLocked = | |
115 rootPage && rootPage.sticky && targetPage.parentPage; | |
116 | |
117 var allPageNames = Array.prototype.concat.call( | |
118 Object.keys(this.registeredPages), | |
119 Object.keys(this.registeredOverlayPages)); | |
120 | |
121 // Notify pages if they will be hidden. | |
122 for (var i = 0; i < allPageNames.length; ++i) { | |
123 var name = allPageNames[i]; | |
124 var page = this.registeredPages[name] || | |
125 this.registeredOverlayPages[name]; | |
126 if (!page.parentPage && isRootPageLocked) | |
127 continue; | |
128 if (page.willHidePage && name != pageName && | |
129 !page.isAncestorOfPage(targetPage)) { | |
130 page.willHidePage(); | |
131 } | |
132 } | |
133 | |
134 // Update visibilities to show only the hierarchy of the target page. | |
135 for (var i = 0; i < allPageNames.length; ++i) { | |
136 var name = allPageNames[i]; | |
137 var page = this.registeredPages[name] || | |
138 this.registeredOverlayPages[name]; | |
139 if (!page.parentPage && isRootPageLocked) | |
140 continue; | |
141 page.visible = name == pageName || page.isAncestorOfPage(targetPage); | |
142 } | |
143 | |
144 // Update the history and current location. | |
145 if (updateHistory) | |
146 this.updateHistoryState_(!!opt_propertyBag.replaceState); | |
147 | |
148 // Update tab title. | |
149 this.setTitle_(targetPage.title); | |
150 | |
151 // Notify pages if they were shown. | |
152 for (var i = 0; i < allPageNames.length; ++i) { | |
153 var name = allPageNames[i]; | |
154 var page = this.registeredPages[name] || | |
155 this.registeredOverlayPages[name]; | |
156 if (!page.parentPage && isRootPageLocked) | |
157 continue; | |
158 if (!targetPageWasVisible && page.didShowPage && | |
159 (name == pageName || page.isAncestorOfPage(targetPage))) { | |
160 page.didShowPage(); | |
161 } | |
162 } | |
163 }; | |
164 | |
165 /** | |
166 * Sets the title of the page. This is accomplished by calling into the | |
167 * parent page API. | |
168 * @param {String} title The title string. | |
169 * @private | |
170 */ | |
171 OptionsPage.setTitle_ = function(title) { | |
172 uber.invokeMethodOnParent('setTitle', {title: title}); | |
173 }; | |
174 | |
175 /** | |
176 * Scrolls the page to the correct position (the top when opening an overlay, | |
177 * or the old scroll position a previously hidden overlay becomes visible). | |
178 * @private | |
179 */ | |
180 OptionsPage.updateScrollPosition_ = function() { | |
181 var container = $('page-container'); | |
182 var scrollTop = container.oldScrollTop || 0; | |
183 container.oldScrollTop = undefined; | |
184 window.scroll(document.body.scrollLeft, scrollTop); | |
185 }; | |
186 | |
187 /** | |
188 * Pushes the current page onto the history stack, overriding the last page | |
189 * if it is the generic chrome://settings/. | |
190 * @param {boolean} replace If true, allow no history events to be created. | |
191 * @param {object=} opt_params A bag of optional params, including: | |
192 * {boolean} ignoreHash Whether to include the hash or not. | |
193 * @private | |
194 */ | |
195 OptionsPage.updateHistoryState_ = function(replace, opt_params) { | |
196 var page = this.getTopmostVisiblePage(); | |
197 var path = window.location.pathname + window.location.hash; | |
198 if (path) | |
199 path = path.slice(1).replace(/\/(?:#|$)/, ''); // Remove trailing slash. | |
200 // The page is already in history (the user may have clicked the same link | |
201 // twice). Do nothing. | |
202 if (path == page.name && | |
203 !document.documentElement.classList.contains('loading')) { | |
204 return; | |
205 } | |
206 | |
207 var hash = opt_params && opt_params.ignoreHash ? '' : window.location.hash; | |
208 | |
209 // If settings are embedded, tell the outer page to set its "path" to the | |
210 // inner frame's path. | |
211 var outerPath = (page == this.getDefaultPage() ? '' : page.name) + hash; | |
212 uber.invokeMethodOnParent('setPath', {path: outerPath}); | |
213 | |
214 // If there is no path, the current location is chrome://settings/. | |
215 // Override this with the new page. | |
216 var historyFunction = path && !replace ? window.history.pushState : | |
217 window.history.replaceState; | |
218 historyFunction.call(window.history, | |
219 {pageName: page.name}, | |
220 page.title, | |
221 '/' + page.name + hash); | |
222 | |
223 // Update tab title. | |
224 this.setTitle_(page.title); | |
225 }; | |
226 | |
227 /** | |
228 * Shows a registered Overlay page. Does not update history. | |
229 * @param {string} overlayName Page name. | |
230 * @param {OptionPage} rootPage The currently visible root-level page. | |
231 * @return {boolean} whether we showed an overlay. | |
232 */ | |
233 OptionsPage.showOverlay_ = function(overlayName, rootPage) { | |
234 var overlay = this.registeredOverlayPages[overlayName.toLowerCase()]; | |
235 if (!overlay || !overlay.canShowPage()) | |
236 return false; | |
237 | |
238 // Save the currently focused element in the page for restoration later. | |
239 var currentPage = this.getTopmostVisiblePage(); | |
240 if (currentPage) | |
241 currentPage.lastFocusedElement = document.activeElement; | |
242 | |
243 if ((!rootPage || !rootPage.sticky) && overlay.parentPage) | |
244 this.showPageByName(overlay.parentPage.name, false); | |
245 | |
246 if (!overlay.visible) { | |
247 overlay.visible = true; | |
248 if (overlay.didShowPage) overlay.didShowPage(); | |
249 } | |
250 | |
251 // Update tab title. | |
252 this.setTitle_(overlay.title); | |
253 | |
254 $('searchBox').setAttribute('aria-hidden', true); | |
255 | |
256 return true; | |
257 }; | |
258 | |
259 /** | |
260 * Returns whether or not an overlay is visible. | |
261 * @return {boolean} True if an overlay is visible. | |
262 * @private | |
263 */ | |
264 OptionsPage.isOverlayVisible_ = function() { | |
265 return this.getVisibleOverlay_() != null; | |
266 }; | |
267 | |
268 /** | |
269 * Returns the currently visible overlay, or null if no page is visible. | |
270 * @return {OptionPage} The visible overlay. | |
271 */ | |
272 OptionsPage.getVisibleOverlay_ = function() { | |
273 var topmostPage = null; | |
274 for (var name in this.registeredOverlayPages) { | |
275 var page = this.registeredOverlayPages[name]; | |
276 if (page.visible && | |
277 (!topmostPage || page.nestingLevel > topmostPage.nestingLevel)) { | |
278 topmostPage = page; | |
279 } | |
280 } | |
281 return topmostPage; | |
282 }; | |
283 | |
284 /** | |
285 * Restores the last focused element on a given page. | |
286 */ | |
287 OptionsPage.restoreLastFocusedElement_ = function() { | |
288 var currentPage = this.getTopmostVisiblePage(); | |
289 if (currentPage.lastFocusedElement) | |
290 currentPage.lastFocusedElement.focus(); | |
291 }; | |
292 | |
293 /** | |
294 * Closes the visible overlay. Updates the history state after closing the | |
295 * overlay. | |
296 */ | |
297 OptionsPage.closeOverlay = function() { | |
298 var overlay = this.getVisibleOverlay_(); | |
299 if (!overlay) | |
300 return; | |
301 | |
302 overlay.visible = false; | |
303 | |
304 if (overlay.didClosePage) overlay.didClosePage(); | |
305 this.updateHistoryState_(false, {ignoreHash: true}); | |
306 | |
307 this.restoreLastFocusedElement_(); | |
308 if (!this.isOverlayVisible_()) | |
309 $('searchBox').removeAttribute('aria-hidden'); | |
310 }; | |
311 | |
312 /** | |
313 * Cancels (closes) the overlay, due to the user pressing <Esc>. | |
314 */ | |
315 OptionsPage.cancelOverlay = function() { | |
316 // Blur the active element to ensure any changed pref value is saved. | |
317 document.activeElement.blur(); | |
318 var overlay = this.getVisibleOverlay_(); | |
319 // Let the overlay handle the <Esc> if it wants to. | |
320 if (overlay.handleCancel) { | |
321 overlay.handleCancel(); | |
322 this.restoreLastFocusedElement_(); | |
323 } else { | |
324 this.closeOverlay(); | |
325 } | |
326 }; | |
327 | |
328 /** | |
329 * Hides the visible overlay. Does not affect the history state. | |
330 * @private | |
331 */ | |
332 OptionsPage.hideOverlay_ = function() { | |
333 var overlay = this.getVisibleOverlay_(); | |
334 if (overlay) | |
335 overlay.visible = false; | |
336 }; | |
337 | |
338 /** | |
339 * Returns the pages which are currently visible, ordered by nesting level | |
340 * (ascending). | |
341 * @return {Array.OptionPage} The pages which are currently visible, ordered | |
342 * by nesting level (ascending). | |
343 */ | |
344 OptionsPage.getVisiblePages_ = function() { | |
345 var visiblePages = []; | |
346 for (var name in this.registeredPages) { | |
347 var page = this.registeredPages[name]; | |
348 if (page.visible) | |
349 visiblePages[page.nestingLevel] = page; | |
350 } | |
351 return visiblePages; | |
352 }; | |
353 | |
354 /** | |
355 * Returns the topmost visible page (overlays excluded). | |
356 * @return {OptionPage} The topmost visible page aside any overlay. | |
357 * @private | |
358 */ | |
359 OptionsPage.getTopmostVisibleNonOverlayPage_ = function() { | |
360 var topPage = null; | |
361 for (var name in this.registeredPages) { | |
362 var page = this.registeredPages[name]; | |
363 if (page.visible && | |
364 (!topPage || page.nestingLevel > topPage.nestingLevel)) | |
365 topPage = page; | |
366 } | |
367 | |
368 return topPage; | |
369 }; | |
370 | |
371 /** | |
372 * Returns the topmost visible page, or null if no page is visible. | |
373 * @return {OptionPage} The topmost visible page. | |
374 */ | |
375 OptionsPage.getTopmostVisiblePage = function() { | |
376 // Check overlays first since they're top-most if visible. | |
377 return this.getVisibleOverlay_() || this.getTopmostVisibleNonOverlayPage_(); | |
378 }; | |
379 | |
380 /** | |
381 * Updates managed banner visibility state based on the topmost page. | |
382 */ | |
383 OptionsPage.updateManagedBannerVisibility = function() { | |
384 var topPage = this.getTopmostVisiblePage(); | |
385 if (topPage) | |
386 topPage.updateManagedBannerVisibility(); | |
387 }; | |
388 | |
389 /** | |
390 * Shows the tab contents for the given navigation tab. | |
391 * @param {!Element} tab The tab that the user clicked. | |
392 */ | |
393 OptionsPage.showTab = function(tab) { | |
394 // Search parents until we find a tab, or the nav bar itself. This allows | |
395 // tabs to have child nodes, e.g. labels in separately-styled spans. | |
396 while (tab && !tab.classList.contains('subpages-nav-tabs') && | |
397 !tab.classList.contains('tab')) { | |
398 tab = tab.parentNode; | |
399 } | |
400 if (!tab || !tab.classList.contains('tab')) | |
401 return; | |
402 | |
403 // Find tab bar of the tab. | |
404 var tabBar = tab; | |
405 while (tabBar && !tabBar.classList.contains('subpages-nav-tabs')) { | |
406 tabBar = tabBar.parentNode; | |
407 } | |
408 if (!tabBar) | |
409 return; | |
410 | |
411 if (tabBar.activeNavTab != null) { | |
412 tabBar.activeNavTab.classList.remove('active-tab'); | |
413 $(tabBar.activeNavTab.getAttribute('tab-contents')).classList. | |
414 remove('active-tab-contents'); | |
415 } | |
416 | |
417 tab.classList.add('active-tab'); | |
418 $(tab.getAttribute('tab-contents')).classList.add('active-tab-contents'); | |
419 tabBar.activeNavTab = tab; | |
420 }; | |
421 | |
422 /** | |
423 * Registers new options page. | |
424 * @param {OptionsPage} page Page to register. | |
425 */ | |
426 OptionsPage.register = function(page) { | |
427 this.registeredPages[page.name.toLowerCase()] = page; | |
428 page.initializePage(); | |
429 }; | |
430 | |
431 /** | |
432 * Find an enclosing section for an element if it exists. | |
433 * @param {Element} element Element to search. | |
434 * @return {OptionPage} The section element, or null. | |
435 * @private | |
436 */ | |
437 OptionsPage.findSectionForNode_ = function(node) { | |
438 while (node = node.parentNode) { | |
439 if (node.nodeName == 'SECTION') | |
440 return node; | |
441 } | |
442 return null; | |
443 }; | |
444 | |
445 /** | |
446 * Registers a new Overlay page. | |
447 * @param {OptionsPage} overlay Overlay to register. | |
448 * @param {OptionsPage} parentPage Associated parent page for this overlay. | |
449 * @param {Array} associatedControls Array of control elements associated with | |
450 * this page. | |
451 */ | |
452 OptionsPage.registerOverlay = function(overlay, | |
453 parentPage, | |
454 associatedControls) { | |
455 this.registeredOverlayPages[overlay.name.toLowerCase()] = overlay; | |
456 overlay.parentPage = parentPage; | |
457 if (associatedControls) { | |
458 overlay.associatedControls = associatedControls; | |
459 if (associatedControls.length) { | |
460 overlay.associatedSection = | |
461 this.findSectionForNode_(associatedControls[0]); | |
462 } | |
463 } | |
464 | |
465 // Reverse the button strip for views. See the documentation of | |
466 // reverseButtonStrip_() for an explanation of why this is necessary. | |
467 if (cr.isViews) | |
468 this.reverseButtonStrip_(overlay); | |
469 | |
470 overlay.tab = undefined; | |
471 overlay.isOverlay = true; | |
472 overlay.initializePage(); | |
473 }; | |
474 | |
475 /** | |
476 * Reverses the child elements of a button strip. This is necessary because | |
477 * WebKit does not alter the tab order for elements that are visually reversed | |
478 * using -webkit-box-direction: reverse, and the button order is reversed for | |
479 * views. See https://bugs.webkit.org/show_bug.cgi?id=62664 for more | |
480 * information. | |
481 * @param {Object} overlay The overlay containing the button strip to reverse. | |
482 * @private | |
483 */ | |
484 OptionsPage.reverseButtonStrip_ = function(overlay) { | |
485 var buttonStrips = overlay.pageDiv.querySelectorAll('.button-strip'); | |
486 | |
487 // Reverse all button-strips in the overlay. | |
488 for (var j = 0; j < buttonStrips.length; j++) { | |
489 var buttonStrip = buttonStrips[j]; | |
490 | |
491 var childNodes = buttonStrip.childNodes; | |
492 for (var i = childNodes.length - 1; i >= 0; i--) | |
493 buttonStrip.appendChild(childNodes[i]); | |
494 } | |
495 }; | |
496 | |
497 /** | |
498 * Callback for window.onpopstate. | |
499 * @param {Object} data State data pushed into history. | |
500 */ | |
501 OptionsPage.setState = function(data) { | |
502 if (data && data.pageName) { | |
503 this.willClose(); | |
504 this.showPageByName(data.pageName, false); | |
505 } | |
506 }; | |
507 | |
508 /** | |
509 * Callback for window.onbeforeunload. Used to notify overlays that they will | |
510 * be closed. | |
511 */ | |
512 OptionsPage.willClose = function() { | |
513 var overlay = this.getVisibleOverlay_(); | |
514 if (overlay && overlay.didClosePage) | |
515 overlay.didClosePage(); | |
516 }; | |
517 | |
518 /** | |
519 * Freezes/unfreezes the scroll position of the root page container. | |
520 * @param {boolean} freeze Whether the page should be frozen. | |
521 * @private | |
522 */ | |
523 OptionsPage.setRootPageFrozen_ = function(freeze) { | |
524 var container = $('page-container'); | |
525 if (container.classList.contains('frozen') == freeze) | |
526 return; | |
527 | |
528 if (freeze) { | |
529 // Lock the width, since auto width computation may change. | |
530 container.style.width = window.getComputedStyle(container).width; | |
531 container.oldScrollTop = document.body.scrollTop; | |
532 container.classList.add('frozen'); | |
533 var verticalPosition = | |
534 container.getBoundingClientRect().top - container.oldScrollTop; | |
535 container.style.top = verticalPosition + 'px'; | |
536 this.updateFrozenElementHorizontalPosition_(container); | |
537 } else { | |
538 container.classList.remove('frozen'); | |
539 container.style.top = ''; | |
540 container.style.left = ''; | |
541 container.style.right = ''; | |
542 container.style.width = ''; | |
543 } | |
544 }; | |
545 | |
546 /** | |
547 * Freezes/unfreezes the scroll position of the root page based on the current | |
548 * page stack. | |
549 */ | |
550 OptionsPage.updateRootPageFreezeState = function() { | |
551 var topPage = OptionsPage.getTopmostVisiblePage(); | |
552 if (topPage) | |
553 this.setRootPageFrozen_(topPage.isOverlay); | |
554 }; | |
555 | |
556 /** | |
557 * Initializes the complete options page. This will cause all C++ handlers to | |
558 * be invoked to do final setup. | |
559 */ | |
560 OptionsPage.initialize = function() { | |
561 chrome.send('coreOptionsInitialize'); | |
562 uber.onContentFrameLoaded(); | |
563 | |
564 document.addEventListener('scroll', this.handleScroll_.bind(this)); | |
565 | |
566 // Trigger the scroll handler manually to set the initial state. | |
567 this.handleScroll_(); | |
568 | |
569 // Shake the dialog if the user clicks outside the dialog bounds. | |
570 var containers = [$('overlay-container-1'), $('overlay-container-2')]; | |
571 for (var i = 0; i < containers.length; i++) { | |
572 var overlay = containers[i]; | |
573 cr.ui.overlay.setupOverlay(overlay); | |
574 overlay.addEventListener('cancelOverlay', | |
575 OptionsPage.cancelOverlay.bind(OptionsPage)); | |
576 } | |
577 }; | |
578 | |
579 /** | |
580 * Does a bounds check for the element on the given x, y client coordinates. | |
581 * @param {Element} e The DOM element. | |
582 * @param {number} x The client X to check. | |
583 * @param {number} y The client Y to check. | |
584 * @return {boolean} True if the point falls within the element's bounds. | |
585 * @private | |
586 */ | |
587 OptionsPage.elementContainsPoint_ = function(e, x, y) { | |
588 var clientRect = e.getBoundingClientRect(); | |
589 return x >= clientRect.left && x <= clientRect.right && | |
590 y >= clientRect.top && y <= clientRect.bottom; | |
591 }; | |
592 | |
593 /** | |
594 * Called when the page is scrolled; moves elements that are position:fixed | |
595 * but should only behave as if they are fixed for vertical scrolling. | |
596 * @private | |
597 */ | |
598 OptionsPage.handleScroll_ = function() { | |
599 this.updateAllFrozenElementPositions_(); | |
600 }; | |
601 | |
602 /** | |
603 * Updates all frozen pages to match the horizontal scroll position. | |
604 * @private | |
605 */ | |
606 OptionsPage.updateAllFrozenElementPositions_ = function() { | |
607 var frozenElements = document.querySelectorAll('.frozen'); | |
608 for (var i = 0; i < frozenElements.length; i++) | |
609 this.updateFrozenElementHorizontalPosition_(frozenElements[i]); | |
610 }; | |
611 | |
612 /** | |
613 * Updates the given frozen element to match the horizontal scroll position. | |
614 * @param {HTMLElement} e The frozen element to update. | |
615 * @private | |
616 */ | |
617 OptionsPage.updateFrozenElementHorizontalPosition_ = function(e) { | |
618 if (isRTL()) | |
619 e.style.right = HORIZONTAL_OFFSET + 'px'; | |
620 else | |
621 e.style.left = HORIZONTAL_OFFSET - document.body.scrollLeft + 'px'; | |
622 }; | |
623 | |
624 OptionsPage.setClearPluginLSODataEnabled = function(enabled) { | |
625 if (enabled) { | |
626 document.documentElement.setAttribute( | |
627 'flashPluginSupportsClearSiteData', ''); | |
628 } else { | |
629 document.documentElement.removeAttribute( | |
630 'flashPluginSupportsClearSiteData'); | |
631 } | |
632 }; | |
633 | |
634 OptionsPage.setPepperFlashSettingsEnabled = function(enabled) { | |
635 if (enabled) { | |
636 document.documentElement.setAttribute( | |
637 'enablePepperFlashSettings', ''); | |
638 } else { | |
639 document.documentElement.removeAttribute( | |
640 'enablePepperFlashSettings'); | |
641 } | |
642 }; | |
643 | |
644 OptionsPage.prototype = { | |
645 __proto__: cr.EventTarget.prototype, | |
646 | |
647 /** | |
648 * The parent page of this option page, or null for top-level pages. | |
649 * @type {OptionsPage} | |
650 */ | |
651 parentPage: null, | |
652 | |
653 /** | |
654 * The section on the parent page that is associated with this page. | |
655 * Can be null. | |
656 * @type {Element} | |
657 */ | |
658 associatedSection: null, | |
659 | |
660 /** | |
661 * An array of controls that are associated with this page. The first | |
662 * control should be located on a top-level page. | |
663 * @type {OptionsPage} | |
664 */ | |
665 associatedControls: null, | |
666 | |
667 /** | |
668 * Initializes page content. | |
669 */ | |
670 initializePage: function() {}, | |
671 | |
672 /** | |
673 * Updates managed banner visibility state. This function iterates over | |
674 * all input fields of a page and if any of these is marked as managed | |
675 * it triggers the managed banner to be visible. The banner can be enforced | |
676 * being on through the managed flag of this class but it can not be forced | |
677 * being off if managed items exist. | |
678 */ | |
679 updateManagedBannerVisibility: function() { | |
680 var bannerDiv = this.pageDiv.querySelector('.managed-prefs-banner'); | |
681 // Create a banner for the overlay if we don't have one. | |
682 if (!bannerDiv) { | |
683 bannerDiv = $('managed-prefs-banner').cloneNode(true); | |
684 bannerDiv.id = null; | |
685 | |
686 if (this.isOverlay) { | |
687 var content = this.pageDiv.querySelector('.content-area'); | |
688 content.parentElement.insertBefore(bannerDiv, content); | |
689 } else { | |
690 bannerDiv.classList.add('main-page-banner'); | |
691 var header = this.pageDiv.querySelector('header'); | |
692 header.appendChild(bannerDiv); | |
693 } | |
694 } | |
695 | |
696 var controlledByPolicy = false; | |
697 var controlledByExtension = false; | |
698 var inputElements = this.pageDiv.querySelectorAll('input[controlled-by]'); | |
699 for (var i = 0; i < inputElements.length; i++) { | |
700 if (inputElements[i].controlledBy == 'policy') | |
701 controlledByPolicy = true; | |
702 else if (inputElements[i].controlledBy == 'extension') | |
703 controlledByExtension = true; | |
704 } | |
705 | |
706 if (!controlledByPolicy && !controlledByExtension) { | |
707 this.pageDiv.classList.remove('showing-banner'); | |
708 } else { | |
709 this.pageDiv.classList.add('showing-banner'); | |
710 | |
711 var text = bannerDiv.querySelector('#managed-prefs-text'); | |
712 if (controlledByPolicy && !controlledByExtension) { | |
713 text.textContent = | |
714 loadTimeData.getString('policyManagedPrefsBannerText'); | |
715 } else if (!controlledByPolicy && controlledByExtension) { | |
716 text.textContent = | |
717 loadTimeData.getString('extensionManagedPrefsBannerText'); | |
718 } else if (controlledByPolicy && controlledByExtension) { | |
719 text.textContent = loadTimeData.getString( | |
720 'policyAndExtensionManagedPrefsBannerText'); | |
721 } | |
722 } | |
723 }, | |
724 | |
725 /** | |
726 * Gets the container div for this page if it is an overlay. | |
727 * @type {HTMLElement} | |
728 */ | |
729 get container() { | |
730 assert(this.isOverlay); | |
731 return this.pageDiv.parentNode; | |
732 }, | |
733 | |
734 /** | |
735 * Gets page visibility state. | |
736 * @type {boolean} | |
737 */ | |
738 get visible() { | |
739 // If this is an overlay dialog it is no longer considered visible while | |
740 // the overlay is fading out. See http://crbug.com/118629. | |
741 if (this.isOverlay && | |
742 this.container.classList.contains('transparent')) { | |
743 return false; | |
744 } | |
745 return !this.pageDiv.hidden; | |
746 }, | |
747 | |
748 /** | |
749 * Sets page visibility. | |
750 * @type {boolean} | |
751 */ | |
752 set visible(visible) { | |
753 if ((this.visible && visible) || (!this.visible && !visible)) | |
754 return; | |
755 | |
756 // If using an overlay, the visibility of the dialog is toggled at the | |
757 // same time as the overlay to show the dialog's out transition. This | |
758 // is handled in setOverlayVisible. | |
759 if (this.isOverlay) { | |
760 this.setOverlayVisible_(visible); | |
761 } else { | |
762 this.pageDiv.hidden = !visible; | |
763 this.onVisibilityChanged_(); | |
764 } | |
765 | |
766 cr.dispatchPropertyChange(this, 'visible', visible, !visible); | |
767 }, | |
768 | |
769 /** | |
770 * Shows or hides an overlay (including any visible dialog). | |
771 * @param {boolean} visible Whether the overlay should be visible or not. | |
772 * @private | |
773 */ | |
774 setOverlayVisible_: function(visible) { | |
775 assert(this.isOverlay); | |
776 var pageDiv = this.pageDiv; | |
777 var container = this.container; | |
778 | |
779 if (visible) { | |
780 uber.invokeMethodOnParent('beginInterceptingEvents'); | |
781 this.pageDiv.removeAttribute('aria-hidden'); | |
782 if (this.parentPage) | |
783 this.parentPage.pageDiv.setAttribute('aria-hidden', true); | |
784 } else { | |
785 if (this.parentPage) | |
786 this.parentPage.pageDiv.removeAttribute('aria-hidden'); | |
787 } | |
788 | |
789 if (container.hidden != visible) { | |
790 if (visible) { | |
791 // If the container is set hidden and then immediately set visible | |
792 // again, the fadeCompleted_ callback would cause it to be erroneously | |
793 // hidden again. Removing the transparent tag avoids that. | |
794 container.classList.remove('transparent'); | |
795 | |
796 // Hide all dialogs in this container since a different one may have | |
797 // been previously visible before fading out. | |
798 var pages = container.querySelectorAll('.page'); | |
799 for (var i = 0; i < pages.length; i++) | |
800 pages[i].hidden = true; | |
801 // Show the new dialog. | |
802 pageDiv.hidden = false; | |
803 } | |
804 return; | |
805 } | |
806 | |
807 if (visible) { | |
808 container.hidden = false; | |
809 pageDiv.hidden = false; | |
810 // NOTE: This is a hacky way to force the container to layout which | |
811 // will allow us to trigger the webkit transition. | |
812 container.scrollTop; | |
813 container.classList.remove('transparent'); | |
814 this.onVisibilityChanged_(); | |
815 } else { | |
816 var self = this; | |
817 // TODO: Use an event delegate to avoid having to subscribe and | |
818 // unsubscribe for webkitTransitionEnd events. | |
819 container.addEventListener('webkitTransitionEnd', function f(e) { | |
820 if (e.target != e.currentTarget || e.propertyName != 'opacity') | |
821 return; | |
822 container.removeEventListener('webkitTransitionEnd', f); | |
823 self.fadeCompleted_(); | |
824 }); | |
825 container.classList.add('transparent'); | |
826 } | |
827 }, | |
828 | |
829 /** | |
830 * Called when a container opacity transition finishes. | |
831 * @private | |
832 */ | |
833 fadeCompleted_: function() { | |
834 if (this.container.classList.contains('transparent')) { | |
835 this.pageDiv.hidden = true; | |
836 this.container.hidden = true; | |
837 this.onVisibilityChanged_(); | |
838 if (this.nestingLevel == 1) | |
839 uber.invokeMethodOnParent('stopInterceptingEvents'); | |
840 } | |
841 }, | |
842 | |
843 /** | |
844 * Called when a page is shown or hidden to update the root options page | |
845 * based on this page's visibility. | |
846 * @private | |
847 */ | |
848 onVisibilityChanged_: function() { | |
849 OptionsPage.updateRootPageFreezeState(); | |
850 OptionsPage.updateManagedBannerVisibility(); | |
851 | |
852 if (this.isOverlay && !this.visible) | |
853 OptionsPage.updateScrollPosition_(); | |
854 }, | |
855 | |
856 /** | |
857 * The nesting level of this page. | |
858 * @type {number} The nesting level of this page (0 for top-level page) | |
859 */ | |
860 get nestingLevel() { | |
861 var level = 0; | |
862 var parent = this.parentPage; | |
863 while (parent) { | |
864 level++; | |
865 parent = parent.parentPage; | |
866 } | |
867 return level; | |
868 }, | |
869 | |
870 /** | |
871 * Whether the page is considered 'sticky', such that it will | |
872 * remain a top-level page even if sub-pages change. | |
873 * @type {boolean} True if this page is sticky. | |
874 */ | |
875 get sticky() { | |
876 return false; | |
877 }, | |
878 | |
879 /** | |
880 * Checks whether this page is an ancestor of the given page in terms of | |
881 * subpage nesting. | |
882 * @param {OptionsPage} page The potential descendent of this page. | |
883 * @return {boolean} True if |page| is nested under this page. | |
884 */ | |
885 isAncestorOfPage: function(page) { | |
886 var parent = page.parentPage; | |
887 while (parent) { | |
888 if (parent == this) | |
889 return true; | |
890 parent = parent.parentPage; | |
891 } | |
892 return false; | |
893 }, | |
894 | |
895 /** | |
896 * Whether it should be possible to show the page. | |
897 * @return {boolean} True if the page should be shown. | |
898 */ | |
899 canShowPage: function() { | |
900 return true; | |
901 }, | |
902 }; | |
903 | |
904 // Export | |
905 return { | |
906 OptionsPage: OptionsPage | |
907 }; | |
908 }); | |
OLD | NEW |