// -*- 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 * * 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"], ].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) { var browser = Browser.selectedBrowser; if (browser && aBrowser != browser) return; var url = this.getDisplayURI(browser); var 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(); }, showToolbar: function showToolbar(aEdit) { this.hidePanel(); this._editURI(aEdit); if (aEdit) this.showAutoComplete(); }, _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.isAutoCompleteOpen()) this._edit.defaultValue = aCaption; else this._edit.value = aCaption; }, _editURI: function _editURI(aEdit) { if (aEdit) { let isOpened = this._edit.hasAttribute("open"); if (!isOpened) { Elements.urlbarState.setAttribute("mode", "edit"); this._edit.defaultValue = this._edit.value; } // 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; if (!this._edit.readOnly || Util.isPortrait()) { this._edit.readOnly = false; // This is a workaround needed to cycle focus for the IME state // to be set properly (bug 488420) this._edit.blur(); gFocusManager.setFocus(this._edit, Ci.nsIFocusManager.FLAG_NOSCROLL); } else { this._edit.readOnly = !isOpened; } } else if (!aEdit) { this._updateToolbar(); } }, updateAwesomeHeader: function updateAwesomeHeader(aVisible) { document.getElementById("awesome-header").hidden = aVisible; }, _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 container = document.getElementById("awesome-panels"); if (aPanel) { container.hidden = false; aPanel.open(); } else { container.hidden = true; BrowserUI.showToolbar(false); } if (this._activePanel) this._activePanel.close(); this._activePanel = aPanel; }, 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(); }, popPopup: function popPopup() { this._popup = null; this._dispatchPopupChanged(); }, _dispatchPopupChanged: function _dispatchPopupChanged() { let stack = document.getElementById("stack"); let event = document.createEvent("Events"); event.initEvent("PopupChanged", true, false); 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(id) { let button = document.getElementsByAttribute("linkedpanel", id)[0]; if (button) button.checked = true; this.blurFocusedElement(); let pane = document.getElementById(id); 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); let awesomePopup = document.getElementById("popup_autocomplete"); awesomePopup.addEventListener("popupshown", this, false); awesomePopup.addEventListener("popuphidden", this, false); document.getElementById("toolbar-main").ignoreDrag = true; let tabs = document.getElementById("tabs"); tabs.addEventListener("TabSelect", this, true); tabs.addEventListener("TabOpen", this, true); // listen content messages messageManager.addMessageListener("DOMLinkAdded", this); messageManager.addMessageListener("DOMTitleChanged", this); messageManager.addMessageListener("DOMWillOpenModalDialog", this); messageManager.addMessageListener("DOMWindowClose", this); messageManager.addMessageListener("Browser:Highlight", this); messageManager.addMessageListener("Browser:OpenURI", this); messageManager.addMessageListener("Browser:ContextMenu", ContextHelper); 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); // Push the panel initialization out of the startup path // (Using a message because we have no good way to delay-init [Bug 535366]) messageManager.addMessageListener("DOMContentLoaded", function() { // We only want to delay one time messageManager.removeMessageListener("DOMContentLoaded", arguments.callee, true); // 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(); #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 }); 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() { var 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(); var 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(true); 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(aResetInput) { if (!this.isAutoCompleteOpen()) return; if (aResetInput) this._edit.popup.close(); else this._edit.popup.closePopup(); // Because the controller is not detached during a blur event for Meego // compatibility with the VKB, we need to detach it manually this._edit.detachController(); this.activePanel = null; }, isAutoCompleteOpen: function isAutoCompleteOpen() { return this._edit.popup.popupOpen; }, 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(false); // 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 }); }, 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.showToolbar(true); } else { // Give the new page lots of room Browser.hideSidebars(); this.closeAutoComplete(true); } 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) { Elements.panelUI.left = 0; Elements.panelUI.hidden = false; Elements.contentShowing.setAttribute("disabled", "true"); if (this.activePanel) this.activePanel = null; 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; } // 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(); #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.target) NewTabPopup.show(aEvent.target); // Workaround to hide the tabstrip if it is partially visible // See bug 524469 if (tabsVisibility > 0.0 && tabsVisibility < 1.0) Browser.hideSidebars(); 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": this.doCommand("cmd_openLocation"); break; default: break; } break; // URL textbox events case "click": this.doCommand("cmd_openLocation"); 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 "popupshown": this._edit.setAttribute("open", "true"); break; case "popuphidden": this._edit.removeAttribute("open"); this._edit.readOnly = true; 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:Highlight": let rects = []; for (let i = 0; i < json.rects.length; i++) { let rect = json.rects[i]; rects.push(new Rect(rect.left, rect.top, rect.width, rect.height)); } TapHighlightHelper.show(rects); break; case "Browser:OpenURI": Browser.addTab(json.uri, false, Browser.selectedTab); } }, 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) { return true; }, doCommand : function(cmd) { 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.showToolbar(true); 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": this._edit.blur(); MenuListHelperUI.show({ title: Elements.browserBundle.getString("opensearch.searchWith"), menupopup: { children: BrowserSearch.engines }, set selectedIndex(aIndex) { let name = this.menupopup.children[aIndex].label; BrowserUI.doOpenSearch(name); } }); 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 bv = Browser._browserView; let union = aRects.reduce(function(a, b) { return a.expandToContain(b); }, new Rect(0, 0, 0, 0)).map(bv.browserToViewport); let vis = Browser.getVisibleRect(); let canvasArea = vis.intersect(union); let overlay = this._overlay; overlay.width = canvasArea.width; overlay.style.width = canvasArea.width + "px"; overlay.height = canvasArea.height; overlay.style.height = canvasArea.height + "px"; let ctx = overlay.getContext("2d"); ctx.save(); ctx.translate(-canvasArea.left, -canvasArea.top); bv.browserToViewportCanvasContext(ctx); overlay.style.left = canvasArea.left + "px"; overlay.style.top = canvasArea.top + "px"; ctx.fillStyle = "rgba(0, 145, 255, .5)"; for (let i = aRects.length - 1; i >= 0; i--) { let rect = aRects[i]; ctx.fillRect(rect.left, rect.top, rect.width, rect.height); } ctx.restore(); overlay.style.display = "block"; }, hide: function hide() { this._overlay.style.display = "none"; } }; var PageActions = { init: function init() { this.register("pageaction-reset", this.updatePagePermissions, this); this.register("pageaction-password", this.updateForgetPassword, this); this.register("pageaction-saveas", this.updatePageSaveAs, this); this.register("pageaction-share", this.updateShare, this); this.register("pageaction-search", BrowserSearch.updatePageSearchEngines, BrowserSearch); }, /** * @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)) { 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, "", null); return logins.some(function(login) login.hostname == host.prePath); }, forgetPassword: function forgetPassword() { let host = Browser.selectedBrowser.currentURI; let lm = this._loginManager; lm.findLogins({}, host.prePath, "", null).forEach(function(login) { if (login.hostname == host.prePath) lm.removeLogin(login); }); }, clearPagePermissions: function clearPagePermissions() { 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); }, savePageAsPDF: function saveAsPDF() { 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"; let browser = Browser.selectedBrowser; let fileName = getDefaultFileName(browser.contentTitle, browser.documentURI, null, null); fileName = fileName.trim(); #ifdef MOZ_PLATFORM_MAEMO fileName = fileName.replace(/[\*\:\?]+/g, " "); #endif picker.defaultString = fileName + ".pdf"; let dm = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager); picker.displayDirectory = dm.defaultDownloadsDirectory; let rv = picker.show(); if (rv == Ci.nsIFilePicker.returnCancel) return; // 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.selectedBrowser.currentURI.spec; stmt.params.name = picker.file.leafName; stmt.params.source = current; stmt.params.target = Services.io.newFileURI(picker.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: picker.file.path }; Browser.selectedBrowser.messageManager.sendAsyncMessage("Browser:SaveAs", data); }, updatePageSaveAs: function updatePageSaveAs(aNode) { // Check for local XUL content let contentDocument = Browser.selectedBrowser.contentDocument; return !(contentDocument && contentDocument 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; let first = null, last = null; for (let i = 0; i < visibleCount; i++) { let node = visibleNodes[i]; node.removeAttribute("selector"); // Note: CSS indexes start at one, so even/odd are swapped. node.setAttribute("even", (i % 2) ? "true" : "false"); } if (visibleCount >= 1) { visibleNodes[visibleCount - 1].setAttribute("selector", "last-child"); visibleNodes[0].setAttribute("selector", "first-child"); } if (visibleCount >= 2) { visibleNodes[visibleCount - 2].setAttribute("selector", "second-last-child"); visibleNodes[0].setAttribute("selector", "first-child"); visibleNodes[1].setAttribute("selector", "second-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() { BrowserUI.pushDialog(this); 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.blur(); this.panel.hidden = true; command.removeAttribute("checked", "true"); BrowserUI.popDialog(); }, 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"); const margin = 10; let [tabsSidebar, controlsSidebar] = [Elements.tabs.getBoundingClientRect(), Elements.controls.getBoundingClientRect()]; this.box.setAttribute(tabsSidebar.left < controlsSidebar.left ? "right" : "left", controlsSidebar.width + margin); this.box.top = BrowserUI.starButton.getBoundingClientRect().top + margin; // 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; 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; Browser.forceChromeReflow(); self._editor.startEditing(); }, save: function BH_save() { this._editor.stopEditing(true); }, hide: function BH_hide() { 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" }, 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); document.getElementById("browsers").addEventListener("URLChanged", this, true); }, receiveMessage: function findHelperReceiveMessage(aMessage) { let json = aMessage.json; switch(aMessage.name) { case "FindAssist:Show": 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() { BrowserUI.pushPopup(this, this._container); Browser._browserView.ignorePageScroll(true); this._container.show(this); this.search(""); this._textbox.focus(); }, hide: function findHelperHide() { BrowserUI.popPopup(); Browser._browserView.ignorePageScroll(false); this._textbox.value = ""; 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) { this._cmdPrevious.setAttribute("disabled", aValue == ""); this._cmdNext.setAttribute("disabled", aValue == ""); }, _zoom: function _findHelperZoom(aElementRect) { let bv = Browser._browserView; let zoomRect = bv.getVisibleRect(); // Zoom to a specified Rect if (aElementRect && bv.allowZoom && Services.prefs.getBoolPref("findhelper.autozoom")) { let zoomLevel = Browser._getZoomLevelForRect(aElementRect); zoomLevel = Math.min(Math.max(kBrowserFormZoomLevelMin, zoomLevel), kBrowserFormZoomLevelMax); 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); // 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); document.getElementById("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