summaryrefslogtreecommitdiffstats
path: root/mobile/android/chrome/content/browser.js
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/chrome/content/browser.js')
-rw-r--r--mobile/android/chrome/content/browser.js6961
1 files changed, 0 insertions, 6961 deletions
diff --git a/mobile/android/chrome/content/browser.js b/mobile/android/chrome/content/browser.js
deleted file mode 100644
index 535f7e607..000000000
--- a/mobile/android/chrome/content/browser.js
+++ /dev/null
@@ -1,6961 +0,0 @@
-// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-"use strict";
-
-var Cc = Components.classes;
-var Ci = Components.interfaces;
-var Cu = Components.utils;
-var Cr = Components.results;
-
-Cu.import("resource://gre/modules/AppConstants.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/AddonManager.jsm");
-Cu.import("resource://gre/modules/AsyncPrefs.jsm");
-Cu.import("resource://gre/modules/DelayedInit.jsm");
-
-if (AppConstants.ACCESSIBILITY) {
- XPCOMUtils.defineLazyModuleGetter(this, "AccessFu",
- "resource://gre/modules/accessibility/AccessFu.jsm");
-}
-
-XPCOMUtils.defineLazyModuleGetter(this, "SpatialNavigation",
- "resource://gre/modules/SpatialNavigation.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "DownloadNotifications",
- "resource://gre/modules/DownloadNotifications.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
- "resource://gre/modules/FileUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "JNI",
- "resource://gre/modules/JNI.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "UITelemetry",
- "resource://gre/modules/UITelemetry.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
- "resource://gre/modules/PluralForm.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
- "resource://gre/modules/Downloads.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "Messaging",
- "resource://gre/modules/Messaging.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "UserAgentOverrides",
- "resource://gre/modules/UserAgentOverrides.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerContent",
- "resource://gre/modules/LoginManagerContent.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerParent",
- "resource://gre/modules/LoginManagerParent.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
-
-#ifdef MOZ_SAFE_BROWSING
- XPCOMUtils.defineLazyModuleGetter(this, "SafeBrowsing",
- "resource://gre/modules/SafeBrowsing.jsm");
-#endif
-
-XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
- "resource://gre/modules/BrowserUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
- "resource://gre/modules/PrivateBrowsingUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "Sanitizer",
- "resource://gre/modules/Sanitizer.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "Prompt",
- "resource://gre/modules/Prompt.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "HelperApps",
- "resource://gre/modules/HelperApps.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "SSLExceptions",
- "resource://gre/modules/SSLExceptions.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "FormHistory",
- "resource://gre/modules/FormHistory.jsm");
-
-XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
- "@mozilla.org/uuid-generator;1",
- "nsIUUIDGenerator");
-
-XPCOMUtils.defineLazyServiceGetter(this, "Profiler",
- "@mozilla.org/tools/profiler;1",
- "nsIProfiler");
-
-XPCOMUtils.defineLazyModuleGetter(this, "SimpleServiceDiscovery",
- "resource://gre/modules/SimpleServiceDiscovery.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "CharsetMenu",
- "resource://gre/modules/CharsetMenu.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "NetErrorHelper",
- "resource://gre/modules/NetErrorHelper.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "PermissionsUtils",
- "resource://gre/modules/PermissionsUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
- "resource://gre/modules/Preferences.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "SharedPreferences",
- "resource://gre/modules/SharedPreferences.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "Notifications",
- "resource://gre/modules/Notifications.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode", "resource://gre/modules/ReaderMode.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "Snackbars", "resource://gre/modules/Snackbars.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "RuntimePermissions", "resource://gre/modules/RuntimePermissions.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "WebsiteMetadata", "resource://gre/modules/WebsiteMetadata.jsm");
-
-XPCOMUtils.defineLazyServiceGetter(this, "FontEnumerator",
- "@mozilla.org/gfx/fontenumerator;1",
- "nsIFontEnumerator");
-
-var lazilyLoadedBrowserScripts = [
- ["SelectHelper", "chrome://browser/content/SelectHelper.js"],
- ["InputWidgetHelper", "chrome://browser/content/InputWidgetHelper.js"],
- ["MasterPassword", "chrome://browser/content/MasterPassword.js"],
- ["PluginHelper", "chrome://browser/content/PluginHelper.js"],
- ["OfflineApps", "chrome://browser/content/OfflineApps.js"],
- ["Linkifier", "chrome://browser/content/Linkify.js"],
- ["CastingApps", "chrome://browser/content/CastingApps.js"],
- ["RemoteDebugger", "chrome://browser/content/RemoteDebugger.js"],
-];
-if (!AppConstants.RELEASE_OR_BETA) {
- lazilyLoadedBrowserScripts.push(
- ["WebcompatReporter", "chrome://browser/content/WebcompatReporter.js"]);
-}
-
-lazilyLoadedBrowserScripts.forEach(function (aScript) {
- let [name, script] = aScript;
- XPCOMUtils.defineLazyGetter(window, name, function() {
- let sandbox = {};
- Services.scriptloader.loadSubScript(script, sandbox);
- return sandbox[name];
- });
-});
-
-var lazilyLoadedObserverScripts = [
- ["MemoryObserver", ["memory-pressure", "Memory:Dump"], "chrome://browser/content/MemoryObserver.js"],
- ["ConsoleAPI", ["console-api-log-event"], "chrome://browser/content/ConsoleAPI.js"],
- ["FindHelper", ["FindInPage:Opened", "FindInPage:Closed", "Tab:Selected"], "chrome://browser/content/FindHelper.js"],
- ["PermissionsHelper", ["Permissions:Check", "Permissions:Get", "Permissions:Clear"], "chrome://browser/content/PermissionsHelper.js"],
- ["FeedHandler", ["Feeds:Subscribe"], "chrome://browser/content/FeedHandler.js"],
- ["Feedback", ["Feedback:Show"], "chrome://browser/content/Feedback.js"],
- ["EmbedRT", ["GeckoView:ImportScript"], "chrome://browser/content/EmbedRT.js"],
- ["Reader", ["Reader:AddToCache", "Reader:RemoveFromCache"], "chrome://browser/content/Reader.js"],
- ["PrintHelper", ["Print:PDF"], "chrome://browser/content/PrintHelper.js"],
-];
-
-lazilyLoadedObserverScripts.push(
-["ActionBarHandler", ["TextSelection:Get", "TextSelection:Action", "TextSelection:End"],
- "chrome://browser/content/ActionBarHandler.js"]
-);
-
-if (AppConstants.MOZ_WEBRTC) {
- lazilyLoadedObserverScripts.push(
- ["WebrtcUI", ["getUserMedia:request",
- "PeerConnection:request",
- "recording-device-events",
- "VideoCapture:Paused",
- "VideoCapture:Resumed"], "chrome://browser/content/WebrtcUI.js"])
-}
-
-lazilyLoadedObserverScripts.forEach(function (aScript) {
- let [name, notifications, script] = aScript;
- XPCOMUtils.defineLazyGetter(window, name, function() {
- let sandbox = {};
- Services.scriptloader.loadSubScript(script, sandbox);
- return sandbox[name];
- });
- let observer = (s, t, d) => {
- Services.obs.removeObserver(observer, t);
- Services.obs.addObserver(window[name], t, false);
- window[name].observe(s, t, d); // Explicitly notify new observer
- };
- notifications.forEach((notification) => {
- Services.obs.addObserver(observer, notification, false);
- });
-});
-
-// Lazily-loaded browser scripts that use message listeners.
-[
- ["Reader", [
- ["Reader:AddToCache", false],
- ["Reader:RemoveFromCache", false],
- ["Reader:ArticleGet", false],
- ["Reader:DropdownClosed", true], // 'true' allows us to survive mid-air cycle-collection.
- ["Reader:DropdownOpened", false],
- ["Reader:FaviconRequest", false],
- ["Reader:ToolbarHidden", false],
- ["Reader:SystemUIVisibility", false],
- ["Reader:UpdateReaderButton", false],
- ], "chrome://browser/content/Reader.js"],
-].forEach(aScript => {
- let [name, messages, script] = aScript;
- XPCOMUtils.defineLazyGetter(window, name, function() {
- let sandbox = {};
- Services.scriptloader.loadSubScript(script, sandbox);
- return sandbox[name];
- });
-
- let mm = window.getGroupMessageManager("browsers");
- let listener = (message) => {
- mm.removeMessageListener(message.name, listener);
- let listenAfterClose = false;
- for (let [name, laClose] of messages) {
- if (message.name === name) {
- listenAfterClose = laClose;
- break;
- }
- }
-
- mm.addMessageListener(message.name, window[name], listenAfterClose);
- window[name].receiveMessage(message);
- };
-
- messages.forEach((message) => {
- let [name, listenAfterClose] = message;
- mm.addMessageListener(name, listener, listenAfterClose);
- });
-});
-
-// Lazily-loaded JS modules that use observer notifications
-[
- ["Home", ["HomeBanner:Get", "HomePanels:Get", "HomePanels:Authenticate", "HomePanels:RefreshView",
- "HomePanels:Installed", "HomePanels:Uninstalled"], "resource://gre/modules/Home.jsm"],
-].forEach(module => {
- let [name, notifications, resource] = module;
- XPCOMUtils.defineLazyModuleGetter(this, name, resource);
- let observer = (s, t, d) => {
- Services.obs.removeObserver(observer, t);
- Services.obs.addObserver(this[name], t, false);
- this[name].observe(s, t, d); // Explicitly notify new observer
- };
- notifications.forEach(notification => {
- Services.obs.addObserver(observer, notification, false);
- });
-});
-
-XPCOMUtils.defineLazyServiceGetter(this, "Haptic",
- "@mozilla.org/widget/hapticfeedback;1", "nsIHapticFeedback");
-
-XPCOMUtils.defineLazyServiceGetter(this, "ParentalControls",
- "@mozilla.org/parental-controls-service;1", "nsIParentalControlsService");
-
-XPCOMUtils.defineLazyServiceGetter(this, "DOMUtils",
- "@mozilla.org/inspector/dom-utils;1", "inIDOMUtils");
-
-XPCOMUtils.defineLazyServiceGetter(window, "URIFixup",
- "@mozilla.org/docshell/urifixup;1", "nsIURIFixup");
-
-if (AppConstants.MOZ_WEBRTC) {
- XPCOMUtils.defineLazyServiceGetter(this, "MediaManagerService",
- "@mozilla.org/mediaManagerService;1", "nsIMediaManagerService");
-}
-
-XPCOMUtils.defineLazyModuleGetter(this, "Log",
- "resource://gre/modules/AndroidLog.jsm", "AndroidLog");
-
-// Define the "dump" function as a binding of the Log.d function so it specifies
-// the "debug" priority and a log tag.
-function dump(msg) {
- Log.d("Browser", msg);
-}
-
-const kStateActive = 0x00000001; // :active pseudoclass for elements
-
-const kXLinkNamespace = "http://www.w3.org/1999/xlink";
-
-function fuzzyEquals(a, b) {
- return (Math.abs(a - b) < 1e-6);
-}
-
-XPCOMUtils.defineLazyGetter(this, "ContentAreaUtils", function() {
- let ContentAreaUtils = {};
- Services.scriptloader.loadSubScript("chrome://global/content/contentAreaUtils.js", ContentAreaUtils);
- return ContentAreaUtils;
-});
-
-XPCOMUtils.defineLazyModuleGetter(this, "Rect", "resource://gre/modules/Geometry.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Point", "resource://gre/modules/Geometry.jsm");
-
-function resolveGeckoURI(aURI) {
- if (!aURI)
- throw "Can't resolve an empty uri";
-
- if (aURI.startsWith("chrome://")) {
- let registry = Cc['@mozilla.org/chrome/chrome-registry;1'].getService(Ci["nsIChromeRegistry"]);
- return registry.convertChromeURL(Services.io.newURI(aURI, null, null)).spec;
- } else if (aURI.startsWith("resource://")) {
- let handler = Services.io.getProtocolHandler("resource").QueryInterface(Ci.nsIResProtocolHandler);
- return handler.resolveURI(Services.io.newURI(aURI, null, null));
- }
- return aURI;
-}
-
-/**
- * Cache of commonly used string bundles.
- */
-var Strings = {
- init: function () {
- XPCOMUtils.defineLazyGetter(Strings, "brand", () => Services.strings.createBundle("chrome://branding/locale/brand.properties"));
- XPCOMUtils.defineLazyGetter(Strings, "browser", () => Services.strings.createBundle("chrome://browser/locale/browser.properties"));
- XPCOMUtils.defineLazyGetter(Strings, "reader", () => Services.strings.createBundle("chrome://global/locale/aboutReader.properties"));
- },
-
- flush: function () {
- Services.strings.flushBundles();
- this.init();
- },
-};
-
-Strings.init();
-
-const kFormHelperModeDisabled = 0;
-const kFormHelperModeEnabled = 1;
-const kFormHelperModeDynamic = 2; // disabled on tablets
-const kMaxHistoryListSize = 50;
-
-function InitLater(fn, object, name) {
- return DelayedInit.schedule(fn, object, name, 15000 /* 15s max wait */);
-}
-
-var BrowserApp = {
- _tabs: [],
- _selectedTab: null,
-
- get isTablet() {
- let sysInfo = Cc["@mozilla.org/system-info;1"].getService(Ci.nsIPropertyBag2);
- delete this.isTablet;
- return this.isTablet = sysInfo.get("tablet");
- },
-
- get isOnLowMemoryPlatform() {
- let memory = Cc["@mozilla.org/xpcom/memory-service;1"].getService(Ci.nsIMemory);
- delete this.isOnLowMemoryPlatform;
- return this.isOnLowMemoryPlatform = memory.isLowMemoryPlatform();
- },
-
- deck: null,
-
- startup: function startup() {
- window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow = new nsBrowserAccess();
- dump("zerdatime " + Date.now() + " - browser chrome startup finished.");
- Services.obs.notifyObservers(this.browser, "BrowserChrome:Ready", null);
-
- this.deck = document.getElementById("browsers");
-
- BrowserEventHandler.init();
-
- ViewportHandler.init();
-
- Services.androidBridge.browserApp = this;
-
- Services.obs.addObserver(this, "Locale:OS", false);
- Services.obs.addObserver(this, "Locale:Changed", false);
- Services.obs.addObserver(this, "Tab:Load", false);
- Services.obs.addObserver(this, "Tab:Selected", false);
- Services.obs.addObserver(this, "Tab:Closed", false);
- Services.obs.addObserver(this, "Session:Back", false);
- Services.obs.addObserver(this, "Session:Forward", false);
- Services.obs.addObserver(this, "Session:Navigate", false);
- Services.obs.addObserver(this, "Session:Reload", false);
- Services.obs.addObserver(this, "Session:Stop", false);
- Services.obs.addObserver(this, "SaveAs:PDF", false);
- Services.obs.addObserver(this, "Browser:Quit", false);
- Services.obs.addObserver(this, "ScrollTo:FocusedInput", false);
- Services.obs.addObserver(this, "Sanitize:ClearData", false);
- Services.obs.addObserver(this, "FullScreen:Exit", false);
- Services.obs.addObserver(this, "Passwords:Init", false);
- Services.obs.addObserver(this, "FormHistory:Init", false);
- Services.obs.addObserver(this, "android-get-pref", false);
- Services.obs.addObserver(this, "android-set-pref", false);
- Services.obs.addObserver(this, "gather-telemetry", false);
- Services.obs.addObserver(this, "keyword-search", false);
- Services.obs.addObserver(this, "sessionstore-state-purge-complete", false);
- Services.obs.addObserver(this, "Fonts:Reload", false);
- Services.obs.addObserver(this, "Vibration:Request", false);
-
- Messaging.addListener(this.getHistory.bind(this), "Session:GetHistory");
-
- window.addEventListener("fullscreen", function() {
- Messaging.sendRequest({
- type: window.fullScreen ? "ToggleChrome:Hide" : "ToggleChrome:Show"
- });
- }, false);
-
- window.addEventListener("fullscreenchange", (e) => {
- // This event gets fired on the document and its entire ancestor chain
- // of documents. When enabling fullscreen, it is fired on the top-level
- // document first and goes down; when disabling the order is reversed
- // (per spec). This means the last event on enabling will be for the innermost
- // document, which will have fullscreenElement set correctly.
- let doc = e.target;
- Messaging.sendRequest({
- type: doc.fullscreenElement ? "DOMFullScreen:Start" : "DOMFullScreen:Stop",
- rootElement: doc.fullscreenElement == doc.documentElement
- });
-
- if (this.fullscreenTransitionTab) {
- // Tab selection has changed during a fullscreen transition, handle it now.
- let tab = this.fullscreenTransitionTab;
- this.fullscreenTransitionTab = null;
- this._handleTabSelected(tab);
- }
- }, false);
-
- NativeWindow.init();
- FormAssistant.init();
- IndexedDB.init();
- XPInstallObserver.init();
- CharacterEncoding.init();
- ActivityObserver.init();
- RemoteDebugger.init();
- UserAgentOverrides.init();
- DesktopUserAgent.init();
- Distribution.init();
- Tabs.init();
- SearchEngines.init();
- Experiments.init();
-
- // XXX maybe we don't do this if the launch was kicked off from external
- Services.io.offline = false;
-
- // Broadcast a UIReady message so add-ons know we are finished with startup
- let event = document.createEvent("Events");
- event.initEvent("UIReady", true, false);
- window.dispatchEvent(event);
-
- if (this._startupStatus) {
- this.onAppUpdated();
- }
-
- if (!ParentalControls.isAllowed(ParentalControls.INSTALL_EXTENSION)) {
- // Disable extension installs
- Services.prefs.setIntPref("extensions.enabledScopes", 1);
- Services.prefs.setIntPref("extensions.autoDisableScopes", 1);
- Services.prefs.setBoolPref("xpinstall.enabled", false);
- } else if (ParentalControls.parentalControlsEnabled) {
- Services.prefs.clearUserPref("extensions.enabledScopes");
- Services.prefs.clearUserPref("extensions.autoDisableScopes");
- Services.prefs.setBoolPref("xpinstall.enabled", true);
- }
-
- if (ParentalControls.parentalControlsEnabled) {
- let isBlockListEnabled = ParentalControls.isAllowed(ParentalControls.BLOCK_LIST);
- Services.prefs.setBoolPref("browser.safebrowsing.forbiddenURIs.enabled", isBlockListEnabled);
- Services.prefs.setBoolPref("browser.safebrowsing.allowOverride", !isBlockListEnabled);
-
- let isTelemetryEnabled = ParentalControls.isAllowed(ParentalControls.TELEMETRY);
- Services.prefs.setBoolPref("toolkit.telemetry.enabled", isTelemetryEnabled);
-
- let isHealthReportEnabled = ParentalControls.isAllowed(ParentalControls.HEALTH_REPORT);
- SharedPreferences.forApp().setBoolPref("android.not_a_preference.healthreport.uploadEnabled", isHealthReportEnabled);
- }
-
- let sysInfo = Cc["@mozilla.org/system-info;1"].getService(Ci.nsIPropertyBag2);
- if (sysInfo.get("version") < 16) {
- let defaults = Services.prefs.getDefaultBranch(null);
- defaults.setBoolPref("media.autoplay.enabled", false);
- }
-
- InitLater(() => {
- // The order that context menu items are added is important
- // Make sure the "Open in App" context menu item appears at the bottom of the list
- this.initContextMenu();
- ExternalApps.init();
- }, NativeWindow, "contextmenus");
-
- if (AppConstants.ACCESSIBILITY) {
- InitLater(() => AccessFu.attach(window), window, "AccessFu");
- }
-
- // Don't delay loading content.js because when we restore reader mode tabs,
- // we require the reader mode scripts in content.js right away.
- let mm = window.getGroupMessageManager("browsers");
- mm.loadFrameScript("chrome://browser/content/content.js", true);
-
- // Notify Java that Gecko has loaded.
- Messaging.sendRequest({ type: "Gecko:Ready" });
-
- this.deck.addEventListener("DOMContentLoaded", function BrowserApp_delayedStartup() {
- BrowserApp.deck.removeEventListener("DOMContentLoaded", BrowserApp_delayedStartup, false);
-
- InitLater(() => Cu.import("resource://gre/modules/NotificationDB.jsm"));
- InitLater(() => Cu.import("resource://gre/modules/PresentationDeviceInfoManager.jsm"));
-
- InitLater(() => Services.obs.notifyObservers(window, "browser-delayed-startup-finished", ""));
- InitLater(() => Messaging.sendRequest({ type: "Gecko:DelayedStartup" }));
-
- if (!AppConstants.RELEASE_OR_BETA) {
- InitLater(() => WebcompatReporter.init());
- }
-
- // Collect telemetry data.
- // We do this at startup because we want to move away from "gather-telemetry" (bug 1127907)
- InitLater(() => {
- Telemetry.addData("FENNEC_TRACKING_PROTECTION_STATE", parseInt(BrowserApp.getTrackingProtectionState()));
- Telemetry.addData("ZOOMED_VIEW_ENABLED", Services.prefs.getBoolPref("ui.zoomedview.enabled"));
- });
-
- InitLater(() => LightWeightThemeWebInstaller.init());
- InitLater(() => SpatialNavigation.init(BrowserApp.deck, null), window, "SpatialNavigation");
- InitLater(() => CastingApps.init(), window, "CastingApps");
- InitLater(() => Services.search.init(), Services, "search");
- InitLater(() => DownloadNotifications.init(), window, "DownloadNotifications");
-
-#ifdef MOZ_SAFE_BROWSING
- // Bug 778855 - Perf regression if we do this here. To be addressed in bug 779008.
- InitLater(() => SafeBrowsing.init(), window, "SafeBrowsing");
-#endif
-
- InitLater(() => Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager));
- InitLater(() => LoginManagerParent.init(), window, "LoginManagerParent");
-
- }, false);
-
- // Pass caret StateChanged events to ActionBarHandler.
- window.addEventListener("mozcaretstatechanged", e => {
- ActionBarHandler.caretStateChangedHandler(e);
- }, /* useCapture = */ true, /* wantsUntrusted = */ false);
- },
-
- get _startupStatus() {
- delete this._startupStatus;
-
- let savedMilestone = null;
- try {
- savedMilestone = Services.prefs.getCharPref("browser.startup.homepage_override.mstone");
- } catch (e) {
- }
- let ourMilestone = AppConstants.MOZ_APP_VERSION;
- this._startupStatus = "";
- if (ourMilestone != savedMilestone) {
- Services.prefs.setCharPref("browser.startup.homepage_override.mstone", ourMilestone);
- this._startupStatus = savedMilestone ? "upgrade" : "new";
- }
-
- return this._startupStatus;
- },
-
- /**
- * Pass this a locale string, such as "fr" or "es_ES".
- */
- setLocale: function (locale) {
- console.log("browser.js: requesting locale set: " + locale);
- Messaging.sendRequest({ type: "Locale:Set", locale: locale });
- },
-
- initContextMenu: function () {
- // We pass a thunk in place of a raw label string. This allows the
- // context menu to automatically accommodate locale changes without
- // having to be rebuilt.
- let stringGetter = name => () => Strings.browser.GetStringFromName(name);
-
- // TODO: These should eventually move into more appropriate classes
- NativeWindow.contextmenus.add(stringGetter("contextmenu.openInNewTab"),
- NativeWindow.contextmenus.linkOpenableNonPrivateContext,
- function(aTarget) {
- UITelemetry.addEvent("action.1", "contextmenu", null, "web_open_new_tab");
- UITelemetry.addEvent("loadurl.1", "contextmenu", null);
-
- let url = NativeWindow.contextmenus._getLinkURL(aTarget);
- ContentAreaUtils.urlSecurityCheck(url, aTarget.ownerDocument.nodePrincipal);
- let tab = BrowserApp.addTab(url, { selected: false, parentId: BrowserApp.selectedTab.id });
-
- let newtabStrings = Strings.browser.GetStringFromName("newtabpopup.opened");
- let label = PluralForm.get(1, newtabStrings).replace("#1", 1);
- let buttonLabel = Strings.browser.GetStringFromName("newtabpopup.switch");
-
- Snackbars.show(label, Snackbars.LENGTH_LONG, {
- action: {
- label: buttonLabel,
- callback: () => { BrowserApp.selectTab(tab); },
- }
- });
- });
-
- let showOpenInPrivateTab = true;
- if ("@mozilla.org/parental-controls-service;1" in Cc) {
- let pc = Cc["@mozilla.org/parental-controls-service;1"].createInstance(Ci.nsIParentalControlsService);
- showOpenInPrivateTab = pc.isAllowed(Ci.nsIParentalControlsService.PRIVATE_BROWSING);
- }
-
- if (showOpenInPrivateTab) {
- NativeWindow.contextmenus.add(stringGetter("contextmenu.openInPrivateTab"),
- NativeWindow.contextmenus.linkOpenableContext,
- function (aTarget) {
- UITelemetry.addEvent("action.1", "contextmenu", null, "web_open_new_tab");
- UITelemetry.addEvent("loadurl.1", "contextmenu", null);
-
- let url = NativeWindow.contextmenus._getLinkURL(aTarget);
- ContentAreaUtils.urlSecurityCheck(url, aTarget.ownerDocument.nodePrincipal);
- let tab = BrowserApp.addTab(url, {selected: false, parentId: BrowserApp.selectedTab.id, isPrivate: true});
-
- let newtabStrings = Strings.browser.GetStringFromName("newprivatetabpopup.opened");
- let label = PluralForm.get(1, newtabStrings).replace("#1", 1);
- let buttonLabel = Strings.browser.GetStringFromName("newtabpopup.switch");
- Snackbars.show(label, Snackbars.LENGTH_LONG, {
- action: {
- label: buttonLabel,
- callback: () => { BrowserApp.selectTab(tab); },
- }
- });
- });
- }
-
- NativeWindow.contextmenus.add(stringGetter("contextmenu.copyLink"),
- NativeWindow.contextmenus.linkCopyableContext,
- function(aTarget) {
- UITelemetry.addEvent("action.1", "contextmenu", null, "web_copy_link");
-
- let url = NativeWindow.contextmenus._getLinkURL(aTarget);
- NativeWindow.contextmenus._copyStringToDefaultClipboard(url);
- });
-
- NativeWindow.contextmenus.add(stringGetter("contextmenu.copyEmailAddress"),
- NativeWindow.contextmenus.emailLinkContext,
- function(aTarget) {
- UITelemetry.addEvent("action.1", "contextmenu", null, "web_copy_email");
-
- let url = NativeWindow.contextmenus._getLinkURL(aTarget);
- let emailAddr = NativeWindow.contextmenus._stripScheme(url);
- NativeWindow.contextmenus._copyStringToDefaultClipboard(emailAddr);
- });
-
- NativeWindow.contextmenus.add(stringGetter("contextmenu.copyPhoneNumber"),
- NativeWindow.contextmenus.phoneNumberLinkContext,
- function(aTarget) {
- UITelemetry.addEvent("action.1", "contextmenu", null, "web_copy_phone");
-
- let url = NativeWindow.contextmenus._getLinkURL(aTarget);
- let phoneNumber = NativeWindow.contextmenus._stripScheme(url);
- NativeWindow.contextmenus._copyStringToDefaultClipboard(phoneNumber);
- });
-
- NativeWindow.contextmenus.add({
- label: stringGetter("contextmenu.shareLink"),
- order: NativeWindow.contextmenus.DEFAULT_HTML5_ORDER - 1, // Show above HTML5 menu items
- selector: NativeWindow.contextmenus._disableRestricted("SHARE", NativeWindow.contextmenus.linkShareableContext),
- showAsActions: function(aElement) {
- return {
- title: aElement.textContent.trim() || aElement.title.trim(),
- uri: NativeWindow.contextmenus._getLinkURL(aElement),
- };
- },
- icon: "drawable://ic_menu_share",
- callback: function(aTarget) {
- // share.1 telemetry is handled in Java via PromptList
- UITelemetry.addEvent("action.1", "contextmenu", null, "web_share_link");
- }
- });
-
- NativeWindow.contextmenus.add({
- label: stringGetter("contextmenu.shareEmailAddress"),
- order: NativeWindow.contextmenus.DEFAULT_HTML5_ORDER - 1,
- selector: NativeWindow.contextmenus._disableRestricted("SHARE", NativeWindow.contextmenus.emailLinkContext),
- showAsActions: function(aElement) {
- let url = NativeWindow.contextmenus._getLinkURL(aElement);
- let emailAddr = NativeWindow.contextmenus._stripScheme(url);
- let title = aElement.textContent || aElement.title;
- return {
- title: title,
- uri: emailAddr,
- };
- },
- icon: "drawable://ic_menu_share",
- callback: function(aTarget) {
- // share.1 telemetry is handled in Java via PromptList
- UITelemetry.addEvent("action.1", "contextmenu", null, "web_share_email");
- }
- });
-
- NativeWindow.contextmenus.add({
- label: stringGetter("contextmenu.sharePhoneNumber"),
- order: NativeWindow.contextmenus.DEFAULT_HTML5_ORDER - 1,
- selector: NativeWindow.contextmenus._disableRestricted("SHARE", NativeWindow.contextmenus.phoneNumberLinkContext),
- showAsActions: function(aElement) {
- let url = NativeWindow.contextmenus._getLinkURL(aElement);
- let phoneNumber = NativeWindow.contextmenus._stripScheme(url);
- let title = aElement.textContent || aElement.title;
- return {
- title: title,
- uri: phoneNumber,
- };
- },
- icon: "drawable://ic_menu_share",
- callback: function(aTarget) {
- // share.1 telemetry is handled in Java via PromptList
- UITelemetry.addEvent("action.1", "contextmenu", null, "web_share_phone");
- }
- });
-
- NativeWindow.contextmenus.add(stringGetter("contextmenu.addToContacts"),
- NativeWindow.contextmenus._disableRestricted("ADD_CONTACT", NativeWindow.contextmenus.emailLinkContext),
- function(aTarget) {
- UITelemetry.addEvent("action.1", "contextmenu", null, "web_contact_email");
-
- let url = NativeWindow.contextmenus._getLinkURL(aTarget);
- Messaging.sendRequest({
- type: "Contact:Add",
- email: url
- });
- });
-
- NativeWindow.contextmenus.add(stringGetter("contextmenu.addToContacts"),
- NativeWindow.contextmenus._disableRestricted("ADD_CONTACT", NativeWindow.contextmenus.phoneNumberLinkContext),
- function(aTarget) {
- UITelemetry.addEvent("action.1", "contextmenu", null, "web_contact_phone");
-
- let url = NativeWindow.contextmenus._getLinkURL(aTarget);
- Messaging.sendRequest({
- type: "Contact:Add",
- phone: url
- });
- });
-
- NativeWindow.contextmenus.add(stringGetter("contextmenu.bookmarkLink"),
- NativeWindow.contextmenus._disableRestricted("BOOKMARK", NativeWindow.contextmenus.linkBookmarkableContext),
- function(aTarget) {
- UITelemetry.addEvent("action.1", "contextmenu", null, "web_bookmark");
- UITelemetry.addEvent("save.1", "contextmenu", null, "bookmark");
-
- let url = NativeWindow.contextmenus._getLinkURL(aTarget);
- let title = aTarget.textContent || aTarget.title || url;
- Messaging.sendRequest({
- type: "Bookmark:Insert",
- url: url,
- title: title
- });
- });
-
- NativeWindow.contextmenus.add(stringGetter("contextmenu.playMedia"),
- NativeWindow.contextmenus.mediaContext("media-paused"),
- function(aTarget) {
- UITelemetry.addEvent("action.1", "contextmenu", null, "web_play");
- aTarget.play();
- });
-
- NativeWindow.contextmenus.add(stringGetter("contextmenu.pauseMedia"),
- NativeWindow.contextmenus.mediaContext("media-playing"),
- function(aTarget) {
- UITelemetry.addEvent("action.1", "contextmenu", null, "web_pause");
- aTarget.pause();
- });
-
- NativeWindow.contextmenus.add(stringGetter("contextmenu.showControls2"),
- NativeWindow.contextmenus.mediaContext("media-hidingcontrols"),
- function(aTarget) {
- UITelemetry.addEvent("action.1", "contextmenu", null, "web_controls_media");
- aTarget.setAttribute("controls", true);
- });
-
- NativeWindow.contextmenus.add({
- label: stringGetter("contextmenu.shareMedia"),
- order: NativeWindow.contextmenus.DEFAULT_HTML5_ORDER - 1,
- selector: NativeWindow.contextmenus._disableRestricted(
- "SHARE", NativeWindow.contextmenus.videoContext()),
- showAsActions: function(aElement) {
- let url = (aElement.currentSrc || aElement.src);
- let title = aElement.textContent || aElement.title;
- return {
- title: title,
- uri: url,
- type: "video/*",
- };
- },
- icon: "drawable://ic_menu_share",
- callback: function(aTarget) {
- // share.1 telemetry is handled in Java via PromptList
- UITelemetry.addEvent("action.1", "contextmenu", null, "web_share_media");
- }
- });
-
- NativeWindow.contextmenus.add(stringGetter("contextmenu.fullScreen"),
- NativeWindow.contextmenus.videoContext("not-fullscreen"),
- function(aTarget) {
- UITelemetry.addEvent("action.1", "contextmenu", null, "web_fullscreen");
- aTarget.requestFullscreen();
- });
-
- NativeWindow.contextmenus.add(stringGetter("contextmenu.mute"),
- NativeWindow.contextmenus.mediaContext("media-unmuted"),
- function(aTarget) {
- UITelemetry.addEvent("action.1", "contextmenu", null, "web_mute");
- aTarget.muted = true;
- });
-
- NativeWindow.contextmenus.add(stringGetter("contextmenu.unmute"),
- NativeWindow.contextmenus.mediaContext("media-muted"),
- function(aTarget) {
- UITelemetry.addEvent("action.1", "contextmenu", null, "web_unmute");
- aTarget.muted = false;
- });
-
- NativeWindow.contextmenus.add(stringGetter("contextmenu.viewImage"),
- NativeWindow.contextmenus.imageLocationCopyableContext,
- function(aTarget) {
- let url = aTarget.src;
- ContentAreaUtils.urlSecurityCheck(url, aTarget.ownerDocument.nodePrincipal,
- Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
-
- UITelemetry.addEvent("action.1", "contextmenu", null, "web_view_image");
- UITelemetry.addEvent("loadurl.1", "contextmenu", null);
- BrowserApp.selectedBrowser.loadURI(url);
- });
-
- NativeWindow.contextmenus.add(stringGetter("contextmenu.copyImageLocation"),
- NativeWindow.contextmenus.imageLocationCopyableContext,
- function(aTarget) {
- UITelemetry.addEvent("action.1", "contextmenu", null, "web_copy_image");
-
- let url = aTarget.src;
- NativeWindow.contextmenus._copyStringToDefaultClipboard(url);
- });
-
- NativeWindow.contextmenus.add({
- label: stringGetter("contextmenu.shareImage"),
- selector: NativeWindow.contextmenus._disableRestricted("SHARE", NativeWindow.contextmenus.imageShareableContext),
- order: NativeWindow.contextmenus.DEFAULT_HTML5_ORDER - 1, // Show above HTML5 menu items
- showAsActions: function(aTarget) {
- let doc = aTarget.ownerDocument;
- let imageCache = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
- .getImgCacheForDocument(doc);
- let props = imageCache.findEntryProperties(aTarget.currentURI, doc);
- let src = aTarget.src;
- return {
- title: src,
- uri: src,
- type: "image/*",
- };
- },
- icon: "drawable://ic_menu_share",
- menu: true,
- callback: function(aTarget) {
- UITelemetry.addEvent("action.1", "contextmenu", null, "web_share_image");
- }
- });
-
- NativeWindow.contextmenus.add(stringGetter("contextmenu.saveImage"),
- NativeWindow.contextmenus.imageSaveableContext,
- function(aTarget) {
- UITelemetry.addEvent("action.1", "contextmenu", null, "web_save_image");
- UITelemetry.addEvent("save.1", "contextmenu", null, "image");
-
- RuntimePermissions.waitForPermissions(RuntimePermissions.WRITE_EXTERNAL_STORAGE).then(function(permissionGranted) {
- if (!permissionGranted) {
- return;
- }
-
- ContentAreaUtils.saveImageURL(aTarget.currentURI.spec, null, "SaveImageTitle",
- false, true, aTarget.ownerDocument.documentURIObject,
- aTarget.ownerDocument);
- });
- });
-
- NativeWindow.contextmenus.add(stringGetter("contextmenu.setImageAs"),
- NativeWindow.contextmenus._disableRestricted("SET_IMAGE", NativeWindow.contextmenus.imageSaveableContext),
- function(aTarget) {
- UITelemetry.addEvent("action.1", "contextmenu", null, "web_background_image");
-
- let src = aTarget.src;
- Messaging.sendRequest({
- type: "Image:SetAs",
- url: src
- });
- });
-
- NativeWindow.contextmenus.add(
- function(aTarget) {
- if (aTarget instanceof HTMLVideoElement) {
- // If a video element is zero width or height, its essentially
- // an HTMLAudioElement.
- if (aTarget.videoWidth == 0 || aTarget.videoHeight == 0 )
- return Strings.browser.GetStringFromName("contextmenu.saveAudio");
- return Strings.browser.GetStringFromName("contextmenu.saveVideo");
- } else if (aTarget instanceof HTMLAudioElement) {
- return Strings.browser.GetStringFromName("contextmenu.saveAudio");
- }
- return Strings.browser.GetStringFromName("contextmenu.saveVideo");
- }, NativeWindow.contextmenus.mediaSaveableContext,
- function(aTarget) {
- UITelemetry.addEvent("action.1", "contextmenu", null, "web_save_media");
- UITelemetry.addEvent("save.1", "contextmenu", null, "media");
-
- let url = aTarget.currentSrc || aTarget.src;
- let filePickerTitleKey = (aTarget instanceof HTMLVideoElement &&
- (aTarget.videoWidth != 0 && aTarget.videoHeight != 0))
- ? "SaveVideoTitle" : "SaveAudioTitle";
- // Skipped trying to pull MIME type out of cache for now
- ContentAreaUtils.internalSave(url, null, null, null, null, false,
- filePickerTitleKey, null, aTarget.ownerDocument.documentURIObject,
- aTarget.ownerDocument, true, null);
- });
-
- NativeWindow.contextmenus.add(stringGetter("contextmenu.showImage"),
- NativeWindow.contextmenus.imageBlockingPolicyContext,
- function(aTarget) {
- UITelemetry.addEvent("action.1", "contextmenu", null, "web_show_image");
- aTarget.setAttribute("data-ctv-show", "true");
- aTarget.setAttribute("src", aTarget.getAttribute("data-ctv-src"));
-
- // Shows a snackbar to unblock all images if browser.image_blocking.enabled is enabled.
- let blockedImgs = aTarget.ownerDocument.querySelectorAll("[data-ctv-src]");
- if (blockedImgs.length == 0) {
- return;
- }
- let message = Strings.browser.GetStringFromName("imageblocking.downloadedImage");
- Snackbars.show(message, Snackbars.LENGTH_LONG, {
- action: {
- label: Strings.browser.GetStringFromName("imageblocking.showAllImages"),
- callback: () => {
- UITelemetry.addEvent("action.1", "toast", null, "web_show_all_image");
- for (let i = 0; i < blockedImgs.length; ++i) {
- blockedImgs[i].setAttribute("data-ctv-show", "true");
- blockedImgs[i].setAttribute("src", blockedImgs[i].getAttribute("data-ctv-src"));
- }
- },
- }
- });
- });
- },
-
- onAppUpdated: function() {
- // initialize the form history and passwords databases on upgrades
- Services.obs.notifyObservers(null, "FormHistory:Init", "");
- Services.obs.notifyObservers(null, "Passwords:Init", "");
-
- if (this._startupStatus === "upgrade") {
- this._migrateUI();
- }
- },
-
- _migrateUI: function() {
- const UI_VERSION = 3;
- let currentUIVersion = 0;
- try {
- currentUIVersion = Services.prefs.getIntPref("browser.migration.version");
- } catch(ex) {}
- if (currentUIVersion >= UI_VERSION) {
- return;
- }
-
- if (currentUIVersion < 1) {
- // Migrate user-set "plugins.click_to_play" pref. See bug 884694.
- // Because the default value is true, a user-set pref means that the pref was set to false.
- if (Services.prefs.prefHasUserValue("plugins.click_to_play")) {
- Services.prefs.setIntPref("plugin.default.state", Ci.nsIPluginTag.STATE_ENABLED);
- Services.prefs.clearUserPref("plugins.click_to_play");
- }
-
- // Migrate the "privacy.donottrackheader.value" pref. See bug 1042135.
- if (Services.prefs.prefHasUserValue("privacy.donottrackheader.value")) {
- // Make sure the doNotTrack value conforms to the conversion from
- // three-state to two-state. (This reverts a setting of "please track me"
- // to the default "don't say anything").
- if (Services.prefs.getBoolPref("privacy.donottrackheader.enabled") &&
- (Services.prefs.getIntPref("privacy.donottrackheader.value") != 1)) {
- Services.prefs.clearUserPref("privacy.donottrackheader.enabled");
- }
-
- // This pref has been removed, so always clear it.
- Services.prefs.clearUserPref("privacy.donottrackheader.value");
- }
-
- // Set the search activity default pref on app upgrade if it has not been set already.
- if (!Services.prefs.prefHasUserValue("searchActivity.default.migrated")) {
- Services.prefs.setBoolPref("searchActivity.default.migrated", true);
- SearchEngines.migrateSearchActivityDefaultPref();
- }
-
- Reader.migrateCache().catch(e => Cu.reportError("Error migrating Reader cache: " + e));
-
- // We removed this pref from user visible settings, so we should reset it.
- // Power users can go into about:config to re-enable this if they choose.
- if (Services.prefs.prefHasUserValue("nglayout.debug.paint_flashing")) {
- Services.prefs.clearUserPref("nglayout.debug.paint_flashing");
- }
- }
-
- if (currentUIVersion < 2) {
- let name;
- if (Services.prefs.prefHasUserValue("browser.search.defaultenginename")) {
- name = Services.prefs.getCharPref("browser.search.defaultenginename");
- }
- if (!name && Services.prefs.prefHasUserValue("browser.search.defaultenginename.US")) {
- name = Services.prefs.getCharPref("browser.search.defaultenginename.US");
- }
- if (name) {
- Services.search.init(() => {
- let engine = Services.search.getEngineByName(name);
- if (engine) {
- Services.search.defaultEngine = engine;
- Services.obs.notifyObservers(null, "default-search-engine-migrated", "");
- }
- });
- }
- }
-
- if (currentUIVersion < 3) {
- const kOldSafeBrowsingPref = "browser.safebrowsing.enabled";
- // Default value is set to true, a user pref means that the pref was
- // set to false.
- if (Services.prefs.prefHasUserValue(kOldSafeBrowsingPref) &&
- !Services.prefs.getBoolPref(kOldSafeBrowsingPref)) {
- Services.prefs.setBoolPref("browser.safebrowsing.phishing.enabled",
- false);
- // Should just remove support for the pref entirely, even if it's
- // only in about:config
- Services.prefs.clearUserPref(kOldSafeBrowsingPref);
- }
- }
-
- // Update the migration version.
- Services.prefs.setIntPref("browser.migration.version", UI_VERSION);
- },
-
- // This function returns false during periods where the browser displayed document is
- // different from the browser content document, so user actions and some kinds of viewport
- // updates should be ignored. This period starts when we start loading a new page or
- // switch tabs, and ends when the new browser content document has been drawn and handed
- // off to the compositor.
- isBrowserContentDocumentDisplayed: function() {
- try {
- if (!Services.androidBridge.isContentDocumentDisplayed(window))
- return false;
- } catch (e) {
- return false;
- }
-
- let tab = this.selectedTab;
- if (!tab)
- return false;
- return tab.contentDocumentIsDisplayed;
- },
-
- contentDocumentChanged: function() {
- window.top.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).isFirstPaint = true;
- Services.androidBridge.contentDocumentChanged(window);
- },
-
- get tabs() {
- return this._tabs;
- },
-
- set selectedTab(aTab) {
- if (this._selectedTab == aTab)
- return;
-
- if (this._selectedTab) {
- this._selectedTab.setActive(false);
- }
-
- this._selectedTab = aTab;
- if (!aTab)
- return;
-
- aTab.setActive(true);
- this.contentDocumentChanged();
- this.deck.selectedPanel = aTab.browser;
- // Focus the browser so that things like selection will be styled correctly.
- aTab.browser.focus();
- },
-
- get selectedBrowser() {
- if (this._selectedTab)
- return this._selectedTab.browser;
- return null;
- },
-
- getTabForId: function getTabForId(aId) {
- let tabs = this._tabs;
- for (let i=0; i < tabs.length; i++) {
- if (tabs[i].id == aId)
- return tabs[i];
- }
- return null;
- },
-
- getTabForBrowser: function getTabForBrowser(aBrowser) {
- let tabs = this._tabs;
- for (let i = 0; i < tabs.length; i++) {
- if (tabs[i].browser == aBrowser)
- return tabs[i];
- }
- return null;
- },
-
- getTabForWindow: function getTabForWindow(aWindow) {
- let tabs = this._tabs;
- for (let i = 0; i < tabs.length; i++) {
- if (tabs[i].browser.contentWindow == aWindow)
- return tabs[i];
- }
- return null;
- },
-
- getBrowserForWindow: function getBrowserForWindow(aWindow) {
- let tabs = this._tabs;
- for (let i = 0; i < tabs.length; i++) {
- if (tabs[i].browser.contentWindow == aWindow)
- return tabs[i].browser;
- }
- return null;
- },
-
- getBrowserForDocument: function getBrowserForDocument(aDocument) {
- let tabs = this._tabs;
- for (let i = 0; i < tabs.length; i++) {
- if (tabs[i].browser.contentDocument == aDocument)
- return tabs[i].browser;
- }
- return null;
- },
-
- loadURI: function loadURI(aURI, aBrowser, aParams) {
- aBrowser = aBrowser || this.selectedBrowser;
- if (!aBrowser)
- return;
-
- aParams = aParams || {};
-
- let flags = "flags" in aParams ? aParams.flags : Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
- let postData = ("postData" in aParams && aParams.postData) ? aParams.postData : null;
- let referrerURI = "referrerURI" in aParams ? aParams.referrerURI : null;
- let charset = "charset" in aParams ? aParams.charset : null;
-
- let tab = this.getTabForBrowser(aBrowser);
- if (tab) {
- if ("userRequested" in aParams) tab.userRequested = aParams.userRequested;
- tab.isSearch = ("isSearch" in aParams) ? aParams.isSearch : false;
- }
-
- try {
- aBrowser.loadURIWithFlags(aURI, flags, referrerURI, charset, postData);
- } catch(e) {
- if (tab) {
- let message = {
- type: "Content:LoadError",
- tabID: tab.id
- };
- Messaging.sendRequest(message);
- dump("Handled load error: " + e)
- }
- }
- },
-
- addTab: function addTab(aURI, aParams) {
- aParams = aParams || {};
-
- let newTab = new Tab(aURI, aParams);
-
- if (typeof aParams.tabIndex == "number") {
- this._tabs.splice(aParams.tabIndex, 0, newTab);
- } else {
- this._tabs.push(newTab);
- }
-
- let selected = "selected" in aParams ? aParams.selected : true;
- if (selected)
- this.selectedTab = newTab;
-
- let pinned = "pinned" in aParams ? aParams.pinned : false;
- if (pinned) {
- let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
- ss.setTabValue(newTab, "appOrigin", aURI);
- }
-
- let evt = document.createEvent("UIEvents");
- evt.initUIEvent("TabOpen", true, false, window, null);
- newTab.browser.dispatchEvent(evt);
-
- return newTab;
- },
-
- // Use this method to close a tab from JS. This method sends a message
- // to Java to close the tab in the Java UI (we'll get a Tab:Closed message
- // back from Java when that happens).
- closeTab: function closeTab(aTab) {
- if (!aTab) {
- Cu.reportError("Error trying to close tab (tab doesn't exist)");
- return;
- }
-
- let message = {
- type: "Tab:Close",
- tabID: aTab.id
- };
- Messaging.sendRequest(message);
- },
-
- // Calling this will update the state in BrowserApp after a tab has been
- // closed in the Java UI.
- _handleTabClosed: function _handleTabClosed(aTab, aShowUndoSnackbar) {
- if (aTab == this.selectedTab)
- this.selectedTab = null;
-
- let tabIndex = this._tabs.indexOf(aTab);
-
- let evt = document.createEvent("UIEvents");
- evt.initUIEvent("TabClose", true, false, window, tabIndex);
- aTab.browser.dispatchEvent(evt);
-
- if (aShowUndoSnackbar) {
- // Get a title for the undo close snackbar. Fall back to the URL if there is no title.
- let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
- let closedTabData = ss.getClosedTabs(window)[0];
-
- let message;
- let title = closedTabData.entries[closedTabData.index - 1].title;
- let isPrivate = PrivateBrowsingUtils.isBrowserPrivate(aTab.browser);
-
- if (isPrivate) {
- message = Strings.browser.GetStringFromName("privateClosedMessage.message");
- } else if (title) {
- message = Strings.browser.formatStringFromName("undoCloseToast.message", [title], 1);
- } else {
- message = Strings.browser.GetStringFromName("undoCloseToast.messageDefault");
- }
-
- Snackbars.show(message, Snackbars.LENGTH_LONG, {
- action: {
- label: Strings.browser.GetStringFromName("undoCloseToast.action2"),
- callback: function() {
- UITelemetry.addEvent("undo.1", "toast", null, "closetab");
- ss.undoCloseTab(window, closedTabData);
- }
- }
- });
- }
-
- aTab.destroy();
- this._tabs.splice(tabIndex, 1);
- },
-
- // Use this method to select a tab from JS. This method sends a message
- // to Java to select the tab in the Java UI (we'll get a Tab:Selected message
- // back from Java when that happens).
- selectTab: function selectTab(aTab) {
- if (!aTab) {
- Cu.reportError("Error trying to select tab (tab doesn't exist)");
- return;
- }
-
- // There's nothing to do if the tab is already selected
- if (aTab == this.selectedTab)
- return;
-
- let doc = this.selectedBrowser.contentDocument;
- if (doc.fullscreenElement) {
- // We'll finish the tab selection once the fullscreen transition has ended,
- // remember the new tab for this.
- this.fullscreenTransitionTab = aTab;
- doc.exitFullscreen();
- }
-
- let message = {
- type: "Tab:Select",
- tabID: aTab.id
- };
- Messaging.sendRequest(message);
- },
-
- /**
- * Gets an open tab with the given URL.
- *
- * @param aURL URL to look for
- * @param aOptions Options for the search. Currently supports:
- ** @option startsWith a Boolean indicating whether to search for a tab who's url starts with the
- * requested url. Useful if you want to ignore hash codes on the end of a url. For instance
- * to have about:downloads match about:downloads#123.
- * @return the tab with the given URL, or null if no such tab exists
- */
- getTabWithURL: function getTabWithURL(aURL, aOptions) {
- aOptions = aOptions || {};
- let uri = Services.io.newURI(aURL, null, null);
- for (let i = 0; i < this._tabs.length; ++i) {
- let tab = this._tabs[i];
- if (aOptions.startsWith) {
- if (tab.browser.currentURI.spec.startsWith(aURL)) {
- return tab;
- }
- } else {
- if (tab.browser.currentURI.equals(uri)) {
- return tab;
- }
- }
- }
- return null;
- },
-
- /**
- * If a tab with the given URL already exists, that tab is selected.
- * Otherwise, a new tab is opened with the given URL.
- *
- * @param aURL URL to open
- * @param aParam Options used if a tab is created
- * @param aFlags Options for the search. Currently supports:
- ** @option startsWith a Boolean indicating whether to search for a tab who's url starts with the
- * requested url. Useful if you want to ignore hash codes on the end of a url. For instance
- * to have about:downloads match about:downloads#123.
- */
- selectOrAddTab: function selectOrAddTab(aURL, aParams, aFlags) {
- let tab = this.getTabWithURL(aURL, aFlags);
- if (tab == null) {
- tab = this.addTab(aURL, aParams);
- } else {
- this.selectTab(tab);
- }
-
- return tab;
- },
-
- // This method updates the state in BrowserApp after a tab has been selected
- // in the Java UI.
- _handleTabSelected: function _handleTabSelected(aTab) {
- if (this.fullscreenTransitionTab) {
- // Defer updating to "fullscreenchange" if tab selection happened during
- // a fullscreen transition.
- return;
- }
- this.selectedTab = aTab;
-
- let evt = document.createEvent("UIEvents");
- evt.initUIEvent("TabSelect", true, false, window, null);
- aTab.browser.dispatchEvent(evt);
- },
-
- quit: function quit(aClear = { sanitize: {}, dontSaveSession: false }) {
- // Notify all windows that an application quit has been requested.
- let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool);
- Services.obs.notifyObservers(cancelQuit, "quit-application-requested", null);
-
- // Quit aborted.
- if (cancelQuit.data) {
- return;
- }
-
- Services.obs.notifyObservers(null, "quit-application-proceeding", null);
-
- // Tell session store to forget about this window
- if (aClear.dontSaveSession) {
- let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
- ss.removeWindow(window);
- }
-
- BrowserApp.sanitize(aClear.sanitize, function() {
- let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"].getService(Ci.nsIAppStartup);
- appStartup.quit(Ci.nsIAppStartup.eForceQuit);
- }, true);
- },
-
- saveAsPDF: function saveAsPDF(aBrowser) {
- RuntimePermissions.waitForPermissions(RuntimePermissions.WRITE_EXTERNAL_STORAGE).then(function(permissionGranted) {
- if (!permissionGranted) {
- return;
- }
-
- Task.spawn(function* () {
- let fileName = ContentAreaUtils.getDefaultFileName(aBrowser.contentTitle, aBrowser.currentURI, null, null);
- fileName = fileName.trim() + ".pdf";
-
- let downloadsDir = yield Downloads.getPreferredDownloadsDirectory();
- let file = OS.Path.join(downloadsDir, fileName);
-
- // Force this to have a unique name.
- let openedFile = yield OS.File.openUnique(file, { humanReadable: true });
- file = openedFile.path;
- yield openedFile.file.close();
-
- let download = yield Downloads.createDownload({
- source: aBrowser.contentWindow,
- target: file,
- saver: "pdf",
- startTime: Date.now(),
- });
-
- let list = yield Downloads.getList(download.source.isPrivate ? Downloads.PRIVATE : Downloads.PUBLIC)
- yield list.add(download);
- yield download.start();
- });
- });
- },
-
- // These values come from pref_tracking_protection_entries in arrays.xml.
- PREF_TRACKING_PROTECTION_ENABLED: "2",
- PREF_TRACKING_PROTECTION_ENABLED_PB: "1",
- PREF_TRACKING_PROTECTION_DISABLED: "0",
-
- /**
- * Returns the current state of the tracking protection pref.
- * (0 = Disabled, 1 = Enabled in PB, 2 = Enabled)
- */
- getTrackingProtectionState: function() {
- if (Services.prefs.getBoolPref("privacy.trackingprotection.enabled")) {
- return this.PREF_TRACKING_PROTECTION_ENABLED;
- }
- if (Services.prefs.getBoolPref("privacy.trackingprotection.pbmode.enabled")) {
- return this.PREF_TRACKING_PROTECTION_ENABLED_PB;
- }
- return this.PREF_TRACKING_PROTECTION_DISABLED;
- },
-
- sanitize: function (aItems, callback, aShutdown) {
- let success = true;
- var promises = [];
-
- for (let key in aItems) {
- if (!aItems[key])
- continue;
-
- key = key.replace("private.data.", "");
-
- switch (key) {
- case "cookies_sessions":
- promises.push(Sanitizer.clearItem("cookies"));
- promises.push(Sanitizer.clearItem("sessions"));
- break;
- default:
- promises.push(Sanitizer.clearItem(key));
- }
- }
-
- Promise.all(promises).then(function() {
- Messaging.sendRequest({
- type: "Sanitize:Finished",
- success: true,
- shutdown: aShutdown === true
- });
-
- if (callback) {
- callback();
- }
- }).catch(function(err) {
- Messaging.sendRequest({
- type: "Sanitize:Finished",
- error: err,
- success: false,
- shutdown: aShutdown === true
- });
-
- if (callback) {
- callback();
- }
- })
- },
-
- getFocusedInput: function(aBrowser, aOnlyInputElements = false) {
- if (!aBrowser)
- return null;
-
- let doc = aBrowser.contentDocument;
- if (!doc)
- return null;
-
- let focused = doc.activeElement;
- while (focused instanceof HTMLFrameElement || focused instanceof HTMLIFrameElement) {
- doc = focused.contentDocument;
- focused = doc.activeElement;
- }
-
- if (focused instanceof HTMLInputElement &&
- (focused.mozIsTextField(false) || focused.type === "number")) {
- return focused;
- }
-
- if (aOnlyInputElements)
- return null;
-
- if (focused && (focused instanceof HTMLTextAreaElement || focused.isContentEditable)) {
-
- if (focused instanceof HTMLBodyElement) {
- // we are putting focus into a contentEditable frame. scroll the frame into
- // view instead of the contentEditable document contained within, because that
- // results in a better user experience
- focused = focused.ownerDocument.defaultView.frameElement;
- }
- return focused;
- }
- return null;
- },
-
- scrollToFocusedInput: function(aBrowser, aAllowZoom = true) {
- let formHelperMode = Services.prefs.getIntPref("formhelper.mode");
- if (formHelperMode == kFormHelperModeDisabled)
- return;
-
- let dwu = aBrowser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
- if (!dwu) {
- return;
- }
-
- let apzFlushDone = function() {
- Services.obs.removeObserver(apzFlushDone, "apz-repaints-flushed", false);
- dwu.zoomToFocusedInput();
- };
-
- let paintDone = function() {
- window.removeEventListener("MozAfterPaint", paintDone, false);
- if (dwu.flushApzRepaints()) {
- Services.obs.addObserver(apzFlushDone, "apz-repaints-flushed", false);
- } else {
- apzFlushDone();
- }
- };
-
- let gotResizeWindow = false;
- let resizeWindow = function(e) {
- gotResizeWindow = true;
- aBrowser.contentWindow.removeEventListener("resize", resizeWindow, false);
- if (dwu.isMozAfterPaintPending) {
- window.addEventListener("MozAfterPaint", paintDone, false);
- } else {
- paintDone();
- }
- }
-
- aBrowser.contentWindow.addEventListener("resize", resizeWindow, false);
-
- // The "resize" event sometimes fails to fire, so set a timer to catch that case
- // and unregister the event listener. See Bug 1253469
- setTimeout(function(e) {
- if (!gotResizeWindow) {
- aBrowser.contentWindow.removeEventListener("resize", resizeWindow, false);
- dwu.zoomToFocusedInput();
- }
- }, 500);
- },
-
- getUALocalePref: function () {
- try {
- return Services.prefs.getComplexValue("general.useragent.locale", Ci.nsIPrefLocalizedString).data;
- } catch (e) {
- try {
- return Services.prefs.getCharPref("general.useragent.locale");
- } catch (ee) {
- return undefined;
- }
- }
- },
-
- getOSLocalePref: function () {
- try {
- return Services.prefs.getCharPref("intl.locale.os");
- } catch (e) {
- return undefined;
- }
- },
-
- setLocalizedPref: function (pref, value) {
- let pls = Cc["@mozilla.org/pref-localizedstring;1"]
- .createInstance(Ci.nsIPrefLocalizedString);
- pls.data = value;
- Services.prefs.setComplexValue(pref, Ci.nsIPrefLocalizedString, pls);
- },
-
- observe: function(aSubject, aTopic, aData) {
- let browser = this.selectedBrowser;
-
- switch (aTopic) {
-
- case "Session:Back":
- browser.goBack();
- break;
-
- case "Session:Forward":
- browser.goForward();
- break;
-
- case "Session:Navigate":
- let index = JSON.parse(aData);
- let webNav = BrowserApp.selectedTab.window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation);
- let historySize = webNav.sessionHistory.count;
-
- if (index < 0) {
- index = 0;
- Log.e("Browser", "Negative index truncated to zero");
- } else if (index >= historySize) {
- Log.e("Browser", "Incorrect index " + index + " truncated to " + historySize - 1);
- index = historySize - 1;
- }
-
- browser.gotoIndex(index);
- break;
-
- case "Session:Reload": {
- let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
-
- // Check to see if this is a message to enable/disable mixed content blocking.
- if (aData) {
- let data = JSON.parse(aData);
-
- if (data.bypassCache) {
- flags |= Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE |
- Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY;
- }
-
- if (data.contentType === "tracking") {
- // Convert document URI into the format used by
- // nsChannelClassifier::ShouldEnableTrackingProtection
- // (any scheme turned into https is correct)
- let normalizedUrl = Services.io.newURI("https://" + browser.currentURI.hostPort, null, null);
- if (data.allowContent) {
- // Add the current host in the 'trackingprotection' consumer of
- // the permission manager using a normalized URI. This effectively
- // places this host on the tracking protection white list.
- if (PrivateBrowsingUtils.isBrowserPrivate(browser)) {
- PrivateBrowsingUtils.addToTrackingAllowlist(normalizedUrl);
- } else {
- Services.perms.add(normalizedUrl, "trackingprotection", Services.perms.ALLOW_ACTION);
- Telemetry.addData("TRACKING_PROTECTION_EVENTS", 1);
- }
- } else {
- // Remove the current host from the 'trackingprotection' consumer
- // of the permission manager. This effectively removes this host
- // from the tracking protection white list (any list actually).
- if (PrivateBrowsingUtils.isBrowserPrivate(browser)) {
- PrivateBrowsingUtils.removeFromTrackingAllowlist(normalizedUrl);
- } else {
- Services.perms.remove(normalizedUrl, "trackingprotection");
- Telemetry.addData("TRACKING_PROTECTION_EVENTS", 2);
- }
- }
- }
- }
-
- // Try to use the session history to reload so that framesets are
- // handled properly. If the window has no session history, fall back
- // to using the web navigation's reload method.
- let webNav = browser.webNavigation;
- try {
- let sh = webNav.sessionHistory;
- if (sh)
- webNav = sh.QueryInterface(Ci.nsIWebNavigation);
- } catch (e) {}
- webNav.reload(flags);
- break;
- }
-
- case "Session:Stop":
- browser.stop();
- break;
-
- case "Tab:Load": {
- let data = JSON.parse(aData);
- let url = data.url;
- let flags = Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP
- | Ci.nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
-
- // Pass LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL to prevent any loads from
- // inheriting the currently loaded document's principal.
- if (data.userEntered) {
- flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL;
- }
-
- let delayLoad = ("delayLoad" in data) ? data.delayLoad : false;
- let params = {
- selected: ("selected" in data) ? data.selected : !delayLoad,
- parentId: ("parentId" in data) ? data.parentId : -1,
- flags: flags,
- tabID: data.tabID,
- isPrivate: (data.isPrivate === true),
- pinned: (data.pinned === true),
- delayLoad: (delayLoad === true),
- desktopMode: (data.desktopMode === true)
- };
-
- params.userRequested = url;
-
- if (data.engine) {
- let engine = Services.search.getEngineByName(data.engine);
- if (engine) {
- let submission = engine.getSubmission(url);
- url = submission.uri.spec;
- params.postData = submission.postData;
- params.isSearch = true;
- }
- }
-
- if (data.newTab) {
- this.addTab(url, params);
- } else {
- if (data.tabId) {
- // Use a specific browser instead of the selected browser, if it exists
- let specificBrowser = this.getTabForId(data.tabId).browser;
- if (specificBrowser)
- browser = specificBrowser;
- }
- this.loadURI(url, browser, params);
- }
- break;
- }
-
- case "Tab:Selected":
- this._handleTabSelected(this.getTabForId(parseInt(aData)));
- break;
-
- case "Tab:Closed": {
- let data = JSON.parse(aData);
- this._handleTabClosed(this.getTabForId(data.tabId), data.showUndoToast);
- break;
- }
-
- case "keyword-search":
- // This event refers to a search via the URL bar, not a bookmarks
- // keyword search. Note that this code assumes that the user can only
- // perform a keyword search on the selected tab.
- this.selectedTab.isSearch = true;
-
- // Don't store queries in private browsing mode.
- let isPrivate = PrivateBrowsingUtils.isBrowserPrivate(this.selectedTab.browser);
- let query = isPrivate ? "" : aData;
-
- let engine = aSubject.QueryInterface(Ci.nsISearchEngine);
- Messaging.sendRequest({
- type: "Search:Keyword",
- identifier: engine.identifier,
- name: engine.name,
- query: query
- });
- break;
-
- case "Browser:Quit":
- // Add-ons like QuitNow and CleanQuit provide aData as an empty-string ("").
- // Pass undefined to invoke the methods default parms.
- this.quit(aData ? JSON.parse(aData) : undefined);
- break;
-
- case "SaveAs:PDF":
- this.saveAsPDF(browser);
- break;
-
- case "ScrollTo:FocusedInput":
- // these messages come from a change in the viewable area and not user interaction
- // we allow scrolling to the selected input, but not zooming the page
- this.scrollToFocusedInput(browser, false);
- break;
-
- case "Sanitize:ClearData":
- this.sanitize(JSON.parse(aData));
- break;
-
- case "FullScreen:Exit":
- browser.contentDocument.exitFullscreen();
- break;
-
- case "Passwords:Init": {
- let storage = Cc["@mozilla.org/login-manager/storage/mozStorage;1"].
- getService(Ci.nsILoginManagerStorage);
- storage.initialize();
- Services.obs.removeObserver(this, "Passwords:Init");
- break;
- }
-
- case "FormHistory:Init": {
- // Force creation/upgrade of formhistory.sqlite
- FormHistory.count({});
- Services.obs.removeObserver(this, "FormHistory:Init");
- break;
- }
-
- case "android-get-pref": {
- // These pref names are not "real" pref names. They are used in the
- // setting menu, and these are passed when initializing the setting
- // menu. aSubject is a nsIWritableVariant to hold the pref value.
- aSubject.QueryInterface(Ci.nsIWritableVariant);
-
- switch (aData) {
- // The plugin pref is actually two separate prefs, so
- // we need to handle it differently
- case "plugin.enable":
- aSubject.setAsAString(PluginHelper.getPluginPreference());
- break;
-
- // Handle master password
- case "privacy.masterpassword.enabled":
- aSubject.setAsBool(MasterPassword.enabled);
- break;
-
- case "privacy.trackingprotection.state": {
- aSubject.setAsAString(this.getTrackingProtectionState());
- break;
- }
-
- // Crash reporter submit pref must be fetched from nsICrashReporter
- // service.
- case "datareporting.crashreporter.submitEnabled":
- let crashReporterBuilt = "nsICrashReporter" in Ci &&
- Services.appinfo instanceof Ci.nsICrashReporter;
- if (crashReporterBuilt) {
- aSubject.setAsBool(Services.appinfo.submitReports);
- }
- break;
- }
- break;
- }
-
- case "android-set-pref": {
- // Pseudo-prefs. aSubject is an nsIWritableVariant that holds the pref
- // value. Set to empty to signal the pref was handled.
- aSubject.QueryInterface(Ci.nsIWritableVariant);
- let value = aSubject.QueryInterface(Ci.nsIVariant);
-
- switch (aData) {
- // The plugin pref is actually two separate prefs, so we need to
- // handle it differently.
- case "plugin.enable":
- PluginHelper.setPluginPreference(value);
- aSubject.setAsEmpty();
- break;
-
- // MasterPassword pref is not real, we just need take action and leave
- case "privacy.masterpassword.enabled":
- if (MasterPassword.enabled) {
- MasterPassword.removePassword(value);
- } else {
- MasterPassword.setPassword(value);
- }
- aSubject.setAsEmpty();
- break;
-
- // "privacy.trackingprotection.state" is not a "real" pref name, but
- // it's used in the setting menu. By default
- // "privacy.trackingprotection.pbmode.enabled" is true, and
- // "privacy.trackingprotection.enabled" is false.
- case "privacy.trackingprotection.state": {
- switch (value) {
- // Tracking protection disabled.
- case this.PREF_TRACKING_PROTECTION_DISABLED:
- Services.prefs.setBoolPref("privacy.trackingprotection.pbmode.enabled", false);
- Services.prefs.setBoolPref("privacy.trackingprotection.enabled", false);
- break;
- // Tracking protection only in private browsing,
- case this.PREF_TRACKING_PROTECTION_ENABLED_PB:
- Services.prefs.setBoolPref("privacy.trackingprotection.pbmode.enabled", true);
- Services.prefs.setBoolPref("privacy.trackingprotection.enabled", false);
- break;
- // Tracking protection everywhere.
- case this.PREF_TRACKING_PROTECTION_ENABLED:
- Services.prefs.setBoolPref("privacy.trackingprotection.pbmode.enabled", true);
- Services.prefs.setBoolPref("privacy.trackingprotection.enabled", true);
- break;
- }
- aSubject.setAsEmpty();
- break;
- }
-
- // Crash reporter preference is in a service; set and return.
- case "datareporting.crashreporter.submitEnabled":
- let crashReporterBuilt = "nsICrashReporter" in Ci &&
- Services.appinfo instanceof Ci.nsICrashReporter;
- if (crashReporterBuilt) {
- Services.appinfo.submitReports = value;
- aSubject.setAsEmpty();
- }
- break;
- }
- break;
- }
-
- case "sessionstore-state-purge-complete":
- Messaging.sendRequest({ type: "Session:StatePurged" });
- break;
-
- case "gather-telemetry":
- Messaging.sendRequest({ type: "Telemetry:Gather" });
- break;
-
- case "Locale:OS":
- // We know the system locale. We use this for generating Accept-Language headers.
- console.log("Locale:OS: " + aData);
- let currentOSLocale = this.getOSLocalePref();
- if (currentOSLocale == aData) {
- break;
- }
-
- console.log("New OS locale.");
-
- // Ensure that this choice is immediately persisted, because
- // Gecko won't be told again if it forgets.
- Services.prefs.setCharPref("intl.locale.os", aData);
- Services.prefs.savePrefFile(null);
-
- let appLocale = this.getUALocalePref();
-
- this.computeAcceptLanguages(aData, appLocale);
- break;
-
- case "Locale:Changed":
- if (aData) {
- // The value provided to Locale:Changed should be a BCP47 language tag
- // understood by Gecko -- for example, "es-ES" or "de".
- console.log("Locale:Changed: " + aData);
-
- // We always write a localized pref, even though sometimes the value is a char pref.
- // (E.g., on desktop single-locale builds.)
- this.setLocalizedPref("general.useragent.locale", aData);
- } else {
- // Resetting.
- console.log("Switching to system locale.");
- Services.prefs.clearUserPref("general.useragent.locale");
- }
-
- Services.prefs.setBoolPref("intl.locale.matchOS", !aData);
-
- // Ensure that this choice is immediately persisted, because
- // Gecko won't be told again if it forgets.
- Services.prefs.savePrefFile(null);
-
- // Blow away the string cache so that future lookups get the
- // correct locale.
- Strings.flush();
-
- // Make sure we use the right Accept-Language header.
- let osLocale;
- try {
- // This should never not be set at this point, but better safe than sorry.
- osLocale = Services.prefs.getCharPref("intl.locale.os");
- } catch (e) {
- }
-
- this.computeAcceptLanguages(osLocale, aData);
- break;
-
- case "Fonts:Reload":
- FontEnumerator.updateFontList();
- break;
-
- case "Vibration:Request":
- if (aSubject instanceof Navigator) {
- let navigator = aSubject;
- let buttons = [
- {
- label: Strings.browser.GetStringFromName("vibrationRequest.denyButton"),
- callback: function() {
- navigator.setVibrationPermission(false);
- }
- },
- {
- label: Strings.browser.GetStringFromName("vibrationRequest.allowButton"),
- callback: function() {
- navigator.setVibrationPermission(true);
- },
- positive: true
- }
- ];
- let message = Strings.browser.GetStringFromName("vibrationRequest.message");
- let options = {};
- NativeWindow.doorhanger.show(message, "vibration-request", buttons,
- BrowserApp.selectedTab.id, options, "VIBRATION");
- }
- break;
-
- default:
- dump('BrowserApp.observe: unexpected topic "' + aTopic + '"\n');
- break;
-
- }
- },
-
- /**
- * Set intl.accept_languages accordingly.
- *
- * After Bug 881510 this will also accept a real Accept-Language choice as
- * input; all Accept-Language logic lives here.
- *
- * osLocale should never be null, but this method is safe regardless.
- * appLocale may explicitly be null.
- */
- computeAcceptLanguages(osLocale, appLocale) {
- let defaultBranch = Services.prefs.getDefaultBranch(null);
- let defaultAccept = defaultBranch.getComplexValue("intl.accept_languages", Ci.nsIPrefLocalizedString).data;
- console.log("Default intl.accept_languages = " + defaultAccept);
-
- // A guard for potential breakage. Bug 438031.
- // This should not be necessary, because we're reading from the default branch,
- // but better safe than sorry.
- if (defaultAccept && defaultAccept.startsWith("chrome://")) {
- defaultAccept = null;
- } else {
- // Ensure lowercase everywhere so we can compare.
- defaultAccept = defaultAccept.toLowerCase();
- }
-
- if (appLocale) {
- appLocale = appLocale.toLowerCase();
- }
-
- if (osLocale) {
- osLocale = osLocale.toLowerCase();
- }
-
- // Eliminate values if they're present in the default.
- let chosen;
- if (defaultAccept) {
- // intl.accept_languages is a comma-separated list, with no q-value params. Those
- // are added when the header is generated.
- chosen = defaultAccept.split(",")
- .map(String.trim)
- .filter((x) => (x != appLocale && x != osLocale));
- } else {
- chosen = [];
- }
-
- if (osLocale) {
- chosen.unshift(osLocale);
- }
-
- if (appLocale && appLocale != osLocale) {
- chosen.unshift(appLocale);
- }
-
- let result = chosen.join(",");
- console.log("Setting intl.accept_languages to " + result);
- this.setLocalizedPref("intl.accept_languages", result);
- },
-
- // nsIAndroidBrowserApp
- get selectedTab() {
- return this._selectedTab;
- },
-
- // nsIAndroidBrowserApp
- getBrowserTab: function(tabId) {
- return this.getTabForId(tabId);
- },
-
- getUITelemetryObserver: function() {
- return UITelemetry;
- },
-
- // This method will return a list of history items and toIndex based on the action provided from the fromIndex to toIndex,
- // optionally selecting selIndex (if fromIndex <= selIndex <= toIndex)
- getHistory: function(data) {
- let action = data.action;
- let webNav = BrowserApp.getTabForId(data.tabId).window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation);
- let historyIndex = webNav.sessionHistory.index;
- let historySize = webNav.sessionHistory.count;
- let canGoBack = webNav.canGoBack;
- let canGoForward = webNav.canGoForward;
- let listitems = [];
- let fromIndex = 0;
- let toIndex = historySize - 1;
- let selIndex = historyIndex;
-
- if (action == "BACK" && canGoBack) {
- fromIndex = Math.max(historyIndex - kMaxHistoryListSize, 0);
- toIndex = historyIndex;
- selIndex = historyIndex;
- } else if (action == "FORWARD" && canGoForward) {
- fromIndex = historyIndex;
- toIndex = Math.min(historySize - 1, historyIndex + kMaxHistoryListSize);
- selIndex = historyIndex;
- } else if (action == "ALL" && (canGoBack || canGoForward)){
- fromIndex = historyIndex - kMaxHistoryListSize / 2;
- toIndex = historyIndex + kMaxHistoryListSize / 2;
- if (fromIndex < 0) {
- toIndex -= fromIndex;
- }
-
- if (toIndex > historySize - 1) {
- fromIndex -= toIndex - (historySize - 1);
- toIndex = historySize - 1;
- }
-
- fromIndex = Math.max(fromIndex, 0);
- selIndex = historyIndex;
- } else {
- // return empty list immediately.
- return {
- "historyItems": listitems,
- "toIndex": toIndex
- };
- }
-
- let browser = this.selectedBrowser;
- let hist = browser.sessionHistory;
- for (let i = toIndex; i >= fromIndex; i--) {
- let entry = hist.getEntryAtIndex(i, false);
- let item = {
- title: entry.title || entry.URI.spec,
- url: entry.URI.spec,
- selected: (i == selIndex)
- };
- listitems.push(item);
- }
-
- return {
- "historyItems": listitems,
- "toIndex": toIndex
- };
- },
-};
-
-var NativeWindow = {
- init: function() {
- Services.obs.addObserver(this, "Menu:Clicked", false);
- Services.obs.addObserver(this, "Doorhanger:Reply", false);
- this.contextmenus.init();
- },
-
- loadDex: function(zipFile, implClass) {
- Messaging.sendRequest({
- type: "Dex:Load",
- zipfile: zipFile,
- impl: implClass || "Main"
- });
- },
-
- unloadDex: function(zipFile) {
- Messaging.sendRequest({
- type: "Dex:Unload",
- zipfile: zipFile
- });
- },
-
- menu: {
- _callbacks: [],
- _menuId: 1,
- toolsMenuID: -1,
- add: function() {
- let options;
- if (arguments.length == 1) {
- options = arguments[0];
- } else if (arguments.length == 3) {
- Log.w("Browser", "This menu addon API has been deprecated. Instead, use the options object API.");
- options = {
- name: arguments[0],
- callback: arguments[2]
- };
- } else {
- throw "Incorrect number of parameters";
- }
-
- options.type = "Menu:Add";
- options.id = this._menuId;
-
- Messaging.sendRequest(options);
- this._callbacks[this._menuId] = options.callback;
- this._menuId++;
- return this._menuId - 1;
- },
-
- remove: function(aId) {
- Messaging.sendRequest({ type: "Menu:Remove", id: aId });
- },
-
- update: function(aId, aOptions) {
- if (!aOptions)
- return;
-
- Messaging.sendRequest({
- type: "Menu:Update",
- id: aId,
- options: aOptions
- });
- }
- },
-
- doorhanger: {
- _callbacks: {},
- _callbacksId: 0,
- _promptId: 0,
-
- /**
- * @param aOptions
- * An options JavaScript object holding additional properties for the
- * notification. The following properties are currently supported:
- * persistence: An integer. The notification will not automatically
- * dismiss for this many page loads. If persistence is set
- * to -1, the doorhanger will never automatically dismiss.
- * persistWhileVisible:
- * A boolean. If true, a visible notification will always
- * persist across location changes.
- * timeout: A time in milliseconds. The notification will not
- * automatically dismiss before this time.
- *
- * checkbox: A string to appear next to a checkbox under the notification
- * message. The button callback functions will be called with
- * the checked state as an argument.
- *
- * actionText: An object that specifies a clickable string, a type of action,
- * and a bundle blob for the consumer to create a click action.
- * { text: <text>,
- * type: <type>,
- * bundle: <blob-object> }
- *
- * @param aCategory
- * Doorhanger type to display (e.g., LOGIN)
- */
- show: function(aMessage, aValue, aButtons, aTabID, aOptions, aCategory) {
- if (aButtons == null) {
- aButtons = [];
- }
-
- if (aButtons.length > 2) {
- console.log("Doorhanger can have a maximum of two buttons!");
- aButtons.length = 2;
- }
-
- aButtons.forEach((function(aButton) {
- this._callbacks[this._callbacksId] = { cb: aButton.callback, prompt: this._promptId };
- aButton.callback = this._callbacksId;
- this._callbacksId++;
- }).bind(this));
-
- this._promptId++;
- let json = {
- type: "Doorhanger:Add",
- message: aMessage,
- value: aValue,
- buttons: aButtons,
- // use the current tab if none is provided
- tabID: aTabID || BrowserApp.selectedTab.id,
- options: aOptions || {},
- category: aCategory
- };
- Messaging.sendRequest(json);
- },
-
- hide: function(aValue, aTabID) {
- Messaging.sendRequest({
- type: "Doorhanger:Remove",
- value: aValue,
- tabID: aTabID
- });
- }
- },
-
- observe: function(aSubject, aTopic, aData) {
- if (aTopic == "Menu:Clicked") {
- if (this.menu._callbacks[aData])
- this.menu._callbacks[aData]();
- } else if (aTopic == "Doorhanger:Reply") {
- let data = JSON.parse(aData);
- let reply_id = data["callback"];
-
- if (this.doorhanger._callbacks[reply_id]) {
- // Pass the value of the optional checkbox to the callback
- let checked = data["checked"];
- this.doorhanger._callbacks[reply_id].cb(checked, data.inputs);
-
- let prompt = this.doorhanger._callbacks[reply_id].prompt;
- for (let id in this.doorhanger._callbacks) {
- if (this.doorhanger._callbacks[id].prompt == prompt) {
- delete this.doorhanger._callbacks[id];
- }
- }
- }
- }
- },
-
- contextmenus: {
- items: {}, // a list of context menu items that we may show
- DEFAULT_HTML5_ORDER: -1, // Sort order for HTML5 context menu items
-
- init: function() {
- // Accessing "NativeWindow.contextmenus" initializes context menus if needed.
- BrowserApp.deck.addEventListener(
- "contextmenu", (e) => NativeWindow.contextmenus.show(e), false);
- },
-
- add: function() {
- let args;
- if (arguments.length == 1) {
- args = arguments[0];
- } else if (arguments.length == 3) {
- args = {
- label : arguments[0],
- selector: arguments[1],
- callback: arguments[2]
- };
- } else {
- throw "Incorrect number of parameters";
- }
-
- if (!args.label)
- throw "Menu items must have a name";
-
- let cmItem = new ContextMenuItem(args);
- this.items[cmItem.id] = cmItem;
- return cmItem.id;
- },
-
- remove: function(aId) {
- delete this.items[aId];
- },
-
- // Although we do not use this ourselves anymore, add-ons may still
- // need it as it has been documented, so we shouldn't remove it.
- SelectorContext: function(aSelector) {
- return {
- matches: function(aElt) {
- if (aElt.matches)
- return aElt.matches(aSelector);
- return false;
- }
- };
- },
-
- linkOpenableNonPrivateContext: {
- matches: function linkOpenableNonPrivateContextMatches(aElement) {
- let doc = aElement.ownerDocument;
- if (!doc || PrivateBrowsingUtils.isContentWindowPrivate(doc.defaultView)) {
- return false;
- }
-
- return NativeWindow.contextmenus.linkOpenableContext.matches(aElement);
- }
- },
-
- linkOpenableContext: {
- matches: function linkOpenableContextMatches(aElement) {
- let uri = NativeWindow.contextmenus._getLink(aElement);
- if (uri) {
- let scheme = uri.scheme;
- let dontOpen = /^(javascript|mailto|news|snews|tel)$/;
- return (scheme && !dontOpen.test(scheme));
- }
- return false;
- }
- },
-
- linkCopyableContext: {
- matches: function linkCopyableContextMatches(aElement) {
- let uri = NativeWindow.contextmenus._getLink(aElement);
- if (uri) {
- let scheme = uri.scheme;
- let dontCopy = /^(mailto|tel)$/;
- return (scheme && !dontCopy.test(scheme));
- }
- return false;
- }
- },
-
- linkShareableContext: {
- matches: function linkShareableContextMatches(aElement) {
- let uri = NativeWindow.contextmenus._getLink(aElement);
- if (uri) {
- let scheme = uri.scheme;
- let dontShare = /^(about|chrome|file|javascript|mailto|resource|tel)$/;
- return (scheme && !dontShare.test(scheme));
- }
- return false;
- }
- },
-
- linkBookmarkableContext: {
- matches: function linkBookmarkableContextMatches(aElement) {
- let uri = NativeWindow.contextmenus._getLink(aElement);
- if (uri) {
- let scheme = uri.scheme;
- let dontBookmark = /^(mailto|tel)$/;
- return (scheme && !dontBookmark.test(scheme));
- }
- return false;
- }
- },
-
- emailLinkContext: {
- matches: function emailLinkContextMatches(aElement) {
- let uri = NativeWindow.contextmenus._getLink(aElement);
- if (uri)
- return uri.schemeIs("mailto");
- return false;
- }
- },
-
- phoneNumberLinkContext: {
- matches: function phoneNumberLinkContextMatches(aElement) {
- let uri = NativeWindow.contextmenus._getLink(aElement);
- if (uri)
- return uri.schemeIs("tel");
- return false;
- }
- },
-
- imageLocationCopyableContext: {
- matches: function imageLinkCopyableContextMatches(aElement) {
- if (aElement instanceof Ci.nsIDOMHTMLImageElement) {
- // The image is blocked by Tap-to-load Images
- if (aElement.hasAttribute("data-ctv-src") && !aElement.hasAttribute("data-ctv-show")) {
- return false;
- }
- }
- return (aElement instanceof Ci.nsIImageLoadingContent && aElement.currentURI);
- }
- },
-
- imageSaveableContext: {
- matches: function imageSaveableContextMatches(aElement) {
- if (aElement instanceof Ci.nsIDOMHTMLImageElement) {
- // The image is blocked by Tap-to-load Images
- if (aElement.hasAttribute("data-ctv-src") && !aElement.hasAttribute("data-ctv-show")) {
- return false;
- }
- }
- if (aElement instanceof Ci.nsIImageLoadingContent && aElement.currentURI) {
- // The image must be loaded to allow saving
- let request = aElement.getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST);
- return (request && (request.imageStatus & request.STATUS_SIZE_AVAILABLE));
- }
- return false;
- }
- },
-
- imageShareableContext: {
- matches: function imageShareableContextMatches(aElement) {
- let imgSrc = '';
- if (aElement instanceof Ci.nsIDOMHTMLImageElement) {
- imgSrc = aElement.src;
- } else if (aElement instanceof Ci.nsIImageLoadingContent &&
- aElement.currentURI &&
- aElement.currentURI.spec) {
- imgSrc = aElement.currentURI.spec;
- }
-
- // In order to share an image, we need to pass the image src over IPC via an Intent (in
- // `ApplicationPackageManager.queryIntentActivities`). However, the transaction has a 1MB limit
- // (shared by all transactions in progress) - otherwise we crash! (bug 1243305)
- // https://developer.android.com/reference/android/os/TransactionTooLargeException.html
- //
- // The transaction limit is 1MB and we arbitrarily choose to cap this transaction at 1/4 of that = 250,000 bytes.
- // In Java, a UTF-8 character is 1-4 bytes so, 250,000 bytes / 4 bytes/char = 62,500 char
- let MAX_IMG_SRC_LEN = 62500;
- let isTooLong = imgSrc.length >= MAX_IMG_SRC_LEN;
- return !isTooLong && this.NativeWindow.contextmenus.imageSaveableContext.matches(aElement);
- }.bind(this)
- },
-
- mediaSaveableContext: {
- matches: function mediaSaveableContextMatches(aElement) {
- return (aElement instanceof HTMLVideoElement ||
- aElement instanceof HTMLAudioElement);
- }
- },
-
- imageBlockingPolicyContext: {
- matches: function imageBlockingPolicyContextMatches(aElement) {
- if (aElement instanceof Ci.nsIDOMHTMLImageElement && aElement.getAttribute("data-ctv-src")) {
- // Only show the menuitem if we are blocking the image
- if (aElement.getAttribute("data-ctv-show") == "true") {
- return false;
- }
- return true;
- }
- return false;
- }
- },
-
- mediaContext: function(aMode) {
- return {
- matches: function(aElt) {
- if (aElt instanceof Ci.nsIDOMHTMLMediaElement) {
- let hasError = aElt.error != null || aElt.networkState == aElt.NETWORK_NO_SOURCE;
- if (hasError)
- return false;
-
- let paused = aElt.paused || aElt.ended;
- if (paused && aMode == "media-paused")
- return true;
- if (!paused && aMode == "media-playing")
- return true;
- let controls = aElt.controls;
- if (!controls && aMode == "media-hidingcontrols")
- return true;
-
- let muted = aElt.muted;
- if (muted && aMode == "media-muted")
- return true;
- else if (!muted && aMode == "media-unmuted")
- return true;
- }
- return false;
- }
- };
- },
-
- videoContext: function(aMode) {
- return {
- matches: function(aElt) {
- if (aElt instanceof HTMLVideoElement) {
- if (!aMode) {
- return true;
- }
- var isFullscreen = aElt.ownerDocument.fullscreenElement == aElt;
- if (aMode == "not-fullscreen") {
- return !isFullscreen;
- }
- if (aMode == "fullscreen") {
- return isFullscreen;
- }
- }
- return false;
- }
- };
- },
-
- /* Holds a WeakRef to the original target element this context menu was shown for.
- * Most API's will have to walk up the tree from this node to find the correct element
- * to act on
- */
- get _target() {
- if (this._targetRef)
- return this._targetRef.get();
- return null;
- },
-
- set _target(aTarget) {
- if (aTarget)
- this._targetRef = Cu.getWeakReference(aTarget);
- else this._targetRef = null;
- },
-
- get defaultContext() {
- delete this.defaultContext;
- return this.defaultContext = Strings.browser.GetStringFromName("browser.menu.context.default");
- },
-
- /* Gets menuitems for an arbitrary node
- * Parameters:
- * element - The element to look at. If this element has a contextmenu attribute, the
- * corresponding contextmenu will be used.
- */
- _getHTMLContextMenuItemsForElement: function(element) {
- let htmlMenu = element.contextMenu;
- if (!htmlMenu) {
- return [];
- }
-
- htmlMenu.QueryInterface(Components.interfaces.nsIHTMLMenu);
- htmlMenu.sendShowEvent();
-
- return this._getHTMLContextMenuItemsForMenu(htmlMenu, element);
- },
-
- /* Add a menuitem for an HTML <menu> node
- * Parameters:
- * menu - The <menu> element to iterate through for menuitems
- * target - The target element these context menu items are attached to
- */
- _getHTMLContextMenuItemsForMenu: function(menu, target) {
- let items = [];
- for (let i = 0; i < menu.childNodes.length; i++) {
- let elt = menu.childNodes[i];
- if (!elt.label)
- continue;
-
- items.push(new HTMLContextMenuItem(elt, target));
- }
-
- return items;
- },
-
- // Searches the current list of menuitems to show for any that match this id
- _findMenuItem: function(aId) {
- if (!this.menus) {
- return null;
- }
-
- for (let context in this.menus) {
- let menu = this.menus[context];
- for (let i = 0; i < menu.length; i++) {
- if (menu[i].id === aId) {
- return menu[i];
- }
- }
- }
- return null;
- },
-
- // Returns true if there are any context menu items to show
- _shouldShow: function() {
- for (let context in this.menus) {
- let menu = this.menus[context];
- if (menu.length > 0) {
- return true;
- }
- }
- return false;
- },
-
- /* Returns a label to be shown in a tabbed ui if there are multiple "contexts". For instance, if this
- * is an image inside an <a> tag, we may have a "link" context and an "image" one.
- */
- _getContextType: function(element) {
- // For anchor nodes, we try to use the scheme to pick a string
- if (element instanceof Ci.nsIDOMHTMLAnchorElement) {
- let uri = this.makeURI(this._getLinkURL(element));
- try {
- return Strings.browser.GetStringFromName("browser.menu.context." + uri.scheme);
- } catch(ex) { }
- }
-
- // Otherwise we try the nodeName
- try {
- return Strings.browser.GetStringFromName("browser.menu.context." + element.nodeName.toLowerCase());
- } catch(ex) { }
-
- // Fallback to the default
- return this.defaultContext;
- },
-
- // Adds context menu items added through the add-on api
- _getNativeContextMenuItems: function(element, x, y) {
- let res = [];
- for (let itemId of Object.keys(this.items)) {
- let item = this.items[itemId];
-
- if (!this._findMenuItem(item.id) && item.matches(element, x, y)) {
- res.push(item);
- }
- }
-
- return res;
- },
-
- /* Checks if there are context menu items to show, and if it finds them
- * sends a contextmenu event to content. We also send showing events to
- * any html5 context menus we are about to show, and fire some local notifications
- * for chrome consumers to do lazy menuitem construction
- */
- show: function(event) {
- // Android Long-press / contextmenu event provides clientX/Y data. This is not provided
- // by mochitest: test_browserElement_inproc_ContextmenuEvents.html.
- if (!event.clientX || !event.clientY) {
- return;
- }
-
- // If the event was already defaultPrevented by somebody (web content, or
- // some other part of gecko), then don't do anything with it.
- if (event.defaultPrevented) {
- return;
- }
-
- // Use the highlighted element for the context menu target. When accessibility is
- // enabled, elements may not be highlighted so use the event target instead.
- this._target = BrowserEventHandler._highlightElement || event.target;
- if (!this._target) {
- return;
- }
-
- // Try to build a list of contextmenu items. If successful, actually show the
- // native context menu by passing the list to Java.
- this._buildMenu(event.clientX, event.clientY);
- if (this._shouldShow()) {
- BrowserEventHandler._cancelTapHighlight();
-
- // Consume / preventDefault the event, and show the contextmenu.
- event.preventDefault();
- this._innerShow(this._target, event.clientX, event.clientY);
- this._target = null;
-
- return;
- }
-
- // If no context-menu for long-press event, it may be meant to trigger text-selection.
- this.menus = null;
- Services.obs.notifyObservers(
- {target: this._target, x: event.clientX, y: event.clientY}, "context-menu-not-shown", "");
- },
-
- // Returns a title for a context menu. If no title attribute exists, will fall back to looking for a url
- _getTitle: function(node) {
- if (node.hasAttribute && node.hasAttribute("title")) {
- return node.getAttribute("title");
- }
- return this._getUrl(node);
- },
-
- // Returns a url associated with a node
- _getUrl: function(node) {
- if ((node instanceof Ci.nsIDOMHTMLAnchorElement && node.href) ||
- (node instanceof Ci.nsIDOMHTMLAreaElement && node.href)) {
- return this._getLinkURL(node);
- } else if (node instanceof Ci.nsIImageLoadingContent && node.currentURI) {
- // The image is blocked by Tap-to-load Images
- let originalURL = node.getAttribute("data-ctv-src");
- if (originalURL) {
- return originalURL;
- }
- return node.currentURI.spec;
- } else if (node instanceof Ci.nsIDOMHTMLMediaElement) {
- let srcUrl = node.currentSrc || node.src;
- // If URL prepended with blob or mediasource, we'll remove it.
- return srcUrl.replace(/^(?:blob|mediasource):/, '');
- }
-
- return "";
- },
-
- // Adds an array of menuitems to the current list of items to show, in the correct context
- _addMenuItems: function(items, context) {
- if (!this.menus[context]) {
- this.menus[context] = [];
- }
- this.menus[context] = this.menus[context].concat(items);
- },
-
- /* Does the basic work of building a context menu to show. Will combine HTML and Native
- * context menus items, as well as sorting menuitems into different menus based on context.
- */
- _buildMenu: function(x, y) {
- // now walk up the tree and for each node look for any context menu items that apply
- let element = this._target;
-
- // this.menus holds a hashmap of "contexts" to menuitems associated with that context
- // For instance, if the user taps an image inside a link, we'll have something like:
- // {
- // link: [ ContextMenuItem, ContextMenuItem ]
- // image: [ ContextMenuItem, ContextMenuItem ]
- // }
- this.menus = {};
-
- while (element) {
- let context = this._getContextType(element);
-
- // First check for any html5 context menus that might exist...
- var items = this._getHTMLContextMenuItemsForElement(element);
- if (items.length > 0) {
- this._addMenuItems(items, context);
- }
-
- // then check for any context menu items registered in the ui.
- items = this._getNativeContextMenuItems(element, x, y);
- if (items.length > 0) {
- this._addMenuItems(items, context);
- }
-
- // walk up the tree and find more items to show
- element = element.parentNode;
- }
- },
-
- // Walks the DOM tree to find a title from a node
- _findTitle: function(node) {
- let title = "";
- while(node && !title) {
- title = this._getTitle(node);
- node = node.parentNode;
- }
- return title;
- },
-
- /* Reformats the list of menus to show into an object that can be sent to Prompt.jsm
- * If there is one menu, will return a flat array of menuitems. If there are multiple
- * menus, will return an array with appropriate tabs/items inside it. i.e. :
- * [
- * { label: "link", items: [...] },
- * { label: "image", items: [...] }
- * ]
- */
- _reformatList: function(target) {
- let contexts = Object.keys(this.menus);
-
- if (contexts.length === 1) {
- // If there's only one context, we'll only show a single flat single select list
- return this._reformatMenuItems(target, this.menus[contexts[0]]);
- }
-
- // If there are multiple contexts, we'll only show a tabbed ui with multiple lists
- return this._reformatListAsTabs(target, this.menus);
- },
-
- /* Reformats the list of menus to show into an object that can be sent to Prompt.jsm's
- * addTabs method. i.e. :
- * { link: [...], image: [...] } becomes
- * [ { label: "link", items: [...] } ]
- *
- * Also reformats items and resolves any parmaeters that aren't known until display time
- * (for instance Helper app menu items adjust their title to reflect what Helper App can be used for this link).
- */
- _reformatListAsTabs: function(target, menus) {
- let itemArray = [];
-
- // Sort the keys so that "link" is always first
- let contexts = Object.keys(this.menus);
- contexts.sort((context1, context2) => {
- if (context1 === this.defaultContext) {
- return -1;
- } else if (context2 === this.defaultContext) {
- return 1;
- }
- return 0;
- });
-
- contexts.forEach(context => {
- itemArray.push({
- label: context,
- items: this._reformatMenuItems(target, menus[context])
- });
- });
-
- return itemArray;
- },
-
- /* Reformats an array of ContextMenuItems into an array that can be handled by Prompt.jsm. Also reformats items
- * and resolves any parmaeters that aren't known until display time
- * (for instance Helper app menu items adjust their title to reflect what Helper App can be used for this link).
- */
- _reformatMenuItems: function(target, menuitems) {
- let itemArray = [];
-
- for (let i = 0; i < menuitems.length; i++) {
- let t = target;
- while(t) {
- if (menuitems[i].matches(t)) {
- let val = menuitems[i].getValue(t);
-
- // hidden menu items will return null from getValue
- if (val) {
- itemArray.push(val);
- break;
- }
- }
-
- t = t.parentNode;
- }
- }
-
- return itemArray;
- },
-
- // Called where we're finally ready to actually show the contextmenu. Sorts the items and shows a prompt.
- _innerShow: function(target, x, y) {
- Haptic.performSimpleAction(Haptic.LongPress);
-
- // spin through the tree looking for a title for this context menu
- let title = this._findTitle(target);
-
- for (let context in this.menus) {
- let menu = this.menus[context];
- menu.sort((a,b) => {
- if (a.order === b.order) {
- return 0;
- }
- return (a.order > b.order) ? 1 : -1;
- });
- }
-
- let useTabs = Object.keys(this.menus).length > 1;
- let prompt = new Prompt({
- window: target.ownerDocument.defaultView,
- title: useTabs ? undefined : title
- });
-
- let items = this._reformatList(target);
- if (useTabs) {
- prompt.addTabs({
- id: "tabs",
- items: items
- });
- } else {
- prompt.setSingleChoiceItems(items);
- }
-
- prompt.show(this._promptDone.bind(this, target, x, y, items));
- },
-
- // Called when the contextmenu prompt is closed
- _promptDone: function(target, x, y, items, data) {
- if (data.button == -1) {
- // Prompt was cancelled, or an ActionView was used.
- return;
- }
-
- let selectedItemId;
- if (data.tabs) {
- let menu = items[data.tabs.tab];
- selectedItemId = menu.items[data.tabs.item].id;
- } else {
- selectedItemId = items[data.list[0]].id
- }
-
- let selectedItem = this._findMenuItem(selectedItemId);
- this.menus = null;
-
- if (!selectedItem || !selectedItem.matches || !selectedItem.callback) {
- return;
- }
-
- // for menuitems added using the native UI, pass the dom element that matched that item to the callback
- while (target) {
- if (selectedItem.matches(target, x, y)) {
- selectedItem.callback(target, x, y);
- break;
- }
- target = target.parentNode;
- }
- },
-
- // XXX - These are stolen from Util.js, we should remove them if we bring it back
- makeURLAbsolute: function makeURLAbsolute(base, url) {
- // Note: makeURI() will throw if url is not a valid URI
- return this.makeURI(url, null, this.makeURI(base)).spec;
- },
-
- makeURI: function makeURI(aURL, aOriginCharset, aBaseURI) {
- return Services.io.newURI(aURL, aOriginCharset, aBaseURI);
- },
-
- _getLink: function(aElement) {
- if (aElement.nodeType == Ci.nsIDOMNode.ELEMENT_NODE &&
- ((aElement instanceof Ci.nsIDOMHTMLAnchorElement && aElement.href) ||
- (aElement instanceof Ci.nsIDOMHTMLAreaElement && aElement.href) ||
- aElement instanceof Ci.nsIDOMHTMLLinkElement ||
- aElement.getAttributeNS(kXLinkNamespace, "type") == "simple")) {
- try {
- let url = this._getLinkURL(aElement);
- return Services.io.newURI(url, null, null);
- } catch (e) {}
- }
- return null;
- },
-
- _disableRestricted: function _disableRestricted(restriction, selector) {
- return {
- matches: function _disableRestrictedMatches(aElement, aX, aY) {
- if (!ParentalControls.isAllowed(ParentalControls[restriction])) {
- return false;
- }
-
- return selector.matches(aElement, aX, aY);
- }
- };
- },
-
- _getLinkURL: function ch_getLinkURL(aLink) {
- let href = aLink.href;
- if (href)
- return href;
-
- href = aLink.getAttribute("href") ||
- aLink.getAttributeNS(kXLinkNamespace, "href");
- if (!href || !href.match(/\S/)) {
- // Without this we try to save as the current doc,
- // for example, HTML case also throws if empty
- throw "Empty href";
- }
-
- return this.makeURLAbsolute(aLink.baseURI, href);
- },
-
- _copyStringToDefaultClipboard: function(aString) {
- let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
- clipboard.copyString(aString);
- Snackbars.show(Strings.browser.GetStringFromName("selectionHelper.textCopied"), Snackbars.LENGTH_LONG);
- },
-
- _stripScheme: function(aString) {
- let index = aString.indexOf(":");
- return aString.slice(index + 1);
- }
- }
-};
-
-XPCOMUtils.defineLazyModuleGetter(this, "PageActions",
- "resource://gre/modules/PageActions.jsm");
-
-// These alias to the old, deprecated NativeWindow interfaces
-[
- ["pageactions", "resource://gre/modules/PageActions.jsm", "PageActions"],
- ["toast", "resource://gre/modules/Snackbars.jsm", "Snackbars"]
-].forEach(item => {
- let [name, script, exprt] = item;
-
- XPCOMUtils.defineLazyGetter(NativeWindow, name, () => {
- var err = Strings.browser.formatStringFromName("nativeWindow.deprecated", ["NativeWindow." + name, script], 2);
- Cu.reportError(err);
-
- let sandbox = {};
- Cu.import(script, sandbox);
- return sandbox[exprt];
- });
-});
-
-var LightWeightThemeWebInstaller = {
- init: function sh_init() {
- let temp = {};
- Cu.import("resource://gre/modules/LightweightThemeConsumer.jsm", temp);
- let theme = new temp.LightweightThemeConsumer(document);
- BrowserApp.deck.addEventListener("InstallBrowserTheme", this, false, true);
- BrowserApp.deck.addEventListener("PreviewBrowserTheme", this, false, true);
- BrowserApp.deck.addEventListener("ResetBrowserThemePreview", this, false, true);
-
- if (ParentalControls.parentalControlsEnabled &&
- !this._manager.currentTheme &&
- ParentalControls.isAllowed(ParentalControls.DEFAULT_THEME)) {
- // We are using the DEFAULT_THEME restriction to differentiate between restricted profiles & guest mode - Bug 1199596
- this._installParentalControlsTheme();
- }
- },
-
- handleEvent: function (event) {
- switch (event.type) {
- case "InstallBrowserTheme":
- case "PreviewBrowserTheme":
- case "ResetBrowserThemePreview":
- // ignore requests from background tabs
- if (event.target.ownerDocument.defaultView.top != content)
- return;
- }
-
- switch (event.type) {
- case "InstallBrowserTheme":
- this._installRequest(event);
- break;
- case "PreviewBrowserTheme":
- this._preview(event);
- break;
- case "ResetBrowserThemePreview":
- this._resetPreview(event);
- break;
- case "pagehide":
- case "TabSelect":
- this._resetPreview();
- break;
- }
- },
-
- get _manager () {
- let temp = {};
- Cu.import("resource://gre/modules/LightweightThemeManager.jsm", temp);
- delete this._manager;
- return this._manager = temp.LightweightThemeManager;
- },
-
- _installParentalControlsTheme: function() {
- let mgr = this._manager;
- let parentalControlsTheme = {
- "headerURL": "resource://android/assets/parental_controls_theme.png",
- "name": "Parental Controls Theme",
- "id": "parental-controls-theme@mozilla.org"
- };
-
- mgr.addBuiltInTheme(parentalControlsTheme);
- mgr.themeChanged(parentalControlsTheme);
- },
-
- _installRequest: function (event) {
- let node = event.target;
- let data = this._getThemeFromNode(node);
- if (!data)
- return;
-
- if (this._isAllowed(node)) {
- this._install(data);
- return;
- }
-
- let allowButtonText = Strings.browser.GetStringFromName("lwthemeInstallRequest.allowButton");
- let message = Strings.browser.formatStringFromName("lwthemeInstallRequest.message", [node.ownerDocument.location.hostname], 1);
- let buttons = [{
- label: allowButtonText,
- callback: function () {
- LightWeightThemeWebInstaller._install(data);
- },
- positive: true
- }];
-
- NativeWindow.doorhanger.show(message, "Personas", buttons, BrowserApp.selectedTab.id);
- },
-
- _install: function (newLWTheme) {
- this._manager.currentTheme = newLWTheme;
- },
-
- _previewWindow: null,
- _preview: function (event) {
- if (!this._isAllowed(event.target))
- return;
- let data = this._getThemeFromNode(event.target);
- if (!data)
- return;
- this._resetPreview();
-
- this._previewWindow = event.target.ownerDocument.defaultView;
- this._previewWindow.addEventListener("pagehide", this, true);
- BrowserApp.deck.addEventListener("TabSelect", this, false);
- this._manager.previewTheme(data);
- },
-
- _resetPreview: function (event) {
- if (!this._previewWindow ||
- event && !this._isAllowed(event.target))
- return;
-
- this._previewWindow.removeEventListener("pagehide", this, true);
- this._previewWindow = null;
- BrowserApp.deck.removeEventListener("TabSelect", this, false);
-
- this._manager.resetPreview();
- },
-
- _isAllowed: function (node) {
- // Make sure the whitelist has been imported to permissions
- PermissionsUtils.importFromPrefs("xpinstall.", "install");
-
- let pm = Services.perms;
-
- let uri = node.ownerDocument.documentURIObject;
- if (!uri.schemeIs("https")) {
- return false;
- }
-
- return pm.testPermission(uri, "install") == pm.ALLOW_ACTION;
- },
-
- _getThemeFromNode: function (node) {
- return this._manager.parseTheme(node.getAttribute("data-browsertheme"), node.baseURI);
- }
-};
-
-var DesktopUserAgent = {
- DESKTOP_UA: null,
- TCO_DOMAIN: "t.co",
- TCO_REPLACE: / Gecko.*/,
-
- init: function ua_init() {
- Services.obs.addObserver(this, "DesktopMode:Change", false);
- UserAgentOverrides.addComplexOverride(this.onRequest.bind(this));
-
- // See https://developer.mozilla.org/en/Gecko_user_agent_string_reference
- this.DESKTOP_UA = Cc["@mozilla.org/network/protocol;1?name=http"]
- .getService(Ci.nsIHttpProtocolHandler).userAgent
- .replace(/Android \d.+?; [a-zA-Z]+/, "X11; Linux x86_64")
- .replace(/Gecko\/[0-9\.]+/, "Gecko/20100101");
- },
-
- onRequest: function(channel, defaultUA) {
- if (AppConstants.NIGHTLY_BUILD && this.TCO_DOMAIN == channel.URI.host) {
- // Force the referrer
- channel.referrer = channel.URI;
-
- // Send a bot-like UA to t.co to get a real redirect. We strip off the
- // "Gecko/x.y Firefox/x.y" part
- return defaultUA.replace(this.TCO_REPLACE, "");
- }
-
- let channelWindow = this._getWindowForRequest(channel);
- let tab = BrowserApp.getTabForWindow(channelWindow);
- if (tab) {
- return this.getUserAgentForTab(tab);
- }
-
- return null;
- },
-
- getUserAgentForWindow: function ua_getUserAgentForWindow(aWindow) {
- let tab = BrowserApp.getTabForWindow(aWindow.top);
- if (tab) {
- return this.getUserAgentForTab(tab);
- }
-
- return null;
- },
-
- getUserAgentForTab: function ua_getUserAgentForTab(aTab) {
- // Send desktop UA if "Request Desktop Site" is enabled.
- if (aTab.desktopMode) {
- return this.DESKTOP_UA;
- }
-
- return null;
- },
-
- _getRequestLoadContext: function ua_getRequestLoadContext(aRequest) {
- if (aRequest && aRequest.notificationCallbacks) {
- try {
- return aRequest.notificationCallbacks.getInterface(Ci.nsILoadContext);
- } catch (ex) { }
- }
-
- if (aRequest && aRequest.loadGroup && aRequest.loadGroup.notificationCallbacks) {
- try {
- return aRequest.loadGroup.notificationCallbacks.getInterface(Ci.nsILoadContext);
- } catch (ex) { }
- }
-
- return null;
- },
-
- _getWindowForRequest: function ua_getWindowForRequest(aRequest) {
- let loadContext = this._getRequestLoadContext(aRequest);
- if (loadContext) {
- try {
- return loadContext.associatedWindow;
- } catch (e) {
- // loadContext.associatedWindow can throw when there's no window
- }
- }
- return null;
- },
-
- observe: function ua_observe(aSubject, aTopic, aData) {
- if (aTopic === "DesktopMode:Change") {
- let args = JSON.parse(aData);
- let tab = BrowserApp.getTabForId(args.tabId);
- if (tab) {
- tab.reloadWithMode(args.desktopMode);
- }
- }
- }
-};
-
-
-function nsBrowserAccess() {
-}
-
-nsBrowserAccess.prototype = {
- QueryInterface: XPCOMUtils.generateQI([Ci.nsIBrowserDOMWindow]),
-
- _getBrowser: function _getBrowser(aURI, aOpener, aWhere, aFlags) {
- let isExternal = !!(aFlags & Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
- if (isExternal && aURI && aURI.schemeIs("chrome"))
- return null;
-
- let loadflags = isExternal ?
- Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL :
- Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
- if (aWhere == Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW) {
- if (isExternal) {
- aWhere = Services.prefs.getIntPref("browser.link.open_external");
- } else {
- aWhere = Services.prefs.getIntPref("browser.link.open_newwindow");
- }
- }
-
- Services.io.offline = false;
-
- let referrer;
- if (aOpener) {
- try {
- let location = aOpener.location;
- referrer = Services.io.newURI(location, null, null);
- } catch(e) { }
- }
-
- let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
- let pinned = false;
-
- if (aURI && aWhere == Ci.nsIBrowserDOMWindow.OPEN_SWITCHTAB) {
- pinned = true;
- let spec = aURI.spec;
- let tabs = BrowserApp.tabs;
- for (let i = 0; i < tabs.length; i++) {
- let appOrigin = ss.getTabValue(tabs[i], "appOrigin");
- if (appOrigin == spec) {
- let tab = tabs[i];
- BrowserApp.selectTab(tab);
- return tab.browser;
- }
- }
- }
-
- // If OPEN_SWITCHTAB was not handled above, we need to open a new tab,
- // along with other OPEN_ values that create a new tab.
- let newTab = (aWhere == Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW ||
- aWhere == Ci.nsIBrowserDOMWindow.OPEN_NEWTAB ||
- aWhere == Ci.nsIBrowserDOMWindow.OPEN_SWITCHTAB);
- let isPrivate = false;
-
- if (newTab) {
- let parentId = -1;
- if (!isExternal && aOpener) {
- let parent = BrowserApp.getTabForWindow(aOpener.top);
- if (parent) {
- parentId = parent.id;
- isPrivate = PrivateBrowsingUtils.isBrowserPrivate(parent.browser);
- }
- }
-
- let openerWindow = (aFlags & Ci.nsIBrowserDOMWindow.OPEN_NO_OPENER) ? null : aOpener;
- // BrowserApp.addTab calls loadURIWithFlags with the appropriate params
- let tab = BrowserApp.addTab(aURI ? aURI.spec : "about:blank", { flags: loadflags,
- referrerURI: referrer,
- external: isExternal,
- parentId: parentId,
- opener: openerWindow,
- selected: true,
- isPrivate: isPrivate,
- pinned: pinned });
-
- return tab.browser;
- }
-
- // OPEN_CURRENTWINDOW and illegal values
- let browser = BrowserApp.selectedBrowser;
- if (aURI && browser) {
- browser.loadURIWithFlags(aURI.spec, loadflags, referrer, null, null);
- }
-
- return browser;
- },
-
- openURI: function browser_openURI(aURI, aOpener, aWhere, aFlags) {
- let browser = this._getBrowser(aURI, aOpener, aWhere, aFlags);
- return browser ? browser.contentWindow : null;
- },
-
- openURIInFrame: function browser_openURIInFrame(aURI, aParams, aWhere, aFlags) {
- let browser = this._getBrowser(aURI, null, aWhere, aFlags);
- return browser ? browser.QueryInterface(Ci.nsIFrameLoaderOwner) : null;
- },
-
- isTabContentWindow: function(aWindow) {
- return BrowserApp.getBrowserForWindow(aWindow) != null;
- },
-
- canClose() {
- return BrowserUtils.canCloseWindow(window);
- },
-};
-
-
-function Tab(aURL, aParams) {
- this.filter = null;
- this.browser = null;
- this.id = 0;
- this.lastTouchedAt = Date.now();
- this._zoom = 1.0;
- this._drawZoom = 1.0;
- this._restoreZoom = false;
- this.userScrollPos = { x: 0, y: 0 };
- this.contentDocumentIsDisplayed = true;
- this.pluginDoorhangerTimeout = null;
- this.shouldShowPluginDoorhanger = true;
- this.clickToPlayPluginsActivated = false;
- this.desktopMode = false;
- this.originalURI = null;
- this.hasTouchListener = false;
- this.playingAudio = false;
-
- this.create(aURL, aParams);
-}
-
-/*
- * Sanity limit for URIs passed to UI code.
- *
- * 2000 is the typical industry limit, largely due to older IE versions.
- *
- * We use 25000, so we'll allow almost any value through.
- *
- * Still, this truncation doesn't affect history, so this is only a practical
- * concern in two ways: the truncated value is used when editing URIs, and as
- * the key for favicon fetches.
- */
-const MAX_URI_LENGTH = 25000;
-
-/*
- * Similar restriction for titles. This is only a display concern.
- */
-const MAX_TITLE_LENGTH = 255;
-
-/**
- * Ensure that a string is of a sane length.
- */
-function truncate(text, max) {
- if (!text || !max) {
- return text;
- }
-
- if (text.length <= max) {
- return text;
- }
-
- return text.slice(0, max) + "…";
-}
-
-Tab.prototype = {
- create: function(aURL, aParams) {
- if (this.browser)
- return;
-
- aParams = aParams || {};
-
- this.browser = document.createElement("browser");
- this.browser.setAttribute("type", "content-targetable");
- this.browser.setAttribute("messagemanagergroup", "browsers");
-
- if (Preferences.get("browser.tabs.remote.force-enable", false)) {
- this.browser.setAttribute("remote", "true");
- }
-
- this.browser.permanentKey = {};
-
- // Check if we have a "parent" window which we need to set as our opener
- if ("opener" in aParams) {
- this.browser.presetOpenerWindow(aParams.opener);
- }
-
- // Make sure the previously selected panel remains selected. The selected panel of a deck is
- // not stable when panels are added.
- let selectedPanel = BrowserApp.deck.selectedPanel;
- BrowserApp.deck.insertBefore(this.browser, aParams.sibling || null);
- BrowserApp.deck.selectedPanel = selectedPanel;
-
- let attrs = {};
- if (BrowserApp.manifestUrl) {
- let appsService = Cc["@mozilla.org/AppsService;1"].getService(Ci.nsIAppsService);
- let manifest = appsService.getAppByManifestURL(BrowserApp.manifestUrl);
- if (manifest) {
- let app = manifest.QueryInterface(Ci.mozIApplication);
- this.browser.docShell.frameType = Ci.nsIDocShell.FRAME_TYPE_APP;
- attrs['appId'] = app.localId;
- }
- }
-
- // Must be called after appendChild so the docShell has been created.
- this.setActive(false);
-
- let isPrivate = ("isPrivate" in aParams) && aParams.isPrivate;
- if (isPrivate) {
- attrs['privateBrowsingId'] = 1;
- }
-
- this.browser.docShell.setOriginAttributes(attrs);
-
- // Set the new docShell load flags based on network state.
- if (Tabs.useCache) {
- this.browser.docShell.defaultLoadFlags |= Ci.nsIRequest.LOAD_FROM_CACHE;
- }
-
- this.browser.stop();
-
- // Only set tab uri if uri is valid
- let uri = null;
- let title = aParams.title || aURL;
- try {
- uri = Services.io.newURI(aURL, null, null).spec;
- } catch (e) {}
-
- // When the tab is stubbed from Java, there's a window between the stub
- // creation and the tab creation in Gecko where the stub could be removed
- // or the selected tab can change (which is easiest to hit during startup).
- // To prevent these races, we need to differentiate between tab stubs from
- // Java and new tabs from Gecko.
- let stub = false;
-
- if (!aParams.zombifying) {
- if ("tabID" in aParams) {
- this.id = aParams.tabID;
- stub = true;
- } else {
- let jenv = JNI.GetForThread();
- let jTabs = JNI.LoadClass(jenv, "org.mozilla.gecko.Tabs", {
- static_methods: [
- { name: "getNextTabId", sig: "()I" }
- ],
- });
- this.id = jTabs.getNextTabId();
- JNI.UnloadClasses(jenv);
- }
-
- this.desktopMode = ("desktopMode" in aParams) ? aParams.desktopMode : false;
-
- let message = {
- type: "Tab:Added",
- tabID: this.id,
- uri: truncate(uri, MAX_URI_LENGTH),
- parentId: ("parentId" in aParams) ? aParams.parentId : -1,
- tabIndex: ("tabIndex" in aParams) ? aParams.tabIndex : -1,
- external: ("external" in aParams) ? aParams.external : false,
- selected: ("selected" in aParams || aParams.cancelEditMode === true) ? aParams.selected : true,
- cancelEditMode: aParams.cancelEditMode === true,
- title: truncate(title, MAX_TITLE_LENGTH),
- delayLoad: aParams.delayLoad || false,
- desktopMode: this.desktopMode,
- isPrivate: isPrivate,
- stub: stub
- };
- Messaging.sendRequest(message);
- }
-
- let flags = Ci.nsIWebProgress.NOTIFY_STATE_ALL |
- Ci.nsIWebProgress.NOTIFY_LOCATION |
- Ci.nsIWebProgress.NOTIFY_SECURITY;
- this.filter = Cc["@mozilla.org/appshell/component/browser-status-filter;1"].createInstance(Ci.nsIWebProgress);
- this.filter.addProgressListener(this, flags)
- this.browser.addProgressListener(this.filter, flags);
- this.browser.sessionHistory.addSHistoryListener(this);
-
- this.browser.addEventListener("DOMContentLoaded", this, true);
- this.browser.addEventListener("DOMFormHasPassword", this, true);
- this.browser.addEventListener("DOMInputPasswordAdded", this, true);
- this.browser.addEventListener("DOMLinkAdded", this, true);
- this.browser.addEventListener("DOMLinkChanged", this, true);
- this.browser.addEventListener("DOMMetaAdded", this, false);
- this.browser.addEventListener("DOMTitleChanged", this, true);
- this.browser.addEventListener("DOMAudioPlaybackStarted", this, true);
- this.browser.addEventListener("DOMAudioPlaybackStopped", this, true);
- this.browser.addEventListener("DOMWindowClose", this, true);
- this.browser.addEventListener("DOMWillOpenModalDialog", this, true);
- this.browser.addEventListener("DOMAutoComplete", this, true);
- this.browser.addEventListener("blur", this, true);
- this.browser.addEventListener("pageshow", this, true);
- this.browser.addEventListener("MozApplicationManifest", this, true);
- this.browser.addEventListener("TabPreZombify", this, true);
-
- // Note that the XBL binding is untrusted
- this.browser.addEventListener("PluginBindingAttached", this, true, true);
- this.browser.addEventListener("VideoBindingAttached", this, true, true);
- this.browser.addEventListener("VideoBindingCast", this, true, true);
-
- Services.obs.addObserver(this, "before-first-paint", false);
- Services.obs.addObserver(this, "media-playback", false);
- Services.obs.addObserver(this, "media-playback-resumed", false);
-
- // Always intialise new tabs with basic session store data to avoid
- // problems with functions that always expect it to be present
- this.browser.__SS_data = {
- entries: [{
- url: aURL,
- title: truncate(title, MAX_TITLE_LENGTH)
- }],
- index: 1,
- desktopMode: this.desktopMode,
- isPrivate: isPrivate
- };
-
- if (aParams.delayLoad) {
- // If this is a zombie tab, mark the browser for delay loading, which will
- // restore the tab when selected using the session data added above
- this.browser.__SS_restore = true;
- } else {
- let flags = "flags" in aParams ? aParams.flags : Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
- let postData = ("postData" in aParams && aParams.postData) ? aParams.postData.value : null;
- let referrerURI = "referrerURI" in aParams ? aParams.referrerURI : null;
- let charset = "charset" in aParams ? aParams.charset : null;
-
- // The search term the user entered to load the current URL
- this.userRequested = "userRequested" in aParams ? aParams.userRequested : "";
- this.isSearch = "isSearch" in aParams ? aParams.isSearch : false;
-
- try {
- this.browser.loadURIWithFlags(aURL, flags, referrerURI, charset, postData);
- } catch(e) {
- let message = {
- type: "Content:LoadError",
- tabID: this.id
- };
- Messaging.sendRequest(message);
- dump("Handled load error: " + e);
- }
- }
- },
-
- /**
- * Reloads the tab with the desktop mode setting.
- */
- reloadWithMode: function (aDesktopMode) {
- // notify desktopmode for PIDOMWindow
- let win = this.browser.contentWindow;
- let dwi = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
- dwi.setDesktopModeViewport(aDesktopMode);
-
- // Set desktop mode for tab and send change to Java
- if (this.desktopMode != aDesktopMode) {
- this.desktopMode = aDesktopMode;
- Messaging.sendRequest({
- type: "DesktopMode:Changed",
- desktopMode: aDesktopMode,
- tabID: this.id
- });
- }
-
- // Only reload the page for http/https schemes
- let currentURI = this.browser.currentURI;
- if (!currentURI.schemeIs("http") && !currentURI.schemeIs("https"))
- return;
-
- let url = currentURI.spec;
- // We need LOAD_FLAGS_BYPASS_CACHE here since we're changing the User-Agent
- // string, and servers typically don't use the Vary: User-Agent header, so
- // not doing this means that we'd get some of the previously cached content.
- let flags = Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE |
- Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY;
- if (this.originalURI && !this.originalURI.equals(currentURI)) {
- // We were redirected; reload the original URL
- url = this.originalURI.spec;
- }
-
- this.browser.docShell.loadURI(url, flags, null, null, null);
- },
-
- destroy: function() {
- if (!this.browser)
- return;
-
- this.browser.removeProgressListener(this.filter);
- this.filter.removeProgressListener(this);
- this.filter = null;
- this.browser.sessionHistory.removeSHistoryListener(this);
-
- this.browser.removeEventListener("DOMContentLoaded", this, true);
- this.browser.removeEventListener("DOMFormHasPassword", this, true);
- this.browser.removeEventListener("DOMInputPasswordAdded", this, true);
- this.browser.removeEventListener("DOMLinkAdded", this, true);
- this.browser.removeEventListener("DOMLinkChanged", this, true);
- this.browser.removeEventListener("DOMMetaAdded", this, false);
- this.browser.removeEventListener("DOMTitleChanged", this, true);
- this.browser.removeEventListener("DOMAudioPlaybackStarted", this, true);
- this.browser.removeEventListener("DOMAudioPlaybackStopped", this, true);
- this.browser.removeEventListener("DOMWindowClose", this, true);
- this.browser.removeEventListener("DOMWillOpenModalDialog", this, true);
- this.browser.removeEventListener("DOMAutoComplete", this, true);
- this.browser.removeEventListener("blur", this, true);
- this.browser.removeEventListener("pageshow", this, true);
- this.browser.removeEventListener("MozApplicationManifest", this, true);
- this.browser.removeEventListener("TabPreZombify", this, true);
-
- this.browser.removeEventListener("PluginBindingAttached", this, true, true);
- this.browser.removeEventListener("VideoBindingAttached", this, true, true);
- this.browser.removeEventListener("VideoBindingCast", this, true, true);
-
- Services.obs.removeObserver(this, "before-first-paint");
- Services.obs.removeObserver(this, "media-playback", false);
- Services.obs.removeObserver(this, "media-playback-resumed", false);
-
- // Make sure the previously selected panel remains selected. The selected panel of a deck is
- // not stable when panels are removed.
- let selectedPanel = BrowserApp.deck.selectedPanel;
- BrowserApp.deck.removeChild(this.browser);
- BrowserApp.deck.selectedPanel = selectedPanel;
-
- this.browser = null;
- },
-
- // This should be called to update the browser when the tab gets selected/unselected
- setActive: function setActive(aActive) {
- if (!this.browser || !this.browser.docShell)
- return;
-
- this.lastTouchedAt = Date.now();
-
- if (aActive) {
- this.browser.setAttribute("type", "content-primary");
- this.browser.focus();
- this.browser.docShellIsActive = true;
- Reader.updatePageAction(this);
- ExternalApps.updatePageAction(this.browser.currentURI, this.browser.contentDocument);
- } else {
- this.browser.setAttribute("type", "content-targetable");
- this.browser.docShellIsActive = false;
- this.browser.blur();
- }
- },
-
- getActive: function getActive() {
- return this.browser.docShellIsActive;
- },
-
- // These constants are used to prioritize high quality metadata over low quality data, so that
- // we can collect data as we find meta tags, and replace low quality metadata with higher quality
- // matches. For instance a msApplicationTile icon is a better tile image than an og:image tag.
- METADATA_GOOD_MATCH: 10,
- METADATA_NORMAL_MATCH: 1,
-
- addMetadata: function(type, value, quality = 1) {
- if (!this.metatags) {
- this.metatags = {
- url: this.browser.currentURI.specIgnoringRef
- };
- }
-
- if (type == "touchIconList") {
- if (!this.metatags['touchIconList']) {
- this.metatags['touchIconList'] = {};
- }
- this.metatags.touchIconList[quality] = value;
- } else if (!this.metatags[type] || this.metatags[type + "_quality"] < quality) {
- this.metatags[type] = value;
- this.metatags[type + "_quality"] = quality;
- }
- },
-
- sanitizeRelString: function(linkRel) {
- // Sanitize the rel string
- let list = [];
- if (linkRel) {
- list = linkRel.toLowerCase().split(/\s+/);
- let hash = {};
- list.forEach(function(value) { hash[value] = true; });
- list = [];
- for (let rel in hash)
- list.push("[" + rel + "]");
- }
- return list;
- },
-
- makeFaviconMessage: function(eventTarget) {
- // We want to get the largest icon size possible for our UI.
- let maxSize = 0;
-
- // We use the sizes attribute if available
- // see http://www.whatwg.org/specs/web-apps/current-work/multipage/links.html#rel-icon
- if (eventTarget.hasAttribute("sizes")) {
- let sizes = eventTarget.getAttribute("sizes").toLowerCase();
-
- if (sizes == "any") {
- // Since Java expects an integer, use -1 to represent icons with sizes="any"
- maxSize = -1;
- } else {
- let tokens = sizes.split(" ");
- tokens.forEach(function(token) {
- // TODO: check for invalid tokens
- let [w, h] = token.split("x");
- maxSize = Math.max(maxSize, Math.max(w, h));
- });
- }
- }
- return {
- type: "Link:Favicon",
- tabID: this.id,
- href: resolveGeckoURI(eventTarget.href),
- size: maxSize,
- mime: eventTarget.getAttribute("type") || ""
- };
- },
-
- makeFeedMessage: function(eventTarget, targetType) {
- try {
- // urlSecurityCeck will throw if things are not OK
- ContentAreaUtils.urlSecurityCheck(eventTarget.href,
- eventTarget.ownerDocument.nodePrincipal,
- Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL);
-
- if (!this.browser.feeds)
- this.browser.feeds = [];
-
- this.browser.feeds.push({
- href: eventTarget.href,
- title: eventTarget.title,
- type: targetType
- });
-
- return {
- type: "Link:Feed",
- tabID: this.id
- };
- } catch (e) {
- return null;
- }
- },
-
- sendOpenSearchMessage: function(eventTarget) {
- let type = eventTarget.type && eventTarget.type.toLowerCase();
- // Replace all starting or trailing spaces or spaces before "*;" globally w/ "".
- type = type.replace(/^\s+|\s*(?:;.*)?$/g, "");
-
- // Check that type matches opensearch.
- let isOpenSearch = (type == "application/opensearchdescription+xml");
- if (isOpenSearch && eventTarget.title && /^(?:https?|ftp):/i.test(eventTarget.href)) {
- Services.search.init(() => {
- let visibleEngines = Services.search.getVisibleEngines();
- // NOTE: Engines are currently identified by name, but this can be changed
- // when Engines are identified by URL (see bug 335102).
- if (visibleEngines.some(function(e) {
- return e.name == eventTarget.title;
- })) {
- // This engine is already present, do nothing.
- return null;
- }
-
- if (this.browser.engines) {
- // This engine has already been handled, do nothing.
- if (this.browser.engines.some(function(e) {
- return e.url == eventTarget.href;
- })) {
- return null;
- }
- } else {
- this.browser.engines = [];
- }
-
- // Get favicon.
- let iconURL = eventTarget.ownerDocument.documentURIObject.prePath + "/favicon.ico";
-
- let newEngine = {
- title: eventTarget.title,
- url: eventTarget.href,
- iconURL: iconURL
- };
-
- this.browser.engines.push(newEngine);
-
- // Don't send a message to display engines if we've already handled an engine.
- if (this.browser.engines.length > 1)
- return null;
-
- // Broadcast message that this tab contains search engines that should be visible.
- Messaging.sendRequest({
- type: "Link:OpenSearch",
- tabID: this.id,
- visible: true
- });
- });
- }
- },
-
- handleEvent: function(aEvent) {
- switch (aEvent.type) {
- case "DOMContentLoaded": {
- let target = aEvent.originalTarget;
-
- // ignore on frames and other documents
- if (target != this.browser.contentDocument)
- return;
-
- // Sample the background color of the page and pass it along. (This is used to draw the
- // checkerboard.) Right now we don't detect changes in the background color after this
- // event fires; it's not clear that doing so is worth the effort.
- var backgroundColor = null;
- try {
- let { contentDocument, contentWindow } = this.browser;
- let computedStyle = contentWindow.getComputedStyle(contentDocument.body);
- backgroundColor = computedStyle.backgroundColor;
- } catch (e) {
- // Ignore. Catching and ignoring exceptions here ensures that Talos succeeds.
- }
-
- let docURI = target.documentURI;
- let errorType = "";
- if (docURI.startsWith("about:certerror")) {
- errorType = "certerror";
- }
- else if (docURI.startsWith("about:blocked")) {
- errorType = "blocked";
- }
- else if (docURI.startsWith("about:neterror")) {
- let error = docURI.search(/e\=/);
- let duffUrl = docURI.search(/\&u\=/);
- let errorExtra = decodeURIComponent(docURI.slice(error + 2, duffUrl));
- // Here is a list of errorExtra types (et_*)
- // http://mxr.mozilla.org/mozilla-central/source/mobile/android/chrome/content/netError.xhtml#287
- UITelemetry.addEvent("neterror.1", "content", null, errorExtra);
- errorType = "neterror";
- }
-
- // Attach a listener to watch for "click" events bubbling up from error
- // pages and other similar page. This lets us fix bugs like 401575 which
- // require error page UI to do privileged things, without letting error
- // pages have any privilege themselves.
- if (docURI.startsWith("about:neterror")) {
- NetErrorHelper.attachToBrowser(this.browser);
- }
-
- Messaging.sendRequest({
- type: "DOMContentLoaded",
- tabID: this.id,
- bgColor: backgroundColor,
- errorType: errorType,
- metadata: this.metatags,
- });
-
- // Reset isSearch so that the userRequested term will be erased on next page load
- this.metatags = null;
-
- if (docURI.startsWith("about:certerror") || docURI.startsWith("about:blocked")) {
- this.browser.addEventListener("click", ErrorPageEventHandler, true);
- let listener = function() {
- this.browser.removeEventListener("click", ErrorPageEventHandler, true);
- this.browser.removeEventListener("pagehide", listener, true);
- }.bind(this);
-
- this.browser.addEventListener("pagehide", listener, true);
- }
-
- if (AppConstants.NIGHTLY_BUILD || AppConstants.MOZ_ANDROID_ACTIVITY_STREAM) {
- WebsiteMetadata.parseAsynchronously(this.browser.contentDocument);
- }
-
- break;
- }
-
- case "DOMFormHasPassword": {
- LoginManagerContent.onDOMFormHasPassword(aEvent,
- this.browser.contentWindow);
-
- // Send logins for this hostname to Java.
- let hostname = aEvent.target.baseURIObject.prePath;
- let foundLogins = Services.logins.findLogins({}, hostname, "", "");
- if (foundLogins.length > 0) {
- let displayHost = IdentityHandler.getEffectiveHost();
- let title = { text: displayHost, resource: hostname };
- let selectObj = { title: title, logins: foundLogins };
- Messaging.sendRequest({ type: "Doorhanger:Logins", data: selectObj });
- }
- break;
- }
-
- case "DOMInputPasswordAdded": {
- LoginManagerContent.onDOMInputPasswordAdded(aEvent,
- this.browser.contentWindow);
- }
-
- case "DOMMetaAdded":
- let target = aEvent.originalTarget;
- let browser = BrowserApp.getBrowserForDocument(target.ownerDocument);
-
- switch (target.name) {
- case "msapplication-TileImage":
- this.addMetadata("tileImage", browser.currentURI.resolve(target.content), this.METADATA_GOOD_MATCH);
- break;
- case "msapplication-TileColor":
- this.addMetadata("tileColor", target.content, this.METADATA_GOOD_MATCH);
- break;
- }
-
- break;
-
- case "DOMLinkAdded":
- case "DOMLinkChanged": {
- let jsonMessage = null;
- let target = aEvent.originalTarget;
- if (!target.href || target.disabled)
- return;
-
- // Ignore on frames and other documents
- if (target.ownerDocument != this.browser.contentDocument)
- return;
-
- // Sanitize rel link
- let list = this.sanitizeRelString(target.rel);
- if (list.indexOf("[icon]") != -1) {
- jsonMessage = this.makeFaviconMessage(target);
- } else if (list.indexOf("[apple-touch-icon]") != -1 ||
- list.indexOf("[apple-touch-icon-precomposed]") != -1) {
- jsonMessage = this.makeFaviconMessage(target);
- jsonMessage['type'] = 'Link:Touchicon';
- this.addMetadata("touchIconList", jsonMessage.href, jsonMessage.size);
- } else if (list.indexOf("[alternate]") != -1 && aEvent.type == "DOMLinkAdded") {
- let type = target.type.toLowerCase().replace(/^\s+|\s*(?:;.*)?$/g, "");
- let isFeed = (type == "application/rss+xml" || type == "application/atom+xml");
-
- if (!isFeed)
- return;
-
- jsonMessage = this.makeFeedMessage(target, type);
- } else if (list.indexOf("[search]") != -1 && aEvent.type == "DOMLinkAdded") {
- this.sendOpenSearchMessage(target);
- }
- if (!jsonMessage)
- return;
-
- Messaging.sendRequest(jsonMessage);
- break;
- }
-
- case "DOMTitleChanged": {
- if (!aEvent.isTrusted)
- return;
-
- // ignore on frames and other documents
- if (aEvent.originalTarget != this.browser.contentDocument)
- return;
-
- Messaging.sendRequest({
- type: "DOMTitleChanged",
- tabID: this.id,
- title: truncate(aEvent.target.title, MAX_TITLE_LENGTH)
- });
- break;
- }
-
- case "TabPreZombify": {
- if (!this.playingAudio) {
- return;
- }
- // Fall through to the DOMAudioPlayback events, so the
- // audio playback indicator gets reset upon zombification.
- }
- case "DOMAudioPlaybackStarted":
- case "DOMAudioPlaybackStopped": {
- if (!Services.prefs.getBoolPref("browser.tabs.showAudioPlayingIcon") ||
- !aEvent.isTrusted) {
- return;
- }
-
- let browser = aEvent.originalTarget;
- if (browser != this.browser) {
- return;
- }
-
- this.playingAudio = aEvent.type === "DOMAudioPlaybackStarted";
-
- Messaging.sendRequest({
- type: "Tab:AudioPlayingChange",
- tabID: this.id,
- isAudioPlaying: this.playingAudio
- });
- return;
- }
-
- case "DOMWindowClose": {
- if (!aEvent.isTrusted)
- return;
-
- // Find the relevant tab, and close it from Java
- if (this.browser.contentWindow == aEvent.target) {
- aEvent.preventDefault();
-
- Messaging.sendRequest({
- type: "Tab:Close",
- tabID: this.id
- });
- }
- break;
- }
-
- case "DOMWillOpenModalDialog": {
- if (!aEvent.isTrusted)
- return;
-
- // We're about to open a modal dialog, make sure the opening
- // tab is brought to the front.
- let tab = BrowserApp.getTabForWindow(aEvent.target.top);
- BrowserApp.selectTab(tab);
- break;
- }
-
- case "DOMAutoComplete":
- case "blur": {
- LoginManagerContent.onUsernameInput(aEvent);
- break;
- }
-
- case "PluginBindingAttached": {
- PluginHelper.handlePluginBindingAttached(this, aEvent);
- break;
- }
-
- case "VideoBindingAttached": {
- CastingApps.handleVideoBindingAttached(this, aEvent);
- break;
- }
-
- case "VideoBindingCast": {
- CastingApps.handleVideoBindingCast(this, aEvent);
- break;
- }
-
- case "MozApplicationManifest": {
- OfflineApps.offlineAppRequested(aEvent.originalTarget.defaultView);
- break;
- }
-
- case "pageshow": {
- LoginManagerContent.onPageShow(aEvent, this.browser.contentWindow);
-
- // The rest of this only handles pageshow for the top-level document.
- if (aEvent.originalTarget.defaultView != this.browser.contentWindow)
- return;
-
- let target = aEvent.originalTarget;
- let docURI = target.documentURI;
- if (!docURI.startsWith("about:neterror") && !this.isSearch) {
- // If this wasn't an error page and the user isn't search, don't retain the typed entry
- this.userRequested = "";
- }
-
- Messaging.sendRequest({
- type: "Content:PageShow",
- tabID: this.id,
- userRequested: this.userRequested,
- fromCache: Tabs.useCache
- });
-
- this.isSearch = false;
-
- if (!aEvent.persisted && Services.prefs.getBoolPref("browser.ui.linkify.phone")) {
- if (!this._linkifier)
- this._linkifier = new Linkifier();
- this._linkifier.linkifyNumbers(this.browser.contentWindow.document);
- }
-
- // Update page actions for helper apps.
- let uri = this.browser.currentURI;
- if (BrowserApp.selectedTab == this) {
- if (ExternalApps.shouldCheckUri(uri)) {
- ExternalApps.updatePageAction(uri, this.browser.contentDocument);
- } else {
- ExternalApps.clearPageAction();
- }
- }
- }
- }
- },
-
- onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus) {
- let contentWin = aWebProgress.DOMWindow;
- if (contentWin != contentWin.top)
- return;
-
- // Filter optimization: Only really send NETWORK state changes to Java listener
- if (aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) {
- if (AppConstants.NIGHTLY_BUILD && (aStateFlags & Ci.nsIWebProgressListener.STATE_START)) {
- Profiler.AddMarker("Load start: " + aRequest.QueryInterface(Ci.nsIChannel).originalURI.spec);
- } else if (AppConstants.NIGHTLY_BUILD && (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) && !aWebProgress.isLoadingDocument) {
- Profiler.AddMarker("Load stop: " + aRequest.QueryInterface(Ci.nsIChannel).originalURI.spec);
- }
-
- if ((aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) && aWebProgress.isLoadingDocument) {
- // We may receive a document stop event while a document is still loading
- // (such as when doing URI fixup). Don't notify Java UI in these cases.
- return;
- }
-
- // Clear page-specific opensearch engines and feeds for a new request.
- if (aStateFlags & Ci.nsIWebProgressListener.STATE_START && aRequest && aWebProgress.isTopLevel) {
- this.browser.engines = null;
- this.browser.feeds = null;
- }
-
- // true if the page loaded successfully (i.e., no 404s or other errors)
- let success = false;
- let uri = "";
- try {
- // Remember original URI for UA changes on redirected pages
- this.originalURI = aRequest.QueryInterface(Components.interfaces.nsIChannel).originalURI;
-
- if (this.originalURI != null)
- uri = this.originalURI.spec;
- } catch (e) { }
- try {
- success = aRequest.QueryInterface(Components.interfaces.nsIHttpChannel).requestSucceeded;
- } catch (e) {
- // If the request does not handle the nsIHttpChannel interface, use nsIRequest's success
- // status. Used for local files. See bug 948849.
- success = aRequest.status == 0;
- }
-
- // Check to see if we restoring the content from a previous presentation (session)
- // since there should be no real network activity
- let restoring = (aStateFlags & Ci.nsIWebProgressListener.STATE_RESTORING) > 0;
-
- let message = {
- type: "Content:StateChange",
- tabID: this.id,
- uri: truncate(uri, MAX_URI_LENGTH),
- state: aStateFlags,
- restoring: restoring,
- success: success
- };
- Messaging.sendRequest(message);
- }
- },
-
- onLocationChange: function(aWebProgress, aRequest, aLocationURI, aFlags) {
- let contentWin = aWebProgress.DOMWindow;
-
- // Browser webapps may load content inside iframes that can not reach across the app/frame boundary
- // i.e. even though the page is loaded in an iframe window.top != webapp
- // Make cure this window is a top level tab before moving on.
- if (BrowserApp.getBrowserForWindow(contentWin) == null)
- return;
-
- this._hostChanged = true;
-
- let fixedURI = aLocationURI;
- try {
- fixedURI = URIFixup.createExposableURI(aLocationURI);
- } catch (ex) { }
-
- // In restricted profiles, we refuse to let you open various urls.
- if (!ParentalControls.isAllowed(ParentalControls.BROWSE, fixedURI)) {
- aRequest.cancel(Cr.NS_BINDING_ABORTED);
-
- this.browser.docShell.displayLoadError(Cr.NS_ERROR_UNKNOWN_PROTOCOL, fixedURI, null);
- }
-
- let contentType = contentWin.document.contentType;
-
- // If fixedURI matches browser.lastURI, we assume this isn't a real location
- // change but rather a spurious addition like a wyciwyg URI prefix. See Bug 747883.
- // Note that we have to ensure fixedURI is not the same as aLocationURI so we
- // don't false-positive page reloads as spurious additions.
- let sameDocument = (aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) != 0 ||
- ((this.browser.lastURI != null) && fixedURI.equals(this.browser.lastURI) && !fixedURI.equals(aLocationURI));
- this.browser.lastURI = fixedURI;
-
- // Let the reader logic know about same document changes because we won't get a DOMContentLoaded
- // or pageshow event, but we'll still want to update the reader view button to account for this change.
- // This mirrors the desktop logic in TabsProgressListener.
- if (aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) {
- this.browser.messageManager.sendAsyncMessage("Reader:PushState", {isArticle: this.browser.isArticle});
- }
-
- // Reset state of click-to-play plugin notifications.
- clearTimeout(this.pluginDoorhangerTimeout);
- this.pluginDoorhangerTimeout = null;
- this.shouldShowPluginDoorhanger = true;
- this.clickToPlayPluginsActivated = false;
-
- let documentURI = contentWin.document.documentURIObject.spec;
-
- // If reader mode, get the base domain for the original url.
- let strippedURI = this._stripAboutReaderURL(documentURI);
-
- // Borrowed from desktop Firefox: http://hg.mozilla.org/mozilla-central/annotate/72835344333f/browser/base/content/urlbarBindings.xml#l236
- let matchedURL = strippedURI.match(/^((?:[a-z]+:\/\/)?(?:[^\/]+@)?)(.+?)(?::\d+)?(?:\/|$)/);
- let baseDomain = "";
- if (matchedURL) {
- var domain = "";
- [, , domain] = matchedURL;
-
- try {
- baseDomain = Services.eTLD.getBaseDomainFromHost(domain);
- if (!domain.endsWith(baseDomain)) {
- // getBaseDomainFromHost converts its resultant to ACE.
- let IDNService = Cc["@mozilla.org/network/idn-service;1"].getService(Ci.nsIIDNService);
- baseDomain = IDNService.convertACEtoUTF8(baseDomain);
- }
- } catch (e) {}
- }
-
- // If we are navigating to a new location with a different host,
- // clear any URL origin that might have been pinned to this tab.
- let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
- let appOrigin = ss.getTabValue(this, "appOrigin");
- if (appOrigin) {
- let originHost = "";
- try {
- originHost = Services.io.newURI(appOrigin, null, null).host;
- } catch (e if (e.result == Cr.NS_ERROR_FAILURE)) {
- // NS_ERROR_FAILURE can be thrown by nsIURI.host if the URI scheme does not possess a host - in this case
- // we just act as if we have an empty host.
- }
- if (originHost != aLocationURI.host) {
- // Note: going 'back' will not make this tab pinned again
- ss.deleteTabValue(this, "appOrigin");
- }
- }
-
- // Update the page actions URI for helper apps.
- if (BrowserApp.selectedTab == this) {
- ExternalApps.updatePageActionUri(fixedURI);
- }
-
- let webNav = contentWin.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation);
-
- let message = {
- type: "Content:LocationChange",
- tabID: this.id,
- uri: truncate(fixedURI.spec, MAX_URI_LENGTH),
- userRequested: this.userRequested || "",
- baseDomain: baseDomain,
- contentType: (contentType ? contentType : ""),
- sameDocument: sameDocument,
-
- historyIndex: webNav.sessionHistory.index,
- historySize: webNav.sessionHistory.count,
- canGoBack: webNav.canGoBack,
- canGoForward: webNav.canGoForward,
- };
-
- Messaging.sendRequest(message);
-
- if (!sameDocument) {
- // XXX This code assumes that this is the earliest hook we have at which
- // browser.contentDocument is changed to the new document we're loading
- this.contentDocumentIsDisplayed = false;
- this.hasTouchListener = false;
- Services.obs.notifyObservers(this.browser, "Session:NotifyLocationChange", null);
- }
- },
-
- _stripAboutReaderURL: function (url) {
- return ReaderMode.getOriginalUrl(url) || url;
- },
-
- // Properties used to cache security state used to update the UI
- _state: null,
- _hostChanged: false, // onLocationChange will flip this bit
-
- onSecurityChange: function(aWebProgress, aRequest, aState) {
- // Don't need to do anything if the data we use to update the UI hasn't changed
- if (this._state == aState && !this._hostChanged)
- return;
-
- this._state = aState;
- this._hostChanged = false;
-
- let identity = IdentityHandler.checkIdentity(aState, this.browser);
-
- let message = {
- type: "Content:SecurityChange",
- tabID: this.id,
- identity: identity
- };
-
- Messaging.sendRequest(message);
- },
-
- onProgressChange: function(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress) {
- // Note: aWebProgess and aRequest will be NULL since we are filtering webprogress
- // notifications using nsBrowserStatusFilter.
- },
-
- onStatusChange: function(aBrowser, aWebProgress, aRequest, aStatus, aMessage) {
- // Note: aWebProgess and aRequest will be NULL since we are filtering webprogress
- // notifications using nsBrowserStatusFilter.
- },
-
- _getGeckoZoom: function() {
- let res = {};
- let cwu = this.browser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
- cwu.getResolution(res);
- let zoom = res.value * window.devicePixelRatio;
- return zoom;
- },
-
- saveSessionZoom: function(aZoom) {
- let cwu = this.browser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
- cwu.setResolutionAndScaleTo(aZoom / window.devicePixelRatio);
- },
-
- restoredSessionZoom: function() {
- let cwu = this.browser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
-
- if (this._restoreZoom && cwu.isResolutionSet) {
- return this._getGeckoZoom();
- }
- return null;
- },
-
- _updateZoomFromHistoryEvent: function(aHistoryEventName) {
- // Restore zoom only when moving in session history, not for new page loads.
- this._restoreZoom = aHistoryEventName !== "New";
- },
-
- OnHistoryNewEntry: function(aUri) {
- this._updateZoomFromHistoryEvent("New");
- },
-
- OnHistoryGoBack: function(aUri) {
- this._updateZoomFromHistoryEvent("Back");
- return true;
- },
-
- OnHistoryGoForward: function(aUri) {
- this._updateZoomFromHistoryEvent("Forward");
- return true;
- },
-
- OnHistoryReload: function(aUri, aFlags) {
- // we don't do anything with this, so don't propagate it
- // for now anyway
- return true;
- },
-
- OnHistoryGotoIndex: function(aIndex, aUri) {
- this._updateZoomFromHistoryEvent("Goto");
- return true;
- },
-
- OnHistoryPurge: function(aNumEntries) {
- this._updateZoomFromHistoryEvent("Purge");
- return true;
- },
-
- OnHistoryReplaceEntry: function(aIndex) {
- // we don't do anything with this, so don't propogate it
- // for now anyway.
- },
-
- ShouldNotifyMediaPlaybackChange: function(activeState) {
- // If the media is active, we would check it's duration, because we don't
- // want to show the media control interface for the short sound which
- // duration is smaller than the threshold. The basic unit is second.
- // Note : the streaming format's duration is infinite.
- if (activeState === "inactive") {
- return true;
- }
-
- const mediaDurationThreshold = 1.0;
-
- let audioElements = this.browser.contentDocument.getElementsByTagName("audio");
- for each (let audio in audioElements) {
- if (!audio.paused && audio.duration < mediaDurationThreshold) {
- return false;
- }
- }
-
- let videoElements = this.browser.contentDocument.getElementsByTagName("video");
- for each (let video in videoElements) {
- if (!video.paused && video.duration < mediaDurationThreshold) {
- return false;
- }
- }
-
- return true;
- },
-
- observe: function(aSubject, aTopic, aData) {
- switch (aTopic) {
- case "before-first-paint":
- // Is it on the top level?
- let contentDocument = aSubject;
- if (contentDocument == this.browser.contentDocument) {
- if (BrowserApp.selectedTab == this) {
- BrowserApp.contentDocumentChanged();
- }
- this.contentDocumentIsDisplayed = true;
-
- if (contentDocument instanceof Ci.nsIImageDocument) {
- contentDocument.shrinkToFit();
- }
- }
- break;
-
- case "media-playback":
- case "media-playback-resumed":
- if (!aSubject) {
- return;
- }
-
- let winId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
- if (this.browser.outerWindowID != winId) {
- return;
- }
-
- if (!this.ShouldNotifyMediaPlaybackChange(aData)) {
- return;
- }
-
- let status;
- if (aTopic == "media-playback") {
- status = (aData === "inactive") ? "end" : "start";
- } else if (aTopic == "media-playback-resumed") {
- status = "resume";
- }
-
- Messaging.sendRequest({
- type: "Tab:MediaPlaybackChange",
- tabID: this.id,
- status: status
- });
- break;
- }
- },
-
- // nsIBrowserTab
- get window() {
- if (!this.browser)
- return null;
- return this.browser.contentWindow;
- },
-
- get scale() {
- return this._zoom;
- },
-
- QueryInterface: XPCOMUtils.generateQI([
- Ci.nsIWebProgressListener,
- Ci.nsISHistoryListener,
- Ci.nsIObserver,
- Ci.nsISupportsWeakReference,
- Ci.nsIBrowserTab
- ])
-};
-
-var BrowserEventHandler = {
- init: function init() {
- this._clickInZoomedView = false;
- Services.obs.addObserver(this, "Gesture:SingleTap", false);
- Services.obs.addObserver(this, "Gesture:ClickInZoomedView", false);
-
- BrowserApp.deck.addEventListener("touchend", this, true);
-
- BrowserApp.deck.addEventListener("DOMUpdatePageReport", PopupBlockerObserver.onUpdatePageReport, false);
- BrowserApp.deck.addEventListener("MozMouseHittest", this, true);
- BrowserApp.deck.addEventListener("OpenMediaWithExternalApp", this, true);
-
- InitLater(() => BrowserApp.deck.addEventListener("click", InputWidgetHelper, true));
- InitLater(() => BrowserApp.deck.addEventListener("click", SelectHelper, true));
-
- // ReaderViews support backPress listeners.
- Messaging.addListener(() => {
- return Reader.onBackPress(BrowserApp.selectedTab.id);
- }, "Browser:OnBackPressed");
- },
-
- handleEvent: function(aEvent) {
- switch (aEvent.type) {
- case 'touchend':
- if (this._inCluster) {
- aEvent.preventDefault();
- }
- break;
- case 'MozMouseHittest':
- this._handleRetargetedTouchStart(aEvent);
- break;
- case 'OpenMediaWithExternalApp': {
- let mediaSrc = aEvent.target.currentSrc || aEvent.target.src;
- let uuid = uuidgen.generateUUID().toString();
- Services.androidBridge.handleGeckoMessage({
- type: "Video:Play",
- uri: mediaSrc,
- uuid: uuid
- });
- break;
- }
- }
- },
-
- _handleRetargetedTouchStart: function(aEvent) {
- // we should only get this called just after a new touchstart with a single
- // touch point.
- if (!BrowserApp.isBrowserContentDocumentDisplayed() || aEvent.defaultPrevented) {
- return;
- }
-
- let target = aEvent.target;
- if (!target) {
- return;
- }
-
- this._inCluster = aEvent.hitCluster;
- if (this._inCluster) {
- return; // No highlight for a cluster of links
- }
-
- let uri = this._getLinkURI(target);
- if (uri) {
- try {
- Services.io.QueryInterface(Ci.nsISpeculativeConnect).speculativeConnect(uri, null);
- } catch (e) {}
- }
- this._doTapHighlight(target);
- },
-
- _getLinkURI: function(aElement) {
- if (aElement.nodeType == Ci.nsIDOMNode.ELEMENT_NODE &&
- ((aElement instanceof Ci.nsIDOMHTMLAnchorElement && aElement.href) ||
- (aElement instanceof Ci.nsIDOMHTMLAreaElement && aElement.href))) {
- try {
- return Services.io.newURI(aElement.href, null, null);
- } catch (e) {}
- }
- return null;
- },
-
- observe: function(aSubject, aTopic, aData) {
- // the remaining events are all dependent on the browser content document being the
- // same as the browser displayed document. if they are not the same, we should ignore
- // the event.
- if (BrowserApp.isBrowserContentDocumentDisplayed()) {
- this.handleUserEvent(aTopic, aData);
- }
- },
-
- handleUserEvent: function(aTopic, aData) {
- switch (aTopic) {
-
- case "Gesture:ClickInZoomedView":
- this._clickInZoomedView = true;
- break;
-
- case "Gesture:SingleTap": {
- let focusedElement = BrowserApp.getFocusedInput(BrowserApp.selectedBrowser);
- let data = JSON.parse(aData);
- let {x, y} = data;
-
- if (this._inCluster && this._clickInZoomedView != true) {
- // If there is a focused element, the display of the zoomed view won't remove the focus.
- // In this case, the form assistant linked to the focused element will never be closed.
- // To avoid this situation, the focus is moved and the form assistant is closed.
- if (focusedElement) {
- try {
- Services.focus.moveFocus(BrowserApp.selectedBrowser.contentWindow, null, Services.focus.MOVEFOCUS_ROOT, 0);
- } catch(e) {
- Cu.reportError(e);
- }
- Messaging.sendRequest({ type: "FormAssist:Hide" });
- }
- this._clusterClicked(x, y);
- } else {
- if (this._clickInZoomedView != true) {
- this._closeZoomedView();
- }
- }
- this._clickInZoomedView = false;
- this._cancelTapHighlight();
- break;
- }
-
- default:
- dump('BrowserEventHandler.handleUserEvent: unexpected topic "' + aTopic + '"');
- break;
- }
- },
-
- _closeZoomedView: function() {
- Messaging.sendRequest({
- type: "Gesture:CloseZoomedView"
- });
- },
-
- _clusterClicked: function(aX, aY) {
- Messaging.sendRequest({
- type: "Gesture:clusteredLinksClicked",
- clickPosition: {
- x: aX,
- y: aY
- }
- });
- },
-
- _highlightElement: null,
-
- _doTapHighlight: function _doTapHighlight(aElement) {
- this._highlightElement = aElement;
- },
-
- _cancelTapHighlight: function _cancelTapHighlight() {
- if (!this._highlightElement)
- return;
-
- this._highlightElement = null;
- }
-};
-
-const ElementTouchHelper = {
- getBoundingContentRect: function(aElement) {
- if (!aElement)
- return {x: 0, y: 0, w: 0, h: 0};
-
- let document = aElement.ownerDocument;
- while (document.defaultView.frameElement)
- document = document.defaultView.frameElement.ownerDocument;
-
- let cwu = document.defaultView.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
- let scrollX = {}, scrollY = {};
- cwu.getScrollXY(false, scrollX, scrollY);
-
- let r = aElement.getBoundingClientRect();
-
- // step out of iframes and frames, offsetting scroll values
- for (let frame = aElement.ownerDocument.defaultView; frame.frameElement && frame != content; frame = frame.parent) {
- // adjust client coordinates' origin to be top left of iframe viewport
- let rect = frame.frameElement.getBoundingClientRect();
- let left = frame.getComputedStyle(frame.frameElement, "").borderLeftWidth;
- let top = frame.getComputedStyle(frame.frameElement, "").borderTopWidth;
- scrollX.value += rect.left + parseInt(left);
- scrollY.value += rect.top + parseInt(top);
- }
-
- return {x: r.left + scrollX.value,
- y: r.top + scrollY.value,
- w: r.width,
- h: r.height };
- }
-};
-
-var ErrorPageEventHandler = {
- handleEvent: function(aEvent) {
- switch (aEvent.type) {
- case "click": {
- // Don't trust synthetic events
- if (!aEvent.isTrusted)
- return;
-
- let target = aEvent.originalTarget;
- let errorDoc = target.ownerDocument;
-
- // If the event came from an ssl error page, it is probably either the "Add
- // Exception…" or "Get me out of here!" button
- if (errorDoc.documentURI.startsWith("about:certerror?e=nssBadCert")) {
- let perm = errorDoc.getElementById("permanentExceptionButton");
- let temp = errorDoc.getElementById("temporaryExceptionButton");
- if (target == temp || target == perm) {
- // Handle setting an cert exception and reloading the page
- try {
- // Add a new SSL exception for this URL
- let uri = Services.io.newURI(errorDoc.location.href, null, null);
- let sslExceptions = new SSLExceptions();
-
- if (target == perm)
- sslExceptions.addPermanentException(uri, errorDoc.defaultView);
- else
- sslExceptions.addTemporaryException(uri, errorDoc.defaultView);
- } catch (e) {
- dump("Failed to set cert exception: " + e + "\n");
- }
- errorDoc.location.reload();
- } else if (target == errorDoc.getElementById("getMeOutOfHereButton")) {
- errorDoc.location = "about:home";
- }
- } else if (errorDoc.documentURI.startsWith("about:blocked")) {
- // The event came from a button on a malware/phishing block page
- // First check whether it's malware, phishing or unwanted, so that we
- // can use the right strings/links
- let isIframe = (errorDoc.defaultView.parent === errorDoc.defaultView);
- let formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].getService(Ci.nsIURLFormatter);
-
- if (target == errorDoc.getElementById("getMeOutButton")) {
- errorDoc.location = "about:home";
- } else if (target == errorDoc.getElementById("reportButton")) {
- // This is the "Why is this site blocked" button. We redirect
- // to the generic page describing phishing/malware protection.
- let url = Services.urlFormatter.formatURLPref("app.support.baseURL");
- BrowserApp.selectedBrowser.loadURI(url + "phishing-malware");
- } else if (target == errorDoc.getElementById("ignoreWarningButton") &&
- Services.prefs.getBoolPref("browser.safebrowsing.allowOverride")) {
- // Allow users to override and continue through to the site,
- let webNav = BrowserApp.selectedBrowser.docShell.QueryInterface(Ci.nsIWebNavigation);
- let location = BrowserApp.selectedBrowser.contentWindow.location;
- webNav.loadURI(location, Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CLASSIFIER, null, null, null);
-
- // ....but add a notify bar as a reminder, so that they don't lose
- // track after, e.g., tab switching.
- NativeWindow.doorhanger.show(Strings.browser.GetStringFromName("safeBrowsingDoorhanger"), "safebrowsing-warning", [], BrowserApp.selectedTab.id);
- }
- }
- break;
- }
- }
- }
-};
-
-var FormAssistant = {
- QueryInterface: XPCOMUtils.generateQI([Ci.nsIFormSubmitObserver]),
-
- // Used to keep track of the element that corresponds to the current
- // autocomplete suggestions
- _currentInputElement: null,
-
- // The value of the currently focused input
- _currentInputValue: null,
-
- // Whether we're in the middle of an autocomplete
- _doingAutocomplete: false,
-
- // Keep track of whether or not an invalid form has been submitted
- _invalidSubmit: false,
-
- init: function() {
- Services.obs.addObserver(this, "FormAssist:AutoComplete", false);
- Services.obs.addObserver(this, "FormAssist:Hidden", false);
- Services.obs.addObserver(this, "FormAssist:Remove", false);
- Services.obs.addObserver(this, "invalidformsubmit", false);
- Services.obs.addObserver(this, "PanZoom:StateChange", false);
-
- // We need to use a capturing listener for focus events
- BrowserApp.deck.addEventListener("focus", this, true);
- BrowserApp.deck.addEventListener("blur", this, true);
- BrowserApp.deck.addEventListener("click", this, true);
- BrowserApp.deck.addEventListener("input", this, false);
- BrowserApp.deck.addEventListener("pageshow", this, false);
- },
-
- observe: function(aSubject, aTopic, aData) {
- switch (aTopic) {
- case "PanZoom:StateChange":
- // If the user is just touching the screen and we haven't entered a pan or zoom state yet do nothing
- if (aData == "TOUCHING" || aData == "WAITING_LISTENERS")
- break;
- if (aData == "NOTHING") {
- // only look for input elements, not contentEditable or multiline text areas
- let focused = BrowserApp.getFocusedInput(BrowserApp.selectedBrowser, true);
- if (!focused)
- break;
-
- if (this._showValidationMessage(focused))
- break;
- let checkResultsClick = hasResults => {
- if (!hasResults) {
- this._hideFormAssistPopup();
- }
- };
- this._showAutoCompleteSuggestions(focused, checkResultsClick);
- } else {
- // temporarily hide the form assist popup while we're panning or zooming the page
- this._hideFormAssistPopup();
- }
- break;
- case "FormAssist:AutoComplete":
- if (!this._currentInputElement)
- break;
-
- let editableElement = this._currentInputElement.QueryInterface(Ci.nsIDOMNSEditableElement);
-
- this._doingAutocomplete = true;
-
- // If we have an active composition string, commit it before sending
- // the autocomplete event with the text that will replace it.
- try {
- let imeEditor = editableElement.editor.QueryInterface(Ci.nsIEditorIMESupport);
- if (imeEditor.composing)
- imeEditor.forceCompositionEnd();
- } catch (e) {}
-
- editableElement.setUserInput(aData);
- this._currentInputValue = aData;
-
- let event = this._currentInputElement.ownerDocument.createEvent("Events");
- event.initEvent("DOMAutoComplete", true, true);
- this._currentInputElement.dispatchEvent(event);
-
- this._doingAutocomplete = false;
-
- break;
-
- case "FormAssist:Hidden":
- this._currentInputElement = null;
- break;
-
- case "FormAssist:Remove":
- if (!this._currentInputElement) {
- break;
- }
-
- FormHistory.update({
- op: "remove",
- fieldname: this._currentInputElement.name,
- value: aData
- });
- break;
- }
- },
-
- notifyInvalidSubmit: function notifyInvalidSubmit(aFormElement, aInvalidElements) {
- if (!aInvalidElements.length)
- return;
-
- // Ignore this notificaiton if the current tab doesn't contain the invalid element
- let currentElement = aInvalidElements.queryElementAt(0, Ci.nsISupports);
- if (BrowserApp.selectedBrowser.contentDocument !=
- currentElement.ownerDocument.defaultView.top.document)
- return;
-
- this._invalidSubmit = true;
-
- // Our focus listener will show the element's validation message
- currentElement.focus();
- },
-
- handleEvent: function(aEvent) {
- switch (aEvent.type) {
- case "focus": {
- let currentElement = aEvent.target;
-
- // Only show a validation message on focus.
- this._showValidationMessage(currentElement);
- break;
- }
-
- case "blur": {
- this._currentInputValue = null;
- break;
- }
-
- case "click": {
- let currentElement = aEvent.target;
-
- // Prioritize a form validation message over autocomplete suggestions
- // when the element is first focused (a form validation message will
- // only be available if an invalid form was submitted)
- if (this._showValidationMessage(currentElement))
- break;
-
- let checkResultsClick = hasResults => {
- if (!hasResults) {
- this._hideFormAssistPopup();
- }
- };
-
- this._showAutoCompleteSuggestions(currentElement, checkResultsClick);
- break;
- }
-
- case "input": {
- let currentElement = aEvent.target;
-
- // If this element isn't focused, we're already in middle of an
- // autocomplete, or its value hasn't changed, don't show the
- // autocomplete popup.
- if (currentElement !== BrowserApp.getFocusedInput(BrowserApp.selectedBrowser) ||
- this._doingAutocomplete ||
- currentElement.value === this._currentInputValue) {
- break;
- }
-
- this._currentInputValue = currentElement.value;
-
- // Since we can only show one popup at a time, prioritze autocomplete
- // suggestions over a form validation message
- let checkResultsInput = hasResults => {
- if (hasResults)
- return;
-
- if (this._showValidationMessage(currentElement))
- return;
-
- // If we're not showing autocomplete suggestions, hide the form assist popup
- this._hideFormAssistPopup();
- };
-
- this._showAutoCompleteSuggestions(currentElement, checkResultsInput);
- break;
- }
-
- // Reset invalid submit state on each pageshow
- case "pageshow": {
- if (!this._invalidSubmit)
- return;
-
- let selectedBrowser = BrowserApp.selectedBrowser;
- if (selectedBrowser) {
- let selectedDocument = selectedBrowser.contentDocument;
- let target = aEvent.originalTarget;
- if (target == selectedDocument || target.ownerDocument == selectedDocument)
- this._invalidSubmit = false;
- }
- break;
- }
- }
- },
-
- // We only want to show autocomplete suggestions for certain elements
- _isAutoComplete: function _isAutoComplete(aElement) {
- if (!(aElement instanceof HTMLInputElement) || aElement.readOnly || aElement.disabled ||
- (aElement.getAttribute("type") == "password") ||
- (aElement.hasAttribute("autocomplete") &&
- aElement.getAttribute("autocomplete").toLowerCase() == "off"))
- return false;
-
- return true;
- },
-
- // Retrieves autocomplete suggestions for an element from the form autocomplete service.
- // aCallback(array_of_suggestions) is called when results are available.
- _getAutoCompleteSuggestions: function _getAutoCompleteSuggestions(aSearchString, aElement, aCallback) {
- // Cache the form autocomplete service for future use
- if (!this._formAutoCompleteService) {
- this._formAutoCompleteService = Cc["@mozilla.org/satchel/form-autocomplete;1"]
- .getService(Ci.nsIFormAutoComplete);
- }
-
- let resultsAvailable = function (results) {
- let suggestions = [];
- for (let i = 0; i < results.matchCount; i++) {
- let value = results.getValueAt(i);
-
- // Do not show the value if it is the current one in the input field
- if (value == aSearchString)
- continue;
-
- // Supply a label and value, since they can differ for datalist suggestions
- suggestions.push({ label: value, value: value });
- }
- aCallback(suggestions);
- };
-
- this._formAutoCompleteService.autoCompleteSearchAsync(aElement.name || aElement.id,
- aSearchString, aElement, null,
- null, resultsAvailable);
- },
-
- /**
- * (Copied from mobile/xul/chrome/content/forms.js)
- * This function is similar to getListSuggestions from
- * components/satchel/src/nsInputListAutoComplete.js but sadly this one is
- * used by the autocomplete.xml binding which is not in used in fennec
- */
- _getListSuggestions: function _getListSuggestions(aElement) {
- if (!(aElement instanceof HTMLInputElement) || !aElement.list)
- return [];
-
- let suggestions = [];
- let filter = !aElement.hasAttribute("mozNoFilter");
- let lowerFieldValue = aElement.value.toLowerCase();
-
- let options = aElement.list.options;
- let length = options.length;
- for (let i = 0; i < length; i++) {
- let item = options.item(i);
-
- let label = item.value;
- if (item.label)
- label = item.label;
- else if (item.text)
- label = item.text;
-
- if (filter && !(label.toLowerCase().includes(lowerFieldValue)) )
- continue;
- suggestions.push({ label: label, value: item.value });
- }
-
- return suggestions;
- },
-
- // Retrieves autocomplete suggestions for an element from the form autocomplete service
- // and sends the suggestions to the Java UI, along with element position data. As
- // autocomplete queries are asynchronous, calls aCallback when done with a true
- // argument if results were found and false if no results were found.
- _showAutoCompleteSuggestions: function _showAutoCompleteSuggestions(aElement, aCallback) {
- if (!this._isAutoComplete(aElement)) {
- aCallback(false);
- return;
- }
- if (this._isDisabledElement(aElement)) {
- aCallback(false);
- return;
- }
-
- let isEmpty = (aElement.value.length === 0);
-
- let resultsAvailable = autoCompleteSuggestions => {
- // On desktop, we show datalist suggestions below autocomplete suggestions,
- // without duplicates removed.
- let listSuggestions = this._getListSuggestions(aElement);
- let suggestions = autoCompleteSuggestions.concat(listSuggestions);
-
- // Return false if there are no suggestions to show
- if (!suggestions.length) {
- aCallback(false);
- return;
- }
-
- Messaging.sendRequest({
- type: "FormAssist:AutoComplete",
- suggestions: suggestions,
- rect: ElementTouchHelper.getBoundingContentRect(aElement),
- isEmpty: isEmpty,
- });
-
- // Keep track of input element so we can fill it in if the user
- // selects an autocomplete suggestion
- this._currentInputElement = aElement;
- aCallback(true);
- };
-
- this._getAutoCompleteSuggestions(aElement.value, aElement, resultsAvailable);
- },
-
- // Only show a validation message if the user submitted an invalid form,
- // there's a non-empty message string, and the element is the correct type
- _isValidateable: function _isValidateable(aElement) {
- if (!this._invalidSubmit ||
- !aElement.validationMessage ||
- !(aElement instanceof HTMLInputElement ||
- aElement instanceof HTMLTextAreaElement ||
- aElement instanceof HTMLSelectElement ||
- aElement instanceof HTMLButtonElement))
- return false;
-
- return true;
- },
-
- // Sends a validation message and position data for an element to the Java UI.
- // Returns true if there's a validation message to show, false otherwise.
- _showValidationMessage: function _sendValidationMessage(aElement) {
- if (!this._isValidateable(aElement))
- return false;
-
- Messaging.sendRequest({
- type: "FormAssist:ValidationMessage",
- validationMessage: aElement.validationMessage,
- rect: ElementTouchHelper.getBoundingContentRect(aElement)
- });
-
- return true;
- },
-
- _hideFormAssistPopup: function _hideFormAssistPopup() {
- Messaging.sendRequest({ type: "FormAssist:Hide" });
- },
-
- _isDisabledElement : function(aElement) {
- let currentElement = aElement;
- while (currentElement) {
- if(currentElement.disabled)
- return true;
-
- currentElement = currentElement.parentElement;
- }
- return false;
- }
-};
-
-var XPInstallObserver = {
- init: function() {
- Services.obs.addObserver(this, "addon-install-origin-blocked", false);
- Services.obs.addObserver(this, "addon-install-disabled", false);
- Services.obs.addObserver(this, "addon-install-blocked", false);
- Services.obs.addObserver(this, "addon-install-started", false);
- Services.obs.addObserver(this, "xpi-signature-changed", false);
- Services.obs.addObserver(this, "browser-delayed-startup-finished", false);
-
- AddonManager.addInstallListener(this);
- },
-
- observe: function(aSubject, aTopic, aData) {
- let installInfo, tab, host;
- if (aSubject && aSubject instanceof Ci.amIWebInstallInfo) {
- installInfo = aSubject;
- tab = BrowserApp.getTabForBrowser(installInfo.browser);
- if (installInfo.originatingURI) {
- host = installInfo.originatingURI.host;
- }
- }
-
- let strings = Strings.browser;
- let brandShortName = Strings.brand.GetStringFromName("brandShortName");
-
- switch (aTopic) {
- case "addon-install-started":
- Snackbars.show(strings.GetStringFromName("alertAddonsDownloading"), Snackbars.LENGTH_LONG);
- break;
- case "addon-install-disabled": {
- if (!tab)
- return;
-
- let enabled = true;
- try {
- enabled = Services.prefs.getBoolPref("xpinstall.enabled");
- } catch (e) {}
-
- let buttons, message, callback;
- if (!enabled) {
- message = strings.GetStringFromName("xpinstallDisabledMessageLocked");
- buttons = [strings.GetStringFromName("unsignedAddonsDisabled.dismiss")];
- callback: (data) => {};
- } else {
- message = strings.formatStringFromName("xpinstallDisabledMessage2", [brandShortName, host], 2);
- buttons = [
- strings.GetStringFromName("xpinstallDisabledButton"),
- strings.GetStringFromName("unsignedAddonsDisabled.dismiss")
- ];
- callback: (data) => {
- if (data.button === 1) {
- Services.prefs.setBoolPref("xpinstall.enabled", true)
- }
- };
- }
-
- new Prompt({
- title: Strings.browser.GetStringFromName("addonError.titleError"),
- message: message,
- buttons: buttons
- }).show(callback);
- break;
- }
- case "addon-install-blocked": {
- if (!tab)
- return;
-
- let message;
- if (host) {
- // We have a host which asked for the install.
- message = strings.formatStringFromName("xpinstallPromptWarning2", [brandShortName, host], 2);
- } else {
- // Without a host we address the add-on as the initiator of the install.
- let addon = null;
- if (installInfo.installs.length > 0) {
- addon = installInfo.installs[0].name;
- }
- if (addon) {
- // We have an addon name, show the regular message.
- message = strings.formatStringFromName("xpinstallPromptWarningLocal", [brandShortName, addon], 2);
- } else {
- // We don't have an addon name, show an alternative message.
- message = strings.formatStringFromName("xpinstallPromptWarningDirect", [brandShortName], 1);
- }
- }
-
- let buttons = [
- strings.GetStringFromName("xpinstallPromptAllowButton"),
- strings.GetStringFromName("unsignedAddonsDisabled.dismiss")
- ];
- new Prompt({
- title: Strings.browser.GetStringFromName("addonError.titleBlocked"),
- message: message,
- buttons: buttons
- }).show((data) => {
- if (data.button === 0) {
- // Kick off the install
- installInfo.install();
- }
- });
- break;
- }
- case "addon-install-origin-blocked": {
- if (!tab)
- return;
-
- new Prompt({
- title: Strings.browser.GetStringFromName("addonError.titleBlocked"),
- message: strings.formatStringFromName("xpinstallPromptWarningDirect", [brandShortName], 1),
- buttons: [strings.GetStringFromName("unsignedAddonsDisabled.dismiss")]
- }).show((data) => {});
- break;
- }
- case "xpi-signature-changed": {
- if (JSON.parse(aData).disabled.length) {
- this._notifyUnsignedAddonsDisabled();
- }
- break;
- }
- case "browser-delayed-startup-finished": {
- let disabledAddons = AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_DISABLED);
- for (let id of disabledAddons) {
- if (AddonManager.getAddonByID(id).signedState <= AddonManager.SIGNEDSTATE_MISSING) {
- this._notifyUnsignedAddonsDisabled();
- break;
- }
- }
- break;
- }
- }
- },
-
- _notifyUnsignedAddonsDisabled: function() {
- new Prompt({
- window: window,
- title: Strings.browser.GetStringFromName("unsignedAddonsDisabled.title"),
- message: Strings.browser.GetStringFromName("unsignedAddonsDisabled.message"),
- buttons: [
- Strings.browser.GetStringFromName("unsignedAddonsDisabled.viewAddons"),
- Strings.browser.GetStringFromName("unsignedAddonsDisabled.dismiss")
- ]
- }).show((data) => {
- if (data.button === 0) {
- // TODO: Open about:addons to show only unsigned add-ons?
- BrowserApp.selectOrAddTab("about:addons", { parentId: BrowserApp.selectedTab.id });
- }
- });
- },
-
- onInstallEnded: function(aInstall, aAddon) {
- // Don't create a notification for distribution add-ons.
- if (Distribution.pendingAddonInstalls.has(aInstall)) {
- Distribution.pendingAddonInstalls.delete(aInstall);
- return;
- }
-
- let needsRestart = false;
- if (aInstall.existingAddon && (aInstall.existingAddon.pendingOperations & AddonManager.PENDING_UPGRADE))
- needsRestart = true;
- else if (aAddon.pendingOperations & AddonManager.PENDING_INSTALL)
- needsRestart = true;
-
- if (needsRestart) {
- this.showRestartPrompt();
- } else {
- // Display completion message for new installs or updates not done Automatically
- if (!aInstall.existingAddon || !AddonManager.shouldAutoUpdate(aInstall.existingAddon)) {
- let message = Strings.browser.GetStringFromName("alertAddonsInstalledNoRestart.message");
- Snackbars.show(message, Snackbars.LENGTH_LONG, {
- action: {
- label: Strings.browser.GetStringFromName("alertAddonsInstalledNoRestart.action2"),
- callback: () => {
- UITelemetry.addEvent("show.1", "toast", null, "addons");
- BrowserApp.selectOrAddTab("about:addons", { parentId: BrowserApp.selectedTab.id });
- },
- }
- });
- }
- }
- },
-
- onInstallFailed: function(aInstall) {
- this._showErrorMessage(aInstall);
- },
-
- onDownloadProgress: function(aInstall) {},
-
- onDownloadFailed: function(aInstall) {
- this._showErrorMessage(aInstall);
- },
-
- onDownloadCancelled: function(aInstall) {},
-
- _showErrorMessage: function(aInstall) {
- // Don't create a notification for distribution add-ons.
- if (Distribution.pendingAddonInstalls.has(aInstall)) {
- Cu.reportError("Error installing distribution add-on: " + aInstall.addon.id);
- Distribution.pendingAddonInstalls.delete(aInstall);
- return;
- }
-
- let host = (aInstall.originatingURI instanceof Ci.nsIStandardURL) && aInstall.originatingURI.host;
- if (!host) {
- host = (aInstall.sourceURI instanceof Ci.nsIStandardURL) && aInstall.sourceURI.host;
- }
-
- let error = (host || aInstall.error == 0) ? "addonError" : "addonLocalError";
- if (aInstall.error < 0) {
- error += aInstall.error;
- } else if (aInstall.addon && aInstall.addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED) {
- error += "Blocklisted";
- } else {
- error += "Incompatible";
- }
-
- let msg = Strings.browser.GetStringFromName(error);
- // TODO: formatStringFromName
- msg = msg.replace("#1", aInstall.name);
- if (host) {
- msg = msg.replace("#2", host);
- }
- msg = msg.replace("#3", Strings.brand.GetStringFromName("brandShortName"));
- msg = msg.replace("#4", Services.appinfo.version);
-
- if (aInstall.error == AddonManager.ERROR_SIGNEDSTATE_REQUIRED) {
- new Prompt({
- window: window,
- title: Strings.browser.GetStringFromName("addonError.titleBlocked"),
- message: msg,
- buttons: [Strings.browser.GetStringFromName("addonError.learnMore")]
- }).show((data) => {
- if (data.button === 0) {
- let url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "unsigned-addons";
- BrowserApp.addTab(url, { parentId: BrowserApp.selectedTab.id });
- }
- });
- } else {
- Services.prompt.alert(null, Strings.browser.GetStringFromName("addonError.titleError"), msg);
- }
- },
-
- showRestartPrompt: function() {
- let buttons = [{
- label: Strings.browser.GetStringFromName("notificationRestart.button"),
- callback: function() {
- // Notify all windows that an application quit has been requested
- let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool);
- Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart");
-
- // If nothing aborted, quit the app
- if (cancelQuit.data == false) {
- Services.obs.notifyObservers(null, "quit-application-proceeding", null);
- let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"].getService(Ci.nsIAppStartup);
- appStartup.quit(Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eAttemptQuit);
- }
- },
- positive: true
- }];
-
- let message = Strings.browser.GetStringFromName("notificationRestart.normal");
- NativeWindow.doorhanger.show(message, "addon-app-restart", buttons, BrowserApp.selectedTab.id, { persistence: -1 });
- },
-
- hideRestartPrompt: function() {
- NativeWindow.doorhanger.hide("addon-app-restart", BrowserApp.selectedTab.id);
- }
-};
-
-var ViewportHandler = {
- init: function init() {
- Services.obs.addObserver(this, "Window:Resize", false);
- },
-
- observe: function(aSubject, aTopic, aData) {
- if (aTopic == "Window:Resize" && aData) {
- let scrollChange = JSON.parse(aData);
- let windowUtils = window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
- windowUtils.setNextPaintSyncId(scrollChange.id);
- }
- }
-};
-
-/**
- * Handler for blocked popups, triggered by DOMUpdatePageReport events in browser.xml
- */
-var PopupBlockerObserver = {
- onUpdatePageReport: function onUpdatePageReport(aEvent) {
- let browser = BrowserApp.selectedBrowser;
- if (aEvent.originalTarget != browser)
- return;
-
- if (!browser.pageReport)
- return;
-
- let result = Services.perms.testExactPermission(BrowserApp.selectedBrowser.currentURI, "popup");
- if (result == Ci.nsIPermissionManager.DENY_ACTION)
- return;
-
- // Only show the notification again if we've not already shown it. Since
- // notifications are per-browser, we don't need to worry about re-adding
- // it.
- if (!browser.pageReport.reported) {
- if (Services.prefs.getBoolPref("privacy.popups.showBrowserMessage")) {
- let brandShortName = Strings.brand.GetStringFromName("brandShortName");
- let popupCount = browser.pageReport.length;
-
- let strings = Strings.browser;
- let message = PluralForm.get(popupCount, strings.GetStringFromName("popup.message"))
- .replace("#1", brandShortName)
- .replace("#2", popupCount);
-
- let buttons = [
- {
- label: strings.GetStringFromName("popup.dontShow"),
- callback: function(aChecked) {
- if (aChecked)
- PopupBlockerObserver.allowPopupsForSite(false);
- }
- },
- {
- label: strings.GetStringFromName("popup.show"),
- callback: function(aChecked) {
- // Set permission before opening popup windows
- if (aChecked)
- PopupBlockerObserver.allowPopupsForSite(true);
-
- PopupBlockerObserver.showPopupsForSite();
- },
- positive: true
- }
- ];
-
- let options = { checkbox: Strings.browser.GetStringFromName("popup.dontAskAgain") };
- NativeWindow.doorhanger.show(message, "popup-blocked", buttons, null, options);
- }
- // Record the fact that we've reported this blocked popup, so we don't
- // show it again.
- browser.pageReport.reported = true;
- }
- },
-
- allowPopupsForSite: function allowPopupsForSite(aAllow) {
- let currentURI = BrowserApp.selectedBrowser.currentURI;
- Services.perms.add(currentURI, "popup", aAllow
- ? Ci.nsIPermissionManager.ALLOW_ACTION
- : Ci.nsIPermissionManager.DENY_ACTION);
- dump("Allowing popups for: " + currentURI);
- },
-
- showPopupsForSite: function showPopupsForSite() {
- let uri = BrowserApp.selectedBrowser.currentURI;
- let pageReport = BrowserApp.selectedBrowser.pageReport;
- if (pageReport) {
- for (let i = 0; i < pageReport.length; ++i) {
- let popupURIspec = pageReport[i].popupWindowURIspec;
-
- // Sometimes the popup URI that we get back from the pageReport
- // isn't useful (for instance, netscape.com's popup URI ends up
- // being "http://www.netscape.com", which isn't really the URI of
- // the popup they're trying to show). This isn't going to be
- // useful to the user, so we won't create a menu item for it.
- if (popupURIspec == "" || popupURIspec == "about:blank" || popupURIspec == uri.spec)
- continue;
-
- let popupFeatures = pageReport[i].popupWindowFeatures;
- let popupName = pageReport[i].popupWindowName;
-
- let parent = BrowserApp.selectedTab;
- let isPrivate = PrivateBrowsingUtils.isBrowserPrivate(parent.browser);
- BrowserApp.addTab(popupURIspec, { parentId: parent.id, isPrivate: isPrivate });
- }
- }
- }
-};
-
-
-var IndexedDB = {
- _permissionsPrompt: "indexedDB-permissions-prompt",
- _permissionsResponse: "indexedDB-permissions-response",
-
- init: function IndexedDB_init() {
- Services.obs.addObserver(this, this._permissionsPrompt, false);
- },
-
- observe: function IndexedDB_observe(subject, topic, data) {
- if (topic != this._permissionsPrompt) {
- throw new Error("Unexpected topic!");
- }
-
- let requestor = subject.QueryInterface(Ci.nsIInterfaceRequestor);
-
- let browser = requestor.getInterface(Ci.nsIDOMNode);
- let tab = BrowserApp.getTabForBrowser(browser);
- if (!tab)
- return;
-
- let host = browser.currentURI.asciiHost;
-
- let strings = Strings.browser;
-
- let message, responseTopic;
- if (topic == this._permissionsPrompt) {
- message = strings.formatStringFromName("offlineApps.ask", [host], 1);
- responseTopic = this._permissionsResponse;
- }
-
- const firstTimeoutDuration = 300000; // 5 minutes
-
- let timeoutId;
-
- let notificationID = responseTopic + host;
- let observer = requestor.getInterface(Ci.nsIObserver);
-
- // This will be set to the result of PopupNotifications.show() below, or to
- // the result of PopupNotifications.getNotification() if this is a
- // quotaCancel notification.
- let notification;
-
- function timeoutNotification() {
- // Remove the notification.
- NativeWindow.doorhanger.hide(notificationID, tab.id);
-
- // Clear all of our timeout stuff. We may be called directly, not just
- // when the timeout actually elapses.
- clearTimeout(timeoutId);
-
- // And tell the page that the popup timed out.
- observer.observe(null, responseTopic, Ci.nsIPermissionManager.UNKNOWN_ACTION);
- }
-
- let buttons = [
- {
- label: strings.GetStringFromName("offlineApps.dontAllow2"),
- callback: function(aChecked) {
- clearTimeout(timeoutId);
- let action = aChecked ? Ci.nsIPermissionManager.DENY_ACTION : Ci.nsIPermissionManager.UNKNOWN_ACTION;
- observer.observe(null, responseTopic, action);
- }
- },
- {
- label: strings.GetStringFromName("offlineApps.allow"),
- callback: function() {
- clearTimeout(timeoutId);
- observer.observe(null, responseTopic, Ci.nsIPermissionManager.ALLOW_ACTION);
- },
- positive: true
- }];
-
- let options = { checkbox: Strings.browser.GetStringFromName("offlineApps.dontAskAgain") };
- NativeWindow.doorhanger.show(message, notificationID, buttons, tab.id, options);
-
- // Set the timeoutId after the popup has been created, and use the long
- // timeout value. If the user doesn't notice the popup after this amount of
- // time then it is most likely not visible and we want to alert the page.
- timeoutId = setTimeout(timeoutNotification, firstTimeoutDuration);
- }
-};
-
-var CharacterEncoding = {
- _charsets: [],
-
- init: function init() {
- Services.obs.addObserver(this, "CharEncoding:Get", false);
- Services.obs.addObserver(this, "CharEncoding:Set", false);
- InitLater(() => this.sendState());
- },
-
- observe: function observe(aSubject, aTopic, aData) {
- switch (aTopic) {
- case "CharEncoding:Get":
- this.getEncoding();
- break;
- case "CharEncoding:Set":
- this.setEncoding(aData);
- break;
- }
- },
-
- sendState: function sendState() {
- let showCharEncoding = "false";
- try {
- showCharEncoding = Services.prefs.getComplexValue("browser.menu.showCharacterEncoding", Ci.nsIPrefLocalizedString).data;
- } catch (e) { /* Optional */ }
-
- Messaging.sendRequest({
- type: "CharEncoding:State",
- visible: showCharEncoding
- });
- },
-
- getEncoding: function getEncoding() {
- function infoToCharset(info) {
- return { code: info.value, title: info.label };
- }
-
- if (!this._charsets.length) {
- let data = CharsetMenu.getData();
-
- // In the desktop UI, the pinned charsets are shown above the rest.
- let pinnedCharsets = data.pinnedCharsets.map(infoToCharset);
- let otherCharsets = data.otherCharsets.map(infoToCharset)
-
- this._charsets = pinnedCharsets.concat(otherCharsets);
- }
-
- // Look for the index of the selected charset. Default to -1 if the
- // doc charset isn't found in the list of available charsets.
- let docCharset = BrowserApp.selectedBrowser.contentDocument.characterSet;
- let selected = -1;
- let charsetCount = this._charsets.length;
-
- for (let i = 0; i < charsetCount; i++) {
- if (this._charsets[i].code === docCharset) {
- selected = i;
- break;
- }
- }
-
- Messaging.sendRequest({
- type: "CharEncoding:Data",
- charsets: this._charsets,
- selected: selected
- });
- },
-
- setEncoding: function setEncoding(aEncoding) {
- let browser = BrowserApp.selectedBrowser;
- browser.docShell.gatherCharsetMenuTelemetry();
- browser.docShell.charset = aEncoding;
- browser.reload(Ci.nsIWebNavigation.LOAD_FLAGS_CHARSET_CHANGE);
- }
-};
-
-var IdentityHandler = {
- // No trusted identity information. No site identity icon is shown.
- IDENTITY_MODE_UNKNOWN: "unknown",
-
- // Domain-Validation SSL CA-signed domain verification (DV).
- IDENTITY_MODE_IDENTIFIED: "identified",
-
- // Extended-Validation SSL CA-signed identity information (EV). A more rigorous validation process.
- IDENTITY_MODE_VERIFIED: "verified",
-
- // Part of the product's UI (built in about: pages)
- IDENTITY_MODE_CHROMEUI: "chromeUI",
-
- // The following mixed content modes are only used if "security.mixed_content.block_active_content"
- // is enabled. Our Java frontend coalesces them into one indicator.
-
- // No mixed content information. No mixed content icon is shown.
- MIXED_MODE_UNKNOWN: "unknown",
-
- // Blocked active mixed content.
- MIXED_MODE_CONTENT_BLOCKED: "blocked",
-
- // Loaded active mixed content.
- MIXED_MODE_CONTENT_LOADED: "loaded",
-
- // The following tracking content modes are only used if tracking protection
- // is enabled. Our Java frontend coalesces them into one indicator.
-
- // No tracking content information. No tracking content icon is shown.
- TRACKING_MODE_UNKNOWN: "unknown",
-
- // Blocked active tracking content. Shield icon is shown, with a popup option to load content.
- TRACKING_MODE_CONTENT_BLOCKED: "tracking_content_blocked",
-
- // Loaded active tracking content. Yellow triangle icon is shown.
- TRACKING_MODE_CONTENT_LOADED: "tracking_content_loaded",
-
- // Cache the most recent SSLStatus and Location seen in getIdentityStrings
- _lastStatus : null,
- _lastLocation : null,
-
- /**
- * Helper to parse out the important parts of _lastStatus (of the SSL cert in
- * particular) for use in constructing identity UI strings
- */
- getIdentityData : function() {
- let result = {};
- let status = this._lastStatus.QueryInterface(Components.interfaces.nsISSLStatus);
- let cert = status.serverCert;
-
- // Human readable name of Subject
- result.subjectOrg = cert.organization;
-
- // SubjectName fields, broken up for individual access
- if (cert.subjectName) {
- result.subjectNameFields = {};
- cert.subjectName.split(",").forEach(function(v) {
- let field = v.split("=");
- this[field[0]] = field[1];
- }, result.subjectNameFields);
-
- // Call out city, state, and country specifically
- result.city = result.subjectNameFields.L;
- result.state = result.subjectNameFields.ST;
- result.country = result.subjectNameFields.C;
- }
-
- // Human readable name of Certificate Authority
- result.caOrg = cert.issuerOrganization || cert.issuerCommonName;
- result.cert = cert;
-
- return result;
- },
-
- /**
- * Determines the identity mode corresponding to the icon we show in the urlbar.
- */
- getIdentityMode: function getIdentityMode(aState, uri) {
- if (aState & Ci.nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL) {
- return this.IDENTITY_MODE_VERIFIED;
- }
-
- if (aState & Ci.nsIWebProgressListener.STATE_IS_SECURE) {
- return this.IDENTITY_MODE_IDENTIFIED;
- }
-
- // We also allow "about:" by allowing the selector to be empty (i.e. '(|.....|...|...)'
- let whitelist = /^about:($|about|accounts|addons|buildconfig|cache|config|crashes|devices|downloads|fennec|firefox|feedback|healthreport|home|license|logins|logo|memory|mozilla|networking|plugins|privatebrowsing|rights|serviceworkers|support|telemetry|webrtc)($|\?)/i;
- if (uri.schemeIs("about") && whitelist.test(uri.spec)) {
- return this.IDENTITY_MODE_CHROMEUI;
- }
-
- return this.IDENTITY_MODE_UNKNOWN;
- },
-
- getMixedDisplayMode: function getMixedDisplayMode(aState) {
- if (aState & Ci.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT) {
- return this.MIXED_MODE_CONTENT_LOADED;
- }
-
- if (aState & Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_DISPLAY_CONTENT) {
- return this.MIXED_MODE_CONTENT_BLOCKED;
- }
-
- return this.MIXED_MODE_UNKNOWN;
- },
-
- getMixedActiveMode: function getActiveDisplayMode(aState) {
- // Only show an indicator for loaded mixed content if the pref to block it is enabled
- if ((aState & Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT) &&
- !Services.prefs.getBoolPref("security.mixed_content.block_active_content")) {
- return this.MIXED_MODE_CONTENT_LOADED;
- }
-
- if (aState & Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT) {
- return this.MIXED_MODE_CONTENT_BLOCKED;
- }
-
- return this.MIXED_MODE_UNKNOWN;
- },
-
- getTrackingMode: function getTrackingMode(aState, aBrowser) {
- if (aState & Ci.nsIWebProgressListener.STATE_BLOCKED_TRACKING_CONTENT) {
- this.shieldHistogramAdd(aBrowser, 2);
- return this.TRACKING_MODE_CONTENT_BLOCKED;
- }
-
- // Only show an indicator for loaded tracking content if the pref to block it is enabled
- let tpEnabled = Services.prefs.getBoolPref("privacy.trackingprotection.enabled") ||
- (Services.prefs.getBoolPref("privacy.trackingprotection.pbmode.enabled") &&
- PrivateBrowsingUtils.isBrowserPrivate(aBrowser));
-
- if ((aState & Ci.nsIWebProgressListener.STATE_LOADED_TRACKING_CONTENT) && tpEnabled) {
- this.shieldHistogramAdd(aBrowser, 1);
- return this.TRACKING_MODE_CONTENT_LOADED;
- }
-
- this.shieldHistogramAdd(aBrowser, 0);
- return this.TRACKING_MODE_UNKNOWN;
- },
-
- shieldHistogramAdd: function(browser, value) {
- if (PrivateBrowsingUtils.isBrowserPrivate(browser)) {
- return;
- }
- Telemetry.addData("TRACKING_PROTECTION_SHIELD", value);
- },
-
- /**
- * Determine the identity of the page being displayed by examining its SSL cert
- * (if available). Return the data needed to update the UI.
- */
- checkIdentity: function checkIdentity(aState, aBrowser) {
- this._lastStatus = aBrowser.securityUI
- .QueryInterface(Components.interfaces.nsISSLStatusProvider)
- .SSLStatus;
-
- // Don't pass in the actual location object, since it can cause us to
- // hold on to the window object too long. Just pass in the fields we
- // care about. (bug 424829)
- let locationObj = {};
- try {
- let location = aBrowser.contentWindow.location;
- locationObj.host = location.host;
- locationObj.hostname = location.hostname;
- locationObj.port = location.port;
- locationObj.origin = location.origin;
- } catch (ex) {
- // Can sometimes throw if the URL being visited has no host/hostname,
- // e.g. about:blank. The _state for these pages means we won't need these
- // properties anyways, though.
- }
- this._lastLocation = locationObj;
-
- let uri = aBrowser.currentURI;
- try {
- uri = Services.uriFixup.createExposableURI(uri);
- } catch (e) {}
-
- let identityMode = this.getIdentityMode(aState, uri);
- let mixedDisplay = this.getMixedDisplayMode(aState);
- let mixedActive = this.getMixedActiveMode(aState);
- let trackingMode = this.getTrackingMode(aState, aBrowser);
- let result = {
- origin: locationObj.origin,
- mode: {
- identity: identityMode,
- mixed_display: mixedDisplay,
- mixed_active: mixedActive,
- tracking: trackingMode
- }
- };
-
- // Don't show identity data for pages with an unknown identity or if any
- // mixed content is loaded (mixed display content is loaded by default).
- // We also return for CHROMEUI pages since they don't have any certificate
- // information to load either. result.secure specifically refers to connection
- // security, which is irrelevant for about: pages, as they're loaded locally.
- if (identityMode == this.IDENTITY_MODE_UNKNOWN ||
- identityMode == this.IDENTITY_MODE_CHROMEUI ||
- aState & Ci.nsIWebProgressListener.STATE_IS_BROKEN) {
- result.secure = false;
- return result;
- }
-
- result.secure = true;
-
- result.host = this.getEffectiveHost();
-
- let iData = this.getIdentityData();
- result.verifier = Strings.browser.formatStringFromName("identity.identified.verifier", [iData.caOrg], 1);
-
- // If the cert is identified, then we can populate the results with credentials
- if (aState & Ci.nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL) {
- result.owner = iData.subjectOrg;
-
- // Build an appropriate supplemental block out of whatever location data we have
- let supplemental = "";
- if (iData.city) {
- supplemental += iData.city + "\n";
- }
- if (iData.state && iData.country) {
- supplemental += Strings.browser.formatStringFromName("identity.identified.state_and_country", [iData.state, iData.country], 2);
- result.country = iData.country;
- } else if (iData.state) { // State only
- supplemental += iData.state;
- } else if (iData.country) { // Country only
- supplemental += iData.country;
- result.country = iData.country;
- }
- result.supplemental = supplemental;
-
- return result;
- }
-
- // Cache the override service the first time we need to check it
- if (!this._overrideService)
- this._overrideService = Cc["@mozilla.org/security/certoverride;1"].getService(Ci.nsICertOverrideService);
-
- // Check whether this site is a security exception. XPConnect does the right
- // thing here in terms of converting _lastLocation.port from string to int, but
- // the overrideService doesn't like undefined ports, so make sure we have
- // something in the default case (bug 432241).
- // .hostname can return an empty string in some exceptional cases -
- // hasMatchingOverride does not handle that, so avoid calling it.
- // Updating the tooltip value in those cases isn't critical.
- // FIXME: Fixing bug 646690 would probably makes this check unnecessary
- if (this._lastLocation.hostname &&
- this._overrideService.hasMatchingOverride(this._lastLocation.hostname,
- (this._lastLocation.port || 443),
- iData.cert, {}, {}))
- result.verifier = Strings.browser.GetStringFromName("identity.identified.verified_by_you");
-
- return result;
- },
-
- /**
- * Attempt to provide proper IDN treatment for host names
- */
- getEffectiveHost: function getEffectiveHost() {
- if (!this._IDNService)
- this._IDNService = Cc["@mozilla.org/network/idn-service;1"]
- .getService(Ci.nsIIDNService);
- try {
- return this._IDNService.convertToDisplayIDN(this._uri.host, {});
- } catch (e) {
- // If something goes wrong (e.g. hostname is an IP address) just fail back
- // to the full domain.
- return this._lastLocation.hostname;
- }
- }
-};
-
-var SearchEngines = {
- _contextMenuId: null,
- PREF_SUGGEST_ENABLED: "browser.search.suggest.enabled",
- PREF_SUGGEST_PROMPTED: "browser.search.suggest.prompted",
-
- // Shared preference key used for search activity default engine.
- PREF_SEARCH_ACTIVITY_ENGINE_KEY: "search.engines.defaultname",
-
- init: function init() {
- Services.obs.addObserver(this, "SearchEngines:Add", false);
- Services.obs.addObserver(this, "SearchEngines:GetVisible", false);
- Services.obs.addObserver(this, "SearchEngines:Remove", false);
- Services.obs.addObserver(this, "SearchEngines:RestoreDefaults", false);
- Services.obs.addObserver(this, "SearchEngines:SetDefault", false);
- Services.obs.addObserver(this, "browser-search-engine-modified", false);
- },
-
- // Fetch list of search engines. all ? All engines : Visible engines only.
- _handleSearchEnginesGetVisible: function _handleSearchEnginesGetVisible(rv, all) {
- if (!Components.isSuccessCode(rv)) {
- Cu.reportError("Could not initialize search service, bailing out.");
- return;
- }
-
- let engineData = Services.search.getVisibleEngines({});
-
- // Our Java UI assumes that the default engine is the first item in the array,
- // so we need to make sure that's the case.
- if (engineData[0] !== Services.search.defaultEngine) {
- engineData = engineData.filter(engine => engine !== Services.search.defaultEngine);
- engineData.unshift(Services.search.defaultEngine);
- }
-
- let searchEngines = engineData.map(function (engine) {
- return {
- name: engine.name,
- identifier: engine.identifier,
- iconURI: (engine.iconURI ? engine.iconURI.spec : null),
- hidden: engine.hidden
- };
- });
-
- let suggestTemplate = null;
- let suggestEngine = null;
-
- // Check to see if the default engine supports search suggestions. We only need to check
- // the default engine because we only show suggestions for the default engine in the UI.
- let engine = Services.search.defaultEngine;
- if (engine.supportsResponseType("application/x-suggestions+json")) {
- suggestEngine = engine.name;
- suggestTemplate = engine.getSubmission("__searchTerms__", "application/x-suggestions+json").uri.spec;
- }
-
- // By convention, the currently configured default engine is at position zero in searchEngines.
- Messaging.sendRequest({
- type: "SearchEngines:Data",
- searchEngines: searchEngines,
- suggest: {
- engine: suggestEngine,
- template: suggestTemplate,
- enabled: Services.prefs.getBoolPref(this.PREF_SUGGEST_ENABLED),
- prompted: Services.prefs.getBoolPref(this.PREF_SUGGEST_PROMPTED)
- }
- });
-
- // Send a speculative connection to the default engine.
- Services.search.defaultEngine.speculativeConnect({window: window});
- },
-
- // Helper method to extract the engine name from a JSON. Simplifies the observe function.
- _extractEngineFromJSON: function _extractEngineFromJSON(aData) {
- let data = JSON.parse(aData);
- return Services.search.getEngineByName(data.engine);
- },
-
- observe: function observe(aSubject, aTopic, aData) {
- let engine;
- switch(aTopic) {
- case "SearchEngines:Add":
- this.displaySearchEnginesList(aData);
- break;
- case "SearchEngines:GetVisible":
- Services.search.init(this._handleSearchEnginesGetVisible.bind(this));
- break;
- case "SearchEngines:Remove":
- // Make sure the engine isn't hidden before removing it, to make sure it's
- // visible if the user later re-adds it (works around bug 341833)
- engine = this._extractEngineFromJSON(aData);
- engine.hidden = false;
- Services.search.removeEngine(engine);
- break;
- case "SearchEngines:RestoreDefaults":
- // Un-hides all default engines.
- Services.search.restoreDefaultEngines();
- break;
- case "SearchEngines:SetDefault":
- engine = this._extractEngineFromJSON(aData);
- // Move the new default search engine to the top of the search engine list.
- Services.search.moveEngine(engine, 0);
- Services.search.defaultEngine = engine;
- break;
- case "browser-search-engine-modified":
- if (aData == "engine-default") {
- this._setSearchActivityDefaultPref(aSubject.QueryInterface(Ci.nsISearchEngine));
- }
- break;
- default:
- dump("Unexpected message type observed: " + aTopic);
- break;
- }
- },
-
- migrateSearchActivityDefaultPref: function migrateSearchActivityDefaultPref() {
- Services.search.init(() => this._setSearchActivityDefaultPref(Services.search.defaultEngine));
- },
-
- // Updates the search activity pref when the default engine changes.
- _setSearchActivityDefaultPref: function _setSearchActivityDefaultPref(engine) {
- SharedPreferences.forApp().setCharPref(this.PREF_SEARCH_ACTIVITY_ENGINE_KEY, engine.name);
- },
-
- // Display context menu listing names of the search engines available to be added.
- displaySearchEnginesList: function displaySearchEnginesList(aData) {
- let data = JSON.parse(aData);
- let tab = BrowserApp.getTabForId(data.tabId);
-
- if (!tab)
- return;
-
- let browser = tab.browser;
- let engines = browser.engines;
-
- let p = new Prompt({
- window: browser.contentWindow
- }).setSingleChoiceItems(engines.map(function(e) {
- return { label: e.title };
- })).show((function(data) {
- if (data.button == -1)
- return;
-
- this.addOpenSearchEngine(engines[data.button]);
- engines.splice(data.button, 1);
-
- if (engines.length < 1) {
- // Broadcast message that there are no more add-able search engines.
- let newEngineMessage = {
- type: "Link:OpenSearch",
- tabID: tab.id,
- visible: false
- };
-
- Messaging.sendRequest(newEngineMessage);
- }
- }).bind(this));
- },
-
- addOpenSearchEngine: function addOpenSearchEngine(engine) {
- Services.search.addEngine(engine.url, Ci.nsISearchEngine.DATA_XML, engine.iconURL, false, {
- onSuccess: function() {
- // Display a toast confirming addition of new search engine.
- Snackbars.show(Strings.browser.formatStringFromName("alertSearchEngineAddedToast", [engine.title], 1), Snackbars.LENGTH_LONG);
- },
-
- onError: function(aCode) {
- let errorMessage;
- if (aCode == 2) {
- // Engine is a duplicate.
- errorMessage = "alertSearchEngineDuplicateToast";
-
- } else {
- // Unknown failure. Display general error message.
- errorMessage = "alertSearchEngineErrorToast";
- }
-
- Snackbars.show(Strings.browser.formatStringFromName(errorMessage, [engine.title], 1), Snackbars.LENGTH_LONG);
- }
- });
- },
-
- /**
- * Build and return an array of sorted form data / Query Parameters
- * for an element in a submission form.
- *
- * @param element
- * A valid submission element of a form.
- */
- _getSortedFormData: function(element) {
- let formData = [];
-
- for (let formElement of element.form.elements) {
- if (!formElement.type) {
- continue;
- }
-
- // Make this text field a generic search parameter.
- if (element == formElement) {
- formData.push({ name: formElement.name, value: "{searchTerms}" });
- continue;
- }
-
- // Add other form elements as parameters.
- switch (formElement.type.toLowerCase()) {
- case "checkbox":
- case "radio":
- if (!formElement.checked) {
- break;
- }
- case "text":
- case "hidden":
- case "textarea":
- formData.push({ name: escape(formElement.name), value: escape(formElement.value) });
- break;
-
- case "select-one": {
- for (let option of formElement.options) {
- if (option.selected) {
- formData.push({ name: escape(formElement.name), value: escape(formElement.value) });
- break;
- }
- }
- }
- }
- };
-
- // Return valid, pre-sorted queryParams.
- return formData.filter(a => a.name && a.value).sort((a, b) => {
- // nsIBrowserSearchService.hasEngineWithURL() ensures sort, but this helps.
- if (a.name > b.name) {
- return 1;
- }
- if (b.name > a.name) {
- return -1;
- }
-
- if (a.value > b.value) {
- return 1;
- }
- if (b.value > a.value) {
- return -1;
- }
-
- return 0;
- });
- },
-
- /**
- * Check if any search engines already handle an EngineURL of type
- * URLTYPE_SEARCH_HTML, matching this request-method, formURL, and queryParams.
- */
- visibleEngineExists: function(element) {
- let formData = this._getSortedFormData(element);
-
- let form = element.form;
- let method = form.method.toUpperCase();
-
- let charset = element.ownerDocument.characterSet;
- let docURI = Services.io.newURI(element.ownerDocument.URL, charset, null);
- let formURL = Services.io.newURI(form.getAttribute("action"), charset, docURI).spec;
-
- return Services.search.hasEngineWithURL(method, formURL, formData);
- },
-
- /**
- * Adds a new search engine to the BrowserSearchService, based on its provided element. Prompts for an engine
- * name, and appends a simple version-number in case of collision with an existing name.
- *
- * @return callback to handle success value. Currently used for ActionBarHandler.js and UI updates.
- */
- addEngine: function addEngine(aElement, resultCallback) {
- let form = aElement.form;
- let charset = aElement.ownerDocument.characterSet;
- let docURI = Services.io.newURI(aElement.ownerDocument.URL, charset, null);
- let formURL = Services.io.newURI(form.getAttribute("action"), charset, docURI).spec;
- let method = form.method.toUpperCase();
- let formData = this._getSortedFormData(aElement);
-
- // prompt user for name of search engine
- let promptTitle = Strings.browser.GetStringFromName("contextmenu.addSearchEngine3");
- let title = { value: (aElement.ownerDocument.title || docURI.host) };
- if (!Services.prompt.prompt(null, promptTitle, null, title, null, {})) {
- if (resultCallback) {
- resultCallback(false);
- };
- return;
- }
-
- // fetch the favicon for this page
- let dbFile = FileUtils.getFile("ProfD", ["browser.db"]);
- let mDBConn = Services.storage.openDatabase(dbFile);
- let stmts = [];
- stmts[0] = mDBConn.createStatement("SELECT favicon FROM history_with_favicons WHERE url = ?");
- stmts[0].bindByIndex(0, docURI.spec);
- let favicon = null;
-
- Services.search.init(function addEngine_cb(rv) {
- if (!Components.isSuccessCode(rv)) {
- Cu.reportError("Could not initialize search service, bailing out.");
- if (resultCallback) {
- resultCallback(false);
- };
- return;
- }
-
- mDBConn.executeAsync(stmts, stmts.length, {
- handleResult: function (results) {
- let bytes = results.getNextRow().getResultByName("favicon");
- if (bytes && bytes.length) {
- favicon = "data:image/x-icon;base64," + btoa(String.fromCharCode.apply(null, bytes));
- }
- },
- handleCompletion: function (reason) {
- // if there's already an engine with this name, add a number to
- // make the name unique (e.g., "Google" becomes "Google 2")
- let name = title.value;
- for (let i = 2; Services.search.getEngineByName(name); i++)
- name = title.value + " " + i;
-
- Services.search.addEngineWithDetails(name, favicon, null, null, method, formURL);
- Snackbars.show(Strings.browser.formatStringFromName("alertSearchEngineAddedToast", [name], 1), Snackbars.LENGTH_LONG);
-
- let engine = Services.search.getEngineByName(name);
- engine.wrappedJSObject._queryCharset = charset;
- formData.forEach(param => { engine.addParam(param.name, param.value, null); });
-
- if (resultCallback) {
- return resultCallback(true);
- };
- }
- });
- });
- }
-};
-
-var ActivityObserver = {
- init: function ao_init() {
- Services.obs.addObserver(this, "application-background", false);
- Services.obs.addObserver(this, "application-foreground", false);
- },
-
- observe: function ao_observe(aSubject, aTopic, aData) {
- let isForeground = false;
- let tab = BrowserApp.selectedTab;
-
- UITelemetry.addEvent("show.1", "system", null, aTopic);
-
- switch (aTopic) {
- case "application-background" :
- let doc = (tab ? tab.browser.contentDocument : null);
- if (doc && doc.fullscreenElement) {
- doc.exitFullscreen();
- }
- isForeground = false;
- break;
- case "application-foreground" :
- isForeground = true;
- break;
- }
-
- if (tab && tab.getActive() != isForeground) {
- tab.setActive(isForeground);
- }
- }
-};
-
-var Telemetry = {
- addData: function addData(aHistogramId, aValue) {
- let histogram = Services.telemetry.getHistogramById(aHistogramId);
- histogram.add(aValue);
- },
-};
-
-var Experiments = {
- // Enable malware download protection (bug 936041)
- MALWARE_DOWNLOAD_PROTECTION: "malware-download-protection",
-
- // Try to load pages from disk cache when network is offline (bug 935190)
- OFFLINE_CACHE: "offline-cache",
-
- init() {
- Messaging.sendRequestForResult({
- type: "Experiments:GetActive"
- }).then(experiments => {
- let names = JSON.parse(experiments);
- for (let name of names) {
- switch (name) {
- case this.MALWARE_DOWNLOAD_PROTECTION: {
- // Apply experiment preferences on the default branch. This allows
- // us to avoid migrating user prefs when experiments are enabled/disabled,
- // and it also allows users to override these prefs in about:config.
- let defaults = Services.prefs.getDefaultBranch(null);
- defaults.setBoolPref("browser.safebrowsing.downloads.enabled", true);
- defaults.setBoolPref("browser.safebrowsing.downloads.remote.enabled", true);
- continue;
- }
-
- case this.OFFLINE_CACHE: {
- let defaults = Services.prefs.getDefaultBranch(null);
- defaults.setBoolPref("browser.tabs.useCache", true);
- continue;
- }
- }
- }
- });
- },
-
- setOverride(name, isEnabled) {
- Messaging.sendRequest({
- type: "Experiments:SetOverride",
- name: name,
- isEnabled: isEnabled
- });
- },
-
- clearOverride(name) {
- Messaging.sendRequest({
- type: "Experiments:ClearOverride",
- name: name
- });
- }
-};
-
-var ExternalApps = {
- _contextMenuId: null,
-
- // extend _getLink to pickup html5 media links.
- _getMediaLink: function(aElement) {
- let uri = NativeWindow.contextmenus._getLink(aElement);
- if (uri == null && aElement.nodeType == Ci.nsIDOMNode.ELEMENT_NODE && (aElement instanceof Ci.nsIDOMHTMLMediaElement)) {
- try {
- let mediaSrc = aElement.currentSrc || aElement.src;
- uri = ContentAreaUtils.makeURI(mediaSrc, null, null);
- } catch (e) {}
- }
- return uri;
- },
-
- init: function helper_init() {
- this._contextMenuId = NativeWindow.contextmenus.add(function(aElement) {
- let uri = null;
- var node = aElement;
- while (node && !uri) {
- uri = ExternalApps._getMediaLink(node);
- node = node.parentNode;
- }
- let apps = [];
- if (uri)
- apps = HelperApps.getAppsForUri(uri);
-
- return apps.length == 1 ? Strings.browser.formatStringFromName("helperapps.openWithApp2", [apps[0].name], 1) :
- Strings.browser.GetStringFromName("helperapps.openWithList2");
- }, this.filter, this.openExternal);
- },
-
- filter: {
- matches: function(aElement) {
- let uri = ExternalApps._getMediaLink(aElement);
- let apps = [];
- if (uri) {
- apps = HelperApps.getAppsForUri(uri);
- }
- return apps.length > 0;
- }
- },
-
- openExternal: function(aElement) {
- if (aElement.pause) {
- aElement.pause();
- }
- let uri = ExternalApps._getMediaLink(aElement);
- HelperApps.launchUri(uri);
- },
-
- shouldCheckUri: function(uri) {
- if (!(uri.schemeIs("http") || uri.schemeIs("https") || uri.schemeIs("file"))) {
- return false;
- }
-
- return true;
- },
-
- updatePageAction: function updatePageAction(uri, contentDocument) {
- HelperApps.getAppsForUri(uri, { filterHttp: true }, (apps) => {
- this.clearPageAction();
- if (apps.length > 0)
- this._setUriForPageAction(uri, apps, contentDocument);
- });
- },
-
- updatePageActionUri: function updatePageActionUri(uri) {
- this._pageActionUri = uri;
- },
-
- _getMediaContentElement(contentDocument) {
- if (!contentDocument.contentType.startsWith("video/") &&
- !contentDocument.contentType.startsWith("audio/")) {
- return null;
- }
-
- let element = contentDocument.activeElement;
-
- if (element instanceof HTMLBodyElement) {
- element = element.firstChild;
- }
-
- if (element instanceof HTMLMediaElement) {
- return element;
- }
-
- return null;
- },
-
- _setUriForPageAction: function setUriForPageAction(uri, apps, contentDocument) {
- this.updatePageActionUri(uri);
-
- // If the pageaction is already added, simply update the URI to be launched when 'onclick' is triggered.
- if (this._pageActionId != undefined)
- return;
-
- let mediaElement = this._getMediaContentElement(contentDocument);
-
- this._pageActionId = PageActions.add({
- title: Strings.browser.GetStringFromName("openInApp.pageAction"),
- icon: "drawable://icon_openinapp",
-
- clickCallback: () => {
- UITelemetry.addEvent("launch.1", "pageaction", null, "helper");
-
- let wasPlaying = mediaElement && !mediaElement.paused && !mediaElement.ended;
- if (wasPlaying) {
- mediaElement.pause();
- }
-
- if (apps.length > 1) {
- // Use the HelperApps prompt here to filter out any Http handlers
- HelperApps.prompt(apps, {
- title: Strings.browser.GetStringFromName("openInApp.pageAction"),
- buttons: [
- Strings.browser.GetStringFromName("openInApp.ok"),
- Strings.browser.GetStringFromName("openInApp.cancel")
- ]
- }, (result) => {
- if (result.button != 0) {
- if (wasPlaying) {
- mediaElement.play();
- }
-
- return;
- }
- apps[result.icongrid0].launch(this._pageActionUri);
- });
- } else {
- apps[0].launch(this._pageActionUri);
- }
- }
- });
- },
-
- clearPageAction: function clearPageAction() {
- if(!this._pageActionId)
- return;
-
- PageActions.remove(this._pageActionId);
- delete this._pageActionId;
- },
-};
-
-var Distribution = {
- // File used to store campaign data
- _file: null,
-
- _preferencesJSON: null,
-
- init: function dc_init() {
- Services.obs.addObserver(this, "Distribution:Changed", false);
- Services.obs.addObserver(this, "Distribution:Set", false);
- Services.obs.addObserver(this, "prefservice:after-app-defaults", false);
- Services.obs.addObserver(this, "Campaign:Set", false);
-
- // Look for file outside the APK:
- // /data/data/org.mozilla.xxx/distribution.json
- this._file = Services.dirsvc.get("XCurProcD", Ci.nsIFile);
- this._file.append("distribution.json");
- this.readJSON(this._file, this.update);
- },
-
- observe: function dc_observe(aSubject, aTopic, aData) {
- switch (aTopic) {
- case "Distribution:Changed":
- // Re-init the search service.
- try {
- Services.search._asyncReInit();
- } catch (e) {
- console.log("Unable to reinit search service.");
- }
- // Fall through.
-
- case "Distribution:Set":
- if (aData) {
- try {
- this._preferencesJSON = JSON.parse(aData);
- } catch (e) {
- console.log("Invalid distribution JSON.");
- }
- }
- // Reload the default prefs so we can observe "prefservice:after-app-defaults"
- Services.prefs.QueryInterface(Ci.nsIObserver).observe(null, "reload-default-prefs", null);
- this.installDistroAddons();
- break;
-
- case "prefservice:after-app-defaults":
- this.getPrefs();
- break;
-
- case "Campaign:Set": {
- // Update the prefs for this session
- try {
- this.update(JSON.parse(aData));
- } catch (ex) {
- Cu.reportError("Distribution: Could not parse JSON: " + ex);
- return;
- }
-
- // Asynchronously copy the data to the file.
- let array = new TextEncoder().encode(aData);
- OS.File.writeAtomic(this._file.path, array, { tmpPath: this._file.path + ".tmp" });
- break;
- }
- }
- },
-
- update: function dc_update(aData) {
- // Force the distribution preferences on the default branch
- let defaults = Services.prefs.getDefaultBranch(null);
- defaults.setCharPref("distribution.id", aData.id);
- defaults.setCharPref("distribution.version", aData.version);
- },
-
- getPrefs: function dc_getPrefs() {
- if (this._preferencesJSON) {
- this.applyPrefs(this._preferencesJSON);
- this._preferencesJSON = null;
- return;
- }
-
- // Get the distribution directory, and bail if it doesn't exist.
- let file = FileUtils.getDir("XREAppDist", [], false);
- if (!file.exists())
- return;
-
- file.append("preferences.json");
- this.readJSON(file, this.applyPrefs);
- },
-
- applyPrefs: function dc_applyPrefs(aData) {
- // Check for required Global preferences
- let global = aData["Global"];
- if (!(global && global["id"] && global["version"] && global["about"])) {
- Cu.reportError("Distribution: missing or incomplete Global preferences");
- return;
- }
-
- // Force the distribution preferences on the default branch
- let defaults = Services.prefs.getDefaultBranch(null);
- defaults.setCharPref("distribution.id", global["id"]);
- defaults.setCharPref("distribution.version", global["version"]);
-
- let locale = BrowserApp.getUALocalePref();
- let aboutString = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
- aboutString.data = global["about." + locale] || global["about"];
- defaults.setComplexValue("distribution.about", Ci.nsISupportsString, aboutString);
-
- let prefs = aData["Preferences"];
- for (let key in prefs) {
- try {
- let value = prefs[key];
- switch (typeof value) {
- case "boolean":
- defaults.setBoolPref(key, value);
- break;
- case "number":
- defaults.setIntPref(key, value);
- break;
- case "string":
- case "undefined":
- defaults.setCharPref(key, value);
- break;
- }
- } catch (e) { /* ignore bad prefs and move on */ }
- }
-
- // Apply a lightweight theme if necessary
- if (prefs && prefs["lightweightThemes.selectedThemeID"]) {
- Services.obs.notifyObservers(null, "lightweight-theme-apply", "");
- }
-
- let localizedString = Cc["@mozilla.org/pref-localizedstring;1"].createInstance(Ci.nsIPrefLocalizedString);
- let localizeablePrefs = aData["LocalizablePreferences"];
- for (let key in localizeablePrefs) {
- try {
- let value = localizeablePrefs[key];
- value = value.replace(/%LOCALE%/g, locale);
- localizedString.data = "data:text/plain," + key + "=" + value;
- defaults.setComplexValue(key, Ci.nsIPrefLocalizedString, localizedString);
- } catch (e) { /* ignore bad prefs and move on */ }
- }
-
- let localizeablePrefsOverrides = aData["LocalizablePreferences." + locale];
- for (let key in localizeablePrefsOverrides) {
- try {
- let value = localizeablePrefsOverrides[key];
- localizedString.data = "data:text/plain," + key + "=" + value;
- defaults.setComplexValue(key, Ci.nsIPrefLocalizedString, localizedString);
- } catch (e) { /* ignore bad prefs and move on */ }
- }
-
- Messaging.sendRequest({ type: "Distribution:Set:OK" });
- },
-
- // aFile is an nsIFile
- // aCallback takes the parsed JSON object as a parameter
- readJSON: function dc_readJSON(aFile, aCallback) {
- Task.spawn(function() {
- let bytes = yield OS.File.read(aFile.path);
- let raw = new TextDecoder().decode(bytes) || "";
-
- try {
- aCallback(JSON.parse(raw));
- } catch (e) {
- Cu.reportError("Distribution: Could not parse JSON: " + e);
- }
- }).then(null, function onError(reason) {
- if (!(reason instanceof OS.File.Error && reason.becauseNoSuchFile)) {
- Cu.reportError("Distribution: Could not read from " + aFile.leafName + " file");
- }
- });
- },
-
- // Track pending installs so we can avoid showing notifications for them.
- pendingAddonInstalls: new Set(),
-
- installDistroAddons: Task.async(function* () {
- const PREF_ADDONS_INSTALLED = "distribution.addonsInstalled";
- try {
- let installed = Services.prefs.getBoolPref(PREF_ADDONS_INSTALLED);
- if (installed) {
- return;
- }
- } catch (e) {
- Services.prefs.setBoolPref(PREF_ADDONS_INSTALLED, true);
- }
-
- let distroPath;
- try {
- distroPath = FileUtils.getDir("XREAppDist", ["extensions"]).path;
-
- let info = yield OS.File.stat(distroPath);
- if (!info.isDir) {
- return;
- }
- } catch (e) {
- return;
- }
-
- let it = new OS.File.DirectoryIterator(distroPath);
- try {
- yield it.forEach(entry => {
- // Only support extensions that are zipped in .xpi files.
- if (entry.isDir || !entry.name.endsWith(".xpi")) {
- dump("Ignoring distribution add-on that isn't an XPI: " + entry.path);
- return;
- }
-
- new Promise((resolve, reject) => {
- AddonManager.getInstallForFile(new FileUtils.File(entry.path), resolve);
- }).then(install => {
- let id = entry.name.substring(0, entry.name.length - 4);
- if (install.addon.id !== id) {
- Cu.reportError("File entry " + entry.path + " contains an add-on with an incorrect ID");
- return;
- }
- this.pendingAddonInstalls.add(install);
- install.install();
- }).catch(e => {
- Cu.reportError("Error installing distribution add-on: " + entry.path + ": " + e);
- });
- });
- } finally {
- it.close();
- }
- })
-};
-
-var Tabs = {
- _enableTabExpiration: false,
- _useCache: false,
- _domains: new Set(),
-
- init: function() {
- // On low-memory platforms, always allow tab expiration. On high-mem
- // platforms, allow it to be turned on once we hit a low-mem situation.
- if (BrowserApp.isOnLowMemoryPlatform) {
- this._enableTabExpiration = true;
- } else {
- Services.obs.addObserver(this, "memory-pressure", false);
- }
-
- // Watch for opportunities to pre-connect to high probability targets.
- Services.obs.addObserver(this, "Session:Prefetch", false);
-
- // Track the network connection so we can efficiently use the cache
- // for possible offline rendering.
- Services.obs.addObserver(this, "network:link-status-changed", false);
- let network = Cc["@mozilla.org/network/network-link-service;1"].getService(Ci.nsINetworkLinkService);
- this.useCache = !network.isLinkUp;
-
- BrowserApp.deck.addEventListener("pageshow", this, false);
- BrowserApp.deck.addEventListener("TabOpen", this, false);
- },
-
- observe: function(aSubject, aTopic, aData) {
- switch (aTopic) {
- case "memory-pressure":
- if (aData != "heap-minimize") {
- // We received a low-memory related notification. This will enable
- // expirations.
- this._enableTabExpiration = true;
- Services.obs.removeObserver(this, "memory-pressure");
- } else {
- // Use "heap-minimize" as a trigger to expire the most stale tab.
- this.expireLruTab();
- }
- break;
- case "Session:Prefetch":
- if (aData) {
- try {
- let uri = Services.io.newURI(aData, null, null);
- if (uri && !this._domains.has(uri.host)) {
- Services.io.QueryInterface(Ci.nsISpeculativeConnect).speculativeConnect(uri, null);
- this._domains.add(uri.host);
- }
- } catch (e) {}
- }
- break;
- case "network:link-status-changed":
- if (["down", "unknown", "up"].indexOf(aData) == -1) {
- return;
- }
- this.useCache = (aData === "down");
- break;
- }
- },
-
- handleEvent: function(aEvent) {
- switch (aEvent.type) {
- case "pageshow":
- // Clear the domain cache whenever a page is loaded into any browser.
- this._domains.clear();
-
- break;
- case "TabOpen":
- // Use opening a new tab as a trigger to expire the most stale tab.
- this.expireLruTab();
- break;
- }
- },
-
- // Manage the most-recently-used list of tabs. Each tab has a timestamp
- // associated with it that indicates when it was last touched.
- expireLruTab: function() {
- if (!this._enableTabExpiration) {
- return false;
- }
- let expireTimeMs = Services.prefs.getIntPref("browser.tabs.expireTime") * 1000;
- if (expireTimeMs < 0) {
- // This behaviour is disabled.
- return false;
- }
- let tabs = BrowserApp.tabs;
- let selected = BrowserApp.selectedTab;
- let lruTab = null;
- // Find the least recently used non-zombie tab.
- for (let i = 0; i < tabs.length; i++) {
- if (tabs[i] == selected ||
- tabs[i].browser.__SS_restore ||
- tabs[i].playingAudio) {
- // This tab is selected, is already a zombie, or is currently playing
- // audio, skip it.
- continue;
- }
- if (lruTab == null || tabs[i].lastTouchedAt < lruTab.lastTouchedAt) {
- lruTab = tabs[i];
- }
- }
- // If the tab was last touched more than browser.tabs.expireTime seconds ago,
- // zombify it.
- if (lruTab) {
- if (Date.now() - lruTab.lastTouchedAt > expireTimeMs) {
- MemoryObserver.zombify(lruTab);
- return true;
- }
- }
- return false;
- },
-
- get useCache() {
- if (!Services.prefs.getBoolPref("browser.tabs.useCache")) {
- return false;
- }
- return this._useCache;
- },
-
- set useCache(aUseCache) {
- if (!Services.prefs.getBoolPref("browser.tabs.useCache")) {
- return;
- }
-
- if (this._useCache == aUseCache) {
- return;
- }
-
- BrowserApp.tabs.forEach(function(tab) {
- if (tab.browser && tab.browser.docShell) {
- if (aUseCache) {
- tab.browser.docShell.defaultLoadFlags |= Ci.nsIRequest.LOAD_FROM_CACHE;
- } else {
- tab.browser.docShell.defaultLoadFlags &= ~Ci.nsIRequest.LOAD_FROM_CACHE;
- }
- }
- });
- this._useCache = aUseCache;
- },
-
- // For debugging
- dump: function(aPrefix) {
- let tabs = BrowserApp.tabs;
- for (let i = 0; i < tabs.length; i++) {
- dump(aPrefix + " | " + "Tab [" + tabs[i].browser.contentWindow.location.href + "]: lastTouchedAt:" + tabs[i].lastTouchedAt + ", zombie:" + tabs[i].browser.__SS_restore);
- }
- },
-};
-
-function ContextMenuItem(args) {
- this.id = uuidgen.generateUUID().toString();
- this.args = args;
-}
-
-ContextMenuItem.prototype = {
- get order() {
- return this.args.order || 0;
- },
-
- matches: function(elt, x, y) {
- return this.args.selector.matches(elt, x, y);
- },
-
- callback: function(elt) {
- this.args.callback(elt);
- },
-
- addVal: function(name, elt, defaultValue) {
- if (!(name in this.args))
- return defaultValue;
-
- if (typeof this.args[name] == "function")
- return this.args[name](elt);
-
- return this.args[name];
- },
-
- getValue: function(elt) {
- return {
- id: this.id,
- label: this.addVal("label", elt),
- showAsActions: this.addVal("showAsActions", elt),
- icon: this.addVal("icon", elt),
- isGroup: this.addVal("isGroup", elt, false),
- inGroup: this.addVal("inGroup", elt, false),
- disabled: this.addVal("disabled", elt, false),
- selected: this.addVal("selected", elt, false),
- isParent: this.addVal("isParent", elt, false),
- };
- }
-}
-
-function HTMLContextMenuItem(elt, target) {
- ContextMenuItem.call(this, { });
-
- this.menuElementRef = Cu.getWeakReference(elt);
- this.targetElementRef = Cu.getWeakReference(target);
-}
-
-HTMLContextMenuItem.prototype = Object.create(ContextMenuItem.prototype, {
- order: {
- value: NativeWindow.contextmenus.DEFAULT_HTML5_ORDER
- },
-
- matches: {
- value: function(target) {
- let t = this.targetElementRef.get();
- return t === target;
- },
- },
-
- callback: {
- value: function(target) {
- let elt = this.menuElementRef.get();
- if (!elt) {
- return;
- }
-
- // If this is a menu item, show a new context menu with the submenu in it
- if (elt instanceof Ci.nsIDOMHTMLMenuElement) {
- try {
- NativeWindow.contextmenus.menus = {};
-
- let elt = this.menuElementRef.get();
- let target = this.targetElementRef.get();
- if (!elt) {
- return;
- }
-
- var items = NativeWindow.contextmenus._getHTMLContextMenuItemsForMenu(elt, target);
- // This menu will always only have one context, but we still make sure its the "right" one.
- var context = NativeWindow.contextmenus._getContextType(target);
- if (items.length > 0) {
- NativeWindow.contextmenus._addMenuItems(items, context);
- }
-
- } catch(ex) {
- Cu.reportError(ex);
- }
- } else {
- // otherwise just click the menu item
- elt.click();
- }
- },
- },
-
- getValue: {
- value: function(target) {
- let elt = this.menuElementRef.get();
- if (!elt) {
- return null;
- }
-
- if (elt.hasAttribute("hidden")) {
- return null;
- }
-
- return {
- id: this.id,
- icon: elt.icon,
- label: elt.label,
- disabled: elt.disabled,
- menu: elt instanceof Ci.nsIDOMHTMLMenuElement
- };
- }
- },
-});
-