michael@0: #filter substitution michael@0: // -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*- michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: "use strict"; michael@0: michael@0: let Cc = Components.classes; michael@0: let Ci = Components.interfaces; michael@0: let Cu = Components.utils; michael@0: let Cr = Components.results; michael@0: michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: Cu.import("resource://gre/modules/AddonManager.jsm"); michael@0: Cu.import("resource://gre/modules/FileUtils.jsm"); michael@0: Cu.import("resource://gre/modules/JNI.jsm"); michael@0: Cu.import('resource://gre/modules/Payment.jsm'); michael@0: Cu.import("resource://gre/modules/NotificationDB.jsm"); michael@0: Cu.import("resource://gre/modules/SpatialNavigation.jsm"); michael@0: Cu.import("resource://gre/modules/UITelemetry.jsm"); michael@0: michael@0: #ifdef ACCESSIBILITY michael@0: Cu.import("resource://gre/modules/accessibility/AccessFu.jsm"); michael@0: #endif michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", michael@0: "resource://gre/modules/PluralForm.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "sendMessageToJava", michael@0: "resource://gre/modules/Messaging.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "DebuggerServer", michael@0: "resource://gre/modules/devtools/dbg-server.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "UserAgentOverrides", michael@0: "resource://gre/modules/UserAgentOverrides.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerContent", michael@0: "resource://gre/modules/LoginManagerContent.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm"); michael@0: XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm"); michael@0: michael@0: #ifdef MOZ_SAFE_BROWSING michael@0: XPCOMUtils.defineLazyModuleGetter(this, "SafeBrowsing", michael@0: "resource://gre/modules/SafeBrowsing.jsm"); michael@0: #endif michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", michael@0: "resource://gre/modules/PrivateBrowsingUtils.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "Sanitizer", michael@0: "resource://gre/modules/Sanitizer.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "Prompt", michael@0: "resource://gre/modules/Prompt.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "HelperApps", michael@0: "resource://gre/modules/HelperApps.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "SSLExceptions", michael@0: "resource://gre/modules/SSLExceptions.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "FormHistory", michael@0: "resource://gre/modules/FormHistory.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(this, "uuidgen", michael@0: "@mozilla.org/uuid-generator;1", michael@0: "nsIUUIDGenerator"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "SimpleServiceDiscovery", michael@0: "resource://gre/modules/SimpleServiceDiscovery.jsm"); michael@0: michael@0: #ifdef NIGHTLY_BUILD michael@0: XPCOMUtils.defineLazyModuleGetter(this, "ShumwayUtils", michael@0: "resource://shumway/ShumwayUtils.jsm"); michael@0: #endif michael@0: michael@0: #ifdef MOZ_ANDROID_SYNTHAPKS michael@0: XPCOMUtils.defineLazyModuleGetter(this, "WebappManager", michael@0: "resource://gre/modules/WebappManager.jsm"); michael@0: #endif michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "CharsetMenu", michael@0: "resource://gre/modules/CharsetMenu.jsm"); michael@0: michael@0: // Lazily-loaded browser scripts: michael@0: [ michael@0: ["SelectHelper", "chrome://browser/content/SelectHelper.js"], michael@0: ["InputWidgetHelper", "chrome://browser/content/InputWidgetHelper.js"], michael@0: ["AboutReader", "chrome://browser/content/aboutReader.js"], michael@0: ["MasterPassword", "chrome://browser/content/MasterPassword.js"], michael@0: ["PluginHelper", "chrome://browser/content/PluginHelper.js"], michael@0: ["OfflineApps", "chrome://browser/content/OfflineApps.js"], michael@0: ["Linkifier", "chrome://browser/content/Linkify.js"], michael@0: ["ZoomHelper", "chrome://browser/content/ZoomHelper.js"], michael@0: ["CastingApps", "chrome://browser/content/CastingApps.js"], michael@0: ].forEach(function (aScript) { michael@0: let [name, script] = aScript; michael@0: XPCOMUtils.defineLazyGetter(window, name, function() { michael@0: let sandbox = {}; michael@0: Services.scriptloader.loadSubScript(script, sandbox); michael@0: return sandbox[name]; michael@0: }); michael@0: }); michael@0: michael@0: [ michael@0: #ifdef MOZ_WEBRTC michael@0: ["WebrtcUI", ["getUserMedia:request", "recording-device-events"], "chrome://browser/content/WebrtcUI.js"], michael@0: #endif michael@0: ["MemoryObserver", ["memory-pressure", "Memory:Dump"], "chrome://browser/content/MemoryObserver.js"], michael@0: ["ConsoleAPI", ["console-api-log-event"], "chrome://browser/content/ConsoleAPI.js"], michael@0: ["FindHelper", ["FindInPage:Find", "FindInPage:Prev", "FindInPage:Next", "FindInPage:Closed", "Tab:Selected"], "chrome://browser/content/FindHelper.js"], michael@0: ["PermissionsHelper", ["Permissions:Get", "Permissions:Clear"], "chrome://browser/content/PermissionsHelper.js"], michael@0: ["FeedHandler", ["Feeds:Subscribe"], "chrome://browser/content/FeedHandler.js"], michael@0: ["Feedback", ["Feedback:Show"], "chrome://browser/content/Feedback.js"], michael@0: ["SelectionHandler", ["TextSelection:Get"], "chrome://browser/content/SelectionHandler.js"], michael@0: ].forEach(function (aScript) { michael@0: let [name, notifications, script] = aScript; michael@0: XPCOMUtils.defineLazyGetter(window, name, function() { michael@0: let sandbox = {}; michael@0: Services.scriptloader.loadSubScript(script, sandbox); michael@0: return sandbox[name]; michael@0: }); michael@0: notifications.forEach(function (aNotification) { michael@0: Services.obs.addObserver(function(s, t, d) { michael@0: window[name].observe(s, t, d) michael@0: }, aNotification, false); michael@0: }); michael@0: }); michael@0: michael@0: // Lazily-loaded JS modules that use observer notifications michael@0: [ michael@0: ["Home", ["HomeBanner:Get", "HomePanels:Get", "HomePanels:Authenticate", "HomePanels:RefreshView", michael@0: "HomePanels:Installed", "HomePanels:Uninstalled"], "resource://gre/modules/Home.jsm"], michael@0: ].forEach(module => { michael@0: let [name, notifications, resource] = module; michael@0: XPCOMUtils.defineLazyModuleGetter(this, name, resource); michael@0: notifications.forEach(notification => { michael@0: Services.obs.addObserver((s,t,d) => { michael@0: this[name].observe(s,t,d) michael@0: }, notification, false); michael@0: }); michael@0: }); michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(this, "Haptic", michael@0: "@mozilla.org/widget/hapticfeedback;1", "nsIHapticFeedback"); michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(this, "DOMUtils", michael@0: "@mozilla.org/inspector/dom-utils;1", "inIDOMUtils"); michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(window, "URIFixup", michael@0: "@mozilla.org/docshell/urifixup;1", "nsIURIFixup"); michael@0: michael@0: #ifdef MOZ_WEBRTC michael@0: XPCOMUtils.defineLazyServiceGetter(this, "MediaManagerService", michael@0: "@mozilla.org/mediaManagerService;1", "nsIMediaManagerService"); michael@0: #endif michael@0: michael@0: const kStateActive = 0x00000001; // :active pseudoclass for elements michael@0: michael@0: const kXLinkNamespace = "http://www.w3.org/1999/xlink"; michael@0: michael@0: const kDefaultCSSViewportWidth = 980; michael@0: const kDefaultCSSViewportHeight = 480; michael@0: michael@0: const kViewportRemeasureThrottle = 500; michael@0: michael@0: const kDoNotTrackPrefState = Object.freeze({ michael@0: NO_PREF: "0", michael@0: DISALLOW_TRACKING: "1", michael@0: ALLOW_TRACKING: "2", michael@0: }); michael@0: michael@0: function dump(a) { michael@0: Services.console.logStringMessage(a); michael@0: } michael@0: michael@0: function doChangeMaxLineBoxWidth(aWidth) { michael@0: gReflowPending = null; michael@0: let webNav = BrowserApp.selectedTab.window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation); michael@0: let docShell = webNav.QueryInterface(Ci.nsIDocShell); michael@0: let docViewer = docShell.contentViewer.QueryInterface(Ci.nsIMarkupDocumentViewer); michael@0: michael@0: let range = null; michael@0: if (BrowserApp.selectedTab._mReflozPoint) { michael@0: range = BrowserApp.selectedTab._mReflozPoint.range; michael@0: } michael@0: michael@0: try { michael@0: docViewer.pausePainting(); michael@0: docViewer.changeMaxLineBoxWidth(aWidth); michael@0: michael@0: if (range) { michael@0: ZoomHelper.zoomInAndSnapToRange(range); michael@0: } else { michael@0: // In this case, we actually didn't zoom into a specific range. It michael@0: // probably happened from a page load reflow-on-zoom event, so we michael@0: // need to make sure painting is re-enabled. michael@0: BrowserApp.selectedTab.clearReflowOnZoomPendingActions(); michael@0: } michael@0: } finally { michael@0: docViewer.resumePainting(); michael@0: } michael@0: } michael@0: michael@0: function fuzzyEquals(a, b) { michael@0: return (Math.abs(a - b) < 1e-6); michael@0: } michael@0: michael@0: /** michael@0: * Convert a font size to CSS pixels (px) from twentieiths-of-a-point michael@0: * (twips). michael@0: */ michael@0: function convertFromTwipsToPx(aSize) { michael@0: return aSize/240 * 16.0; michael@0: } michael@0: michael@0: #ifdef MOZ_CRASHREPORTER michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: XPCOMUtils.defineLazyServiceGetter(this, "CrashReporter", michael@0: "@mozilla.org/xre/app-info;1", "nsICrashReporter"); michael@0: #endif michael@0: michael@0: XPCOMUtils.defineLazyGetter(this, "ContentAreaUtils", function() { michael@0: let ContentAreaUtils = {}; michael@0: Services.scriptloader.loadSubScript("chrome://global/content/contentAreaUtils.js", ContentAreaUtils); michael@0: return ContentAreaUtils; michael@0: }); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "Rect", michael@0: "resource://gre/modules/Geometry.jsm"); michael@0: michael@0: function resolveGeckoURI(aURI) { michael@0: if (!aURI) michael@0: throw "Can't resolve an empty uri"; michael@0: michael@0: if (aURI.startsWith("chrome://")) { michael@0: let registry = Cc['@mozilla.org/chrome/chrome-registry;1'].getService(Ci["nsIChromeRegistry"]); michael@0: return registry.convertChromeURL(Services.io.newURI(aURI, null, null)).spec; michael@0: } else if (aURI.startsWith("resource://")) { michael@0: let handler = Services.io.getProtocolHandler("resource").QueryInterface(Ci.nsIResProtocolHandler); michael@0: return handler.resolveURI(Services.io.newURI(aURI, null, null)); michael@0: } michael@0: return aURI; michael@0: } michael@0: michael@0: /** michael@0: * Cache of commonly used string bundles. michael@0: */ michael@0: var Strings = {}; michael@0: [ michael@0: ["brand", "chrome://branding/locale/brand.properties"], michael@0: ["browser", "chrome://browser/locale/browser.properties"] michael@0: ].forEach(function (aStringBundle) { michael@0: let [name, bundle] = aStringBundle; michael@0: XPCOMUtils.defineLazyGetter(Strings, name, function() { michael@0: return Services.strings.createBundle(bundle); michael@0: }); michael@0: }); michael@0: michael@0: const kFormHelperModeDisabled = 0; michael@0: const kFormHelperModeEnabled = 1; michael@0: const kFormHelperModeDynamic = 2; // disabled on tablets michael@0: michael@0: var BrowserApp = { michael@0: _tabs: [], michael@0: _selectedTab: null, michael@0: _prefObservers: [], michael@0: isGuest: false, michael@0: michael@0: get isTablet() { michael@0: let sysInfo = Cc["@mozilla.org/system-info;1"].getService(Ci.nsIPropertyBag2); michael@0: delete this.isTablet; michael@0: return this.isTablet = sysInfo.get("tablet"); michael@0: }, michael@0: michael@0: get isOnLowMemoryPlatform() { michael@0: let memory = Cc["@mozilla.org/xpcom/memory-service;1"].getService(Ci.nsIMemory); michael@0: delete this.isOnLowMemoryPlatform; michael@0: return this.isOnLowMemoryPlatform = memory.isLowMemoryPlatform(); michael@0: }, michael@0: michael@0: deck: null, michael@0: michael@0: startup: function startup() { michael@0: window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow = new nsBrowserAccess(); michael@0: dump("zerdatime " + Date.now() + " - browser chrome startup finished."); michael@0: michael@0: this.deck = document.getElementById("browsers"); michael@0: this.deck.addEventListener("DOMContentLoaded", function BrowserApp_delayedStartup() { michael@0: try { michael@0: BrowserApp.deck.removeEventListener("DOMContentLoaded", BrowserApp_delayedStartup, false); michael@0: Services.obs.notifyObservers(window, "browser-delayed-startup-finished", ""); michael@0: sendMessageToJava({ type: "Gecko:DelayedStartup" }); michael@0: } catch(ex) { console.log(ex); } michael@0: }, false); michael@0: michael@0: BrowserEventHandler.init(); michael@0: ViewportHandler.init(); michael@0: michael@0: Services.androidBridge.browserApp = this; michael@0: michael@0: Services.obs.addObserver(this, "Locale:Changed", false); michael@0: Services.obs.addObserver(this, "Tab:Load", false); michael@0: Services.obs.addObserver(this, "Tab:Selected", false); michael@0: Services.obs.addObserver(this, "Tab:Closed", false); michael@0: Services.obs.addObserver(this, "Session:Back", false); michael@0: Services.obs.addObserver(this, "Session:ShowHistory", false); michael@0: Services.obs.addObserver(this, "Session:Forward", false); michael@0: Services.obs.addObserver(this, "Session:Reload", false); michael@0: Services.obs.addObserver(this, "Session:Stop", false); michael@0: Services.obs.addObserver(this, "SaveAs:PDF", false); michael@0: Services.obs.addObserver(this, "Browser:Quit", false); michael@0: Services.obs.addObserver(this, "Preferences:Set", false); michael@0: Services.obs.addObserver(this, "ScrollTo:FocusedInput", false); michael@0: Services.obs.addObserver(this, "Sanitize:ClearData", false); michael@0: Services.obs.addObserver(this, "FullScreen:Exit", false); michael@0: Services.obs.addObserver(this, "Viewport:Change", false); michael@0: Services.obs.addObserver(this, "Viewport:Flush", false); michael@0: Services.obs.addObserver(this, "Viewport:FixedMarginsChanged", false); michael@0: Services.obs.addObserver(this, "Passwords:Init", false); michael@0: Services.obs.addObserver(this, "FormHistory:Init", false); michael@0: Services.obs.addObserver(this, "gather-telemetry", false); michael@0: Services.obs.addObserver(this, "keyword-search", false); michael@0: #ifdef MOZ_ANDROID_SYNTHAPKS michael@0: Services.obs.addObserver(this, "webapps-runtime-install", false); michael@0: Services.obs.addObserver(this, "webapps-runtime-install-package", false); michael@0: Services.obs.addObserver(this, "webapps-ask-install", false); michael@0: Services.obs.addObserver(this, "webapps-launch", false); michael@0: Services.obs.addObserver(this, "webapps-uninstall", false); michael@0: Services.obs.addObserver(this, "Webapps:AutoInstall", false); michael@0: Services.obs.addObserver(this, "Webapps:Load", false); michael@0: Services.obs.addObserver(this, "Webapps:AutoUninstall", false); michael@0: #endif michael@0: Services.obs.addObserver(this, "sessionstore-state-purge-complete", false); michael@0: michael@0: function showFullScreenWarning() { michael@0: NativeWindow.toast.show(Strings.browser.GetStringFromName("alertFullScreenToast"), "short"); michael@0: } michael@0: michael@0: window.addEventListener("fullscreen", function() { michael@0: sendMessageToJava({ michael@0: type: window.fullScreen ? "ToggleChrome:Show" : "ToggleChrome:Hide" michael@0: }); michael@0: }, false); michael@0: michael@0: window.addEventListener("mozfullscreenchange", function() { michael@0: sendMessageToJava({ michael@0: type: document.mozFullScreen ? "DOMFullScreen:Start" : "DOMFullScreen:Stop" michael@0: }); michael@0: michael@0: if (document.mozFullScreen) michael@0: showFullScreenWarning(); michael@0: }, false); michael@0: michael@0: // When a restricted key is pressed in DOM full-screen mode, we should display michael@0: // the "Press ESC to exit" warning message. michael@0: window.addEventListener("MozShowFullScreenWarning", showFullScreenWarning, true); michael@0: michael@0: NativeWindow.init(); michael@0: LightWeightThemeWebInstaller.init(); michael@0: Downloads.init(); michael@0: FormAssistant.init(); michael@0: IndexedDB.init(); michael@0: HealthReportStatusListener.init(); michael@0: XPInstallObserver.init(); michael@0: CharacterEncoding.init(); michael@0: ActivityObserver.init(); michael@0: #ifdef MOZ_ANDROID_SYNTHAPKS michael@0: // TODO: replace with Android implementation of WebappOSUtils.isLaunchable. michael@0: Cu.import("resource://gre/modules/Webapps.jsm"); michael@0: DOMApplicationRegistry.allAppsLaunchable = true; michael@0: #else michael@0: WebappsUI.init(); michael@0: #endif michael@0: RemoteDebugger.init(); michael@0: Reader.init(); michael@0: UserAgentOverrides.init(); michael@0: DesktopUserAgent.init(); michael@0: CastingApps.init(); michael@0: Distribution.init(); michael@0: Tabs.init(); michael@0: #ifdef ACCESSIBILITY michael@0: AccessFu.attach(window); michael@0: #endif michael@0: #ifdef NIGHTLY_BUILD michael@0: ShumwayUtils.init(); michael@0: #endif michael@0: michael@0: // Init LoginManager michael@0: Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager); michael@0: michael@0: let url = null; michael@0: let pinned = false; michael@0: if ("arguments" in window) { michael@0: if (window.arguments[0]) michael@0: url = window.arguments[0]; michael@0: if (window.arguments[1]) michael@0: gScreenWidth = window.arguments[1]; michael@0: if (window.arguments[2]) michael@0: gScreenHeight = window.arguments[2]; michael@0: if (window.arguments[3]) michael@0: pinned = window.arguments[3]; michael@0: if (window.arguments[4]) michael@0: this.isGuest = window.arguments[4]; michael@0: } michael@0: michael@0: if (pinned) { michael@0: this._initRuntime(this._startupStatus, url, aUrl => this.addTab(aUrl)); michael@0: } else { michael@0: SearchEngines.init(); michael@0: this.initContextMenu(); michael@0: } michael@0: // The order that context menu items are added is important michael@0: // Make sure the "Open in App" context menu item appears at the bottom of the list michael@0: ExternalApps.init(); michael@0: michael@0: // XXX maybe we don't do this if the launch was kicked off from external michael@0: Services.io.offline = false; michael@0: michael@0: // Broadcast a UIReady message so add-ons know we are finished with startup michael@0: let event = document.createEvent("Events"); michael@0: event.initEvent("UIReady", true, false); michael@0: window.dispatchEvent(event); michael@0: michael@0: if (this._startupStatus) michael@0: this.onAppUpdated(); michael@0: michael@0: // Store the low-precision buffer pref michael@0: this.gUseLowPrecision = Services.prefs.getBoolPref("layers.low-precision-buffer"); michael@0: michael@0: // notify java that gecko has loaded michael@0: sendMessageToJava({ type: "Gecko:Ready" }); michael@0: michael@0: #ifdef MOZ_SAFE_BROWSING michael@0: // Bug 778855 - Perf regression if we do this here. To be addressed in bug 779008. michael@0: setTimeout(function() { SafeBrowsing.init(); }, 5000); michael@0: #endif michael@0: }, michael@0: michael@0: get _startupStatus() { michael@0: delete this._startupStatus; michael@0: michael@0: let savedMilestone = null; michael@0: try { michael@0: savedMilestone = Services.prefs.getCharPref("browser.startup.homepage_override.mstone"); michael@0: } catch (e) { michael@0: } michael@0: #expand let ourMilestone = "__MOZ_APP_VERSION__"; michael@0: this._startupStatus = ""; michael@0: if (ourMilestone != savedMilestone) { michael@0: Services.prefs.setCharPref("browser.startup.homepage_override.mstone", ourMilestone); michael@0: this._startupStatus = savedMilestone ? "upgrade" : "new"; michael@0: } michael@0: michael@0: return this._startupStatus; michael@0: }, michael@0: michael@0: /** michael@0: * Pass this a locale string, such as "fr" or "es_ES". michael@0: */ michael@0: setLocale: function (locale) { michael@0: console.log("browser.js: requesting locale set: " + locale); michael@0: sendMessageToJava({ type: "Locale:Set", locale: locale }); michael@0: }, michael@0: michael@0: _initRuntime: function(status, url, callback) { michael@0: let sandbox = {}; michael@0: Services.scriptloader.loadSubScript("chrome://browser/content/WebappRT.js", sandbox); michael@0: window.WebappRT = sandbox.WebappRT; michael@0: WebappRT.init(status, url, callback); michael@0: }, michael@0: michael@0: initContextMenu: function ba_initContextMenu() { michael@0: // TODO: These should eventually move into more appropriate classes michael@0: NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.openInNewTab"), michael@0: NativeWindow.contextmenus.linkOpenableNonPrivateContext, michael@0: function(aTarget) { michael@0: UITelemetry.addEvent("action.1", "contextmenu", null, "web_open_new_tab"); michael@0: UITelemetry.addEvent("loadurl.1", "contextmenu", null); michael@0: michael@0: let url = NativeWindow.contextmenus._getLinkURL(aTarget); michael@0: ContentAreaUtils.urlSecurityCheck(url, aTarget.ownerDocument.nodePrincipal); michael@0: BrowserApp.addTab(url, { selected: false, parentId: BrowserApp.selectedTab.id }); michael@0: michael@0: let newtabStrings = Strings.browser.GetStringFromName("newtabpopup.opened"); michael@0: let label = PluralForm.get(1, newtabStrings).replace("#1", 1); michael@0: NativeWindow.toast.show(label, "short"); michael@0: }); michael@0: michael@0: NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.openInPrivateTab"), michael@0: NativeWindow.contextmenus.linkOpenableContext, michael@0: function(aTarget) { michael@0: UITelemetry.addEvent("action.1", "contextmenu", null, "web_open_private_tab"); michael@0: UITelemetry.addEvent("loadurl.1", "contextmenu", null); michael@0: michael@0: let url = NativeWindow.contextmenus._getLinkURL(aTarget); michael@0: ContentAreaUtils.urlSecurityCheck(url, aTarget.ownerDocument.nodePrincipal); michael@0: BrowserApp.addTab(url, { selected: false, parentId: BrowserApp.selectedTab.id, isPrivate: true }); michael@0: michael@0: let newtabStrings = Strings.browser.GetStringFromName("newprivatetabpopup.opened"); michael@0: let label = PluralForm.get(1, newtabStrings).replace("#1", 1); michael@0: NativeWindow.toast.show(label, "short"); michael@0: }); michael@0: michael@0: NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.copyLink"), michael@0: NativeWindow.contextmenus.linkCopyableContext, michael@0: function(aTarget) { michael@0: UITelemetry.addEvent("action.1", "contextmenu", null, "web_copy_link"); michael@0: michael@0: let url = NativeWindow.contextmenus._getLinkURL(aTarget); michael@0: NativeWindow.contextmenus._copyStringToDefaultClipboard(url); michael@0: }); michael@0: michael@0: NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.copyEmailAddress"), michael@0: NativeWindow.contextmenus.emailLinkContext, michael@0: function(aTarget) { michael@0: UITelemetry.addEvent("action.1", "contextmenu", null, "web_copy_email"); michael@0: michael@0: let url = NativeWindow.contextmenus._getLinkURL(aTarget); michael@0: let emailAddr = NativeWindow.contextmenus._stripScheme(url); michael@0: NativeWindow.contextmenus._copyStringToDefaultClipboard(emailAddr); michael@0: }); michael@0: michael@0: NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.copyPhoneNumber"), michael@0: NativeWindow.contextmenus.phoneNumberLinkContext, michael@0: function(aTarget) { michael@0: UITelemetry.addEvent("action.1", "contextmenu", null, "web_copy_phone"); michael@0: michael@0: let url = NativeWindow.contextmenus._getLinkURL(aTarget); michael@0: let phoneNumber = NativeWindow.contextmenus._stripScheme(url); michael@0: NativeWindow.contextmenus._copyStringToDefaultClipboard(phoneNumber); michael@0: }); michael@0: michael@0: NativeWindow.contextmenus.add({ michael@0: label: Strings.browser.GetStringFromName("contextmenu.shareLink"), michael@0: order: NativeWindow.contextmenus.DEFAULT_HTML5_ORDER - 1, // Show above HTML5 menu items michael@0: selector: NativeWindow.contextmenus._disableInGuest(NativeWindow.contextmenus.linkShareableContext), michael@0: showAsActions: function(aElement) { michael@0: return { michael@0: title: aElement.textContent.trim() || aElement.title.trim(), michael@0: uri: NativeWindow.contextmenus._getLinkURL(aElement), michael@0: }; michael@0: }, michael@0: icon: "drawable://ic_menu_share", michael@0: callback: function(aTarget) { michael@0: UITelemetry.addEvent("action.1", "contextmenu", null, "web_share_link"); michael@0: } michael@0: }); michael@0: michael@0: NativeWindow.contextmenus.add({ michael@0: label: Strings.browser.GetStringFromName("contextmenu.shareEmailAddress"), michael@0: order: NativeWindow.contextmenus.DEFAULT_HTML5_ORDER - 1, michael@0: selector: NativeWindow.contextmenus._disableInGuest(NativeWindow.contextmenus.emailLinkContext), michael@0: showAsActions: function(aElement) { michael@0: let url = NativeWindow.contextmenus._getLinkURL(aElement); michael@0: let emailAddr = NativeWindow.contextmenus._stripScheme(url); michael@0: let title = aElement.textContent || aElement.title; michael@0: return { michael@0: title: title, michael@0: uri: emailAddr, michael@0: }; michael@0: }, michael@0: icon: "drawable://ic_menu_share", michael@0: callback: function(aTarget) { michael@0: UITelemetry.addEvent("action.1", "contextmenu", null, "web_share_email"); michael@0: } michael@0: }); michael@0: michael@0: NativeWindow.contextmenus.add({ michael@0: label: Strings.browser.GetStringFromName("contextmenu.sharePhoneNumber"), michael@0: order: NativeWindow.contextmenus.DEFAULT_HTML5_ORDER - 1, michael@0: selector: NativeWindow.contextmenus._disableInGuest(NativeWindow.contextmenus.phoneNumberLinkContext), michael@0: showAsActions: function(aElement) { michael@0: let url = NativeWindow.contextmenus._getLinkURL(aElement); michael@0: let phoneNumber = NativeWindow.contextmenus._stripScheme(url); michael@0: let title = aElement.textContent || aElement.title; michael@0: return { michael@0: title: title, michael@0: uri: phoneNumber, michael@0: }; michael@0: }, michael@0: icon: "drawable://ic_menu_share", michael@0: callback: function(aTarget) { michael@0: UITelemetry.addEvent("action.1", "contextmenu", null, "web_share_phone"); michael@0: } michael@0: }); michael@0: michael@0: NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.addToContacts"), michael@0: NativeWindow.contextmenus._disableInGuest(NativeWindow.contextmenus.emailLinkContext), michael@0: function(aTarget) { michael@0: UITelemetry.addEvent("action.1", "contextmenu", null, "web_contact_email"); michael@0: michael@0: let url = NativeWindow.contextmenus._getLinkURL(aTarget); michael@0: sendMessageToJava({ michael@0: type: "Contact:Add", michael@0: email: url michael@0: }); michael@0: }); michael@0: michael@0: NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.addToContacts"), michael@0: NativeWindow.contextmenus._disableInGuest(NativeWindow.contextmenus.phoneNumberLinkContext), michael@0: function(aTarget) { michael@0: UITelemetry.addEvent("action.1", "contextmenu", null, "web_contact_phone"); michael@0: michael@0: let url = NativeWindow.contextmenus._getLinkURL(aTarget); michael@0: sendMessageToJava({ michael@0: type: "Contact:Add", michael@0: phone: url michael@0: }); michael@0: }); michael@0: michael@0: NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.bookmarkLink"), michael@0: NativeWindow.contextmenus._disableInGuest(NativeWindow.contextmenus.linkBookmarkableContext), michael@0: function(aTarget) { michael@0: UITelemetry.addEvent("action.1", "contextmenu", null, "web_bookmark"); michael@0: michael@0: let url = NativeWindow.contextmenus._getLinkURL(aTarget); michael@0: let title = aTarget.textContent || aTarget.title || url; michael@0: sendMessageToJava({ michael@0: type: "Bookmark:Insert", michael@0: url: url, michael@0: title: title michael@0: }); michael@0: }); michael@0: michael@0: NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.playMedia"), michael@0: NativeWindow.contextmenus.mediaContext("media-paused"), michael@0: function(aTarget) { michael@0: UITelemetry.addEvent("action.1", "contextmenu", null, "web_play"); michael@0: aTarget.play(); michael@0: }); michael@0: michael@0: NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.pauseMedia"), michael@0: NativeWindow.contextmenus.mediaContext("media-playing"), michael@0: function(aTarget) { michael@0: UITelemetry.addEvent("action.1", "contextmenu", null, "web_pause"); michael@0: aTarget.pause(); michael@0: }); michael@0: michael@0: NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.showControls2"), michael@0: NativeWindow.contextmenus.mediaContext("media-hidingcontrols"), michael@0: function(aTarget) { michael@0: UITelemetry.addEvent("action.1", "contextmenu", null, "web_controls_media"); michael@0: aTarget.setAttribute("controls", true); michael@0: }); michael@0: michael@0: NativeWindow.contextmenus.add({ michael@0: label: Strings.browser.GetStringFromName("contextmenu.shareMedia"), michael@0: order: NativeWindow.contextmenus.DEFAULT_HTML5_ORDER - 1, michael@0: selector: NativeWindow.contextmenus._disableInGuest(NativeWindow.contextmenus.SelectorContext("video")), michael@0: showAsActions: function(aElement) { michael@0: let url = (aElement.currentSrc || aElement.src); michael@0: let title = aElement.textContent || aElement.title; michael@0: return { michael@0: title: title, michael@0: uri: url, michael@0: type: "video/*", michael@0: }; michael@0: }, michael@0: icon: "drawable://ic_menu_share", michael@0: callback: function(aTarget) { michael@0: UITelemetry.addEvent("action.1", "contextmenu", null, "web_share_media"); michael@0: } michael@0: }); michael@0: michael@0: NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.fullScreen"), michael@0: NativeWindow.contextmenus.SelectorContext("video:not(:-moz-full-screen)"), michael@0: function(aTarget) { michael@0: UITelemetry.addEvent("action.1", "contextmenu", null, "web_fullscreen"); michael@0: aTarget.mozRequestFullScreen(); michael@0: }); michael@0: michael@0: NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.mute"), michael@0: NativeWindow.contextmenus.mediaContext("media-unmuted"), michael@0: function(aTarget) { michael@0: UITelemetry.addEvent("action.1", "contextmenu", null, "web_mute"); michael@0: aTarget.muted = true; michael@0: }); michael@0: michael@0: NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.unmute"), michael@0: NativeWindow.contextmenus.mediaContext("media-muted"), michael@0: function(aTarget) { michael@0: UITelemetry.addEvent("action.1", "contextmenu", null, "web_unmute"); michael@0: aTarget.muted = false; michael@0: }); michael@0: michael@0: NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.copyImageLocation"), michael@0: NativeWindow.contextmenus.imageLocationCopyableContext, michael@0: function(aTarget) { michael@0: UITelemetry.addEvent("action.1", "contextmenu", null, "web_copy_image"); michael@0: michael@0: let url = aTarget.src; michael@0: NativeWindow.contextmenus._copyStringToDefaultClipboard(url); michael@0: }); michael@0: michael@0: NativeWindow.contextmenus.add({ michael@0: label: Strings.browser.GetStringFromName("contextmenu.shareImage"), michael@0: selector: NativeWindow.contextmenus._disableInGuest(NativeWindow.contextmenus.imageSaveableContext), michael@0: order: NativeWindow.contextmenus.DEFAULT_HTML5_ORDER - 1, // Show above HTML5 menu items michael@0: showAsActions: function(aTarget) { michael@0: let doc = aTarget.ownerDocument; michael@0: let imageCache = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools) michael@0: .getImgCacheForDocument(doc); michael@0: let props = imageCache.findEntryProperties(aTarget.currentURI, doc.characterSet); michael@0: let src = aTarget.src; michael@0: return { michael@0: title: src, michael@0: uri: src, michael@0: type: "image/*", michael@0: }; michael@0: }, michael@0: icon: "drawable://ic_menu_share", michael@0: menu: true, michael@0: callback: function(aTarget) { michael@0: UITelemetry.addEvent("action.1", "contextmenu", null, "web_share_image"); michael@0: } michael@0: }); michael@0: michael@0: NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.saveImage"), michael@0: NativeWindow.contextmenus.imageSaveableContext, michael@0: function(aTarget) { michael@0: UITelemetry.addEvent("action.1", "contextmenu", null, "web_save_image"); michael@0: michael@0: ContentAreaUtils.saveImageURL(aTarget.currentURI.spec, null, "SaveImageTitle", michael@0: false, true, aTarget.ownerDocument.documentURIObject, michael@0: aTarget.ownerDocument); michael@0: }); michael@0: michael@0: NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.setImageAs"), michael@0: NativeWindow.contextmenus._disableInGuest(NativeWindow.contextmenus.imageSaveableContext), michael@0: function(aTarget) { michael@0: UITelemetry.addEvent("action.1", "contextmenu", null, "web_background_image"); michael@0: michael@0: let src = aTarget.src; michael@0: sendMessageToJava({ michael@0: type: "Image:SetAs", michael@0: url: src michael@0: }); michael@0: }); michael@0: michael@0: NativeWindow.contextmenus.add( michael@0: function(aTarget) { michael@0: if (aTarget instanceof HTMLVideoElement) { michael@0: // If a video element is zero width or height, its essentially michael@0: // an HTMLAudioElement. michael@0: if (aTarget.videoWidth == 0 || aTarget.videoHeight == 0 ) michael@0: return Strings.browser.GetStringFromName("contextmenu.saveAudio"); michael@0: return Strings.browser.GetStringFromName("contextmenu.saveVideo"); michael@0: } else if (aTarget instanceof HTMLAudioElement) { michael@0: return Strings.browser.GetStringFromName("contextmenu.saveAudio"); michael@0: } michael@0: return Strings.browser.GetStringFromName("contextmenu.saveVideo"); michael@0: }, NativeWindow.contextmenus.mediaSaveableContext, michael@0: function(aTarget) { michael@0: UITelemetry.addEvent("action.1", "contextmenu", null, "web_save_media"); michael@0: michael@0: let url = aTarget.currentSrc || aTarget.src; michael@0: let filePickerTitleKey = (aTarget instanceof HTMLVideoElement && michael@0: (aTarget.videoWidth != 0 && aTarget.videoHeight != 0)) michael@0: ? "SaveVideoTitle" : "SaveAudioTitle"; michael@0: // Skipped trying to pull MIME type out of cache for now michael@0: ContentAreaUtils.internalSave(url, null, null, null, null, false, michael@0: filePickerTitleKey, null, aTarget.ownerDocument.documentURIObject, michael@0: aTarget.ownerDocument, true, null); michael@0: }); michael@0: }, michael@0: michael@0: onAppUpdated: function() { michael@0: // initialize the form history and passwords databases on upgrades michael@0: Services.obs.notifyObservers(null, "FormHistory:Init", ""); michael@0: Services.obs.notifyObservers(null, "Passwords:Init", ""); michael@0: michael@0: // Migrate user-set "plugins.click_to_play" pref. See bug 884694. michael@0: // Because the default value is true, a user-set pref means that the pref was set to false. michael@0: if (Services.prefs.prefHasUserValue("plugins.click_to_play")) { michael@0: Services.prefs.setIntPref("plugin.default.state", Ci.nsIPluginTag.STATE_ENABLED); michael@0: Services.prefs.clearUserPref("plugins.click_to_play"); michael@0: } michael@0: }, michael@0: michael@0: shutdown: function shutdown() { michael@0: NativeWindow.uninit(); michael@0: LightWeightThemeWebInstaller.uninit(); michael@0: FormAssistant.uninit(); michael@0: IndexedDB.uninit(); michael@0: ViewportHandler.uninit(); michael@0: XPInstallObserver.uninit(); michael@0: HealthReportStatusListener.uninit(); michael@0: CharacterEncoding.uninit(); michael@0: SearchEngines.uninit(); michael@0: #ifndef MOZ_ANDROID_SYNTHAPKS michael@0: WebappsUI.uninit(); michael@0: #endif michael@0: RemoteDebugger.uninit(); michael@0: Reader.uninit(); michael@0: UserAgentOverrides.uninit(); michael@0: DesktopUserAgent.uninit(); michael@0: ExternalApps.uninit(); michael@0: CastingApps.uninit(); michael@0: Distribution.uninit(); michael@0: Tabs.uninit(); michael@0: }, michael@0: michael@0: // This function returns false during periods where the browser displayed document is michael@0: // different from the browser content document, so user actions and some kinds of viewport michael@0: // updates should be ignored. This period starts when we start loading a new page or michael@0: // switch tabs, and ends when the new browser content document has been drawn and handed michael@0: // off to the compositor. michael@0: isBrowserContentDocumentDisplayed: function() { michael@0: try { michael@0: if (!Services.androidBridge.isContentDocumentDisplayed()) michael@0: return false; michael@0: } catch (e) { michael@0: return false; michael@0: } michael@0: michael@0: let tab = this.selectedTab; michael@0: if (!tab) michael@0: return false; michael@0: return tab.contentDocumentIsDisplayed; michael@0: }, michael@0: michael@0: contentDocumentChanged: function() { michael@0: window.top.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).isFirstPaint = true; michael@0: Services.androidBridge.contentDocumentChanged(); michael@0: }, michael@0: michael@0: get tabs() { michael@0: return this._tabs; michael@0: }, michael@0: michael@0: get selectedTab() { michael@0: return this._selectedTab; michael@0: }, michael@0: michael@0: set selectedTab(aTab) { michael@0: if (this._selectedTab == aTab) michael@0: return; michael@0: michael@0: if (this._selectedTab) { michael@0: this._selectedTab.setActive(false); michael@0: } michael@0: michael@0: this._selectedTab = aTab; michael@0: if (!aTab) michael@0: return; michael@0: michael@0: aTab.setActive(true); michael@0: aTab.setResolution(aTab._zoom, true); michael@0: this.contentDocumentChanged(); michael@0: this.deck.selectedPanel = aTab.browser; michael@0: // Focus the browser so that things like selection will be styled correctly. michael@0: aTab.browser.focus(); michael@0: }, michael@0: michael@0: get selectedBrowser() { michael@0: if (this._selectedTab) michael@0: return this._selectedTab.browser; michael@0: return null; michael@0: }, michael@0: michael@0: getTabForId: function getTabForId(aId) { michael@0: let tabs = this._tabs; michael@0: for (let i=0; i < tabs.length; i++) { michael@0: if (tabs[i].id == aId) michael@0: return tabs[i]; michael@0: } michael@0: return null; michael@0: }, michael@0: michael@0: getTabForBrowser: function getTabForBrowser(aBrowser) { michael@0: let tabs = this._tabs; michael@0: for (let i = 0; i < tabs.length; i++) { michael@0: if (tabs[i].browser == aBrowser) michael@0: return tabs[i]; michael@0: } michael@0: return null; michael@0: }, michael@0: michael@0: getTabForWindow: function getTabForWindow(aWindow) { michael@0: let tabs = this._tabs; michael@0: for (let i = 0; i < tabs.length; i++) { michael@0: if (tabs[i].browser.contentWindow == aWindow) michael@0: return tabs[i]; michael@0: } michael@0: return null; michael@0: }, michael@0: michael@0: getBrowserForWindow: function getBrowserForWindow(aWindow) { michael@0: let tabs = this._tabs; michael@0: for (let i = 0; i < tabs.length; i++) { michael@0: if (tabs[i].browser.contentWindow == aWindow) michael@0: return tabs[i].browser; michael@0: } michael@0: return null; michael@0: }, michael@0: michael@0: getBrowserForDocument: function getBrowserForDocument(aDocument) { michael@0: let tabs = this._tabs; michael@0: for (let i = 0; i < tabs.length; i++) { michael@0: if (tabs[i].browser.contentDocument == aDocument) michael@0: return tabs[i].browser; michael@0: } michael@0: return null; michael@0: }, michael@0: michael@0: loadURI: function loadURI(aURI, aBrowser, aParams) { michael@0: aBrowser = aBrowser || this.selectedBrowser; michael@0: if (!aBrowser) michael@0: return; michael@0: michael@0: aParams = aParams || {}; michael@0: michael@0: let flags = "flags" in aParams ? aParams.flags : Ci.nsIWebNavigation.LOAD_FLAGS_NONE; michael@0: let postData = ("postData" in aParams && aParams.postData) ? aParams.postData : null; michael@0: let referrerURI = "referrerURI" in aParams ? aParams.referrerURI : null; michael@0: let charset = "charset" in aParams ? aParams.charset : null; michael@0: michael@0: let tab = this.getTabForBrowser(aBrowser); michael@0: if (tab) { michael@0: if ("userSearch" in aParams) tab.userSearch = aParams.userSearch; michael@0: } michael@0: michael@0: try { michael@0: aBrowser.loadURIWithFlags(aURI, flags, referrerURI, charset, postData); michael@0: } catch(e) { michael@0: if (tab) { michael@0: let message = { michael@0: type: "Content:LoadError", michael@0: tabID: tab.id michael@0: }; michael@0: sendMessageToJava(message); michael@0: dump("Handled load error: " + e) michael@0: } michael@0: } michael@0: }, michael@0: michael@0: addTab: function addTab(aURI, aParams) { michael@0: aParams = aParams || {}; michael@0: michael@0: let newTab = new Tab(aURI, aParams); michael@0: this._tabs.push(newTab); michael@0: michael@0: let selected = "selected" in aParams ? aParams.selected : true; michael@0: if (selected) michael@0: this.selectedTab = newTab; michael@0: michael@0: let pinned = "pinned" in aParams ? aParams.pinned : false; michael@0: if (pinned) { michael@0: let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore); michael@0: ss.setTabValue(newTab, "appOrigin", aURI); michael@0: } michael@0: michael@0: let evt = document.createEvent("UIEvents"); michael@0: evt.initUIEvent("TabOpen", true, false, window, null); michael@0: newTab.browser.dispatchEvent(evt); michael@0: michael@0: return newTab; michael@0: }, michael@0: michael@0: // Use this method to close a tab from JS. This method sends a message michael@0: // to Java to close the tab in the Java UI (we'll get a Tab:Closed message michael@0: // back from Java when that happens). michael@0: closeTab: function closeTab(aTab) { michael@0: if (!aTab) { michael@0: Cu.reportError("Error trying to close tab (tab doesn't exist)"); michael@0: return; michael@0: } michael@0: michael@0: let message = { michael@0: type: "Tab:Close", michael@0: tabID: aTab.id michael@0: }; michael@0: sendMessageToJava(message); michael@0: }, michael@0: michael@0: #ifdef MOZ_ANDROID_SYNTHAPKS michael@0: _loadWebapp: function(aMessage) { michael@0: michael@0: this._initRuntime(this._startupStatus, aMessage.url, aUrl => { michael@0: this.manifestUrl = aMessage.url; michael@0: this.addTab(aUrl, { title: aMessage.name }); michael@0: }); michael@0: }, michael@0: #endif michael@0: michael@0: // Calling this will update the state in BrowserApp after a tab has been michael@0: // closed in the Java UI. michael@0: _handleTabClosed: function _handleTabClosed(aTab) { michael@0: if (aTab == this.selectedTab) michael@0: this.selectedTab = null; michael@0: michael@0: let evt = document.createEvent("UIEvents"); michael@0: evt.initUIEvent("TabClose", true, false, window, null); michael@0: aTab.browser.dispatchEvent(evt); michael@0: michael@0: aTab.destroy(); michael@0: this._tabs.splice(this._tabs.indexOf(aTab), 1); michael@0: }, michael@0: michael@0: // Use this method to select a tab from JS. This method sends a message michael@0: // to Java to select the tab in the Java UI (we'll get a Tab:Selected message michael@0: // back from Java when that happens). michael@0: selectTab: function selectTab(aTab) { michael@0: if (!aTab) { michael@0: Cu.reportError("Error trying to select tab (tab doesn't exist)"); michael@0: return; michael@0: } michael@0: michael@0: // There's nothing to do if the tab is already selected michael@0: if (aTab == this.selectedTab) michael@0: return; michael@0: michael@0: let message = { michael@0: type: "Tab:Select", michael@0: tabID: aTab.id michael@0: }; michael@0: sendMessageToJava(message); michael@0: }, michael@0: michael@0: /** michael@0: * Gets an open tab with the given URL. michael@0: * michael@0: * @param aURL URL to look for michael@0: * @return the tab with the given URL, or null if no such tab exists michael@0: */ michael@0: getTabWithURL: function getTabWithURL(aURL) { michael@0: let uri = Services.io.newURI(aURL, null, null); michael@0: for (let i = 0; i < this._tabs.length; ++i) { michael@0: let tab = this._tabs[i]; michael@0: if (tab.browser.currentURI.equals(uri)) { michael@0: return tab; michael@0: } michael@0: } michael@0: return null; michael@0: }, michael@0: michael@0: /** michael@0: * If a tab with the given URL already exists, that tab is selected. michael@0: * Otherwise, a new tab is opened with the given URL. michael@0: * michael@0: * @param aURL URL to open michael@0: */ michael@0: selectOrOpenTab: function selectOrOpenTab(aURL) { michael@0: let tab = this.getTabWithURL(aURL); michael@0: if (tab == null) { michael@0: this.addTab(aURL); michael@0: } else { michael@0: this.selectTab(tab); michael@0: } michael@0: }, michael@0: michael@0: // This method updates the state in BrowserApp after a tab has been selected michael@0: // in the Java UI. michael@0: _handleTabSelected: function _handleTabSelected(aTab) { michael@0: this.selectedTab = aTab; michael@0: michael@0: let evt = document.createEvent("UIEvents"); michael@0: evt.initUIEvent("TabSelect", true, false, window, null); michael@0: aTab.browser.dispatchEvent(evt); michael@0: }, michael@0: michael@0: quit: function quit() { michael@0: // Figure out if there's at least one other browser window around. michael@0: let lastBrowser = true; michael@0: let e = Services.wm.getEnumerator("navigator:browser"); michael@0: while (e.hasMoreElements() && lastBrowser) { michael@0: let win = e.getNext(); michael@0: if (!win.closed && win != window) michael@0: lastBrowser = false; michael@0: } michael@0: michael@0: if (lastBrowser) { michael@0: // Let everyone know we are closing the last browser window michael@0: let closingCanceled = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool); michael@0: Services.obs.notifyObservers(closingCanceled, "browser-lastwindow-close-requested", null); michael@0: if (closingCanceled.data) michael@0: return; michael@0: michael@0: Services.obs.notifyObservers(null, "browser-lastwindow-close-granted", null); michael@0: } michael@0: michael@0: window.QueryInterface(Ci.nsIDOMChromeWindow).minimize(); michael@0: window.close(); michael@0: }, michael@0: michael@0: saveAsPDF: function saveAsPDF(aBrowser) { michael@0: // Create the final destination file location michael@0: let fileName = ContentAreaUtils.getDefaultFileName(aBrowser.contentTitle, aBrowser.currentURI, null, null); michael@0: fileName = fileName.trim() + ".pdf"; michael@0: michael@0: let dm = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager); michael@0: let downloadsDir = dm.defaultDownloadsDirectory; michael@0: michael@0: let file = downloadsDir.clone(); michael@0: file.append(fileName); michael@0: file.createUnique(file.NORMAL_FILE_TYPE, parseInt("666", 8)); michael@0: michael@0: let printSettings = Cc["@mozilla.org/gfx/printsettings-service;1"].getService(Ci.nsIPrintSettingsService).newPrintSettings; michael@0: printSettings.printSilent = true; michael@0: printSettings.showPrintProgress = false; michael@0: printSettings.printBGImages = true; michael@0: printSettings.printBGColors = true; michael@0: printSettings.printToFile = true; michael@0: printSettings.toFileName = file.path; michael@0: printSettings.printFrameType = Ci.nsIPrintSettings.kFramesAsIs; michael@0: printSettings.outputFormat = Ci.nsIPrintSettings.kOutputFormatPDF; michael@0: michael@0: //XXX we probably need a preference here, the header can be useful michael@0: printSettings.footerStrCenter = ""; michael@0: printSettings.footerStrLeft = ""; michael@0: printSettings.footerStrRight = ""; michael@0: printSettings.headerStrCenter = ""; michael@0: printSettings.headerStrLeft = ""; michael@0: printSettings.headerStrRight = ""; michael@0: michael@0: // Create a valid mimeInfo for the PDF michael@0: let ms = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService); michael@0: let mimeInfo = ms.getFromTypeAndExtension("application/pdf", "pdf"); michael@0: michael@0: let webBrowserPrint = aBrowser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIWebBrowserPrint); michael@0: michael@0: let cancelable = { michael@0: cancel: function (aReason) { michael@0: webBrowserPrint.cancel(); michael@0: } michael@0: } michael@0: let isPrivate = PrivateBrowsingUtils.isWindowPrivate(aBrowser.contentWindow); michael@0: let download = dm.addDownload(Ci.nsIDownloadManager.DOWNLOAD_TYPE_DOWNLOAD, michael@0: aBrowser.currentURI, michael@0: Services.io.newFileURI(file), "", mimeInfo, michael@0: Date.now() * 1000, null, cancelable, isPrivate); michael@0: michael@0: webBrowserPrint.print(printSettings, download); michael@0: }, michael@0: michael@0: notifyPrefObservers: function(aPref) { michael@0: this._prefObservers[aPref].forEach(function(aRequestId) { michael@0: this.getPreferences(aRequestId, [aPref], 1); michael@0: }, this); michael@0: }, michael@0: michael@0: handlePreferencesRequest: function handlePreferencesRequest(aRequestId, michael@0: aPrefNames, michael@0: aListen) { michael@0: michael@0: let prefs = []; michael@0: michael@0: for (let prefName of aPrefNames) { michael@0: let pref = { michael@0: name: prefName, michael@0: type: "", michael@0: value: null michael@0: }; michael@0: michael@0: if (aListen) { michael@0: if (this._prefObservers[prefName]) michael@0: this._prefObservers[prefName].push(aRequestId); michael@0: else michael@0: this._prefObservers[prefName] = [ aRequestId ]; michael@0: Services.prefs.addObserver(prefName, this, false); michael@0: } michael@0: michael@0: // These pref names are not "real" pref names. michael@0: // They are used in the setting menu, michael@0: // and these are passed when initializing the setting menu. michael@0: switch (prefName) { michael@0: // The plugin pref is actually two separate prefs, so michael@0: // we need to handle it differently michael@0: case "plugin.enable": michael@0: pref.type = "string";// Use a string type for java's ListPreference michael@0: pref.value = PluginHelper.getPluginPreference(); michael@0: prefs.push(pref); michael@0: continue; michael@0: // Handle master password michael@0: case "privacy.masterpassword.enabled": michael@0: pref.type = "bool"; michael@0: pref.value = MasterPassword.enabled; michael@0: prefs.push(pref); michael@0: continue; michael@0: // Handle do-not-track preference michael@0: case "privacy.donottrackheader": michael@0: pref.type = "string"; michael@0: michael@0: let enableDNT = Services.prefs.getBoolPref("privacy.donottrackheader.enabled"); michael@0: if (!enableDNT) { michael@0: pref.value = kDoNotTrackPrefState.NO_PREF; michael@0: } else { michael@0: let dntState = Services.prefs.getIntPref("privacy.donottrackheader.value"); michael@0: pref.value = (dntState === 0) ? kDoNotTrackPrefState.ALLOW_TRACKING : michael@0: kDoNotTrackPrefState.DISALLOW_TRACKING; michael@0: } michael@0: michael@0: prefs.push(pref); michael@0: continue; michael@0: #ifdef MOZ_CRASHREPORTER michael@0: // Crash reporter submit pref must be fetched from nsICrashReporter service. michael@0: case "datareporting.crashreporter.submitEnabled": michael@0: pref.type = "bool"; michael@0: pref.value = CrashReporter.submitReports; michael@0: prefs.push(pref); michael@0: continue; michael@0: #endif michael@0: } michael@0: michael@0: try { michael@0: switch (Services.prefs.getPrefType(prefName)) { michael@0: case Ci.nsIPrefBranch.PREF_BOOL: michael@0: pref.type = "bool"; michael@0: pref.value = Services.prefs.getBoolPref(prefName); michael@0: break; michael@0: case Ci.nsIPrefBranch.PREF_INT: michael@0: pref.type = "int"; michael@0: pref.value = Services.prefs.getIntPref(prefName); michael@0: break; michael@0: case Ci.nsIPrefBranch.PREF_STRING: michael@0: default: michael@0: pref.type = "string"; michael@0: try { michael@0: // Try in case it's a localized string (will throw an exception if not) michael@0: pref.value = Services.prefs.getComplexValue(prefName, Ci.nsIPrefLocalizedString).data; michael@0: } catch (e) { michael@0: pref.value = Services.prefs.getCharPref(prefName); michael@0: } michael@0: break; michael@0: } michael@0: } catch (e) { michael@0: dump("Error reading pref [" + prefName + "]: " + e); michael@0: // preference does not exist; do not send it michael@0: continue; michael@0: } michael@0: michael@0: // Some Gecko preferences use integers or strings to reference michael@0: // state instead of directly representing the value. michael@0: // Since the Java UI uses the type to determine which ui elements michael@0: // to show and how to handle them, we need to normalize these michael@0: // preferences to the correct type. michael@0: switch (prefName) { michael@0: // (string) index for determining which multiple choice value to display. michael@0: case "browser.chrome.titlebarMode": michael@0: case "network.cookie.cookieBehavior": michael@0: case "font.size.inflation.minTwips": michael@0: case "home.sync.updateMode": michael@0: pref.type = "string"; michael@0: pref.value = pref.value.toString(); michael@0: break; michael@0: } michael@0: michael@0: prefs.push(pref); michael@0: } michael@0: michael@0: sendMessageToJava({ michael@0: type: "Preferences:Data", michael@0: requestId: aRequestId, // opaque request identifier, can be any string/int/whatever michael@0: preferences: prefs michael@0: }); michael@0: }, michael@0: michael@0: setPreferences: function setPreferences(aPref) { michael@0: let json = JSON.parse(aPref); michael@0: michael@0: switch (json.name) { michael@0: // The plugin pref is actually two separate prefs, so michael@0: // we need to handle it differently michael@0: case "plugin.enable": michael@0: PluginHelper.setPluginPreference(json.value); michael@0: return; michael@0: michael@0: // MasterPassword pref is not real, we just need take action and leave michael@0: case "privacy.masterpassword.enabled": michael@0: if (MasterPassword.enabled) michael@0: MasterPassword.removePassword(json.value); michael@0: else michael@0: MasterPassword.setPassword(json.value); michael@0: return; michael@0: michael@0: // "privacy.donottrackheader" is not "real" pref name, it's used in the setting menu. michael@0: case "privacy.donottrackheader": michael@0: switch (json.value) { michael@0: // Don't tell anything about tracking me michael@0: case kDoNotTrackPrefState.NO_PREF: michael@0: Services.prefs.setBoolPref("privacy.donottrackheader.enabled", false); michael@0: Services.prefs.clearUserPref("privacy.donottrackheader.value"); michael@0: break; michael@0: // Accept tracking me michael@0: case kDoNotTrackPrefState.ALLOW_TRACKING: michael@0: Services.prefs.setBoolPref("privacy.donottrackheader.enabled", true); michael@0: Services.prefs.setIntPref("privacy.donottrackheader.value", 0); michael@0: break; michael@0: // Not accept tracking me michael@0: case kDoNotTrackPrefState.DISALLOW_TRACKING: michael@0: Services.prefs.setBoolPref("privacy.donottrackheader.enabled", true); michael@0: Services.prefs.setIntPref("privacy.donottrackheader.value", 1); michael@0: break; michael@0: } michael@0: return; michael@0: michael@0: // Enabling or disabling suggestions will prevent future prompts michael@0: case SearchEngines.PREF_SUGGEST_ENABLED: michael@0: Services.prefs.setBoolPref(SearchEngines.PREF_SUGGEST_PROMPTED, true); michael@0: break; michael@0: michael@0: #ifdef MOZ_CRASHREPORTER michael@0: // Crash reporter preference is in a service; set and return. michael@0: case "datareporting.crashreporter.submitEnabled": michael@0: CrashReporter.submitReports = json.value; michael@0: return; michael@0: #endif michael@0: // When sending to Java, we normalized special preferences that use michael@0: // integers and strings to represent booleans. Here, we convert them back michael@0: // to their actual types so we can store them. michael@0: case "browser.chrome.titlebarMode": michael@0: case "network.cookie.cookieBehavior": michael@0: case "font.size.inflation.minTwips": michael@0: case "home.sync.updateMode": michael@0: json.type = "int"; michael@0: json.value = parseInt(json.value); michael@0: break; michael@0: } michael@0: michael@0: switch (json.type) { michael@0: case "bool": michael@0: Services.prefs.setBoolPref(json.name, json.value); michael@0: break; michael@0: case "int": michael@0: Services.prefs.setIntPref(json.name, json.value); michael@0: break; michael@0: default: { michael@0: let pref = Cc["@mozilla.org/pref-localizedstring;1"].createInstance(Ci.nsIPrefLocalizedString); michael@0: pref.data = json.value; michael@0: Services.prefs.setComplexValue(json.name, Ci.nsISupportsString, pref); michael@0: break; michael@0: } michael@0: } michael@0: }, michael@0: michael@0: sanitize: function (aItems) { michael@0: let json = JSON.parse(aItems); michael@0: let success = true; michael@0: michael@0: for (let key in json) { michael@0: if (!json[key]) michael@0: continue; michael@0: michael@0: try { michael@0: switch (key) { michael@0: case "cookies_sessions": michael@0: Sanitizer.clearItem("cookies"); michael@0: Sanitizer.clearItem("sessions"); michael@0: break; michael@0: default: michael@0: Sanitizer.clearItem(key); michael@0: } michael@0: } catch (e) { michael@0: dump("sanitize error: " + e); michael@0: success = false; michael@0: } michael@0: } michael@0: michael@0: sendMessageToJava({ michael@0: type: "Sanitize:Finished", michael@0: success: success michael@0: }); michael@0: }, michael@0: michael@0: getFocusedInput: function(aBrowser, aOnlyInputElements = false) { michael@0: if (!aBrowser) michael@0: return null; michael@0: michael@0: let doc = aBrowser.contentDocument; michael@0: if (!doc) michael@0: return null; michael@0: michael@0: let focused = doc.activeElement; michael@0: while (focused instanceof HTMLFrameElement || focused instanceof HTMLIFrameElement) { michael@0: doc = focused.contentDocument; michael@0: focused = doc.activeElement; michael@0: } michael@0: michael@0: if (focused instanceof HTMLInputElement && focused.mozIsTextField(false)) michael@0: return focused; michael@0: michael@0: if (aOnlyInputElements) michael@0: return null; michael@0: michael@0: if (focused && (focused instanceof HTMLTextAreaElement || focused.isContentEditable)) { michael@0: michael@0: if (focused instanceof HTMLBodyElement) { michael@0: // we are putting focus into a contentEditable frame. scroll the frame into michael@0: // view instead of the contentEditable document contained within, because that michael@0: // results in a better user experience michael@0: focused = focused.ownerDocument.defaultView.frameElement; michael@0: } michael@0: return focused; michael@0: } michael@0: return null; michael@0: }, michael@0: michael@0: scrollToFocusedInput: function(aBrowser, aAllowZoom = true) { michael@0: let formHelperMode = Services.prefs.getIntPref("formhelper.mode"); michael@0: if (formHelperMode == kFormHelperModeDisabled) michael@0: return; michael@0: michael@0: let focused = this.getFocusedInput(aBrowser); michael@0: michael@0: if (focused) { michael@0: let shouldZoom = Services.prefs.getBoolPref("formhelper.autozoom"); michael@0: if (formHelperMode == kFormHelperModeDynamic && this.isTablet) michael@0: shouldZoom = false; michael@0: // ZoomHelper.zoomToElement will handle not sending any message if this input is already mostly filling the screen michael@0: ZoomHelper.zoomToElement(focused, -1, false, michael@0: aAllowZoom && shouldZoom && !ViewportHandler.getViewportMetadata(aBrowser.contentWindow).isSpecified); michael@0: } michael@0: }, michael@0: michael@0: observe: function(aSubject, aTopic, aData) { michael@0: let browser = this.selectedBrowser; michael@0: michael@0: switch (aTopic) { michael@0: michael@0: case "Session:Back": michael@0: browser.goBack(); michael@0: break; michael@0: michael@0: case "Session:Forward": michael@0: browser.goForward(); michael@0: break; michael@0: michael@0: case "Session:Reload": { michael@0: let flags = Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY | Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE; michael@0: michael@0: // Check to see if this is a message to enable/disable mixed content blocking. michael@0: if (aData) { michael@0: let allowMixedContent = JSON.parse(aData).allowMixedContent; michael@0: if (allowMixedContent) { michael@0: // Set a flag to disable mixed content blocking. michael@0: flags = Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_MIXED_CONTENT; michael@0: } else { michael@0: // Set mixedContentChannel to null to re-enable mixed content blocking. michael@0: let docShell = browser.webNavigation.QueryInterface(Ci.nsIDocShell); michael@0: docShell.mixedContentChannel = null; michael@0: } michael@0: } michael@0: michael@0: // Try to use the session history to reload so that framesets are michael@0: // handled properly. If the window has no session history, fall back michael@0: // to using the web navigation's reload method. michael@0: let webNav = browser.webNavigation; michael@0: try { michael@0: let sh = webNav.sessionHistory; michael@0: if (sh) michael@0: webNav = sh.QueryInterface(Ci.nsIWebNavigation); michael@0: } catch (e) {} michael@0: webNav.reload(flags); michael@0: break; michael@0: } michael@0: michael@0: case "Session:Stop": michael@0: browser.stop(); michael@0: break; michael@0: michael@0: case "Session:ShowHistory": { michael@0: let data = JSON.parse(aData); michael@0: this.showHistory(data.fromIndex, data.toIndex, data.selIndex); michael@0: break; michael@0: } michael@0: michael@0: case "Tab:Load": { michael@0: let data = JSON.parse(aData); michael@0: michael@0: // Pass LOAD_FLAGS_DISALLOW_INHERIT_OWNER to prevent any loads from michael@0: // inheriting the currently loaded document's principal. michael@0: let flags = Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP | michael@0: Ci.nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS; michael@0: if (data.userEntered) { michael@0: flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_OWNER; michael@0: } michael@0: michael@0: let delayLoad = ("delayLoad" in data) ? data.delayLoad : false; michael@0: let params = { michael@0: selected: ("selected" in data) ? data.selected : !delayLoad, michael@0: parentId: ("parentId" in data) ? data.parentId : -1, michael@0: flags: flags, michael@0: tabID: data.tabID, michael@0: isPrivate: (data.isPrivate === true), michael@0: pinned: (data.pinned === true), michael@0: delayLoad: (delayLoad === true), michael@0: desktopMode: (data.desktopMode === true) michael@0: }; michael@0: michael@0: let url = data.url; michael@0: if (data.engine) { michael@0: let engine = Services.search.getEngineByName(data.engine); michael@0: if (engine) { michael@0: params.userSearch = url; michael@0: let submission = engine.getSubmission(url); michael@0: url = submission.uri.spec; michael@0: params.postData = submission.postData; michael@0: } michael@0: } michael@0: michael@0: if (data.newTab) { michael@0: this.addTab(url, params); michael@0: } else { michael@0: if (data.tabId) { michael@0: // Use a specific browser instead of the selected browser, if it exists michael@0: let specificBrowser = this.getTabForId(data.tabId).browser; michael@0: if (specificBrowser) michael@0: browser = specificBrowser; michael@0: } michael@0: this.loadURI(url, browser, params); michael@0: } michael@0: break; michael@0: } michael@0: michael@0: case "Tab:Selected": michael@0: this._handleTabSelected(this.getTabForId(parseInt(aData))); michael@0: break; michael@0: michael@0: case "Tab:Closed": michael@0: this._handleTabClosed(this.getTabForId(parseInt(aData))); michael@0: break; michael@0: michael@0: case "keyword-search": michael@0: // This event refers to a search via the URL bar, not a bookmarks michael@0: // keyword search. Note that this code assumes that the user can only michael@0: // perform a keyword search on the selected tab. michael@0: this.selectedTab.userSearch = aData; michael@0: michael@0: let engine = aSubject.QueryInterface(Ci.nsISearchEngine); michael@0: sendMessageToJava({ michael@0: type: "Search:Keyword", michael@0: identifier: engine.identifier, michael@0: name: engine.name, michael@0: }); michael@0: break; michael@0: michael@0: case "Browser:Quit": michael@0: this.quit(); michael@0: break; michael@0: michael@0: case "SaveAs:PDF": michael@0: this.saveAsPDF(browser); michael@0: break; michael@0: michael@0: case "Preferences:Set": michael@0: this.setPreferences(aData); michael@0: break; michael@0: michael@0: case "ScrollTo:FocusedInput": michael@0: // these messages come from a change in the viewable area and not user interaction michael@0: // we allow scrolling to the selected input, but not zooming the page michael@0: this.scrollToFocusedInput(browser, false); michael@0: break; michael@0: michael@0: case "Sanitize:ClearData": michael@0: this.sanitize(aData); michael@0: break; michael@0: michael@0: case "FullScreen:Exit": michael@0: browser.contentDocument.mozCancelFullScreen(); michael@0: break; michael@0: michael@0: case "Viewport:Change": michael@0: if (this.isBrowserContentDocumentDisplayed()) michael@0: this.selectedTab.setViewport(JSON.parse(aData)); michael@0: break; michael@0: michael@0: case "Viewport:Flush": michael@0: this.contentDocumentChanged(); michael@0: break; michael@0: michael@0: case "Passwords:Init": { michael@0: let storage = Cc["@mozilla.org/login-manager/storage/mozStorage;1"]. michael@0: getService(Ci.nsILoginManagerStorage); michael@0: storage.init(); michael@0: Services.obs.removeObserver(this, "Passwords:Init"); michael@0: break; michael@0: } michael@0: michael@0: case "FormHistory:Init": { michael@0: // Force creation/upgrade of formhistory.sqlite michael@0: FormHistory.count({}); michael@0: Services.obs.removeObserver(this, "FormHistory:Init"); michael@0: break; michael@0: } michael@0: michael@0: case "sessionstore-state-purge-complete": michael@0: sendMessageToJava({ type: "Session:StatePurged" }); michael@0: break; michael@0: michael@0: case "gather-telemetry": michael@0: sendMessageToJava({ type: "Telemetry:Gather" }); michael@0: break; michael@0: michael@0: case "Viewport:FixedMarginsChanged": michael@0: gViewportMargins = JSON.parse(aData); michael@0: this.selectedTab.updateViewportSize(gScreenWidth); michael@0: break; michael@0: michael@0: case "nsPref:changed": michael@0: this.notifyPrefObservers(aData); michael@0: break; michael@0: michael@0: #ifdef MOZ_ANDROID_SYNTHAPKS michael@0: case "webapps-runtime-install": michael@0: WebappManager.install(JSON.parse(aData), aSubject); michael@0: break; michael@0: michael@0: case "webapps-runtime-install-package": michael@0: WebappManager.installPackage(JSON.parse(aData), aSubject); michael@0: break; michael@0: michael@0: case "webapps-ask-install": michael@0: WebappManager.askInstall(JSON.parse(aData)); michael@0: break; michael@0: michael@0: case "webapps-launch": { michael@0: WebappManager.launch(JSON.parse(aData)); michael@0: break; michael@0: } michael@0: michael@0: case "webapps-uninstall": { michael@0: WebappManager.uninstall(JSON.parse(aData)); michael@0: break; michael@0: } michael@0: michael@0: case "Webapps:AutoInstall": michael@0: WebappManager.autoInstall(JSON.parse(aData)); michael@0: break; michael@0: michael@0: case "Webapps:Load": michael@0: this._loadWebapp(JSON.parse(aData)); michael@0: break; michael@0: michael@0: case "Webapps:AutoUninstall": michael@0: WebappManager.autoUninstall(JSON.parse(aData)); michael@0: break; michael@0: #endif michael@0: michael@0: case "Locale:Changed": michael@0: // The value provided to Locale:Changed should be a BCP47 language tag michael@0: // understood by Gecko -- for example, "es-ES" or "de". michael@0: console.log("Locale:Changed: " + aData); michael@0: michael@0: // TODO: do we need to be more nuanced here -- e.g., checking for the michael@0: // OS locale -- or should it always be false on Fennec? michael@0: Services.prefs.setBoolPref("intl.locale.matchOS", false); michael@0: Services.prefs.setCharPref("general.useragent.locale", aData); michael@0: break; michael@0: michael@0: default: michael@0: dump('BrowserApp.observe: unexpected topic "' + aTopic + '"\n'); michael@0: break; michael@0: michael@0: } michael@0: }, michael@0: michael@0: get defaultBrowserWidth() { michael@0: delete this.defaultBrowserWidth; michael@0: let width = Services.prefs.getIntPref("browser.viewport.desktopWidth"); michael@0: return this.defaultBrowserWidth = width; michael@0: }, michael@0: michael@0: // nsIAndroidBrowserApp michael@0: getBrowserTab: function(tabId) { michael@0: return this.getTabForId(tabId); michael@0: }, michael@0: michael@0: getUITelemetryObserver: function() { michael@0: return UITelemetry; michael@0: }, michael@0: michael@0: getPreferences: function getPreferences(requestId, prefNames, count) { michael@0: this.handlePreferencesRequest(requestId, prefNames, false); michael@0: }, michael@0: michael@0: observePreferences: function observePreferences(requestId, prefNames, count) { michael@0: this.handlePreferencesRequest(requestId, prefNames, true); michael@0: }, michael@0: michael@0: removePreferenceObservers: function removePreferenceObservers(aRequestId) { michael@0: let newPrefObservers = []; michael@0: for (let prefName in this._prefObservers) { michael@0: let requestIds = this._prefObservers[prefName]; michael@0: // Remove the requestID from the preference handlers michael@0: let i = requestIds.indexOf(aRequestId); michael@0: if (i >= 0) { michael@0: requestIds.splice(i, 1); michael@0: } michael@0: michael@0: // If there are no more request IDs, remove the observer michael@0: if (requestIds.length == 0) { michael@0: Services.prefs.removeObserver(prefName, this); michael@0: } else { michael@0: newPrefObservers[prefName] = requestIds; michael@0: } michael@0: } michael@0: this._prefObservers = newPrefObservers; michael@0: }, michael@0: michael@0: // This method will print a list from fromIndex to toIndex, optionally michael@0: // selecting selIndex(if fromIndex<=selIndex<=toIndex) michael@0: showHistory: function(fromIndex, toIndex, selIndex) { michael@0: let browser = this.selectedBrowser; michael@0: let hist = browser.sessionHistory; michael@0: let listitems = []; michael@0: for (let i = toIndex; i >= fromIndex; i--) { michael@0: let entry = hist.getEntryAtIndex(i, false); michael@0: let item = { michael@0: label: entry.title || entry.URI.spec, michael@0: selected: (i == selIndex) michael@0: }; michael@0: listitems.push(item); michael@0: } michael@0: michael@0: let p = new Prompt({ michael@0: window: browser.contentWindow michael@0: }).setSingleChoiceItems(listitems).show(function(data) { michael@0: let selected = data.button; michael@0: if (selected == -1) michael@0: return; michael@0: michael@0: browser.gotoIndex(toIndex-selected); michael@0: }); michael@0: }, michael@0: }; michael@0: michael@0: var NativeWindow = { michael@0: init: function() { michael@0: Services.obs.addObserver(this, "Menu:Clicked", false); michael@0: Services.obs.addObserver(this, "PageActions:Clicked", false); michael@0: Services.obs.addObserver(this, "PageActions:LongClicked", false); michael@0: Services.obs.addObserver(this, "Doorhanger:Reply", false); michael@0: Services.obs.addObserver(this, "Toast:Click", false); michael@0: Services.obs.addObserver(this, "Toast:Hidden", false); michael@0: this.contextmenus.init(); michael@0: }, michael@0: michael@0: uninit: function() { michael@0: Services.obs.removeObserver(this, "Menu:Clicked"); michael@0: Services.obs.removeObserver(this, "PageActions:Clicked"); michael@0: Services.obs.removeObserver(this, "PageActions:LongClicked"); michael@0: Services.obs.removeObserver(this, "Doorhanger:Reply"); michael@0: Services.obs.removeObserver(this, "Toast:Click", false); michael@0: Services.obs.removeObserver(this, "Toast:Hidden", false); michael@0: this.contextmenus.uninit(); michael@0: }, michael@0: michael@0: loadDex: function(zipFile, implClass) { michael@0: sendMessageToJava({ michael@0: type: "Dex:Load", michael@0: zipfile: zipFile, michael@0: impl: implClass || "Main" michael@0: }); michael@0: }, michael@0: michael@0: unloadDex: function(zipFile) { michael@0: sendMessageToJava({ michael@0: type: "Dex:Unload", michael@0: zipfile: zipFile michael@0: }); michael@0: }, michael@0: michael@0: toast: { michael@0: _callbacks: {}, michael@0: show: function(aMessage, aDuration, aOptions) { michael@0: let msg = { michael@0: type: "Toast:Show", michael@0: message: aMessage, michael@0: duration: aDuration michael@0: }; michael@0: michael@0: if (aOptions && aOptions.button) { michael@0: msg.button = { michael@0: label: aOptions.button.label, michael@0: id: uuidgen.generateUUID().toString(), michael@0: // If the caller specified a button, make sure we convert any chrome urls michael@0: // to jar:jar urls so that the frontend can show them michael@0: icon: aOptions.button.icon ? resolveGeckoURI(aOptions.button.icon) : null, michael@0: }; michael@0: this._callbacks[msg.button.id] = aOptions.button.callback; michael@0: } michael@0: michael@0: sendMessageToJava(msg); michael@0: } michael@0: }, michael@0: michael@0: pageactions: { michael@0: _items: { }, michael@0: add: function(aOptions) { michael@0: let id = uuidgen.generateUUID().toString(); michael@0: sendMessageToJava({ michael@0: type: "PageActions:Add", michael@0: id: id, michael@0: title: aOptions.title, michael@0: icon: resolveGeckoURI(aOptions.icon), michael@0: important: "important" in aOptions ? aOptions.important : false michael@0: }); michael@0: this._items[id] = { michael@0: clickCallback: aOptions.clickCallback, michael@0: longClickCallback: aOptions.longClickCallback michael@0: }; michael@0: return id; michael@0: }, michael@0: remove: function(id) { michael@0: sendMessageToJava({ michael@0: type: "PageActions:Remove", michael@0: id: id michael@0: }); michael@0: delete this._items[id]; michael@0: } michael@0: }, michael@0: michael@0: menu: { michael@0: _callbacks: [], michael@0: _menuId: 1, michael@0: toolsMenuID: -1, michael@0: add: function() { michael@0: let options; michael@0: if (arguments.length == 1) { michael@0: options = arguments[0]; michael@0: } else if (arguments.length == 3) { michael@0: options = { michael@0: name: arguments[0], michael@0: icon: arguments[1], michael@0: callback: arguments[2] michael@0: }; michael@0: } else { michael@0: throw "Incorrect number of parameters"; michael@0: } michael@0: michael@0: options.type = "Menu:Add"; michael@0: options.id = this._menuId; michael@0: michael@0: sendMessageToJava(options); michael@0: this._callbacks[this._menuId] = options.callback; michael@0: this._menuId++; michael@0: return this._menuId - 1; michael@0: }, michael@0: michael@0: remove: function(aId) { michael@0: sendMessageToJava({ type: "Menu:Remove", id: aId }); michael@0: }, michael@0: michael@0: update: function(aId, aOptions) { michael@0: if (!aOptions) michael@0: return; michael@0: michael@0: sendMessageToJava({ michael@0: type: "Menu:Update", michael@0: id: aId, michael@0: options: aOptions michael@0: }); michael@0: } michael@0: }, michael@0: michael@0: doorhanger: { michael@0: _callbacks: {}, michael@0: _callbacksId: 0, michael@0: _promptId: 0, michael@0: michael@0: /** michael@0: * @param aOptions michael@0: * An options JavaScript object holding additional properties for the michael@0: * notification. The following properties are currently supported: michael@0: * persistence: An integer. The notification will not automatically michael@0: * dismiss for this many page loads. If persistence is set michael@0: * to -1, the doorhanger will never automatically dismiss. michael@0: * persistWhileVisible: michael@0: * A boolean. If true, a visible notification will always michael@0: * persist across location changes. michael@0: * timeout: A time in milliseconds. The notification will not michael@0: * automatically dismiss before this time. michael@0: * checkbox: A string to appear next to a checkbox under the notification michael@0: * message. The button callback functions will be called with michael@0: * the checked state as an argument. michael@0: */ michael@0: show: function(aMessage, aValue, aButtons, aTabID, aOptions) { michael@0: if (aButtons == null) { michael@0: aButtons = []; michael@0: } michael@0: michael@0: aButtons.forEach((function(aButton) { michael@0: this._callbacks[this._callbacksId] = { cb: aButton.callback, prompt: this._promptId }; michael@0: aButton.callback = this._callbacksId; michael@0: this._callbacksId++; michael@0: }).bind(this)); michael@0: michael@0: this._promptId++; michael@0: let json = { michael@0: type: "Doorhanger:Add", michael@0: message: aMessage, michael@0: value: aValue, michael@0: buttons: aButtons, michael@0: // use the current tab if none is provided michael@0: tabID: aTabID || BrowserApp.selectedTab.id, michael@0: options: aOptions || {} michael@0: }; michael@0: sendMessageToJava(json); michael@0: }, michael@0: michael@0: hide: function(aValue, aTabID) { michael@0: sendMessageToJava({ michael@0: type: "Doorhanger:Remove", michael@0: value: aValue, michael@0: tabID: aTabID michael@0: }); michael@0: } michael@0: }, michael@0: michael@0: observe: function(aSubject, aTopic, aData) { michael@0: if (aTopic == "Menu:Clicked") { michael@0: if (this.menu._callbacks[aData]) michael@0: this.menu._callbacks[aData](); michael@0: } else if (aTopic == "PageActions:Clicked") { michael@0: if (this.pageactions._items[aData].clickCallback) michael@0: this.pageactions._items[aData].clickCallback(); michael@0: } else if (aTopic == "PageActions:LongClicked") { michael@0: if (this.pageactions._items[aData].longClickCallback) michael@0: this.pageactions._items[aData].longClickCallback(); michael@0: } else if (aTopic == "Toast:Click") { michael@0: if (this.toast._callbacks[aData]) { michael@0: this.toast._callbacks[aData](); michael@0: delete this.toast._callbacks[aData]; michael@0: } michael@0: } else if (aTopic == "Toast:Hidden") { michael@0: if (this.toast._callbacks[aData]) michael@0: delete this.toast._callbacks[aData]; michael@0: } else if (aTopic == "Doorhanger:Reply") { michael@0: let data = JSON.parse(aData); michael@0: let reply_id = data["callback"]; michael@0: michael@0: if (this.doorhanger._callbacks[reply_id]) { michael@0: // Pass the value of the optional checkbox to the callback michael@0: let checked = data["checked"]; michael@0: this.doorhanger._callbacks[reply_id].cb(checked, data.inputs); michael@0: michael@0: let prompt = this.doorhanger._callbacks[reply_id].prompt; michael@0: for (let id in this.doorhanger._callbacks) { michael@0: if (this.doorhanger._callbacks[id].prompt == prompt) { michael@0: delete this.doorhanger._callbacks[id]; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: }, michael@0: contextmenus: { michael@0: items: {}, // a list of context menu items that we may show michael@0: DEFAULT_HTML5_ORDER: -1, // Sort order for HTML5 context menu items michael@0: michael@0: init: function() { michael@0: Services.obs.addObserver(this, "Gesture:LongPress", false); michael@0: }, michael@0: michael@0: uninit: function() { michael@0: Services.obs.removeObserver(this, "Gesture:LongPress"); michael@0: }, michael@0: michael@0: add: function() { michael@0: let args; michael@0: if (arguments.length == 1) { michael@0: args = arguments[0]; michael@0: } else if (arguments.length == 3) { michael@0: args = { michael@0: label : arguments[0], michael@0: selector: arguments[1], michael@0: callback: arguments[2] michael@0: }; michael@0: } else { michael@0: throw "Incorrect number of parameters"; michael@0: } michael@0: michael@0: if (!args.label) michael@0: throw "Menu items must have a name"; michael@0: michael@0: let cmItem = new ContextMenuItem(args); michael@0: this.items[cmItem.id] = cmItem; michael@0: return cmItem.id; michael@0: }, michael@0: michael@0: remove: function(aId) { michael@0: delete this.items[aId]; michael@0: }, michael@0: michael@0: SelectorContext: function(aSelector) { michael@0: return { michael@0: matches: function(aElt) { michael@0: if (aElt.mozMatchesSelector) michael@0: return aElt.mozMatchesSelector(aSelector); michael@0: return false; michael@0: } michael@0: }; michael@0: }, michael@0: michael@0: linkOpenableNonPrivateContext: { michael@0: matches: function linkOpenableNonPrivateContextMatches(aElement) { michael@0: let doc = aElement.ownerDocument; michael@0: if (!doc || PrivateBrowsingUtils.isWindowPrivate(doc.defaultView)) { michael@0: return false; michael@0: } michael@0: michael@0: return NativeWindow.contextmenus.linkOpenableContext.matches(aElement); michael@0: } michael@0: }, michael@0: michael@0: linkOpenableContext: { michael@0: matches: function linkOpenableContextMatches(aElement) { michael@0: let uri = NativeWindow.contextmenus._getLink(aElement); michael@0: if (uri) { michael@0: let scheme = uri.scheme; michael@0: let dontOpen = /^(javascript|mailto|news|snews|tel)$/; michael@0: return (scheme && !dontOpen.test(scheme)); michael@0: } michael@0: return false; michael@0: } michael@0: }, michael@0: michael@0: linkCopyableContext: { michael@0: matches: function linkCopyableContextMatches(aElement) { michael@0: let uri = NativeWindow.contextmenus._getLink(aElement); michael@0: if (uri) { michael@0: let scheme = uri.scheme; michael@0: let dontCopy = /^(mailto|tel)$/; michael@0: return (scheme && !dontCopy.test(scheme)); michael@0: } michael@0: return false; michael@0: } michael@0: }, michael@0: michael@0: linkShareableContext: { michael@0: matches: function linkShareableContextMatches(aElement) { michael@0: let uri = NativeWindow.contextmenus._getLink(aElement); michael@0: if (uri) { michael@0: let scheme = uri.scheme; michael@0: let dontShare = /^(about|chrome|file|javascript|mailto|resource|tel)$/; michael@0: return (scheme && !dontShare.test(scheme)); michael@0: } michael@0: return false; michael@0: } michael@0: }, michael@0: michael@0: linkBookmarkableContext: { michael@0: matches: function linkBookmarkableContextMatches(aElement) { michael@0: let uri = NativeWindow.contextmenus._getLink(aElement); michael@0: if (uri) { michael@0: let scheme = uri.scheme; michael@0: let dontBookmark = /^(mailto|tel)$/; michael@0: return (scheme && !dontBookmark.test(scheme)); michael@0: } michael@0: return false; michael@0: } michael@0: }, michael@0: michael@0: emailLinkContext: { michael@0: matches: function emailLinkContextMatches(aElement) { michael@0: let uri = NativeWindow.contextmenus._getLink(aElement); michael@0: if (uri) michael@0: return uri.schemeIs("mailto"); michael@0: return false; michael@0: } michael@0: }, michael@0: michael@0: phoneNumberLinkContext: { michael@0: matches: function phoneNumberLinkContextMatches(aElement) { michael@0: let uri = NativeWindow.contextmenus._getLink(aElement); michael@0: if (uri) michael@0: return uri.schemeIs("tel"); michael@0: return false; michael@0: } michael@0: }, michael@0: michael@0: imageLocationCopyableContext: { michael@0: matches: function imageLinkCopyableContextMatches(aElement) { michael@0: return (aElement instanceof Ci.nsIImageLoadingContent && aElement.currentURI); michael@0: } michael@0: }, michael@0: michael@0: imageSaveableContext: { michael@0: matches: function imageSaveableContextMatches(aElement) { michael@0: if (aElement instanceof Ci.nsIImageLoadingContent && aElement.currentURI) { michael@0: // The image must be loaded to allow saving michael@0: let request = aElement.getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST); michael@0: return (request && (request.imageStatus & request.STATUS_SIZE_AVAILABLE)); michael@0: } michael@0: return false; michael@0: } michael@0: }, michael@0: michael@0: mediaSaveableContext: { michael@0: matches: function mediaSaveableContextMatches(aElement) { michael@0: return (aElement instanceof HTMLVideoElement || michael@0: aElement instanceof HTMLAudioElement); michael@0: } michael@0: }, michael@0: michael@0: mediaContext: function(aMode) { michael@0: return { michael@0: matches: function(aElt) { michael@0: if (aElt instanceof Ci.nsIDOMHTMLMediaElement) { michael@0: let hasError = aElt.error != null || aElt.networkState == aElt.NETWORK_NO_SOURCE; michael@0: if (hasError) michael@0: return false; michael@0: michael@0: let paused = aElt.paused || aElt.ended; michael@0: if (paused && aMode == "media-paused") michael@0: return true; michael@0: if (!paused && aMode == "media-playing") michael@0: return true; michael@0: let controls = aElt.controls; michael@0: if (!controls && aMode == "media-hidingcontrols") michael@0: return true; michael@0: michael@0: let muted = aElt.muted; michael@0: if (muted && aMode == "media-muted") michael@0: return true; michael@0: else if (!muted && aMode == "media-unmuted") michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: }; michael@0: }, michael@0: michael@0: /* Holds a WeakRef to the original target element this context menu was shown for. michael@0: * Most API's will have to walk up the tree from this node to find the correct element michael@0: * to act on michael@0: */ michael@0: get _target() { michael@0: if (this._targetRef) michael@0: return this._targetRef.get(); michael@0: return null; michael@0: }, michael@0: michael@0: set _target(aTarget) { michael@0: if (aTarget) michael@0: this._targetRef = Cu.getWeakReference(aTarget); michael@0: else this._targetRef = null; michael@0: }, michael@0: michael@0: get defaultContext() { michael@0: delete this.defaultContext; michael@0: return this.defaultContext = Strings.browser.GetStringFromName("browser.menu.context.default"); michael@0: }, michael@0: michael@0: /* Gets menuitems for an arbitrary node michael@0: * Parameters: michael@0: * element - The element to look at. If this element has a contextmenu attribute, the michael@0: * corresponding contextmenu will be used. michael@0: */ michael@0: _getHTMLContextMenuItemsForElement: function(element) { michael@0: let htmlMenu = element.contextMenu; michael@0: if (!htmlMenu) { michael@0: return []; michael@0: } michael@0: michael@0: htmlMenu.QueryInterface(Components.interfaces.nsIHTMLMenu); michael@0: htmlMenu.sendShowEvent(); michael@0: michael@0: return this._getHTMLContextMenuItemsForMenu(htmlMenu, element); michael@0: }, michael@0: michael@0: /* Add a menuitem for an HTML