// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*- /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla Mobile Browser. * * The Initial Developer of the Original Code is * Mozilla Corporation. * Portions created by the Initial Developer are Copyright (C) 2008 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Mark Finkle * Matt Brubeck * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); XPCOMUtils.defineLazyGetter(this, "PluralForm", function() { Cu.import("resource://gre/modules/PluralForm.jsm"); return PluralForm; }); XPCOMUtils.defineLazyGetter(this, "PlacesUtils", function() { Cu.import("resource://gre/modules/PlacesUtils.jsm"); return PlacesUtils; }); XPCOMUtils.defineLazyServiceGetter(window, "gHistSvc", "@mozilla.org/browser/nav-history-service;1", "nsINavHistoryService", "nsIBrowserHistory"); XPCOMUtils.defineLazyServiceGetter(window, "gURIFixup", "@mozilla.org/docshell/urifixup;1", "nsIURIFixup"); XPCOMUtils.defineLazyServiceGetter(window, "gFaviconService", "@mozilla.org/browser/favicon-service;1", "nsIFaviconService"); XPCOMUtils.defineLazyServiceGetter(window, "gFocusManager", "@mozilla.org/focus-manager;1", "nsIFocusManager"); [ ["AllPagesList", "popup_autocomplete", "cmd_openLocation"], ["HistoryList", "history-items", "cmd_history"], ["BookmarkList", "bookmarks-items", "cmd_bookmarks"], #ifdef MOZ_SERVICES_SYNC ["RemoteTabsList", "remotetabs-items", "cmd_remoteTabs"] #endif ].forEach(function(aPanel) { let [name, id, command] = aPanel; XPCOMUtils.defineLazyGetter(window, name, function() { return new AwesomePanel(id, command); }); }); /** * Cache of commonly used elements. */ let Elements = {}; [ ["browserBundle", "bundle_browser"], ["contentShowing", "bcast_contentShowing"], ["urlbarState", "bcast_urlbarState"], ["stack", "stack"], ["tabs", "tabs-container"], ["controls", "browser-controls"], ["panelUI", "panel-container"], ["viewBuffer", "view-buffer"], ["toolbarContainer", "toolbar-container"], ["browsers", "browsers"] ].forEach(function (aElementGlobal) { let [name, id] = aElementGlobal; XPCOMUtils.defineLazyGetter(Elements, name, function() { return document.getElementById(id); }); }); const TOOLBARSTATE_LOADING = 1; const TOOLBARSTATE_LOADED = 2; var BrowserUI = { _edit : null, _throbber : null, _favicon : null, _dialogs: [], _domWillOpenModalDialog: function(aBrowser) { // We're about to open a modal dialog, make sure the opening // tab is brought to the front. for (let i = 0; i < Browser.tabs.length; i++) { if (Browser._tabs[i].browser == aBrowser) { Browser.selectedTab = Browser.tabs[i]; break; } } }, _titleChanged: function(aBrowser) { let browser = Browser.selectedBrowser; if (browser && aBrowser != browser) return; let url = this.getDisplayURI(browser); let caption = browser.contentTitle || url; if (Util.isURLEmpty(url)) caption = ""; this._setURI(caption); }, /* * Dispatched by window.close() to allow us to turn window closes into tabs * closes. */ _domWindowClose: function(aBrowser) { // Find the relevant tab, and close it. let browsers = Browser.browsers; for (let i = 0; i < browsers.length; i++) { if (browsers[i] == aBrowser) { Browser.closeTab(Browser.getTabAtIndex(i)); return { preventDefault: true }; } } }, _updateButtons: function(aBrowser) { let back = document.getElementById("cmd_back"); let forward = document.getElementById("cmd_forward"); back.setAttribute("disabled", !aBrowser.canGoBack); forward.setAttribute("disabled", !aBrowser.canGoForward); }, _updateToolbar: function _updateToolbar() { let mode = Elements.urlbarState.getAttribute("mode"); if (Browser.selectedTab.isLoading() && mode != "loading") { Elements.urlbarState.setAttribute("mode", "loading"); } else if (mode != "view") { Elements.urlbarState.setAttribute("mode", "view"); } }, _tabSelect: function(aEvent) { let browser = Browser.selectedBrowser; this._titleChanged(browser); this._updateToolbar(); this._updateButtons(browser); this._updateIcon(browser.mIconURL); this.updateStar(); }, _toolbarLocked: 0, isToolbarLocked: function isToolbarLocked() { return this._toolbarLocked; }, lockToolbar: function lockToolbar() { this._toolbarLocked++; document.getElementById("toolbar-moveable-container").top = "0"; if (this._toolbarLocked == 1) Browser.forceChromeReflow(); }, unlockToolbar: function unlockToolbar() { if (!this._toolbarLocked) return; this._toolbarLocked--; if (!this._toolbarLocked) document.getElementById("toolbar-moveable-container").top = ""; }, _setURI: function _setURI(aCaption) { if (this.activePanel) this._edit.defaultValue = aCaption; else this._edit.value = aCaption; }, _editURI: function _editURI(aEdit) { Elements.urlbarState.setAttribute("mode", "edit"); this._edit.defaultValue = this._edit.value; this._showURI(); }, _showURI: function _showURI() { // Replace the web page title by the url of the page let urlString = this.getDisplayURI(Browser.selectedBrowser); if (Util.isURLEmpty(urlString)) urlString = ""; this._edit.value = urlString; }, updateAwesomeHeader: function updateAwesomeHeader(aString) { document.getElementById("awesome-header").hidden = (aString != ""); // During an awesome search we always show the popup_autocomplete/AllPagesList // panel since this looks in every places and the rationale behind typing // is to find something, whereever it is. if (this.activePanel != AllPagesList) { let inputField = this._edit; let oldClickSelectsAll = inputField.clickSelectsAll; inputField.clickSelectsAll = false; this.activePanel = AllPagesList; // changing the searchString property call updateAwesomeHeader again inputField.controller.searchString = aString; inputField.readOnly = false; inputField.clickSelectsAll = oldClickSelectsAll; return; } let event = document.createEvent("Events"); event.initEvent("onsearchbegin", true, true); this._edit.dispatchEvent(event); }, _closeOrQuit: function _closeOrQuit() { // Close active dialog, if we have one. If not then close the application. if (this.activePanel) { this.activePanel = null; } else if (this.activeDialog) { this.activeDialog.close(); } else { // Check to see if we should really close the window if (Browser.closing()) window.close(); } }, _activePanel: null, get activePanel() { return this._activePanel; }, set activePanel(aPanel) { if (this._activePanel == aPanel) return; let awesomePanel = document.getElementById("awesome-panels"); let awesomeHeader = document.getElementById("awesome-header"); let willShowPanel = (!this._activePanel && aPanel); if (willShowPanel) { this.pushDialog(aPanel); this._edit.attachController(); this._editURI(); awesomePanel.hidden = awesomeHeader.hidden = false; }; if (aPanel) { aPanel.open(); if (this._edit.value == "") this._showURI(); } let willHidePanel = (this._activePanel && !aPanel); if (willHidePanel) { awesomePanel.hidden = true; awesomeHeader.hidden = false; this._updateToolbar(); this._edit.reset(); this._edit.detachController(); this.popDialog(); } if (this._activePanel) this._activePanel.close(); // The readOnly state of the field enabled/disabled the VKB let isReadOnly = !(aPanel == AllPagesList && Util.isPortrait()); this._edit.readOnly = isReadOnly; if (isReadOnly) this._edit.blur(); this._activePanel = aPanel; if (willHidePanel || willShowPanel) { let event = document.createEvent("UIEvents"); event.initUIEvent("NavigationPanel" + (willHidePanel ? "Hidden" : "Shown"), true, true, window, false); window.dispatchEvent(event); } }, get activeDialog() { // Return the topmost dialog if (this._dialogs.length) return this._dialogs[this._dialogs.length - 1]; return null; }, pushDialog: function pushDialog(aDialog) { // If we have a dialog push it on the stack and set the attr for CSS if (aDialog) { this.lockToolbar(); this._dialogs.push(aDialog); document.getElementById("toolbar-main").setAttribute("dialog", "true"); Elements.contentShowing.setAttribute("disabled", "true"); } }, popDialog: function popDialog() { if (this._dialogs.length) { this._dialogs.pop(); this.unlockToolbar(); } // If no more dialogs are being displayed, remove the attr for CSS if (!this._dialogs.length) { document.getElementById("toolbar-main").removeAttribute("dialog"); Elements.contentShowing.removeAttribute("disabled"); } }, pushPopup: function pushPopup(aPanel, aElements) { this._hidePopup(); this._popup = { "panel": aPanel, "elements": (aElements instanceof Array) ? aElements : [aElements] }; this._dispatchPopupChanged(true); }, popPopup: function popPopup() { this._popup = null; this._dispatchPopupChanged(false); }, _dispatchPopupChanged: function _dispatchPopupChanged(aVisible) { let stack = document.getElementById("stack"); let event = document.createEvent("UIEvents"); event.initUIEvent("PopupChanged", true, true, window, aVisible); event.popup = this._popup; stack.dispatchEvent(event); }, _hidePopup: function _hidePopup() { if (!this._popup) return; let panel = this._popup.panel; if (panel.hide) panel.hide(); }, _isEventInsidePopup: function _isEventInsidePopup(aEvent) { if (!this._popup) return false; let elements = this._popup.elements; let targetNode = aEvent ? aEvent.target : null; while (targetNode && elements.indexOf(targetNode) == -1) targetNode = targetNode.parentNode; return targetNode ? true : false; }, switchPane: function switchPane(aPanelId) { let button = document.getElementsByAttribute("linkedpanel", aPanelId)[0]; if (button) button.checked = true; this.blurFocusedElement(); let pane = document.getElementById(aPanelId); document.getElementById("panel-items").selectedPanel = pane; }, get toolbarH() { if (!this._toolbarH) { let toolbar = document.getElementById("toolbar-main"); this._toolbarH = toolbar.boxObject.height; } return this._toolbarH; }, get sidebarW() { delete this._sidebarW; return this._sidebarW = Elements.controls.getBoundingClientRect().width; }, get starButton() { delete this.starButton; return this.starButton = document.getElementById("tool-star"); }, sizeControls: function(windowW, windowH) { // tabs document.getElementById("tabs").resize(); // awesomebar and related panels let popup = document.getElementById("awesome-panels"); popup.top = this.toolbarH; popup.height = windowH - this.toolbarH; popup.width = windowW; // content navigator helper let contentHelper = document.getElementById("content-navigator"); contentHelper.top = windowH - contentHelper.getBoundingClientRect().height; }, init: function() { this._edit = document.getElementById("urlbar-edit"); this._throbber = document.getElementById("urlbar-throbber"); this._favicon = document.getElementById("urlbar-favicon"); this._favicon.addEventListener("error", this, false); this._edit.addEventListener("click", this, false); this._edit.addEventListener("mousedown", this, false); BadgeHandlers.register(this._edit.popup); window.addEventListener("NavigationPanelShown", this, false); window.addEventListener("NavigationPanelHidden", this, false); document.getElementById("toolbar-main").ignoreDrag = true; let tabs = document.getElementById("tabs"); tabs.addEventListener("TabSelect", this, true); tabs.addEventListener("TabOpen", this, true); window.addEventListener("PanFinished", this, true); // listen content messages messageManager.addMessageListener("DOMLinkAdded", this); messageManager.addMessageListener("DOMTitleChanged", this); messageManager.addMessageListener("DOMWillOpenModalDialog", this); messageManager.addMessageListener("DOMWindowClose", this); messageManager.addMessageListener("Browser:OpenURI", this); messageManager.addMessageListener("Browser:SaveAs:Return", this); // listening mousedown for automatically dismiss some popups (e.g. larry) window.addEventListener("mousedown", this, true); // listening escape to dismiss dialog on VK_ESCAPE window.addEventListener("keypress", this, true); // listening AppCommand to handle special keys window.addEventListener("AppCommand", this, true); // We can delay some initialization until after startup. We wait until // the first page is shown, then dispatch a UIReadyDelayed event. messageManager.addMessageListener("pageshow", function() { if (getBrowser().currentURI.spec == "about:blank") return; messageManager.removeMessageListener("pageshow", arguments.callee, true); let event = document.createEvent("Events"); event.initEvent("UIReadyDelayed", true, false); window.dispatchEvent(event); }); // Delay the panel UI and Sync initialization. window.addEventListener("UIReadyDelayed", function(aEvent) { window.removeEventListener(aEvent.type, arguments.callee, false); // We unhide the panelUI so the XBL and settings can initialize Elements.panelUI.hidden = false; // Init the views ExtensionsView.init(); DownloadsView.init(); PreferencesView.init(); ConsoleView.init(); FullScreenVideo.init(); #ifdef MOZ_IPC // Pre-start the content process Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime) .ensureContentProcess(); #endif #ifdef MOZ_SERVICES_SYNC // Init the sync system WeaveGlue.init(); #endif }, false); FormHelperUI.init(); FindHelperUI.init(); PageActions.init(); }, uninit: function() { ExtensionsView.uninit(); ConsoleView.uninit(); FormHelperUI.uninit(); }, update: function(aState) { let browser = Browser.selectedBrowser; switch (aState) { case TOOLBARSTATE_LOADED: if (Elements.urlbarState.getAttribute("mode") != "edit") this._updateToolbar(); this._updateIcon(browser.mIconURL); this.unlockToolbar(); break; case TOOLBARSTATE_LOADING: if (Elements.urlbarState.getAttribute("mode") != "edit") this._updateToolbar(); browser.mIconURL = ""; this._updateIcon(); this.lockToolbar(); break; } }, _updateIcon: function(aIconSrc) { this._favicon.src = aIconSrc || ""; if (Browser.selectedTab.isLoading()) { this._throbber.hidden = false; this._throbber.setAttribute("loading", "true"); this._favicon.hidden = true; } else { this._favicon.hidden = false; this._throbber.hidden = true; this._throbber.removeAttribute("loading"); } }, getDisplayURI: function(browser) { let uri = browser.currentURI; try { uri = gURIFixup.createExposableURI(uri); } catch (ex) {} return uri.spec; }, /* Set the location to the current content */ updateURI: function() { let browser = Browser.selectedBrowser; // FIXME: deckbrowser should not fire TabSelect on the initial tab (bug 454028) if (!browser.currentURI) return; // Update the navigation buttons this._updateButtons(browser); // Check for a bookmarked page this.updateStar(); let urlString = this.getDisplayURI(browser); if (Util.isURLEmpty(urlString)) urlString = ""; this._setURI(urlString); }, goToURI: function(aURI) { aURI = aURI || this._edit.value; if (!aURI) return; // Make sure we're online before attempting to load Util.forceOnline(); // Give the new page lots of room Browser.hideSidebars(); this.closeAutoComplete(); this._edit.value = aURI; Browser.loadURI(aURI, { flags: Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP }); // Delay doing the fixup so the raw URI is passed to loadURIWithFlags // and the proper third-party fixup can be done let fixupFlags = Ci.nsIURIFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP; let uri = gURIFixup.createFixupURI(aURI, fixupFlags); gHistSvc.markPageAsTyped(uri); }, showAutoComplete: function showAutoComplete() { if (this.isAutoCompleteOpen()) return; this._hidePopup(); this.activePanel = AllPagesList; }, closeAutoComplete: function closeAutoComplete() { dump("=========================== called\n"); if (this.isAutoCompleteOpen()) this._edit.popup.closePopup(); this.activePanel = null; }, isAutoCompleteOpen: function isAutoCompleteOpen() { return this.activePanel == AllPagesList; }, doOpenSearch: function doOpenSearch(aName) { // save the current value of the urlbar let searchValue = this._edit.value; // Give the new page lots of room Browser.hideSidebars(); this.closeAutoComplete(); // Make sure we're online before attempting to load Util.forceOnline(); let engine = Services.search.getEngineByName(aName); let submission = engine.getSubmission(searchValue, null); Browser.loadURI(submission.uri.spec, { postData: submission.postData }); }, updateUIFocus: function _updateUIFocus() { let state = (Elements.contentShowing.getAttribute("disabled") == "true") ? "Blur" : "Focus"; Browser.selectedBrowser.messageManager.sendAsyncMessage("Browser:" + state, {}); }, updateStar: function() { if (PlacesUtils.getMostRecentBookmarkForURI(Browser.selectedBrowser.currentURI) != -1) this.starButton.setAttribute("starred", "true"); else this.starButton.removeAttribute("starred"); }, newTab: function newTab(aURI, aOwner) { aURI = aURI || "about:blank"; let tab = Browser.addTab(aURI, true, aOwner); this.hidePanel(); if (aURI == "about:blank") { // Display awesomebar UI this.showAutoComplete(); } else { // Give the new page lots of room Browser.hideSidebars(); this.closeAutoComplete(); } return tab; }, newOrSelectTab: function newOrSelectTab(aURI, aOwner) { let tabs = Browser.tabs; for (let i = 0; i < tabs.length; i++) { if (tabs[i].browser.currentURI.spec == aURI) { Browser.selectedTab = tabs[i]; return; } } this.newTab(aURI, aOwner); }, closeTab: function closeTab(aTab) { // If no tab is passed in, assume the current tab Browser.closeTab(aTab || Browser.selectedTab); }, selectTab: function selectTab(aTab) { this.activePanel = null; Browser.selectedTab = aTab; }, undoCloseTab: function undoCloseTab(aIndex) { let tab = null; let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore); if (ss.getClosedTabCount(window) > (aIndex || 0)) { tab = ss.undoCloseTab(window, aIndex || 0); } return tab; }, isTabsVisible: function isTabsVisible() { // The _1, _2 and _3 are to make the js2 emacs mode happy let [leftvis,_1,_2,_3] = Browser.computeSidebarVisibility(); return (leftvis > 0.002); }, showPanel: function showPanel(aPage) { if (this.activePanel) this.activePanel = null; Elements.panelUI.left = 0; Elements.panelUI.hidden = false; Elements.contentShowing.setAttribute("disabled", "true"); if (aPage != undefined) this.switchPane(aPage); }, hidePanel: function hidePanel() { if (!this.isPanelVisible()) return; Elements.panelUI.hidden = true; Elements.contentShowing.removeAttribute("disabled"); this.blurFocusedElement(); }, isPanelVisible: function isPanelVisible() { return (!Elements.panelUI.hidden && Elements.panelUI.left == 0); }, blurFocusedElement: function blurFocusedElement() { let focusedElement = document.commandDispatcher.focusedElement; if (focusedElement) focusedElement.blur(); }, switchTask: function switchTask() { try { let phone = Cc["@mozilla.org/phone/support;1"].createInstance(Ci.nsIPhoneSupport); phone.switchTask(); } catch(e) { } }, handleEscape: function (aEvent) { aEvent.stopPropagation(); // Check open popups if (this._popup) { this._hidePopup(); return; } // Check active panel if (this.activePanel) { this.activePanel = null; return; } // Check open dialogs let dialog = this.activeDialog; if (dialog) { dialog.close(); return; } // Check open modal elements let modalElementsLength = document.getElementsByClassName("modal-block").length; if (modalElementsLength > 0) return; // Check open panel if (this.isPanelVisible()) { this.hidePanel(); return; } // Check content helper let contentHelper = document.getElementById("content-navigator"); if (contentHelper.isActive) { contentHelper.hide(); return; } // Only if there are no dialogs, popups, or panels open let tab = Browser.selectedTab; let browser = tab.browser; if (browser.canGoBack) { browser.goBack(); } else if (tab.owner) { this.closeTab(tab); } #ifdef ANDROID else { window.QueryInterface(Ci.nsIDOMChromeWindow).minimize(); if (tab.closeOnExit) this.closeTab(tab); } #endif }, handleEvent: function handleEvent(aEvent) { switch (aEvent.type) { // Browser events case "TabSelect": this._tabSelect(aEvent); break; case "TabOpen": { let [tabsVisibility,,,] = Browser.computeSidebarVisibility(); if (!(tabsVisibility == 1.0) && Browser.selectedTab.chromeTab != aEvent.originalTarget) NewTabPopup.show(aEvent.originalTarget); // Workaround to hide the tabstrip if it is partially visible // See bug 524469 if (tabsVisibility > 0.0 && tabsVisibility < 1.0) Browser.hideSidebars(); break; } case "PanFinished": let [tabsVisibility,,,] = Browser.computeSidebarVisibility(); if (tabsVisibility == 0.0) document.getElementById("tabs").removeClosedTab(); break; // Window events case "keypress": if (aEvent.keyCode == aEvent.DOM_VK_ESCAPE) this.handleEscape(aEvent); break; case "AppCommand": aEvent.stopPropagation(); switch (aEvent.command) { case "Menu": this.doCommand("cmd_menu"); break; case "Search": AllPagesList.doCommand(); break; default: break; } break; // URL textbox events case "click": // if there is an already opened panel, keep it active (bug 596614). if (this.activePanel && this._edit.readOnly) this._edit.readOnly = false; else if (!this.activePanel) AllPagesList.doCommand(); break; case "mousedown": if (!this._isEventInsidePopup(aEvent)) this._hidePopup(); let selectAll = Services.prefs.getBoolPref("browser.urlbar.doubleClickSelectsAll"); if (aEvent.detail == 2 && aEvent.button == 0 && selectAll && aEvent.target == this._edit) { this._edit.editor.selectAll(); aEvent.preventDefault(); } break; // Favicon events case "error": this._favicon.src = ""; break; // Awesome popup event case "NavigationPanelShown": this._edit.setAttribute("open", "true"); break; case "NavigationPanelHidden": this._edit.removeAttribute("open"); break; } }, receiveMessage: function receiveMessage(aMessage) { let browser = aMessage.target; let json = aMessage.json; switch (aMessage.name) { case "DOMTitleChanged": this._titleChanged(browser); break; case "DOMWillOpenModalDialog": return this._domWillOpenModalDialog(browser); break; case "DOMWindowClose": return this._domWindowClose(browser); break; case "DOMLinkAdded": if (Browser.selectedBrowser == browser) this._updateIcon(Browser.selectedBrowser.mIconURL); break; case "Browser:SaveAs:Return": if (json.type != Ci.nsIPrintSettings.kOutputFormatPDF) return; let dm = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager); let db = dm.DBConnection; let stmt = db.createStatement("UPDATE moz_downloads SET endTime = :endTime, state = :state WHERE id = :id"); stmt.params.endTime = Date.now() * 1000; stmt.params.state = Ci.nsIDownloadManager.DOWNLOAD_FINISHED; stmt.params.id = json.id; stmt.execute(); stmt.finalize(); let download = dm.getDownload(json.id); try { DownloadsView.downloadCompleted(download); let element = DownloadsView.getElementForDownload(json.id); element.setAttribute("state", Ci.nsIDownloadManager.DOWNLOAD_FINISHED); element.setAttribute("endTime", Date.now()); element.setAttribute("referrer", json.referrer); DownloadsView._updateTime(element); DownloadsView._updateStatus(element); } catch(e) {} Services.obs.notifyObservers(download, "dl-done", null); break; case "Browser:OpenURI": Browser.addTab(json.uri, json.bringFront, Browser.selectedTab); break; } }, supportsCommand : function(cmd) { var isSupported = false; switch (cmd) { case "cmd_back": case "cmd_forward": case "cmd_reload": case "cmd_forceReload": case "cmd_stop": case "cmd_go": case "cmd_openLocation": case "cmd_star": case "cmd_opensearch": case "cmd_bookmarks": case "cmd_history": case "cmd_remoteTabs": case "cmd_quit": case "cmd_close": case "cmd_menu": case "cmd_newTab": case "cmd_closeTab": case "cmd_undoCloseTab": case "cmd_actions": case "cmd_panel": case "cmd_sanitize": case "cmd_zoomin": case "cmd_zoomout": case "cmd_volumeLeft": case "cmd_volumeRight": case "cmd_lockscreen": isSupported = true; break; default: isSupported = false; break; } return isSupported; }, isCommandEnabled : function(cmd) { let elem = document.getElementById(cmd); if (elem && (elem.getAttribute("disabled") == "true")) return false; return true; }, doCommand : function(cmd) { if (!this.isCommandEnabled(cmd)) return; let browser = getBrowser(); switch (cmd) { case "cmd_back": browser.goBack(); break; case "cmd_forward": browser.goForward(); break; case "cmd_reload": browser.reload(); break; case "cmd_forceReload": { // Simulate a new page browser.lastLocation = null; const reloadFlags = Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY | Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE; browser.reloadWithFlags(reloadFlags); break; } case "cmd_stop": browser.stop(); break; case "cmd_go": this.goToURI(); break; case "cmd_openLocation": this.hidePanel(); this.showAutoComplete(); break; case "cmd_star": { let bookmarkURI = browser.currentURI; let autoClose = false; if (PlacesUtils.getMostRecentBookmarkForURI(bookmarkURI) == -1) { let bookmarkTitle = browser.contentTitle || bookmarkURI.spec; let bookmarkService = PlacesUtils.bookmarks; let bookmarkId = bookmarkService.insertBookmark(BookmarkList.panel.mobileRoot, bookmarkURI, bookmarkService.DEFAULT_INDEX, bookmarkTitle); this.updateStar(); // autoclose the bookmark popup autoClose = true; } // Show/hide bookmark popup BookmarkPopup.toggle(autoClose); break; } case "cmd_opensearch": BrowserSearch.toggle(); break; case "cmd_bookmarks": this.activePanel = BookmarkList; break; case "cmd_history": this.activePanel = HistoryList; break; case "cmd_remoteTabs": this.activePanel = RemoteTabsList; break; case "cmd_quit": goQuitApplication(); break; case "cmd_close": this._closeOrQuit(); break; case "cmd_menu": getIdentityHandler().toggle(); break; case "cmd_newTab": this.newTab(); break; case "cmd_closeTab": this.closeTab(); break; case "cmd_undoCloseTab": this.undoCloseTab(); break; case "cmd_sanitize": { // disable the button temporarily to indicate something happened let button = document.getElementById("prefs-clear-data"); button.disabled = true; setTimeout(function() { button.disabled = false; }, 5000); Sanitizer.sanitize(); break; } case "cmd_panel": { if (BrowserUI.isPanelVisible()) this.hidePanel(); else this.showPanel(); break; } case "cmd_zoomin": Browser.zoom(-1); break; case "cmd_zoomout": Browser.zoom(1); break; case "cmd_volumeLeft": // Zoom in (portrait) or out (landscape) Browser.zoom(Util.isPortrait() ? -1 : 1); break; case "cmd_volumeRight": // Zoom out (portrait) or in (landscape) Browser.zoom(Util.isPortrait() ? 1 : -1); break; case "cmd_lockscreen": { let locked = Services.prefs.getBoolPref("toolkit.screen.lock"); Services.prefs.setBoolPref("toolkit.screen.lock", !locked); let strings = Elements.browserBundle; let alerts = Cc["@mozilla.org/alerts-service;1"].getService(Ci.nsIAlertsService); alerts.showAlertNotification(null, strings.getString("alertLockScreen"), strings.getString("alertLockScreen." + (!locked ? "locked" : "unlocked")), false, "", null); break; } } } }; var TapHighlightHelper = { get _overlay() { delete this._overlay; return this._overlay = document.getElementById("content-overlay"); }, show: function show(aRects) { let browser = getBrowser(); let scroll = browser.getPosition(); let canvasArea = aRects.reduce(function(a, b) { return a.expandToContain(b); }, new Rect(0, 0, 0, 0)).map(function(val) val * browser.scale) .translate(-scroll.x, -scroll.y); let overlay = this._overlay; overlay.setAttribute("width", canvasArea.width); overlay.setAttribute("height", canvasArea.height); let ctx = overlay.getContext("2d"); ctx.save(); ctx.translate(-canvasArea.left, -canvasArea.top); ctx.scale(browser.scale, browser.scale); overlay.setAttribute("left", canvasArea.left); overlay.setAttribute("top", canvasArea.top); ctx.fillStyle = "rgba(0, 145, 255, .5)"; for (let i = aRects.length - 1; i >= 0; i--) { let rect = aRects[i]; ctx.fillRect(rect.left - scroll.x / browser.scale, rect.top - scroll.y / browser.scale, rect.width, rect.height); } ctx.restore(); overlay.style.display = "block"; addEventListener("MozBeforePaint", this, false); mozRequestAnimationFrame(); }, /** * Hide the highlight. aGuaranteeShowMsecs specifies how many milliseconds the * highlight should be shown before it disappears. */ hide: function hide(aGuaranteeShowMsecs) { if (this._overlay.style.display == "none") return; this._guaranteeShow = Math.max(0, aGuaranteeShowMsecs); if (this._guaranteeShow) { // _shownAt is set once highlight has been painted if (this._shownAt) setTimeout(this._hide.bind(this), Math.max(0, this._guaranteeShow - (mozAnimationStartTime - this._shownAt))); } else { this._hide(); } }, /** Helper function that hides popup immediately. */ _hide: function _hide() { this._shownAt = 0; this._guaranteeShow = 0; this._overlay.style.display = "none"; }, handleEvent: function handleEvent(ev) { removeEventListener("MozBeforePaint", this, false); this._shownAt = ev.timeStamp; // hide has been called, so hide the tap highlight after it has // been shown for a moment. if (this._guaranteeShow) this.hide(this._guaranteeShow); } }; var PageActions = { init: function init() { document.getElementById("pageactions-container").addEventListener("click", this, false); this.register("pageaction-reset", this.updatePagePermissions, this); this.register("pageaction-password", this.updateForgetPassword, this); #ifdef NS_PRINTING this.register("pageaction-saveas", this.updatePageSaveAs, this); #endif this.register("pageaction-share", this.updateShare, this); this.register("pageaction-search", BrowserSearch.updatePageSearchEngines, BrowserSearch); }, handleEvent: function handleEvent(aEvent) { switch (aEvent.type) { case "click": getIdentityHandler().hide(); break; } }, /** * @param aId id of a pageaction element * @param aCallback function that takes an element and returns true if it should be visible * @param aThisObj (optional) scope object for aCallback */ register: function register(aId, aCallback, aThisObj) { this._handlers.push({id: aId, callback: aCallback, obj: aThisObj}); }, _handlers: [], updateSiteMenu: function updateSiteMenu() { this._handlers.forEach(function(action) { let node = document.getElementById(action.id); node.hidden = !action.callback.call(action.obj, node); }); this._updateAttributes(); }, get _loginManager() { delete this._loginManager; return this._loginManager = Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager); }, // This is easy for an addon to add his own perm type here _permissions: ["popup", "offline-app", "geo"], _forEachPermissions: function _forEachPermissions(aHost, aCallback) { let pm = Services.perms; for (let i = 0; i < this._permissions.length; i++) { let type = this._permissions[i]; if (!pm.testPermission(aHost, type)) continue; let perms = pm.enumerator; while (perms.hasMoreElements()) { let permission = perms.getNext().QueryInterface(Ci.nsIPermission); if (permission.host == aHost.asciiHost && permission.type == type) aCallback(type); } } }, updatePagePermissions: function updatePagePermissions(aNode) { let host = Browser.selectedBrowser.currentURI; let permissions = []; this._forEachPermissions(host, function(aType) { permissions.push("pageactions." + aType); }); if (!this._loginManager.getLoginSavingEnabled(host.prePath)) { // If rememberSignons is false, then getLoginSavingEnabled returns false // for all pages, so we should just ignore it (Bug 601163). if (Services.prefs.getBoolPref("signon.rememberSignons")) permissions.push("pageactions.password"); } let descriptions = permissions.map(function(s) Elements.browserBundle.getString(s)); aNode.setAttribute("description", descriptions.join(", ")); return (permissions.length > 0); }, updateForgetPassword: function updateForgetPassword(aNode) { let host = Browser.selectedBrowser.currentURI; let logins = this._loginManager.findLogins({}, host.prePath, "", ""); return logins.some(function(login) login.hostname == host.prePath); }, forgetPassword: function forgetPassword(aEvent) { let host = Browser.selectedBrowser.currentURI; let lm = this._loginManager; lm.findLogins({}, host.prePath, "", "").forEach(function(login) { if (login.hostname == host.prePath) lm.removeLogin(login); }); this.hideItem(aEvent.target); aEvent.stopPropagation(); // Don't hide the site menu. }, clearPagePermissions: function clearPagePermissions(aEvent) { let pm = Services.perms; let host = Browser.selectedBrowser.currentURI; this._forEachPermissions(host, function(aType) { pm.remove(host.asciiHost, aType); }); let lm = this._loginManager; if (!lm.getLoginSavingEnabled(host.prePath)) lm.setLoginSavingEnabled(host.prePath, true); this.hideItem(aEvent.target); aEvent.stopPropagation(); // Don't hide the site menu. }, savePageAsPDF: function saveAsPDF() { let browser = Browser.selectedBrowser; let fileName = getDefaultFileName(browser.contentTitle, browser.documentURI, null, null); fileName = fileName.trim() + ".pdf"; #ifdef MOZ_PLATFORM_MAEMO fileName = fileName.replace(/[\*\:\?]+/g, " "); #endif let dm = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager); let downloadsDir = dm.defaultDownloadsDirectory; #ifdef ANDROID let file = downloadsDir.clone(); file.append(fileName); #else let strings = Elements.browserBundle; let picker = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker); picker.init(window, strings.getString("pageactions.saveas.pdf"), Ci.nsIFilePicker.modeSave); picker.appendFilter("PDF", "*.pdf"); picker.defaultExtension = "pdf"; picker.defaultString = fileName; picker.displayDirectory = downloadsDir; let rv = picker.show(); if (rv == Ci.nsIFilePicker.returnCancel) return; let file = picker.file; #endif // We must manually add this to the download system let db = dm.DBConnection; let stmt = db.createStatement( "INSERT INTO moz_downloads (name, source, target, startTime, endTime, state, referrer) " + "VALUES (:name, :source, :target, :startTime, :endTime, :state, :referrer)" ); let current = browser.currentURI.spec; stmt.params.name = file.leafName; stmt.params.source = current; stmt.params.target = Services.io.newFileURI(file).spec; stmt.params.startTime = Date.now() * 1000; stmt.params.endTime = Date.now() * 1000; stmt.params.state = Ci.nsIDownloadManager.DOWNLOAD_NOTSTARTED; stmt.params.referrer = current; stmt.execute(); stmt.finalize(); let newItemId = db.lastInsertRowID; let download = dm.getDownload(newItemId); try { DownloadsView.downloadStarted(download); } catch(e) {} Services.obs.notifyObservers(download, "dl-start", null); let data = { type: Ci.nsIPrintSettings.kOutputFormatPDF, id: newItemId, referrer: current, filePath: file.path }; Browser.selectedBrowser.messageManager.sendAsyncMessage("Browser:SaveAs", data); }, updatePageSaveAs: function updatePageSaveAs(aNode) { // Check for local XUL content let contentWindow = Browser.selectedBrowser.contentWindow; return !(contentWindow && contentWindow.document instanceof XULDocument); }, updateShare: function updateShare(aNode) { return Util.isShareableScheme(Browser.selectedBrowser.currentURI.scheme); }, hideItem: function hideItem(aNode) { aNode.hidden = true; this._updateAttributes(); }, _updateAttributes: function _updateAttributes() { let container = document.getElementById("pageactions-container"); let visibleNodes = container.querySelectorAll("pageaction:not([hidden=true])"); let visibleCount = visibleNodes.length; for (let i = 0; i < visibleCount; i++) visibleNodes[i].classList.remove("odd-last-child"); if (visibleCount % 2) visibleNodes[visibleCount - 1].classList.add("odd-last-child"); } }; var NewTabPopup = { _timeout: 0, _tabs: [], get box() { delete this.box; let box = document.getElementById("newtab-popup"); // Move the popup on the other side if we are in RTL let [leftSidebar, rightSidebar] = [Elements.tabs.getBoundingClientRect(), Elements.controls.getBoundingClientRect()]; if (leftSidebar.left > rightSidebar.left) { let margin = box.getAttribute("left"); box.removeAttribute("left"); box.setAttribute("right", margin); } return this.box = box; }, _updateLabel: function() { let newtabStrings = Elements.browserBundle.getString("newtabpopup.opened"); let label = PluralForm.get(this._tabs.length, newtabStrings).replace("#1", this._tabs.length); this.box.firstChild.setAttribute("value", label); }, hide: function() { if (this._timeout) { clearTimeout(this._timeout); this._timeout = 0; } this._tabs = []; this.box.hidden = true; BrowserUI.popPopup(); }, show: function(aTab) { BrowserUI.pushPopup(this, this.box); this._tabs.push(aTab); this._updateLabel(); this.box.top = aTab.getBoundingClientRect().top + (aTab.getBoundingClientRect().height / 3); this.box.hidden = false; if (this._timeout) clearTimeout(this._timeout); this._timeout = setTimeout(function(self) { self.hide(); }, 2000, this); }, selectTab: function() { BrowserUI.selectTab(this._tabs.pop()); this.hide(); } }; var AwesomePanel = function(aElementId, aCommandId) { let command = document.getElementById(aCommandId); this.panel = document.getElementById(aElementId), this.open = function aw_open() { command.setAttribute("checked", "true"); this.panel.hidden = false; if (this.panel.hasAttribute("onshow")) { let func = new Function("panel", this.panel.getAttribute("onshow")); func.call(this.panel); } if (this.panel.open) this.panel.open(); }, this.close = function aw_close() { if (this.panel.hasAttribute("onhide")) { let func = new Function("panel", this.panel.getAttribute("onhide")); func.call(this.panel); } if (this.panel.close) this.panel.close(); this.panel.hidden = true; command.removeAttribute("checked", "true"); }, this.doCommand = function aw_doCommand() { BrowserUI.doCommand(aCommandId); }, this.openLink = function aw_openLink(aEvent) { let item = aEvent.originalTarget; let uri = item.getAttribute("url") || item.getAttribute("uri"); if (uri != "") BrowserUI.goToURI(uri); } }; var BookmarkPopup = { get box() { delete this.box; this.box = document.getElementById("bookmark-popup"); let [tabsSidebar, controlsSidebar] = [Elements.tabs.getBoundingClientRect(), Elements.controls.getBoundingClientRect()]; this.box.setAttribute(tabsSidebar.left < controlsSidebar.left ? "right" : "left", controlsSidebar.width - this.box.offset); this.box.top = BrowserUI.starButton.getBoundingClientRect().top - this.box.offset; // Hide the popup if there is any new page loading let self = this; messageManager.addMessageListener("pagehide", function(aMessage) { self.hide(); }); return this.box; }, _bookmarkPopupTimeout: -1, hide : function hide() { if (this._bookmarkPopupTimeout != -1) { clearTimeout(this._bookmarkPopupTimeout); this._bookmarkPopupTimeout = -1; } this.box.hidden = true; BrowserUI.popPopup(); }, show : function show(aAutoClose) { this.box.hidden = false; this.box.anchorTo(BrowserUI.starButton); if (aAutoClose) { this._bookmarkPopupTimeout = setTimeout(function (self) { self._bookmarkPopupTimeout = -1; self.hide(); }, 2000, this); } // include starButton here, so that click-to-dismiss works as expected BrowserUI.pushPopup(this, [this.box, BrowserUI.starButton]); }, toggle : function toggle(aAutoClose) { if (this.box.hidden) this.show(aAutoClose); else this.hide(); } }; var BookmarkHelper = { _panel: null, _editor: null, edit: function BH_edit(aURI) { if (!aURI) aURI = getBrowser().currentURI; let itemId = PlacesUtils.getMostRecentBookmarkForURI(aURI); if (itemId == -1) return; let title = PlacesUtils.bookmarks.getItemTitle(itemId); let tags = PlacesUtils.tagging.getTagsForURI(aURI, {}); const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; this._editor = document.createElementNS(XULNS, "placeitem"); this._editor.setAttribute("id", "bookmark-item"); this._editor.setAttribute("flex", "1"); this._editor.setAttribute("type", "bookmark"); this._editor.setAttribute("ui", "manage"); this._editor.setAttribute("title", title); this._editor.setAttribute("uri", aURI.spec); this._editor.setAttribute("itemid", itemId); this._editor.setAttribute("tags", tags.join(", ")); this._editor.setAttribute("onclose", "BookmarkHelper.hide()"); document.getElementById("bookmark-form").appendChild(this._editor); let toolbar = document.getElementById("toolbar-main"); let top = toolbar.top + toolbar.boxObject.height; this._panel = document.getElementById("bookmark-container"); this._panel.top = (top < 0 ? 0 : top); this._panel.hidden = false; BrowserUI.pushPopup(this, this._panel); let self = this; BrowserUI.lockToolbar(); Browser.forceChromeReflow(); self._editor.startEditing(); }, save: function BH_save() { this._editor.stopEditing(true); }, hide: function BH_hide() { BrowserUI.unlockToolbar(); BrowserUI.updateStar(); // Note: the _editor will have already saved the data, if needed, by the time // this method is called, since this method is called via the "close" event. this._editor.parentNode.removeChild(this._editor); this._editor = null; this._panel.hidden = true; BrowserUI.popPopup(); }, removeBookmarksForURI: function BH_removeBookmarksForURI(aURI) { //XXX blargle xpconnect! might not matter, but a method on // nsINavBookmarksService that takes an array of items to // delete would be faster. better yet, a method that takes a URI! let itemIds = PlacesUtils.getBookmarksForURI(aURI); itemIds.forEach(PlacesUtils.bookmarks.removeItem); BrowserUI.updateStar(); } }; var FindHelperUI = { type: "find", commands: { next: "cmd_findNext", previous: "cmd_findPrevious", close: "cmd_findClose" }, _status: null, get status() { return this._status; }, set status(val) { if (val != this._status) { this._status = val; this._textbox.setAttribute("status", val); this.updateCommands(this._textbox.value); } }, init: function findHelperInit() { this._textbox = document.getElementById("find-helper-textbox"); this._container = document.getElementById("content-navigator"); this._cmdPrevious = document.getElementById(this.commands.previous); this._cmdNext = document.getElementById(this.commands.next); // Listen for form assistant messages from content messageManager.addMessageListener("FindAssist:Show", this); messageManager.addMessageListener("FindAssist:Hide", this); // Listen for events where form assistant should be closed document.getElementById("tabs").addEventListener("TabSelect", this, true); Elements.browsers.addEventListener("URLChanged", this, true); }, receiveMessage: function findHelperReceiveMessage(aMessage) { let json = aMessage.json; switch(aMessage.name) { case "FindAssist:Show": this.status = json.result; if (json.rect) this._zoom(Rect.fromRect(json.rect)); break; case "FindAssist:Hide": if (this._container.getAttribute("type") == this.type) this.hide(); break; } }, handleEvent: function findHelperHandleEvent(aEvent) { if (aEvent.type == "TabSelect" || aEvent.type == "URLChanged") this.hide(); }, show: function findHelperShow() { this._container.show(this); this.search(""); this._textbox.focus(); }, hide: function findHelperHide() { this._textbox.value = ""; this._textbox.blur(); this._container.hide(this); }, goToPrevious: function findHelperGoToPrevious() { Browser.selectedBrowser.messageManager.sendAsyncMessage("FindAssist:Previous", { }); }, goToNext: function findHelperGoToNext() { Browser.selectedBrowser.messageManager.sendAsyncMessage("FindAssist:Next", { }); }, search: function findHelperSearch(aValue) { this.updateCommands(aValue); Browser.selectedBrowser.messageManager.sendAsyncMessage("FindAssist:Find", { searchString: aValue }); }, updateCommands: function findHelperUpdateCommands(aValue) { let disabled = (this._status == Ci.nsITypeAheadFind.FIND_NOTFOUND) || (aValue == ""); this._cmdPrevious.setAttribute("disabled", disabled); this._cmdNext.setAttribute("disabled", disabled); }, _zoom: function _findHelperZoom(aElementRect) { // Zoom to a specified Rect if (aElementRect && Browser.selectedTab.allowZoom && Services.prefs.getBoolPref("findhelper.autozoom")) { let zoomLevel = Browser._getZoomLevelForRect(aElementRect); zoomLevel = Math.min(Math.max(kBrowserFormZoomLevelMin, zoomLevel), kBrowserFormZoomLevelMax); let zoomRect = Browser._getZoomRectForPoint(aElementRect.center().x, aElementRect.y, zoomLevel); Browser.animatedZoomTo(zoomRect); } } }; /** * Responsible for navigating forms and filling in information. * - Navigating forms is handled by next and previous commands. * - When an element is focused, the browser view zooms in to the control. * - The caret positionning and the view are sync to keep the type * in text into view for input fields (text/textarea). * - Provides autocomplete box for input fields. */ var FormHelperUI = { type: "form", commands: { next: "cmd_formNext", previous: "cmd_formPrevious", close: "cmd_formClose" }, //for resize/rotate case _currentCaretRect: null, _currentElementRect: null, init: function formHelperInit() { this._container = document.getElementById("content-navigator"); this._autofillContainer = document.getElementById("form-helper-autofill"); this._cmdPrevious = document.getElementById(this.commands.previous); this._cmdNext = document.getElementById(this.commands.next); this._visibleScreenArea = new Rect(0, 0, 0, 0); // Listen for form assistant messages from content messageManager.addMessageListener("FormAssist:Show", this); messageManager.addMessageListener("FormAssist:Hide", this); messageManager.addMessageListener("FormAssist:Update", this); messageManager.addMessageListener("FormAssist:Resize", this); messageManager.addMessageListener("FormAssist:AutoComplete", this); // Listen for events where form assistant should be closed document.getElementById("tabs").addEventListener("TabSelect", this, true); Elements.browsers.addEventListener("URLChanged", this, true); // Listen for modal dialog to show/hide the UI messageManager.addMessageListener("DOMWillOpenModalDialog", this); messageManager.addMessageListener("DOMModalDialogClosed", this); Services.obs.addObserver(this, "softkb-change", false); }, uninit: function formHelperUninit() { Services.obs.removeObserver(this, "softkb-change"); }, show: function formHelperShow(aElement, aHasPrevious, aHasNext) { this._open = true; // Update the next/previous commands this._cmdPrevious.setAttribute("disabled", !aHasPrevious); this._cmdNext.setAttribute("disabled", !aHasNext); let lastElement = this._currentElement || null; this._currentElement = { id: aElement.id, name: aElement.name, value: aElement.value, maxLength: aElement.maxLength, type: aElement.type, isAutocomplete: aElement.isAutocomplete, list: aElement.choices } this._updateContainer(lastElement, this._currentElement); //hide all sidebars, this will adjust the visible rect. Browser.hideSidebars(); //save the element Rect and reuse it to avoid jumps in cases the element moves slighty on the website. this._currentElementRect = Rect.fromRect(aElement.rect); this._zoom(this._currentElementRect, Rect.fromRect(aElement.caretRect)); }, hide: function formHelperHide() { if (!this._open) return; // reset current Element and Caret Rect this._currentElementRect = null; this._currentCaretRect = null; this._updateContainerForSelect(this._currentElement, null); this._open = false; }, handleEvent: function formHelperHandleEvent(aEvent) { if (aEvent.type == "TabSelect" || aEvent.type == "URLChanged") this.hide(); }, receiveMessage: function formHelperReceiveMessage(aMessage) { let json = aMessage.json; switch (aMessage.name) { case "FormAssist:Show": // if the user has manually disabled the Form Assistant UI we still // want to show a UI for